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
This commit is contained in:
SKINMAKER 2023-09-28 23:22:46 +09:00 committed by GitHub
parent 4ea5881b17
commit 606f3cbc82
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 2827 additions and 2575 deletions

View File

@ -5,16 +5,18 @@ const DiscordAvatar = dynamic(() => import('@components/DiscordAvatar'))
const ServerIcon = dynamic(() => import('@components/ServerIcon')) const ServerIcon = dynamic(() => import('@components/ServerIcon'))
const Application: React.FC<ApplicationProps> = ({ type, id, name }) => { const Application: React.FC<ApplicationProps> = ({ type, id, name }) => {
return <Link href={`/developers/applications/${type + 's'}/${id}`}> return (
<div className='relative px-2 py-4 text-center dark:bg-discord-black bg-little-white rounded-lg cursor-pointer transform hover:-translate-y-1 transition duration-100 ease-in'> <Link href={`/developers/applications/${type + 's'}/${id}`} legacyBehavior>
{ <div className='relative px-2 py-4 text-center dark:bg-discord-black bg-little-white rounded-lg cursor-pointer transform hover:-translate-y-1 transition duration-100 ease-in'>
type === 'bot' ? {
<DiscordAvatar userID={id} className='px-2 w-full rounded-xl' /> : type === 'bot' ?
<ServerIcon id={id} className='px-2 w-full rounded-xl' /> <DiscordAvatar userID={id} className='px-2 w-full rounded-xl' /> :
} <ServerIcon id={id} className='px-2 w-full rounded-xl' />
<h2 className='pt-2 whitespace-nowrap text-xl font-medium truncate'>{name}</h2> }
</div> <h2 className='pt-2 whitespace-nowrap text-xl font-medium truncate'>{name}</h2>
</Link> </div>
</Link>
)
} }

View File

@ -10,107 +10,113 @@ const Tag = dynamic(() => import('@components/Tag'))
const DiscordAvatar = dynamic(() => import('@components/DiscordAvatar')) const DiscordAvatar = dynamic(() => import('@components/DiscordAvatar'))
const BotCard: React.FC<BotCardProps> = ({ manage = false, bot }) => { const BotCard: React.FC<BotCardProps> = ({ manage = false, bot }) => {
return <div className='min-w-0 container mb-16 transform hover:-translate-y-1 transition duration-100 ease-in cursor-pointer'> return (
<div className='relative'> <div className='min-w-0 container mb-16 transform hover:-translate-y-1 transition duration-100 ease-in cursor-pointer'>
<div className='container mx-auto'> <div className='relative'>
<div className='h-full'> <div className='container mx-auto'>
<div <div className='h-full'>
className='relative mx-auto h-full text-black dark:text-white dark:bg-discord-black bg-little-white rounded-2xl shadow-xl' <div
style={ className='relative mx-auto h-full text-black dark:text-white dark:bg-discord-black bg-little-white rounded-2xl shadow-xl'
checkBotFlag(bot.flags, 'trusted') && bot.banner style={
? { checkBotFlag(bot.flags, 'trusted') && bot.banner
background: `linear-gradient(to right, rgba(34, 36, 38, 0.68), rgba(34, 36, 38, 0.68)), url("${bot.banner}") center top / cover no-repeat`, ? {
color: 'white', background: `linear-gradient(to right, rgba(34, 36, 38, 0.68), rgba(34, 36, 38, 0.68)), url("${bot.banner}") center top / cover no-repeat`,
} color: 'white',
: {} }
} : {}
> }
<Link href={makeBotURL(bot)}> >
<div> <Link href={makeBotURL(bot)} legacyBehavior>
<div className='flex flex-col'>
<div className='flex'>
<div className='w-3/5 flex justify-start'>
<DiscordAvatar
size={128}
userID={bot.id}
alt='Avatar'
className='absolute -left-2 -top-8 mx-auto w-32 h-32 bg-white rounded-full'
/>
</div>
<div className='grid grid-cols-1 pr-5 pt-5 w-2/5'>
<Tag
text={
<>
<i className='fas fa-heart text-red-600' /> {formatNumber(bot.votes)}
</>
}
dark
/>
<Tag
blurple
text={bot.servers ? <>{formatNumber(bot.servers)} </> : 'N/A'}
dark
/>
</div>
</div>
</div>
<div className='mt-3 px-4 h-16'>
<h2 className='px-1 text-sm'>
<i className={`fas fa-circle text-${Status[bot.status]?.color}`} />
{Status[bot.status]?.text}
</h2>
<h1 className='mb-3 text-left text-xl sm:text-2xl font-bold truncate'>{bot.name}</h1>
</div>
<p className='mb-10 px-4 h-6 text-left text-gray-400 text-sm'>
{bot.intro}
</p>
<div> <div>
<div className='category flex flex-wrap px-2'> <div className='flex flex-col'>
{bot.category.slice(0, 3).map(el => ( <div className='flex'>
<Tag key={el} text={el} href={`/bots/categories/${el}`} dark /> <div className='w-3/5 flex justify-start'>
))}{' '} <DiscordAvatar
{bot.category.length > 3 && <Tag text={`+${bot.category.length - 3}`} dark />} size={128}
userID={bot.id}
alt='Avatar'
className='absolute -left-2 -top-8 mx-auto w-32 h-32 bg-white rounded-full'
/>
</div>
<div className='grid grid-cols-1 pr-5 pt-5 w-2/5'>
<Tag
text={
<>
<i className='fas fa-heart text-red-600' /> {formatNumber(bot.votes)}
</>
}
dark
/>
<Tag
blurple
text={bot.servers ? <>{formatNumber(bot.servers)} </> : 'N/A'}
dark
/>
</div>
</div>
</div>
<div className='mt-3 px-4 h-16'>
<h2 className='px-1 text-sm'>
<i className={`fas fa-circle text-${Status[bot.status]?.color}`} />
{Status[bot.status]?.text}
</h2>
<h1 className='mb-3 text-left text-xl sm:text-2xl font-bold truncate'>{bot.name}</h1>
</div>
<p className='mb-10 px-4 h-6 text-left text-gray-400 text-sm'>
{bot.intro}
</p>
<div>
<div className='category flex flex-wrap px-2'>
{bot.category.slice(0, 3).map(el => (
<Tag key={el} text={el} href={`/bots/categories/${el}`} dark />
))}{' '}
{bot.category.length > 3 && <Tag text={`+${bot.category.length - 3}`} dark />}
</div>
</div> </div>
</div> </div>
</div> </Link>
</Link> <Divider />
<Divider /> <div className='w-full'>
<div className='w-full'> <div className='flex justify-evenly'>
<div className='flex justify-evenly'> <Link
<Link href={makeBotURL(bot)}> href={makeBotURL(bot)}
<a className='py-3 w-full text-center text-koreanbots-blue hover:text-white text-sm font-bold hover:bg-koreanbots-blue rounded-bl-2xl hover:shadow-lg transition duration-100 ease-in'> className='py-3 w-full text-center text-koreanbots-blue hover:text-white text-sm font-bold hover:bg-koreanbots-blue rounded-bl-2xl hover:shadow-lg transition duration-100 ease-in'>
</a>
</Link>
{manage ? (
<Link href={`/bots/${bot.id}/edit`}>
<a className='py-3 w-full text-center text-emerald-500 hover:text-white text-sm font-bold hover:bg-emerald-500 rounded-br-2xl hover:shadow-lg transition duration-100 ease-in'>
</a>
</Link> </Link>
) : bot.state !== 'ok' ? <a {manage ? (
className='py-3 w-full text-center text-discord-blurple text-sm font-bold rounded-br-2xl hover:shadow-lg transition duration-100 ease-in opacity-50 cursor-default select-none' <Link
> href={`/bots/${bot.id}/edit`}
className='py-3 w-full text-center text-emerald-500 hover:text-white text-sm font-bold hover:bg-emerald-500 rounded-br-2xl hover:shadow-lg transition duration-100 ease-in'>
</a> :
<a
href={
makeBotURL(bot) + '/invite' </Link>
} ) : bot.state !== 'ok' ? <a
rel='noopener noreferrer' className='py-3 w-full text-center text-discord-blurple text-sm font-bold rounded-br-2xl hover:shadow-lg transition duration-100 ease-in opacity-50 cursor-default select-none'
target='_blank'
className='py-3 w-full text-center text-discord-blurple hover:text-white text-sm font-bold hover:bg-discord-blurple rounded-br-2xl hover:shadow-lg transition duration-100 ease-in'
> >
</a> </a> :
} <a
href={
makeBotURL(bot) + '/invite'
}
rel='noopener noreferrer'
target='_blank'
className='py-3 w-full text-center text-discord-blurple hover:text-white text-sm font-bold hover:bg-discord-blurple rounded-br-2xl hover:shadow-lg transition duration-100 ease-in'
>
</a>
}
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> )
} }

View File

@ -9,13 +9,13 @@ const Button: React.FC<ButtonProps> = ({
disabled=false, disabled=false,
onClick, onClick,
}) => { }) => {
return href ? <Link href={!disabled && href}> return href ? <Link
<a href={!disabled && href}
className={`cursor-pointer rounded-md px-4 py-2 transition duration-300 ease select-none outline-none foucs:outline-none mr-1.5 ${className ?? className={`cursor-pointer rounded-md px-4 py-2 transition duration-300 ease select-none outline-none foucs:outline-none mr-1.5 ${className ??
'bg-discord-blurple hover:opacity-80 dark:bg-very-black dark:hover:bg-discord-dark-hover text-white'}`} 'bg-discord-blurple hover:opacity-80 dark:bg-very-black dark:hover:bg-discord-dark-hover text-white'}`}>
>
{children} {children}
</a>
</Link> </Link>
: onClick ? <button : onClick ? <button
type={disabled ? 'button' : type} type={disabled ? 'button' : type}

View File

@ -12,89 +12,98 @@ const Divider = dynamic(() => import('@components/Divider'))
const DeveloperLayout: React.FC<DeveloperLayout> = ({ children, enabled, docs, currentDoc }:DeveloperLayout) => { const DeveloperLayout: React.FC<DeveloperLayout> = ({ children, enabled, docs, currentDoc }:DeveloperLayout) => {
const [ navbarEnabled, setNavbarOpen ] = useState(false) const [ navbarEnabled, setNavbarOpen ] = useState(false)
return <div className='flex min-h-screen'> return (
<NextSeo title='한디리 개발자' description='한국 디스코드 리스트 API를 활용하여 봇에 다양한 기능을 추가해보세요.' openGraph={{ <div className='flex min-h-screen'>
title:'한디리 개발자', <NextSeo title='한디리 개발자' description='한국 디스코드 리스트 API를 활용하여 봇에 다양한 기능을 추가해보세요.' openGraph={{
description:'한국 디스코드 리스트 API를 활용하여 봇에 다양한 기능을 추가해보세요.' title:'한디리 개발자',
}} /> description:'한국 디스코드 리스트 API를 활용하여 봇에 다양한 기능을 추가해보세요.'
<div className='block lg:hidden h-screen relative'> }} />
<div className='w-18 pt-20 px-2 h-full text-center bg-little-white dark:bg-discord-black fixed'> <div className='block lg:hidden h-screen relative'>
<ul className='text-gray-600 dark:text-gray-300'> <div className='w-18 pt-20 px-2 h-full text-center bg-little-white dark:bg-discord-black fixed'>
<li className={`cursor-pointer py-2 px-4 mb-2 rounded-md ${enabled === 'applications' ? 'bg-discord-blurple text-white' : 'hover:text-gray-500 dark:hover:text-white'}`}> <ul className='text-gray-600 dark:text-gray-300'>
<Link href='/developers/applications'><i className='fas fa-robot'/></Link> <li className={`cursor-pointer py-2 px-4 mb-2 rounded-md ${enabled === 'applications' ? 'bg-discord-blurple text-white' : 'hover:text-gray-500 dark:hover:text-white'}`}>
</li> <Link href='/developers/applications' legacyBehavior><i className='fas fa-robot'/></Link>
<li className={`cursor-pointer py-2 px-4 my-2 rounded-md ${enabled === 'docs' ? 'bg-discord-blurple text-white' : 'hover:text-gray-500 dark:hover:text-white'}`}> </li>
<Link href='/developers/docs'><i className='fas fa-book'/></Link> <li className={`cursor-pointer py-2 px-4 my-2 rounded-md ${enabled === 'docs' ? 'bg-discord-blurple text-white' : 'hover:text-gray-500 dark:hover:text-white'}`}>
</li> <Link href='/developers/docs' legacyBehavior><i className='fas fa-book'/></Link>
</li>
{
enabled === 'docs' && <>
<Divider />
<li className='cursor-pointer py-2 px-4 my-2 rounded-md hover:text-gray-500 dark:hover:text-white' onKeyDown={() => setNavbarOpen(true)} onClick={() => setNavbarOpen(true)}>
<i className='fas fa-bars'/>
</li></>
}
</ul>
</div>
</div>
<div className={`${navbarEnabled ? 'block' : 'hidden'} lg:block relative`}>
<div className='bg-little-white dark:bg-discord-black pt-20 px-6 fixed h-screen w-screen lg:w-60 overflow-y-auto'>
<ul className='text-base text-gray-600 dark:text-gray-300 mb-6 hidden lg:block'>
<li className='cursor-pointer py-2 px-4 rounded-md hover:text-gray-500 dark:hover:text-white lg:hidden' onKeyDown={() => setNavbarOpen(false)} onClick={() => setNavbarOpen(false)}></li>
<Divider className='lg:hidden' />
<Link href='/developers/applications' legacyBehavior>
<li className={`cursor-pointer py-2 px-4 rounded-md ${enabled === 'applications' ? 'bg-discord-blurple text-white' : 'hover:text-gray-500 dark:hover:text-white'}`}>
</li>
</Link>
<Link href='/developers/docs' legacyBehavior>
<li className={`cursor-pointer py-2 px-4 rounded-md ${enabled === 'docs' ? 'bg-discord-blurple text-white' : 'hover:text-gray-500 dark:hover:text-white'}`}>
</li>
</Link>
</ul>
{ {
enabled === 'docs' && <> enabled === 'docs' && <>
<Divider /> <Divider className='hidden lg:block' />
<li className='cursor-pointer py-2 px-4 my-2 rounded-md hover:text-gray-500 dark:hover:text-white' onKeyDown={() => setNavbarOpen(true)} onClick={() => setNavbarOpen(true)}> <ul className='text-sm text-gray-600 dark:text-gray-300 px-0.5 lg:mt-6'>
<i className='fas fa-bars'/> <li onClick={() => setNavbarOpen(false)} className='lg:hidden cursor-pointer py-1 px-4 rounded-md mb-2'>
</li></> <i className='fas fa-times' />
</li>
<Divider className='lg:hidden' />
{
docs?.map(el => {
if(el.list) return (
<div key={el.name} className='mt-2'>
<span className='text-gray-600 dark:text-gray-100 font-bold mb-1'>{el.name}</span>
<ul className='text-sm py-3'>
{
el.list.map(e =>
<Link
key={e.name}
href={`/developers/docs/${el.name}/${e.name}`}
legacyBehavior>
<li onClick={() => setNavbarOpen(false)} className={`cursor-pointer px-4 py-2 rounded-md ${currentDoc === e.name ? 'bg-discord-blurple text-white' : 'hover:text-gray-500 dark:hover:text-white'}`}>
{e.name}
</li>
</Link>
)
}
</ul>
</div>
)
return (
<Link key={el.name} href={`/developers/docs/${el.name}`} legacyBehavior>
<li onClick={() => setNavbarOpen(false)} className={`cursor-pointer py-2 px-4 rounded-md ${currentDoc === el.name ? 'bg-discord-blurple text-white' : 'hover:text-gray-500 dark:hover:text-white'}`}>
{el.name}
</li>
</Link>
)
})
}
</ul>
</>
} }
</ul> </div>
</div>
<div className='w-full py-28 lg:pl-60 pl-16'>
<Container>
{children}
</Container>
</div> </div>
</div> </div>
<div className={`${navbarEnabled ? 'block' : 'hidden'} lg:block relative`}> )
<div className='bg-little-white dark:bg-discord-black pt-20 px-6 fixed h-screen w-screen lg:w-60 overflow-y-auto'>
<ul className='text-base text-gray-600 dark:text-gray-300 mb-6 hidden lg:block'>
<li className='cursor-pointer py-2 px-4 rounded-md hover:text-gray-500 dark:hover:text-white lg:hidden' onKeyDown={() => setNavbarOpen(false)} onClick={() => setNavbarOpen(false)}></li>
<Divider className='lg:hidden' />
<Link href='/developers/applications'>
<li className={`cursor-pointer py-2 px-4 rounded-md ${enabled === 'applications' ? 'bg-discord-blurple text-white' : 'hover:text-gray-500 dark:hover:text-white'}`}>
</li>
</Link>
<Link href='/developers/docs'>
<li className={`cursor-pointer py-2 px-4 rounded-md ${enabled === 'docs' ? 'bg-discord-blurple text-white' : 'hover:text-gray-500 dark:hover:text-white'}`}>
</li>
</Link>
</ul>
{
enabled === 'docs' && <>
<Divider className='hidden lg:block' />
<ul className='text-sm text-gray-600 dark:text-gray-300 px-0.5 lg:mt-6'>
<li onClick={() => setNavbarOpen(false)} className='lg:hidden cursor-pointer py-1 px-4 rounded-md mb-2'>
<i className='fas fa-times' />
</li>
<Divider className='lg:hidden' />
{
docs?.map(el => {
if(el.list) return <div key={el.name} className='mt-2'>
<span className='text-gray-600 dark:text-gray-100 font-bold mb-1'>{el.name}</span>
<ul className='text-sm py-3'>
{
el.list.map(e =>
<Link key={e.name} href={`/developers/docs/${el.name}/${e.name}`}>
<li onClick={() => setNavbarOpen(false)} className={`cursor-pointer px-4 py-2 rounded-md ${currentDoc === e.name ? 'bg-discord-blurple text-white' : 'hover:text-gray-500 dark:hover:text-white'}`}>
{e.name}
</li>
</Link>
)
}
</ul>
</div>
return <Link key={el.name} href={`/developers/docs/${el.name}`}>
<li onClick={() => setNavbarOpen(false)} className={`cursor-pointer py-2 px-4 rounded-md ${currentDoc === el.name ? 'bg-discord-blurple text-white' : 'hover:text-gray-500 dark:hover:text-white'}`}>
{el.name}
</li>
</Link>
})
}
</ul>
</>
}
</div>
</div>
<div className='w-full py-28 lg:pl-60 pl-16'>
<Container>
{children}
</Container>
</div>
</div>
} }
interface DeveloperLayout { interface DeveloperLayout {

View File

@ -16,9 +16,9 @@ const Footer: React.FC<FooterProps> = ({ theme, setTheme }) => {
<span className='text-base'>2020-2023 , All rights reserved.</span> <span className='text-base'>2020-2023 , All rights reserved.</span>
<div className='text-2xl flex space-x-1'> <div className='text-2xl flex space-x-1'>
<Link href='/discord'> <Link href='/discord'>
<a>
<i className='fab fa-discord inline-block w-full' /> <i className='fab fa-discord inline-block w-full' />
</a>
</Link> </Link>
<a href='https://github.com/koreanbots'> <a href='https://github.com/koreanbots'>
<i className='fab fa-github inline-block w-full' /> <i className='fab fa-github inline-block w-full' />
@ -33,18 +33,18 @@ const Footer: React.FC<FooterProps> = ({ theme, setTheme }) => {
<h2 className='text-koreanbots-blue text-base font-bold'> </h2> <h2 className='text-koreanbots-blue text-base font-bold'> </h2>
<ul className='text-sm'> <ul className='text-sm'>
<li> <li>
<Link href='/about'> <Link href='/about' className='hover:text-gray-300'>
<a className='hover:text-gray-300'></a>
</Link> </Link>
</li> </li>
<li> <li>
<Link href='/developers'> <Link href='/developers' className='hover:text-gray-300'>
<a className='hover:text-gray-300'></a>
</Link> </Link>
</li> </li>
<li> <li>
<Link href='/security'> <Link href='/security' className='hover:text-gray-300'>
<a className='hover:text-gray-300'> </a>
</Link> </Link>
</li> </li>
</ul> </ul>
@ -53,23 +53,23 @@ const Footer: React.FC<FooterProps> = ({ theme, setTheme }) => {
<h2 className='text-koreanbots-blue text-base font-bold'></h2> <h2 className='text-koreanbots-blue text-base font-bold'></h2>
<ul className='text-sm'> <ul className='text-sm'>
<li> <li>
<Link href='/tos'> <Link href='/tos' className='hover:text-gray-300'>
<a className='hover:text-gray-300'> </a>
</Link> </Link>
</li> </li>
<li> <li>
<Link href='/privacy'> <Link href='/privacy' className='hover:text-gray-300'>
<a className='hover:text-gray-300'></a>
</Link> </Link>
</li> </li>
<li> <li>
<Link href='/guidelines'> <Link href='/guidelines' className='hover:text-gray-300'>
<a className='hover:text-gray-300'></a>
</Link> </Link>
</li> </li>
<li> <li>
<Link href='/license'> <Link href='/license' className='hover:text-gray-300'>
<a className='hover:text-gray-300'></a>
</Link> </Link>
</li> </li>
</ul> </ul>
@ -83,8 +83,8 @@ const Footer: React.FC<FooterProps> = ({ theme, setTheme }) => {
</Link> </Link>
</li> */} </li> */}
<li> <li>
<Link href='/verification'> <Link href='/verification' className='hover:text-gray-300'>
<a className='hover:text-gray-300'></a>
</Link> </Link>
</li> </li>
</ul> </ul>

View File

@ -15,6 +15,18 @@ const Select: React.FC<SelectProps> = ({ placeholder, options, handleChange, han
}, },
} }
}, },
placeholder: provided => {
return {
...provided,
position: 'absolute'
}
},
singleValue: provided => {
return {
...provided,
position: 'absolute'
}
}
}} }}
className='border-grey-light border dark:border-transparent rounded' className='border-grey-light border dark:border-transparent rounded'
classNamePrefix='outline-none text-black dark:bg-very-black dark:text-white ' classNamePrefix='outline-none text-black dark:bg-very-black dark:text-white '

View File

