diff --git a/apps/web/package.json b/apps/web/package.json index 87d95a8..a8377ba 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -16,8 +16,8 @@ "@dnd-kit/utilities": "^3.2.2", "@dropit/contract": "workspace:*", "@dropit/i18n": "workspace:*", - "@dropit/schemas": "workspace:*", "@dropit/permissions": "workspace:*", + "@dropit/schemas": "workspace:*", "@fullcalendar/core": "^6.1.17", "@fullcalendar/daygrid": "^6.1.17", "@fullcalendar/interaction": "^6.1.17", @@ -52,6 +52,7 @@ "react": "19.0.0", "react-dom": "19.0.0", "react-hook-form": "^7.54.2", + "recharts": "^3.3.0", "tailwind-merge": "^2.5.5", "tailwindcss": "^3.0.0", "tailwindcss-animate": "^1.0.7", diff --git a/apps/web/src/assets/images/hero-pages/1993010.svg b/apps/web/src/assets/images/hero-pages/1993010.svg new file mode 100644 index 0000000..c5ec022 --- /dev/null +++ b/apps/web/src/assets/images/hero-pages/1993010.svg @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/web/src/assets/images/hero-pages/1993011.svg b/apps/web/src/assets/images/hero-pages/1993011.svg new file mode 100644 index 0000000..3120caf --- /dev/null +++ b/apps/web/src/assets/images/hero-pages/1993011.svg @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/web/src/assets/images/hero-pages/1993012.svg b/apps/web/src/assets/images/hero-pages/1993012.svg new file mode 100644 index 0000000..fdb76e6 --- /dev/null +++ b/apps/web/src/assets/images/hero-pages/1993012.svg @@ -0,0 +1,63 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/web/src/assets/images/hero-pages/1993013.svg b/apps/web/src/assets/images/hero-pages/1993013.svg new file mode 100644 index 0000000..53f25e7 --- /dev/null +++ b/apps/web/src/assets/images/hero-pages/1993013.svg @@ -0,0 +1,71 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/web/src/assets/images/hero-pages/1993014.svg b/apps/web/src/assets/images/hero-pages/1993014.svg new file mode 100644 index 0000000..44531bc --- /dev/null +++ b/apps/web/src/assets/images/hero-pages/1993014.svg @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/web/src/assets/images/hero-pages/1993015.svg b/apps/web/src/assets/images/hero-pages/1993015.svg new file mode 100644 index 0000000..dc81ac9 --- /dev/null +++ b/apps/web/src/assets/images/hero-pages/1993015.svg @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/web/src/assets/images/hero-pages/199306.svg b/apps/web/src/assets/images/hero-pages/199306.svg new file mode 100644 index 0000000..f0103bd --- /dev/null +++ b/apps/web/src/assets/images/hero-pages/199306.svg @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/web/src/assets/images/hero-pages/199307.svg b/apps/web/src/assets/images/hero-pages/199307.svg new file mode 100644 index 0000000..0a4f510 --- /dev/null +++ b/apps/web/src/assets/images/hero-pages/199307.svg @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/web/src/assets/images/hero-pages/199308.svg b/apps/web/src/assets/images/hero-pages/199308.svg new file mode 100644 index 0000000..14449dd --- /dev/null +++ b/apps/web/src/assets/images/hero-pages/199308.svg @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/web/src/assets/images/hero-pages/199309.svg b/apps/web/src/assets/images/hero-pages/199309.svg new file mode 100644 index 0000000..bb8e654 --- /dev/null +++ b/apps/web/src/assets/images/hero-pages/199309.svg @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/web/src/assets/images/hero-pages/19930a.svg b/apps/web/src/assets/images/hero-pages/19930a.svg new file mode 100644 index 0000000..7c481e6 --- /dev/null +++ b/apps/web/src/assets/images/hero-pages/19930a.svg @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/web/src/assets/images/hero-pages/19930b.svg b/apps/web/src/assets/images/hero-pages/19930b.svg new file mode 100644 index 0000000..3154b84 --- /dev/null +++ b/apps/web/src/assets/images/hero-pages/19930b.svg @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/web/src/assets/images/hero-pages/19930c.svg b/apps/web/src/assets/images/hero-pages/19930c.svg new file mode 100644 index 0000000..7e0a86b --- /dev/null +++ b/apps/web/src/assets/images/hero-pages/19930c.svg @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/web/src/assets/images/hero-pages/19930e.svg b/apps/web/src/assets/images/hero-pages/19930e.svg new file mode 100644 index 0000000..636ab7e --- /dev/null +++ b/apps/web/src/assets/images/hero-pages/19930e.svg @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/web/src/assets/images/hero-pages/login.svg b/apps/web/src/assets/images/hero-pages/login.svg new file mode 100644 index 0000000..b6c6477 --- /dev/null +++ b/apps/web/src/assets/images/hero-pages/login.svg @@ -0,0 +1,147 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apps/web/src/assets/images/hero-pages/logo-dropit.png b/apps/web/src/assets/images/hero-pages/logo-dropit.png new file mode 100644 index 0000000..5e7369e Binary files /dev/null and b/apps/web/src/assets/images/hero-pages/logo-dropit.png differ diff --git a/apps/web/src/features/athletes/athlete-detail.tsx b/apps/web/src/features/athletes/athlete-detail.tsx index 6ece2ae..fbe46cd 100644 --- a/apps/web/src/features/athletes/athlete-detail.tsx +++ b/apps/web/src/features/athletes/athlete-detail.tsx @@ -1,8 +1,3 @@ -import { - Avatar, - AvatarFallback, - AvatarImage, -} from '@/shared/components/ui/avatar'; import { Button } from '@/shared/components/ui/button'; import { Card, CardContent } from '@/shared/components/ui/card'; import { @@ -39,7 +34,7 @@ import { } from '@radix-ui/react-select'; import { format as formatDate } from 'date-fns'; import { fr } from 'date-fns/locale'; -import { Dumbbell, Pencil, Plus } from 'lucide-react'; +import { Dumbbell, Pencil } from 'lucide-react'; import { Form, useForm } from 'react-hook-form'; type AthleteDetailProps = { @@ -61,11 +56,6 @@ type AthleteDetailProps = { onCreateCompetitorStatus: (data: CreateCompetitorStatus) => void; }; -// Helper function to generate initials from name -function getInitials(firstName: string, lastName: string): string { - return `${firstName.charAt(0)}${lastName.charAt(0)}`.toUpperCase(); -} - export function AthleteDetail({ athlete, personalRecords, @@ -84,31 +74,9 @@ export function AthleteDetail({ return (
- {/* Profile Header with Avatar */} -
- - - - {getInitials(athlete.firstName, athlete.lastName)} - - - -
-

- {athlete.firstName} {athlete.lastName} -

- {athlete.country && ( -

{athlete.country}

- )} -
-
-
{/* Personal Information */} - +

{t('athletes:details.personal_info')} @@ -148,7 +116,7 @@ export function AthleteDetail({ {/* Stats & Club */}
{/* Stats */} - +

{t('athletes:details.statistics')} @@ -172,7 +140,7 @@ export function AthleteDetail({ {/* Competitor Status */} - +

@@ -182,7 +150,6 @@ export function AthleteDetail({ {athlete.competitorStatus && ( )} -

@@ -434,7 +398,7 @@ export function AthleteDetail({ > {t('athletes:details.cancel')} -

@@ -470,7 +434,7 @@ export function AthleteDetail({

{/* Personal Records Section */} - +

@@ -494,7 +458,7 @@ export function AthleteDetail({
{athlete.personalRecords ? ( <> - +
@@ -505,7 +469,7 @@ export function AthleteDetail({
- +
@@ -516,7 +480,7 @@ export function AthleteDetail({
- +
@@ -544,7 +508,7 @@ export function AthleteDetail({
{personalRecords ? ( personalRecords.map((record) => ( - +
{record.exerciseName || @@ -578,7 +542,7 @@ export function AthleteDetail({ {/* History */} - +

{t('athletes:details.training_history')} diff --git a/apps/web/src/features/athletes/columns.tsx b/apps/web/src/features/athletes/columns.tsx index 716527d..c62592f 100644 --- a/apps/web/src/features/athletes/columns.tsx +++ b/apps/web/src/features/athletes/columns.tsx @@ -3,16 +3,19 @@ import { AvatarFallback, AvatarImage, } from '@/shared/components/ui/avatar'; +import { Badge } from '@/shared/components/ui/badge'; import { Checkbox } from '@/shared/components/ui/checkbox'; import { useTranslation } from '@dropit/i18n'; import { AthleteDetailsDto } from '@dropit/schemas'; import { ColumnDef } from '@tanstack/react-table'; +import { Mars, Venus } from 'lucide-react'; +import { getLevelBadgeVariant } from '@/shared/utils'; export const columns: ColumnDef[] = [ { id: 'select', header: ({ table }) => ( -
+
[] = [
), cell: ({ row }) => ( -
+
row.toggleSelected(!!value)} @@ -65,10 +68,26 @@ export const columns: ColumnDef[] = [ id: 'sexCategory', header: () => { const { t } = useTranslation(['athletes']); - return t('columns.sex_category'); + return
{t('columns.sex_category')}
; }, cell: ({ row }) => { - return row.original.competitorStatus?.sexCategory || '-'; + const sexCategory = row.original.competitorStatus?.sexCategory; + if (!sexCategory) return
-
; + + const isMale = sexCategory.toLowerCase() === 'men' || sexCategory.toLowerCase() === 'male' || sexCategory.toLowerCase() === 'm'; + const isFemale = sexCategory.toLowerCase() === 'women' || sexCategory.toLowerCase() === 'female' || sexCategory.toLowerCase() === 'f'; + + return ( +
+ {isMale ? ( + + ) : isFemale ? ( + + ) : ( + sexCategory + )} +
+ ); }, }, { @@ -125,7 +144,16 @@ export const columns: ColumnDef[] = [ return t('columns.level'); }, cell: ({ row }) => { - return row.original.competitorStatus?.level || '-'; + const level = row.original.competitorStatus?.level; + if (!level) return '-'; + + const badgeVariant = getLevelBadgeVariant(level); + + return ( + + {level} + + ); }, }, { diff --git a/apps/web/src/features/athletes/data-table.tsx b/apps/web/src/features/athletes/data-table.tsx index 512a66b..9e8387f 100644 --- a/apps/web/src/features/athletes/data-table.tsx +++ b/apps/web/src/features/athletes/data-table.tsx @@ -6,6 +6,7 @@ import { DropdownMenuTrigger, } from '@/shared/components/ui/dropdown-menu'; import { Input } from '@/shared/components/ui/input'; +import { ScrollArea } from '@/shared/components/ui/scroll-area'; import { Select, SelectContent, @@ -36,7 +37,7 @@ import { getSortedRowModel, useReactTable, } from '@tanstack/react-table'; -import { ChevronLeft, ChevronRight, Search } from 'lucide-react'; +import { ChevronDown, ChevronLeft, ChevronRight, Search } from 'lucide-react'; import { useState } from 'react'; interface DataTableProps { @@ -103,153 +104,163 @@ export function DataTable({ }); return ( -
- {/* Filters */} -
-
-
- - setGlobalFilter(event.target.value)} - className="pl-8 bg-sidebar max-w-lg" - /> -
-
- - - - - - {table - .getAllColumns() - .filter((column) => column.getCanHide()) - .map((column) => { - return ( - - column.toggleVisibility(!!value) - } - > - {column.id} - - ); - })} - - - - +
+ {/* Fixed Filters Section */} +
+
+
+
+ + setGlobalFilter(event.target.value)} + className="pl-8 bg-background max-w-lg" + /> +
+
+ + + + + + {table + .getAllColumns() + .filter((column) => column.getCanHide()) + .map((column) => { + return ( + + column.toggleVisibility(!!value) + } + > + {column.id} + + ); + })} + + + + +
- {/* Table */} -
- - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => { - return ( - - {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext() + + {/* Scrollable Table and Pagination Section */} + +
+ {/* Table */} +
+
+ + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} + + ); + })} + + ))} + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + onRowClick?.(row.original.id)} + > + {row.getVisibleCells().map((cell) => ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext() )} - - ); - })} - - ))} - - - {table.getRowModel().rows?.length ? ( - table.getRowModel().rows.map((row) => ( - onRowClick?.(row.original.id)} - > - {row.getVisibleCells().map((cell) => ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} + + ))} + + )) + ) : ( + + + {t('athletes:filters.no_results')} - ))} - - )) - ) : ( - - - {t('athletes:filters.no_results')} - - - )} - -
-
- {/* Pagination */} -
-
-
- {t('common:table.selected_rows', { - count: table.getFilteredSelectedRowModel().rows.length, - total: table.getFilteredRowModel().rows.length, - })} + + )} + +
-
- {t('common:table.rows_per_page')} - + + {/* Pagination */} +
+
+
+ {t('common:table.selected_rows', { + count: table.getFilteredSelectedRowModel().rows.length, + total: table.getFilteredRowModel().rows.length, + })} +
+
+ {t('common:table.rows_per_page')} + +
+
+
+ + +
-
- - -
-
+
); } diff --git a/apps/web/src/features/complex/complex-card.tsx b/apps/web/src/features/complex/complex-card.tsx index 3183450..8762641 100644 --- a/apps/web/src/features/complex/complex-card.tsx +++ b/apps/web/src/features/complex/complex-card.tsx @@ -26,7 +26,7 @@ interface ComplexCardProps { export function ComplexCard({ complex, onClick }: ComplexCardProps) { return ( diff --git a/apps/web/src/features/complex/complex-detail.tsx b/apps/web/src/features/complex/complex-detail.tsx index 02f0bc8..570f04f 100644 --- a/apps/web/src/features/complex/complex-detail.tsx +++ b/apps/web/src/features/complex/complex-detail.tsx @@ -1,7 +1,7 @@ import { api } from '@/lib/api'; import { Badge } from '@/shared/components/ui/badge'; import { Button } from '@/shared/components/ui/button'; -import { Card, CardContent, CardHeader } from '@/shared/components/ui/card'; +import { CardContent, CardHeader } from '@/shared/components/ui/card'; import { Form, FormControl, @@ -10,7 +10,6 @@ import { FormLabel, FormMessage, } from '@/shared/components/ui/form'; -import { Input } from '@/shared/components/ui/input'; import { Label } from '@/shared/components/ui/label'; import { Select, @@ -19,7 +18,6 @@ import { SelectTrigger, SelectValue, } from '@/shared/components/ui/select'; -import { Separator } from '@/shared/components/ui/separator'; import { Textarea } from '@/shared/components/ui/textarea'; import { useToast } from '@/shared/hooks/use-toast'; import { @@ -45,7 +43,7 @@ import { zodResolver } from '@hookform/resolvers/zod'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { format } from 'date-fns'; import { fr } from 'date-fns/locale'; -import { GripVertical, PlusCircle, Trash2 } from 'lucide-react'; +import { GripVertical, Trash2 } from 'lucide-react'; import { useState } from 'react'; import { UseFormReturn, useFieldArray, useForm } from 'react-hook-form'; import { z } from 'zod'; @@ -62,7 +60,6 @@ function SortableExerciseItem({ id, index, children, - form, remove, }: { id: string; @@ -100,24 +97,6 @@ function SortableExerciseItem({
- ( - - - field.onChange(parseInt(e.target.value))} - /> - - - )} - /> - {children} {index >= 2 && ( @@ -301,7 +280,6 @@ export function ComplexDetail({ complex }: ComplexDetailProps) { className="space-y-6" > {/* Informations principales */} - - {/* Catégorie dans une Card séparée */} - - {/* Liste des exercices avec drag & drop */} - @@ -455,9 +428,6 @@ export function ComplexDetail({ complex }: ComplexDetailProps) { - - -
diff --git a/apps/web/src/features/complex/complex-filters.tsx b/apps/web/src/features/complex/complex-filters.tsx index f912589..1953564 100644 --- a/apps/web/src/features/complex/complex-filters.tsx +++ b/apps/web/src/features/complex/complex-filters.tsx @@ -39,7 +39,7 @@ export function ComplexFilters({ placeholder={t('complex.filters.search_placeholder')} onChange={(e) => onFilterChange(e.target.value)} disabled={disabled} - className="pl-8 bg-sidebar max-w-lg" + className="pl-8 bg-background max-w-lg" />
{/* Filters */} diff --git a/apps/web/src/features/exercises/columns.tsx b/apps/web/src/features/exercises/columns.tsx index 3d760e0..01fc337 100644 --- a/apps/web/src/features/exercises/columns.tsx +++ b/apps/web/src/features/exercises/columns.tsx @@ -20,7 +20,7 @@ export const columns: ColumnDef[] = [ { id: 'select', header: ({ table }) => ( -
+
[] = [
), cell: ({ row }) => ( -
+
row.toggleSelected(!!value)} diff --git a/apps/web/src/features/exercises/data-table.tsx b/apps/web/src/features/exercises/data-table.tsx index 304a119..fa19df2 100644 --- a/apps/web/src/features/exercises/data-table.tsx +++ b/apps/web/src/features/exercises/data-table.tsx @@ -6,6 +6,7 @@ import { DropdownMenuTrigger, } from '@/shared/components/ui/dropdown-menu'; import { Input } from '@/shared/components/ui/input'; +import { ScrollArea } from '@/shared/components/ui/scroll-area'; import { Separator } from '@/shared/components/ui/separator'; import { Table, @@ -28,7 +29,7 @@ import { getSortedRowModel, useReactTable, } from '@tanstack/react-table'; -import { ChevronLeft, ChevronRight, Search } from 'lucide-react'; +import { ChevronDown, ChevronLeft, ChevronRight, Search } from 'lucide-react'; import { useState } from 'react'; interface DataTableProps { @@ -69,135 +70,145 @@ export function DataTable({ }); return ( -
- {/* Filters */} -
-
-
- - - table.getColumn('name')?.setFilterValue(event.target.value) - } - className="pl-8 bg-sidebar max-w-lg" - /> -
-
- - - - - - {table - .getAllColumns() - .filter((column) => column.getCanHide()) - .map((column) => { - return ( - - column.toggleVisibility(!!value) - } - > - {column.id} - - ); - })} - - - - +
+ {/* Fixed Filters Section */} +
+
+
+
+ + + table.getColumn('name')?.setFilterValue(event.target.value) + } + className="pl-8 bg-background max-w-lg" + /> +
+
+ + + + + + {table + .getAllColumns() + .filter((column) => column.getCanHide()) + .map((column) => { + return ( + + column.toggleVisibility(!!value) + } + > + {column.id} + + ); + })} + + + + +
- {/* Table */} -
- - - {table.getHeaderGroups().map((headerGroup) => ( - - {headerGroup.headers.map((header) => { - return ( - - {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext() + + {/* Scrollable Table and Pagination Section */} + +
+ {/* Table */} +
+
+ + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext() + )} + + ); + })} + + ))} + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + onRowClick?.(row.original.id)} + > + {row.getVisibleCells().map((cell) => ( + + {flexRender( + cell.column.columnDef.cell, + cell.getContext() )} - - ); - })} - - ))} - - - {table.getRowModel().rows?.length ? ( - table.getRowModel().rows.map((row) => ( - onRowClick?.(row.original.id)} - > - {row.getVisibleCells().map((cell) => ( - - {flexRender( - cell.column.columnDef.cell, - cell.getContext() - )} + + ))} + + )) + ) : ( + + + {t('exercise.table.no_results')} - ))} - - )) - ) : ( - - - {t('exercise.table.no_results')} - - - )} - -
-
- {/* Pagination */} -
-
- {t('exercise.table.selected_rows', { - count: table.getFilteredSelectedRowModel().rows.length, - total: table.getFilteredRowModel().rows.length - })} -
-
- - + + )} + + +
+ + {/* Pagination */} +
+
+ {t('exercise.table.selected_rows', { + count: table.getFilteredSelectedRowModel().rows.length, + total: table.getFilteredRowModel().rows.length + })} +
+
+ + +
+
-
+
); } diff --git a/apps/web/src/features/exercises/exercise-detail.tsx b/apps/web/src/features/exercises/exercise-detail.tsx index a20d766..6b0c89c 100644 --- a/apps/web/src/features/exercises/exercise-detail.tsx +++ b/apps/web/src/features/exercises/exercise-detail.tsx @@ -18,7 +18,6 @@ import { SelectTrigger, SelectValue, } from '@/shared/components/ui/select'; -import { Separator } from '@/shared/components/ui/separator'; import { Textarea } from '@/shared/components/ui/textarea'; import { toast } from '@/shared/hooks/use-toast'; import { UpdateExercise, updateExerciseSchema } from '@dropit/schemas'; @@ -260,8 +259,6 @@ export function ExerciseDetail({ exercise }: ExerciseDetailProps) { - -
-
-

- {athlete.firstName} {athlete.lastName} -

-
-
-
- - {/* Main Content */} -
-
- -
-
+
+
); } diff --git a/apps/web/src/routes/__home.athletes.tsx b/apps/web/src/routes/__home.athletes.tsx index d77c8cff..93db067 100644 --- a/apps/web/src/routes/__home.athletes.tsx +++ b/apps/web/src/routes/__home.athletes.tsx @@ -9,6 +9,8 @@ import { DataTable } from '../features/athletes/data-table'; import { DialogCreation } from '../features/athletes/dialog-creation'; import { usePageMeta } from '../shared/hooks/use-page-meta'; import { Button } from '../shared/components/ui/button'; +import { HeroCard } from '../shared/components/ui/hero-card'; +import { Users } from 'lucide-react'; export const Route = createFileRoute('/__home/athletes')({ component: AthletesPage, @@ -47,14 +49,32 @@ function AthletesPage() { if (isAthleteDetail) { return ; } - if (athletesLoading) return
{t('common:loading')}
; - if (!athletes) return
{t('common:no_results')}
; return ( -
-

{t('athletes:description')}

+
+ {/* Fixed header section */} +
+ { + console.log('Open athletes tutorial video'); + } + } + }} + /> +
-
+ {/* DataTable with internal scroll management */} +
{athletesLoading ? (
{t('common:loading')} diff --git a/apps/web/src/routes/__home.dashboard.tsx b/apps/web/src/routes/__home.dashboard.tsx index ba5e2de..2d176e0 100644 --- a/apps/web/src/routes/__home.dashboard.tsx +++ b/apps/web/src/routes/__home.dashboard.tsx @@ -1,11 +1,12 @@ import { createFileRoute } from '@tanstack/react-router'; import { useTranslation } from '@dropit/i18n'; import { usePageMeta } from '../shared/hooks/use-page-meta'; -import { Card, CardContent, CardHeader, CardTitle } from '../shared/components/ui/card'; +import { Card, CardContent, CardHeader } from '../shared/components/ui/card'; import { Button } from '../shared/components/ui/button'; -import { Badge } from '../shared/components/ui/badge'; -import { Users, Calendar, Dumbbell, TrendingUp, Plus, UserPlus, CalendarPlus, ChevronRight } from 'lucide-react'; +import { ScrollArea } from '../shared/components/ui/scroll-area'; import { useEffect } from 'react'; +import { cn } from '@/lib/utils'; +import { AreaChart, Area, ResponsiveContainer, Tooltip, PieChart, Pie, Cell } from 'recharts'; export const Route = createFileRoute('/__home/dashboard')({ component: Dashboard, @@ -19,124 +20,268 @@ function Dashboard() { setPageMeta({ title: t('dashboard.title') }); }, [setPageMeta, t]); + // Données pour le graphique de participation (6 derniers mois) + const participationData = [ + { month: 'Mai', rate: 68 }, + { month: 'Juin', rate: 10 }, + { month: 'Juil', rate: 72 }, + { month: 'Août', rate: 50 }, + { month: 'Sept', rate: 60 }, + { month: 'Oct', rate: 94 }, + ]; + + // Données pour le graphique de répartition des entraînements + const trainingDistribution = [ + { name: 'Exercices', value: 89, color: 'hsl(256, 100%, 65%)' }, // purple-700 + { name: 'Complexes', value: 24, color: 'hsl(256, 100%, 85%)' }, // purple-500 + { name: 'Entraînements', value: 32, color: 'hsl(256, 100%, 88%)' }, // purple-300 + ]; + + // Générer les 7 prochains jours pour le calendrier + const getDaysArray = () => { + const days = []; + const today = new Date(); + for (let i = 0; i < 7; i++) { + const date = new Date(today); + date.setDate(today.getDate() + i); + days.push({ + day: date.toLocaleDateString('fr-FR', { weekday: 'short' }), + date: date.getDate(), + hasSession: i === 2, // Session dans 2 jours + }); + } + return days; + }; + + const calendarDays = getDaysArray(); + return ( -
-

{t('dashboard.description')}

- -
-
- - - - Athlètes actifs - - - - -
24
-

- +2 ce mois -

-
-
+ +
- - - - Séances prévues - - - - -
156
-

- 12 aujourd'hui -

-
-
+
+ {/* Row main KPIs */} +
+ {/* Colonne 1 : Athlètes */} + + +
+
+

Athlètes dans votre club

+

24

+
+ +
+
+
+ + {/* Colonne 2 : Planning */} + + +
+
+

Sessions programmées

+

12

+
+ +
+
+
+ + {/* Colonne 3: Entrainements */} + + +
+
+

Entrainements programmés

+

12

+
+ +
+
+
+
- - - - Exercices disponibles - - - - -
89
-

- +5 récents -

+ {/*Particitation graph*/} + + +
+
+

Taux de participation

+

94%

+
+
+
+ + + + + + + + + [`${value}%`, 'Taux']} + /> + + + +
- - - - Taux de participation - - - - -
94%
-

- +3% ce mois -

-
+ {/* Planning */} + + +
+

Planning des 7 prochains jours

+
+ {calendarDays.map((day, index) => ( +
+ {day.day} + {day.date} + {day.hasSession && ( +
+ )} +
+ ))} +
+
+ -
-
- - - Actions rapides - - - - - + {/* Library repartition */} + + +
+
+

Répartition bibliothèque

+
+ + + + {trainingDistribution.map((entry) => ( + + ))} + + + + +
+
+ {trainingDistribution.map((item) => ( +
+
+
+

{item.name}

+

{item.value}

+
+
+ ))} +
+
+
+
- - - Séances d'aujourd'hui - - -
-
-

Groupe Elite - Force

-

9:00 - 10:30

+
+ + {/* Club news */} +
+ +

Actualité du club

+
+ +
+
+

Nouvelle session de force débute lundi

+

Il y a 2 heures

+
+
+

Compétition régionale ce weekend

+

Il y a 5 heures

+
+
+

Mise à jour du planning de novembre

+

Il y a 1 jour

+
+
+

3 nouveaux athlètes ont rejoint le club

+

Il y a 2 jours

+
- - En cours - -
-
-
-

Formation junior

-

14:00 - 15:30

+ + +
+ + {/* Next competitions */} +
+ +
+

Prochaines compétitions

+
+
+

Régional Sénior

+

28 Octobre 2025

+
+
+

8

+

athlètes inscrits

+
+
- - Planifiée - -
- + +
-
+ ); } diff --git a/apps/web/src/routes/__home.help.tsx b/apps/web/src/routes/__home.help.tsx index c925591..98cd534 100644 --- a/apps/web/src/routes/__home.help.tsx +++ b/apps/web/src/routes/__home.help.tsx @@ -15,5 +15,5 @@ function RouteComponent() { setPageMeta({ title: t('sidebar.menu.help') }) }, [setPageMeta, t]) - return
Hello "/__home/help"!
+ return
Hello "/__home/help"!
} diff --git a/apps/web/src/routes/__home.library.complex.tsx b/apps/web/src/routes/__home.library.complex.tsx index 96bd539..dcd23bc 100644 --- a/apps/web/src/routes/__home.library.complex.tsx +++ b/apps/web/src/routes/__home.library.complex.tsx @@ -1,5 +1,7 @@ import { api } from '@/lib/api' import { DetailsPanel } from '@/shared/components/ui/details-panel' +import { HeroCard } from '@/shared/components/ui/hero-card' +import { ScrollArea } from '@/shared/components/ui/scroll-area' import { Spinner } from '@/shared/components/ui/spinner' import { useTranslation } from '@dropit/i18n' import { useQuery, useQueryClient } from '@tanstack/react-query' @@ -11,6 +13,8 @@ import { ComplexFilters } from '../features/complex/complex-filters' import { ComplexGrid } from '../features/complex/complex-grid' import { DialogCreation } from '../features/exercises/dialog-creation' import { usePageMeta } from '../shared/hooks/use-page-meta' +import { Button } from '@/shared/components/ui/button' +import { Zap } from 'lucide-react' export const Route = createFileRoute('/__home/library/complex')({ component: ComplexPage, @@ -76,11 +80,28 @@ function ComplexPage() { } return ( -
-
-

{t('library.description')}

+
+
+ {/* Fixed header section */} +
+ { + console.log('Open complex tutorial video'); + } + } + }} + /> -
+
+ {/* Scrollable content section */} + {isLoading ? (
{t('common.loading')} @@ -97,6 +121,7 @@ function ComplexPage() {

{t('complex.filters.no_results')}

{t('common.start_create')}

+
) : ( setSelectedComplex(complexId)} /> )} -
+
{t('common.loading')}
- if (!exercises) return
{t('exercise.filters.no_results')}
- return ( -
-
-

{t('library.description')}

+
+
+ {/* Fixed header section */} +
+ { + console.log('Open exercises tutorial video'); + } + } + }} + /> +
+ {/* DataTable with internal scroll management */}
{exercisesLoading ? (
diff --git a/apps/web/src/routes/__home.library.workouts.tsx b/apps/web/src/routes/__home.library.workouts.tsx index 8771add..00f0b72 100644 --- a/apps/web/src/routes/__home.library.workouts.tsx +++ b/apps/web/src/routes/__home.library.workouts.tsx @@ -1,11 +1,14 @@ import { WorkoutFilters } from '@/features/workout/workout-filters'; import { WorkoutGrid } from '@/features/workout/workout-grid'; import { api } from '@/lib/api'; +import { HeroCard } from '@/shared/components/ui/hero-card'; +import { ScrollArea } from '@/shared/components/ui/scroll-area'; import { useTranslation } from '@dropit/i18n'; import { useQuery } from '@tanstack/react-query'; import { Outlet, createFileRoute, useMatches } from '@tanstack/react-router'; import { useState, useEffect } from 'react'; import { usePageMeta } from '../shared/hooks/use-page-meta'; +import { Layers } from 'lucide-react'; export const Route = createFileRoute('/__home/library/workouts')({ component: WorkoutPage, @@ -71,17 +74,35 @@ function WorkoutPage() { navigate({ to: `/workouts/${workoutId}` }); }; - // Si on est sur un détail de workout, on affiche directement le contenu + // If we are on a workout detail, display the content directly if (isWorkoutDetail) { return ; } - // Sinon on affiche la grille des workouts + // Otherwise display the grid of workouts return ( -
-

{t('library.description')}

+
+ {/* Fixed header section */} +
+ { + // TODO: Ouvrir une popup avec vidéo explicative + console.log('Open workout tutorial video'); + } + } + }} + /> -
- {isLoading ? ( -
- {t('common.loading')} -
- ) : !workouts?.length ? ( -
-

{t('workout.filters.no_results')}

-

{t('common.start_create')}

-
- ) : ( - - )} + {/* Scrollable content section */} + + {isLoading ? ( +
+ {t('common.loading')} +
+ ) : !workouts?.length ? ( +
+

{t('workout.filters.no_results')}

+

{t('common.start_create')}

+
+ ) : ( + + )} +
); } diff --git a/apps/web/src/routes/__home.planning.tsx b/apps/web/src/routes/__home.planning.tsx index 863a867..df27924 100644 --- a/apps/web/src/routes/__home.planning.tsx +++ b/apps/web/src/routes/__home.planning.tsx @@ -51,7 +51,12 @@ function PlanningPage() { navigate({ to: '/workouts/create', search: { date: dateParam } }); }; - const handleEventClick = (info: EventClickArg) => { + const handleEventClick = (info: EventClickArg, currentView: string) => { + // Ne pas ouvrir le popover en vue semaine (les détails sont déjà affichés) + if (currentView === 'dayGridWeek') { + return; + } + // Fermer d'abord si un autre popover est déjà ouvert if (isDetailPopoverOpen) { setIsDetailPopoverOpen(false); @@ -155,16 +160,13 @@ function PlanningPage() { if (calendarEventsLoading) return
{t('common:loading')}
; return ( -
-

{t('description')}

- +
- +
Profile page
) diff --git a/apps/web/src/routes/__home.settings.tsx b/apps/web/src/routes/__home.settings.tsx index dd79fc0..816de81 100644 --- a/apps/web/src/routes/__home.settings.tsx +++ b/apps/web/src/routes/__home.settings.tsx @@ -15,5 +15,5 @@ function RouteComponent() { setPageMeta({ title: t('sidebar.menu.settings') }) }, [setPageMeta, t]) - return
Hello "/__home/settings"!
+ return
Hello "/__home/settings"!
} diff --git a/apps/web/src/routes/__home.tsx b/apps/web/src/routes/__home.tsx index 915f8ae..e4a4010 100644 --- a/apps/web/src/routes/__home.tsx +++ b/apps/web/src/routes/__home.tsx @@ -59,18 +59,20 @@ function HomeLayout() { return ( -
- +
+
+ -
- +
+ -
-
- +
+
+ +
-
-
+
+
); diff --git a/apps/web/src/routes/__home.workouts.$workoutId.tsx b/apps/web/src/routes/__home.workouts.$workoutId.tsx index 8522f83..ee08ee1 100644 --- a/apps/web/src/routes/__home.workouts.$workoutId.tsx +++ b/apps/web/src/routes/__home.workouts.$workoutId.tsx @@ -1,9 +1,11 @@ import { WorkoutDetail } from '@/features/workout/workout-detail'; import { WorkoutEditor } from '@/features/workout/workout-editor'; import { api } from '@/lib/api'; +import { usePageMeta } from '@/shared/hooks/use-page-meta'; +import { useTranslation } from '@dropit/i18n'; import { useQuery } from '@tanstack/react-query'; import { createFileRoute } from '@tanstack/react-router'; -import { useState } from 'react'; +import { useState, useEffect } from 'react'; export const Route = createFileRoute('/__home/workouts/$workoutId')({ component: WorkoutDetailPage, @@ -17,6 +19,8 @@ export const Route = createFileRoute('/__home/workouts/$workoutId')({ function WorkoutDetailPage() { const { workoutId } = Route.useParams(); const navigate = Route.useNavigate(); + const { setPageMeta } = usePageMeta(); + const { t } = useTranslation(); const [isEditing, setIsEditing] = useState(false); const { data: workout, isLoading } = useQuery({ @@ -31,6 +35,24 @@ function WorkoutDetailPage() { }, }); + // Update page meta with title and back button + useEffect(() => { + setPageMeta({ + title: t('workout.detail.title'), + showBackButton: true, + onBackClick: () => navigate({ to: '/library/workouts' }) + }); + + // Cleanup: reset to default when leaving the page + return () => { + setPageMeta({ + title: t('library.title'), + showBackButton: false, + onBackClick: undefined + }); + }; + }, [setPageMeta, navigate, t]); + if (isLoading) { return (
@@ -50,10 +72,11 @@ function WorkoutDetailPage() { } return ( - navigate({ to: '/library/workouts' })} - onEdit={() => setIsEditing(true)} - /> +
+ setIsEditing(true)} + /> +
); } diff --git a/apps/web/src/routes/__home.workouts.create.tsx b/apps/web/src/routes/__home.workouts.create.tsx index efd051e..7c8f31b 100644 --- a/apps/web/src/routes/__home.workouts.create.tsx +++ b/apps/web/src/routes/__home.workouts.create.tsx @@ -1,14 +1,12 @@ -import { WorkoutCreationStepper } from '@/features/workout/workout-creation-stepper'; +import { WorkoutCreationStepper, workoutCreationSteps } from '@/features/workout/workout-creation-stepper'; import { api } from '@/lib/api'; -import { Button } from '@/shared/components/ui/button'; +import { Steps } from '@/shared/components/ui/steps'; import { useToast } from '@/shared/hooks/use-toast'; -import { useTranslation } from '@dropit/i18n'; +import { usePageMeta } from '@/shared/hooks/use-page-meta'; import { CreateWorkout } from '@dropit/schemas'; import { useMutation, useQueryClient } from '@tanstack/react-query'; import { createFileRoute } from '@tanstack/react-router'; -import { format } from 'date-fns'; -import { enGB, fr } from 'date-fns/locale'; -import { ArrowLeft } from 'lucide-react'; +import { useEffect, useState } from 'react'; import { z } from 'zod'; const createWorkoutSearchSchema = z.object({ @@ -22,13 +20,10 @@ export const Route = createFileRoute('/__home/workouts/create')({ function CreateWorkoutPage() { const navigate = Route.useNavigate(); - const { date } = Route.useSearch(); const queryClient = useQueryClient(); - const { t, i18n } = useTranslation(); const { toast } = useToast(); - const locale = i18n.language === 'fr' ? fr : enGB; - - const selectedDate = date ? new Date(date) : undefined; + const { setPageMeta } = usePageMeta(); + const [currentStep, setCurrentStep] = useState(0); // Mutation pour créer un workout const { mutate: createWorkoutMutation } = useMutation({ @@ -70,32 +65,40 @@ function CreateWorkoutPage() { navigate({ to: '/library/workouts' }); }; - return ( -
- {/* Navigation Bar */} -
-
- -
-

- {t('workout.creation.title')} - {selectedDate && ` - ${format(selectedDate, 'PPP', { locale })}`} -

-
-
-
+ // Update page meta with title, back button, and steps in the middle + useEffect(() => { + setPageMeta({ + title: 'Création entrainement', + showBackButton: true, + onBackClick: handleCancel, + middleContent: ( + + ), + }); - {/* Main Content */} -
-
- -
-
+ // Cleanup on unmount + return () => { + setPageMeta({ + title: undefined, + showBackButton: false, + onBackClick: undefined, + middleContent: undefined, + }); + }; + }, [setPageMeta, currentStep, handleCancel]); + + return ( +
+
); } \ No newline at end of file diff --git a/apps/web/src/routes/create-organization.tsx b/apps/web/src/routes/create-organization.tsx index 317dfc6..2d7c705 100644 --- a/apps/web/src/routes/create-organization.tsx +++ b/apps/web/src/routes/create-organization.tsx @@ -12,7 +12,7 @@ import { useMutation } from '@tanstack/react-query'; import { useNavigate } from '@tanstack/react-router'; import { z } from 'zod'; import { useForm } from 'react-hook-form'; -import { Building } from 'lucide-react'; +import { BicepsFlexed } from 'lucide-react'; export const Route = createFileRoute('/create-organization')({ beforeLoad: async () => { @@ -75,20 +75,25 @@ function CreateOrganizationPage() { }; return ( -
+
- - -
- + + + {/* Logo */} +
+ + Dropit
- {t('create_organization.title')} - + + {t('create_organization.title')} + {t('create_organization.description')}
- -
+ +
{createOrganizationMutation.isPending diff --git a/apps/web/src/routes/join-organization.tsx b/apps/web/src/routes/join-organization.tsx index 0335df5..1e62d67 100644 --- a/apps/web/src/routes/join-organization.tsx +++ b/apps/web/src/routes/join-organization.tsx @@ -8,11 +8,10 @@ import { zodResolver } from "@hookform/resolvers/zod"; import { useForm } from "react-hook-form"; import { z } from "zod"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/shared/components/ui/card"; -import { Users } from "lucide-react"; +import { BicepsFlexed, ArrowRight } from "lucide-react"; import { Label } from "@/shared/components/ui/label"; import { Input } from "@/shared/components/ui/input"; import { Button } from "@/shared/components/ui/button"; -import { ArrowRight } from "lucide-react"; export const Route = createFileRoute('/join-organization')({ beforeLoad: async () => { @@ -81,19 +80,25 @@ function JoinOrganizationPage() { }; return ( -
- - -
- -
- {t('join_organization.title')} - - {t('join_organization.description')} - -
- - +
+
+ + + {/* Logo */} +
+ + Dropit +
+ + {t('join_organization.title')} + + {t('join_organization.description')} + +
+ +
{joinOrganizationMutation.isPending ? ( @@ -120,22 +125,23 @@ function JoinOrganizationPage() { ) : ( <> {t('join_organization.button')} - + )} -
+

{t('join_organization.help.no_code')}

-

+

{t('join_organization.help.contact_coach')}

+
); } diff --git a/apps/web/src/routes/onboarding.tsx b/apps/web/src/routes/onboarding.tsx index db656aa..6b2d7df 100644 --- a/apps/web/src/routes/onboarding.tsx +++ b/apps/web/src/routes/onboarding.tsx @@ -4,12 +4,12 @@ import { Button } from '@/shared/components/ui/button'; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/shared/components/ui/card'; import { useNavigate } from '@tanstack/react-router'; import { useTranslation } from '@dropit/i18n'; -import { - Trophy, +import { + Trophy, ArrowRight, - Building, UserCheck } from 'lucide-react'; +import { cn } from '@/lib/utils'; export const Route = createFileRoute('/onboarding')({ beforeLoad: async () => { @@ -34,24 +34,36 @@ function OnboardingChoice() { }; return ( -
-
+
+
+ {/* En-tête */}
-
- +
+
-

+

{t('onboarding.welcome.title')}

-

+

{t('onboarding.welcome.subtitle')}

-
+ {/* Cartes de choix */} +
{/* Option Coach */} - -
+
handleChoice('coach')} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { @@ -62,50 +74,66 @@ function OnboardingChoice() { tabIndex={0} role="button" aria-label="Choisir le rôle de coach" + className="p-8" > - -
- + +
+
- {t('onboarding.roles.coach.title')} - + {t('onboarding.roles.coach.title')} + {t('onboarding.roles.coach.description')}
- -
-
- + + +
    +
  • +
    + +
    {t('onboarding.roles.coach.features.create_organization')} -
-
- + +
  • +
    + +
    {t('onboarding.roles.coach.features.manage_athletes')} -
  • -
    - + +
  • +
    + +
    {t('onboarding.roles.coach.features.create_programs')} -
  • -
    - + +
  • +
    + +
    {t('onboarding.roles.coach.features.track_performance')} -
  • -
    - -
    {/* Option Athlète */} - -
    +
    handleChoice('athlete')} onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { @@ -116,76 +144,72 @@ function OnboardingChoice() { tabIndex={0} role="button" aria-label="Choisir le rôle d'athlète" + className="p-8" > - -
    - + +
    +
    - {t('onboarding.roles.athlete.title')} - + {t('onboarding.roles.athlete.title')} + {t('onboarding.roles.athlete.description')}
    - -
    -
    - + + +
      +
    • +
      + +
      {t('onboarding.roles.athlete.features.join_club')} -
    -
    - + +
  • +
    + +
    {t('onboarding.roles.athlete.features.follow_training')} -
  • -
    - + +
  • +
    + +
    {t('onboarding.roles.athlete.features.record_performance')} -
  • -
    - + +
  • +
    + +
    {t('onboarding.roles.athlete.features.communicate_coach')} -
  • -
    - -
    - - {/* Footer */} -
    -

    - {t('onboarding.footer.unsure')}{' '} - - {t('onboarding.footer.learn_more')} - -

    -
    ); } -// Composant CheckCircle pour les listes -const CheckCircle = ({ className }: { className?: string }) => ( - ( + - Checkmark - + Check icon + ); \ No newline at end of file diff --git a/apps/web/src/shared/components/auth/login-form.tsx b/apps/web/src/shared/components/auth/login-form.tsx index 03ca7ee..83e53ac 100644 --- a/apps/web/src/shared/components/auth/login-form.tsx +++ b/apps/web/src/shared/components/auth/login-form.tsx @@ -1,7 +1,6 @@ import { toast } from '@/shared/hooks/use-toast'; import { zodResolver } from '@hookform/resolvers/zod'; import { useMutation } from '@tanstack/react-query'; -import { Github } from 'lucide-react'; import { useForm } from 'react-hook-form'; import { z } from 'zod'; import { authClient } from '@/lib/auth-client'; @@ -15,7 +14,6 @@ import { FormMessage, } from '@/shared/components/ui/form'; import { Input } from '@/shared/components/ui/input'; -import { Separator } from '@/shared/components/ui/separator'; import { useTranslation } from '@dropit/i18n'; function getFormSchema(t: (key: string) => string) { @@ -35,15 +33,13 @@ type LoginFormData = { interface LoginFormProps { onSuccess?: () => void; onError?: (error: Error) => void; - showAlternative?: boolean; showRedirect?: boolean; className?: string; } -export function LoginForm({ - onSuccess, - onError, - showAlternative = true, +export function LoginForm({ + onSuccess, + onError, showRedirect = true, className = "" }: LoginFormProps) { @@ -136,34 +132,14 @@ export function LoginForm({ - {showAlternative && ( - <> -
    -
    - -
    -
    - - {t('login.alternative')} - -
    -
    - - - - )} - {showRedirect && ( -

    - {t('login.redirect', { link: t('login.redirectLink') })}{' '} +

    + Vous n'avez pas de compte ?{' '} - {t('login.redirectLink')} + S'inscrire

    )} diff --git a/apps/web/src/shared/components/auth/signup-form.tsx b/apps/web/src/shared/components/auth/signup-form.tsx index 9c6f640..0065848 100644 --- a/apps/web/src/shared/components/auth/signup-form.tsx +++ b/apps/web/src/shared/components/auth/signup-form.tsx @@ -2,7 +2,6 @@ import { authClient } from '@/lib/auth-client'; import { toast } from '@/shared/hooks/use-toast'; import { zodResolver } from '@hookform/resolvers/zod'; import { useMutation } from '@tanstack/react-query'; -import { Github } from 'lucide-react'; import { useForm } from 'react-hook-form'; import { z } from 'zod'; import { Button } from '@/shared/components/ui/button'; @@ -15,7 +14,6 @@ import { FormMessage, } from '@/shared/components/ui/form'; import { Input } from '@/shared/components/ui/input'; -import { Separator } from '@/shared/components/ui/separator'; import { useTranslation } from '@dropit/i18n'; function getFormSchema(t: (key: string) => string) { @@ -37,16 +35,14 @@ type SignupFormData = { interface SignupFormProps { onSuccess?: () => void; onError?: (error: Error) => void; - showAlternative?: boolean; showRedirect?: boolean; showTerms?: boolean; className?: string; } -export function SignupForm({ - onSuccess, - onError, - showAlternative = true, +export function SignupForm({ + onSuccess, + onError, showRedirect = true, showTerms = true, className = "" @@ -155,55 +151,35 @@ export function SignupForm({ - {showAlternative && ( - <> -
    -
    - -
    -
    - - {t('signup.alternative')} - -
    -
    - - - - )} - {showRedirect && ( -

    - {t('signup.redirect', { link: t('signup.redirectLink') })}{' '} +

    + Vous avez déjà un compte ?{' '} - {t('signup.redirectLink')} + Se connecter

    )} {showTerms && ( -

    - {t('signup.terms.prefix')}{' '} +

    + En cliquant sur continuer, vous acceptez nos{' '} - {t('signup.termsLink')} + Conditions d'utilisation {' '} - {t('signup.terms.middle')}{' '} + et notre{' '} - {t('signup.privacyLink')} + Politique de confidentialité - {t('signup.terms.suffix')} + .

    )}
    diff --git a/apps/web/src/shared/components/layout/app-header.tsx b/apps/web/src/shared/components/layout/app-header.tsx index b6f9edd..5dbe2e4 100644 --- a/apps/web/src/shared/components/layout/app-header.tsx +++ b/apps/web/src/shared/components/layout/app-header.tsx @@ -1,10 +1,10 @@ import { Link, useMatches, useRouter } from '@tanstack/react-router'; -import { ChevronLeft, Bell } from 'lucide-react'; +import { ChevronLeft } from 'lucide-react'; import { authClient } from '@/lib/auth-client'; import { Avatar, AvatarFallback } from '@/shared/components/ui/avatar'; import { Button } from '@/shared/components/ui/button'; -import { usePageTitle } from '@/shared/hooks/use-page-meta'; - +import { usePageMeta } from '@/shared/hooks/use-page-meta'; +import { useTranslation } from '@dropit/i18n'; interface Tab { label: string; path: string; @@ -12,15 +12,18 @@ interface Tab { interface AppHeaderProps { tabs?: Tab[]; - showBackButton?: boolean; - onBackClick?: () => void; } -export function AppHeader({ tabs, showBackButton = false, onBackClick }: AppHeaderProps) { +export function AppHeader({ tabs }: AppHeaderProps) { const matches = useMatches(); const router = useRouter(); const { data: session } = authClient.useSession(); - const pageTitle = usePageTitle(); + const { pageMeta } = usePageMeta(); + const { t } = useTranslation(); + const pageTitle = pageMeta.title; + const showBackButton = pageMeta.showBackButton || false; + const onBackClick = pageMeta.onBackClick; + const middleContent = pageMeta.middleContent; // Function to get user initials from name const getUserInitials = (name?: string) => { @@ -43,29 +46,37 @@ export function AppHeader({ tabs, showBackButton = false, onBackClick }: AppHead const currentPath = matches[matches.length - 1]?.pathname || ''; return ( -
    - {/* Left side: Back button and Page Title */} -
    - {showBackButton && ( - - )} - - {pageTitle && ( -

    +
    + {/* Left side: Back button OR Page Title */} +
    + {showBackButton ? ( +
    + + {t('common.back')} +
    + ) : pageTitle ? ( +

    {pageTitle}

    - )} + ) : null}
    - {/* Center: Tabs */} - {tabs && tabs.length > 0 && ( + {/* Center: Custom Content OR Detail Title OR Tabs */} + {middleContent ? ( +
    + {middleContent} +
    + ) : showBackButton && pageTitle ? ( +

    + {pageTitle} +

    + ) : tabs && tabs.length > 0 ? ( - )} + ) : null} {/* Right side: Notifications and User Menu */} -
    +
    {/* Notifications */} - {/* User Profile */} -
    diff --git a/apps/web/src/shared/components/layout/app-sidebar.tsx b/apps/web/src/shared/components/layout/app-sidebar.tsx index 6b873d3..d434ebc 100644 --- a/apps/web/src/shared/components/layout/app-sidebar.tsx +++ b/apps/web/src/shared/components/layout/app-sidebar.tsx @@ -21,11 +21,11 @@ export function AppSidebar() { const handleLogout = async () => { try { - // Appeler directement l'API pour se déconnecter - // Avec credentials: 'include', les cookies seront automatiquement envoyés + // Call the API directly to logout + // With credentials: 'include', the cookies will be automatically sent await authClient.signOut(); - // Rediriger vers la page de connexion + // Redirect to the login page toast({ title: 'Logout successful', description: 'You have been logged out successfully', @@ -46,7 +46,7 @@ export function AppSidebar() { } }; - const items = [ + const mainItems = [ { title: t('sidebar.menu.dashboard'), url: '/dashboard', @@ -67,6 +67,9 @@ export function AppSidebar() { url: '/athletes', icon: GraduationCap, }, + ] + + const secondaryItems = [ { title: t('sidebar.menu.help'), url: '/help', @@ -79,11 +82,11 @@ export function AppSidebar() { }, ]; - // Fonction pour vérifier si un item est actif + // Function to check if an item is active const isActiveItem = (itemUrl: string) => { const currentPath = matches[matches.length - 1]?.pathname || ''; - // Gestion spéciale pour les routes imbriquées + // Special handling for nested routes if (itemUrl === '/library/workouts') { return currentPath.startsWith('/library/') || currentPath.startsWith('/workouts/'); } @@ -100,53 +103,73 @@ export function AppSidebar() { }; return ( -