wip(shared): Removed useApp hook in favor of contexts
This removes the useApp hook from all org pages in favor of various context. This means there is no longer global state that gets passed around, instead each component that requires access to something shared (like account, or navigation) can just use the context instead. This is a first step, as a lot of shared components stil rely on app not to mention the dev and lab sites.
This commit is contained in:
parent
e717457454
commit
e47c18177b
84 changed files with 1457 additions and 1296 deletions
|
@ -237,7 +237,7 @@ dev:
|
|||
'@mdx-js/mdx': *mdx
|
||||
'@mdx-js/react': *mdx
|
||||
'@mdx-js/runtime': &mdxRuntime '2.0.0-next.9'
|
||||
'@next/bundle-analyzer': &next '13.3.0'
|
||||
'@next/bundle-analyzer': &next '13.3.1'
|
||||
'@tailwindcss/typography': &tailwindTypography '0.5.9'
|
||||
'algoliasearch': '4.17.0'
|
||||
'daisyui': &daisyui '2.51.5'
|
||||
|
|
|
@ -29,14 +29,14 @@
|
|||
"@mdx-js/mdx": "2.3.0",
|
||||
"@mdx-js/react": "2.3.0",
|
||||
"@mdx-js/runtime": "2.0.0-next.9",
|
||||
"@next/bundle-analyzer": "13.3.0",
|
||||
"@next/bundle-analyzer": "13.3.1",
|
||||
"@tailwindcss/typography": "0.5.9",
|
||||
"algoliasearch": "4.17.0",
|
||||
"daisyui": "2.51.5",
|
||||
"lodash.get": "4.4.2",
|
||||
"lodash.orderby": "4.6.0",
|
||||
"lodash.set": "4.3.2",
|
||||
"next": "13.3.0",
|
||||
"next": "13.3.1",
|
||||
"react": "18.2.0",
|
||||
"react-copy-to-clipboard": "5.1.0",
|
||||
"react-dom": "18.2.0",
|
||||
|
@ -55,7 +55,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "10.4.14",
|
||||
"eslint-config-next": "13.3.0",
|
||||
"eslint-config-next": "13.3.1",
|
||||
"js-yaml": "4.1.0",
|
||||
"postcss": "8.4.21",
|
||||
"remark-extract-frontmatter": "3.2.0",
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
"lodash.get": "4.4.2",
|
||||
"lodash.orderby": "4.6.0",
|
||||
"lodash.set": "4.3.2",
|
||||
"next": "13.3.0",
|
||||
"next": "13.3.1",
|
||||
"next-i18next": "13.2.2",
|
||||
"react": "18.2.0",
|
||||
"react-copy-to-clipboard": "5.1.0",
|
||||
|
@ -60,7 +60,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "10.4.14",
|
||||
"eslint-config-next": "13.3.0",
|
||||
"eslint-config-next": "13.3.1",
|
||||
"js-yaml": "4.1.0",
|
||||
"postcss": "8.4.21",
|
||||
"remark-extract-frontmatter": "3.2.0",
|
||||
|
|
|
@ -1,16 +1,26 @@
|
|||
// Hooks
|
||||
import { useContext } from 'react'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
// Context
|
||||
import { LoadingContext } from 'shared/context/loading-context.mjs'
|
||||
// Components
|
||||
import { Spinner } from 'shared/components/spinner.mjs'
|
||||
import Link from 'next/link'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
||||
export const ContinueButton = ({ app, btnProps = {}, link = false }) => {
|
||||
// Context
|
||||
const { loading } = useContext(LoadingContext)
|
||||
|
||||
// Hooks
|
||||
const { t } = useTranslation(['account'])
|
||||
|
||||
let classes = 'btn mt-8 capitalize w-full '
|
||||
if (app.state.loading) classes += 'btn-accent '
|
||||
if (loading) classes += 'btn-accent '
|
||||
else classes += 'btn-primary '
|
||||
|
||||
const children = (
|
||||
<span className="flex flex-row items-center gap-2">
|
||||
{app.state.loading ? (
|
||||
{loading ? (
|
||||
<>
|
||||
<Spinner />
|
||||
<span>{t('processing')}</span>
|
||||
|
|
|
@ -1,23 +1,29 @@
|
|||
import { Spinner } from 'shared/components/spinner.mjs'
|
||||
// Hooks
|
||||
import { useContext } from 'react'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
// Context
|
||||
import { LoadingContext } from 'shared/context/loading-context.mjs'
|
||||
// Components
|
||||
import { Spinner } from 'shared/components/spinner.mjs'
|
||||
|
||||
export const SaveSettingsButton = ({ app, btnProps = {}, welcome = false, label = false }) => {
|
||||
export const SaveSettingsButton = ({ btnProps = {}, welcome = false, label = false }) => {
|
||||
const { loading } = useContext(LoadingContext)
|
||||
const { t } = useTranslation(['account'])
|
||||
let classes = 'btn mt-4 capitalize '
|
||||
if (welcome) {
|
||||
classes += 'w-64 '
|
||||
if (app.state.loading) classes += 'btn-accent '
|
||||
if (loading) classes += 'btn-accent '
|
||||
else classes += 'btn-secondary '
|
||||
} else {
|
||||
classes += 'w-full '
|
||||
if (app.state.loading) classes += 'btn-accent '
|
||||
if (loading) classes += 'btn-accent '
|
||||
else classes += 'btn-primary '
|
||||
}
|
||||
|
||||
return (
|
||||
<button className={classes} tabIndex="-1" role="button" {...btnProps}>
|
||||
<span className="flex flex-row items-center gap-2">
|
||||
{app.state.loading ? (
|
||||
{loading ? (
|
||||
<>
|
||||
<Spinner />
|
||||
<span>{t('processing')}</span>
|
||||
|
|
11
sites/org/components/header/header.nl.yaml
Normal file
11
sites/org/components/header/header.nl.yaml
Normal file
|
@ -0,0 +1,11 @@
|
|||
menu: Menu
|
||||
designs: Collectie
|
||||
showcase: Voorbeelden
|
||||
docs: Documentatie
|
||||
blog: Blog
|
||||
community: Gemeenschap
|
||||
account: Account
|
||||
theme: Thema
|
||||
language: Taal
|
||||
search: Zoeken
|
||||
|
|
@ -3,6 +3,7 @@ import { useState, useEffect, useContext } from 'react'
|
|||
import { useTranslation } from 'next-i18next'
|
||||
// Context
|
||||
import { ModalContext } from 'shared/context/modal-context.mjs'
|
||||
import { LoadingContext } from 'shared/context/loading-context.mjs'
|
||||
// Components
|
||||
import {
|
||||
CommunityIcon,
|
||||
|
@ -60,14 +61,14 @@ export const colors = {
|
|||
account: 'purple',
|
||||
}
|
||||
|
||||
const NavIcons = ({ app, setModal, setSearch }) => {
|
||||
const NavIcons = ({ setModal, setSearch }) => {
|
||||
const { t } = useTranslation(['header'])
|
||||
const iconSize = 'h-6 w-6 lg:h-12 lg:w-12'
|
||||
|
||||
return (
|
||||
<>
|
||||
<NavButton
|
||||
onClick={() => setModal(<ModalMenu app={app} />)}
|
||||
onClick={() => setModal(<ModalMenu />)}
|
||||
label={t('header:menu')}
|
||||
color={colors.menu}
|
||||
>
|
||||
|
@ -111,14 +112,14 @@ const NavIcons = ({ app, setModal, setSearch }) => {
|
|||
</NavButton>
|
||||
<NavSpacer />
|
||||
<NavButton
|
||||
onClick={() => setModal(<ModalThemePicker app={app} />)}
|
||||
onClick={() => setModal(<ModalThemePicker />)}
|
||||
label={t('header:theme')}
|
||||
color={colors.theme}
|
||||
>
|
||||
<ThemeIcon className={iconSize} />
|
||||
</NavButton>
|
||||
<NavButton
|
||||
onClick={() => setModal(<ModalLocalePicker app={app} />)}
|
||||
onClick={() => setModal(<ModalLocalePicker />)}
|
||||
label={t('header:language')}
|
||||
color={colors.language}
|
||||
>
|
||||
|
@ -135,8 +136,9 @@ const NavIcons = ({ app, setModal, setSearch }) => {
|
|||
)
|
||||
}
|
||||
|
||||
export const Header = ({ app, setSearch }) => {
|
||||
export const Header = ({ setSearch }) => {
|
||||
const { setModal } = useContext(ModalContext)
|
||||
const { loading } = useContext(LoadingContext)
|
||||
const [prevScrollPos, setPrevScrollPos] = useState(0)
|
||||
const [show, setShow] = useState(true)
|
||||
|
||||
|
@ -162,11 +164,7 @@ export const Header = ({ app, setSearch }) => {
|
|||
w-full
|
||||
z-30
|
||||
transition-transform
|
||||
${
|
||||
show || app.state.loading
|
||||
? ''
|
||||
: 'fixed bottom-0 lg:top-0 left-0 translate-y-36 lg:-translate-y-36'
|
||||
}
|
||||
${show || loading ? '' : 'fixed bottom-0 lg:top-0 left-0 translate-y-36 lg:-translate-y-36'}
|
||||
drop-shadow-xl
|
||||
`}
|
||||
>
|
||||
|
@ -174,16 +172,16 @@ export const Header = ({ app, setSearch }) => {
|
|||
<div className="p-0 flex flex-row gap-2 justify-between text-neutral-content items-center">
|
||||
{/* Non-mobile content */}
|
||||
<div className="hidden lg:flex lg:px-2 flex-row items-center justify-center w-full">
|
||||
<NavIcons app={app} setModal={setModal} setSearch={setSearch} />
|
||||
<NavIcons setModal={setModal} setSearch={setSearch} />
|
||||
</div>
|
||||
|
||||
{/* Mobile content */}
|
||||
<div className="flex lg:hidden flex-row items-center justify-between w-full">
|
||||
<NavIcons app={app} setModal={setModal} setSearch={setSearch} />
|
||||
<NavIcons setModal={setModal} setSearch={setSearch} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Ribbon loading={app.state.loading} theme={app.theme} />
|
||||
<Ribbon />
|
||||
</header>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -2,9 +2,9 @@ import { AsideNavigation } from 'shared/components/navigation/aside.mjs'
|
|||
|
||||
export const ns = []
|
||||
|
||||
export const BareLayout = ({ app, children = [] }) => (
|
||||
export const BareLayout = (props) => (
|
||||
<>
|
||||
<AsideNavigation app={app} mobileOnly />
|
||||
{children}
|
||||
<AsideNavigation mobileOnly />
|
||||
{props.children}
|
||||
</>
|
||||
)
|
||||
|
|
|
@ -1,20 +1,26 @@
|
|||
import { useContext } from 'react'
|
||||
// Components
|
||||
import { AsideNavigation, ns as navNs } from 'shared/components/navigation/aside.mjs'
|
||||
import { Breadcrumbs } from 'shared/components/breadcrumbs.mjs'
|
||||
import { NavigationContext } from 'shared/context/navigation-context.mjs'
|
||||
|
||||
export const ns = navNs
|
||||
|
||||
export const DocsLayout = ({ app, children = [], title }) => (
|
||||
<div className="grid grid-cols-4 m-auto justify-center place-items-stretch lg:mt-16">
|
||||
<AsideNavigation app={app} />
|
||||
<section className="col-span-4 lg:col-span-3 py-24 px-4 lg:pl-8 bg-base-50">
|
||||
{title && (
|
||||
<div className="xl:pl-4">
|
||||
<Breadcrumbs crumbs={app.state.crumbs} title={title} />
|
||||
<h1 className="break-words">{title}</h1>
|
||||
</div>
|
||||
)}
|
||||
<div className="xl:pl-4">{children}</div>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
export const DocsLayout = ({ children = [] }) => {
|
||||
const { title, crumbs } = useContext(NavigationContext)
|
||||
|
||||
return (
|
||||
<div className="grid grid-cols-4 m-auto justify-center place-items-stretch lg:mt-16">
|
||||
<AsideNavigation />
|
||||
<section className="col-span-4 lg:col-span-3 py-24 px-4 lg:pl-8 bg-base-50">
|
||||
{title && (
|
||||
<div className="xl:pl-4">
|
||||
<Breadcrumbs crumbs={crumbs} title={title} />
|
||||
<h1 className="break-words">{title}</h1>
|
||||
</div>
|
||||
)}
|
||||
<div className="xl:pl-4">{children}</div>
|
||||
</section>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { useContext, useEffect } from 'react'
|
||||
import { NavigationContext } from 'shared/context/navigation-context.mjs'
|
||||
import get from 'lodash.get'
|
||||
import { prebuildNavigation as pbn } from 'site/prebuild/navigation.mjs'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
|
@ -81,6 +83,10 @@ const sitePages = (t = false, control = 99) => {
|
|||
}
|
||||
}
|
||||
}
|
||||
pages.account.reload = {
|
||||
t: t(`account:reload`),
|
||||
s: `account/reload`,
|
||||
}
|
||||
|
||||
return pages
|
||||
}
|
||||
|
@ -106,7 +112,7 @@ const createSections = (nav) => {
|
|||
return orderBy(sections, ['o', 't'])
|
||||
}
|
||||
|
||||
export const useNavigation = (path = [], locale = 'en') => {
|
||||
export const useNavigation = ({ path, locale }) => {
|
||||
const { t } = useTranslation(ns)
|
||||
const { account } = useAccount()
|
||||
|
||||
|
@ -117,10 +123,9 @@ export const useNavigation = (path = [], locale = 'en') => {
|
|||
const sections = createSections(nav)
|
||||
|
||||
return {
|
||||
path,
|
||||
slug: path.join('/'),
|
||||
crumbs,
|
||||
sections,
|
||||
slug: path.join('/'),
|
||||
nav: path.length > 1 ? get(nav, path[0]) : path.length === 0 ? sections : nav[path[0]],
|
||||
title: crumbs.length > 0 ? crumbs.slice(-1)[0].t : '',
|
||||
}
|
||||
|
|
|
@ -40,7 +40,7 @@
|
|||
"lodash.orderby": "4.6.0",
|
||||
"lodash.set": "4.3.2",
|
||||
"luxon": "3.3.0",
|
||||
"next": "13.3.0",
|
||||
"next": "13.3.1",
|
||||
"react-dropzone": "14.2.3",
|
||||
"react-hotkeys-hook": "4.3.8",
|
||||
"react-instantsearch-dom": "6.39.1",
|
||||
|
@ -60,7 +60,7 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"autoprefixer": "10.4.14",
|
||||
"eslint-config-next": "13.3.0",
|
||||
"eslint-config-next": "13.3.1",
|
||||
"js-yaml": "4.1.0",
|
||||
"postcss": "8.4.21",
|
||||
"remark-extract-frontmatter": "3.2.0",
|
||||
|
|
|
@ -5,6 +5,7 @@ import Bugsnag from '@bugsnag/js'
|
|||
import BugsnagPluginReact from '@bugsnag/plugin-react'
|
||||
import { freeSewingConfig } from 'site/freesewing.config.mjs'
|
||||
import { Toaster as DefaultToaster } from 'react-hot-toast'
|
||||
import { ContextWrapper } from 'shared/components/wrappers/context.mjs'
|
||||
|
||||
Bugsnag.start({
|
||||
apiKey: freeSewingConfig.bugsnag.key,
|
||||
|
@ -16,25 +17,27 @@ const ErrorBoundary = Bugsnag.getPlugin('react').createErrorBoundary(React)
|
|||
|
||||
const FreeSewingOrg = ({ Component, pageProps }) => (
|
||||
<ErrorBoundary>
|
||||
<Component {...pageProps} />
|
||||
<DefaultToaster
|
||||
position="bottom-right"
|
||||
toastOptions={{
|
||||
className: 'bg-base-100 text-base-content',
|
||||
success: {
|
||||
className: 'bg-success text-success-content',
|
||||
},
|
||||
error: {
|
||||
className: 'bg-error text-error-content',
|
||||
},
|
||||
loading: {
|
||||
className: 'bg-warning text-warning-content',
|
||||
},
|
||||
custom: {
|
||||
className: 'bg-accent text-accent-content',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<ContextWrapper>
|
||||
<Component {...pageProps} />
|
||||
<DefaultToaster
|
||||
position="bottom-right"
|
||||
toastOptions={{
|
||||
className: 'bg-base-100 text-base-content',
|
||||
success: {
|
||||
className: 'bg-success text-success-content',
|
||||
},
|
||||
error: {
|
||||
className: 'bg-error text-error-content',
|
||||
},
|
||||
loading: {
|
||||
className: 'bg-warning text-warning-content',
|
||||
},
|
||||
custom: {
|
||||
className: 'bg-accent text-accent-content',
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</ContextWrapper>
|
||||
</ErrorBoundary>
|
||||
)
|
||||
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// Hooks
|
||||
import { useApp } from 'shared/hooks/use-app.mjs'
|
||||
// Dependencies
|
||||
import dynamic from 'next/dynamic'
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
|
@ -25,25 +23,28 @@ const DynamicApikeys = dynamic(
|
|||
{ ssr: false }
|
||||
)
|
||||
|
||||
const AccountPage = (props) => {
|
||||
const app = useApp(props)
|
||||
/*
|
||||
* Each page MUST be wrapped in the PageWrapper component.
|
||||
* You also MUST spread props.page into this wrapper component
|
||||
* when path and locale come from static props (as here)
|
||||
* or set them manually.
|
||||
*/
|
||||
const AccountApikeysPage = ({ page }) => (
|
||||
<PageWrapper {...page}>
|
||||
<DynamicAuthWrapper>
|
||||
<DynamicApikeys />
|
||||
</DynamicAuthWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
|
||||
return (
|
||||
<PageWrapper app={app}>
|
||||
<DynamicAuthWrapper app={app}>
|
||||
<DynamicApikeys app={app} />
|
||||
</DynamicAuthWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export default AccountPage
|
||||
export default AccountApikeysPage
|
||||
|
||||
export async function getStaticProps({ locale }) {
|
||||
return {
|
||||
props: {
|
||||
...(await serverSideTranslations(locale, namespaces)),
|
||||
page: {
|
||||
locale,
|
||||
path: ['account', 'apikeys'],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// Hooks
|
||||
import { useApp } from 'shared/hooks/use-app.mjs'
|
||||
// Dependencies
|
||||
import dynamic from 'next/dynamic'
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
|
@ -25,25 +23,28 @@ const DynamicBio = dynamic(
|
|||
{ ssr: false }
|
||||
)
|
||||
|
||||
const AccountPage = (props) => {
|
||||
const app = useApp(props)
|
||||
/*
|
||||
* Each page MUST be wrapped in the PageWrapper component.
|
||||
* You also MUST spread props.page into this wrapper component
|
||||
* when path and locale come from static props (as here)
|
||||
* or set them manually.
|
||||
*/
|
||||
const AccountBioPage = ({ page }) => (
|
||||
<PageWrapper {...page}>
|
||||
<DynamicAuthWrapper>
|
||||
<DynamicBio title />
|
||||
</DynamicAuthWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
|
||||
return (
|
||||
<PageWrapper app={app}>
|
||||
<DynamicAuthWrapper app={app}>
|
||||
<DynamicBio app={app} title />
|
||||
</DynamicAuthWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export default AccountPage
|
||||
export default AccountBioPage
|
||||
|
||||
export async function getStaticProps({ locale }) {
|
||||
return {
|
||||
props: {
|
||||
...(await serverSideTranslations(locale, namespaces)),
|
||||
page: {
|
||||
locale,
|
||||
path: ['account', 'bio'],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// Hooks
|
||||
import { useApp } from 'shared/hooks/use-app.mjs'
|
||||
// Dependencies
|
||||
import dynamic from 'next/dynamic'
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
|
@ -25,25 +23,28 @@ const DynamicCompare = dynamic(
|
|||
{ ssr: false }
|
||||
)
|
||||
|
||||
const AccountPage = (props) => {
|
||||
const app = useApp(props)
|
||||
/*
|
||||
* Each page MUST be wrapped in the PageWrapper component.
|
||||
* You also MUST spread props.page into this wrapper component
|
||||
* when path and locale come from static props (as here)
|
||||
* or set them manually.
|
||||
*/
|
||||
const AccountComparePage = ({ page }) => (
|
||||
<PageWrapper {...page}>
|
||||
<DynamicAuthWrapper>
|
||||
<DynamicCompare title />
|
||||
</DynamicAuthWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
|
||||
return (
|
||||
<PageWrapper app={app}>
|
||||
<DynamicAuthWrapper app={app}>
|
||||
<DynamicCompare app={app} title />
|
||||
</DynamicAuthWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export default AccountPage
|
||||
export default AccountComparePage
|
||||
|
||||
export async function getStaticProps({ locale }) {
|
||||
return {
|
||||
props: {
|
||||
...(await serverSideTranslations(locale, namespaces)),
|
||||
page: {
|
||||
locale,
|
||||
path: ['account', 'compare'],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// Hooks
|
||||
import { useApp } from 'shared/hooks/use-app.mjs'
|
||||
// Dependencies
|
||||
import dynamic from 'next/dynamic'
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
|
@ -25,25 +23,28 @@ const DynamicConsent = dynamic(
|
|||
{ ssr: false }
|
||||
)
|
||||
|
||||
const AccountPage = (props) => {
|
||||
const app = useApp(props)
|
||||
/*
|
||||
* Each page MUST be wrapped in the PageWrapper component.
|
||||
* You also MUST spread props.page into this wrapper component
|
||||
* when path and locale come from static props (as here)
|
||||
* or set them manually.
|
||||
*/
|
||||
const AccountConsentPage = ({ page }) => (
|
||||
<PageWrapper {...page}>
|
||||
<DynamicAuthWrapper>
|
||||
<DynamicConsent title />
|
||||
</DynamicAuthWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
|
||||
return (
|
||||
<PageWrapper app={app}>
|
||||
<DynamicAuthWrapper app={app}>
|
||||
<DynamicConsent app={app} title />
|
||||
</DynamicAuthWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export default AccountPage
|
||||
export default AccountConsentPage
|
||||
|
||||
export async function getStaticProps({ locale }) {
|
||||
return {
|
||||
props: {
|
||||
...(await serverSideTranslations(locale, namespaces)),
|
||||
page: {
|
||||
locale,
|
||||
path: ['account', 'consent'],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// Hooks
|
||||
import { useApp } from 'shared/hooks/use-app.mjs'
|
||||
// Dependencies
|
||||
import dynamic from 'next/dynamic'
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
|
@ -25,17 +23,19 @@ const DynamicControl = dynamic(
|
|||
{ ssr: false }
|
||||
)
|
||||
|
||||
const AccountPage = (props) => {
|
||||
const app = useApp(props)
|
||||
|
||||
return (
|
||||
<PageWrapper app={app}>
|
||||
<DynamicAuthWrapper app={app}>
|
||||
<DynamicControl app={app} title />
|
||||
</DynamicAuthWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
||||
/*
|
||||
* Each page MUST be wrapped in the PageWrapper component.
|
||||
* You also MUST spread props.page into this wrapper component
|
||||
* when path and locale come from static props (as here)
|
||||
* or set them manually.
|
||||
*/
|
||||
const AccountPage = ({ page }) => (
|
||||
<PageWrapper {...page}>
|
||||
<DynamicAuthWrapper>
|
||||
<DynamicControl title />
|
||||
</DynamicAuthWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
|
||||
export default AccountPage
|
||||
|
||||
|
@ -44,6 +44,7 @@ export async function getStaticProps({ locale }) {
|
|||
props: {
|
||||
...(await serverSideTranslations(locale, namespaces)),
|
||||
page: {
|
||||
locale,
|
||||
path: ['account', 'control'],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// Hooks
|
||||
import { useApp } from 'shared/hooks/use-app.mjs'
|
||||
// Dependencies
|
||||
import dynamic from 'next/dynamic'
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
|
@ -25,25 +23,28 @@ const DynamicEmail = dynamic(
|
|||
{ ssr: false }
|
||||
)
|
||||
|
||||
const AccountPage = (props) => {
|
||||
const app = useApp(props)
|
||||
/*
|
||||
* Each page MUST be wrapped in the PageWrapper component.
|
||||
* You also MUST spread props.page into this wrapper component
|
||||
* when path and locale come from static props (as here)
|
||||
* or set them manually.
|
||||
*/
|
||||
const AccountEmailPage = ({ page }) => (
|
||||
<PageWrapper {...page}>
|
||||
<DynamicAuthWrapper>
|
||||
<DynamicEmail title />
|
||||
</DynamicAuthWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
|
||||
return (
|
||||
<PageWrapper app={app}>
|
||||
<DynamicAuthWrapper app={app}>
|
||||
<DynamicEmail app={app} title />
|
||||
</DynamicAuthWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export default AccountPage
|
||||
export default AccountEmailPage
|
||||
|
||||
export async function getStaticProps({ locale }) {
|
||||
return {
|
||||
props: {
|
||||
...(await serverSideTranslations(locale, namespaces)),
|
||||
page: {
|
||||
locale,
|
||||
path: ['account', 'email'],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// Hooks
|
||||
import { useApp } from 'shared/hooks/use-app.mjs'
|
||||
// Dependencies
|
||||
import dynamic from 'next/dynamic'
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
|
@ -25,17 +23,19 @@ const DynamicGithub = dynamic(
|
|||
{ ssr: false }
|
||||
)
|
||||
|
||||
const AccountPage = (props) => {
|
||||
const app = useApp(props)
|
||||
|
||||
return (
|
||||
<PageWrapper app={app}>
|
||||
<DynamicAuthWrapper app={app}>
|
||||
<DynamicGithub app={app} title />
|
||||
</DynamicAuthWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
||||
/*
|
||||
* Each page MUST be wrapped in the PageWrapper component.
|
||||
* You also MUST spread props.page into this wrapper component
|
||||
* when path and locale come from static props (as here)
|
||||
* or set them manually.
|
||||
*/
|
||||
const AccountPage = ({ page }) => (
|
||||
<PageWrapper {...page}>
|
||||
<DynamicAuthWrapper>
|
||||
<DynamicGithub title />
|
||||
</DynamicAuthWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
|
||||
export default AccountPage
|
||||
|
||||
|
@ -44,6 +44,7 @@ export async function getStaticProps({ locale }) {
|
|||
props: {
|
||||
...(await serverSideTranslations(locale, namespaces)),
|
||||
page: {
|
||||
locale,
|
||||
path: ['account', 'github'],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// Hooks
|
||||
import { useApp } from 'shared/hooks/use-app.mjs'
|
||||
// Dependencies
|
||||
import dynamic from 'next/dynamic'
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
|
@ -25,17 +23,19 @@ const DynamicImg = dynamic(
|
|||
{ ssr: false }
|
||||
)
|
||||
|
||||
const AccountPage = (props) => {
|
||||
const app = useApp(props)
|
||||
|
||||
return (
|
||||
<PageWrapper app={app}>
|
||||
<DynamicAuthWrapper app={app}>
|
||||
<DynamicImg app={app} title />
|
||||
</DynamicAuthWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
||||
/*
|
||||
* Each page MUST be wrapped in the PageWrapper component.
|
||||
* You also MUST spread props.page into this wrapper component
|
||||
* when path and locale come from static props (as here)
|
||||
* or set them manually.
|
||||
*/
|
||||
const AccountPage = ({ page }) => (
|
||||
<PageWrapper {...page}>
|
||||
<DynamicAuthWrapper>
|
||||
<DynamicImg title />
|
||||
</DynamicAuthWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
|
||||
export default AccountPage
|
||||
|
||||
|
@ -44,6 +44,7 @@ export async function getStaticProps({ locale }) {
|
|||
props: {
|
||||
...(await serverSideTranslations(locale, namespaces)),
|
||||
page: {
|
||||
locale,
|
||||
path: ['account', 'img'],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// Hooks
|
||||
import { useApp } from 'shared/hooks/use-app.mjs'
|
||||
// Dependencies
|
||||
import dynamic from 'next/dynamic'
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
|
@ -24,25 +22,22 @@ const DynamicAccountOverview = dynamic(
|
|||
{ ssr: false }
|
||||
)
|
||||
|
||||
const AccountPage = (props) => {
|
||||
const app = useApp(props)
|
||||
const AccountIndexPage = ({ page }) => (
|
||||
<PageWrapper {...page}>
|
||||
<DynamicAuthWrapper>
|
||||
<DynamicAccountOverview />
|
||||
</DynamicAuthWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
|
||||
return (
|
||||
<PageWrapper app={app}>
|
||||
<DynamicAuthWrapper app={app}>
|
||||
<DynamicAccountOverview app={app} />
|
||||
</DynamicAuthWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export default AccountPage
|
||||
export default AccountIndexPage
|
||||
|
||||
export async function getStaticProps({ locale }) {
|
||||
return {
|
||||
props: {
|
||||
...(await serverSideTranslations(locale, ns)),
|
||||
page: {
|
||||
locale,
|
||||
path: ['account'],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// Hooks
|
||||
import { useApp } from 'shared/hooks/use-app.mjs'
|
||||
// Dependencies
|
||||
import dynamic from 'next/dynamic'
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
|
@ -25,25 +23,28 @@ const DynamicLanguage = dynamic(
|
|||
{ ssr: false }
|
||||
)
|
||||
|
||||
const AccountPage = (props) => {
|
||||
const app = useApp(props)
|
||||
/*
|
||||
* Each page MUST be wrapped in the PageWrapper component.
|
||||
* You also MUST spread props.page into this wrapper component
|
||||
* when path and locale come from static props (as here)
|
||||
* or set them manually.
|
||||
*/
|
||||
const AccountLanguagePage = ({ page }) => (
|
||||
<PageWrapper {...page}>
|
||||
<DynamicAuthWrapper>
|
||||
<DynamicLanguage title />
|
||||
</DynamicAuthWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
|
||||
return (
|
||||
<PageWrapper app={app}>
|
||||
<DynamicAuthWrapper app={app}>
|
||||
<DynamicLanguage app={app} title />
|
||||
</DynamicAuthWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export default AccountPage
|
||||
export default AccountLanguagePage
|
||||
|
||||
export async function getStaticProps({ locale }) {
|
||||
return {
|
||||
props: {
|
||||
...(await serverSideTranslations(locale, namespaces)),
|
||||
page: {
|
||||
locale,
|
||||
path: ['account', 'language'],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// Hooks
|
||||
import { useApp } from 'shared/hooks/use-app.mjs'
|
||||
// Dependencies
|
||||
import dynamic from 'next/dynamic'
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
|
@ -25,25 +23,28 @@ const DynamicMfa = dynamic(
|
|||
{ ssr: false }
|
||||
)
|
||||
|
||||
const AccountPage = (props) => {
|
||||
const app = useApp(props)
|
||||
/*
|
||||
* Each page MUST be wrapped in the PageWrapper component.
|
||||
* You also MUST spread props.page into this wrapper component
|
||||
* when path and locale come from static props (as here)
|
||||
* or set them manually.
|
||||
*/
|
||||
const AccountMfaPage = ({ page }) => (
|
||||
<PageWrapper {...page}>
|
||||
<DynamicAuthWrapper>
|
||||
<DynamicMfa title />
|
||||
</DynamicAuthWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
|
||||
return (
|
||||
<PageWrapper app={app}>
|
||||
<DynamicAuthWrapper app={app}>
|
||||
<DynamicMfa app={app} title />
|
||||
</DynamicAuthWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export default AccountPage
|
||||
export default AccountMfaPage
|
||||
|
||||
export async function getStaticProps({ locale }) {
|
||||
return {
|
||||
props: {
|
||||
...(await serverSideTranslations(locale, namespaces)),
|
||||
page: {
|
||||
locale,
|
||||
path: ['account', 'mfa'],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// Hooks
|
||||
import { useApp } from 'shared/hooks/use-app.mjs'
|
||||
// Dependencies
|
||||
import dynamic from 'next/dynamic'
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
|
@ -25,25 +23,28 @@ const DynamicNewsletter = dynamic(
|
|||
{ ssr: false }
|
||||
)
|
||||
|
||||
const AccountPage = (props) => {
|
||||
const app = useApp(props)
|
||||
/*
|
||||
* Each page MUST be wrapped in the PageWrapper component.
|
||||
* You also MUST spread props.page into this wrapper component
|
||||
* when path and locale come from static props (as here)
|
||||
* or set them manually.
|
||||
*/
|
||||
const AccountNewsletterPage = ({ page }) => (
|
||||
<PageWrapper {...page}>
|
||||
<DynamicAuthWrapper>
|
||||
<DynamicNewsletter title />
|
||||
</DynamicAuthWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
|
||||
return (
|
||||
<PageWrapper app={app}>
|
||||
<DynamicAuthWrapper app={app}>
|
||||
<DynamicNewsletter app={app} title />
|
||||
</DynamicAuthWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export default AccountPage
|
||||
export default AccountNewsletterPage
|
||||
|
||||
export async function getStaticProps({ locale }) {
|
||||
return {
|
||||
props: {
|
||||
...(await serverSideTranslations(locale, namespaces)),
|
||||
page: {
|
||||
locale,
|
||||
path: ['account', 'newsletter'],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// Hooks
|
||||
import { useApp } from 'shared/hooks/use-app.mjs'
|
||||
// Dependencies
|
||||
import dynamic from 'next/dynamic'
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
|
@ -25,25 +23,28 @@ const DynamicPassword = dynamic(
|
|||
{ ssr: false }
|
||||
)
|
||||
|
||||
const AccountPage = (props) => {
|
||||
const app = useApp(props)
|
||||
/*
|
||||
* Each page MUST be wrapped in the PageWrapper component.
|
||||
* You also MUST spread props.page into this wrapper component
|
||||
* when path and locale come from static props (as here)
|
||||
* or set them manually.
|
||||
*/
|
||||
const AccountPasswordPage = ({ page }) => (
|
||||
<PageWrapper {...page}>
|
||||
<DynamicAuthWrapper>
|
||||
<DynamicPassword title />
|
||||
</DynamicAuthWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
|
||||
return (
|
||||
<PageWrapper app={app}>
|
||||
<DynamicAuthWrapper app={app}>
|
||||
<DynamicPassword app={app} title />
|
||||
</DynamicAuthWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export default AccountPage
|
||||
export default AccountPasswordPage
|
||||
|
||||
export async function getStaticProps({ locale }) {
|
||||
return {
|
||||
props: {
|
||||
...(await serverSideTranslations(locale, namespaces)),
|
||||
page: {
|
||||
locale,
|
||||
path: ['account', 'password'],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// Hooks
|
||||
import { useApp } from 'shared/hooks/use-app.mjs'
|
||||
// Dependencies
|
||||
import dynamic from 'next/dynamic'
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
|
@ -25,25 +23,28 @@ const DynamicReload = dynamic(
|
|||
{ ssr: false }
|
||||
)
|
||||
|
||||
const AccountPage = (props) => {
|
||||
const app = useApp(props)
|
||||
/*
|
||||
* Each page MUST be wrapped in the PageWrapper component.
|
||||
* You also MUST spread props.page into this wrapper component
|
||||
* when path and locale come from static props (as here)
|
||||
* or set them manually.
|
||||
*/
|
||||
const AccountReloadPage = ({ page }) => (
|
||||
<PageWrapper {...page}>
|
||||
<DynamicAuthWrapper>
|
||||
<DynamicReload title />
|
||||
</DynamicAuthWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
|
||||
return (
|
||||
<PageWrapper app={app}>
|
||||
<DynamicAuthWrapper app={app}>
|
||||
<DynamicReload app={app} title />
|
||||
</DynamicAuthWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export default AccountPage
|
||||
export default AccountReloadPage
|
||||
|
||||
export async function getStaticProps({ locale }) {
|
||||
return {
|
||||
props: {
|
||||
...(await serverSideTranslations(locale, namespaces)),
|
||||
page: {
|
||||
locale,
|
||||
path: ['account', 'reload'],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// Hooks
|
||||
import { useApp } from 'shared/hooks/use-app.mjs'
|
||||
// Dependencies
|
||||
import dynamic from 'next/dynamic'
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
|
@ -25,25 +23,28 @@ const DynamicSets = dynamic(
|
|||
{ ssr: false }
|
||||
)
|
||||
|
||||
const AccountPage = (props) => {
|
||||
const app = useApp(props)
|
||||
/*
|
||||
* Each page MUST be wrapped in the PageWrapper component.
|
||||
* You also MUST spread props.page into this wrapper component
|
||||
* when path and locale come from static props (as here)
|
||||
* or set them manually.
|
||||
*/
|
||||
const AccountSetsPage = ({ page }) => (
|
||||
<PageWrapper {...page}>
|
||||
<DynamicAuthWrapper>
|
||||
<DynamicSets />
|
||||
</DynamicAuthWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
|
||||
return (
|
||||
<PageWrapper app={app}>
|
||||
<DynamicAuthWrapper app={app}>
|
||||
<DynamicSets app={app} />
|
||||
</DynamicAuthWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export default AccountPage
|
||||
export default AccountSetsPage
|
||||
|
||||
export async function getStaticProps({ locale }) {
|
||||
return {
|
||||
props: {
|
||||
...(await serverSideTranslations(locale, namespaces)),
|
||||
page: {
|
||||
locale,
|
||||
path: ['account', 'sets'],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// Hooks
|
||||
import { useApp } from 'shared/hooks/use-app.mjs'
|
||||
// Dependencies
|
||||
import dynamic from 'next/dynamic'
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
|
@ -25,25 +23,28 @@ const DynamicImperial = dynamic(
|
|||
{ ssr: false }
|
||||
)
|
||||
|
||||
const AccountPage = (props) => {
|
||||
const app = useApp(props)
|
||||
/*
|
||||
* Each page MUST be wrapped in the PageWrapper component.
|
||||
* You also MUST spread props.page into this wrapper component
|
||||
* when path and locale come from static props (as here)
|
||||
* or set them manually.
|
||||
*/
|
||||
const AccountUnitsPage = ({ page }) => (
|
||||
<PageWrapper {...page}>
|
||||
<DynamicAuthWrapper>
|
||||
<DynamicImperial title />
|
||||
</DynamicAuthWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
|
||||
return (
|
||||
<PageWrapper app={app}>
|
||||
<DynamicAuthWrapper app={app}>
|
||||
<DynamicImperial app={app} title />
|
||||
</DynamicAuthWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export default AccountPage
|
||||
export default AccountUnitsPage
|
||||
|
||||
export async function getStaticProps({ locale }) {
|
||||
return {
|
||||
props: {
|
||||
...(await serverSideTranslations(locale, namespaces)),
|
||||
page: {
|
||||
locale,
|
||||
path: ['account', 'units'],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// Hooks
|
||||
import { useApp } from 'shared/hooks/use-app.mjs'
|
||||
// Dependencies
|
||||
import dynamic from 'next/dynamic'
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
|
@ -25,17 +23,19 @@ const DynamicUsername = dynamic(
|
|||
{ ssr: false }
|
||||
)
|
||||
|
||||
const AccountPage = (props) => {
|
||||
const app = useApp(props)
|
||||
|
||||
return (
|
||||
<PageWrapper app={app}>
|
||||
<DynamicAuthWrapper app={app}>
|
||||
<DynamicUsername app={app} title />
|
||||
</DynamicAuthWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
||||
/*
|
||||
* Each page MUST be wrapped in the PageWrapper component.
|
||||
* You also MUST spread props.page into this wrapper component
|
||||
* when path and locale come from static props (as here)
|
||||
* or set them manually.
|
||||
*/
|
||||
const AccountPage = ({ page }) => (
|
||||
<PageWrapper {...page}>
|
||||
<DynamicAuthWrapper>
|
||||
<DynamicUsername title />
|
||||
</DynamicAuthWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
|
||||
export default AccountPage
|
||||
|
||||
|
@ -44,6 +44,7 @@ export async function getStaticProps({ locale }) {
|
|||
props: {
|
||||
...(await serverSideTranslations(locale, namespaces)),
|
||||
page: {
|
||||
locale,
|
||||
path: ['account', 'username'],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// Hooks
|
||||
import { useApp } from 'shared/hooks/use-app.mjs'
|
||||
// Dependencies
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
// Components
|
||||
|
@ -9,25 +7,28 @@ import { V3Wip } from 'shared/components/v3-wip.mjs'
|
|||
// Translation namespaces used on this page
|
||||
const namespaces = [...new Set(['designs', ...pageNs])]
|
||||
|
||||
const DesignsPage = (props) => {
|
||||
const app = useApp(props)
|
||||
/*
|
||||
* Each page MUST be wrapped in the PageWrapper component.
|
||||
* You also MUST spread props.page into this wrapper component
|
||||
* when path and locale come from static props (as here)
|
||||
* or set them manually.
|
||||
*/
|
||||
const BlogIndexPage = ({ page }) => (
|
||||
<PageWrapper {...page}>
|
||||
<div className="max-w-2xl">
|
||||
<V3Wip />
|
||||
</div>
|
||||
</PageWrapper>
|
||||
)
|
||||
|
||||
return (
|
||||
<PageWrapper app={app}>
|
||||
<div className="max-w-2xl">
|
||||
<V3Wip />
|
||||
</div>
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export default DesignsPage
|
||||
export default BlogIndexPage
|
||||
|
||||
export async function getStaticProps({ locale }) {
|
||||
return {
|
||||
props: {
|
||||
...(await serverSideTranslations(locale, namespaces)),
|
||||
page: {
|
||||
locale,
|
||||
path: ['blog'],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// Hooks
|
||||
import { useApp } from 'shared/hooks/use-app.mjs'
|
||||
// Dependencies
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
// Components
|
||||
|
@ -9,25 +7,28 @@ import { V3Wip } from 'shared/components/v3-wip.mjs'
|
|||
// Translation namespaces used on this page
|
||||
const namespaces = [...new Set(['community', ...pageNs])]
|
||||
|
||||
const CommunityPage = (props) => {
|
||||
const app = useApp(props)
|
||||
/*
|
||||
* Each page MUST be wrapped in the PageWrapper component.
|
||||
* You also MUST spread props.page into this wrapper component
|
||||
* when path and locale come from static props (as here)
|
||||
* or set them manually.
|
||||
*/
|
||||
const CommunityIndexPage = ({ page }) => (
|
||||
<PageWrapper {...page}>
|
||||
<div className="max-w-2xl">
|
||||
<V3Wip />
|
||||
</div>
|
||||
</PageWrapper>
|
||||
)
|
||||
|
||||
return (
|
||||
<PageWrapper app={app}>
|
||||
<div className="max-w-2xl">
|
||||
<V3Wip />
|
||||
</div>
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export default CommunityPage
|
||||
export default CommunityIndexPage
|
||||
|
||||
export async function getStaticProps({ locale }) {
|
||||
return {
|
||||
props: {
|
||||
...(await serverSideTranslations(locale, namespaces)),
|
||||
page: {
|
||||
locale,
|
||||
path: ['community'],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
// Hooks
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useApp } from 'shared/hooks/use-app.mjs'
|
||||
import { useEffect, useState, useContext } from 'react'
|
||||
import { useBackend } from 'shared/hooks/use-backend.mjs'
|
||||
import { useAccount } from 'shared/hooks/use-account.mjs'
|
||||
import { useToast } from 'shared/hooks/use-toast.mjs'
|
||||
import { useRouter } from 'next/router'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
// Context
|
||||
import { LoadingContext } from 'shared/context/loading-context.mjs'
|
||||
// Dependencies
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
import Link from 'next/link'
|
||||
|
@ -20,28 +21,27 @@ import { HelpIcon } from 'shared/components/icons.mjs'
|
|||
// Translation namespaces used on this page
|
||||
const ns = Array.from(new Set([...pageNs, 'account']))
|
||||
|
||||
const ConfirmSignUpPage = (props) => {
|
||||
const router = useRouter()
|
||||
// Get confirmation ID and check from url
|
||||
const [id, check] = router.asPath.slice(1).split('/').slice(2)
|
||||
const ConfirmSignUpPage = ({ page }) => {
|
||||
// Context
|
||||
const { loading, startLoading, stopLoading } = useContext(LoadingContext)
|
||||
|
||||
const app = useApp({
|
||||
...props,
|
||||
page: {
|
||||
path: ['confirm', 'emailchange', id],
|
||||
},
|
||||
})
|
||||
// Hooks
|
||||
const { setAccount, setToken, token } = useAccount()
|
||||
const backend = useBackend()
|
||||
const toast = useToast()
|
||||
const { t } = useTranslation(ns)
|
||||
const router = useRouter()
|
||||
// Get confirmation ID and check from url
|
||||
const [id, check] = router.asPath.slice(1).split('/').slice(2)
|
||||
|
||||
// State
|
||||
const [error, setError] = useState(false)
|
||||
|
||||
// Effects
|
||||
useEffect(() => {
|
||||
// Async inside useEffect requires this approach
|
||||
const confirmEmail = async () => {
|
||||
app.startLoading()
|
||||
startLoading()
|
||||
const confirmation = await backend.loadConfirmation({ id, check })
|
||||
if (confirmation?.result === 'success' && confirmation.confirmation) {
|
||||
const result = await backend.updateAccount({
|
||||
|
@ -52,16 +52,16 @@ const ConfirmSignUpPage = (props) => {
|
|||
if (result.success) {
|
||||
setAccount(result.data.account)
|
||||
setToken(result.data.token)
|
||||
app.stopLoading()
|
||||
stopLoading()
|
||||
setError(false)
|
||||
toast.for.settingsSaved()
|
||||
router.push('/account')
|
||||
} else {
|
||||
app.stopLoading()
|
||||
stopLoading()
|
||||
setError(true)
|
||||
}
|
||||
} else {
|
||||
app.stopLoading()
|
||||
stopLoading()
|
||||
setError(true)
|
||||
}
|
||||
}
|
||||
|
@ -69,10 +69,14 @@ const ConfirmSignUpPage = (props) => {
|
|||
if (token) confirmEmail()
|
||||
}, [id, check, token])
|
||||
|
||||
// Update path with dynamic ID
|
||||
if (!page) return null
|
||||
if (page) page.path = ['confirm', 'emailchange', id]
|
||||
|
||||
// Short-circuit errors
|
||||
if (error)
|
||||
return (
|
||||
<PageWrapper app={app} title={t('account:politeOhCrap')} layout={BareLayout} footer={false}>
|
||||
<PageWrapper {...page} title={t('account:politeOhCrap')} layout={BareLayout} footer={false}>
|
||||
<div className="max-w-md flex flex-col items-center m-auto justify-center h-screen text-center">
|
||||
<h1 className="text-center">{t('account:politeOhCrap')}</h1>
|
||||
<Robot pose="ohno" className="w-48" embed />
|
||||
|
@ -88,7 +92,7 @@ const ConfirmSignUpPage = (props) => {
|
|||
)
|
||||
|
||||
return (
|
||||
<PageWrapper app={app} title={t('account:oneMomentPlease')} layout={BareLayout} footer={false}>
|
||||
<PageWrapper {...page} title={t('account:oneMomentPlease')} layout={BareLayout} footer={false}>
|
||||
<div className="max-w-md flex flex-col items-center m-auto justify-center h-screen text-center">
|
||||
<h1 className="text-center">{t('account:oneMomentPlease')}</h1>
|
||||
<p className="text-center">
|
||||
|
@ -105,6 +109,7 @@ export async function getStaticProps({ locale }) {
|
|||
return {
|
||||
props: {
|
||||
...(await serverSideTranslations(locale, ns)),
|
||||
page: { locale },
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
// Hooks
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useApp } from 'shared/hooks/use-app.mjs'
|
||||
import { useAccount } from 'shared/hooks/use-account.mjs'
|
||||
import { useBackend } from 'shared/hooks/use-backend.mjs'
|
||||
import { useRouter } from 'next/router'
|
||||
|
@ -37,25 +36,23 @@ export const SigninLinkExpired = () => {
|
|||
)
|
||||
}
|
||||
|
||||
const Wrapper = ({ app, t, children }) => (
|
||||
<PageWrapper app={app} title={t('signin:oneMomentPlease')} layout={BareLayout} footer={false}>
|
||||
const Wrapper = ({ page, t, children }) => (
|
||||
<PageWrapper {...page} title={t('signin:oneMomentPlease')} layout={BareLayout} footer={false}>
|
||||
<section className="m-0 p-0 w-full">
|
||||
<div className="mt-4 lg:mt-32 max-w-xl m-auto">{children}</div>
|
||||
</section>
|
||||
</PageWrapper>
|
||||
)
|
||||
|
||||
const ConfirmSignInPage = (props) => {
|
||||
/*
|
||||
* Each page MUST be wrapped in the PageWrapper component.
|
||||
* You also MUST spread props.page into this wrapper component
|
||||
* and set path and locale when it's dynamic (like on this page)
|
||||
*/
|
||||
const ConfirmSignInPage = ({ page }) => {
|
||||
const router = useRouter()
|
||||
// Get confirmation ID and check from url
|
||||
const [confirmationId, confirmationCheck] = router.asPath.slice(1).split('/').slice(2)
|
||||
const app = useApp({
|
||||
...props,
|
||||
page: {
|
||||
path: ['confirm', 'emailchange', confirmationId],
|
||||
},
|
||||
})
|
||||
|
||||
const { setAccount, setToken } = useAccount()
|
||||
const backend = useBackend()
|
||||
const { t } = useTranslation(ns)
|
||||
|
@ -90,16 +87,18 @@ const ConfirmSignInPage = (props) => {
|
|||
getConfirmation()
|
||||
}, [])
|
||||
|
||||
if (page) page.path = ['confirm', 'emailchange', confirmationId]
|
||||
|
||||
// Short-circuit errors
|
||||
if (error)
|
||||
return (
|
||||
<Wrapper app={app} t={t}>
|
||||
<Wrapper page={page} t={t}>
|
||||
<SigninLinkExpired />
|
||||
</Wrapper>
|
||||
)
|
||||
|
||||
return (
|
||||
<Wrapper app={app} t={t}>
|
||||
<Wrapper page={page} t={t}>
|
||||
<h1>{t('oneMomentPlease')}</h1>
|
||||
<Spinner className="w-8 h-8 m-auto animate-spin" />
|
||||
</Wrapper>
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
// Hooks
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useApp } from 'shared/hooks/use-app.mjs'
|
||||
import { useAccount } from 'shared/hooks/use-account.mjs'
|
||||
import { useBackend } from 'shared/hooks/use-backend.mjs'
|
||||
import { useRouter } from 'next/router'
|
||||
|
@ -24,215 +23,8 @@ import {
|
|||
// Translation namespaces used on this page
|
||||
const ns = Array.from(new Set([...pageNs, ...layoutNs, ...gdprNs, 'confirm', 'locales', 'themes']))
|
||||
|
||||
export const SignupLinkExpired = () => {
|
||||
const { t } = useTranslation('confirm')
|
||||
|
||||
return (
|
||||
<div className="p-8 max-w-md">
|
||||
<h1 className="text-center">{t('signupLinkExpired')}</h1>
|
||||
<Robot pose="shrug" className="w-full" embed />
|
||||
<Link className="btn btn-primary btn-lg w-full" href="/signup">
|
||||
{t('signupAgain')}
|
||||
</Link>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const Checkbox = ({ value, setter, label, children = null }) => (
|
||||
<div
|
||||
className={`form-control p-4 hover:cursor-pointer rounded border-l-8 my-2
|
||||
${value ? 'border-success bg-success' : 'border-error bg-error'}
|
||||
bg-opacity-10 shadow`}
|
||||
onClick={() => setter(value ? false : true)}
|
||||
>
|
||||
<div className="form-control flex flex-row items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
className="checkbox"
|
||||
checked={value ? 'checked' : ''}
|
||||
onChange={() => setter(value ? false : true)}
|
||||
/>
|
||||
<span className="label-text">{label}</span>
|
||||
</div>
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
|
||||
const ConfirmSignUpPage = (props) => {
|
||||
const router = useRouter()
|
||||
// Get confirmation ID and check from url
|
||||
const [confirmationId, confirmationCheck] = router.asPath.slice(1).split('/').slice(2)
|
||||
const app = useApp({
|
||||
...props,
|
||||
page: {
|
||||
path: ['confirm', 'emailchange', confirmationId],
|
||||
},
|
||||
})
|
||||
const { setAccount, setToken } = useAccount()
|
||||
const backend = useBackend()
|
||||
const { t } = useTranslation(ns)
|
||||
|
||||
const [id, setId] = useState(false)
|
||||
const [pDetails, setPDetails] = useState(false)
|
||||
const [mDetails, setMDetails] = useState(false)
|
||||
const [profile, setProfile] = useState(false)
|
||||
const [measurements, setMeasurements] = useState(false)
|
||||
const [openData, setOpenData] = useState(true)
|
||||
const [ready, setReady] = useState(false)
|
||||
const [error, setError] = useState(false)
|
||||
|
||||
const createAccount = async () => {
|
||||
let consent = 0
|
||||
if (profile) consent = 1
|
||||
if (profile && measurements) consent = 2
|
||||
if (profile && measurements && openData) consent = 3
|
||||
if (consent > 0 && id) {
|
||||
const result = await backend.confirmSignup({ consent, id })
|
||||
if (result.success) {
|
||||
setToken(result.data.token)
|
||||
setAccount(result.data.account)
|
||||
router.push('/welcome')
|
||||
} else {
|
||||
// Something went wrong
|
||||
console.log('something went wrong')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
// Async inside useEffect requires this approach
|
||||
const getConfirmation = async () => {
|
||||
// Reach out to backend
|
||||
const data = await backend.loadConfirmation({
|
||||
id: confirmationId,
|
||||
check: confirmationCheck,
|
||||
})
|
||||
if (data instanceof Error) setError(true)
|
||||
setReady(true)
|
||||
setId(confirmationId)
|
||||
}
|
||||
// Call async method
|
||||
getConfirmation()
|
||||
}, [])
|
||||
|
||||
// Short-circuit errors
|
||||
if (error)
|
||||
return (
|
||||
<PageWrapper app={app} title={t('joinFreeSewing')} layout={BareLayout} footer={false}>
|
||||
<WelcomeWrapper theme={app.theme}>
|
||||
<SignupLinkExpired />
|
||||
</WelcomeWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
|
||||
const partA = (
|
||||
<>
|
||||
<h5 className="mt-8">{t('profileQuestion')}</h5>
|
||||
{pDetails ? <GdprProfileDetails /> : null}
|
||||
{profile ? (
|
||||
<Checkbox value={profile} setter={setProfile} label={t('yesIDo')} />
|
||||
) : (
|
||||
<>
|
||||
<button
|
||||
className="btn btn-primary btn-lg w-full mt-4 flex flex-row items-center justify-between"
|
||||
onClick={() => setProfile(!profile)}
|
||||
>
|
||||
{t('clickHere')}
|
||||
<span>1/2</span>
|
||||
</button>
|
||||
<p className="text-center">
|
||||
<button
|
||||
className="btn btn-neutral btn-ghost btn-sm"
|
||||
onClick={() => setPDetails(!pDetails)}
|
||||
>
|
||||
{t(pDetails ? 'hideDetails' : 'showDetails')}
|
||||
</button>
|
||||
</p>
|
||||
<Popout warning>{t('noConsentNoAccount')}</Popout>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
const partB = (
|
||||
<>
|
||||
<h5 className="mt-8">{t('peopleQuestion')}</h5>
|
||||
{mDetails ? <GdprMeasurementsDetails /> : null}
|
||||
{measurements ? (
|
||||
<Checkbox value={measurements} setter={setMeasurements} label={t('yesIDo')} />
|
||||
) : (
|
||||
<button
|
||||
className="btn btn-primary btn-lg w-full mt-4 flex flex-row items-center justify-between"
|
||||
onClick={() => setMeasurements(!measurements)}
|
||||
>
|
||||
{t('clickHere')}
|
||||
<span>2/2</span>
|
||||
</button>
|
||||
)}
|
||||
{mDetails && measurements ? (
|
||||
<Checkbox value={openData} setter={setOpenData} label={t('openDataQuestion')} />
|
||||
) : null}
|
||||
{measurements && !openData ? <Popout note>{t('openDataInfo')}</Popout> : null}
|
||||
{!measurements && (
|
||||
<>
|
||||
<p className="text-center">
|
||||
<button
|
||||
className="btn btn-neutral btn-ghost btn-sm"
|
||||
onClick={() => setMDetails(!mDetails)}
|
||||
>
|
||||
{t(mDetails ? 'hideDetails' : 'showDetails')}
|
||||
</button>
|
||||
</p>
|
||||
<Popout warning>{t('noConsentNoPatterns')}</Popout>
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
|
||||
return (
|
||||
<PageWrapper app={app} title={t('joinFreeSewing')} layout={BareLayout} footer={false}>
|
||||
<WelcomeWrapper theme={app.theme}>
|
||||
{ready ? (
|
||||
<>
|
||||
<h1>{t('privacyMatters')}</h1>
|
||||
<p>{t('compliant')}</p>
|
||||
<p>{t('consentWhyAnswer')}</p>
|
||||
{partA}
|
||||
{profile && partB}
|
||||
</>
|
||||
) : (
|
||||
<Spinner className="w-8 h-8 m-auto" />
|
||||
)}
|
||||
{profile && !measurements && (
|
||||
<button
|
||||
className="btn btn-primary btn-outline btn-lg w-full mt-4"
|
||||
onClick={createAccount}
|
||||
>
|
||||
{t('createLimitedAccount')}
|
||||
</button>
|
||||
)}
|
||||
{profile && measurements && (
|
||||
<button
|
||||
onClick={createAccount}
|
||||
className={`btn btn-lg w-full mt-8 ${app.state.loading ? 'btn-accent' : 'btn-primary'}`}
|
||||
>
|
||||
{app.state.loading ? (
|
||||
<>
|
||||
<Spinner />
|
||||
<span>{t('processing')}</span>
|
||||
</>
|
||||
) : (
|
||||
<span>{t('createAccount')}</span>
|
||||
)}
|
||||
</button>
|
||||
)}
|
||||
<p className="text-center opacity-50 mt-12">
|
||||
<Link href="/docs/various/privacy" className="hover:text-secondary underline">
|
||||
FreeSewing Privacy Notice
|
||||
</Link>
|
||||
</p>
|
||||
</WelcomeWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
return <PageWrapper></PageWrapper>
|
||||
}
|
||||
|
||||
export default ConfirmSignUpPage
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// Hooks
|
||||
import { useApp } from 'shared/hooks/use-app.mjs'
|
||||
// Dependencies
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
// Components
|
||||
|
@ -9,17 +7,19 @@ import { V3Wip } from 'shared/components/v3-wip.mjs'
|
|||
// Translation namespaces used on this page
|
||||
const namespaces = [...new Set(['designs', ...pageNs])]
|
||||
|
||||
const DesignsPage = (props) => {
|
||||
const app = useApp(props)
|
||||
|
||||
return (
|
||||
<PageWrapper app={app}>
|
||||
<div className="max-w-2xl">
|
||||
<V3Wip />
|
||||
</div>
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
||||
/*
|
||||
* Each page MUST be wrapped in the PageWrapper component.
|
||||
* You also MUST spread props.page into this wrapper component
|
||||
* when path and locale come from static props (as here)
|
||||
* or set them manually.
|
||||
*/
|
||||
const DesignsPage = ({ page }) => (
|
||||
<PageWrapper {...page}>
|
||||
<div className="max-w-2xl">
|
||||
<V3Wip />
|
||||
</div>
|
||||
</PageWrapper>
|
||||
)
|
||||
|
||||
export default DesignsPage
|
||||
|
||||
|
@ -28,6 +28,7 @@ export async function getStaticProps({ locale }) {
|
|||
props: {
|
||||
...(await serverSideTranslations(locale, namespaces)),
|
||||
page: {
|
||||
locale,
|
||||
path: ['designs'],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// Hooks
|
||||
import { useApp } from 'shared/hooks/use-app.mjs'
|
||||
// Dependencies
|
||||
import Head from 'next/head'
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
|
@ -13,52 +11,44 @@ import { TocWrapper } from 'shared/components/wrappers/toc.mjs'
|
|||
import { PageWrapper } from 'shared/components/wrappers/page.mjs'
|
||||
import { components } from 'site/components/mdx/index.mjs'
|
||||
|
||||
const MdxPage = (props) => {
|
||||
// This hook is used for shared code and global state
|
||||
const app = useApp(props)
|
||||
const title = props.page.title
|
||||
const fullTitle = title + ' - FreeSewing.org'
|
||||
|
||||
/*
|
||||
* Each page should be wrapped in the Page wrapper component
|
||||
* You MUST pass it the result of useApp() as the `app` prop
|
||||
* and for MDX pages you can spread the props.page props to it
|
||||
* to automatically set the title and navigation
|
||||
*
|
||||
* Like breadcrumbs and updating the primary navigation with
|
||||
* active state
|
||||
*/
|
||||
return (
|
||||
<PageWrapper app={app} {...props.page}>
|
||||
<Head>
|
||||
<meta property="og:title" content={props.page.title} key="title" />
|
||||
<meta property="og:type" content="article" key="type" />
|
||||
<meta property="og:description" content={props.intro} key="type" />
|
||||
<meta property="og:article:author" content="Joost De Cock" key="author" />
|
||||
<meta
|
||||
property="og:image"
|
||||
content={`https://canary.backend.freesewing.org/og-img/en/dev/${props.page.slug}`}
|
||||
key="image"
|
||||
/>
|
||||
<meta property="og:image:type" content="image/png" />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
<meta property="og:url" content={`https://freesewing.dev/${props.page.slug}`} key="url" />
|
||||
<meta property="og:locale" content="en_US" key="locale" />
|
||||
<meta property="og:site_name" content="freesewing.dev" key="site" />
|
||||
<title>{fullTitle}</title>
|
||||
</Head>
|
||||
<div className="flex flex-row-reverse flex-wrap xl:flex-nowrap justify-end">
|
||||
{props.toc && (
|
||||
<div className="mb-8 w-full xl:w-80 2xl:w-96 xl:pl-8 2xl:pl-16">
|
||||
<TocWrapper toc={props.toc} app={app} />
|
||||
</div>
|
||||
)}
|
||||
<MdxWrapper mdx={props.mdx} app={app} components={components} />
|
||||
/*
|
||||
* Each page MUST be wrapped in the PageWrapper component.
|
||||
* You also MUST spread props.page into this wrapper component
|
||||
* when path and locale come from static props (as here)
|
||||
* or set them manually.
|
||||
*/
|
||||
const MdxPage = ({ page, mdx, toc, intro }) => (
|
||||
<PageWrapper {...page}>
|
||||
<Head>
|
||||
<meta property="og:title" content={page.t} key="title" />
|
||||
<meta property="og:type" content="article" key="type" />
|
||||
<meta property="og:description" content={intro} key="type" />
|
||||
<meta property="og:article:author" content="Joost De Cock" key="author" />
|
||||
<meta
|
||||
property="og:image"
|
||||
content={`https://canary.backend.freesewing.org/og-img/en/dev/${page.slug}`}
|
||||
key="image"
|
||||
/>
|
||||
<meta property="og:image:type" content="image/png" />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
<meta property="og:url" content={`https://freesewing.dev/${page.slug}`} key="url" />
|
||||
<meta property="og:locale" content="en_US" key="locale" />
|
||||
<meta property="og:site_name" content="freesewing.dev" key="site" />
|
||||
<title>{typeof page.t === 'string' ? page.t : ''} - FreeSewing.org</title>
|
||||
</Head>
|
||||
<div className="flex flex-row-reverse flex-wrap xl:flex-nowrap justify-end">
|
||||
{toc && (
|
||||
<div className="mb-8 w-full xl:w-80 2xl:w-96 xl:pl-8 2xl:pl-16">
|
||||
<TocWrapper toc={toc} />
|
||||
</div>
|
||||
)}
|
||||
<div className="max-w-prose">
|
||||
<MdxWrapper mdx={mdx} components={components} />
|
||||
</div>
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</PageWrapper>
|
||||
)
|
||||
|
||||
/*
|
||||
* Default export is the NextJS page object
|
||||
|
@ -92,6 +82,7 @@ export async function getStaticProps({ params, locale }) {
|
|||
intro: intro.join(' '),
|
||||
page: {
|
||||
path, // path to page as array
|
||||
locale,
|
||||
...mdxMeta[locale][path.join('/')],
|
||||
},
|
||||
params,
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// Hooks
|
||||
import { useApp } from 'shared/hooks/use-app.mjs'
|
||||
// Dependencies
|
||||
import Head from 'next/head'
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
|
@ -9,60 +7,55 @@ import mdxMeta from 'site/prebuild/mdx.js'
|
|||
// Components
|
||||
import { MdxWrapper } from 'shared/components/wrappers/mdx.mjs'
|
||||
import { TocWrapper } from 'shared/components/wrappers/toc.mjs'
|
||||
import { PageWrapper } from 'shared/components/wrappers/page.mjs'
|
||||
import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs'
|
||||
import { components } from 'site/components/mdx/index.mjs'
|
||||
|
||||
const MdxPage = (props) => {
|
||||
// This hook is used for shared code and global state
|
||||
const app = useApp(props)
|
||||
const title = props.page.title
|
||||
const fullTitle = title + ' - FreeSewing.org'
|
||||
// Translation namespaces used on this page
|
||||
const namespaces = [...new Set(['docs', ...pageNs])]
|
||||
|
||||
/*
|
||||
* Each page should be wrapped in the Page wrapper component
|
||||
* You MUST pass it the result of useApp() as the `app` prop
|
||||
* and for MDX pages you can spread the props.page props to it
|
||||
* to automatically set the title and navigation
|
||||
*
|
||||
* Like breadcrumbs and updating the primary navigation with
|
||||
* active state
|
||||
*/
|
||||
return (
|
||||
<PageWrapper app={app} {...props.page}>
|
||||
<Head>
|
||||
<meta property="og:title" content={props.page.title} key="title" />
|
||||
<meta property="og:type" content="article" key="type" />
|
||||
<meta property="og:description" content={props.intro} key="type" />
|
||||
<meta property="og:article:author" content="Joost De Cock" key="author" />
|
||||
<meta
|
||||
property="og:image"
|
||||
content={`https://canary.backend.freesewing.org/og-img/en/dev/${props.page.slug}`}
|
||||
key="image"
|
||||
/>
|
||||
<meta property="og:image:type" content="image/png" />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
<meta property="og:url" content={`https://freesewing.dev/${props.page.slug}`} key="url" />
|
||||
<meta property="og:locale" content="en_US" key="locale" />
|
||||
<meta property="og:site_name" content="freesewing.dev" key="site" />
|
||||
<title>{fullTitle}</title>
|
||||
</Head>
|
||||
<div className="flex flex-row-reverse flex-wrap xl:flex-nowrap justify-end">
|
||||
{props.toc && (
|
||||
<div className="mb-8 w-full xl:w-80 2xl:w-96 xl:pl-8 2xl:pl-16">
|
||||
<TocWrapper toc={props.toc} app={app} />
|
||||
</div>
|
||||
)}
|
||||
<MdxWrapper mdx={props.mdx} app={app} components={components} />
|
||||
/*
|
||||
* Each page MUST be wrapped in the PageWrapper component.
|
||||
* You also MUST spread props.page into this wrapper component
|
||||
* when path and locale come from static props (as here)
|
||||
* or set them manually.
|
||||
*/
|
||||
const DocsIndexPage = ({ page, mdx, toc, intro }) => (
|
||||
<PageWrapper {...page}>
|
||||
<Head>
|
||||
<meta property="og:title" content={page.t} key="title" />
|
||||
<meta property="og:type" content="article" key="type" />
|
||||
<meta property="og:description" content={intro} key="type" />
|
||||
<meta property="og:article:author" content="Joost De Cock" key="author" />
|
||||
<meta
|
||||
property="og:image"
|
||||
content={`https://canary.backend.freesewing.org/og-img/en/dev/${page.slug}`}
|
||||
key="image"
|
||||
/>
|
||||
<meta property="og:image:type" content="image/png" />
|
||||
<meta property="og:image:width" content="1200" />
|
||||
<meta property="og:image:height" content="630" />
|
||||
<meta property="og:url" content={`https://freesewing.dev/${page.slug}`} key="url" />
|
||||
<meta property="og:locale" content="en_US" key="locale" />
|
||||
<meta property="og:site_name" content="freesewing.dev" key="site" />
|
||||
<title>{typeof page.t === 'string' ? page.t : ''} - FreeSewing.org</title>
|
||||
</Head>
|
||||
<div className="flex flex-row-reverse flex-wrap xl:flex-nowrap justify-end">
|
||||
{toc && (
|
||||
<div className="mb-8 w-full xl:w-80 2xl:w-96 xl:pl-8 2xl:pl-16">
|
||||
<TocWrapper toc={toc} />
|
||||
</div>
|
||||
)}
|
||||
<div className="max-w-prose">
|
||||
<MdxWrapper mdx={mdx} components={components} />
|
||||
</div>
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</PageWrapper>
|
||||
)
|
||||
|
||||
/*
|
||||
* Default export is the NextJS page object
|
||||
*/
|
||||
export default MdxPage
|
||||
export default DocsIndexPage
|
||||
|
||||
/*
|
||||
* getStaticProps() is used to fetch data at build-time.
|
||||
|
@ -85,9 +78,10 @@ export async function getStaticProps({ locale }) {
|
|||
intro: intro.join(' '),
|
||||
page: {
|
||||
path: ['docs'],
|
||||
locale,
|
||||
...mdxMeta[locale]['docs'],
|
||||
},
|
||||
...(await serverSideTranslations(locale)),
|
||||
...(await serverSideTranslations(locale, namespaces)),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// Hooks
|
||||
import { useApp } from 'shared/hooks/use-app.mjs'
|
||||
// Dependencies
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
//import { useTranslation } from 'next-i18next'
|
||||
|
@ -10,52 +8,50 @@ import { Popout } from 'shared/components/popout.mjs'
|
|||
import { BareLayout } from 'site/components/layouts/bare.mjs'
|
||||
import { PageLink } from 'shared/components/page-link.mjs'
|
||||
|
||||
const HomePage = (props) => {
|
||||
const app = useApp(props)
|
||||
const title = 'Welcome to FreeSewing.org'
|
||||
// Not using translation for now
|
||||
// const { t } = useTranslation(['homepage', 'ograph'])
|
||||
|
||||
return (
|
||||
<PageWrapper app={app} title={title} layout={BareLayout}>
|
||||
<Head>
|
||||
<title>{title}</title>
|
||||
</Head>
|
||||
<div>
|
||||
<div className="max-w-xl m-auto my-32 px-6">
|
||||
<Popout fixme>
|
||||
Create homepage. Meanwhile check <PageLink href="/signup" txt="the signup flow" />
|
||||
</Popout>
|
||||
<h2>What is FreeSewing?</h2>
|
||||
<small>(by ChatGPT)</small>
|
||||
<p>
|
||||
Freesewing is an open-source pattern making software that allows users to generate
|
||||
custom sewing patterns based on their own measurements. It is designed to be flexible
|
||||
and customizable, and can be used to create a wide range of garments, from simple
|
||||
t-shirts and skirts to more complex dresses and jackets.
|
||||
</p>
|
||||
<p>
|
||||
Freesewing is available for free, and users can access a wide range of pre-made patterns
|
||||
or create their own from scratch. The software is designed to be easy to use, with an
|
||||
intuitive interface that guides users through the process of creating a pattern
|
||||
step-by-step.
|
||||
</p>
|
||||
<p>
|
||||
In addition to the pattern making software, freesewing also has an active online
|
||||
community of sewists and pattern makers who share tips, techniques, and advice on all
|
||||
aspects of sewing. The community also collaborates on creating new patterns and
|
||||
improving existing ones, and users can contribute their own patterns to the project as
|
||||
well.
|
||||
</p>
|
||||
<p>
|
||||
Overall, freesewing is a powerful tool for anyone interested in sewing and pattern
|
||||
making, whether they are seasoned professionals or beginners just starting out.
|
||||
</p>
|
||||
</div>
|
||||
/*
|
||||
* Each page MUST be wrapped in the PageWrapper component.
|
||||
* You also MUST spread props.page into this wrapper component
|
||||
* when path and locale come from static props (as here)
|
||||
* or set them manually.
|
||||
*/
|
||||
const HomePage = ({ page }) => (
|
||||
<PageWrapper {...page} layout={BareLayout}>
|
||||
<Head>
|
||||
<title>Welcome to FreeSewing.org</title>
|
||||
</Head>
|
||||
<div>
|
||||
<div className="max-w-xl m-auto my-32 px-6">
|
||||
<Popout fixme>
|
||||
Create homepage. Meanwhile check <PageLink href="/signup" txt="the signup flow" />
|
||||
</Popout>
|
||||
<h2>What is FreeSewing?</h2>
|
||||
<small>(by ChatGPT)</small>
|
||||
<p>
|
||||
Freesewing is an open-source pattern making software that allows users to generate custom
|
||||
sewing patterns based on their own measurements. It is designed to be flexible and
|
||||
customizable, and can be used to create a wide range of garments, from simple t-shirts and
|
||||
skirts to more complex dresses and jackets.
|
||||
</p>
|
||||
<p>
|
||||
Freesewing is available for free, and users can access a wide range of pre-made patterns
|
||||
or create their own from scratch. The software is designed to be easy to use, with an
|
||||
intuitive interface that guides users through the process of creating a pattern
|
||||
step-by-step.
|
||||
</p>
|
||||
<p>
|
||||
In addition to the pattern making software, freesewing also has an active online community
|
||||
of sewists and pattern makers who share tips, techniques, and advice on all aspects of
|
||||
sewing. The community also collaborates on creating new patterns and improving existing
|
||||
ones, and users can contribute their own patterns to the project as well.
|
||||
</p>
|
||||
<p>
|
||||
Overall, freesewing is a powerful tool for anyone interested in sewing and pattern making,
|
||||
whether they are seasoned professionals or beginners just starting out.
|
||||
</p>
|
||||
</div>
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</PageWrapper>
|
||||
)
|
||||
|
||||
export default HomePage
|
||||
|
||||
|
@ -64,6 +60,7 @@ export async function getStaticProps({ locale }) {
|
|||
props: {
|
||||
...(await serverSideTranslations(locale)),
|
||||
page: {
|
||||
locale,
|
||||
path: [],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// Hooks
|
||||
import { useApp } from 'shared/hooks/use-app.mjs'
|
||||
// Dependencies
|
||||
import dynamic from 'next/dynamic'
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
|
@ -25,25 +23,28 @@ const DynamicNewApikey = dynamic(
|
|||
{ ssr: false }
|
||||
)
|
||||
|
||||
const AccountPage = (props) => {
|
||||
const app = useApp(props)
|
||||
/*
|
||||
* Each page MUST be wrapped in the PageWrapper component.
|
||||
* You also MUST spread props.page into this wrapper component
|
||||
* when path and locale come from static props (as here)
|
||||
* or set them manually.
|
||||
*/
|
||||
const NewApikeyPage = ({ page }) => (
|
||||
<PageWrapper {...page}>
|
||||
<DynamicAuthWrapper>
|
||||
<DynamicNewApikey standAlone />
|
||||
</DynamicAuthWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
|
||||
return (
|
||||
<PageWrapper app={app}>
|
||||
<DynamicAuthWrapper app={app}>
|
||||
<DynamicNewApikey app={app} standAlone />
|
||||
</DynamicAuthWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export default AccountPage
|
||||
export default NewApikeyPage
|
||||
|
||||
export async function getStaticProps({ locale }) {
|
||||
return {
|
||||
props: {
|
||||
...(await serverSideTranslations(locale, namespaces)),
|
||||
page: {
|
||||
locale,
|
||||
path: ['new', 'apikey'],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// Hooks
|
||||
import { useApp } from 'shared/hooks/use-app.mjs'
|
||||
// Dependencies
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
// Components
|
||||
|
@ -10,30 +8,33 @@ import { Popout } from 'shared/components/popout.mjs'
|
|||
// Note that we include the account namespace here for the 'new' keyword
|
||||
const namespaces = [...pageNs, 'account']
|
||||
|
||||
const AccountPage = (props) => {
|
||||
const app = useApp(props)
|
||||
/*
|
||||
* Each page MUST be wrapped in the PageWrapper component.
|
||||
* You also MUST spread props.page into this wrapper component
|
||||
* when path and locale come from static props (as here)
|
||||
* or set them manually.
|
||||
*/
|
||||
const NewIndexPage = ({ page }) => (
|
||||
<PageWrapper {...page}>
|
||||
<div className="max-w-lg">
|
||||
<Popout fixme>
|
||||
<h5>This needs an umbrella page</h5>
|
||||
<p>
|
||||
We need to create content here linking to all the <em>new something</em> pages
|
||||
</p>
|
||||
</Popout>
|
||||
</div>
|
||||
</PageWrapper>
|
||||
)
|
||||
|
||||
return (
|
||||
<PageWrapper app={app}>
|
||||
<div className="max-w-lg">
|
||||
<Popout fixme>
|
||||
<h5>This needs an umbrella page</h5>
|
||||
<p>
|
||||
We need to create content here linking to all the <em>new something</em> pages
|
||||
</p>
|
||||
</Popout>
|
||||
</div>
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export default AccountPage
|
||||
export default NewIndexPage
|
||||
|
||||
export async function getStaticProps({ locale }) {
|
||||
return {
|
||||
props: {
|
||||
...(await serverSideTranslations(locale, namespaces)),
|
||||
page: {
|
||||
locale,
|
||||
path: ['new'],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
// Hooks
|
||||
import { useApp } from 'shared/hooks/use-app.mjs'
|
||||
import { useAccount } from 'shared/hooks/use-account.mjs'
|
||||
// Dependencies
|
||||
import dynamic from 'next/dynamic'
|
||||
|
@ -28,14 +27,18 @@ const DynamicAccountProfile = dynamic(
|
|||
{ ssr: false }
|
||||
)
|
||||
|
||||
const AccountPage = (props) => {
|
||||
const app = useApp(props)
|
||||
/*
|
||||
* Each page MUST be wrapped in the PageWrapper component.
|
||||
* You also MUST spread props.page into this wrapper component
|
||||
* when path and locale come from static props (as here)
|
||||
* or set them manually.
|
||||
*/
|
||||
const ProfilePage = ({ page }) => {
|
||||
const { account } = useAccount()
|
||||
|
||||
return (
|
||||
<PageWrapper app={app}>
|
||||
<DynamicAuthWrapper app={app}>
|
||||
<DynamicAccountProfile app={app} />
|
||||
<PageWrapper {...page}>
|
||||
<DynamicAuthWrapper>
|
||||
<DynamicAccountProfile />
|
||||
<Popout link compact>
|
||||
<PageLink href={`/users/${account.username}`} txt={`/users/${account.username}`} />
|
||||
</Popout>
|
||||
|
@ -45,13 +48,14 @@ const AccountPage = (props) => {
|
|||
)
|
||||
}
|
||||
|
||||
export default AccountPage
|
||||
export default ProfilePage
|
||||
|
||||
export async function getStaticProps({ locale }) {
|
||||
return {
|
||||
props: {
|
||||
...(await serverSideTranslations(locale, namespaces)),
|
||||
page: {
|
||||
locale,
|
||||
path: ['profile'],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// Hooks
|
||||
import { useApp } from 'shared/hooks/use-app.mjs'
|
||||
// Dependencies
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
// Components
|
||||
|
@ -7,19 +5,21 @@ import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs'
|
|||
import { V3Wip } from 'shared/components/v3-wip.mjs'
|
||||
|
||||
// Translation namespaces used on this page
|
||||
const namespaces = [...new Set(['designs', ...pageNs])]
|
||||
const namespaces = [...new Set(['showcase', ...pageNs])]
|
||||
|
||||
const DesignsPage = (props) => {
|
||||
const app = useApp(props)
|
||||
|
||||
return (
|
||||
<PageWrapper app={app}>
|
||||
<div className="max-w-2xl">
|
||||
<V3Wip />
|
||||
</div>
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
||||
/*
|
||||
* Each page MUST be wrapped in the PageWrapper component.
|
||||
* You also MUST spread props.page into this wrapper component
|
||||
* when path and locale come from static props (as here)
|
||||
* or set them manually.
|
||||
*/
|
||||
const DesignsPage = ({ page }) => (
|
||||
<PageWrapper {...page}>
|
||||
<div className="max-w-2xl">
|
||||
<V3Wip />
|
||||
</div>
|
||||
</PageWrapper>
|
||||
)
|
||||
|
||||
export default DesignsPage
|
||||
|
||||
|
@ -28,6 +28,7 @@ export async function getStaticProps({ locale }) {
|
|||
props: {
|
||||
...(await serverSideTranslations(locale, namespaces)),
|
||||
page: {
|
||||
locale,
|
||||
path: ['showcase'],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
// Hooks
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useApp } from 'shared/hooks/use-app.mjs'
|
||||
import { useState, useEffect, useContext } from 'react'
|
||||
import { useAccount } from 'shared/hooks/use-account.mjs'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { useBackend } from 'shared/hooks/use-backend.mjs'
|
||||
import { useToast } from 'shared/hooks/use-toast.mjs'
|
||||
import { useRouter } from 'next/router'
|
||||
// Context
|
||||
import { LoadingContext } from 'shared/context/loading-context.mjs'
|
||||
// Dependencies
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
// Components
|
||||
|
@ -55,11 +56,17 @@ export const ButtonText = ({ children }) => (
|
|||
<div className="flex flex-row items-center justify-between w-full">{children}</div>
|
||||
)
|
||||
|
||||
const SignInPage = (props) => {
|
||||
const app = useApp(props)
|
||||
const { setAccount, setToken } = useAccount()
|
||||
/*
|
||||
* Each page MUST be wrapped in the PageWrapper component.
|
||||
* You also MUST spread props.page into this wrapper component
|
||||
* when path and locale come from static props (as here)
|
||||
* or set them manually.
|
||||
*/
|
||||
const SignInPage = ({ page }) => {
|
||||
const { startLoading, stopLoading } = useContext(LoadingContext)
|
||||
const { setAccount, setToken, seenUser, setSeenUser, clear } = useAccount()
|
||||
const { t } = useTranslation(['signin', 'signup', 'toast'])
|
||||
const backend = useBackend(app)
|
||||
const backend = useBackend()
|
||||
const toast = useToast()
|
||||
const router = useRouter()
|
||||
|
||||
|
@ -69,8 +76,9 @@ const SignInPage = (props) => {
|
|||
const [magicLink, setMagicLink] = useState(true)
|
||||
const [signInFailed, setSignInFailed] = useState(false)
|
||||
const [magicLinkSent, setMagicLinkSent] = useState(false)
|
||||
const [seenBefore, setSeenBefore] = useState(false)
|
||||
|
||||
const clearUsername = () => app.setUsername(false)
|
||||
const clearUsername = () => setUsername(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof window !== 'undefined' && signInFailed) {
|
||||
|
@ -78,9 +86,20 @@ const SignInPage = (props) => {
|
|||
}
|
||||
}, [signInFailed])
|
||||
|
||||
// Avoid SSR rendering mismatch by setting this in effect
|
||||
useEffect(() => {
|
||||
if (seenUser) {
|
||||
setSeenBefore(seenUser)
|
||||
setUsername(seenUser)
|
||||
} else {
|
||||
setSeenBefore(false)
|
||||
setUsername('')
|
||||
}
|
||||
}, [seenUser])
|
||||
|
||||
const signinHandler = async (evt) => {
|
||||
evt.preventDefault()
|
||||
app.startLoading()
|
||||
startLoading()
|
||||
const result = magicLink
|
||||
? await backend.signIn({ username, password: false })
|
||||
: await backend.signIn({ username, password })
|
||||
|
@ -93,7 +112,9 @@ const SignInPage = (props) => {
|
|||
} else {
|
||||
setAccount(result.data.account)
|
||||
setToken(result.data.token)
|
||||
msg = t('signin:welcomeName', { name: result.data.account.username })
|
||||
setSeenUser(result.data.account.username)
|
||||
msg = t('signin:welcomeBackName', { name: result.data.account.username })
|
||||
stopLoading()
|
||||
router.push('/account')
|
||||
}
|
||||
return toast.success(<b>{msg}</b>)
|
||||
|
@ -116,7 +137,7 @@ const SignInPage = (props) => {
|
|||
setSignInFailed(msg)
|
||||
return toast.warning(<b>{msg}</b>)
|
||||
}
|
||||
app.stopLoading()
|
||||
stopLoading()
|
||||
}
|
||||
|
||||
const btnClasses = `btn capitalize w-full mt-4 ${
|
||||
|
@ -132,8 +153,8 @@ const SignInPage = (props) => {
|
|||
|
||||
if (magicLinkSent)
|
||||
return (
|
||||
<PageWrapper app={app} title={t('welcomeBack')} layout={BareLayout} footer={false}>
|
||||
<SusiWrapper theme={app.theme}>
|
||||
<PageWrapper {...page} title={t('welcomeBack')} layout={BareLayout} footer={false}>
|
||||
<SusiWrapper>
|
||||
<h1 className="text-neutral-content font-light text-3xl mb-4 pb-0 text-center">
|
||||
{t('signup:emailSent')}
|
||||
</h1>
|
||||
|
@ -154,15 +175,15 @@ const SignInPage = (props) => {
|
|||
)
|
||||
|
||||
return (
|
||||
<PageWrapper app={app} title={t('welcomeBack')} layout={BareLayout} footer={false}>
|
||||
<SusiWrapper theme={app.theme}>
|
||||
<PageWrapper {...page} title={t('welcomeBack')} layout={BareLayout} footer={false}>
|
||||
<SusiWrapper>
|
||||
<h1 className="text-neutral-content font-light text-3xl mb-4 pb-0 text-center">
|
||||
{t('signin:welcomeName', { name: app.username || '' })}
|
||||
{seenBefore ? t('signin:welcomeBackName', { name: seenUser }) : t('signin:welcome')}
|
||||
</h1>
|
||||
<p className="text-neutral-content text-center">
|
||||
{t('signin:signInToThing', { thing: 'FreeSewing.org' })}
|
||||
</p>
|
||||
{!app.username && <UsernameField {...{ username, setUsername, t }} />}
|
||||
{!seenBefore && <UsernameField {...{ username, setUsername, t }} />}
|
||||
{magicLink ? (
|
||||
<button className={btnClasses} tabIndex="-1" role="button" onClick={signinHandler}>
|
||||
{signInFailed ? (
|
||||
|
@ -190,29 +211,31 @@ const SignInPage = (props) => {
|
|||
</button>
|
||||
</>
|
||||
)}
|
||||
<ul className="mt-4 flex flex-row gap-2 text-sm items-center justify-center">
|
||||
<ul className="mt-4 mb-2 flex flex-row gap-2 text-sm items-center justify-center">
|
||||
<li>
|
||||
<button className={darkLinkClasses} onClick={() => setMagicLink(!magicLink)}>
|
||||
{magicLink ? t('signin:signInWith') : t('signin:emailSignInLink')}
|
||||
{magicLink ? t('signin:usePassword') : t('signin:emailSignInLink')}
|
||||
</button>
|
||||
</li>
|
||||
{app.username ? (
|
||||
{seenBefore ? (
|
||||
<>
|
||||
<li>|</li>
|
||||
<li>
|
||||
<button className={darkLinkClasses} onClick={clearUsername}>
|
||||
<button className={darkLinkClasses} onClick={() => setSeenUser(false)}>
|
||||
Sign in as another user
|
||||
</button>
|
||||
</li>
|
||||
</>
|
||||
) : null}
|
||||
</ul>
|
||||
<p className="text-neutral-content text-sm mt-4 opacity-80 text-center">
|
||||
{t('signin:dontHaveAnAccount')}{' '}
|
||||
<Link className={darkLinkClasses} href="/signup">
|
||||
{t('signin:signUpHere')}
|
||||
</Link>
|
||||
</p>
|
||||
{!seenBefore ? (
|
||||
<p className="text-neutral-content text-sm mt-4 opacity-80 text-center">
|
||||
{t('signin:dontHaveAnAccount')}{' '}
|
||||
<Link className={darkLinkClasses} href="/signup">
|
||||
{t('signin:signUpHere')}
|
||||
</Link>
|
||||
</p>
|
||||
) : null}
|
||||
</SusiWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
|
@ -225,6 +248,7 @@ export async function getStaticProps({ locale }) {
|
|||
props: {
|
||||
...(await serverSideTranslations(locale)),
|
||||
page: {
|
||||
locale,
|
||||
path: ['signin'],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
welcomeName: "Welcome { name }"
|
||||
welcome: Welcome
|
||||
welcomeBackName: "Welcome back { name }"
|
||||
emailUsernameId: "Your Email address, Username, or User #"
|
||||
password: Your Password
|
||||
dontHaveAnAccount: Don't have an account yet?
|
||||
signIn: Sign in
|
||||
signInToThing: "Sign in to { thing }"
|
||||
signInWith: Sign in with username and password
|
||||
usePassword: Use your password
|
||||
signUpHere: Sign up here
|
||||
processing: Processing
|
||||
signInFailed: Sign in failed
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
// Hooks
|
||||
import { useState } from 'react'
|
||||
import { useApp } from 'shared/hooks/use-app.mjs'
|
||||
import { useState, useContext } from 'react'
|
||||
import { useBackend } from 'shared/hooks/use-backend.mjs'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
// Context
|
||||
import { ModalContext } from 'shared/context/modal-context.mjs'
|
||||
// Dependencies
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
import { validateEmail, validateTld } from 'site/utils.mjs'
|
||||
|
@ -25,8 +26,10 @@ const DarkLink = ({ href, txt }) => (
|
|||
</Link>
|
||||
)
|
||||
|
||||
const SignUpPage = (props) => {
|
||||
const app = useApp(props)
|
||||
const SignUpPage = ({ page }) => {
|
||||
// Context
|
||||
const { setModal } = useContext(ModalContext)
|
||||
|
||||
const backend = useBackend()
|
||||
const { t, i18n } = useTranslation(namespaces)
|
||||
|
||||
|
@ -34,6 +37,7 @@ const SignUpPage = (props) => {
|
|||
const [emailValid, setEmailValid] = useState(false)
|
||||
const [result, setResult] = useState(false)
|
||||
const [error, setError] = useState(null)
|
||||
const [loading, setLoading] = useState(false)
|
||||
|
||||
const updateEmail = (evt) => {
|
||||
const value = evt.target.value
|
||||
|
@ -44,6 +48,7 @@ const SignUpPage = (props) => {
|
|||
|
||||
const signupHandler = async (evt) => {
|
||||
evt.preventDefault()
|
||||
setLoading(true)
|
||||
if (!emailValid) return
|
||||
let res
|
||||
try {
|
||||
|
@ -53,14 +58,13 @@ const SignUpPage = (props) => {
|
|||
setResult,
|
||||
})
|
||||
} catch (err) {
|
||||
setError(app.error(err))
|
||||
// Here to keep the stupid linter happy
|
||||
console.log(err)
|
||||
}
|
||||
if (res.result === 'success') setResult('success')
|
||||
else {
|
||||
app.setModal(
|
||||
<ModalWrapper app={app} bg="base-100 lg:bg-base-300">
|
||||
setModal(
|
||||
<ModalWrapper bg="base-100 lg:bg-base-300">
|
||||
<div className="bg-base-100 rounded-lg p-4 lg:px-8 max-w-xl lg:shadow-lg">
|
||||
<h3>An error occured while trying to process your request</h3>
|
||||
<Robot pose="ohno" className="m-auto w-56" embed />
|
||||
|
@ -80,13 +84,14 @@ const SignUpPage = (props) => {
|
|||
</ModalWrapper>
|
||||
)
|
||||
}
|
||||
setLoading(false)
|
||||
}
|
||||
|
||||
const loadingClasses = app.state.loading ? 'opacity-50' : ''
|
||||
const loadingClasses = loading ? 'opacity-50' : ''
|
||||
|
||||
return (
|
||||
<PageWrapper app={app} title={t('joinFreeSewing')} layout={BareLayout} footer={false}>
|
||||
<SusiWrapper theme={app.theme} error={result && result !== 'success'}>
|
||||
<PageWrapper {...page} title={t('joinFreeSewing')} layout={BareLayout} footer={false}>
|
||||
<SusiWrapper error={result && result !== 'success'}>
|
||||
<h1 className={`text-neutral-content font-light text-3xl mb-0 pb-0 ${loadingClasses}`}>
|
||||
{result ? (
|
||||
result === 'success' ? (
|
||||
|
@ -145,7 +150,6 @@ const SignUpPage = (props) => {
|
|||
/>
|
||||
<EmailValidButton
|
||||
email={email}
|
||||
app={app}
|
||||
t={t}
|
||||
validText={t('emailSignupLink')}
|
||||
invalidText={t('pleaseProvideValidEmail')}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// Hooks
|
||||
import { useApp } from 'shared/hooks/use-app.mjs'
|
||||
// Dependencies
|
||||
import { serverSideTranslations } from 'next-i18next/serverSideTranslations'
|
||||
// Components
|
||||
|
@ -9,91 +7,88 @@ import { Popout } from 'shared/components/popout.mjs'
|
|||
// Translation namespaces used on this page
|
||||
const namespaces = [...new Set(pageNs)]
|
||||
|
||||
const TypographyPage = (props) => {
|
||||
const app = useApp(props)
|
||||
// Re-use this
|
||||
const p = (
|
||||
<p>
|
||||
This paragraph is here to show the vertical spacing between headings and paragraphs. In
|
||||
addition, let's make it a bit longer so we can see the line height as the text wraps.
|
||||
</p>
|
||||
)
|
||||
|
||||
const p = (
|
||||
<p>
|
||||
This paragraph is here to show the vertical spacing between headings and paragraphs. In
|
||||
addition, let's make it a bit longer so we can see the line height as the text wraps.
|
||||
</p>
|
||||
)
|
||||
|
||||
return (
|
||||
<PageWrapper app={app}>
|
||||
<div className="text-primary mdx max-w-prose text-base-content max-w-prose text-base xl:pl-4">
|
||||
<p>This typography page shows an overview of different elements and how they are styled.</p>
|
||||
<p>It's a good starting point for theme development.</p>
|
||||
<h2>Headings (this is h2)</h2>
|
||||
{p} {p}
|
||||
<h3>This is h3</h3>
|
||||
{p} {p}
|
||||
<h4>This is h4</h4>
|
||||
{p} {p}
|
||||
<h5>This is h5</h5>
|
||||
{p} {p}
|
||||
<h6>This is h6</h6>
|
||||
{p} {p}
|
||||
<h2>Links and buttons</h2>
|
||||
<p>
|
||||
A regular link <a href="#">looks like this</a>, whereas buttons look like this:
|
||||
</p>
|
||||
<h3>Main button styles</h3>
|
||||
<div className="flex flex-row gap-2 flex-wrap">
|
||||
<button className="btn btn-neutral">Neutral button</button>
|
||||
<button className="btn btn-primary">Primary button</button>
|
||||
<button className="btn btn-secondary">Secondary button</button>
|
||||
<button className="btn btn-accent">Accent button</button>
|
||||
</div>
|
||||
<h3>State button styles</h3>
|
||||
<div className="flex flex-row gap-2 flex-wrap">
|
||||
<button className="btn btn-info">Info button</button>
|
||||
<button className="btn btn-success">Success button</button>
|
||||
<button className="btn btn-warning">Warning button</button>
|
||||
<button className="btn btn-error">Error button</button>
|
||||
</div>
|
||||
<h3>Other button styles</h3>
|
||||
<div className="flex flex-row gap-2 flex-wrap">
|
||||
<button className="btn btn-ghost">Ghost button</button>
|
||||
<button className="btn btn-link">Link button</button>
|
||||
</div>
|
||||
<h3>Outlined button styles</h3>
|
||||
<div className="flex flex-row gap-2 flex-wrap">
|
||||
<button className="btn btn-outline btn-neutral">Neutral button</button>
|
||||
<button className="btn btn-outline btn-primary">Primary button</button>
|
||||
<button className="btn btn-outline btn-secondary">Secondary button</button>
|
||||
<button className="btn btn-outline btn-accent">Accent button</button>
|
||||
</div>
|
||||
<h3>Button sizes</h3>
|
||||
<div className="flex flex-row gap-2 flex-wrap">
|
||||
<button className="btn btn-primary btn-lg">Large</button>
|
||||
<button className="btn btn-primary">Normal</button>
|
||||
<button className="btn btn-primary btn-sm">Small</button>
|
||||
<button className="btn btn-primary btn-xs">Tiny</button>
|
||||
<button className="btn btn-primary btn-lg btn-wide">Large wide</button>
|
||||
<button className="btn btn-primary btn-wide">Normal wide</button>
|
||||
<button className="btn btn-primary btn-sm btn-wide">Small wide</button>
|
||||
<button className="btn btn-primary btn-xs bnt-wide">Tiny wide</button>
|
||||
</div>
|
||||
<h2>Popouts</h2>
|
||||
<p>The Popout component is what powers various custom MDX components under the hood:</p>
|
||||
{['note', 'tip', 'warning', 'fixme', 'link', 'related', 'none'].map((type) => {
|
||||
const props = {}
|
||||
props[type] = true
|
||||
return (
|
||||
<div key={type}>
|
||||
<h3 className="capitalize">{type}</h3>
|
||||
<Popout {...props}>
|
||||
<h5>I am the {type} title</h5>
|
||||
{p}
|
||||
</Popout>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
const TypographyPage = ({ page }) => (
|
||||
<PageWrapper {...page}>
|
||||
<div className="text-primary mdx max-w-prose text-base-content max-w-prose text-base xl:pl-4">
|
||||
<p>This typography page shows an overview of different elements and how they are styled.</p>
|
||||
<p>It's a good starting point for theme development.</p>
|
||||
<h2>Headings (this is h2)</h2>
|
||||
{p} {p}
|
||||
<h3>This is h3</h3>
|
||||
{p} {p}
|
||||
<h4>This is h4</h4>
|
||||
{p} {p}
|
||||
<h5>This is h5</h5>
|
||||
{p} {p}
|
||||
<h6>This is h6</h6>
|
||||
{p} {p}
|
||||
<h2>Links and buttons</h2>
|
||||
<p>
|
||||
A regular link <a href="#">looks like this</a>, whereas buttons look like this:
|
||||
</p>
|
||||
<h3>Main button styles</h3>
|
||||
<div className="flex flex-row gap-2 flex-wrap">
|
||||
<button className="btn btn-neutral">Neutral button</button>
|
||||
<button className="btn btn-primary">Primary button</button>
|
||||
<button className="btn btn-secondary">Secondary button</button>
|
||||
<button className="btn btn-accent">Accent button</button>
|
||||
</div>
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
||||
<h3>State button styles</h3>
|
||||
<div className="flex flex-row gap-2 flex-wrap">
|
||||
<button className="btn btn-info">Info button</button>
|
||||
<button className="btn btn-success">Success button</button>
|
||||
<button className="btn btn-warning">Warning button</button>
|
||||
<button className="btn btn-error">Error button</button>
|
||||
</div>
|
||||
<h3>Other button styles</h3>
|
||||
<div className="flex flex-row gap-2 flex-wrap">
|
||||
<button className="btn btn-ghost">Ghost button</button>
|
||||
<button className="btn btn-link">Link button</button>
|
||||
</div>
|
||||
<h3>Outlined button styles</h3>
|
||||
<div className="flex flex-row gap-2 flex-wrap">
|
||||
<button className="btn btn-outline btn-neutral">Neutral button</button>
|
||||
<button className="btn btn-outline btn-primary">Primary button</button>
|
||||
<button className="btn btn-outline btn-secondary">Secondary button</button>
|
||||
<button className="btn btn-outline btn-accent">Accent button</button>
|
||||
</div>
|
||||
<h3>Button sizes</h3>
|
||||
<div className="flex flex-row gap-2 flex-wrap">
|
||||
<button className="btn btn-primary btn-lg">Large</button>
|
||||
<button className="btn btn-primary">Normal</button>
|
||||
<button className="btn btn-primary btn-sm">Small</button>
|
||||
<button className="btn btn-primary btn-xs">Tiny</button>
|
||||
<button className="btn btn-primary btn-lg btn-wide">Large wide</button>
|
||||
<button className="btn btn-primary btn-wide">Normal wide</button>
|
||||
<button className="btn btn-primary btn-sm btn-wide">Small wide</button>
|
||||
<button className="btn btn-primary btn-xs bnt-wide">Tiny wide</button>
|
||||
</div>
|
||||
<h2>Popouts</h2>
|
||||
<p>The Popout component is what powers various custom MDX components under the hood:</p>
|
||||
{['note', 'tip', 'warning', 'fixme', 'link', 'related', 'none'].map((type) => {
|
||||
const props = {}
|
||||
props[type] = true
|
||||
return (
|
||||
<div key={type}>
|
||||
<h3 className="capitalize">{type}</h3>
|
||||
<Popout {...props}>
|
||||
<h5>I am the {type} title</h5>
|
||||
{p}
|
||||
</Popout>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</PageWrapper>
|
||||
)
|
||||
|
||||
export default TypographyPage
|
||||
|
||||
|
@ -102,6 +97,7 @@ export async function getStaticProps({ locale }) {
|
|||
props: {
|
||||
...(await serverSideTranslations(locale, namespaces)),
|
||||
page: {
|
||||
locale,
|
||||
path: ['typography'],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
// Hooks
|
||||
import { useApp } from 'shared/hooks/use-app.mjs'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
// Dependencies
|
||||
import dynamic from 'next/dynamic'
|
||||
|
@ -26,28 +25,34 @@ const DynamicBio = dynamic(
|
|||
{ ssr: false }
|
||||
)
|
||||
|
||||
const BioPage = (props) => {
|
||||
const app = useApp(props)
|
||||
/*
|
||||
* Each page MUST be wrapped in the PageWrapper component.
|
||||
* You also MUST spread props.page into this wrapper component
|
||||
* when path and locale come from static props (as here)
|
||||
* or set them manually.
|
||||
*/
|
||||
const WelcomeBioPage = ({ page }) => {
|
||||
const { t } = useTranslation(namespaces)
|
||||
|
||||
return (
|
||||
<PageWrapper app={app} title={t('title')} layout={BareLayout} footer={false}>
|
||||
<DynamicAuthWrapper app={app}>
|
||||
<PageWrapper {...page} layout={BareLayout} footer={false}>
|
||||
<DynamicAuthWrapper>
|
||||
<div className="m-auto max-w-lg text-center lg:mt-24 p-8">
|
||||
<DynamicBio app={app} title welcome />
|
||||
<DynamicBio title welcome />
|
||||
</div>
|
||||
</DynamicAuthWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export default BioPage
|
||||
export default WelcomeBioPage
|
||||
|
||||
export async function getStaticProps({ locale }) {
|
||||
return {
|
||||
props: {
|
||||
...(await serverSideTranslations(locale, namespaces)),
|
||||
page: {
|
||||
locale,
|
||||
path: ['welcome', 'bio'],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
// Hooks
|
||||
import { useApp } from 'shared/hooks/use-app.mjs'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
// Dependencies
|
||||
import dynamic from 'next/dynamic'
|
||||
|
@ -26,28 +25,34 @@ const DynamicCompare = dynamic(
|
|||
{ ssr: false }
|
||||
)
|
||||
|
||||
const ComparePage = (props) => {
|
||||
const app = useApp(props)
|
||||
/*
|
||||
* Each page MUST be wrapped in the PageWrapper component.
|
||||
* You also MUST spread props.page into this wrapper component
|
||||
* when path and locale come from static props (as here)
|
||||
* or set them manually.
|
||||
*/
|
||||
const WelcomeComparePage = ({ page }) => {
|
||||
const { t } = useTranslation(namespaces)
|
||||
|
||||
return (
|
||||
<PageWrapper app={app} title={t('title')} layout={BareLayout} footer={false}>
|
||||
<DynamicAuthWrapper app={app}>
|
||||
<PageWrapper {...page} layout={BareLayout} footer={false}>
|
||||
<DynamicAuthWrapper>
|
||||
<div className="m-auto max-w-lg text-center lg:mt-24 p-8">
|
||||
<DynamicCompare app={app} title welcome />
|
||||
<DynamicCompare title welcome />
|
||||
</div>
|
||||
</DynamicAuthWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export default ComparePage
|
||||
export default WelcomeComparePage
|
||||
|
||||
export async function getStaticProps({ locale }) {
|
||||
return {
|
||||
props: {
|
||||
...(await serverSideTranslations(locale, namespaces)),
|
||||
page: {
|
||||
locale,
|
||||
path: ['welcome', 'compare'],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
// Hooks
|
||||
import { useApp } from 'shared/hooks/use-app.mjs'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
// Dependencies
|
||||
import dynamic from 'next/dynamic'
|
||||
|
@ -26,28 +25,34 @@ const DynamicImg = dynamic(
|
|||
{ ssr: false }
|
||||
)
|
||||
|
||||
const ImgPage = (props) => {
|
||||
const app = useApp(props)
|
||||
/*
|
||||
* Each page MUST be wrapped in the PageWrapper component.
|
||||
* You also MUST spread props.page into this wrapper component
|
||||
* when path and locale come from static props (as here)
|
||||
* or set them manually.
|
||||
*/
|
||||
const WelcomeImgPage = ({ page }) => {
|
||||
const { t } = useTranslation(namespaces)
|
||||
|
||||
return (
|
||||
<PageWrapper app={app} title={t('imgTitle')} layout={BareLayout} footer={false}>
|
||||
<DynamicAuthWrapper app={app}>
|
||||
<PageWrapper {...page} title={t('imgTitle')} layout={BareLayout} footer={false}>
|
||||
<DynamicAuthWrapper>
|
||||
<div className="m-auto max-w-lg text-center lg:mt-24 p-8">
|
||||
<DynamicImg app={app} title welcome />
|
||||
<DynamicImg title welcome />
|
||||
</div>
|
||||
</DynamicAuthWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export default ImgPage
|
||||
export default WelcomeImgPage
|
||||
|
||||
export async function getStaticProps({ locale }) {
|
||||
return {
|
||||
props: {
|
||||
...(await serverSideTranslations(locale, namespaces)),
|
||||
page: {
|
||||
locale,
|
||||
path: ['welcome', 'img'],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
// Hooks
|
||||
import { useApp } from 'shared/hooks/use-app.mjs'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
// Dependencies
|
||||
import dynamic from 'next/dynamic'
|
||||
|
@ -26,15 +25,20 @@ const DynamicControl = dynamic(
|
|||
{ ssr: false }
|
||||
)
|
||||
|
||||
const WelcomePage = (props) => {
|
||||
const app = useApp(props)
|
||||
/*
|
||||
* Each page MUST be wrapped in the PageWrapper component.
|
||||
* You also MUST spread props.page into this wrapper component
|
||||
* when path and locale come from static props (as here)
|
||||
* or set them manually.
|
||||
*/
|
||||
const WelcomePage = ({ page }) => {
|
||||
const { t } = useTranslation(namespaces)
|
||||
|
||||
return (
|
||||
<PageWrapper app={app} title={t('title')} layout={BareLayout} footer={false}>
|
||||
<DynamicAuthWrapper app={app}>
|
||||
<PageWrapper {...page} layout={BareLayout} footer={false}>
|
||||
<DynamicAuthWrapper>
|
||||
<div className="m-auto max-w-lg text-center lg:mt-24 p-8">
|
||||
<DynamicControl app={app} title welcome />
|
||||
<DynamicControl title welcome />
|
||||
</div>
|
||||
</DynamicAuthWrapper>
|
||||
</PageWrapper>
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
// Hooks
|
||||
import { useApp } from 'shared/hooks/use-app.mjs'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
// Dependencies
|
||||
import dynamic from 'next/dynamic'
|
||||
|
@ -27,28 +26,34 @@ const DynamicNewsletter = dynamic(
|
|||
{ ssr: false }
|
||||
)
|
||||
|
||||
const WelcomePage = (props) => {
|
||||
const app = useApp(props)
|
||||
/*
|
||||
* Each page MUST be wrapped in the PageWrapper component.
|
||||
* You also MUST spread props.page into this wrapper component
|
||||
* when path and locale come from static props (as here)
|
||||
* or set them manually.
|
||||
*/
|
||||
const WelcomeNewsletterPage = ({ page }) => {
|
||||
const { t } = useTranslation(namespaces)
|
||||
|
||||
return (
|
||||
<PageWrapper app={app} title={t('title')} layout={BareLayout} footer={false}>
|
||||
<DynamicAuthWrapper app={app}>
|
||||
<PageWrapper {...page} title={t('title')} layout={BareLayout} footer={false}>
|
||||
<DynamicAuthWrapper>
|
||||
<div className="m-auto max-w-lg text-center lg:mt-24 p-8">
|
||||
<DynamicNewsletter app={app} title welcome />
|
||||
<DynamicNewsletter title welcome />
|
||||
</div>
|
||||
</DynamicAuthWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export default WelcomePage
|
||||
export default WelcomeNewsletterPage
|
||||
|
||||
export async function getStaticProps({ locale }) {
|
||||
return {
|
||||
props: {
|
||||
...(await serverSideTranslations(locale, namespaces)),
|
||||
page: {
|
||||
locale,
|
||||
path: ['welcome', 'newsletter'],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
// Hooks
|
||||
import { useApp } from 'shared/hooks/use-app.mjs'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
// Dependencies
|
||||
import dynamic from 'next/dynamic'
|
||||
|
@ -26,28 +25,34 @@ const DynamicImperial = dynamic(
|
|||
{ ssr: false }
|
||||
)
|
||||
|
||||
const UnitsPage = (props) => {
|
||||
const app = useApp(props)
|
||||
/*
|
||||
* Each page MUST be wrapped in the PageWrapper component.
|
||||
* You also MUST spread props.page into this wrapper component
|
||||
* when path and locale come from static props (as here)
|
||||
* or set them manually.
|
||||
*/
|
||||
const WelcomeUnitsPage = ({ page }) => {
|
||||
const { t } = useTranslation(namespaces)
|
||||
|
||||
return (
|
||||
<PageWrapper app={app} title={t('title')} layout={BareLayout} footer={false}>
|
||||
<DynamicAuthWrapper app={app}>
|
||||
<PageWrapper {...page} layout={BareLayout} footer={false}>
|
||||
<DynamicAuthWrapper>
|
||||
<div className="m-auto max-w-lg text-center lg:mt-24 p-8">
|
||||
<DynamicImperial app={app} title welcome />
|
||||
<DynamicImperial title welcome />
|
||||
</div>
|
||||
</DynamicAuthWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export default UnitsPage
|
||||
export default WelcomeUnitsPage
|
||||
|
||||
export async function getStaticProps({ locale }) {
|
||||
return {
|
||||
props: {
|
||||
...(await serverSideTranslations(locale, namespaces)),
|
||||
page: {
|
||||
locale,
|
||||
path: ['welcome', 'units'],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
// Hooks
|
||||
import { useApp } from 'shared/hooks/use-app.mjs'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
// Dependencies
|
||||
import dynamic from 'next/dynamic'
|
||||
|
@ -26,28 +25,34 @@ const DynamicUsername = dynamic(
|
|||
{ ssr: false }
|
||||
)
|
||||
|
||||
const UsernamePage = (props) => {
|
||||
const app = useApp(props)
|
||||
/*
|
||||
* Each page MUST be wrapped in the PageWrapper component.
|
||||
* You also MUST spread props.page into this wrapper component
|
||||
* when path and locale come from static props (as here)
|
||||
* or set them manually.
|
||||
*/
|
||||
const WelcomeUsernamePage = ({ page }) => {
|
||||
const { t } = useTranslation(namespaces)
|
||||
|
||||
return (
|
||||
<PageWrapper app={app} title={t('title')} layout={BareLayout} footer={false}>
|
||||
<DynamicAuthWrapper app={app}>
|
||||
<PageWrapper {...page} title={t('title')} layout={BareLayout} footer={false}>
|
||||
<DynamicAuthWrapper>
|
||||
<div className="m-auto max-w-lg text-center lg:mt-24 p-8">
|
||||
<DynamicUsername app={app} title welcome />
|
||||
<DynamicUsername title welcome />
|
||||
</div>
|
||||
</DynamicAuthWrapper>
|
||||
</PageWrapper>
|
||||
)
|
||||
}
|
||||
|
||||
export default UsernamePage
|
||||
export default WelcomeUsernamePage
|
||||
|
||||
export async function getStaticProps({ locale }) {
|
||||
return {
|
||||
props: {
|
||||
...(await serverSideTranslations(locale, namespaces)),
|
||||
page: {
|
||||
locale,
|
||||
path: ['welcome', 'username'],
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
// Dependencies
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useState, useEffect, useContext } from 'react'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { DateTime } from 'luxon'
|
||||
import { CopyToClipboard } from 'react-copy-to-clipboard'
|
||||
// Context
|
||||
import { LoadingContext } from 'shared/context/loading-context.mjs'
|
||||
import { ModalContext } from 'shared/context/modal-context.mjs'
|
||||
// Hooks
|
||||
import { useAccount } from 'shared/hooks/use-account.mjs'
|
||||
import { useBackend } from 'shared/hooks/use-backend.mjs'
|
||||
|
@ -111,7 +114,17 @@ const ShowKey = ({ apikey, t, clear }) => (
|
|||
</div>
|
||||
)
|
||||
|
||||
const NewKey = ({ app, t, account, setGenerate, keyAdded, backend, toast, standAlone = false }) => {
|
||||
const NewKey = ({
|
||||
t,
|
||||
account,
|
||||
setGenerate,
|
||||
keyAdded,
|
||||
backend,
|
||||
toast,
|
||||
startLoading,
|
||||
stopLoading,
|
||||
standAlone = false,
|
||||
}) => {
|
||||
const router = useRouter()
|
||||
const [name, setName] = useState('')
|
||||
const [level, setLevel] = useState(1)
|
||||
|
@ -121,7 +134,7 @@ const NewKey = ({ app, t, account, setGenerate, keyAdded, backend, toast, standA
|
|||
const levels = account.role === 'admin' ? [0, 1, 2, 3, 4, 5, 6, 7, 8] : [0, 1, 2, 3, 4]
|
||||
|
||||
const createKey = async () => {
|
||||
app.startLoading()
|
||||
startLoading()
|
||||
const result = await backend.createApikey({
|
||||
name,
|
||||
level,
|
||||
|
@ -132,7 +145,7 @@ const NewKey = ({ app, t, account, setGenerate, keyAdded, backend, toast, standA
|
|||
setApikey(result.data.apikey)
|
||||
keyAdded()
|
||||
} else toast.for.backendError()
|
||||
app.stopLoading()
|
||||
stopLoading()
|
||||
}
|
||||
|
||||
const clear = () => {
|
||||
|
@ -187,7 +200,9 @@ const NewKey = ({ app, t, account, setGenerate, keyAdded, backend, toast, standA
|
|||
)
|
||||
}
|
||||
|
||||
const Apikey = ({ apikey, t, account, backend, keyAdded, app }) => {
|
||||
const Apikey = ({ apikey, t, account, backend, keyAdded }) => {
|
||||
//const { startLoading, stopLoading } = useContext(LoadingContext)
|
||||
const { setModal } = useContext(ModalContext)
|
||||
const toast = useToast()
|
||||
|
||||
const fields = {
|
||||
|
@ -201,19 +216,19 @@ const Apikey = ({ apikey, t, account, backend, keyAdded, app }) => {
|
|||
const expired = DateTime.fromISO(apikey.expiresAt).valueOf() < DateTime.now().valueOf()
|
||||
|
||||
const remove = async () => {
|
||||
app.startLoading()
|
||||
startLoading()
|
||||
const result = await backend.removeApikey(apikey.id)
|
||||
if (result) toast.success(t('gone'))
|
||||
else toast.for.backendError()
|
||||
// This just forces a refresh of the list from the server
|
||||
// We obviously did not add a key here, but rather removed one
|
||||
keyAdded()
|
||||
app.stopLoading()
|
||||
stopLoading()
|
||||
}
|
||||
|
||||
const removeModal = () => {
|
||||
app.setModal(
|
||||
<ModalWrapper app={app} slideFrom="top">
|
||||
setModal(
|
||||
<ModalWrapper slideFrom="top">
|
||||
<h2>{t('areYouCertain')}</h2>
|
||||
<p>{t('deleteKeyWarning')}</p>
|
||||
<p className="flex flex-row gap-4 items-center justify-center">
|
||||
|
@ -268,36 +283,60 @@ const Apikey = ({ apikey, t, account, backend, keyAdded, app }) => {
|
|||
}
|
||||
|
||||
// Component for the 'new/apikey' page
|
||||
export const NewApikey = ({ app, standAlone = false }) => {
|
||||
export const NewApikey = ({ standAlone = false }) => {
|
||||
// Context
|
||||
const { startLoading, stopLoading } = useContext(LoadingContext)
|
||||
|
||||
// Hooks
|
||||
const { account, token } = useAccount()
|
||||
const backend = useBackend(token)
|
||||
const { t } = useTranslation(ns)
|
||||
const toast = useToast()
|
||||
|
||||
// State
|
||||
const [keys, setKeys] = useState([])
|
||||
const [generate, setGenerate] = useState(false)
|
||||
const [added, setAdded] = useState(0)
|
||||
|
||||
// Helper method to force refresh
|
||||
const keyAdded = () => setAdded(added + 1)
|
||||
|
||||
return (
|
||||
<div className="max-w-xl xl:pl-4">
|
||||
<NewKey {...{ app, t, account, setGenerate, backend, toast, keyAdded, standAlone }} />
|
||||
<NewKey
|
||||
{...{
|
||||
t,
|
||||
account,
|
||||
setGenerate,
|
||||
backend,
|
||||
toast,
|
||||
keyAdded,
|
||||
standAlone,
|
||||
startLoading,
|
||||
stopLoading,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// Component for the account/apikeys page
|
||||
export const Apikeys = ({ app }) => {
|
||||
export const Apikeys = () => {
|
||||
// Context
|
||||
const { startLoading, stopLoading, loading } = useContext(LoadingContext)
|
||||
|
||||
// Hooks
|
||||
const { account, token } = useAccount()
|
||||
const backend = useBackend(token)
|
||||
const { t } = useTranslation(ns)
|
||||
const toast = useToast()
|
||||
|
||||
// State
|
||||
const [keys, setKeys] = useState([])
|
||||
const [generate, setGenerate] = useState(false)
|
||||
const [added, setAdded] = useState(0)
|
||||
|
||||
// Effects
|
||||
useEffect(() => {
|
||||
const getApikeys = async () => {
|
||||
const result = await backend.getApikeys()
|
||||
|
@ -306,12 +345,24 @@ export const Apikeys = ({ app }) => {
|
|||
getApikeys()
|
||||
}, [added])
|
||||
|
||||
// Helper method to force refresh
|
||||
const keyAdded = () => setAdded(added + 1)
|
||||
|
||||
return (
|
||||
<div className="max-w-xl xl:pl-4">
|
||||
{generate ? (
|
||||
<NewKey {...{ app, t, account, setGenerate, backend, toast, keyAdded }} />
|
||||
<NewKey
|
||||
{...{
|
||||
t,
|
||||
account,
|
||||
setGenerate,
|
||||
backend,
|
||||
toast,
|
||||
keyAdded,
|
||||
startLoading,
|
||||
stopLoading,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<>
|
||||
<h2>{t('apikeys')}</h2>
|
||||
|
@ -324,7 +375,7 @@ export const Apikeys = ({ app }) => {
|
|||
>
|
||||
{t('newApikey')}
|
||||
</button>
|
||||
<BackToAccountButton loading={app.state.loading} />
|
||||
<BackToAccountButton loading={loading} />
|
||||
{account.control < 5 ? (
|
||||
<Popout tip>
|
||||
<h5>Refer to FreeSewing.dev for details (English only)</h5>
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
// Dependencies
|
||||
import { useState } from 'react'
|
||||
import { useState, useContext } from 'react'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
// Hooks
|
||||
import { useAccount } from 'shared/hooks/use-account.mjs'
|
||||
import { useBackend } from 'shared/hooks/use-backend.mjs'
|
||||
import { useToast } from 'shared/hooks/use-toast.mjs'
|
||||
// Context
|
||||
import { LoadingContext } from 'shared/context/loading-context.mjs'
|
||||
// Components
|
||||
import Markdown from 'react-markdown'
|
||||
import { Icons, welcomeSteps, BackToAccountButton } from './shared.mjs'
|
||||
|
@ -24,29 +26,38 @@ export const Tab = ({ id, activeTab, setActiveTab, t }) => (
|
|||
</button>
|
||||
)
|
||||
|
||||
export const BioSettings = ({ app, title = false, welcome = false }) => {
|
||||
export const BioSettings = ({ title = false, welcome = false }) => {
|
||||
// Context
|
||||
const { loading, startLoading, stopLoading } = useContext(LoadingContext)
|
||||
|
||||
// Hooks
|
||||
const { account, setAccount, token } = useAccount()
|
||||
const backend = useBackend(token)
|
||||
const { t } = useTranslation(ns)
|
||||
const toast = useToast()
|
||||
|
||||
// State
|
||||
const [bio, setBio] = useState(account.bio)
|
||||
const [activeTab, setActiveTab] = useState('edit')
|
||||
|
||||
// Helper method to save bio
|
||||
const save = async () => {
|
||||
app.startLoading()
|
||||
startLoading()
|
||||
const result = await backend.updateAccount({ bio })
|
||||
if (result.success) {
|
||||
setAccount(result.data.account)
|
||||
toast.for.settingsSaved()
|
||||
} else toast.for.backendError()
|
||||
app.stopLoading()
|
||||
stopLoading()
|
||||
}
|
||||
|
||||
// Next step in the onboarding
|
||||
const nextHref =
|
||||
welcomeSteps[account.control].length > 5
|
||||
? '/welcome/' + welcomeSteps[account.control][6]
|
||||
: '/docs/guide'
|
||||
|
||||
// Shared props for tabs
|
||||
const tabProps = { activeTab, setActiveTab, t }
|
||||
|
||||
return (
|
||||
|
@ -71,15 +82,15 @@ export const BioSettings = ({ app, title = false, welcome = false }) => {
|
|||
</div>
|
||||
)}
|
||||
</div>
|
||||
<SaveSettingsButton app={app} btnProps={{ onClick: save }} welcome={welcome} />
|
||||
{!welcome && <BackToAccountButton loading={app.state.loading} />}
|
||||
<SaveSettingsButton btnProps={{ onClick: save }} welcome={welcome} />
|
||||
{!welcome && <BackToAccountButton loading={loading} />}
|
||||
<Popout tip compact>
|
||||
{t('mdSupport')}
|
||||
</Popout>
|
||||
|
||||
{welcome ? (
|
||||
<>
|
||||
<ContinueButton app={app} btnProps={{ href: nextHref }} link />
|
||||
<ContinueButton btnProps={{ href: nextHref }} link />
|
||||
{welcomeSteps[account.control].length > 0 ? (
|
||||
<>
|
||||
<progress
|
||||
|
|
|
@ -1,36 +1,48 @@
|
|||
// Dependencies
|
||||
import { useState } from 'react'
|
||||
import { useState, useContext } from 'react'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
// Hooks
|
||||
import { useAccount } from 'shared/hooks/use-account.mjs'
|
||||
import { useBackend } from 'shared/hooks/use-backend.mjs'
|
||||
import { useToast } from 'shared/hooks/use-toast.mjs'
|
||||
// Context
|
||||
import { LoadingContext } from 'shared/context/loading-context.mjs'
|
||||
// Components
|
||||
import { Choice, Icons, welcomeSteps, BackToAccountButton } from './shared.mjs'
|
||||
import { ContinueButton } from 'site/components/buttons/continue-button.mjs'
|
||||
|
||||
export const ns = ['account', 'toast']
|
||||
|
||||
export const CompareSettings = ({ app, title = false, welcome = false }) => {
|
||||
export const CompareSettings = ({ title = false, welcome = false }) => {
|
||||
// Context
|
||||
const { loading, startLoading, stopLoading } = useContext(LoadingContext)
|
||||
|
||||
// Hooks
|
||||
const { account, setAccount, token } = useAccount()
|
||||
const backend = useBackend(token)
|
||||
const toast = useToast()
|
||||
const { t } = useTranslation(ns)
|
||||
|
||||
// State
|
||||
const [selection, setSelection] = useState(account?.compare ? 'yes' : 'no')
|
||||
|
||||
// Helper method to update the account
|
||||
const update = async (val) => {
|
||||
if (val !== selection) {
|
||||
app.startLoading()
|
||||
const result = await backend.updateAccount({ compare: val === 'yes' ? true : false })
|
||||
startLoading()
|
||||
const result = await backend.updateAccount({
|
||||
compare: val === 'yes' ? true : false,
|
||||
})
|
||||
if (result.success) {
|
||||
setAccount(result.data.account)
|
||||
setSelection(val)
|
||||
toast.for.settingsSaved()
|
||||
} else toast.for.backendError()
|
||||
app.stopLoading()
|
||||
stopLoading()
|
||||
}
|
||||
}
|
||||
|
||||
// Link to the next onboarding step
|
||||
const nextHref =
|
||||
welcomeSteps[account?.control].length > 3
|
||||
? '/welcome/' + welcomeSteps[account?.control][4]
|
||||
|
@ -53,7 +65,7 @@ export const CompareSettings = ({ app, title = false, welcome = false }) => {
|
|||
))}
|
||||
{welcome ? (
|
||||
<>
|
||||
<ContinueButton app={app} btnProps={{ href: nextHref }} link />
|
||||
<ContinueButton btnProps={{ href: nextHref }} link />
|
||||
{welcomeSteps[account?.control].length > 0 ? (
|
||||
<>
|
||||
<progress
|
||||
|
@ -73,10 +85,8 @@ export const CompareSettings = ({ app, title = false, welcome = false }) => {
|
|||
) : null}
|
||||
</>
|
||||
) : (
|
||||
<BackToAccountButton loading={app.state.loading} />
|
||||
<BackToAccountButton loading={loading} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default CompareSettings
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
// Dependencies
|
||||
import { useState } from 'react'
|
||||
import { useState, useContext } from 'react'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
// Hooks
|
||||
import { useAccount } from 'shared/hooks/use-account.mjs'
|
||||
import { useBackend } from 'shared/hooks/use-backend.mjs'
|
||||
import { useToast } from 'shared/hooks/use-toast.mjs'
|
||||
// Context
|
||||
import { LoadingContext } from 'shared/context/loading-context.mjs'
|
||||
// Components
|
||||
import Link from 'next/link'
|
||||
import { Popout } from 'shared/components/popout.mjs'
|
||||
|
@ -38,40 +40,48 @@ const Checkbox = ({ value, setter, label, children = null }) => (
|
|||
</div>
|
||||
)
|
||||
|
||||
export const ConsentSettings = ({ app, title = false }) => {
|
||||
const { account, token } = useAccount()
|
||||
export const ConsentSettings = ({ title = false }) => {
|
||||
// Context
|
||||
const { loading, startLoading, stopLoading } = useContext(LoadingContext)
|
||||
|
||||
// Hooks
|
||||
const { account, token, setAccount, setToken } = useAccount()
|
||||
const backend = useBackend(token)
|
||||
const toast = useToast()
|
||||
const { t } = useTranslation(ns)
|
||||
|
||||
// State
|
||||
const [profile, setProfile] = useState(account?.consent > 0)
|
||||
const [measurements, setMeasurements] = useState(account?.consent > 1)
|
||||
const [openData, setOpenData] = useState(account?.consent > 2)
|
||||
|
||||
// Helper method to update the account
|
||||
const update = async () => {
|
||||
let newConsent = 0
|
||||
if (profile) newConsent = 1
|
||||
if (profile && measurements) newConsent = 2
|
||||
if (profile && measurements && openData) newConsent = 3
|
||||
if (newConsent !== account.consent) {
|
||||
app.startLoading()
|
||||
startLoading()
|
||||
const result = await backend.updateAccount({ consent: newConsent })
|
||||
if (result === true) toast.for.settingsSaved()
|
||||
else toast.for.backendError()
|
||||
app.stopLoading()
|
||||
stopLoading()
|
||||
}
|
||||
}
|
||||
|
||||
// Helper method to remove the account
|
||||
const removeAccount = async () => {
|
||||
app.startLoading()
|
||||
startLoading()
|
||||
const result = await backend.removeAccount()
|
||||
if (result === true) toast.for.settingsSaved()
|
||||
else toast.for.backendError()
|
||||
app.setToken(null)
|
||||
app.setAccount({ username: false })
|
||||
app.stopLoading()
|
||||
setToken(null)
|
||||
setAccount({ username: false })
|
||||
stopLoading()
|
||||
}
|
||||
|
||||
// Part A of the consent screen
|
||||
const partA = (
|
||||
<>
|
||||
<h5 className="mt-8">{t('profileQuestion')}</h5>
|
||||
|
@ -92,6 +102,7 @@ export const ConsentSettings = ({ app, title = false }) => {
|
|||
)}
|
||||
</>
|
||||
)
|
||||
// Part B of the consent screen
|
||||
const partB = (
|
||||
<>
|
||||
<h5 className="mt-8">{t('setQuestion')}</h5>
|
||||
|
@ -122,12 +133,9 @@ export const ConsentSettings = ({ app, title = false }) => {
|
|||
<p>{t('consentWhyAnswer')}</p>
|
||||
{partA}
|
||||
{profile && partB}
|
||||
{profile && measurements ? (
|
||||
<SaveSettingsButton app={app} btnProps={{ onClick: update }} />
|
||||
) : null}
|
||||
{profile && measurements ? <SaveSettingsButton btnProps={{ onClick: update }} /> : null}
|
||||
{profile && !measurements ? (
|
||||
<SaveSettingsButton
|
||||
app={app}
|
||||
label={t('revokeConsent')}
|
||||
btnProps={{
|
||||
onClick: update,
|
||||
|
@ -137,7 +145,6 @@ export const ConsentSettings = ({ app, title = false }) => {
|
|||
) : null}
|
||||
{!profile ? (
|
||||
<SaveSettingsButton
|
||||
app={app}
|
||||
label={t('account:removeAccount')}
|
||||
btnProps={{
|
||||
onClick: removeAccount,
|
||||
|
@ -146,7 +153,7 @@ export const ConsentSettings = ({ app, title = false }) => {
|
|||
/>
|
||||
) : null}
|
||||
|
||||
<BackToAccountButton loading={app.state.loading} />
|
||||
<BackToAccountButton loading={loading} />
|
||||
<p className="text-center opacity-50 mt-12">
|
||||
<Link href="/docs/various/privacy" className="hover:text-secondary underline">
|
||||
FreeSewing Privacy Notice
|
||||
|
|
|
@ -1,38 +1,51 @@
|
|||
// Dependencies
|
||||
import { useState } from 'react'
|
||||
import { useState, useContext } from 'react'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
// Hooks
|
||||
import { useAccount } from 'shared/hooks/use-account.mjs'
|
||||
import { useBackend } from 'shared/hooks/use-backend.mjs'
|
||||
import { useToast } from 'shared/hooks/use-toast.mjs'
|
||||
// Context
|
||||
import { LoadingContext } from 'shared/context/loading-context.mjs'
|
||||
// Components
|
||||
import { BackToAccountButton, Choice, Icons, welcomeSteps } from './shared.mjs'
|
||||
import { ContinueButton } from 'site/components/buttons/continue-button.mjs'
|
||||
|
||||
export const ns = ['account', 'toast']
|
||||
|
||||
export const ControlSettings = ({ app, title = false, welcome = false }) => {
|
||||
export const ControlSettings = ({ title = false, welcome = false }) => {
|
||||
// Context
|
||||
const { loading, startLoading, stopLoading } = useContext(LoadingContext)
|
||||
|
||||
// Hooks
|
||||
const { account, setAccount, token } = useAccount()
|
||||
const backend = useBackend(token)
|
||||
const toast = useToast()
|
||||
const { t } = useTranslation(ns)
|
||||
|
||||
// State
|
||||
const [selection, setSelection] = useState(account.control || 2)
|
||||
|
||||
// Method to update the control setting
|
||||
const update = async (control) => {
|
||||
if (control !== selection) {
|
||||
app.startLoading()
|
||||
startLoading()
|
||||
const result = await backend.updateAccount({ control })
|
||||
if (result.success) {
|
||||
setSelection(control)
|
||||
toast.for.settingsSaved()
|
||||
setAccount(result.data.account)
|
||||
} else toast.for.backendError()
|
||||
app.stopLoading()
|
||||
stopLoading()
|
||||
}
|
||||
}
|
||||
|
||||
const nextHref =
|
||||
welcomeSteps[selection].length > 1 ? '/welcome/' + welcomeSteps[selection][1] : '/docs/guide'
|
||||
// Helper to get the link to the next onboarding step
|
||||
const nextHref = welcome
|
||||
? welcomeSteps[selection].length > 1
|
||||
? '/welcome/' + welcomeSteps[selection][1]
|
||||
: '/docs/guide'
|
||||
: false
|
||||
|
||||
return (
|
||||
<div className="max-w-xl">
|
||||
|
@ -58,7 +71,7 @@ export const ControlSettings = ({ app, title = false, welcome = false }) => {
|
|||
})}
|
||||
{welcome ? (
|
||||
<>
|
||||
<ContinueButton app={app} btnProps={{ href: nextHref }} link />
|
||||
<ContinueButton btnProps={{ href: nextHref }} link />
|
||||
{welcomeSteps[selection].length > 1 ? (
|
||||
<>
|
||||
<progress
|
||||
|
@ -74,7 +87,7 @@ export const ControlSettings = ({ app, title = false, welcome = false }) => {
|
|||
) : null}
|
||||
</>
|
||||
) : (
|
||||
<BackToAccountButton loading={app.state.loading} />
|
||||
<BackToAccountButton />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
// Dependencies
|
||||
import { useState } from 'react'
|
||||
import { useState, useContext } from 'react'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
// Hooks
|
||||
import { useAccount } from 'shared/hooks/use-account.mjs'
|
||||
import { useBackend } from 'shared/hooks/use-backend.mjs'
|
||||
import { useToast } from 'shared/hooks/use-toast.mjs'
|
||||
// Context
|
||||
import { LoadingContext } from 'shared/context/loading-context.mjs'
|
||||
// Verification methods
|
||||
import { validateEmail, validateTld } from 'site/utils.mjs'
|
||||
// Components
|
||||
|
@ -13,23 +15,33 @@ import { Popout } from 'shared/components/popout.mjs'
|
|||
|
||||
export const ns = ['account', 'toast']
|
||||
|
||||
export const EmailSettings = ({ app, title = false }) => {
|
||||
export const EmailSettings = ({ title = false }) => {
|
||||
// Context
|
||||
const { loading, startLoading, stopLoading } = useContext(LoadingContext)
|
||||
|
||||
// Hooks
|
||||
const { account, setAccount, token } = useAccount()
|
||||
const backend = useBackend(token)
|
||||
const { t } = useTranslation(ns)
|
||||
const toast = useToast()
|
||||
|
||||
// State
|
||||
const [email, setEmail] = useState(account.email)
|
||||
const [changed, setChanged] = useState(false)
|
||||
|
||||
// Helper method to update account
|
||||
const save = async () => {
|
||||
startLoading()
|
||||
const result = await backend.updateAccount({ email })
|
||||
if (result.success) {
|
||||
setAccount(result.data.account)
|
||||
toast.for.settingsSaved()
|
||||
} else toast.for.backendError()
|
||||
setChanged(true)
|
||||
stopLoading()
|
||||
}
|
||||
|
||||
// Is email valid?
|
||||
const valid = (validateEmail(email) && validateTld(email)) || false
|
||||
|
||||
return (
|
||||
|
@ -59,7 +71,7 @@ export const EmailSettings = ({ app, title = false }) => {
|
|||
</button>
|
||||
</>
|
||||
)}
|
||||
<BackToAccountButton loading={app.state.loading} />
|
||||
<BackToAccountButton loading={loading} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,31 +1,40 @@
|
|||
// Dependencies
|
||||
import { useState } from 'react'
|
||||
import { useState, useContext } from 'react'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
// Hooks
|
||||
import { useAccount } from 'shared/hooks/use-account.mjs'
|
||||
import { useBackend } from 'shared/hooks/use-backend.mjs'
|
||||
import { useToast } from 'shared/hooks/use-toast.mjs'
|
||||
// Context
|
||||
import { LoadingContext } from 'shared/context/loading-context.mjs'
|
||||
// Components
|
||||
import { BackToAccountButton } from './shared.mjs'
|
||||
import { SaveSettingsButton } from 'site/components/buttons/save-settings-button.mjs'
|
||||
|
||||
export const ns = ['account', 'toast']
|
||||
|
||||
export const GithubSettings = ({ app, title = false, welcome = false }) => {
|
||||
export const GithubSettings = ({ title = false, welcome = false }) => {
|
||||
// Context
|
||||
const { loading, startLoading, stopLoading } = useContext(LoadingContext)
|
||||
|
||||
// Hooks
|
||||
const { account, setAccount, token } = useAccount()
|
||||
const backend = useBackend(token)
|
||||
const { t } = useTranslation(ns)
|
||||
const toast = useToast()
|
||||
|
||||
// State
|
||||
const [github, setGithub] = useState(account.github || '')
|
||||
|
||||
// Helper method to save changes
|
||||
const save = async () => {
|
||||
app.startLoading()
|
||||
startLoading()
|
||||
const result = await backend.updateAccount({ github })
|
||||
if (result.success) {
|
||||
setAccount(result.data.account)
|
||||
toast.for.settingsSaved()
|
||||
} else toast.for.backendError()
|
||||
app.stopLoading()
|
||||
stopLoading()
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -40,8 +49,8 @@ export const GithubSettings = ({ app, title = false, welcome = false }) => {
|
|||
placeholder={t('github')}
|
||||
/>
|
||||
</div>
|
||||
<SaveSettingsButton app={app} btnProps={{ onClick: save }} />
|
||||
{!welcome && <BackToAccountButton loading={app.state.loading} />}
|
||||
<SaveSettingsButton btnProps={{ onClick: save }} />
|
||||
{!welcome && <BackToAccountButton loading={loading} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
// Dependencies
|
||||
import { useState, useCallback } from 'react'
|
||||
import { useState, useContext, useCallback } from 'react'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { useDropzone } from 'react-dropzone'
|
||||
// Context
|
||||
import { LoadingContext } from 'shared/context/loading-context.mjs'
|
||||
// Hooks
|
||||
import { useAccount } from 'shared/hooks/use-account.mjs'
|
||||
import { useBackend } from 'shared/hooks/use-backend.mjs'
|
||||
|
@ -13,7 +15,8 @@ import { SaveSettingsButton } from 'site/components/buttons/save-settings-button
|
|||
|
||||
export const ns = ['account', 'toast']
|
||||
|
||||
export const ImgSettings = ({ app, title = false, welcome = false }) => {
|
||||
export const ImgSettings = ({ title = false, welcome = false }) => {
|
||||
const { loading, startLoading, stopLoading } = useContext(LoadingContext)
|
||||
const { account, setAccount, token } = useAccount()
|
||||
const backend = useBackend(token)
|
||||
const toast = useToast()
|
||||
|
@ -32,13 +35,13 @@ export const ImgSettings = ({ app, title = false, welcome = false }) => {
|
|||
const { getRootProps, getInputProps } = useDropzone({ onDrop })
|
||||
|
||||
const save = async () => {
|
||||
app.startLoading()
|
||||
startLoading()
|
||||
const result = await backend.updateAccount({ img })
|
||||
if (result.success) {
|
||||
setAccount(result.data.account)
|
||||
toast.for.settingsSaved()
|
||||
} else toast.for.backendError()
|
||||
app.stopLoading()
|
||||
stopLoading()
|
||||
}
|
||||
|
||||
const nextHref = '/docs/guide'
|
||||
|
@ -71,7 +74,7 @@ export const ImgSettings = ({ app, title = false, welcome = false }) => {
|
|||
<button className={`btn btn-secondary mt-4 w-64`} onClick={save} disabled={!img}>
|
||||
{t('save')}
|
||||
</button>
|
||||
<ContinueButton app={app} btnProps={{ href: nextHref }} link />
|
||||
<ContinueButton btnProps={{ href: nextHref }} link />
|
||||
{welcomeSteps[account.control].length > 0 ? (
|
||||
<>
|
||||
<progress
|
||||
|
@ -92,8 +95,8 @@ export const ImgSettings = ({ app, title = false, welcome = false }) => {
|
|||
</>
|
||||
) : (
|
||||
<>
|
||||
<SaveSettingsButton app={app} btnProps={{ onClick: save }} />
|
||||
<BackToAccountButton loading={app.state.loading} />
|
||||
<SaveSettingsButton btnProps={{ onClick: save }} />
|
||||
<BackToAccountButton loading={loading} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -1,36 +1,46 @@
|
|||
// Dependencies
|
||||
import { useState } from 'react'
|
||||
import { useState, useContext } from 'react'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
// Hooks
|
||||
import { useAccount } from 'shared/hooks/use-account.mjs'
|
||||
import { useBackend } from 'shared/hooks/use-backend.mjs'
|
||||
import { useToast } from 'shared/hooks/use-toast.mjs'
|
||||
// Context
|
||||
import { LoadingContext } from 'shared/context/loading-context.mjs'
|
||||
// Components
|
||||
import { Choice, Icons, welcomeSteps, BackToAccountButton } from './shared.mjs'
|
||||
import { ContinueButton } from 'site/components/buttons/continue-button.mjs'
|
||||
|
||||
export const ns = ['account', 'toast']
|
||||
|
||||
export const ImperialSettings = ({ app, title = false, welcome = false }) => {
|
||||
export const ImperialSettings = ({ title = false, welcome = false }) => {
|
||||
// Context
|
||||
const { loading, startLoading, stopLoading } = useContext(LoadingContext)
|
||||
|
||||
// Hooks
|
||||
const { account, setAccount, token } = useAccount()
|
||||
const backend = useBackend(token)
|
||||
const toast = useToast()
|
||||
const { t } = useTranslation(ns)
|
||||
|
||||
// State
|
||||
const [selection, setSelection] = useState(account?.imperial === true ? 'imperial' : 'metric')
|
||||
|
||||
// Helper method to update account
|
||||
const update = async (val) => {
|
||||
if (val !== selection) {
|
||||
app.startLoading()
|
||||
startLoading()
|
||||
const result = await backend.updateAccount({ imperial: val === 'imperial' ? true : false })
|
||||
if (result.success) {
|
||||
setAccount(result.data.account)
|
||||
setSelection(val)
|
||||
toast.for.settingsSaved()
|
||||
} else toast.for.backendError()
|
||||
app.stopLoading()
|
||||
stopLoading()
|
||||
}
|
||||
}
|
||||
|
||||
// Next step in the onboarding
|
||||
const nextHref =
|
||||
welcomeSteps[account?.control].length > 3
|
||||
? '/welcome/' + welcomeSteps[account?.control][3]
|
||||
|
@ -57,7 +67,7 @@ export const ImperialSettings = ({ app, title = false, welcome = false }) => {
|
|||
))}
|
||||
{welcome ? (
|
||||
<>
|
||||
<ContinueButton app={app} btnProps={{ href: nextHref }} link />
|
||||
<ContinueButton btnProps={{ href: nextHref }} link />
|
||||
{welcomeSteps[account?.control].length > 0 ? (
|
||||
<>
|
||||
<progress
|
||||
|
@ -77,7 +87,7 @@ export const ImperialSettings = ({ app, title = false, welcome = false }) => {
|
|||
) : null}
|
||||
</>
|
||||
) : (
|
||||
<BackToAccountButton loading={app.state.loading} />
|
||||
<BackToAccountButton loading={loading} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
// Dependencies
|
||||
import { useState } from 'react'
|
||||
import { useState, useContext } from 'react'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
// Hooks
|
||||
import { useAccount } from 'shared/hooks/use-account.mjs'
|
||||
import { useBackend } from 'shared/hooks/use-backend.mjs'
|
||||
import { useToast } from 'shared/hooks/use-toast.mjs'
|
||||
// Context
|
||||
import { LoadingContext } from 'shared/context/loading-context.mjs'
|
||||
// Components
|
||||
import { BackToAccountButton, Choice } from './shared.mjs'
|
||||
// Config
|
||||
|
@ -12,23 +14,30 @@ import { freeSewingConfig as conf } from 'site/freesewing.config.mjs'
|
|||
|
||||
export const ns = ['account', 'locales', 'toast']
|
||||
|
||||
export const LanguageSettings = ({ app, title = false }) => {
|
||||
export const LanguageSettings = ({ title = false }) => {
|
||||
// Context
|
||||
const { loading, startLoading, stopLoading } = useContext(LoadingContext)
|
||||
|
||||
// Hooks
|
||||
const { account, setAccount, token } = useAccount()
|
||||
const backend = useBackend(token)
|
||||
const toast = useToast()
|
||||
const { t } = useTranslation(ns)
|
||||
|
||||
// State
|
||||
const [language, setLanguage] = useState(account.language || 'en')
|
||||
|
||||
// Helper method to update the account
|
||||
const update = async (lang) => {
|
||||
if (lang !== language) {
|
||||
app.startLoading()
|
||||
startLoading()
|
||||
setLanguage(lang)
|
||||
const result = await backend.updateAccount({ language: lang })
|
||||
if (result.success) {
|
||||
setAccount(result.data.account)
|
||||
toast.for.settingsSaved()
|
||||
} else toast.for.backendError()
|
||||
app.stopLoading()
|
||||
stopLoading()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -40,7 +49,7 @@ export const LanguageSettings = ({ app, title = false }) => {
|
|||
<span className="block text-lg leading-5">{t(`locales:${val}`)}</span>
|
||||
</Choice>
|
||||
))}
|
||||
<BackToAccountButton loading={app.state.loading} />
|
||||
<BackToAccountButton loading={loading} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -38,8 +38,8 @@ const actions = {
|
|||
remove: 2,
|
||||
}
|
||||
|
||||
export const AccountLinks = ({ app }) => {
|
||||
const { account } = useAccount()
|
||||
export const AccountLinks = () => {
|
||||
const { account, clear } = useAccount()
|
||||
const { t } = useTranslation(ns)
|
||||
|
||||
const lprops = { t, control: account.control }
|
||||
|
@ -53,7 +53,7 @@ export const AccountLinks = ({ app }) => {
|
|||
<Link className="btn btn-secondary grow capitalize" href="/account/sets">
|
||||
{t('newSet')}
|
||||
</Link>
|
||||
<button className="btn btn-warning btnoutline mb-2 capitalize" onClick={app.clear}>
|
||||
<button className="btn btn-warning btnoutline mb-2 capitalize" onClick={clear}>
|
||||
{t('signOut')}
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
// Dependencies
|
||||
import { useState } from 'react'
|
||||
import { useState, useContext } from 'react'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
// Hooks
|
||||
import { useAccount } from 'shared/hooks/use-account.mjs'
|
||||
import { useBackend } from 'shared/hooks/use-backend.mjs'
|
||||
import { useToast } from 'shared/hooks/use-toast.mjs'
|
||||
// Context
|
||||
import { LoadingContext } from 'shared/context/loading-context.mjs'
|
||||
// Components
|
||||
import { BackToAccountButton } from './shared.mjs'
|
||||
import { Popout } from 'shared/components/popout.mjs'
|
||||
|
@ -22,26 +24,33 @@ const CodeInput = ({ code, setCode, t }) => (
|
|||
/>
|
||||
)
|
||||
|
||||
export const MfaSettings = ({ app, title = false, welcome = false }) => {
|
||||
export const MfaSettings = ({ title = false, welcome = false }) => {
|
||||
// Context
|
||||
const { loading, startLoading, stopLoading } = useContext(LoadingContext)
|
||||
|
||||
// Hooks
|
||||
const { account, setAccount, token } = useAccount()
|
||||
const backend = useBackend(token)
|
||||
const { t } = useTranslation(ns)
|
||||
const toast = useToast()
|
||||
|
||||
// State
|
||||
const [enable, setEnable] = useState(false)
|
||||
const [disable, setDisable] = useState(false)
|
||||
const [code, setCode] = useState('')
|
||||
const [password, setPassword] = useState('')
|
||||
|
||||
// Helper method to enable MFA
|
||||
const enableMfa = async () => {
|
||||
app.startLoading()
|
||||
startLoading()
|
||||
const result = await backend.enableMfa()
|
||||
if (result.success) setEnable(result.data.mfa)
|
||||
app.stopLoading()
|
||||
stopLoading()
|
||||
}
|
||||
|
||||
// Helper method to disable MFA
|
||||
const disableMfa = async () => {
|
||||
app.startLoading()
|
||||
startLoading()
|
||||
const result = await backend.disableMfa({
|
||||
mfa: false,
|
||||
password,
|
||||
|
@ -57,11 +66,12 @@ export const MfaSettings = ({ app, title = false, welcome = false }) => {
|
|||
setCode('')
|
||||
setPassword('')
|
||||
}
|
||||
app.stopLoading()
|
||||
stopLoading()
|
||||
}
|
||||
|
||||
// Helper method to confirm MFA
|
||||
const confirmMfa = async () => {
|
||||
app.startLoading()
|
||||
startLoading()
|
||||
const result = await backend.confirmMfa({
|
||||
mfa: true,
|
||||
secret: enable.secret,
|
||||
|
@ -73,9 +83,10 @@ export const MfaSettings = ({ app, title = false, welcome = false }) => {
|
|||
} else toast.for.backendError()
|
||||
setEnable(false)
|
||||
setCode('')
|
||||
app.stopLoading()
|
||||
stopLoading()
|
||||
}
|
||||
|
||||
// Figure out what title to use
|
||||
let titleText = account.mfaEnabled ? t('mfaEnabled') : t('mfaDisabled')
|
||||
if (enable) titleText = t('mfaSetup')
|
||||
|
||||
|
@ -145,7 +156,7 @@ export const MfaSettings = ({ app, title = false, welcome = false }) => {
|
|||
</div>
|
||||
)}
|
||||
</div>
|
||||
{!welcome && <BackToAccountButton loading={app.state.loading} />}
|
||||
{!welcome && <BackToAccountButton loading={loading} />}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,36 +1,46 @@
|
|||
// Dependencies
|
||||
import { useState } from 'react'
|
||||
import { useState, useContext } from 'react'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
// Hooks
|
||||
import { useAccount } from 'shared/hooks/use-account.mjs'
|
||||
import { useBackend } from 'shared/hooks/use-backend.mjs'
|
||||
import { useToast } from 'shared/hooks/use-toast.mjs'
|
||||
// Context
|
||||
import { LoadingContext } from 'shared/context/loading-context.mjs'
|
||||
// Components
|
||||
import { BackToAccountButton, Choice, Icons, welcomeSteps } from './shared.mjs'
|
||||
import { ContinueButton } from 'site/components/buttons/continue-button.mjs'
|
||||
|
||||
export const ns = ['account', 'toast']
|
||||
|
||||
export const NewsletterSettings = ({ app, title = false, welcome = false }) => {
|
||||
export const NewsletterSettings = ({ title = false, welcome = false }) => {
|
||||
// Context
|
||||
const { loading, startLoading, stopLoading } = useContext(LoadingContext)
|
||||
|
||||
// Hooks
|
||||
const { account, setAccount, token } = useAccount()
|
||||
const backend = useBackend(token)
|
||||
const toast = useToast()
|
||||
const { t } = useTranslation(ns)
|
||||
|
||||
// State
|
||||
const [selection, setSelection] = useState(account?.newsletter ? 'yes' : 'no')
|
||||
|
||||
// Helper method to update account
|
||||
const update = async (val) => {
|
||||
if (val !== selection) {
|
||||
app.startLoading()
|
||||
startLoading()
|
||||
const result = await backend.updateAccount({ newsletter: val === 'yes' ? true : false })
|
||||
if (result.success) {
|
||||
setAccount(result.data.account)
|
||||
setSelection(val)
|
||||
toast.for.settingsSaved()
|
||||
} else toast.for.backendError()
|
||||
app.stopLoading()
|
||||
stopLoading()
|
||||
}
|
||||
}
|
||||
|
||||
// Next step for onboarding
|
||||
const nextHref =
|
||||
welcomeSteps[account?.control].length > 2
|
||||
? '/welcome/' + welcomeSteps[account?.control][2]
|
||||
|
@ -53,7 +63,7 @@ export const NewsletterSettings = ({ app, title = false, welcome = false }) => {
|
|||
))}
|
||||
{welcome ? (
|
||||
<>
|
||||
<ContinueButton app={app} btnProps={{ href: nextHref }} link />
|
||||
<ContinueButton btnProps={{ href: nextHref }} link />
|
||||
{welcomeSteps[account?.control].length > 0 ? (
|
||||
<>
|
||||
<progress
|
||||
|
@ -73,7 +83,7 @@ export const NewsletterSettings = ({ app, title = false, welcome = false }) => {
|
|||
) : null}
|
||||
</>
|
||||
) : (
|
||||
<BackToAccountButton loading={app.state.loading} />
|
||||
<BackToAccountButton loading={loading} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
// Dependencies
|
||||
import { useState } from 'react'
|
||||
import { useState, useContext } from 'react'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
// Hooks
|
||||
import { useAccount } from 'shared/hooks/use-account.mjs'
|
||||
import { useBackend } from 'shared/hooks/use-backend.mjs'
|
||||
import { useToast } from 'shared/hooks/use-toast.mjs'
|
||||
// Context
|
||||
import { LoadingContext } from 'shared/context/loading-context.mjs'
|
||||
// Components
|
||||
import Link from 'next/link'
|
||||
import { BackToAccountButton } from './shared.mjs'
|
||||
|
@ -14,22 +16,29 @@ import { RightIcon } from 'shared/components/icons.mjs'
|
|||
|
||||
export const ns = ['account', 'toast']
|
||||
|
||||
export const PasswordSettings = ({ app, title = false, welcome = false }) => {
|
||||
export const PasswordSettings = ({ title = false, welcome = false }) => {
|
||||
// Context
|
||||
const { loading, startLoading, stopLoading } = useContext(LoadingContext)
|
||||
|
||||
// Hooks
|
||||
const { account, setAccount, token } = useAccount()
|
||||
const backend = useBackend(token)
|
||||
const { t } = useTranslation(ns)
|
||||
const toast = useToast()
|
||||
|
||||
// State
|
||||
const [password, setPassword] = useState('')
|
||||
const [reveal, setReveal] = useState(false)
|
||||
|
||||
// Helper method to save password to account
|
||||
const save = async () => {
|
||||
app.startLoading()
|
||||
startLoading()
|
||||
const result = await backend.updateAccount({ password })
|
||||
if (result.success) {
|
||||
setAccount(result.data.account)
|
||||
toast.for.settingsSaved()
|
||||
} else toast.for.backendError()
|
||||
app.stopLoading()
|
||||
stopLoading()
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -52,8 +61,8 @@ export const PasswordSettings = ({ app, title = false, welcome = false }) => {
|
|||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<SaveSettingsButton app={app} btnProps={{ onClick: save, disabled: password.length < 4 }} />
|
||||
{!welcome && <BackToAccountButton loading={app.state.loading} />}
|
||||
<SaveSettingsButton btnProps={{ onClick: save, disabled: password.length < 4 }} />
|
||||
{!welcome && <BackToAccountButton loading={loading} />}
|
||||
{!account.mfaEnabled && (
|
||||
<Popout tip>
|
||||
<h5>{t('mfaTipTitle')}</h5>
|
||||
|
|
|
@ -1,38 +1,46 @@
|
|||
// Dependencies
|
||||
import { useTranslation } from 'next-i18next'
|
||||
// Hooks
|
||||
import { useContext } from 'react'
|
||||
import { useAccount } from 'shared/hooks/use-account.mjs'
|
||||
import { useBackend } from 'shared/hooks/use-backend.mjs'
|
||||
import { useToast } from 'shared/hooks/use-toast.mjs'
|
||||
// Context
|
||||
import { LoadingContext } from 'shared/context/loading-context.mjs'
|
||||
// Components
|
||||
import { BackToAccountButton } from './shared.mjs'
|
||||
|
||||
export const ns = ['account', 'toast']
|
||||
|
||||
export const ReloadAccount = ({ app, title = false }) => {
|
||||
export const ReloadAccount = ({ title = false }) => {
|
||||
// Context
|
||||
const { loading, startLoading, stopLoading } = useContext(LoadingContext)
|
||||
|
||||
// Hooks
|
||||
const { setAccount, token } = useAccount()
|
||||
const backend = useBackend(token)
|
||||
const { t } = useTranslation(ns)
|
||||
const toast = useToast()
|
||||
|
||||
// Helper method to reload account
|
||||
const reload = async () => {
|
||||
app.startLoading()
|
||||
startLoading()
|
||||
const result = await backend.reloadAccount()
|
||||
if (result.success) {
|
||||
setAccount(result.data.account)
|
||||
toast.success(<span>{t('nailedIt')}</span>)
|
||||
} else toast.for.backendError()
|
||||
app.stopLoading()
|
||||
stopLoading()
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="max-w-xl">
|
||||
{title ? <h2>{t('reloadMsg1')}</h2> : null}
|
||||
<p>{t('reloadMsg2')}</p>
|
||||
<button className="btn btn-primary capitalize w-full my-2" onClick={reload}>
|
||||
{t('reload')}
|
||||
</button>
|
||||
<BackToAccountButton loading={app.state.loading} />
|
||||
</>
|
||||
<BackToAccountButton loading={loading} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// Dependencies
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useState, useEffect, useContext } from 'react'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
import { DateTime } from 'luxon'
|
||||
import { CopyToClipboard } from 'react-copy-to-clipboard'
|
||||
|
@ -8,6 +8,9 @@ import { useAccount } from 'shared/hooks/use-account.mjs'
|
|||
import { useBackend } from 'shared/hooks/use-backend.mjs'
|
||||
import { useToast } from 'shared/hooks/use-toast.mjs'
|
||||
import { useRouter } from 'next/router'
|
||||
// Context
|
||||
import { LoadingContext } from 'shared/context/loading-context.mjs'
|
||||
import { ModalContext } from 'shared/context/modal-context.mjs'
|
||||
// Components
|
||||
import { BackToAccountButton, Choice } from './shared.mjs'
|
||||
import { Popout } from 'shared/components/popout.mjs'
|
||||
|
@ -22,15 +25,22 @@ import { Tab } from './bio.mjs'
|
|||
|
||||
export const ns = ['account', 'toast']
|
||||
|
||||
const NewSet = ({ app, t, account, setGenerate, oneAdded, backend, toast, standAlone = false }) => {
|
||||
const NewSet = ({ t, account, setGenerate, oneAdded, backend, toast, standAlone = false }) => {
|
||||
// Context
|
||||
const { loading, startLoading, stopLoading } = useContext(LoadingContext)
|
||||
|
||||
// Hooks
|
||||
const router = useRouter()
|
||||
|
||||
// State
|
||||
const [name, setName] = useState('')
|
||||
const [notes, setNotes] = useState('')
|
||||
const [set, setSet] = useState(false)
|
||||
const [activeTab, setActiveTab] = useState('edit')
|
||||
|
||||
// Helper method to create a new set
|
||||
const createSet = async () => {
|
||||
app.startLoading()
|
||||
startLoading()
|
||||
const result = await backend.createSet({
|
||||
name,
|
||||
})
|
||||
|
@ -39,13 +49,16 @@ const NewSet = ({ app, t, account, setGenerate, oneAdded, backend, toast, standA
|
|||
setSet(result.data.set)
|
||||
oneAdded()
|
||||
} else toast.for.backendError()
|
||||
app.stopLoading()
|
||||
stopLoading()
|
||||
}
|
||||
|
||||
// Helper method to clear inputs
|
||||
const clear = () => {
|
||||
setSet(false)
|
||||
setGenerate(false)
|
||||
}
|
||||
|
||||
// Shared props for tabs
|
||||
const tabProps = { activeTab, setActiveTab, t }
|
||||
|
||||
return (
|
||||
|
@ -104,7 +117,12 @@ const NewSet = ({ app, t, account, setGenerate, oneAdded, backend, toast, standA
|
|||
)
|
||||
}
|
||||
|
||||
const MeasurementsSet = ({ apikey, t, account, backend, oneAdded, app }) => {
|
||||
const MeasurementsSet = ({ apikey, t, account, backend, oneAdded }) => {
|
||||
// Context
|
||||
const { loading, startLoading, stopLoading } = useContext(LoadingContext)
|
||||
const { setModal } = useContext(ModalContext)
|
||||
|
||||
// Hooks
|
||||
const toast = useToast()
|
||||
|
||||
const fields = {
|
||||
|
@ -118,19 +136,19 @@ const MeasurementsSet = ({ apikey, t, account, backend, oneAdded, app }) => {
|
|||
const expired = DateTime.fromISO(apikey.expiresAt).valueOf() < DateTime.now().valueOf()
|
||||
|
||||
const remove = async () => {
|
||||
app.startLoading()
|
||||
startLoading()
|
||||
const result = await backend.removeApikey(apikey.id)
|
||||
if (result) toast.success(t('gone'))
|
||||
else toast.for.backendError()
|
||||
// This just forces a refresh of the list from the server
|
||||
// We obviously did not add a key here, but rather removed one
|
||||
oneAdded()
|
||||
app.stopLoading()
|
||||
stopLoading()
|
||||
}
|
||||
|
||||
const removeModal = () => {
|
||||
app.setModal(
|
||||
<ModalWrapper app={app} slideFrom="top">
|
||||
setModal(
|
||||
<ModalWrapper slideFrom="top">
|
||||
<h2>{t('areYouCertain')}</h2>
|
||||
<p>{t('deleteKeyWarning')}</p>
|
||||
<p className="flex flex-row gap-4 items-center justify-center">
|
||||
|
@ -205,16 +223,22 @@ const MeasurementsSet = ({ apikey, t, account, backend, oneAdded, app }) => {
|
|||
//}
|
||||
|
||||
// Component for the account/sets page
|
||||
export const Sets = ({ app }) => {
|
||||
export const Sets = () => {
|
||||
// Context
|
||||
const { loading, startLoading, stopLoading } = useContext(LoadingContext)
|
||||
|
||||
// Hooks
|
||||
const { account, token } = useAccount()
|
||||
const backend = useBackend(token)
|
||||
const { t } = useTranslation(ns)
|
||||
const toast = useToast()
|
||||
|
||||
// State
|
||||
const [sets, setSets] = useState([])
|
||||
const [generate, setGenerate] = useState(false)
|
||||
const [added, setAdded] = useState(0)
|
||||
|
||||
// Effects
|
||||
useEffect(() => {
|
||||
const getSets = async () => {
|
||||
const result = await backend.getSets()
|
||||
|
@ -223,17 +247,18 @@ export const Sets = ({ app }) => {
|
|||
getSets()
|
||||
}, [added])
|
||||
|
||||
// Helper method to force a refresh
|
||||
const oneAdded = () => setAdded(added + 1)
|
||||
|
||||
return (
|
||||
<div className="max-w-xl xl:pl-4">
|
||||
{generate ? (
|
||||
<NewSet {...{ app, t, account, setGenerate, backend, toast, oneAdded }} />
|
||||
<NewSet {...{ t, account, setGenerate, backend, toast, oneAdded }} />
|
||||
) : (
|
||||
<>
|
||||
<h2>{t('sets')}</h2>
|
||||
{sets.map((set) => (
|
||||
<Set {...{ app, account, apikey, t, backend, oneAdded }} key={apikey.id} />
|
||||
<Set {...{ account, apikey, t, backend, oneAdded }} key={apikey.id} />
|
||||
))}
|
||||
<button
|
||||
className="btn btn-primary w-full capitalize mt-4"
|
||||
|
@ -241,7 +266,7 @@ export const Sets = ({ app }) => {
|
|||
>
|
||||
{t('newSet')}
|
||||
</button>
|
||||
<BackToAccountButton loading={app.state.loading} />
|
||||
<BackToAccountButton loading={loading} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
|
|
|
@ -1,10 +1,12 @@
|
|||
// Dependencies
|
||||
import { useState } from 'react'
|
||||
import { useState, useContext } from 'react'
|
||||
import { useTranslation } from 'next-i18next'
|
||||
// Hooks
|
||||
import { useAccount } from 'shared/hooks/use-account.mjs'
|
||||
import { useBackend } from 'shared/hooks/use-backend.mjs'
|
||||
import { useToast } from 'shared/hooks/use-toast.mjs'
|
||||
// Context
|
||||
import { LoadingContext } from 'shared/context/loading-context.mjs'
|
||||
// Components
|
||||
import { Spinner } from 'shared/components/spinner.mjs'
|
||||
import { Icons, welcomeSteps, BackToAccountButton } from './shared.mjs'
|
||||
|
@ -13,7 +15,11 @@ import { ContinueButton } from 'site/components/buttons/continue-button.mjs'
|
|||
|
||||
export const ns = ['account', 'toast']
|
||||
|
||||
export const UsernameSettings = ({ app, title = false, welcome = false }) => {
|
||||
export const UsernameSettings = ({ title = false, welcome = false }) => {
|
||||
// Context
|
||||
const { loading, startLoading, stopLoading } = useContext(LoadingContext)
|
||||
|
||||
// Hooks
|
||||
const { account, setAccount, token } = useAccount()
|
||||
const backend = useBackend(token)
|
||||
const toast = useToast()
|
||||
|
@ -31,13 +37,13 @@ export const UsernameSettings = ({ app, title = false, welcome = false }) => {
|
|||
}
|
||||
|
||||
const save = async () => {
|
||||
app.startLoading()
|
||||
startLoading()
|
||||
const result = await backend.updateAccount({ username })
|
||||
if (result.success) {
|
||||
setAccount(result.data.account)
|
||||
toast.for.settingsSaved()
|
||||
} else toast.for.backendError()
|
||||
app.stopLoading()
|
||||
stopLoading()
|
||||
}
|
||||
|
||||
const nextHref =
|
||||
|
@ -48,11 +54,11 @@ export const UsernameSettings = ({ app, title = false, welcome = false }) => {
|
|||
let btnClasses = 'btn mt-4 capitalize '
|
||||
if (welcome) {
|
||||
btnClasses += 'w-64 '
|
||||
if (app.state.loading) btnClasses += 'btn-accent '
|
||||
if (loading) btnClasses += 'btn-accent '
|
||||
else btnClasses += 'btn-secondary '
|
||||
} else {
|
||||
btnClasses += 'w-full '
|
||||
if (app.state.loading) btnClasses += 'btn-accent '
|
||||
if (loading) btnClasses += 'btn-accent '
|
||||
else btnClasses += 'btn-primary '
|
||||
}
|
||||
|
||||
|
@ -77,7 +83,7 @@ export const UsernameSettings = ({ app, title = false, welcome = false }) => {
|
|||
</div>
|
||||
<button className={btnClasses} disabled={!available} onClick={save}>
|
||||
<span className="flex flex-row items-center gap-2">
|
||||
{app.state.loading ? (
|
||||
{loading ? (
|
||||
<>
|
||||
<Spinner />
|
||||
<span>{t('processing')}</span>
|
||||
|
@ -92,7 +98,7 @@ export const UsernameSettings = ({ app, title = false, welcome = false }) => {
|
|||
|
||||
{welcome ? (
|
||||
<>
|
||||
<ContinueButton app={app} btnProps={{ href: nextHref }} link />
|
||||
<ContinueButton btnProps={{ href: nextHref }} link />
|
||||
{welcomeSteps[account.control].length > 0 ? (
|
||||
<>
|
||||
<progress
|
||||
|
@ -112,7 +118,7 @@ export const UsernameSettings = ({ app, title = false, welcome = false }) => {
|
|||
) : null}
|
||||
</>
|
||||
) : (
|
||||
<BackToAccountButton loading={app.state.loading} />
|
||||
<BackToAccountButton loading={loading} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -7,8 +7,7 @@ export const ns = ['footer', ...sponsorsNs]
|
|||
|
||||
export const Footer = ({ app }) => (
|
||||
<footer className="bg-neutral">
|
||||
<Ribbon loading={app.loading} theme={app.theme} />
|
||||
|
||||
<Ribbon />
|
||||
<div className="w-full sm:w-auto flex flex-col gap-2 items-center justify-center pt-12">
|
||||
<FreeSewingIcon className="w-24 lg:w-40 m-auto m-auto text-neutral-content" />
|
||||
<div className="mt-4">
|
||||
|
|
|
@ -9,7 +9,7 @@ import { HttpMethod, HttpStatusCode } from './http.mjs'
|
|||
|
||||
const Fixme = () => <p>FIXME</p>
|
||||
|
||||
export const MdxComponents = (app = false) => ({
|
||||
export const MdxComponents = () => ({
|
||||
// Custom components
|
||||
Method: HttpMethod,
|
||||
StatusCode: HttpStatusCode,
|
||||
|
@ -17,7 +17,7 @@ export const MdxComponents = (app = false) => ({
|
|||
Fixme: (props) => <Popout {...props} fixme />,
|
||||
Link: (props) => <Popout {...props} link />,
|
||||
Note: (props) => <Popout {...props} note />,
|
||||
ReadMore: (props) => <ReadMore {...props} app={app} slug={app.state.slug} />,
|
||||
ReadMore,
|
||||
Related: (props) => <Popout {...props} related />,
|
||||
Tip: (props) => <Popout {...props} tip />,
|
||||
Warning: (props) => <Popout {...props} warning />,
|
||||
|
@ -31,7 +31,7 @@ export const MdxComponents = (app = false) => ({
|
|||
),
|
||||
Tab,
|
||||
Tabs,
|
||||
Example: (props) => <Example {...props} app={app} />,
|
||||
Example,
|
||||
PatternDocs: Fixme,
|
||||
PatternOptions: Fixme,
|
||||
})
|
||||
|
|
|
@ -2,13 +2,12 @@ import { MainSections, ActiveSection, ns as navNs } from './primary.mjs'
|
|||
|
||||
export const ns = navNs
|
||||
|
||||
export const AsideNavigation = ({ app, mobileOnly = false, before = [], after = [] }) => (
|
||||
export const AsideNavigation = ({ mobileOnly = false, before = [], after = [] }) => (
|
||||
<aside
|
||||
className={`
|
||||
fixed top-0 right-0 h-screen
|
||||
overflow-y-auto z-20
|
||||
bg-base-100 text-base-content
|
||||
${app.state?.menu?.main ? '' : 'translate-x-[-120%]'} transition-transform
|
||||
px-0 pb-20 pt-8 shrink-0
|
||||
|
||||
lg:w-auto
|
||||
|
@ -22,9 +21,9 @@ export const AsideNavigation = ({ app, mobileOnly = false, before = [], after =
|
|||
>
|
||||
<div className="w-screen lg:w-auto">
|
||||
{before}
|
||||
<MainSections app={app} />
|
||||
<div className=" border border-l-0 border-r-0 border-b-0 border-dashed border-base-300 mt-4 pt-4">
|
||||
<ActiveSection app={app} />
|
||||
<MainSections />
|
||||
<div className="border border-l-0 border-r-0 border-b-0 border-dashed border-base-300 mt-4 pt-4">
|
||||
<ActiveSection />
|
||||
</div>
|
||||
{after}
|
||||
</div>
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { NavigationContext } from 'shared/context/navigation-context.mjs'
|
||||
import { useContext } from 'react'
|
||||
import Link from 'next/link'
|
||||
import orderBy from 'lodash.orderby'
|
||||
import {
|
||||
|
@ -195,13 +197,14 @@ export const Icons = ({
|
|||
return <ul className={ulClasses}>{output}</ul>
|
||||
}
|
||||
|
||||
export const MainSections = ({ app }) => {
|
||||
if (!app.state.sections) return null
|
||||
export const MainSections = () => {
|
||||
const { sections = false, slug } = useContext(NavigationContext)
|
||||
if (!sections) return null
|
||||
// Ensure each page as an `o` key so we can put them in order
|
||||
const sortableSections = app.state.sections.map((s) => ({ ...s, o: s.o ? s.o : s.t }))
|
||||
const sortableSections = sections.map((s) => ({ ...s, o: s.o ? s.o : s.t }))
|
||||
const output = []
|
||||
for (const page of orderBy(sortableSections, ['o', 't'])) {
|
||||
const act = isActive(page.s, app.state.slug)
|
||||
const act = isActive(page.s, slug)
|
||||
const txt = (
|
||||
<>
|
||||
{icons[page.s] ? (
|
||||
|
@ -252,45 +255,47 @@ export const MainSections = ({ app }) => {
|
|||
return <ul>{output}</ul>
|
||||
}
|
||||
|
||||
const getCrumb = (index, app) => app.state.crumbs[index].s.split('/').pop()
|
||||
const getCrumb = (index, crumbs) => crumbs[index].s.split('/').pop()
|
||||
|
||||
export const ActiveSection = () => {
|
||||
// Get navigation context
|
||||
const { sections = false, crumbs = [], nav = {}, slug } = useContext(NavigationContext)
|
||||
|
||||
export const ActiveSection = ({ app }) => {
|
||||
// Don't bother if we don't know where we are
|
||||
if (!app.state.crumbs || !Array.isArray(app.state.crumbs) || app.state.crumbs.length < 1)
|
||||
return null
|
||||
if (!crumbs || !Array.isArray(crumbs) || crumbs.length < 1) return null
|
||||
|
||||
let slice = 1
|
||||
let nodes = app.state.nav
|
||||
let nodes = nav
|
||||
// Some sections are further trimmed
|
||||
if (app.state.crumbs && app.state.crumbs[0].s === 'docs') {
|
||||
if (app.state.crumbs.length > 1 && app.state.crumbs[1].s === 'docs/faq') {
|
||||
if (crumbs && crumbs[0].s === 'docs') {
|
||||
if (crumbs.length > 1 && crumbs[1].s === 'docs/faq') {
|
||||
slice = 2
|
||||
nodes = app.state.nav[getCrumb(1, app)]
|
||||
} else if (app.state.crumbs.length === 2) {
|
||||
nodes = nav[getCrumb(1, crumbs)]
|
||||
} else if (crumbs.length === 2) {
|
||||
slice = 2
|
||||
nodes = app.state.nav[getCrumb(1, app)]
|
||||
nodes = nav[getCrumb(1, crumbs)]
|
||||
} else if (
|
||||
app.state.crumbs.length === 4 &&
|
||||
app.state.crumbs[1].s === 'docs/patterns' &&
|
||||
app.state.crumbs[3].s.split('/').pop() === 'options'
|
||||
crumbs.length === 4 &&
|
||||
crumbs[1].s === 'docs/patterns' &&
|
||||
crumbs[3].s.split('/').pop() === 'options'
|
||||
) {
|
||||
slice = 4
|
||||
nodes = app.state.nav[getCrumb(1, app)][getCrumb(2, app)][getCrumb(3, app)]
|
||||
} else if (app.state.crumbs.length > 2 && app.state.crumbs[1].s === 'docs/patterns') {
|
||||
nodes = nav[getCrumb(1, crumbs)][getCrumb(2, crumbs)][getCrumb(3, crumbs)]
|
||||
} else if (crumbs.length > 2 && crumbs[1].s === 'docs/patterns') {
|
||||
slice = 3
|
||||
nodes = app.state.nav[getCrumb(1, app)][getCrumb(2, app)]
|
||||
nodes = nav[getCrumb(1, crumbs)][getCrumb(2, crumbs)]
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{app.state.crumbs ? (
|
||||
{crumbs ? (
|
||||
<div className="pl-4 my-2">
|
||||
<Breadcrumbs crumbs={app.state.crumbs.slice(0, slice)} />
|
||||
<Breadcrumbs crumbs={crumbs.slice(0, slice)} />
|
||||
</div>
|
||||
) : null}
|
||||
<div className="pr-2">
|
||||
<SubLevel hasChildren={1} nodes={nodes} active={app.state.slug} />
|
||||
<SubLevel hasChildren={1} nodes={nodes} active={slug} />
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
|
|
@ -1,7 +1,16 @@
|
|||
export const Ribbon = ({ loading = false }) => (
|
||||
<div
|
||||
className={`flex flex-col justify-between p-0 transition-transform
|
||||
${loading ? 'theme-gradient loading h-1' : 'h-0 -translate-y-1'}
|
||||
`}
|
||||
></div>
|
||||
)
|
||||
// Hooks
|
||||
import { useContext } from 'react'
|
||||
// Context
|
||||
import { LoadingContext } from 'shared/context/loading-context.mjs'
|
||||
|
||||
export const Ribbon = () => {
|
||||
const { loading } = useContext(LoadingContext)
|
||||
|
||||
return (
|
||||
<div
|
||||
className={`flex flex-col justify-between p-0 transition-transform
|
||||
${loading ? 'theme-gradient loading h-1' : 'h-0 -translate-y-1'}
|
||||
`}
|
||||
></div>
|
||||
)
|
||||
}
|
||||
|
|
11
sites/shared/components/wrappers/context.mjs
Normal file
11
sites/shared/components/wrappers/context.mjs
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { ModalContextProvider } from 'shared/context/modal-context.mjs'
|
||||
import { LoadingContextProvider } from 'shared/context/loading-context.mjs'
|
||||
import { NavigationContextProvider } from 'shared/context/navigation-context.mjs'
|
||||
|
||||
export const ContextWrapper = ({ children }) => (
|
||||
<ModalContextProvider>
|
||||
<LoadingContextProvider>
|
||||
<NavigationContextProvider>{children}</NavigationContextProvider>
|
||||
</LoadingContextProvider>
|
||||
</ModalContextProvider>
|
||||
)
|
|
@ -1,5 +1,5 @@
|
|||
// Dependencies
|
||||
import React, { useState, useEffect } from 'react'
|
||||
import React, { useState, useEffect, useContext } from 'react'
|
||||
import { useHotkeys } from 'react-hotkeys-hook'
|
||||
// Hooks
|
||||
import { useTheme } from 'shared/hooks/use-theme.mjs'
|
||||
|
@ -9,19 +9,20 @@ import { LayoutWrapper, ns as layoutNs } from 'site/components/wrappers/layout.m
|
|||
import { DocsLayout, ns as docsNs } from 'site/components/layouts/docs.mjs'
|
||||
import { Feeds } from 'site/components/feeds.mjs'
|
||||
import { Spinner } from 'shared/components/spinner.mjs'
|
||||
import { ModalContextProvider, ModalContextConsumer } from 'shared/context/modal-context.mjs'
|
||||
import { ModalContext } from 'shared/context/modal-context.mjs'
|
||||
import { NavigationContext } from 'shared/context/navigation-context.mjs'
|
||||
|
||||
export const ns = [...new Set([...layoutNs, ...docsNs])]
|
||||
|
||||
/* This component should wrap all page content */
|
||||
export const PageWrapper = ({
|
||||
noSearch = false,
|
||||
app = false,
|
||||
layout = DocsLayout,
|
||||
footer = true,
|
||||
children = [],
|
||||
title = <Spinner className="h-12 w-12 animate-spin text-primary" />,
|
||||
}) => {
|
||||
export const PageWrapper = (props) => {
|
||||
/*
|
||||
* Deconstruct props
|
||||
*/
|
||||
const { layout = DocsLayout, footer = true, children = [], path = [], locale = 'en' } = props
|
||||
// Title is typically set in props.t but check props.title too
|
||||
const pageTitle = props.t ? props.t : props.title ? props.title : null
|
||||
|
||||
/*
|
||||
* This forces a re-render upon initial bootstrap of the app
|
||||
* This is needed to avoid hydration errors because theme can't be set reliably in SSR
|
||||
|
@ -30,6 +31,23 @@ export const PageWrapper = ({
|
|||
const [currentTheme, setCurrentTheme] = useState()
|
||||
useEffect(() => setCurrentTheme(theme), [currentTheme, theme])
|
||||
|
||||
/*
|
||||
* Contexts
|
||||
*/
|
||||
const { modalContent } = useContext(ModalContext)
|
||||
const { title, setTitle, setNavigation } = useContext(NavigationContext)
|
||||
|
||||
/*
|
||||
* Update navigation context with title and path
|
||||
*/
|
||||
useEffect(() => {
|
||||
setNavigation({
|
||||
title: pageTitle,
|
||||
locale,
|
||||
path,
|
||||
})
|
||||
}, [path, title, pageTitle])
|
||||
|
||||
/*
|
||||
* Hotkeys (keyboard actions)
|
||||
*/
|
||||
|
@ -43,34 +61,24 @@ export const PageWrapper = ({
|
|||
const [search, setSearch] = useState(false)
|
||||
|
||||
// Helper object to pass props down (keeps things DRY)
|
||||
const childProps = {
|
||||
app: app,
|
||||
footer,
|
||||
search,
|
||||
setSearch,
|
||||
toggleSearch: () => setSearch(!search),
|
||||
noSearch: noSearch,
|
||||
title: app.state.title ? app.state.title : title,
|
||||
}
|
||||
const childProps = { footer, title: pageTitle }
|
||||
|
||||
// Make layout prop into a (uppercase) component
|
||||
const Layout = layout
|
||||
|
||||
// Return wrapper
|
||||
return (
|
||||
<ModalContextProvider>
|
||||
<SwipeWrapper app={app}>
|
||||
<div
|
||||
data-theme={currentTheme} // This facilitates CSS selectors
|
||||
key={currentTheme} // This forces the data-theme update
|
||||
>
|
||||
<Feeds />
|
||||
<LayoutWrapper {...childProps}>
|
||||
{Layout ? <Layout {...childProps}>{children}</Layout> : children}
|
||||
</LayoutWrapper>
|
||||
<ModalContextConsumer />
|
||||
</div>
|
||||
</SwipeWrapper>
|
||||
</ModalContextProvider>
|
||||
<SwipeWrapper>
|
||||
<div
|
||||
data-theme={currentTheme} // This facilitates CSS selectors
|
||||
key={currentTheme} // This forces the data-theme update
|
||||
>
|
||||
<Feeds />
|
||||
<LayoutWrapper {...childProps}>
|
||||
{Layout ? <Layout {...childProps}>{children}</Layout> : children}
|
||||
</LayoutWrapper>
|
||||
{modalContent}
|
||||
</div>
|
||||
</SwipeWrapper>
|
||||
)
|
||||
}
|
||||
|
|
24
sites/shared/context/loading-context.mjs
Normal file
24
sites/shared/context/loading-context.mjs
Normal file
|
@ -0,0 +1,24 @@
|
|||
import React, { useState } from 'react'
|
||||
|
||||
export const LoadingContext = React.createContext(false)
|
||||
|
||||
export const LoadingContextProvider = ({ children }) => {
|
||||
function stopLoading() {
|
||||
__setLoading({ ...__loading, loading: false })
|
||||
}
|
||||
function startLoading() {
|
||||
__setLoading({ ...__loading, loading: true })
|
||||
}
|
||||
function setLoading(loading) {
|
||||
__setLoading({ ...__loading, loading })
|
||||
}
|
||||
|
||||
const [__loading, __setLoading] = useState({
|
||||
setLoading,
|
||||
startLoading,
|
||||
stopLoading,
|
||||
loading: false,
|
||||
})
|
||||
|
||||
return <LoadingContext.Provider value={__loading}>{children}</LoadingContext.Provider>
|
||||
}
|
|
@ -19,9 +19,3 @@ export const ModalContextProvider = ({ children }) => {
|
|||
|
||||
return <ModalContext.Provider value={__modal}>{children}</ModalContext.Provider>
|
||||
}
|
||||
|
||||
export const ModalContextConsumer = ({}) => {
|
||||
const { modalContent } = useContext(ModalContext)
|
||||
|
||||
return modalContent ? modalContent : null
|
||||
}
|
||||
|
|
37
sites/shared/context/navigation-context.mjs
Normal file
37
sites/shared/context/navigation-context.mjs
Normal file
|
@ -0,0 +1,37 @@
|
|||
import React, { useState, useContext } from 'react'
|
||||
import { useNavigation } from 'site/hooks/use-navigation.mjs'
|
||||
|
||||
const defaultNavigationContext = {
|
||||
path: [],
|
||||
title: 'FIXME: No title (default)',
|
||||
locale: 'en',
|
||||
crumbs: [],
|
||||
}
|
||||
|
||||
export const NavigationContext = React.createContext(defaultNavigationContext)
|
||||
|
||||
export const NavigationContextProvider = ({ children }) => {
|
||||
function setNavigation(newValues) {
|
||||
setValue({
|
||||
...value,
|
||||
...newValues,
|
||||
setNavigation,
|
||||
})
|
||||
}
|
||||
|
||||
const [value, setValue] = useState({
|
||||
...defaultNavigationContext,
|
||||
setNavigation,
|
||||
})
|
||||
|
||||
const navState = useNavigation({
|
||||
path: value.path,
|
||||
locale: value.locale,
|
||||
})
|
||||
|
||||
return (
|
||||
<NavigationContext.Provider value={{ ...value, ...navState }}>
|
||||
{children}
|
||||
</NavigationContext.Provider>
|
||||
)
|
||||
}
|
|
@ -5,6 +5,7 @@ import createPersistedState from 'use-persisted-state'
|
|||
*/
|
||||
const usePersistedAccount = createPersistedState('fs-account')
|
||||
const usePersistedToken = createPersistedState('fs-token')
|
||||
const usePersistedSeenUser = createPersistedState('fs-seen-user')
|
||||
|
||||
/*
|
||||
* Make it possible to always check for account.username
|
||||
|
@ -18,9 +19,10 @@ export function useAccount() {
|
|||
// (persisted) State (saved to local storage)
|
||||
const [account, setAccount] = usePersistedAccount(noAccount)
|
||||
const [token, setToken] = usePersistedToken(null)
|
||||
const [seenUser, setSeenUser] = usePersistedSeenUser(false)
|
||||
|
||||
// Clear user data. This gets called when signing out
|
||||
const clear = () => {
|
||||
const logout = () => {
|
||||
setAccount(noAccount)
|
||||
setToken(null)
|
||||
}
|
||||
|
@ -30,6 +32,8 @@ export function useAccount() {
|
|||
setAccount,
|
||||
token,
|
||||
setToken,
|
||||
clear,
|
||||
seenUser,
|
||||
setSeenUser,
|
||||
logout,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,66 +0,0 @@
|
|||
// Hooks
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useNavigation } from 'site/hooks/use-navigation.mjs'
|
||||
// Dependencies
|
||||
import get from 'lodash.get'
|
||||
import set from 'lodash.set'
|
||||
import unset from 'lodash.unset'
|
||||
|
||||
const defaultState = {
|
||||
loading: false,
|
||||
modal: null,
|
||||
menu: {
|
||||
main: null,
|
||||
},
|
||||
}
|
||||
|
||||
/*
|
||||
* The useApp hook
|
||||
*/
|
||||
export function useApp(props = {}) {
|
||||
const { page = {}, loadState = {} } = props
|
||||
const { path = false } = page
|
||||
|
||||
if (!path) throw 'You MUST pass a page.path prop to the useApp hook'
|
||||
|
||||
const navState = useNavigation(path)
|
||||
|
||||
// React state
|
||||
const [state, setState] = useState(() => ({ ...defaultState, ...loadState }))
|
||||
|
||||
useEffect(() => {
|
||||
// Force update of navigation info (nav, title, crumbs) on each page change
|
||||
if (path) setState({ ...state, ...navState })
|
||||
}, [path, state.slug, state.title])
|
||||
|
||||
/*
|
||||
* Helper methods for partial state updates
|
||||
*/
|
||||
const updateState = (path, value) => setState(set({ ...state }, path, value))
|
||||
const unsetState = (path) => setState(unset({ ...state }, path))
|
||||
|
||||
/*
|
||||
* Helper methods for specific state updates
|
||||
*/
|
||||
const setModal = (content) => updateState('modal', content)
|
||||
const closeModal = () => updateState('modal', null)
|
||||
const closeMenu = (name) =>
|
||||
get(state, `menu.${name}`, false) ? updateState(`menu.${name}`, false) : null
|
||||
const startLoading = () => updateState('loading', true)
|
||||
const stopLoading = () => updateState('loading', false)
|
||||
|
||||
return {
|
||||
// All-purpose React state object
|
||||
state,
|
||||
setState,
|
||||
updateState,
|
||||
unsetState,
|
||||
|
||||
// Helper methods
|
||||
setModal,
|
||||
closeModal,
|
||||
closeMenu,
|
||||
startLoading,
|
||||
stopLoading,
|
||||
}
|
||||
}
|
14
sites/shared/hooks/use-navigation.mjs
Normal file
14
sites/shared/hooks/use-navigation.mjs
Normal file
|
@ -0,0 +1,14 @@
|
|||
import { useEffect, useContext } from 'react'
|
||||
import { NavigationContext } from 'shared/context/navigation-context.mjs'
|
||||
|
||||
export const useNavigation = ({ page }) => {
|
||||
const all = useContext(NavigationContext)
|
||||
|
||||
useEffect(() => {}, [page.path, page.t])
|
||||
|
||||
return {
|
||||
title: page.t,
|
||||
slug: page.s,
|
||||
order: page.o,
|
||||
}
|
||||
}
|
150
yarn.lock
150
yarn.lock
|
@ -3211,17 +3211,17 @@
|
|||
hey-listen "^1.0.8"
|
||||
tslib "^2.3.1"
|
||||
|
||||
"@next/bundle-analyzer@13.3.0":
|
||||
version "13.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@next/bundle-analyzer/-/bundle-analyzer-13.3.0.tgz#3dc6224d2020ee7198423b9989a173d19a069eff"
|
||||
integrity sha512-MNtsx3MFh/kqq2DDa4ladjzfUV8McncEowNvt/s7Z9UjvX6DkuJGFRdjq/RYtxWvjQAsDgiOsgyIOLzDbvQo3g==
|
||||
"@next/bundle-analyzer@13.3.1":
|
||||
version "13.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@next/bundle-analyzer/-/bundle-analyzer-13.3.1.tgz#df6c8c969646456b694dc1cd41d2847eae331248"
|
||||
integrity sha512-Qkd0ENOczIyJlKjjN7ENwQ2S5B6wqrTnVlzfWqAImw38VNgaVqZRgaM9Ph8JM43E6iLPjsMOHh8EN/7FxHyQ/g==
|
||||
dependencies:
|
||||
webpack-bundle-analyzer "4.7.0"
|
||||
|
||||
"@next/env@13.3.0":
|
||||
version "13.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@next/env/-/env-13.3.0.tgz#cc2e49f03060a4684ce7ec7fd617a21bc5b9edba"
|
||||
integrity sha512-AjppRV4uG3No7L1plinoTQETH+j2F10TEnrMfzbTUYwze5sBUPveeeBAPZPm8OkJZ1epq9OyYKhZrvbD6/9HCQ==
|
||||
"@next/env@13.3.1":
|
||||
version "13.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@next/env/-/env-13.3.1.tgz#589707043065f6b71d411ed9b8f1ffd057c0fd4a"
|
||||
integrity sha512-EDtCoedIZC7JlUQ3uaQpSc4aVmyhbLHmQVALg7pFfQgOTjgSnn7mKtA0DiCMkYvvsx6aFb5octGMtWrOtGXW9A==
|
||||
|
||||
"@next/eslint-plugin-next@13.3.0":
|
||||
version "13.3.0"
|
||||
|
@ -3230,50 +3230,57 @@
|
|||
dependencies:
|
||||
glob "7.1.7"
|
||||
|
||||
"@next/swc-darwin-arm64@13.3.0":
|
||||
version "13.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.3.0.tgz#38f18e0639cd4c7edc6a38d4b83fe00f38eea4f2"
|
||||
integrity sha512-DmIQCNq6JtccLPPBzf0dgh2vzMWt5wjxbP71pCi5EWpWYE3MsP6FcRXi4MlAmFNDQOfcFXR2r7kBeG1LpZUh1w==
|
||||
"@next/eslint-plugin-next@13.3.1":
|
||||
version "13.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@next/eslint-plugin-next/-/eslint-plugin-next-13.3.1.tgz#aa08601f1fec5e1ffbb5850761585734f110345a"
|
||||
integrity sha512-Hpd74UrYGF+bq9bBSRDXRsRfaWkPpcwjhvachy3sr/R/5fY6feC0T0s047pUthyqcaeNsqKOY1nUGQQJNm4WyA==
|
||||
dependencies:
|
||||
glob "7.1.7"
|
||||
|
||||
"@next/swc-darwin-x64@13.3.0":
|
||||
version "13.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-13.3.0.tgz#b670ed1fd1d231aa21279173ec52e3ad56dc6aeb"
|
||||
integrity sha512-oQoqFa88OGgwnYlnAGHVct618FRI/749se0N3S8t9Bzdv5CRbscnO0RcX901+YnNK4Q6yeiizfgO3b7kogtsZg==
|
||||
"@next/swc-darwin-arm64@13.3.1":
|
||||
version "13.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-darwin-arm64/-/swc-darwin-arm64-13.3.1.tgz#2c9719dd10a9cdf63bf50a7576b05dcf78999fe8"
|
||||
integrity sha512-UXPtriEc/pBP8luSLSCZBcbzPeVv+SSjs9cH/KygTbhmACye8/OOXRZO13Z2Wq1G0gLmEAIHQAOuF+vafPd2lw==
|
||||
|
||||
"@next/swc-linux-arm64-gnu@13.3.0":
|
||||
version "13.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.3.0.tgz#b114935f6b4c94c123f6cac55a4823d483209ba5"
|
||||
integrity sha512-Wzz2p/WqAJUqTVoLo6H18WMeAXo3i+9DkPDae4oQG8LMloJ3if4NEZTnOnTUlro6cq+S/W4pTGa97nWTrOjbGw==
|
||||
"@next/swc-darwin-x64@13.3.1":
|
||||
version "13.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-darwin-x64/-/swc-darwin-x64-13.3.1.tgz#0be90342c89e53a390ccd9bece15f7f5cd480049"
|
||||
integrity sha512-lT36yYxosCfLtplFzJWgo0hrPu6/do8+msgM7oQkPeohDNdhjtjFUgOOwdSnPublLR6Mo2Ym4P/wl5OANuD2bw==
|
||||
|
||||
"@next/swc-linux-arm64-musl@13.3.0":
|
||||
version "13.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.3.0.tgz#67a57309f8761c7d00d629d6785d56ed0567a0d2"
|
||||
integrity sha512-xPVrIQOQo9WXJYgmoTlMnAD/HlR/1e1ZIWGbwIzEirXBVBqMARUulBEIKdC19zuvoJ477qZJgBDCKtKEykCpyQ==
|
||||
"@next/swc-linux-arm64-gnu@13.3.1":
|
||||
version "13.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-13.3.1.tgz#a7353265839f8b8569a346a444dc3ab3770d297e"
|
||||
integrity sha512-wRb76nLWJhonH8s3kxC/1tFguEkeOPayIwe9mkaz1G/yeS3OrjeyKMJsb4+Kdg0zbTo53bNCOl59NNtDM7yyyw==
|
||||
|
||||
"@next/swc-linux-x64-gnu@13.3.0":
|
||||
version "13.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.3.0.tgz#11bd2bea7c00b40be111c0dd16e71171f3792086"
|
||||
integrity sha512-jOFlpGuPD7W2tuXVJP4wt9a3cpNxWAPcloq5EfMJRiXsBBOjLVFZA7boXYxEBzSVgUiVVr1V9T0HFM7pULJ1qA==
|
||||
"@next/swc-linux-arm64-musl@13.3.1":
|
||||
version "13.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-13.3.1.tgz#24552e6102c350e372f83f505a1d93c880551a50"
|
||||
integrity sha512-qz3BzjJRZ16Iq/jrp+pjiYOc0jTjHlfmxQmZk9x/+5uhRP6/eWQSTAPVJ33BMo6oK5O5N4644OgTAbzXzorecg==
|
||||
|
||||
"@next/swc-linux-x64-musl@13.3.0":
|
||||
version "13.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.3.0.tgz#d57e99f85890799b78719c3ea32a4624de8d701b"
|
||||
integrity sha512-2OwKlzaBgmuet9XYHc3KwsEilzb04F540rlRXkAcjMHL7eCxB7uZIGtsVvKOnQLvC/elrUegwSw1+5f7WmfyOw==
|
||||
"@next/swc-linux-x64-gnu@13.3.1":
|
||||
version "13.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-13.3.1.tgz#5f335a683b6eafa52307b12af97782993b6c45ff"
|
||||
integrity sha512-6mgkLmwlyWlomQmpl21I3hxgqE5INoW4owTlcLpNsd1V4wP+J46BlI/5zV5KWWbzjfncIqzXoeGs5Eg+1GHODA==
|
||||
|
||||
"@next/swc-win32-arm64-msvc@13.3.0":
|
||||
version "13.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.3.0.tgz#0c209aa35d1c88b01e78259a89cd68f4139b5093"
|
||||
integrity sha512-OeHiA6YEvndxT46g+rzFK/MQTfftKxJmzslERMu9LDdC6Kez0bdrgEYed5eXFK2Z1viKZJCGRlhd06rBusyztA==
|
||||
"@next/swc-linux-x64-musl@13.3.1":
|
||||
version "13.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-13.3.1.tgz#58e5aad6f97203a0788783f66324456c8f9cdb50"
|
||||
integrity sha512-uqm5sielhQmKJM+qayIhgZv1KlS5pqTdQ99b+Z7hMWryXS96qE0DftTmMZowBcUL6x7s2vSXyH5wPtO1ON7LBg==
|
||||
|
||||
"@next/swc-win32-ia32-msvc@13.3.0":
|
||||
version "13.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.3.0.tgz#52ae74da1dd6d840c3743923367d27ed013803dd"
|
||||
integrity sha512-4aB7K9mcVK1lYEzpOpqWrXHEZympU3oK65fnNcY1Qc4HLJFLJj8AViuqQd4jjjPNuV4sl8jAwTz3gN5VNGWB7w==
|
||||
"@next/swc-win32-arm64-msvc@13.3.1":
|
||||
version "13.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-13.3.1.tgz#f8ed1badab57ed4503969758754e6fb0cf326753"
|
||||
integrity sha512-WomIiTj/v3LevltlibNQKmvrOymNRYL+a0dp5R73IwPWN5FvXWwSELN/kiNALig/+T3luc4qHNTyvMCp9L6U5Q==
|
||||
|
||||
"@next/swc-win32-x64-msvc@13.3.0":
|
||||
version "13.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.3.0.tgz#db7b55fee834dc8c2c484c696469e65bae2ee770"
|
||||
integrity sha512-Reer6rkLLcoOvB0dd66+Y7WrWVFH7sEEkF/4bJCIfsSKnTStTYaHtwIJAwbqnt9I392Tqvku0KkoqZOryWV9LQ==
|
||||
"@next/swc-win32-ia32-msvc@13.3.1":
|
||||
version "13.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-win32-ia32-msvc/-/swc-win32-ia32-msvc-13.3.1.tgz#7f599c8975b09ee5527cc49b9e5a4d13be50635a"
|
||||
integrity sha512-M+PoH+0+q658wRUbs285RIaSTYnGBSTdweH/0CdzDgA6Q4rBM0sQs4DHmO3BPP0ltCO/vViIoyG7ks66XmCA5g==
|
||||
|
||||
"@next/swc-win32-x64-msvc@13.3.1":
|
||||
version "13.3.1"
|
||||
resolved "https://registry.yarnpkg.com/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-13.3.1.tgz#192d43ab44ebb98bd4f5865d0e1d7ce62703182f"
|
||||
integrity sha512-Sl1F4Vp5Z1rNXWZYqJwMuWRRol4bqOB6+/d7KqkgQ4AcafKPN1PZmpkCoxv4UFHtFNIB7EotnuIhtXu3zScicQ==
|
||||
|
||||
"@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1":
|
||||
version "5.1.1-v1"
|
||||
|
@ -4277,10 +4284,10 @@
|
|||
resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553"
|
||||
integrity sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==
|
||||
|
||||
"@swc/helpers@0.4.14":
|
||||
version "0.4.14"
|
||||
resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.4.14.tgz#1352ac6d95e3617ccb7c1498ff019654f1e12a74"
|
||||
integrity sha512-4C7nX/dvpzB7za4Ql9K81xK3HPxCpHMgwTZVyf+9JQ6VUbn9jjZVN7/Nkdz/Ugzs2CSjqnL/UPXroiVBVHUWUw==
|
||||
"@swc/helpers@0.5.0":
|
||||
version "0.5.0"
|
||||
resolved "https://registry.yarnpkg.com/@swc/helpers/-/helpers-0.5.0.tgz#bf1d807b60f7290d0ec763feea7ccdeda06e85f1"
|
||||
integrity sha512-SjY/p4MmECVVEWspzSRpQEM3sjR17sP8PbGxELWrT+YZMBfiUyt1MRUNjMV23zohwlG2HYtCQOsCwsTHguXkyg==
|
||||
dependencies:
|
||||
tslib "^2.4.0"
|
||||
|
||||
|
@ -8307,7 +8314,22 @@ escodegen@^2.0.0:
|
|||
optionalDependencies:
|
||||
source-map "~0.6.1"
|
||||
|
||||
eslint-config-next@13.3.0, eslint-config-next@^13.0.6:
|
||||
eslint-config-next@13.3.1:
|
||||
version "13.3.1"
|
||||
resolved "https://registry.yarnpkg.com/eslint-config-next/-/eslint-config-next-13.3.1.tgz#a4441b9b8a708383e9444492c21bab0099851d90"
|
||||
integrity sha512-DieA5djybeE3Q0IqnDXihmhgRSp44x1ywWBBpVRA9pSx+m5Icj8hFclx7ffXlAvb9MMLN6cgj/hqJ4lka/QmvA==
|
||||
dependencies:
|
||||
"@next/eslint-plugin-next" "13.3.1"
|
||||
"@rushstack/eslint-patch" "^1.1.3"
|
||||
"@typescript-eslint/parser" "^5.42.0"
|
||||
eslint-import-resolver-node "^0.3.6"
|
||||
eslint-import-resolver-typescript "^3.5.2"
|
||||
eslint-plugin-import "^2.26.0"
|
||||
eslint-plugin-jsx-a11y "^6.5.1"
|
||||
eslint-plugin-react "^7.31.7"
|
||||
eslint-plugin-react-hooks "^4.5.0"
|
||||
|
||||
eslint-config-next@^13.0.6:
|
||||
version "13.3.0"
|
||||
resolved "https://registry.yarnpkg.com/eslint-config-next/-/eslint-config-next-13.3.0.tgz#c302fbecfe2b976ea306f7622af637ef9d9e3802"
|
||||
integrity sha512-6YEwmFBX0VjBd3ODGW9df0Is0FLaRFdMN8eAahQG9CN6LjQ28J8AFr19ngxqMSg7Qv6Uca/3VeeBosJh1bzu0w==
|
||||
|
@ -13844,27 +13866,27 @@ next-i18next@13.2.2:
|
|||
hoist-non-react-statics "^3.3.2"
|
||||
i18next-fs-backend "^2.1.1"
|
||||
|
||||
next@13.3.0:
|
||||
version "13.3.0"
|
||||
resolved "https://registry.yarnpkg.com/next/-/next-13.3.0.tgz#40632d303d74fc8521faa0a5bf4a033a392749b1"
|
||||
integrity sha512-OVTw8MpIPa12+DCUkPqRGPS3thlJPcwae2ZL4xti3iBff27goH024xy4q2lhlsdoYiKOi8Kz6uJoLW/GXwgfOA==
|
||||
next@13.3.1:
|
||||
version "13.3.1"
|
||||
resolved "https://registry.yarnpkg.com/next/-/next-13.3.1.tgz#17625f7423db2e059d71b41bd9031756cf2b33bc"
|
||||
integrity sha512-eByWRxPzKHs2oQz1yE41LX35umhz86ZSZ+mYyXBqn2IBi2hyUqxBA88avywdr4uyH+hCJczegGsDGWbzQA5Rqw==
|
||||
dependencies:
|
||||
"@next/env" "13.3.0"
|
||||
"@swc/helpers" "0.4.14"
|
||||
"@next/env" "13.3.1"
|
||||
"@swc/helpers" "0.5.0"
|
||||
busboy "1.6.0"
|
||||
caniuse-lite "^1.0.30001406"
|
||||
postcss "8.4.14"
|
||||
styled-jsx "5.1.1"
|
||||
optionalDependencies:
|
||||
"@next/swc-darwin-arm64" "13.3.0"
|
||||
"@next/swc-darwin-x64" "13.3.0"
|
||||
"@next/swc-linux-arm64-gnu" "13.3.0"
|
||||
"@next/swc-linux-arm64-musl" "13.3.0"
|
||||
"@next/swc-linux-x64-gnu" "13.3.0"
|
||||
"@next/swc-linux-x64-musl" "13.3.0"
|
||||
"@next/swc-win32-arm64-msvc" "13.3.0"
|
||||
"@next/swc-win32-ia32-msvc" "13.3.0"
|
||||
"@next/swc-win32-x64-msvc" "13.3.0"
|
||||
"@next/swc-darwin-arm64" "13.3.1"
|
||||
"@next/swc-darwin-x64" "13.3.1"
|
||||
"@next/swc-linux-arm64-gnu" "13.3.1"
|
||||
"@next/swc-linux-arm64-musl" "13.3.1"
|
||||
"@next/swc-linux-x64-gnu" "13.3.1"
|
||||
"@next/swc-linux-x64-musl" "13.3.1"
|
||||
"@next/swc-win32-arm64-msvc" "13.3.1"
|
||||
"@next/swc-win32-ia32-msvc" "13.3.1"
|
||||
"@next/swc-win32-x64-msvc" "13.3.1"
|
||||
|
||||
nise@^5.1.4:
|
||||
version "5.1.4"
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue