diff --git a/src/features/study/group/api/apply-group-study.ts b/src/features/study/group/api/apply-group-study.ts new file mode 100644 index 00000000..372dfed8 --- /dev/null +++ b/src/features/study/group/api/apply-group-study.ts @@ -0,0 +1,17 @@ +import { axiosInstance } from '@/shared/tanstack-query/axios'; +import { + ApplyGroupStudyRequest, + ApplyGroupStudyResponse, +} from './group-study-types'; + +// 그룹 스터디 신청 요청 +export const applyGroupStudy = async ({ + groupStudyId, + answer, +}: ApplyGroupStudyRequest): Promise => { + const res = await axiosInstance.post(`/group-studies/${groupStudyId}/apply`, { + answer, + }); + + return res.data; +}; diff --git a/src/features/study/group/api/group-study-types.ts b/src/features/study/group/api/group-study-types.ts index 4edc08b5..219f78be 100644 --- a/src/features/study/group/api/group-study-types.ts +++ b/src/features/study/group/api/group-study-types.ts @@ -7,6 +7,24 @@ import { THUMBNAIL_EXTENSION, } from '../const/group-study-const'; +// 그룹 스터디 신청 상태 +type ApplicationStatus = 'PENDING' | 'APPROVED' | 'REJECTED' | 'KICKED'; + +// 그룹 스터디 신청 Request 타입 +export interface ApplyGroupStudyRequest { + groupStudyId: number; + answer: string[]; +} + +// 그룹 스터디 신청 Response 타입 +export interface ApplyGroupStudyResponse { + applyId: number; + applicantId: number; + groupStudyId: number; + status: ApplicationStatus; + createdAt: string; +} + export type StudyType = (typeof STUDY_TYPES)[number]; export type TargetRole = (typeof TARGET_ROLE_OPTIONS)[number]; export type ExperienceLevel = (typeof EXPERIENCE_LEVEL_OPTIONS)[number]; diff --git a/src/features/study/group/model/use-apply-group-study.tsx b/src/features/study/group/model/use-apply-group-study.tsx new file mode 100644 index 00000000..f015af51 --- /dev/null +++ b/src/features/study/group/model/use-apply-group-study.tsx @@ -0,0 +1,9 @@ +import { useMutation } from '@tanstack/react-query'; +import { applyGroupStudy } from '../api/apply-group-study'; + +// 그룹 스터디 신청 훅 +export const useApplyGroupStudyMutation = () => { + return useMutation({ + mutationFn: applyGroupStudy, + }); +}; diff --git a/src/features/study/group/ui/apply-group-study-modal.tsx b/src/features/study/group/ui/apply-group-study-modal.tsx new file mode 100644 index 00000000..922bd197 --- /dev/null +++ b/src/features/study/group/ui/apply-group-study-modal.tsx @@ -0,0 +1,226 @@ +'use client'; + +import { zodResolver } from '@hookform/resolvers/zod'; +import { XIcon } from 'lucide-react'; +import { useState } from 'react'; + +import { useController, useForm } from 'react-hook-form'; +import { z } from 'zod'; +import Button from '@/shared/ui/button'; +import Checkbox from '@/shared/ui/checkbox'; +import { Modal } from '@/shared/ui/modal'; +import { useApplyGroupStudyMutation } from '../model/use-apply-group-study'; + +interface ApplyGroupStudyModalProps { + groupStudyId: number; + title: string; + questions: string[]; +} + +export default function ApplyGroupStudyModal({ + groupStudyId, + title, + questions, +}: ApplyGroupStudyModalProps) { + const [open, setOpen] = useState(false); + + return ( + + + + + + + + + + + 스터디 신청서 작성하기 + + setOpen(false)}> + + + + + setOpen(false)} + /> + + + + ); +} + +const ApplyGroupStudyFormSchema = z.object({ + answer: z.array( + z.string().min(1, '답변을 작성해주세요.'), // 각 항목에 최소 1글자 이상 + ), + agree: z + .boolean() + .refine((val) => val === true, { message: '참여 규칙에 동의해야 합니다.' }), +}); + +type ApplyGroupStudyFormData = z.infer; + +function ApplyGroupStudyForm({ + groupStudyId, + title, + questions, + onClose, +}: { + groupStudyId: number; + title: string; + questions: string[]; + onClose: () => void; +}) { + const { + register, + handleSubmit, + control, + formState: { errors }, + } = useForm({ + resolver: zodResolver(ApplyGroupStudyFormSchema), + defaultValues: { + answer: Array(questions.length).fill(''), + }, + }); + + // ✅ checkbox를 controller로 제어 + const { + field: { value: checked, onChange: onToggle }, + } = useController({ + name: 'agree', + control, + }); + + const { mutate: applyGroupStudy } = useApplyGroupStudyMutation(); + + const onSubmit = (data: ApplyGroupStudyFormData) => { + const { answer } = data; + + applyGroupStudy( + { answer, groupStudyId }, + { + onSuccess: () => { + alert('스터디 신청이 완료되었습니다.'); + onClose(); + }, + }, + ); + }; + + return ( + <> + +

{title}

+ +
+ + 리더의 질문 + + +
+ {questions.map((question, index) => ( +
+ + +
+