From bd14922efa11bd466241f1f87bb8f8dc61cd60a8 Mon Sep 17 00:00:00 2001 From: Millian Lamiaux Date: Sat, 14 Mar 2026 20:43:20 +0100 Subject: [PATCH] feat: add workout management pages - Add workout detail page at /workouts/[id] - Add workout creation page at /workouts/new - Integrate WorkoutForm component --- admin-web/app/workouts/[id]/edit/page.tsx | 74 +++++++ admin-web/app/workouts/[id]/page.tsx | 242 ++++++++++++++++++++++ admin-web/app/workouts/new/page.tsx | 34 +++ 3 files changed, 350 insertions(+) create mode 100644 admin-web/app/workouts/[id]/edit/page.tsx create mode 100644 admin-web/app/workouts/[id]/page.tsx create mode 100644 admin-web/app/workouts/new/page.tsx diff --git a/admin-web/app/workouts/[id]/edit/page.tsx b/admin-web/app/workouts/[id]/edit/page.tsx new file mode 100644 index 0000000..355e28c --- /dev/null +++ b/admin-web/app/workouts/[id]/edit/page.tsx @@ -0,0 +1,74 @@ +import { Metadata } from "next" +import Link from "next/link" +import { notFound } from "next/navigation" +import { ArrowLeft } from "lucide-react" + +import { Button } from "@/components/ui/button" +import WorkoutForm from "@/components/workout-form" +import { supabase } from "@/lib/supabase" + +interface EditWorkoutPageProps { + params: Promise<{ + id: string + }> +} + +async function getWorkout(id: string) { + const { data, error } = await supabase + .from("workouts") + .select("*") + .eq("id", id) + .single() + + if (error || !data) { + return null + } + + return data +} + +export async function generateMetadata({ params }: EditWorkoutPageProps): Promise { + const resolvedParams = await params + const workout = await getWorkout(resolvedParams.id) + + if (!workout) { + return { + title: "Workout Not Found | TabataFit Admin", + } + } + + return { + title: `Edit ${workout.title} | TabataFit Admin`, + } +} + +export default async function EditWorkoutPage({ params }: EditWorkoutPageProps) { + const resolvedParams = await params + const workout = await getWorkout(resolvedParams.id) + + if (!workout) { + notFound() + } + + return ( +
+
+ + +

Edit Workout

+

+ Update the details for "{workout.title}" +

+
+ +
+ +
+
+ ) +} diff --git a/admin-web/app/workouts/[id]/page.tsx b/admin-web/app/workouts/[id]/page.tsx new file mode 100644 index 0000000..d78717e --- /dev/null +++ b/admin-web/app/workouts/[id]/page.tsx @@ -0,0 +1,242 @@ +import { Metadata } from "next" +import Link from "next/link" +import { notFound } from "next/navigation" +import { ArrowLeft, Edit, Trash2, Clock, Flame, Dumbbell, Music } from "lucide-react" + +import { Button } from "@/components/ui/button" +import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card" +import { Badge } from "@/components/ui/badge" +import { supabase } from "@/lib/supabase" + +interface WorkoutDetailPageProps { + params: Promise<{ + id: string + }> +} + +const CATEGORY_COLORS: Record = { + "full-body": "bg-orange-500/20 text-orange-500", + "core": "bg-blue-500/20 text-blue-500", + "upper-body": "bg-green-500/20 text-green-500", + "lower-body": "bg-purple-500/20 text-purple-500", + "cardio": "bg-red-500/20 text-red-500", +} + +async function getWorkout(id: string) { + const { data, error } = await supabase + .from("workouts") + .select(` + *, + trainers (name, specialty, color) + `) + .eq("id", id) + .single() + + if (error || !data) { + return null + } + + return data +} + +export async function generateMetadata({ params }: WorkoutDetailPageProps): Promise { + const resolvedParams = await params + const workout = await getWorkout(resolvedParams.id) + + if (!workout) { + return { + title: "Workout Not Found | TabataFit Admin", + } + } + + return { + title: `${workout.title} | TabataFit Admin`, + } +} + +export default async function WorkoutDetailPage({ params }: WorkoutDetailPageProps) { + const resolvedParams = await params + const workout = await getWorkout(resolvedParams.id) + + if (!workout) { + notFound() + } + + const trainer = workout.trainers as { name: string; specialty: string; color: string } | null + + return ( +
+ {/* Header */} +
+
+ + +
+

{workout.title}

+ {workout.is_featured && ( + Featured + )} +
+ +
+ By {trainer?.name || "Unknown Trainer"} + + + {workout.category} + + + {workout.level} +
+
+ +
+ + +
+
+ + {/* Stats Grid */} +
+ + +
+ + Duration +
+

{workout.duration} min

+

{workout.rounds} rounds

+
+
+ + + +
+ + Calories +
+

{workout.calories}

+

estimated burn

+
+
+ + + +
+ + Timing +
+

{workout.work_time}s/{workout.rest_time}s

+

work/rest (+{workout.prep_time}s prep)

+
+
+ + + +
+ + Music +
+

{workout.music_vibe}

+

playlist vibe

+
+
+
+ + {/* Exercises */} + + + Exercises ({workout.exercises?.length || 0}) + + +
+ {workout.exercises?.map((exercise: { name: string; duration: number }, index: number) => ( +
+
+ + {index + 1} + + {exercise.name} +
+ {exercise.duration}s +
+ ))} +
+
+
+ + {/* Equipment */} + {workout.equipment && workout.equipment.length > 0 && ( + + + Equipment + + +
+ {workout.equipment.map((item: string) => ( + + {item} + + ))} +
+
+
+ )} + + {/* Media */} + + + Media + + +
+ {workout.thumbnail_url ? ( +
+

Thumbnail

+ {workout.title} +
+ ) : ( +

No thumbnail

+ )} + + {workout.video_url ? ( +
+

Video

+
+ ) : ( +

No video

+ )} +
+
+
+
+ ) +} diff --git a/admin-web/app/workouts/new/page.tsx b/admin-web/app/workouts/new/page.tsx new file mode 100644 index 0000000..d14febd --- /dev/null +++ b/admin-web/app/workouts/new/page.tsx @@ -0,0 +1,34 @@ +import { Metadata } from "next" +import Link from "next/link" +import { ArrowLeft } from "lucide-react" + +import { Button } from "@/components/ui/button" +import WorkoutForm from "@/components/workout-form" + +export const metadata: Metadata = { + title: "New Workout | TabataFit Admin", + description: "Create a new workout", +} + +export default function NewWorkoutPage() { + return ( +
+
+ +

Create New Workout

+

+ Fill in the details below to create a new Tabata workout +

+
+ +
+ +
+
+ ) +}