fix: router instance called at serverside while rendering

This commit is contained in:
wonderlandpark 2021-05-06 09:14:48 +09:00
parent 7ba2096c31
commit dcdd7e0710
No known key found for this signature in database
GPG Key ID: E3E650B146478C64
10 changed files with 60 additions and 61 deletions

16
components/Login.tsx Normal file
View File

@ -0,0 +1,16 @@
import { redirectTo } from '@utils/Tools'
import { useRouter } from 'next/router'
import { useEffect } from 'react'
const Login: React.FC = ({ children }) => {
const router = useRouter()
useEffect(() => {
localStorage.redirectTo = window.location.href
redirectTo(router, 'login')
})
return <>
{children}
</>
}
export default Login

View File

@ -1,4 +1,4 @@
import { ReactNode } from 'react' import { ReactNode, useEffect } from 'react'
import dynamic from 'next/dynamic' import dynamic from 'next/dynamic'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
@ -6,22 +6,25 @@ import { redirectTo } from '@utils/Tools'
const Container = dynamic(() => import('@components/Container')) const Container = dynamic(() => import('@components/Container'))
const Redirect = ({ to, children }:RedirectProps):JSX.Element => { const Redirect = ({ to, text=true, children }:RedirectProps):JSX.Element => {
const router = useRouter() const router = useRouter()
if(!to) throw new Error('No Link') if(!to) throw new Error('No Link')
redirectTo(router, to) useEffect(() => {
redirectTo(router, to)
}, [])
if(children) return <> if(children) return <>
{children} {children}
</> </>
return <Container paddingTop> return <Container paddingTop>
<div> <div>
<a href={to} className='text-blue-400'> .</a> <a href={to} className='text-blue-400'>{text && '자동으로 리다이렉트되지 않는다면 클릭해세요.'}</a>
</div> </div>
</Container> </Container>
} }
interface RedirectProps { interface RedirectProps {
to: string to: string
text?: boolean
children?: ReactNode children?: ReactNode
} }

View File

@ -35,6 +35,7 @@ const Button = dynamic(() => import('@components/Button'))
const TextArea = dynamic(() => import('@components/Form/TextArea')) const TextArea = dynamic(() => import('@components/Form/TextArea'))
const Modal = dynamic(() => import('@components/Modal')) const Modal = dynamic(() => import('@components/Modal'))
const NSFW = dynamic(() => import('@components/NSFW')) const NSFW = dynamic(() => import('@components/NSFW'))
const Login = dynamic(() => import('@components/Login'))
const Bots: NextPage<BotsProps> = ({ data, desc, date, user, theme, csrfToken }) => { const Bots: NextPage<BotsProps> = ({ data, desc, date, user, theme, csrfToken }) => {
const bg = checkBotFlag(data?.flags, 'trusted') && data?.banner const bg = checkBotFlag(data?.flags, 'trusted') && data?.banner
@ -42,10 +43,6 @@ const Bots: NextPage<BotsProps> = ({ data, desc, date, user, theme, csrfToken })
const [ nsfw, setNSFW ] = useState<boolean>() const [ nsfw, setNSFW ] = useState<boolean>()
const [ reportModal, setReportModal ] = useState(false) const [ reportModal, setReportModal ] = useState(false)
const [ reportRes, setReportRes ] = useState<ResponseProps<null>>(null) const [ reportRes, setReportRes ] = useState<ResponseProps<null>>(null)
function toLogin() {
localStorage.redirectTo = window.location.href
redirectTo(router, 'login')
}
useEffect(() => { useEffect(() => {
setNSFW(localStorage.nsfw) setNSFW(localStorage.nsfw)
}, []) }, [])
@ -188,7 +185,7 @@ const Bots: NextPage<BotsProps> = ({ data, desc, date, user, theme, csrfToken })
))} ))}
<div className='list grid'> <div className='list grid'>
<a className='text-red-600 hover:underline cursor-pointer' onClick={() => { <a className='text-red-600 hover:underline cursor-pointer' onClick={() => {
if(!user) toLogin() if(!user) return <Login />
else setReportModal(true) else setReportModal(true)
}} aria-hidden='true'> }} aria-hidden='true'>
<i className='far fa-flag' /> <i className='far fa-flag' />

