diff --git a/components/AboutModal/index.scss b/components/AboutModal/index.scss deleted file mode 100644 index a1c82a2c..00000000 --- a/components/AboutModal/index.scss +++ /dev/null @@ -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; - } - } -} diff --git a/components/AboutModal/index.tsx b/components/AboutModal/index.tsx deleted file mode 100644 index 7935befd..00000000 --- a/components/AboutModal/index.tsx +++ /dev/null @@ -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() - - return ( - - -
  • - {t('modals.about.title')} -
  • -
    - event.preventDefault()} - onEscapeKeyDown={() => {}} - > -
    - {t('menu.about')} - - - - - -
    - -
    -
    -

    - Granblue.team is a tool to save and share team comps for{' '} - - Granblue Fantasy - - . -

    -

    - Start adding to a team and a URL will be created for you to share - wherever you like, no account needed. -

    -

    - 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. -

    -
    - -
    -

    Feedback

    -

    - This is an evolving project so feedback and suggestions are - greatly appreciated! -

    -

    - 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! -

    - -
    - -
    -

    Credits

    -

    - Granblue.team was built by{' '} - - @jedmund - {' '} - with a lot of help from{' '} - - @lalalalinna - {' '} - and{' '} - - @tarngerine - - . -

    -

    - Many thanks also go to Disinfect, Slipper, Jif, Bless, 9highwind, - and everyone else in{' '} - - Fireplace - {' '} - that helped with bug testing and feature requests. (P.S. - We're recruiting!) And yoey, but he won't join our crew. -

    -
    - -
    -

    Contributing

    -

    - This app is open source and licensed under{' '} - - GNU AGPLv3 - - . 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. -

    - -
    -
    -
    -
    - ) -} - -export default AboutModal diff --git a/components/AboutPage/index.scss b/components/AboutPage/index.scss new file mode 100644 index 00000000..38d21a98 --- /dev/null +++ b/components/AboutPage/index.scss @@ -0,0 +1,11 @@ +.About.PageContent { + .Links { + display: grid; + gap: $unit; + margin: $unit-2x 0; + } + + div.LinkItem { + margin-top: $unit-2x; + } +} diff --git a/components/AboutPage/index.tsx b/components/AboutPage/index.tsx new file mode 100644 index 00000000..0e3c546b --- /dev/null +++ b/components/AboutPage/index.tsx @@ -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) => { + const { t: common } = useTranslation('common') + return ( +
    +

    {common('about.segmented_control.about')}

    +
    +

    + Granblue.team is a tool to save and share team comps for{' '} + + Granblue Fantasy + + . +

    +

    + Start adding to a team and a URL will be created for you to share + wherever you like, no account needed. +

    +

    + 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. +

    +
    + +
    +

    Feedback

    +

    + This is an evolving project so feedback and suggestions are greatly + appreciated! +

    +

    + 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! +

    +
    + + +
    + +

    granblue-tools

    +
    + +
    + +
    +
    + +
    +

    Credits

    +

    + Granblue.team was built by{' '} + + @jedmund + {' '} + with a lot of help from{' '} + + @lalalalinna + {' '} + and{' '} + + @tarngerine + + . +

    +

    + Many thanks also go to Disinfect, Slipper, Jif, Bless, 9highwind, and + everyone else in{' '} + + Fireplace + {' '} + that helped with bug testing and feature requests. (P.S. We're + recruiting!) And yoey, but he won't join our crew. +

    +
    + +
    +

    Contributing

    +

    + This app is open source and licensed under{' '} + + GNU AGPLv3 + + . 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. +

    + +
    +
    + ) +} + +export default AboutPage diff --git a/components/AccountModal/index.tsx b/components/AccountModal/index.tsx index c10ee25c..0567380e 100644 --- a/components/AccountModal/index.tsx +++ b/components/AccountModal/index.tsx @@ -156,7 +156,9 @@ const AccountModal = (props: Props) => { 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 = { id: user.id, diff --git a/components/ChangelogModal/index.tsx b/components/ChangelogModal/index.tsx deleted file mode 100644 index 5ba0677f..00000000 --- a/components/ChangelogModal/index.tsx +++ /dev/null @@ -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() - - return ( - - -
  • - {t('modals.changelog.title')} -
  • -
    - event.preventDefault()} - onEscapeKeyDown={() => {}} - > -
    - - {t('menu.changelog')} - - - - - - -
    - -
    -
    -
    -

    1.0.1

    - -
    -
      -
    • Extra party fields: Full Auto, Clear Time, and more
    • -
    • Support for Youtube short URLs
    • -
    • Responsive grids and lots of other mobile fixes
    • -
    • Many other bug fixes
    • -
    -
    -
    -
    -

    2022-12 Legend Festival

    - -
    -
    -
    -

    New characters

    -
    - - - -
    -
    -
    -

    New weapons

    -
    - - - -
    -
    -
    -

    New summons

    -
    - -
    -
    -
    -
    -
    -
    -

    2022-12 Flash Gala

    - -
    -
    -
    -

    New characters

    -
    - - -
    -
    -
    -

    New weapons

    -
    - - -
    -
    -
    -
    -
    -
    -

    1.0.0

    - -
    -
      -
    • First release!
    • -
    • You can embed Youtube videos now
    • -
    • Better clicking - right-click and open in a new tab
    • -
    • Manually set dark mode in Account Settings
    • -
    • Lots of bugs squashed
    • -
    -
    -
    -
    -
    - ) -} - -export default ChangelogModal diff --git a/components/HeaderMenu/index.tsx b/components/HeaderMenu/index.tsx index 6c229f59..d42795b2 100644 --- a/components/HeaderMenu/index.tsx +++ b/components/HeaderMenu/index.tsx @@ -8,10 +8,7 @@ import { retrieveCookies, retrieveLocaleCookies } from '~utils/retrieveCookies' import Link from 'next/link' import * as Switch from '@radix-ui/react-switch' -import AboutModal from '~components/AboutModal' import AccountModal from '~components/AccountModal' -import ChangelogModal from '~components/ChangelogModal' -import RoadmapModal from '~components/RoadmapModal' import LoginModal from '~components/LoginModal' import SignupModal from '~components/SignupModal' @@ -65,7 +62,11 @@ const HeaderMenu = (props: Props) => { function handleCheckedChange(value: boolean) { 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 }) } @@ -110,9 +111,21 @@ const HeaderMenu = (props: Props) => {
    - - - +
  • + + {t('about.segmented_control.about')} + +
  • +
  • + + {t('about.segmented_control.updates')} + +
  • +
  • + + {t('about.segmented_control.roadmap')} + +
  • {
    - - - +
  • + + {t('about.segmented_control.about')} + +
  • +
  • + + {t('about.segmented_control.updates')} + +
  • +
  • + + {t('about.segmented_control.roadmap')} + +
  • diff --git a/components/Layout/index.scss b/components/Layout/index.scss new file mode 100644 index 00000000..b8403bc0 --- /dev/null +++ b/components/Layout/index.scss @@ -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; +} diff --git a/components/Layout/index.tsx b/components/Layout/index.tsx index dafc8b14..3716d012 100644 --- a/components/Layout/index.tsx +++ b/components/Layout/index.tsx @@ -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 UpdateToast from '~components/UpdateToast' -interface Props { - children: ReactElement -} +import './index.scss' + +interface Props {} + +const Layout = ({ children }: PropsWithChildren) => { + 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) ? ( + + ) : ( + '' + ) + } -const Layout = ({ children }: Props) => { return ( <> + {updateToast()}
    {children}
    ) diff --git a/components/LoginModal/index.tsx b/components/LoginModal/index.tsx index 15d485c9..be2f72c6 100644 --- a/components/LoginModal/index.tsx +++ b/components/LoginModal/index.tsx @@ -133,7 +133,9 @@ const LoginModal = () => { 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 setUserToken() @@ -144,6 +146,9 @@ const LoginModal = () => { const user = response.data // Set user data in the user cookie + const expiresAt = new Date() + expiresAt.setDate(expiresAt.getDate() + 60) + setCookie( 'user', { @@ -153,7 +158,7 @@ const LoginModal = () => { gender: user.gender, theme: user.theme, }, - { path: '/' } + { path: '/', expires: expiresAt } ) // Set the user data in the account state diff --git a/components/RoadmapModal/index.scss b/components/RoadmapModal/index.scss deleted file mode 100644 index ad57a3b0..00000000 --- a/components/RoadmapModal/index.scss +++ /dev/null @@ -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; - } - } - } - } -} diff --git a/components/RoadmapModal/index.tsx b/components/RoadmapModal/index.tsx deleted file mode 100644 index 0c956bc1..00000000 --- a/components/RoadmapModal/index.tsx +++ /dev/null @@ -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() - - return ( - - -
  • - {t('modals.roadmap.title')} -
  • -
    - event.preventDefault()} - onEscapeKeyDown={() => {}} - > -
    - {t('title')} - - - - - -
    - -
    -
    -

    {t('blurb')}

    -

    {t('link.intro')}

    - -
    - -
    -

    {t('subtitle')}

    -
      -
    • -

      {t('roadmap.item1.title')}

      -

      {t('roadmap.item1.description')}

      -
    • -
    • -

      {t('roadmap.item2.title')}

      -

      {t('roadmap.item2.description')}

      -
    • -
    • -

      {t('roadmap.item3.title')}

      -

      {t('roadmap.item3.description')}

      -
    • -
    • -

      {t('roadmap.item4.title')}

      -

      {t('roadmap.item4.description')}

      -
    • -
    • -

      {t('roadmap.item5.title')}

      -

      {t('roadmap.item5.description')}

      -
    • -
    • -

      {t('roadmap.item6.title')}

      -

      {t('roadmap.item6.description')}

      -
    • -
    -
    -
    -
    -
    - ) -} - -export default RoadmapModal diff --git a/components/RoadmapPage/index.scss b/components/RoadmapPage/index.scss new file mode 100644 index 00000000..2c106d30 --- /dev/null +++ b/components/RoadmapPage/index.scss @@ -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; + } + } + } +} diff --git a/components/RoadmapPage/index.tsx b/components/RoadmapPage/index.tsx new file mode 100644 index 00000000..e9fbab81 --- /dev/null +++ b/components/RoadmapPage/index.tsx @@ -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) => { + const { t: common } = useTranslation('common') + const { t: roadmap } = useTranslation('roadmap') + return ( +
    +

    {common('about.segmented_control.roadmap')}

    +
    +

    {roadmap('blurb')}

    +

    {roadmap('link.intro')}

    + +
    + +
    +

    {roadmap('subtitle')}

    +
      +
    • +

      {roadmap('roadmap.item1.title')}

      +

      {roadmap('roadmap.item1.description')}

      +
    • +
    • +

      {roadmap('roadmap.item2.title')}

      +

      {roadmap('roadmap.item2.description')}

      +
    • +
    • +

      {roadmap('roadmap.item3.title')}

      +

      {roadmap('roadmap.item3.description')}

      +
    • +
    • +

      {roadmap('roadmap.item4.title')}

      +

      {roadmap('roadmap.item4.description')}

      +
    • +
    • +

      {roadmap('roadmap.item5.title')}

      +

      {roadmap('roadmap.item5.description')}

      +
    • +
    • +

      {roadmap('roadmap.item6.title')}

      +

      {roadmap('roadmap.item6.description')}

      +
    • +
    +
    +
    + ) +} + +export default RoadmapPage diff --git a/components/SearchModal/index.tsx b/components/SearchModal/index.tsx index e0fa32e9..b8edf073 100644 --- a/components/SearchModal/index.tsx +++ b/components/SearchModal/index.tsx @@ -142,8 +142,14 @@ const SearchModal = (props: Props) => { } } + const expiresAt = new Date() + expiresAt.setDate(expiresAt.getDate() + 60) + if (recents && recents.length > 5) recents.pop() - setCookie(`recent_${props.object}`, recents, { path: '/' }) + setCookie(`recent_${props.object}`, recents, { + path: '/', + expires: expiresAt, + }) sendData(result) } diff --git a/components/SignupModal/index.tsx b/components/SignupModal/index.tsx index 88c96372..a142d348 100644 --- a/components/SignupModal/index.tsx +++ b/components/SignupModal/index.tsx @@ -91,7 +91,9 @@ const SignupModal = (props: Props) => { 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 setUserToken() @@ -106,6 +108,9 @@ const SignupModal = (props: Props) => { const user = response.data // Set user data in the user cookie + const expiresAt = new Date() + expiresAt.setDate(expiresAt.getDate() + 60) + setCookie( 'user', { @@ -115,7 +120,7 @@ const SignupModal = (props: Props) => { gender: user.gender, theme: user.theme, }, - { path: '/' } + { path: '/', expires: expiresAt } ) // Set the user data in the account state diff --git a/components/Toast/index.scss b/components/Toast/index.scss new file mode 100644 index 00000000..b89923ae --- /dev/null +++ b/components/Toast/index.scss @@ -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; + } +} diff --git a/components/Toast/index.tsx b/components/Toast/index.tsx new file mode 100644 index 00000000..8f85cb50 --- /dev/null +++ b/components/Toast/index.tsx @@ -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) => { + const classes = classNames(props.className, { + Toast: true, + }) + + return ( + +
    + {title && ( + +

    {title}

    +
    + )} + + × + +
    + +

    {content}

    +
    + {children && ( + + {children} + + )} +
    + ) +} + +export default Toast diff --git a/components/UpdateToast/index.scss b/components/UpdateToast/index.scss new file mode 100644 index 00000000..f444e5c0 --- /dev/null +++ b/components/UpdateToast/index.scss @@ -0,0 +1,11 @@ +.Notice { + align-items: center; + border-radius: $card-corner; + background: blue; + display: flex; + padding: $unit; + + p { + font-size: $font-small; + } +} diff --git a/components/UpdateToast/index.tsx b/components/UpdateToast/index.tsx new file mode 100644 index 00000000..51b6d009 --- /dev/null +++ b/components/UpdateToast/index.tsx @@ -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 ( + +