Skip to content

Commit 2ce91f4

Browse files
authored
[#252] ✨ 커뮤니티 api 연동 (#253)
* [#237] 🔧 change api docs path * [#237] ✨ team api services / queries * [#237] ✨ team api related sates handled by useReducer * [#237] ✨ team realted components edited * [#237] ✨ team related constants * [#237] 🔧 team types update * [#237] 🔧 next image domain added * [#237] ✨ team page * [#237] ✨ create team * [#237] 🔧 next config domains => remote patterns * [#237] ✨ team detail * [#237] ✨ update team type with queries * [#237] ✨ new modal contents * [#237] ✨ edit team * [#237] 🐛 build error * [#237] 🐛 merge conflicts * [#252] ✨ community list api * [#252] ✨ create community api * [#252] ✨ community top5 api * [#252] ✨ community detail * [#252] ✨ delete community api * [#252] ✨ update community api * [#252] 💄 community comment ui (30%)
1 parent 55bbd20 commit 2ce91f4

File tree

12 files changed

+711
-312
lines changed

12 files changed

+711
-312
lines changed
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
'use client'
2+
3+
import { useParams } from 'next/navigation'
4+
5+
import { Controller, useForm } from 'react-hook-form'
6+
7+
import { commuintyCategoryOptions } from '@/constants/selectOptions'
8+
import { TipTapEditor } from '@/lib/tiptap/TipTapEditor'
9+
import {
10+
CreateCommunityRequest,
11+
GetCommunityDetailResponse,
12+
UpdateCommunityRequest,
13+
} from '@/types/api/Community.types'
14+
import { zodResolver } from '@hookform/resolvers/zod'
15+
import { z } from 'zod'
16+
17+
import { Button, Link } from '@/components/common/button'
18+
import { Container } from '@/components/common/containers'
19+
import { Label } from '@/components/common/label'
20+
import { Text } from '@/components/common/text'
21+
import { Form } from '@/components/shared/form'
22+
import { Select } from '@/components/shared/select'
23+
24+
import {
25+
useCommunity,
26+
useCreateCommunity,
27+
useUpdateCommunity,
28+
} from '@/queries/community'
29+
30+
const createCommunitySchema = z.object({
31+
communityTitle: z.string().nonempty('제목을 입력해주세요.'),
32+
communityContent: z.string().nonempty('내용을 입력해주세요.'),
33+
communityCategory: z.enum(['SKILL', 'CAREER', 'OTHER'], {
34+
errorMap: () => ({ message: '질문 유형을 선택해주세요.' }),
35+
}),
36+
isComment: z.boolean().optional(),
37+
})
38+
39+
export default function UpdateCommunityPage(): JSX.Element {
40+
const params = useParams<{ id: string }>()
41+
const communityId = Number(params.id)
42+
43+
const {
44+
data: communityDetail,
45+
isLoading,
46+
isError,
47+
} = useCommunity(communityId)
48+
49+
const { communityTitle, communityContent, communityCategory, isComment } =
50+
(communityDetail as GetCommunityDetailResponse) ?? {
51+
communityTitle: '',
52+
communityContent: '',
53+
isComment: false,
54+
}
55+
56+
const { mutate } = useUpdateCommunity(communityId)
57+
58+
const methods = useForm<CreateCommunityRequest>({
59+
mode: 'onBlur',
60+
resolver: zodResolver(createCommunitySchema),
61+
defaultValues: {
62+
communityTitle,
63+
communityContent,
64+
isComment,
65+
communityCategory,
66+
},
67+
})
68+
const { handleSubmit, control } = methods
69+
const onSubmit = (data: UpdateCommunityRequest) => {
70+
mutate(data)
71+
}
72+
73+
if (isLoading) return <div>d</div>
74+
if (isError) return <div>d</div>
75+
76+
return (
77+
<Container className='mx-auto my-80 flex flex-col gap-40'>
78+
<div className='flex flex-col gap-8'>
79+
<Text.Heading variant='heading2' as='h2' weight='700'>
80+
작성하기
81+
</Text.Heading>
82+
<Text.Body variant='body2' color='gray600'>
83+
궁금한 점을 작성하고 다른 개발자들과 소통해보세요
84+
</Text.Body>
85+
</div>
86+
<Form methods={methods} onSubmit={handleSubmit(onSubmit)}>
87+
<div className='mb-20 flex flex-col gap-4'>
88+
<Label required labelText='카테고리' />
89+
<Controller
90+
name='communityCategory'
91+
control={control}
92+
rules={{ required: '게시글 카테고리를 선택해주세요.' }}
93+
render={({ field, fieldState: { error } }) => (
94+
<div>
95+
<Select
96+
options={commuintyCategoryOptions}
97+
selectedValue={field.value || ''}
98+
onSingleChange={field.onChange}
99+
isMulti={false}
100+
>
101+
<Select.Trigger placeholder='카테고리 선택' />
102+
<Select.Menu>
103+
{commuintyCategoryOptions.map(
104+
({ label, value }: Option) => (
105+
<Select.Option
106+
key={value}
107+
label={label}
108+
value={value}
109+
/>
110+
)
111+
)}
112+
</Select.Menu>
113+
</Select>
114+
{error?.message && (
115+
<Form.Message hasError={!!error}>
116+
{error.message}
117+
</Form.Message>
118+
)}
119+
</div>
120+
)}
121+
/>
122+
</div>
123+
<Label required labelText='제목' className='mb-20'>
124+
<Form.Text
125+
name='communityTitle'
126+
required
127+
placeholder='궁금한 점을 작성해보세요!'
128+
/>
129+
</Label>
130+
<div className='mb-20 flex flex-col gap-4'>
131+
<Label required labelText='내용' />
132+
<Controller
133+
name='communityContent'
134+
control={control}
135+
defaultValue={communityContent}
136+
render={({ field: { onChange }, fieldState: { error } }) => (
137+
<div>
138+
<TipTapEditor content={communityContent} onChange={onChange} />
139+
{error?.message && (
140+
<Form.Message hasError={!!error}>
141+
{error.message}
142+
</Form.Message>
143+
)}
144+
</div>
145+
)}
146+
/>
147+
<Text.Caption variant='caption1' color='gray500'>
148+
텍스트는 줄 바꿈은 엔터(Enter)를 통해 구분합니다.
149+
</Text.Caption>
150+
</div>
151+
<Label labelText='답변 동의 여부' className='mb-40'>
152+
<Form.Checkbox
153+
variant='checkbox'
154+
name='isComment'
155+
label='다른 분들의 답변을 받아보시겠어요?'
156+
className='text-body2 font-medium'
157+
/>
158+
</Label>
159+
<div className='flex justify-end gap-10'>
160+
<Link variant='outlined' href='/community'>
161+
취소
162+
</Link>
163+
<Button type='submit'>수정하기</Button>
164+
</div>
165+
</Form>
166+
</Container>
167+
)
168+
}

src/app/(pages)/community/[id]/page.tsx

Lines changed: 71 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -10,80 +10,41 @@ import {
1010
IcHeart,
1111
IcShare,
1212
} from '@/assets/IconList'
13-
import { CommunityDetail } from '@/types/api/Community.types'
13+
import { communityCategoryToLabelMap } from '@/constants/stateToLabelMaps'
14+
import { GetCommunityDetailResponse } from '@/types/api/Community.types'
1415

16+
import { Comment, CommentList } from '@/components/comment'
1517
import { Avatar } from '@/components/common/avatar'
16-
import { Button, Clickable } from '@/components/common/button'
18+
import { Button, Clickable, Link } from '@/components/common/button'
1719
import { Chip } from '@/components/common/chip'
1820
import { Container } from '@/components/common/containers'
1921
import { Divider } from '@/components/common/divider'
2022
import { Text } from '@/components/common/text'
2123
import { ContentViewer } from '@/components/shared/contentViewer'
24+
import { PostDeleteAlertModalContent } from '@/components/shared/modalContent'
2225

23-
const dummyCommunityDetail: CommunityDetail = {
24-
id: 1,
25-
writer: {
26-
id: 1,
27-
nickname: '개발왕김코딩',
28-
imageUrl: 'https://picsum.photos/200',
29-
},
30-
views: 128,
31-
answers: 5,
32-
likes: 23,
33-
createdAt: '2024-03-15T09:00:00Z',
34-
updatedAt: '2024-03-15T10:30:00Z',
35-
communityCategory: 'SKILL',
36-
communityTitle: 'React 커스텀 훅에서 타입 에러가 발생합니다.',
37-
communityContent: `<h2>문제 상황</h2>
38-
<p>React 커스텀 훅을 만들면서 타입 에러가 발생했습니다. 아래 코드를 봐주시면 감사하겠습니다.</p>
26+
import { useCommunity, useDeleteCommunity } from '@/queries/community'
3927

40-
<pre><code class="language-typescript">interface UseToggleProps {
41-
initialValue?: boolean
42-
}
43-
44-
const useToggle = ({ initialValue = false }: UseToggleProps) => {
45-
const [value, setValue] = useState(initialValue)
46-
47-
const toggle = useCallback(() => {
48-
setValue(prev => !prev)
49-
}, [])
28+
import useModalStore from '@/stores/useModalStore'
5029

51-
return {
52-
value,
53-
toggle,
54-
}
55-
}
56-
</code></pre>
30+
export default function CommunityDetailPage(): JSX.Element {
31+
const params = useParams<{ id: string }>()
32+
const communityId = Number(params.id)
5733

58-
<h2>에러 메시지</h2>
59-
<p>TypeScript 에러가 발생합니다:</p>
60-
<pre><code class="language-bash">Type '{ value: boolean; toggle: () => void; }' is missing the following properties from type 'UseToggleReturn': isOpen, onToggle ts(2739)</code></pre>
34+
const {
35+
data: communityDetail,
36+
isLoading,
37+
isError,
38+
} = useCommunity(communityId)
6139

62-
<h2>시도해본 것</h2>
63-
<ul>
64-
<li>return 타입을 명시적으로 지정해보았습니다</li>
65-
<li>interface를 사용하여 반환 타입을 정의해보았습니다</li>
66-
<li>제네릭을 사용해보았습니다</li>
67-
</ul>
40+
const isOwnPost = !!(communityId % 2)
41+
const { openModal } = useModalStore()
6842

69-
<h2>개발 환경</h2>
70-
<ul>
71-
<li>React 18.2.0</li>
72-
<li>TypeScript 5.0.4</li>
73-
<li>Next.js 13.4.1</li>
74-
</ul>
43+
const { mutate: deleteCommunity } = useDeleteCommunity(communityId)
7544

76-
<p>어떻게 해결할 수 있을까요? 도움 부탁드립니다! 🙏</p>`,
77-
isComment: true,
78-
}
45+
if (isLoading) return <div>d</div>
46+
if (isError) return <div>d</div>
7947

80-
export default function CommunityDetailPage(): JSX.Element {
81-
const params = useParams<{ id: string }>()
82-
83-
const { id } = params
84-
console.log(id)
85-
86-
const data = dummyCommunityDetail
8748
const {
8849
communityTitle,
8950
communityContent,
@@ -94,15 +55,7 @@ export default function CommunityDetailPage(): JSX.Element {
9455
likes,
9556
createdAt,
9657
isComment,
97-
} = data
98-
99-
const categoryMap = {
100-
SKILL: '기술',
101-
CAREER: '커리어',
102-
OTHER: '기타',
103-
}
104-
105-
// HTML 파싱 옵션 설정
58+
} = communityDetail as GetCommunityDetailResponse
10659

10760
return (
10861
<Container className='mx-auto my-80 flex flex-col gap-20'>
@@ -133,7 +86,7 @@ export default function CommunityDetailPage(): JSX.Element {
13386
</div>
13487
</div>
13588
<div className='mb-12'>
136-
<Chip label={categoryMap[communityCategory]} />
89+
<Chip label={communityCategoryToLabelMap[communityCategory]} />
13790
</div>
13891
<div className='mb-20'>
13992
<Text.Heading variant='heading3' as='h3' weight='700'>
@@ -173,27 +126,58 @@ export default function CommunityDetailPage(): JSX.Element {
173126
<IcShare width={24} height={24} />
174127
공유
175128
</Button>
176-
<Button
177-
variant='outlined'
178-
size='lg'
179-
borderColor='gray'
180-
textColor='gray800'
181-
>
182-
<IcEdit width={24} height={24} />
183-
수정
184-
</Button>
185-
<Button
186-
variant='outlined'
187-
size='lg'
188-
borderColor='gray'
189-
textColor='gray800'
190-
>
191-
<IcBin width={24} height={24} />
192-
삭제
193-
</Button>
129+
{isOwnPost && (
130+
<>
131+
<Link
132+
href={`/community/${communityId}/edit`}
133+
variant='outlined'
134+
size='lg'
135+
borderColor='gray'
136+
textColor='gray800'
137+
>
138+
<IcEdit width={24} height={24} />
139+
수정
140+
</Link>
141+
<Button
142+
variant='outlined'
143+
size='lg'
144+
borderColor='gray'
145+
textColor='gray800'
146+
onClick={() =>
147+
openModal(
148+
<PostDeleteAlertModalContent
149+
onDelete={() => deleteCommunity(communityId)}
150+
/>
151+
)
152+
}
153+
>
154+
<IcBin width={24} height={24} />
155+
삭제
156+
</Button>
157+
</>
158+
)}
194159
</div>
195160
</section>
196161
<Divider isVertical={false} />
162+
<section className='flex flex-col gap-20'>
163+
<div className='flex gap-8'>
164+
<Avatar size={48} />
165+
<div className='flex-grow'>
166+
<Comment variant='comment' />
167+
</div>
168+
</div>
169+
<div>
170+
<CommentList
171+
writer={{
172+
id: 1,
173+
imageUrl: 'https://picsum.photos/200',
174+
nickname: '망곰쓰 귀여워..',
175+
}}
176+
content='오 같이 참여하고 싶습니다! 신청은 어디서 하면 될까요?'
177+
createdAt='2024. 09. 26 10:28'
178+
/>
179+
</div>
180+
</section>
197181
</Container>
198182
)
199183
}

0 commit comments

Comments
 (0)