Merge branch 'staging' into shields-manabelly
This commit is contained in:
commit
749ed4a7c3
42 changed files with 1188 additions and 722 deletions
|
|
@ -1,98 +0,0 @@
|
||||||
.About.DialogContent {
|
|
||||||
gap: 0;
|
|
||||||
padding-bottom: $unit;
|
|
||||||
|
|
||||||
.content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: $unit-2x;
|
|
||||||
padding: 0 $unit-4x;
|
|
||||||
}
|
|
||||||
|
|
||||||
.sections {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: $unit-2x;
|
|
||||||
}
|
|
||||||
|
|
||||||
section {
|
|
||||||
margin-bottom: $unit;
|
|
||||||
|
|
||||||
& > h2 {
|
|
||||||
font-size: $font-medium;
|
|
||||||
font-weight: $medium;
|
|
||||||
margin-bottom: $unit * 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
color: var(--text-secondary);
|
|
||||||
font-size: $font-regular;
|
|
||||||
line-height: 1.3;
|
|
||||||
margin-bottom: $unit;
|
|
||||||
|
|
||||||
&:last-of-type {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.Links {
|
|
||||||
display: grid;
|
|
||||||
gap: $unit;
|
|
||||||
margin: $unit-2x 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.LinkItem {
|
|
||||||
margin-top: $unit-2x;
|
|
||||||
}
|
|
||||||
|
|
||||||
.LinkItem {
|
|
||||||
$diameter: $unit-6x;
|
|
||||||
border: 1px solid var(--link-item-bg);
|
|
||||||
border-radius: $card-corner;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--link-item-bg);
|
|
||||||
|
|
||||||
svg {
|
|
||||||
fill: var(--link-item-image-color-hover);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
display: flex;
|
|
||||||
padding: $unit-2x;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Left {
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
gap: $unit-2x;
|
|
||||||
flex-grow: 1;
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-weight: 600;
|
|
||||||
max-width: 70%;
|
|
||||||
line-height: 1.3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
svg {
|
|
||||||
fill: var(--link-item-image-color);
|
|
||||||
width: $diameter;
|
|
||||||
height: auto;
|
|
||||||
|
|
||||||
&.ShareIcon {
|
|
||||||
width: $unit-4x;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-weight: $bold;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,196 +0,0 @@
|
||||||
import React from 'react'
|
|
||||||
import Link from 'next/link'
|
|
||||||
import { useTranslation } from 'next-i18next'
|
|
||||||
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogClose,
|
|
||||||
DialogTitle,
|
|
||||||
DialogTrigger,
|
|
||||||
} from '~components/Dialog'
|
|
||||||
import DialogContent from '~components/DialogContent'
|
|
||||||
|
|
||||||
import CrossIcon from '~public/icons/Cross.svg'
|
|
||||||
import ShareIcon from '~public/icons/Share.svg'
|
|
||||||
import DiscordIcon from '~public/icons/discord.svg'
|
|
||||||
import GithubIcon from '~public/icons/github.svg'
|
|
||||||
|
|
||||||
import './index.scss'
|
|
||||||
|
|
||||||
const AboutModal = () => {
|
|
||||||
const { t } = useTranslation('common')
|
|
||||||
const headerRef = React.createRef<HTMLDivElement>()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog>
|
|
||||||
<DialogTrigger asChild>
|
|
||||||
<li className="MenuItem">
|
|
||||||
<span>{t('modals.about.title')}</span>
|
|
||||||
</li>
|
|
||||||
</DialogTrigger>
|
|
||||||
<DialogContent
|
|
||||||
className="About"
|
|
||||||
headerref={headerRef}
|
|
||||||
onOpenAutoFocus={(event) => event.preventDefault()}
|
|
||||||
onEscapeKeyDown={() => {}}
|
|
||||||
>
|
|
||||||
<div className="DialogHeader" ref={headerRef}>
|
|
||||||
<DialogTitle className="DialogTitle">{t('menu.about')}</DialogTitle>
|
|
||||||
<DialogClose className="DialogClose" asChild>
|
|
||||||
<span>
|
|
||||||
<CrossIcon />
|
|
||||||
</span>
|
|
||||||
</DialogClose>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="content">
|
|
||||||
<section>
|
|
||||||
<p>
|
|
||||||
Granblue.team is a tool to save and share team comps for{' '}
|
|
||||||
<a
|
|
||||||
href="https://game.granbluefantasy.jp"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
Granblue Fantasy
|
|
||||||
</a>
|
|
||||||
.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Start adding to a team and a URL will be created for you to share
|
|
||||||
wherever you like, no account needed.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
However, if you do make an account, you can save any teams you
|
|
||||||
find for future reference and keep all of your teams together in
|
|
||||||
one place.
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<h2>Feedback</h2>
|
|
||||||
<p>
|
|
||||||
This is an evolving project so feedback and suggestions are
|
|
||||||
greatly appreciated!
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
If you have a feature request, would like to report a bug, or are
|
|
||||||
enjoying the tool and want to say thanks, come hang out in
|
|
||||||
Discord!
|
|
||||||
</p>
|
|
||||||
<div className="LinkItem">
|
|
||||||
<Link href="https://discord.gg/qyZ5hGdPC8">
|
|
||||||
<a
|
|
||||||
href="https://discord.gg/qyZ5hGdPC8"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
<div className="Left">
|
|
||||||
<DiscordIcon />
|
|
||||||
<h3>granblue-tools</h3>
|
|
||||||
</div>
|
|
||||||
<ShareIcon className="ShareIcon" />
|
|
||||||
</a>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<h2>Credits</h2>
|
|
||||||
<p>
|
|
||||||
Granblue.team was built by{' '}
|
|
||||||
<a
|
|
||||||
href="https://twitter.com/jedmund"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
@jedmund
|
|
||||||
</a>{' '}
|
|
||||||
with a lot of help from{' '}
|
|
||||||
<a
|
|
||||||
href="https://twitter.com/lalalalinna"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
@lalalalinna
|
|
||||||
</a>{' '}
|
|
||||||
and{' '}
|
|
||||||
<a
|
|
||||||
href="https://twitter.com/tarngerine"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
@tarngerine
|
|
||||||
</a>
|
|
||||||
.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Many thanks also go to Disinfect, Slipper, Jif, Bless, 9highwind,
|
|
||||||
and everyone else in{' '}
|
|
||||||
<a
|
|
||||||
href="https://game.granbluefantasy.jp/#guild/detail/1190185"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
Fireplace
|
|
||||||
</a>{' '}
|
|
||||||
that helped with bug testing and feature requests. (P.S.
|
|
||||||
We're recruiting!) And yoey, but he won't join our crew.
|
|
||||||
</p>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section>
|
|
||||||
<h2>Contributing</h2>
|
|
||||||
<p>
|
|
||||||
This app is open source and licensed under{' '}
|
|
||||||
<a
|
|
||||||
href="https://choosealicense.com/licenses/agpl-3.0/"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
GNU AGPLv3
|
|
||||||
</a>
|
|
||||||
. Plainly, that means you can download the source, modify it, and
|
|
||||||
redistribute it if you attribute this project, use the same
|
|
||||||
license, and keep it open source. You can contribute on Github.
|
|
||||||
</p>
|
|
||||||
<ul className="Links">
|
|
||||||
<li className="LinkItem">
|
|
||||||
<Link href="https://github.com/jedmund/hensei-api">
|
|
||||||
<a
|
|
||||||
href="https://github.com/jedmund/hensei-api"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
<div className="Left">
|
|
||||||
<GithubIcon />
|
|
||||||
<h3>jedmund/hensei-api</h3>
|
|
||||||
</div>
|
|
||||||
<ShareIcon className="ShareIcon" />
|
|
||||||
</a>
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
<li className="LinkItem">
|
|
||||||
<Link href="https://github.com/jedmund/hensei-web">
|
|
||||||
<a
|
|
||||||
href="https://github.com/jedmund/hensei-web"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
<div className="Left">
|
|
||||||
<GithubIcon />
|
|
||||||
<h3>jedmund/hensei-web</h3>
|
|
||||||
</div>
|
|
||||||
<ShareIcon className="ShareIcon" />
|
|
||||||
</a>
|
|
||||||
</Link>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default AboutModal
|
|
||||||
11
components/AboutPage/index.scss
Normal file
11
components/AboutPage/index.scss
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
.About.PageContent {
|
||||||
|
.Links {
|
||||||
|
display: grid;
|
||||||
|
gap: $unit;
|
||||||
|
margin: $unit-2x 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
div.LinkItem {
|
||||||
|
margin-top: $unit-2x;
|
||||||
|
}
|
||||||
|
}
|
||||||
165
components/AboutPage/index.tsx
Normal file
165
components/AboutPage/index.tsx
Normal file
|
|
@ -0,0 +1,165 @@
|
||||||
|
import React from 'react'
|
||||||
|
import Link from 'next/link'
|
||||||
|
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import { useTranslation } from 'next-i18next'
|
||||||
|
|
||||||
|
import ShareIcon from '~public/icons/Share.svg'
|
||||||
|
import DiscordIcon from '~public/icons/discord.svg'
|
||||||
|
import GithubIcon from '~public/icons/github.svg'
|
||||||
|
|
||||||
|
import './index.scss'
|
||||||
|
|
||||||
|
interface Props {}
|
||||||
|
|
||||||
|
const AboutPage: React.FC<Props> = (props: Props) => {
|
||||||
|
const { t: common } = useTranslation('common')
|
||||||
|
return (
|
||||||
|
<div className="About PageContent">
|
||||||
|
<h1>{common('about.segmented_control.about')}</h1>
|
||||||
|
<section>
|
||||||
|
<p>
|
||||||
|
Granblue.team is a tool to save and share team comps for{' '}
|
||||||
|
<a
|
||||||
|
href="https://game.granbluefantasy.jp"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
Granblue Fantasy
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Start adding to a team and a URL will be created for you to share
|
||||||
|
wherever you like, no account needed.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
However, if you do make an account, you can save any teams you find
|
||||||
|
for future reference and keep all of your teams together in one place.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Feedback</h2>
|
||||||
|
<p>
|
||||||
|
This is an evolving project so feedback and suggestions are greatly
|
||||||
|
appreciated!
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
If you have a feature request, would like to report a bug, or are
|
||||||
|
enjoying the tool and want to say thanks, come hang out in Discord!
|
||||||
|
</p>
|
||||||
|
<div className="LinkItem">
|
||||||
|
<Link href="https://discord.gg/qyZ5hGdPC8">
|
||||||
|
<a
|
||||||
|
href="https://discord.gg/qyZ5hGdPC8"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
<div className="Left">
|
||||||
|
<DiscordIcon />
|
||||||
|
<h3>granblue-tools</h3>
|
||||||
|
</div>
|
||||||
|
<ShareIcon className="ShareIcon" />
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Credits</h2>
|
||||||
|
<p>
|
||||||
|
Granblue.team was built by{' '}
|
||||||
|
<a
|
||||||
|
href="https://twitter.com/jedmund"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
@jedmund
|
||||||
|
</a>{' '}
|
||||||
|
with a lot of help from{' '}
|
||||||
|
<a
|
||||||
|
href="https://twitter.com/lalalalinna"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
@lalalalinna
|
||||||
|
</a>{' '}
|
||||||
|
and{' '}
|
||||||
|
<a
|
||||||
|
href="https://twitter.com/tarngerine"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
@tarngerine
|
||||||
|
</a>
|
||||||
|
.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Many thanks also go to Disinfect, Slipper, Jif, Bless, 9highwind, and
|
||||||
|
everyone else in{' '}
|
||||||
|
<a
|
||||||
|
href="https://game.granbluefantasy.jp/#guild/detail/1190185"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
Fireplace
|
||||||
|
</a>{' '}
|
||||||
|
that helped with bug testing and feature requests. (P.S. We're
|
||||||
|
recruiting!) And yoey, but he won't join our crew.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Contributing</h2>
|
||||||
|
<p>
|
||||||
|
This app is open source and licensed under{' '}
|
||||||
|
<a
|
||||||
|
href="https://choosealicense.com/licenses/agpl-3.0/"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
GNU AGPLv3
|
||||||
|
</a>
|
||||||
|
. Plainly, that means you can download the source, modify it, and
|
||||||
|
redistribute it if you attribute this project, use the same license,
|
||||||
|
and keep it open source. You can contribute on Github.
|
||||||
|
</p>
|
||||||
|
<ul className="Links">
|
||||||
|
<li className="LinkItem">
|
||||||
|
<Link href="https://github.com/jedmund/hensei-api">
|
||||||
|
<a
|
||||||
|
href="https://github.com/jedmund/hensei-api"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
<div className="Left">
|
||||||
|
<GithubIcon />
|
||||||
|
<h3>jedmund/hensei-api</h3>
|
||||||
|
</div>
|
||||||
|
<ShareIcon className="ShareIcon" />
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li className="LinkItem">
|
||||||
|
<Link href="https://github.com/jedmund/hensei-web">
|
||||||
|
<a
|
||||||
|
href="https://github.com/jedmund/hensei-web"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
<div className="Left">
|
||||||
|
<GithubIcon />
|
||||||
|
<h3>jedmund/hensei-web</h3>
|
||||||
|
</div>
|
||||||
|
<ShareIcon className="ShareIcon" />
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AboutPage
|
||||||
|
|
@ -156,7 +156,9 @@ const AccountModal = (props: Props) => {
|
||||||
theme: user.theme,
|
theme: user.theme,
|
||||||
}
|
}
|
||||||
|
|
||||||
setCookie('user', cookieObj, { path: '/' })
|
const expiresAt = new Date()
|
||||||
|
expiresAt.setDate(expiresAt.getDate() + 60)
|
||||||
|
setCookie('user', cookieObj, { path: '/', expires: expiresAt })
|
||||||
|
|
||||||
accountState.account.user = {
|
accountState.account.user = {
|
||||||
id: user.id,
|
id: user.id,
|
||||||
|
|
|
||||||
|
|
@ -1,166 +0,0 @@
|
||||||
import React from 'react'
|
|
||||||
import { useTranslation } from 'next-i18next'
|
|
||||||
|
|
||||||
import ChangelogUnit from '~components/ChangelogUnit'
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogClose,
|
|
||||||
DialogTitle,
|
|
||||||
DialogTrigger,
|
|
||||||
} from '~components/Dialog'
|
|
||||||
import DialogContent from '~components/DialogContent'
|
|
||||||
|
|
||||||
import CrossIcon from '~public/icons/Cross.svg'
|
|
||||||
|
|
||||||
import './index.scss'
|
|
||||||
|
|
||||||
const ChangelogModal = () => {
|
|
||||||
const { t } = useTranslation('common')
|
|
||||||
const headerRef = React.createRef<HTMLDivElement>()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog>
|
|
||||||
<DialogTrigger asChild>
|
|
||||||
<li className="MenuItem">
|
|
||||||
<span>{t('modals.changelog.title')}</span>
|
|
||||||
</li>
|
|
||||||
</DialogTrigger>
|
|
||||||
<DialogContent
|
|
||||||
className="Changelog"
|
|
||||||
title={t('menu.changelog')}
|
|
||||||
headerref={headerRef}
|
|
||||||
onOpenAutoFocus={(event) => event.preventDefault()}
|
|
||||||
onEscapeKeyDown={() => {}}
|
|
||||||
>
|
|
||||||
<div className="DialogHeader" ref={headerRef}>
|
|
||||||
<DialogTitle className="DialogTitle">
|
|
||||||
{t('menu.changelog')}
|
|
||||||
</DialogTitle>
|
|
||||||
<DialogClose className="DialogClose" asChild>
|
|
||||||
<span>
|
|
||||||
<CrossIcon />
|
|
||||||
</span>
|
|
||||||
</DialogClose>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="updates">
|
|
||||||
<section className="version" data-version="1.0">
|
|
||||||
<div className="top">
|
|
||||||
<h3>1.0.1</h3>
|
|
||||||
<time>2023/01/08</time>
|
|
||||||
</div>
|
|
||||||
<ul className="notes">
|
|
||||||
<li>Extra party fields: Full Auto, Clear Time, and more</li>
|
|
||||||
<li>Support for Youtube short URLs</li>
|
|
||||||
<li>Responsive grids and lots of other mobile fixes</li>
|
|
||||||
<li>Many other bug fixes</li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
<section className="content version" data-version="2022-12L">
|
|
||||||
<div className="top">
|
|
||||||
<h3>2022-12 Legend Festival</h3>
|
|
||||||
<time>2022/12/26</time>
|
|
||||||
</div>
|
|
||||||
<div className="update">
|
|
||||||
<section className="characters">
|
|
||||||
<h4>New characters</h4>
|
|
||||||
<div className="items">
|
|
||||||
<ChangelogUnit
|
|
||||||
name="Michael (Grand)"
|
|
||||||
id="3040440000"
|
|
||||||
type="character"
|
|
||||||
/>
|
|
||||||
<ChangelogUnit
|
|
||||||
name="Makura"
|
|
||||||
id="3040441000"
|
|
||||||
type="character"
|
|
||||||
/>
|
|
||||||
<ChangelogUnit
|
|
||||||
name="Ultimate Friday"
|
|
||||||
id="3040442000"
|
|
||||||
type="character"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<section className="weapons">
|
|
||||||
<h4>New weapons</h4>
|
|
||||||
<div className="items">
|
|
||||||
<ChangelogUnit
|
|
||||||
name="Crimson Scale"
|
|
||||||
id="1040315900"
|
|
||||||
type="weapon"
|
|
||||||
/>
|
|
||||||
<ChangelogUnit
|
|
||||||
name="Leporidius"
|
|
||||||
id="1040914500"
|
|
||||||
type="weapon"
|
|
||||||
/>
|
|
||||||
<ChangelogUnit
|
|
||||||
name="FRIED Spear"
|
|
||||||
id="1040218200"
|
|
||||||
type="weapon"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<section className="summons">
|
|
||||||
<h4>New summons</h4>
|
|
||||||
<div className="items">
|
|
||||||
<ChangelogUnit name="Yatima" id="2040417000" type="summon" />
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<section className="content version" data-version="2022-12F2">
|
|
||||||
<div className="top">
|
|
||||||
<h3>2022-12 Flash Gala</h3>
|
|
||||||
<time>2022/12/26</time>
|
|
||||||
</div>
|
|
||||||
<div className="update">
|
|
||||||
<section className="characters">
|
|
||||||
<h4>New characters</h4>
|
|
||||||
<div className="items">
|
|
||||||
<ChangelogUnit
|
|
||||||
name="Charlotta (Grand)"
|
|
||||||
id="3040438000"
|
|
||||||
type="character"
|
|
||||||
/>
|
|
||||||
<ChangelogUnit name="Erin" id="3040439000" type="character" />
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<section className="weapons">
|
|
||||||
<h4>New weapons</h4>
|
|
||||||
<div className="items">
|
|
||||||
<ChangelogUnit
|
|
||||||
name="Claíomh Solais Díon"
|
|
||||||
id="1040024200"
|
|
||||||
type="weapon"
|
|
||||||
/>
|
|
||||||
<ChangelogUnit
|
|
||||||
name="Crystal Edge"
|
|
||||||
id="1040116500"
|
|
||||||
type="weapon"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
<section className="version" data-version="1.0">
|
|
||||||
<div className="top">
|
|
||||||
<h3>1.0.0</h3>
|
|
||||||
<time>2022/12/26</time>
|
|
||||||
</div>
|
|
||||||
<ul className="notes">
|
|
||||||
<li>First release!</li>
|
|
||||||
<li>You can embed Youtube videos now</li>
|
|
||||||
<li>Better clicking - right-click and open in a new tab</li>
|
|
||||||
<li>Manually set dark mode in Account Settings</li>
|
|
||||||
<li>Lots of bugs squashed</li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ChangelogModal
|
|
||||||
|
|
@ -8,10 +8,7 @@ import { retrieveCookies, retrieveLocaleCookies } from '~utils/retrieveCookies'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
import * as Switch from '@radix-ui/react-switch'
|
import * as Switch from '@radix-ui/react-switch'
|
||||||
|
|
||||||
import AboutModal from '~components/AboutModal'
|
|
||||||
import AccountModal from '~components/AccountModal'
|
import AccountModal from '~components/AccountModal'
|
||||||
import ChangelogModal from '~components/ChangelogModal'
|
|
||||||
import RoadmapModal from '~components/RoadmapModal'
|
|
||||||
import LoginModal from '~components/LoginModal'
|
import LoginModal from '~components/LoginModal'
|
||||||
import SignupModal from '~components/SignupModal'
|
import SignupModal from '~components/SignupModal'
|
||||||
|
|
||||||
|
|
@ -65,7 +62,11 @@ const HeaderMenu = (props: Props) => {
|
||||||
|
|
||||||
function handleCheckedChange(value: boolean) {
|
function handleCheckedChange(value: boolean) {
|
||||||
const language = value ? 'ja' : 'en'
|
const language = value ? 'ja' : 'en'
|
||||||
setCookie('NEXT_LOCALE', language, { path: '/' })
|
|
||||||
|
const expiresAt = new Date()
|
||||||
|
expiresAt.setDate(expiresAt.getDate() + 60)
|
||||||
|
|
||||||
|
setCookie('NEXT_LOCALE', language, { path: '/', expires: expiresAt })
|
||||||
router.push(router.asPath, undefined, { locale: language })
|
router.push(router.asPath, undefined, { locale: language })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -110,9 +111,21 @@ const HeaderMenu = (props: Props) => {
|
||||||
</li>
|
</li>
|
||||||
</div>
|
</div>
|
||||||
<div className="MenuGroup">
|
<div className="MenuGroup">
|
||||||
<AboutModal />
|
<li className="MenuItem">
|
||||||
<ChangelogModal />
|
<a href="/about" target="_blank">
|
||||||
<RoadmapModal />
|
{t('about.segmented_control.about')}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li className="MenuItem">
|
||||||
|
<a href="/updates" target="_blank">
|
||||||
|
{t('about.segmented_control.updates')}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li className="MenuItem">
|
||||||
|
<a href="/roadmap" target="_blank">
|
||||||
|
{t('about.segmented_control.roadmap')}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</div>
|
</div>
|
||||||
<div className="MenuGroup">
|
<div className="MenuGroup">
|
||||||
<AccountModal
|
<AccountModal
|
||||||
|
|
@ -160,9 +173,21 @@ const HeaderMenu = (props: Props) => {
|
||||||
</li>
|
</li>
|
||||||
</div>
|
</div>
|
||||||
<div className="MenuGroup">
|
<div className="MenuGroup">
|
||||||
<AboutModal />
|
<li className="MenuItem">
|
||||||
<ChangelogModal />
|
<a href="/about" target="_blank">
|
||||||
<RoadmapModal />
|
{t('about.segmented_control.about')}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li className="MenuItem">
|
||||||
|
<a href="/updates" target="_blank">
|
||||||
|
{t('about.segmented_control.updates')}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li className="MenuItem">
|
||||||
|
<a href="/roadmap" target="_blank">
|
||||||
|
{t('about.segmented_control.roadmap')}
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</div>
|
</div>
|
||||||
<div className="MenuGroup">
|
<div className="MenuGroup">
|
||||||
<LoginModal />
|
<LoginModal />
|
||||||
|
|
|
||||||
15
components/Layout/index.scss
Normal file
15
components/Layout/index.scss
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
.ToastViewport {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0px;
|
||||||
|
right: 0px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 340px;
|
||||||
|
max-width: 100vw;
|
||||||
|
z-index: 2147483647;
|
||||||
|
padding: 25px;
|
||||||
|
gap: 10px;
|
||||||
|
margin: 0px;
|
||||||
|
list-style: none;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
|
@ -1,14 +1,72 @@
|
||||||
import type { ReactElement } from 'react'
|
import { PropsWithChildren, useEffect, useState } from 'react'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import { add, format } from 'date-fns'
|
||||||
|
import { getCookie } from 'cookies-next'
|
||||||
|
|
||||||
|
import { appState } from '~utils/appState'
|
||||||
|
|
||||||
import TopHeader from '~components/Header'
|
import TopHeader from '~components/Header'
|
||||||
|
import UpdateToast from '~components/UpdateToast'
|
||||||
|
|
||||||
interface Props {
|
import './index.scss'
|
||||||
children: ReactElement
|
|
||||||
}
|
interface Props {}
|
||||||
|
|
||||||
|
const Layout = ({ children }: PropsWithChildren<Props>) => {
|
||||||
|
const router = useRouter()
|
||||||
|
const [updateToastOpen, setUpdateToastOpen] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const cookie = getToastCookie()
|
||||||
|
const now = new Date()
|
||||||
|
const updatedAt = new Date(appState.version.updated_at)
|
||||||
|
const validUntil = add(updatedAt, { days: 7 })
|
||||||
|
|
||||||
|
if (now < validUntil && !cookie.seen) setUpdateToastOpen(true)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
function getToastCookie() {
|
||||||
|
if (appState.version.updated_at !== '') {
|
||||||
|
const updatedAt = new Date(appState.version.updated_at)
|
||||||
|
const cookieValues = getCookie(
|
||||||
|
`update-${format(updatedAt, 'yyyy-MM-dd')}`
|
||||||
|
)
|
||||||
|
return cookieValues
|
||||||
|
? (JSON.parse(cookieValues as string) as { seen: true })
|
||||||
|
: { seen: false }
|
||||||
|
} else {
|
||||||
|
return { seen: false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleToastActionClicked() {
|
||||||
|
setUpdateToastOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleToastClosed() {
|
||||||
|
setUpdateToastOpen(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateToast = () => {
|
||||||
|
const path = router.asPath.replaceAll('/', '')
|
||||||
|
|
||||||
|
return !['about', 'updates', 'roadmap'].includes(path) ? (
|
||||||
|
<UpdateToast
|
||||||
|
open={updateToastOpen}
|
||||||
|
updateType="feature"
|
||||||
|
onActionClicked={handleToastActionClicked}
|
||||||
|
onCloseClicked={handleToastClosed}
|
||||||
|
lastUpdated={appState.version.updated_at}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
''
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
const Layout = ({ children }: Props) => {
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<TopHeader />
|
<TopHeader />
|
||||||
|
{updateToast()}
|
||||||
<main>{children}</main>
|
<main>{children}</main>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -133,7 +133,9 @@ const LoginModal = () => {
|
||||||
token: resp.access_token,
|
token: resp.access_token,
|
||||||
}
|
}
|
||||||
|
|
||||||
setCookie('account', cookieObj, { path: '/' })
|
const expiresAt = new Date()
|
||||||
|
expiresAt.setDate(expiresAt.getDate() + 60)
|
||||||
|
setCookie('account', cookieObj, { path: '/', expires: expiresAt })
|
||||||
|
|
||||||
// Set Axios default headers
|
// Set Axios default headers
|
||||||
setUserToken()
|
setUserToken()
|
||||||
|
|
@ -144,6 +146,9 @@ const LoginModal = () => {
|
||||||
const user = response.data
|
const user = response.data
|
||||||
|
|
||||||
// Set user data in the user cookie
|
// Set user data in the user cookie
|
||||||
|
const expiresAt = new Date()
|
||||||
|
expiresAt.setDate(expiresAt.getDate() + 60)
|
||||||
|
|
||||||
setCookie(
|
setCookie(
|
||||||
'user',
|
'user',
|
||||||
{
|
{
|
||||||
|
|
@ -153,7 +158,7 @@ const LoginModal = () => {
|
||||||
gender: user.gender,
|
gender: user.gender,
|
||||||
theme: user.theme,
|
theme: user.theme,
|
||||||
},
|
},
|
||||||
{ path: '/' }
|
{ path: '/', expires: expiresAt }
|
||||||
)
|
)
|
||||||
|
|
||||||
// Set the user data in the account state
|
// Set the user data in the account state
|
||||||
|
|
|
||||||
|
|
@ -1,118 +0,0 @@
|
||||||
.Roadmap.DialogContent {
|
|
||||||
gap: 0;
|
|
||||||
padding-bottom: $unit-2x;
|
|
||||||
|
|
||||||
h3.priority {
|
|
||||||
font-weight: $medium;
|
|
||||||
font-size: $font-large;
|
|
||||||
margin-bottom: $unit-4x;
|
|
||||||
|
|
||||||
&.in_progress {
|
|
||||||
color: $yellow;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.high {
|
|
||||||
color: $red;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.mid {
|
|
||||||
color: $orange-10;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.low {
|
|
||||||
color: $blue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: $unit-2x;
|
|
||||||
padding: 0 $unit-4x;
|
|
||||||
|
|
||||||
section.notes {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: $unit;
|
|
||||||
margin-bottom: $unit-2x;
|
|
||||||
|
|
||||||
p {
|
|
||||||
margin-bottom: $unit;
|
|
||||||
}
|
|
||||||
|
|
||||||
.LinkItem {
|
|
||||||
$diameter: $unit-6x;
|
|
||||||
border: 1px solid var(--link-item-bg);
|
|
||||||
border-radius: $card-corner;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
background-color: var(--link-item-bg);
|
|
||||||
|
|
||||||
svg {
|
|
||||||
fill: var(--link-item-image-color-hover);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
display: flex;
|
|
||||||
padding: $unit-2x;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.Left {
|
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
gap: $unit-2x;
|
|
||||||
flex-grow: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
svg {
|
|
||||||
fill: var(--link-item-image-color);
|
|
||||||
width: $diameter;
|
|
||||||
height: auto;
|
|
||||||
|
|
||||||
&.ShareIcon {
|
|
||||||
width: $unit-4x;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-weight: $bold;
|
|
||||||
max-width: 70%;
|
|
||||||
line-height: 1.3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
color: var(--text-secondary);
|
|
||||||
|
|
||||||
font-size: $font-regular;
|
|
||||||
line-height: 1.3;
|
|
||||||
}
|
|
||||||
|
|
||||||
ul {
|
|
||||||
color: var(--text-primary);
|
|
||||||
list-style-type: none;
|
|
||||||
|
|
||||||
li {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: $unit;
|
|
||||||
margin-bottom: $unit-2x;
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
font-size: $font-medium;
|
|
||||||
font-weight: $bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: $font-regular;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,101 +0,0 @@
|
||||||
import React from 'react'
|
|
||||||
import Link from 'next/link'
|
|
||||||
import { useTranslation } from 'next-i18next'
|
|
||||||
import {
|
|
||||||
Dialog,
|
|
||||||
DialogClose,
|
|
||||||
DialogTitle,
|
|
||||||
DialogTrigger,
|
|
||||||
} from '~components/Dialog'
|
|
||||||
import DialogContent from '~components/DialogContent'
|
|
||||||
|
|
||||||
import CrossIcon from '~public/icons/Cross.svg'
|
|
||||||
import ShareIcon from '~public/icons/Share.svg'
|
|
||||||
import GithubIcon from '~public/icons/github.svg'
|
|
||||||
|
|
||||||
import './index.scss'
|
|
||||||
|
|
||||||
const RoadmapModal = () => {
|
|
||||||
const { t } = useTranslation('roadmap')
|
|
||||||
const headerRef = React.createRef<HTMLDivElement>()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog>
|
|
||||||
<DialogTrigger asChild>
|
|
||||||
<li className="MenuItem">
|
|
||||||
<span>{t('modals.roadmap.title')}</span>
|
|
||||||
</li>
|
|
||||||
</DialogTrigger>
|
|
||||||
<DialogContent
|
|
||||||
className="Roadmap"
|
|
||||||
title={t('title')}
|
|
||||||
headerref={headerRef}
|
|
||||||
onOpenAutoFocus={(event) => event.preventDefault()}
|
|
||||||
onEscapeKeyDown={() => {}}
|
|
||||||
>
|
|
||||||
<div className="DialogHeader" ref={headerRef}>
|
|
||||||
<DialogTitle className="DialogTitle">{t('title')}</DialogTitle>
|
|
||||||
<DialogClose className="DialogClose" asChild>
|
|
||||||
<span>
|
|
||||||
<CrossIcon />
|
|
||||||
</span>
|
|
||||||
</DialogClose>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="content">
|
|
||||||
<section className="notes">
|
|
||||||
<p>{t('blurb')}</p>
|
|
||||||
<p>{t('link.intro')}</p>
|
|
||||||
<div className="LinkItem">
|
|
||||||
<Link href="https://github.com/users/jedmund/projects/1/views/3">
|
|
||||||
<a
|
|
||||||
href="https://github.com/users/jedmund/projects/1/views/3"
|
|
||||||
target="_blank"
|
|
||||||
rel="noreferrer"
|
|
||||||
>
|
|
||||||
<div className="Left">
|
|
||||||
<GithubIcon />
|
|
||||||
<h3>{t('link.title')}</h3>
|
|
||||||
</div>
|
|
||||||
<ShareIcon className="ShareIcon" />
|
|
||||||
</a>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
|
|
||||||
<section className="features">
|
|
||||||
<h3 className="priority in_progress">{t('subtitle')}</h3>
|
|
||||||
<ul>
|
|
||||||
<li>
|
|
||||||
<h4>{t('roadmap.item1.title')}</h4>
|
|
||||||
<p>{t('roadmap.item1.description')}</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<h4>{t('roadmap.item2.title')}</h4>
|
|
||||||
<p>{t('roadmap.item2.description')}</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<h4>{t('roadmap.item3.title')}</h4>
|
|
||||||
<p>{t('roadmap.item3.description')}</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<h4>{t('roadmap.item4.title')}</h4>
|
|
||||||
<p>{t('roadmap.item4.description')}</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<h4>{t('roadmap.item5.title')}</h4>
|
|
||||||
<p>{t('roadmap.item5.description')}</p>
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<h4>{t('roadmap.item6.title')}</h4>
|
|
||||||
<p>{t('roadmap.item6.description')}</p>
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
</div>
|
|
||||||
</DialogContent>
|
|
||||||
</Dialog>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default RoadmapModal
|
|
||||||
108
components/RoadmapPage/index.scss
Normal file
108
components/RoadmapPage/index.scss
Normal file
|
|
@ -0,0 +1,108 @@
|
||||||
|
.Roadmap.PageContent {
|
||||||
|
h3.priority {
|
||||||
|
font-weight: $medium;
|
||||||
|
font-size: $font-large;
|
||||||
|
margin-bottom: $unit-4x;
|
||||||
|
|
||||||
|
&.in_progress {
|
||||||
|
color: $yellow;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.high {
|
||||||
|
color: $red;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.mid {
|
||||||
|
color: $orange-10;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.low {
|
||||||
|
color: $blue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.notes {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: $unit;
|
||||||
|
margin-bottom: $unit-2x;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: $unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
.LinkItem {
|
||||||
|
$diameter: $unit-6x;
|
||||||
|
border: 1px solid var(--link-item-bg);
|
||||||
|
border-radius: $card-corner;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--link-item-bg);
|
||||||
|
|
||||||
|
svg {
|
||||||
|
fill: var(--link-item-image-color-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
display: flex;
|
||||||
|
padding: $unit-2x;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Left {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
gap: $unit-2x;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
fill: var(--link-item-image-color);
|
||||||
|
width: $diameter;
|
||||||
|
height: auto;
|
||||||
|
|
||||||
|
&.ShareIcon {
|
||||||
|
width: $unit-4x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-weight: $bold;
|
||||||
|
max-width: 70%;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
|
||||||
|
font-size: $font-regular;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul {
|
||||||
|
color: var(--text-primary);
|
||||||
|
list-style-type: none;
|
||||||
|
|
||||||
|
li {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: $unit;
|
||||||
|
margin-bottom: $unit-2x;
|
||||||
|
|
||||||
|
h4 {
|
||||||
|
font-size: $font-medium;
|
||||||
|
font-weight: $bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: $font-regular;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
73
components/RoadmapPage/index.tsx
Normal file
73
components/RoadmapPage/index.tsx
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
import React from 'react'
|
||||||
|
import Link from 'next/link'
|
||||||
|
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import { useTranslation } from 'next-i18next'
|
||||||
|
|
||||||
|
import ShareIcon from '~public/icons/Share.svg'
|
||||||
|
import GithubIcon from '~public/icons/github.svg'
|
||||||
|
|
||||||
|
import './index.scss'
|
||||||
|
|
||||||
|
interface Props {}
|
||||||
|
|
||||||
|
const RoadmapPage: React.FC<Props> = (props: Props) => {
|
||||||
|
const { t: common } = useTranslation('common')
|
||||||
|
const { t: roadmap } = useTranslation('roadmap')
|
||||||
|
return (
|
||||||
|
<div className="Roadmap PageContent">
|
||||||
|
<h1>{common('about.segmented_control.roadmap')}</h1>
|
||||||
|
<section className="notes">
|
||||||
|
<p>{roadmap('blurb')}</p>
|
||||||
|
<p>{roadmap('link.intro')}</p>
|
||||||
|
<div className="LinkItem">
|
||||||
|
<Link href="https://github.com/users/jedmund/projects/1/views/3">
|
||||||
|
<a
|
||||||
|
href="https://github.com/users/jedmund/projects/1/views/3"
|
||||||
|
target="_blank"
|
||||||
|
rel="noreferrer"
|
||||||
|
>
|
||||||
|
<div className="Left">
|
||||||
|
<GithubIcon />
|
||||||
|
<h3>{roadmap('link.title')}</h3>
|
||||||
|
</div>
|
||||||
|
<ShareIcon className="ShareIcon" />
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="features">
|
||||||
|
<h3 className="priority in_progress">{roadmap('subtitle')}</h3>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
<h4>{roadmap('roadmap.item1.title')}</h4>
|
||||||
|
<p>{roadmap('roadmap.item1.description')}</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<h4>{roadmap('roadmap.item2.title')}</h4>
|
||||||
|
<p>{roadmap('roadmap.item2.description')}</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<h4>{roadmap('roadmap.item3.title')}</h4>
|
||||||
|
<p>{roadmap('roadmap.item3.description')}</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<h4>{roadmap('roadmap.item4.title')}</h4>
|
||||||
|
<p>{roadmap('roadmap.item4.description')}</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<h4>{roadmap('roadmap.item5.title')}</h4>
|
||||||
|
<p>{roadmap('roadmap.item5.description')}</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<h4>{roadmap('roadmap.item6.title')}</h4>
|
||||||
|
<p>{roadmap('roadmap.item6.description')}</p>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RoadmapPage
|
||||||
|
|
@ -142,8 +142,14 @@ const SearchModal = (props: Props) => {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const expiresAt = new Date()
|
||||||
|
expiresAt.setDate(expiresAt.getDate() + 60)
|
||||||
|
|
||||||
if (recents && recents.length > 5) recents.pop()
|
if (recents && recents.length > 5) recents.pop()
|
||||||
setCookie(`recent_${props.object}`, recents, { path: '/' })
|
setCookie(`recent_${props.object}`, recents, {
|
||||||
|
path: '/',
|
||||||
|
expires: expiresAt,
|
||||||
|
})
|
||||||
sendData(result)
|
sendData(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -91,7 +91,9 @@ const SignupModal = (props: Props) => {
|
||||||
token: resp.token,
|
token: resp.token,
|
||||||
}
|
}
|
||||||
|
|
||||||
setCookie('account', cookieObj, { path: '/' })
|
const expiresAt = new Date()
|
||||||
|
expiresAt.setDate(expiresAt.getDate() + 60)
|
||||||
|
setCookie('account', cookieObj, { path: '/', expires: expiresAt })
|
||||||
|
|
||||||
// Set Axios default headers
|
// Set Axios default headers
|
||||||
setUserToken()
|
setUserToken()
|
||||||
|
|
@ -106,6 +108,9 @@ const SignupModal = (props: Props) => {
|
||||||
const user = response.data
|
const user = response.data
|
||||||
|
|
||||||
// Set user data in the user cookie
|
// Set user data in the user cookie
|
||||||
|
const expiresAt = new Date()
|
||||||
|
expiresAt.setDate(expiresAt.getDate() + 60)
|
||||||
|
|
||||||
setCookie(
|
setCookie(
|
||||||
'user',
|
'user',
|
||||||
{
|
{
|
||||||
|
|
@ -115,7 +120,7 @@ const SignupModal = (props: Props) => {
|
||||||
gender: user.gender,
|
gender: user.gender,
|
||||||
theme: user.theme,
|
theme: user.theme,
|
||||||
},
|
},
|
||||||
{ path: '/' }
|
{ path: '/', expires: expiresAt }
|
||||||
)
|
)
|
||||||
|
|
||||||
// Set the user data in the account state
|
// Set the user data in the account state
|
||||||
|
|
|
||||||
35
components/Toast/index.scss
Normal file
35
components/Toast/index.scss
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
.Toast {
|
||||||
|
background: var(--dialog-bg);
|
||||||
|
border-radius: $card-corner;
|
||||||
|
box-shadow: 0 1px 12px rgba(0, 0, 0, 0.18);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: $unit-2x;
|
||||||
|
padding: $unit-3x;
|
||||||
|
|
||||||
|
.Header {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-size: $font-regular;
|
||||||
|
font-weight: $medium;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: $font-regular;
|
||||||
|
font-weight: $bold;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
}
|
||||||
48
components/Toast/index.tsx
Normal file
48
components/Toast/index.tsx
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
import React, { PropsWithChildren } from 'react'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
|
||||||
|
import * as ToastPrimitive from '@radix-ui/react-toast'
|
||||||
|
import './index.scss'
|
||||||
|
|
||||||
|
interface Props extends ToastPrimitive.ToastProps {
|
||||||
|
className?: string
|
||||||
|
title?: string
|
||||||
|
content: string
|
||||||
|
onCloseClick: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const Toast = ({
|
||||||
|
children,
|
||||||
|
title,
|
||||||
|
content,
|
||||||
|
...props
|
||||||
|
}: PropsWithChildren<Props>) => {
|
||||||
|
const classes = classNames(props.className, {
|
||||||
|
Toast: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ToastPrimitive.Root {...props} className={classes}>
|
||||||
|
<div className="Header">
|
||||||
|
{title && (
|
||||||
|
<ToastPrimitive.Title asChild>
|
||||||
|
<h3>{title}</h3>
|
||||||
|
</ToastPrimitive.Title>
|
||||||
|
)}
|
||||||
|
<ToastPrimitive.Close aria-label="Close" onClick={props.onCloseClick}>
|
||||||
|
<span aria-hidden>×</span>
|
||||||
|
</ToastPrimitive.Close>
|
||||||
|
</div>
|
||||||
|
<ToastPrimitive.Description asChild>
|
||||||
|
<p>{content}</p>
|
||||||
|
</ToastPrimitive.Description>
|
||||||
|
{children && (
|
||||||
|
<ToastPrimitive.Action asChild altText={content}>
|
||||||
|
{children}
|
||||||
|
</ToastPrimitive.Action>
|
||||||
|
)}
|
||||||
|
</ToastPrimitive.Root>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Toast
|
||||||
11
components/UpdateToast/index.scss
Normal file
11
components/UpdateToast/index.scss
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
.Notice {
|
||||||
|
align-items: center;
|
||||||
|
border-radius: $card-corner;
|
||||||
|
background: blue;
|
||||||
|
display: flex;
|
||||||
|
padding: $unit;
|
||||||
|
|
||||||
|
p {
|
||||||
|
font-size: $font-small;
|
||||||
|
}
|
||||||
|
}
|
||||||
74
components/UpdateToast/index.tsx
Normal file
74
components/UpdateToast/index.tsx
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
import React from 'react'
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import { setCookie } from 'cookies-next'
|
||||||
|
import { add, format } from 'date-fns'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
|
||||||
|
import Button from '~components/Button'
|
||||||
|
import Toast from '~components/Toast'
|
||||||
|
|
||||||
|
import './index.scss'
|
||||||
|
import { useTranslation } from 'next-i18next'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
open: boolean
|
||||||
|
updateType: 'feature' | 'content'
|
||||||
|
lastUpdated: string
|
||||||
|
onActionClicked: () => void
|
||||||
|
onCloseClicked: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const UpdateToast = ({
|
||||||
|
open,
|
||||||
|
updateType,
|
||||||
|
lastUpdated,
|
||||||
|
onActionClicked,
|
||||||
|
onCloseClicked,
|
||||||
|
}: Props) => {
|
||||||
|
const { t } = useTranslation('roadmap')
|
||||||
|
|
||||||
|
const classes = classNames({
|
||||||
|
Update: true,
|
||||||
|
})
|
||||||
|
|
||||||
|
function setToastCookie() {
|
||||||
|
const updatedAt = new Date(lastUpdated)
|
||||||
|
const expiresAt = add(updatedAt, { days: 7 })
|
||||||
|
setCookie(
|
||||||
|
`update-${format(updatedAt, 'yyyy-MM-dd')}`,
|
||||||
|
{ seen: true },
|
||||||
|
{ path: '/', expires: expiresAt }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleButtonClicked() {
|
||||||
|
window.open('/updates', '_blank')
|
||||||
|
setToastCookie()
|
||||||
|
onActionClicked()
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleCloseClicked() {
|
||||||
|
setToastCookie()
|
||||||
|
onCloseClicked()
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Toast
|
||||||
|
className={classes}
|
||||||
|
title={t(`toasts.title`)}
|
||||||
|
content={t(`toasts.description.${updateType}`)}
|
||||||
|
open={open}
|
||||||
|
type="background"
|
||||||
|
onCloseClick={handleCloseClicked}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
buttonSize="small"
|
||||||
|
contained={true}
|
||||||
|
onClick={handleButtonClicked}
|
||||||
|
text={t('toasts.button')}
|
||||||
|
/>
|
||||||
|
</Toast>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UpdateToast
|
||||||
|
|
@ -1,17 +1,4 @@
|
||||||
.Changelog.DialogContent {
|
.Updates.PageContent {
|
||||||
gap: 0;
|
|
||||||
|
|
||||||
.updates {
|
|
||||||
padding: 0 $unit-4x;
|
|
||||||
}
|
|
||||||
|
|
||||||
.updates {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: $unit-4x;
|
|
||||||
margin-bottom: $unit-4x;
|
|
||||||
}
|
|
||||||
|
|
||||||
.version {
|
.version {
|
||||||
&.content {
|
&.content {
|
||||||
.top h3 {
|
.top h3 {
|
||||||
122
components/UpdatesPage/index.tsx
Normal file
122
components/UpdatesPage/index.tsx
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import { useTranslation } from 'next-i18next'
|
||||||
|
|
||||||
|
import ChangelogUnit from '~components/ChangelogUnit'
|
||||||
|
|
||||||
|
import './index.scss'
|
||||||
|
|
||||||
|
interface Props {}
|
||||||
|
|
||||||
|
const UpdatesPage: React.FC<Props> = (props: Props) => {
|
||||||
|
const { t: common } = useTranslation('common')
|
||||||
|
return (
|
||||||
|
<div className="Updates PageContent">
|
||||||
|
<h1>{common('about.segmented_control.updates')}</h1>
|
||||||
|
<section className="version" data-version="1.0">
|
||||||
|
<div className="top">
|
||||||
|
<h3>1.0.1</h3>
|
||||||
|
<time>2023/01/08</time>
|
||||||
|
</div>
|
||||||
|
<ul className="notes">
|
||||||
|
<li>Extra party fields: Full Auto, Clear Time, and more</li>
|
||||||
|
<li>Support for Youtube short URLs</li>
|
||||||
|
<li>Responsive grids and lots of other mobile fixes</li>
|
||||||
|
<li>Many other bug fixes</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
<section className="content version" data-version="2022-12L">
|
||||||
|
<div className="top">
|
||||||
|
<h3>2022-12 Legend Festival</h3>
|
||||||
|
<time>2022/12/26</time>
|
||||||
|
</div>
|
||||||
|
<div className="update">
|
||||||
|
<section className="characters">
|
||||||
|
<h4>New characters</h4>
|
||||||
|
<div className="items">
|
||||||
|
<ChangelogUnit
|
||||||
|
name="Michael (Grand)"
|
||||||
|
id="3040440000"
|
||||||
|
type="character"
|
||||||
|
/>
|
||||||
|
<ChangelogUnit name="Makura" id="3040441000" type="character" />
|
||||||
|
<ChangelogUnit
|
||||||
|
name="Ultimate Friday"
|
||||||
|
id="3040442000"
|
||||||
|
type="character"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section className="weapons">
|
||||||
|
<h4>New weapons</h4>
|
||||||
|
<div className="items">
|
||||||
|
<ChangelogUnit
|
||||||
|
name="Crimson Scale"
|
||||||
|
id="1040315900"
|
||||||
|
type="weapon"
|
||||||
|
/>
|
||||||
|
<ChangelogUnit name="Leporidius" id="1040914500" type="weapon" />
|
||||||
|
<ChangelogUnit name="FRIED Spear" id="1040218200" type="weapon" />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section className="summons">
|
||||||
|
<h4>New summons</h4>
|
||||||
|
<div className="items">
|
||||||
|
<ChangelogUnit name="Yatima" id="2040417000" type="summon" />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section className="content version" data-version="2022-12F2">
|
||||||
|
<div className="top">
|
||||||
|
<h3>2022-12 Flash Gala</h3>
|
||||||
|
<time>2022/12/26</time>
|
||||||
|
</div>
|
||||||
|
<div className="update">
|
||||||
|
<section className="characters">
|
||||||
|
<h4>New characters</h4>
|
||||||
|
<div className="items">
|
||||||
|
<ChangelogUnit
|
||||||
|
name="Charlotta (Grand)"
|
||||||
|
id="3040438000"
|
||||||
|
type="character"
|
||||||
|
/>
|
||||||
|
<ChangelogUnit name="Erin" id="3040439000" type="character" />
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section className="weapons">
|
||||||
|
<h4>New weapons</h4>
|
||||||
|
<div className="items">
|
||||||
|
<ChangelogUnit
|
||||||
|
name="Claíomh Solais Díon"
|
||||||
|
id="1040024200"
|
||||||
|
type="weapon"
|
||||||
|
/>
|
||||||
|
<ChangelogUnit
|
||||||
|
name="Crystal Edge"
|
||||||
|
id="1040116500"
|
||||||
|
type="weapon"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section className="version" data-version="1.0">
|
||||||
|
<div className="top">
|
||||||
|
<h3>1.0.0</h3>
|
||||||
|
<time>2022/12/26</time>
|
||||||
|
</div>
|
||||||
|
<ul className="notes">
|
||||||
|
<li>First release!</li>
|
||||||
|
<li>You can embed Youtube videos now</li>
|
||||||
|
<li>Better clicking - right-click and open in a new tab</li>
|
||||||
|
<li>Manually set dark mode in Account Settings</li>
|
||||||
|
<li>Lots of bugs squashed</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UpdatesPage
|
||||||
|
|
@ -27,6 +27,14 @@ module.exports = {
|
||||||
source: '/weapons',
|
source: '/weapons',
|
||||||
destination: '/new',
|
destination: '/new',
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
source: '/updates',
|
||||||
|
destination: '/about',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
source: '/roadmap',
|
||||||
|
destination: '/about',
|
||||||
|
},
|
||||||
{
|
{
|
||||||
source: '/p/:shortcode/characters',
|
source: '/p/:shortcode/characters',
|
||||||
destination: '/p/:shortcode',
|
destination: '/p/:shortcode',
|
||||||
|
|
|
||||||
18
package-lock.json
generated
18
package-lock.json
generated
|
|
@ -20,6 +20,7 @@
|
||||||
"axios": "^0.25.0",
|
"axios": "^0.25.0",
|
||||||
"classnames": "^2.3.1",
|
"classnames": "^2.3.1",
|
||||||
"cookies-next": "^2.1.1",
|
"cookies-next": "^2.1.1",
|
||||||
|
"date-fns": "^2.29.3",
|
||||||
"fix-date": "^1.1.6",
|
"fix-date": "^1.1.6",
|
||||||
"i18next": "^21.6.13",
|
"i18next": "^21.6.13",
|
||||||
"i18next-browser-languagedetector": "^6.1.3",
|
"i18next-browser-languagedetector": "^6.1.3",
|
||||||
|
|
@ -3905,6 +3906,18 @@
|
||||||
"integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==",
|
"integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"node_modules/date-fns": {
|
||||||
|
"version": "2.29.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz",
|
||||||
|
"integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA==",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.11"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"type": "opencollective",
|
||||||
|
"url": "https://opencollective.com/date-fns"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/debug": {
|
"node_modules/debug": {
|
||||||
"version": "4.3.4",
|
"version": "4.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||||
|
|
@ -10037,6 +10050,11 @@
|
||||||
"integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==",
|
"integrity": "sha512-sdQSFB7+llfUcQHUQO3+B8ERRj0Oa4w9POWMI/puGtuf7gFywGmkaLCElnudfTiKZV+NvHqL0ifzdrI8Ro7ESA==",
|
||||||
"dev": true
|
"dev": true
|
||||||
},
|
},
|
||||||
|
"date-fns": {
|
||||||
|
"version": "2.29.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.29.3.tgz",
|
||||||
|
"integrity": "sha512-dDCnyH2WnnKusqvZZ6+jA1O51Ibt8ZMRNkDZdyAyK4YfbDwa/cEmuztzG5pk6hqlp9aSBPYcjOlktquahGwGeA=="
|
||||||
|
},
|
||||||
"debug": {
|
"debug": {
|
||||||
"version": "4.3.4",
|
"version": "4.3.4",
|
||||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,7 @@
|
||||||
"axios": "^0.25.0",
|
"axios": "^0.25.0",
|
||||||
"classnames": "^2.3.1",
|
"classnames": "^2.3.1",
|
||||||
"cookies-next": "^2.1.1",
|
"cookies-next": "^2.1.1",
|
||||||
|
"date-fns": "^2.29.3",
|
||||||
"fix-date": "^1.1.6",
|
"fix-date": "^1.1.6",
|
||||||
"i18next": "^21.6.13",
|
"i18next": "^21.6.13",
|
||||||
"i18next-browser-languagedetector": "^6.1.3",
|
"i18next-browser-languagedetector": "^6.1.3",
|
||||||
|
|
|
||||||
|
|
@ -9,10 +9,13 @@ import InfiniteScroll from 'react-infinite-scroll-component'
|
||||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||||
|
|
||||||
import api from '~utils/api'
|
import api from '~utils/api'
|
||||||
|
|
||||||
import setUserToken from '~utils/setUserToken'
|
import setUserToken from '~utils/setUserToken'
|
||||||
import extractFilters from '~utils/extractFilters'
|
import extractFilters from '~utils/extractFilters'
|
||||||
|
import fetchLatestVersion from '~utils/fetchLatestVersion'
|
||||||
import organizeRaids from '~utils/organizeRaids'
|
import organizeRaids from '~utils/organizeRaids'
|
||||||
import useDidMountEffect from '~utils/useDidMountEffect'
|
import useDidMountEffect from '~utils/useDidMountEffect'
|
||||||
|
import { appState } from '~utils/appState'
|
||||||
import { elements, allElement } from '~data/elements'
|
import { elements, allElement } from '~data/elements'
|
||||||
import { emptyPaginationObject } from '~utils/emptyStates'
|
import { emptyPaginationObject } from '~utils/emptyStates'
|
||||||
import { printError } from '~utils/reportError'
|
import { printError } from '~utils/reportError'
|
||||||
|
|
@ -30,6 +33,7 @@ interface Props {
|
||||||
meta: PaginationObject
|
meta: PaginationObject
|
||||||
raids: Raid[]
|
raids: Raid[]
|
||||||
sortedRaids: Raid[][]
|
sortedRaids: Raid[][]
|
||||||
|
version: AppUpdate
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProfileRoute: React.FC<Props> = (props: Props) => {
|
const ProfileRoute: React.FC<Props> = (props: Props) => {
|
||||||
|
|
@ -99,6 +103,7 @@ const ProfileRoute: React.FC<Props> = (props: Props) => {
|
||||||
setTotalPages(props.meta.totalPages)
|
setTotalPages(props.meta.totalPages)
|
||||||
setRecordCount(props.meta.count)
|
setRecordCount(props.meta.count)
|
||||||
replaceResults(props.meta.count, props.teams)
|
replaceResults(props.meta.count, props.teams)
|
||||||
|
appState.version = props.version
|
||||||
}
|
}
|
||||||
setCurrentPage(1)
|
setCurrentPage(1)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
@ -352,6 +357,9 @@ export const getServerSideProps = async ({ req, res, locale, query }: { req: Nex
|
||||||
setUserToken(req, res)
|
setUserToken(req, res)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Fetch latest version
|
||||||
|
const version = await fetchLatestVersion()
|
||||||
|
|
||||||
// Fetch and organize raids
|
// Fetch and organize raids
|
||||||
let { raids, sortedRaids } = await api.endpoints.raids
|
let { raids, sortedRaids } = await api.endpoints.raids
|
||||||
.getAll()
|
.getAll()
|
||||||
|
|
@ -393,6 +401,7 @@ export const getServerSideProps = async ({ req, res, locale, query }: { req: Nex
|
||||||
meta: meta,
|
meta: meta,
|
||||||
raids: raids,
|
raids: raids,
|
||||||
sortedRaids: sortedRaids,
|
sortedRaids: sortedRaids,
|
||||||
|
version: version,
|
||||||
...(await serverSideTranslations(locale, ['common', 'roadmap'])),
|
...(await serverSideTranslations(locale, ['common', 'roadmap'])),
|
||||||
// Will be passed to the page component as props
|
// Will be passed to the page component as props
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect } from 'react'
|
||||||
import { getCookie, getCookies } from 'cookies-next'
|
import { getCookie } from 'cookies-next'
|
||||||
import { appWithTranslation } from 'next-i18next'
|
import { appWithTranslation } from 'next-i18next'
|
||||||
import { ThemeProvider, useTheme } from 'next-themes'
|
import { ThemeProvider } from 'next-themes'
|
||||||
|
|
||||||
import type { AppProps } from 'next/app'
|
import type { AppProps } from 'next/app'
|
||||||
import Layout from '~components/Layout'
|
import Layout from '~components/Layout'
|
||||||
|
|
@ -10,6 +10,7 @@ import { accountState } from '~utils/accountState'
|
||||||
import setUserToken from '~utils/setUserToken'
|
import setUserToken from '~utils/setUserToken'
|
||||||
|
|
||||||
import '../styles/globals.scss'
|
import '../styles/globals.scss'
|
||||||
|
import { ToastProvider, Viewport } from '@radix-ui/react-toast'
|
||||||
|
|
||||||
function MyApp({ Component, pageProps }: AppProps) {
|
function MyApp({ Component, pageProps }: AppProps) {
|
||||||
const accountCookie = getCookie('account')
|
const accountCookie = getCookie('account')
|
||||||
|
|
@ -43,9 +44,12 @@ function MyApp({ Component, pageProps }: AppProps) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
<Layout>
|
<ToastProvider>
|
||||||
<Component {...pageProps} />
|
<Layout>
|
||||||
</Layout>
|
<Component {...pageProps} />
|
||||||
|
</Layout>
|
||||||
|
<Viewport className="ToastViewport" />
|
||||||
|
</ToastProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
174
pages/about.tsx
Normal file
174
pages/about.tsx
Normal file
|
|
@ -0,0 +1,174 @@
|
||||||
|
import React, { useEffect, useState } from 'react'
|
||||||
|
import Head from 'next/head'
|
||||||
|
|
||||||
|
import { useRouter } from 'next/router'
|
||||||
|
import { useTranslation } from 'next-i18next'
|
||||||
|
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||||
|
|
||||||
|
import { AboutTabs } from '~utils/enums'
|
||||||
|
import setUserToken from '~utils/setUserToken'
|
||||||
|
|
||||||
|
import AboutPage from '~components/AboutPage'
|
||||||
|
import UpdatesPage from '~components/UpdatesPage'
|
||||||
|
import RoadmapPage from '~components/RoadmapPage'
|
||||||
|
import SegmentedControl from '~components/SegmentedControl'
|
||||||
|
import Segment from '~components/Segment'
|
||||||
|
|
||||||
|
import type { NextApiRequest, NextApiResponse } from 'next'
|
||||||
|
|
||||||
|
interface Props {}
|
||||||
|
|
||||||
|
const AboutRoute: React.FC<Props> = (props: Props) => {
|
||||||
|
// Set up router
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
|
// Import translations
|
||||||
|
const { t } = useTranslation('common')
|
||||||
|
|
||||||
|
const [currentTab, setCurrentTab] = useState<AboutTabs>(AboutTabs.About)
|
||||||
|
const [currentPage, setCurrentPage] = useState('')
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const parts = router.asPath.split('/')
|
||||||
|
const tab = parts[parts.length - 1]
|
||||||
|
|
||||||
|
switch (tab) {
|
||||||
|
case 'about':
|
||||||
|
setCurrentTab(AboutTabs.About)
|
||||||
|
setCurrentPage(parts[1])
|
||||||
|
break
|
||||||
|
case 'updates':
|
||||||
|
setCurrentTab(AboutTabs.Updates)
|
||||||
|
setCurrentPage(parts[1])
|
||||||
|
break
|
||||||
|
case 'roadmap':
|
||||||
|
setCurrentTab(AboutTabs.Roadmap)
|
||||||
|
setCurrentPage(parts[1])
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}, [router.asPath])
|
||||||
|
|
||||||
|
function handleTabClicked(event: React.ChangeEvent<HTMLInputElement>) {
|
||||||
|
const parts = router.asPath.split('/')
|
||||||
|
const path = `/${event.target.value}`
|
||||||
|
|
||||||
|
switch (event.target.value) {
|
||||||
|
case 'about':
|
||||||
|
router.replace(path)
|
||||||
|
setCurrentTab(AboutTabs.About)
|
||||||
|
setCurrentPage(parts[1])
|
||||||
|
break
|
||||||
|
case 'updates':
|
||||||
|
router.replace(path)
|
||||||
|
setCurrentTab(AboutTabs.Updates)
|
||||||
|
setCurrentPage(parts[1])
|
||||||
|
break
|
||||||
|
case 'roadmap':
|
||||||
|
router.replace(path)
|
||||||
|
setCurrentTab(AboutTabs.Roadmap)
|
||||||
|
setCurrentPage(parts[1])
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentSection = () => {
|
||||||
|
switch (currentTab) {
|
||||||
|
case AboutTabs.About:
|
||||||
|
return <AboutPage />
|
||||||
|
case AboutTabs.Updates:
|
||||||
|
return <UpdatesPage />
|
||||||
|
case AboutTabs.Roadmap:
|
||||||
|
return <RoadmapPage />
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div id="About">
|
||||||
|
<Head>
|
||||||
|
{/* HTML */}
|
||||||
|
<title>{t(`page.titles.${currentPage}`)}</title>
|
||||||
|
<meta
|
||||||
|
name="description"
|
||||||
|
content={t(`page.descriptions.${currentPage}`)}
|
||||||
|
/>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
|
||||||
|
{/* OpenGraph */}
|
||||||
|
<meta property="og:title" content={t(`page.titles.${currentPage}`)} />
|
||||||
|
<meta
|
||||||
|
property="og:description"
|
||||||
|
content={t('page.descriptions.about')}
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
property="og:url"
|
||||||
|
content={`https://app.granblue.team/${currentPage}`}
|
||||||
|
/>
|
||||||
|
<meta property="og:type" content="website" />
|
||||||
|
|
||||||
|
{/* Twitter */}
|
||||||
|
<meta name="twitter:card" content="summary_large_image" />
|
||||||
|
<meta property="twitter:domain" content="app.granblue.team" />
|
||||||
|
<meta name="twitter:title" content={t(`page.titles.${currentPage}`)} />
|
||||||
|
<meta
|
||||||
|
name="twitter:description"
|
||||||
|
content={t(`page.descriptions.${currentPage}`)}
|
||||||
|
/>
|
||||||
|
</Head>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<SegmentedControl>
|
||||||
|
<Segment
|
||||||
|
groupName="about"
|
||||||
|
name="about"
|
||||||
|
selected={currentTab == AboutTabs.About}
|
||||||
|
onClick={handleTabClicked}
|
||||||
|
>
|
||||||
|
{t('about.segmented_control.about')}
|
||||||
|
</Segment>
|
||||||
|
<Segment
|
||||||
|
groupName="about"
|
||||||
|
name="updates"
|
||||||
|
selected={currentTab == AboutTabs.Updates}
|
||||||
|
onClick={handleTabClicked}
|
||||||
|
>
|
||||||
|
{t('about.segmented_control.updates')}
|
||||||
|
</Segment>
|
||||||
|
<Segment
|
||||||
|
groupName="about"
|
||||||
|
name="roadmap"
|
||||||
|
selected={currentTab == AboutTabs.Roadmap}
|
||||||
|
onClick={handleTabClicked}
|
||||||
|
>
|
||||||
|
{t('about.segmented_control.roadmap')}
|
||||||
|
</Segment>
|
||||||
|
</SegmentedControl>
|
||||||
|
{currentSection()}
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getServerSidePaths = async () => {
|
||||||
|
return {
|
||||||
|
paths: [],
|
||||||
|
fallback: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// prettier-ignore
|
||||||
|
export const getServerSideProps = async ({ req, res, locale, query }: { req: NextApiRequest, res: NextApiResponse, locale: string, query: { [index: string]: string } }) => {
|
||||||
|
// Set headers for server-side requests
|
||||||
|
setUserToken(req, res)
|
||||||
|
|
||||||
|
// Fetch and organize raids
|
||||||
|
return {
|
||||||
|
props: {
|
||||||
|
...(await serverSideTranslations(locale, ['common', 'roadmap'])),
|
||||||
|
// Will be passed to the page component as props
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default AboutRoute
|
||||||
|
|
@ -6,6 +6,7 @@ import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||||
import Party from '~components/Party'
|
import Party from '~components/Party'
|
||||||
|
|
||||||
import api from '~utils/api'
|
import api from '~utils/api'
|
||||||
|
import fetchLatestVersion from '~utils/fetchLatestVersion'
|
||||||
import organizeRaids from '~utils/organizeRaids'
|
import organizeRaids from '~utils/organizeRaids'
|
||||||
import setUserToken from '~utils/setUserToken'
|
import setUserToken from '~utils/setUserToken'
|
||||||
import { appState } from '~utils/appState'
|
import { appState } from '~utils/appState'
|
||||||
|
|
@ -21,6 +22,7 @@ interface Props {
|
||||||
raids: Raid[]
|
raids: Raid[]
|
||||||
sortedRaids: Raid[][]
|
sortedRaids: Raid[][]
|
||||||
weaponKeys: GroupedWeaponKeys
|
weaponKeys: GroupedWeaponKeys
|
||||||
|
version: AppUpdate
|
||||||
}
|
}
|
||||||
|
|
||||||
const NewRoute: React.FC<Props> = (props: Props) => {
|
const NewRoute: React.FC<Props> = (props: Props) => {
|
||||||
|
|
@ -41,6 +43,7 @@ const NewRoute: React.FC<Props> = (props: Props) => {
|
||||||
appState.jobs = props.jobs
|
appState.jobs = props.jobs
|
||||||
appState.jobSkills = props.jobSkills
|
appState.jobSkills = props.jobSkills
|
||||||
appState.weaponKeys = props.weaponKeys
|
appState.weaponKeys = props.weaponKeys
|
||||||
|
appState.version = props.version
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|
@ -84,6 +87,10 @@ export const getServerSideProps = async ({ req, res, locale, query }: { req: Nex
|
||||||
setUserToken(req, res)
|
setUserToken(req, res)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Fetch latest version
|
||||||
|
const version = await fetchLatestVersion()
|
||||||
|
|
||||||
|
// Fetch and organize raids
|
||||||
let { raids, sortedRaids } = await api.endpoints.raids
|
let { raids, sortedRaids } = await api.endpoints.raids
|
||||||
.getAll()
|
.getAll()
|
||||||
.then((response) => organizeRaids(response.data))
|
.then((response) => organizeRaids(response.data))
|
||||||
|
|
@ -105,6 +112,7 @@ export const getServerSideProps = async ({ req, res, locale, query }: { req: Nex
|
||||||
raids: raids,
|
raids: raids,
|
||||||
sortedRaids: sortedRaids,
|
sortedRaids: sortedRaids,
|
||||||
weaponKeys: weaponKeys,
|
weaponKeys: weaponKeys,
|
||||||
|
version: version,
|
||||||
...(await serverSideTranslations(locale, ['common', 'roadmap'])),
|
...(await serverSideTranslations(locale, ['common', 'roadmap'])),
|
||||||
// Will be passed to the page component as props
|
// Will be passed to the page component as props
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,10 @@ import clonedeep from 'lodash.clonedeep'
|
||||||
import api from '~utils/api'
|
import api from '~utils/api'
|
||||||
import setUserToken from '~utils/setUserToken'
|
import setUserToken from '~utils/setUserToken'
|
||||||
import extractFilters from '~utils/extractFilters'
|
import extractFilters from '~utils/extractFilters'
|
||||||
|
import fetchLatestVersion from '~utils/fetchLatestVersion'
|
||||||
import organizeRaids from '~utils/organizeRaids'
|
import organizeRaids from '~utils/organizeRaids'
|
||||||
import useDidMountEffect from '~utils/useDidMountEffect'
|
import useDidMountEffect from '~utils/useDidMountEffect'
|
||||||
|
import { appState } from '~utils/appState'
|
||||||
import { elements, allElement } from '~data/elements'
|
import { elements, allElement } from '~data/elements'
|
||||||
import { emptyPaginationObject } from '~utils/emptyStates'
|
import { emptyPaginationObject } from '~utils/emptyStates'
|
||||||
import { printError } from '~utils/reportError'
|
import { printError } from '~utils/reportError'
|
||||||
|
|
@ -30,6 +32,7 @@ interface Props {
|
||||||
meta: PaginationObject
|
meta: PaginationObject
|
||||||
raids: Raid[]
|
raids: Raid[]
|
||||||
sortedRaids: Raid[][]
|
sortedRaids: Raid[][]
|
||||||
|
version: AppUpdate
|
||||||
}
|
}
|
||||||
|
|
||||||
const SavedRoute: React.FC<Props> = (props: Props) => {
|
const SavedRoute: React.FC<Props> = (props: Props) => {
|
||||||
|
|
@ -98,6 +101,7 @@ const SavedRoute: React.FC<Props> = (props: Props) => {
|
||||||
setTotalPages(props.meta.totalPages)
|
setTotalPages(props.meta.totalPages)
|
||||||
setRecordCount(props.meta.count)
|
setRecordCount(props.meta.count)
|
||||||
replaceResults(props.meta.count, props.teams)
|
replaceResults(props.meta.count, props.teams)
|
||||||
|
appState.version = props.version
|
||||||
}
|
}
|
||||||
setCurrentPage(1)
|
setCurrentPage(1)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
@ -354,6 +358,9 @@ export const getServerSideProps = async ({ req, res, locale, query }: { req: Nex
|
||||||
setUserToken(req, res)
|
setUserToken(req, res)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// Fetch latest version
|
||||||
|
const version = await fetchLatestVersion()
|
||||||
|
|
||||||
// Fetch and organize raids
|
// Fetch and organize raids
|
||||||
let { raids, sortedRaids } = await api.endpoints.raids
|
let { raids, sortedRaids } = await api.endpoints.raids
|
||||||
.getAll()
|
.getAll()
|
||||||
|
|
@ -384,6 +391,7 @@ export const getServerSideProps = async ({ req, res, locale, query }: { req: Nex
|
||||||
meta: meta,
|
meta: meta,
|
||||||
raids: raids,
|
raids: raids,
|
||||||
sortedRaids: sortedRaids,
|
sortedRaids: sortedRaids,
|
||||||
|
version: version,
|
||||||
...(await serverSideTranslations(locale, ['common', 'roadmap'])),
|
...(await serverSideTranslations(locale, ['common', 'roadmap'])),
|
||||||
// Will be passed to the page component as props
|
// Will be passed to the page component as props
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -12,8 +12,10 @@ import clonedeep from 'lodash.clonedeep'
|
||||||
import api from '~utils/api'
|
import api from '~utils/api'
|
||||||
import setUserToken from '~utils/setUserToken'
|
import setUserToken from '~utils/setUserToken'
|
||||||
import extractFilters from '~utils/extractFilters'
|
import extractFilters from '~utils/extractFilters'
|
||||||
|
import fetchLatestVersion from '~utils/fetchLatestVersion'
|
||||||
import organizeRaids from '~utils/organizeRaids'
|
import organizeRaids from '~utils/organizeRaids'
|
||||||
import useDidMountEffect from '~utils/useDidMountEffect'
|
import useDidMountEffect from '~utils/useDidMountEffect'
|
||||||
|
import { appState } from '~utils/appState'
|
||||||
import { elements, allElement } from '~data/elements'
|
import { elements, allElement } from '~data/elements'
|
||||||
import { emptyPaginationObject } from '~utils/emptyStates'
|
import { emptyPaginationObject } from '~utils/emptyStates'
|
||||||
import { printError } from '~utils/reportError'
|
import { printError } from '~utils/reportError'
|
||||||
|
|
@ -29,6 +31,7 @@ interface Props {
|
||||||
teams?: Party[]
|
teams?: Party[]
|
||||||
meta: PaginationObject
|
meta: PaginationObject
|
||||||
sortedRaids: Raid[][]
|
sortedRaids: Raid[][]
|
||||||
|
version: AppUpdate
|
||||||
}
|
}
|
||||||
|
|
||||||
const TeamsRoute: React.FC<Props> = (props: Props) => {
|
const TeamsRoute: React.FC<Props> = (props: Props) => {
|
||||||
|
|
@ -97,6 +100,7 @@ const TeamsRoute: React.FC<Props> = (props: Props) => {
|
||||||
setTotalPages(props.meta.totalPages)
|
setTotalPages(props.meta.totalPages)
|
||||||
setRecordCount(props.meta.count)
|
setRecordCount(props.meta.count)
|
||||||
replaceResults(props.meta.count, props.teams)
|
replaceResults(props.meta.count, props.teams)
|
||||||
|
appState.version = props.version
|
||||||
}
|
}
|
||||||
setCurrentPage(1)
|
setCurrentPage(1)
|
||||||
}, [])
|
}, [])
|
||||||
|
|
@ -364,8 +368,11 @@ export const getServerSideProps = async ({ req, res, locale, query }: { req: Nex
|
||||||
// Set headers for server-side requests
|
// Set headers for server-side requests
|
||||||
setUserToken(req, res)
|
setUserToken(req, res)
|
||||||
|
|
||||||
// Fetch and organize raids
|
|
||||||
try {
|
try {
|
||||||
|
// Fetch latest version
|
||||||
|
const version = await fetchLatestVersion()
|
||||||
|
|
||||||
|
// Fetch and organize raids
|
||||||
let { raids, sortedRaids } = await api.endpoints.raids
|
let { raids, sortedRaids } = await api.endpoints.raids
|
||||||
.getAll()
|
.getAll()
|
||||||
.then((response) => organizeRaids(response.data))
|
.then((response) => organizeRaids(response.data))
|
||||||
|
|
@ -395,6 +402,7 @@ export const getServerSideProps = async ({ req, res, locale, query }: { req: Nex
|
||||||
meta: meta,
|
meta: meta,
|
||||||
raids: raids,
|
raids: raids,
|
||||||
sortedRaids: sortedRaids,
|
sortedRaids: sortedRaids,
|
||||||
|
version: version,
|
||||||
...(await serverSideTranslations(locale, ['common', 'roadmap'])),
|
...(await serverSideTranslations(locale, ['common', 'roadmap'])),
|
||||||
// Will be passed to the page component as props
|
// Will be passed to the page component as props
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,11 @@
|
||||||
{
|
{
|
||||||
|
"about": {
|
||||||
|
"segmented_control": {
|
||||||
|
"about": "About",
|
||||||
|
"updates": "Updates",
|
||||||
|
"roadmap": "Roadmap"
|
||||||
|
}
|
||||||
|
},
|
||||||
"alert": {
|
"alert": {
|
||||||
"incompatible_weapon": "You've selected a weapon that can't be added to the Additional Weapon slots."
|
"incompatible_weapon": "You've selected a weapon that can't be added to the Additional Weapon slots."
|
||||||
},
|
},
|
||||||
|
|
@ -338,6 +345,9 @@
|
||||||
},
|
},
|
||||||
"page": {
|
"page": {
|
||||||
"titles": {
|
"titles": {
|
||||||
|
"about": "About granblue.team",
|
||||||
|
"updates": "Updates / granblue.team",
|
||||||
|
"roadmap": "Roadmap / granblue.team",
|
||||||
"discover": "Discover teams / granblue.team",
|
"discover": "Discover teams / granblue.team",
|
||||||
"new": "Create a new team / granblue.team",
|
"new": "Create a new team / granblue.team",
|
||||||
"profile": "@{{username}}'s Teams / granblue.team",
|
"profile": "@{{username}}'s Teams / granblue.team",
|
||||||
|
|
@ -345,6 +355,9 @@
|
||||||
"saved": "Your saved teams / granblue.team"
|
"saved": "Your saved teams / granblue.team"
|
||||||
},
|
},
|
||||||
"descriptions": {
|
"descriptions": {
|
||||||
|
"about": "More about granblue.team / Save and discover teams to use in Granblue Fantasy",
|
||||||
|
"updates": "Latest updates to granblue.team",
|
||||||
|
"roadmap": "Upcoming planned features for granblue.team",
|
||||||
"discover": "Save and discover teams to use in Granblue Fantasy and search by raid, element or recency",
|
"discover": "Save and discover teams to use in Granblue Fantasy and search by raid, element or recency",
|
||||||
"new": "Create and theorycraft teams to use in Granblue Fantasy and share with the community",
|
"new": "Create and theorycraft teams to use in Granblue Fantasy and share with the community",
|
||||||
"profile": "Browse @{{username}}'s Teams and filter by raid, element or recency",
|
"profile": "Browse @{{username}}'s Teams and filter by raid, element or recency",
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,14 @@
|
||||||
"title": "Roadmap"
|
"title": "Roadmap"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"toasts": {
|
||||||
|
"title": "New update",
|
||||||
|
"description": {
|
||||||
|
"content": "New items have been added from the latest Granblue Fantasy update.",
|
||||||
|
"feature": "Now you can remix other people's teams, add Character rings and earrings, set Shields and Manatura on parties, and more!"
|
||||||
|
},
|
||||||
|
"button": "Learn more"
|
||||||
|
},
|
||||||
"title": "Roadmap",
|
"title": "Roadmap",
|
||||||
"subtitle": "Next update",
|
"subtitle": "Next update",
|
||||||
"blurb": "I'm aiming for this update to release between late-January and early-February. I'm losing a week to top 2k in Guild Wars and after that I'm back at my full-time job, so progress will be a bit slower.",
|
"blurb": "I'm aiming for this update to release between late-January and early-February. I'm losing a week to top 2k in Guild Wars and after that I'm back at my full-time job, so progress will be a bit slower.",
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,11 @@
|
||||||
{
|
{
|
||||||
|
"about": {
|
||||||
|
"segmented_control": {
|
||||||
|
"about": "サイトについて",
|
||||||
|
"updates": "変更ログ",
|
||||||
|
"roadmap": "ロードマップ"
|
||||||
|
}
|
||||||
|
},
|
||||||
"alert": {
|
"alert": {
|
||||||
"incompatible_weapon": "Additional Weaponsに装備できない武器を入れました。"
|
"incompatible_weapon": "Additional Weaponsに装備できない武器を入れました。"
|
||||||
},
|
},
|
||||||
|
|
@ -339,6 +346,9 @@
|
||||||
},
|
},
|
||||||
"page": {
|
"page": {
|
||||||
"titles": {
|
"titles": {
|
||||||
|
"about": "granblue.teamについて",
|
||||||
|
"updates": "変更ログ / granblue.team",
|
||||||
|
"roadmap": "ロードマップ / granblue.team",
|
||||||
"discover": "編成を見出す / granblue.team",
|
"discover": "編成を見出す / granblue.team",
|
||||||
"new": "新しい編成 / granblue.team",
|
"new": "新しい編成 / granblue.team",
|
||||||
"profile": "@{{username}}さんの作った編成 / granblue.team",
|
"profile": "@{{username}}さんの作った編成 / granblue.team",
|
||||||
|
|
@ -346,6 +356,9 @@
|
||||||
"saved": "保存した編成"
|
"saved": "保存した編成"
|
||||||
},
|
},
|
||||||
"descriptions": {
|
"descriptions": {
|
||||||
|
"about": "granblue.teamについて / グランブルーファンタジーの編成を探したり保存したりできる",
|
||||||
|
"updates": "granblue.teamの最新変更について",
|
||||||
|
"roadmap": "granblue.teamの開発予定機能",
|
||||||
"discover": "グランブルーファンタジーの編成をマルチ、属性、作った時間などで探したり保存したりできる",
|
"discover": "グランブルーファンタジーの編成をマルチ、属性、作った時間などで探したり保存したりできる",
|
||||||
"new": "グランブルーファンタジーの編成を作成し、騎空士とシェアできるサイトgranblue.team",
|
"new": "グランブルーファンタジーの編成を作成し、騎空士とシェアできるサイトgranblue.team",
|
||||||
"profile": "@{{username}}の編成を調査し、マルチ、属性、または作った時間でフィルターする",
|
"profile": "@{{username}}の編成を調査し、マルチ、属性、または作った時間でフィルターする",
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,14 @@
|
||||||
"title": "ロードマップ"
|
"title": "ロードマップ"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"toasts": {
|
||||||
|
"title": "新アプデ",
|
||||||
|
"description": {
|
||||||
|
"content": "グランブルーファンタジーの新アプデのコンテンツが追加しました。",
|
||||||
|
"feature": "編成をリミックスしたり、キャラの指輪や耳飾りを付けたり、盾やマナベリを装備したりことをできるようにしました。"
|
||||||
|
},
|
||||||
|
"button": "詳細をみる"
|
||||||
|
},
|
||||||
"title": "ロードマップ",
|
"title": "ロードマップ",
|
||||||
"subtitle": "次回更新予定",
|
"subtitle": "次回更新予定",
|
||||||
"blurb": "1月下旬〜2月上旬に更新する予定があります。火古戦場に2000位を狙っており、その後は仕事に戻るので開発はちょっとだけ遅くなります。",
|
"blurb": "1月下旬〜2月上旬に更新する予定があります。火古戦場に2000位を狙っており、その後は仕事に戻るので開発はちょっとだけ遅くなります。",
|
||||||
|
|
|
||||||
|
|
@ -119,6 +119,86 @@ select {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.PageContent {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: $unit-4x;
|
||||||
|
max-width: $grid-width;
|
||||||
|
margin: $unit-4x auto 0;
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: $font-xxlarge;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: $font-medium;
|
||||||
|
font-weight: $medium;
|
||||||
|
margin-bottom: $unit * 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
font-size: $font-regular;
|
||||||
|
line-height: 1.3;
|
||||||
|
margin-bottom: $unit;
|
||||||
|
|
||||||
|
&:last-of-type {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.LinkItem {
|
||||||
|
$diameter: $unit-6x;
|
||||||
|
border: 1px solid var(--link-item-bg);
|
||||||
|
border-radius: $card-corner;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--link-item-bg);
|
||||||
|
|
||||||
|
svg {
|
||||||
|
fill: var(--link-item-image-color-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
display: flex;
|
||||||
|
padding: $unit-2x;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.Left {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
gap: $unit-2x;
|
||||||
|
flex-grow: 1;
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-weight: 600;
|
||||||
|
max-width: 70%;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
fill: var(--link-item-image-color);
|
||||||
|
width: $diameter;
|
||||||
|
height: auto;
|
||||||
|
|
||||||
|
&.ShareIcon {
|
||||||
|
width: $unit-4x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
font-weight: $bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.Hovercard {
|
.Hovercard {
|
||||||
background: #222;
|
background: #222;
|
||||||
border-radius: $unit;
|
border-radius: $unit;
|
||||||
|
|
|
||||||
5
types/AppUpdate.d.ts
vendored
Normal file
5
types/AppUpdate.d.ts
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
interface AppUpdate {
|
||||||
|
version: string
|
||||||
|
update_type: string
|
||||||
|
updated_at: string
|
||||||
|
}
|
||||||
|
|
@ -153,6 +153,11 @@ class Api {
|
||||||
const resourceUrl = `${this.url}/users/info/${id}`
|
const resourceUrl = `${this.url}/users/info/${id}`
|
||||||
return axios.get(resourceUrl)
|
return axios.get(resourceUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
version() {
|
||||||
|
const resourceUrl = `${this.url}/version`
|
||||||
|
return axios.get(resourceUrl)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const api: Api = new Api({ url: process.env.NEXT_PUBLIC_SIERO_API_URL || 'https://localhost:3000/api/v1'})
|
const api: Api = new Api({ url: process.env.NEXT_PUBLIC_SIERO_API_URL || 'https://localhost:3000/api/v1'})
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,7 @@ interface AppState {
|
||||||
jobs: Job[]
|
jobs: Job[]
|
||||||
jobSkills: JobSkill[]
|
jobSkills: JobSkill[]
|
||||||
weaponKeys: GroupedWeaponKeys
|
weaponKeys: GroupedWeaponKeys
|
||||||
|
version: AppUpdate
|
||||||
}
|
}
|
||||||
|
|
||||||
export const initialAppState: AppState = {
|
export const initialAppState: AppState = {
|
||||||
|
|
@ -140,6 +141,11 @@ export const initialAppState: AppState = {
|
||||||
gauph: [],
|
gauph: [],
|
||||||
emblem: [],
|
emblem: [],
|
||||||
},
|
},
|
||||||
|
version: {
|
||||||
|
version: '0.0',
|
||||||
|
update_type: '',
|
||||||
|
updated_at: '',
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
export const appState = proxy(initialAppState)
|
export const appState = proxy(initialAppState)
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,10 @@ export default function changeLanguage(
|
||||||
newLanguage: string
|
newLanguage: string
|
||||||
) {
|
) {
|
||||||
if (newLanguage !== router.locale) {
|
if (newLanguage !== router.locale) {
|
||||||
setCookie('NEXT_LOCALE', newLanguage, { path: '/' })
|
const expiresAt = new Date()
|
||||||
|
expiresAt.setDate(expiresAt.getDate() + 60)
|
||||||
|
|
||||||
|
setCookie('NEXT_LOCALE', newLanguage, { path: '/', expires: expiresAt })
|
||||||
router.push(router.asPath, undefined, { locale: newLanguage })
|
router.push(router.asPath, undefined, { locale: newLanguage })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,3 +19,9 @@ export enum TeamElement {
|
||||||
Dark,
|
Dark,
|
||||||
Light,
|
Light,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum AboutTabs {
|
||||||
|
About,
|
||||||
|
Updates,
|
||||||
|
Roadmap,
|
||||||
|
}
|
||||||
|
|
|
||||||
10
utils/fetchLatestVersion.tsx
Normal file
10
utils/fetchLatestVersion.tsx
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
import api from './api'
|
||||||
|
|
||||||
|
export default async function fetchLatestVersion() {
|
||||||
|
try {
|
||||||
|
const response = await api.version()
|
||||||
|
return response.data as AppUpdate
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue