new.tsx (4507B)
1 import { SubmitButton } from '@/components/elements/Button' 2 import Title from '@/components/elements/Title' 3 import Field from '@/components/form/Field' 4 import Fields from '@/components/form/Fields' 5 import Form from '@/components/form/Form' 6 import Container from '@/components/layout/Container' 7 import Hero from '@/components/layout/Hero' 8 import Section from '@/components/layout/Section' 9 import { isApiError } from '@/lib/apierror' 10 import { ERR_CODE_DUPLICATED_SLUG, ERR_CODE_INVALID_SLUG, ERR_CODE_INVALID_TITLE } from '@/lib/error_codes' 11 import { generateRandomName } from '@/lib/random_name' 12 import { useRouter } from 'next/router' 13 import { useCallback, useEffect, useState } from 'react' 14 15 export default function WikiNewPage () { 16 return ( 17 <> 18 <Hero> 19 <Title kind="headline">새 위키 만들기</Title> 20 </Hero> 21 22 <Container> 23 <Section> 24 <NewWikiForm/> 25 </Section> 26 </Container> 27 </> 28 ) 29 } 30 31 function NewWikiForm () { 32 const router = useRouter() 33 34 const [title, setTitle] = useState('') 35 const [slug, setSlug] = useState('') 36 const [description, setDescription] = useState('') 37 38 const [titleError, setTitleError] = useState<string | null>(null) 39 const [slugError, setSlugError] = useState<string | null>(null) 40 const [descriptionError, setDescriptionError] = useState<string | null>(null) 41 42 const [isLoading, setLoading] = useState(false) 43 44 const handleSubmit = useCallback(() => { 45 const handleError = (status: number, err: any) => { 46 setLoading(false) 47 48 if (!isApiError(err)) { 49 setTitleError('알 수 없는 오류가 발생했습니다.') 50 return 51 } 52 53 switch (status) { 54 case 400: 55 switch (err.code) { 56 case ERR_CODE_INVALID_SLUG: 57 setSlugError('경로가 유효하지 않습니다.') 58 return 59 60 case ERR_CODE_INVALID_TITLE: 61 setTitleError('제목이 유효하지 않습니다.') 62 return 63 64 case ERR_CODE_DUPLICATED_SLUG: 65 setSlugError('이미 사용중인 경로입니다.') 66 return 67 } 68 break 69 70 case 401: 71 setTitleError('로그인이 필요합니다.') 72 return 73 } 74 75 setTitleError('알 수 없는 오류가 발생했습니다.') 76 } 77 78 setLoading(true) 79 setTitleError(null) 80 setSlugError(null) 81 setDescriptionError(null) 82 83 !(async () => { 84 const res = await fetch('/api/wiki', { 85 method: 'POST', 86 headers: { 87 'Content-Type': 'application/json', 88 }, 89 body: JSON.stringify({ title, slug, description }), 90 }) 91 92 if (!res.ok) { 93 const err = await res.json() 94 handleError(res.status, err) 95 return 96 } 97 98 await router.push(`/wiki/${slug}`) 99 })().catch(err => { 100 console.error(err) 101 handleError(err.status, err) 102 }) 103 }, [router, title, slug, description]) 104 105 // Random slug generation 106 107 const [randomSlug, setRandomSlug] = useState('') 108 109 const applyRandomSlug = useCallback(() => { 110 setSlug(randomSlug) 111 setRandomSlug(generateRandomName()) 112 }, [randomSlug]) 113 114 useEffect(() => { 115 setRandomSlug(generateRandomName()) 116 }, []) 117 118 return ( 119 <Form onSubmit={handleSubmit}> 120 <Fields> 121 <Field 122 type="text" 123 placeholder="이름" 124 value={title} 125 onValueChange={setTitle} 126 message={titleError ?? undefined} 127 color={titleError != null ? 'error' : undefined} 128 disabled={isLoading} 129 /> 130 131 <Field 132 type="text" 133 placeholder="경로" 134 value={slug} 135 onValueChange={setSlug} 136 message={slugError ?? undefined} 137 color={slugError != null ? 'error' : undefined} 138 disabled={isLoading} 139 /> 140 141 <p>URL의 일부로써 사용되며, 전체 위키에 대해 고유해야 합니다.<br/> 142 예를 들어, <a onClick={applyRandomSlug}>'{randomSlug}'</a> 같은 걸 의미합니다.</p> 143 144 <Field 145 type="text" 146 placeholder="짧은 설명" 147 value={description} 148 onValueChange={setDescription} 149 message={descriptionError ?? undefined} 150 color={descriptionError != null ? 'error' : undefined} 151 disabled={isLoading} 152 /> 153 154 <SubmitButton 155 color="primary" 156 value="만들기" 157 disabled={isLoading} 158 /> 159 </Fields> 160 </Form> 161 ) 162 }