Browse Source

chore/build errors (#15)

* chore: remove unused payment pages and related components to streamline the codebase
refactor: update routing logic to use a consistent root path across authentication flows
fix: update error handling messages to use a consistent structure for better localization
feat: add new password update functionality with validation and error handling
feat: implement profile image upload functionality with user feedback
style: improve layout components for better consistency and readability
refactor: clean up workout session components and improve state management
chore: remove deprecated

* chore: clean and build errors part 2
Mat B. 1 month ago
parent
commit
b9480cb778

+ 0 - 1
app/[locale]/(legal-and-payment)/layout.tsx

@@ -1,5 +1,4 @@
 import { LayoutParams } from "@/shared/types/next";
-import { Footer } from "@/features/layout/Footer";
 
 type LocaleParams = Record<string, string> & {
   locale: string;

+ 0 - 3
app/[locale]/_manifest.ts

@@ -1,11 +1,8 @@
-import { getI18n } from "locales/server";
 import { SiteConfig } from "@/shared/config/site-config";
 
 import type { MetadataRoute } from "next";
 
 export default async function manifest(): Promise<MetadataRoute.Manifest> {
-  const t = await getI18n();
-
   return {
     name: SiteConfig.title,
     short_name: SiteConfig.title,

+ 2 - 4
app/[locale]/page.tsx

@@ -1,12 +1,10 @@
 import React from "react";
 
-import { getI18n } from "locales/server";
 import { WorkoutStepper } from "@/features/workout-builder";
-import { serverAuth } from "@/entities/user/model/get-server-session-user";
 
 export default async function HomePage() {
-  const user = await serverAuth();
-  const t = await getI18n();
+  // const user = await serverAuth();
+  // const t = await getI18n();
 
   return (
     <div className="bg-background text-foreground relative flex h-fit flex-col">

+ 13 - 0
locales/en.ts

@@ -15,10 +15,18 @@ export default {
   register_privacy_link_2: "Privacy Policy",
   password_forgot_title: "Forgot password?",
   password_forgot_subtitle: "Enter your email to reset your password",
+  new_password: "New password",
+  new_password_placeholder: "Enter your new password",
+  current_password: "Current password",
+  current_password_placeholder: "Enter your current password",
+  confirm_password: "Confirm password",
+  confirm_password_placeholder: "Confirm your password",
 
   success: {
+    feedback_sent: "Feedback sent",
     password_forgot_success: "Email sent",
     reset_password_success: "Password reset successfully",
+    password_updated_successfully: "Password updated successfully",
   },
 
   error: {
@@ -326,5 +334,10 @@ export default {
     back_to_login: "Back to login",
     sending: "Sending...",
     send_me_link: "Send me a link",
+    extremely_dissatisfied: "Extremely dissatisfied",
+    somewhat_dissatisfied: "Somewhat dissatisfied",
+    neutral: "Neutral",
+    satisfied: "Satisfied",
+    support: "Support",
   },
 } as const;

+ 13 - 0
locales/fr.ts

@@ -15,10 +15,18 @@ export default {
   register_privacy_link_2: "Politique de confidentialité",
   password_forgot_title: "Forgot password?",
   password_forgot_subtitle: "Enter your email to reset your password",
+  new_password: "Nouveau mot de passe",
+  new_password_placeholder: "Entrez votre nouveau mot de passe",
+  current_password: "Mot de passe actuel",
+  current_password_placeholder: "Entrez votre mot de passe actuel",
+  confirm_password: "Confirmer le mot de passe",
+  confirm_password_placeholder: "Confirmez votre mot de passe",
 
   success: {
+    feedback_sent: "Feedback envoyé",
     password_forgot_success: "Email envoyé",
     reset_password_success: "Mot de passe réinitialisé avec succès",
+    password_updated_successfully: "Mot de passe mis à jour avec succès",
   },
 
   error: {
@@ -327,5 +335,10 @@ export default {
     back_to_login: "Retour à la connexion",
     sending: "Envoi...",
     send_me_link: "Envoyer un lien",
+    extremely_dissatisfied: "Très insatisfait",
+    somewhat_dissatisfied: "Insatisfait",
+    neutral: "Neutre",
+    satisfied: "Satisfait",
+    support: "Support",
   },
 } as const;

+ 1 - 1
src/features/auth/signup/ui/signup-form.tsx

@@ -119,7 +119,7 @@ export const SignUpForm = () => {
             <span className="w-full border-t" />
           </div>
           <div className="relative flex justify-center text-xs uppercase">
-            <span className="bg-background text-muted-foreground px-2">{t("or")}</span>
+            <span className="bg-background text-muted-foreground px-2">{t("commons.or")}</span>
           </div>
         </div>
       </Form>

+ 4 - 6
src/features/contact-feedback/ui/ReviewInput.tsx

@@ -1,5 +1,3 @@
-"use client";
-
 import { Angry, Frown, Meh, SmilePlus } from "lucide-react";
 
 import { useI18n } from "locales/client";
@@ -10,10 +8,10 @@ export const ReviewInput = ({ onChange, value }: { onChange: (value: string) =>
   const t = useI18n();
 
   const options = [
-    { value: "1", icon: Angry, tooltip: t("extremely_dissatisfied") },
-    { value: "2", icon: Frown, tooltip: t("somewhat_dissatisfied") },
-    { value: "3", icon: Meh, tooltip: t("neutral") },
-    { value: "4", icon: SmilePlus, tooltip: t("satisfied") },
+    { value: "1", icon: Angry, tooltip: t("commons.extremely_dissatisfied") },
+    { value: "2", icon: Frown, tooltip: t("commons.somewhat_dissatisfied") },
+    { value: "3", icon: Meh, tooltip: t("commons.neutral") },
+    { value: "4", icon: SmilePlus, tooltip: t("commons.satisfied") },
   ];
 
   return (

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

@@ -39,11 +39,11 @@ export const ContactFeedbackPopover = (props: ContactFeedbackPopoverProps) => {
     }
 
     if (result.serverError) {
-      brandedToast({ title: t(result.serverError as keyof typeof t), variant: "error" });
+      brandedToast({ title: t(`backend_errors.${result.serverError}` as keyof typeof t), variant: "error" });
       return;
     }
 
-    brandedToast({ title: t("feedback_sent"), variant: "success" });
+    brandedToast({ title: t("success.feedback_sent"), variant: "success" });
     form.reset();
     open.setFalse();
   };

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

@@ -52,7 +52,7 @@ export const ContactSupportDialog = (props: ContactSupportDialogProps) => {
   return (
     <Dialog onOpenChange={(v) => setOpen(v)} open={open}>
       <DialogTrigger asChild className="cursor-pointer">
-        {props.children ? props.children : <span className={props.className}>{t("support")}</span>}
+        {props.children ? props.children : <span className={props.className}>{t("commons.support")}</span>}
       </DialogTrigger>
       <DialogContent>
         <DialogHeader>

+ 0 - 27
src/features/email/EmailFormSection.tsx

@@ -1,27 +0,0 @@
-import { Typography } from "@/components/ui/typography";
-
-import { EmailForm } from "./EmailForm";
-
-import { SectionLayout } from "@/features/landing/SectionLayout";
-
-export const EmailFormSection = () => {
-  return (
-    <SectionLayout className="relative flex w-full flex-col items-center gap-16" size="lg">
-      <div className="relative m-auto flex max-w-xl flex-col gap-4 text-center">
-        <Typography className="font-extrabold uppercase text-primary" variant="small">
-          Be the first to use our Link in Bio service
-        </Typography>
-        <Typography className="text-center text-4xl lg:text-5xl" variant="h2">
-          Join the waiting list of{" "}
-          <span className="text-gradient bg-gradient-to-r from-red-500 via-red-400 to-yellow-400 font-mono font-extrabold uppercase">
-            Workout.cool
-          </span>
-        </Typography>
-        <Typography variant="h3">Get early access to our powerful link management platform.</Typography>
-        <div className="mx-auto mt-6 w-full max-w-md">
-          <EmailForm submitButtonLabel="Join" successMessage="Thank you for joining the waiting list" />
-        </div>
-      </div>
-    </SectionLayout>
-  );
-};

+ 5 - 5
src/features/update-password/ui/password-form.tsx

@@ -5,9 +5,9 @@ import { LockKeyhole, LockKeyholeOpen } from "lucide-react";
 import { zodResolver } from "@hookform/resolvers/zod";
 
 import { useI18n } from "locales/client";
-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 { Input } from "@/workoutcool/components/ui/input";
+import { Button } from "@/workoutcool/components/ui/button";
+import { updatePasswordAction } from "@/features/update-password/model/update-password.action";
 import { brandedToast } from "@/components/ui/toast";
 import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/components/ui/form";
 
@@ -45,10 +45,10 @@ export function PasswordForm() {
         return;
       }
 
-      brandedToast({ title: t("password_updated_successfully"), variant: "success" });
+      brandedToast({ title: t("success.password_updated_successfully"), variant: "success" });
       form.reset();
     } catch (error) {
-      brandedToast({ title: t("generic_error"), variant: "error" });
+      brandedToast({ title: t("error.generic_error"), variant: "error" });
       console.error(error);
     }
   };

+ 3 - 3
src/features/user/ui/UserDropdown.tsx

@@ -27,18 +27,18 @@ export const UserDropdown = ({ children }: PropsWithChildren) => {
     <DropdownMenu>
       <DropdownMenuTrigger asChild>{children}</DropdownMenuTrigger>
       <DropdownMenuContent className="w-56">
-        <DropdownMenuLabel>{t("profile")}</DropdownMenuLabel>
+        <DropdownMenuLabel>{t("commons.profile")}</DropdownMenuLabel>
         <DropdownMenuItem asChild>
           <Link href="/account">
             <User2 className="mr-2 size-4" />
-            {t("my_account")}
+            {t("commons.my_account")}
           </Link>
         </DropdownMenuItem>
         <DropdownMenuSeparator />
         <DropdownMenuItem asChild>
           <Link href="/dashboard">
             <LayoutDashboard className="mr-2 size-4" />
-            {t("dashboard")}
+            {t("commons.dashboard")}
           </Link>
         </DropdownMenuItem>
 

+ 4 - 22
src/features/workout-builder/ui/exercise-card.tsx

@@ -1,10 +1,8 @@
-"use client";
-
 import { useState } from "react";
 import Image from "next/image";
 import { Play, Shuffle, MoreVertical, Trash2, Info, Target } from "lucide-react";
 
-import { useCurrentLocale, useI18n } from "locales/client";
+import { useI18n } from "locales/client";
 import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";
 import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu";
 import { Card, CardContent, CardHeader } from "@/components/ui/card";
@@ -25,7 +23,6 @@ interface ExerciseCardProps {
 
 export function ExerciseCard({ exercise, muscle, onShuffle, onPick, onDelete }: ExerciseCardProps) {
   const t = useI18n();
-  const locale = useCurrentLocale();
   const [imageError, setImageError] = useState(false);
   const [showVideo, setShowVideo] = useState(false);
 
@@ -38,13 +35,10 @@ export function ExerciseCard({ exercise, muscle, onShuffle, onPick, onDelete }:
 
   const mechanicsType = exercise.attributes?.find((attr) => attr.attributeName.name === "MECHANICS_TYPE")?.attributeValue.value;
 
-  const exerciseName = locale === "fr" ? exercise.name : exercise.nameEn
-
   const handlePlayVideo = () => {
     setShowVideo(true);
   };
 
-
   return (
     <TooltipProvider>
       <Card className="group relative overflow-hidden bg-white dark:bg-slate-800 border border-slate-200 dark:border-slate-700 hover:shadow-lg transition-all duration-200 hover:border-blue-200 dark:hover:border-blue-800">
@@ -62,12 +56,7 @@ export function ExerciseCard({ exercise, muscle, onShuffle, onPick, onDelete }:
                   src={exercise.fullVideoImageUrl}
                 />
                 <div className="absolute inset-0 bg-black/20 opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center">
-                  <Button
-                    className="bg-white/90 text-slate-900"
-                    onClick={handlePlayVideo}
-                    size="small"
-                    variant="secondary"
-                  >
+                  <Button className="bg-white/90 text-slate-900" onClick={handlePlayVideo} size="small" variant="secondary">
                     <Play className="h-4 w-4 mr-2" />
                     {t("workout_builder.exercise.watch_video")}
                   </Button>
@@ -183,14 +172,7 @@ export function ExerciseCard({ exercise, muscle, onShuffle, onPick, onDelete }:
         </CardContent>
       </Card>
       {/* Video Modal */}
-      {exercise.fullVideoUrl && (
-        <ExerciseVideoModal
-          onOpenChange={setShowVideo}
-          open={showVideo}
-          title={exerciseName || exercise.name}
-          videoUrl={exercise.fullVideoUrl}
-        />
-      )}
-    </TooltipProvider >
+      {exercise.fullVideoUrl && <ExerciseVideoModal exercise={exercise} onOpenChange={setShowVideo} open={showVideo} />}
+    </TooltipProvider>
   );
 }

+ 1 - 4
src/features/workout-builder/ui/exercise-list-item.tsx

@@ -1,5 +1,3 @@
-"use client";
-
 import { useState } from "react";
 import Image from "next/image";
 import { Play, Shuffle, Star, Trash2, GripVertical } from "lucide-react";
@@ -20,10 +18,9 @@ interface ExerciseListItemProps {
   onShuffle: (exerciseId: string, muscle: string) => void;
   onPick: (exerciseId: string) => void;
   onDelete: (exerciseId: string, muscle: string) => void;
-  isPicked?: boolean;
 }
 
-export function ExerciseListItem({ exercise, muscle, onShuffle, onPick, onDelete, isPicked = false }: ExerciseListItemProps) {
+export function ExerciseListItem({ exercise, muscle, onShuffle, onPick, onDelete }: ExerciseListItemProps) {
   const t = useI18n();
   const [isHovered, setIsHovered] = useState(false);
   const locale = useCurrentLocale();

+ 0 - 1
src/features/workout-builder/ui/exercises-selection.tsx

@@ -96,7 +96,6 @@ export const ExercisesSelection = ({
                 {flatExercises.map((item) => (
                   <ExerciseListItem
                     exercise={item.exercise}
-                    isPicked={true}
                     key={item.id}
                     muscle={item.muscle}
                     onDelete={onDelete}

+ 0 - 99
src/shared/lib/storage/workout-session-storage.ts

@@ -1,99 +0,0 @@
-// src/shared/lib/storage/workout-session-storage.ts
-import { v4 as uuidv4 } from "uuid";
-
-import { WorkoutSession } from "@/features/workout-session/types/workout-set";
-
-import { fetchSessions, createSession, updateSession } from "@/features/workout-session/model/workout-session.actions";
-
-const STORAGE_KEY = "workout-sessions-v2";
-
-export type SyncStatus = "pending" | "synced" | "error";
-
-export interface LocalWorkoutSession extends WorkoutSession {
-  syncStatus?: SyncStatus;
-  updatedAt: string; // ISO string
-}
-
-function getNow() {
-  return new Date().toISOString();
-}
-
-// --- Local Storage ---
-export function getLocalSessions(): LocalWorkoutSession[] {
-  if (typeof window === "undefined") return [];
-  const raw = localStorage.getItem(STORAGE_KEY);
-  if (!raw) return [];
-  try {
-    return JSON.parse(raw) as LocalWorkoutSession[];
-  } catch {
-    return [];
-  }
-}
-
-export function saveLocalSessions(sessions: LocalWorkoutSession[]) {
-  if (typeof window === "undefined") return;
-  localStorage.setItem(STORAGE_KEY, JSON.stringify(sessions));
-}
-
-export function addLocalSession(session: WorkoutSession) {
-  const sessions = getLocalSessions();
-  const newSession: LocalWorkoutSession = {
-    ...session,
-    id: session.id || uuidv4(),
-    syncStatus: "pending",
-    updatedAt: getNow(),
-  };
-  saveLocalSessions([...sessions, newSession]);
-}
-
-export function updateLocalSession(session: LocalWorkoutSession) {
-  const sessions = getLocalSessions();
-  const idx = sessions.findIndex((s) => s.id === session.id);
-  if (idx !== -1) {
-    sessions[idx] = { ...session, updatedAt: getNow(), syncStatus: "pending" };
-    saveLocalSessions(sessions);
-  }
-}
-
-export function deleteLocalSession(sessionId: string) {
-  const sessions = getLocalSessions().filter((s) => s.id !== sessionId);
-  saveLocalSessions(sessions);
-}
-
-// --- Synchronisation ---
-export async function syncSessions(userId: string) {
-  // 1. Récupère local et distant
-  const local = getLocalSessions();
-  const remote = await fetchSessions(userId);
-
-  // 2. Fusionne (par id, updatedAt)
-  const merged: LocalWorkoutSession[] = [];
-  const allIds = Array.from(new Set([...local.map((s) => s.id), ...remote.map((s) => s.id)]));
-
-  for (const id of allIds) {
-    const localSession = local.find((s) => s.id === id);
-    const remoteSession = remote.find((s) => s.id === id);
-
-    if (localSession && remoteSession) {
-      // Conflit : on garde la plus récente
-      if (new Date(localSession.updatedAt) > new Date(remoteSession.updatedAt)) {
-        // Update remote
-        await updateSession(userId, localSession);
-        merged.push({ ...localSession, syncStatus: "synced" });
-      } else {
-        // Update local
-        merged.push({ ...remoteSession, syncStatus: "synced" });
-      }
-    } else if (localSession && !remoteSession) {
-      // Nouvelle session locale à pousser
-      await createSession(userId, localSession);
-      merged.push({ ...localSession, syncStatus: "synced" });
-    } else if (!localSession && remoteSession) {
-      // Nouvelle session distante à rapatrier
-      merged.push({ ...remoteSession, syncStatus: "synced" });
-    }
-  }
-
-  saveLocalSessions(merged);
-  return merged;
-}

+ 1 - 0
src/shared/lib/workout-session/workout-session.api.ts

@@ -1,3 +1,4 @@
+/* eslint-disable @typescript-eslint/no-unused-vars */
 import type { WorkoutSession } from "./types/workout-session";
 
 export const workoutSessionApi = {

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

@@ -9,7 +9,8 @@ export async function syncLocalWorkoutSessions() {
     try {
       const { id: serverId } = await workoutSessionApi.create(session);
       workoutSessionLocal.markSynced(session.id, serverId);
-    } catch (e) {
+    } catch (error) {
+      console.error(error);
       brandedToast({ title: "SYNC ERROR", variant: "error" });
     }
   }