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 Application: React.FC<ApplicationProps> = ({ type, id, name }) => {
return <Link href={`/developers/applications/${type + 's'}/${id}`}>
<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' /> :
<ServerIcon id={id} className='px-2 w-full rounded-xl' />
}
<h2 className='pt-2 whitespace-nowrap text-xl font-medium truncate'>{name}</h2>
</div>
</Link>
return (
<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' /> :
<ServerIcon id={id} className='px-2 w-full rounded-xl' />
}
<h2 className='pt-2 whitespace-nowrap text-xl font-medium truncate'>{name}</h2>
</div>
</Link>
)
}

View File

@ -10,107 +10,113 @@ const Tag = dynamic(() => import('@components/Tag'))
const DiscordAvatar = dynamic(() => import('@components/DiscordAvatar'))
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'>
<div className='relative'>
<div className='container mx-auto'>
<div className='h-full'>
<div
className='relative mx-auto h-full text-black dark:text-white dark:bg-discord-black bg-little-white rounded-2xl shadow-xl'
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',
}
: {}
}
>
<Link href={makeBotURL(bot)}>
<div>
<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>
return (
<div className='min-w-0 container mb-16 transform hover:-translate-y-1 transition duration-100 ease-in cursor-pointer'>
<div className='relative'>
<div className='container mx-auto'>
<div className='h-full'>
<div
className='relative mx-auto h-full text-black dark:text-white dark:bg-discord-black bg-little-white rounded-2xl shadow-xl'
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',
}
: {}
}
>
<Link href={makeBotURL(bot)} legacyBehavior>
<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 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 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>
</Link>
<Divider />
<div className='w-full'>
<div className='flex justify-evenly'>
<Link 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'>
</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>
<Divider />
<div className='w-full'>
<div className='flex justify-evenly'>
<Link
href={makeBotURL(bot)}
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>
) : bot.state !== 'ok' ? <a
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'
>
</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'
{manage ? (
<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'>
</Link>
) : bot.state !== 'ok' ? <a
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'
>
</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>
)
}

View File

