-
Notifications
You must be signed in to change notification settings - Fork 2
[FEAT] 승패 투표 기능 추가 #387
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
[FEAT] 승패 투표 기능 추가 #387
Changes from all commits
0415e54
beca8d1
702cdd8
4a892fa
cd8cc58
2b6518c
755e196
433b783
fb7fd34
6351b37
9030215
731d70d
ee9983a
fed2a0e
662bff0
66066e5
ca7c048
314facb
cf7cf7a
d5e7202
3a52de5
3c74696
1062c7c
e6d8b9c
744da7b
50437c1
a33ce25
6211fdf
d9258e9
6dfae79
c951c52
7e94cb0
2588e21
ba9b815
7cd3101
bd4cb28
452e103
d2a24aa
2b6c228
0dfed86
a08add3
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,99 @@ | ||
| import { VoterPollInfo } from '../../type/type'; | ||
| import { ApiUrl } from '../endpoints'; | ||
| import { request } from '../primitives'; | ||
| import { | ||
| GetPollResponseType, | ||
| GetVoterPollInfoResponseType, | ||
| PatchPollResponseType, | ||
| PostPollResponseType, | ||
| PostVoterPollInfoResponseType, | ||
| } from '../responses/poll'; | ||
|
|
||
| // Template | ||
| /* | ||
| export async function apiFunc( | ||
|
|
||
| ): Promise<ReturnType> { | ||
| const requestUrl: string = ApiUrl. | ||
| const response = await request<ReturnType>( | ||
| method, | ||
| requestUrl, | ||
| data, | ||
| params, | ||
| ); | ||
|
|
||
| return response.data; | ||
| } | ||
| */ | ||
|
|
||
| // POST /api/polls/{tableId} | ||
| export async function postPoll(tableId: number): Promise<PostPollResponseType> { | ||
| const requestUrl: string = ApiUrl.poll; | ||
| const response = await request<PostPollResponseType>( | ||
| 'POST', | ||
| requestUrl + `/${tableId}`, | ||
| null, | ||
| null, | ||
| ); | ||
|
|
||
| return response.data; | ||
| } | ||
|
|
||
| // GET /api/polls/{pollId} | ||
| export async function getPollInfo( | ||
| pollId: number, | ||
| ): Promise<GetPollResponseType> { | ||
| const requestUrl: string = ApiUrl.poll; | ||
| const response = await request<GetPollResponseType>( | ||
| 'GET', | ||
| requestUrl + `/${pollId}`, | ||
| null, | ||
| null, | ||
| ); | ||
| return response.data; | ||
| } | ||
|
|
||
| // PATCH /api/polls/{pollId} | ||
| export async function patchEndPoll( | ||
| pollId: number, | ||
| ): Promise<PatchPollResponseType> { | ||
| const requestUrl: string = ApiUrl.poll; | ||
| const response = await request<PatchPollResponseType>( | ||
| 'PATCH', | ||
| requestUrl + `/${pollId}`, | ||
| null, | ||
| null, | ||
| ); | ||
| return response.data; | ||
| } | ||
|
|
||
| // GET /api/polls/{pollId}/votes | ||
| export async function getVoterPollInfo( | ||
| pollId: number, | ||
| ): Promise<GetVoterPollInfoResponseType> { | ||
| const requestUrl: string = ApiUrl.poll; | ||
| const response = await request<GetVoterPollInfoResponseType>( | ||
| 'GET', | ||
| requestUrl + `/${pollId}/votes`, | ||
| null, | ||
| null, | ||
| ); | ||
|
|
||
| return response.data; | ||
| } | ||
|
|
||
| // POST /api/polls/{pollId}/votes | ||
| export async function postVoterPollInfo( | ||
| pollId: number, | ||
| voterInfo: VoterPollInfo, | ||
| ): Promise<PostVoterPollInfoResponseType> { | ||
| const requestUrl: string = ApiUrl.poll; | ||
| const response = await request<PostVoterPollInfoResponseType>( | ||
| 'POST', | ||
| requestUrl + `/${pollId}/votes`, | ||
| voterInfo, | ||
| null, | ||
| ); | ||
|
|
||
| return response.data; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| import { BasePollInfo, PollInfo, VoterPollInfo } from '../../type/type'; | ||
|
|
||
| // POST /api/polls/{tableId} | ||
| export interface PostPollResponseType extends BasePollInfo { | ||
| id: number; | ||
| } | ||
|
|
||
| // GET /api/polls/{pollId} | ||
| export interface GetPollResponseType extends PollInfo { | ||
| id: number; | ||
| } | ||
|
|
||
| // PATCH /api/polls/{pollId} | ||
| export interface PatchPollResponseType extends PollInfo { | ||
| id: number; | ||
| } | ||
|
|
||
| // GET /api/polls/{pollId}/votes | ||
| export interface GetVoterPollInfoResponseType extends PollInfo { | ||
| id: number; | ||
| participateCode: string; | ||
| } | ||
|
|
||
| // POST /api/polls/{pollId}/votes | ||
|
|
||
| export interface PostVoterPollInfoResponseType extends VoterPollInfo { | ||
| id: number; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| import DTCheck from './Check'; | ||
| import clsx from 'clsx'; | ||
|
|
||
| interface CheckBoxProps { | ||
| checked?: boolean; | ||
| size?: number | string; | ||
| className?: string; | ||
| } | ||
|
|
||
| /** | ||
| *text-* 클래스가 포함되면 자동으로 DTCheck로 전달 | ||
| */ | ||
| export default function CheckBox({ | ||
| checked = false, | ||
| size = 24, | ||
| className = '', | ||
| }: CheckBoxProps) { | ||
| // text-로 시작하는 클래스만 추출 | ||
| const textClass = | ||
| className.split(' ').find((c) => c.startsWith('text-')) ?? ''; | ||
|
|
||
| return ( | ||
| <div | ||
| className={clsx( | ||
| 'flex items-center justify-center rounded-md border', | ||
| checked ? 'border-transparent' : 'border-neutral-400', | ||
| className, | ||
| )} | ||
| style={{ | ||
| width: size, | ||
| height: size, | ||
| }} | ||
| > | ||
| {checked && <DTCheck className={clsx('w-2/3', textClass)} />} | ||
| </div> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| import { postPoll } from '../../apis/apis/poll'; | ||
| import { PostPollResponseType } from '../../apis/responses/poll'; | ||
| import { usePreventDuplicateMutation } from './usePreventDuplicateMutation'; | ||
|
|
||
| export default function usePostPoll(onSuccess: (id: number) => void) { | ||
| return usePreventDuplicateMutation({ | ||
|
Contributor
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. 중복호출방지 훅 사용까지 !!! 🥹 |
||
| mutationFn: (id: number) => postPoll(id), | ||
| onSuccess: (response: PostPollResponseType) => { | ||
| onSuccess(response.id); | ||
| }, | ||
| }); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| import { patchEndPoll } from '../../apis/apis/poll'; | ||
| import { PatchPollResponseType } from '../../apis/responses/poll'; | ||
| import { usePreventDuplicateMutation } from './usePreventDuplicateMutation'; | ||
|
|
||
| export default function useFetchEndPoll(onSuccess: (id: number) => void) { | ||
| return usePreventDuplicateMutation({ | ||
| mutationFn: (pollId: number) => patchEndPoll(pollId), | ||
| onSuccess: (response: PatchPollResponseType) => { | ||
| onSuccess(response.id); | ||
| }, | ||
| }); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| import { postVoterPollInfo } from '../../apis/apis/poll'; | ||
| import { VoterPollInfo } from '../../type/type'; | ||
| import { usePreventDuplicateMutation } from './usePreventDuplicateMutation'; | ||
|
|
||
| export default function usePostVoterPollInfo(onSuccess: () => void) { | ||
| return usePreventDuplicateMutation({ | ||
| mutationFn: ({ | ||
| pollId, | ||
| voterInfo, | ||
| }: { | ||
| pollId: number; | ||
| voterInfo: VoterPollInfo; | ||
| }) => postVoterPollInfo(pollId, voterInfo), | ||
| onSuccess: () => { | ||
| onSuccess(); | ||
| }, | ||
| }); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| import { useQuery } from '@tanstack/react-query'; | ||
| import { getPollInfo } from '../../apis/apis/poll'; | ||
| import { GetPollResponseType } from '../../apis/responses/poll'; | ||
|
|
||
| export function useGetPollInfo( | ||
| pollId: number, | ||
| options?: { refetchInterval?: number | false; enabled?: boolean }, | ||
| ) { | ||
| return useQuery<GetPollResponseType>({ | ||
| queryKey: ['Poll', pollId], | ||
| queryFn: () => getPollInfo(pollId), | ||
| refetchInterval: options?.refetchInterval ?? false, | ||
| enabled: options?.enabled, | ||
| }); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,14 @@ | ||
| import { useQuery } from '@tanstack/react-query'; | ||
| import { getVoterPollInfo } from '../../apis/apis/poll'; | ||
| import { GetVoterPollInfoResponseType } from '../../apis/responses/poll'; | ||
|
|
||
| export function useGetVoterPollInfo( | ||
| pollId: number, | ||
| options?: { enabled?: boolean }, | ||
| ) { | ||
| return useQuery<GetVoterPollInfoResponseType>({ | ||
| queryKey: ['VoterPoll', pollId], | ||
| queryFn: () => getVoterPollInfo(pollId), | ||
| enabled: options?.enabled, | ||
| }); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,4 +1,9 @@ | ||
| import { customizeHandlers } from './customize'; | ||
| import { memberHandlers } from './member'; | ||
| import { pollHandlers } from './poll'; | ||
|
|
||
| export const allHandlers = [...memberHandlers, ...customizeHandlers]; | ||
| export const allHandlers = [ | ||
| ...memberHandlers, | ||
| ...customizeHandlers, | ||
| ...pollHandlers, | ||
| ]; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| import { http, HttpResponse } from 'msw'; | ||
| import { ApiUrl } from '../../apis/endpoints'; | ||
| import { | ||
| PatchPollResponseType, | ||
| PostPollResponseType, | ||
| PostVoterPollInfoResponseType, | ||
| } from '../../apis/responses/poll'; | ||
|
|
||
| export const pollHandlers = [ | ||
| // GET /api/polls/:pollId | ||
| http.get(ApiUrl.poll + '/:pollId', ({ params }) => { | ||
| const { pollId } = params; | ||
| console.log(`# pollId = ${pollId}`); | ||
|
|
||
| return HttpResponse.json({ | ||
| id: 7, | ||
| status: 'PROGRESS', | ||
| prosTeamName: '찬성', | ||
| consTeamName: '반대', | ||
| totalCount: 1, | ||
| prosCount: 1, | ||
| consCount: 0, | ||
| voterNames: ['ㅇㄹㄴ', 'ㅁㄴㅇ'], | ||
| }); | ||
| }), | ||
|
|
||
| // POST /api/polls/{tableId} | ||
| http.post(ApiUrl.poll + '/:tableId', async ({ request }) => { | ||
| const result = (await request.json()) as PostPollResponseType; | ||
| console.log( | ||
| `# pollId = ${result?.id}, prosTeamName = ${result?.prosTeamName}, consTeamName = ${result?.consTeamName}`, | ||
| ); | ||
| return HttpResponse.json({ | ||
| id: 7, | ||
| status: 'PROGRESS', | ||
| prosTeamName: '찬성', | ||
| consTeamName: '반대', | ||
| }); | ||
| }), | ||
i-meant-to-be marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // PATCH /api/polls/{pollId} | ||
| http.patch(ApiUrl.poll + '/:pollId', async ({ request, params }) => { | ||
| const result = (await request.json()) as PatchPollResponseType; | ||
| const { pollId } = params; | ||
| console.log( | ||
| `pollId = ${pollId}, status = ${result?.status}, prosTeamName = ${result?.prosTeamName}, consTeamName = ${result?.consTeamName}`, | ||
| ); | ||
|
|
||
| return HttpResponse.json({ | ||
| id: 7, | ||
| status: 'DONE', | ||
| prosTeamName: '찬성', | ||
| consTeamName: '반대', | ||
| totalCount: 1, | ||
| prosCount: 1, | ||
| consCount: 0, | ||
| voterNames: ['ㅇㄹㄴ', 'ㅁㄴㅇ'], | ||
| }); | ||
| }), | ||
i-meant-to-be marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| // GET /api/polls/{pollId}/votes | ||
|
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. 주석의 HTTP 메서드 수정 필요. 주석에 "GET"으로 표기되어 있지만 실제로는 DELETE 핸들러입니다. 다음 diff를 적용하여 수정하세요: - // GET /api/polls/{pollId}/votes
+ // DELETE /api/polls/{pollId}/votes
http.delete(ApiUrl.poll + `/:pollId/votes`, ({ params }) => {🤖 Prompt for AI Agents
i-meant-to-be marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| http.get(ApiUrl.poll + `/:pollId/votes`, ({ params }) => { | ||
| const { pollId } = params; | ||
| console.log(`# pollId = ${pollId}`); | ||
|
|
||
| return HttpResponse.json({ | ||
| id: 7, | ||
| status: 'PROGRESS', | ||
| prosTeamName: '찬성', | ||
| consTeamName: '반대', | ||
| participateCode: '494bcec7-f8e8-4e96-8922-511cd7114a07', | ||
| totalCount: 1, | ||
| prosCount: 1, | ||
| consCount: 0, | ||
| }); | ||
| }), | ||
|
|
||
| // POST /api/polls/{pollId}/votes | ||
i-meant-to-be marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| http.post(ApiUrl.poll + '/:pollId/votes', async ({ request, params }) => { | ||
| const { pollId } = params; | ||
| const result = (await request.json()) as PostVoterPollInfoResponseType; | ||
| console.log( | ||
| `# pollId = ${pollId}, name = ${result?.name}, participateCode = ${result?.participateCode}, team = ${result?.team}`, | ||
| ); | ||
|
|
||
| return HttpResponse.json({ | ||
| id: 7, | ||
| name: 'ㅇㄹㄴ', | ||
| participateCode: 'string', | ||
| team: 'PROS', | ||
| }); | ||
| }), | ||
| ]; | ||
i-meant-to-be marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,25 @@ | ||||||||||||||||
| import { Meta, StoryObj } from '@storybook/react'; | ||||||||||||||||
| import DebateEndPage from './DebateEndPage'; | ||||||||||||||||
|
|
||||||||||||||||
| const meta: Meta<typeof DebateEndPage> = { | ||||||||||||||||
| title: 'page/DebateEndPage', | ||||||||||||||||
| component: DebateEndPage, | ||||||||||||||||
| tags: ['autodocs'], | ||||||||||||||||
| parameters: { | ||||||||||||||||
| layout: 'fullscreen', // Storybook에서 전체 화면으로 표시 | ||||||||||||||||
| route: '/table/customize/123/end/vote', | ||||||||||||||||
| routePattern: '/table/customize/:id/end/vote', | ||||||||||||||||
|
Comment on lines
+10
to
+11
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. 라우트 설정이 잘못되었습니다. 현재 route가 다음 diff를 적용하세요: parameters: {
layout: 'fullscreen', // Storybook에서 전체 화면으로 표시
- route: '/table/customize/123/end/vote',
- routePattern: '/table/customize/:id/end/vote',
+ route: '/table/customize/123/end',
+ routePattern: '/table/customize/:id/end',
},📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||
| }, | ||||||||||||||||
| }; | ||||||||||||||||
|
|
||||||||||||||||
| export default meta; | ||||||||||||||||
|
|
||||||||||||||||
| type Story = StoryObj<typeof DebateEndPage>; | ||||||||||||||||
|
|
||||||||||||||||
| export const Default: Story = { | ||||||||||||||||
| render: () => ( | ||||||||||||||||
| <div style={{ height: '100vh', width: '100vw', overflow: 'hidden' }}> | ||||||||||||||||
| <DebateEndPage /> | ||||||||||||||||
| </div> | ||||||||||||||||
| ), | ||||||||||||||||
| }; | ||||||||||||||||
Uh oh!
There was an error while loading. Please reload this page.