Bläddra i källkod

style/chronometer down the screen (#47)

Mat B. 1 månad sedan
förälder
incheckning
0cc15f525a

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

@@ -7,7 +7,7 @@ export default async function HomePage() {
   // const t = await getI18n();
 
   return (
-    <div className="bg-background text-foreground relative flex h-fit flex-col h-full">
+    <div className="bg-background text-foreground relative flex  flex-col h-full">
       <WorkoutStepper />
     </div>
   );

+ 6 - 6
next.config.ts

@@ -23,12 +23,12 @@ const nextConfig: NextConfig = {
       },
     },
   },
-  webpack: (config, { dev }) => {
-    if (dev) {
-      config.devtool = "cheap-module-source-map";
-    }
-    return config;
-  },
+  // webpack: (config, { dev }) => {
+  //   if (dev) {
+  //     config.devtool = "cheap-module-source-map";
+  //   }
+  //   return config;
+  // },
 };
 
 export default nextConfig;

BIN
public/favicon.ico


+ 5 - 3
src/features/layout/Footer.tsx

@@ -4,6 +4,7 @@ import { getI18n } from "locales/server";
 import { TFunction } from "locales/client";
 import { cn } from "@/shared/lib/utils";
 import { paths } from "@/shared/constants/paths";
+import { WorkoutSessionTimer } from "@/features/workout-session/ui/workout-session-timer";
 import { Link } from "@/components/ui/link";
 import { DiscordSvg } from "@/components/svg/DiscordSvg";
 
