diff --git a/config/dependencies.yaml b/config/dependencies.yaml index 6b1a89713dd..bae15ff7f36 100644 --- a/config/dependencies.yaml +++ b/config/dependencies.yaml @@ -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' diff --git a/sites/dev/package.json b/sites/dev/package.json index fb5c1413b35..0f3151ab407 100644 --- a/sites/dev/package.json +++ b/sites/dev/package.json @@ -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", diff --git a/sites/lab/package.json b/sites/lab/package.json index 8de17369fec..d9a3cc66b29 100644 --- a/sites/lab/package.json +++ b/sites/lab/package.json @@ -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", diff --git a/sites/org/components/buttons/continue-button.mjs b/sites/org/components/buttons/continue-button.mjs index 3094bb43a6e..eee93f807ae 100644 --- a/sites/org/components/buttons/continue-button.mjs +++ b/sites/org/components/buttons/continue-button.mjs @@ -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 = ( - {app.state.loading ? ( + {loading ? ( <> {t('processing')} diff --git a/sites/org/components/buttons/save-settings-button.mjs b/sites/org/components/buttons/save-settings-button.mjs index a5cfdf1f485..ef90a19de10 100644 --- a/sites/org/components/buttons/save-settings-button.mjs +++ b/sites/org/components/buttons/save-settings-button.mjs @@ -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 ( -

- -

