feat: support pwa

This commit is contained in:
wonderlandpark 2021-05-16 22:58:21 +09:00
parent 94be30b623
commit e841ee7eee
38 changed files with 1711 additions and 69 deletions

15
.gitignore vendored
View File

@ -41,8 +41,17 @@ yarn-error.log*
# Prevent commiting lock file.
package-lock.json
secret.json
# sub module
api-docs/
api-docs/
# next-pwa
public/precache.*.*.js
public/sw.js
public/workbox-*.js
public/worker-*.js
public/fallback-*.js
public/precache.*.*.js.map
public/sw.js.map
public/workbox-*.js.map
public/worker-*.js.map
public/fallback-*.js

View File

@ -11,7 +11,7 @@ import { User, UserCache } from '@types'
import DiscordAvatar from '@components/DiscordAvatar'
import Fetch from '@utils/Fetch'
const Navbar = ({ token }:{ token: string }): JSX.Element => {
const Navbar = ({ token, pwa }:{ token: string, pwa: boolean }): JSX.Element => {
const [userCache, setUserCache] = useState<UserCache>()
const [navbarOpen, setNavbarOpen] = useState<boolean>(false)
const [dropdownOpen, setDropdownOpen] = useState<boolean>(false)
@ -124,18 +124,11 @@ const Navbar = ({ token }:{ token: string }): JSX.Element => {
</ul>
</div>
</> :
<a tabIndex={0} onKeyPress={()=> {
if(!(logged)) {
localStorage.redirectTo = window.location.href
setNavbarOpen(false)
redirectTo(router, 'login')
}
}} onClick={()=> {
if(!(logged)) {
localStorage.redirectTo = window.location.href
setNavbarOpen(false)
redirectTo(router, 'login')
}
<a tabIndex={0} onClick={()=> {
localStorage.redirectTo = window.location.href
setNavbarOpen(false)
if(pwa) window.open('/api/auth/discord')
else 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>
@ -204,15 +197,15 @@ const Navbar = ({ token }:{ token: string }): JSX.Element => {
<i className='fas fa-sign-out-alt' />
<span className='px-2 font-medium'></span>
</a>
</> : <Link href='/api/auth/discord'>
<a onClick={()=> {
localStorage.redirectTo = window.location.href
setNavbarOpen(false)
}} 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>
</Link>
</> : <a onClick={() => {
localStorage.redirectTo = window.location.href
setNavbarOpen(false)
if(pwa) window.open('/api/auth/discord')
else 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>

View File

@ -1,8 +1,12 @@
/* eslint-disable @typescript-eslint/no-var-requires */
const { withSentryConfig } = require('@sentry/nextjs')
const withPWA = require('next-pwa')
const VERSION = require('./package.json').version
module.exports = withSentryConfig({
module.exports = withSentryConfig(withPWA({
pwa: {
dest: 'public'
},
env: {
NEXT_PUBLIC_RELEASE_VERSION: VERSION,
SENTRY_SKIP_AUTO_RELEASE: true
@ -13,4 +17,4 @@ module.exports = withSentryConfig({
experimental: {
scrollRestoration: true
}
}, {})
}), {})

View File

@ -40,6 +40,7 @@
"mysql": "2.18.1",
"next": "10.2.0",
"next-connect": "0.10.1",
"next-pwa": "5.2.21",
"next-session": "3.4.0",
"node-emoji": "1.10.0",
"nprogress": "0.2.0",

View File

@ -7,7 +7,7 @@ import { GlobalHotKeys } from 'react-hotkeys'
import NProgress from 'nprogress'
import Logger from '@utils/Logger'
import { parseCookie, systemTheme } from '@utils/Tools'
import { handlePWA, parseCookie, systemTheme } from '@utils/Tools'
import { shortcutKeyMap } from '@utils/Constants'
import { Theme } from '@types'
@ -34,6 +34,7 @@ Router.events.on('routeChangeError', NProgress.done)
const KoreanbotsApp = ({ Component, pageProps, err, cookie }: KoreanbotsProps): JSX.Element => {
const [ shortcutModal, setShortcutModal ] = useState(false)
const [ theme, setTheme ] = useState<Theme>('system')
const [ standalone, setStandalone ] = useState(false)
const router = useRouter()
useEffect(() => {
@ -50,24 +51,17 @@ const KoreanbotsApp = ({ Component, pageProps, err, cookie }: KoreanbotsProps):
setTheme(systemTheme())
}
else setTheme(localStorage.theme)
setStandalone(handlePWA())
}, [])
return <div className={theme}>
<Head>
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
<meta name='viewport' content='width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no' />
<title> </title>
<meta name='description' content='다양한 국내 디스코드봇들을 확인하고, 초대해보세요!' />
<meta name='og:title' content='한국 디스코드봇 리스트' />
<meta name='og:url' content='https://koreanbots.dev' />
<meta name='og:description' content='다양한 국내 디스코드봇들을 확인하고, 초대해보세요!' />
<meta name='og:image' content='/logo.png' />
<meta charSet='utf-8' />
<link rel='shortcut icon' href='/logo.png' />
<meta name='theme-color' content='#3366FF' />
</Head>
<Navbar token={cookie.token} />
<Navbar token={cookie.token} pwa={standalone} />
<div className='iu-is-the-best min-h-screen text-black dark:text-gray-100 dark:bg-discord-dark bg-white'>
<Component {...pageProps} err={err} theme={theme} setTheme={setTheme} />
<Component {...pageProps} err={err} theme={theme} setTheme={setTheme} pwa={standalone} />
</div>
{
!(router.pathname.startsWith('/developers')) && <Footer theme={theme} setTheme={setTheme} />

View File

@ -10,11 +10,36 @@ class MyDocument extends Document {
return (
<Html>
<Head>
<meta charSet='utf-8' />
<meta httpEquiv='X-UA-Compatible' content='IE=edge' />
<meta name='description' content='다양한 국내 디스코드봇들을 확인하고, 초대해보세요!' />
<meta name='og:title' content='한국 디스코드봇 리스트' />
<meta name='og:url' content='https://koreanbots.dev' />
<meta name='og:description' content='다양한 국내 디스코드봇들을 확인하고, 초대해보세요!' />
<meta name='og:image' content='/favicon.ico' />
<link rel='shortcut icon' href='/favicon.ico' />
<link
rel='stylesheet'
href='//cdnjs.cloudflare.com/ajax/libs/highlight.js/10.6.0/styles/solarized-dark.min.css'
/>
<link rel='search' type='application/opensearchdescription+xml' title='한국 디스코드봇 리스트' href='/opensearch.xml' />
<link rel='apple-touch-icon' sizes='57x57' href='/apple-icon-57x57.png' />
<link rel='apple-touch-icon' sizes='60x60' href='/apple-icon-60x60.png' />
<link rel='apple-touch-icon' sizes='72x72' href='/apple-icon-72x72.png' />
<link rel='apple-touch-icon' sizes='76x76' href='/apple-icon-76x76.png' />
<link rel='apple-touch-icon' sizes='114x114' href='/apple-icon-114x114.png' />
<link rel='apple-touch-icon' sizes='120x120' href='/apple-icon-120x120.png' />
<link rel='apple-touch-icon' sizes='144x144' href='/apple-icon-144x144.png' />
<link rel='apple-touch-icon' sizes='152x152' href='/apple-icon-152x152.png' />
<link rel='apple-touch-icon' sizes='180x180' href='/apple-icon-180x180.png' />
<link rel='icon' type='image/png' sizes='192x192' href='/android-icon-192x192.png' />
<link rel='icon' type='image/png' sizes='32x32' href='/favicon-32x32.png' />
<link rel='icon' type='image/png' sizes='96x96' href='/favicon-96x96.png' />
<link rel='icon' type='image/png' sizes='16x16' href='/favicon-16x16.png' />
<link rel='manifest' href='/manifest.json' />
<meta name='msapplication-TileColor' content='#3366FF' />
<meta name='msapplication-TileImage' content='/ms-icon-144x144.png' />
<meta name='theme-color' content='#3366FF' />
<script src='//cdnjs.cloudflare.com/ajax/libs/highlight.js/10.5.0/highlight.min.js'></script>
<script
data-ad-client='ca-pub-4856582423981759'

29
pages/_offline.tsx Normal file
View File

@ -0,0 +1,29 @@
import { NextPage } from 'next'
import Link from 'next/link'
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'>
<i className='fab fa-discord' />
</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>
}
export default MyError

View File

@ -1,7 +1,7 @@
import { NextPage} from 'next'
import dynamic from 'next/dynamic'
import { useRouter } from 'next/router'
import { useEffect } from 'react'
import { useEffect, useState } from 'react'
import { redirectTo } from '@utils/Tools'
@ -9,13 +9,19 @@ const Loader = dynamic(() => import('@components/Loader'))
const DiscordCallback:NextPage = () => {
const router = useRouter()
const [ notRedirecting, setNotRedirecting ] = useState(false)
useEffect(() => {
redirectTo(router, localStorage.redirectTo ?? '/')
localStorage.removeItem('redirectTo')
if(window.opener) {
setNotRedirecting(true)
window.opener.location.reload()
} else {
redirectTo(router, localStorage.redirectTo ?? '/')
localStorage.removeItem('redirectTo')
}
}, [router])
return <>
<Loader text={<> .</>} />
<Loader text={notRedirecting ? '해당 창을 닫고 원래 앱으로 돌아가주세요.' : '리다이렉트 중 입니다.'} />
</>
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

BIN
public/apple-icon-57x57.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

BIN
public/apple-icon-60x60.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

BIN
public/apple-icon-72x72.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

BIN
public/apple-icon-76x76.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

BIN
public/apple-icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

2
public/browserconfig.xml Normal file
View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig><msapplication><tile><square70x70logo src="/ms-icon-70x70.png"/><square150x150logo src="/ms-icon-150x150.png"/><square310x310logo src="/ms-icon-310x310.png"/><TileColor>#ffffff</TileColor></tile></msapplication></browserconfig>

BIN
public/favicon-16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

BIN
public/favicon-32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
public/favicon-96x96.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

BIN
public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

47
public/manifest.json Normal file
View File

@ -0,0 +1,47 @@
{
"name": "한국 디스코드봇 리스트",
"short_name": "한국 디스코드봇 리스트",
"lang": "ko-KR",
"description": "다양한 국내 디스코드봇들을 확인하고, 초대해보세요!",
"start_url": "/",
"theme_color": "#3366FF",
"display": "standalone",
"icons": [
{
"src": "\/android-icon-36x36.png",
"sizes": "36x36",
"type": "image\/png",
"density": "0.75"
},
{
"src": "\/android-icon-48x48.png",
"sizes": "48x48",
"type": "image\/png",
"density": "1.0"
},
{
"src": "\/android-icon-72x72.png",
"sizes": "72x72",
"type": "image\/png",
"density": "1.5"
},
{
"src": "\/android-icon-96x96.png",
"sizes": "96x96",
"type": "image\/png",
"density": "2.0"
},
{
"src": "\/android-icon-144x144.png",
"sizes": "144x144",
"type": "image\/png",
"density": "3.0"
},
{
"src": "\/android-icon-192x192.png",
"sizes": "192x192",
"type": "image\/png",
"density": "4.0"
}
]
}

BIN
public/ms-icon-144x144.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

BIN
public/ms-icon-150x150.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.5 KiB

BIN
public/ms-icon-310x310.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
public/ms-icon-70x70.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 KiB

1
types/global.d.ts vendored
View File

@ -2,6 +2,7 @@ import * as Yup from 'yup'
declare global {
interface Window {
ga(a: string, b: string, c: string): void
hljs: {
initHighlighting(): void
highlightBlock(e: Element): void

View File

@ -6,6 +6,16 @@ import { BotFlags, ImageOptions, UserFlags } from '@types'
import { BASE_URLs, KoreanbotsEndPoints, Oauth } from './Constants'
import { NextRouter } from 'next/router'
export function handlePWA(): boolean {
let displayMode = 'browser'
const mqStandAlone = '(display-mode: standalone)'
if (window.navigator.standalone || window.matchMedia(mqStandAlone).matches) {
displayMode = 'standalone'
}
window?.ga('set', 'dimension1', displayMode)
return displayMode === 'standalone'
}
export function formatNumber(value: number):string {
const suffixes = ['', '만', '억', '조','해']
const suffixNum = Math.floor((''+value).length/4)

1573
yarn.lock

File diff suppressed because it is too large Load Diff