From e47c18177b5e161738e502669eac32ee0b91581b Mon Sep 17 00:00:00 2001 From: Joost De Cock Date: Fri, 28 Apr 2023 21:23:06 +0200 Subject: [PATCH] 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. --- config/dependencies.yaml | 2 +- sites/dev/package.json | 6 +- sites/lab/package.json | 4 +- .../components/buttons/continue-button.mjs | 16 +- .../buttons/save-settings-button.mjs | 16 +- sites/org/components/header/header.nl.yaml | 11 + sites/org/components/header/index.mjs | 24 +- sites/org/components/layouts/bare.mjs | 6 +- sites/org/components/layouts/docs.mjs | 34 +-- sites/org/hooks/use-navigation.mjs | 11 +- sites/org/package.json | 4 +- sites/org/pages/_app.mjs | 41 ++-- sites/org/pages/account/apikeys.mjs | 29 +-- sites/org/pages/account/bio.mjs | 29 +-- sites/org/pages/account/compare.mjs | 29 +-- sites/org/pages/account/consent.mjs | 29 +-- sites/org/pages/account/control.mjs | 27 +-- sites/org/pages/account/email.mjs | 29 +-- sites/org/pages/account/github.mjs | 27 +-- sites/org/pages/account/img.mjs | 27 +-- sites/org/pages/account/index.mjs | 23 +- sites/org/pages/account/language.mjs | 29 +-- sites/org/pages/account/mfa.mjs | 29 +-- sites/org/pages/account/newsletter.mjs | 29 +-- sites/org/pages/account/password.mjs | 29 +-- sites/org/pages/account/reload.mjs | 29 +-- sites/org/pages/account/sets.mjs | 29 +-- sites/org/pages/account/units.mjs | 29 +-- sites/org/pages/account/username.mjs | 27 +-- sites/org/pages/blog/index.mjs | 29 +-- sites/org/pages/community/index.mjs | 29 +-- .../confirm/emailchange/[...confirmation].mjs | 41 ++-- .../confirm/signin/[...confirmation].mjs | 25 +-- .../confirm/signup/[...confirmation].mjs | 210 +----------------- sites/org/pages/designs/index.mjs | 27 +-- sites/org/pages/docs/[...mdxslug].mjs | 85 ++++--- sites/org/pages/docs/index.mjs | 92 ++++---- sites/org/pages/index.mjs | 91 ++++---- sites/org/pages/new/apikey/index.mjs | 29 +-- sites/org/pages/new/index.mjs | 39 ++-- sites/org/pages/profile.mjs | 20 +- sites/org/pages/showcase/index.mjs | 29 +-- sites/org/pages/signin/index.mjs | 76 ++++--- sites/org/pages/signin/signin.en.yaml | 5 +- sites/org/pages/signup/index.mjs | 26 ++- sites/org/pages/typography.mjs | 166 +++++++------- sites/org/pages/welcome/bio.mjs | 19 +- sites/org/pages/welcome/compare.mjs | 19 +- sites/org/pages/welcome/img.mjs | 19 +- sites/org/pages/welcome/index.mjs | 16 +- sites/org/pages/welcome/newsletter.mjs | 19 +- sites/org/pages/welcome/units.mjs | 19 +- sites/org/pages/welcome/username.mjs | 19 +- sites/shared/components/account/apikeys.mjs | 79 +++++-- sites/shared/components/account/bio.mjs | 25 ++- sites/shared/components/account/compare.mjs | 28 ++- sites/shared/components/account/consent.mjs | 37 +-- sites/shared/components/account/control.mjs | 29 ++- sites/shared/components/account/email.mjs | 18 +- sites/shared/components/account/github.mjs | 21 +- sites/shared/components/account/img.mjs | 17 +- sites/shared/components/account/imperial.mjs | 22 +- sites/shared/components/account/language.mjs | 19 +- sites/shared/components/account/links.mjs | 6 +- sites/shared/components/account/mfa.mjs | 29 ++- .../shared/components/account/newsletter.mjs | 22 +- sites/shared/components/account/password.mjs | 21 +- sites/shared/components/account/reload.mjs | 20 +- sites/shared/components/account/sets.mjs | 51 +++-- sites/shared/components/account/username.mjs | 24 +- sites/shared/components/footer/index.mjs | 3 +- sites/shared/components/mdx/index.mjs | 6 +- sites/shared/components/navigation/aside.mjs | 9 +- .../shared/components/navigation/primary.mjs | 51 +++-- sites/shared/components/ribbon.mjs | 23 +- sites/shared/components/wrappers/context.mjs | 11 + sites/shared/components/wrappers/page.mjs | 74 +++--- sites/shared/context/loading-context.mjs | 24 ++ sites/shared/context/modal-context.mjs | 6 - sites/shared/context/navigation-context.mjs | 37 +++ sites/shared/hooks/use-account.mjs | 8 +- sites/shared/hooks/use-app.mjs | 66 ------ sites/shared/hooks/use-navigation.mjs | 14 ++ yarn.lock | 150 +++++++------ 84 files changed, 1457 insertions(+), 1296 deletions(-) create mode 100644 sites/org/components/header/header.nl.yaml create mode 100644 sites/shared/components/wrappers/context.mjs create mode 100644 sites/shared/context/loading-context.mjs create mode 100644 sites/shared/context/navigation-context.mjs delete mode 100644 sites/shared/hooks/use-app.mjs create mode 100644 sites/shared/hooks/use-navigation.mjs 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 }) => {