Преглед изворни кода

chore: clean and build errors

chore: clean and build errors
Mat B. пре 1 месец
родитељ
комит
e849294d49
48 измењених фајлова са 385 додато и 892 уклоњено
  1. 0 30
      app/[locale]/(legal-and-payment)/payment/cancel/page.tsx
  2. 0 25
      app/[locale]/(legal-and-payment)/payment/success/page.tsx
  3. 1 1
      app/[locale]/admin/[...catchAll]/not-found.tsx
  4. 1 1
      app/[locale]/admin/[...catchAll]/page.tsx
  5. 2 2
      app/[locale]/auth/(auth-layout)/layout.tsx
  6. 1 1
      app/[locale]/auth/verify-email/layout.tsx
  7. 1 1
      app/[locale]/profile/page.tsx
  8. 0 12
      app/test-stepper/page.tsx
  9. 33 0
      locales/en.ts
  10. 33 0
      locales/fr.ts
  11. 217 199
      pnpm-lock.yaml
  12. 5 5
      src/components/svg/LogoSvg.tsx
  13. 1 1
      src/components/ui/form.tsx
  14. 0 217
      src/components/ui/particles.tsx
  15. 3 2
      src/components/ui/toast.tsx
  16. 0 52
      src/entities/user/ui/profile-image-upload-form.tsx
  17. 2 2
      src/features/auth/forgot-password/model/useForgotPassword.tsx
  18. 3 3
      src/features/auth/forgot-password/ui/forgot-password-form.tsx
  19. 2 1
      src/features/auth/lib/better-auth.ts
  20. 3 3
      src/features/auth/reset-password/model/useResetPassword.ts
  21. 2 2
      src/features/auth/signin/model/useSignIn.ts
  22. 1 1
      src/features/auth/signup/model/signup.action.ts
  23. 1 1
      src/features/auth/signup/model/useSignUp.ts
  24. 3 3
      src/features/auth/verify-email/model/useResendEmail.ts
  25. 1 1
      src/features/contact-feedback/ui/contact-feedback-popover.tsx
  26. 1 1
      src/features/contact/support/ContactSupportDialog.tsx
  27. 1 1
      src/features/dialogs-provider/DialogProvider.tsx
  28. 15 107
      src/features/layout/authenticated-header.tsx
  29. 28 0
      src/features/page/layout.tsx
  30. 0 39
      src/features/settings/edit-profile/model/edit-profile.action.ts
  31. 0 8
      src/features/settings/edit-profile/schema/edit-profile.schema.ts
  32. 0 120
      src/features/settings/edit-profile/ui/edit-profile-form.tsx
  33. 0 0
      src/features/update-password/lib/hash.ts
  34. 0 0
      src/features/update-password/lib/validate-password.ts
  35. 3 3
      src/features/update-password/model/update-password.action.ts
  36. 0 0
      src/features/update-password/model/update-password.schema.ts
  37. 2 2
      src/features/update-password/ui/password-form.tsx
  38. 1 1
      src/features/workout-builder/model/use-workout-session.ts
  39. 6 6
      src/features/workout-builder/ui/equipment-selection.tsx
  40. 1 11
      src/features/workout-builder/ui/quit-workout-dialog.tsx
  41. 0 5
      src/features/workout-builder/ui/workout-stepper.tsx
  42. 0 9
      src/features/workout-session/ui/workout-session-header.tsx
  43. 1 1
      src/features/workout-session/ui/workout-session-list.tsx
  44. 2 7
      src/features/workout-session/ui/workout-session-set.tsx
  45. 2 1
      src/shared/constants/paths.ts
  46. 2 2
      src/shared/lib/workout-session/types/workout-session.ts
  47. 1 1
      src/shared/lib/workout-session/workout-session.service.ts
  48. 3 1
      src/shared/lib/workout-session/workout-session.sync.ts

+ 0 - 30
app/[locale]/(legal-and-payment)/payment/cancel/page.tsx

@@ -1,30 +0,0 @@
-import Link from "next/link";
-
-import { buttonVariants } from "@/components/ui/button";
-import { Badge } from "@/components/ui/badge";
-
-import { Layout, LayoutContent, LayoutDescription, LayoutHeader, LayoutTitle } from "@/features/page/layout";
-
-export default function CancelPaymentPage() {
-  return (
-    <Layout>
-      <LayoutHeader>
-        <Badge variant="outline">Payment failed</Badge>
-        <LayoutTitle>We&apos;re sorry, but we couldn&apos;t process your payment</LayoutTitle>
-        <LayoutDescription>
-          We encountered an issue processing your payment.
-          <br /> Please check your payment details and try again. <br />
-          If the problem persists, don&apos;t hesitate to contact us for assistance.
-          <br />
-          We&apos;re here to help you resolve this smoothly.
-        </LayoutDescription>
-      </LayoutHeader>
-      <LayoutContent className="flex items-center gap-2">
-        <Link className={buttonVariants({ variant: "default" })} href="/">
-          Home
-        </Link>
-        {/* <ContactSupportDialog /> */}
-      </LayoutContent>
-    </Layout>
-  );
-}

+ 0 - 25
app/[locale]/(legal-and-payment)/payment/success/page.tsx

@@ -1,25 +0,0 @@
-import Link from "next/link";
-
-import { Layout, LayoutContent, LayoutDescription, LayoutHeader, LayoutTitle } from "@/features/page/layout";
-import { buttonVariants } from "@/components/ui/button";
-
-export default function SuccessPaymentPage() {
-  return (
-    <>
-      <Layout>
-        <LayoutHeader>
-          <LayoutTitle>Thank You for Your Purchase!</LayoutTitle>
-          <LayoutDescription>
-            Your payment was successful! You now have full access to all our premium resources. If you have any questions, we&apos;re here
-            to help.
-          </LayoutDescription>
-        </LayoutHeader>
-        <LayoutContent>
-          <Link className={buttonVariants({ size: "large" })} href="/">
-            Get Started
-          </Link>
-        </LayoutContent>
-      </Layout>
-    </>
-  );
-}

+ 1 - 1
app/[locale]/admin/[...catchAll]/not-found.tsx