- {t('noConsentNoAccount')} - - )} - - ) - const partB = ( - <> -
{t('peopleQuestion')}
- {mDetails ? : null} - {measurements ? ( - - ) : ( - - )} - {mDetails && measurements ? ( - - ) : null} - {measurements && !openData ? {t('openDataInfo')} : null} - {!measurements && ( - <> -

- -

- {t('noConsentNoPatterns')} - - )} - - ) - - return ( - - - {ready ? ( - <> -

{t('privacyMatters')}

-

{t('compliant')}

-

{t('consentWhyAnswer')}

- {partA} - {profile && partB} - - ) : ( - - )} - {profile && !measurements && ( - - )} - {profile && measurements && ( - - )} -

- - FreeSewing Privacy Notice - -

-
-
- ) + return } export default ConfirmSignUpPage diff --git a/sites/org/pages/designs/index.mjs b/sites/org/pages/designs/index.mjs index aa8c6b51c21..c52466715ee 100644 --- a/sites/org/pages/designs/index.mjs +++ b/sites/org/pages/designs/index.mjs @@ -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 ( - -
- -
-
- ) -} +/* + * 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 }) => ( + +
+ +
+
+) export default DesignsPage @@ -28,6 +28,7 @@ export async function getStaticProps({ locale }) { props: { ...(await serverSideTranslations(locale, namespaces)), page: { + locale, path: ['designs'], }, }, diff --git a/sites/org/pages/docs/[...mdxslug].mjs b/sites/org/pages/docs/[...mdxslug].mjs index 49e8cde8820..f2f8e35ef4d 100644 --- a/sites/org/pages/docs/[...mdxslug].mjs +++ b/sites/org/pages/docs/[...mdxslug].mjs @@ -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 ( - - - - - - - - - - - - - - {fullTitle} - -
- {props.toc && ( -
- -
- )} - +/* + * 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 }) => ( + + + + + + + + + + + + + + {typeof page.t === 'string' ? page.t : ''} - FreeSewing.org + +
+ {toc && ( +
+ +
+ )} +
+
- - ) -} +
+
+) /* * 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, diff --git a/sites/org/pages/docs/index.mjs b/sites/org/pages/docs/index.mjs index 57fce9be4e7..3b8f3b13a8c 100644 --- a/sites/org/pages/docs/index.mjs +++ b/sites/org/pages/docs/index.mjs @@ -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 ( - - - - - - - - - - - - - - {fullTitle} - -
- {props.toc && ( -
- -
- )} - +/* + * 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 }) => ( + + + + + + + + + + + + + + {typeof page.t === 'string' ? page.t : ''} - FreeSewing.org + +
+ {toc && ( +
+ +
+ )} +
+
- - ) -} +
+
+) /* * 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)), }, } } diff --git a/sites/org/pages/index.mjs b/sites/org/pages/index.mjs index f1824928e45..11e2e478194 100644 --- a/sites/org/pages/index.mjs +++ b/sites/org/pages/index.mjs @@ -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 ( - - - {title} - -
-
- - Create homepage. Meanwhile check - -

What is FreeSewing?

- (by ChatGPT) -

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

-

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

-

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

-

- Overall, freesewing is a powerful tool for anyone interested in sewing and pattern - making, whether they are seasoned professionals or beginners just starting out. -

-
+/* + * 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 }) => ( + + + Welcome to FreeSewing.org + +
+
+ + Create homepage. Meanwhile check + +

What is FreeSewing?

+ (by ChatGPT) +

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

+

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

+

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

+

+ Overall, freesewing is a powerful tool for anyone interested in sewing and pattern making, + whether they are seasoned professionals or beginners just starting out. +

- - ) -} +
+
+) export default HomePage @@ -64,6 +60,7 @@ export async function getStaticProps({ locale }) { props: { ...(await serverSideTranslations(locale)), page: { + locale, path: [], }, }, diff --git a/sites/org/pages/new/apikey/index.mjs b/sites/org/pages/new/apikey/index.mjs index cda0d853831..6f05d6f924e 100644 --- a/sites/org/pages/new/apikey/index.mjs +++ b/sites/org/pages/new/apikey/index.mjs @@ -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 }) => ( + + + + + +) - return ( - - - - - - ) -} - -export default AccountPage +export default NewApikeyPage export async function getStaticProps({ locale }) { return { props: { ...(await serverSideTranslations(locale, namespaces)), page: { + locale, path: ['new', 'apikey'], }, }, diff --git a/sites/org/pages/new/index.mjs b/sites/org/pages/new/index.mjs index df9331322f5..4165e23f841 100644 --- a/sites/org/pages/new/index.mjs +++ b/sites/org/pages/new/index.mjs @@ -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 }) => ( + +
+ +
This needs an umbrella page
+

+ We need to create content here linking to all the new something pages +

+
+
+
+) - return ( - -
- -
This needs an umbrella page
-

- We need to create content here linking to all the new something pages -

-
-
-
- ) -} - -export default AccountPage +export default NewIndexPage export async function getStaticProps({ locale }) { return { props: { ...(await serverSideTranslations(locale, namespaces)), page: { + locale, path: ['new'], }, }, diff --git a/sites/org/pages/profile.mjs b/sites/org/pages/profile.mjs index e6855ce9c6a..a391639e181 100644 --- a/sites/org/pages/profile.mjs +++ b/sites/org/pages/profile.mjs @@ -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 ( - - - + + + @@ -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'], }, }, diff --git a/sites/org/pages/showcase/index.mjs b/sites/org/pages/showcase/index.mjs index b1d89cd4a07..8b39fb430d3 100644 --- a/sites/org/pages/showcase/index.mjs +++ b/sites/org/pages/showcase/index.mjs @@ -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 ( - -
- -
-
- ) -} +/* + * 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 }) => ( + +
+ +
+
+) export default DesignsPage @@ -28,6 +28,7 @@ export async function getStaticProps({ locale }) { props: { ...(await serverSideTranslations(locale, namespaces)), page: { + locale, path: ['showcase'], }, }, diff --git a/sites/org/pages/signin/index.mjs b/sites/org/pages/signin/index.mjs index 7b2dd257971..7feddd160d3 100644 --- a/sites/org/pages/signin/index.mjs +++ b/sites/org/pages/signin/index.mjs @@ -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 }) => (
{children}
) -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({msg}) @@ -116,7 +137,7 @@ const SignInPage = (props) => { setSignInFailed(msg) return toast.warning({msg}) } - app.stopLoading() + stopLoading() } const btnClasses = `btn capitalize w-full mt-4 ${ @@ -132,8 +153,8 @@ const SignInPage = (props) => { if (magicLinkSent) return ( - - + +

{t('signup:emailSent')}

@@ -154,15 +175,15 @@ const SignInPage = (props) => { ) return ( - - + +

- {t('signin:welcomeName', { name: app.username || '' })} + {seenBefore ? t('signin:welcomeBackName', { name: seenUser }) : t('signin:welcome')}

{t('signin:signInToThing', { thing: 'FreeSewing.org' })}

- {!app.username && } + {!seenBefore && } {magicLink ? ( )} -
    +
    • - {app.username ? ( + {seenBefore ? ( <>
    • |
    • -
    • ) : null}
    -

    - {t('signin:dontHaveAnAccount')}{' '} - - {t('signin:signUpHere')} - -

    + {!seenBefore ? ( +

    + {t('signin:dontHaveAnAccount')}{' '} + + {t('signin:signUpHere')} + +

    + ) : null} ) @@ -225,6 +248,7 @@ export async function getStaticProps({ locale }) { props: { ...(await serverSideTranslations(locale)), page: { + locale, path: ['signin'], }, }, diff --git a/sites/org/pages/signin/signin.en.yaml b/sites/org/pages/signin/signin.en.yaml index 1ce42ec9677..915b3d0e49b 100644 --- a/sites/org/pages/signin/signin.en.yaml +++ b/sites/org/pages/signin/signin.en.yaml @@ -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 diff --git a/sites/org/pages/signup/index.mjs b/sites/org/pages/signup/index.mjs index 51866beed48..782080d8d8b 100644 --- a/sites/org/pages/signup/index.mjs +++ b/sites/org/pages/signup/index.mjs @@ -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 }) => ( ) -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( - + setModal( +

    An error occured while trying to process your request

    @@ -80,13 +84,14 @@ const SignUpPage = (props) => { ) } + setLoading(false) } - const loadingClasses = app.state.loading ? 'opacity-50' : '' + const loadingClasses = loading ? 'opacity-50' : '' return ( - - + +

    {result ? ( result === 'success' ? ( @@ -145,7 +150,6 @@ const SignUpPage = (props) => { /> { - const app = useApp(props) +// Re-use this +const 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. +

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

    - ) - - return ( - -
    -

    This typography page shows an overview of different elements and how they are styled.

    -

    It's a good starting point for theme development.

    -

    Headings (this is h2)

    - {p} {p} -

    This is h3

    - {p} {p} -

    This is h4

    - {p} {p} -
    This is h5
    - {p} {p} -
    This is h6
    - {p} {p} -

    Links and buttons

    -

    - A regular link looks like this, whereas buttons look like this: -

    -

    Main button styles

    -
    - - - - -
    -

    State button styles

    -
    - - - - -
    -

    Other button styles

    -
    - - -
    -

    Outlined button styles

    -
    - - - - -
    -

    Button sizes

    -
    - - - - - - - - -
    -

    Popouts

    -

    The Popout component is what powers various custom MDX components under the hood:

    - {['note', 'tip', 'warning', 'fixme', 'link', 'related', 'none'].map((type) => { - const props = {} - props[type] = true - return ( -
    -

    {type}

    - -
    I am the {type} title
    - {p} -
    -
    - ) - })} +const TypographyPage = ({ page }) => ( + +
    +

    This typography page shows an overview of different elements and how they are styled.

    +

    It's a good starting point for theme development.

    +

    Headings (this is h2)

    + {p} {p} +

    This is h3

    + {p} {p} +

    This is h4

    + {p} {p} +
    This is h5
    + {p} {p} +
    This is h6
    + {p} {p} +

    Links and buttons

    +

    + A regular link looks like this, whereas buttons look like this: +

    +

    Main button styles

    +
    + + + +
    - - ) -} +

    State button styles

    +
    + + + + +
    +

    Other button styles

    +
    + + +
    +

    Outlined button styles

    +
    + + + + +
    +

    Button sizes

    +
    + + + + + + + + +
    +

    Popouts

    +

    The Popout component is what powers various custom MDX components under the hood:

    + {['note', 'tip', 'warning', 'fixme', 'link', 'related', 'none'].map((type) => { + const props = {} + props[type] = true + return ( +
    +

    {type}

    + +
    I am the {type} title
    + {p} +
    +
    + ) + })} +
    +
    +) export default TypographyPage @@ -102,6 +97,7 @@ export async function getStaticProps({ locale }) { props: { ...(await serverSideTranslations(locale, namespaces)), page: { + locale, path: ['typography'], }, }, diff --git a/sites/org/pages/welcome/bio.mjs b/sites/org/pages/welcome/bio.mjs index c549a2d0f95..4bc394a3b93 100644 --- a/sites/org/pages/welcome/bio.mjs +++ b/sites/org/pages/welcome/bio.mjs @@ -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 ( - - + +
    - +
    ) } -export default BioPage +export default WelcomeBioPage export async function getStaticProps({ locale }) { return { props: { ...(await serverSideTranslations(locale, namespaces)), page: { + locale, path: ['welcome', 'bio'], }, }, diff --git a/sites/org/pages/welcome/compare.mjs b/sites/org/pages/welcome/compare.mjs index b236cf666a0..74f6cef9af9 100644 --- a/sites/org/pages/welcome/compare.mjs +++ b/sites/org/pages/welcome/compare.mjs @@ -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 ( - - + +
    - +
    ) } -export default ComparePage +export default WelcomeComparePage export async function getStaticProps({ locale }) { return { props: { ...(await serverSideTranslations(locale, namespaces)), page: { + locale, path: ['welcome', 'compare'], }, }, diff --git a/sites/org/pages/welcome/img.mjs b/sites/org/pages/welcome/img.mjs index 2327dec6c5f..82eaaa13a10 100644 --- a/sites/org/pages/welcome/img.mjs +++ b/sites/org/pages/welcome/img.mjs @@ -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 ( - - + +
    - +
    ) } -export default ImgPage +export default WelcomeImgPage export async function getStaticProps({ locale }) { return { props: { ...(await serverSideTranslations(locale, namespaces)), page: { + locale, path: ['welcome', 'img'], }, }, diff --git a/sites/org/pages/welcome/index.mjs b/sites/org/pages/welcome/index.mjs index 82201d826d1..beeac93ea52 100644 --- a/sites/org/pages/welcome/index.mjs +++ b/sites/org/pages/welcome/index.mjs @@ -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 ( - - + +
    - +
    diff --git a/sites/org/pages/welcome/newsletter.mjs b/sites/org/pages/welcome/newsletter.mjs index 56158388060..50f8d585df7 100644 --- a/sites/org/pages/welcome/newsletter.mjs +++ b/sites/org/pages/welcome/newsletter.mjs @@ -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 ( - - + +
    - +
    ) } -export default WelcomePage +export default WelcomeNewsletterPage export async function getStaticProps({ locale }) { return { props: { ...(await serverSideTranslations(locale, namespaces)), page: { + locale, path: ['welcome', 'newsletter'], }, }, diff --git a/sites/org/pages/welcome/units.mjs b/sites/org/pages/welcome/units.mjs index 4c3ed986aa9..6cec59aafdd 100644 --- a/sites/org/pages/welcome/units.mjs +++ b/sites/org/pages/welcome/units.mjs @@ -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 ( - - + +
    - +
    ) } -export default UnitsPage +export default WelcomeUnitsPage export async function getStaticProps({ locale }) { return { props: { ...(await serverSideTranslations(locale, namespaces)), page: { + locale, path: ['welcome', 'units'], }, }, diff --git a/sites/org/pages/welcome/username.mjs b/sites/org/pages/welcome/username.mjs index 355256ac03a..6325c5aeac7 100644 --- a/sites/org/pages/welcome/username.mjs +++ b/sites/org/pages/welcome/username.mjs @@ -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 ( - - + +
    - +
    ) } -export default UsernamePage +export default WelcomeUsernamePage export async function getStaticProps({ locale }) { return { props: { ...(await serverSideTranslations(locale, namespaces)), page: { + locale, path: ['welcome', 'username'], }, }, diff --git a/sites/shared/components/account/apikeys.mjs b/sites/shared/components/account/apikeys.mjs index a1e3c1d8737..c31d1383c49 100644 --- a/sites/shared/components/account/apikeys.mjs +++ b/sites/shared/components/account/apikeys.mjs @@ -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 }) => (
    ) -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( - + setModal( +

    {t('areYouCertain')}

    {t('deleteKeyWarning')}

    @@ -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 (

    - +
    ) } // 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 (
    {generate ? ( - + ) : ( <>

    {t('apikeys')}

    @@ -324,7 +375,7 @@ export const Apikeys = ({ app }) => { > {t('newApikey')} - + {account.control < 5 ? (
    Refer to FreeSewing.dev for details (English only)
    diff --git a/sites/shared/components/account/bio.mjs b/sites/shared/components/account/bio.mjs index 82f1873abdc..5f28785602b 100644 --- a/sites/shared/components/account/bio.mjs +++ b/sites/shared/components/account/bio.mjs @@ -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 }) => ( ) -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 }) => {
    )}

    - - {!welcome && } + + {!welcome && } {t('mdSupport')} {welcome ? ( <> - + {welcomeSteps[account.control].length > 0 ? ( <> { +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 ? ( <> - + {welcomeSteps[account?.control].length > 0 ? ( <> { ) : null} ) : ( - + )}
) } - -export default CompareSettings diff --git a/sites/shared/components/account/consent.mjs b/sites/shared/components/account/consent.mjs index 74d2d8324ba..1dc03e03808 100644 --- a/sites/shared/components/account/consent.mjs +++ b/sites/shared/components/account/consent.mjs @@ -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 }) => (
) -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 = ( <>
{t('profileQuestion')}
@@ -92,6 +102,7 @@ export const ConsentSettings = ({ app, title = false }) => { )} ) + // Part B of the consent screen const partB = ( <>
{t('setQuestion')}
@@ -122,12 +133,9 @@ export const ConsentSettings = ({ app, title = false }) => {

{t('consentWhyAnswer')}

{partA} {profile && partB} - {profile && measurements ? ( - - ) : null} + {profile && measurements ? : null} {profile && !measurements ? ( { ) : null} {!profile ? ( { /> ) : null} - +

FreeSewing Privacy Notice diff --git a/sites/shared/components/account/control.mjs b/sites/shared/components/account/control.mjs index 1354a8c961a..b5e2efe4c22 100644 --- a/sites/shared/components/account/control.mjs +++ b/sites/shared/components/account/control.mjs @@ -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 (

@@ -58,7 +71,7 @@ export const ControlSettings = ({ app, title = false, welcome = false }) => { })} {welcome ? ( <> - + {welcomeSteps[selection].length > 1 ? ( <> { ) : null} ) : ( - + )}
) diff --git a/sites/shared/components/account/email.mjs b/sites/shared/components/account/email.mjs index 5da50bb4258..9fbe5fa20cd 100644 --- a/sites/shared/components/account/email.mjs +++ b/sites/shared/components/account/email.mjs @@ -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 }) => { )} - +
) } diff --git a/sites/shared/components/account/github.mjs b/sites/shared/components/account/github.mjs index 104b0645700..c71939685a2 100644 --- a/sites/shared/components/account/github.mjs +++ b/sites/shared/components/account/github.mjs @@ -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')} /> - - {!welcome && } + + {!welcome && } ) } diff --git a/sites/shared/components/account/img.mjs b/sites/shared/components/account/img.mjs index d3f3a067f2e..8c93840ecec 100644 --- a/sites/shared/components/account/img.mjs +++ b/sites/shared/components/account/img.mjs @@ -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 }) => { - + {welcomeSteps[account.control].length > 0 ? ( <> { ) : ( <> - - + + )} diff --git a/sites/shared/components/account/imperial.mjs b/sites/shared/components/account/imperial.mjs index 48d48b7aa89..ee84ac6957f 100644 --- a/sites/shared/components/account/imperial.mjs +++ b/sites/shared/components/account/imperial.mjs @@ -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 ? ( <> - + {welcomeSteps[account?.control].length > 0 ? ( <> { ) : null} ) : ( - + )} ) diff --git a/sites/shared/components/account/language.mjs b/sites/shared/components/account/language.mjs index d2306a2915c..a72ca4fe4e5 100644 --- a/sites/shared/components/account/language.mjs +++ b/sites/shared/components/account/language.mjs @@ -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 }) => { {t(`locales:${val}`)} ))} - + ) } diff --git a/sites/shared/components/account/links.mjs b/sites/shared/components/account/links.mjs index 06e973e1039..d2ea0b21da3 100644 --- a/sites/shared/components/account/links.mjs +++ b/sites/shared/components/account/links.mjs @@ -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 }) => { {t('newSet')} - diff --git a/sites/shared/components/account/mfa.mjs b/sites/shared/components/account/mfa.mjs index 79fd32e33af..b1afd0d4290 100644 --- a/sites/shared/components/account/mfa.mjs +++ b/sites/shared/components/account/mfa.mjs @@ -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 }) => { )} - {!welcome && } + {!welcome && } ) } diff --git a/sites/shared/components/account/newsletter.mjs b/sites/shared/components/account/newsletter.mjs index 65eff7175ae..449cf1befed 100644 --- a/sites/shared/components/account/newsletter.mjs +++ b/sites/shared/components/account/newsletter.mjs @@ -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 ? ( <> - + {welcomeSteps[account?.control].length > 0 ? ( <> { ) : null} ) : ( - + )} ) diff --git a/sites/shared/components/account/password.mjs b/sites/shared/components/account/password.mjs index f0e4853067b..913884cd747 100644 --- a/sites/shared/components/account/password.mjs +++ b/sites/shared/components/account/password.mjs @@ -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 }) => {
- - {!welcome && } + + {!welcome && } {!account.mfaEnabled && (
{t('mfaTipTitle')}
diff --git a/sites/shared/components/account/reload.mjs b/sites/shared/components/account/reload.mjs index e48a3c83eda..10f8a8b62f4 100644 --- a/sites/shared/components/account/reload.mjs +++ b/sites/shared/components/account/reload.mjs @@ -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({t('nailedIt')}) } else toast.for.backendError() - app.stopLoading() + stopLoading() } return ( - <> +
{title ?

{t('reloadMsg1')}

: null}

{t('reloadMsg2')}

- - + +
) } diff --git a/sites/shared/components/account/sets.mjs b/sites/shared/components/account/sets.mjs index bc33906eb3e..2d4be1d3df5 100644 --- a/sites/shared/components/account/sets.mjs +++ b/sites/shared/components/account/sets.mjs @@ -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( - + setModal( +

{t('areYouCertain')}

{t('deleteKeyWarning')}

@@ -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 (

{generate ? ( - + ) : ( <>

{t('sets')}

{sets.map((set) => ( - + ))} - + )}
diff --git a/sites/shared/components/account/username.mjs b/sites/shared/components/account/username.mjs index 88c42dc98fd..0d7e6f2cc5c 100644 --- a/sites/shared/components/account/username.mjs +++ b/sites/shared/components/account/username.mjs @@ -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 }) => {