diff --git a/src/app/api/events/[event]/participants/edit-participant/route.ts b/src/app/api/events/[event]/participants/edit-participant/route.ts new file mode 100644 index 0000000..a52ad4e --- /dev/null +++ b/src/app/api/events/[event]/participants/edit-participant/route.ts @@ -0,0 +1,29 @@ +import { prisma } from "@/prisma" +import { NextRequest, NextResponse } from "next/server" + +export async function POST(request: NextRequest) { + // TODO: validar com Zod + const { + id, + name, + email, + github, + }: { id: string; name: string; email: string; github?: string } = + await request.json() + + const participant = await prisma.user.update({ + where: { + id, + }, + data: { + name, + email, + github: !!github ? github : undefined, + }, + }) + + return NextResponse.json({ + data: participant, + status: 201, + }) +} diff --git a/src/app/events/[event]/error.tsx b/src/app/events/[event]/error.tsx new file mode 100644 index 0000000..7b44d48 --- /dev/null +++ b/src/app/events/[event]/error.tsx @@ -0,0 +1 @@ +"use client" diff --git a/src/app/events/[event]/page.tsx b/src/app/events/[event]/page.tsx index 7f6c5c8..fdf7bd6 100644 --- a/src/app/events/[event]/page.tsx +++ b/src/app/events/[event]/page.tsx @@ -1,28 +1,30 @@ "use client" -import { ChangeEvent, useState } from "react" +import { ChangeEvent, useCallback, useState } from "react" import { useRouter } from "next/navigation" import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query" import { TableParticipants } from "./table-participants" import { Button, Checkbox, + EditParticipantModal, Input, Modal, ModalTrigger, RegisterParticipantModal, } from "@/components" -import { Filter as FilterIcon } from "lucide-react" +import { Filter as FilterIcon, Edit as EditIcon } from "lucide-react" import { getParticipants, setSelectedParticipants, checkParticipant, registerParticipant, removeParticipant, + editParticipant, } from "./participants/data-participants" import { upload } from "./participants/upload" import { getRandomInteger } from "@/lib/get-random-integer" -import { RegisterParticipant } from "@/shared/types" +import { EditParticipant, RegisterParticipant } from "@/shared/types" type EventProps = { params: { @@ -82,10 +84,43 @@ export default function Event({ params }: EventProps) { }, }) + const editParticipantMutation = useMutation({ + mutationFn: editParticipant, + onSuccess: () => { + queryClient.invalidateQueries({ queryKey: ["participants", { event }] }) + }, + }) + const handleRegisterParticipant = async (data: RegisterParticipant) => { registerParticipantMutation.mutate({ data, event }) } + const handleEditParticipant = useCallback( + async (data: EditParticipant) => { + console.log("edit participant:", data) + await editParticipantMutation.mutateAsync({ data, event }) + }, + [editParticipantMutation, event], + ) + + const editParticipantModal = useCallback( + (participant: EditParticipant) => ( + + ), + [ + editParticipantMutation.error, + editParticipantMutation.isLoading, + editParticipantMutation.isError, + handleEditParticipant, + ], + ) + const handleCheckParticipant = ({ id, checked, @@ -223,11 +258,48 @@ export default function Event({ params }: EventProps) { ) } + +type EditParticipantModalContainerProps = { + participant: EditParticipant + onEditParticipant: (data: EditParticipant) => Promise + isLoading: boolean + isError: boolean + error: unknown +} +function EditParticipantModalContainer({ + participant, + onEditParticipant, + isLoading, + isError, + error, +}: EditParticipantModalContainerProps) { + const [openEditModal, setOpenEditModal] = useState(false) + + const handleEditParticipant = async (participant: EditParticipant) => { + await onEditParticipant(participant) + setOpenEditModal(false) + } + + return ( + + + + + + + ) +} diff --git a/src/app/events/[event]/participants/[participantId]/edit/page.tsx b/src/app/events/[event]/participants/[participantId]/edit/page.tsx deleted file mode 100644 index 78005c4..0000000 --- a/src/app/events/[event]/participants/[participantId]/edit/page.tsx +++ /dev/null @@ -1,12 +0,0 @@ -type EditParticipantProps = { - params: { - participantId: string - } -} - -export default function EditParticipant({ params }: EditParticipantProps) { - console.log('params:', params) - return ( - Edit participant: {params.participantId} - ) -} diff --git a/src/app/events/[event]/participants/data-participants.ts b/src/app/events/[event]/participants/data-participants.ts index fe35301..c9c3ac8 100644 --- a/src/app/events/[event]/participants/data-participants.ts +++ b/src/app/events/[event]/participants/data-participants.ts @@ -4,6 +4,7 @@ import { type Participant, type ParticipantInGroup, type RegisterParticipant, + EditParticipant, } from "@/shared/types" export async function getParticipants(event: string): Promise { @@ -91,6 +92,34 @@ export async function registerParticipant({ return { data: dataOrError } } +type EditParticipantInput = { + event: string + data: EditParticipant +} + +export async function editParticipant({ event, data }: EditParticipantInput) { + const result = await fetch( + `/api/events/${event}/participants/edit-participant`, + { + method: "POST", + body: JSON.stringify(data), + headers: { + "content-type": "application/json", + }, + }, + ) + + const dataOrError = await result.json() + + if (!result.ok) { + const { message } = dataOrError + // TODO: handle the error in the future + return Promise.reject(message) + } + + return { data: dataOrError } +} + type SetWinnerInput = { userId: string event: string diff --git a/src/app/events/[event]/table-participants.tsx b/src/app/events/[event]/table-participants.tsx index 3b8bb0a..3f54cb2 100644 --- a/src/app/events/[event]/table-participants.tsx +++ b/src/app/events/[event]/table-participants.tsx @@ -1,5 +1,4 @@ -import Link from "next/link" -import type { Participant } from "@/shared/types" +import type { EditParticipant, Participant } from "@/shared/types" import { DebouncedCheckbox, Table, @@ -9,20 +8,20 @@ import { TableHead, TableCell, } from "@/components" -import { Trash2 as RemoveIcon, Edit as EditIcon } from "lucide-react" +import { Trash2 as RemoveIcon } from "lucide-react" type TableBodyProps = { - event: string participants: Participant[] onCheckParticipant: (args: { id: string; checked: boolean }) => void onRemoveParticipant: (id: string) => void + editParticipantModal: (data: EditParticipant) => React.ReactNode } export function TableParticipants({ - event, participants, onCheckParticipant, onRemoveParticipant, + editParticipantModal, }: TableBodyProps) { return ( @@ -69,11 +68,8 @@ export function TableParticipants({ )} - - - + + {editParticipantModal(participant)} diff --git a/src/components/edit-participant-modal/index.ts b/src/components/edit-participant-modal/index.ts new file mode 100644 index 0000000..a30909e --- /dev/null +++ b/src/components/edit-participant-modal/index.ts @@ -0,0 +1 @@ +export * from "./modal" diff --git a/src/components/edit-participant-modal/modal.tsx b/src/components/edit-participant-modal/modal.tsx new file mode 100644 index 0000000..0f9b3fc --- /dev/null +++ b/src/components/edit-participant-modal/modal.tsx @@ -0,0 +1,144 @@ +import { useEffect, useState } from "react" + +import { Button } from "../button" +import { Input } from "../input" +import { ModalContent } from "../modal" +import { + User, + AtSign, + Github, + ArrowLeft, + CheckCircle, + XCircle, +} from "lucide-react" + +import { useForm, SubmitHandler } from "react-hook-form" +import { zodResolver } from "@hookform/resolvers/zod" + +import { EditParticipant, editParticipantSchema } from "@/shared/types" + +export type EditParticipantModalProps = { + onEditParticipant: (data: EditParticipant) => Promise + loading?: boolean + success: boolean | null + error: string | null + initialData: EditParticipant +} + +export function EditParticipantModal({ + onEditParticipant, + loading, + success, + error, + initialData, +}: EditParticipantModalProps) { + const { + register, + handleSubmit, + formState: { errors }, + reset, + } = useForm({ + resolver: zodResolver(editParticipantSchema), + }) + + const [internalSuccess, setInternalSuccess] = useState(success) + const [internalError, setInternalError] = useState(error) + + useEffect(() => { + setInternalSuccess(success) + setInternalError(error) + }, [success, error]) + + useEffect(() => { + if (success) { + reset() + } + }, [success, reset]) + + const handleEditParticipant: SubmitHandler = async ( + data, + ) => { + await onEditParticipant(data) + } + + const handleBackToForm = () => { + setInternalSuccess(null) + setInternalError(null) + } + + return ( + + + {(internalSuccess || internalError) && ( + <> + + + + + + + + + {internalSuccess && ( + <> + + + Registrado com sucesso! + + > + )} + {!!internalError && ( + <> + + + {error} + + > + )} + + + > + )} + + {!internalSuccess && !internalError && ( + <> + + Editar Participante + + + + } + error={errors.name?.message} + {...register("name")} + /> + } + error={errors.email?.message} + {...register("email")} + /> + } + error={errors.github?.message} + {...register("github")} + /> + + + Confirmar + + + > + )} + + + ) +} diff --git a/src/components/index.ts b/src/components/index.ts index 614e218..09281f9 100644 --- a/src/components/index.ts +++ b/src/components/index.ts @@ -1,3 +1,5 @@ +"use client" + export * from "./sidebar" export * from "./rounds-list" export * from "./sidebar" @@ -8,4 +10,5 @@ export * from "./table" export * from "./input" export * from "./modal" export * from "./register-participant-modal" +export * from "./edit-participant-modal" export * from "./spinner" diff --git a/src/components/modal/modal.tsx b/src/components/modal/modal.tsx index bf09d2a..de022b8 100644 --- a/src/components/modal/modal.tsx +++ b/src/components/modal/modal.tsx @@ -1,5 +1,3 @@ -"use client" - import * as React from "react" import * as ModalPrimitive from "@radix-ui/react-dialog" import { X } from "lucide-react" diff --git a/src/components/register-participant-modal/modal.tsx b/src/components/register-participant-modal/modal.tsx index 0847b24..4d495ec 100644 --- a/src/components/register-participant-modal/modal.tsx +++ b/src/components/register-participant-modal/modal.tsx @@ -1,5 +1,3 @@ -"use client" - import { useEffect, useState } from "react" import { Button } from "../button" @@ -55,7 +53,7 @@ export function RegisterParticipantModal({ } }, [success, reset]) - const handleRegisterParticpant: SubmitHandler = async ( + const handleRegisterParticipant: SubmitHandler = async ( data, ) => { await onRegisterParticipant(data) @@ -107,7 +105,7 @@ export function RegisterParticipantModal({ +export type ParticipantInGroup = z.infer export const registerParticipantSchema = z.object({ name: z.string().min(1, { message: "Name is required" }), @@ -42,4 +44,12 @@ export const registerParticipantSchema = z.object({ export type RegisterParticipant = z.infer -export type ParticipantInGroup = z.infer +export const participantSchemaWithId = z.object({ + id: z.string().uuid(), +}) +export const editParticipantSchema = z.intersection( + participantSchemaWithId, + registerParticipantSchema, +) + +export type EditParticipant = z.infer
+ Registrado com sucesso! +
+ {error} +