Skip to content

Commit 5a4e3d3

Browse files
authored
[#263] portfolio api 연동 (#265)
* [#263] ✨ portfolio list * [#263] ✨ create portfolio api
1 parent 46e2293 commit 5a4e3d3

File tree

7 files changed

+446
-154
lines changed

7 files changed

+446
-154
lines changed

src/app/(pages)/portfolio/new/page.tsx

Lines changed: 79 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
'use client'
22

3-
import { useRouter } from 'next/navigation'
4-
5-
import { useState } from 'react'
63
import { Controller, useForm } from 'react-hook-form'
74

85
import { PORTFOLIO_EDITOR_CONTENT } from '@/constants/tiptap'
96
import { TipTapEditor } from '@/lib/tiptap/TipTapEditor'
107
import { CreatePortfolioRequest } from '@/types/api/Portfolio.types'
11-
12-
import { authProxy } from '@/app/api/auth/authProxy'
8+
import { zodResolver } from '@hookform/resolvers/zod'
9+
import { z } from 'zod'
1310

1411
import { Button, Link } from '@/components/common/button'
1512
import { Container } from '@/components/common/containers'
@@ -25,9 +22,45 @@ import {
2522
TechStackSelect,
2623
} from '@/components/shared/select'
2724

25+
import { useCreatePortfolio } from '@/queries/portfolio'
26+
27+
const createPortfolioSchema = z.object({
28+
request: z.object({
29+
portTitle: z.string().nonempty('제목을 입력해주세요.'),
30+
portContent: z.string().nonempty('내용을 입력해주세요.'),
31+
portPosition: z.string().nonempty('포지션을 입력해주세요.'),
32+
techStacks: z
33+
.array(z.string())
34+
.max(10, '태그는 최대 10개까지 입력할 수 있습니다.'),
35+
tags: z
36+
.array(z.string())
37+
.max(10, '태그는 최대 10개까지 입력할 수 있습니다.')
38+
.optional(),
39+
links: z
40+
.array(z.string())
41+
.max(10, '태그는 최대 10개까지 입력할 수 있습니다.')
42+
.optional(),
43+
educations: z
44+
.array(z.string())
45+
.max(10, '태그는 최대 10개까지 입력할 수 있습니다.')
46+
.optional(),
47+
awards: z
48+
.array(z.string())
49+
.max(10, '태그는 최대 10개까지 입력할 수 있습니다.')
50+
.optional(),
51+
careers: z
52+
.array(z.string())
53+
.max(10, '태그는 최대 10개까지 입력할 수 있습니다.')
54+
.optional(),
55+
}),
56+
})
57+
2858
export default function CreatePortfolioPage(): JSX.Element {
59+
const { mutate } = useCreatePortfolio()
60+
2961
const methods = useForm<CreatePortfolioRequest>({
3062
mode: 'onBlur',
63+
resolver: zodResolver(createPortfolioSchema),
3164
defaultValues: {
3265
request: {
3366
portTitle: '',
@@ -36,56 +69,18 @@ export default function CreatePortfolioPage(): JSX.Element {
3669
educations: [],
3770
awards: [],
3871
careers: [],
39-
links: [{ type: undefined, url: undefined }],
72+
links: [],
4073
tags: [],
4174
},
4275
},
4376
})
44-
const { handleSubmit, control, watch } = methods
45-
const router = useRouter()
46-
const [isLoading, setIsLoading] = useState(false)
4777

48-
const updateProfile = async (
49-
data: CreatePortfolioRequest,
50-
profileImage?: File
51-
): Promise<ApiResponse> => {
52-
const formData = new FormData()
53-
formData.append(
54-
'request',
55-
new Blob([JSON.stringify(data)], { type: 'application/json' })
56-
)
78+
const { handleSubmit, control } = methods
5779

58-
if (profileImage) {
59-
formData.append('profileImage', profileImage)
60-
}
80+
const onSubmit = (data: CreatePortfolioRequest) => {
81+
const formdata = new FormData()
6182

62-
return await authProxy
63-
.post('v1/portfolio', {
64-
body: formData,
65-
headers: {
66-
'Content-Type': 'multipart/form-data',
67-
},
68-
})
69-
.json()
70-
}
71-
72-
const onSubmit = async (data: CreatePortfolioRequest) => {
73-
setIsLoading(true)
74-
try {
75-
const file = data.file instanceof FileList ? data.file[0] : undefined
76-
const response = await updateProfile(data, file)
77-
78-
if (response.isSuccess) {
79-
router.push('/portfolio')
80-
} else {
81-
alert(response.message || '포트폴리오 등록에 실패했습니다.')
82-
}
83-
} catch (error) {
84-
console.error('포트폴리오 등록 중 오류 발생:', error)
85-
alert('포트폴리오 등록 중 오류가 발생했습니다.')
86-
} finally {
87-
setIsLoading(false)
88-
}
83+
mutate(data)
8984
}
9085

9186
return (
@@ -110,50 +105,62 @@ export default function CreatePortfolioPage(): JSX.Element {
110105
<Label required labelText='포지션' />
111106
<PositionSelect name='request.portPosition' />
112107
</div>
113-
<div className='mb-20 flex flex-col gap-4'>
114-
<Label required labelText='링크' />
115-
<LinkSelect name={'request.links'} />
116-
</div>
117108
<div className='mb-20 flex flex-col gap-4'>
118109
<Label required labelText='기술 스택' />
119110
<TechStackSelect name='request.techStacks' />
120111
</div>
121-
<div className='mb-20 flex flex-col gap-4'>
122-
<Label required labelText='학력' />
123-
<EducationSelect name='request.educations' />
124-
</div>
125-
<div className='mb-20 flex flex-col gap-4'>
126-
<Label required labelText='수상 및 기타' />
127-
<AwardSelect name='request.awards' />
128-
</div>
129-
<div className='mb-20 flex flex-col gap-4'>
130-
<Label required labelText='경력' />
131-
<CareerSelect name='request.careers' />
132-
</div>
133112
<div className='mb-20 flex flex-col gap-4'>
134113
<Label required labelText='내용' />
135114
<Controller
136115
name='request.portContent'
137116
control={control}
138117
defaultValue={''}
139-
render={({ field: { onChange } }) => (
140-
<TipTapEditor
141-
content={PORTFOLIO_EDITOR_CONTENT}
142-
onChange={onChange}
143-
/>
118+
render={({ field: { onChange }, fieldState: { error } }) => (
119+
<div>
120+
<TipTapEditor
121+
content={PORTFOLIO_EDITOR_CONTENT}
122+
onChange={onChange}
123+
/>
124+
{error?.message && (
125+
<Form.Message hasError={!!error}>
126+
{error.message}
127+
</Form.Message>
128+
)}
129+
</div>
144130
)}
145131
/>
146132
<Text.Caption variant='caption1' color='gray500'>
147133
텍스트는 줄 바꿈은 엔터(Enter)를 통해 구분합니다.
148134
</Text.Caption>
149135
</div>
136+
<Label required labelText='태그' className='mb-20'>
137+
<Form.TagInput
138+
name='request.tags'
139+
placeholder='태그를 입력하고 엔터를 눌러주세요. 태그 최대 개수는 10개입니다.'
140+
/>
141+
</Label>
142+
<div className='mb-20 flex flex-col gap-4'>
143+
<Label labelText='링크' />
144+
<LinkSelect name={'request.links'} />
145+
</div>
146+
147+
<div className='mb-20 flex flex-col gap-4'>
148+
<Label labelText='학력' />
149+
<EducationSelect name='request.educations' />
150+
</div>
151+
<div className='mb-20 flex flex-col gap-4'>
152+
<Label labelText='수상 및 기타' />
153+
<AwardSelect name='request.awards' />
154+
</div>
155+
<div className='mb-20 flex flex-col gap-4'>
156+
<Label labelText='경력' />
157+
<CareerSelect name='request.careers' />
158+
</div>
150159
<div className='flex justify-end gap-10'>
151160
<Link variant='outlined' href='/team'>
152161
취소
153162
</Link>
154-
<Button type='submit' disabled={isLoading}>
155-
{isLoading ? '등록 중...' : '등록하기'}
156-
</Button>
163+
<Button type='submit'>등록하기</Button>
157164
</div>
158165
</Form>
159166
</Container>

0 commit comments

Comments
 (0)