core/pages/addserver/[id].tsx
SKINMAKER 606f3cbc82
deps: update next.js to 13 (#627)
* deps: update next.js to 13

* chore: migrate to new Link component

* chore: remove future option from next.config

* chore: update react-select

* chore: enable hideSourceMaps on sentry

* chore: assert type as string

* chore: make placeholder and value absolute

* feat: set timeout for redirect

* chore: ignore ts error

* chore: add generics

* chore:

* chore: add ts comment

* feat: use dnd-kit instead of react-sortable-hoc

* fix: give absolute position to placeholder
2023-09-28 23:22:46 +09:00

232 lines
11 KiB
TypeScript

import { NextPage, NextPageContext } from 'next'
import { useRef, useState } from 'react'
import { useRouter } from 'next/router'
import dynamic from 'next/dynamic'
import Link from 'next/link'
import { NextSeo } from 'next-seo'
import { Form, Formik } from 'formik'
import HCaptcha from '@hcaptcha/react-hcaptcha'
import { get } from '@utils/Query'
import { cleanObject, getRandom, parseCookie, redirectTo } from '@utils/Tools'
import { AddServerSubmitSchema, AddServerSubmit } from '@utils/Yup'
import { serverCategories, ServerIntroList } from '@utils/Constants'
import { getToken } from '@utils/Csrf'
import Fetch from '@utils/Fetch'
import { ResponseProps, Server, ServerData, Theme, User } from '@types'
import Forbidden from '@components/Forbidden'
const CheckBox = dynamic(() => import('@components/Form/CheckBox'))
const Label = dynamic(() => import('@components/Form/Label'))
const Login = dynamic(() => import('@components/Login'))
const Input = dynamic(() => import('@components/Form/Input'))
const Divider = dynamic(() => import('@components/Divider'))
const TextArea = dynamic(() => import('@components/Form/TextArea'))
const Segment = dynamic(() => import('@components/Segment'))
const Markdown = dynamic(() => import('@components/Markdown'))
const Selects = dynamic(() => import('@components/Form/Selects'))
const Button = dynamic(() => import('@components/Button'))
const Container = dynamic(() => import('@components/Container'))
const Message = dynamic(() => import('@components/Message'))
const Captcha = dynamic(() => import('@components/Captcha'))
const AddServer:NextPage<AddServerProps> = ({ logged, user, csrfToken, server, serverData, theme }) => {
const [ data, setData ] = useState<ResponseProps<AddServerSubmit>>(null)
const [ captcha, setCaptcha ] = useState(false)
const [ touchedSumbit, setTouched ] = useState(false)
const captchaRef = useRef<HCaptcha>()
const router = useRouter()
const initialValues: AddServerSubmit = {
agree: false,
invite: '',
intro: '',
desc: `<!-- 이 설명을 지우시고 원하시는 설명을 적으셔도 좋습니다! -->
# 서버 이름
자신의 서버를 자유롭게 표현해보세요!
## ✏️ 소개
무엇이 목적인 서버인가요?
어떤 주제인가요?
## 💬 특징
- 어떤
- 특징이
- 있나요?`,
category: [],
_csrf: csrfToken,
_captcha: 'captcha'
}
function toLogin() {
localStorage.redirectTo = window.location.href
redirectTo(router, 'login')
}
async function submitServer(id: string, value: AddServerSubmit, token: string) {
const res = await Fetch<AddServerSubmit>(`/servers/${id}`, { method: 'POST', body: JSON.stringify(cleanObject<AddServerSubmit>({ ...value, _captcha: token })) })
setData(res)
}
if(!logged) return <Login>
<NextSeo title='새로운 서버 추가하기' description='자신의 서버를 한국 디스코드 리스트에 등록하세요.' openGraph={{
title:'새로운 서버 추가하기', description: '자신의 서버를 한국 디스코드 리스트에 등록하세요.'
}} />
</Login>
if(data?.data && data.code == 200) {
setTimeout(
() => redirectTo(router, `/servers/${router.query.id}`),
1_000
)
}
return (
<Container paddingTop className='py-5'>
<NextSeo title='새로운 서버 추가하기' description='자신의 서버를 한국 디스코드 리스트에 등록하세요.' openGraph={{
title:'새로운 서버 추가하기', description: '자신의 서버를 한국 디스코드 리스트에 등록하세요.'
}} />
<h1 className='text-3xl font-bold'> </h1>
<div className='mt-1 mb-5'>
, <span className='font-semibold'>{user.username}#{user.tag}</span>! <a role='button' tabIndex={0} onKeyDown={toLogin} onClick={toLogin} className='text-discord-blurple cursor-pointer outline-none'> ?</a>
</div>
{
data ? data.code == 200 && data.data ? <Message type='success'>
<h2 className='text-lg font-extrabold'> !</h2>
<p> ! !</p>
</Message> : <Message type='error'>
<h2 className='text-lg font-extrabold'>{data.message || '오류가 발생했습니다.'}</h2>
<ul className='list-disc list-inside'>
{data.errors?.map((el, n) => <li key={n}>{el}</li>)}
</ul>
</Message> : <></>
}
{
server ? <Message type='warning'>
<h2 className='text-lg font-extrabold'> .</h2>
</Message> :
!serverData ? <Message type='info'>
<h2 className='text-lg font-extrabold'> .</h2>
<p> .</p>
<p> 1 .</p>
</Message>
: serverData.admins.includes(user.id) || serverData.owner.includes(user.id) ? <Formik initialValues={initialValues}
validationSchema={AddServerSubmitSchema}
onSubmit={() => setCaptcha(true)}>
{({ errors, touched, values, isValid, setFieldTouched, setFieldValue }) => (
<Form>
<div className='py-3'>
<Message type='warning'>
<h2 className='text-lg font-extrabold'> !</h2>
<ul className='list-disc list-inside'>
<li><Link
href='/discord'
rel='noreferrer'
target='_blank'
className='text-blue-500 hover:text-blue-600'> </Link> .</li>
<li> <Link
href='/guidelines'
rel='noreferrer'
target='_blank'
className='text-blue-500 hover:text-blue-600'></Link> ?</li>
<li> <strong></strong> .</li>
<li> .</li>
<li>, API에 .</li>
</ul>
</Message>
</div>
<Label For='agree' error={errors.agree && touched.agree ? errors.agree : null} grid={false}>
<div className='flex items-center'>
<CheckBox name='agree' />
<strong className='text-sm ml-2'> , .</strong>
</div>
</Label>
<Divider />
<Label For='id' label='서버' labelDesc='등록하시는 대상 서버 입니다.'>
<p>
<strong>{serverData.name}</strong>
<br/> ID: {router.query.id}
</p>
</Label>
<Divider />
<Label For='category' label='카테고리' labelDesc='서버에 해당되는 카테고리를 선택해주세요' required error={errors.category && touched.category ? errors.category as string : null}>
<Selects options={serverCategories.map(el=> ({ label: el, value: el }))} handleChange={(value) => {
setFieldValue('category', value.map(v=> v.value))
}} handleTouch={() => setFieldTouched('category', true)} values={values.category as string[]} setValues={(value) => setFieldValue('category', value)} />
<span className='text-gray-400 mt-1 text-sm'> 3 . . <strong> .</strong></span>
</Label>
<Label For='invite' label='서버 초대코드' labelDesc='서버의 초대코드를 입력해주세요. (만료되지 않는 코드로 입력해주세요!)' error={errors.invite && touched.invite ? errors.invite : null} short required>
<div className='flex items-center'>
discord.gg/<Input name='invite' placeholder='JEh53MQ' />
</div>
</Label>
<Divider />
<Label For='intro' label='서버 소개' labelDesc='서버를 소개할 수 있는 간단한 설명을 적어주세요. (최대 60자)' error={errors.intro && touched.intro ? errors.intro : null} required>
<Input name='intro' placeholder={getRandom(ServerIntroList)} />
</Label>
<Label For='desc' label='서버 설명' labelDesc={<> ! ( 1500)<br/> !</>} error={errors.desc && touched.desc ? errors.desc : null} required>
<TextArea max={1500} name='desc' placeholder='서버에 대해 최대한 자세히 설명해주세요!' theme={theme === 'dark' ? 'dark' : 'light'} value={values.desc} setValue={(value) => setFieldValue('desc', value)} />
</Label>
<Label For='preview' label='설명 미리보기' labelDesc='다음 결과는 실제와 다를 수 있습니다.'>
<Segment>
<Markdown text={values.desc} />
</Segment>
</Label>
<Divider />
<p className='text-base mt-2 mb-5'>
<span className='text-red-500 font-semibold'> *</span> =
</p>
{
captcha ? <Captcha ref={captchaRef} dark={theme === 'dark'} onVerify={(token) => {
submitServer(router.query.id as string, values, token)
window.scrollTo({ top: 0 })
setCaptcha(false)
captchaRef?.current?.resetCaptcha()
}} /> : <>
{
touchedSumbit && !isValid && <div className='my-1 text-red-500 text-xs font-light'> . .</div>
}
<Button type='submit' onClick={() => {
setTouched(true)
if(!isValid) window.scrollTo({ top: 0 })
} }>
<>
<i className='far fa-paper-plane'/>
</>
</Button>
</>
}
</Form>
)}
</Formik>
: <Forbidden />
}
</Container>
)
}
export const getServerSideProps = async (ctx: NextPageContext) => {
const parsed = parseCookie(ctx.req)
const user = await get.Authorization(parsed?.token)
const server = (await get.server.load(ctx.query.id as string)) || null
const serverData = (await get.serverData(ctx.query.id as string)) || null
return { props: {
logged: !!user, user: await get.user.load(user || ''),
csrfToken: getToken(ctx.req, ctx.res),
server,
serverData: (+new Date() - +new Date(serverData?.updatedAt)) < 2 * 60 * 1000 ? serverData : null
} }
}
interface AddServerProps {
logged: boolean
user: User
csrfToken: string
server: Server | null
serverData: ServerData | null
theme: Theme
}
export default AddServer