From fbc231dc9ae6b5f6ef5b00c6431911e897547b3a Mon Sep 17 00:00:00 2001 From: Mathis HERRIOT <197931332+0x485254@users.noreply.github.com> Date: Wed, 14 Jan 2026 16:37:55 +0100 Subject: [PATCH] refactor!: remove unused resizable components and enhance efficiency in critical areas Remove outdated `ResizablePanelGroup`, `ResizablePanel`, and `ResizableHandle` components from the codebase. Optimize API error handling with anti-spam protection for repetitive errors. Update Dockerfile for streamlined builds, improve sitemap generation in `app`, and refactor lazy loading using `React.Suspense`. Refine services to support enhanced query parameters like `author`. --- frontend/Dockerfile | 65 +++++++------------- frontend/src/app/(dashboard)/layout.tsx | 8 ++- frontend/src/app/(dashboard)/recent/page.tsx | 8 +-- frontend/src/app/(dashboard)/trends/page.tsx | 8 +-- frontend/src/app/sitemap.ts | 2 +- frontend/src/components/ui/resizable.tsx | 56 ----------------- frontend/src/lib/api.ts | 27 ++++++++ frontend/src/services/content.service.ts | 1 + 8 files changed, 61 insertions(+), 114 deletions(-) delete mode 100644 frontend/src/components/ui/resizable.tsx diff --git a/frontend/Dockerfile b/frontend/Dockerfile index ef10474..a26b050 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,66 +1,45 @@ # syntax=docker.io/docker/dockerfile:1 -FROM pnpm/pnpm:20-alpine AS base +FROM node:22-alpine AS base +ENV PNPM_HOME="/pnpm" +ENV PATH="$PNPM_HOME:$PATH" +RUN corepack enable && corepack prepare pnpm@latest --activate -# Install dependencies only when needed -FROM base AS deps -# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. -RUN apk add --no-cache libc6-compat -WORKDIR /app - -# Install dependencies based on the preferred package manager -COPY package.json yarn.lock* package-lock.json* pnpm-lock.yaml* .npmrc* source.config.ts* next.config.* ./ -RUN \ - if [ -f pnpm-lock.yaml ]; then pnpm i --frozen-lockfile; \ - elif [ -f package-lock.json ]; then npm ci; \ - elif [ -f yarn.lock ]; then yarn --frozen-lockfile; \ - else echo "Lockfile not found." && exit 1; \ - fi - - -# Rebuild the source code only when needed FROM base AS builder -WORKDIR /app -COPY --from=deps /app/node_modules ./node_modules +WORKDIR /usr/src/app +COPY pnpm-lock.yaml pnpm-workspace.yaml package.json ./ +COPY backend/package.json ./backend/ +COPY frontend/package.json ./frontend/ +COPY documentation/package.json ./documentation/ +RUN pnpm install --no-frozen-lockfile COPY . . +# On réinstalle après COPY pour s'assurer que tous les scripts de cycle de vie et les liens sont corrects +RUN pnpm install --no-frozen-lockfile +RUN pnpm run --filter @memegoat/frontend build -# Next.js collects completely anonymous telemetry data about general usage. -# Learn more here: https://nextjs.org/telemetry -# Uncomment the following line in case you want to disable telemetry during the build. -# ENV NEXT_TELEMETRY_DISABLED=1 - -RUN \ - if [ -f pnpm-lock.yaml ]; then pnpm run build; \ - elif [ -f package-lock.json ]; then npm run build; \ - elif [ -f yarn.lock ]; then yarn run build; \ - else echo "Lockfile not found." && exit 1; \ - fi - -# Production image, copy all the files and run next -FROM base AS runner +FROM node:22-alpine AS runner WORKDIR /app ENV NODE_ENV=production -# Uncomment the following line in case you want to disable telemetry during runtime. -# ENV NEXT_TELEMETRY_DISABLED=1 +ENV NEXT_TELEMETRY_DISABLED=1 RUN addgroup --system --gid 1001 nodejs RUN adduser --system --uid 1001 nextjs -COPY --from=builder /app/public ./public +COPY --from=builder /usr/src/app/frontend/public ./frontend/public # Automatically leverage output traces to reduce image size # https://nextjs.org/docs/advanced-features/output-file-tracing -COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./ -COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static +COPY --from=builder --chown=nextjs:nodejs /usr/src/app/frontend/.next/standalone ./ +COPY --from=builder --chown=nextjs:nodejs /usr/src/app/frontend/.next/static ./frontend/.next/static USER nextjs EXPOSE 3000 ENV PORT=3000 - -# server.js is created by next build from the standalone output -# https://nextjs.org/docs/pages/api-reference/config/next-config-js/output ENV HOSTNAME="0.0.0.0" -CMD ["node", "server.js"] + +# Note: server.js is created in the standalone output. +# In a monorepo, it's often inside a subdirectory matching the package name. +CMD ["node", "frontend/server.js"] diff --git a/frontend/src/app/(dashboard)/layout.tsx b/frontend/src/app/(dashboard)/layout.tsx index 671e9cf..792cba6 100644 --- a/frontend/src/app/(dashboard)/layout.tsx +++ b/frontend/src/app/(dashboard)/layout.tsx @@ -24,9 +24,13 @@ export default function DashboardLayout({ {children} {modal} - + + + - + + + ); diff --git a/frontend/src/app/(dashboard)/recent/page.tsx b/frontend/src/app/(dashboard)/recent/page.tsx index c892c3d..46dcaaa 100644 --- a/frontend/src/app/(dashboard)/recent/page.tsx +++ b/frontend/src/app/(dashboard)/recent/page.tsx @@ -1,13 +1,9 @@ +"use client"; + import * as React from "react"; -import type { Metadata } from "next"; import { ContentList } from "@/components/content-list"; import { ContentService } from "@/services/content.service"; -export const metadata: Metadata = { - title: "Nouveautés | MemeGoat", - description: "Découvrez les derniers mèmes publiés sur MemeGoat.", -}; - export default function RecentPage() { const fetchFn = React.useCallback((params: { limit: number; offset: number }) => ContentService.getRecent(params.limit, params.offset), diff --git a/frontend/src/app/(dashboard)/trends/page.tsx b/frontend/src/app/(dashboard)/trends/page.tsx index 1038a9a..6e69a4d 100644 --- a/frontend/src/app/(dashboard)/trends/page.tsx +++ b/frontend/src/app/(dashboard)/trends/page.tsx @@ -1,13 +1,9 @@ +"use client"; + import * as React from "react"; -import type { Metadata } from "next"; import { ContentList } from "@/components/content-list"; import { ContentService } from "@/services/content.service"; -export const metadata: Metadata = { - title: "Tendances | MemeGoat", - description: "Découvrez les mèmes les plus populaires du moment sur MemeGoat.", -}; - export default function TrendsPage() { const fetchFn = React.useCallback((params: { limit: number; offset: number }) => ContentService.getTrends(params.limit, params.offset), diff --git a/frontend/src/app/sitemap.ts b/frontend/src/app/sitemap.ts index 81dfc3f..2ddac0e 100644 --- a/frontend/src/app/sitemap.ts +++ b/frontend/src/app/sitemap.ts @@ -6,7 +6,7 @@ export default async function sitemap(): Promise { const baseUrl = process.env.NEXT_PUBLIC_APP_URL || "https://memegoat.local"; // Pages statiques - const routes = ["", "/trends", "/recent"].map((route) => ({ + const routes: MetadataRoute.Sitemap = ["", "/trends", "/recent"].map((route) => ({ url: `${baseUrl}${route}`, lastModified: new Date(), changeFrequency: "daily" as const, diff --git a/frontend/src/components/ui/resizable.tsx b/frontend/src/components/ui/resizable.tsx deleted file mode 100644 index 12bbd0b..0000000 --- a/frontend/src/components/ui/resizable.tsx +++ /dev/null @@ -1,56 +0,0 @@ -"use client" - -import * as React from "react" -import { GripVerticalIcon } from "lucide-react" -import * as ResizablePrimitive from "react-resizable-panels" - -import { cn } from "@/lib/utils" - -function ResizablePanelGroup({ - className, - ...props -}: React.ComponentProps) { - return ( - - ) -} - -function ResizablePanel({ - ...props -}: React.ComponentProps) { - return -} - -function ResizableHandle({ - withHandle, - className, - ...props -}: React.ComponentProps & { - withHandle?: boolean -}) { - return ( - div]:rotate-90", - className - )} - {...props} - > - {withHandle && ( -
- -
- )} -
- ) -} - -export { ResizablePanelGroup, ResizablePanel, ResizableHandle } diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 48caa04..2343f98 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -8,4 +8,31 @@ const api = axios.create({ }, }); +// Système anti-spam rudimentaire pour les erreurs répétitives +const errorCache = new Map(); +const SPAM_THRESHOLD_MS = 2000; // 2 secondes de silence après une erreur sur le même endpoint + +api.interceptors.response.use( + (response) => { + // Nettoyer le cache d'erreur en cas de succès sur cet endpoint + const url = response.config.url || ""; + errorCache.delete(url); + return response; + }, + (error) => { + const url = error.config?.url || "unknown"; + const now = Date.now(); + const lastErrorTime = errorCache.get(url); + + if (lastErrorTime && now - lastErrorTime < SPAM_THRESHOLD_MS) { + // Ignorer l'erreur si elle se produit trop rapidement (déjà signalée) + // On retourne une promesse qui ne se résout jamais ou on rejette avec une marque spéciale + return new Promise(() => {}); + } + + errorCache.set(url, now); + return Promise.reject(error); + } +); + export default api; diff --git a/frontend/src/services/content.service.ts b/frontend/src/services/content.service.ts index f5f6b0a..2c5c48b 100644 --- a/frontend/src/services/content.service.ts +++ b/frontend/src/services/content.service.ts @@ -9,6 +9,7 @@ export const ContentService = { tag?: string; category?: string; query?: string; + author?: string; }): Promise> { const { data } = await api.get>("/contents/explore", { params,