2024-10-26 13:50:57 +09:00

235 lines
7.2 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* eslint-disable no-mixed-spaces-and-tabs */
import { NextPage, NextPageContext } from 'next'
import dynamic from 'next/dynamic'
import { useRouter } from 'next/router'
import { useState } from 'react'
import useClipboard from 'react-use-clipboard'
import { get } from '@utils/Query'
import { cleanObject, parseCookie, redirectTo } from '@utils/Tools'
import { getToken } from '@utils/Csrf'
import Fetch from '@utils/Fetch'
import { ParsedUrlQuery } from 'querystring'
import { Bot, BotSpec, ResponseProps, Theme, WebhookStatus } from '@types'
import NotFound from 'pages/404'
import Link from 'next/link'
import { Form, Formik } from 'formik'
import { DeveloperBot, DeveloperBotSchema } from '@utils/Yup'
import Input from '@components/Form/Input'
import Tooltip from '@components/Tooltip'
const Button = dynamic(() => import('@components/Button'))
const DeveloperLayout = dynamic(() => import('@components/DeveloperLayout'))
const DiscordAvatar = dynamic(() => import('@components/DiscordAvatar'))
const Message = dynamic(() => import('@components/Message'))
const Modal = dynamic(() => import('@components/Modal'))
const BotApplication: NextPage<BotApplicationProps> = ({ user, spec, bot, theme, csrfToken }) => {
const router = useRouter()
const [data, setData] = useState<ResponseProps<unknown>>(null)
const [modalOpened, setModalOpen] = useState(false)
const [showToken, setShowToken] = useState(false)
const [tokenCopied, setTokenCopied] = useClipboard(spec?.token, {
successDuration: 1000,
})
async function updateApplication(d: DeveloperBot) {
const res = await Fetch(`/applications/bots/${bot.id}`, {
method: 'PATCH',
body: JSON.stringify(cleanObject(d)),
})
setData(res)
}
async function resetToken() {
const res = await Fetch<{ token: string }>(`/applications/bots/${bot.id}/reset`, {
method: 'POST',
body: JSON.stringify({ token: spec.token, _csrf: csrfToken }),
})
setData(res)
return res
}
if (!user && typeof window !== 'undefined') {
localStorage.redirectTo = window.location.href
redirectTo(router, 'login')
return
}
if (!bot || !spec) return <NotFound />
return (
<DeveloperLayout enabled='applications'>
<Link href='/developers/applications' className='text-blue-500 hover:text-blue-400'>
<i className='fas fa-arrow-left' />
</Link>
<h1 className='text-3xl font-bold'> </h1>
<p className='text-gray-400'>
API에 .
</p>
<div className='pt-6 lg:flex'>
<div className='lg:w-1/5'>
<DiscordAvatar userID={bot.id} />
</div>
<div className='relative lg:w-4/5'>
<div className='mt-4'>
{!data ? (
''
) : data.code === 200 ? (
<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-inside list-disc'>
{data.errors?.map((el, i) => <li key={i}>{el}</li>)}
</ul>
</Message>
)}
</div>
<div className='grid px-6 text-left'>
<h2 className='mb-2 mt-3 text-3xl font-bold'>
{bot.name}#{bot.tag}
</h2>
<h3 className='text-lg font-semibold'> </h3>
<pre className='w-full overflow-x-scroll text-sm'>
{showToken ? spec.token : '******************'}
</pre>
<div className='pb-6 pt-3'>
<Button onClick={() => setShowToken(!showToken)}>
{showToken ? '숨기기' : '보기'}
</Button>
<Button
onClick={setTokenCopied}
className={tokenCopied ? 'bg-emerald-400 text-white' : null}
>
{tokenCopied ? '복사됨' : '복사'}
</Button>
<Button onClick={() => setModalOpen(true)}></Button>
<Modal
isOpen={modalOpened}
onClose={() => setModalOpen(false)}
dark={theme === 'dark'}
header='정말로 토큰을 재발급하시겠습니까?'
>
<p> </p>
<div className='pt-6 text-right'>
<Button
className='bg-gray-500 text-white hover:opacity-90'
onClick={() => setModalOpen(false)}
>
</Button>
<Button
onClick={async () => {
const res = await resetToken()
spec.token = res.data.token
setModalOpen(false)
}}
>
</Button>
</div>
</Modal>
</div>
<Formik
validationSchema={DeveloperBotSchema}
initialValues={{
webhookURL: spec.webhookURL || '',
_csrf: csrfToken,
}}
onSubmit={updateApplication}
>
{({ errors, touched }) => (
<Form>
<div className='mb-2'>
<h3 className='mb-1 font-bold'>
URL
{(!data || data.code !== 200) &&
spec.webhookStatus === WebhookStatus.Disabled && (
<Tooltip
direction='left'
text='웹훅 링크가 유효하지 않아 웹훅이 중지되었습니다.'
>
<span
className='pl-1 text-base font-semibold text-red-500'
role='img'
aria-label='warning'
>
</span>
</Tooltip>
)}
</h3>
<p className='mb-1 text-sm text-gray-400'>
.
<br />
,
.
<br />
{' '}
<Link
href={'/developers/docs/%EC%9B%B9%ED%9B%84%ED%81%AC'}
className='font-semibold text-blue-500 hover:text-blue-400'
>
</Link>
.
</p>
<Input name='webhookURL' placeholder='https://webhook.koreanbots.dev' />
{touched.webhookURL && errors.webhookURL ? (
<div className='mt-1 text-xs font-light text-red-500'>
{errors.webhookURL}
</div>
) : null}
</div>
<Button type='submit'>
<i className='far fa-save' />
</Button>
</Form>
)}
</Formik>
</div>
</div>
</div>
</DeveloperLayout>
)
}
interface BotApplicationProps {
user: string
spec: BotSpec
bot: Bot
csrfToken: string
theme: Theme
}
export const getServerSideProps = async (ctx: Context) => {
const parsed = parseCookie(ctx.req)
const user = (await get.Authorization(parsed?.token)) || ''
return {
props: {
user,
spec: await get.botSpec(ctx.query.id, user),
bot: await get.bot.load(ctx.query.id),
csrfToken: getToken(ctx.req, ctx.res),
},
}
}
interface Context extends NextPageContext {
query: URLQuery
}
interface URLQuery extends ParsedUrlQuery {
id: string
date: string
}
export default BotApplication