View File

@ -5,7 +5,7 @@ import { useRouter } from 'next/router'
import { Bot, CsrfContext, ResponseProps, Theme, User } from '@types' import { Bot, CsrfContext, ResponseProps, Theme, User } from '@types'
import { get } from '@utils/Query' import { get } from '@utils/Query'
import { makeBotURL, parseCookie, checkBotFlag, redirectTo } from '@utils/Tools' import { makeBotURL, parseCookie, checkBotFlag } from '@utils/Tools'
import { ParsedUrlQuery } from 'querystring' import { ParsedUrlQuery } from 'querystring'
@ -16,6 +16,7 @@ import { useState } from 'react'
import Fetch from '@utils/Fetch' import Fetch from '@utils/Fetch'
import Day from '@utils/Day' import Day from '@utils/Day'
import { getJosaPicker } from 'josa' import { getJosaPicker } from 'josa'
import { KoreanbotsEndPoints } from '@utils/Constants'
const Container = dynamic(() => import('@components/Container')) const Container = dynamic(() => import('@components/Container'))
@ -25,6 +26,7 @@ const Tag = dynamic(() => import('@components/Tag'))
const Segment = dynamic(() => import('@components/Segment')) const Segment = dynamic(() => import('@components/Segment'))
const SEO = dynamic(() => import('@components/SEO')) const SEO = dynamic(() => import('@components/SEO'))
const Advertisement = dynamic(() => import('@components/Advertisement')) const Advertisement = dynamic(() => import('@components/Advertisement'))
const Login = dynamic(() => import('@components/Login'))
const VoteBot: NextPage<VoteBotProps> = ({ data, user, csrfToken, theme }) => { const VoteBot: NextPage<VoteBotProps> = ({ data, user, csrfToken, theme }) => {
const [ votingStatus, setVotingStatus ] = useState(0) const [ votingStatus, setVotingStatus ] = useState(0)
@ -35,18 +37,13 @@ const VoteBot: NextPage<VoteBotProps> = ({ data, user, csrfToken, theme }) => {
router.push(`/bots/${data.id}`) router.push(`/bots/${data.id}`)
return <></> return <></>
} }
if(!user) { if(!user) return <Login>
localStorage.redirectTo = window.location.href <SEO title={data.name} description={`한국 디스코드봇 리스트에서 ${data.name}에 투표하세요`} image={KoreanbotsEndPoints.CDN.avatar(data.id, { format: 'png', size: 256 })} />
redirectTo(router, 'login') </Login>
return <SEO title='새로운 봇 추가하기' description='자신의 봇을 한국 디스코드봇 리스트에 등록하세요.'/>
}
if((checkBotFlag(data.flags, 'trusted') || checkBotFlag(data.flags, 'partnered')) && data.vanity && data.vanity !== router.query.id) router.push(`/bots/${data.vanity}/vote?csrfToken=${csrfToken}`) if((checkBotFlag(data.flags, 'trusted') || checkBotFlag(data.flags, 'partnered')) && data.vanity && data.vanity !== router.query.id) router.push(`/bots/${data.vanity}/vote?csrfToken=${csrfToken}`)
return <Container paddingTop className='py-10'> return <Container paddingTop className='py-10'>
<SEO title={data.name} description={`한국 디스코드봇 리스트에서 ${data.name}에 투표하세요`} image={ <SEO title={data.name} description={`한국 디스코드봇 리스트에서 ${data.name}에 투표하세요`} image={KoreanbotsEndPoints.CDN.avatar(data.id, { format: 'png', size: 256 })} />
data.avatar
? `https://cdn.discordapp.com/avatars/${data.id}/${data.avatar}.png?size=1024`
: `https://cdn.discordapp.com/embed/avatars/${Number(data.tag) % 5}.png?size=1024`
} />
<Advertisement /> <Advertisement />
<Link href={makeBotURL(data)}> <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> <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>

View File

@ -1,22 +1,20 @@
import { NextPage, NextPageContext } from 'next' import { NextPage, NextPageContext } from 'next'
import dynamic from 'next/dynamic' import dynamic from 'next/dynamic'
import { useRouter } from 'next/router'
import { get } from '@utils/Query' import { get } from '@utils/Query'
import { parseCookie, redirectTo } from '@utils/Tools' import { parseCookie } from '@utils/Tools'
import { Bot, User } from '@types' import { Bot, User } from '@types'
const Application = dynamic(() => import('@components/Application')) const Application = dynamic(() => import('@components/Application'))
const DeveloperLayout = dynamic(() => import('@components/DeveloperLayout')) const DeveloperLayout = dynamic(() => import('@components/DeveloperLayout'))
const SEO = dynamic(() => import('@components/SEO'))
const Login = dynamic(() => import('@components/Login'))
const Applications: NextPage<ApplicationsProps> = ({ user }) => { const Applications: NextPage<ApplicationsProps> = ({ user }) => {
const router = useRouter() if(!user) return <Login>
if(!user) { <SEO title='한디리 개발자' description='한국 디스코드봇 리스트 API를 활용하여 봇에 다양한 기능을 추가해보세요.' />
localStorage.redirectTo = window.location.href </Login>
redirectTo(router, 'login')
return
}
return <DeveloperLayout enabled='applications'> return <DeveloperLayout enabled='applications'>
<h1 className='text-3xl font-bold'> </h1> <h1 className='text-3xl font-bold'> </h1>
<p className='text-gray-400'> API를 .</p> <p className='text-gray-400'> API를 .</p>

View File

@ -35,6 +35,7 @@ const Message = dynamic(() => import('@components/Message'))
const Modal = dynamic(() => import('@components/Modal')) const Modal = dynamic(() => import('@components/Modal'))
const Captcha = dynamic(() => import('@components/Captcha')) const Captcha = dynamic(() => import('@components/Captcha'))
const SEO = dynamic(() => import('@components/SEO')) const SEO = dynamic(() => import('@components/SEO'))
const Login = dynamic(() => import('@components/Login'))
const ManageBotPage:NextPage<ManageBotProps> = ({ bot, user, csrfToken, theme }) => { const ManageBotPage:NextPage<ManageBotProps> = ({ bot, user, csrfToken, theme }) => {
const [ data, setData ] = useState(null) const [ data, setData ] = useState(null)
@ -42,10 +43,6 @@ const ManageBotPage:NextPage<ManageBotProps> = ({ bot, user, csrfToken, theme })
const [ transferModal, setTransferModal ] = useState(false) const [ transferModal, setTransferModal ] = useState(false)
const [ deleteModal, setDeleteModal ] = useState(false) const [ deleteModal, setDeleteModal ] = useState(false)
const router = useRouter() const router = useRouter()
function toLogin() {
localStorage.redirectTo = window.location.href
redirectTo(router, 'login')
}
async function submitBot(value: ManageBot) { async function submitBot(value: ManageBot) {
const res = await Fetch(`/bots/${bot.id}`, { method: 'PATCH', body: JSON.stringify(cleanObject<ManageBot>(value)) }) const res = await Fetch(`/bots/${bot.id}`, { method: 'PATCH', body: JSON.stringify(cleanObject<ManageBot>(value)) })
@ -59,10 +56,9 @@ const ManageBotPage:NextPage<ManageBotProps> = ({ bot, user, csrfToken, theme })
} }
if(!bot) return <NotFound /> if(!bot) return <NotFound />
if(!user) { if(!user) return <Login>
toLogin() <SEO title='봇 정보 수정하기' description='봇의 정보를 수정합니다.'/>
return <SEO title='봇 정보 수정하기' description='봇의 정보를 수정합니다.'/> </Login>
}
if(!(bot.owners as User[]).find(el => el.id === user.id) && !checkUserFlag(user.flags, 'staff')) return <Forbidden /> if(!(bot.owners as User[]).find(el => el.id === user.id) && !checkUserFlag(user.flags, 'staff')) return <Forbidden />
return <Container paddingTop className='pt-5 pb-10'> return <Container paddingTop className='pt-5 pb-10'>
<SEO title={`${bot.name} 수정하기`} description='봇의 정보를 수정합니다.'/> <SEO title={`${bot.name} 수정하기`} description='봇의 정보를 수정합니다.'/>

View File

@ -4,7 +4,7 @@ import dynamic from 'next/dynamic'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import { get } from '@utils/Query' import { get } from '@utils/Query'
import { parseCookie, redirectTo } from '@utils/Tools' import { parseCookie } from '@utils/Tools'
import { Bot, SubmittedBot, User } from '@types' import { Bot, SubmittedBot, User } from '@types'
import Fetch from '@utils/Fetch' import Fetch from '@utils/Fetch'
import { getToken } from '@utils/Csrf' import { getToken } from '@utils/Csrf'
@ -15,18 +15,14 @@ const ResponsiveGrid = dynamic(() => import('@components/ResponsiveGrid'))
const Button = dynamic(() => import('@components/Button')) const Button = dynamic(() => import('@components/Button'))
const BotCard = dynamic(() => import('@components/BotCard')) const BotCard = dynamic(() => import('@components/BotCard'))
const SubmittedBotCard = dynamic(() => import('@components/SubmittedBotCard')) const SubmittedBotCard = dynamic(() => import('@components/SubmittedBotCard'))
const Login = dynamic(() => import('@components/Login'))
const Panel:NextPage<PanelProps> = ({ logged, user, submits, csrfToken }) => { const Panel:NextPage<PanelProps> = ({ logged, user, submits, csrfToken }) => {
const router = useRouter() const router = useRouter()
const [ submitLimit, setSubmitLimit ] = useState(8) const [ submitLimit, setSubmitLimit ] = useState(8)
function toLogin() { if(!logged) return <Login>
localStorage.redirectTo = window.location.href <SEO title='관리 패널' />
redirectTo(router, 'login') </Login>
}
if(!logged) {
toLogin()
return <SEO title='관리 패널' />
}
return <Container paddingTop className='pt-5 pb-10'> return <Container paddingTop className='pt-5 pb-10'>
<SEO title='관리 패널' /> <SEO title='관리 패널' />
<h1 className='text-4xl font-bold'> </h1> <h1 className='text-4xl font-bold'> </h1>

View File

@ -1,12 +1,10 @@
import { NextPage, NextPageContext } from 'next' import { NextPage, NextPageContext } from 'next'
import dynamic from 'next/dynamic' import dynamic from 'next/dynamic'
import { useRouter } from 'next/router'
import { ParsedUrlQuery } from 'querystring' import { ParsedUrlQuery } from 'querystring'
import { BotList } from '@types' import { BotList } from '@types'
import { get } from '@utils/Query' import { get } from '@utils/Query'
import { SearchQuerySchema } from '@utils/Yup' import { SearchQuerySchema } from '@utils/Yup'
import { redirectTo } from '@utils/Tools'
const Hero = dynamic(() => import('@components/Hero')) const Hero = dynamic(() => import('@components/Hero'))
@ -16,13 +14,10 @@ const BotCard = dynamic(() => import('@components/BotCard'))
const Container = dynamic(() => import('@components/Container')) const Container = dynamic(() => import('@components/Container'))
const ResponsiveGrid = dynamic(() => import('@components/ResponsiveGrid')) const ResponsiveGrid = dynamic(() => import('@components/ResponsiveGrid'))
const Paginator = dynamic(() => import('@components/Paginator')) const Paginator = dynamic(() => import('@components/Paginator'))
const Redirect = dynamic(() => import('@components/Redirect'))
const Search:NextPage<SearchProps> = ({ data, query }) => { const Search:NextPage<SearchProps> = ({ data, query }) => {
const router = useRouter() if(!query?.q) return <Redirect text={false} to='/' />
if(!query?.q) {
redirectTo(router, '/')
return <></>
}
return <> return <>
<Hero header={`"${query.q}" 검색 결과`} description={`'${query.q}' 에 대한 검색 결과입니다.`} /> <Hero header={`"${query.q}" 검색 결과`} description={`'${query.q}' 에 대한 검색 결과입니다.`} />
<SEO title={`"${query.q}" 검색 결과`} description={`'${query.q}' 에 대한 검색 결과입니다.`} /> <SEO title={`"${query.q}" 검색 결과`} description={`'${query.q}' 에 대한 검색 결과입니다.`} />
@ -46,6 +41,11 @@ const Search:NextPage<SearchProps> = ({ data, query }) => {
} }
export const getServerSideProps = async(ctx: Context) => { export const getServerSideProps = async(ctx: Context) => {
if(!ctx.query?.q) {
ctx.res.statusCode = 301
ctx.res.setHeader('Location', '/')
return { props: {} }
}
let data: BotList let data: BotList
if(!ctx.query.page) ctx.query.page = '1' if(!ctx.query.page) ctx.query.page = '1'
const validate = await SearchQuerySchema.validate(ctx.query).then(el => el).catch(() => null) const validate = await SearchQuerySchema.validate(ctx.query).then(el => el).catch(() => null)

View File

@ -1,6 +1,5 @@
import { NextPage, NextPageContext } from 'next' import { NextPage, NextPageContext } from 'next'
import { useState } from 'react' import { useState } from 'react'
import { useRouter } from 'next/router'
import dynamic from 'next/dynamic' import dynamic from 'next/dynamic'
import { SnowflakeUtil } from 'discord.js' import { SnowflakeUtil } from 'discord.js'
import { ParsedUrlQuery } from 'querystring' import { ParsedUrlQuery } from 'querystring'
@ -9,7 +8,7 @@ import { Field, Form, Formik } from 'formik'
import { Bot, User, ResponseProps, Theme } from '@types' import { Bot, User, ResponseProps, Theme } from '@types'
import { get } from '@utils/Query' import { get } from '@utils/Query'
import { checkUserFlag, parseCookie, redirectTo } from '@utils/Tools' import { checkUserFlag, parseCookie } from '@utils/Tools'
import { getToken } from '@utils/Csrf' import { getToken } from '@utils/Csrf'
import Fetch from '@utils/Fetch' import Fetch from '@utils/Fetch'
import { ReportSchema } from '@utils/Yup' import { ReportSchema } from '@utils/Yup'
@ -30,15 +29,11 @@ const Message = dynamic(() => import('@components/Message'))
const Modal = dynamic(() => import('@components/Modal')) const Modal = dynamic(() => import('@components/Modal'))
const Button = dynamic(() => import('@components/Button')) const Button = dynamic(() => import('@components/Button'))
const TextArea = dynamic(() => import('@components/Form/TextArea')) const TextArea = dynamic(() => import('@components/Form/TextArea'))
const Login = dynamic(() => import('@components/Login'))
const Users: NextPage<UserProps> = ({ user, data, csrfToken, theme }) => { const Users: NextPage<UserProps> = ({ user, data, csrfToken, theme }) => {
const router = useRouter()
const [ reportModal, setReportModal ] = useState(false) const [ reportModal, setReportModal ] = useState(false)
const [ reportRes, setReportRes ] = useState<ResponseProps<null>>(null) const [ reportRes, setReportRes ] = useState<ResponseProps<null>>(null)
function toLogin() {
localStorage.redirectTo = window.location.href
redirectTo(router, 'login')
}
if (!data?.id) return <NotFound /> if (!data?.id) return <NotFound />
return ( return (
<Container paddingTop className='py-10'> <Container paddingTop className='py-10'>
@ -92,7 +87,7 @@ const Users: NextPage<UserProps> = ({ user, data, csrfToken, theme }) => {
)} )}
<div className='list-none mt-2'> <div className='list-none mt-2'>
<a className='text-red-600 hover:underline cursor-pointer' onClick={() => { <a className='text-red-600 hover:underline cursor-pointer' onClick={() => {
if(!user) toLogin() if(!user) return <Login />
else setReportModal(true) else setReportModal(true)
}} aria-hidden='true'> }} aria-hidden='true'>
<i className='far fa-flag' /> <i className='far fa-flag' />

View File

@ -103,7 +103,8 @@ export function bufferToStream(binary: Buffer) {
return readableInstanceStream return readableInstanceStream
} }
export function parseCookie(req: { headers: { cookie?: string }}): { [key: string]: string } { export function parseCookie(req?: { headers: { cookie?: string }}): { [key: string]: string } {
if(!req) return {}
return cookie.parse(req.headers.cookie || '') return cookie.parse(req.headers.cookie || '')
} }