@@ -38,10 +39,11 @@ const NAVIGATION = (t: TFunction) => [
 
 export const Footer = async () => {
   const t = await getI18n();
-
   return (
-    <footer className="border-t border-base-300 dark:border-gray-800 bg-base-100 dark:bg-black px-4 sm:px-6 py-4 rounded-b-lg">
-      <div className="flex  sm:flex-row justify-between items-center gap-4">
+    <footer className="relative border-t border-base-300 dark:border-gray-800 bg-base-100 dark:bg-black px-4 sm:px-6 py-4 rounded-b-lg">
+      <WorkoutSessionTimer />
+
+      <div className="flex sm:flex-row justify-between items-center gap-4">
         {/* Social Icons */}
         <div className="flex gap-2">
           {SOCIAL_LINKS.map(({ href, icon: Icon, label }) => (

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

@@ -92,7 +92,6 @@ export const ExercisesSelection = ({
       </div>
     );
   }
-  console.log("flatExercises:", flatExercises);
 
   return (
     <div className="space-y-6">

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

@@ -1,7 +1,6 @@
 "use client";
 
 import React from "react";
-import { useTheme } from "next-themes";
 import { Check } from "lucide-react";
 
 import { cn } from "@/shared/lib/utils";
@@ -119,7 +118,6 @@ function StepperStep({
 }
 
 export function StepperHeader({ steps, currentStep, onStepClick }: StepperHeaderProps) {
-  const { resolvedTheme } = useTheme();
   return (
     <div className={cn("w-full my-8 px-2 sm:px-6")}>
       {/* Layout mobile - vertical */}

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

@@ -11,8 +11,6 @@ export function WorkoutBuilderFooter({
   onPrevious,
   onNext,
   onStartWorkout,
-  selectedEquipment,
-  selectedMuscles,
 }: {
   currentStep: number;
   totalSteps: number;
@@ -20,8 +18,6 @@ export function WorkoutBuilderFooter({
   onPrevious: VoidFunction;
   onNext: VoidFunction;
   onStartWorkout?: VoidFunction;
-  selectedEquipment: any[];
-  selectedMuscles: any[];
 }) {
   const t = useI18n();
   const isFirstStep = currentStep === 1;

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

@@ -84,7 +84,6 @@ export function WorkoutStepper() {
 
   const handleShuffleExercise = async (exerciseId: string, muscle: string) => {
     try {
-      // Convertir le muscle string vers enum
       const muscleEnum = muscle as ExerciseAttributeValueEnum;
       await shuffleExercise(exerciseId, muscleEnum);
     } catch (error) {
@@ -96,7 +95,6 @@ export function WorkoutStepper() {
   const handlePickExercise = async (exerciseId: string) => {
     try {
       await pickExercise(exerciseId);
-      // Optionnel: afficher un toast de succès
       console.log("Exercise picked successfully!");
     } catch (error) {
       console.error("Error picking exercise:", error);
@@ -255,8 +253,6 @@ export function WorkoutStepper() {
         onNext={nextStep}
         onPrevious={prevStep}
         onStartWorkout={handleStartWorkout}
-        selectedEquipment={selectedEquipment}
-        selectedMuscles={selectedMuscles}
         totalSteps={STEPPER_STEPS.length}
       />
     </div>

+ 21 - 63
src/features/workout-session/ui/workout-session-header.tsx

@@ -1,13 +1,12 @@
 "use client";
 
 import { useState, useEffect } from "react";
-import { Clock, Play, Pause, RotateCcw, X, Target, Weight } from "lucide-react";
+import { X, Target, Weight } from "lucide-react";
 
 import { useCurrentLocale, useI18n } from "locales/client";
 import { type WeightUnit } from "@/shared/lib/weight-conversion";
 import { cn } from "@/shared/lib/utils";
 import { useWorkoutSession } from "@/features/workout-session/model/use-workout-session";
-import { Timer } from "@/components/ui/timer";
 import { Button } from "@/components/ui/button";
 
 import { QuitWorkoutDialog } from "../../workout-builder/ui/quit-workout-dialog";
@@ -60,14 +59,9 @@ export function WorkoutSessionHeader({
     setShowQuitDialog(false);
   };
 
-  const handleReset = () => {
-    onResetTimer();
-    setResetCount((c) => c + 1);
-  };
-
   return (
     <>
-      <div className="w-full mt-4 mb-8 px-2 sm:px-6">
+      <div className="w-full mt-4 mb-12 px-2 sm:px-6">
         <div className="rounded-xl p-3 bg-slate-50 dark:bg-slate-900/80 border border-slate-200 dark:border-slate-700">
           <div className="flex items-center justify-between mb-4">
             <div className="flex items-center gap-2">
@@ -87,65 +81,27 @@ export function WorkoutSessionHeader({
             </Button>
           </div>
 
-          <div className="grid grid-cols-1 sm:grid-cols-3 gap-3">
-            {/* Card 1: elapsed time */}
-            <div className="bg-white dark:bg-slate-800 rounded-lg p-3 border border-slate-200 dark:border-slate-700 transition-colors duration-200 dark:text-white dark:hover:bg-slate-700">
-              <div className="flex items-center gap-2 mb-2">
-                <div className="w-8 h-8 rounded-full bg-blue-500/20 flex items-center justify-center">
-                  <Clock className="h-4 w-4 text-blue-400" />
-                </div>
-                <div>
-                  <h3 className="text-slate-700 dark:text-white font-semibold text-base">{t("workout_builder.session.chronometer")}</h3>
-                </div>
-              </div>
-
-              <div className="text-center">
-                <div className="text-2xl font-mono font-bold text-slate-900 dark:text-white mb-2 tracking-wider">
-                  <Timer initialSeconds={typeof elapsedTime === "number" ? elapsedTime : 0} isRunning={isTimerRunning} key={resetCount} />
-                </div>
-
-                <div className="flex items-center justify-center gap-2">
-                  <Button
-                    className={cn(
-                      "w-8 h-8 rounded-full p-0 text-white",
-                      isTimerRunning ? "bg-amber-500 hover:bg-amber-600" : "bg-emerald-500 hover:bg-emerald-600",
-                    )}
-                    onClick={onToggleTimer}
-                  >
-                    {isTimerRunning ? <Pause className="h-4 w-4" /> : <Play className="h-4 w-4" />}
-                  </Button>
-
-                  <Button
-                    className="w-8 h-8 rounded-full p-0 border-slate-200 text-slate-400 hover:bg-slate-200 dark:border-slate-600 hover:dark:bg-slate-700"
-                    onClick={handleReset}
-                    variant="outline"
-                  >
-                    <RotateCcw className="h-4 w-4" />
-                  </Button>
-                </div>
-              </div>
-            </div>
-
+          <div className="grid grid-cols-2 sm:grid-cols-3 gap-2 sm:gap-3">
             {/* Card 2: progress */}
-            <div className="bg-white dark:bg-slate-800 rounded-lg p-3 border border-slate-200 dark:border-slate-700 transition-colors duration-200 dark:text-white dark:hover:bg-slate-700">
-              <div className="flex items-center gap-2 mb-2">
-                <div className="w-8 h-8 rounded-full bg-purple-500/20 flex items-center justify-center">
-                  <Target className="h-4 w-4 text-purple-400" />
+            <div className="bg-white dark:bg-slate-800 rounded-lg p-2 sm:p-3 border border-slate-200 dark:border-slate-700 transition-colors duration-200 dark:text-white dark:hover:bg-slate-700">
+              <div className="flex items-center gap-1.5 sm:gap-2 mb-1.5 sm:mb-2">
+                <div className="w-6 h-6 sm:w-8 sm:h-8 rounded-full bg-purple-500/20 flex items-center justify-center">
+                  <Target className="h-3 w-3 sm:h-4 sm:w-4 text-purple-400" />
                 </div>
                 <div>
-                  <h3 className="text-slate-700 dark:text-white font-semibold text-base">
+                  <h3 className="text-slate-700 dark:text-white font-semibold text-sm sm:text-base">
                     {t("workout_builder.session.exercise_progress")}
                   </h3>
                 </div>
               </div>
 
-              <div className="space-y-2">
+              <div className="space-y-1.5 sm:space-y-2">
                 <div className="flex items-center justify-between">
-                  <span className="text-lg font-bold text-slate-900 dark:text-white">{exercisesCompleted}</span>
+                  <span className="text-base sm:text-lg font-bold text-slate-900 dark:text-white">{exercisesCompleted}</span>
                   <span className="text-slate-400 dark:text-slate-400">/ {totalExercises}</span>
                 </div>
 
-                <div className="w-full bg-slate-200 dark:bg-slate-700 rounded-full h-2 overflow-hidden">
+                <div className="w-full bg-slate-200 dark:bg-slate-700 rounded-full h-1.5 sm:h-2 overflow-hidden">
                   <div
                     className="h-full bg-gradient-to-r from-purple-500 to-pink-500 transition-all duration-500 ease-out"
                     style={{ width: `${(exercisesCompleted / totalExercises) * 100}%` }}
@@ -161,24 +117,26 @@ export function WorkoutSessionHeader({
             </div>
 
             {/* Card 3: Volume Total */}
-            <div className="bg-white dark:bg-slate-800 rounded-lg p-3 border border-slate-200 dark:border-slate-700 transition-colors duration-200 dark:text-white dark:hover:bg-slate-700">
-              <div className="flex items-center gap-2 mb-2">
-                <div className="w-8 h-8 rounded-full bg-orange-500/20 flex items-center justify-center">
-                  <Weight className="h-4 w-4 text-orange-400" />
+            <div className="bg-white dark:bg-slate-800 rounded-lg p-2 sm:p-3 border border-slate-200 dark:border-slate-700 transition-colors duration-200 dark:text-white dark:hover:bg-slate-700">
+              <div className="flex items-center gap-1.5 sm:gap-2 mb-1.5 sm:mb-2">
+                <div className="w-6 h-6 sm:w-8 sm:h-8 rounded-full bg-orange-500/20 flex items-center justify-center">
+                  <Weight className="h-3 w-3 sm:h-4 sm:w-4 text-orange-400" />
                 </div>
                 <div>
-                  <h3 className="text-slate-700 dark:text-white font-semibold text-base">{t("workout_builder.session.total_volume")}</h3>
+                  <h3 className="text-slate-700 dark:text-white font-semibold text-sm sm:text-base">
+                    {t("workout_builder.session.total_volume")}
+                  </h3>
                 </div>
               </div>
 
               <div className="text-center">
-                <div className="text-2xl font-bold text-slate-900 dark:text-white mb-1">
+                <div className="text-xl sm:text-2xl font-bold text-slate-900 dark:text-white mb-1">
                   {totalVolume.toFixed(volumeUnit === "lbs" ? 1 : 0)}
                 </div>
                 <div className="flex items-center justify-center gap-1">
                   <button
                     className={cn(
-                      "text-xs px-2 py-1 rounded transition-colors",
+                      "text-xs px-1.5 sm:px-2 py-0.5 sm:py-1 rounded transition-colors",
                       volumeUnit === "kg"
                         ? "bg-orange-100 text-orange-700 dark:bg-orange-900 dark:text-orange-100"
                         : "text-slate-400 hover:text-slate-600 dark:hover:text-slate-300",
@@ -190,7 +148,7 @@ export function WorkoutSessionHeader({
                   <span className="text-slate-300 dark:text-slate-600">|</span>
                   <button
                     className={cn(
-                      "text-xs px-2 py-1 rounded transition-colors",
+                      "text-xs px-1.5 sm:px-2 py-0.5 sm:py-1 rounded transition-colors",
                       volumeUnit === "lbs"
                         ? "bg-orange-100 text-orange-700 dark:bg-orange-900 dark:text-orange-100"
                         : "text-slate-400 hover:text-slate-600 dark:hover:text-slate-300",

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

@@ -207,7 +207,7 @@ export function WorkoutSessionSets({
         })}
       </ol>
       {isWorkoutActive && (
-        <div className="flex justify-center mt-8">
+        <div className="flex justify-center mt-8 mb-24">
           <Button
             aria-label="Terminer la séance"
             className="flex items-center gap-2 bg-green-600 hover:bg-green-700 text-white font-bold px-8 py-3 text-lg rounded-2xl border border-green-700 transition-all duration-200 active:scale-95 focus:ring-2 focus:ring-green-400"

+ 64 - 0
src/features/workout-session/ui/workout-session-timer.tsx

@@ -0,0 +1,64 @@
+"use client";
+
+import { useState } from "react";
+import { Play, Pause, RotateCcw } from "lucide-react";
+
+import { useI18n } from "locales/client";
+import { cn } from "@/shared/lib/utils";
+import { useWorkoutSession } from "@/features/workout-builder";
+import { Timer } from "@/components/ui/timer";
+import { Button } from "@/components/ui/button";
+
+export function WorkoutSessionTimer() {
+  const t = useI18n();
+
+  const { isWorkoutActive, isTimerRunning, toggleTimer, resetTimer } = useWorkoutSession();
+  console.log("isWorkoutActive:", isWorkoutActive);
+
+  const [resetCount, setResetCount] = useState(0);
+
+  const handleReset = () => {
+    resetTimer();
+    setResetCount((c) => c + 1);
+  };
+
+  if (!isWorkoutActive) {
+    return null;
+  }
+
+  return (
+    <div className="absolute bottom-16 left-0 right-0 flex justify-center mb-3">
+      <div className="bg-white dark:bg-slate-900 rounded-full px-6 py-4 border border-slate-200 dark:border-slate-700 shadow-lg backdrop-blur-sm">
+        <div className="flex items-center justify-between gap-4">
+          {/* Timer display */}
+          <div className="flex items-center gap-3">
+            <div className="text-xl font-mono font-bold text-slate-900 dark:text-white tracking-wider">
+              <Timer initialSeconds={0} isRunning={isTimerRunning} key={resetCount} />
+            </div>
+          </div>
+
+          {/* Control buttons */}
+          <div className="flex items-center gap-3">
+            <Button
+              className={cn(
+                "w-12 h-12 rounded-full p-0 text-white shadow-md",
+                isTimerRunning ? "bg-amber-500 hover:bg-amber-600" : "bg-emerald-500 hover:bg-emerald-600",
+              )}
+              onClick={toggleTimer}
+            >
+              {isTimerRunning ? <Pause className="h-5 w-5" /> : <Play className="h-5 w-5" />}
+            </Button>
+
+            <Button
+              className="w-12 h-12 rounded-full p-0 border-slate-200 text-slate-400 hover:bg-slate-200 dark:border-slate-600 hover:dark:bg-slate-700 shadow-md"
+              onClick={handleReset}
+              variant="outline"
+            >
+              <RotateCcw className="h-5 w-5" />
+            </Button>
+          </div>
+        </div>
+      </div>
+    </div>
+  );
+}

+ 14 - 5
src/shared/lib/youtube.ts

@@ -66,16 +66,25 @@ export function getYouTubeEmbedUrl(url: string, options: YouTubeEmbedOptions = {
 
   // --- Construct Embed URL ---
 
-  const baseEmbedUrl = "https://www.youtube.com/embed/";
+  const baseEmbedUrl = "https://www.youtube-nocookie.com/embed/";
   const queryParams = new URLSearchParams();
 
   if (options.autoplay) {
     queryParams.set("autoplay", "1");
   }
-  // Add other options here
-  // if (options.controls !== undefined) queryParams.set('controls', options.controls ? '1' : '0');
-  // if (options.loop) queryParams.set('loop', '1'); // Note: loop often requires playlist param too
-  // if (options.start) queryParams.set('start', String(options.start));
+
+  // Maximum `YouTube` branding removal
+  queryParams.set("modestbranding", "1");
+  queryParams.set("rel", "0");
+  queryParams.set("showinfo", "0");
+  queryParams.set("mute", "1");
+  queryParams.set("controls", "1");
+  queryParams.set("iv_load_policy", "3"); // Hide annotations
+  queryParams.set("cc_load_policy", "0"); // Hide captions by default
+  queryParams.set("fs", "0"); // Disable fullscreen button
+  queryParams.set("disablekb", "1"); // Disable keyboard controls
+  queryParams.set("playsinline", "1"); // Mobile optimization
+  queryParams.set("origin", typeof window !== "undefined" ? window.location.origin : "");
 
   // Prioritize playlist embed if playlistId is found
   if (playlistId) {