@ -9,13 +9,13 @@ const Button: React.FC<ButtonProps> = ({
disabled=false,
onClick,
}) => {
return href ? <Link href={!disabled && href}>
<a
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'}`}
>
{children}
</a>
return href ? <Link
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 ??
'bg-discord-blurple hover:opacity-80 dark:bg-very-black dark:hover:bg-discord-dark-hover text-white'}`}>
{children}
</Link>
: onClick ? <button
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 [ navbarEnabled, setNavbarOpen ] = useState(false)
return <div className='flex min-h-screen'>
<NextSeo title='한디리 개발자' description='한국 디스코드 리스트 API를 활용하여 봇에 다양한 기능을 추가해보세요.' openGraph={{
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'>
<ul className='text-gray-600 dark:text-gray-300'>
<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'}`}>
<Link href='/developers/applications'><i className='fas fa-robot'/></Link>
</li>
<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'}`}>
<Link href='/developers/docs'><i className='fas fa-book'/></Link>
</li>
return (
<div className='flex min-h-screen'>
<NextSeo title='한디리 개발자' description='한국 디스코드 리스트 API를 활용하여 봇에 다양한 기능을 추가해보세요.' openGraph={{
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'>
<ul className='text-gray-600 dark:text-gray-300'>
<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'}`}>
<Link href='/developers/applications' legacyBehavior><i className='fas fa-robot'/></Link>
</li>
<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'}`}>
<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' && <>
<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></>
<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}`}
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 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 {

View File

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

View File

@ -1,60 +1,92 @@
import { ComponentType } from 'react'
import ReactSelect, { components, GroupTypeBase, MultiValueProps, OptionTypeBase } from 'react-select'
import React, { MouseEventHandler } from 'react'
import ReactSelect, {
components,
MultiValueProps,
MultiValueRemoveProps,
} from 'react-select'
import { closestCenter, DndContext, DragEndEvent } from '@dnd-kit/core'
import { restrictToParentElement } from '@dnd-kit/modifiers'
import {
SortableContainer,
SortableElement,
SortableHandle,
} from 'react-sortable-hoc'
arrayMove,
horizontalListSortingStrategy,
SortableContext,
useSortable,
} from '@dnd-kit/sortable'
import { CSS } from '@dnd-kit/utilities'
function arrayMove(array, from, to) {
array = array.slice()
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 => {
const MultiValue = (props: MultiValueProps<Option>) => {
const onMouseDown: MouseEventHandler<HTMLDivElement> = (e) => {
e.preventDefault()
e.stopPropagation()
}
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 => (
<components.MultiValueLabel {...props} />
))
return (
<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 onSortEnd = ({ oldIndex, newIndex }) => {
const newValue = arrayMove(values, oldIndex, newIndex)
const onSortEnd = (event: DragEndEvent) => {
const { active, over } = event
const newValue = arrayMove(values, values.findIndex(i => i === active.id), values.findIndex(i => i === over.id))
setValues(newValue)
}
return <SortableSelect useDragHandle axis='xy' distance={4} getHelperDimensions={({ node }) => node.getBoundingClientRect()} onSortEnd={onSortEnd}
// select props
styles={{
control: (provided) => {
return { ...provided, border: 'none' }
},
option: (provided) => {
return { ...provided, cursor: 'pointer', ':hover': {
opacity: '0.7'
} }
}
}} 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: SortableMultiValue as ComponentType<MultiValueProps<OptionTypeBase, GroupTypeBase<{ label: string, value: string}>>>,
MultiValueLabel: SortableMultiValueLabel,
}}
closeMenuOnSelect={false}
/>
return <DndContext modifiers={[restrictToParentElement]} onDragEnd={onSortEnd} collisionDetection={closestCenter}>
<SortableContext
items={values}
strategy={horizontalListSortingStrategy}>
<ReactSelect
styles={{
placeholder: (provided) => {
return { ...provided, position: 'absolute' }
},
control: (provided) => {
return { ...provided, border: 'none' }
},
option: (provided) => {
return { ...provided, cursor: 'pointer', ':hover': {
opacity: '0.7'
} }
}
}}
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 {

View File

@ -22,6 +22,8 @@ const TextArea: React.FC<TextAreaProps> = ({ name, placeholder, theme='auto', ma
<div ref={ref}>
<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) => {
setEmojiPickerHidden(true)
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'
const Login: React.FC = ({ children }) => {
const Login: React.FC<React.PropsWithChildren> = ({ children }) => {
const router = useRouter()
useEffect(() => {
localStorage.redirectTo = window.location.href

View File

@ -9,11 +9,15 @@ const LongButton: React.FC<LongButtonProps> = ({ children, newTab=false, href, o
{children}
</div>
</a>
else return <Link href={href}>
<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`}>
else return (
<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}
</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`}>
{children}

View File

@ -41,241 +41,288 @@ const Navbar: React.FC<NavbarProps> = ({ token }) => {
setUserCache(null)
}
}, [ token ])
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'>
<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'>
<Link 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`}
>
{ dev ? <><i className='fas fa-tools mr-1'/> DEVELOPERS</> : 'KOREANLIST'}
</a>
</Link>
<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'
type='button'
onClick={() => setNavbarOpen(!navbarOpen)}
>
<i className={`fas ${!navbarOpen ? 'fa-bars' : 'fa-times'}`}></i>
</button>
<ul className='hidden lg:flex flex-col list-none lg:flex-row lg:ml-auto'>
<li className='flex items-center'>
<Link href={dev ? '/' : '/developers'}>
<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'>
{dev ? '홈' : '개발자'}
</a>
</Link>
</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>
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'>
<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'>
<Link
href={dev ? '/developers' : '/'}
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'}
</Link>
<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'
type='button'
onClick={() => setNavbarOpen(!navbarOpen)}
>
<i className={`fas ${!navbarOpen ? 'fa-bars' : 'fa-times'}`}></i>
</button>
<ul className='hidden lg:flex flex-col list-none lg:flex-row lg:ml-auto'>
<li className='flex items-center'>
<Link
href={dev ? '/' : '/developers'}
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'>
{dev ? '홈' : '개발자'}
<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 href='/panel'>
<a 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>
</a>
</li>
{
type !== 'bot' && <li className='flex items-center'>
<Link
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>
<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>
}
</li>
<li className='flex items-center'>
<Link
href='/about'
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' 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'
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>
</>
)
</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 {

View File

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

View File

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

View File

@ -115,7 +115,7 @@ const Search: React.FC = () => {
data.data.bots.length === 0 ?
<li className='px-3 py-3.5'> .</li> :
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'>
<DiscordAvatar className='mt-1 w-12 h-12' size={128} userID={el.id} />
<div className='ml-2'>
@ -133,7 +133,7 @@ const Search: React.FC = () => {
data.data.servers.length === 0 ?
<li className='px-3 py-3.5'> .</li> :
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'>
<ServerIcon className='mt-1 w-12 h-12' size={128} id={el.id} />
<div className='ml-2'>
@ -177,7 +177,10 @@ const Search: React.FC = () => {
</li>
{
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'>
<i className='fas fa-history' /> {el?.value}
<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 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'>
<div className='relative'>
<div className='container mx-auto'>
<div className='h-full'>
<div
className='relative mx-auto h-full text-black dark:text-white dark:bg-discord-black bg-little-white rounded-2xl shadow-xl'
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',
}
: {}
}
>
<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
return (
<div className='min-w-0 container mb-16 transform hover:-translate-y-1 transition duration-100 ease-in cursor-pointer'>
<div className='relative'>
<div className='container mx-auto'>
<div className='h-full'>
<div
className='relative mx-auto h-full text-black dark:text-white dark:bg-discord-black bg-little-white rounded-2xl shadow-xl'
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',
}
</p>
: {}
}
>
<Link
href={type !== 'add' ? makeServerURL(server) : newServerLink}
legacyBehavior>
<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 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>
<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>
</Link>
<Divider />
<div className='w-full'>
<div className='flex justify-evenly'>
{
type === 'add' ?
server.data ? <Link 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'>
</a>
</Link> : <Link href={newServerLink}>
<a
</Link>
<Divider />
<div className='w-full'>
<div className='flex justify-evenly'>
{
type === 'add' ?
server.data ? <Link
href={newServerLink}
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'>
</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'
rel='noopener noreferrer'
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>
target='_blank'>
</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'>
</a>
:
<>
<Link
href={makeServerURL(server)}
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>
) : !['ok', 'unreachable'].includes(server.state) ? <a
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'
>
</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'
{type === 'manage' ? (
<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'>
</Link>
) : !['ok', 'unreachable'].includes(server.state) ? <a
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'
>
</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>
)
}

View File

@ -7,36 +7,38 @@ import Link from 'next/link'
const SubmittedBotCard: React.FC<SubmittedBotProps> = ({ href, submit }) => {
return (
<Link href={href}>
<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'>
<div className='h-18'>
<div className='flex'>
<div className='grow w-full'>
<h2 className='text-lg'>{submit.id}</h2>
</div>
<div className='absolute right-0 grid grid-cols-1 px-4 w-2/5 h-0'>
<Tag
text={
<>
<i
className={`fas fa-circle text-${
[Status.offline, Status.online, Status.dnd][submit.state]?.color
}`}
/>{' '}
{['대기중', '승인됨', '거부됨'][submit.state]}
</>
}
dark
/>
</div>
(<Link
href={href}
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='h-18'>
<div className='flex'>
<div className='grow w-full'>
<h2 className='text-lg'>{submit.id}</h2>
</div>
<div className='absolute right-0 grid grid-cols-1 px-4 w-2/5 h-0'>
<Tag
text={
<>
<i
className={`fas fa-circle text-${
[Status.offline, Status.online, Status.dnd][submit.state]?.color
}`}
/>{' '}
{['대기중', '승인됨', '거부됨'][submit.state]}
</>
}
dark
/>
</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>
</a>
</Link>
<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>
</Link>)
)
}

View File

@ -37,27 +37,27 @@ const Tag: React.FC<LabelProps> = ({
{text}
</a>
) : (
<Link href={href}>
<a
className={`${className ?? ''} text-center text-base ${
dark
? blurple
? 'bg-discord-blurple text-white'
: 'bg-little-white-hover hover:bg-little-white dark:bg-very-black'
: github
? 'bg-gray-900 text-white hover:bg-gray-700'
: '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'
} ${
circular
? `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'}`
} mr-1 mb-${marginBottom} dark:hover:bg-discord-dark-hover transition duration-100 ease-in`}
>
{text}
</a>
</Link>
(<Link
href={href}
className={`${className ?? ''} text-center text-base ${
dark
? blurple
? 'bg-discord-blurple text-white'
: 'bg-little-white-hover hover:bg-little-white dark:bg-very-black'
: github
? 'bg-gray-900 text-white hover:bg-gray-700'
: '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'
} ${
circular
? `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'}`
} mr-1 mb-${marginBottom} dark:hover:bg-discord-dark-hover transition duration-100 ease-in`}>
{text}
</Link>)
)
) : (
<a

View File

@ -8,53 +8,53 @@ const Tooltip: React.FC<TooltipProps> = ({
text,
}) => {
return href ? (
<Link href={href}>
<a className='inline'>
<div className='relative inline py-3'>
<div className='group relative inline-block text-center cursor-pointer'>
{children}
<div
className={`opacity-0 ${
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`}
>
{text}
{direction === 'left' ? (
<svg
className='absolute left-5 top-full mr-3 h-2 text-black'
x='0px'
y='0px'
viewBox='0 0 255 255'
xmlSpace='preserve'
>
<polygon className='fill-current' points='0,0 127.5,127.5 255,0' />
</svg>
) : direction === 'center' ? (
<svg
className='absolute left-0 top-full w-full h-2 text-black'
x='0px'
y='0px'
viewBox='0 0 255 255'
xmlSpace='preserve'
>
<polygon className='fill-current' points='0,0 127.5,127.5 255,0' />
</svg>
) : (
<svg
className='absolute right-5 top-full mr-3 h-2 text-black'
x='0px'
y='0px'
viewBox='0 0 255 255'
xmlSpace='preserve'
>
<polygon className='fill-current' points='0,0 127.5,127.5 255,0' />
</svg>
)}
</div>
(<Link href={href} className='inline'>
<div className='relative inline py-3'>
<div className='group relative inline-block text-center cursor-pointer'>
{children}
<div
className={`opacity-0 ${
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`}
>
{text}
{direction === 'left' ? (
<svg
className='absolute left-5 top-full mr-3 h-2 text-black'
x='0px'
y='0px'
viewBox='0 0 255 255'
xmlSpace='preserve'
>
<polygon className='fill-current' points='0,0 127.5,127.5 255,0' />
</svg>
) : direction === 'center' ? (
<svg
className='absolute left-0 top-full w-full h-2 text-black'
x='0px'
y='0px'
viewBox='0 0 255 255'
xmlSpace='preserve'
>
<polygon className='fill-current' points='0,0 127.5,127.5 255,0' />
</svg>
) : (
<svg
className='absolute right-5 top-full mr-3 h-2 text-black'
x='0px'
y='0px'
viewBox='0 0 255 255'
xmlSpace='preserve'
>
<polygon className='fill-current' points='0,0 127.5,127.5 255,0' />
</svg>
)}
</div>
</div>
</a>
</Link>
</div>
</Link>)
) : (
<a className='inline'>
<div className='relative inline py-3'>

View File

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

View File

@ -14,6 +14,10 @@
"docker": "docker-compose up -d --build"
},
"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",
"@hcaptcha/react-hcaptcha": "^1.8.1",
"@sentry/nextjs": "^7.57.0",
@ -37,7 +41,7 @@
"knex": "^2.4.0",
"mongoose": "6.11.3",
"mysql": "2.18.1",
"next": "^12.3.2",
"next": "^13.5.2",
"next-connect": "0.10.1",
"next-pwa": "^5.6.0",
"next-seo": "^6.1.0",
@ -46,15 +50,14 @@
"postcss": "^8.4.24",
"postcss-preset-env": "8.5.1",
"rc-tooltip": "5.1.1",
"react": "17.0.2",
"react": "^18.2.0",
"react-adsense": "0.1.0",
"react-dom": "17.0.2",
"react-dom": "^18.2.0",
"react-ga": "^3.3.1",
"react-hotkeys": "2.0.0",
"react-responsive-modal": "6.4.2",
"react-select": "4.3.1",
"react-select": "^5.7.5",
"react-showdown": "2.3.1",
"react-sortable-hoc": "2.0.0",
"react-use-clipboard": "1.0.9",
"sanitize-html": "^2.11.0",
"tailwindcss": "^3.3.2",
@ -75,8 +78,7 @@
"@types/node-fetch": "^2.5.12",
"@types/nprogress": "0.2.0",
"@types/rc-tooltip": "^3.7.4",
"@types/react": "^17.0.15",
"@types/react-select": "^4.0.17",
"@types/react": "^18.2.22",
"@types/sanitize-html": "^2.8.0",
"@types/url-regex-safe": "1.0.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 MyError: NextPage = () => {
return <div
className='flex items-center h-screen select-none px-20'
>
<Container>
<h2 className='text-4xl font-semibold'>{getRandom(ErrorMessage)}</h2>
<p className='text-md mt-1'> . !</p>
<a className='text-sm text-blue-500 hover:text-blue-400' href='https://status.koreanbots.dev' target='_blank' rel='noreferrer'> </a>
<div>
<Link href='/discord'>
<a target='_blank' rel='noreferrer' className='text-lg hover:opacity-80 cursor-pointer'>
return (
<div
className='flex items-center h-screen select-none px-20'
>
<Container>
<h2 className='text-4xl font-semibold'>{getRandom(ErrorMessage)}</h2>
<p className='text-md mt-1'> . !</p>
<a className='text-sm text-blue-500 hover:text-blue-400' href='https://status.koreanbots.dev' target='_blank' rel='noreferrer'> </a>
<div>
<Link
href='/discord'
target='_blank'
rel='noreferrer'
className='text-lg hover:opacity-80 cursor-pointer'>
<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>
</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>
</div>
</Container>
</div>
</div>
</Container>
</div>
)
}
export default MyError

View File

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

View File

@ -76,134 +76,154 @@ const AddBot:NextPage<AddBotProps> = ({ logged, user, csrfToken, theme }) => {
title:'새로운 봇 추가하기', description: '자신의 봇을 한국 디스코드 리스트에 등록하세요.'
}} />
</Login>
return <Container paddingTop className='py-5'>
<NextSeo title='새로운 봇 추가하기' description='자신의 봇을 한국 디스코드 리스트에 등록하세요.' openGraph={{
title:'새로운 봇 추가하기', description: '자신의 봇을 한국 디스코드 리스트에 등록하세요.'
}} />
<h1 className='text-3xl font-bold'> </h1>
<div className='mt-1 mb-5'>
, <span className='font-semibold'>{user.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>
</div>
{
data ? data.code == 200 && data.data ? <Message type='success'>
<h2 className='text-lg font-extrabold'> !</h2>
<p> ! . {redirectTo(router, `/pendingBots/${data.data.id}/${data.data.date}`)}</p>
</Message> : <Message type='error'>
<h2 className='text-lg font-extrabold'>{data.message || '오류가 발생했습니다.'}</h2>
<ul className='list-disc list-inside'>
{data.errors?.map((el, n) => <li key={n}>{el}</li>)}
</ul>
</Message> : <></>
}
<Formik initialValues={initialValues}
validationSchema={AddBotSubmitSchema}
onSubmit={() => setCaptcha(true)}>
{({ errors, touched, values, isValid, setFieldTouched, setFieldValue }) => (
<Form>
<div className='py-3'>
<Message type='warning'>
<h2 className='text-lg font-extrabold'> !</h2>
<ul className='list-disc list-inside'>
<li><Link href='/discord'><a rel='noreferrer' target='_blank' className='text-blue-500 hover:text-blue-600'> </a></Link> ?</li>
<li> <Link href='/guidelines'><a rel='noreferrer' target='_blank' className='text-blue-500 hover:text-blue-600'></a></Link> ?</li>
<li> ? , .</li>
<li>, API에 .</li>
</ul>
</Message>
</div>
<Label For='agree' error={errors.agree && touched.agree ? errors.agree : null} grid={false}>
<div className='flex items-center'>
<CheckBox name='agree' />
<strong className='text-sm ml-2'> , .</strong>
if(data?.data && data.code === 200) {
setTimeout(
() => redirectTo(router, `/pendingBots/${data.data.id}/${data.data.date}`),
1_000
)
}
return (
<Container paddingTop className='py-5'>
<NextSeo title='새로운 봇 추가하기' description='자신의 봇을 한국 디스코드 리스트에 등록하세요.' openGraph={{
title:'새로운 봇 추가하기', description: '자신의 봇을 한국 디스코드 리스트에 등록하세요.'
}} />
<h1 className='text-3xl font-bold'> </h1>
<div className='mt-1 mb-5'>
, <span className='font-semibold'>{user.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>
</div>
{
data ? data.code == 200 && data.data ? <Message type='success'>
<h2 className='text-lg font-extrabold'> !</h2>
<p> ! .</p>
</Message> : <Message type='error'>
<h2 className='text-lg font-extrabold'>{data.message || '오류가 발생했습니다.'}</h2>
<ul className='list-disc list-inside'>
{data.errors?.map((el, n) => <li key={n}>{el}</li>)}
</ul>
</Message> : <></>
}
<Formik initialValues={initialValues}
validationSchema={AddBotSubmitSchema}
onSubmit={() => setCaptcha(true)}>
{({ errors, touched, values, isValid, setFieldTouched, setFieldValue }) => (
<Form>
<div className='py-3'>
<Message type='warning'>
<h2 className='text-lg font-extrabold'> !</h2>
<ul className='list-disc list-inside'>
<li><Link
href='/discord'
rel='noreferrer'
target='_blank'
className='text-blue-500 hover:text-blue-600'> </Link> ?</li>
<li> <Link
href='/guidelines'
rel='noreferrer'
target='_blank'
className='text-blue-500 hover:text-blue-600'></Link> ?</li>
<li> ? , .</li>
<li>, API에 .</li>
</ul>
</Message>
</div>
</Label>
<Divider />
<Advertisement />
<Label For='id' label='봇 ID' labelDesc='봇의 클라이언트 ID를 의미합니다.' error={errors.id && touched.id ? errors.id : null} short required>
<Input name='id' placeholder='653534001742741552' />
</Label>
<Label For='prefix' label='접두사' labelDesc='봇의 사용시 앞 쪽에 붙은 기호를 의미합니다. (Prefix)' error={errors.prefix && touched.prefix ? errors.prefix : null} short required>
<Input name='prefix' placeholder='!' />
</Label>
<Label For='library' label='라이브러리' labelDesc='봇에 사용된 라이브러리를 선택해주세요. 해당되는 라이브러리가 없다면 기타를 선택해주세요.' short required error={errors.library && touched.library ? errors.library : null}>
<Select options={library.map(el=> ({ label: el, value: el }))} handleChange={(value) => setFieldValue('library', value.value)} handleTouch={() => setFieldTouched('library', true)} />
</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)} />
<p className='text-gray-400 mt-1 text-sm'> 3 . . <strong> .</strong><br/>
<a className='text-blue-500 hover:text-blue-400' href='https://contents.koreanbots.dev/categories'></a> !</p>
</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'>
<a rel='noreferrer' target='_blank' className='text-blue-500 hover:text-blue-400'></a>
</Link> !
</span>
</Label>
{
values.category.includes('빗금 명령어') && <Message type='warning'>
<h2 className='text-lg font-semibold'> (Slash Command) .</h2>
<p> .
.</p>
</Message>
}
<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>
</Label>
<Divider />
<Label For='intro' label='봇 소개' labelDesc='봇을 소개할 수 있는 간단한 설명을 적어주세요. (최대 60자)' error={errors.intro && touched.intro ? errors.intro : null} required>
<Input name='intro' placeholder='국내 봇을 한 곳에서.' />
</Label>
<Label For='intro' label='봇 설명' labelDesc={<> ! ( 1500)<br/> !</>} error={errors.desc && touched.desc ? errors.desc : null} required>
<TextArea max={1500} name='desc' placeholder='봇에 대해 최대한 자세히 설명해주세요!' theme={theme === 'dark' ? 'dark' : 'light'} value={values.desc} setValue={(value) => setFieldValue('desc', value)} />
</Label>
<Label For='preview' label='설명 미리보기' labelDesc='다음 결과는 실제와 다를 수 있습니다.'>
<Segment>
<Markdown text={values.desc} />
</Segment>
</Label>
<Divider />
<p className='text-base mt-2 mb-5'>
<span className='text-red-500 font-semibold'> *</span> =
</p>
{
captcha ? <Captcha ref={captchaRef} dark={theme === 'dark'} onVerify={(token) => {
submitBot(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>
<Advertisement />
</Container>
<Label For='agree' error={errors.agree && touched.agree ? errors.agree : null} grid={false}>
<div className='flex items-center'>
<CheckBox name='agree' />
<strong className='text-sm ml-2'> , .</strong>
</div>
</Label>
<Divider />
<Advertisement />
<Label For='id' label='봇 ID' labelDesc='봇의 클라이언트 ID를 의미합니다.' error={errors.id && touched.id ? errors.id : null} short required>
<Input name='id' placeholder='653534001742741552' />
</Label>
<Label For='prefix' label='접두사' labelDesc='봇의 사용시 앞 쪽에 붙은 기호를 의미합니다. (Prefix)' error={errors.prefix && touched.prefix ? errors.prefix : null} short required>
<Input name='prefix' placeholder='!' />
</Label>
<Label For='library' label='라이브러리' labelDesc='봇에 사용된 라이브러리를 선택해주세요. 해당되는 라이브러리가 없다면 기타를 선택해주세요.' short required error={errors.library && touched.library ? errors.library : null}>
<Select options={library.map(el=> ({ label: el, value: el }))} handleChange={(value) => setFieldValue('library', value.value)} handleTouch={() => setFieldTouched('library', true)} />
</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)} />
<p className='text-gray-400 mt-1 text-sm'> 3 . . <strong> .</strong><br/>
<a className='text-blue-500 hover:text-blue-400' href='https://contents.koreanbots.dev/categories'></a> !</p>
</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>
}
<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>
</Label>
<Divider />
<Label For='intro' label='봇 소개' labelDesc='봇을 소개할 수 있는 간단한 설명을 적어주세요. (최대 60자)' error={errors.intro && touched.intro ? errors.intro : null} required>
<Input name='intro' placeholder='국내 봇을 한 곳에서.' />
</Label>
<Label For='intro' label='봇 설명' labelDesc={<> ! ( 1500)<br/> !</>} error={errors.desc && touched.desc ? errors.desc : null} required>
<TextArea max={1500} name='desc' placeholder='봇에 대해 최대한 자세히 설명해주세요!' theme={theme === 'dark' ? 'dark' : 'light'} value={values.desc} setValue={(value) => setFieldValue('desc', value)} />
</Label>
<Label For='preview' label='설명 미리보기' labelDesc='다음 결과는 실제와 다를 수 있습니다.'>
<Segment>
<Markdown text={values.desc} />
</Segment>
</Label>
<Divider />
<p className='text-base mt-2 mb-5'>
<span className='text-red-500 font-semibold'> *</span> =
</p>
{
captcha ? <Captcha ref={captchaRef} dark={theme === 'dark'} onVerify={(token) => {
submitBot(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>
<Advertisement />
</Container>
)
}
export const getServerSideProps = async (ctx: NextPageContext) => {

View File

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

View File

@ -60,270 +60,276 @@ const ManageBotPage:NextPage<ManageBotProps> = ({ bot, user, csrfToken, theme })
<NextSeo title='봇 정보 수정하기' description='봇의 정보를 수정합니다.'/>
</Login>
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'>
<NextSeo title={`${bot.name} 수정하기`} description='봇의 정보를 수정합니다.'/>
<h1 className='text-3xl font-bold mb-8'> </h1>
<Formik initialValues={cleanObject({
agree: false,
id: bot.id,
prefix: bot.prefix,
library: bot.lib,
category: bot.category,
intro: bot.intro,
desc: bot.desc,
website: bot.web,
url: bot.url,
git: bot.git,
discord: bot.discord,
_csrf: csrfToken
})}
validationSchema={ManageBotSchema}
onSubmit={submitBot}>
{({ errors, touched, values, setFieldTouched, setFieldValue }) => (
<Form>
<div className='md:flex text-center md:text-left'>
<DiscordAvatar userID={bot.id} className='md:mx-1 mx-auto rounded-full'/>
<div className='md:w-2/3 px-8 py-6'>
<h1 className='text-3xl font-bold'>{bot.name}#{bot.tag}</h1>
<h2>ID: {bot.id}</h2>
return (
<Container paddingTop className='pt-5 pb-10'>
<NextSeo title={`${bot.name} 수정하기`} description='봇의 정보를 수정합니다.'/>
<h1 className='text-3xl font-bold mb-8'> </h1>
<Formik initialValues={cleanObject({
agree: false,
id: bot.id,
prefix: bot.prefix,
library: bot.lib,
category: bot.category,
intro: bot.intro,
desc: bot.desc,
website: bot.web,
url: bot.url,
git: bot.git,
discord: bot.discord,
_csrf: csrfToken
})}
validationSchema={ManageBotSchema}
onSubmit={submitBot}>
{({ errors, touched, values, setFieldTouched, setFieldValue }) => (
<Form>
<div className='md:flex text-center md:text-left'>
<DiscordAvatar userID={bot.id} className='md:mx-1 mx-auto rounded-full'/>
<div className='md:w-2/3 px-8 py-6'>
<h1 className='text-3xl font-bold'>{bot.name}#{bot.tag}</h1>
<h2>ID: {bot.id}</h2>
</div>
</div>
</div>
{
data ? data.code === 200 ? <div className='mt-4'>
<Redirect to={makeBotURL(bot)}>
<Message type='success'>
<h2 className='text-lg font-extrabold'> .</h2>
<p> !</p>
{
data ? data.code === 200 ? <div className='mt-4'>
<Redirect to={makeBotURL(bot)}>
<Message type='success'>
<h2 className='text-lg font-extrabold'> .</h2>
<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>
</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>
</div> : ''
}
<Label For='prefix' label='접두사' labelDesc='봇의 사용시 앞 쪽에 붙은 기호를 의미합니다. (Prefix)' error={errors.prefix && touched.prefix ? errors.prefix : null} short required>
<Input name='prefix' placeholder='!' />
</Label>
<Label For='library' label='라이브러리' labelDesc='봇에 사용된 라이브러리를 선택해주세요. 해당되는 라이브러리가 없다면 기타를 선택해주세요.' short required error={errors.library && touched.library ? errors.library : null}>
<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)} />
</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>
</div> : ''
}
<Label For='prefix' label='접두사' labelDesc='봇의 사용시 앞 쪽에 붙은 기호를 의미합니다. (Prefix)' error={errors.prefix && touched.prefix ? errors.prefix : null} short required>
<Input name='prefix' placeholder='!' />
</Label>
<Label For='library' label='라이브러리' labelDesc='봇에 사용된 라이브러리를 선택해주세요. 해당되는 라이브러리가 없다면 기타를 선택해주세요.' short required error={errors.library && touched.library ? errors.library : null}>
<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)} />
</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>
}
<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>
</Label>
<Divider />
<Label For='intro' label='봇 소개' labelDesc='봇을 소개할 수 있는 간단한 설명을 적어주세요. (최대 60자)' error={errors.intro && touched.intro ? errors.intro : null} required>
<Input name='intro' placeholder='국내 봇을 한 곳에서.' />
</Label>
<Label For='intro' label='봇 설명' labelDesc={<> ! ( 1500)<br/> !</>} error={errors.desc && touched.desc ? errors.desc : null} required>
<TextArea name='desc' placeholder='봇에 대해 최대한 자세히 설명해주세요!' theme={theme === 'dark' ? 'dark' : 'light'} value={values.desc} setValue={(value) => setFieldValue('desc', value)} max={1500} />
</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 />
<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'>
<a rel='noreferrer' target='_blank' className='text-blue-500 hover:text-blue-400'></a>
</Link> !
</span>
</Label>
{
values.category.includes('빗금 명령어') && <Message type='warning'>
<h2 className='text-lg font-semibold'> (Slash Command) .</h2>
<p> .
.</p>
</Message>
}
<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>
</Label>
<Divider />
<Label For='intro' label='봇 소개' labelDesc='봇을 소개할 수 있는 간단한 설명을 적어주세요. (최대 60자)' error={errors.intro && touched.intro ? errors.intro : null} required>
<Input name='intro' placeholder='국내 봇을 한 곳에서.' />
</Label>
<Label For='intro' label='봇 설명' labelDesc={<> ! ( 1500)<br/> !</>} error={errors.desc && touched.desc ? errors.desc : null} required>
<TextArea name='desc' placeholder='봇에 대해 최대한 자세히 설명해주세요!' theme={theme === 'dark' ? 'dark' : 'light'} value={values.desc} setValue={(value) => setFieldValue('desc', value)} max={1500} />
</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 />
<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', '')
<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} />)
}
}}>
<i className='fas fa-user-plus text-white' />
</Button>
</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', '')
}
}}>
<i className='fas fa-user-plus text-white' />
</Button>
</div>
</div>
</div>
<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>
</Form>
}
</Formik>
</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>
<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>
</Form>
}
</Formik>
</Modal>
</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>
<Modal full header={`${bot.name} 소유권 이전하기`} isOpen={transferModal} dark={theme === 'dark'} onClose={() => setTransferModal(false)} closeIcon>
<Formik initialValues={{ ownerID: '', name: '', _captcha: '' }} onSubmit={async (v) => {
const res = await Fetch(`/bots/${bot.id}/owners`, { method: 'PATCH', body: JSON.stringify({
_captcha: v._captcha,
_csrf: csrfToken,
owners: [ v.ownerID ]
}) })
if(res.code === 200) {
alert('성공적으로 소유권을 이전했습니다.')
router.push('/')
} else {
alert(res.message)
setTransferModal(false)
}
}}>
{
({ values, setFieldValue }) => <Form>
<Message type='warning'>
<h2 className='text-2xl font-bold'>!</h2>
<p> , .</p>
</Message>
<div className='py-4'>
<h2 className='text-md my-1'> ID를 .</h2>
<Input name='ownerID' placeholder='이전할 유저 ID' />
<Divider />
<h2 className='text-md my-1'> <strong>{bot.name}</strong>{getJosaPicker('을')(bot.name)} .</h2>
<Input name='name' placeholder={bot.name} />
</div>
<Captcha dark={theme === 'dark'} onVerify={(k) => setFieldValue('_captcha', k)} />
<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>
</Form>
}
</Formik>
</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>
<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>
<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>
<Formik initialValues={{ ownerID: '', name: '', _captcha: '' }} onSubmit={async (v) => {
const res = await Fetch(`/bots/${bot.id}/owners`, { method: 'PATCH', body: JSON.stringify({
_captcha: v._captcha,
_csrf: csrfToken,
owners: [ v.ownerID ]
}) })
if(res.code === 200) {
alert('성공적으로 소유권을 이전했습니다.')
router.push('/')
} else {
alert(res.message)
setTransferModal(false)
}
}}>
{
({ values, setFieldValue }) => <Form>
<Message type='warning'>
<h2 className='text-2xl font-bold'>!</h2>
<p> , .</p>
</Message>
<div className='py-4'>
<h2 className='text-md my-1'> ID를 .</h2>
<Input name='ownerID' placeholder='이전할 유저 ID' />
<Divider />
<h2 className='text-md my-1'> <strong>{bot.name}</strong>{getJosaPicker('을')(bot.name)} .</h2>
<Input name='name' placeholder={bot.name} />
</div>
<Captcha dark={theme === 'dark'} onVerify={(k) => setFieldValue('_captcha', k)} />
<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>
</Form>
}
</Formik>
</Modal>
</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>
<Modal full header={`${bot.name} 삭제하기`} isOpen={deleteModal} dark={theme === 'dark'} onClose={() => setDeleteModal(false)} closeIcon>
<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) {
alert('성공적으로 삭제하였습니다.')
redirectTo(router, '/')
}
else alert(res.message)
}}>
{
({ values, setFieldValue }) => <Form>
<Message type='warning'>
<p> .<br/> .</p>
<p> <strong>{bot.name}</strong>{getJosaPicker('을')(bot.name)} .</p>
</Message>
<div className='py-4'>
<Input name='name' placeholder={bot.name} />
</div>
<Captcha dark={theme === 'dark'} onVerify={(k) => setFieldValue('_captcha', k)} />
<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>
</Form>
}
</Formik>
</Modal>
</div>
</Segment>
</div>
}
</Container>
<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>
<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>
<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) {
alert('성공적으로 삭제하였습니다.')
redirectTo(router, '/')
}
else alert(res.message)
}}>
{
({ values, setFieldValue }) => <Form>
<Message type='warning'>
<p> .<br/> .</p>
<p> <strong>{bot.name}</strong>{getJosaPicker('을')(bot.name)} .</p>
</Message>
<div className='py-4'>
<Input name='name' placeholder={bot.name} />
</div>
<Captcha dark={theme === 'dark'} onVerify={(k) => setFieldValue('_captcha', k)} />
<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>
</Form>
}
</Formik>
</Modal>
</div>
</Segment>
</div>
}
</Container>
)
}
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)
}, [])
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` } : {}}>
<Container paddingTop className='py-10'>
<NextSeo
title={data.name}
description={data.intro}
twitter={{
cardType: 'summary_large_image'
}}
openGraph={{
images: [
{
url: KoreanbotsEndPoints.OG.bot(data.id, data.name, data.intro, data.category, [formatNumber(data.votes), formatNumber(data.servers)]),
width: 2048,
height: 1170,
alt: 'Bot Preview Image'
}
]
}}
/>
{
data.state === 'blocked' ? <div className='pb-40'>
<Message type='error'>
<h2 className='text-lg font-extrabold'> .</h2>
</Message>
</div>
: data.category.includes('NSFW') && !nsfw ? <NSFW onClick={() => setNSFW(true)} onDisableClick={() => localStorage.nsfw = true} />
: <>
<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'>
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` } : {}}>
<Container paddingTop className='py-10'>
<NextSeo
title={data.name}
description={data.intro}
twitter={{
cardType: 'summary_large_image'
}}
openGraph={{
images: [
{
url: KoreanbotsEndPoints.OG.bot(data.id, data.name, data.intro, data.category, [formatNumber(data.votes), formatNumber(data.servers)]),
width: 2048,
height: 1170,
alt: 'Bot Preview Image'
}
]
}}
/>
{
data.state === 'blocked' ? <div className='pb-40'>
<Message type='error'>
<h2 className='text-lg font-extrabold'> .</h2>
</Message>
</div>
: data.category.includes('NSFW') && !nsfw ? <NSFW onClick={() => setNSFW(true)} onDisableClick={() => localStorage.nsfw = true} />
: <>
<div className='w-full pb-2'>
{
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`}>
<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>
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' className='text-blue-500 hover:text-blue-400'></Link> <Link href='/discord' className='text-blue-500 hover:text-blue-400'> </Link> .</p>
</Message> : ''
}
</div>
</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='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='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 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>
<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 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>
<Divider className='px-5' />
<div className='hidden lg:block'>
<Advertisement />
</div>
</div>
</>
}
</Container>
</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='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) => {

View File

@ -30,83 +30,85 @@ const ReportBot: NextPage<ReportBotProps> = ({ data, user, csrfToken }) => {
if(!user) return <Login>
<NextSeo title='신고하기' />
</Login>
return <Container paddingTop className='py-10'>
<NextSeo title={`${data.name} 신고하기`} />
<Link href={makeBotURL(data)}>
<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>
{
reportRes?.code === 200 ? <Message type='success'>
<h2 className='text-lg font-semibold'> !</h2>
<p> . <strong> <a className='text-blue-600 hover:text-blue-500' href='/discord'> </a> !!</strong></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>
return (
<Container paddingTop className='py-10'>
<NextSeo title={`${data.name} 신고하기`} />
<Link href={makeBotURL(data)} className='text-blue-500 hover:opacity-80'>
<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>
<p> . <strong> <a className='text-blue-600 hover:text-blue-500' href='/discord'> </a> !!</strong></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>
)
}
<div className='mt-1 text-red-500 text-xs font-light'>{errors.category && touched.category ? errors.category : null}</div>
{
values.category && <>
{
}
<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>
{
values.category && <>
{
[reportCats[2]]: <Message type='info'>
<h3 className='font-bold text-xl'> ?</h3>
<p> .</p>
<p className='list-disc list-item list-inside'> 1393 | 1388</p>
</Message>,
[reportCats[5]]: <DMCA values={values} errors={errors} touched={touched} setFieldValue={setFieldValue} />,
[reportCats[6]]: <Message type='warning'>
<h3 className='font-bold text-xl'> ?</h3>
<p><a className='text-blue-400' target='_blank' rel='noreferrer' href='http://dis.gd/report'> </a> .</p>
</Message>
}[values.category]
}
{
!['오픈소스 라이선스, 저작권 위반 등 권리 침해'].includes(values.category) && <>
<h3 className='font-bold mt-2'></h3>
<p className='text-gray-400 text-sm mb-1'> .</p>
<TextField values={values} errors={errors} touched={touched} setFieldValue={setFieldValue} />
</>
}
</>
}
</div>
</Form>
)
}
</Formik>
}
</Container>
{
[reportCats[2]]: <Message type='info'>
<h3 className='font-bold text-xl'> ?</h3>
<p> .</p>
<p className='list-disc list-item list-inside'> 1393 | 1388</p>
</Message>,
[reportCats[5]]: <DMCA values={values} errors={errors} touched={touched} setFieldValue={setFieldValue} />,
[reportCats[6]]: <Message type='warning'>
<h3 className='font-bold text-xl'> ?</h3>
<p><a className='text-blue-400' target='_blank' rel='noreferrer' href='http://dis.gd/report'> </a> .</p>
</Message>
}[values.category]
}
{
!['오픈소스 라이선스, 저작권 위반 등 권리 침해'].includes(values.category) && <>
<h3 className='font-bold mt-2'></h3>
<p className='text-gray-400 text-sm mb-1'> .</p>
<TextField values={values} errors={errors} touched={touched} setFieldValue={setFieldValue} />
</>
}
</>
}
</div>
</Form>
)
}
</Formik>
}
</Container>
)
}
export const getServerSideProps = async (ctx: Context) => {

View File

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

View File

@ -58,86 +58,91 @@ const BotApplication: NextPage<BotApplicationProps> = ({ user, spec, bot, theme,
return
}
if(!bot || !spec) return <NotFound />
return <DeveloperLayout enabled='applications'>
<Link href='/developers/applications'>
<a className='text-blue-500 hover:text-blue-400'>
<i className='fas fa-arrow-left' />
</a>
</Link>
<h1 className='text-3xl font-bold'> </h1>
<p className='text-gray-400'> API에 .</p>
<div className='lg:flex pt-6'>
<div className='lg:w-1/5'>
<DiscordAvatar userID={bot.id} />
</div>
<div className='lg:w-4/5 relative'>
<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>
}
return (
<DeveloperLayout enabled='applications'>
<Link
href='/developers/applications'
className='text-blue-500 hover:text-blue-400'>
<i className='fas fa-arrow-left' />
</Link>
<h1 className='text-3xl font-bold'> </h1>
<p className='text-gray-400'> API에 .</p>
<div className='lg:flex pt-6'>
<div className='lg:w-1/5'>
<DiscordAvatar userID={bot.id} />
</div>
<div className='grid text-left px-6'>
<h2 className='text-3xl font-bold mb-2 mt-3'>{bot.name}#{bot.tag}</h2>
<h3 className='text-lg font-semibold'> </h3>
<pre className='text-sm overflow-x-scroll w-full'>{showToken ? spec.token : '******************'}</pre>
<div className='pt-3 pb-6'>
<Button onClick={() => setShowToken(!showToken)}>{showToken ? '숨기기' : '보기'}</Button>
<Button onClick={setTokenCopied} className={tokenCopied ? 'bg-emerald-400 text-white' : null}>{tokenCopied ? '복사됨' : '복사'}</Button>
<Button onClick={()=> setModalOpen(true)}></Button>
<Modal isOpen={modalOpened} onClose={() => setModalOpen(false)} dark={theme === 'dark'} header='정말로 토큰을 재발급하시겠습니까?'>
<p> </p>
<div className='text-right pt-6'>
<Button className='bg-gray-500 text-white hover:opacity-90' onClick={()=> setModalOpen(false)}></Button>
<Button onClick={async ()=> {
const res = await resetToken()
spec.token = res.data.token
setModalOpen(false)
}}></Button>
</div>
</Modal>
<div className='lg:w-4/5 relative'>
<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>
<Formik validationSchema={DeveloperBotSchema} initialValues={{
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'}><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 className='grid text-left px-6'>
<h2 className='text-3xl font-bold mb-2 mt-3'>{bot.name}#{bot.tag}</h2>
<h3 className='text-lg font-semibold'> </h3>
<pre className='text-sm overflow-x-scroll w-full'>{showToken ? spec.token : '******************'}</pre>
<div className='pt-3 pb-6'>
<Button onClick={() => setShowToken(!showToken)}>{showToken ? '숨기기' : '보기'}</Button>
<Button onClick={setTokenCopied} className={tokenCopied ? 'bg-emerald-400 text-white' : null}>{tokenCopied ? '복사됨' : '복사'}</Button>
<Button onClick={()=> setModalOpen(true)}></Button>
<Modal isOpen={modalOpened} onClose={() => setModalOpen(false)} dark={theme === 'dark'} header='정말로 토큰을 재발급하시겠습니까?'>
<p> </p>
<div className='text-right pt-6'>
<Button className='bg-gray-500 text-white hover:opacity-90' onClick={()=> setModalOpen(false)}></Button>
<Button onClick={async ()=> {
const res = await resetToken()
spec.token = res.data.token
setModalOpen(false)
}}></Button>
</div>
<Button type='submit'><i className='far fa-save'/> </Button>
</Form>
)}
</Formik>
</Modal>
</div>
<Formik validationSchema={DeveloperBotSchema} initialValues={{
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>
</DeveloperLayout>
</DeveloperLayout>
)
}
@ -151,7 +156,7 @@ interface BotApplicationProps {
export const getServerSideProps = async (ctx: Context) => {
const parsed = parseCookie(ctx.req)
const user = await get.Authorization(parsed?.token) || ''
const user = (await get.Authorization(parsed?.token)) || ''
return {
props: { user, spec: await get.botSpec(ctx.query.id, user), bot: await get.bot.load(ctx.query.id), csrfToken: getToken(ctx.req, ctx.res) }

View File

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

View File

@ -57,95 +57,100 @@ const ServerApplication: NextPage<ServerApplicationProps> = ({ user, spec, serve
return
}
if(!server) return <NotFound />
return <DeveloperLayout enabled='applications'>
<Link href='/developers/applications'>
<a className='text-blue-500 hover:text-blue-400'>
<i className='fas fa-arrow-left' />
</a>
</Link>
<h1 className='text-3xl font-bold'> </h1>
<p className='text-gray-400'> API에 .</p>
{
spec ? <>
<div className='lg:flex pt-6'>
<div className='lg:w-1/5'>
<ServerIcon id={server.id} />
</div>
<div className='lg:w-4/5 relative'>
<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>
}
return (
<DeveloperLayout enabled='applications'>
<Link
href='/developers/applications'
className='text-blue-500 hover:text-blue-400'>
<i className='fas fa-arrow-left' />
</Link>
<h1 className='text-3xl font-bold'> </h1>
<p className='text-gray-400'> API에 .</p>
{
spec ? <>
<div className='lg:flex pt-6'>
<div className='lg:w-1/5'>
<ServerIcon id={server.id} />
</div>
<div className='grid text-left px-6'>
<h2 className='text-3xl font-bold mb-2 mt-3'>{server.name}</h2>
<h3 className='text-lg font-semibold'> </h3>
<pre className='text-sm overflow-x-scroll w-full'>{showToken ? spec.token : '******************'}</pre>
<div className='pt-3 pb-6'>
<Button onClick={() => setShowToken(!showToken)}>{showToken ? '숨기기' : '보기'}</Button>
<Button onClick={setTokenCopied} className={tokenCopied ? 'bg-emerald-400 text-white' : null}>{tokenCopied ? '복사됨' : '복사'}</Button>
<Button onClick={()=> setModalOpen(true)}></Button>
<Modal isOpen={modalOpened} onClose={() => setModalOpen(false)} dark={theme === 'dark'} header='정말로 토큰을 재발급하시겠습니까?'>
<p> </p>
<div className='text-right pt-6'>
<Button className='bg-gray-500 text-white hover:opacity-90' onClick={()=> setModalOpen(false)}></Button>
<Button onClick={async ()=> {
const res = await resetToken()
if(res.data?.token) spec.token = res.data.token
setModalOpen(false)
}}></Button>
</div>
</Modal>
<div className='lg:w-4/5 relative'>
<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>
<Formik validationSchema={DeveloperServerSchema} initialValues={{
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'}><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 className='grid text-left px-6'>
<h2 className='text-3xl font-bold mb-2 mt-3'>{server.name}</h2>
<h3 className='text-lg font-semibold'> </h3>
<pre className='text-sm overflow-x-scroll w-full'>{showToken ? spec.token : '******************'}</pre>
<div className='pt-3 pb-6'>
<Button onClick={() => setShowToken(!showToken)}>{showToken ? '숨기기' : '보기'}</Button>
<Button onClick={setTokenCopied} className={tokenCopied ? 'bg-emerald-400 text-white' : null}>{tokenCopied ? '복사됨' : '복사'}</Button>
<Button onClick={()=> setModalOpen(true)}></Button>
<Modal isOpen={modalOpened} onClose={() => setModalOpen(false)} dark={theme === 'dark'} header='정말로 토큰을 재발급하시겠습니까?'>
<p> </p>
<div className='text-right pt-6'>
<Button className='bg-gray-500 text-white hover:opacity-90' onClick={()=> setModalOpen(false)}></Button>
<Button onClick={async ()=> {
const res = await resetToken()
if(res.data?.token) spec.token = res.data.token
setModalOpen(false)
}}></Button>
</div>
<Button type='submit'><i className='far fa-save'/> </Button>
</Form>
)}
</Formik>
</Modal>
</div>
<Formik validationSchema={DeveloperServerSchema} initialValues={{
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 className='mt-5'>
<Message type='error'>
<h2 className='text-lg font-extrabold'> .</h2>
<p> , .</p>
</Message>
</div>
</> : <div className='mt-5'>
<Message type='error'>
<h2 className='text-lg font-extrabold'> .</h2>
<p> , .</p>
</Message>
</div>
}
</DeveloperLayout>
}
</DeveloperLayout>
)
}
@ -159,7 +164,7 @@ interface ServerApplicationProps {
export const getServerSideProps = async (ctx: Context) => {
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)
return {
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) => {
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)
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
})
if(!data) return <NotFound />
return <Container paddingTop className='py-10'>
<NextSeo title='심사이력' />
<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>
<p> .</p>
</Message>
: data.state === 1 ? <Message type='success'>
<h2 className='text-lg font-extrabold'></h2>
<p> !</p>
<p><Link href={`/bots/${data.id}`}><a className='text-blue-500 hover:text-blue-400'> </a></Link></p>
</Message> : <Message type='error'>
<h2 className='text-lg font-extrabold'></h2>
<p> .</p>
{
data.reason && <>
<p>: <strong>{BotSubmissionDenyReasonPresetsName[data.reason] || data.reason}</strong></p>
<div className='pt-2'>
{DenyPresetsArticle[data.reason]}
</div>
</>
}
<div className='pt-2'>
{data.strikes < 3 ? (
<p>
{3 - data.strikes} . . <br/>
'프라이빗 봇', '봇 오프라인', '공식 디스코드 서버 미참여' .
</p>
) : (
<p> .</p>
)}
</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`
return (
<Container paddingTop className='py-10'>
<NextSeo title='심사이력' />
<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>
<p> .</p>
</Message>
: data.state === 1 ? <Message type='success'>
<h2 className='text-lg font-extrabold'></h2>
<p> !</p>
<p><Link href={`/bots/${data.id}`} className='text-blue-500 hover:text-blue-400'> </Link></p>
</Message> : <Message type='error'>
<h2 className='text-lg font-extrabold'></h2>
<p> .</p>
{
data.reason && <>
<p>: <strong>{BotSubmissionDenyReasonPresetsName[data.reason] || data.reason}</strong></p>
<div className='pt-2'>
{DenyPresetsArticle[data.reason]}
</div>
</>
}
<div className='pt-2'>
{data.strikes < 3 ? (
<p>
{3 - data.strikes} . . <br/>
'프라이빗 봇', '봇 오프라인', '공식 디스코드 서버 미참여' .
</p>
) : (
<p> .</p>
)}
</div>
</Message>
}
>
<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>
<p className='dark:text-gray-300 text-gray-800 text-base mt-3'>{data.intro}</p>
</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 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`
}
>
<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>
<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 className='markdown-body pt-10 w-full lg:pr-5 lg:w-3/4'>
<Advertisement />
<Segment className='my-4'>
<Markdown text={data.desc}/>
</Segment>
<Advertisement />
</div>
<Advertisement size='tall' />
</div>
<div className='markdown-body pt-10 w-full lg:pr-5 lg:w-3/4'>
<Advertisement />
<Segment className='my-4'>
<Markdown text={data.desc}/>
</Segment>
<Advertisement />
</div>
</div>
</Container>
</Container>
)
}
export const getServerSideProps = async (ctx: Context) => {

View File

@ -44,232 +44,236 @@ const Servers: NextPage<ServersProps> = ({ data, desc, date, user, theme }) => {
})
}, [ data ])
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` } : {}}>
<Container paddingTop className='py-10'>
<NextSeo
title={data.name}
description={data.intro}
twitter={{
cardType: 'summary_large_image'
}}
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'>
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` } : {}}>
<Container paddingTop className='py-10'>
<NextSeo
title={data.name}
description={data.intro}
twitter={{
cardType: 'summary_large_image'
}}
openGraph={{
images: [
{
data.state === 'unreachable' ? <Message type='error'>
<h2 className='text-lg font-extrabold'> .</h2>
<p> , .</p>
{
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> : ''
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'
}
</div>
<div className='lg:flex w-full'>
<div className='w-full text-center lg:w-2/12'>
<ServerIcon
id={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'>
<h1 className='mb-2 mt-3 text-4xl font-bold' style={bg ? { color: 'white' } : {}}>
{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'>
]
}}
/>
{
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'>
{
['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>
</LongButton>
data.state === 'unreachable' ? <Message type='error'>
<h2 className='text-lg font-extrabold'> .</h2>
<p> , .</p>
{
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' 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`}>
<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>
{
(owners?.find(el => el.id === user?.id) || checkUserFlag(user?.flags, 'staff')) && <>
<LongButton href={`/servers/${data.id}/edit`}>
<h4>
<i className='fas fa-cogs' />
</div>
<div className='lg:flex w-full'>
<div className='w-full text-center lg:w-2/12'>
<ServerIcon
id={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'>
<h1 className='mb-2 mt-3 text-4xl font-bold' style={bg ? { color: 'white' } : {}}>
{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
newTab
href={`/servers/${router.query.id}/join`}
>
<h4 className='whitespace-nowrap'>
<i className='fas fa-user-plus text-discord-blurple' />
</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()
}}>
}
<Link href={`/servers/${router.query.id}/vote`} legacyBehavior>
<LongButton>
<h4>
<i className='fas fa-sync' />
<i className='fas fa-heart text-red-600' />
</h4>
</LongButton> */}
</>
}
</div>
</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>
<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>
{
(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>
<Advertisement size='tall' />
</div>
<div className='w-full lg:pr-5 lg:w-3/4'>
<Segment className='my-4'>
<Markdown text={desc}/>
</Segment>
<Divider className='px-5' />
<div className='hidden lg:block'>
<Advertisement />
</div>
</div>
</>
}
</Container>
</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`}
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) => {

View File

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

View File

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

View File

@ -38,78 +38,80 @@ const ReportUser: NextPage<ReportUserProps> = ({ data, user, csrfToken }) => {
</div>
</div>
</>
return <Container paddingTop className='py-10'>
<NextSeo title={`${data.globalName} 신고하기`} />
<Link href={makeUserURL(data)}>
<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>
{
reportRes?.code === 200 ? <Message type='success'>
<h2 className='text-lg font-semibold'> !</h2>
<p> . <strong> <a className='text-blue-600 hover:text-blue-500' href='/discord'> </a> !!</strong></p>
</Message> : <Formik onSubmit={async (body) => {
const res = await Fetch(`/users/${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>
return (
<Container paddingTop className='py-10'>
<NextSeo title={`${data.globalName} 신고하기`} />
<Link href={makeUserURL(data)} className='text-blue-500 hover:opacity-80'>
<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>
<p> . <strong> <a className='text-blue-600 hover:text-blue-500' href='/discord'> </a> !!</strong></p>
</Message> : <Formik onSubmit={async (body) => {
const res = await Fetch(`/users/${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>
)
}
<div className='mt-1 text-red-500 text-xs font-light'>{errors.category && touched.category ? errors.category : null}</div>
{
values.category && <>
{
values.category === '오픈소스 라이선스, 저작권 위반 등 권리 침해' ? <DMCA values={values} errors={errors} touched={touched} setFieldValue={setFieldValue} /> :
values.category === '괴롭힘, 모욕, 명예훼손' ? <>
<Message type='info'>
<h3 className='font-bold text-xl'> ?</h3>
<p> .</p>
<p className='list-disc list-item list-inside'> 1393 | 1388</p>
</Message>
</> : ''
}
{
!['오픈소스 라이선스, 저작권 위반 등 권리 침해'].includes(values.category) && <>
<h3 className='font-bold mt-2'></h3>
<p className='text-gray-400 text-sm mb-1'> .</p>
<TextField values={values} errors={errors} touched={touched} setFieldValue={setFieldValue} />
</>
}
</>
}
</div>
</Form>
)
}
</Formik>
}
</Container>
}
<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>
{
values.category && <>
{
values.category === '오픈소스 라이선스, 저작권 위반 등 권리 침해' ? <DMCA values={values} errors={errors} touched={touched} setFieldValue={setFieldValue} /> :
values.category === '괴롭힘, 모욕, 명예훼손' ? <>
<Message type='info'>
<h3 className='font-bold text-xl'> ?</h3>
<p> .</p>
<p className='list-disc list-item list-inside'> 1393 | 1388</p>
</Message>
</> : ''
}
{
!['오픈소스 라이선스, 저작권 위반 등 권리 침해'].includes(values.category) && <>
<h3 className='font-bold mt-2'></h3>
<p className='text-gray-400 text-sm mb-1'> .</p>
<TextField values={values} errors={errors} touched={touched} setFieldValue={setFieldValue} />
</>
}
</>
}
</div>
</Form>
)
}
</Formik>
}
</Container>
)
}
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].boostTier = data?.boostTier ?? null
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
} else {
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"
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"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.22.5.tgz#8564dd588182ce0047d55d7a75e93921107b57ec"
integrity sha512-ecjvYlnAaZ/KVneE/OdKYBYfgXV3Ptu6zQWmgEF7vwKhQnvVS6bjMD2XYgj+SNvQ1GfK/pjgokfPkC/2CO8CuA==
@ -2098,6 +2098,45 @@
tslib "^2.6.1"
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":
version "11.11.0"
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"
integrity sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==
"@emotion/react@^11.1.1":
"@emotion/react@^11.8.1":
version "11.11.1"
resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.11.1.tgz#b2c36afac95b184f73b08da8c214fdf861fa4157"
integrity sha512-5mlW1DquU5HaxjLkfkGN1GA/fvVGdyHURRiX/0FHl2cfIfRxSOfmxEH5YS43edp0OldZrZ+dkBKbngxcNCdZvA==
@ -2150,7 +2189,7 @@
"@emotion/weak-memoize" "^0.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"
resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.1.2.tgz#017a6e4c9b8a803bd576ff3d52a0ea6fa5a62b51"
integrity sha512-zR6a/fkFP4EAcCMQtLOhIgpprZOwNmCldtpaISpvz348+DP4Mz8ZoKaGGCQpbzepNIUWbq4w6hNZkwDyKoS+HA==
@ -2218,6 +2257,26 @@
resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.44.0.tgz#961a5903c74139390478bdc808bcde3fc45ab7af"
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":
version "5.15.4"
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/sourcemap-codec" "1.4.14"
"@next/env@12.3.4":
version "12.3.4"
resolved "https://registry.yarnpkg.com/@next/env/-/env-12.3.4.tgz#c787837d36fcad75d72ff8df6b57482027d64a47"
integrity sha512-H/69Lc5Q02dq3o+dxxy5O/oNxFsZpdL6WREtOOtOM1B/weonIwDXkekr1KV5DPVPr12IHFPrMrcJQ6bgPMfn7A==
"@next/env@13.5.2":
version "13.5.2"
resolved "https://registry.yarnpkg.com/@next/env/-/env-13.5.2.tgz#1c09e6cf1df8b1edf3cf0ca9c0e0119a49802a5d"
integrity sha512-dUseBIQVax+XtdJPzhwww4GetTjlkRSsXeQnisIJWBaHsnxYcN2RGzsPHi58D6qnkATjnhuAtQTJmR1hKYQQPg==
"@next/eslint-plugin-next@13.4.7":
version "13.4.7"
@ -2526,70 +2585,50 @@
dependencies:
glob "7.1.7"
"@next/swc-android-arm-eabi@12.3.4":
version "12.3.4"
resolved "https://registry.yarnpkg.com/@next/swc-android-arm-eabi/-/swc-android-arm-eabi-12.3.4.tgz#fd1c2dafe92066c6120761c6a39d19e666dc5dd0"
integrity sha512-cM42Cw6V4Bz/2+j/xIzO8nK/Q3Ly+VSlZJTa1vHzsocJRYz8KT6MrreXaci2++SIZCF1rVRCDgAg5PpqRibdIA==
"@next/swc-darwin-arm64@13.5.2":
version "13.5.2"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.5.2.tgz#f099a36fdd06b1949eb4e190aee95a52b97d3885"
integrity sha512-7eAyunAWq6yFwdSQliWMmGhObPpHTesiKxMw4DWVxhm5yLotBj8FCR4PXGkpRP2tf8QhaWuVba+/fyAYggqfQg==
"@next/swc-android-arm64@12.3.4":
version "12.3.4"
resolved "https://registry.yarnpkg.com/@next/swc-android-arm64/-/swc-android-arm64-12.3.4.tgz#11a146dae7b8bca007239b21c616e83f77b19ed4"
integrity sha512-5jf0dTBjL+rabWjGj3eghpLUxCukRhBcEJgwLedewEA/LJk2HyqCvGIwj5rH+iwmq1llCWbOky2dO3pVljrapg==
"@next/swc-darwin-x64@13.5.2":
version "13.5.2"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-13.5.2.tgz#b8950fbe150db6f82961619e31fc6e9232fce8f4"
integrity sha512-WxXYWE7zF1ch8rrNh5xbIWzhMVas6Vbw+9BCSyZvu7gZC5EEiyZNJsafsC89qlaSA7BnmsDXVWQmc+s1feSYbQ==
"@next/swc-darwin-arm64@12.3.4":
version "12.3.4"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-12.3.4.tgz#14ac8357010c95e67327f47082af9c9d75d5be79"
integrity sha512-DqsSTd3FRjQUR6ao0E1e2OlOcrF5br+uegcEGPVonKYJpcr0MJrtYmPxd4v5T6UCJZ+XzydF7eQo5wdGvSZAyA==
"@next/swc-linux-arm64-gnu@13.5.2":
version "13.5.2"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.5.2.tgz#8134d31fa9ad6848561b6969d27a8c07ab090974"
integrity sha512-URSwhRYrbj/4MSBjLlefPTK3/tvg95TTm6mRaiZWBB6Za3hpHKi8vSdnCMw5D2aP6k0sQQIEG6Pzcfwm+C5vrg==
"@next/swc-darwin-x64@12.3.4":
version "12.3.4"
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-12.3.4.tgz#e7dc63cd2ac26d15fb84d4d2997207fb9ba7da0f"
integrity sha512-PPF7tbWD4k0dJ2EcUSnOsaOJ5rhT3rlEt/3LhZUGiYNL8KvoqczFrETlUx0cUYaXe11dRA3F80Hpt727QIwByQ==
"@next/swc-linux-arm64-musl@13.5.2":
version "13.5.2"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.5.2.tgz#56233fe5140ed437c638194f0a01a3f89821ca89"
integrity sha512-HefiwAdIygFyNmyVsQeiJp+j8vPKpIRYDlmTlF9/tLdcd3qEL/UEBswa1M7cvO8nHcr27ZTKXz5m7dkd56/Esg==
"@next/swc-freebsd-x64@12.3.4":
version "12.3.4"
resolved "https://registry.yarnpkg.com/@next/swc-freebsd-x64/-/swc-freebsd-x64-12.3.4.tgz#fe7ceec58746fdf03f1fcb37ec1331c28e76af93"
integrity sha512-KM9JXRXi/U2PUM928z7l4tnfQ9u8bTco/jb939pdFUHqc28V43Ohd31MmZD1QzEK4aFlMRaIBQOWQZh4D/E5lQ==
"@next/swc-linux-x64-gnu@13.5.2":
version "13.5.2"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.5.2.tgz#1947a9dc603e6d5d5a8e99db7d42e2240c78e713"
integrity sha512-htGVVroW0tdHgMYwKWkxWvVoG2RlAdDXRO1RQxYDvOBQsaV0nZsgKkw0EJJJ3urTYnwKskn/MXm305cOgRxD2w==
"@next/swc-linux-arm-gnueabihf@12.3.4":
version "12.3.4"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm-gnueabihf/-/swc-linux-arm-gnueabihf-12.3.4.tgz#d7016934d02bfc8bd69818ffb0ae364b77b17af7"
integrity sha512-3zqD3pO+z5CZyxtKDTnOJ2XgFFRUBciOox6EWkoZvJfc9zcidNAQxuwonUeNts6Xbm8Wtm5YGIRC0x+12YH7kw==
"@next/swc-linux-x64-musl@13.5.2":
version "13.5.2"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.5.2.tgz#83eea3985eed84fbbbb1004a555d2f093d4ed245"
integrity sha512-UBD333GxbHVGi7VDJPPDD1bKnx30gn2clifNJbla7vo5nmBV+x5adyARg05RiT9amIpda6yzAEEUu+s774ldkw==
"@next/swc-linux-arm64-gnu@12.3.4":
version "12.3.4"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-12.3.4.tgz#43a7bc409b03487bff5beb99479cacdc7bd29af5"
integrity sha512-kiX0vgJGMZVv+oo1QuObaYulXNvdH/IINmvdZnVzMO/jic/B8EEIGlZ8Bgvw8LCjH3zNVPO3mGrdMvnEEPEhKA==
"@next/swc-win32-arm64-msvc@13.5.2":
version "13.5.2"
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.5.2.tgz#c3734235e85458b76ec170dd0d6c13c2fdfac5ed"
integrity sha512-Em9ApaSFIQnWXRT3K6iFnr9uBXymixLc65Xw4eNt7glgH0eiXpg+QhjmgI2BFyc7k4ZIjglfukt9saNpEyolWA==
"@next/swc-linux-arm64-musl@12.3.4":
version "12.3.4"
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-12.3.4.tgz#4d1db6de6dc982b974cd1c52937111e3e4a34bd3"
integrity sha512-EETZPa1juczrKLWk5okoW2hv7D7WvonU+Cf2CgsSoxgsYbUCZ1voOpL4JZTOb6IbKMDo6ja+SbY0vzXZBUMvkQ==
"@next/swc-win32-ia32-msvc@13.5.2":
version "13.5.2"
resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.5.2.tgz#cf16184af9be8b8f7750833a441c116b7a76b273"
integrity sha512-TBACBvvNYU+87X0yklSuAseqdpua8m/P79P0SG1fWUvWDDA14jASIg7kr86AuY5qix47nZLEJ5WWS0L20jAUNw==
"@next/swc-linux-x64-gnu@12.3.4":
version "12.3.4"
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-12.3.4.tgz#c3b414d77bab08b35f7dd8943d5586f0adb15e38"
integrity sha512-4csPbRbfZbuWOk3ATyWcvVFdD9/Rsdq5YHKvRuEni68OCLkfy4f+4I9OBpyK1SKJ00Cih16NJbHE+k+ljPPpag==
"@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==
"@next/swc-win32-x64-msvc@13.5.2":
version "13.5.2"
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.5.2.tgz#cf8db00763d9219567655b90853b7d484f3fcad6"
integrity sha512-LfTHt+hTL8w7F9hnB3H4nRasCzLD/fP+h4/GUVBTxrkMJOnh/7OZ0XbYDKO/uuWwryJS9kZjhxcruBiYwc5UDw==
"@nodelib/fs.scandir@2.1.5":
version "2.1.5"
@ -2962,10 +3001,10 @@
magic-string "^0.25.0"
string.prototype.matchall "^4.0.6"
"@swc/helpers@0.4.11":
version "0.4.11"
resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.4.11.tgz#db23a376761b3d31c26502122f349a21b592c8de"
integrity sha512-rEUrBSGIoSFuYxwBYtlUFMlE2CwGhmW+w9355/5oduSw8e5h2+Tj4UrAGNNgP9915++wj5vkQo0UuOBqOAq4nw==
"@swc/helpers@0.5.2":
version "0.5.2"
resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.2.tgz#85ea0c76450b61ad7d10a37050289eded783c27d"
integrity sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==
dependencies:
tslib "^2.4.0"
@ -3217,24 +3256,7 @@
dependencies:
"@types/react" "*"
"@types/react-dom@*":
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@*":
"@types/react-transition-group@^4.4.0":
version "4.4.6"
resolved "https://registry.yarnpkg.com/@types/react-transition-group/-/react-transition-group-4.4.6.tgz#18187bcda5281f8e10dfc48f0943e2fdf4f75e2e"
integrity sha512-VnCdSxfcm08KjsJVQcfBmhEQAPnLB8G08hAxn39azX1qYBQ/5RVQuoHuKIcfKOdncuaUvEpFKFzEvbtIMsfVew==
@ -3250,6 +3272,15 @@
"@types/scheduler" "*"
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":
version "1.17.1"
resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.17.1.tgz#3afd6ad8967c77e4376c598a82ddd58f46ec45d6"
@ -3977,7 +4008,7 @@ bundle-name@^3.0.0:
dependencies:
run-applescript "^5.0.0"
busboy@^1.6.0:
busboy@1.6.0, busboy@^1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/busboy/-/busboy-1.6.0.tgz#966ea36a9502e43cdb9146962523b92f531f6893"
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"
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:
version "5.0.0"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5"
@ -5554,6 +5590,11 @@ glob-parent@^6.0.2:
dependencies:
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:
version "7.1.6"
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"
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:
version "4.3.0"
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"
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"
resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf"
integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==
@ -7095,10 +7129,10 @@ makeerror@1.0.12:
dependencies:
tmpl "1.0.5"
memoize-one@^5.0.0:
version "5.2.1"
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e"
integrity sha512-zYiwtZUcYyXKo/np96AGZAckk+FWWsUdJ3cHGGmld7+AhvcWmQyGCYUh1hc4Q/pkOhb65dQR/pqCyK0cOaHz4Q==
memoize-one@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-6.0.0.tgz#b2591b871ed82948aee4727dc6abceeeac8c1045"
integrity sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==
memory-pager@^1.0.2:
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"
integrity sha512-iMBpFoJsR5zWhguHJvsoBDxDSmdYTHtnVPB1ij+CD0NReQCP78ZxxbdL9qkKIf4oEuZEqZkrjAQLB0bkII7RYA==
next@^12.3.2:
version "12.3.4"
resolved "https://registry.yarnpkg.com/next/-/next-12.3.4.tgz#f2780a6ebbf367e071ce67e24bd8a6e05de2fcb1"
integrity sha512-VcyMJUtLZBGzLKo3oMxrEF0stxh8HwuW976pAzlHhI3t8qJ4SROjCrSh1T24bhrbjw55wfZXAbXPGwPt5FLRfQ==
next@^13.5.2:
version "13.5.2"
resolved "https://registry.yarnpkg.com/next/-/next-13.5.2.tgz#809dd84e481049e298fe79d28b1d66b587483fca"
integrity sha512-vog4UhUaMYAzeqfiAAmgB/QWLW7p01/sg+2vn6bqc/CxHFYizMzLv6gjxKzl31EVFkfl/F+GbxlKizlkTE9RdA==
dependencies:
"@next/env" "12.3.4"
"@swc/helpers" "0.4.11"
"@next/env" "13.5.2"
"@swc/helpers" "0.5.2"
busboy "1.6.0"
caniuse-lite "^1.0.30001406"
postcss "8.4.14"
styled-jsx "5.0.7"
use-sync-external-store "1.2.0"
styled-jsx "5.1.1"
watchpack "2.4.0"
zod "3.21.4"
optionalDependencies:
"@next/swc-android-arm-eabi" "12.3.4"
"@next/swc-android-arm64" "12.3.4"
"@next/swc-darwin-arm64" "12.3.4"
"@next/swc-darwin-x64" "12.3.4"
"@next/swc-freebsd-x64" "12.3.4"
"@next/swc-linux-arm-gnueabihf" "12.3.4"
"@next/swc-linux-arm64-gnu" "12.3.4"
"@next/swc-linux-arm64-musl" "12.3.4"
"@next/swc-linux-x64-gnu" "12.3.4"
"@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"
"@next/swc-darwin-arm64" "13.5.2"
"@next/swc-darwin-x64" "13.5.2"
"@next/swc-linux-arm64-gnu" "13.5.2"
"@next/swc-linux-arm64-musl" "13.5.2"
"@next/swc-linux-x64-gnu" "13.5.2"
"@next/swc-linux-x64-musl" "13.5.2"
"@next/swc-win32-arm64-msvc" "13.5.2"
"@next/swc-win32-ia32-msvc" "13.5.2"
"@next/swc-win32-x64-msvc" "13.5.2"
node-abort-controller@^3.0.1:
version "3.1.1"
@ -8268,7 +8300,7 @@ prompts@^2.0.1:
kleur "^3.0.3"
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"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
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"
integrity sha512-VzGMH9qA7bBKAveK1joFWphhB7e2Y1ToSGzUa8b7eit3+VSkg/2MSvKxe5d/r4mcmkOHu6CHNJW0nn6RQC3IZA==
react-dom@17.0.2:
version "17.0.2"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23"
integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==
react-dom@^18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-18.2.0.tgz#22aaf38708db2674ed9ada224ca4aa708d821e3d"
integrity sha512-6IMTriUmvsjHUjNtEDudZfuDQUoWXVxKHhlEGSk81n4YFS+r/Kl99wXiwlVXtPBtJenozv2P+hxDsw9eA7Xo6g==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
scheduler "^0.20.2"
scheduler "^0.23.0"
react-fast-compare@^2.0.1:
version "2.0.4"
@ -8419,13 +8450,6 @@ react-hotkeys@2.0.0:
dependencies:
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:
version "16.13.1"
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"
classnames "^2.3.1"
react-select@4.3.1:
version "4.3.1"
resolved "https://registry.yarnpkg.com/react-select/-/react-select-4.3.1.tgz#389fc07c9bc7cf7d3c377b7a05ea18cd7399cb81"
integrity sha512-HBBd0dYwkF5aZk1zP81Wx5UsLIIT2lSvAY2JiJo199LjoLHoivjn9//KsmvQMEFGNhe58xyuOITjfxKCcGc62Q==
react-select@^5.7.5:
version "5.7.5"
resolved "https://registry.yarnpkg.com/react-select/-/react-select-5.7.5.tgz#d2d0f29994e0f06000147bfb2adf58324926c2fd"
integrity sha512-jgYZa2xgKP0DVn5GZk7tZwbRx7kaVz1VqU41S8z1KWmshRDhlrpKS0w80aS1RaK5bVIXpttgSou7XCjWw1ncKA==
dependencies:
"@babel/runtime" "^7.12.0"
"@emotion/cache" "^11.4.0"
"@emotion/react" "^11.1.1"
memoize-one "^5.0.0"
"@emotion/react" "^11.8.1"
"@floating-ui/dom" "^1.0.1"
"@types/react-transition-group" "^4.4.0"
memoize-one "^6.0.0"
prop-types "^15.6.0"
react-input-autosize "^3.0.0"
react-transition-group "^4.3.0"
use-isomorphic-layout-effect "^1.1.2"
react-showdown@2.3.1:
version "2.3.1"
@ -8467,15 +8493,6 @@ react-showdown@2.3.1:
htmlparser2 "^6.0.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:
version "4.4.5"
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:
copy-to-clipboard "^3.3.1"
react@17.0.2:
version "17.0.2"
resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"
integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==
react@^18.2.0:
version "18.2.0"
resolved "https://registry.yarnpkg.com/react/-/react-18.2.0.tgz#555bd98592883255fa00de14f1151a917b5d77d5"
integrity sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
read-cache@^1.0.0:
version "1.0.0"
@ -8835,13 +8851,12 @@ saslprep@^1.0.3:
dependencies:
sparse-bitfield "^3.0.3"
scheduler@^0.20.2:
version "0.20.2"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.20.2.tgz#4baee39436e34aa93b4874bddcbf0fe8b8b50e91"
integrity sha512-2eWfGgAqqWFGqtdMmcL5zCMK1U8KlXv8SQFGglL3CEtd0aDVDWgeF/YoCmvln55m5zSk3J/20hTaSBeSObsQDQ==
scheduler@^0.23.0:
version "0.23.0"
resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.23.0.tgz#ba8041afc3d30eb206a487b6b384002e4e61fdfe"
integrity sha512-CtuThmgHNg7zIZWAXi3AsyIzA3n4xx7aNyjwC2VJldO2LMVDhFK+63xGqq6CsJH4rTAt6/M+N4GhZiDYPx9eUw==
dependencies:
loose-envify "^1.1.0"
object-assign "^4.1.1"
schema-utils@^2.6.5:
version "2.7.1"
@ -9296,10 +9311,12 @@ strnum@^1.0.5:
resolved "https://registry.yarnpkg.com/strnum/-/strnum-1.0.5.tgz#5c4e829fe15ad4ff0d20c3db5ac97b73c9b072db"
integrity sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==
styled-jsx@5.0.7:
version "5.0.7"
resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.0.7.tgz#be44afc53771b983769ac654d355ca8d019dff48"
integrity sha512-b3sUzamS086YLRuvnaDigdAewz1/EFYlHpYBP5mZovKEdQQOIIYq8lApylub3HHZ6xFjV051kkGU7cudJmrXEA==
styled-jsx@5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/styled-jsx/-/styled-jsx-5.1.1.tgz#839a1c3aaacc4e735fed0781b8619ea5d0009d1f"
integrity sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==
dependencies:
client-only "0.0.1"
stylis@4.2.0:
version "4.2.0"
@ -9811,10 +9828,10 @@ url-regex-safe@2.0.2:
re2 "^1.15.9"
tlds "^1.217.0"
use-sync-external-store@1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz#7dbefd6ef3fe4e767a0cf5d7287aacfb5846928a"
integrity sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==
use-isomorphic-layout-effect@^1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz#497cefb13d863d687b08477d9e5a164ad8c1a6fb"
integrity sha512-49L8yCO3iGT/ZF9QttjwLF/ZD9Iwto5LnH5LmEdk/6cFmXddqi2ulF0edxTwjj+7mqvpVVGQWvbXZdn32wRSHA==
util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1:
version "1.0.2"
@ -9855,6 +9872,14 @@ walker@^1.0.8:
dependencies:
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:
version "1.0.1"
resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8"
@ -10269,3 +10294,8 @@ yup@0.32.9:
nanoclone "^0.2.1"
property-expr "^2.0.4"
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==