@@ -1,4 +1,4 @@
-import { Page404 } from "@/features/page/Page404";
+import { Page404 } from "@/widgets/404";
 
 export default function NotFoundPage() {
   return <Page404 />;

+ 1 - 1
app/[locale]/admin/[...catchAll]/page.tsx

@@ -1,4 +1,4 @@
-import { Page404 } from "@/features/page/Page404";
+import { Page404 } from "@/widgets/404";
 
 export default function AdminCatchAll() {
   return <Page404 />;

+ 2 - 2
app/[locale]/auth/(auth-layout)/layout.tsx

@@ -21,14 +21,14 @@ export default async function AuthLayout(props: LayoutParams<{}>) {
   const user = await auth.api.getSession({ headers: headerStore });
 
   if (user) {
-    redirect(`/${paths.dashboard}`);
+    redirect(`/${paths.root}`);
   }
 
   return (
     <>
       <div>
         <div className="flex justify-center gap-2">
-          <Link className="flex items-center gap-2 font-medium" href={`/${paths.dashboard}`}>
+          <Link className="flex items-center gap-2 font-medium" href={`/${paths.root}`}>
             <Image alt="workout cool logo" className="w-16" height={64} src={Logo} width={64} />
           </Link>
         </div>

+ 1 - 1
app/[locale]/auth/verify-email/layout.tsx

@@ -14,7 +14,7 @@ export default async function RootLayout({ children }: RootLayoutProps) {
   const auth = await serverRequiredUser();
 
   if (auth.emailVerified) {
-    redirect(`${getServerUrl()}/${paths.dashboard}`);
+    redirect(`${getServerUrl()}/${paths.root}`);
   }
 
   return <div>{children}</div>;

+ 1 - 1
app/[locale]/profile/page.tsx

@@ -25,7 +25,7 @@ export default function ProfilePage() {
   return (
     <div>
       <WorkoutSessionHeatmap until={until} values={values} />
-      <WorkoutSessionList onSelect={(id) => router.push(`/?sessionId=${id}`)} />
+      <WorkoutSessionList />
       <div className="mt-8 flex justify-center">
         <Button onClick={() => router.push("/")} size="large">
           {t("profile.new_workout")}

+ 0 - 12
app/test-stepper/page.tsx

@@ -1,12 +0,0 @@
-import { WorkoutStepper } from "@/features/workout-builder";
-
-export default function TestStepperPage() {
-  return (
-    <div className="min-h-screen bg-base-200 py-8">
-      <div className="container mx-auto">
-        <h1 className="text-3xl font-bold text-center mb-8">Workout Builder Test</h1>
-        <WorkoutStepper />
-      </div>
-    </div>
-  );
-}

+ 33 - 0
locales/en.ts

@@ -1,4 +1,11 @@
 export default {
+  email_sent: "Email sent",
+  cant_send_email: "Can't send email",
+  logout: "Logout",
+  verify_email: "Verify your email",
+  verify_email_subtitle: "Please verify your email to continue.",
+  resend_email: "Resend email",
+  resend_email_countdown: "Resend email in {seconds} seconds",
   signin_error_subtitle: "Please check your credentials and try again.",
   register_title: "Create an account",
   register_description: "Enter your information below to create your account",
@@ -6,8 +13,28 @@ export default {
   register_privacy: "Terms of Service",
   register_privacy_link: "and our",
   register_privacy_link_2: "Privacy Policy",
+  password_forgot_title: "Forgot password?",
+  password_forgot_subtitle: "Enter your email to reset your password",
+
+  success: {
+    password_forgot_success: "Email sent",
+    reset_password_success: "Password reset successfully",
+  },
+
   error: {
     invalid_credentials: "Invalid credentials or account does not exist",
+    upload_failed: "Upload failed",
+    generic_error: "Error during operation",
+    sending_email: "Error sending email",
+  },
+
+  backend_errors: {
+    EMAIL_ALREADY_EXISTS: "Email already exists",
+    INVALID_FILE_TYPE: "Invalid file type",
+    FILE_TOO_LARGE: "File too large",
+    NO_FILE_UPLOADED: "No file uploaded",
+    IMAGE_PROCESSING_ERROR: "Image processing error",
+    upload_failed: "Upload failed",
   },
 
   profile: {
@@ -293,5 +320,11 @@ export default {
     dashboard: "Dashboard",
     home: "Home",
     changelog: "Changelog",
+    stop_impersonation_button: "Stop impersonation",
+    impersonating_user_label: "Impersonating user",
+    re_hello: "Re Hello",
+    back_to_login: "Back to login",
+    sending: "Sending...",
+    send_me_link: "Send me a link",
   },
 } as const;

+ 33 - 0
locales/fr.ts

@@ -1,4 +1,11 @@
 export default {
+  email_sent: "Email envoyé",
+  cant_send_email: "Impossible d'envoyer l'email",
+  logout: "Déconnexion",
+  verify_email: "Vérifier votre email",
+  verify_email_subtitle: "Veuillez vérifier votre email pour continuer.",
+  resend_email: "Renvoyer l'email",
+  resend_email_countdown: "Renvoyer l'email dans {seconds} secondes",
   signin_error_subtitle: "Veuillez vérifier vos identifiants et réessayer.",
   register_title: "Créer un compte",
   register_description: "Entrez vos informations ci-dessous pour créer votre compte",
@@ -6,8 +13,28 @@ export default {
   register_privacy: "Conditions d'utilisation",
   register_privacy_link: "et notre",
   register_privacy_link_2: "Politique de confidentialité",
+  password_forgot_title: "Forgot password?",
+  password_forgot_subtitle: "Enter your email to reset your password",
+
+  success: {
+    password_forgot_success: "Email envoyé",
+    reset_password_success: "Mot de passe réinitialisé avec succès",
+  },
+
   error: {
     invalid_credentials: "Identifiants invalides ou compte inexistant",
+    upload_failed: "Erreur lors du téléchargement",
+    generic_error: "Erreur lors de l'opération",
+    sending_email: "Erreur lors de l'envoi de l'email",
+  },
+
+  backend_errors: {
+    EMAIL_ALREADY_EXISTS: "Email déjà existant",
+    INVALID_FILE_TYPE: "Type de fichier invalide",
+    FILE_TOO_LARGE: "Fichier trop grand",
+    NO_FILE_UPLOADED: "Aucun fichier téléchargé",
+    IMAGE_PROCESSING_ERROR: "Erreur lors du traitement de l'image",
+    upload_failed: "Erreur lors du téléchargement",
   },
 
   profile: {
@@ -294,5 +321,11 @@ export default {
     dashboard: "Tableau de bord",
     home: "Accueil",
     changelog: "Annonces & notes de version",
+    stop_impersonation_button: "Arrêter l'impersonnalisation",
+    impersonating_user_label: "Impersonnification en cours",
+    re_hello: "Re Hello",
+    back_to_login: "Retour à la connexion",
+    sending: "Envoi...",
+    send_me_link: "Envoyer un lien",
   },
 } as const;

Разлика између датотеке није приказан због своје велике величине
+ 217 - 199
pnpm-lock.yaml


+ 5 - 5
src/components/svg/LogoSvg.tsx

@@ -4,18 +4,18 @@ export type LogoSvgProps = ComponentPropsWithoutRef<"svg"> & { size?: number };
 
 export const LogoSvg = ({ size = 32, ...props }: LogoSvgProps) => {
   return (
-    <svg height={size} width={size} viewBox="0 0 100 40" xmlns="http://www.w3.org/2000/svg" {...props}>
+    <svg height={size} viewBox="0 0 100 40" width={size} xmlns="http://www.w3.org/2000/svg" {...props}>
       {/* Disque gauche */}
-      <circle cx="15" cy="20" r="12" fill="currentColor" className="opacity-90" />
+      <circle className="opacity-90" cx="15" cy="20" fill="currentColor" r="12" />
 
       {/* Barre centrale */}
-      <rect x="12" y="18" width="76" height="4" fill="currentColor" rx="2" />
+      <rect fill="currentColor" height="4" rx="2" width="76" x="12" y="18" />
 
       {/* Disque droit */}
-      <circle cx="85" cy="20" r="12" fill="currentColor" className="opacity-90" />
+      <circle className="opacity-90" cx="85" cy="20" fill="currentColor" r="12" />
 
       {/* Poignée centrale (optionnel pour plus de détail) */}
-      <rect x="40" y="16" width="20" height="8" fill="none" stroke="currentColor" strokeWidth="1" rx="4" className="opacity-50" />
+      <rect className="opacity-50" fill="none" height="8" rx="4" stroke="currentColor" strokeWidth="1" width="20" x="40" y="16" />
     </svg>
   );
 };

+ 1 - 1
src/components/ui/form.tsx

@@ -147,7 +147,7 @@ type UseZodFormProps<Schema extends ZodSchema> = Exclude<UseFormProps<z.infer<Sc
 function useZodForm<Schema extends ZodSchema>({ schema, ...formProps }: UseZodFormProps<Schema>): UseFormReturn<z.infer<Schema>> {
   return useForm<z.infer<Schema>>({
     ...formProps,
-    resolver: zodResolver(schema),
+    resolver: zodResolver(schema as any),
   });
 }
 

+ 0 - 217
src/components/ui/particles.tsx

@@ -1,217 +0,0 @@
-"use client";
-
-import React, { useRef, useEffect } from "react";
-
-import useMousePosition from "@/hooks/useMousePosition";
-
-interface ParticlesProps {
-  className?: string;
-  quantity?: number;
-  staticity?: number;
-  fillStyle?: string;
-  ease?: number;
-  refresh?: boolean;
-}
-
-export default function Particles({
-  className = "",
-  quantity = 30,
-  staticity = 50,
-  ease = 50,
-  fillStyle,
-  refresh = false,
-}: ParticlesProps) {
-  const canvasRef = useRef<HTMLCanvasElement>(null);
-  const canvasContainerRef = useRef<HTMLDivElement>(null);
-  const context = useRef<CanvasRenderingContext2D | null>(null);
-  const circles = useRef<any[]>([]);
-  const mousePosition = useMousePosition();
-  const mouse = useRef<{ x: number; y: number }>({ x: 0, y: 0 });
-  const canvasSize = useRef<{ w: number; h: number }>({ w: 0, h: 0 });
-  const dpr = typeof window !== "undefined" ? window.devicePixelRatio : 1;
-
-  useEffect(() => {
-    if (canvasRef.current) {
-      context.current = canvasRef.current.getContext("2d");
-    }
-    initCanvas();
-    animate();
-    window.addEventListener("resize", initCanvas);
-
-    return () => {
-      window.removeEventListener("resize", initCanvas);
-    };
-  }, []);
-
-  useEffect(() => {
-    onMouseMove();
-  }, [mousePosition.x, mousePosition.y]);
-
-  useEffect(() => {
-    initCanvas();
-  }, [refresh]);
-
-  const initCanvas = () => {
-    resizeCanvas();
-    drawParticles();
-  };
-
-  const onMouseMove = () => {
-    if (canvasRef.current) {
-      const rect = canvasRef.current.getBoundingClientRect();
-      const { w, h } = canvasSize.current;
-      const x = mousePosition.x - rect.left - w / 2;
-      const y = mousePosition.y - rect.top - h / 2;
-      const inside = x < w / 2 && x > -w / 2 && y < h / 2 && y > -h / 2;
-      if (inside) {
-        mouse.current.x = x;
-        mouse.current.y = y;
-      }
-    }
-  };
-
-  type Circle = {
-    x: number;
-    y: number;
-    translateX: number;
-    translateY: number;
-    size: number;
-    alpha: number;
-    targetAlpha: number;
-    dx: number;
-    dy: number;
-    magnetism: number;
-  };
-
-  const resizeCanvas = () => {
-    if (canvasContainerRef.current && canvasRef.current && context.current) {
-      circles.current.length = 0;
-      canvasSize.current.w = canvasContainerRef.current.offsetWidth;
-      canvasSize.current.h = canvasContainerRef.current.offsetHeight;
-      canvasRef.current.width = canvasSize.current.w * dpr;
-      canvasRef.current.height = canvasSize.current.h * dpr;
-      canvasRef.current.style.width = canvasSize.current.w + "px";
-      canvasRef.current.style.height = canvasSize.current.h + "px";
-      context.current.scale(dpr, dpr);
-    }
-  };
-
-  const circleParams = (): Circle => {
-    const x = Math.floor(Math.random() * canvasSize.current.w);
-    const y = Math.floor(Math.random() * canvasSize.current.h);
-    const translateX = 0;
-    const translateY = 0;
-    const size = Math.floor(Math.random() * 2) + 1;
-    const alpha = 0;
-    const targetAlpha = parseFloat((Math.random() * 0.6 + 0.1).toFixed(1));
-    const dx = (Math.random() - 0.5) * 0.2;
-    const dy = (Math.random() - 0.5) * 0.2;
-    const magnetism = 0.1 + Math.random() * 4;
-    return {
-      x,
-      y,
-      translateX,
-      translateY,
-      size,
-      alpha,
-      targetAlpha,
-      dx,
-      dy,
-      magnetism,
-    };
-  };
-
-  const drawCircle = (circle: Circle, update = false) => {
-    if (context.current) {
-      const { x, y, translateX, translateY, size, alpha } = circle;
-      context.current.translate(translateX, translateY);
-      context.current.beginPath();
-      context.current.arc(x, y, size, 0, 2 * Math.PI);
-      context.current.fillStyle = fillStyle || `rgba(255, 255, 255, ${alpha})`;
-      context.current.fill();
-      context.current.setTransform(dpr, 0, 0, dpr, 0, 0);
-
-      if (!update) {
-        circles.current.push(circle);
-      }
-    }
-  };
-
-  const clearContext = () => {
-    if (context.current) {
-      context.current.clearRect(0, 0, canvasSize.current.w, canvasSize.current.h);
-    }
-  };
-
-  const drawParticles = () => {
-    clearContext();
-    const particleCount = quantity;
-    for (let i = 0; i < particleCount; i++) {
-      const circle = circleParams();
-      drawCircle(circle);
-    }
-  };
-
-  const remapValue = (value: number, start1: number, end1: number, start2: number, end2: number): number => {
-    const remapped = ((value - start1) * (end2 - start2)) / (end1 - start1) + start2;
-    return remapped > 0 ? remapped : 0;
-  };
-
-  const animate = () => {
-    clearContext();
-    circles.current.forEach((circle: Circle, i: number) => {
-      // Handle the alpha value
-      const edge = [
-        circle.x + circle.translateX - circle.size, // distance from left edge
-        canvasSize.current.w - circle.x - circle.translateX - circle.size, // distance from right edge
-        circle.y + circle.translateY - circle.size, // distance from top edge
-        canvasSize.current.h - circle.y - circle.translateY - circle.size, // distance from bottom edge
-      ];
-      const closestEdge = edge.reduce((a, b) => Math.min(a, b));
-      const remapClosestEdge = parseFloat(remapValue(closestEdge, 0, 20, 0, 1).toFixed(2));
-      if (remapClosestEdge > 1) {
-        circle.alpha += 0.02;
-        if (circle.alpha > circle.targetAlpha) circle.alpha = circle.targetAlpha;
-      } else {
-        circle.alpha = circle.targetAlpha * remapClosestEdge;
-      }
-      circle.x += circle.dx;
-      circle.y += circle.dy;
-      circle.translateX += (mouse.current.x / (staticity / circle.magnetism) - circle.translateX) / ease;
-      circle.translateY += (mouse.current.y / (staticity / circle.magnetism) - circle.translateY) / ease;
-      // circle gets out of the canvas
-      if (
-        circle.x < -circle.size ||
-        circle.x > canvasSize.current.w + circle.size ||
-        circle.y < -circle.size ||
-        circle.y > canvasSize.current.h + circle.size
-      ) {
-        // remove the circle from the array
-        circles.current.splice(i, 1);
-        // create a new circle
-        const newCircle = circleParams();
-        drawCircle(newCircle);
-        // update the circle position
-      } else {
-        drawCircle(
-          {
-            ...circle,
-            x: circle.x,
-            y: circle.y,
-            translateX: circle.translateX,
-            translateY: circle.translateY,
-            alpha: circle.alpha,
-          },
-          true,
-        );
-      }
-    });
-    window.requestAnimationFrame(animate);
-  };
-
-  return (
-    <div aria-hidden="true" className={className} ref={canvasContainerRef}>
-      <canvas ref={canvasRef} />
-    </div>
-  );
-}

+ 3 - 2
src/components/ui/toast.tsx

@@ -2,12 +2,13 @@
 
 import { toast } from "sonner";
 import * as React from "react";
+import Image from "next/image";
 import { X, CheckCircle2, AlertTriangle, Info, XCircle } from "lucide-react";
 import { cva, type VariantProps } from "class-variance-authority";
 import * as ToastPrimitives from "@radix-ui/react-toast";
 
+import Logo from "@public/logo.png";
 import { cn } from "@/shared/lib/utils";
-import { LogoSvg } from "@/components/svg/LogoSvg";
 
 const ToastProvider = ToastPrimitives.Provider;
 
@@ -140,7 +141,7 @@ const BrandedToastContent = ({ title, subtitle, variant = "default" }: BrandedTo
   return (
     <ToastDescription className={"dark:bg-black-dark dark:text-white"}>
       <div className={`-mt-0.5 flex items-center gap-2 border-b px-4 py-3 ${color}`}>
-        <LogoSvg className="h-4 w-12" />
+        <Image alt="logo" height={24} src={Logo} width={24} />
       </div>
       <div className="flex items-center p-4">
         {icon}

+ 0 - 52
src/entities/user/ui/profile-image-upload-form.tsx

@@ -1,52 +0,0 @@
-"use client";
-
-import { Camera } from "lucide-react";
-
-import { useI18n } from "locales/client";
-import { ImageUpload } from "@/shared/ui/image-upload";
-import { getImageUrl } from "@/shared/lib/storage/get-image";
-import { useProfileImageUpload } from "@/features/layout/authenticated-header";
-import { useSession } from "@/features/auth/lib/auth-client";
-import { useCurrentUser } from "@/entities/user/model/useCurrentUser";
-import { brandedToast } from "@/components/ui/toast";
-import { Skeleton } from "@/components/ui/skeleton";
-
-export function ProfileImageUploadForm({ isDisabled }: { isDisabled: boolean }) {
-  const t = useI18n();
-  const user = useCurrentUser();
-  const uploadMutation = useProfileImageUpload();
-  const { refetch: refetchSession } = useSession();
-
-  const handleUploadSuccess = () => {
-    brandedToast({ title: t("upload_success"), variant: "success" });
-    refetchSession();
-  };
-
-  const handleUploadError = (error: Error) => {
-    console.log("error:", error);
-
-    if (error.message === "FILE_TOO_LARGE") {
-      brandedToast({ title: t("FILE_TOO_LARGE"), variant: "error" });
-    } else {
-      brandedToast({ title: t("generic_error"), variant: "error" });
-    }
-  };
-
-  return (
-    <div className="flex flex-col items-center gap-2 py-4">
-      <ImageUpload
-        imageTypeLabel={t("profile_image")}
-        initialImageUrl={user?.image ? getImageUrl(user.image) : null}
-        isDisabled={isDisabled || uploadMutation.isPending}
-        labelContent={<Camera className="size-7 text-white" />}
-        onUploadError={handleUploadError}
-        onUploadSuccess={handleUploadSuccess}
-        placeholder={<Skeleton className="h-full w-full rounded-full bg-gray-500" />}
-        previewClassName="rounded-full"
-        previewSize={{ width: 72, height: 72 }}
-        uploadMutation={uploadMutation}
-      />
-      <span className="text-xs text-gray-500">{t("profile_image_hint")}</span>
-    </div>
-  );
-}

+ 2 - 2
src/features/auth/forgot-password/model/useForgotPassword.tsx

@@ -21,14 +21,14 @@ export const useForgotPassword = () => {
       });
 
       if (error) {
-        setFieldError(t("error_sending_email"));
+        setFieldError(t("error.sending_email"));
         return;
       }
 
       setIsEmailSent(true);
     } catch (error) {
       console.error(error);
-      setFieldError(t("generic_error"));
+      setFieldError(t("error.generic_error"));
     } finally {
       setIsLoading(false);
     }

+ 3 - 3
src/features/auth/forgot-password/ui/forgot-password-form.tsx

@@ -37,7 +37,7 @@ export const ForgotPasswordForm = () => {
   if (isEmailSent) {
     return (
       <Alert variant="success">
-        <AlertDescription>{t("password_forgot_success")}</AlertDescription>
+        <AlertDescription>{t("success.password_forgot_success")}</AlertDescription>
       </Alert>
     );
   }
@@ -65,13 +65,13 @@ export const ForgotPasswordForm = () => {
         />
 
         <Button className="mt-6 w-full" disabled={isLoading} size="large" type="submit">
-          {isLoading ? t("sending") : t("send_me_link")}
+          {isLoading ? t("commons.sending") : t("commons.send_me_link")}
         </Button>
       </Form>
 
       <div className="text-center text-sm">
         <Link className="text-primary hover:underline" href={`/${paths.signIn}`}>
-          {t("back_to_login")}
+          {t("commons.back_to_login")}
         </Link>
       </div>
     </div>

+ 2 - 1
src/features/auth/lib/better-auth.ts

@@ -8,8 +8,9 @@ import { VerifyEmail } from "@emails/VerifyEmail";
 import { ResetPasswordEmail } from "@emails/ResetPasswordEmail";
 import { prisma } from "@/shared/lib/prisma";
 import { sendEmail } from "@/shared/lib/mail/sendEmail";
-import { hashStringWithSalt } from "@/features/settings/update-password/lib/hash";
+import { hashStringWithSalt } from "@/features/update-password/lib/hash";
 import { env } from "@/env";
+
 export const auth = betterAuth({
   // trustedOrigins: [SiteConfig.prodUrl, "localhost:3000", "https://better-auth.com", "http://localhost:3000"],
   trustedOrigins: ["*"],

+ 3 - 3
src/features/auth/reset-password/model/useResetPassword.ts

@@ -32,15 +32,15 @@ export const useResetPassword = (): UseResetPasswordResult => {
       const { error } = await authClient.resetPassword({ token, newPassword: password });
 
       if (error) {
-        brandedToast({ title: t("generic_error"), variant: "error" });
+        brandedToast({ title: t("error.generic_error"), variant: "error" });
         return;
       }
 
-      brandedToast({ title: t("reset_password_success"), variant: "success" });
+      brandedToast({ title: t("success.reset_password_success"), variant: "success" });
       router.push(`/${paths.signIn}?reset=success`);
     } catch (e) {
       console.error(e);
-      brandedToast({ title: t("generic_error"), variant: "error" });
+      brandedToast({ title: t("error.generic_error"), variant: "error" });
     } finally {
       setIsLoading(false);
     }

+ 2 - 2
src/features/auth/signin/model/useSignIn.ts

@@ -11,11 +11,11 @@ export const useSignIn = () => {
     const response = await authClient.signIn.email({
       email: values.email,
       password: values.password,
-      callbackURL: `/${paths.dashboard}`,
+      callbackURL: `/${paths.root}`,
     });
 
     if (response?.error) {
-      brandedToast({ title: t("INVLID_EMAIL_OR_PASSWORD"), variant: "error" });
+      brandedToast({ title: t("error.invalid_credentials"), variant: "error" });
       return;
     }
   };

+ 1 - 1
src/features/auth/signup/model/signup.action.ts

@@ -38,6 +38,6 @@ export const signUpAction = actionClient.schema(signUpSchema).action(async ({ pa
     return user;
   } catch (error) {
     console.error(error);
-    throw new ActionError(t("EMAIL_ALREADY_EXISTS"));
+    throw new ActionError(t("backend_errors.EMAIL_ALREADY_EXISTS"));
   }
 });

+ 1 - 1
src/features/auth/signup/model/useSignUp.ts

@@ -39,7 +39,7 @@ export const useSignUp = () => {
     },
 
     onError: (error: unknown) => {
-      const message = error instanceof Error ? t(error.message as keyof typeof t) : t("generic_error");
+      const message = error instanceof Error ? t(error.message as keyof typeof t) : t("error.generic_error");
 
       brandedToast({ title: message, variant: "error" });
     },

+ 3 - 3
src/features/auth/verify-email/model/useResendEmail.ts

@@ -27,14 +27,14 @@ export const useResendEmail = (email: string) => {
 
       const res = await authClient.sendVerificationEmail({
         email,
-        callbackURL: `${getServerUrl()}/${paths.dashboard}`,
+        callbackURL: `${getServerUrl()}/${paths.root}`,
       });
 
       if (res.error) brandedToast({ title: t(res.error.message as keyof typeof t), variant: "error" });
-      if (res.data?.status) brandedToast({ title: t("EMAIL_SENT"), variant: "success" });
+      if (res.data?.status) brandedToast({ title: t("email_sent"), variant: "success" });
     } catch (err) {
       console.error(err);
-      brandedToast({ title: t("CANT_SEND_EMAIL"), variant: "error" });
+      brandedToast({ title: t("cant_send_email"), variant: "error" });
     } finally {
       setIsDisabled(false);
     }

+ 1 - 1
src/features/contact-feedback/ui/contact-feedback-popover.tsx

@@ -34,7 +34,7 @@ export const ContactFeedbackPopover = (props: ContactFeedbackPopoverProps) => {
     const result = await contactFeedbackAction(values);
 
     if (!result) {
-      brandedToast({ title: t("generic_error"), variant: "error" });
+      brandedToast({ title: t("error.generic_error"), variant: "error" });
       return;
     }
 

+ 1 - 1
src/features/contact/support/ContactSupportDialog.tsx

@@ -40,7 +40,7 @@ export const ContactSupportDialog = (props: ContactSupportDialogProps) => {
     const action = await contactSupportAction(values);
 
     if (!action || !action.data) {
-      brandedToast({ title: action?.serverError ?? t("generic_error"), variant: "error" });
+      brandedToast({ title: action?.serverError ?? t("error.generic_error"), variant: "error" });
       return;
     }
 

+ 1 - 1
src/features/dialogs-provider/DialogProvider.tsx

@@ -58,7 +58,7 @@ const useDialogStore = create<DialogStore>((set, get) => ({
               })
               .catch((e) => {
                 logger.error(e);
-                brandedToast({ title: t("generic_error"), variant: "error" });
+                brandedToast({ title: t("error.generic_error"), variant: "error" });
               });
           } else {
             dialog.action?.onClick();

+ 15 - 107
src/features/layout/authenticated-header.tsx

@@ -26,19 +26,16 @@ import {
   SquareKanban,
   TableProperties,
   UserCog,
-  Camera,
 } from "lucide-react";
 import { useMutation } from "@tanstack/react-query";
 
 import { useI18n } from "locales/client";
-import { LanguageSelector } from "@/widgets/language-selector/language-selector";
-import { cn } from "@/shared/lib/utils";
-import { getImageUrl } from "@/shared/lib/storage/get-image";
-import { PLACEHOLDERS } from "@/shared/constants/placeholders";
-import { paths } from "@/shared/constants/paths";
 import { HoverCard, HoverCardContent, HoverCardTrigger } from "@/workoutcool/components/ui/hover-card";
 import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/workoutcool/components/ui/dropdown-menu";
 import { Button } from "@/workoutcool/components/ui/button";
+import { LanguageSelector } from "@/widgets/language-selector/language-selector";
+import { PLACEHOLDERS } from "@/shared/constants/placeholders";
+import { paths } from "@/shared/constants/paths";
 import { useSidebarToggle } from "@/features/layout/useSidebarToggle";
 import NavLink from "@/features/layout/nav-link";
 import { ContactFeedbackPopover } from "@/features/contact-feedback/ui/contact-feedback-popover";
@@ -75,22 +72,22 @@ export function useProfileImageUpload() {
         const data = await res.json();
 
         if (res.status === 415) {
-          brandedToast({ title: t("INVALID_FILE_TYPE"), variant: "error" });
+          brandedToast({ title: t("backend_errors.INVALID_FILE_TYPE"), variant: "error" });
         }
 
         if (res.status === 413) {
-          brandedToast({ title: t("FILE_TOO_LARGE"), variant: "error" });
+          brandedToast({ title: t("backend_errors.FILE_TOO_LARGE"), variant: "error" });
         }
 
         if (res.status === 400) {
-          brandedToast({ title: t("NO_FILE_UPLOADED"), variant: "error" });
+          brandedToast({ title: t("backend_errors.NO_FILE_UPLOADED"), variant: "error" });
         }
 
         if (res.status === 500) {
-          brandedToast({ title: t("IMAGE_PROCESSING_ERROR"), variant: "error" });
+          brandedToast({ title: t("backend_errors.IMAGE_PROCESSING_ERROR"), variant: "error" });
         }
 
-        throw new Error(data.error || t("upload_failed"));
+        throw new Error(data.error || t("error.upload_failed"));
       }
 
       return res.json();
@@ -98,95 +95,6 @@ export function useProfileImageUpload() {
   });
 }
 
-export function ProfileImageUploadForm({ isDisabled }: { isDisabled: boolean }) {
-  const t = useI18n();
-  const [isUploading, setIsUploading] = useState(false);
-  const user = useCurrentUser();
-  const initialUrl = user?.image ? getImageUrl(user.image) : null;
-  const [preview, setPreview] = useState<string | null>(initialUrl);
-  const uploadMutation = useProfileImageUpload();
-
-  const [mounted, setMounted] = useState(false);
-  useEffect(() => {
-    setMounted(true);
-  }, []);
-
-  const handleUpload = (file: File) => {
-    if (isDisabled) return;
-    setIsUploading(true);
-    uploadMutation.mutate(
-      { file },
-      {
-        onSuccess: () => {
-          setIsUploading(false);
-          brandedToast({ title: t("upload_success"), variant: "success" });
-        },
-        onError: (error) => {
-          setPreview(initialUrl);
-          setIsUploading(false);
-          console.error("error", error);
-        },
-      },
-    );
-  };
-
-  return (
-    <div className="flex flex-col items-center gap-2 py-4">
-      <div className="group relative">
-        <div
-          className={cn(
-            "flex size-[72px] items-center justify-center overflow-hidden rounded-full border-2 border-gray-200 bg-gray-100 transition-opacity",
-            (isUploading || isDisabled) && "opacity-60",
-          )}
-        >
-          {!mounted ? (
-            <>
-              <Skeleton height={40} rounded="rounded-full" width={40} />
-            </>
-          ) : preview ? (
-            <Image alt="Preview" className="h-full w-full object-cover" height={72} src={preview} width={72} />
-          ) : (
-            <Skeleton height={40} rounded="rounded-full" width={40} />
-          )}
-          <label
-            className={cn(
-              "absolute inset-0 flex cursor-pointer items-center justify-center rounded-full bg-black/40 opacity-0 transition group-hover:bg-black/40 group-hover:opacity-100",
-              (isUploading || isDisabled) && "pointer-events-none",
-            )}
-            htmlFor="profileImage"
-            tabIndex={0}
-            title={t("change_profile_picture")}
-          >
-            <Camera className="size-7 text-white" />
-            <input
-              accept="image/png, image/jpeg"
-              className="hidden"
-              disabled={isUploading || isDisabled}
-              id="profileImage"
-              name="profileImage"
-              onChange={(e) => {
-                if (isDisabled) return;
-                const file = e.target.files?.[0];
-                if (file) {
-                  setPreview(URL.createObjectURL(file));
-                  handleUpload(file);
-                }
-              }}
-              type="file"
-            />
-          </label>
-          {(isUploading || isDisabled) && (
-            <div className="absolute inset-0 flex items-center justify-center rounded-full bg-white/60">
-              {isUploading ? <span className="h-6 w-6 animate-spin rounded-full border-2 border-gray-300 border-t-black" /> : null}
-            </div>
-          )}
-        </div>
-      </div>
-      <span className="text-xs text-gray-500">{t("profile_image_hint")}</span>
-    </div>
-  );
-}
-
 export const AuthenticatedHeader = () => {
   const t = useI18n();
   const pathName = usePathname();
@@ -207,7 +115,7 @@ export const AuthenticatedHeader = () => {
     } catch (error) {
       console.error("Erreur lors de l'arrêt de l'impersonnalisation:", error);
       brandedToast({
-        title: t("generic_error"),
+        title: t("error.generic_error"),
         variant: "error",
       });
     }
@@ -244,7 +152,7 @@ export const AuthenticatedHeader = () => {
                       alt="Profile Img"
                       className="h-full w-full object-cover"
                       height={32}
-                      src={user?.image ? getImageUrl(user.image) : PLACEHOLDERS.PROFILE_IMAGE}
+                      src={user?.image || PLACEHOLDERS.PROFILE_IMAGE}
                       width={32}
                     />
                   </div>
@@ -257,7 +165,7 @@ export const AuthenticatedHeader = () => {
                     ) : user ? (
                       <>
                         <h5 className="line-clamp-1 text-[10px]/3 font-semibold dark:text-gray-500">
-                          {isImpersonating ? t("impersonating_user_label") : t("re_hello")}
+                          {isImpersonating ? t("commons.impersonating_user_label") : t("commons.re_hello")}
                         </h5>
                         <h2 className="line-clamp-1 text-xs font-bold text-black dark:text-white">
                           {displayFirstNameAndFirstLetterLastName(user)}
@@ -288,17 +196,17 @@ export const AuthenticatedHeader = () => {
                       variant="ghost"
                     >
                       <LogOut className="size-[18px] shrink-0" />
-                      {t("stop_impersonation_button")}
+                      {t("commons.stop_impersonation_button")}
                     </Button>
                   </DropdownMenuItem>
                 )}
                 <DropdownMenuItem className="p-0">
                   <Link
-                    className={`flex items-center gap-1.5 rounded-lg px-3 py-2 ${pathName === `/${paths.settings}` && "!bg-gray-400 !text-black dark:!bg-white/5 dark:!text-white"}`}
-                    href={`/${paths.settings}`}
+                    className={`flex items-center gap-1.5 rounded-lg px-3 py-2 ${pathName === `/${paths.profile}` && "!bg-gray-400 !text-black dark:!bg-white/5 dark:!text-white"}`}
+                    href={`/${paths.profile}`}
                   >
                     <UserCog className="size-[18px] shrink-0" />
-                    {t("profile")}
+                    {t("commons.profile")}
                   </Link>
                 </DropdownMenuItem>
                 <DropdownMenuItem className="p-0">

+ 28 - 0
src/features/page/layout.tsx

@@ -0,0 +1,28 @@
+import { cn } from "@/shared/lib/utils";
+import { Typography } from "@/components/ui/typography";
+
+import type { ComponentPropsWithoutRef } from "react";
+
+export const Layout = (props: ComponentPropsWithoutRef<"div">) => {
+  return <div {...props} className={cn("", props.className)} />;
+};
+
+export const LayoutHeader = (props: ComponentPropsWithoutRef<"div">) => {
+  return <div {...props} className={cn("flex w-full min-w-[200px] flex-col items-start gap-2 md:flex-1", props.className)} />;
+};
+
+export const LayoutTitle = (props: ComponentPropsWithoutRef<"h1">) => {
+  return <Typography {...props} className={cn(props.className)} variant="h2" />;
+};
+
+export const LayoutDescription = (props: ComponentPropsWithoutRef<"p">) => {
+  return <Typography {...props} className={cn(props.className)} />;
+};
+
+export const LayoutActions = (props: ComponentPropsWithoutRef<"div">) => {
+  return <div {...props} className={cn("flex items-center", props.className)} />;
+};
+
+export const LayoutContent = (props: ComponentPropsWithoutRef<"div">) => {
+  return <div {...props} className={cn("w-full", props.className)} />;
+};

+ 0 - 39
src/features/settings/edit-profile/model/edit-profile.action.ts

@@ -1,39 +0,0 @@
-"use server";
-
-import { prisma } from "@/shared/lib/prisma";
-import { ERROR_MESSAGES } from "@/shared/constants/errors";
-import { actionClient, ActionError } from "@/shared/api/safe-actions";
-import { editProfileFormSchema } from "@/features/settings/edit-profile/schema/edit-profile.schema";
-import { serverRequiredUser } from "@/entities/user/model/get-server-session-user";
-
-export const updateProfileAction = actionClient.schema(editProfileFormSchema).action(async ({ parsedInput }) => {
-  const { firstName, lastName, email } = parsedInput;
-  const user = await serverRequiredUser();
-
-  const existing = await prisma.user.findFirst({
-    where: {
-      email,
-      id: { not: user.id },
-    },
-  });
-  if (existing) {
-    throw new ActionError(ERROR_MESSAGES.EMAIL_ALREADY_USED);
-  }
-
-  const updatedUser = await prisma.user.update({
-    where: { id: user.id },
-    data: {
-      firstName,
-      lastName,
-      email,
-    },
-  });
-
-  return {
-    id: updatedUser.id,
-    firstName: updatedUser.firstName,
-    lastName: updatedUser.lastName,
-    email: updatedUser.email,
-    image: updatedUser.image,
-  };
-});

+ 0 - 8
src/features/settings/edit-profile/schema/edit-profile.schema.ts

@@ -1,8 +0,0 @@
-import { z } from "zod";
-
-export const editProfileFormSchema = z.object({
-  firstName: z.string().min(1, "First name is required"),
-  lastName: z.string().min(1, "Last name is required"),
-  email: z.string().email("Invalid email"),
-});
-export type EditProfileFormSchemaType = z.infer<typeof editProfileFormSchema>;

+ 0 - 120
src/features/settings/edit-profile/ui/edit-profile-form.tsx

@@ -1,120 +0,0 @@
-"use client";
-import { useForm } from "react-hook-form";
-import { useEffect } from "react";
-import { Mail, User } from "lucide-react";
-import { zodResolver } from "@hookform/resolvers/zod";
-
-import { useI18n } from "locales/client";
-import { Input } from "@/workoutcool/components/ui/input";
-import { Button } from "@/workoutcool/components/ui/button";
-import { editProfileFormSchema, EditProfileFormSchemaType } from "@/features/settings/edit-profile/schema/edit-profile.schema";
-import { updateProfileAction } from "@/features/settings/edit-profile/model/edit-profile.action";
-import { ProfileImageUploadForm } from "@/entities/user/ui/profile-image-upload-form";
-import { useCurrentUser } from "@/entities/user/model/useCurrentUser";
-import { brandedToast } from "@/components/ui/toast";
-import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
-
-export function EditProfileForm() {
-  const t = useI18n();
-  const user = useCurrentUser();
-  const form = useForm<EditProfileFormSchemaType>({
-    resolver: zodResolver(editProfileFormSchema),
-    defaultValues: {
-      firstName: user?.firstName,
-      lastName: user?.lastName,
-      email: user?.email,
-    },
-  });
-
-  useEffect(() => {
-    form.reset({
-      firstName: user?.firstName,
-      lastName: user?.lastName,
-      email: user?.email,
-    });
-  }, [user]);
-
-  const handleSubmit = async (values: EditProfileFormSchemaType) => {
-    try {
-      const result = await updateProfileAction(values);
-      if (result?.serverError) {
-        brandedToast({ title: t(result.serverError as keyof typeof t), variant: "error" });
-        return;
-      }
-      brandedToast({ title: t("profile_updated_successfully"), variant: "success" });
-    } catch (error) {
-      brandedToast({ title: "Failed to update profile", variant: "error" });
-      console.error(error);
-    }
-  };
-
-  return (
-    <>
-      <ProfileImageUploadForm isDisabled={form.formState.isSubmitting} />
-
-      <Form form={form} onSubmit={handleSubmit}>
-        <div className="space-y-5 p-4">
-          <FormField
-            control={form.control}
-            name="firstName"
-            render={({ field }) => (
-              <FormItem>
-                <FormLabel className="font-semibold leading-tight">{t("commons.first_name")}</FormLabel>
-                <div className="relative">
-                  <FormControl>
-                    <Input {...field} className="ltr:pl-9 rtl:pr-9" placeholder="Carla" type="text" />
-                  </FormControl>
-                  <User className="absolute top-3 size-4 ltr:left-3 rtl:right-3" />
-                </div>
-                <FormMessage />
-              </FormItem>
-            )}
-          />
-
-          <FormField
-            control={form.control}
-            name="lastName"
-            render={({ field }) => (
-              <FormItem>
-                <FormLabel className="font-semibold leading-tight">{t("commons.last_name")}</FormLabel>
-                <div className="relative">
-                  <FormControl>
-                    <Input {...field} className="ltr:pl-9 rtl:pr-9" placeholder="Williams" type="text" />
-                  </FormControl>
-                  <User className="absolute top-3 size-4 ltr:left-3 rtl:right-3" />
-                </div>
-                <FormMessage />
-              </FormItem>
-            )}
-          />
-
-          <FormField
-            control={form.control}
-            name="email"
-            render={({ field }) => (
-              <FormItem>
-                <FormLabel className="font-semibold leading-tight">{t("commons.email")}</FormLabel>
-                <div className="relative">
-                  <FormControl>
-                    <Input {...field} className="ltr:pl-9 rtl:pr-20" placeholder="CarlaVWilliams@gmail.com" type="email" />
-                  </FormControl>
-                  <Mail className="absolute top-3 size-4 ltr:left-3 rtl:right-3" />
-                </div>
-                <FormMessage />
-              </FormItem>
-            )}
-          />
-
-          <div className="flex items-center justify-end gap-4">
-            <Button className="text-danger" size="large" type="button" variant="outline-general">
-              {t("commons.cancel")}
-            </Button>
-            <Button disabled={form.formState.isSubmitting} size="large" type="submit" variant="black">
-              {form.formState.isSubmitting ? t("commons.saving") : t("commons.save_changes")}
-            </Button>
-          </div>
-        </div>
-      </Form>
-    </>
-  );
-}

+ 0 - 0
src/features/settings/update-password/lib/hash.ts → src/features/update-password/lib/hash.ts


+ 0 - 0
src/features/settings/update-password/lib/validate-password.ts → src/features/update-password/lib/validate-password.ts


+ 3 - 3
src/features/settings/update-password/model/update-password.action.ts → src/features/update-password/model/update-password.action.ts

@@ -3,9 +3,9 @@
 import { prisma } from "@/shared/lib/prisma";
 import { ERROR_MESSAGES } from "@/shared/constants/errors";
 import { actionClient, ActionError } from "@/shared/api/safe-actions";
-import { UpdatePasswordSchema } from "@/features/settings/update-password/model/update-password.schema";
-import { validatePassword } from "@/features/settings/update-password/lib/validate-password";
-import { hashStringWithSalt } from "@/features/settings/update-password/lib/hash";
+import { UpdatePasswordSchema } from "@/features/update-password/model/update-password.schema";
+import { validatePassword } from "@/features/update-password/lib/validate-password";
+import { hashStringWithSalt } from "@/features/update-password/lib/hash";
 import { env } from "@/env";
 import { serverRequiredUser } from "@/entities/user/model/get-server-session-user";
 

+ 0 - 0
src/features/settings/update-password/model/update-password.schema.ts → src/features/update-password/model/update-password.schema.ts


+ 2 - 2
src/features/settings/update-password/ui/password-form.tsx → src/features/update-password/ui/password-form.tsx

@@ -5,8 +5,8 @@ import { LockKeyhole, LockKeyholeOpen } from "lucide-react";
 import { zodResolver } from "@hookform/resolvers/zod";
 
 import { useI18n } from "locales/client";
-import { Input } from "@/workoutcool/components/ui/input";
-import { Button } from "@/workoutcool/components/ui/button";
+import { Input } from "@/fitlinks/components/ui/input";
+import { Button } from "@/fitlinks/components/ui/button";
 import { updatePasswordAction } from "@/features/settings/update-password/model/update-password.action";
 import { brandedToast } from "@/components/ui/toast";
 import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";

+ 1 - 1
src/features/workout-builder/model/use-workout-session.ts

@@ -2,7 +2,7 @@
 
 import { useWorkoutSessionStore } from "@/features/workout-session/model/workout-session.store";
 
-export function useWorkoutSession(sessionId?: string) {
+export function useWorkoutSession() {
   // Le paramètre sessionId n'est plus utilisé ici, la logique de persistance reste dans workoutSessionLocal
   // (si besoin, on peut l'utiliser pour charger une session spécifique dans le store)
   return useWorkoutSessionStore();

+ 6 - 6
src/features/workout-builder/ui/equipment-selection.tsx

@@ -1,5 +1,3 @@
-"use client";
-
 import Image from "next/image";
 import { Check, Zap } from "lucide-react";
 import { ExerciseAttributeValueEnum } from "@prisma/client";
@@ -12,7 +10,7 @@ import { Badge } from "@/components/ui/badge";
 import { EQUIPMENT_CONFIG } from "../model/equipment-config";
 
 interface EquipmentSelectionProps {
-  onClearEquipment: () => void;
+  onClearEquipment: VoidFunction;
   onToggleEquipment: (equipment: ExerciseAttributeValueEnum) => void;
   selectedEquipment: ExerciseAttributeValueEnum[];
 }
@@ -135,7 +133,8 @@ function EquipmentCard({ equipment, isSelected, onToggle }: EquipmentCardProps)
   );
 }
 
-function StatsHeader({ selectedCount }: { selectedCount: number }) {
+function _StatsHeader({ selectedCount }: { selectedCount: number }) {
+  // eslint-disable-next-line react-hooks/rules-of-hooks
   const t = useI18n();
 
   return (
@@ -169,7 +168,8 @@ function StatsHeader({ selectedCount }: { selectedCount: number }) {
   );
 }
 
-function ActionBar({ selectedCount, onClearEquipment }: { selectedCount: number; onClearEquipment: () => void }) {
+function _ActionBar({ selectedCount, onClearEquipment }: { selectedCount: number; onClearEquipment: () => void }) {
+  // eslint-disable-next-line react-hooks/rules-of-hooks
   const t = useI18n();
 
   if (selectedCount === 0) return null;
@@ -198,7 +198,7 @@ function ActionBar({ selectedCount, onClearEquipment }: { selectedCount: number;
   );
 }
 
-export function EquipmentSelection({ onClearEquipment, onToggleEquipment, selectedEquipment }: EquipmentSelectionProps) {
+export function EquipmentSelection({ onToggleEquipment, selectedEquipment }: EquipmentSelectionProps) {
   return (
     <div className="space-y-6">
       <div className="grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 gap-4">

+ 1 - 11
src/features/workout-builder/ui/quit-workout-dialog.tsx

@@ -9,22 +9,12 @@ import { Button } from "@/components/ui/button";
 interface QuitWorkoutDialogProps {
   isOpen: boolean;
   onClose: VoidFunction;
-  onQuitWithSave: VoidFunction;
   onQuitWithoutSave: VoidFunction;
-  elapsedTime: string;
   exercisesCompleted: number;
   totalExercises: number;
 }
 
-export function QuitWorkoutDialog({
-  isOpen,
-  onClose,
-  onQuitWithSave,
-  onQuitWithoutSave,
-  elapsedTime,
-  exercisesCompleted,
-  totalExercises,
-}: QuitWorkoutDialogProps) {
+export function QuitWorkoutDialog({ isOpen, onClose, onQuitWithoutSave, exercisesCompleted, totalExercises }: QuitWorkoutDialogProps) {
   const t = useI18n();
 
   return (

+ 0 - 5
src/features/workout-builder/ui/workout-stepper.tsx

@@ -141,11 +141,6 @@ export function WorkoutStepper() {
             isTimerRunning={isTimerRunning}
             onQuitWorkout={quitWorkout}
             onResetTimer={resetTimer}
-            onSaveAndQuit={() => {
-              // TODO: Implémenter la sauvegarde pour plus tard
-              console.log("Save workout for later");
-              quitWorkout();
-            }}
             onToggleTimer={toggleTimer}
           />
         )}

+ 0 - 9
src/features/workout-session/ui/workout-session-header.tsx

@@ -17,7 +17,6 @@ interface WorkoutSessionHeaderProps {
   onToggleTimer: VoidFunction;
   onResetTimer: VoidFunction;
   onQuitWorkout: VoidFunction;
-  onSaveAndQuit?: VoidFunction;
   currentExerciseIndex: number;
 }
 
@@ -27,7 +26,6 @@ export function WorkoutSessionHeader({
   onToggleTimer,
   onResetTimer,
   onQuitWorkout,
-  onSaveAndQuit,
   currentExerciseIndex,
 }: WorkoutSessionHeaderProps) {
   const t = useI18n();
@@ -42,11 +40,6 @@ export function WorkoutSessionHeader({
     setShowQuitDialog(true);
   };
 
-  const handleQuitWithSave = () => {
-    onSaveAndQuit?.();
-    setShowQuitDialog(false);
-  };
-
   const handleQuitWithoutSave = () => {
     onQuitWorkout();
     setShowQuitDialog(false);
@@ -157,12 +150,10 @@ export function WorkoutSessionHeader({
       </div>
 
       <QuitWorkoutDialog
-        elapsedTime={elapsedTime}
         exercisesCompleted={exercisesCompleted}
         isOpen={showQuitDialog}
         onClose={() => setShowQuitDialog(false)}
         onQuitWithoutSave={handleQuitWithoutSave}
-        onQuitWithSave={handleQuitWithSave}
         totalExercises={totalExercises}
       />
     </>

+ 1 - 1
src/features/workout-session/ui/workout-session-list.tsx

@@ -19,7 +19,7 @@ const BADGE_COLORS = [
   "bg-pink-100 text-pink-700 border-pink-300",
 ];
 
-export function WorkoutSessionList({ onSelect }: { onSelect: (id: string) => void }) {
+export function WorkoutSessionList() {
   const locale = useCurrentLocale();
   const t = useI18n();
   const router = useRouter();

+ 2 - 7
src/features/workout-session/ui/workout-session-set.tsx

@@ -12,16 +12,11 @@ interface WorkoutSetRowProps {
   onRemove: () => void;
 }
 
-const SET_TYPES: WorkoutSetType[] = ["REPS", "WEIGHT", "TIME", "BODYWEIGHT", "NA"];
-const UNITS: WorkoutSetUnit[] = ["kg", "lbs"];
-
 export function WorkoutSessionSet({ set, setIndex, onChange, onFinish, onRemove }: WorkoutSetRowProps) {
   const t = useI18n();
-  // On utilise un tableau de types pour gérer plusieurs colonnes
   const types = set.types || [];
   const maxColumns = 4;
 
-  // Handlers pour chaque champ
   const handleTypeChange = (columnIndex: number) => (e: React.ChangeEvent<HTMLSelectElement>) => {
     const newTypes = [...types];
     newTypes[columnIndex] = e.target.value as WorkoutSetType;
@@ -166,7 +161,7 @@ export function WorkoutSessionSet({ set, setIndex, onChange, onFinish, onRemove
         </Button>
       </div>
 
-      {/* Colonnes de types, stack vertical on mobile, horizontal on md+ */}
+      {/* Columns of types, stack vertical on mobile, horizontal on md+ */}
       <div className="flex flex-col md:flex-row gap-2 w-full">
         {types.map((type, columnIndex) => (
           <div className="flex flex-col w-full md:w-auto" key={columnIndex}>
@@ -198,7 +193,7 @@ export function WorkoutSessionSet({ set, setIndex, onChange, onFinish, onRemove
         ))}
       </div>
 
-      {/* Bouton pour ajouter une colonne, sous les colonnes */}
+      {/* Add column button */}
       {types.length < maxColumns && (
         <div className="flex w-full justify-start mt-1">
           <Button

+ 2 - 1
src/shared/constants/paths.ts

@@ -1,8 +1,9 @@
 export const paths = {
-  dashboard: "dashboard",
+  root: "/",
   signUp: "auth/signup",
   signIn: "auth/signin",
   forgotPassword: "auth/forgot-password",
   resetPassword: "auth/reset-password",
   verifyEmail: "auth/verify-email",
+  profile: "profile",
 } as const;

+ 2 - 2
src/shared/lib/workout-session/types/workout-session.ts

@@ -1,7 +1,7 @@
 import { WorkoutSessionExercise } from "@/features/workout-session/types/workout-set";
 
 export interface WorkoutSession {
-  id: string; // local: "local-xxx", serveur: uuid
+  id: string; // local: "local-xxx", server: uuid
   userId: string;
   status?: "active" | "completed" | "synced";
   startedAt: string;
@@ -10,7 +10,7 @@ export interface WorkoutSession {
   exercises: WorkoutSessionExercise[];
   currentExerciseIndex?: number;
   isActive?: boolean;
-  serverId?: string; // Si synchronisé
+  serverId?: string; // If synced
 }
 
 export type WorkoutSessionStatus = WorkoutSession["status"];

+ 1 - 1
src/shared/lib/workout-session/workout-session.service.ts

@@ -3,7 +3,7 @@ import { workoutSessionApi } from "./workout-session.api";
 
 import type { WorkoutSession } from "./types/workout-session";
 
-// À remplacer par ton vrai hook/contexte d'auth
+// TODO: replace with auth context
 function isUserLoggedIn(): boolean {
   return !!localStorage.getItem("userToken");
 }

+ 3 - 1
src/shared/lib/workout-session/workout-session.sync.ts

@@ -1,3 +1,5 @@
+import { brandedToast } from "@/components/ui/toast";
+
 import { workoutSessionLocal } from "./workout-session.local";
 import { workoutSessionApi } from "./workout-session.api";
 
@@ -8,7 +10,7 @@ export async function syncLocalWorkoutSessions() {
       const { id: serverId } = await workoutSessionApi.create(session);
       workoutSessionLocal.markSynced(session.id, serverId);
     } catch (e) {
-      // Gérer l'erreur (toast, etc.)
+      brandedToast({ title: "SYNC ERROR", variant: "error" });
     }
   }
   workoutSessionLocal.purgeSynced();

Неке датотеке нису приказане због велике количине промена