-
Notifications
You must be signed in to change notification settings - Fork 1
Feat/#107 이벤트 흐름 변경에 따른 기능 구현 및 리팩토링 #119
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
7a9b814
04e2197
cc09c1b
3821113
5c28836
128a573
9f98804
10fe338
da01968
2024c31
5f752b7
f67a9cb
7cc3bec
3859fe2
db2d655
0b518ff
5d90756
a9c2269
470105a
486b979
8b9c40d
fe0c5d8
0c3e879
4c99c78
5f830b1
3383d15
d067171
ebb1fc3
cd89e0b
f1d51ce
f4e1607
49f083b
ea51bf3
5659b41
062aa3e
68b01a3
7a4bbf2
87781fb
63ac813
9516ba4
04598bc
1e00a65
61575ce
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| import { useMutation, useQueryClient } from '@tanstack/react-query' | ||
| import { addToast } from '@heroui/react' | ||
|
|
||
| import { submitWinnerForm } from '../services/event' | ||
| import type { EventWinnerForm } from '@/_apis/schemas/event' | ||
| import { EventQueryKeys } from '@/_apis/queries/event' | ||
|
|
||
| type UseSubmitWinnerFormParams = { | ||
| eventId: string | ||
| } | ||
|
|
||
| export const useSubmitWinnerForm = ({ eventId }: UseSubmitWinnerFormParams) => { | ||
| const queryClient = useQueryClient() | ||
|
|
||
| return useMutation({ | ||
| mutationFn: (data: EventWinnerForm) => submitWinnerForm(eventId, data), | ||
| onSuccess: () => { | ||
| queryClient.invalidateQueries({ | ||
| queryKey: [...EventQueryKeys.result(eventId)], | ||
| }) | ||
|
|
||
| addToast({ | ||
| title: '전화번호가 성공적으로 제출되었습니다!', | ||
| severity: 'success', | ||
| }) | ||
| }, | ||
| onError: (error) => { | ||
| console.error('Failed to submit winner phone number:', error) | ||
|
|
||
| addToast({ | ||
| title: '전화번호 제출에 실패했습니다. 잠시 후 다시 시도해주세요.', | ||
| severity: 'danger', | ||
| }) | ||
| }, | ||
| }) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,31 +1,39 @@ | ||
| import { queryOptions } from '@tanstack/react-query' | ||
| import { | ||
| getPublicEventInfo, | ||
| getPrivateEventInfo, | ||
| getEventByPublic, | ||
| getEventByPrivate, | ||
| getEventResult, | ||
| getEventByEntries, | ||
| } from '@/_apis/services/event' | ||
|
|
||
| export const EventQueryKeys = { | ||
| all: () => ['event'] as const, | ||
| publicInfo: () => [...EventQueryKeys.all(), 'info', 'public'] as const, | ||
| privateInfo: () => [...EventQueryKeys.all(), 'info', 'private'] as const, | ||
| result: () => [...EventQueryKeys.all(), 'result'] as const, | ||
| byPublic: () => [...EventQueryKeys.all(), 'info', 'public'] as const, | ||
| byPrivate: () => [...EventQueryKeys.all(), 'info', 'private'] as const, | ||
| byEntry: () => [...EventQueryKeys.all(), 'entry'] as const, | ||
| result: (eventId: string) => | ||
| [...EventQueryKeys.all(), 'result', eventId] as const, | ||
| } | ||
|
|
||
| export const useEventQueries = { | ||
| publicInfo: () => | ||
| byPublic: () => | ||
| queryOptions({ | ||
| queryKey: EventQueryKeys.publicInfo(), | ||
| queryFn: getPublicEventInfo, | ||
| queryKey: EventQueryKeys.byPublic(), | ||
| queryFn: getEventByPublic, | ||
| }), | ||
| privateInfo: () => | ||
| byPrivate: () => | ||
| queryOptions({ | ||
| queryKey: EventQueryKeys.privateInfo(), | ||
| queryFn: getPrivateEventInfo, | ||
| queryKey: EventQueryKeys.byPrivate(), | ||
| queryFn: getEventByPrivate, | ||
| }), | ||
| result: () => | ||
| byEntry: () => | ||
| queryOptions({ | ||
| queryKey: EventQueryKeys.result(), | ||
| queryFn: getEventResult, | ||
| queryKey: EventQueryKeys.byEntry(), | ||
| queryFn: getEventByEntries, | ||
| }), | ||
| result: (eventId: string) => | ||
| queryOptions({ | ||
| queryKey: EventQueryKeys.result(eventId), | ||
| queryFn: () => getEventResult(eventId), | ||
| }), | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,32 +1,55 @@ | ||
| import { z } from 'zod' | ||
|
|
||
| export const PublicEventSchema = z.object({ | ||
| prize: z.object({ | ||
| description: z.string(), | ||
| imageUrl: z.string(), | ||
| }), | ||
| export const EventPrizeSchema = z.object({ | ||
| description: z.string(), | ||
| imageUrl: z.string(), | ||
| }) | ||
|
|
||
| export const PrivateEventSchema = z.object({ | ||
| export const BaseEventSchema = z.object({ | ||
| eventId: z.number().transform(String), | ||
| prize: z.object({ | ||
| description: z.string(), | ||
| imageUrl: z.string(), | ||
| }), | ||
| prize: EventPrizeSchema, | ||
| totalWinnersCount: z.number(), | ||
| participantsCount: z.number(), | ||
| usedTicketsCount: z.number(), | ||
| remainingTicketsCount: z.number(), | ||
| eventEndDate: z.string(), | ||
| }) | ||
|
|
||
| export const EventResultSchema = z.object({ | ||
| eventId: z.string(), | ||
| export const EventByPublicSchema = z.nullable( | ||
| z.object({ | ||
| prize: EventPrizeSchema, | ||
| }), | ||
| ) | ||
|
|
||
| export const EventByPrivateSchema = z.nullable( | ||
| BaseEventSchema.extend({ | ||
| usedTicketsCount: z.number(), | ||
| remainingTicketsCount: z.number(), | ||
| }), | ||
| ) | ||
|
|
||
| export const EventEntrySchema = BaseEventSchema | ||
|
|
||
| export const EventResultSchema = BaseEventSchema.extend({ | ||
| isWinner: z.boolean(), | ||
| participantsCount: z.number(), | ||
| usedTicketsCount: z.number(), | ||
| isPhoneSubmitted: z.boolean(), | ||
| }) | ||
|
|
||
| export const EventWinnerFormSchema = z.object({ | ||
| phoneNumber: z | ||
| .string() | ||
| .min(1, '전화번호를 입력해주세요.') | ||
| .regex( | ||
| /^010-\d{4}-\d{4}$/, | ||
| '올바른 형식으로 입력해주세요 (예: 010-1234-5678)', | ||
| ), | ||
| agreements: z.object({ | ||
| termsAgreed: z.boolean().refine((val) => val === true), | ||
| privacyAgreed: z.boolean().refine((val) => val === true), | ||
| }), | ||
| }) | ||
|
|
||
| export type PublicEvent = z.infer<typeof PublicEventSchema> | ||
| export type PrivateEvent = z.infer<typeof PrivateEventSchema> | ||
| export type EventByPublic = z.infer<typeof EventByPublicSchema> | ||
| export type EventByPrivate = z.infer<typeof EventByPrivateSchema> | ||
| export type EventByEntry = z.infer<typeof EventEntrySchema> | ||
| export type EventResult = z.infer<typeof EventResultSchema> | ||
| export type EventWinnerForm = z.infer<typeof EventWinnerFormSchema> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,33 +1,48 @@ | ||
| import axiosInstance from '@/_lib/axiosInstance' | ||
| import { API_PATH } from '@/_constants/path' | ||
| import { | ||
| PrivateEventSchema, | ||
| PublicEventSchema, | ||
| EventByPrivateSchema, | ||
| EventByPublicSchema, | ||
| EventEntrySchema, | ||
| EventResultSchema, | ||
| type EventByEntry, | ||
| type EventResult, | ||
| type PrivateEvent, | ||
| type PublicEvent, | ||
| type EventByPrivate, | ||
| type EventByPublic, | ||
| type EventWinnerForm, | ||
| } from '@/_apis/schemas/event' | ||
|
|
||
| export const getPublicEventInfo = async (): Promise<PublicEvent> => { | ||
| export const getEventByPublic = async (): Promise<EventByPublic> => { | ||
| const { data } = await axiosInstance.get(API_PATH.EVENT.INFO) | ||
| return PublicEventSchema.parse(data) | ||
| return EventByPublicSchema.parse(data) | ||
| } | ||
|
|
||
| export const getPrivateEventInfo = async (): Promise<PrivateEvent> => { | ||
| export const getEventByPrivate = async (): Promise<EventByPrivate> => { | ||
| const { data } = await axiosInstance.get(API_PATH.EVENT.INFO) | ||
| return PrivateEventSchema.parse(data) | ||
| return EventByPrivateSchema.parse(data) | ||
| } | ||
|
|
||
| export const participationEvent = async (body: { | ||
| eventId: string | ||
| ticketsCount: number | ||
| }) => { | ||
| const { data } = await axiosInstance.post(API_PATH.EVENT.PARTICIPATIONS, body) | ||
| const { data } = await axiosInstance.post(API_PATH.EVENT.ENTRIES, body) | ||
| return data | ||
| } | ||
|
|
||
| export const getEventResult = async (): Promise<EventResult | null> => { | ||
| const { data } = await axiosInstance.get(API_PATH.EVENT.RESULT) | ||
| export const getEventByEntries = async (): Promise<EventByEntry[]> => { | ||
| const { data } = await axiosInstance.get(API_PATH.EVENT.ENTRIES) | ||
| return EventEntrySchema.array().parse(data) | ||
| } | ||
|
|
||
| export const getEventResult = async (eventId: string): Promise<EventResult> => { | ||
| const { data } = await axiosInstance.get(API_PATH.EVENT.RESULT(eventId)) | ||
| return EventResultSchema.parse(data) | ||
| } | ||
|
|
||
| export const submitWinnerForm = async ( | ||
| eventId: string, | ||
| data: EventWinnerForm, | ||
| ): Promise<void> => { | ||
| await axiosInstance.post(API_PATH.EVENT.APPLY(eventId), data) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -36,8 +36,9 @@ export const API_PATH = { | |
| }, | ||
| EVENT: { | ||
| INFO: '/events', | ||
| PARTICIPATIONS: '/events/entries', | ||
| RESULT: '/events/results', | ||
| ENTRIES: '/events/entries', | ||
| RESULT: (eventId: string) => `/events/${eventId}/entries`, | ||
| APPLY: (eventId: string) => `/events/${eventId}/apply`, | ||
|
Comment on lines
+39
to
+41
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: cat -n apps/web/app/_constants/path.tsRepository: knu-matzip/Matzip-Client Length of output: 2991 🏁 Script executed: rg -n "API_PATH\.EVENT\.RESULT" --type ts --type tsx -A 2 -B 2Repository: knu-matzip/Matzip-Client Length of output: 95 🏁 Script executed: rg -n "API_PATH\.EVENT\.RESULT[^(]" --type ts --type tsxRepository: knu-matzip/Matzip-Client Length of output: 95 🏁 Script executed: rg "API_PATH\.EVENT\.RESULT" -A 2 -B 2Repository: knu-matzip/Matzip-Client Length of output: 450 🏁 Script executed: rg "EVENT\.RESULT" --type tsRepository: knu-matzip/Matzip-Client Length of output: 179 🏁 Script executed: fd -t ts -t tsx . | head -20Repository: knu-matzip/Matzip-Client Length of output: 237 🏁 Script executed: rg "EVENT\.RESULT" -iRepository: knu-matzip/Matzip-Client Length of output: 179 🏁 Script executed: cat -n apps/web/app/_apis/services/event.tsRepository: knu-matzip/Matzip-Client Length of output: 1842 함수에 명시적 반환 타입 추가 필요 코딩 가이드라인에서 "TypeScript 파일의 함수는 반환 타입이 필수"라고 명시되어 있으나, 현재 파일의 모든 매개변수를 받는 함수들이 명시적 반환 타입을 선언하지 않고 있습니다. 예시:
🤖 Prompt for AI Agents |
||
| }, | ||
| REQUEST: { | ||
| LIST: '/requests/places', | ||
|
|
@@ -66,6 +67,7 @@ export const CLIENT_PATH = { | |
| REQUEST_DETAIL: (id: string | number) => `/requests/${id}`, | ||
| EVENTS_FOOD_SLOT: '/events/food-slot', | ||
| EVENTS_LUCKY_DRAW: '/events/lucky-draw', | ||
| EVENTS_RESULT: (id: string | number) => `/events/lucky-draw/result/${id}`, | ||
| EVENT_GIFTICON: '/events/gifticon', | ||
| EVENT_GIFTICON_DETAIL: (id: string | number) => `/events/gifticon/${id}`, | ||
| LOGIN: '/login', | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,16 +1,19 @@ | ||
| import { http, HttpResponse } from 'msw' | ||
| import { eventResult } from '../data/event' | ||
| import { EVENT_ENTRIES } from '../data/event' | ||
| import { API_PATH } from '@/_constants/path' | ||
| import { addBaseUrl } from './addBaseUrl' | ||
|
|
||
| export const EventHandlers = [ | ||
| // http.get(addBaseUrl(API_PATH.EVENT.INFO), () => { | ||
| // return HttpResponse.json(event) | ||
| // }), | ||
| http.post(addBaseUrl(API_PATH.EVENT.PARTICIPATIONS), () => { | ||
| return HttpResponse.json({ message: '성공' }) | ||
| }), | ||
| http.get(addBaseUrl(API_PATH.EVENT.RESULT), () => { | ||
| return HttpResponse.json(eventResult) | ||
| // http.post(addBaseUrl(API_PATH.EVENT.ENTRIES), () => { | ||
| // return HttpResponse.json({ message: '성공' }) | ||
| // }), | ||
| // http.get(addBaseUrl('/events/:eventId/entries'), () => { | ||
| // return HttpResponse.json(eventResult) | ||
| // }), | ||
| http.get(addBaseUrl(API_PATH.EVENT.ENTRIES), () => { | ||
| return HttpResponse.json(EVENT_ENTRIES) | ||
| }), | ||
| ] |
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
EventResultSchema가 결과 응답 계약을 과도하게 강제하고 있습니다.Line 31에서
BaseEventSchema.extend(...)를 사용하면 결과 응답에prize,totalWinnersCount,participantsCount,eventEndDate가 모두 필수가 됩니다. 현재 PR의apps/web/app/_mocks/data/event.ts의eventResult형태와도 충돌해서 타입/파싱 실패 위험이 큽니다. 결과 전용 스키마를 별도로 두는 게 안전합니다.🔧 제안 수정안
📝 Committable suggestion
🤖 Prompt for AI Agents