From 25c3459842eb42c4194e3af288dec4506d9be1bd Mon Sep 17 00:00:00 2001 From: Simon ZHANG <102216594+userMeh@users.noreply.github.com> Date: Tue, 4 Jun 2024 10:08:57 +0200 Subject: [PATCH] feat: Finished poll (#161) * feat: polls updates * feat: poll results * update: dependencies * fix: votes test * fix: wrap searchParams in Suspense --- .../(dashboard)/dashboard/activities/page.tsx | 6 +- .../(dashboard)/dashboard/addresses/page.tsx | 2 +- .../(dashboard)/dashboard/materials/page.tsx | 4 +- .../dashboard/posts/details/page.tsx | 6 +- .../app/(dashboard)/dashboard/posts/page.tsx | 2 +- .../dashboard/tournaments/page.tsx | 4 +- .../dashboard/votes/details/page.tsx | 76 ++++++ .../app/(dashboard)/dashboard/votes/page.tsx | 216 +++++++++--------- .../ui/dashboard/materials/AddMaterial.tsx | 1 - .../tournaments/matches/DeleteMatch.tsx | 2 - .../tournaments/matches/DeleteRound.tsx | 2 - .../votes/{addVotes.tsx => AddVote.tsx} | 13 +- .../app/ui/dashboard/votes/DeleteVote.tsx | 88 +++++++ .../admin/app/ui/dashboard/votes/EditVote.tsx | 167 ++++++++++++++ apps/admin/app/ui/dashboard/votes/VoteRow.tsx | 62 +++++ .../app/ui/dashboard/votes/VotesList.tsx | 20 ++ .../app/ui/dashboard/votes/editVotes.tsx | 141 ------------ apps/admin/package.json | 2 +- apps/api/package.json | 8 +- apps/api/src/handlers/votes.ts | 9 +- apps/api/src/routes/votes.ts | 9 +- apps/api/test/votes.test.ts | 19 +- apps/client/app/(auth)/account/page.tsx | 2 +- apps/client/app/ui/NavBar.tsx | 2 +- apps/client/app/ui/auth/LoginForm.tsx | 2 +- apps/client/app/ui/auth/SignupForm.tsx | 2 +- apps/client/package.json | 2 +- packages/ui/package.json | 3 +- packages/ui/src/components/ui/progress.tsx | 25 ++ pnpm-lock.yaml | 187 ++++++++------- 30 files changed, 706 insertions(+), 378 deletions(-) create mode 100644 apps/admin/app/(dashboard)/dashboard/votes/details/page.tsx rename apps/admin/app/ui/dashboard/votes/{addVotes.tsx => AddVote.tsx} (96%) create mode 100644 apps/admin/app/ui/dashboard/votes/DeleteVote.tsx create mode 100644 apps/admin/app/ui/dashboard/votes/EditVote.tsx create mode 100644 apps/admin/app/ui/dashboard/votes/VoteRow.tsx create mode 100644 apps/admin/app/ui/dashboard/votes/VotesList.tsx delete mode 100644 apps/admin/app/ui/dashboard/votes/editVotes.tsx create mode 100644 packages/ui/src/components/ui/progress.tsx diff --git a/apps/admin/app/(dashboard)/dashboard/activities/page.tsx b/apps/admin/app/(dashboard)/dashboard/activities/page.tsx index e5d2de58..270f113f 100644 --- a/apps/admin/app/(dashboard)/dashboard/activities/page.tsx +++ b/apps/admin/app/(dashboard)/dashboard/activities/page.tsx @@ -96,7 +96,7 @@ function ShowContent({ sports, addresses }: { sports: Sport[]; addresses: Addres setMaxPage(Math.ceil(data.count / 10)); }) .catch((error: Error) => { - console.log(error); + console.error(error); }); }, 500); @@ -189,7 +189,7 @@ export default function Page(): JSX.Element { setSports(sportsArray); }) .catch((error: Error) => { - console.log(error); + console.error(error); }); fetch(`${urlApi}/addresses?${queryParams}`, { @@ -218,7 +218,7 @@ export default function Page(): JSX.Element { setAddresses(addressesArray); }) .catch((error: Error) => { - console.log(error); + console.error(error); }); }, [router]); diff --git a/apps/admin/app/(dashboard)/dashboard/addresses/page.tsx b/apps/admin/app/(dashboard)/dashboard/addresses/page.tsx index bad700bb..b9761fd1 100644 --- a/apps/admin/app/(dashboard)/dashboard/addresses/page.tsx +++ b/apps/admin/app/(dashboard)/dashboard/addresses/page.tsx @@ -67,7 +67,7 @@ function ShowContent() { setMaxPage(Math.ceil(data.count / 10)); }) .catch((error: Error) => { - console.log(error); + console.error(error); }); }, 500); diff --git a/apps/admin/app/(dashboard)/dashboard/materials/page.tsx b/apps/admin/app/(dashboard)/dashboard/materials/page.tsx index 86d08b8b..0ddd1e73 100644 --- a/apps/admin/app/(dashboard)/dashboard/materials/page.tsx +++ b/apps/admin/app/(dashboard)/dashboard/materials/page.tsx @@ -73,7 +73,7 @@ function ShowContent({ addresses }: { addresses: Address[] }): JSX.Element { setMaterials(data.data); }) .catch((error: Error) => { - console.log(error); + console.error(error); }); }, 500); @@ -167,7 +167,7 @@ export default function Page(): JSX.Element { setAddresses(addressesArray); }) .catch((error: Error) => { - console.log(error); + console.error(error); }); }, [router]); diff --git a/apps/admin/app/(dashboard)/dashboard/posts/details/page.tsx b/apps/admin/app/(dashboard)/dashboard/posts/details/page.tsx index dabf5314..82056248 100644 --- a/apps/admin/app/(dashboard)/dashboard/posts/details/page.tsx +++ b/apps/admin/app/(dashboard)/dashboard/posts/details/page.tsx @@ -82,7 +82,7 @@ function ShowReports() { setPost(data); }) .catch((error) => { - console.log(error); + console.error(error); }); const queryParams = new URLSearchParams({ @@ -108,7 +108,7 @@ function ShowReports() { setMaxPage(Math.ceil(data.count / 10)); }) .catch((error) => { - console.log(error); + console.error(error); }); fetch(`${urlApi}/reasons`, { @@ -127,7 +127,7 @@ function ShowReports() { setReasons(data); }) .catch((error) => { - console.log(error); + console.error(error); }); }, [idPost, router, page]); diff --git a/apps/admin/app/(dashboard)/dashboard/posts/page.tsx b/apps/admin/app/(dashboard)/dashboard/posts/page.tsx index be0d9a38..5155695f 100644 --- a/apps/admin/app/(dashboard)/dashboard/posts/page.tsx +++ b/apps/admin/app/(dashboard)/dashboard/posts/page.tsx @@ -60,7 +60,7 @@ function ShowContent() { setMaxPage(Math.ceil(data.count / 10)); }) .catch((error: Error) => { - console.log(error); + console.error(error); }); }, 500); diff --git a/apps/admin/app/(dashboard)/dashboard/tournaments/page.tsx b/apps/admin/app/(dashboard)/dashboard/tournaments/page.tsx index e1ec9739..9facf7d6 100644 --- a/apps/admin/app/(dashboard)/dashboard/tournaments/page.tsx +++ b/apps/admin/app/(dashboard)/dashboard/tournaments/page.tsx @@ -70,7 +70,7 @@ function ShowContent({ addresses }: { addresses: Address[] }): JSX.Element { setMaxPage(Math.ceil(data.count / 10)); }) .catch((error: Error) => { - console.log(error); + console.error(error); }); }, 500); @@ -147,7 +147,7 @@ function page() { setAddresses(data.data); }) .catch((error: Error) => { - console.log(error); + console.error(error); }); }, [router.push, urlApi]); diff --git a/apps/admin/app/(dashboard)/dashboard/votes/details/page.tsx b/apps/admin/app/(dashboard)/dashboard/votes/details/page.tsx new file mode 100644 index 00000000..c1434603 --- /dev/null +++ b/apps/admin/app/(dashboard)/dashboard/votes/details/page.tsx @@ -0,0 +1,76 @@ +'use client'; + +import type { Vote } from '@/app/(dashboard)/dashboard/votes/page'; +import { Progress } from '@repo/ui/components/ui/progress'; +import { useRouter, useSearchParams } from 'next/navigation'; +import React, { Suspense, useEffect, useState } from 'react'; + +function ShowContent() { + const searchParams = useSearchParams(); + const router = useRouter(); + const idPoll = searchParams.get('id'); + const [results, setResults] = useState<{ id: number; votes: number; content: string }[]>([]); + const [title, setTitle] = useState(''); + const [totalVotes, setTotalVotes] = useState(0); + + useEffect(() => { + const urlApi = process.env.NEXT_PUBLIC_API_URL; + if (!idPoll) { + router.push('/dashboard/votes'); + } + + fetch(`${urlApi}/polls/${idPoll}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${localStorage.getItem('access_token')}`, + }, + }) + .then(async (response) => { + if (response.status === 403) { + router.push('/'); + } + return response.json(); + }) + .then((data: Vote) => { + setTitle(data.title); + setResults(data.results); + setTotalVotes(data.results.reduce((acc, result) => acc + result.votes, 0)); + }) + .catch((error: Error) => { + console.error(error); + }); + }, [router, idPoll]); + + return ( + <> +
+

Vote: {title}

+
+
+
+ {results.map((result) => ( +
+
+ {result.content} ({((result.votes / totalVotes) * 100).toFixed(2)} %) +
+ +
+ ))} +
+
+ + ); +} + +function page() { + return ( +
+ Chargement...}> + + +
+ ); +} + +export default page; diff --git a/apps/admin/app/(dashboard)/dashboard/votes/page.tsx b/apps/admin/app/(dashboard)/dashboard/votes/page.tsx index c71d3097..cfb3c641 100644 --- a/apps/admin/app/(dashboard)/dashboard/votes/page.tsx +++ b/apps/admin/app/(dashboard)/dashboard/votes/page.tsx @@ -1,11 +1,14 @@ 'use client'; -import AddVote from '@/app/ui/dashboard/votes/addVotes'; -import EditVote from '@/app/ui/dashboard/votes/editVotes'; -import { Button } from '@repo/ui/components/ui/button'; -import { Dialog, DialogContent } from '@repo/ui/components/ui/dialog'; -import { toast } from '@repo/ui/components/ui/sonner'; +import PaginationComponent from '@/app/ui/Pagination'; +import AddVote from '@/app/ui/dashboard/votes/AddVote'; +import VotesList from '@/app/ui/dashboard/votes/VotesList'; +import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@repo/ui/components/ui/card'; +import { Input } from '@repo/ui/components/ui/input'; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@repo/ui/components/ui/table'; +import { Tabs, TabsContent } from '@repo/ui/components/ui/tabs'; +import { useSearchParams } from 'next/navigation'; +import { useRouter } from 'next/navigation'; import { Suspense, useEffect, useState } from 'react'; export type Vote = { @@ -16,132 +19,117 @@ export type Vote = { max_choices: number; start_at: string; end_at: string; - options: { id: number; content: string }[]; - results: { id_choice: number; count: number }[]; + results: { id: number; votes: number; content: string }[]; }; -function deleteVote(id: number, votes: Vote[], setVotes: React.Dispatch>) { - const api = process.env.NEXT_PUBLIC_API_URL; - - fetch(`${api}/polls/${id}`, { - method: 'DELETE', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${localStorage.getItem('access_token')}`, - }, - }) - .then(async (response) => { - return await response.json(); - }) - .then(() => { - setVotes(votes.filter((vote) => vote.id !== id)); - toast.success('Vote supprimé', { duration: 2000, description: 'Le vote a bien été supprimé' }); - }) - .catch((error: Error) => { - toast.error('Erreur', { duration: 2000, description: 'Une erreur est survenue lors de la suppression du vote' }); - }); -} +type VoteData = { + data: Vote[]; + count: number; +}; -function VotesList({ page = 1 }: { page?: number }) { - const api = process.env.NEXT_PUBLIC_API_URL; +function ShowContent() { + const searchParams = useSearchParams(); + const router = useRouter(); + let page = searchParams.get('page') || 1; + if (typeof page === 'string') { + page = Number.parseInt(page); + } - const [votes, setVotes] = useState([]); const [maxPage, setMaxPage] = useState(1); - const [editingVote, setEditingVote] = useState(null); + const [votes, setVotes] = useState([]); + const [searchTerm, setSearchTerm] = useState(''); useEffect(() => { - const queryParams = new URLSearchParams({ - skip: `${page - 1}`, - take: '10', - }); + const urlApi = process.env.NEXT_PUBLIC_API_URL; - fetch(`${api}/polls?${queryParams}`, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${localStorage.getItem('access_token')}`, - }, - }) - .then(async (response) => { - return await response.json(); - }) - .then((data: Vote[]) => { - setVotes(data); - setMaxPage(Math.ceil(data.length / 10)); - }) - .catch((error: Error) => { - console.error(error); + setTimeout(() => { + const queryParams = new URLSearchParams({ + skip: `${page - 1}`, + take: '10', + search: searchTerm, }); - }, [page, api]); + + fetch(`${urlApi}/polls?${queryParams}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${localStorage.getItem('access_token')}`, + }, + }) + .then(async (response) => { + if (response.status === 403) { + router.push('/'); + } + return response.json(); + }) + .then((data: VoteData) => { + console.log(data.data); + setVotes(data.data); + setMaxPage(Math.ceil(data.count / 10)); + }) + .catch((error: Error) => { + console.error(error); + }); + }, 500); + }, [page, searchTerm, router]); + + const handleSearch = (event: React.ChangeEvent) => { + setSearchTerm(event.target.value); + }; return ( -
-
- -
-
-
- {votes?.length === 0 &&
Aucun vote
} - {votes?.length > 0 && ( - - - - Titre - Description - Date de début - Date de fin - Choix maximum - Actions - - - - {votes?.map((vote) => ( - - {vote.title} - {vote.description} - {new Date(vote.start_at).toLocaleDateString()} - {new Date(vote.end_at).toLocaleDateString()} - {vote.max_choices} - - - - - - - ))} - -
- )} -
- {editingVote !== null && ( - setEditingVote(null)}> - - - - - )} -
-
+ + + + Gestion des votes + + + +
+ +
+ + + + Titre + Description + Date de début + Date de fin + Choix maximum + Actions + + + + + +
+
+ + + +
+
); } export default function Votes() { return ( -
-
-
-
-

Votes

-
- Chargement...
}> - - +
+
+
+ + + + +
-
+ ); } diff --git a/apps/admin/app/ui/dashboard/materials/AddMaterial.tsx b/apps/admin/app/ui/dashboard/materials/AddMaterial.tsx index 261b7e75..08c37bfb 100644 --- a/apps/admin/app/ui/dashboard/materials/AddMaterial.tsx +++ b/apps/admin/app/ui/dashboard/materials/AddMaterial.tsx @@ -37,7 +37,6 @@ interface Props { function AddMaterial({ materials, setMaterials }: Props): JSX.Element { const [open, setOpen] = useState(false); const router = useRouter(); - console.log(materials); const formSchema = z.object({ name: z.string().min(1, { message: 'Le champ est requis' }), diff --git a/apps/admin/app/ui/dashboard/tournaments/matches/DeleteMatch.tsx b/apps/admin/app/ui/dashboard/tournaments/matches/DeleteMatch.tsx index d3c4d86d..db6755ba 100644 --- a/apps/admin/app/ui/dashboard/tournaments/matches/DeleteMatch.tsx +++ b/apps/admin/app/ui/dashboard/tournaments/matches/DeleteMatch.tsx @@ -7,7 +7,6 @@ import { DialogTitle, DialogTrigger, } from '@repo/ui/components/ui/dialog'; -import { useRouter } from 'next/navigation'; import { useState } from 'react'; interface DeleteMatchProps { @@ -18,7 +17,6 @@ interface DeleteMatchProps { function DeleteMatch(props: DeleteMatchProps) { const [open, setOpen] = useState(false); - const router = useRouter(); function deleteMatch() { const urlApi = process.env.NEXT_PUBLIC_API_URL; diff --git a/apps/admin/app/ui/dashboard/tournaments/matches/DeleteRound.tsx b/apps/admin/app/ui/dashboard/tournaments/matches/DeleteRound.tsx index 3f53dfba..fbb1016f 100644 --- a/apps/admin/app/ui/dashboard/tournaments/matches/DeleteRound.tsx +++ b/apps/admin/app/ui/dashboard/tournaments/matches/DeleteRound.tsx @@ -7,7 +7,6 @@ import { DialogTitle, DialogTrigger, } from '@repo/ui/components/ui/dialog'; -import { useRouter } from 'next/navigation'; import { useState } from 'react'; type Round = { @@ -23,7 +22,6 @@ interface DeleteRoundProps { function DeleteRound(props: DeleteRoundProps) { const [open, setOpen] = useState(false); - const router = useRouter(); function deleteRound(round: Round) { const urlApi = process.env.NEXT_PUBLIC_API_URL; diff --git a/apps/admin/app/ui/dashboard/votes/addVotes.tsx b/apps/admin/app/ui/dashboard/votes/AddVote.tsx similarity index 96% rename from apps/admin/app/ui/dashboard/votes/addVotes.tsx rename to apps/admin/app/ui/dashboard/votes/AddVote.tsx index 7affd838..34aa5ce8 100644 --- a/apps/admin/app/ui/dashboard/votes/addVotes.tsx +++ b/apps/admin/app/ui/dashboard/votes/AddVote.tsx @@ -34,7 +34,7 @@ function AddVote({ votes, setVotes }: Props) { const formSchema = z.object({ title: z.string().min(2, { message: 'Le titre doit contenir au moins 2 caractères' }), - description: z.string().min(2, { message: 'La description doit contenir au moins 2 caractères' }), + description: z.string().optional(), start_at: z.string(), end_at: z.string(), max_choices: z.coerce.number().min(1, { message: 'Le nombre de choix doit être supérieur à 0' }), @@ -85,10 +85,9 @@ function AddVote({ votes, setVotes }: Props) { return response.json(); }) .then((data: Vote) => { - if (data) { - toast.success('Vote créé', { duration: 2000, description: 'Le vote a été créé avec succès' }); - setVotes([...votes, data]); - } + toast.success('Vote créé', { duration: 2000, description: 'Le vote a été créé avec succès' }); + setVotes([...votes, data]); + form.reset(); }) .catch((error: Error) => { toast.error('Erreur', { duration: 2000, description: 'Une erreur est survenue' }); @@ -109,7 +108,7 @@ function AddVote({ votes, setVotes }: Props) { Création d'un vote - +
@@ -188,7 +187,7 @@ function AddVote({ votes, setVotes }: Props) { )} />
-
+
{fields.map((field, index) => (
>; + description: React.Dispatch>; + maxChoices: React.Dispatch>; + startAt: React.Dispatch>; + endAt: React.Dispatch>; +}; + +interface Props { + vote: Vote; + setter: Setter; +} + +function DeleteVote({ vote, setter }: Props) { + const [open, setOpen] = useState(false); + const router = useRouter(); + + function deletePoll() { + const urlApi = process.env.NEXT_PUBLIC_API_URL; + + fetch(`${urlApi}/polls/${vote.id}`, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${localStorage.getItem('access_token')}`, + }, + }) + .then((response) => { + if (response.status === 403) { + router.push('/'); + } + }) + .then(() => { + setter.title('Supprimé'); + setter.description(''); + setter.maxChoices(0); + setter.startAt(''); + setter.endAt(''); + toast.success('Le vote a bien été supprimé', { duration: 5000 }); + }); + } + + return ( + + + + + + + Suppression du vote + + Etes vous sûr de vouloir supprimer le vote ? +
+ + +
+
+
+
+
+ ); +} + +export default DeleteVote; diff --git a/apps/admin/app/ui/dashboard/votes/EditVote.tsx b/apps/admin/app/ui/dashboard/votes/EditVote.tsx new file mode 100644 index 00000000..93b2bee6 --- /dev/null +++ b/apps/admin/app/ui/dashboard/votes/EditVote.tsx @@ -0,0 +1,167 @@ +'use client'; + +import type { Vote } from '@/app/(dashboard)/dashboard/votes/page'; +import { zodResolver } from '@hookform/resolvers/zod'; +import { Button } from '@repo/ui/components/ui/button'; +import { + Dialog, + DialogContent, + DialogDescription, + DialogHeader, + DialogTitle, + DialogTrigger, +} from '@repo/ui/components/ui/dialog'; +import { Form, FormControl, FormField, FormItem, FormMessage } from '@repo/ui/components/ui/form'; +import { Input } from '@repo/ui/components/ui/input'; +import { Label } from '@repo/ui/components/ui/label'; +import { toast } from '@repo/ui/components/ui/sonner'; +import { MoreHorizontal } from 'lucide-react'; +import { useRouter } from 'next/navigation'; +import type React from 'react'; +import { useState } from 'react'; +import { useForm } from 'react-hook-form'; +import { z } from 'zod'; + +type Setter = { + title: React.Dispatch>; + description: React.Dispatch>; + maxChoices: React.Dispatch>; + startAt: React.Dispatch>; + endAt: React.Dispatch>; +}; + +interface Props { + vote: Vote; + setter: Setter; +} + +function EditVote({ vote, setter }: Props) { + const [open, setOpen] = useState(false); + const router = useRouter(); + + const formSchema = z.object({ + title: z.string().min(2, { message: 'Le titre doit contenir au moins 2 caractères' }), + description: z.string().optional(), + max_choices: z.coerce.number().min(1, { message: 'Le nombre de choix doit être supérieur à 0' }), + }); + + const form = useForm>({ + resolver: zodResolver(formSchema), + defaultValues: { + title: vote.title, + description: vote.description, + max_choices: vote.max_choices, + }, + }); + + async function submitEdit(values: z.infer) { + const urlApi = process.env.NEXT_PUBLIC_API_URL; + + fetch(`${urlApi}/polls/${vote.id}`, { + method: 'PATCH', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${localStorage.getItem('access_token')}`, + }, + body: JSON.stringify({ + title: values.title, + description: values.description, + max_choices: values.max_choices, + }), + }) + .then((response) => { + if (response.status === 403) { + router.push('/'); + } + return response.json(); + }) + .then((data: Vote) => { + setter.title(data.title); + setter.description(data.description); + setter.maxChoices(data.max_choices); + toast.success('Succès', { duration: 2000, description: 'Le vote a été modifié avec succès' }); + }) + .catch((error: Error) => { + console.error(error); + toast.error('Erreur', { duration: 2000, description: 'Une erreur est survenue' }); + }); + } + + return ( + + + + + + + Modification du vote + + + +
+
+ ( + + + + + + + + )} + /> +
+
+ ( + + + + + + + + )} + /> +
+
+ ( + + + + + + + + )} + /> +
+
+
+ + +
+ + +
+
+
+
+ ); +} + +export default EditVote; diff --git a/apps/admin/app/ui/dashboard/votes/VoteRow.tsx b/apps/admin/app/ui/dashboard/votes/VoteRow.tsx new file mode 100644 index 00000000..243c2279 --- /dev/null +++ b/apps/admin/app/ui/dashboard/votes/VoteRow.tsx @@ -0,0 +1,62 @@ +'use client'; + +import type { Vote } from '@/app/(dashboard)/dashboard/votes/page'; +import DeleteVote from '@/app/ui/dashboard/votes/DeleteVote'; +import EditVote from '@/app/ui/dashboard/votes/EditVote'; +import { TableCell, TableRow } from '@repo/ui/components/ui/table'; +import Link from 'next/link'; +import { useState } from 'react'; + +interface Props { + vote: Vote; +} + +function VoteRow({ vote }: Props) { + const [title, setTitle] = useState(vote.title); + const [description, setDescription] = useState(vote.description); + const [maxChoices, setMaxChoices] = useState(vote.max_choices); + const [startAt, setStartAt] = useState(vote.start_at); + const [endAt, setEndAt] = useState(vote.end_at); + + const setter = { + title: setTitle, + description: setDescription, + maxChoices: setMaxChoices, + startAt: setStartAt, + endAt: setEndAt, + }; + + const startDate = new Date(startAt); + const formattedStartDate = `${startDate.getFullYear()}/${String(startDate.getMonth() + 1).padStart(2, '0')}/${String( + startDate.getDate(), + ).padStart(2, '0')} ${String(startDate.getHours()).padStart(2, '0')}:${String(startDate.getMinutes()).padStart( + 2, + '0', + )}:${String(startDate.getSeconds()).padStart(2, '0')}`; + const endDate = new Date(endAt); + const formattedEndDate = `${endDate.getFullYear()}/${String(endDate.getMonth() + 1).padStart(2, '0')}/${String( + endDate.getDate(), + ).padStart(2, '0')} ${String(endDate.getHours()).padStart(2, '0')}:${String(endDate.getMinutes()).padStart( + 2, + '0', + )}:${String(endDate.getSeconds()).padStart(2, '0')}`; + return ( + + + {title} + + {description} + {formattedStartDate} + {formattedEndDate} + {maxChoices} + {title !== 'Supprimé' && ( + + + + + )} + + ); +} + +export default VoteRow; diff --git a/apps/admin/app/ui/dashboard/votes/VotesList.tsx b/apps/admin/app/ui/dashboard/votes/VotesList.tsx new file mode 100644 index 00000000..fb613d20 --- /dev/null +++ b/apps/admin/app/ui/dashboard/votes/VotesList.tsx @@ -0,0 +1,20 @@ +'use client'; + +import type { Vote } from '@/app/(dashboard)/dashboard/votes/page'; +import VoteRow from '@/app/ui/dashboard/votes/VoteRow'; + +interface Props { + votes: Vote[]; +} + +function ActivitiesList({ votes }: Props) { + return ( + <> + {votes.map((vote: Vote) => ( + + ))} + + ); +} + +export default ActivitiesList; diff --git a/apps/admin/app/ui/dashboard/votes/editVotes.tsx b/apps/admin/app/ui/dashboard/votes/editVotes.tsx deleted file mode 100644 index ef03a5e3..00000000 --- a/apps/admin/app/ui/dashboard/votes/editVotes.tsx +++ /dev/null @@ -1,141 +0,0 @@ -'use client'; - -import type { Vote } from '@/app/(dashboard)/dashboard/votes/page'; -import { zodResolver } from '@hookform/resolvers/zod'; -import { Button } from '@repo/ui/components/ui/button'; -import { Form, FormControl, FormField, FormItem, FormMessage } from '@repo/ui/components/ui/form'; -import { Input } from '@repo/ui/components/ui/input'; -import { Label } from '@repo/ui/components/ui/label'; -import { toast } from '@repo/ui/components/ui/sonner'; -import { useRouter } from 'next/navigation'; -import type React from 'react'; -import { useForm } from 'react-hook-form'; -import { z } from 'zod'; - -interface Props { - votes: Vote[]; - setVotes: React.Dispatch>; - id: number; - setEditingVote: React.Dispatch>; -} - -function EditVote({ votes, setVotes, id, setEditingVote }: Props) { - const router = useRouter(); - const editedVote: Vote | undefined = votes.find((vote: Vote) => vote.id === id); - - const formSchema = z.object({ - title: z.string().min(2, { message: 'Le titre doit contenir au moins 2 caractères' }), - description: z.string().min(2, { message: 'La description doit contenir au moins 2 caractères' }), - max_choices: z.number().min(1, { message: 'Le nombre de choix doit être supérieur à 0' }), - }); - - const form = useForm>({ - resolver: zodResolver(formSchema), - defaultValues: { - title: editedVote?.title || '', - description: editedVote?.description || '', - max_choices: editedVote?.max_choices || 1, - }, - }); - - async function callApi(values: z.infer, id: number) { - const urlApi = process.env.NEXT_PUBLIC_API_URL; - - fetch(`${urlApi}/polls/${id}`, { - method: 'PATCH', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${localStorage.getItem('access_token')}`, - }, - body: JSON.stringify({ - title: values.title, - description: values.description, - max_choices: Number(values.max_choices), - }), - }) - .then((response) => { - if (response.status === 403) { - router.push('/'); - } - return response.json(); - }) - .then((data: Vote) => { - setVotes(votes.map((vote) => (vote.id === id ? data : vote))); - setEditingVote(null); - toast.success('Succès', { duration: 2000, description: 'Le vote a été modifié avec succès' }); - }) - .catch((error: Error) => { - console.error(error); - toast.error('Erreur', { duration: 2000, description: 'Une erreur est survenue' }); - }); - } - - return ( -
- - callApi(values, id), - )} - method="POST" - > -
-
- ( - - - - - - - - )} - /> -
-
- ( - - - - - - - - )} - /> -
-
- ( - - - - - - - - )} - /> -
-
-
- - -
-
- - ); -} - -export default EditVote; diff --git a/apps/admin/package.json b/apps/admin/package.json index 56975231..d5020b12 100644 --- a/apps/admin/package.json +++ b/apps/admin/package.json @@ -26,7 +26,7 @@ "devDependencies": { "@repo/biome-config": "workspace:*", "@repo/typescript-config": "workspace:*", - "@types/node": "^20.13.0", + "@types/node": "^20.14.1", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "autoprefixer": "^10.4.19", diff --git a/apps/api/package.json b/apps/api/package.json index 49fb6cae..e2fe0077 100644 --- a/apps/api/package.json +++ b/apps/api/package.json @@ -21,18 +21,18 @@ "@hono/zod-validator": "^0.2.2", "@repo/types": "workspace:*", "@supabase/supabase-js": "^2.43.4", - "hono": "^4.4.2", - "stripe": "^15.8.0", + "hono": "^4.4.3", + "stripe": "^15.9.0", "zod": "^3.23.8", "zod-validation-error": "^3.3.0" }, "devDependencies": { "@repo/biome-config": "workspace:*", "@repo/typescript-config": "workspace:*", - "@types/node": "^20.13.0", + "@types/node": "^20.14.1", "@types/swagger-ui-dist": "^3.30.4", "supabase": "^1.172.2", - "tsx": "^4.11.0", + "tsx": "^4.11.2", "typescript": "^5.4.5", "vitest": "^1.6.0" } diff --git a/apps/api/src/handlers/votes.ts b/apps/api/src/handlers/votes.ts index f0df8d95..2acd88f8 100644 --- a/apps/api/src/handlers/votes.ts +++ b/apps/api/src/handlers/votes.ts @@ -31,7 +31,7 @@ polls.openapi(getAllPolls, async (c) => { query.range(from, to); } - const { data, error } = await query; + const { count, data, error } = await query; if (error) { return c.json({ error: error.message }, 500); @@ -42,7 +42,12 @@ polls.openapi(getAllPolls, async (c) => { results: poll.results.map((option) => ({ ...option, votes: option.votes.length })), })); - return c.json(format, 200); + const responseData = { + data: format || [], + count: count || 0, + }; + + return c.json(responseData, 200); }); polls.openapi(getOnePoll, async (c) => { diff --git a/apps/api/src/routes/votes.ts b/apps/api/src/routes/votes.ts index 34249a18..a2a5539a 100644 --- a/apps/api/src/routes/votes.ts +++ b/apps/api/src/routes/votes.ts @@ -17,11 +17,11 @@ export const pollsSchema = z.object({ export const createPollSchema = z.object({ title: z.string().max(50), - description: z.string().max(255).nullable(), + description: z.string().max(255).optional(), start_at: z.string().datetime(), end_at: z.string().datetime(), max_choices: z.number().min(1), - assembly: z.number().min(1).nullable(), + assembly: z.number().min(1).optional(), options: z.array(z.object({ content: z.string() })), }); @@ -83,7 +83,10 @@ export const getAllPolls = createRoute({ description: 'Successful response', content: { 'application/json': { - schema: z.array(pollsSchema), + schema: z.object({ + data: z.array(pollsSchema), + count: z.number(), + }), }, }, }, diff --git a/apps/api/test/votes.test.ts b/apps/api/test/votes.test.ts index 148f90e1..d28b824a 100644 --- a/apps/api/test/votes.test.ts +++ b/apps/api/test/votes.test.ts @@ -10,6 +10,7 @@ describe('Votes tests', () => { let id_auth: string; let jwt: string; let id_polls: number; + let option_id: number; beforeAll(async () => { const res = await app.request(`${path}/auth/signup`, { @@ -57,7 +58,6 @@ describe('Votes tests', () => { start_at: new Date().toISOString(), end_at: new Date(new Date().getTime() + 24 * 60 * 60 * 1000).toISOString(), max_choices: 2, - assembly: null, options: [{ content: 'Option test' }, { content: 'Option test 2' }, { content: 'Option test 3' }], }), }); @@ -77,6 +77,19 @@ describe('Votes tests', () => { expect(res.status).toBe(200); }); + test('Get poll by id', async () => { + const res = await app.request(`${path}/polls/${id_polls}`, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + Authorization: `Bearer ${jwt}`, + }, + }); + expect(res.status).toBe(200); + const result: { results: [{ id: number; votes: number; content: string }] } = await res.json(); + option_id = result.results[0].id; + }); + test('Vote to poll', async () => { const res = await app.request(`${path}/polls/${id_polls}/vote`, { method: 'POST', @@ -85,7 +98,7 @@ describe('Votes tests', () => { Authorization: `Bearer ${jwt}`, }, body: JSON.stringify({ - options: [1, 2], + options: [option_id], }), }); expect(res.status).toBe(201); @@ -99,7 +112,7 @@ describe('Votes tests', () => { Authorization: `Bearer ${jwt}`, }, body: JSON.stringify({ - options: [1, 3], + options: [option_id], }), }); expect(res.status).toBe(400); diff --git a/apps/client/app/(auth)/account/page.tsx b/apps/client/app/(auth)/account/page.tsx index 93fcabe2..61312854 100644 --- a/apps/client/app/(auth)/account/page.tsx +++ b/apps/client/app/(auth)/account/page.tsx @@ -35,7 +35,7 @@ async function updateUserInformation(id: number, username: string, first_name: s } localStorage.setItem('user', JSON.stringify(data)); }) - .catch((error: Error) => console.log(error)); + .catch((error: Error) => console.error(error)); } export default function UserAccount() { diff --git a/apps/client/app/ui/NavBar.tsx b/apps/client/app/ui/NavBar.tsx index 468491b5..a540ac53 100644 --- a/apps/client/app/ui/NavBar.tsx +++ b/apps/client/app/ui/NavBar.tsx @@ -33,7 +33,7 @@ function LogoutUser() { window.location.href = '/'; } }) - .catch((error: Error) => console.log(error)); + .catch((error: Error) => console.error(error)); } export const NavBar: React.FC = ({ links }) => { diff --git a/apps/client/app/ui/auth/LoginForm.tsx b/apps/client/app/ui/auth/LoginForm.tsx index cba51407..e276bf32 100644 --- a/apps/client/app/ui/auth/LoginForm.tsx +++ b/apps/client/app/ui/auth/LoginForm.tsx @@ -48,7 +48,7 @@ export default function LoginForm(): JSX.Element { } }) .catch((error: Error) => { - console.log(error); + console.error(error); }); } diff --git a/apps/client/app/ui/auth/SignupForm.tsx b/apps/client/app/ui/auth/SignupForm.tsx index 73d28226..bdbb63b8 100644 --- a/apps/client/app/ui/auth/SignupForm.tsx +++ b/apps/client/app/ui/auth/SignupForm.tsx @@ -56,7 +56,7 @@ export default function SignupForm(): JSX.Element { .then(() => { router.push('/login'); }) - .catch((error: Error) => console.log(error)); + .catch((error: Error) => console.error(error)); } return ( diff --git a/apps/client/package.json b/apps/client/package.json index 1ddfc4ed..63656aa8 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -25,7 +25,7 @@ "devDependencies": { "@repo/biome-config": "workspace:*", "@repo/typescript-config": "workspace:*", - "@types/node": "^20.13.0", + "@types/node": "^20.14.1", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "autoprefixer": "^10.4.19", diff --git a/packages/ui/package.json b/packages/ui/package.json index b8e35cb0..3d7ec9e0 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -21,7 +21,7 @@ "@repo/biome-config": "workspace:*", "@repo/typescript-config": "workspace:*", "@turbo/gen": "^1.13.3", - "@types/node": "^20.13.0", + "@types/node": "^20.14.1", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "autoprefixer": "^10.4.19", @@ -39,6 +39,7 @@ "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-popover": "^1.0.7", + "@radix-ui/react-progress": "^1.0.3", "@radix-ui/react-radio-group": "^1.1.3", "@radix-ui/react-scroll-area": "^1.0.5", "@radix-ui/react-select": "^2.0.0", diff --git a/packages/ui/src/components/ui/progress.tsx b/packages/ui/src/components/ui/progress.tsx new file mode 100644 index 00000000..f6be774a --- /dev/null +++ b/packages/ui/src/components/ui/progress.tsx @@ -0,0 +1,25 @@ +'use client'; + +import * as ProgressPrimitive from '@radix-ui/react-progress'; +import * as React from 'react'; + +import { cn } from '@repo/ui/lib/utils'; + +const Progress = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, value, ...props }, ref) => ( + + + +)); +Progress.displayName = ProgressPrimitive.Root.displayName; + +export { Progress }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c76cfa64..36216675 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -64,8 +64,8 @@ importers: specifier: workspace:* version: link:../../packages/typescript-config '@types/node': - specifier: ^20.13.0 - version: 20.13.0 + specifier: ^20.14.1 + version: 20.14.1 '@types/react': specifier: ^18.3.3 version: 18.3.3 @@ -80,7 +80,7 @@ importers: version: 8.4.38 tailwindcss: specifier: ^3.4.3 - version: 3.4.3(ts-node@10.9.2(@types/node@20.13.0)(typescript@5.4.5)) + version: 3.4.3(ts-node@10.9.2(@types/node@20.14.1)(typescript@5.4.5)) typescript: specifier: ^5.4.5 version: 5.4.5 @@ -92,13 +92,13 @@ importers: version: 1.11.2 '@hono/swagger-ui': specifier: ^0.2.2 - version: 0.2.2(hono@4.4.2) + version: 0.2.2(hono@4.4.3) '@hono/zod-openapi': specifier: 0.14.1 - version: 0.14.1(hono@4.4.2)(zod@3.23.8) + version: 0.14.1(hono@4.4.3)(zod@3.23.8) '@hono/zod-validator': specifier: ^0.2.2 - version: 0.2.2(hono@4.4.2)(zod@3.23.8) + version: 0.2.2(hono@4.4.3)(zod@3.23.8) '@repo/types': specifier: workspace:* version: link:../../packages/types @@ -106,11 +106,11 @@ importers: specifier: ^2.43.4 version: 2.43.4 hono: - specifier: ^4.4.2 - version: 4.4.2 + specifier: ^4.4.3 + version: 4.4.3 stripe: - specifier: ^15.8.0 - version: 15.8.0 + specifier: ^15.9.0 + version: 15.9.0 zod: specifier: ^3.23.8 version: 3.23.8 @@ -125,8 +125,8 @@ importers: specifier: workspace:* version: link:../../packages/typescript-config '@types/node': - specifier: ^20.13.0 - version: 20.13.0 + specifier: ^20.14.1 + version: 20.14.1 '@types/swagger-ui-dist': specifier: ^3.30.4 version: 3.30.4 @@ -134,14 +134,14 @@ importers: specifier: ^1.172.2 version: 1.172.2 tsx: - specifier: ^4.11.0 - version: 4.11.0 + specifier: ^4.11.2 + version: 4.11.2 typescript: specifier: ^5.4.5 version: 5.4.5 vitest: specifier: ^1.6.0 - version: 1.6.0(@types/node@20.13.0) + version: 1.6.0(@types/node@20.14.1) apps/client: dependencies: @@ -180,8 +180,8 @@ importers: specifier: workspace:* version: link:../../packages/typescript-config '@types/node': - specifier: ^20.13.0 - version: 20.13.0 + specifier: ^20.14.1 + version: 20.14.1 '@types/react': specifier: ^18.3.3 version: 18.3.3 @@ -196,7 +196,7 @@ importers: version: 8.4.38 tailwindcss: specifier: ^3.4.3 - version: 3.4.3(ts-node@10.9.2(@types/node@20.13.0)(typescript@5.4.5)) + version: 3.4.3(ts-node@10.9.2(@types/node@20.14.1)(typescript@5.4.5)) typescript: specifier: ^5.4.5 version: 5.4.5 @@ -244,6 +244,9 @@ importers: '@radix-ui/react-popover': specifier: ^1.0.7 version: 1.0.7(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@radix-ui/react-progress': + specifier: ^1.0.3 + version: 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-radio-group': specifier: ^1.1.3 version: 1.1.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -297,7 +300,7 @@ importers: version: 2.3.0 tailwindcss-animate: specifier: ^1.0.7 - version: 1.0.7(tailwindcss@3.4.3(ts-node@10.9.2(@types/node@20.13.0)(typescript@5.4.5))) + version: 1.0.7(tailwindcss@3.4.3(ts-node@10.9.2(@types/node@20.14.1)(typescript@5.4.5))) zod: specifier: ^3.23.8 version: 3.23.8 @@ -310,10 +313,10 @@ importers: version: link:../typescript-config '@turbo/gen': specifier: ^1.13.3 - version: 1.13.3(@types/node@20.13.0)(typescript@5.4.5) + version: 1.13.3(@types/node@20.14.1)(typescript@5.4.5) '@types/node': - specifier: ^20.13.0 - version: 20.13.0 + specifier: ^20.14.1 + version: 20.14.1 '@types/react': specifier: ^18.3.3 version: 18.3.3 @@ -331,7 +334,7 @@ importers: version: 18.3.1 tailwindcss: specifier: ^3.4.3 - version: 3.4.3(ts-node@10.9.2(@types/node@20.13.0)(typescript@5.4.5)) + version: 3.4.3(ts-node@10.9.2(@types/node@20.14.1)(typescript@5.4.5)) typescript: specifier: ^5.4.5 version: 5.4.5 @@ -1131,6 +1134,19 @@ packages: '@types/react-dom': optional: true + '@radix-ui/react-progress@1.0.3': + resolution: {integrity: sha512-5G6Om/tYSxjSeEdrb1VfKkfZfn/1IlPWd731h2RfPuSbIfNUgfqAwbKfJCg/PP6nuUCTrYzalwHSpSinoWoCag==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + '@radix-ui/react-radio-group@1.1.3': resolution: {integrity: sha512-x+yELayyefNeKeTx4fjK6j99Fs6c4qKm3aY38G3swQVTN6xMpsrbigC0uHs2L//g8q4qR7qOcww8430jJmi2ag==} peerDependencies: @@ -1923,8 +1939,8 @@ packages: '@types/minimatch@5.1.2': resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==} - '@types/node@20.13.0': - resolution: {integrity: sha512-FM6AOb3khNkNIXPnHFDYaHerSv8uN22C91z098AnGccVu+Pcdhi+pNUFDi0iLmPIsVE0JBD0KVS7mzUYt4nRzQ==} + '@types/node@20.14.1': + resolution: {integrity: sha512-T2MzSGEu+ysB/FkWfqmhV3PLyQlowdptmmgD20C6QxsS8Fmv5SjpZ1ayXaEC0S21/h5UJ9iA6W/5vSNU5l00OA==} '@types/phoenix@1.6.4': resolution: {integrity: sha512-B34A7uot1Cv0XtaHRYDATltAdKx0BvVKNgYNqE4WjtPUa4VQJM7kxeXcVKaH+KS+kCmZ+6w+QaUdcljiheiBJA==} @@ -2106,8 +2122,8 @@ packages: resolution: {integrity: sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==} engines: {node: '>= 6'} - caniuse-lite@1.0.30001626: - resolution: {integrity: sha512-JRW7kAH8PFJzoPCJhLSHgDgKg5348hsQ68aqb+slnzuB5QFERv846oA/mRChmlLAOdEDeOkRn3ynb1gSFnjt3w==} + caniuse-lite@1.0.30001627: + resolution: {integrity: sha512-4zgNiB8nTyV/tHhwZrFs88ryjls/lHiqFhrxCW4qSTeuRByBVnPYpDInchOIySWknznucaf31Z4KYqjfbrecVw==} chai@4.4.1: resolution: {integrity: sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==} @@ -2315,8 +2331,8 @@ packages: eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - electron-to-chromium@1.4.788: - resolution: {integrity: sha512-ubp5+Ev/VV8KuRoWnfP2QF2Bg+O2ZFdb49DiiNbz2VmgkIqrnyYaqIOqj8A6K/3p1xV0QcU5hBQ1+BmB6ot1OA==} + electron-to-chromium@1.4.789: + resolution: {integrity: sha512-0VbyiaXoT++Fi2vHGo2ThOeS6X3vgRCWrjPeO2FeIAWL6ItiSJ9BqlH8LfCXe3X1IdcG+S0iLoNaxQWhfZoGzQ==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -2515,8 +2531,8 @@ packages: header-case@1.0.1: resolution: {integrity: sha512-i0q9mkOeSuhXw6bGgiQCCBgY/jlZuV/7dZXyZ9c6LcBrqwvT8eT719E9uxE5LiZftdl+z81Ugbg/VvXV4OJOeQ==} - hono@4.4.2: - resolution: {integrity: sha512-bRhZ+BM9r04lRN2i9wiZ18yQNbZxHsmmRIItoAb43nRkHnIDsFhFh4mJ0seEs06FvenibpAgSVNHQ8ZzcDQx2A==} + hono@4.4.3: + resolution: {integrity: sha512-G7rTruKzrHXPz1KB4B50deKydPA9+aeei+WC1hikP0abN9N+a6yORuweageaqWocYfYNkpoqA5ezGV2mzQasvw==} engines: {node: '>=16.0.0'} http-proxy-agent@7.0.2: @@ -2645,8 +2661,8 @@ packages: isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - jackspeak@3.1.2: - resolution: {integrity: sha512-kWmLKn2tRtfYMF/BakihVVRzBKOxz4gJMiL2Rj91WnAB5TPZumSH99R/Yf1qE1u4uRimvCSJfm6hnxohXeEXjQ==} + jackspeak@3.2.3: + resolution: {integrity: sha512-htOzIMPbpLid/Gq9/zaz9SfExABxqRe1sSCdxntlO/aMD6u0issZQiY25n2GKQUtJ02j7z5sfptlAOMpWWOmvw==} engines: {node: '>=14'} jiti@1.21.0: @@ -3297,8 +3313,8 @@ packages: strip-literal@2.1.0: resolution: {integrity: sha512-Op+UycaUt/8FbN/Z2TWPBLge3jWrP3xj10f3fnYxf052bKuS3EKs1ZQcVGjnEMdsNVAM+plXRdmjrZ/KgG3Skw==} - stripe@15.8.0: - resolution: {integrity: sha512-7eEPMgehd1I16cXeP7Rcn/JKkPWIadB9vGIeE+vbCzQXaY5R95AoNmkZx0vmlu1H4QIDs7j1pYIKPRm9Dr4LKg==} + stripe@15.9.0: + resolution: {integrity: sha512-C7NAK17wGr6DOybxThO0n1zVcqSShWno7kx/UcJorQ/nWZ5KnfHQ38DUTb9NgizC8TCII3BPIhqq6Zc/1aUkrg==} engines: {node: '>=12.*'} styled-jsx@5.1.1: @@ -3420,8 +3436,8 @@ packages: tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} - tsx@4.11.0: - resolution: {integrity: sha512-vzGGELOgAupsNVssAmZjbUDfdm/pWP4R+Kg8TVdsonxbXk0bEpE1qh0yV6/QxUVXaVlNemgcPajGdJJ82n3stg==} + tsx@4.11.2: + resolution: {integrity: sha512-V5DL5v1BuItjsQ2FN9+4OjR7n5cr8hSgN+VGmm/fd2/0cgQdBIWHcQ3bFYm/5ZTmyxkTDBUIaRuW2divgfPe0A==} engines: {node: '>=18.0.0'} hasBin: true @@ -3847,25 +3863,25 @@ snapshots: '@hono/node-server@1.11.2': {} - '@hono/swagger-ui@0.2.2(hono@4.4.2)': + '@hono/swagger-ui@0.2.2(hono@4.4.3)': dependencies: - hono: 4.4.2 + hono: 4.4.3 - '@hono/zod-openapi@0.14.1(hono@4.4.2)(zod@3.23.8)': + '@hono/zod-openapi@0.14.1(hono@4.4.3)(zod@3.23.8)': dependencies: '@asteasolutions/zod-to-openapi': 7.0.0(zod@3.23.8) - '@hono/zod-validator': 0.2.1(hono@4.4.2)(zod@3.23.8) - hono: 4.4.2 + '@hono/zod-validator': 0.2.1(hono@4.4.3)(zod@3.23.8) + hono: 4.4.3 zod: 3.23.8 - '@hono/zod-validator@0.2.1(hono@4.4.2)(zod@3.23.8)': + '@hono/zod-validator@0.2.1(hono@4.4.3)(zod@3.23.8)': dependencies: - hono: 4.4.2 + hono: 4.4.3 zod: 3.23.8 - '@hono/zod-validator@0.2.2(hono@4.4.2)(zod@3.23.8)': + '@hono/zod-validator@0.2.2(hono@4.4.3)(zod@3.23.8)': dependencies: - hono: 4.4.2 + hono: 4.4.3 zod: 3.23.8 '@hookform/resolvers@3.4.2(react-hook-form@7.51.5(react@18.3.1))': @@ -4355,6 +4371,17 @@ snapshots: '@types/react': 18.3.3 '@types/react-dom': 18.3.0 + '@radix-ui/react-progress@1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@babel/runtime': 7.24.6 + '@radix-ui/react-context': 1.0.1(@types/react@18.3.3)(react@18.3.1) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optionalDependencies: + '@types/react': 18.3.3 + '@types/react-dom': 18.3.0 + '@radix-ui/react-radio-group@1.1.3(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: '@babel/runtime': 7.24.6 @@ -5513,7 +5540,7 @@ snapshots: '@tsconfig/node16@1.0.4': {} - '@turbo/gen@1.13.3(@types/node@20.13.0)(typescript@5.4.5)': + '@turbo/gen@1.13.3(@types/node@20.14.1)(typescript@5.4.5)': dependencies: '@turbo/workspaces': 1.13.3 chalk: 2.4.2 @@ -5523,7 +5550,7 @@ snapshots: minimatch: 9.0.4 node-plop: 0.26.3 proxy-agent: 6.4.0 - ts-node: 10.9.2(@types/node@20.13.0)(typescript@5.4.5) + ts-node: 10.9.2(@types/node@20.14.1)(typescript@5.4.5) update-check: 1.5.4 validate-npm-package-name: 5.0.1 transitivePeerDependencies: @@ -5553,7 +5580,7 @@ snapshots: '@types/glob@7.2.0': dependencies: '@types/minimatch': 5.1.2 - '@types/node': 20.13.0 + '@types/node': 20.14.1 '@types/inquirer@6.5.0': dependencies: @@ -5562,7 +5589,7 @@ snapshots: '@types/minimatch@5.1.2': {} - '@types/node@20.13.0': + '@types/node@20.14.1': dependencies: undici-types: 5.26.5 @@ -5583,13 +5610,13 @@ snapshots: '@types/through@0.0.33': dependencies: - '@types/node': 20.13.0 + '@types/node': 20.14.1 '@types/tinycolor2@1.4.6': {} '@types/ws@8.5.10': dependencies: - '@types/node': 20.13.0 + '@types/node': 20.14.1 '@vitest/expect@1.6.0': dependencies: @@ -5683,7 +5710,7 @@ snapshots: autoprefixer@10.4.19(postcss@8.4.38): dependencies: browserslist: 4.23.0 - caniuse-lite: 1.0.30001626 + caniuse-lite: 1.0.30001627 fraction.js: 4.3.7 normalize-range: 0.1.2 picocolors: 1.0.1 @@ -5726,8 +5753,8 @@ snapshots: browserslist@4.23.0: dependencies: - caniuse-lite: 1.0.30001626 - electron-to-chromium: 1.4.788 + caniuse-lite: 1.0.30001627 + electron-to-chromium: 1.4.789 node-releases: 2.0.14 update-browserslist-db: 1.0.16(browserslist@4.23.0) @@ -5757,7 +5784,7 @@ snapshots: camelcase-css@2.0.1: {} - caniuse-lite@1.0.30001626: {} + caniuse-lite@1.0.30001627: {} chai@4.4.1: dependencies: @@ -5974,7 +6001,7 @@ snapshots: eastasianwidth@0.2.0: {} - electron-to-chromium@1.4.788: {} + electron-to-chromium@1.4.789: {} emoji-regex@8.0.0: {} @@ -6159,7 +6186,7 @@ snapshots: glob@10.4.1: dependencies: foreground-child: 3.1.1 - jackspeak: 3.1.2 + jackspeak: 3.2.3 minimatch: 9.0.4 minipass: 7.1.2 path-scurry: 1.11.1 @@ -6225,7 +6252,7 @@ snapshots: no-case: 2.3.2 upper-case: 1.1.3 - hono@4.4.2: {} + hono@4.4.3: {} http-proxy-agent@7.0.2: dependencies: @@ -6360,7 +6387,7 @@ snapshots: isexe@2.0.0: {} - jackspeak@3.1.2: + jackspeak@3.2.3: dependencies: '@isaacs/cliui': 8.0.2 optionalDependencies: @@ -6505,7 +6532,7 @@ snapshots: '@next/env': 14.2.3 '@swc/helpers': 0.5.5 busboy: 1.6.0 - caniuse-lite: 1.0.30001626 + caniuse-lite: 1.0.30001627 graceful-fs: 4.2.11 postcss: 8.4.31 react: 18.3.1 @@ -6698,13 +6725,13 @@ snapshots: camelcase-css: 2.0.1 postcss: 8.4.38 - postcss-load-config@4.0.2(postcss@8.4.38)(ts-node@10.9.2(@types/node@20.13.0)(typescript@5.4.5)): + postcss-load-config@4.0.2(postcss@8.4.38)(ts-node@10.9.2(@types/node@20.14.1)(typescript@5.4.5)): dependencies: lilconfig: 3.1.1 yaml: 2.4.3 optionalDependencies: postcss: 8.4.38 - ts-node: 10.9.2(@types/node@20.13.0)(typescript@5.4.5) + ts-node: 10.9.2(@types/node@20.14.1)(typescript@5.4.5) postcss-nested@6.0.1(postcss@8.4.38): dependencies: @@ -7086,9 +7113,9 @@ snapshots: dependencies: js-tokens: 9.0.0 - stripe@15.8.0: + stripe@15.9.0: dependencies: - '@types/node': 20.13.0 + '@types/node': 20.14.1 qs: 6.12.1 styled-jsx@5.1.1(react@18.3.1): @@ -7134,11 +7161,11 @@ snapshots: dependencies: '@babel/runtime': 7.24.6 - tailwindcss-animate@1.0.7(tailwindcss@3.4.3(ts-node@10.9.2(@types/node@20.13.0)(typescript@5.4.5))): + tailwindcss-animate@1.0.7(tailwindcss@3.4.3(ts-node@10.9.2(@types/node@20.14.1)(typescript@5.4.5))): dependencies: - tailwindcss: 3.4.3(ts-node@10.9.2(@types/node@20.13.0)(typescript@5.4.5)) + tailwindcss: 3.4.3(ts-node@10.9.2(@types/node@20.14.1)(typescript@5.4.5)) - tailwindcss@3.4.3(ts-node@10.9.2(@types/node@20.13.0)(typescript@5.4.5)): + tailwindcss@3.4.3(ts-node@10.9.2(@types/node@20.14.1)(typescript@5.4.5)): dependencies: '@alloc/quick-lru': 5.2.0 arg: 5.0.2 @@ -7157,7 +7184,7 @@ snapshots: postcss: 8.4.38 postcss-import: 15.1.0(postcss@8.4.38) postcss-js: 4.0.1(postcss@8.4.38) - postcss-load-config: 4.0.2(postcss@8.4.38)(ts-node@10.9.2(@types/node@20.13.0)(typescript@5.4.5)) + postcss-load-config: 4.0.2(postcss@8.4.38)(ts-node@10.9.2(@types/node@20.14.1)(typescript@5.4.5)) postcss-nested: 6.0.1(postcss@8.4.38) postcss-selector-parser: 6.1.0 resolve: 1.22.8 @@ -7214,14 +7241,14 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-node@10.9.2(@types/node@20.13.0)(typescript@5.4.5): + ts-node@10.9.2(@types/node@20.14.1)(typescript@5.4.5): dependencies: '@cspotcode/source-map-support': 0.8.1 '@tsconfig/node10': 1.0.11 '@tsconfig/node12': 1.0.11 '@tsconfig/node14': 1.0.3 '@tsconfig/node16': 1.0.4 - '@types/node': 20.13.0 + '@types/node': 20.14.1 acorn: 8.11.3 acorn-walk: 8.3.2 arg: 4.1.3 @@ -7236,7 +7263,7 @@ snapshots: tslib@2.6.2: {} - tsx@4.11.0: + tsx@4.11.2: dependencies: esbuild: 0.20.2 get-tsconfig: 4.7.5 @@ -7323,13 +7350,13 @@ snapshots: validate-npm-package-name@5.0.1: {} - vite-node@1.6.0(@types/node@20.13.0): + vite-node@1.6.0(@types/node@20.14.1): dependencies: cac: 6.7.14 debug: 4.3.5 pathe: 1.1.2 picocolors: 1.0.1 - vite: 5.2.12(@types/node@20.13.0) + vite: 5.2.12(@types/node@20.14.1) transitivePeerDependencies: - '@types/node' - less @@ -7340,16 +7367,16 @@ snapshots: - supports-color - terser - vite@5.2.12(@types/node@20.13.0): + vite@5.2.12(@types/node@20.14.1): dependencies: esbuild: 0.20.2 postcss: 8.4.38 rollup: 4.18.0 optionalDependencies: - '@types/node': 20.13.0 + '@types/node': 20.14.1 fsevents: 2.3.3 - vitest@1.6.0(@types/node@20.13.0): + vitest@1.6.0(@types/node@20.14.1): dependencies: '@vitest/expect': 1.6.0 '@vitest/runner': 1.6.0 @@ -7368,11 +7395,11 @@ snapshots: strip-literal: 2.1.0 tinybench: 2.8.0 tinypool: 0.8.4 - vite: 5.2.12(@types/node@20.13.0) - vite-node: 1.6.0(@types/node@20.13.0) + vite: 5.2.12(@types/node@20.14.1) + vite-node: 1.6.0(@types/node@20.14.1) why-is-node-running: 2.2.2 optionalDependencies: - '@types/node': 20.13.0 + '@types/node': 20.14.1 transitivePeerDependencies: - less - lightningcss