diff --git a/src/components/modals/create-evaluation-modal.tsx b/src/components/modals/create-evaluation-modal.tsx
index 22fcdb5a..b10c198b 100644
--- a/src/components/modals/create-evaluation-modal.tsx
+++ b/src/components/modals/create-evaluation-modal.tsx
@@ -11,6 +11,7 @@ import {
useCreateEvaluation,
useGetMissionEvaluationGrades,
} from '@/hooks/queries/evaluation-api';
+import { useToastStore } from '@/stores/use-toast-store';
import { TextAreaInput } from '../ui/input';
const CreateEvaluationFormSchema = z.object({
@@ -89,6 +90,7 @@ function CreateEvaluationForm({
const { data: grades } = useGetMissionEvaluationGrades();
const { mutate: createEvaluation } = useCreateEvaluation();
+ const showToast = useToastStore((state) => state.showToast);
const onValidSubmit = (values: CreateEvaluationFormValues) => {
createEvaluation(
@@ -98,11 +100,11 @@ function CreateEvaluationForm({
},
{
onSuccess: () => {
- alert('평가가 성공적으로 제출되었습니다!');
+ showToast('평가가 성공적으로 제출되었습니다!');
onClose();
},
onError: () => {
- alert('평가 제출에 실패했습니다. 다시 시도해주세요.');
+ showToast('평가 제출에 실패했습니다. 다시 시도해주세요.', 'error');
},
},
);
diff --git a/src/components/modals/create-mission-modal.tsx b/src/components/modals/create-mission-modal.tsx
index 2d265e82..ffd5c251 100644
--- a/src/components/modals/create-mission-modal.tsx
+++ b/src/components/modals/create-mission-modal.tsx
@@ -11,6 +11,7 @@ import { BaseInput, TextAreaInput } from '@/components/ui/input';
import { Modal } from '@/components/ui/modal';
import { useGroupStudyDetailQuery } from '@/features/study/group/model/use-study-query';
import { useCreateMission, useGetMissions } from '@/hooks/queries/mission-api';
+import { useToastStore } from '@/stores/use-toast-store';
import {
createDisabledDateMatcherForMission,
MissionPeriod,
@@ -129,6 +130,7 @@ function CreateMissionForm({
const { handleSubmit, formState, control } = methods;
const { mutate: createMission } = useCreateMission();
+ const showToast = useToastStore((state) => state.showToast);
const onValidSubmit = (values: CreateMissionFormValues) => {
const startDate = dayjs(values.dateRange.from).format('YYYY-MM-DD');
@@ -148,11 +150,11 @@ function CreateMissionForm({
},
{
onSuccess: () => {
- alert('미션이 성공적으로 생성되었습니다!');
+ showToast('미션이 성공적으로 생성되었습니다!');
onClose();
},
onError: () => {
- alert('미션 생성에 실패했습니다. 다시 시도해주세요.');
+ showToast('미션 생성에 실패했습니다. 다시 시도해주세요.', 'error');
},
},
);
diff --git a/src/components/modals/delete-evaluation-modal.tsx b/src/components/modals/delete-evaluation-modal.tsx
index 8183c177..15c13582 100644
--- a/src/components/modals/delete-evaluation-modal.tsx
+++ b/src/components/modals/delete-evaluation-modal.tsx
@@ -1,5 +1,6 @@
import { useState } from 'react';
import { useDeleteEvaluation } from '@/hooks/queries/evaluation-api';
+import { useToastStore } from '@/stores/use-toast-store';
import Button from '../ui/button';
import { Modal } from '../ui/modal';
@@ -14,15 +15,16 @@ export default function DeleteEvaluationModal({
const [open, setOpen] = useState
(false);
const { mutate: deleteEvaluation } = useDeleteEvaluation();
+ const showToast = useToastStore((state) => state.showToast);
const handleDelete = () => {
deleteEvaluation(evaluationId, {
onSuccess: () => {
- alert('평가가 삭제되었습니다.');
+ showToast('평가가 삭제되었습니다.');
setOpen(false);
},
onError: () => {
- alert('평가 삭제에 실패했습니다. 다시 시도해주세요.');
+ showToast('평가 삭제에 실패했습니다. 다시 시도해주세요.', 'error');
},
});
};
diff --git a/src/components/modals/delete-homework-modal.tsx b/src/components/modals/delete-homework-modal.tsx
index be617de5..06853ae4 100644
--- a/src/components/modals/delete-homework-modal.tsx
+++ b/src/components/modals/delete-homework-modal.tsx
@@ -1,5 +1,6 @@
import { useState } from 'react';
import { useDeleteHomework } from '@/hooks/queries/group-study-homework-api';
+import { useToastStore } from '@/stores/use-toast-store';
import Button from '../ui/button';
import { Modal } from '../ui/modal';
@@ -16,16 +17,17 @@ export default function DeleteHomeworkModal({
const [open, setOpen] = useState(false);
const { mutate: deleteHomework } = useDeleteHomework();
+ const showToast = useToastStore((state) => state.showToast);
const handleDelete = () => {
deleteHomework(homeworkId, {
onSuccess: () => {
- alert('과제가 성공적으로 삭제되었습니다!');
+ showToast('과제가 성공적으로 삭제되었습니다!');
setOpen(false);
onSuccess?.();
},
onError: () => {
- alert('과제 삭제에 실패했습니다. 다시 시도해주세요.');
+ showToast('과제 삭제에 실패했습니다. 다시 시도해주세요.', 'error');
},
});
};
diff --git a/src/components/modals/delete-mission-modal.tsx b/src/components/modals/delete-mission-modal.tsx
index 82a3ddbe..fb08170d 100644
--- a/src/components/modals/delete-mission-modal.tsx
+++ b/src/components/modals/delete-mission-modal.tsx
@@ -2,6 +2,7 @@ import { useState } from 'react';
import { MissionListResponse } from '@/api/openapi';
import { useDeleteMission } from '@/hooks/queries/mission-api';
+import { useToastStore } from '@/stores/use-toast-store';
import Button from '../ui/button';
import { Modal } from '../ui/modal';
@@ -20,18 +21,19 @@ export default function DeleteMissionModal({
const [open, setOpen] = useState(false);
const { mutate: deleteMission } = useDeleteMission();
+ const showToast = useToastStore((state) => state.showToast);
const handleDelete = () => {
deleteMission(
{ missionId, groupStudyId },
{
onSuccess: () => {
- alert('미션이 성공적으로 삭제되었습니다!');
+ showToast('미션이 성공적으로 삭제되었습니다!');
setOpen(false);
onSuccess?.();
},
onError: () => {
- alert('미션 삭제에 실패했습니다. 다시 시도해주세요.');
+ showToast('미션 삭제에 실패했습니다. 다시 시도해주세요.', 'error');
},
},
);
diff --git a/src/components/modals/delete-peer-review-modal.tsx b/src/components/modals/delete-peer-review-modal.tsx
index 95403cd5..c5e10d51 100644
--- a/src/components/modals/delete-peer-review-modal.tsx
+++ b/src/components/modals/delete-peer-review-modal.tsx
@@ -1,4 +1,5 @@
import { useDeletePeerReview } from '@/hooks/queries/peer-review-api';
+import { useToastStore } from '@/stores/use-toast-store';
import Button from '../ui/button';
import { Modal } from '../ui/modal';
@@ -15,15 +16,16 @@ export default function DeletePeerReviewModal({
onOpenChange,
}: DeletePeerReviewModalProps) {
const { mutate: deletePeerReview } = useDeletePeerReview();
+ const showToast = useToastStore((state) => state.showToast);
const handleDelete = () => {
deletePeerReview(peerReviewId, {
onSuccess: () => {
- alert('피어 리뷰가 삭제되었습니다!');
+ showToast('피어 리뷰가 삭제되었습니다!');
onOpenChange(false);
},
onError: () => {
- alert('피어 리뷰 삭제에 실패했습니다. 다시 시도해주세요.');
+ showToast('피어 리뷰 삭제에 실패했습니다. 다시 시도해주세요.', 'error');
},
});
};
diff --git a/src/components/modals/discretionary-evaluation-modal.tsx b/src/components/modals/discretionary-evaluation-modal.tsx
index e9beb76d..7acd2996 100644
--- a/src/components/modals/discretionary-evaluation-modal.tsx
+++ b/src/components/modals/discretionary-evaluation-modal.tsx
@@ -7,6 +7,7 @@ import Button from '@/components/ui/button';
import FormField from '@/components/ui/form/form-field';
import { Modal } from '@/components/ui/modal';
import { useUpdateMemberDiscretion } from '@/hooks/queries/group-study-member-api';
+import { useToastStore } from '@/stores/use-toast-store';
import { TextAreaInput } from '../ui/input';
const DiscretionaryEvaluationFormSchema = z.object({
@@ -89,8 +90,8 @@ function DiscretionaryEvaluationForm({
const { handleSubmit, formState } = methods;
- // TODO: API hook 연결 필요
const { mutate: updateMemberDiscretion } = useUpdateMemberDiscretion();
+ const showToast = useToastStore((state) => state.showToast);
const onValidSubmit = (values: DiscretionaryEvaluationFormValues) => {
updateMemberDiscretion(
@@ -103,11 +104,11 @@ function DiscretionaryEvaluationForm({
},
{
onSuccess: () => {
- alert('재량 평가가 성공적으로 제출되었습니다!');
+ showToast('재량 평가가 성공적으로 제출되었습니다!');
onClose();
},
onError: () => {
- alert('재량 평가 제출에 실패했습니다. 다시 시도해주세요.');
+ showToast('재량 평가 제출에 실패했습니다. 다시 시도해주세요.', 'error');
},
},
);
diff --git a/src/components/modals/edit-evaluation-modal.tsx b/src/components/modals/edit-evaluation-modal.tsx
index d5728fc3..ab2c3f46 100644
--- a/src/components/modals/edit-evaluation-modal.tsx
+++ b/src/components/modals/edit-evaluation-modal.tsx
@@ -12,6 +12,7 @@ import {
useGetMissionEvaluationGrades,
useUpdateEvaluation,
} from '@/hooks/queries/evaluation-api';
+import { useToastStore } from '@/stores/use-toast-store';
import { TextAreaInput } from '../ui/input';
const EditEvaluationFormSchema = z.object({
@@ -99,6 +100,7 @@ function EditEvaluationForm({
const { data: grades } = useGetMissionEvaluationGrades();
const { mutate: updateEvaluation } = useUpdateEvaluation();
+ const showToast = useToastStore((state) => state.showToast);
const onValidSubmit = (values: EditEvaluationFormValues) => {
updateEvaluation(
@@ -108,11 +110,11 @@ function EditEvaluationForm({
},
{
onSuccess: () => {
- alert('평가가 성공적으로 수정되었습니다!');
+ showToast('평가가 성공적으로 수정되었습니다!');
onClose();
},
onError: () => {
- alert('평가 수정에 실패했습니다. 다시 시도해주세요.');
+ showToast('평가 수정에 실패했습니다. 다시 시도해주세요.', 'error');
},
},
);
diff --git a/src/components/modals/edit-homework-modal.tsx b/src/components/modals/edit-homework-modal.tsx
index 13bd9149..2df7f7e0 100644
--- a/src/components/modals/edit-homework-modal.tsx
+++ b/src/components/modals/edit-homework-modal.tsx
@@ -8,6 +8,7 @@ import FormField from '@/components/ui/form/form-field';
import { Modal } from '@/components/ui/modal';
import { useEditHomework } from '@/hooks/queries/group-study-homework-api';
+import { useToastStore } from '@/stores/use-toast-store';
import { BaseInput, TextAreaInput } from '../ui/input';
const EditHomeworkFormSchema = z.object({
@@ -92,6 +93,7 @@ function EditHomeworkForm({
const { handleSubmit, formState } = methods;
const { mutate: editHomework } = useEditHomework();
+ const showToast = useToastStore((state) => state.showToast);
const onValidSubmit = (values: EditHomeworkFormValues) => {
editHomework(
@@ -104,12 +106,12 @@ function EditHomeworkForm({
},
{
onSuccess: async () => {
- alert('과제가 성공적으로 수정되었습니다!');
+ showToast('과제가 성공적으로 수정되었습니다!');
onClose();
onSuccess?.();
},
onError: () => {
- alert('과제 수정에 실패했습니다. 다시 시도해주세요.');
+ showToast('과제 수정에 실패했습니다. 다시 시도해주세요.', 'error');
},
},
);
diff --git a/src/components/modals/edit-mission-modal.tsx b/src/components/modals/edit-mission-modal.tsx
index aa4ec2a7..50ca9b7d 100644
--- a/src/components/modals/edit-mission-modal.tsx
+++ b/src/components/modals/edit-mission-modal.tsx
@@ -15,6 +15,7 @@ import {
useGetMissions,
useUpdateMission,
} from '@/hooks/queries/mission-api';
+import { useToastStore } from '@/stores/use-toast-store';
import {
createDisabledDateMatcherForMission,
MissionPeriod,
@@ -165,6 +166,7 @@ function EditMissionForm({
}, [missionData, reset]);
const { mutate: updateMission } = useUpdateMission();
+ const showToast = useToastStore((state) => state.showToast);
const onValidSubmit = (values: EditMissionFormValues) => {
const startDate = dayjs(values.dateRange.from).format('YYYY-MM-DD');
@@ -184,11 +186,11 @@ function EditMissionForm({
},
{
onSuccess: () => {
- alert('미션이 성공적으로 수정되었습니다!');
+ showToast('미션이 성공적으로 수정되었습니다!');
onClose();
},
onError: () => {
- alert('미션 수정에 실패했습니다. 다시 시도해주세요.');
+ showToast('미션 수정에 실패했습니다. 다시 시도해주세요.', 'error');
},
},
);
diff --git a/src/components/modals/submit-homework-modal.tsx b/src/components/modals/submit-homework-modal.tsx
index 3a2441f5..5fd887c8 100644
--- a/src/components/modals/submit-homework-modal.tsx
+++ b/src/components/modals/submit-homework-modal.tsx
@@ -8,6 +8,7 @@ import FormField from '@/components/ui/form/form-field';
import { Modal } from '@/components/ui/modal';
import { useSubmitHomework } from '@/hooks/queries/group-study-homework-api';
+import { useToastStore } from '@/stores/use-toast-store';
import { BaseInput, TextAreaInput } from '../ui/input';
const SubmitHomeworkFormSchema = z.object({
@@ -87,6 +88,7 @@ function SubmitHomeworkForm({
const { handleSubmit, formState } = methods;
const { mutate: submitHomework } = useSubmitHomework();
+ const showToast = useToastStore((state) => state.showToast);
const onValidSubmit = (values: SubmitHomeworkFormValues) => {
submitHomework(
@@ -99,12 +101,12 @@ function SubmitHomeworkForm({
},
{
onSuccess: async () => {
- alert('과제가 성공적으로 제출되었습니다!');
+ showToast('과제가 성공적으로 제출되었습니다!');
onClose();
onSuccess?.();
},
onError: () => {
- alert('과제 제출에 실패했습니다. 다시 시도해주세요.');
+ showToast('과제 제출에 실패했습니다. 다시 시도해주세요.', 'error');
},
},
);
diff --git a/src/components/pages/group-study-detail-page.tsx b/src/components/pages/group-study-detail-page.tsx
index f35e88ac..f4b1b9a6 100644
--- a/src/components/pages/group-study-detail-page.tsx
+++ b/src/components/pages/group-study-detail-page.tsx
@@ -7,6 +7,7 @@ import MoreMenu from '@/components/ui/dropdown/more-menu';
import Tabs from '@/components/ui/tabs';
import { STUDY_DETAIL_TABS, StudyTabValue } from '@/config/constants';
import { useGetGroupStudyMyStatus } from '@/hooks/queries/group-study-member-api';
+import { useToastStore } from '@/stores/use-toast-store';
import { useLeaderStore } from '@/stores/useLeaderStore';
import { Leader } from '../../features/study/group/api/group-study-types';
import ChannelSection from '../../features/study/group/channel/ui/lounge-section';
@@ -36,6 +37,7 @@ export default function StudyDetailPage({
const router = useRouter();
const searchParams = useSearchParams();
const setLeaderInfo = useLeaderStore((state) => state.setLeaderInfo);
+ const showToast = useToastStore((state) => state.showToast);
const tabFromUrl = searchParams.get('tab') as StudyTabValue | null;
@@ -85,11 +87,11 @@ export default function StudyDetailPage({
event: 'group_study_end',
group_study_id: String(groupStudyId),
});
- alert('스터디가 종료되었습니다.');
+ showToast('스터디가 종료되었습니다.');
router.push('/group-study');
},
onError: () => {
- alert('스터디 종료에 실패하였습니다.');
+ showToast('스터디 종료에 실패하였습니다.', 'error');
},
onSettled: () => {
setShowModal(false);
@@ -116,11 +118,11 @@ export default function StudyDetailPage({
event: 'group_study_delete',
group_study_id: String(groupStudyId),
});
- alert('스터디가 삭제되었습니다.');
+ showToast('스터디가 삭제되었습니다.');
router.push('/group-study');
},
onError: () => {
- alert('스터디 삭제에 실패하였습니다.');
+ showToast('스터디 삭제에 실패하였습니다.', 'error');
},
onSettled: () => {
setShowModal(false);
diff --git a/src/components/pages/premium-study-detail-page.tsx b/src/components/pages/premium-study-detail-page.tsx
index 3000e84a..79d76b64 100644
--- a/src/components/pages/premium-study-detail-page.tsx
+++ b/src/components/pages/premium-study-detail-page.tsx
@@ -11,6 +11,7 @@ import {
Leader,
} from '@/features/study/group/api/group-study-types';
import { useGetGroupStudyMyStatus } from '@/hooks/queries/group-study-member-api';
+import { useToastStore } from '@/stores/use-toast-store';
import { useLeaderStore } from '@/stores/useLeaderStore';
import ChannelSection from '../../features/study/group/channel/ui/lounge-section';
import {
@@ -39,6 +40,7 @@ export default function PremiumStudyDetailPage({
const pathname = usePathname();
const searchParams = useSearchParams();
const setLeaderInfo = useLeaderStore((state) => state.setLeaderInfo);
+ const showToast = useToastStore((state) => state.showToast);
const activeTab = (searchParams.get('tab') as StudyTabValue) || 'intro';
@@ -89,7 +91,7 @@ export default function PremiumStudyDetailPage({
event: 'premium_study_end',
group_study_id: String(groupStudyId),
});
- alert('스터디가 종료되었습니다.');
+ showToast('스터디가 종료되었습니다.');
},
onSettled: () => {
setShowModal(false);
@@ -117,10 +119,10 @@ export default function PremiumStudyDetailPage({
event: 'premium_study_delete',
group_study_id: String(groupStudyId),
});
- alert('스터디가 삭제되었습니다.');
+ showToast('스터디가 삭제되었습니다.');
},
onError: () => {
- alert('스터디 삭제에 실패하였습니다.');
+ showToast('스터디 삭제에 실패하였습니다.', 'error');
},
onSettled: () => {
refetchStudyDetail().catch(() => {});
diff --git a/src/components/summary/study-info-summary.tsx b/src/components/summary/study-info-summary.tsx
index 2555572a..547fc1bc 100644
--- a/src/components/summary/study-info-summary.tsx
+++ b/src/components/summary/study-info-summary.tsx
@@ -9,6 +9,7 @@ import Button from '@/components/ui/button';
import { GroupStudyFullResponse } from '@/features/study/group/api/group-study-types';
import ApplyGroupStudyModal from '@/features/study/group/ui/apply-group-study-modal';
import { useGetGroupStudyMyStatus } from '@/hooks/queries/group-study-member-api';
+import { useToastStore } from '@/stores/use-toast-store';
import { useUserStore } from '@/stores/useUserStore';
import {
EXPERIENCE_LEVEL_LABELS,
@@ -28,6 +29,7 @@ export default function SummaryStudyInfo({ data }: Props) {
const queryClient = useQueryClient();
const [isExpanded, setIsExpanded] = useState(false);
const memberId = useUserStore((state) => state.memberId);
+ const showToast = useToastStore((state) => state.showToast);
const { basicInfo, detailInfo, interviewPost } = data;
const {
@@ -124,7 +126,7 @@ export default function SummaryStudyInfo({ data }: Props) {
const handleCopyURL = async () => {
await navigator.clipboard.writeText(window.location.href);
- alert('스터디 링크가 복사되었습니다!');
+ showToast('스터디 링크가 복사되었습니다!');
};
const handleApplySuccess = async () => {
diff --git a/src/components/ui/global-toast.tsx b/src/components/ui/global-toast.tsx
new file mode 100644
index 00000000..7880e0f0
--- /dev/null
+++ b/src/components/ui/global-toast.tsx
@@ -0,0 +1,17 @@
+'use client';
+
+import { useToastStore } from '@/stores/use-toast-store';
+import Toast from './toast';
+
+export default function GlobalToast() {
+ const { isVisible, message, variant, hideToast } = useToastStore();
+
+ return (
+
+ );
+}
diff --git a/src/components/ui/toast.tsx b/src/components/ui/toast.tsx
index c9df3a04..ce1335e8 100644
--- a/src/components/ui/toast.tsx
+++ b/src/components/ui/toast.tsx
@@ -1,6 +1,6 @@
'use client';
-import { CheckCircle2 } from 'lucide-react';
+import { CheckCircle2, XCircle } from 'lucide-react';
import React, { useEffect } from 'react';
import { cn } from '@/components/ui/(shadcn)/lib/utils';
@@ -9,6 +9,7 @@ interface ToastProps {
isVisible: boolean;
onClose: () => void;
duration?: number;
+ variant?: 'success' | 'error';
}
export default function Toast({
@@ -16,6 +17,7 @@ export default function Toast({
isVisible,
onClose,
duration = 3000,
+ variant = 'success',
}: ToastProps) {
useEffect(() => {
if (isVisible) {
@@ -29,12 +31,15 @@ export default function Toast({
if (!isVisible) return null;
+ const isSuccess = variant === 'success';
+
return (
-
+ {isSuccess ? (
+
+ ) : (
+
+ )}
{message}
);
diff --git a/src/features/study/group/ui/apply-group-study-modal.tsx b/src/features/study/group/ui/apply-group-study-modal.tsx
index 1f2c3b1a..a82a08f1 100644
--- a/src/features/study/group/ui/apply-group-study-modal.tsx
+++ b/src/features/study/group/ui/apply-group-study-modal.tsx
@@ -10,6 +10,7 @@ import Checkbox from '@/components/ui/checkbox';
import { Modal } from '@/components/ui/modal';
import { usePhoneVerificationStore } from '@/features/phone-verification/model/store';
import PhoneVerificationModal from '@/features/phone-verification/ui/phone-verification-modal';
+import { useToastStore } from '@/stores/use-toast-store';
import { GroupStudyDetailResponse } from '../api/group-study-types';
import {
ApplyGroupStudyFormData,
@@ -132,6 +133,7 @@ function ApplyGroupStudyForm({
});
const { mutate: applyGroupStudy } = useApplyGroupStudyMutation(groupStudyId);
+ const showToast = useToastStore((state) => state.showToast);
const onSubmit = (data: ApplyGroupStudyFormData) => {
const { answer } = data;
@@ -145,7 +147,7 @@ function ApplyGroupStudyForm({
group_study_id: String(groupStudyId),
group_study_title: title,
});
- alert('스터디 신청이 완료되었습니다.');
+ showToast('스터디 신청이 완료되었습니다.');
onClose();
onSuccess?.();
},
diff --git a/src/features/study/group/ui/group-notice-modal.tsx b/src/features/study/group/ui/group-notice-modal.tsx
index c34da39f..0597f93e 100644
--- a/src/features/study/group/ui/group-notice-modal.tsx
+++ b/src/features/study/group/ui/group-notice-modal.tsx
@@ -10,6 +10,7 @@ import Button from '@/components/ui/button';
import FormField from '@/components/ui/form/form-field';
import { BaseInput, TextAreaInput } from '@/components/ui/input';
import { Modal } from '@/components/ui/modal';
+import { useToastStore } from '@/stores/use-toast-store';
import { GroupStudyNoticeRequest } from '../api/group-study-types';
import {
buildGroupStudyNoticeDefaults,
@@ -66,6 +67,7 @@ function GroupStudyNoticeForm({
}) {
const qc = useQueryClient();
const { mutate: groupStudyNotice, isPending } = useGroupStudyNoticeMutation();
+ const showToast = useToastStore((state) => state.showToast);
const methods = useForm({
resolver: zodResolver(GroupStudyNoticeFormSchema),
@@ -90,15 +92,18 @@ function GroupStudyNoticeForm({
{ groupStudyId, payload: form },
{
onSuccess: async () => {
- alert(`스터디 공지가 ${type === 'add' ? '등록' : '수정'}되었습니다!`);
+ showToast(
+ `스터디 공지가 ${type === 'add' ? '등록' : '수정'}되었습니다!`,
+ );
onClose();
await qc.invalidateQueries({
queryKey: ['post', groupStudyId],
});
},
onError: () => {
- alert(
+ showToast(
`공지 ${type === 'add' ? '등록' : '수정'} 중 오류가 발생했습니다. 다시 시도해 주세요.`,
+ 'error',
);
},
},
diff --git a/src/features/study/participation/ui/start-study-modal.tsx b/src/features/study/participation/ui/start-study-modal.tsx
index 19d9b940..ef4bef5d 100644
--- a/src/features/study/participation/ui/start-study-modal.tsx
+++ b/src/features/study/participation/ui/start-study-modal.tsx
@@ -33,6 +33,7 @@ import {
toJoinStudyRequest,
} from '@/features/study/participation/model/start-study-form.schema';
import { useJoinStudyMutation } from '@/features/study/participation/model/use-participation-query';
+import { useToastStore } from '@/stores/use-toast-store';
interface StartStudyModalProps {
memberId: number;
@@ -200,6 +201,7 @@ function StartStudyForm({
const { mutate: updateProfile } = useUpdateUserProfileMutation(memberId);
const { mutate: updateProfileInfo } =
useUpdateUserProfileInfoMutation(memberId);
+ const showToast = useToastStore((state) => state.showToast);
const { data: profile } = useUserProfileQuery(memberId);
@@ -351,7 +353,7 @@ function StartStudyForm({
available_times_count: values.availableStudyTimeIds.length,
tech_stacks_count: values.techStackIds.length,
});
- alert('스터디 신청이 완료되었습니다!');
+ showToast('스터디 신청이 완료되었습니다!');
onClose();
router.refresh();
},
@@ -360,7 +362,10 @@ function StartStudyForm({
event: 'study_apply_error',
location: 'home',
});
- alert('스터디 신청 중 오류가 발생했습니다. 다시 시도해 주세요.');
+ showToast(
+ '스터디 신청 중 오류가 발생했습니다. 다시 시도해 주세요.',
+ 'error',
+ );
},
});
})
@@ -375,7 +380,7 @@ function StartStudyForm({
available_times_count: values.availableStudyTimeIds.length,
tech_stacks_count: values.techStackIds.length,
});
- alert('스터디 신청이 완료되었습니다!');
+ showToast('스터디 신청이 완료되었습니다!');
onClose();
router.refresh();
},
@@ -384,7 +389,10 @@ function StartStudyForm({
event: 'study_apply_error',
location: 'home',
});
- alert('스터디 신청 중 오류가 발생했습니다. 다시 시도해 주세요.');
+ showToast(
+ '스터디 신청 중 오류가 발생했습니다. 다시 시도해 주세요.',
+ 'error',
+ );
},
});
});
diff --git a/src/stores/use-toast-store.ts b/src/stores/use-toast-store.ts
new file mode 100644
index 00000000..ee6b6c6d
--- /dev/null
+++ b/src/stores/use-toast-store.ts
@@ -0,0 +1,20 @@
+import { create } from 'zustand';
+
+type ToastVariant = 'success' | 'error';
+
+interface ToastState {
+ message: string;
+ isVisible: boolean;
+ variant: ToastVariant;
+ showToast: (message: string, variant?: ToastVariant) => void;
+ hideToast: () => void;
+}
+
+export const useToastStore = create((set) => ({
+ message: '',
+ isVisible: false,
+ variant: 'success',
+ showToast: (message, variant = 'success') =>
+ set({ message, variant, isVisible: true }),
+ hideToast: () => set({ isVisible: false }),
+}));
From 32dce126c2e4dc345466daa935ee27dce4645472 Mon Sep 17 00:00:00 2001
From: Jeong Ha Seung <88266129+HA-SEUNG-JEONG@users.noreply.github.com>
Date: Fri, 6 Feb 2026 14:02:31 +0900
Subject: [PATCH 2/6] =?UTF-8?q?fix:=20=EC=95=8C=EB=A6=BC=20=EB=B0=A9?=
=?UTF-8?q?=EC=8B=9D=20=EA=B0=9C=EC=84=A0=20=EB=B0=8F=20=ED=94=84=EB=A1=9C?=
=?UTF-8?q?=ED=95=84=20=EC=B9=B4=EB=93=9C=20=ED=81=AC=EA=B8=B0=20=EC=A1=B0?=
=?UTF-8?q?=EC=A0=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/features/my-page/ui/applicant-page.tsx | 7 +++++--
src/features/my-page/ui/profile-card.tsx | 2 +-
2 files changed, 6 insertions(+), 3 deletions(-)
diff --git a/src/features/my-page/ui/applicant-page.tsx b/src/features/my-page/ui/applicant-page.tsx
index f3213d99..6e1220f4 100644
--- a/src/features/my-page/ui/applicant-page.tsx
+++ b/src/features/my-page/ui/applicant-page.tsx
@@ -8,6 +8,7 @@ import {
useApplicantsByStatusQuery,
useUpdateApplicantByStatusMutation,
} from '@/features/study/group/application/model/use-applicant-qeury';
+import { useToastStore } from '@/stores/use-toast-store';
import ProfileCard from './profile-card';
interface ApplicantListProps {
@@ -21,7 +22,9 @@ export default function ApplicantPage(props: ApplicantListProps) {
status: 'PENDING',
});
- const { mutate, isPending } = useUpdateApplicantByStatusMutation();
+ const { mutate } = useUpdateApplicantByStatusMutation();
+
+ const showToast = useToastStore((state) => state.showToast);
const handleApprove = (
studyId: number,
@@ -36,7 +39,7 @@ export default function ApplicantPage(props: ApplicantListProps) {
},
{
onSuccess: async () => {
- alert('적용되었습니다.');
+ showToast('적용되었습니다.');
await refetch();
},
onError: (err) => console.log(err),
diff --git a/src/features/my-page/ui/profile-card.tsx b/src/features/my-page/ui/profile-card.tsx
index a6ca01bb..032a9416 100644
--- a/src/features/my-page/ui/profile-card.tsx
+++ b/src/features/my-page/ui/profile-card.tsx
@@ -47,7 +47,7 @@ export default function ProfileCard(props: ProfileCardProps) {
+
Date: Fri, 6 Feb 2026 14:33:24 +0900
Subject: [PATCH 3/6] =?UTF-8?q?fix:=20API=20=EB=AC=B8=EC=84=9C=20=EB=A7=81?=
=?UTF-8?q?=ED=81=AC=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=ED=8F=89=EA=B0=80?=
=?UTF-8?q?=20=EC=BD=94=EB=A9=98=ED=8A=B8=20=EC=8A=A4=ED=83=80=EC=9D=BC=20?=
=?UTF-8?q?=EA=B0=9C=EC=84=A0?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
CLAUDE.md | 4 ++++
src/components/contents/homework-detail-content.tsx | 6 ++++--
2 files changed, 8 insertions(+), 2 deletions(-)
diff --git a/CLAUDE.md b/CLAUDE.md
index c11a6756..abb206b6 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -34,6 +34,10 @@ CI 파이프라인: lint → typecheck → prettier → build → build-storyboo
### API 레이어
+**백엔드 API 문서 (Swagger):**
+- 스테이징: https://test-api.zeroone.it.kr/v3/api-docs
+- Swagger UI: https://test-api.zeroone.it.kr/swagger-ui/index.html
+
두 가지 통신 패턴이 공존:
1. **레거시 axios** (`src/api/client/axios.ts`): baseURL `/api/v1/`, 토큰 갱신 큐 구현 (AUTH001 에러 시 갱신 트리거). 커스텀 엔드포인트에 사용.
diff --git a/src/components/contents/homework-detail-content.tsx b/src/components/contents/homework-detail-content.tsx
index ecb1751e..85cb1b43 100644
--- a/src/components/contents/homework-detail-content.tsx
+++ b/src/components/contents/homework-detail-content.tsx
@@ -192,7 +192,7 @@ function EvaluationResult({ evaluation }: { evaluation: EvaluationResponse }) {
평가 코멘트
-
+
{evaluation.comment}
@@ -432,7 +432,9 @@ function PeerReviewItem({ review, homeworkId }: PeerReviewItemProps) {
) : (
-