@ -1,60 +1,92 @@
import { ComponentType } from 'react' import React, { MouseEventHandler } from 'react'
import ReactSelect, { components, GroupTypeBase, MultiValueProps, OptionTypeBase } from 'react-select' import ReactSelect, {
components,
MultiValueProps,
MultiValueRemoveProps,
} from 'react-select'
import { closestCenter, DndContext, DragEndEvent } from '@dnd-kit/core'
import { restrictToParentElement } from '@dnd-kit/modifiers'
import { import {
SortableContainer, arrayMove,
SortableElement, horizontalListSortingStrategy,
SortableHandle, SortableContext,
} from 'react-sortable-hoc' useSortable,
} from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
function arrayMove(array, from, to) { const MultiValue = (props: MultiValueProps<Option>) => {
array = array.slice() const onMouseDown: MouseEventHandler<HTMLDivElement> = (e) => {
array.splice(to < 0 ? array.length + to : to, 0, array.splice(from, 1)[0])
return array
}
const SortableMultiValue = SortableElement(props => {
// this prevents the menu from being opened/closed when the user clicks
// on a value to begin dragging it. ideally, detecting a click (instead of
// a drag) would still focus the control and toggle the menu, but that
// requires some magic with refs that are out of scope for this example
const onMouseDown = e => {
e.preventDefault() e.preventDefault()
e.stopPropagation() e.stopPropagation()
} }
const innerProps = { ...props.innerProps, onMouseDown } const innerProps = { ...props.innerProps, onMouseDown }
return <components.MultiValue {...props} innerProps={innerProps} /> const { attributes, listeners, setNodeRef, transform, transition } =
}) useSortable({ id: props.data.value })
const style = {
transform: CSS.Transform.toString(transform),
transition,
}
const SortableMultiValueLabel = SortableHandle(props => ( return (
<components.MultiValueLabel {...props} /> <div style={style} ref={setNodeRef} {...attributes} {...listeners}>
)) <components.MultiValue {...props} innerProps={innerProps} />
</div>
)
}
const SortableSelect = SortableContainer(ReactSelect) const MultiValueRemove = (props: MultiValueRemoveProps<Option>) => {
return (
<components.MultiValueRemove
{...props}
innerProps={{
onPointerDown: (e) => e.stopPropagation(),
...props.innerProps,
}}
/>
)
}
const Select: React.FC<SelectProps> = ({ placeholder, options, values, setValues, handleChange, handleTouch }) => { const Select: React.FC<SelectProps> = ({ placeholder, options, values, setValues, handleChange, handleTouch }) => {
const onSortEnd = ({ oldIndex, newIndex }) => { const onSortEnd = (event: DragEndEvent) => {
const newValue = arrayMove(values, oldIndex, newIndex) const { active, over } = event
const newValue = arrayMove(values, values.findIndex(i => i === active.id), values.findIndex(i => i === over.id))
setValues(newValue) setValues(newValue)
} }
return <SortableSelect useDragHandle axis='xy' distance={4} getHelperDimensions={({ node }) => node.getBoundingClientRect()} onSortEnd={onSortEnd} return <DndContext modifiers={[restrictToParentElement]} onDragEnd={onSortEnd} collisionDetection={closestCenter}>
// select props <SortableContext
styles={{ items={values}
control: (provided) => { strategy={horizontalListSortingStrategy}>
return { ...provided, border: 'none' } <ReactSelect
}, styles={{
option: (provided) => { placeholder: (provided) => {
return { ...provided, cursor: 'pointer', ':hover': { return { ...provided, position: 'absolute' }
opacity: '0.7' },
} } control: (provided) => {
} return { ...provided, border: 'none' }
}} isMulti className='border border-grey-light dark:border-transparent rounded' classNamePrefix='outline-none text-black dark:bg-very-black dark:text-white cursor-pointer ' placeholder={placeholder || '선택해주세요.'} options={options} onChange={handleChange} onBlur={handleTouch} noOptionsMessage={() => '검색 결과가 없습니다.'} },
value={values.map(el => ({ label: el, value: el}))} option: (provided) => {
components={{ return { ...provided, cursor: 'pointer', ':hover': {
MultiValue: SortableMultiValue as ComponentType<MultiValueProps<OptionTypeBase, GroupTypeBase<{ label: string, value: string}>>>, opacity: '0.7'
MultiValueLabel: SortableMultiValueLabel, } }
}} }
closeMenuOnSelect={false} }}
/> isMulti
className='border border-grey-light dark:border-transparent rounded'
classNamePrefix='outline-none text-black dark:bg-very-black dark:text-white cursor-pointer '
placeholder={placeholder || '선택해주세요.'}
options={options}
onChange={handleChange}
onBlur={handleTouch}
noOptionsMessage={() => '검색 결과가 없습니다.'}
value={values.map(el => ({ label: el, value: el}))}
components={{
MultiValue,
MultiValueRemove,
}}
closeMenuOnSelect={false}
/>
</SortableContext>
</DndContext>
} }
interface SelectProps { interface SelectProps {

View File

@ -22,6 +22,8 @@ const TextArea: React.FC<TextAreaProps> = ({ name, placeholder, theme='auto', ma
<div ref={ref}> <div ref={ref}>
<div className='absolute bottom-12 left-10 z-30'> <div className='absolute bottom-12 left-10 z-30'>
{ {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
!emojiPickerHidden && <Picker title='선택해주세요' emoji='sunglasses' set='twitter' enableFrequentEmojiSort theme={theme} showSkinTones={false} onSelect={(e) => { !emojiPickerHidden && <Picker title='선택해주세요' emoji='sunglasses' set='twitter' enableFrequentEmojiSort theme={theme} showSkinTones={false} onSelect={(e) => {
setEmojiPickerHidden(true) setEmojiPickerHidden(true)
setValue(value + ' ' + ((e as { native: string }).native || e.colons)) setValue(value + ' ' + ((e as { native: string }).native || e.colons))

View File

@ -3,7 +3,7 @@ import { useRouter } from 'next/router'
import { redirectTo } from '@utils/Tools' import { redirectTo } from '@utils/Tools'
const Login: React.FC = ({ children }) => { const Login: React.FC<React.PropsWithChildren> = ({ children }) => {
const router = useRouter() const router = useRouter()
useEffect(() => { useEffect(() => {
localStorage.redirectTo = window.location.href localStorage.redirectTo = window.location.href

View File

@ -9,11 +9,15 @@ const LongButton: React.FC<LongButtonProps> = ({ children, newTab=false, href, o
{children} {children}
</div> </div>
</a> </a>
else return <Link href={href}> else return (
<a className={`${center ? 'justify-center ': '' }text-base bg-little-white dark:bg-discord-black text-black dark:text-gray-400 rounded flex hover:bg-little-white-hover dark:hover:bg-discord-dark-hover cursor-pointer px-4 py-4 mb-1`}> <Link
href={href}
className={`${center ? 'justify-center ': '' }text-base bg-little-white dark:bg-discord-black text-black dark:text-gray-400 rounded flex hover:bg-little-white-hover dark:hover:bg-discord-dark-hover cursor-pointer px-4 py-4 mb-1`}>
{children} {children}
</a>
</Link> </Link>
)
} }
if(onClick) return <div onKeyPress={onClick} onClick={onClick} className={`${center ? 'justify-center ': '' }text-base bg-little-white dark:bg-discord-black text-black dark:text-gray-400 rounded flex hover:bg-little-white-hover dark:hover:bg-discord-dark-hover cursor-pointer px-4 py-4 mb-1`}> if(onClick) return <div onKeyPress={onClick} onClick={onClick} className={`${center ? 'justify-center ': '' }text-base bg-little-white dark:bg-discord-black text-black dark:text-gray-400 rounded flex hover:bg-little-white-hover dark:hover:bg-discord-dark-hover cursor-pointer px-4 py-4 mb-1`}>
{children} {children}

View File

@ -41,241 +41,288 @@ const Navbar: React.FC<NavbarProps> = ({ token }) => {
setUserCache(null) setUserCache(null)
} }
}, [ token ]) }, [ token ])
return ( return <>
<> <nav className='fixed z-40 top-0 flex flex-wrap items-center justify-between px-2 py-3 w-full text-gray-100 dark:bg-discord-black bg-discord-blurple lg:absolute'>
<nav className='fixed z-40 top-0 flex flex-wrap items-center justify-between px-2 py-3 w-full text-gray-100 dark:bg-discord-black bg-discord-blurple lg:absolute'> <div className='container flex flex-wrap items-center justify-between mx-auto px-4'>
<div className='container flex flex-wrap items-center justify-between mx-auto px-4'> <div className='relative flex justify-between w-full lg:justify-start lg:w-auto'>
<div className='relative flex justify-between w-full lg:justify-start lg:w-auto'> <Link
<Link href={dev ? '/developers' : '/'}> href={dev ? '/developers' : '/'}
<a className={`${dev ? 'dark:text-koreanbots-blue ' : ''}logofont text-large whitespace-no-wrap inline-block mr-4 py-2 hover:text-gray-300 font-semibold leading-relaxed uppercase sm:text-2xl`} className={`${dev ? 'dark:text-koreanbots-blue ' : ''}logofont text-large whitespace-no-wrap inline-block mr-4 py-2 hover:text-gray-300 font-semibold leading-relaxed uppercase sm:text-2xl`}>
>
{ dev ? <><i className='fas fa-tools mr-1'/> DEVELOPERS</> : 'KOREANLIST'} { dev ? <><i className='fas fa-tools mr-1'/> DEVELOPERS</> : 'KOREANLIST'}
</a>
</Link> </Link>
<button <button
className='block px-3 py-1 dark:text-gray-200 text-xl leading-none bg-transparent border border-solid border-transparent rounded outline-none focus:outline-none cursor-pointer lg:hidden' className='block px-3 py-1 dark:text-gray-200 text-xl leading-none bg-transparent border border-solid border-transparent rounded outline-none focus:outline-none cursor-pointer lg:hidden'
type='button' type='button'
onClick={() => setNavbarOpen(!navbarOpen)} onClick={() => setNavbarOpen(!navbarOpen)}
> >
<i className={`fas ${!navbarOpen ? 'fa-bars' : 'fa-times'}`}></i> <i className={`fas ${!navbarOpen ? 'fa-bars' : 'fa-times'}`}></i>
</button> </button>
<ul className='hidden lg:flex flex-col list-none lg:flex-row lg:ml-auto'> <ul className='hidden lg:flex flex-col list-none lg:flex-row lg:ml-auto'>
<li className='flex items-center'> <li className='flex items-center'>
<Link href={dev ? '/' : '/developers'}> <Link
<a className='lg:hover:text-gray-300 flex items-center px-3 py-4 w-full hover:text-gray-500 text-gray-700 text-sm font-semibold sm:w-auto lg:py-2 lg:text-gray-100'> href={dev ? '/' : '/developers'}
{dev ? '홈' : '개발자'} className='lg:hover:text-gray-300 flex items-center px-3 py-4 w-full hover:text-gray-500 text-gray-700 text-sm font-semibold sm:w-auto lg:py-2 lg:text-gray-100'>
</a>
</Link> {dev ? '홈' : '개발자'}
</li>
{
type !== 'bot' && <li className='flex items-center'>
<Link href='/bots'>
<a className='lg:hover:text-gray-300 flex items-center px-3 py-4 w-full hover:text-gray-500 text-gray-700 text-sm font-semibold sm:w-auto lg:py-2 lg:text-gray-100'>
</a>
</Link>
</li>
}
{
type !== 'server' && <li className='flex items-center'>
<Link href='/servers'>
<a className='lg:hover:text-gray-300 flex items-center px-3 py-4 w-full hover:text-gray-500 text-gray-700 text-sm font-semibold sm:w-auto lg:py-2 lg:text-gray-100'>
</a>
</Link>
</li>
}
<li className='flex items-center'>
<Link href='/discord'>
<a target='_blank' rel='noreferrer' className='lg:hover:text-gray-300 flex items-center px-3 py-4 w-full hover:text-gray-500 text-gray-700 text-sm font-semibold sm:w-auto lg:py-2 lg:text-gray-100'
>
</a>
</Link>
</li>
<li className='flex items-center'>
<Link href='/about'>
<a className='lg:hover:text-gray-300 flex items-center px-3 py-4 w-full hover:text-gray-500 text-gray-700 text-sm font-semibold sm:w-auto lg:py-2 lg:text-gray-100'>
</a>
</Link>
</li>
<li className='flex items-center' onFocus={() => setAddDropdownOpen(true)} onMouseOver={() => setAddDropdownOpen(true)} onMouseOut={() => setAddDropdownOpen(false)} onBlur={() => setAddDropdownOpen(false)}>
<span className='lg:hover:text-gray-300 flex items-center px-3 py-4 w-full hover:text-gray-500 text-gray-700 text-sm font-semibold sm:w-auto lg:py-2 lg:text-gray-100 cursor-pointer'>
</span>
<div className={`rounded shadow-md absolute mt-11 top-0 w-40 bg-white text-black dark:bg-very-black dark:text-gray-300 text-sm ${addDropdownOpen ? 'block' : 'hidden'}`}>
<ul className='relative'>
<li>
<Link href='/addbot'>
<a className='px-4 py-2 block hover:bg-gray-100 dark:hover:bg-discord-dark-hover rounded-t'><i className='fas fa-robot' /> </a>
</Link>
</li>
<li>
<Link href='/addserver'>
<a className='px-4 py-2 block hover:bg-gray-100 dark:hover:bg-discord-dark-hover rounded-b'><i className='fas fa-users' /> </a>
</Link>
</li>
</ul>
</div>
</li>
</ul>
</div>
<div className='hidden grow items-center bg-white lg:flex lg:bg-transparent lg:shadow-none'>
<ul className='flex flex-col list-none lg:flex-row lg:ml-auto'>
<li className='flex items-center outline-none' onFocus={() => setDropdownOpen(true)} onMouseOver={() => setDropdownOpen(true)} onMouseOut={() => setDropdownOpen(false)} onBlur={() => setDropdownOpen(false)}>
{
logged ?
<>
<a
className='lg:hover:text-gray-300 flex items-center px-3 py-4 w-full hover:text-gray-500 text-gray-700 text-sm font-semibold sm:w-auto lg:py-2 lg:text-gray-100 cursor-pointer'>
<DiscordAvatar userID={userCache.id} className='w-8 h-8 rounded-full mr-1.5' size={128}/>
{userCache.username} <i className='ml-2 fas fa-sort-down' />
</a>
<div className={`rounded shadow-md absolute mt-14 top-0 w-48 bg-white text-black dark:bg-very-black dark:text-gray-300 text-sm ${dropdownOpen ? 'block' : 'hidden'}`}>
<ul className='relative'>
<li>
<Link href={`/users/${userCache.id}`}>
<a className='px-4 py-2 block hover:bg-gray-100 dark:hover:bg-discord-dark-hover rounded-t'><i className='fas fa-user' /> </a>
</Link>
</li>
<li>
<Link href='/panel'>
<a className='px-4 py-2 block hover:bg-gray-100 dark:hover:bg-discord-dark-hover'><i className='fas fa-cogs' /> </a>
</Link>
</li>
{/* <li><hr className='border-t mx-2'/></li> */}
<li>
<a onKeyPress={() => {
localStorage.removeItem('userCache')
redirectTo(router, 'logout')
}
} onClick={() => {
localStorage.removeItem('userCache')
redirectTo(router, 'logout')
}} className='px-4 py-2 block text-red-500 hover:bg-gray-100 dark:hover:bg-discord-dark-hover rounded-b cursor-pointer'><i className='fas fa-sign-out-alt' /> </a>
</li>
</ul>
</div>
</> :
<a tabIndex={0} onClick={()=> {
localStorage.redirectTo = window.location.href
setNavbarOpen(false)
redirectTo(router, 'login')
}} className='lg:hover:text-gray-300 flex items-center px-3 py-4 w-full hover:text-gray-500 text-gray-700 text-sm font-semibold sm:w-auto lg:py-2 lg:text-gray-100 cursor-pointer outline-none'>
</a>
}
</li>
</ul>
</div>
</div>
</nav>
<div
className={`z-30 w-full h-full fixed bg-discord-blurple dark:bg-discord-black mt-8 sm:mt-0 lg:hidden overflow-y-scroll lg:scroll-none ${
navbarOpen ? 'block' : 'hidden'
}`}
>
<nav className='mt-20'>
<Link href={dev ? '/' : '/developers'}>
<a onClick={()=> setNavbarOpen(false)} className='flex items-center px-8 py-2 text-gray-100 hover:text-gray-300'>
{
dev ? <i className='fas fa-home' /> : <i className='fas fa-tools' />
}
<span className='px-2 font-medium'>
{dev ? '홈' : '개발자'}
</span>
</a>
</Link>
{
type !== 'bot' && <Link href='/bots'>
<a onClick={()=> setNavbarOpen(false)} className='flex items-center px-8 py-2 text-gray-100 hover:text-gray-300'>
<i className='fas fa-robot' />
<span className='px-2 font-medium'> </span>
</a>
</Link>
}
{
type !== 'server' && <Link href='/servers'>
<a onClick={()=> setNavbarOpen(false)} className='flex items-center px-8 py-2 text-gray-100 hover:text-gray-300'>
<i className='fas fa-users' />
<span className='px-2 font-medium'> </span>
</a>
</Link>
}
<Link href='/discord'>
<a target='_blank' rel='noreferrer' onClick={()=> setNavbarOpen(false)} className='flex items-center px-8 py-2 text-gray-100 hover:text-gray-300'>
<i className='fab fa-discord' />
<span className='px-2 font-medium'> </span>
</a>
</Link>
<Link href='/about'>
<a onClick={()=> setNavbarOpen(false)} className='flex items-center px-8 py-2 text-gray-100 hover:text-gray-300'>
<i className='fas fa-layer-group' />
<span className='px-2 font-medium'></span>
</a>
</Link>
<a
onClick={()=> {
setMobileAddDropdownOpen(!mobileAddDropdownOpen)
}}
className='flex items-center px-8 py-2 text-gray-100'
>
<i className='fas fa-plus' />
<span className='px-2 font-medium'></span>
</a>
<div className={mobileAddDropdownOpen ? 'px-4 flex flex-col' : 'px-4 hidden'}>
<Link href='/addbot'>
<a onClick={()=> setNavbarOpen(false)} className='flex items-center px-8 py-2 text-gray-100 hover:text-gray-300'>
<i className='fas fa-robot' />
<span className='px-2 font-medium'> </span>
</a>
</Link>
<Link href='/addserver'>
<a onClick={()=> setNavbarOpen(false)} className='flex items-center px-8 py-2 text-gray-100 hover:text-gray-300'>
<i className='fas fa-users' />
<span className='px-2 font-medium'> </span>
</a>
</Link>
</div>
</nav>
<div className='my-10'>
{
logged ? <>
<Link href={`/users/${userCache.id}`}>
<a className='flex items-center px-8 py-2 text-gray-100 hover:text-gray-300' onClick={() => setNavbarOpen(!navbarOpen)}>
<i className='far fa-user' />
<span className='px-2 font-medium'>{userCache.username}</span>
</a>
</Link> </Link>
<Link href='/panel'> </li>
<a className='flex items-center px-8 py-2 text-gray-100 hover:text-gray-300' onClick={() => setNavbarOpen(!navbarOpen)}> {
<i className='fas fa-cogs' /> type !== 'bot' && <li className='flex items-center'>
<span className='px-2 font-medium'></span> <Link
</a> href='/bots'
className='lg:hover:text-gray-300 flex items-center px-3 py-4 w-full hover:text-gray-500 text-gray-700 text-sm font-semibold sm:w-auto lg:py-2 lg:text-gray-100'>
</Link>
</li>
}
{
type !== 'server' && <li className='flex items-center'>
<Link
href='/servers'
className='lg:hover:text-gray-300 flex items-center px-3 py-4 w-full hover:text-gray-500 text-gray-700 text-sm font-semibold sm:w-auto lg:py-2 lg:text-gray-100'>
</Link>
</li>
}
<li className='flex items-center'>
<Link
href='/discord'
target='_blank'
rel='noreferrer'
className='lg:hover:text-gray-300 flex items-center px-3 py-4 w-full hover:text-gray-500 text-gray-700 text-sm font-semibold sm:w-auto lg:py-2 lg:text-gray-100'>
</Link> </Link>
<a onClick={()=> { </li>
setNavbarOpen(!navbarOpen) <li className='flex items-center'>
localStorage.removeItem('userCache') <Link
redirectTo(router, 'logout') href='/about'
}} className='flex items-center px-8 py-2 text-red-500 hover:text-red-400'> className='lg:hover:text-gray-300 flex items-center px-3 py-4 w-full hover:text-gray-500 text-gray-700 text-sm font-semibold sm:w-auto lg:py-2 lg:text-gray-100'>
<i className='fas fa-sign-out-alt' />
<span className='px-2 font-medium'></span>
</a>
</> : <a onClick={() => { </Link>
localStorage.redirectTo = window.location.href </li>
setNavbarOpen(false) <li className='flex items-center' onFocus={() => setAddDropdownOpen(true)} onMouseOver={() => setAddDropdownOpen(true)} onMouseOut={() => setAddDropdownOpen(false)} onBlur={() => setAddDropdownOpen(false)}>
redirectTo(router, 'login') <span className='lg:hover:text-gray-300 flex items-center px-3 py-4 w-full hover:text-gray-500 text-gray-700 text-sm font-semibold sm:w-auto lg:py-2 lg:text-gray-100 cursor-pointer'>
}} className='flex items-center px-8 py-2 text-gray-100 hover:text-gray-300'>
<i className='far fa-user' /> </span>
<span className='px-2 font-medium'></span> <div className={`rounded shadow-md absolute mt-11 top-0 w-40 bg-white text-black dark:bg-very-black dark:text-gray-300 text-sm ${addDropdownOpen ? 'block' : 'hidden'}`}>
</a> <ul className='relative'>
} <li>
<Link
href='/addbot'
className='px-4 py-2 block hover:bg-gray-100 dark:hover:bg-discord-dark-hover rounded-t'>
<i className='fas fa-robot' />
</Link>
</li>
<li>
<Link
href='/addserver'
className='px-4 py-2 block hover:bg-gray-100 dark:hover:bg-discord-dark-hover rounded-b'>
<i className='fas fa-users' />
</Link>
</li>
</ul>
</div>
</li>
</ul>
</div>
<div className='hidden grow items-center bg-white lg:flex lg:bg-transparent lg:shadow-none'>
<ul className='flex flex-col list-none lg:flex-row lg:ml-auto'>
<li className='flex items-center outline-none' onFocus={() => setDropdownOpen(true)} onMouseOver={() => setDropdownOpen(true)} onMouseOut={() => setDropdownOpen(false)} onBlur={() => setDropdownOpen(false)}>
{
logged ?
<>
<a
className='lg:hover:text-gray-300 flex items-center px-3 py-4 w-full hover:text-gray-500 text-gray-700 text-sm font-semibold sm:w-auto lg:py-2 lg:text-gray-100 cursor-pointer'>
<DiscordAvatar userID={userCache.id} className='w-8 h-8 rounded-full mr-1.5' size={128}/>
{userCache.username} <i className='ml-2 fas fa-sort-down' />
</a>
<div className={`rounded shadow-md absolute mt-14 top-0 w-48 bg-white text-black dark:bg-very-black dark:text-gray-300 text-sm ${dropdownOpen ? 'block' : 'hidden'}`}>
<ul className='relative'>
<li>
<Link
href={`/users/${userCache.id}`}
className='px-4 py-2 block hover:bg-gray-100 dark:hover:bg-discord-dark-hover rounded-t'>
<i className='fas fa-user' />
</Link>
</li>
<li>
<Link
href='/panel'
className='px-4 py-2 block hover:bg-gray-100 dark:hover:bg-discord-dark-hover'>
<i className='fas fa-cogs' />
</Link>
</li>
{/* <li><hr className='border-t mx-2'/></li> */}
<li>
<a onKeyPress={() => {
localStorage.removeItem('userCache')
redirectTo(router, 'logout')
}
} onClick={() => {
localStorage.removeItem('userCache')
redirectTo(router, 'logout')
}} className='px-4 py-2 block text-red-500 hover:bg-gray-100 dark:hover:bg-discord-dark-hover rounded-b cursor-pointer'><i className='fas fa-sign-out-alt' /> </a>
</li>
</ul>
</div>
</> :
<a tabIndex={0} onClick={()=> {
localStorage.redirectTo = window.location.href
setNavbarOpen(false)
redirectTo(router, 'login')
}} className='lg:hover:text-gray-300 flex items-center px-3 py-4 w-full hover:text-gray-500 text-gray-700 text-sm font-semibold sm:w-auto lg:py-2 lg:text-gray-100 cursor-pointer outline-none'>
</a>
}
</li>
</ul>
</div> </div>
</div> </div>
</> </nav>
) <div
className={`z-30 w-full h-full fixed bg-discord-blurple dark:bg-discord-black mt-8 sm:mt-0 lg:hidden overflow-y-scroll lg:scroll-none ${
navbarOpen ? 'block' : 'hidden'
}`}
>
<nav className='mt-20'>
<Link
href={dev ? '/' : '/developers'}
onClick={()=> setNavbarOpen(false)}
className='flex items-center px-8 py-2 text-gray-100 hover:text-gray-300'>
{
dev ? <i className='fas fa-home' /> : <i className='fas fa-tools' />
}
<span className='px-2 font-medium'>
{dev ? '홈' : '개발자'}
</span>
</Link>
{
type !== 'bot' && <Link
href='/bots'
onClick={()=> setNavbarOpen(false)}
className='flex items-center px-8 py-2 text-gray-100 hover:text-gray-300'>
<i className='fas fa-robot' />
<span className='px-2 font-medium'> </span>
</Link>
}
{
type !== 'server' && <Link
href='/servers'
onClick={()=> setNavbarOpen(false)}
className='flex items-center px-8 py-2 text-gray-100 hover:text-gray-300'>
<i className='fas fa-users' />
<span className='px-2 font-medium'> </span>
</Link>
}
<Link
href='/discord'
target='_blank'
rel='noreferrer'
onClick={()=> setNavbarOpen(false)}
className='flex items-center px-8 py-2 text-gray-100 hover:text-gray-300'>
<i className='fab fa-discord' />
<span className='px-2 font-medium'> </span>
</Link>
<Link
href='/about'
onClick={()=> setNavbarOpen(false)}
className='flex items-center px-8 py-2 text-gray-100 hover:text-gray-300'>
<i className='fas fa-layer-group' />
<span className='px-2 font-medium'></span>
</Link>
<a
onClick={()=> {
setMobileAddDropdownOpen(!mobileAddDropdownOpen)
}}
className='flex items-center px-8 py-2 text-gray-100'
>
<i className='fas fa-plus' />
<span className='px-2 font-medium'></span>
</a>
<div className={mobileAddDropdownOpen ? 'px-4 flex flex-col' : 'px-4 hidden'}>
<Link
href='/addbot'
onClick={()=> setNavbarOpen(false)}
className='flex items-center px-8 py-2 text-gray-100 hover:text-gray-300'>
<i className='fas fa-robot' />
<span className='px-2 font-medium'> </span>
</Link>
<Link
href='/addserver'
onClick={()=> setNavbarOpen(false)}
className='flex items-center px-8 py-2 text-gray-100 hover:text-gray-300'>
<i className='fas fa-users' />
<span className='px-2 font-medium'> </span>
</Link>
</div>
</nav>
<div className='my-10'>
{
logged ? <>
<Link
href={`/users/${userCache.id}`}
className='flex items-center px-8 py-2 text-gray-100 hover:text-gray-300'
onClick={() => setNavbarOpen(!navbarOpen)}>
<i className='far fa-user' />
<span className='px-2 font-medium'>{userCache.username}</span>
</Link>
<Link
href='/panel'
className='flex items-center px-8 py-2 text-gray-100 hover:text-gray-300'
onClick={() => setNavbarOpen(!navbarOpen)}>
<i className='fas fa-cogs' />
<span className='px-2 font-medium'></span>
</Link>
<a onClick={()=> {
setNavbarOpen(!navbarOpen)
localStorage.removeItem('userCache')
redirectTo(router, 'logout')
}} className='flex items-center px-8 py-2 text-red-500 hover:text-red-400'>
<i className='fas fa-sign-out-alt' />
<span className='px-2 font-medium'></span>
</a>
</> : <a onClick={() => {
localStorage.redirectTo = window.location.href
setNavbarOpen(false)
redirectTo(router, 'login')
}} className='flex items-center px-8 py-2 text-gray-100 hover:text-gray-300'>
<i className='far fa-user' />
<span className='px-2 font-medium'></span>
</a>
}
</div>
</div>
</>
} }
interface NavbarProps { interface NavbarProps {

View File

@ -3,17 +3,19 @@ import DiscordAvatar from '@components/DiscordAvatar'
const Owner: React.FC<OwnerProps> = ({ id, globalName, username, tag, crown=false }) => { const Owner: React.FC<OwnerProps> = ({ id, globalName, username, tag, crown=false }) => {
return ( return (
<Link href={`/users/${id}`}> (<Link
<a className='dark:hover:bg-discord-dark-hover flex mb-1 px-4 py-4 text-black dark:text-gray-400 text-base dark:bg-discord-black bg-little-white hover:bg-little-white-hover rounded cursor-pointer'> href={`/users/${id}`}
<div className='relative shrink-0 mr-3 mt-1 w-8 h-8 rounded-full shadow-inner overflow-hidden'> className='dark:hover:bg-discord-dark-hover flex mb-1 px-4 py-4 text-black dark:text-gray-400 text-base dark:bg-discord-black bg-little-white hover:bg-little-white-hover rounded cursor-pointer'>
<DiscordAvatar userID={id} className='z-negative absolute inset-0 w-full h-full' />
</div> <div className='relative shrink-0 mr-3 mt-1 w-8 h-8 rounded-full shadow-inner overflow-hidden'>
<div className='flex-1 w-0 leading-snug'> <DiscordAvatar userID={id} className='z-negative absolute inset-0 w-full h-full' />
<h4 className='whitespace-nowrap truncate'>{ crown && <i className='fas fa-crown text-amber-300 text-xs' /> }{tag === '0' ? globalName : username}</h4> </div>
<span className='text-gray-600 text-sm'>{tag === '0' ? '@' + username : '#' + tag}</span> <div className='flex-1 w-0 leading-snug'>
</div> <h4 className='whitespace-nowrap truncate'>{ crown && <i className='fas fa-crown text-amber-300 text-xs' /> }{tag === '0' ? globalName : username}</h4>
</a> <span className='text-gray-600 text-sm'>{tag === '0' ? '@' + username : '#' + tag}</span>
</Link> </div>
</Link>)
) )
} }

View File

@ -29,44 +29,45 @@ const Paginator: React.FC<PaginatorProps> = ({ currentPage, totalPage, pathname,
return ( return (
<div className='flex flex-col items-center justify-center py-4 text-center'> <div className='flex flex-col items-center justify-center py-4 text-center'>
<div className='flex'> <div className='flex'>
<Link href={{ pathname, hash: 'list', query: { ...searchParams, page: currentPage - 1 } }}> <Link
<a href={{ pathname, hash: 'list', query: { ...searchParams, page: currentPage - 1 } }}
className={`${ className={`${
currentPage === 1 ? 'invisible' : '' currentPage === 1 ? 'invisible' : ''
} h-12 w-12 mr-1 flex justify-center items-center rounded-full transition duration-150 ease-in bg-gray-200 dark:bg-discord-black hover:bg-gray-300 dark:hover:bg-discord-dark-hover cursor-pointer text-center`} } h-12 w-12 mr-1 flex justify-center items-center rounded-full transition duration-150 ease-in bg-gray-200 dark:bg-discord-black hover:bg-gray-300 dark:hover:bg-discord-dark-hover cursor-pointer text-center`}>
>
<i className='fas fa-chevron-left'></i> <i className='fas fa-chevron-left'></i>
</a>
</Link> </Link>
{pages.map((el, i) => ( {pages.map((el, i) => (
<Link key={i} href={{ pathname, hash: 'list', query: { ...searchParams, page: el } }}> (<Link
<a key={i}
className={`w-12 flex justify-center items-center cursor-pointer leading-5 transition duration-150 ease-in ${ href={{ pathname, hash: 'list', query: { ...searchParams, page: el } }}
i === 0 && i === pages.length - 1 className={`w-12 flex justify-center items-center cursor-pointer leading-5 transition duration-150 ease-in ${
? 'rounded-full' i === 0 && i === pages.length - 1
: i === 0 ? 'rounded-full'
? 'rounded-l-full' : i === 0
: i === pages.length - 1 ? 'rounded-l-full'
? 'rounded-r-full' : i === pages.length - 1
: '' ? 'rounded-r-full'
} ${ : ''
currentPage === el } ${
? 'bg-gray-300 dark:bg-discord-dark-hover' currentPage === el
: 'bg-gray-200 dark:bg-discord-black hover:bg-gray-300 dark:hover:bg-discord-dark-hover' ? 'bg-gray-300 dark:bg-discord-dark-hover'
}`} : 'bg-gray-200 dark:bg-discord-black hover:bg-gray-300 dark:hover:bg-discord-dark-hover'
> }`}>
{el}
</a> {el}
</Link>
</Link>)
))} ))}
<Link href={{ pathname, hash: 'list', query: { ...searchParams, page: currentPage + 1 } }}> <Link
<a href={{ pathname, hash: 'list', query: { ...searchParams, page: currentPage + 1 } }}
className={`${ className={`${
currentPage === totalPage ? 'invisible' : '' currentPage === totalPage ? 'invisible' : ''
} h-12 w-12 ml-1 flex justify-center items-center rounded-full transition duration-150 ease-in bg-gray-200 dark:bg-discord-black hover:bg-gray-300 dark:hover:bg-discord-dark-hover cursor-pointer text-center`} } h-12 w-12 ml-1 flex justify-center items-center rounded-full transition duration-150 ease-in bg-gray-200 dark:bg-discord-black hover:bg-gray-300 dark:hover:bg-discord-dark-hover cursor-pointer text-center`}>
>
<i className='fas fa-chevron-right'></i> <i className='fas fa-chevron-right'></i>
</a>
</Link> </Link>
</div> </div>
</div> </div>

View File

@ -1,4 +1,4 @@
const ResponsiveGrid: React.FC = ({ children }) => { const ResponsiveGrid: React.FC<React.PropsWithChildren> = ({ children }) => {
return <div className='grid gap-x-4 grid-rows-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 mt-10 -mb-10'> return <div className='grid gap-x-4 grid-rows-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 mt-10 -mb-10'>
{children} {children}
</div> </div>

View File

@ -115,7 +115,7 @@ const Search: React.FC = () => {
data.data.bots.length === 0 ? data.data.bots.length === 0 ?
<li className='px-3 py-3.5'> .</li> : <li className='px-3 py-3.5'> .</li> :
data.data.bots.map(el => ( data.data.bots.map(el => (
<Link key={el.id} href={makeBotURL(el)}> <Link key={el.id} href={makeBotURL(el)} legacyBehavior>
<li className='h-15 flex px-3 py-2 cursor-pointer'> <li className='h-15 flex px-3 py-2 cursor-pointer'>
<DiscordAvatar className='mt-1 w-12 h-12' size={128} userID={el.id} /> <DiscordAvatar className='mt-1 w-12 h-12' size={128} userID={el.id} />
<div className='ml-2'> <div className='ml-2'>
@ -133,7 +133,7 @@ const Search: React.FC = () => {
data.data.servers.length === 0 ? data.data.servers.length === 0 ?
<li className='px-3 py-3.5'> .</li> : <li className='px-3 py-3.5'> .</li> :
data.data.servers.map(el => ( data.data.servers.map(el => (
<Link key={el.id} href={makeServerURL(el)}> <Link key={el.id} href={makeServerURL(el)} legacyBehavior>
<li className='h-15 flex px-3 py-2 cursor-pointer'> <li className='h-15 flex px-3 py-2 cursor-pointer'>
<ServerIcon className='mt-1 w-12 h-12' size={128} id={el.id} /> <ServerIcon className='mt-1 w-12 h-12' size={128} id={el.id} />
<div className='ml-2'> <div className='ml-2'>
@ -177,7 +177,10 @@ const Search: React.FC = () => {
</li> </li>
{ {
recentSearch.slice(0, 10).map((el, n) => ( recentSearch.slice(0, 10).map((el, n) => (
<Link key={n} href={`/search?q=${encodeURIComponent(el?.value)}`}> <Link
key={n}
href={`/search?q=${encodeURIComponent(el?.value)}`}
legacyBehavior>
<li className='h-15 px-3 py-2 cursor-pointer'> <li className='h-15 px-3 py-2 cursor-pointer'>
<i className='fas fa-history' /> {el?.value} <i className='fas fa-history' /> {el?.value}
<span className='absolute right-0 pr-10 text-gray-400 text-sm'> <span className='absolute right-0 pr-10 text-gray-400 text-sm'>

View File

@ -11,131 +11,141 @@ const ServerIcon = dynamic(() => import('@components/ServerIcon'))
const ServerCard: React.FC<BotCardProps> = ({ type, server }) => { const ServerCard: React.FC<BotCardProps> = ({ type, server }) => {
const newServerLink = server.data ? `/addserver/${server.id}` : `${DiscordEnpoints.InviteApplication(DSKR_BOT_ID, {}, 'bot', null, server.id)}&disable_guild_select=true` const newServerLink = server.data ? `/addserver/${server.id}` : `${DiscordEnpoints.InviteApplication(DSKR_BOT_ID, {}, 'bot', null, server.id)}&disable_guild_select=true`
return <div className='min-w-0 container mb-16 transform hover:-translate-y-1 transition duration-100 ease-in cursor-pointer'> return (
<div className='relative'> <div className='min-w-0 container mb-16 transform hover:-translate-y-1 transition duration-100 ease-in cursor-pointer'>
<div className='container mx-auto'> <div className='relative'>
<div className='h-full'> <div className='container mx-auto'>
<div <div className='h-full'>
className='relative mx-auto h-full text-black dark:text-white dark:bg-discord-black bg-little-white rounded-2xl shadow-xl' <div
style={ className='relative mx-auto h-full text-black dark:text-white dark:bg-discord-black bg-little-white rounded-2xl shadow-xl'
checkServerFlag(server.flags, 'trusted') && server.banner style={
? { checkServerFlag(server.flags, 'trusted') && server.banner
background: `linear-gradient(to right, rgba(34, 36, 38, 0.68), rgba(34, 36, 38, 0.68)), url("${server.banner}") center top / cover no-repeat`, ? {
color: 'white', background: `linear-gradient(to right, rgba(34, 36, 38, 0.68), rgba(34, 36, 38, 0.68)), url("${server.banner}") center top / cover no-repeat`,
} color: 'white',
: {}
}
>
<Link href={type !== 'add' ? makeServerURL(server) : newServerLink}>
<div>
<div className='flex flex-col'>
<div className='flex'>
<div className='w-3/5 flex justify-start'>
<ServerIcon
size={128}
id={server.id}
hash={type === 'add' && server.icon}
alt='Icon'
className='absolute -left-2 -top-8 mx-auto w-32 h-32 bg-white rounded-full'
/>
</div>
<div className='grid grid-cols-1 pr-5 pt-5 w-2/5'>
<Tag
text={
<>
<i className='fas fa-heart text-red-600' /> {formatNumber(server.votes)}
</>
}
dark
/>
<Tag
blurple
text={server.members ? <>{formatNumber(server.members)} </> : 'N/A'}
dark
/>
</div>
</div>
<div className='mt-3 px-4 h-16'>
<h2 className={`px-1 text-sm ${server.state !== 'unreachable' ? ' invisible' : ''}`}>
<i className='fas fa-ban text-red-600' />
</h2>
<h1 className='mb-3 text-left text-xl sm:text-2xl font-bold truncate'>{server.name}</h1>
</div>
</div>
<p className='mb-10 px-4 h-6 text-left text-gray-400 text-sm font'>
{type === 'add' ?
server.data ? '지금 바로 서버를 등록할 수 있습니다.' : '봇을 초대해야 서버를 등록할 수 있습니다.'
: server.intro
} }
</p> : {}
}
>
<Link
href={type !== 'add' ? makeServerURL(server) : newServerLink}
legacyBehavior>
<div> <div>
<div className='category flex flex-wrap px-2'> <div className='flex flex-col'>
{server.category?.slice(0, 3).map(el => ( <div className='flex'>
<Tag key={el} text={el} href={`/servers/categories/${el}`} dark /> <div className='w-3/5 flex justify-start'>
))}{' '} <ServerIcon
{server.category?.length > 3 && <Tag text={`+${server.category.length - 3}`} dark />} size={128}
id={server.id}
hash={type === 'add' && server.icon}
alt='Icon'
className='absolute -left-2 -top-8 mx-auto w-32 h-32 bg-white rounded-full'
/>
</div>
<div className='grid grid-cols-1 pr-5 pt-5 w-2/5'>
<Tag
text={
<>
<i className='fas fa-heart text-red-600' /> {formatNumber(server.votes)}
</>
}
dark
/>
<Tag
blurple
text={server.members ? <>{formatNumber(server.members)} </> : 'N/A'}
dark
/>
</div>
</div>
<div className='mt-3 px-4 h-16'>
<h2 className={`px-1 text-sm ${server.state !== 'unreachable' ? ' invisible' : ''}`}>
<i className='fas fa-ban text-red-600' />
</h2>
<h1 className='mb-3 text-left text-xl sm:text-2xl font-bold truncate'>{server.name}</h1>
</div>
</div>
<p className='mb-10 px-4 h-6 text-left text-gray-400 text-sm font'>
{type === 'add' ?
server.data ? '지금 바로 서버를 등록할 수 있습니다.' : '봇을 초대해야 서버를 등록할 수 있습니다.'
: server.intro
}
</p>
<div>
<div className='category flex flex-wrap px-2'>
{server.category?.slice(0, 3).map(el => (
<Tag key={el} text={el} href={`/servers/categories/${el}`} dark />
))}{' '}
{server.category?.length > 3 && <Tag text={`+${server.category.length - 3}`} dark />}
</div>
</div> </div>
</div> </div>
</div> </Link>
</Link> <Divider />
<Divider /> <div className='w-full'>
<div className='w-full'> <div className='flex justify-evenly'>
<div className='flex justify-evenly'> {
{ type === 'add' ?
type === 'add' ? server.data ? <Link
server.data ? <Link href={newServerLink}> href={newServerLink}
<a className='py-3 w-full text-center text-emerald-500 hover:text-white text-sm font-bold hover:bg-emerald-500 rounded-b-2xl hover:shadow-lg transition duration-100 ease-in'> className='py-3 w-full text-center text-emerald-500 hover:text-white text-sm font-bold hover:bg-emerald-500 rounded-b-2xl hover:shadow-lg transition duration-100 ease-in'>
</a>
</Link> : <Link href={newServerLink}>
<a </Link> : <Link
href={newServerLink}
className='py-3 w-full text-center text-discord-blurple hover:text-white text-sm font-bold hover:bg-discord-blurple rounded-b-2xl hover:shadow-lg transition duration-100 ease-in' className='py-3 w-full text-center text-discord-blurple hover:text-white text-sm font-bold hover:bg-discord-blurple rounded-b-2xl hover:shadow-lg transition duration-100 ease-in'
rel='noopener noreferrer' rel='noopener noreferrer'
target='_blank' target='_blank'>
>
</a>
</Link>
:
<>
<Link href={makeServerURL(server)}>
<a className='py-3 w-full text-center text-koreanbots-blue hover:text-white text-sm font-bold hover:bg-koreanbots-blue rounded-bl-2xl hover:shadow-lg transition duration-100 ease-in'>
</a>
</Link> </Link>
{type === 'manage' ? ( :
<Link href={`/servers/${server.id}/edit`}> <>
<a className='py-3 w-full text-center text-emerald-500 hover:text-white text-sm font-bold hover:bg-emerald-500 rounded-br-2xl hover:shadow-lg transition duration-100 ease-in'> <Link
href={makeServerURL(server)}
</a> className='py-3 w-full text-center text-koreanbots-blue hover:text-white text-sm font-bold hover:bg-koreanbots-blue rounded-bl-2xl hover:shadow-lg transition duration-100 ease-in'>
</Link> </Link>
) : !['ok', 'unreachable'].includes(server.state) ? <a {type === 'manage' ? (
className='py-3 w-full text-center text-discord-blurple text-sm font-bold rounded-br-2xl hover:shadow-lg transition duration-100 ease-in opacity-50 cursor-default select-none' <Link
> href={`/servers/${server.id}/edit`}
className='py-3 w-full text-center text-emerald-500 hover:text-white text-sm font-bold hover:bg-emerald-500 rounded-br-2xl hover:shadow-lg transition duration-100 ease-in'>
</a> :
<a
href={
makeServerURL(server) + '/join' </Link>
} ) : !['ok', 'unreachable'].includes(server.state) ? <a
rel='noopener noreferrer' className='py-3 w-full text-center text-discord-blurple text-sm font-bold rounded-br-2xl hover:shadow-lg transition duration-100 ease-in opacity-50 cursor-default select-none'
target='_blank'
className='py-3 w-full text-center text-discord-blurple hover:text-white text-sm font-bold hover:bg-discord-blurple rounded-br-2xl hover:shadow-lg transition duration-100 ease-in'
> >
</a> </a> :
} <a
</> href={
makeServerURL(server) + '/join'
} }
rel='noopener noreferrer'
target='_blank'
className='py-3 w-full text-center text-discord-blurple hover:text-white text-sm font-bold hover:bg-discord-blurple rounded-br-2xl hover:shadow-lg transition duration-100 ease-in'
>
</a>
}
</>
}
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> )
} }

View File

@ -7,36 +7,38 @@ import Link from 'next/link'
const SubmittedBotCard: React.FC<SubmittedBotProps> = ({ href, submit }) => { const SubmittedBotCard: React.FC<SubmittedBotProps> = ({ href, submit }) => {
return ( return (
<Link href={href}> (<Link
<a className='relative mx-auto px-4 py-5 w-full h-full text-black dark:text-white dark:bg-discord-black bg-little-white rounded-2xl shadow-xl transform hover:-translate-y-1 transition duration-100 ease-in'> href={href}
<div className='h-18'> className='relative mx-auto px-4 py-5 w-full h-full text-black dark:text-white dark:bg-discord-black bg-little-white rounded-2xl shadow-xl transform hover:-translate-y-1 transition duration-100 ease-in'>
<div className='flex'>
<div className='grow w-full'> <div className='h-18'>
<h2 className='text-lg'>{submit.id}</h2> <div className='flex'>
</div> <div className='grow w-full'>
<div className='absolute right-0 grid grid-cols-1 px-4 w-2/5 h-0'> <h2 className='text-lg'>{submit.id}</h2>
<Tag </div>
text={ <div className='absolute right-0 grid grid-cols-1 px-4 w-2/5 h-0'>
<> <Tag
<i text={
className={`fas fa-circle text-${ <>
[Status.offline, Status.online, Status.dnd][submit.state]?.color <i
}`} className={`fas fa-circle text-${
/>{' '} [Status.offline, Status.online, Status.dnd][submit.state]?.color
{['대기중', '승인됨', '거부됨'][submit.state]} }`}
</> />{' '}
} {['대기중', '승인됨', '거부됨'][submit.state]}
dark </>
/> }
</div> dark
/>
</div> </div>
<p className='mt-1.5 w-full h-6 text-left text-gray-400 text-sm font-medium truncate'>
{submit.intro.slice(0, 25)}
{submit.intro.length > 25 && '...'}
</p>
</div> </div>
</a> <p className='mt-1.5 w-full h-6 text-left text-gray-400 text-sm font-medium truncate'>
</Link> {submit.intro.slice(0, 25)}
{submit.intro.length > 25 && '...'}
</p>
</div>
</Link>)
) )
} }

View File

@ -37,27 +37,27 @@ const Tag: React.FC<LabelProps> = ({
{text} {text}
</a> </a>
) : ( ) : (
<Link href={href}> (<Link
<a href={href}
className={`${className ?? ''} text-center text-base ${ className={`${className ?? ''} text-center text-base ${
dark dark
? blurple ? blurple
? 'bg-discord-blurple text-white' ? 'bg-discord-blurple text-white'
: 'bg-little-white-hover hover:bg-little-white dark:bg-very-black' : 'bg-little-white-hover hover:bg-little-white dark:bg-very-black'
: github : github
? 'bg-gray-900 text-white hover:bg-gray-700' ? 'bg-gray-900 text-white hover:bg-gray-700'
: 'bg-little-white dark:bg-discord-black hover:bg-little-white-hover' : 'bg-little-white dark:bg-discord-black hover:bg-little-white-hover'
} ${ } ${
!blurple && !github ? 'text-black dark:text-gray-400' : 'hover:bg-little-white-hover' !blurple && !github ? 'text-black dark:text-gray-400' : 'hover:bg-little-white-hover'
} ${ } ${
circular circular
? `rounded-3xl ${bigger ? 'px-3.5 py-2.5' : 'px-2.5 py-1.5'}` ? `rounded-3xl ${bigger ? 'px-3.5 py-2.5' : 'px-2.5 py-1.5'}`
: `rounded ${bigger ? 'px-3 py-2' : 'px-2 py-1'}` : `rounded ${bigger ? 'px-3 py-2' : 'px-2 py-1'}`
} mr-1 mb-${marginBottom} dark:hover:bg-discord-dark-hover transition duration-100 ease-in`} } mr-1 mb-${marginBottom} dark:hover:bg-discord-dark-hover transition duration-100 ease-in`}>
>
{text} {text}
</a>
</Link> </Link>)
) )
) : ( ) : (
<a <a

View File

@ -8,53 +8,53 @@ const Tooltip: React.FC<TooltipProps> = ({
text, text,
}) => { }) => {
return href ? ( return href ? (
<Link href={href}> (<Link href={href} className='inline'>
<a className='inline'>
<div className='relative inline py-3'> <div className='relative inline py-3'>
<div className='group relative inline-block text-center cursor-pointer'> <div className='group relative inline-block text-center cursor-pointer'>
{children} {children}
<div <div
className={`opacity-0 ${ className={`opacity-0 ${
size === 'small' ? 'w-44' : 'w-60' size === 'small' ? 'w-44' : 'w-60'
} bg-black text-white text-center text-xs rounded-lg py-2 px-3 absolute z-10 group-hover:opacity-100 bottom-full -left-4 pointer-events-none`} } bg-black text-white text-center text-xs rounded-lg py-2 px-3 absolute z-10 group-hover:opacity-100 bottom-full -left-4 pointer-events-none`}
> >
{text} {text}
{direction === 'left' ? ( {direction === 'left' ? (
<svg <svg
className='absolute left-5 top-full mr-3 h-2 text-black' className='absolute left-5 top-full mr-3 h-2 text-black'
x='0px' x='0px'
y='0px' y='0px'
viewBox='0 0 255 255' viewBox='0 0 255 255'
xmlSpace='preserve' xmlSpace='preserve'
> >
<polygon className='fill-current' points='0,0 127.5,127.5 255,0' /> <polygon className='fill-current' points='0,0 127.5,127.5 255,0' />
</svg> </svg>
) : direction === 'center' ? ( ) : direction === 'center' ? (
<svg <svg
className='absolute left-0 top-full w-full h-2 text-black' className='absolute left-0 top-full w-full h-2 text-black'
x='0px' x='0px'
y='0px' y='0px'
viewBox='0 0 255 255' viewBox='0 0 255 255'
xmlSpace='preserve' xmlSpace='preserve'
> >
<polygon className='fill-current' points='0,0 127.5,127.5 255,0' /> <polygon className='fill-current' points='0,0 127.5,127.5 255,0' />
</svg> </svg>
) : ( ) : (
<svg <svg
className='absolute right-5 top-full mr-3 h-2 text-black' className='absolute right-5 top-full mr-3 h-2 text-black'
x='0px' x='0px'
y='0px' y='0px'
viewBox='0 0 255 255' viewBox='0 0 255 255'
xmlSpace='preserve' xmlSpace='preserve'
> >
<polygon className='fill-current' points='0,0 127.5,127.5 255,0' /> <polygon className='fill-current' points='0,0 127.5,127.5 255,0' />
</svg> </svg>
)} )}
</div>
</div> </div>
</div> </div>
</a> </div>
</Link>
</Link>)
) : ( ) : (
<a className='inline'> <a className='inline'>
<div className='relative inline py-3'> <div className='relative inline py-3'>

View File

@ -20,7 +20,6 @@ const NextConfig = {
NEXT_PUBLIC_RELEASE_VERSION: VERSION, NEXT_PUBLIC_RELEASE_VERSION: VERSION,
SENTRY_SKIP_AUTO_RELEASE: true SENTRY_SKIP_AUTO_RELEASE: true
}, },
future: {},
experimental: { experimental: {
scrollRestoration: true scrollRestoration: true
}, },
@ -42,6 +41,9 @@ const NextConfig = {
sentry: process.env.CI ? { sentry: process.env.CI ? {
disableServerWebpackPlugin: true, disableServerWebpackPlugin: true,
disableClientWebpackPlugin: true, disableClientWebpackPlugin: true,
} : {} hideSourceMaps: true,
} : {
hideSourceMaps: true,
},
} }
module.exports = withSentryConfig(withPWA(NextConfig)) module.exports = withSentryConfig(withPWA(NextConfig))

View File

@ -14,6 +14,10 @@
"docker": "docker-compose up -d --build" "docker": "docker-compose up -d --build"
}, },
"dependencies": { "dependencies": {
"@dnd-kit/core": "^6.0.8",
"@dnd-kit/modifiers": "^6.0.1",
"@dnd-kit/sortable": "^7.0.2",
"@dnd-kit/utilities": "^3.2.1",
"@fortawesome/fontawesome-free": "5.15.4", "@fortawesome/fontawesome-free": "5.15.4",
"@hcaptcha/react-hcaptcha": "^1.8.1", "@hcaptcha/react-hcaptcha": "^1.8.1",
"@sentry/nextjs": "^7.57.0", "@sentry/nextjs": "^7.57.0",
@ -37,7 +41,7 @@
"knex": "^2.4.0", "knex": "^2.4.0",
"mongoose": "6.11.3", "mongoose": "6.11.3",
"mysql": "2.18.1", "mysql": "2.18.1",
"next": "^12.3.2", "next": "^13.5.2",
"next-connect": "0.10.1", "next-connect": "0.10.1",
"next-pwa": "^5.6.0", "next-pwa": "^5.6.0",
"next-seo": "^6.1.0", "next-seo": "^6.1.0",
@ -46,15 +50,14 @@
"postcss": "^8.4.24", "postcss": "^8.4.24",
"postcss-preset-env": "8.5.1", "postcss-preset-env": "8.5.1",
"rc-tooltip": "5.1.1", "rc-tooltip": "5.1.1",
"react": "17.0.2", "react": "^18.2.0",
"react-adsense": "0.1.0", "react-adsense": "0.1.0",
"react-dom": "17.0.2", "react-dom": "^18.2.0",
"react-ga": "^3.3.1", "react-ga": "^3.3.1",
"react-hotkeys": "2.0.0", "react-hotkeys": "2.0.0",
"react-responsive-modal": "6.4.2", "react-responsive-modal": "6.4.2",
"react-select": "4.3.1", "react-select": "^5.7.5",
"react-showdown": "2.3.1", "react-showdown": "2.3.1",
"react-sortable-hoc": "2.0.0",
"react-use-clipboard": "1.0.9", "react-use-clipboard": "1.0.9",
"sanitize-html": "^2.11.0", "sanitize-html": "^2.11.0",
"tailwindcss": "^3.3.2", "tailwindcss": "^3.3.2",
@ -75,8 +78,7 @@
"@types/node-fetch": "^2.5.12", "@types/node-fetch": "^2.5.12",
"@types/nprogress": "0.2.0", "@types/nprogress": "0.2.0",
"@types/rc-tooltip": "^3.7.4", "@types/rc-tooltip": "^3.7.4",
"@types/react": "^17.0.15", "@types/react": "^18.2.22",
"@types/react-select": "^4.0.17",
"@types/sanitize-html": "^2.8.0", "@types/sanitize-html": "^2.8.0",
"@types/url-regex-safe": "1.0.0", "@types/url-regex-safe": "1.0.0",
"@typescript-eslint/eslint-plugin": "^5.61.0", "@typescript-eslint/eslint-plugin": "^5.61.0",

View File

@ -7,25 +7,31 @@ import { ErrorMessage } from '@utils/Constants'
const Container = dynamic(() => import('@components/Container')) const Container = dynamic(() => import('@components/Container'))
const MyError: NextPage = () => { const MyError: NextPage = () => {
return <div return (
className='flex items-center h-screen select-none px-20' <div
> className='flex items-center h-screen select-none px-20'
<Container> >
<h2 className='text-4xl font-semibold'>{getRandom(ErrorMessage)}</h2> <Container>
<p className='text-md mt-1'> . !</p> <h2 className='text-4xl font-semibold'>{getRandom(ErrorMessage)}</h2>
<a className='text-sm text-blue-500 hover:text-blue-400' href='https://status.koreanbots.dev' target='_blank' rel='noreferrer'> </a> <p className='text-md mt-1'> . !</p>
<div> <a className='text-sm text-blue-500 hover:text-blue-400' href='https://status.koreanbots.dev' target='_blank' rel='noreferrer'> </a>
<Link href='/discord'> <div>
<a target='_blank' rel='noreferrer' className='text-lg hover:opacity-80 cursor-pointer'> <Link
href='/discord'
target='_blank'
rel='noreferrer'
className='text-lg hover:opacity-80 cursor-pointer'>
<i className='fab fa-discord' /> <i className='fab fa-discord' />
</Link>
<a href='https://twitter.com/koreanbots' target='_blank' rel='noreferrer' className='text-lg ml-2 hover:opacity-80 cursor-pointer'>
<i className='fab fa-twitter' />
</a> </a>
</Link> </div>
<a href='https://twitter.com/koreanbots' target='_blank' rel='noreferrer' className='text-lg ml-2 hover:opacity-80 cursor-pointer'> </Container>
<i className='fab fa-twitter' /> </div>
</a> )
</div>
</Container>
</div>
} }
export default MyError export default MyError

View File

@ -5,25 +5,31 @@ import dynamic from 'next/dynamic'
const Container = dynamic(() => import('@components/Container')) const Container = dynamic(() => import('@components/Container'))
const MyError: NextPage = () => { const MyError: NextPage = () => {
return <div return (
className='flex items-center h-screen select-none px-20' <div
> className='flex items-center h-screen select-none px-20'
<Container> >
<h2 className='text-4xl font-semibold'> ...</h2> <Container>
<p className='text-md mt-1'> !</p> <h2 className='text-4xl font-semibold'> ...</h2>
<a className='text-sm text-blue-500 hover:text-blue-400' href='https://status.koreanbots.dev' target='_blank' rel='noreferrer'> </a> <p className='text-md mt-1'> !</p>
<div> <a className='text-sm text-blue-500 hover:text-blue-400' href='https://status.koreanbots.dev' target='_blank' rel='noreferrer'> </a>
<Link href='/discord'> <div>
<a target='_blank' rel='noreferrer' className='text-lg hover:opacity-80 cursor-pointer'> <Link
href='/discord'
target='_blank'
rel='noreferrer'
className='text-lg hover:opacity-80 cursor-pointer'>
<i className='fab fa-discord' /> <i className='fab fa-discord' />
</Link>
<a href='https://twitter.com/koreanbots' target='_blank' rel='noreferrer' className='text-lg ml-2 hover:opacity-80 cursor-pointer'>
<i className='fab fa-twitter' />
</a> </a>
</Link> </div>
<a href='https://twitter.com/koreanbots' target='_blank' rel='noreferrer' className='text-lg ml-2 hover:opacity-80 cursor-pointer'> </Container>
<i className='fab fa-twitter' /> </div>
</a> )
</div>
</Container>
</div>
} }
export default MyError export default MyError

View File

@ -76,134 +76,154 @@ const AddBot:NextPage<AddBotProps> = ({ logged, user, csrfToken, theme }) => {
title:'새로운 봇 추가하기', description: '자신의 봇을 한국 디스코드 리스트에 등록하세요.' title:'새로운 봇 추가하기', description: '자신의 봇을 한국 디스코드 리스트에 등록하세요.'
}} /> }} />
</Login> </Login>
return <Container paddingTop className='py-5'> if(data?.data && data.code === 200) {
<NextSeo title='새로운 봇 추가하기' description='자신의 봇을 한국 디스코드 리스트에 등록하세요.' openGraph={{ setTimeout(
title:'새로운 봇 추가하기', description: '자신의 봇을 한국 디스코드 리스트에 등록하세요.' () => redirectTo(router, `/pendingBots/${data.data.id}/${data.data.date}`),
}} /> 1_000
<h1 className='text-3xl font-bold'> </h1> )
<div className='mt-1 mb-5'> }
, <span className='font-semibold'>{user.tag === '0' ? `@${user.username}` : `${user.username}#${user.tag}`}</span>! <a role='button' tabIndex={0} onKeyDown={toLogin} onClick={toLogin} className='text-discord-blurple cursor-pointer outline-none'> ?</a> return (
</div> <Container paddingTop className='py-5'>
{ <NextSeo title='새로운 봇 추가하기' description='자신의 봇을 한국 디스코드 리스트에 등록하세요.' openGraph={{
data ? data.code == 200 && data.data ? <Message type='success'> title:'새로운 봇 추가하기', description: '자신의 봇을 한국 디스코드 리스트에 등록하세요.'
<h2 className='text-lg font-extrabold'> !</h2> }} />
<p> ! . {redirectTo(router, `/pendingBots/${data.data.id}/${data.data.date}`)}</p> <h1 className='text-3xl font-bold'> </h1>
</Message> : <Message type='error'> <div className='mt-1 mb-5'>
<h2 className='text-lg font-extrabold'>{data.message || '오류가 발생했습니다.'}</h2> , <span className='font-semibold'>{user.tag === '0' ? `@${user.username}` : `${user.username}#${user.tag}`}</span>! <a role='button' tabIndex={0} onKeyDown={toLogin} onClick={toLogin} className='text-discord-blurple cursor-pointer outline-none'> ?</a>
<ul className='list-disc list-inside'> </div>
{data.errors?.map((el, n) => <li key={n}>{el}</li>)} {
</ul> data ? data.code == 200 && data.data ? <Message type='success'>
<h2 className='text-lg font-extrabold'> !</h2>
</Message> : <></> <p> ! .</p>
} </Message> : <Message type='error'>
<Formik initialValues={initialValues} <h2 className='text-lg font-extrabold'>{data.message || '오류가 발생했습니다.'}</h2>
validationSchema={AddBotSubmitSchema} <ul className='list-disc list-inside'>
onSubmit={() => setCaptcha(true)}> {data.errors?.map((el, n) => <li key={n}>{el}</li>)}
{({ errors, touched, values, isValid, setFieldTouched, setFieldValue }) => ( </ul>
<Form>
<div className='py-3'> </Message> : <></>
<Message type='warning'> }
<h2 className='text-lg font-extrabold'> !</h2> <Formik initialValues={initialValues}
<ul className='list-disc list-inside'> validationSchema={AddBotSubmitSchema}
<li><Link href='/discord'><a rel='noreferrer' target='_blank' className='text-blue-500 hover:text-blue-600'> </a></Link> ?</li> onSubmit={() => setCaptcha(true)}>
<li> <Link href='/guidelines'><a rel='noreferrer' target='_blank' className='text-blue-500 hover:text-blue-600'></a></Link> ?</li> {({ errors, touched, values, isValid, setFieldTouched, setFieldValue }) => (
<li> ? , .</li> <Form>
<li>, API에 .</li> <div className='py-3'>
</ul> <Message type='warning'>
</Message> <h2 className='text-lg font-extrabold'> !</h2>
</div> <ul className='list-disc list-inside'>
<Label For='agree' error={errors.agree && touched.agree ? errors.agree : null} grid={false}> <li><Link
<div className='flex items-center'> href='/discord'
<CheckBox name='agree' /> rel='noreferrer'
<strong className='text-sm ml-2'> , .</strong> 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> ? , .</li>
<li>, API에 .</li>
</ul>
</Message>
</div> </div>
</Label> <Label For='agree' error={errors.agree && touched.agree ? errors.agree : null} grid={false}>
<Divider /> <div className='flex items-center'>
<Advertisement /> <CheckBox name='agree' />
<Label For='id' label='봇 ID' labelDesc='봇의 클라이언트 ID를 의미합니다.' error={errors.id && touched.id ? errors.id : null} short required> <strong className='text-sm ml-2'> , .</strong>
<Input name='id' placeholder='653534001742741552' /> </div>
</Label> </Label>
<Label For='prefix' label='접두사' labelDesc='봇의 사용시 앞 쪽에 붙은 기호를 의미합니다. (Prefix)' error={errors.prefix && touched.prefix ? errors.prefix : null} short required> <Divider />
<Input name='prefix' placeholder='!' /> <Advertisement />
</Label> <Label For='id' label='봇 ID' labelDesc='봇의 클라이언트 ID를 의미합니다.' error={errors.id && touched.id ? errors.id : null} short required>
<Label For='library' label='라이브러리' labelDesc='봇에 사용된 라이브러리를 선택해주세요. 해당되는 라이브러리가 없다면 기타를 선택해주세요.' short required error={errors.library && touched.library ? errors.library : null}> <Input name='id' placeholder='653534001742741552' />
<Select options={library.map(el=> ({ label: el, value: el }))} handleChange={(value) => setFieldValue('library', value.value)} handleTouch={() => setFieldTouched('library', true)} /> </Label>
</Label> <Label For='prefix' label='접두사' labelDesc='봇의 사용시 앞 쪽에 붙은 기호를 의미합니다. (Prefix)' error={errors.prefix && touched.prefix ? errors.prefix : null} short required>
<Label For='category' label='카테고리' labelDesc='봇에 해당되는 카테고리를 선택해주세요' required error={errors.category && touched.category ? errors.category as string : null}> <Input name='prefix' placeholder='!' />
<Selects options={botCategories.map(el=> ({ label: el, value: el }))} handleChange={(value) => { </Label>
setFieldValue('category', value.map(v=> v.value)) <Label For='library' label='라이브러리' labelDesc='봇에 사용된 라이브러리를 선택해주세요. 해당되는 라이브러리가 없다면 기타를 선택해주세요.' short required error={errors.library && touched.library ? errors.library : null}>
}} handleTouch={() => setFieldTouched('category', true)} values={values.category as string[]} setValues={(value) => setFieldValue('category', value)} /> <Select options={library.map(el=> ({ label: el, value: el }))} handleChange={(value) => setFieldValue('library', value.value)} handleTouch={() => setFieldTouched('library', true)} />
<p className='text-gray-400 mt-1 text-sm'> 3 . . <strong> .</strong><br/> </Label>
<a className='text-blue-500 hover:text-blue-400' href='https://contents.koreanbots.dev/categories'></a> !</p> <Label For='category' label='카테고리' labelDesc='봇에 해당되는 카테고리를 선택해주세요' required error={errors.category && touched.category ? errors.category as string : null}>
</Label> <Selects options={botCategories.map(el=> ({ label: el, value: el }))} handleChange={(value) => {
<Divider /> setFieldValue('category', value.map(v=> v.value))
<Label For='website' label='웹사이트' labelDesc='봇의 웹사이트를 작성해주세요.' error={errors.website && touched.website ? errors.website : null}> }} handleTouch={() => setFieldTouched('category', true)} values={values.category as string[]} setValues={(value) => setFieldValue('category', value)} />
<Input name='website' placeholder='https://koreanbots.dev' /> <p className='text-gray-400 mt-1 text-sm'> 3 . . <strong> .</strong><br/>
</Label> <a className='text-blue-500 hover:text-blue-400' href='https://contents.koreanbots.dev/categories'></a> !</p>
<Label For='git' label='Git URL' labelDesc='봇 소스코드의 Git 주소를 입력해주세요. (오픈소스인 경우)' error={errors.git && touched.git ? errors.git : null}> </Label>
<Input name='git' placeholder='https://github.com/koreanbots/koreanbots'/> <Divider />
</Label> <Label For='website' label='웹사이트' labelDesc='봇의 웹사이트를 작성해주세요.' error={errors.website && touched.website ? errors.website : null}>
<Label For='inviteLink' label='초대링크' labelDesc='봇의 초대링크입니다. 비워두시면 자동으로 생성합니다.' error={errors.url && touched.url ? errors.url : null}> <Input name='website' placeholder='https://koreanbots.dev' />
<Input name='url' placeholder='https://discord.com/oauth2/authorize?client_id=653534001742741552&scope=bot&permissions=0' /> </Label>
<span className='text-gray-400 mt-1 text-sm'> <Label For='git' label='Git URL' labelDesc='봇 소스코드의 Git 주소를 입력해주세요. (오픈소스인 경우)' error={errors.git && touched.git ? errors.git : null}>
<Link href='/calculator'> <Input name='git' placeholder='https://github.com/koreanbots/koreanbots'/>
<a rel='noreferrer' target='_blank' className='text-blue-500 hover:text-blue-400'></a> </Label>
</Link> ! <Label For='inviteLink' label='초대링크' labelDesc='봇의 초대링크입니다. 비워두시면 자동으로 생성합니다.' error={errors.url && touched.url ? errors.url : null}>
</span> <Input name='url' placeholder='https://discord.com/oauth2/authorize?client_id=653534001742741552&scope=bot&permissions=0' />
</Label> <span className='text-gray-400 mt-1 text-sm'>
{ <Link
values.category.includes('빗금 명령어') && <Message type='warning'> href='/calculator'
<h2 className='text-lg font-semibold'> (Slash Command) .</h2> rel='noreferrer'
<p> . target='_blank'
.</p> className='text-blue-500 hover:text-blue-400'>
</Message>
} </Link> !
<Label For='discord' label='지원 디스코드 서버' labelDesc='봇의 지원 디스코드 서버를 입력해주세요. (봇에 대해 도움을 받을 수 있는 공간입니다.)' error={errors.discord && touched.discord ? errors.discord : null} short> </span>
<div className='flex items-center'> </Label>
discord.gg/<Input name='discord' placeholder='JEh53MQ' /> {
</div> values.category.includes('빗금 명령어') && <Message type='warning'>
</Label> <h2 className='text-lg font-semibold'> (Slash Command) .</h2>
<Divider /> <p> .
<Label For='intro' label='봇 소개' labelDesc='봇을 소개할 수 있는 간단한 설명을 적어주세요. (최대 60자)' error={errors.intro && touched.intro ? errors.intro : null} required> .</p>
<Input name='intro' placeholder='국내 봇을 한 곳에서.' /> </Message>
</Label> }
<Label For='intro' label='봇 설명' labelDesc={<> ! ( 1500)<br/> !</>} error={errors.desc && touched.desc ? errors.desc : null} required> <Label For='discord' label='지원 디스코드 서버' labelDesc='봇의 지원 디스코드 서버를 입력해주세요. (봇에 대해 도움을 받을 수 있는 공간입니다.)' error={errors.discord && touched.discord ? errors.discord : null} short>
<TextArea max={1500} name='desc' placeholder='봇에 대해 최대한 자세히 설명해주세요!' theme={theme === 'dark' ? 'dark' : 'light'} value={values.desc} setValue={(value) => setFieldValue('desc', value)} /> <div className='flex items-center'>
</Label> discord.gg/<Input name='discord' placeholder='JEh53MQ' />
<Label For='preview' label='설명 미리보기' labelDesc='다음 결과는 실제와 다를 수 있습니다.'> </div>
<Segment> </Label>
<Markdown text={values.desc} /> <Divider />
</Segment> <Label For='intro' label='봇 소개' labelDesc='봇을 소개할 수 있는 간단한 설명을 적어주세요. (최대 60자)' error={errors.intro && touched.intro ? errors.intro : null} required>
</Label> <Input name='intro' placeholder='국내 봇을 한 곳에서.' />
<Divider /> </Label>
<p className='text-base mt-2 mb-5'> <Label For='intro' label='봇 설명' labelDesc={<> ! ( 1500)<br/> !</>} error={errors.desc && touched.desc ? errors.desc : null} required>
<span className='text-red-500 font-semibold'> *</span> = <TextArea max={1500} name='desc' placeholder='봇에 대해 최대한 자세히 설명해주세요!' theme={theme === 'dark' ? 'dark' : 'light'} value={values.desc} setValue={(value) => setFieldValue('desc', value)} />
</p> </Label>
{ <Label For='preview' label='설명 미리보기' labelDesc='다음 결과는 실제와 다를 수 있습니다.'>
captcha ? <Captcha ref={captchaRef} dark={theme === 'dark'} onVerify={(token) => { <Segment>
submitBot(values, token) <Markdown text={values.desc} />
window.scrollTo({ top: 0 }) </Segment>
setCaptcha(false) </Label>
captchaRef?.current?.resetCaptcha() <Divider />
}} /> : <> <p className='text-base mt-2 mb-5'>
{ <span className='text-red-500 font-semibold'> *</span> =
touchedSumbit && !isValid && <div className='my-1 text-red-500 text-xs font-light'> . .</div> </p>
} {
<Button type='submit' onClick={() => { captcha ? <Captcha ref={captchaRef} dark={theme === 'dark'} onVerify={(token) => {
setTouched(true) submitBot(values, token)
if(!isValid) window.scrollTo({ top: 0 }) window.scrollTo({ top: 0 })
} }> setCaptcha(false)
<> captchaRef?.current?.resetCaptcha()
<i className='far fa-paper-plane'/> }} /> : <>
</> {
</Button> touchedSumbit && !isValid && <div className='my-1 text-red-500 text-xs font-light'> . .</div>
</> }
} <Button type='submit' onClick={() => {
</Form> setTouched(true)
)} if(!isValid) window.scrollTo({ top: 0 })
</Formik> } }>
<Advertisement /> <>
</Container> <i className='far fa-paper-plane'/>
</>
</Button>
</>
}
</Form>
)}
</Formik>
<Advertisement />
</Container>
)
} }
export const getServerSideProps = async (ctx: NextPageContext) => { export const getServerSideProps = async (ctx: NextPageContext) => {

View File

@ -76,126 +76,142 @@ const AddServer:NextPage<AddServerProps> = ({ logged, user, csrfToken, server, s
title:'새로운 서버 추가하기', description: '자신의 서버를 한국 디스코드 리스트에 등록하세요.' title:'새로운 서버 추가하기', description: '자신의 서버를 한국 디스코드 리스트에 등록하세요.'
}} /> }} />
</Login> </Login>
return <Container paddingTop className='py-5'> if(data?.data && data.code == 200) {
<NextSeo title='새로운 서버 추가하기' description='자신의 서버를 한국 디스코드 리스트에 등록하세요.' openGraph={{ setTimeout(
title:'새로운 서버 추가하기', description: '자신의 서버를 한국 디스코드 리스트에 등록하세요.' () => redirectTo(router, `/servers/${router.query.id}`),
}} /> 1_000
<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> return (
</div> <Container paddingTop className='py-5'>
{ <NextSeo title='새로운 서버 추가하기' description='자신의 서버를 한국 디스코드 리스트에 등록하세요.' openGraph={{
data ? data.code == 200 && data.data ? <Message type='success'> title:'새로운 서버 추가하기', description: '자신의 서버를 한국 디스코드 리스트에 등록하세요.'
<h2 className='text-lg font-extrabold'> !</h2> }} />
<p> ! ! {redirectTo(router, `/servers/${router.query.id}`)}</p> <h1 className='text-3xl font-bold'> </h1>
</Message> : <Message type='error'> <div className='mt-1 mb-5'>
<h2 className='text-lg font-extrabold'>{data.message || '오류가 발생했습니다.'}</h2> , <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>
<ul className='list-disc list-inside'> </div>
{data.errors?.map((el, n) => <li key={n}>{el}</li>)} {
</ul> data ? data.code == 200 && data.data ? <Message type='success'>
<h2 className='text-lg font-extrabold'> !</h2>
</Message> : <></> <p> ! !</p>
} </Message> : <Message type='error'>
{ <h2 className='text-lg font-extrabold'>{data.message || '오류가 발생했습니다.'}</h2>
server ? <Message type='warning'> <ul className='list-disc list-inside'>
<h2 className='text-lg font-extrabold'> .</h2> {data.errors?.map((el, n) => <li key={n}>{el}</li>)}
</Message> : </ul>
!serverData ? <Message type='info'>
<h2 className='text-lg font-extrabold'> .</h2> </Message> : <></>
<p> .</p> }
<p> 1 .</p> {
</Message> server ? <Message type='warning'>
: serverData.admins.includes(user.id) || serverData.owner.includes(user.id) ? <Formik initialValues={initialValues} <h2 className='text-lg font-extrabold'> .</h2>
validationSchema={AddServerSubmitSchema} </Message> :
onSubmit={() => setCaptcha(true)}> !serverData ? <Message type='info'>
{({ errors, touched, values, isValid, setFieldTouched, setFieldValue }) => ( <h2 className='text-lg font-extrabold'> .</h2>
<Form> <p> .</p>
<div className='py-3'> <p> 1 .</p>
<Message type='warning'> </Message>
<h2 className='text-lg font-extrabold'> !</h2> : serverData.admins.includes(user.id) || serverData.owner.includes(user.id) ? <Formik initialValues={initialValues}
<ul className='list-disc list-inside'> validationSchema={AddServerSubmitSchema}
<li><Link href='/discord'><a rel='noreferrer' target='_blank' className='text-blue-500 hover:text-blue-600'> </a></Link> .</li> onSubmit={() => setCaptcha(true)}>
<li> <Link href='/guidelines'><a rel='noreferrer' target='_blank' className='text-blue-500 hover:text-blue-600'></a></Link> ?</li> {({ errors, touched, values, isValid, setFieldTouched, setFieldValue }) => (
<li> <strong></strong> .</li> <Form>
<li> .</li> <div className='py-3'>
<li>, API에 .</li> <Message type='warning'>
</ul> <h2 className='text-lg font-extrabold'> !</h2>
</Message> <ul className='list-disc list-inside'>
</div> <li><Link
<Label For='agree' error={errors.agree && touched.agree ? errors.agree : null} grid={false}> href='/discord'
<div className='flex items-center'> rel='noreferrer'
<CheckBox name='agree' /> target='_blank'
<strong className='text-sm ml-2'> , .</strong> 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> </div>
</Label> <Label For='agree' error={errors.agree && touched.agree ? errors.agree : null} grid={false}>
<Divider /> <div className='flex items-center'>
<Label For='id' label='서버' labelDesc='등록하시는 대상 서버 입니다.'> <CheckBox name='agree' />
<p> <strong className='text-sm ml-2'> , .</strong>
<strong>{serverData.name}</strong> </div>
<br/> ID: {router.query.id} </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> </p>
</Label> {
<Divider /> captcha ? <Captcha ref={captchaRef} dark={theme === 'dark'} onVerify={(token) => {
<Label For='category' label='카테고리' labelDesc='서버에 해당되는 카테고리를 선택해주세요' required error={errors.category && touched.category ? errors.category as string : null}> submitServer(router.query.id as string, values, token)
<Selects options={serverCategories.map(el=> ({ label: el, value: el }))} handleChange={(value) => { window.scrollTo({ top: 0 })
setFieldValue('category', value.map(v=> v.value)) setCaptcha(false)
}} handleTouch={() => setFieldTouched('category', true)} values={values.category as string[]} setValues={(value) => setFieldValue('category', value)} /> captchaRef?.current?.resetCaptcha()
<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> touchedSumbit && !isValid && <div className='my-1 text-red-500 text-xs font-light'> . .</div>
<div className='flex items-center'> }
discord.gg/<Input name='invite' placeholder='JEh53MQ' /> <Button type='submit' onClick={() => {
</div> setTouched(true)
</Label> if(!isValid) window.scrollTo({ top: 0 })
<Divider /> } }>
<Label For='intro' label='서버 소개' labelDesc='서버를 소개할 수 있는 간단한 설명을 적어주세요. (최대 60자)' error={errors.intro && touched.intro ? errors.intro : null} required> <>
<Input name='intro' placeholder={getRandom(ServerIntroList)} /> <i className='far fa-paper-plane'/>
</Label> </>
<Label For='desc' label='서버 설명' labelDesc={<> ! ( 1500)<br/> !</>} error={errors.desc && touched.desc ? errors.desc : null} required> </Button>
<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='다음 결과는 실제와 다를 수 있습니다.'> </Form>
<Segment> )}
<Markdown text={values.desc} /> </Formik>
</Segment> : <Forbidden />
</Label> }
<Divider /> </Container>
<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) => { export const getServerSideProps = async (ctx: NextPageContext) => {
const parsed = parseCookie(ctx.req) const parsed = parseCookie(ctx.req)
const user = await get.Authorization(parsed?.token) const user = await get.Authorization(parsed?.token)
const server = await get.server.load(ctx.query.id as string) || null const server = (await get.server.load(ctx.query.id as string)) || null
const serverData = await get.serverData(ctx.query.id as string) || null const serverData = (await get.serverData(ctx.query.id as string)) || null
return { props: { return { props: {
logged: !!user, user: await get.user.load(user || ''), logged: !!user, user: await get.user.load(user || ''),
csrfToken: getToken(ctx.req, ctx.res), csrfToken: getToken(ctx.req, ctx.res),

View File

@ -60,270 +60,276 @@ const ManageBotPage:NextPage<ManageBotProps> = ({ bot, user, csrfToken, theme })
<NextSeo title='봇 정보 수정하기' description='봇의 정보를 수정합니다.'/> <NextSeo title='봇 정보 수정하기' description='봇의 정보를 수정합니다.'/>
</Login> </Login>
if(!(bot.owners as User[]).find(el => el.id === user.id) && !checkUserFlag(user.flags, 'staff')) return <Forbidden /> if(!(bot.owners as User[]).find(el => el.id === user.id) && !checkUserFlag(user.flags, 'staff')) return <Forbidden />
return <Container paddingTop className='pt-5 pb-10'> return (
<NextSeo title={`${bot.name} 수정하기`} description='봇의 정보를 수정합니다.'/> <Container paddingTop className='pt-5 pb-10'>
<h1 className='text-3xl font-bold mb-8'> </h1> <NextSeo title={`${bot.name} 수정하기`} description='봇의 정보를 수정합니다.'/>
<Formik initialValues={cleanObject({ <h1 className='text-3xl font-bold mb-8'> </h1>
agree: false, <Formik initialValues={cleanObject({
id: bot.id, agree: false,
prefix: bot.prefix, id: bot.id,
library: bot.lib, prefix: bot.prefix,
category: bot.category, library: bot.lib,
intro: bot.intro, category: bot.category,
desc: bot.desc, intro: bot.intro,
website: bot.web, desc: bot.desc,
url: bot.url, website: bot.web,
git: bot.git, url: bot.url,
discord: bot.discord, git: bot.git,
_csrf: csrfToken discord: bot.discord,
})} _csrf: csrfToken
validationSchema={ManageBotSchema} })}
onSubmit={submitBot}> validationSchema={ManageBotSchema}
{({ errors, touched, values, setFieldTouched, setFieldValue }) => ( onSubmit={submitBot}>
<Form> {({ errors, touched, values, setFieldTouched, setFieldValue }) => (
<div className='md:flex text-center md:text-left'> <Form>
<DiscordAvatar userID={bot.id} className='md:mx-1 mx-auto rounded-full'/> <div className='md:flex text-center md:text-left'>
<div className='md:w-2/3 px-8 py-6'> <DiscordAvatar userID={bot.id} className='md:mx-1 mx-auto rounded-full'/>
<h1 className='text-3xl font-bold'>{bot.name}#{bot.tag}</h1> <div className='md:w-2/3 px-8 py-6'>
<h2>ID: {bot.id}</h2> <h1 className='text-3xl font-bold'>{bot.name}#{bot.tag}</h1>
<h2>ID: {bot.id}</h2>
</div>
</div> </div>
</div> {
{ data ? data.code === 200 ? <div className='mt-4'>
data ? data.code === 200 ? <div className='mt-4'> <Redirect to={makeBotURL(bot)}>
<Redirect to={makeBotURL(bot)}> <Message type='success'>
<Message type='success'> <h2 className='text-lg font-extrabold'> .</h2>
<h2 className='text-lg font-extrabold'> .</h2> <p> !</p>
<p> !</p> </Message>
</Redirect>
</div> : <div className='mt-4'>
<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> </Message>
</Redirect> </div> : ''
}
</div> : <div className='mt-4'> <Label For='prefix' label='접두사' labelDesc='봇의 사용시 앞 쪽에 붙은 기호를 의미합니다. (Prefix)' error={errors.prefix && touched.prefix ? errors.prefix : null} short required>
<Message type='error'> <Input name='prefix' placeholder='!' />
<h2 className='text-lg font-extrabold'>{data.message || '오류가 발생했습니다.'}</h2> </Label>
<ul className='list-disc list-inside'> <Label For='library' label='라이브러리' labelDesc='봇에 사용된 라이브러리를 선택해주세요. 해당되는 라이브러리가 없다면 기타를 선택해주세요.' short required error={errors.library && touched.library ? errors.library : null}>
{data.errors?.map((el, n) => <li key={n}>{el}</li>)} <Select value={{ label: bot.lib, value: bot.lib }} options={library.map(el=> ({ label: el, value: el }))} handleChange={(value) => setFieldValue('library', value.value)} handleTouch={() => setFieldTouched('library', true)} />
</ul> </Label>
<Label For='category' label='카테고리' labelDesc='봇에 해당되는 카테고리를 선택해주세요' required error={errors.category && touched.category ? errors.category as string : null}>
<Selects options={botCategories.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>
<Divider />
<Label For='website' label='웹사이트' labelDesc='봇의 웹사이트를 작성해주세요.' error={errors.website && touched.website ? errors.website : null}>
<Input name='website' placeholder='https://koreanbots.dev' />
</Label>
<Label For='git' label='Git URL' labelDesc='봇 소스코드의 Git 주소를 입력해주세요 (오픈소스인 경우)' error={errors.git && touched.git ? errors.git : null}>
<Input name='git' placeholder='https://github.com/koreanbots/koreanbots'/>
</Label>
<Label For='inviteLink' label='초대링크' labelDesc='봇의 초대링크입니다. 비워두시면 자동으로 생성합니다.' error={errors.url && touched.url ? errors.url : null}>
<Input name='url' placeholder='https://discord.com/oauth2/authorize?client_id=653534001742741552&scope=bot&permissions=0' />
<span className='text-gray-400 mt-1 text-sm'>
<Link
href='/calculator'
rel='noreferrer'
target='_blank'
className='text-blue-500 hover:text-blue-400'>
</Link> !
</span>
</Label>
{
values.category.includes('빗금 명령어') && <Message type='warning'>
<h2 className='text-lg font-semibold'> (Slash Command) .</h2>
<p> .
.</p>
</Message> </Message>
</div> : '' }
} <Label For='discord' label='지원 디스코드 서버' labelDesc='봇의 지원 디스코드 서버를 입력해주세요. (봇에 대해 도움을 받을 수 있는 공간입니다.)' error={errors.discord && touched.discord ? errors.discord : null} short>
<Label For='prefix' label='접두사' labelDesc='봇의 사용시 앞 쪽에 붙은 기호를 의미합니다. (Prefix)' error={errors.prefix && touched.prefix ? errors.prefix : null} short required> <div className='flex items-center'>
<Input name='prefix' placeholder='!' /> discord.gg/<Input name='discord' placeholder='JEh53MQ' />
</Label> </div>
<Label For='library' label='라이브러리' labelDesc='봇에 사용된 라이브러리를 선택해주세요. 해당되는 라이브러리가 없다면 기타를 선택해주세요.' short required error={errors.library && touched.library ? errors.library : null}> </Label>
<Select value={{ label: bot.lib, value: bot.lib }} options={library.map(el=> ({ label: el, value: el }))} handleChange={(value) => setFieldValue('library', value.value)} handleTouch={() => setFieldTouched('library', true)} /> <Divider />
</Label> <Label For='intro' label='봇 소개' labelDesc='봇을 소개할 수 있는 간단한 설명을 적어주세요. (최대 60자)' error={errors.intro && touched.intro ? errors.intro : null} required>
<Label For='category' label='카테고리' labelDesc='봇에 해당되는 카테고리를 선택해주세요' required error={errors.category && touched.category ? errors.category as string : null}> <Input name='intro' placeholder='국내 봇을 한 곳에서.' />
<Selects options={botCategories.map(el=> ({ label: el, value: el }))} handleChange={(value) => { </Label>
setFieldValue('category', value.map(v=> v.value)) <Label For='intro' label='봇 설명' labelDesc={<> ! ( 1500)<br/> !</>} error={errors.desc && touched.desc ? errors.desc : null} required>
}} handleTouch={() => setFieldTouched('category', true)} values={values.category as string[]} setValues={(value) => setFieldValue('category', value)} /> <TextArea name='desc' placeholder='봇에 대해 최대한 자세히 설명해주세요!' theme={theme === 'dark' ? 'dark' : 'light'} value={values.desc} setValue={(value) => setFieldValue('desc', value)} max={1500} />
<span className='text-gray-400 mt-1 text-sm'> 3 . . <strong> .</strong></span> </Label>
</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>
<Button type='submit' onClick={() => window.scrollTo({ top: 0 })}>
<>
<i className='far fa-save'/>
</>
</Button>
</Form>
)}
</Formik>
{
(checkUserFlag(user.flags, 'staff') || (bot.owners as User[])[0].id === user.id) && <div className='py-4'>
<Divider /> <Divider />
<Label For='website' label='웹사이트' labelDesc='봇의 웹사이트를 작성해주세요.' error={errors.website && touched.website ? errors.website : null}> <h2 className='text-2xl font-semibold pb-2'></h2>
<Input name='website' placeholder='https://koreanbots.dev' /> <Segment>
</Label> <div className='lg:flex items-center'>
<Label For='git' label='Git URL' labelDesc='봇 소스코드의 Git 주소를 입력해주세요 (오픈소스인 경우)' error={errors.git && touched.git ? errors.git : null}> <div className='grow py-1'>
<Input name='git' placeholder='https://github.com/koreanbots/koreanbots'/> <h3 className='text-lg font-semibold'> </h3>
</Label> <p className='text-gray-400'> .</p>
<Label For='inviteLink' label='초대링크' labelDesc='봇의 초대링크입니다. 비워두시면 자동으로 생성합니다.' error={errors.url && touched.url ? errors.url : null}> </div>
<Input name='url' placeholder='https://discord.com/oauth2/authorize?client_id=653534001742741552&scope=bot&permissions=0' /> <Button onClick={() => setAdminModal(true)} className='h-10 bg-red-500 hover:opacity-80 text-white lg:w-1/8'><i className='fas fa-user-cog' /> </Button>
<span className='text-gray-400 mt-1 text-sm'> <Modal full header='관리자 수정' isOpen={adminModal} dark={theme === 'dark'} onClose={() => setAdminModal(false)} closeIcon>
<Link href='/calculator'> <Formik initialValues={{ owners: (bot.owners as User[]), id: '', _captcha: '' }} onSubmit={async (v) => {
<a rel='noreferrer' target='_blank' className='text-blue-500 hover:text-blue-400'></a> const res = await Fetch(`/bots/${bot.id}/owners`, { method: 'PATCH', body: JSON.stringify({
</Link> ! _captcha: v._captcha,
</span> _csrf: csrfToken,
</Label> owners: v.owners.map(el => el.id)
{ }) })
values.category.includes('빗금 명령어') && <Message type='warning'> if(res.code === 200) {
<h2 className='text-lg font-semibold'> (Slash Command) .</h2> alert('성공적으로 수정했습니다.')
<p> . router.push(makeBotURL(bot))
.</p> } else {
</Message> alert(res.message)
} setAdminModal(false)
<Label For='discord' label='지원 디스코드 서버' labelDesc='봇의 지원 디스코드 서버를 입력해주세요. (봇에 대해 도움을 받을 수 있는 공간입니다.)' error={errors.discord && touched.discord ? errors.discord : null} short> }
<div className='flex items-center'> }}>
discord.gg/<Input name='discord' placeholder='JEh53MQ' /> {
</div> ({ values, setFieldValue }) => <Form>
</Label> <Message type='warning'>
<Divider /> <p> . .</p>
<Label For='intro' label='봇 소개' labelDesc='봇을 소개할 수 있는 간단한 설명을 적어주세요. (최대 60자)' error={errors.intro && touched.intro ? errors.intro : null} required> </Message>
<Input name='intro' placeholder='국내 봇을 한 곳에서.' /> <div className='py-4'>
</Label> <h2 className='text-md my-1'> ID를 .</h2>
<Label For='intro' label='봇 설명' labelDesc={<> ! ( 1500)<br/> !</>} error={errors.desc && touched.desc ? errors.desc : null} required> <div className='flex flex-wrap'>
<TextArea name='desc' placeholder='봇에 대해 최대한 자세히 설명해주세요!' theme={theme === 'dark' ? 'dark' : 'light'} value={values.desc} setValue={(value) => setFieldValue('desc', value)} max={1500} /> {
</Label> (values.owners as User[]).map((el, n) => <Tag className='flex items-center' text={<>
<Label For='preview' label='설명 미리보기' labelDesc='다음 결과는 실제와 다를 수 있습니다'> <DiscordAvatar userID={el.id} size={128} className='w-6 h-6 mr-1 rounded-full' /> {el.tag === '0' ? `${el.globalName} (@${el.username})` : `${el.username}#${el.tag}`}
<Segment> {
<Markdown text={values.desc} /> n !== 0 && <button className='ml-0.5 hover:text-red-500' onClick={() => {
</Segment> setFieldValue('owners', (() => {
</Label> const arr = [...values.owners]
<Divider /> arr.splice(n, 1)
<p className='text-base mt-2 mb-5'> return arr
<span className='text-red-500 font-semibold'> *</span> = })())
</p> }}>
<Button type='submit' onClick={() => window.scrollTo({ top: 0 })}> <i className='fas fa-times' />
<> </button>
<i className='far fa-save'/> }
</> </>} key={el.id} />)
</Button>
</Form>
)}
</Formik>
{
(checkUserFlag(user.flags, 'staff') || (bot.owners as User[])[0].id === user.id) && <div className='py-4'>
<Divider />
<h2 className='text-2xl font-semibold pb-2'></h2>
<Segment>
<div className='lg:flex items-center'>
<div className='grow py-1'>
<h3 className='text-lg font-semibold'> </h3>
<p className='text-gray-400'> .</p>
</div>
<Button onClick={() => setAdminModal(true)} className='h-10 bg-red-500 hover:opacity-80 text-white lg:w-1/8'><i className='fas fa-user-cog' /> </Button>
<Modal full header='관리자 수정' isOpen={adminModal} dark={theme === 'dark'} onClose={() => setAdminModal(false)} closeIcon>
<Formik initialValues={{ owners: (bot.owners as User[]), id: '', _captcha: '' }} onSubmit={async (v) => {
const res = await Fetch(`/bots/${bot.id}/owners`, { method: 'PATCH', body: JSON.stringify({
_captcha: v._captcha,
_csrf: csrfToken,
owners: v.owners.map(el => el.id)
}) })
if(res.code === 200) {
alert('성공적으로 수정했습니다.')
router.push(makeBotURL(bot))
} else {
alert(res.message)
setAdminModal(false)
}
}}>
{
({ values, setFieldValue }) => <Form>
<Message type='warning'>
<p> . .</p>
</Message>
<div className='py-4'>
<h2 className='text-md my-1'> ID를 .</h2>
<div className='flex flex-wrap'>
{
(values.owners as User[]).map((el, n) => <Tag className='flex items-center' text={<>
<DiscordAvatar userID={el.id} size={128} className='w-6 h-6 mr-1 rounded-full' /> {el.tag === '0' ? `${el.globalName} (@${el.username})` : `${el.username}#${el.tag}`}
{
n !== 0 && <button className='ml-0.5 hover:text-red-500' onClick={() => {
setFieldValue('owners', (() => {
const arr = [...values.owners]
arr.splice(n, 1)
return arr
})())
}}>
<i className='fas fa-times' />
</button>
}
</>} key={el.id} />)
}
</div>
<div className='flex'>
<div className='grow pr-2'>
<Input name='id' placeholder='추가할 유저 ID' />
</div>
<Button className='w-16 bg-discord-blurple' onClick={async () => {
if(values.owners.find(el => el.id === values.id)) return alert('이미 존재하는 유저입니다.')
const user = await getUser(values.id)
const arr = [...values.owners]
if(!user) return alert('올바르지 않은 유저입니다.')
else {
arr.push(user)
setFieldValue('owners', arr)
setFieldValue('id', '')
} }
}}> </div>
<i className='fas fa-user-plus text-white' /> <div className='flex'>
</Button> <div className='grow pr-2'>
<Input name='id' placeholder='추가할 유저 ID' />
</div>
<Button className='w-16 bg-discord-blurple' onClick={async () => {
if(values.owners.find(el => el.id === values.id)) return alert('이미 존재하는 유저입니다.')
const user = await getUser(values.id)
const arr = [...values.owners]
if(!user) return alert('올바르지 않은 유저입니다.')
else {
arr.push(user)
setFieldValue('owners', arr)
setFieldValue('id', '')
}
}}>
<i className='fas fa-user-plus text-white' />
</Button>
</div>
</div> </div>
</div> <Captcha dark={theme === 'dark'} onVerify={(k) => setFieldValue('_captcha', k)} />
<Captcha dark={theme === 'dark'} onVerify={(k) => setFieldValue('_captcha', k)} /> <Button disabled={!values._captcha} className={`mt-2 bg-red-500 text-white ${!values._captcha ? 'opacity-80' : 'hover:opacity-80'}`} type='submit'><i className='fas fa-save text-sm' /> </Button>
<Button disabled={!values._captcha} className={`mt-2 bg-red-500 text-white ${!values._captcha ? 'opacity-80' : 'hover:opacity-80'}`} type='submit'><i className='fas fa-save text-sm' /> </Button> </Form>
</Form> }
} </Formik>
</Formik> </Modal>
</Modal>
</div>
<Divider />
<div className='lg:flex items-center'>
<div className='grow py-1'>
<h3 className='text-lg font-semibold'> </h3>
<p className='text-gray-400'> . .</p>
</div> </div>
<Button onClick={() => setTransferModal(true)} className='h-10 bg-red-500 hover:opacity-80 text-white lg:w-1/8'><i className='fas fa-exchange-alt' /> </Button> <Divider />
<Modal full header={`${bot.name} 소유권 이전하기`} isOpen={transferModal} dark={theme === 'dark'} onClose={() => setTransferModal(false)} closeIcon> <div className='lg:flex items-center'>
<Formik initialValues={{ ownerID: '', name: '', _captcha: '' }} onSubmit={async (v) => { <div className='grow py-1'>
const res = await Fetch(`/bots/${bot.id}/owners`, { method: 'PATCH', body: JSON.stringify({ <h3 className='text-lg font-semibold'> </h3>
_captcha: v._captcha, <p className='text-gray-400'> . .</p>
_csrf: csrfToken, </div>
owners: [ v.ownerID ] <Button onClick={() => setTransferModal(true)} className='h-10 bg-red-500 hover:opacity-80 text-white lg:w-1/8'><i className='fas fa-exchange-alt' /> </Button>
}) }) <Modal full header={`${bot.name} 소유권 이전하기`} isOpen={transferModal} dark={theme === 'dark'} onClose={() => setTransferModal(false)} closeIcon>
if(res.code === 200) { <Formik initialValues={{ ownerID: '', name: '', _captcha: '' }} onSubmit={async (v) => {
alert('성공적으로 소유권을 이전했습니다.') const res = await Fetch(`/bots/${bot.id}/owners`, { method: 'PATCH', body: JSON.stringify({
router.push('/') _captcha: v._captcha,
} else { _csrf: csrfToken,
alert(res.message) owners: [ v.ownerID ]
setTransferModal(false) }) })
} if(res.code === 200) {
}}> alert('성공적으로 소유권을 이전했습니다.')
{ router.push('/')
({ values, setFieldValue }) => <Form> } else {
<Message type='warning'> alert(res.message)
<h2 className='text-2xl font-bold'>!</h2> setTransferModal(false)
<p> , .</p> }
</Message> }}>
<div className='py-4'> {
<h2 className='text-md my-1'> ID를 .</h2> ({ values, setFieldValue }) => <Form>
<Input name='ownerID' placeholder='이전할 유저 ID' /> <Message type='warning'>
<Divider /> <h2 className='text-2xl font-bold'>!</h2>
<h2 className='text-md my-1'> <strong>{bot.name}</strong>{getJosaPicker('을')(bot.name)} .</h2> <p> , .</p>
<Input name='name' placeholder={bot.name} /> </Message>
</div> <div className='py-4'>
<Captcha dark={theme === 'dark'} onVerify={(k) => setFieldValue('_captcha', k)} /> <h2 className='text-md my-1'> ID를 .</h2>
<Button disabled={!values.ownerID || values.name !== bot.name || !values._captcha} className={`mt-4 bg-red-500 text-white ${!values.ownerID ||values.name !== bot.name || !values._captcha ? 'opacity-80' : 'hover:opacity-80'}`} type='submit'><i className='fas fa-exchange-alt' /> </Button> <Input name='ownerID' placeholder='이전할 유저 ID' />
</Form> <Divider />
} <h2 className='text-md my-1'> <strong>{bot.name}</strong>{getJosaPicker('을')(bot.name)} .</h2>
</Formik> <Input name='name' placeholder={bot.name} />
</Modal> </div>
</div> <Captcha dark={theme === 'dark'} onVerify={(k) => setFieldValue('_captcha', k)} />
<Divider /> <Button disabled={!values.ownerID || values.name !== bot.name || !values._captcha} className={`mt-4 bg-red-500 text-white ${!values.ownerID ||values.name !== bot.name || !values._captcha ? 'opacity-80' : 'hover:opacity-80'}`} type='submit'><i className='fas fa-exchange-alt' /> </Button>
<div className='lg:flex items-center'> </Form>
<div className='grow py-1'> }
<h3 className='text-lg font-semibold'> </h3> </Formik>
<p className='text-gray-400'> .</p> </Modal>
</div> </div>
<Button onClick={() => setDeleteModal(true)} className='h-10 bg-red-500 hover:opacity-80 text-white lg:w-1/8'><i className='fas fa-trash' /> </Button> <Divider />
<Modal full header={`${bot.name} 삭제하기`} isOpen={deleteModal} dark={theme === 'dark'} onClose={() => setDeleteModal(false)} closeIcon> <div className='lg:flex items-center'>
<Formik initialValues={{ name: '', _captcha: '', _csrf: csrfToken }} onSubmit={async (v) => { <div className='grow py-1'>
const res = await Fetch(`/bots/${bot.id}`, { method: 'DELETE', body: JSON.stringify(v) }) <h3 className='text-lg font-semibold'> </h3>
if(res.code === 200) { <p className='text-gray-400'> .</p>
alert('성공적으로 삭제하였습니다.') </div>
redirectTo(router, '/') <Button onClick={() => setDeleteModal(true)} className='h-10 bg-red-500 hover:opacity-80 text-white lg:w-1/8'><i className='fas fa-trash' /> </Button>
} <Modal full header={`${bot.name} 삭제하기`} isOpen={deleteModal} dark={theme === 'dark'} onClose={() => setDeleteModal(false)} closeIcon>
else alert(res.message) <Formik initialValues={{ name: '', _captcha: '', _csrf: csrfToken }} onSubmit={async (v) => {
}}> const res = await Fetch(`/bots/${bot.id}`, { method: 'DELETE', body: JSON.stringify(v) })
{ if(res.code === 200) {
({ values, setFieldValue }) => <Form> alert('성공적으로 삭제하였습니다.')
<Message type='warning'> redirectTo(router, '/')
<p> .<br/> .</p> }
<p> <strong>{bot.name}</strong>{getJosaPicker('을')(bot.name)} .</p> else alert(res.message)
</Message> }}>
<div className='py-4'> {
<Input name='name' placeholder={bot.name} /> ({ values, setFieldValue }) => <Form>
</div> <Message type='warning'>
<Captcha dark={theme === 'dark'} onVerify={(k) => setFieldValue('_captcha', k)} /> <p> .<br/> .</p>
<Button disabled={values.name !== bot.name || !values._captcha} className={`mt-4 bg-red-500 text-white ${values.name !== bot.name || !values._captcha ? 'opacity-80' : 'hover:opacity-80'}`} type='submit'><i className='fas fa-trash' /> </Button> <p> <strong>{bot.name}</strong>{getJosaPicker('을')(bot.name)} .</p>
</Form> </Message>
} <div className='py-4'>
</Formik> <Input name='name' placeholder={bot.name} />
</Modal> </div>
</div> <Captcha dark={theme === 'dark'} onVerify={(k) => setFieldValue('_captcha', k)} />
</Segment> <Button disabled={values.name !== bot.name || !values._captcha} className={`mt-4 bg-red-500 text-white ${values.name !== bot.name || !values._captcha ? 'opacity-80' : 'hover:opacity-80'}`} type='submit'><i className='fas fa-trash' /> </Button>
</div> </Form>
} }
</Container> </Formik>
</Modal>
</div>
</Segment>
</div>
}
</Container>
)
} }
export const getServerSideProps = async (ctx: Context) => { export const getServerSideProps = async (ctx: Context) => {

View File

@ -46,294 +46,298 @@ const Bots: NextPage<BotsProps> = ({ data, desc, date, user, theme, csrfToken })
setNSFW(localStorage.nsfw) setNSFW(localStorage.nsfw)
}, []) }, [])
if (!data?.id) return <NotFound /> if (!data?.id) return <NotFound />
return <div style={bg ? { background: `linear-gradient(to right, rgba(34, 36, 38, 0.68), rgba(34, 36, 38, 0.68)), url("${data.bg}") center top / cover no-repeat fixed` } : {}}> return (
<Container paddingTop className='py-10'> <div style={bg ? { background: `linear-gradient(to right, rgba(34, 36, 38, 0.68), rgba(34, 36, 38, 0.68)), url("${data.bg}") center top / cover no-repeat fixed` } : {}}>
<NextSeo <Container paddingTop className='py-10'>
title={data.name} <NextSeo
description={data.intro} title={data.name}
twitter={{ description={data.intro}
cardType: 'summary_large_image' twitter={{
}} cardType: 'summary_large_image'
openGraph={{ }}
images: [ openGraph={{
{ images: [
url: KoreanbotsEndPoints.OG.bot(data.id, data.name, data.intro, data.category, [formatNumber(data.votes), formatNumber(data.servers)]), {
width: 2048, url: KoreanbotsEndPoints.OG.bot(data.id, data.name, data.intro, data.category, [formatNumber(data.votes), formatNumber(data.servers)]),
height: 1170, width: 2048,
alt: 'Bot Preview Image' height: 1170,
} alt: 'Bot Preview Image'
] }
}} ]
/> }}
{ />
data.state === 'blocked' ? <div className='pb-40'> {
<Message type='error'> data.state === 'blocked' ? <div className='pb-40'>
<h2 className='text-lg font-extrabold'> .</h2> <Message type='error'>
</Message> <h2 className='text-lg font-extrabold'> .</h2>
</div> </Message>
: data.category.includes('NSFW') && !nsfw ? <NSFW onClick={() => setNSFW(true)} onDisableClick={() => localStorage.nsfw = true} /> </div>
: <> : data.category.includes('NSFW') && !nsfw ? <NSFW onClick={() => setNSFW(true)} onDisableClick={() => localStorage.nsfw = true} />
<div className='w-full pb-2'> : <>
{ <div className='w-full pb-2'>
data.state === 'private' ? <Message type='info'>
<h2 className='text-lg font-extrabold'> .</h2>
<p> . .</p>
</Message> :
data.state === 'reported' ?
<Message type='error'>
<h2 className='text-lg font-extrabold'> , .</h2>
<p> .</p>
<p> <Link href='/guidelines'><a className='text-blue-500 hover:text-blue-400'></a></Link> <Link href='/discord'><a className='text-blue-500 hover:text-blue-400'> </a></Link> .</p>
</Message> : ''
}
</div>
<div className='lg:flex w-full'>
<div className='w-full text-center lg:w-2/12'>
<DiscordAvatar
userID={data.id}
size={256}
className='w-full rounded-full'
/>
</div>
<div className='grow px-5 py-12 w-full text-center lg:w-5/12 lg:text-left'>
<Tag
circular
text={
<>
<i className={`fas fa-circle text-${Status[data.status]?.color}`} />{' '}
{Status[data.status]?.text}
</>
}
/>
<h1 className='mb-2 mt-3 text-4xl font-bold' style={bg ? { color: 'white' } : {}}>
{data.name}{' '}
{checkBotFlag(data.flags, 'trusted') ? (
<Tooltip placement='bottom' overlay='해당 봇은 한국 디스코드 리스트에서 엄격한 기준을 통과한 봇입니다!'>
<span className='text-koreanbots-blue text-3xl'>
<i className='fas fa-award' />
</span>
</Tooltip>
) : ''}
</h1>
<p className={`${bg ? 'text-gray-300' : 'dark:text-gray-300 text-gray-800'} text-base`}>{data.intro}</p>
</div>
<div className='w-full lg:w-1/4'>
{ {
data.state === 'ok' && <LongButton data.state === 'private' ? <Message type='info'>
newTab <h2 className='text-lg font-extrabold'> .</h2>
href={`/bots/${router.query.id}/invite`} <p> . .</p>
> </Message> :
<h4 className='whitespace-nowrap'> data.state === 'reported' ?
<i className='fas fa-user-plus text-discord-blurple' /> <Message type='error'>
</h4> <h2 className='text-lg font-extrabold'> , .</h2>
</LongButton> <p> .</p>
} <p> <Link href='/guidelines' className='text-blue-500 hover:text-blue-400'></Link> <Link href='/discord' className='text-blue-500 hover:text-blue-400'> </Link> .</p>
<Link href={`/bots/${router.query.id}/vote`}> </Message> : ''
<LongButton>
<h4>
<i className='fas fa-heart text-red-600' />
</h4>
<span className='ml-1 px-2 text-center text-black dark:text-gray-400 text-sm bg-little-white-hover dark:bg-very-black rounded-lg'>
{formatNumber(data.votes)}
</span>
</LongButton>
</Link>
{
((data.owners as User[]).find(el => el.id === user?.id) || checkUserFlag(user?.flags, 'staff')) && <LongButton href={`/bots/${data.id}/edit`}>
<h4>
<i className='fas fa-cogs' />
</h4>
</LongButton>
}
{
((data.owners as User[]).find(el => el.id === user?.id) || checkUserFlag(user?.flags, 'staff')) && <LongButton onClick={async() => {
const res = await Fetch(`/bots/${data.id}/stats`, { method: 'PATCH'} )
if(res.code !== 200) return alert(res.message)
else window.location.reload()
}}>
<h4>
<i className='fas fa-sync' />
</h4>
</LongButton>
} }
</div> </div>
</div> <div className='lg:flex w-full'>
<Divider className='px-5' /> <div className='w-full text-center lg:w-2/12'>
<div className='hidden lg:block'> <DiscordAvatar
<Advertisement /> userID={data.id}
</div> size={256}
<div className='lg:flex lg:flex-row-reverse' style={bg ? { color: 'white' } : {}}> className='w-full rounded-full'
<div className='mb-1 w-full lg:w-1/4'>
<h2 className='3xl mb-2 font-bold'></h2>
<div className='grid gap-4 grid-cols-2 px-4 py-4 text-black dark:text-gray-400 dark:bg-discord-black bg-little-white rounded-sm'>
<div>
<i className='far fa-flag' />
</div>
<div className='markdown-body text-black dark:text-gray-400'>
<code>{data.prefix}</code>
</div>
<div>
<i className='fas fa-users' />
</div>
<div>{data.servers || 'N/A'}</div>
{
data.shards && data.servers > 1500 && <>
<div>
<i className='fas fa-sitemap' />
</div>
<div>{data.shards}</div>
</>
}
<div>
<i className='fas fa-calendar-day' />
</div>
<div>{Day(date).fromNow(false)}</div>
{
checkBotFlag(data.flags, 'verified') ?
<Tooltip overlay='해당 봇은 디스코드측에서 인증된 봇입니다.'>
<div className='col-span-2'>
<i className='fas fa-check text-discord-blurple' />
</div>
</Tooltip>
: ''
}
</div>
<h2 className='3xl mb-2 mt-2 font-bold'></h2>
<div className='flex flex-wrap'>
{data.category.map(el => (
<Tag key={el} text={el} href={`/bots/categories/${el}`} />
))}
</div>
<h2 className='3xl mb-2 mt-2 font-bold'></h2>
{(data.owners as User[]).map(el => (
<Owner
key={el.id}
id={el.id}
tag={el.tag}
globalName={el.globalName}
username={el.username}
/> />
))}
<div className='list grid'>
<Link href={`/bots/${router.query.id}/report`}>
<a className='text-red-600 hover:underline cursor-pointer' aria-hidden='true'>
<i className='far fa-flag' />
</a>
</Link>
<Modal header={`${data.name}#${data.tag} 신고하기`} closeIcon isOpen={reportModal} onClose={() => {
setReportModal(false)
setReportRes(null)
}} full dark={theme === 'dark'}>
{
reportRes?.code === 200 ? <Message type='success'>
<h2 className='text-lg font-semibold'> !</h2>
<p> ! <a className='text-blue-600 hover:text-blue-500' href='/discord'> </a> </p>
</Message> : <Formik onSubmit={async (body) => {
const res = await Fetch(`/bots/${data.id}/report`, { method: 'POST', body: JSON.stringify(body) })
setReportRes(res)
}} validationSchema={ReportSchema} initialValues={{
category: null,
description: '',
_csrf: csrfToken
}}>
{
({ errors, touched, values, setFieldValue }) => (
<Form>
<div className='mb-5'>
{
reportRes && <div className='my-5'>
<Message type='error'>
<h2 className='text-lg font-semibold'>{reportRes.message}</h2>
<ul className='list-disc'>
{reportRes.errors?.map((el, n) => <li key={n}>{el}</li>)}
</ul>
</Message>
</div>
}
<h3 className='font-bold'> </h3>
<p className='text-gray-400 text-sm mb-1'> .</p>
{
reportCats.map(el =>
<div key={el}>
<label>
<Field type='radio' name='category' value={el} className='mr-1.5 py-2' />
{el}
</label>
</div>
)
}
<div className='mt-1 text-red-500 text-xs font-light'>{errors.category && touched.category ? errors.category : null}</div>
<h3 className='font-bold mt-2'></h3>
<p className='text-gray-400 text-sm mb-1'> .</p>
<TextArea name='description' placeholder='최대한 자세하게 설명해주세요!' theme={theme === 'dark' ? 'dark' : 'light'} value={values.description} setValue={(value) => setFieldValue('description', value)} />
<div className='mt-1 text-red-500 text-xs font-light'>{errors.description && touched.description ? errors.description : null}</div>
</div>
<div className='text-right'>
<Button className='bg-gray-500 hover:opacity-90 text-white' onClick={()=> setReportModal(false)}></Button>
<Button type='submit' className='bg-red-500 hover:opacity-90 text-white'></Button>
</div>
</Form>
)
}
</Formik>
}
</Modal>
{data.discord && (
<a
rel='noopener noreferrer'
target='_blank'
className='text-discord-blurple hover:underline'
href={`https://discord.gg/${data.discord}`}
>
<i className='fab fa-discord' />
</a>
)}
{data.web && (
<a
rel='noopener noreferrer'
target='_blank'
className='text-blue-500 hover:underline'
href={data.web}
>
<i className='fas fa-globe' />
</a>
)}
{data.git && (
<a
rel='noopener noreferrer'
target='_blank'
className='hover:underline'
href={data.git}
>
<i className={`fab fa-${git[new URL(data.git).hostname]?.icon ?? 'git-alt'}`} />
{git[new URL(data.git).hostname]?.text ?? 'Git'}
</a>
)}
</div> </div>
<Advertisement size='tall' /> <div className='grow px-5 py-12 w-full text-center lg:w-5/12 lg:text-left'>
<Tag
circular
text={
<>
<i className={`fas fa-circle text-${Status[data.status]?.color}`} />{' '}
{Status[data.status]?.text}
</>
}
/>
<h1 className='mb-2 mt-3 text-4xl font-bold' style={bg ? { color: 'white' } : {}}>
{data.name}{' '}
{checkBotFlag(data.flags, 'trusted') ? (
<Tooltip placement='bottom' overlay='해당 봇은 한국 디스코드 리스트에서 엄격한 기준을 통과한 봇입니다!'>
<span className='text-koreanbots-blue text-3xl'>
<i className='fas fa-award' />
</span>
</Tooltip>
) : ''}
</h1>
<p className={`${bg ? 'text-gray-300' : 'dark:text-gray-300 text-gray-800'} text-base`}>{data.intro}</p>
</div>
<div className='w-full lg:w-1/4'>
{
data.state === 'ok' && <LongButton
newTab
href={`/bots/${router.query.id}/invite`}
>
<h4 className='whitespace-nowrap'>
<i className='fas fa-user-plus text-discord-blurple' />
</h4>
</LongButton>
}
<Link href={`/bots/${router.query.id}/vote`} legacyBehavior>
<LongButton>
<h4>
<i className='fas fa-heart text-red-600' />
</h4>
<span className='ml-1 px-2 text-center text-black dark:text-gray-400 text-sm bg-little-white-hover dark:bg-very-black rounded-lg'>
{formatNumber(data.votes)}
</span>
</LongButton>
</Link>
{
((data.owners as User[]).find(el => el.id === user?.id) || checkUserFlag(user?.flags, 'staff')) && <LongButton href={`/bots/${data.id}/edit`}>
<h4>
<i className='fas fa-cogs' />
</h4>
</LongButton>
}
{
((data.owners as User[]).find(el => el.id === user?.id) || checkUserFlag(user?.flags, 'staff')) && <LongButton onClick={async() => {
const res = await Fetch(`/bots/${data.id}/stats`, { method: 'PATCH'} )
if(res.code !== 200) return alert(res.message)
else window.location.reload()
}}>
<h4>
<i className='fas fa-sync' />
</h4>
</LongButton>
}
</div>
</div> </div>
<div className='w-full lg:pr-5 lg:w-3/4'> <Divider className='px-5' />
{ <div className='hidden lg:block'>
checkBotFlag(data.flags, 'hackerthon') ? <Segment className='mt-10'>
<h1 className='text-3xl font-semibold'>
<i className='fas fa-trophy mr-4 my-2 text-amber-300' /> !
</h1>
<p> "한국 디스코드 리스트 제1회 해커톤" .</p>
<p> <a className='text-blue-500 hover:text-blue-400' href='https://blog.koreanbots.dev/first-hackathon-results/'> </a> .</p>
</Segment> : ''
}
<Segment className='my-4'>
<Markdown text={desc}/>
</Segment>
<Advertisement /> <Advertisement />
</div> </div>
</div> <div className='lg:flex lg:flex-row-reverse' style={bg ? { color: 'white' } : {}}>
</> <div className='mb-1 w-full lg:w-1/4'>
} <h2 className='3xl mb-2 font-bold'></h2>
</Container> <div className='grid gap-4 grid-cols-2 px-4 py-4 text-black dark:text-gray-400 dark:bg-discord-black bg-little-white rounded-sm'>
</div> <div>
<i className='far fa-flag' />
</div>
<div className='markdown-body text-black dark:text-gray-400'>
<code>{data.prefix}</code>
</div>
<div>
<i className='fas fa-users' />
</div>
<div>{data.servers || 'N/A'}</div>
{
data.shards && data.servers > 1500 && <>
<div>
<i className='fas fa-sitemap' />
</div>
<div>{data.shards}</div>
</>
}
<div>
<i className='fas fa-calendar-day' />
</div>
<div>{Day(date).fromNow(false)}</div>
{
checkBotFlag(data.flags, 'verified') ?
<Tooltip overlay='해당 봇은 디스코드측에서 인증된 봇입니다.'>
<div className='col-span-2'>
<i className='fas fa-check text-discord-blurple' />
</div>
</Tooltip>
: ''
}
</div>
<h2 className='3xl mb-2 mt-2 font-bold'></h2>
<div className='flex flex-wrap'>
{data.category.map(el => (
<Tag key={el} text={el} href={`/bots/categories/${el}`} />
))}
</div>
<h2 className='3xl mb-2 mt-2 font-bold'></h2>
{(data.owners as User[]).map(el => (
<Owner
key={el.id}
id={el.id}
tag={el.tag}
globalName={el.globalName}
username={el.username}
/>
))}
<div className='list grid'>
<Link
href={`/bots/${router.query.id}/report`}
className='text-red-600 hover:underline cursor-pointer'
aria-hidden='true'>
<i className='far fa-flag' />
</Link>
<Modal header={`${data.name}#${data.tag} 신고하기`} closeIcon isOpen={reportModal} onClose={() => {
setReportModal(false)
setReportRes(null)
}} full dark={theme === 'dark'}>
{
reportRes?.code === 200 ? <Message type='success'>
<h2 className='text-lg font-semibold'> !</h2>
<p> ! <a className='text-blue-600 hover:text-blue-500' href='/discord'> </a> </p>
</Message> : <Formik onSubmit={async (body) => {
const res = await Fetch(`/bots/${data.id}/report`, { method: 'POST', body: JSON.stringify(body) })
setReportRes(res)
}} validationSchema={ReportSchema} initialValues={{
category: null,
description: '',
_csrf: csrfToken
}}>
{
({ errors, touched, values, setFieldValue }) => (
<Form>
<div className='mb-5'>
{
reportRes && <div className='my-5'>
<Message type='error'>
<h2 className='text-lg font-semibold'>{reportRes.message}</h2>
<ul className='list-disc'>
{reportRes.errors?.map((el, n) => <li key={n}>{el}</li>)}
</ul>
</Message>
</div>
}
<h3 className='font-bold'> </h3>
<p className='text-gray-400 text-sm mb-1'> .</p>
{
reportCats.map(el =>
<div key={el}>
<label>
<Field type='radio' name='category' value={el} className='mr-1.5 py-2' />
{el}
</label>
</div>
)
}
<div className='mt-1 text-red-500 text-xs font-light'>{errors.category && touched.category ? errors.category as string: null}</div>
<h3 className='font-bold mt-2'></h3>
<p className='text-gray-400 text-sm mb-1'> .</p>
<TextArea name='description' placeholder='최대한 자세하게 설명해주세요!' theme={theme === 'dark' ? 'dark' : 'light'} value={values.description} setValue={(value) => setFieldValue('description', value)} />
<div className='mt-1 text-red-500 text-xs font-light'>{errors.description && touched.description ? errors.description : null}</div>
</div>
<div className='text-right'>
<Button className='bg-gray-500 hover:opacity-90 text-white' onClick={()=> setReportModal(false)}></Button>
<Button type='submit' className='bg-red-500 hover:opacity-90 text-white'></Button>
</div>
</Form>
)
}
</Formik>
}
</Modal>
{data.discord && (
<a
rel='noopener noreferrer'
target='_blank'
className='text-discord-blurple hover:underline'
href={`https://discord.gg/${data.discord}`}
>
<i className='fab fa-discord' />
</a>
)}
{data.web && (
<a
rel='noopener noreferrer'
target='_blank'
className='text-blue-500 hover:underline'
href={data.web}
>
<i className='fas fa-globe' />
</a>
)}
{data.git && (
<a
rel='noopener noreferrer'
target='_blank'
className='hover:underline'
href={data.git}
>
<i className={`fab fa-${git[new URL(data.git).hostname]?.icon ?? 'git-alt'}`} />
{git[new URL(data.git).hostname]?.text ?? 'Git'}
</a>
)}
</div>
<Advertisement size='tall' />
</div>
<div className='w-full lg:pr-5 lg:w-3/4'>
{
checkBotFlag(data.flags, 'hackerthon') ? <Segment className='mt-10'>
<h1 className='text-3xl font-semibold'>
<i className='fas fa-trophy mr-4 my-2 text-amber-300' /> !
</h1>
<p> "한국 디스코드 리스트 제1회 해커톤" .</p>
<p> <a className='text-blue-500 hover:text-blue-400' href='https://blog.koreanbots.dev/first-hackathon-results/'> </a> .</p>
</Segment> : ''
}
<Segment className='my-4'>
<Markdown text={desc}/>
</Segment>
<Advertisement />
</div>
</div>
</>
}
</Container>
</div>
)
} }
export const getServerSideProps = async (ctx: Context) => { export const getServerSideProps = async (ctx: Context) => {

View File

@ -30,83 +30,85 @@ const ReportBot: NextPage<ReportBotProps> = ({ data, user, csrfToken }) => {
if(!user) return <Login> if(!user) return <Login>
<NextSeo title='신고하기' /> <NextSeo title='신고하기' />
</Login> </Login>
return <Container paddingTop className='py-10'> return (
<NextSeo title={`${data.name} 신고하기`} /> <Container paddingTop className='py-10'>
<Link href={makeBotURL(data)}> <NextSeo title={`${data.name} 신고하기`} />
<a className='text-blue-500 hover:opacity-80'><i className='fas fa-arrow-left mt-3 mb-3' /> <strong>{data.name}</strong>{getJosaPicker('로')(data.name)} </a> <Link href={makeBotURL(data)} className='text-blue-500 hover:opacity-80'>
</Link> <i className='fas fa-arrow-left mt-3 mb-3' /> <strong>{data.name}</strong>{getJosaPicker('로')(data.name)}
{ </Link>
reportRes?.code === 200 ? <Message type='success'> {
<h2 className='text-lg font-semibold'> !</h2> reportRes?.code === 200 ? <Message type='success'>
<p> . <strong> <a className='text-blue-600 hover:text-blue-500' href='/discord'> </a> !!</strong></p> <h2 className='text-lg font-semibold'> !</h2>
</Message> : <Formik onSubmit={async (body) => { <p> . <strong> <a className='text-blue-600 hover:text-blue-500' href='/discord'> </a> !!</strong></p>
const res = await Fetch(`/bots/${data.id}/report`, { method: 'POST', body: JSON.stringify(body) }) </Message> : <Formik onSubmit={async (body) => {
setReportRes(res) const res = await Fetch(`/bots/${data.id}/report`, { method: 'POST', body: JSON.stringify(body) })
}} validationSchema={ReportSchema} initialValues={{ setReportRes(res)
category: null, }} validationSchema={ReportSchema} initialValues={{
description: '', category: null,
_csrf: csrfToken description: '',
}}> _csrf: csrfToken
{ }}>
({ errors, touched, values, setFieldValue }) => ( {
<Form> ({ errors, touched, values, setFieldValue }) => (
<div className='mb-5'> <Form>
{ <div className='mb-5'>
reportRes && <div className='my-5'> {
<Message type='error'> reportRes && <div className='my-5'>
<h2 className='text-lg font-semibold'>{reportRes.message}</h2> <Message type='error'>
<ul className='list-disc'> <h2 className='text-lg font-semibold'>{reportRes.message}</h2>
{reportRes.errors?.map((el, n) => <li key={n}>{el}</li>)} <ul className='list-disc'>
</ul> {reportRes.errors?.map((el, n) => <li key={n}>{el}</li>)}
</Message> </ul>
</div> </Message>
}
<h3 className='font-bold'> </h3>
<p className='text-gray-400 text-sm mb-1'> .</p>
{
reportCats.map(el =>
<div key={el}>
<label>
<Field type='radio' name='category' value={el} className='mr-1.5 py-2' />
{el}
</label>
</div> </div>
) }
} <h3 className='font-bold'> </h3>
<div className='mt-1 text-red-500 text-xs font-light'>{errors.category && touched.category ? errors.category : null}</div> <p className='text-gray-400 text-sm mb-1'> .</p>
{ {
values.category && <> reportCats.map(el =>
{ <div key={el}>
<label>
<Field type='radio' name='category' value={el} className='mr-1.5 py-2' />
{el}
</label>
</div>
)
}
<div className='mt-1 text-red-500 text-xs font-light'>{errors.category && touched.category ? errors.category as string: null}</div>
{
values.category && <>
{ {
[reportCats[2]]: <Message type='info'> {
<h3 className='font-bold text-xl'> ?</h3> [reportCats[2]]: <Message type='info'>
<p> .</p> <h3 className='font-bold text-xl'> ?</h3>
<p className='list-disc list-item list-inside'> 1393 | 1388</p> <p> .</p>
</Message>, <p className='list-disc list-item list-inside'> 1393 | 1388</p>
[reportCats[5]]: <DMCA values={values} errors={errors} touched={touched} setFieldValue={setFieldValue} />, </Message>,
[reportCats[6]]: <Message type='warning'> [reportCats[5]]: <DMCA values={values} errors={errors} touched={touched} setFieldValue={setFieldValue} />,
<h3 className='font-bold text-xl'> ?</h3> [reportCats[6]]: <Message type='warning'>
<p><a className='text-blue-400' target='_blank' rel='noreferrer' href='http://dis.gd/report'> </a> .</p> <h3 className='font-bold text-xl'> ?</h3>
</Message> <p><a className='text-blue-400' target='_blank' rel='noreferrer' href='http://dis.gd/report'> </a> .</p>
</Message>
}[values.category]
} }[values.category]
{ }
!['오픈소스 라이선스, 저작권 위반 등 권리 침해'].includes(values.category) && <> {
<h3 className='font-bold mt-2'></h3> !['오픈소스 라이선스, 저작권 위반 등 권리 침해'].includes(values.category) && <>
<p className='text-gray-400 text-sm mb-1'> .</p> <h3 className='font-bold mt-2'></h3>
<TextField values={values} errors={errors} touched={touched} setFieldValue={setFieldValue} /> <p className='text-gray-400 text-sm mb-1'> .</p>
</> <TextField values={values} errors={errors} touched={touched} setFieldValue={setFieldValue} />
} </>
</> }
} </>
</div> }
</Form> </div>
) </Form>
} )
</Formik> }
} </Formik>
</Container> }
</Container>
)
} }
export const getServerSideProps = async (ctx: Context) => { export const getServerSideProps = async (ctx: Context) => {

View File

@ -48,58 +48,60 @@ const VoteBot: NextPage<VoteBotProps> = ({ data, user, theme, csrfToken }) => {
</Login> </Login>
if((checkBotFlag(data.flags, 'trusted') || checkBotFlag(data.flags, 'partnered')) && data.vanity && data.vanity !== router.query.id) router.push(`/bots/${data.vanity}/vote?csrfToken=${csrfToken}`) if((checkBotFlag(data.flags, 'trusted') || checkBotFlag(data.flags, 'partnered')) && data.vanity && data.vanity !== router.query.id) router.push(`/bots/${data.vanity}/vote?csrfToken=${csrfToken}`)
return <Container paddingTop className='py-10'> return (
<NextSeo title={data.name} description={`한국 디스코드 리스트에서 ${data.name}에 투표하세요.`} openGraph={{ <Container paddingTop className='py-10'>
images: [ <NextSeo title={data.name} description={`한국 디스코드 리스트에서 ${data.name}에 투표하세요.`} openGraph={{
{ images: [
url: KoreanbotsEndPoints.CDN.avatar(data.id, { format: 'png', size: 256 }), {
width: 256, url: KoreanbotsEndPoints.CDN.avatar(data.id, { format: 'png', size: 256 }),
height: 256, width: 256,
alt: 'Bot Avatar' height: 256,
} alt: 'Bot Avatar'
] }
}} /> ]
{ }} />
data.state === 'blocked' ? <div className='pb-40'> {
<Message type='error'> data.state === 'blocked' ? <div className='pb-40'>
<h2 className='text-lg font-extrabold'> .</h2> <Message type='error'>
</Message> <h2 className='text-lg font-extrabold'> .</h2>
</div> : <> </Message>
<Advertisement /> </div> : <>
<Link href={makeBotURL(data)}> <Advertisement />
<a className='text-blue-500 hover:opacity-80'><i className='fas fa-arrow-left mt-3 mb-3' /> <strong>{data.name}</strong>{getJosaPicker('로')(data.name)} </a> <Link href={makeBotURL(data)} className='text-blue-500 hover:opacity-80'>
</Link> <i className='fas fa-arrow-left mt-3 mb-3' /> <strong>{data.name}</strong>{getJosaPicker('로')(data.name)}
<Segment className='mb-16 py-8'> </Link>
<div className='text-center'> <Segment className='mb-16 py-8'>
<DiscordAvatar userID={data.id} className='mx-auto w-52 h-52 bg-white mb-4 rounded-full' /> <div className='text-center'>
<Tag text={<span><i className='fas fa-heart text-red-600' /> {data.votes}</span>} dark /> <DiscordAvatar userID={data.id} className='mx-auto w-52 h-52 bg-white mb-4 rounded-full' />
<h1 className='text-3xl font-bold mt-3'>{data.name}</h1> <Tag text={<span><i className='fas fa-heart text-red-600' /> {data.votes}</span>} dark />
<h4 className='text-md mt-1'>12 .</h4> <h1 className='text-3xl font-bold mt-3'>{data.name}</h1>
<div className='inline-block mt-2'> <h4 className='text-md mt-1'>12 .</h4>
{ <div className='inline-block mt-2'>
votingStatus === 0 ? <Button onClick={()=> setVotingStatus(1)}> {
<><i className='far fa-heart text-red-600'/> </> votingStatus === 0 ? <Button onClick={()=> setVotingStatus(1)}>
</Button> <><i className='far fa-heart text-red-600'/> </>
: votingStatus === 1 ? <Captcha dark={theme === 'dark'} onVerify={async (key) => { </Button>
const res = await Fetch<{ retryAfter: number }|unknown>(`/bots/${data.id}/vote`, { method: 'POST', body: JSON.stringify({ _csrf: csrfToken, _captcha: key }) }) : votingStatus === 1 ? <Captcha dark={theme === 'dark'} onVerify={async (key) => {
setResult(res) const res = await Fetch<{ retryAfter: number }|unknown>(`/bots/${data.id}/vote`, { method: 'POST', body: JSON.stringify({ _csrf: csrfToken, _captcha: key }) })
setVotingStatus(2) setResult(res)
}} setVotingStatus(2)
/> }}
: result.code === 200 ? <h2 className='text-2xl font-bold'> !</h2> />
: result.code === 429 ? <> : result.code === 200 ? <h2 className='text-2xl font-bold'> !</h2>
<h2 className='text-2xl font-bold'> .</h2> : result.code === 429 ? <>
<h4 className='text-md mt-1'>{Day(+new Date() + result.data?.retryAfter).fromNow()} .</h4> <h2 className='text-2xl font-bold'> .</h2>
</> <h4 className='text-md mt-1'>{Day(+new Date() + result.data?.retryAfter).fromNow()} .</h4>
: <p>{result.message}</p> </>
} : <p>{result.message}</p>
}
</div>
</div> </div>
</Segment>
</div> <Advertisement /></>
</Segment> }
<Advertisement /></> </Container>
} )
</Container>
} }
export const getServerSideProps = async (ctx: Context) => { export const getServerSideProps = async (ctx: Context) => {

View File

@ -58,86 +58,91 @@ const BotApplication: NextPage<BotApplicationProps> = ({ user, spec, bot, theme,
return return
} }
if(!bot || !spec) return <NotFound /> if(!bot || !spec) return <NotFound />
return <DeveloperLayout enabled='applications'> return (
<Link href='/developers/applications'> <DeveloperLayout enabled='applications'>
<a className='text-blue-500 hover:text-blue-400'> <Link
<i className='fas fa-arrow-left' /> href='/developers/applications'
</a> className='text-blue-500 hover:text-blue-400'>
</Link>
<h1 className='text-3xl font-bold'> </h1> <i className='fas fa-arrow-left' />
<p className='text-gray-400'> API에 .</p> </Link>
<div className='lg:flex pt-6'> <h1 className='text-3xl font-bold'> </h1>
<div className='lg:w-1/5'> <p className='text-gray-400'> API에 .</p>
<DiscordAvatar userID={bot.id} /> <div className='lg:flex pt-6'>
</div> <div className='lg:w-1/5'>
<div className='lg:w-4/5 relative'> <DiscordAvatar userID={bot.id} />
<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-disc list-inside'>
{
data.errors?.map((el, i)=> <li key={i}>{el}</li>)
}
</ul>
</Message>
}
</div> </div>
<div className='grid text-left px-6'> <div className='lg:w-4/5 relative'>
<h2 className='text-3xl font-bold mb-2 mt-3'>{bot.name}#{bot.tag}</h2> <div className='mt-4'>
<h3 className='text-lg font-semibold'> </h3> {
<pre className='text-sm overflow-x-scroll w-full'>{showToken ? spec.token : '******************'}</pre> !data ? '' : data.code === 200 ?
<div className='pt-3 pb-6'> <Message type='success'>
<Button onClick={() => setShowToken(!showToken)}>{showToken ? '숨기기' : '보기'}</Button> <h2 className='text-lg font-extrabold'> !</h2>
<Button onClick={setTokenCopied} className={tokenCopied ? 'bg-emerald-400 text-white' : null}>{tokenCopied ? '복사됨' : '복사'}</Button> <p> .</p>
<Button onClick={()=> setModalOpen(true)}></Button> </Message> : <Message type='error'>
<Modal isOpen={modalOpened} onClose={() => setModalOpen(false)} dark={theme === 'dark'} header='정말로 토큰을 재발급하시겠습니까?'> <h2 className='text-lg font-extrabold'>{data.message}</h2>
<p> </p> <ul className='list-disc list-inside'>
<div className='text-right pt-6'> {
<Button className='bg-gray-500 text-white hover:opacity-90' onClick={()=> setModalOpen(false)}></Button> data.errors?.map((el, i)=> <li key={i}>{el}</li>)
<Button onClick={async ()=> { }
const res = await resetToken() </ul>
spec.token = res.data.token </Message>
setModalOpen(false) }
}}></Button>
</div>
</Modal>
</div> </div>
<Formik validationSchema={DeveloperBotSchema} initialValues={{ <div className='grid text-left px-6'>
webhookURL: spec.webhookURL || '', <h2 className='text-3xl font-bold mb-2 mt-3'>{bot.name}#{bot.tag}</h2>
_csrf: csrfToken <h3 className='text-lg font-semibold'> </h3>
}} <pre className='text-sm overflow-x-scroll w-full'>{showToken ? spec.token : '******************'}</pre>
onSubmit={updateApplication}> <div className='pt-3 pb-6'>
{({ errors, touched }) => ( <Button onClick={() => setShowToken(!showToken)}>{showToken ? '숨기기' : '보기'}</Button>
<Form> <Button onClick={setTokenCopied} className={tokenCopied ? 'bg-emerald-400 text-white' : null}>{tokenCopied ? '복사됨' : '복사'}</Button>
<div className='mb-2'> <Button onClick={()=> setModalOpen(true)}></Button>
<h3 className='font-bold mb-1'> <Modal isOpen={modalOpened} onClose={() => setModalOpen(false)} dark={theme === 'dark'} header='정말로 토큰을 재발급하시겠습니까?'>
URL <p> </p>
{(!data || data.code !== 200) && spec.webhookStatus === WebhookStatus.Disabled && ( <div className='text-right pt-6'>
<Tooltip direction='left' text='웹훅 링크가 유효하지 않아 웹훅이 중지되었습니다.'> <Button className='bg-gray-500 text-white hover:opacity-90' onClick={()=> setModalOpen(false)}></Button>
<span className='text-red-500 text-base font-semibold pl-1' role='img' aria-label='warning'></span> <Button onClick={async ()=> {
</Tooltip> const res = await resetToken()
)} spec.token = res.data.token
</h3> setModalOpen(false)
<p className='text-gray-400 text-sm mb-1'> .<br/> }}></Button>
, .<br/>
<Link href={'/developers/docs/%EC%9B%B9%ED%9B%84%ED%81%AC'}><a className='text-blue-500 hover:text-blue-400 font-semibold'> </a></Link> .
</p>
<Input name='webhookURL' placeholder='https://webhook.koreanbots.dev' />
{touched.webhookURL && errors.webhookURL ? <div className='text-red-500 text-xs font-light mt-1'>{errors.webhookURL}</div> : null}
</div> </div>
<Button type='submit'><i className='far fa-save'/> </Button> </Modal>
</Form> </div>
)} <Formik validationSchema={DeveloperBotSchema} initialValues={{
</Formik> webhookURL: spec.webhookURL || '',
_csrf: csrfToken
}}
onSubmit={updateApplication}>
{({ errors, touched }) => (
<Form>
<div className='mb-2'>
<h3 className='font-bold mb-1'>
URL
{(!data || data.code !== 200) && spec.webhookStatus === WebhookStatus.Disabled && (
<Tooltip direction='left' text='웹훅 링크가 유효하지 않아 웹훅이 중지되었습니다.'>
<span className='text-red-500 text-base font-semibold pl-1' role='img' aria-label='warning'></span>
</Tooltip>
)}
</h3>
<p className='text-gray-400 text-sm mb-1'> .<br/>
, .<br/>
<Link
href={'/developers/docs/%EC%9B%B9%ED%9B%84%ED%81%AC'}
className='text-blue-500 hover:text-blue-400 font-semibold'> </Link> .
</p>
<Input name='webhookURL' placeholder='https://webhook.koreanbots.dev' />
{touched.webhookURL && errors.webhookURL ? <div className='text-red-500 text-xs font-light mt-1'>{errors.webhookURL}</div> : null}
</div>
<Button type='submit'><i className='far fa-save'/> </Button>
</Form>
)}
</Formik>
</div>
</div> </div>
</div> </div>
</div> </DeveloperLayout>
</DeveloperLayout> )
} }
@ -151,7 +156,7 @@ interface BotApplicationProps {
export const getServerSideProps = async (ctx: Context) => { export const getServerSideProps = async (ctx: Context) => {
const parsed = parseCookie(ctx.req) const parsed = parseCookie(ctx.req)
const user = await get.Authorization(parsed?.token) || '' const user = (await get.Authorization(parsed?.token)) || ''
return { 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) } props: { user, spec: await get.botSpec(ctx.query.id, user), bot: await get.bot.load(ctx.query.id), csrfToken: getToken(ctx.req, ctx.res) }

View File

@ -40,7 +40,7 @@ interface ApplicationsProps {
export const getServerSideProps = async (ctx: NextPageContext) => { export const getServerSideProps = async (ctx: NextPageContext) => {
const parsed = parseCookie(ctx.req) const parsed = parseCookie(ctx.req)
const user = await get.Authorization(parsed?.token) || '' const user = (await get.Authorization(parsed?.token)) || ''
return { return {
props: { user: await get.user.load(user) } props: { user: await get.user.load(user) }

View File

@ -57,95 +57,100 @@ const ServerApplication: NextPage<ServerApplicationProps> = ({ user, spec, serve
return return
} }
if(!server) return <NotFound /> if(!server) return <NotFound />
return <DeveloperLayout enabled='applications'> return (
<Link href='/developers/applications'> <DeveloperLayout enabled='applications'>
<a className='text-blue-500 hover:text-blue-400'> <Link
<i className='fas fa-arrow-left' /> href='/developers/applications'
</a> className='text-blue-500 hover:text-blue-400'>
</Link>
<h1 className='text-3xl font-bold'> </h1> <i className='fas fa-arrow-left' />
<p className='text-gray-400'> API에 .</p> </Link>
{ <h1 className='text-3xl font-bold'> </h1>
spec ? <> <p className='text-gray-400'> API에 .</p>
<div className='lg:flex pt-6'> {
<div className='lg:w-1/5'> spec ? <>
<ServerIcon id={server.id} /> <div className='lg:flex pt-6'>
</div> <div className='lg:w-1/5'>
<div className='lg:w-4/5 relative'> <ServerIcon id={server.id} />
<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-disc list-inside'>
{
data.errors?.map((el, i)=> <li key={i}>{el}</li>)
}
</ul>
</Message>
}
</div> </div>
<div className='grid text-left px-6'> <div className='lg:w-4/5 relative'>
<h2 className='text-3xl font-bold mb-2 mt-3'>{server.name}</h2> <div className='mt-4'>
<h3 className='text-lg font-semibold'> </h3> {
<pre className='text-sm overflow-x-scroll w-full'>{showToken ? spec.token : '******************'}</pre> !data ? '' : data.code === 200 ?
<div className='pt-3 pb-6'> <Message type='success'>
<Button onClick={() => setShowToken(!showToken)}>{showToken ? '숨기기' : '보기'}</Button> <h2 className='text-lg font-extrabold'> !</h2>
<Button onClick={setTokenCopied} className={tokenCopied ? 'bg-emerald-400 text-white' : null}>{tokenCopied ? '복사됨' : '복사'}</Button> <p> .</p>
<Button onClick={()=> setModalOpen(true)}></Button> </Message> : <Message type='error'>
<Modal isOpen={modalOpened} onClose={() => setModalOpen(false)} dark={theme === 'dark'} header='정말로 토큰을 재발급하시겠습니까?'> <h2 className='text-lg font-extrabold'>{data.message}</h2>
<p> </p> <ul className='list-disc list-inside'>
<div className='text-right pt-6'> {
<Button className='bg-gray-500 text-white hover:opacity-90' onClick={()=> setModalOpen(false)}></Button> data.errors?.map((el, i)=> <li key={i}>{el}</li>)
<Button onClick={async ()=> { }
const res = await resetToken() </ul>
if(res.data?.token) spec.token = res.data.token </Message>
setModalOpen(false) }
}}></Button>
</div>
</Modal>
</div> </div>
<Formik validationSchema={DeveloperServerSchema} initialValues={{ <div className='grid text-left px-6'>
webhookURL: spec.webhookURL || '', <h2 className='text-3xl font-bold mb-2 mt-3'>{server.name}</h2>
_csrf: csrfToken <h3 className='text-lg font-semibold'> </h3>
}} <pre className='text-sm overflow-x-scroll w-full'>{showToken ? spec.token : '******************'}</pre>
onSubmit={updateApplication}> <div className='pt-3 pb-6'>
{({ errors, touched }) => ( <Button onClick={() => setShowToken(!showToken)}>{showToken ? '숨기기' : '보기'}</Button>
<Form> <Button onClick={setTokenCopied} className={tokenCopied ? 'bg-emerald-400 text-white' : null}>{tokenCopied ? '복사됨' : '복사'}</Button>
<div className='mb-2'> <Button onClick={()=> setModalOpen(true)}></Button>
<h3 className='font-bold mb-1'> <Modal isOpen={modalOpened} onClose={() => setModalOpen(false)} dark={theme === 'dark'} header='정말로 토큰을 재발급하시겠습니까?'>
URL <p> </p>
{(!data || data.code !== 200) && spec.webhookStatus === WebhookStatus.Disabled && ( <div className='text-right pt-6'>
<Tooltip direction='left' text='웹훅 링크가 유효하지 않아 웹훅이 중지되었습니다.'> <Button className='bg-gray-500 text-white hover:opacity-90' onClick={()=> setModalOpen(false)}></Button>
<span className='text-red-500 text-base font-semibold pl-1' role='img' aria-label='warning'></span> <Button onClick={async ()=> {
</Tooltip> const res = await resetToken()
)} if(res.data?.token) spec.token = res.data.token
</h3> setModalOpen(false)
<p className='text-gray-400 text-sm mb-1'> .<br/> }}></Button>
, .<br/>
<Link href={'/developers/docs/%EC%9B%B9%ED%9B%84%ED%81%AC'}><a className='text-blue-500 hover:text-blue-400 font-semibold'> </a></Link> .
</p>
<Input name='webhookURL' placeholder='https://webhook.koreanbots.dev' />
{touched.webhookURL && errors.webhookURL ? <div className='text-red-500 text-xs font-light mt-1'>{errors.webhookURL}</div> : null}
</div> </div>
<Button type='submit'><i className='far fa-save'/> </Button> </Modal>
</Form> </div>
)} <Formik validationSchema={DeveloperServerSchema} initialValues={{
</Formik> webhookURL: spec.webhookURL || '',
_csrf: csrfToken
}}
onSubmit={updateApplication}>
{({ errors, touched }) => (
<Form>
<div className='mb-2'>
<h3 className='font-bold mb-1'>
URL
{(!data || data.code !== 200) && spec.webhookStatus === WebhookStatus.Disabled && (
<Tooltip direction='left' text='웹훅 링크가 유효하지 않아 웹훅이 중지되었습니다.'>
<span className='text-red-500 text-base font-semibold pl-1' role='img' aria-label='warning'></span>
</Tooltip>
)}
</h3>
<p className='text-gray-400 text-sm mb-1'> .<br/>
, .<br/>
<Link
href={'/developers/docs/%EC%9B%B9%ED%9B%84%ED%81%AC'}
className='text-blue-500 hover:text-blue-400 font-semibold'> </Link> .
</p>
<Input name='webhookURL' placeholder='https://webhook.koreanbots.dev' />
{touched.webhookURL && errors.webhookURL ? <div className='text-red-500 text-xs font-light mt-1'>{errors.webhookURL}</div> : null}
</div>
<Button type='submit'><i className='far fa-save'/> </Button>
</Form>
)}
</Formik>
</div>
</div> </div>
</div> </div>
</> : <div className='mt-5'>
<Message type='error'>
<h2 className='text-lg font-extrabold'> .</h2>
<p> , .</p>
</Message>
</div> </div>
</> : <div className='mt-5'> }
<Message type='error'> </DeveloperLayout>
<h2 className='text-lg font-extrabold'> .</h2> )
<p> , .</p>
</Message>
</div>
}
</DeveloperLayout>
} }
@ -159,7 +164,7 @@ interface ServerApplicationProps {
export const getServerSideProps = async (ctx: Context) => { export const getServerSideProps = async (ctx: Context) => {
const parsed = parseCookie(ctx.req) const parsed = parseCookie(ctx.req)
const user = await get.Authorization(parsed?.token) || '' const user = (await get.Authorization(parsed?.token)) || ''
const server = await get.server.load(ctx.query.id) const server = await get.server.load(ctx.query.id)
return { return {
props: { user, spec: server?.state === 'unreachable' ? null : await get.serverSpec(ctx.query.id, user), server, csrfToken: getToken(ctx.req, ctx.res) } props: { user, spec: server?.state === 'unreachable' ? null : await get.serverSpec(ctx.query.id, user), server, csrfToken: getToken(ctx.req, ctx.res) }

View File

@ -87,7 +87,7 @@ const Panel:NextPage<PanelProps> = ({ logged, user, submits, csrfToken }) => {
export const getServerSideProps = async (ctx: NextPageContext) => { export const getServerSideProps = async (ctx: NextPageContext) => {
const parsed = parseCookie(ctx.req) const parsed = parseCookie(ctx.req)
const user = await get.Authorization(parsed?.token) || '' const user = (await get.Authorization(parsed?.token)) || ''
const submits = await get.botSubmits.load(user) const submits = await get.botSubmits.load(user)
return { props: { logged: !!user, user: await get.user.load(user), submits, csrfToken: getToken(ctx.req, ctx.res) } } return { props: { logged: !!user, user: await get.user.load(user), submits, csrfToken: getToken(ctx.req, ctx.res) } }

View File

@ -29,158 +29,160 @@ const PendingBot: NextPage<PendingBotProps> = ({ data }) => {
successDuration: 1000 successDuration: 1000
}) })
if(!data) return <NotFound /> if(!data) return <NotFound />
return <Container paddingTop className='py-10'> return (
<NextSeo title='심사이력' /> <Container paddingTop className='py-10'>
<div className='lg:flex w-full'> <NextSeo title='심사이력' />
<div className='w-full lg:w-3/4 lg:pr-5 py-8 text-center lg:text-left'> <div className='lg:flex w-full'>
{ <div className='w-full lg:w-3/4 lg:pr-5 py-8 text-center lg:text-left'>
data.state === 0 ? <Message type='info'> {
<h2 className='text-lg font-extrabold'> </h2> data.state === 0 ? <Message type='info'>
<p> .</p> <h2 className='text-lg font-extrabold'> </h2>
<p> .</p>
</Message>
: data.state === 1 ? <Message type='success'> </Message>
<h2 className='text-lg font-extrabold'></h2> : data.state === 1 ? <Message type='success'>
<p> !</p> <h2 className='text-lg font-extrabold'></h2>
<p><Link href={`/bots/${data.id}`}><a className='text-blue-500 hover:text-blue-400'> </a></Link></p> <p> !</p>
</Message> : <Message type='error'> <p><Link href={`/bots/${data.id}`} className='text-blue-500 hover:text-blue-400'> </Link></p>
<h2 className='text-lg font-extrabold'></h2> </Message> : <Message type='error'>
<p> .</p> <h2 className='text-lg font-extrabold'></h2>
{ <p> .</p>
data.reason && <> {
<p>: <strong>{BotSubmissionDenyReasonPresetsName[data.reason] || data.reason}</strong></p> data.reason && <>
<div className='pt-2'> <p>: <strong>{BotSubmissionDenyReasonPresetsName[data.reason] || data.reason}</strong></p>
{DenyPresetsArticle[data.reason]} <div className='pt-2'>
</div> {DenyPresetsArticle[data.reason]}
</> </div>
} </>
<div className='pt-2'> }
{data.strikes < 3 ? ( <div className='pt-2'>
<p> {data.strikes < 3 ? (
{3 - data.strikes} . . <br/> <p>
'프라이빗 봇', '봇 오프라인', '공식 디스코드 서버 미참여' . {3 - data.strikes} . . <br/>
</p> '프라이빗 봇', '봇 오프라인', '공식 디스코드 서버 미참여' .
) : ( </p>
<p> .</p> ) : (
)} <p> .</p>
</div> )}
</Message> </div>
} </Message>
<p className='dark:text-gray-300 text-gray-800 text-base mt-3'>{data.intro}</p>
</div>
<div className='w-full lg:w-1/4 lg:pt-8'>
<LongButton
newTab
href={
data.url ??
`https://discordapp.com/oauth2/authorize?client_id=${data.id}&scope=bot&permissions=0`
} }
> <p className='dark:text-gray-300 text-gray-800 text-base mt-3'>{data.intro}</p>
<h4 className='whitespace-nowrap'>
<i className='fas fa-user-plus text-discord-blurple' />
</h4>
</LongButton>
<LongButton onClick={setCopied}>
<h4>
{ isCopied ? <><i className='fas fa-check text-emerald-400' /> </> : <><i className='far fa-copy'/> </>}
</h4>
</LongButton>
</div>
</div>
<Divider className='px-5' />
<div className='lg:flex lg:flex-row-reverse'>
<div className='mb-1 w-full lg:w-1/4'>
<h2 className='3xl mb-2 font-bold'></h2>
<div className='grid gap-4 grid-cols-1 px-4 py-4 text-black dark:text-gray-400 dark:bg-discord-black bg-little-white rounded-sm'>
<div className='flex'>
<div className='w-2/5'>
<i className='fas fa-fingerprint' /> ID
</div>
<div className='text-black dark:text-gray-400 truncate'>
{data.id}
</div>
</div>
<div className='flex'>
<div className='w-2/5'>
<i className='fas fa-calendar-day' />
</div>
<div className='text-black dark:text-gray-400'>
{Day(data.date * 1000).format('LLL')}
</div>
</div>
<div className='flex'>
<div className='w-2/5'>
<i className='far fa-flag' />
</div>
<div className='markdown-body text-black dark:text-gray-400'>
<code>{data.prefix}</code>
</div>
</div>
</div> </div>
<h2 className='3xl mb-2 mt-2 font-bold'></h2> <div className='w-full lg:w-1/4 lg:pt-8'>
<div className='flex flex-wrap'> <LongButton
{data.category.map(el => ( newTab
<Tag key={el} text={el} href={`/bots/categories/${el}`} /> href={
data.url ??
`https://discordapp.com/oauth2/authorize?client_id=${data.id}&scope=bot&permissions=0`
}
>
<h4 className='whitespace-nowrap'>
<i className='fas fa-user-plus text-discord-blurple' />
</h4>
</LongButton>
<LongButton onClick={setCopied}>
<h4>
{ isCopied ? <><i className='fas fa-check text-emerald-400' /> </> : <><i className='far fa-copy'/> </>}
</h4>
</LongButton>
</div>
</div>
<Divider className='px-5' />
<div className='lg:flex lg:flex-row-reverse'>
<div className='mb-1 w-full lg:w-1/4'>
<h2 className='3xl mb-2 font-bold'></h2>
<div className='grid gap-4 grid-cols-1 px-4 py-4 text-black dark:text-gray-400 dark:bg-discord-black bg-little-white rounded-sm'>
<div className='flex'>
<div className='w-2/5'>
<i className='fas fa-fingerprint' /> ID
</div>
<div className='text-black dark:text-gray-400 truncate'>
{data.id}
</div>
</div>
<div className='flex'>
<div className='w-2/5'>
<i className='fas fa-calendar-day' />
</div>
<div className='text-black dark:text-gray-400'>
{Day(data.date * 1000).format('LLL')}
</div>
</div>
<div className='flex'>
<div className='w-2/5'>
<i className='far fa-flag' />
</div>
<div className='markdown-body text-black dark:text-gray-400'>
<code>{data.prefix}</code>
</div>
</div>
</div>
<h2 className='3xl mb-2 mt-2 font-bold'></h2>
<div className='flex flex-wrap'>
{data.category.map(el => (
<Tag key={el} text={el} href={`/bots/categories/${el}`} />
))}
</div>
<h2 className='3xl mb-2 mt-2 font-bold'></h2>
{(data.owners as User[]).map(el => (
<Owner
key={el.id}
id={el.id}
tag={el.tag}
globalName={el.globalName}
username={el.username}
/>
))} ))}
<div className='list grid'>
{data.discord && (
<a
rel='noopener noreferrer'
target='_blank'
className='text-discord-blurple hover:underline'
href={`https://discord.gg/${data.discord}`}
>
<i className='fab fa-discord' />
</a>
)}
{data.web && (
<a
rel='noopener noreferrer'
target='_blank'
className='text-blue-500 hover:underline'
href={data.web}
>
<i className='fas fa-globe' />
</a>
)}
{data.git && (
<a
rel='noopener noreferrer'
target='_blank'
className='hover:underline'
href={data.git}
>
<i className={`fab fa-${git[new URL(data.git).hostname]?.icon ?? 'git-alt'}`} />
{git[new URL(data.git).hostname]?.text ?? 'Git'}
</a>
)}
</div>
<Advertisement size='tall' />
</div> </div>
<h2 className='3xl mb-2 mt-2 font-bold'></h2> <div className='markdown-body pt-10 w-full lg:pr-5 lg:w-3/4'>
{(data.owners as User[]).map(el => ( <Advertisement />
<Owner <Segment className='my-4'>
key={el.id} <Markdown text={data.desc}/>
id={el.id} </Segment>
tag={el.tag} <Advertisement />
globalName={el.globalName}
username={el.username}
/>
))}
<div className='list grid'>
{data.discord && (
<a
rel='noopener noreferrer'
target='_blank'
className='text-discord-blurple hover:underline'
href={`https://discord.gg/${data.discord}`}
>
<i className='fab fa-discord' />
</a>
)}
{data.web && (
<a
rel='noopener noreferrer'
target='_blank'
className='text-blue-500 hover:underline'
href={data.web}
>
<i className='fas fa-globe' />
</a>
)}
{data.git && (
<a
rel='noopener noreferrer'
target='_blank'
className='hover:underline'
href={data.git}
>
<i className={`fab fa-${git[new URL(data.git).hostname]?.icon ?? 'git-alt'}`} />
{git[new URL(data.git).hostname]?.text ?? 'Git'}
</a>
)}
</div> </div>
<Advertisement size='tall' />
</div> </div>
<div className='markdown-body pt-10 w-full lg:pr-5 lg:w-3/4'> </Container>
<Advertisement /> )
<Segment className='my-4'>
<Markdown text={data.desc}/>
</Segment>
<Advertisement />
</div>
</div>
</Container>
} }
export const getServerSideProps = async (ctx: Context) => { export const getServerSideProps = async (ctx: Context) => {

View File

@ -44,232 +44,236 @@ const Servers: NextPage<ServersProps> = ({ data, desc, date, user, theme }) => {
}) })
}, [ data ]) }, [ data ])
if (!data?.id) return <NotFound /> if (!data?.id) return <NotFound />
return <div style={bg ? { background: `linear-gradient(to right, rgba(34, 36, 38, 0.68), rgba(34, 36, 38, 0.68)), url("${data.bg}") center top / cover no-repeat fixed` } : {}}> return (
<Container paddingTop className='py-10'> <div style={bg ? { background: `linear-gradient(to right, rgba(34, 36, 38, 0.68), rgba(34, 36, 38, 0.68)), url("${data.bg}") center top / cover no-repeat fixed` } : {}}>
<NextSeo <Container paddingTop className='py-10'>
title={data.name} <NextSeo
description={data.intro} title={data.name}
twitter={{ description={data.intro}
cardType: 'summary_large_image' twitter={{
}} cardType: 'summary_large_image'
openGraph={{ }}
images: [ openGraph={{
{ images: [
url: KoreanbotsEndPoints.OG.server(data.id, data.name, data.intro, data.category, [formatNumber(data.votes), formatNumber(data.members)]),
width: 2048,
height: 1170,
alt: 'Server Preview Image'
}
]
}}
/>
{
data.state === 'blocked' ? <div className='pb-40'>
<Message type='error'>
<h2 className='text-lg font-extrabold'> .</h2>
</Message>
</div>
: <>
<div className='w-full pb-2'>
{ {
data.state === 'unreachable' ? <Message type='error'> url: KoreanbotsEndPoints.OG.server(data.id, data.name, data.intro, data.category, [formatNumber(data.votes), formatNumber(data.members)]),
<h2 className='text-lg font-extrabold'> .</h2> width: 2048,
<p> , .</p> height: 1170,
{ alt: 'Server Preview Image'
owners?.find(el => el.id === user?.id) && <>
<h3 className='text-md font-bold pt-2'> !</h3>
<p> <a className='text-blue-600 hover:text-blue-500 cursor-pointer' href={`${DiscordEnpoints.InviteApplication(DSKR_BOT_ID, {}, 'bot', null, data.id)}&disable_guild_select=true`}></a> !</p>
</>
}
</Message> :
data.state === 'reported' ?
<Message type='error'>
<h2 className='text-lg font-extrabold'> , .</h2>
<p> .</p>
<p> <Link href='/guidelines'><a className='text-blue-500 hover:text-blue-400'></a></Link> <Link href='/discord'><a className='text-blue-500 hover:text-blue-400'> </a></Link> .</p>
</Message> : ''
} }
</div> ]
<div className='lg:flex w-full'> }}
<div className='w-full text-center lg:w-2/12'> />
<ServerIcon {
id={data.id} data.state === 'blocked' ? <div className='pb-40'>
size={256} <Message type='error'>
className='w-full rounded-full' <h2 className='text-lg font-extrabold'> .</h2>
/> </Message>
</div> </div>
<div className='grow px-5 py-12 w-full text-center lg:w-5/12 lg:text-left'> : <>
<h1 className='mb-2 mt-3 text-4xl font-bold' style={bg ? { color: 'white' } : {}}> <div className='w-full pb-2'>
{data.name}{' '}
{checkServerFlag(data.flags, 'trusted') ? (
<Tooltip placement='bottom' overlay='해당 서버는 한국 디스코드 리스트에서 엄격한 기준을 통과한 서버 입니다!'>
<span className='text-koreanbots-blue text-3xl'>
<i className='fas fa-award' />
</span>
</Tooltip>
) : ''}
</h1>
<p className={`${bg ? 'text-gray-300' : 'dark:text-gray-300 text-gray-800'} text-base`}>{data.intro}</p>
</div>
<div className='w-full lg:w-1/4'>
{ {
['ok', 'unreachable'].includes(data.state) && <LongButton data.state === 'unreachable' ? <Message type='error'>
newTab <h2 className='text-lg font-extrabold'> .</h2>
href={`/servers/${router.query.id}/join`} <p> , .</p>
> {
<h4 className='whitespace-nowrap'> owners?.find(el => el.id === user?.id) && <>
<i className='fas fa-user-plus text-discord-blurple' /> <h3 className='text-md font-bold pt-2'> !</h3>
</h4> <p> <a className='text-blue-600 hover:text-blue-500 cursor-pointer' href={`${DiscordEnpoints.InviteApplication(DSKR_BOT_ID, {}, 'bot', null, data.id)}&disable_guild_select=true`}></a> !</p>
</LongButton> </>
}
</Message> :
data.state === 'reported' ?
<Message type='error'>
<h2 className='text-lg font-extrabold'> , .</h2>
<p> .</p>
<p> <Link href='/guidelines' className='text-blue-500 hover:text-blue-400'></Link> <Link href='/discord' className='text-blue-500 hover:text-blue-400'> </Link> .</p>
</Message> : ''
} }
<Link href={`/servers/${router.query.id}/vote`}> </div>
<LongButton> <div className='lg:flex w-full'>
<h4> <div className='w-full text-center lg:w-2/12'>
<i className='fas fa-heart text-red-600' /> <ServerIcon
</h4> id={data.id}
<span className='ml-1 px-2 text-center text-black dark:text-gray-400 text-sm bg-little-white-hover dark:bg-very-black rounded-lg'> size={256}
{formatNumber(data.votes)} className='w-full rounded-full'
</span> />
</LongButton> </div>
</Link> <div className='grow px-5 py-12 w-full text-center lg:w-5/12 lg:text-left'>
{ <h1 className='mb-2 mt-3 text-4xl font-bold' style={bg ? { color: 'white' } : {}}>
(owners?.find(el => el.id === user?.id) || checkUserFlag(user?.flags, 'staff')) && <> {data.name}{' '}
<LongButton href={`/servers/${data.id}/edit`}> {checkServerFlag(data.flags, 'trusted') ? (
<h4> <Tooltip placement='bottom' overlay='해당 서버는 한국 디스코드 리스트에서 엄격한 기준을 통과한 서버 입니다!'>
<i className='fas fa-cogs' /> <span className='text-koreanbots-blue text-3xl'>
<i className='fas fa-award' />
</span>
</Tooltip>
) : ''}
</h1>
<p className={`${bg ? 'text-gray-300' : 'dark:text-gray-300 text-gray-800'} text-base`}>{data.intro}</p>
</div>
<div className='w-full lg:w-1/4'>
{
['ok', 'unreachable'].includes(data.state) && <LongButton
newTab
href={`/servers/${router.query.id}/join`}
>
<h4 className='whitespace-nowrap'>
<i className='fas fa-user-plus text-discord-blurple' />
</h4> </h4>
</LongButton> </LongButton>
{/* <LongButton onClick={async() => { }
const res = await Fetch(`/servers/${data.id}/stats`, { method: 'PATCH'} ) <Link href={`/servers/${router.query.id}/vote`} legacyBehavior>
if(res.code !== 200) return alert(res.message) <LongButton>
else window.location.reload()
}}>
<h4> <h4>
<i className='fas fa-sync' /> <i className='fas fa-heart text-red-600' />
</h4> </h4>
</LongButton> */} <span className='ml-1 px-2 text-center text-black dark:text-gray-400 text-sm bg-little-white-hover dark:bg-very-black rounded-lg'>
</> {formatNumber(data.votes)}
} </span>
</div> </LongButton>
</div>
<Divider className='px-5' />
<div className='hidden lg:block'>
<Advertisement />
</div>
<div className='lg:flex lg:flex-row-reverse' style={bg ? { color: 'white' } : {}}>
<div className='mb-1 w-full lg:w-1/4'>
<h2 className='3xl mb-2 font-bold'></h2>
<div className='grid gap-4 grid-cols-2 px-4 py-4 text-black dark:text-gray-400 dark:bg-discord-black bg-little-white rounded-sm'>
<div>
<i className='fas fa-users' />
</div>
<div>{data.members || 'N/A'}</div>
<div>
<i className='far fa-gem' />
</div>
<div>{typeof data.boostTier === 'number' ? `${data.boostTier}레벨` : 'N/A'}</div>
<div>
<i className='fas fa-calendar-day' />
</div>
<div>{Day(date).fromNow(false)}</div>
{
checkServerFlag(data.flags, 'discord_partnered') ?
<Tooltip overlay='해당 서버는 디스코드 파트너 입니다.'>
<div className='col-span-2'>
<i className='fas fa-infinity text-discord-blurple' />
</div>
</Tooltip>
: ''
}
{
checkServerFlag(data.flags, 'verified') ?
<Tooltip overlay='해당 서버는 디스코드에서 인증된 서버입니다.'>
<div className='col-span-2'>
<i className='fas fa-check text-discord-blurple' />
</div>
</Tooltip>
: ''
}
</div>
<h2 className='3xl mb-2 mt-2 font-bold'></h2>
<div className='flex flex-wrap'>
{data.category.map(el => (
<Tag key={el} text={el} href={`/servers/categories/${el}`} />
))}
</div>
{
data.emojis.length !== 0 && <>
<h2 className='3xl mb-2 mt-2 font-bold'></h2>
<div className='flex flex-wrap'>
{
data.emojis.slice(0, 5).map(el => <Image src={el.url} key={el.name} className='h-8 m-1' />)
}
{
data.emojis.length > 5 && <Tag className='cursor-pointer' onClick={() => setEmojisModal(true)} text={`+${data.emojis.length - 5}`} />
}
</div>
<Modal header='이모지 전체보기' closeIcon isOpen={emojisModal} onClose={() => setEmojisModal(false)}
full dark={theme === 'dark'}>
<strong>{data.emojis.length}</strong> .
<div className='flex flex-wrap'>
{
data.emojis.map(el => <Tooltip zIndex={1000} key={el.name} placement='top' overlay={`:${el.name}:`} mouseLeaveDelay={0}>
<div>
<Image src={el.url} className='h-8 m-1' />
</div>
</Tooltip>)
}
</div>
</Modal>
</>
}
<h2 className='3xl mb-2 mt-2 font-bold'></h2>
{
data.owner && <Owner
key={data.owner.id}
id={data.owner.id}
tag={data.owner.tag}
globalName={data.owner.globalName}
username={data.owner.username}
/>
}
<LongButton onClick={() => setOwnersModal(true)}> </LongButton>
<Modal header='관리자 전체보기' closeIcon isOpen={ownersModal} onClose={() => setOwnersModal(false)}
full dark={theme === 'dark'}>
<div className='grid gap-x-1 grid-rows-1 md:grid-cols-2'>
{owners ? owners.map(el => (
<Owner
key={el.id}
id={el.id}
tag={el.tag}
globalName={el.globalName}
username={el.username}
crown={el.id === data.owner?.id}
/>
)) : <strong> ...</strong>}
</div>
</Modal>
<div className='list grid'>
<Link href={`/servers/${router.query.id}/report`}>
<a className='text-red-600 hover:underline cursor-pointer' aria-hidden='true'>
<i className='far fa-flag' />
</a>
</Link> </Link>
{
(owners?.find(el => el.id === user?.id) || checkUserFlag(user?.flags, 'staff')) && <>
<LongButton href={`/servers/${data.id}/edit`}>
<h4>
<i className='fas fa-cogs' />
</h4>
</LongButton>
{/* <LongButton onClick={async() => {
const res = await Fetch(`/servers/${data.id}/stats`, { method: 'PATCH'} )
if(res.code !== 200) return alert(res.message)
else window.location.reload()
}}>
<h4>
<i className='fas fa-sync' />
</h4>
</LongButton> */}
</>
}
</div> </div>
<Advertisement size='tall' />
</div> </div>
<div className='w-full lg:pr-5 lg:w-3/4'> <Divider className='px-5' />
<Segment className='my-4'> <div className='hidden lg:block'>
<Markdown text={desc}/>
</Segment>
<Advertisement /> <Advertisement />
</div> </div>
</div> <div className='lg:flex lg:flex-row-reverse' style={bg ? { color: 'white' } : {}}>
</> <div className='mb-1 w-full lg:w-1/4'>
} <h2 className='3xl mb-2 font-bold'></h2>
</Container> <div className='grid gap-4 grid-cols-2 px-4 py-4 text-black dark:text-gray-400 dark:bg-discord-black bg-little-white rounded-sm'>
</div> <div>
<i className='fas fa-users' />
</div>
<div>{data.members || 'N/A'}</div>
<div>
<i className='far fa-gem' />
</div>
<div>{typeof data.boostTier === 'number' ? `${data.boostTier}레벨` : 'N/A'}</div>
<div>
<i className='fas fa-calendar-day' />
</div>
<div>{Day(date).fromNow(false)}</div>
{
checkServerFlag(data.flags, 'discord_partnered') ?
<Tooltip overlay='해당 서버는 디스코드 파트너 입니다.'>
<div className='col-span-2'>
<i className='fas fa-infinity text-discord-blurple' />
</div>
</Tooltip>
: ''
}
{
checkServerFlag(data.flags, 'verified') ?
<Tooltip overlay='해당 서버는 디스코드에서 인증된 서버입니다.'>
<div className='col-span-2'>
<i className='fas fa-check text-discord-blurple' />
</div>
</Tooltip>
: ''
}
</div>
<h2 className='3xl mb-2 mt-2 font-bold'></h2>
<div className='flex flex-wrap'>
{data.category.map(el => (
<Tag key={el} text={el} href={`/servers/categories/${el}`} />
))}
</div>
{
data.emojis.length !== 0 && <>
<h2 className='3xl mb-2 mt-2 font-bold'></h2>
<div className='flex flex-wrap'>
{
data.emojis.slice(0, 5).map(el => <Image src={el.url} key={el.name} className='h-8 m-1' />)
}
{
data.emojis.length > 5 && <Tag className='cursor-pointer' onClick={() => setEmojisModal(true)} text={`+${data.emojis.length - 5}`} />
}
</div>
<Modal header='이모지 전체보기' closeIcon isOpen={emojisModal} onClose={() => setEmojisModal(false)}
full dark={theme === 'dark'}>
<strong>{data.emojis.length}</strong> .
<div className='flex flex-wrap'>
{
data.emojis.map(el => <Tooltip zIndex={1000} key={el.name} placement='top' overlay={`:${el.name}:`} mouseLeaveDelay={0}>
<div>
<Image src={el.url} className='h-8 m-1' />
</div>
</Tooltip>)
}
</div>
</Modal>
</>
}
<h2 className='3xl mb-2 mt-2 font-bold'></h2>
{
data.owner && <Owner
key={data.owner.id}
id={data.owner.id}
tag={data.owner.tag}
globalName={data.owner.globalName}
username={data.owner.username}
/>
}
<LongButton onClick={() => setOwnersModal(true)}> </LongButton>
<Modal header='관리자 전체보기' closeIcon isOpen={ownersModal} onClose={() => setOwnersModal(false)}
full dark={theme === 'dark'}>
<div className='grid gap-x-1 grid-rows-1 md:grid-cols-2'>
{owners ? owners.map(el => (
<Owner
key={el.id}
id={el.id}
tag={el.tag}
globalName={el.globalName}
username={el.username}
crown={el.id === data.owner?.id}
/>
)) : <strong> ...</strong>}
</div>
</Modal>
<div className='list grid'>
<Link
href={`/servers/${router.query.id}/report`}
className='text-red-600 hover:underline cursor-pointer'
aria-hidden='true'>
<i className='far fa-flag' />
</Link>
</div>
<Advertisement size='tall' />
</div>
<div className='w-full lg:pr-5 lg:w-3/4'>
<Segment className='my-4'>
<Markdown text={desc}/>
</Segment>
<Advertisement />
</div>
</div>
</>
}
</Container>
</div>
)
} }
export const getServerSideProps = async (ctx: Context) => { export const getServerSideProps = async (ctx: Context) => {

View File

@ -30,83 +30,85 @@ const ReportServer: NextPage<ReportServerProps> = ({ data, user, csrfToken }) =>
if(!user) return <Login> if(!user) return <Login>
<NextSeo title='신고하기' /> <NextSeo title='신고하기' />
</Login> </Login>
return <Container paddingTop className='py-10'> return (
<NextSeo title={`${data.name} 신고하기`} /> <Container paddingTop className='py-10'>
<Link href={makeServerURL(data)}> <NextSeo title={`${data.name} 신고하기`} />
<a className='text-blue-500 hover:opacity-80'><i className='fas fa-arrow-left mt-3 mb-3' /> <strong>{data.name}</strong>{getJosaPicker('로')(data.name)} </a> <Link href={makeServerURL(data)} className='text-blue-500 hover:opacity-80'>
</Link> <i className='fas fa-arrow-left mt-3 mb-3' /> <strong>{data.name}</strong>{getJosaPicker('로')(data.name)}
{ </Link>
reportRes?.code === 200 ? <Message type='success'> {
<h2 className='text-lg font-semibold'> !</h2> reportRes?.code === 200 ? <Message type='success'>
<p> . <strong> <a className='text-blue-600 hover:text-blue-500' href='/discord'> </a> !!</strong></p> <h2 className='text-lg font-semibold'> !</h2>
</Message> : <Formik onSubmit={async (body) => { <p> . <strong> <a className='text-blue-600 hover:text-blue-500' href='/discord'> </a> !!</strong></p>
const res = await Fetch(`/servers/${data.id}/report`, { method: 'POST', body: JSON.stringify(body) }) </Message> : <Formik onSubmit={async (body) => {
setReportRes(res) const res = await Fetch(`/servers/${data.id}/report`, { method: 'POST', body: JSON.stringify(body) })
}} validationSchema={ReportSchema} initialValues={{ setReportRes(res)
category: null, }} validationSchema={ReportSchema} initialValues={{
description: '', category: null,
_csrf: csrfToken description: '',
}}> _csrf: csrfToken
{ }}>
({ errors, touched, values, setFieldValue }) => ( {
<Form> ({ errors, touched, values, setFieldValue }) => (
<div className='mb-5'> <Form>
{ <div className='mb-5'>
reportRes && <div className='my-5'> {
<Message type='error'> reportRes && <div className='my-5'>
<h2 className='text-lg font-semibold'>{reportRes.message}</h2> <Message type='error'>
<ul className='list-disc'> <h2 className='text-lg font-semibold'>{reportRes.message}</h2>
{reportRes.errors?.map((el, n) => <li key={n}>{el}</li>)} <ul className='list-disc'>
</ul> {reportRes.errors?.map((el, n) => <li key={n}>{el}</li>)}
</Message> </ul>
</div> </Message>
}
<h3 className='font-bold'> </h3>
<p className='text-gray-400 text-sm mb-1'> .</p>
{
serverReportCats.map(el =>
<div key={el}>
<label>
<Field type='radio' name='category' value={el} className='mr-1.5 py-2' />
{el}
</label>
</div> </div>
) }
} <h3 className='font-bold'> </h3>
<div className='mt-1 text-red-500 text-xs font-light'>{errors.category && touched.category ? errors.category : null}</div> <p className='text-gray-400 text-sm mb-1'> .</p>
{ {
values.category && <> serverReportCats.map(el =>
{ <div key={el}>
<label>
<Field type='radio' name='category' value={el} className='mr-1.5 py-2' />
{el}
</label>
</div>
)
}
<div className='mt-1 text-red-500 text-xs font-light'>{errors.category && touched.category ? errors.category as string : null}</div>
{
values.category && <>
{ {
[serverReportCats[1]]: <Message type='info'> {
<h3 className='font-bold text-xl'> ?</h3> [serverReportCats[1]]: <Message type='info'>
<p> .</p> <h3 className='font-bold text-xl'> ?</h3>
<p className='list-disc list-item list-inside'> 1393 | 1388</p> <p> .</p>
</Message>, <p className='list-disc list-item list-inside'> 1393 | 1388</p>
[serverReportCats[3]]: <DMCA values={values} errors={errors} touched={touched} setFieldValue={setFieldValue} />, </Message>,
[serverReportCats[4]]: <Message type='warning'> [serverReportCats[3]]: <DMCA values={values} errors={errors} touched={touched} setFieldValue={setFieldValue} />,
<h3 className='font-bold text-xl'> ?</h3> [serverReportCats[4]]: <Message type='warning'>
<p><a className='text-blue-400' target='_blank' rel='noreferrer' href='http://dis.gd/report'> </a> .</p> <h3 className='font-bold text-xl'> ?</h3>
</Message> <p><a className='text-blue-400' target='_blank' rel='noreferrer' href='http://dis.gd/report'> </a> .</p>
</Message>
}[values.category]
} }[values.category]
{ }
![serverReportCats[3]].includes(values.category) && <> {
<h3 className='font-bold mt-2'></h3> ![serverReportCats[3]].includes(values.category) && <>
<p className='text-gray-400 text-sm mb-1'> .</p> <h3 className='font-bold mt-2'></h3>
<TextField values={values} errors={errors} touched={touched} setFieldValue={setFieldValue} /> <p className='text-gray-400 text-sm mb-1'> .</p>
</> <TextField values={values} errors={errors} touched={touched} setFieldValue={setFieldValue} />
} </>
</> }
} </>
</div> }
</Form> </div>
) </Form>
} )
</Formik> }
} </Formik>
</Container> }
</Container>
)
} }
export const getServerSideProps = async (ctx: Context) => { export const getServerSideProps = async (ctx: Context) => {

View File

@ -48,58 +48,60 @@ const VoteServer: NextPage<VoteServerProps> = ({ data, user, theme, csrfToken })
</Login> </Login>
if((checkServerFlag(data.flags, 'trusted') || checkServerFlag(data.flags, 'partnered')) && data.vanity && data.vanity !== router.query.id) router.push(`/servers/${data.vanity}/vote?csrfToken=${csrfToken}`) if((checkServerFlag(data.flags, 'trusted') || checkServerFlag(data.flags, 'partnered')) && data.vanity && data.vanity !== router.query.id) router.push(`/servers/${data.vanity}/vote?csrfToken=${csrfToken}`)
return <Container paddingTop className='py-10'> return (
<NextSeo title={data.name} description={`한국 디스코드 리스트에서 ${data.name}에 투표하세요.`} openGraph={{ <Container paddingTop className='py-10'>
images: [ <NextSeo title={data.name} description={`한국 디스코드 리스트에서 ${data.name}에 투표하세요.`} openGraph={{
{ images: [
url: KoreanbotsEndPoints.CDN.icon(data.id, { format: 'png', size: 256 }), {
width: 256, url: KoreanbotsEndPoints.CDN.icon(data.id, { format: 'png', size: 256 }),
height: 256, width: 256,
alt: 'Server Avatar' height: 256,
} alt: 'Server Avatar'
] }
}} /> ]
{ }} />
data.state === 'blocked' ? <div className='pb-40'> {
<Message type='error'> data.state === 'blocked' ? <div className='pb-40'>
<h2 className='text-lg font-extrabold'> .</h2> <Message type='error'>
</Message> <h2 className='text-lg font-extrabold'> .</h2>
</div> : <> </Message>
<Advertisement /> </div> : <>
<Link href={makeServerURL(data)}> <Advertisement />
<a className='text-blue-500 hover:opacity-80'><i className='fas fa-arrow-left mt-3 mb-3' /> <strong>{data.name}</strong>{getJosaPicker('로')(data.name)} </a> <Link href={makeServerURL(data)} className='text-blue-500 hover:opacity-80'>
</Link> <i className='fas fa-arrow-left mt-3 mb-3' /> <strong>{data.name}</strong>{getJosaPicker('로')(data.name)}
<Segment className='mb-16 py-8'> </Link>
<div className='text-center'> <Segment className='mb-16 py-8'>
<ServerIcon id={data.id} className='mx-auto w-52 h-52 bg-white mb-4 rounded-full' /> <div className='text-center'>
<Tag text={<span><i className='fas fa-heart text-red-600' /> {data.votes}</span>} dark /> <ServerIcon id={data.id} className='mx-auto w-52 h-52 bg-white mb-4 rounded-full' />
<h1 className='text-3xl font-bold mt-3'>{data.name}</h1> <Tag text={<span><i className='fas fa-heart text-red-600' /> {data.votes}</span>} dark />
<h4 className='text-md mt-1'>12 .</h4> <h1 className='text-3xl font-bold mt-3'>{data.name}</h1>
<div className='inline-block mt-2'> <h4 className='text-md mt-1'>12 .</h4>
{ <div className='inline-block mt-2'>
votingStatus === 0 ? <Button onClick={()=> setVotingStatus(1)}> {
<><i className='far fa-heart text-red-600'/> </> votingStatus === 0 ? <Button onClick={()=> setVotingStatus(1)}>
</Button> <><i className='far fa-heart text-red-600'/> </>
: votingStatus === 1 ? <Captcha dark={theme === 'dark'} onVerify={async (key) => { </Button>
const res = await Fetch<{ retryAfter: number }|unknown>(`/servers/${data.id}/vote`, { method: 'POST', body: JSON.stringify({ _csrf: csrfToken, _captcha: key }) }) : votingStatus === 1 ? <Captcha dark={theme === 'dark'} onVerify={async (key) => {
setResult(res) const res = await Fetch<{ retryAfter: number }|unknown>(`/servers/${data.id}/vote`, { method: 'POST', body: JSON.stringify({ _csrf: csrfToken, _captcha: key }) })
setVotingStatus(2) setResult(res)
}} setVotingStatus(2)
/> }}
: result.code === 200 ? <h2 className='text-2xl font-bold'> !</h2> />
: result.code === 429 ? <> : result.code === 200 ? <h2 className='text-2xl font-bold'> !</h2>
<h2 className='text-2xl font-bold'> .</h2> : result.code === 429 ? <>
<h4 className='text-md mt-1'>{Day(+new Date() + result.data?.retryAfter).fromNow()} .</h4> <h2 className='text-2xl font-bold'> .</h2>
</> <h4 className='text-md mt-1'>{Day(+new Date() + result.data?.retryAfter).fromNow()} .</h4>
: <p>{result.message}</p> </>
} : <p>{result.message}</p>
}
</div>
</div> </div>
</Segment>
</div> <Advertisement /></>
</Segment> }
<Advertisement /></> </Container>
} )
</Container>
} }
export const getServerSideProps = async (ctx: Context) => { export const getServerSideProps = async (ctx: Context) => {

View File

@ -97,10 +97,12 @@ const Users: NextPage<UserProps> = ({ user, data }) => {
)} )}
{ {
user?.id !== data.id && <div className='list-none mt-2'> user?.id !== data.id && <div className='list-none mt-2'>
<Link href={`/users/${router.query.id}/report`}> <Link
<a className='text-red-600 hover:underline cursor-pointer' aria-hidden='true'> href={`/users/${router.query.id}/report`}
<i className='far fa-flag' /> className='text-red-600 hover:underline cursor-pointer'
</a> aria-hidden='true'>
<i className='far fa-flag' />
</Link> </Link>
</div> </div>
} }
@ -138,9 +140,9 @@ const Users: NextPage<UserProps> = ({ user, data }) => {
export const getServerSideProps = async (ctx: Context) => { export const getServerSideProps = async (ctx: Context) => {
const parsed = parseCookie(ctx.req) const parsed = parseCookie(ctx.req)
const user = await get.Authorization(parsed?.token) || '' const user = (await get.Authorization(parsed?.token)) || ''
const data = await get.user.load(ctx.query.id) const data = await get.user.load(ctx.query.id)
return { props: { user: await get.user.load(user) || {}, data, date: Number(SnowflakeUtil.deconstruct(data?.id ?? '0')?.timestamp), csrfToken: getToken(ctx.req, ctx.res) } } return { props: { user: (await get.user.load(user)) || {}, data, date: Number(SnowflakeUtil.deconstruct(data?.id ?? '0')?.timestamp), csrfToken: getToken(ctx.req, ctx.res) } }
} }
interface UserProps { interface UserProps {

View File

@ -38,78 +38,80 @@ const ReportUser: NextPage<ReportUserProps> = ({ data, user, csrfToken }) => {
</div> </div>
</div> </div>
</> </>
return <Container paddingTop className='py-10'> return (
<NextSeo title={`${data.globalName} 신고하기`} /> <Container paddingTop className='py-10'>
<Link href={makeUserURL(data)}> <NextSeo title={`${data.globalName} 신고하기`} />
<a className='text-blue-500 hover:opacity-80'><i className='fas fa-arrow-left mt-3 mb-3' /> <strong>{data.globalName}</strong>{getJosaPicker('로')(data.globalName)} </a> <Link href={makeUserURL(data)} className='text-blue-500 hover:opacity-80'>
</Link> <i className='fas fa-arrow-left mt-3 mb-3' /> <strong>{data.globalName}</strong>{getJosaPicker('로')(data.globalName)}
{ </Link>
reportRes?.code === 200 ? <Message type='success'> {
<h2 className='text-lg font-semibold'> !</h2> reportRes?.code === 200 ? <Message type='success'>
<p> . <strong> <a className='text-blue-600 hover:text-blue-500' href='/discord'> </a> !!</strong></p> <h2 className='text-lg font-semibold'> !</h2>
</Message> : <Formik onSubmit={async (body) => { <p> . <strong> <a className='text-blue-600 hover:text-blue-500' href='/discord'> </a> !!</strong></p>
const res = await Fetch(`/users/${data.id}/report`, { method: 'POST', body: JSON.stringify(body) }) </Message> : <Formik onSubmit={async (body) => {
setReportRes(res) const res = await Fetch(`/users/${data.id}/report`, { method: 'POST', body: JSON.stringify(body) })
}} validationSchema={ReportSchema} initialValues={{ setReportRes(res)
category: null, }} validationSchema={ReportSchema} initialValues={{
description: '', category: null,
_csrf: csrfToken description: '',
}}> _csrf: csrfToken
{ }}>
({ errors, touched, values, setFieldValue }) => ( {
<Form> ({ errors, touched, values, setFieldValue }) => (
<div className='mb-5'> <Form>
{ <div className='mb-5'>
reportRes && <div className='my-5'> {
<Message type='error'> reportRes && <div className='my-5'>
<h2 className='text-lg font-semibold'>{reportRes.message}</h2> <Message type='error'>
<ul className='list-disc'> <h2 className='text-lg font-semibold'>{reportRes.message}</h2>
{reportRes.errors?.map((el, n) => <li key={n}>{el}</li>)} <ul className='list-disc'>
</ul> {reportRes.errors?.map((el, n) => <li key={n}>{el}</li>)}
</Message> </ul>
</div> </Message>
}
<h3 className='font-bold'> </h3>
<p className='text-gray-400 text-sm mb-1'> .</p>
{
reportCats.map(el =>
<div key={el}>
<label>
<Field type='radio' name='category' value={el} className='mr-1.5 py-2' />
{el}
</label>
</div> </div>
) }
} <h3 className='font-bold'> </h3>
<div className='mt-1 text-red-500 text-xs font-light'>{errors.category && touched.category ? errors.category : null}</div> <p className='text-gray-400 text-sm mb-1'> .</p>
{ {
values.category && <> reportCats.map(el =>
{ <div key={el}>
values.category === '오픈소스 라이선스, 저작권 위반 등 권리 침해' ? <DMCA values={values} errors={errors} touched={touched} setFieldValue={setFieldValue} /> : <label>
values.category === '괴롭힘, 모욕, 명예훼손' ? <> <Field type='radio' name='category' value={el} className='mr-1.5 py-2' />
<Message type='info'> {el}
<h3 className='font-bold text-xl'> ?</h3> </label>
<p> .</p> </div>
<p className='list-disc list-item list-inside'> 1393 | 1388</p> )
</Message> }
</> : '' <div className='mt-1 text-red-500 text-xs font-light'>{errors.category && touched.category ? errors.category as string: null}</div>
} {
{ values.category && <>
!['오픈소스 라이선스, 저작권 위반 등 권리 침해'].includes(values.category) && <> {
<h3 className='font-bold mt-2'></h3> values.category === '오픈소스 라이선스, 저작권 위반 등 권리 침해' ? <DMCA values={values} errors={errors} touched={touched} setFieldValue={setFieldValue} /> :
<p className='text-gray-400 text-sm mb-1'> .</p> values.category === '괴롭힘, 모욕, 명예훼손' ? <>
<TextField values={values} errors={errors} touched={touched} setFieldValue={setFieldValue} /> <Message type='info'>
</> <h3 className='font-bold text-xl'> ?</h3>
} <p> .</p>
</> <p className='list-disc list-item list-inside'> 1393 | 1388</p>
} </Message>
</div> </> : ''
</Form> }
) {
} !['오픈소스 라이선스, 저작권 위반 등 권리 침해'].includes(values.category) && <>
</Formik> <h3 className='font-bold mt-2'></h3>
} <p className='text-gray-400 text-sm mb-1'> .</p>
</Container> <TextField values={values} errors={errors} touched={touched} setFieldValue={setFieldValue} />
</>
}
</>
}
</div>
</Form>
)
}
</Formik>
}
</Container>
)
} }
export const getServerSideProps = async (ctx: Context) => { export const getServerSideProps = async (ctx: Context) => {

View File

@ -122,7 +122,7 @@ async function getServer(id: string, topLevel=true): Promise<Server> {
res[0].category = JSON.parse(res[0].category) res[0].category = JSON.parse(res[0].category)
res[0].boostTier = data?.boostTier ?? null res[0].boostTier = data?.boostTier ?? null
if(topLevel) { if(topLevel) {
res[0].owner = await get._rawUser.load(data?.owner || '') || null res[0].owner = (await get._rawUser.load(data?.owner || '')) || null
res[0].bots = (await Promise.all(data?.bots.slice(0, 3).map(el => get._rawBot.load(el)) || [])).filter(el => el) || null res[0].bots = (await Promise.all(data?.bots.slice(0, 3).map(el => get._rawBot.load(el)) || [])).filter(el => el) || null
} else { } else {
res[0].owner = data?.owner || null res[0].owner = data?.owner || null

364
yarn.lock
View File

@ -1703,7 +1703,7 @@
resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310" resolved "https://registry.yarnpkg.com/@babel/regjsgen/-/regjsgen-0.8.0.tgz#f0ba69b075e1f05fb2825b7fad991e7adbb18310"
integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA== integrity sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==
"@babel/runtime@^7.0.0", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.5", "@babel/runtime@^7.11.1", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.17.9", "@babel/runtime@^7.18.3", "@babel/runtime@^7.2.0", "@babel/runtime@^7.20.7", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7": "@babel/runtime@^7.0.0", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.5", "@babel/runtime@^7.11.1", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.0", "@babel/runtime@^7.12.5", "@babel/runtime@^7.17.9", "@babel/runtime@^7.18.3", "@babel/runtime@^7.20.7", "@babel/runtime@^7.5.5", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7":
version "7.22.5" version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.5.tgz#8564dd588182ce0047d55d7a75e93921107b57ec" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.5.tgz#8564dd588182ce0047d55d7a75e93921107b57ec"
integrity sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA== integrity sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==
@ -2098,6 +2098,45 @@
tslib "^2.6.1" tslib "^2.6.1"
ws "^8.13.0" ws "^8.13.0"
"@dnd-kit/accessibility@^3.0.0":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@dnd-kit/accessibility/-/accessibility-3.0.1.tgz#3ccbefdfca595b0a23a5dc57d3de96bc6935641c"
integrity sha512-HXRrwS9YUYQO9lFRc/49uO/VICbM+O+ZRpFDe9Pd1rwVv2PCNkRiTZRdxrDgng/UkvdC3Re9r2vwPpXXrWeFzg==
dependencies:
tslib "^2.0.0"
"@dnd-kit/core@^6.0.8":
version "6.0.8"
resolved "https://registry.yarnpkg.com/@dnd-kit/core/-/core-6.0.8.tgz#040ae13fea9787ee078e5f0361f3b49b07f3f005"
integrity sha512-lYaoP8yHTQSLlZe6Rr9qogouGUz9oRUj4AHhDQGQzq/hqaJRpFo65X+JKsdHf8oUFBzx5A+SJPUvxAwTF2OabA==
dependencies:
"@dnd-kit/accessibility" "^3.0.0"
"@dnd-kit/utilities" "^3.2.1"
tslib "^2.0.0"
"@dnd-kit/modifiers@^6.0.1":
version "6.0.1"
resolved "https://registry.yarnpkg.com/@dnd-kit/modifiers/-/modifiers-6.0.1.tgz#9e39b25fd6e323659604cc74488fe044d33188c8"
integrity sha512-rbxcsg3HhzlcMHVHWDuh9LCjpOVAgqbV78wLGI8tziXY3+qcMQ61qVXIvNKQFuhj75dSfD+o+PYZQ/NUk2A23A==
dependencies:
"@dnd-kit/utilities" "^3.2.1"
tslib "^2.0.0"
"@dnd-kit/sortable@^7.0.2":
version "7.0.2"
resolved "https://registry.yarnpkg.com/@dnd-kit/sortable/-/sortable-7.0.2.tgz#791d550872457f3f3c843e00d159b640f982011c"
integrity sha512-wDkBHHf9iCi1veM834Gbk1429bd4lHX4RpAwT0y2cHLf246GAvU2sVw/oxWNpPKQNQRQaeGXhAVgrOl1IT+iyA==
dependencies:
"@dnd-kit/utilities" "^3.2.0"
tslib "^2.0.0"
"@dnd-kit/utilities@^3.2.0", "@dnd-kit/utilities@^3.2.1":
version "3.2.1"
resolved "https://registry.yarnpkg.com/@dnd-kit/utilities/-/utilities-3.2.1.tgz#53f9e2016fd2506ec49e404c289392cfff30332a"
integrity sha512-OOXqISfvBw/1REtkSK2N3Fi2EQiLMlWUlqnOK/UpOISqBZPWpE6TqL+jcPtMOkE8TqYGiURvRdPSI9hltNUjEA==
dependencies:
tslib "^2.0.0"
"@emotion/babel-plugin@^11.11.0": "@emotion/babel-plugin@^11.11.0":
version "11.11.0" version "11.11.0"
resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz#c2d872b6a7767a9d176d007f5b31f7d504bb5d6c" resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz#c2d872b6a7767a9d176d007f5b31f7d504bb5d6c"
@ -2136,7 +2175,7 @@
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.1.tgz#c1ddb040429c6d21d38cc945fe75c818cfb68e17" resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.8.1.tgz#c1ddb040429c6d21d38cc945fe75c818cfb68e17"
integrity sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA== integrity sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==
"@emotion/react@^11.1.1": "@emotion/react@^11.8.1":
version "11.11.1" version "11.11.1"
resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.11.1.tgz#b2c36afac95b184f73b08da8c214fdf861fa4157" resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.11.1.tgz#b2c36afac95b184f73b08da8c214fdf861fa4157"
integrity sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA== integrity sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA==
@ -2150,7 +2189,7 @@
"@emotion/weak-memoize" "^0.3.1" "@emotion/weak-memoize" "^0.3.1"
hoist-non-react-statics "^3.3.1" hoist-non-react-statics "^3.3.1"
"@emotion/serialize@^1.0.0", "@emotion/serialize@^1.1.2": "@emotion/serialize@^1.1.2":
version "1.1.2" version "1.1.2"
resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.1.2.tgz#017a6e4c9b8a803bd576ff3d52a0ea6fa5a62b51" resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.1.2.tgz#017a6e4c9b8a803bd576ff3d52a0ea6fa5a62b51"
integrity sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA== integrity sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA==
@ -2218,6 +2257,26 @@
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.44.0.tgz#961a5903c74139390478bdc808bcde3fc45ab7af" resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.44.0.tgz#961a5903c74139390478bdc808bcde3fc45ab7af"
integrity sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw== integrity sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw==
"@floating-ui/core@^1.4.2":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@floating-ui/core/-/core-1.5.0.tgz#5c05c60d5ae2d05101c3021c1a2a350ddc027f8c"
integrity sha512-kK1h4m36DQ0UHGj5Ah4db7R0rHemTqqO0QLvUqi1/mUUp3LuAWbWxdxSIf/XsnH9VS6rRVPLJCncjRzUvyCLXg==
dependencies:
"@floating-ui/utils" "^0.1.3"
"@floating-ui/dom@^1.0.1":
version "1.5.3"
resolved "https://registry.yarnpkg.com/@floating-ui/dom/-/dom-1.5.3.tgz#54e50efcb432c06c23cd33de2b575102005436fa"
integrity sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==
dependencies:
"@floating-ui/core" "^1.4.2"
"@floating-ui/utils" "^0.1.3"
"@floating-ui/utils@^0.1.3":
version "0.1.4"
resolved "https://registry.yarnpkg.com/@floating-ui/utils/-/utils-0.1.4.tgz#19654d1026cc410975d46445180e70a5089b3e7d"
integrity sha512-qprfWkn82Iw821mcKofJ5Pk9wgioHicxcQMxx+5zt5GSKoqdWvgG5AxVmpmUUjzTLPVSH5auBrhI93Deayn/DA==
"@fortawesome/fontawesome-free@5.15.4": "@fortawesome/fontawesome-free@5.15.4":
version "5.15.4" version "5.15.4"
resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-5.15.4.tgz#ecda5712b61ac852c760d8b3c79c96adca5554e5" resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-free/-/fontawesome-free-5.15.4.tgz#ecda5712b61ac852c760d8b3c79c96adca5554e5"
@ -2514,10 +2573,10 @@
"@jridgewell/resolve-uri" "3.1.0" "@jridgewell/resolve-uri" "3.1.0"
"@jridgewell/sourcemap-codec" "1.4.14" "@jridgewell/sourcemap-codec" "1.4.14"
"@next/env@12.3.4": "@next/env@13.5.2":
version "12.3.4" version "13.5.2"
resolved "https://registry.yarnpkg.com/@next/env/-/env-12.3.4.tgz#c787837d36fcad75d72ff8df6b57482027d64a47" resolved "https://registry.yarnpkg.com/@next/env/-/env-13.5.2.tgz#1c09e6cf1df8b1edf3cf0ca9c0e0119a49802a5d"
integrity sha512-H/69Lc5Q02dq3o+dxxy5O/oNxFsZpdL6WREtOOtOM1B/weonIwDXkekr1KV5DPVPr12IHFPrMrcJQ6bgPMfn7A== integrity sha512-dUseBIQVax+XtdJPzhwww4GetTjlkRSsXeQnisIJWBaHsnxYcN2RGzsPHi58D6qnkATjnhuAtQTJmR1hKYQQPg==
"@next/eslint-plugin-next@13.4.7": "@next/eslint-plugin-next@13.4.7":
version "13.4.7" version "13.4.7"
@ -2526,70 +2585,50 @@
dependencies: dependencies:
glob "7.1.7" glob "7.1.7"
"@next/swc-android-arm-eabi@12.3.4": "@next/swc-darwin-arm64@13.5.2":
version "12.3.4" version "13.5.2"
resolved "https://registry.yarnpkg.com/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-12.3.4.tgz#fd1c2dafe92066c6120761c6a39d19e666dc5dd0" resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.5.2.tgz#f099a36fdd06b1949eb4e190aee95a52b97d3885"
integrity sha512-cM42Cw6V4Bz/2+j/xIzO8nK/Q3Ly+VSlZJTa1vHzsocJRYz8KT6MrreXaci2++SIZCF1rVRCDgAg5PpqRibdIA== integrity sha512-7eAyunAWq6yFwdSQliWMmGhObPpHTesiKxMw4DWVxhm5yLotBj8FCR4PXGkpRP2tf8QhaWuVba+/fyAYggqfQg==
"@next/swc-android-arm64@12.3.4": "@next/swc-darwin-x64@13.5.2":
version "12.3.4" version "13.5.2"
resolved "https://registry.yarnpkg.com/@next/swc-android-arm64/-/swc-android-arm64-12.3.4.tgz#11a146dae7b8bca007239b21c616e83f77b19ed4" resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-13.5.2.tgz#b8950fbe150db6f82961619e31fc6e9232fce8f4"
integrity sha512-5jf0dTBjL+rabWjGj3eghpLUxCukRhBcEJgwLedewEA/LJk2HyqCvGIwj5rH+iwmq1llCWbOky2dO3pVljrapg== integrity sha512-WxXYWE7zF1ch8rrNh5xbIWzhMVas6Vbw+9BCSyZvu7gZC5EEiyZNJsafsC89qlaSA7BnmsDXVWQmc+s1feSYbQ==
"@next/swc-darwin-arm64@12.3.4": "@next/swc-linux-arm64-gnu@13.5.2":
version "12.3.4" version "13.5.2"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-12.3.4.tgz#14ac8357010c95e67327f47082af9c9d75d5be79" resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.5.2.tgz#8134d31fa9ad6848561b6969d27a8c07ab090974"
integrity sha512-DqsSTd3FRjQUR6ao0E1e2OlOcrF5br+uegcEGPVonKYJpcr0MJrtYmPxd4v5T6UCJZ+XzydF7eQo5wdGvSZAyA== integrity sha512-URSwhRYrbj/4MSBjLlefPTK3/tvg95TTm6mRaiZWBB6Za3hpHKi8vSdnCMw5D2aP6k0sQQIEG6Pzcfwm+C5vrg==
"@next/swc-darwin-x64@12.3.4": "@next/swc-linux-arm64-musl@13.5.2":
version "12.3.4" version "13.5.2"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-12.3.4.tgz#e7dc63cd2ac26d15fb84d4d2997207fb9ba7da0f" resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.5.2.tgz#56233fe5140ed437c638194f0a01a3f89821ca89"
integrity sha512-PPF7tbWD4k0dJ2EcUSnOsaOJ5rhT3rlEt/3LhZUGiYNL8KvoqczFrETlUx0cUYaXe11dRA3F80Hpt727QIwByQ== integrity sha512-HefiwAdIygFyNmyVsQeiJp+j8vPKpIRYDlmTlF9/tLdcd3qEL/UEBswa1M7cvO8nHcr27ZTKXz5m7dkd56/Esg==
"@next/swc-freebsd-x64@12.3.4": "@next/swc-linux-x64-gnu@13.5.2":
version "12.3.4" version "13.5.2"
resolved "https://registry.yarnpkg.com/@next/swc-freebsd-x64/-/swc-freebsd-x64-12.3.4.tgz#fe7ceec58746fdf03f1fcb37ec1331c28e76af93" resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.5.2.tgz#1947a9dc603e6d5d5a8e99db7d42e2240c78e713"
integrity sha512-KM9JXRXi/U2PUM928z7l4tnfQ9u8bTco/jb939pdFUHqc28V43Ohd31MmZD1QzEK4aFlMRaIBQOWQZh4D/E5lQ== integrity sha512-htGVVroW0tdHgMYwKWkxWvVoG2RlAdDXRO1RQxYDvOBQsaV0nZsgKkw0EJJJ3urTYnwKskn/MXm305cOgRxD2w==
"@next/swc-linux-arm-gnueabihf@12.3.4": "@next/swc-linux-x64-musl@13.5.2":
version "12.3.4" version "13.5.2"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-12.3.4.tgz#d7016934d02bfc8bd69818ffb0ae364b77b17af7" resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.5.2.tgz#83eea3985eed84fbbbb1004a555d2f093d4ed245"
integrity sha512-3zqD3pO+z5CZyxtKDTnOJ2XgFFRUBciOox6EWkoZvJfc9zcidNAQxuwonUeNts6Xbm8Wtm5YGIRC0x+12YH7kw== integrity sha512-UBD333GxbHVGi7VDJPPDD1bKnx30gn2clifNJbla7vo5nmBV+x5adyARg05RiT9amIpda6yzAEEUu+s774ldkw==
"@next/swc-linux-arm64-gnu@12.3.4": "@next/swc-win32-arm64-msvc@13.5.2":
version "12.3.4" version "13.5.2"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-12.3.4.tgz#43a7bc409b03487bff5beb99479cacdc7bd29af5" resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.5.2.tgz#c3734235e85458b76ec170dd0d6c13c2fdfac5ed"
integrity sha512-kiX0vgJGMZVv+oo1QuObaYulXNvdH/IINmvdZnVzMO/jic/B8EEIGlZ8Bgvw8LCjH3zNVPO3mGrdMvnEEPEhKA== integrity sha512-Em9ApaSFIQnWXRT3K6iFnr9uBXymixLc65Xw4eNt7glgH0eiXpg+QhjmgI2BFyc7k4ZIjglfukt9saNpEyolWA==
"@next/swc-linux-arm64-musl@12.3.4": "@next/swc-win32-ia32-msvc@13.5.2":
version "12.3.4" version "13.5.2"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-12.3.4.tgz#4d1db6de6dc982b974cd1c52937111e3e4a34bd3" resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.5.2.tgz#cf16184af9be8b8f7750833a441c116b7a76b273"
integrity sha512-EETZPa1juczrKLWk5okoW2hv7D7WvonU+Cf2CgsSoxgsYbUCZ1voOpL4JZTOb6IbKMDo6ja+SbY0vzXZBUMvkQ== integrity sha512-TBACBvvNYU+87X0yklSuAseqdpua8m/P79P0SG1fWUvWDDA14jASIg7kr86AuY5qix47nZLEJ5WWS0L20jAUNw==
"@next/swc-linux-x64-gnu@12.3.4": "@next/swc-win32-x64-msvc@13.5.2":
version "12.3.4" version "13.5.2"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-12.3.4.tgz#c3b414d77bab08b35f7dd8943d5586f0adb15e38" resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.5.2.tgz#cf8db00763d9219567655b90853b7d484f3fcad6"
integrity sha512-4csPbRbfZbuWOk3ATyWcvVFdD9/Rsdq5YHKvRuEni68OCLkfy4f+4I9OBpyK1SKJ00Cih16NJbHE+k+ljPPpag== integrity sha512-LfTHt+hTL8w7F9hnB3H4nRasCzLD/fP+h4/GUVBTxrkMJOnh/7OZ0XbYDKO/uuWwryJS9kZjhxcruBiYwc5UDw==
"@next/swc-linux-x64-musl@12.3.4":
version "12.3.4"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-12.3.4.tgz#187a883ec09eb2442a5ebf126826e19037313c61"
integrity sha512-YeBmI+63Ro75SUiL/QXEVXQ19T++58aI/IINOyhpsRL1LKdyfK/35iilraZEFz9bLQrwy1LYAR5lK200A9Gjbg==
"@next/swc-win32-arm64-msvc@12.3.4":
version "12.3.4"
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-12.3.4.tgz#89befa84e453ed2ef9a888f375eba565a0fde80b"
integrity sha512-Sd0qFUJv8Tj0PukAYbCCDbmXcMkbIuhnTeHm9m4ZGjCf6kt7E/RMs55Pd3R5ePjOkN7dJEuxYBehawTR/aPDSQ==
"@next/swc-win32-ia32-msvc@12.3.4":
version "12.3.4"
resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-12.3.4.tgz#cb50c08f0e40ead63642a7f269f0c8254261f17c"
integrity sha512-rt/vv/vg/ZGGkrkKcuJ0LyliRdbskQU+91bje+PgoYmxTZf/tYs6IfbmgudBJk6gH3QnjHWbkphDdRQrseRefQ==
"@next/swc-win32-x64-msvc@12.3.4":
version "12.3.4"
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-12.3.4.tgz#d28ea15a72cdcf96201c60a43e9630cd7fda168f"
integrity sha512-DQ20JEfTBZAgF8QCjYfJhv2/279M6onxFjdG/+5B0Cyj00/EdBxiWb2eGGFgQhrBbNv/lsvzFbbi0Ptf8Vw/bg==
"@nodelib/fs.scandir@2.1.5": "@nodelib/fs.scandir@2.1.5":
version "2.1.5" version "2.1.5"
@ -2962,10 +3001,10 @@
magic-string "^0.25.0" magic-string "^0.25.0"
string.prototype.matchall "^4.0.6" string.prototype.matchall "^4.0.6"
"@swc/helpers@0.4.11": "@swc/helpers@0.5.2":
version "0.4.11" version "0.5.2"
resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.4.11.tgz#db23a376761b3d31c26502122f349a21b592c8de" resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.2.tgz#85ea0c76450b61ad7d10a37050289eded783c27d"
integrity sha512-rEUrBSGIoSFuYxwBYtlUFMlE2CwGhmW+w9355/5oduSw8e5h2+Tj4UrAGNNgP9915++wj5vkQo0UuOBqOAq4nw== integrity sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==
dependencies: dependencies:
tslib "^2.4.0" tslib "^2.4.0"
@ -3217,24 +3256,7 @@
dependencies: dependencies:
"@types/react" "*" "@types/react" "*"
"@types/react-dom@*": "@types/react-transition-group@^4.4.0":
version "18.2.5"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.5.tgz#5c5f13548bda23cd98f50ca4a59107238bfe18f3"
integrity sha512-sRQsOS/sCLnpQhR4DSKGTtWFE3FZjpQa86KPVbhUqdYMRZ9FEFcfAytKhR/vUG2rH1oFbOOej6cuD7MFSobDRQ==
dependencies:
"@types/react" "*"
"@types/react-select@^4.0.17":
version "4.0.18"
resolved "https://registry.yarnpkg.com/@types/react-select/-/react-select-4.0.18.tgz#f907f406411afa862217a9d86c54a301367a35c1"
integrity sha512-uCPRMPshd96BwHuT7oCrFduiv5d6km3VwmtW7rVl9g4XetS3VoJ9nZo540LiwtQgaFcW96POwaxQDZDAyYaepg==
dependencies:
"@emotion/serialize" "^1.0.0"
"@types/react" "*"
"@types/react-dom" "*"
"@types/react-transition-group" "*"
"@types/react-transition-group@*":
version "4.4.6" version "4.4.6"
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.6.tgz#18187bcda5281f8e10dfc48f0943e2fdf4f75e2e" resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.6.tgz#18187bcda5281f8e10dfc48f0943e2fdf4f75e2e"
integrity sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew== integrity sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew==
@ -3250,6 +3272,15 @@
"@types/scheduler" "*" "@types/scheduler" "*"
csstype "^3.0.2" csstype "^3.0.2"
"@types/react@^18.2.22":
version "18.2.22"
resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.22.tgz#abe778a1c95a07fa70df40a52d7300a40b949ccb"
integrity sha512-60fLTOLqzarLED2O3UQImc/lsNRgG0jE/a1mPW9KjMemY0LMITWEsbS4VvZ4p6rorEHd5YKxxmMKSDK505GHpA==
dependencies:
"@types/prop-types" "*"
"@types/scheduler" "*"
csstype "^3.0.2"
"@types/resolve@1.17.1": "@types/resolve@1.17.1":
version "1.17.1" version "1.17.1"
resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6" resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6"
@ -3977,7 +4008,7 @@ bundle-name@^3.0.0:
dependencies: dependencies:
run-applescript "^5.0.0" run-applescript "^5.0.0"
busboy@^1.6.0: busboy@1.6.0, busboy@^1.6.0:
version "1.6.0" version "1.6.0"
resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893" resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893"
integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA== integrity sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==
@ -4134,6 +4165,11 @@ cli-width@^3.0.0:
resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6" resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-3.0.0.tgz#a2f48437a2caa9a22436e794bf071ec9e61cedf6"
integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw== integrity sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==
client-only@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/client-only/-/client-only-0.0.1.tgz#38bba5d403c41ab150bff64a95c85013cf73bca1"
integrity sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==
cliui@^5.0.0: cliui@^5.0.0:
version "5.0.0" version "5.0.0"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5" resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5"
@ -5554,6 +5590,11 @@ glob-parent@^6.0.2:
dependencies: dependencies:
is-glob "^4.0.3" is-glob "^4.0.3"
glob-to-regexp@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"
integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
glob@7.1.6: glob@7.1.6:
version "7.1.6" version "7.1.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
@ -5940,13 +5981,6 @@ interpret@^2.2.0:
resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9"
integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw== integrity sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==
invariant@^2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==
dependencies:
loose-envify "^1.0.0"
ip-regex@^4.3.0: ip-regex@^4.3.0:
version "4.3.0" version "4.3.0"
resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-4.3.0.tgz#687275ab0f57fa76978ff8f4dddc8a23d5990db5" resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-4.3.0.tgz#687275ab0f57fa76978ff8f4dddc8a23d5990db5"
@ -7000,7 +7034,7 @@ long@^5.0.0:
resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1" resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1"
integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q== integrity sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==
loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: loose-envify@^1.1.0, loose-envify@^1.4.0:
version "1.4.0" version "1.4.0"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
@ -7095,10 +7129,10 @@ makeerror@1.0.12:
dependencies: dependencies:
tmpl "1.0.5" tmpl "1.0.5"
memoize-one@^5.0.0: memoize-one@^6.0.0:
version "5.2.1" version "6.0.0"
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e" resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-6.0.0.tgz#b2591b871ed82948aee4727dc6abceeeac8c1045"
integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q== integrity sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==
memory-pager@^1.0.2: memory-pager@^1.0.2:
version "1.5.0" version "1.5.0"
@ -7405,31 +7439,29 @@ next-seo@^6.1.0:
resolved "https://registry.yarnpkg.com/next-seo/-/next-seo-6.1.0.tgz#b60b06958cc77e7ed56f0a61b2d6cd0afed88ebb" resolved "https://registry.yarnpkg.com/next-seo/-/next-seo-6.1.0.tgz#b60b06958cc77e7ed56f0a61b2d6cd0afed88ebb"
integrity sha512-iMBpFoJsR5zWhguHJvsoBDxDSmdYTHtnVPB1ij+CD0NReQCP78ZxxbdL9qkKIf4oEuZEqZkrjAQLB0bkII7RYA== integrity sha512-iMBpFoJsR5zWhguHJvsoBDxDSmdYTHtnVPB1ij+CD0NReQCP78ZxxbdL9qkKIf4oEuZEqZkrjAQLB0bkII7RYA==
next@^12.3.2: next@^13.5.2:
version "12.3.4" version "13.5.2"
resolved "https://registry.yarnpkg.com/next/-/next-12.3.4.tgz#f2780a6ebbf367e071ce67e24bd8a6e05de2fcb1" resolved "https://registry.yarnpkg.com/next/-/next-13.5.2.tgz#809dd84e481049e298fe79d28b1d66b587483fca"
integrity sha512-VcyMJUtLZBGzLKo3oMxrEF0stxh8HwuW976pAzlHhI3t8qJ4SROjCrSh1T24bhrbjw55wfZXAbXPGwPt5FLRfQ== integrity sha512-vog4UhUaMYAzeqfiAAmgB/QWLW7p01/sg+2vn6bqc/CxHFYizMzLv6gjxKzl31EVFkfl/F+GbxlKizlkTE9RdA==
dependencies: dependencies:
"@next/env" "12.3.4" "@next/env" "13.5.2"
"@swc/helpers" "0.4.11" "@swc/helpers" "0.5.2"
busboy "1.6.0"
caniuse-lite "^1.0.30001406" caniuse-lite "^1.0.30001406"
postcss "8.4.14" postcss "8.4.14"
styled-jsx "5.0.7" styled-jsx "5.1.1"
use-sync-external-store "1.2.0" watchpack "2.4.0"
zod "3.21.4"
optionalDependencies: optionalDependencies:
"@next/swc-android-arm-eabi" "12.3.4" "@next/swc-darwin-arm64" "13.5.2"
"@next/swc-android-arm64" "12.3.4" "@next/swc-darwin-x64" "13.5.2"
"@next/swc-darwin-arm64" "12.3.4" "@next/swc-linux-arm64-gnu" "13.5.2"
"@next/swc-darwin-x64" "12.3.4" "@next/swc-linux-arm64-musl" "13.5.2"
"@next/swc-freebsd-x64" "12.3.4" "@next/swc-linux-x64-gnu" "13.5.2"
"@next/swc-linux-arm-gnueabihf" "12.3.4" "@next/swc-linux-x64-musl" "13.5.2"
"@next/swc-linux-arm64-gnu" "12.3.4" "@next/swc-win32-arm64-msvc" "13.5.2"
"@next/swc-linux-arm64-musl" "12.3.4" "@next/swc-win32-ia32-msvc" "13.5.2"
"@next/swc-linux-x64-gnu" "12.3.4" "@next/swc-win32-x64-msvc" "13.5.2"
"@next/swc-linux-x64-musl" "12.3.4"
"@next/swc-win32-arm64-msvc" "12.3.4"
"@next/swc-win32-ia32-msvc" "12.3.4"
"@next/swc-win32-x64-msvc" "12.3.4"
node-abort-controller@^3.0.1: node-abort-controller@^3.0.1:
version "3.1.1" version "3.1.1"
@ -8268,7 +8300,7 @@ prompts@^2.0.1:
kleur "^3.0.3" kleur "^3.0.3"
sisteransi "^1.0.5" sisteransi "^1.0.5"
prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.8.1: prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.8.1:
version "15.8.1" version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@ -8393,14 +8425,13 @@ react-adsense@0.1.0:
resolved "https://registry.yarnpkg.com/react-adsense/-/react-adsense-0.1.0.tgz#f170007a9ab05ee2cd1c915cdc9788624658e3c1" resolved "https://registry.yarnpkg.com/react-adsense/-/react-adsense-0.1.0.tgz#f170007a9ab05ee2cd1c915cdc9788624658e3c1"
integrity sha512-VzGMH9qA7bBKAveK1joFWphhB7e2Y1ToSGzUa8b7eit3+VSkg/2MSvKxe5d/r4mcmkOHu6CHNJW0nn6RQC3IZA== integrity sha512-VzGMH9qA7bBKAveK1joFWphhB7e2Y1ToSGzUa8b7eit3+VSkg/2MSvKxe5d/r4mcmkOHu6CHNJW0nn6RQC3IZA==
react-dom@17.0.2: react-dom@^18.2.0:
version "17.0.2" version "18.2.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23" resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA== integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==
dependencies: dependencies:
loose-envify "^1.1.0" loose-envify "^1.1.0"
object-assign "^4.1.1" scheduler "^0.23.0"
scheduler "^0.20.2"
react-fast-compare@^2.0.1: react-fast-compare@^2.0.1:
version "2.0.4" version "2.0.4"
@ -8419,13 +8450,6 @@ react-hotkeys@2.0.0:
dependencies: dependencies:
prop-types "^15.6.1" prop-types "^15.6.1"
react-input-autosize@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/react-input-autosize/-/react-input-autosize-3.0.0.tgz#6b5898c790d4478d69420b55441fcc31d5c50a85"
integrity sha512-nL9uS7jEs/zu8sqwFE5MAPx6pPkNAriACQ2rGLlqmKr2sPGtN7TXTyDdQt4lbNXVx7Uzadb40x8qotIuru6Rhg==
dependencies:
prop-types "^15.5.8"
react-is@^16.12.0, react-is@^16.13.1, react-is@^16.7.0: react-is@^16.12.0, react-is@^16.13.1, react-is@^16.7.0:
version "16.13.1" version "16.13.1"
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
@ -8445,18 +8469,20 @@ react-responsive-modal@6.4.2:
body-scroll-lock "^3.1.5" body-scroll-lock "^3.1.5"
classnames "^2.3.1" classnames "^2.3.1"
react-select@4.3.1: react-select@^5.7.5:
version "4.3.1" version "5.7.5"
resolved "https://registry.yarnpkg.com/react-select/-/react-select-4.3.1.tgz#389fc07c9bc7cf7d3c377b7a05ea18cd7399cb81" resolved "https://registry.yarnpkg.com/react-select/-/react-select-5.7.5.tgz#d2d0f29994e0f06000147bfb2adf58324926c2fd"
integrity sha512-HBBd0dYwkF5aZk1zP81Wx5UsLIIT2lSvAY2JiJo199LjoLHoivjn9//KsmvQMEFGNhe58xyuOITjfxKCcGc62Q== integrity sha512-jgYZa2xgKP0DVn5GZk7tZwbRx7kaVz1VqU41S8z1KWmshRDhlrpKS0w80aS1RaK5bVIXpttgSou7XCjWw1ncKA==
dependencies: dependencies:
"@babel/runtime" "^7.12.0" "@babel/runtime" "^7.12.0"
"@emotion/cache" "^11.4.0" "@emotion/cache" "^11.4.0"
"@emotion/react" "^11.1.1" "@emotion/react" "^11.8.1"
memoize-one "^5.0.0" "@floating-ui/dom" "^1.0.1"
"@types/react-transition-group" "^4.4.0"
memoize-one "^6.0.0"
prop-types "^15.6.0" prop-types "^15.6.0"
react-input-autosize "^3.0.0"
react-transition-group "^4.3.0" react-transition-group "^4.3.0"
use-isomorphic-layout-effect "^1.1.2"
react-showdown@2.3.1: react-showdown@2.3.1:
version "2.3.1" version "2.3.1"
@ -8467,15 +8493,6 @@ react-showdown@2.3.1:
htmlparser2 "^6.0.1" htmlparser2 "^6.0.1"
showdown "^1.9.1" showdown "^1.9.1"
react-sortable-hoc@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/react-sortable-hoc/-/react-sortable-hoc-2.0.0.tgz#f6780d8aa4b922a21f3e754af542f032677078b7"
integrity sha512-JZUw7hBsAHXK7PTyErJyI7SopSBFRcFHDjWW5SWjcugY0i6iH7f+eJkY8cJmGMlZ1C9xz1J3Vjz0plFpavVeRg==
dependencies:
"@babel/runtime" "^7.2.0"
invariant "^2.2.4"
prop-types "^15.5.7"
react-transition-group@^4.3.0: react-transition-group@^4.3.0:
version "4.4.5" version "4.4.5"
resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.5.tgz#e53d4e3f3344da8521489fbef8f2581d42becdd1"
@ -8493,13 +8510,12 @@ react-use-clipboard@1.0.9:
dependencies: dependencies:
copy-to-clipboard "^3.3.1" copy-to-clipboard "^3.3.1"
react@17.0.2: react@^18.2.0:
version "17.0.2" version "18.2.0"
resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037" resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA== integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
dependencies: dependencies:
loose-envify "^1.1.0" loose-envify "^1.1.0"
object-assign "^4.1.1"
read-cache@^1.0.0: read-cache@^1.0.0:
version "1.0.0" version "1.0.0"
@ -8835,13 +8851,12 @@ saslprep@^1.0.3:
dependencies: dependencies:
sparse-bitfield "^3.0.3" sparse-bitfield "^3.0.3"
scheduler@^0.20.2: scheduler@^0.23.0:
version "0.20.2" version "0.23.0"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91" resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe"
integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ== integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==
dependencies: dependencies:
loose-envify "^1.1.0" loose-envify "^1.1.0"
object-assign "^4.1.1"
schema-utils@^2.6.5: schema-utils@^2.6.5:
version "2.7.1" version "2.7.1"
@ -9296,10 +9311,12 @@ strnum@^1.0.5:
resolved "https://registry.yarnpkg.com/strnum/-/strnum-1.0.5.tgz#5c4e829fe15ad4ff0d20c3db5ac97b73c9b072db" resolved "https://registry.yarnpkg.com/strnum/-/strnum-1.0.5.tgz#5c4e829fe15ad4ff0d20c3db5ac97b73c9b072db"
integrity sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA== integrity sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==
styled-jsx@5.0.7: styled-jsx@5.1.1:
version "5.0.7" version "5.1.1"
resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.0.7.tgz#be44afc53771b983769ac654d355ca8d019dff48" resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.1.1.tgz#839a1c3aaacc4e735fed0781b8619ea5d0009d1f"
integrity sha512-b3sUzamS086YLRuvnaDigdAewz1/EFYlHpYBP5mZovKEdQQOIIYq8lApylub3HHZ6xFjV051kkGU7cudJmrXEA== integrity sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==
dependencies:
client-only "0.0.1"
stylis@4.2.0: stylis@4.2.0:
version "4.2.0" version "4.2.0"
@ -9811,10 +9828,10 @@ url-regex-safe@2.0.2:
re2 "^1.15.9" re2 "^1.15.9"
tlds "^1.217.0" tlds "^1.217.0"
use-sync-external-store@1.2.0: use-isomorphic-layout-effect@^1.1.2:
version "1.2.0" version "1.1.2"
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a" resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz#497cefb13d863d687b08477d9e5a164ad8c1a6fb"
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA== integrity sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==
util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1:
version "1.0.2" version "1.0.2"
@ -9855,6 +9872,14 @@ walker@^1.0.8:
dependencies: dependencies:
makeerror "1.0.12" makeerror "1.0.12"
watchpack@2.4.0:
version "2.4.0"
resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d"
integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==
dependencies:
glob-to-regexp "^0.4.1"
graceful-fs "^4.1.2"
wcwidth@^1.0.1: wcwidth@^1.0.1:
version "1.0.1" version "1.0.1"
resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8"
@ -10269,3 +10294,8 @@ yup@0.32.9:
nanoclone "^0.2.1" nanoclone "^0.2.1"
property-expr "^2.0.4" property-expr "^2.0.4"
toposort "^2.0.2" toposort "^2.0.2"
zod@3.21.4:
version "3.21.4"
resolved "https://registry.yarnpkg.com/zod/-/zod-3.21.4.tgz#10882231d992519f0a10b5dd58a38c9dabbb64db"
integrity sha512-m46AKbrzKVzOzs/DZgVnG5H55N1sv1M8qZU3A8RIKbs3mrACDNeIOeilDymVb2HdmP8uwshOCF4uJ8uM9rCqJw==