diff --git a/sites/org/components/github/inputs.mjs b/sites/org/components/github/inputs.mjs index ff15a7bd6dc..fccd0ca9daa 100644 --- a/sites/org/components/github/inputs.mjs +++ b/sites/org/components/github/inputs.mjs @@ -60,12 +60,11 @@ export const SlugInput = ({ slug, setSlug, title, slugAvailable }) => { useEffect(() => { if (title !== slug) setSlug(slugify(title)) }, [title]) - console.log(slugAvailable) return ( ( - - - - - -) +const AccountBioPage = ({ page }) => { + const { t } = useTranslation(ns) + + return ( + + + + + + ) +} export default AccountBioPage export async function getStaticProps({ locale }) { return { props: { - ...(await serverSideTranslations(locale, namespaces)), + ...(await serverSideTranslations(locale, ns)), page: { locale, path: ['account', 'bio'], diff --git a/sites/org/pages/account/compare.mjs b/sites/org/pages/account/compare.mjs index 81e72c86778..ae708de91bc 100644 --- a/sites/org/pages/account/compare.mjs +++ b/sites/org/pages/account/compare.mjs @@ -1,13 +1,16 @@ // Dependencies import dynamic from 'next/dynamic' import { serverSideTranslations } from 'next-i18next/serverSideTranslations' +import { nsMerge } from 'shared/utils.mjs' +// Hooks +import { useTranslation } from 'next-i18next' // Components import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs' import { ns as authNs } from 'shared/components/wrappers/auth/index.mjs' import { ns as compareNs } from 'shared/components/account/compare.mjs' // Translation namespaces used on this page -const namespaces = [...new Set([...compareNs, ...authNs, ...pageNs])] +const ns = nsMerge(compareNs, authNs, pageNs) /* * Some things should never generated as SSR @@ -29,13 +32,17 @@ const DynamicCompare = dynamic( * when path and locale come from static props (as here) * or set them manually. */ -const AccountComparePage = ({ page }) => ( - - - - - -) +const AccountComparePage = ({ page }) => { + const { t } = useTranslation(ns) + + return ( + + + + + + ) +} export default AccountComparePage diff --git a/sites/org/pages/account/control.mjs b/sites/org/pages/account/control.mjs index 41a62db003a..a3e598f5bdf 100644 --- a/sites/org/pages/account/control.mjs +++ b/sites/org/pages/account/control.mjs @@ -1,13 +1,16 @@ // Dependencies import dynamic from 'next/dynamic' import { serverSideTranslations } from 'next-i18next/serverSideTranslations' +import { nsMerge } from 'shared/utils.mjs' +// Hooks +import { useTranslation } from 'next-i18next' // Components import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs' import { ns as authNs } from 'shared/components/wrappers/auth/index.mjs' import { ns as controlNs } from 'shared/components/account/control.mjs' // Translation namespaces used on this page -const namespaces = [...new Set([...controlNs, ...authNs, ...pageNs])] +const ns = nsMerge(controlNs, authNs, pageNs) /* * Some things should never generated as SSR @@ -29,20 +32,24 @@ const DynamicControl = dynamic( * when path and locale come from static props (as here) * or set them manually. */ -const AccountPage = ({ page }) => ( - - - - - -) +const AccountPage = ({ page }) => { + const { t } = useTranslation(ns) + + return ( + + + + + + ) +} export default AccountPage export async function getStaticProps({ locale }) { return { props: { - ...(await serverSideTranslations(locale, namespaces)), + ...(await serverSideTranslations(locale, ns)), page: { locale, path: ['account', 'control'], diff --git a/sites/org/pages/account/email.mjs b/sites/org/pages/account/email.mjs index 5c6a79a28ce..38443b2e029 100644 --- a/sites/org/pages/account/email.mjs +++ b/sites/org/pages/account/email.mjs @@ -1,13 +1,16 @@ // Dependencies import dynamic from 'next/dynamic' import { serverSideTranslations } from 'next-i18next/serverSideTranslations' +import { nsMerge } from 'shared/utils.mjs' +// Hooks +import { useTranslation } from 'next-i18next' // Components import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs' import { ns as authNs } from 'shared/components/wrappers/auth/index.mjs' import { ns as emailNs } from 'shared/components/account/email.mjs' // Translation namespaces used on this page -const namespaces = [...new Set([...emailNs, ...authNs, ...pageNs])] +const ns = nsMerge(emailNs, authNs, pageNs) /* * Some things should never generated as SSR @@ -29,20 +32,24 @@ const DynamicEmail = dynamic( * when path and locale come from static props (as here) * or set them manually. */ -const AccountEmailPage = ({ page }) => ( - - - - - -) +const AccountEmailPage = ({ page }) => { + const { t } = useTranslation(ns) + + return ( + + + + + + ) +} export default AccountEmailPage export async function getStaticProps({ locale }) { return { props: { - ...(await serverSideTranslations(locale, namespaces)), + ...(await serverSideTranslations(locale, ns)), page: { locale, path: ['account', 'email'], diff --git a/sites/org/pages/account/img.mjs b/sites/org/pages/account/img.mjs index 22548486266..971fad057e3 100644 --- a/sites/org/pages/account/img.mjs +++ b/sites/org/pages/account/img.mjs @@ -1,13 +1,16 @@ // Dependencies import dynamic from 'next/dynamic' import { serverSideTranslations } from 'next-i18next/serverSideTranslations' +import { nsMerge } from 'shared/utils.mjs' +// Hooks +import { useTranslation } from 'next-i18next' // Components import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs' import { ns as authNs } from 'shared/components/wrappers/auth/index.mjs' import { ns as imgNs } from 'shared/components/account/img.mjs' // Translation namespaces used on this page -const namespaces = [...new Set([...imgNs, ...authNs, ...pageNs])] +const ns = nsMerge(imgNs, authNs, pageNs) /* * Some things should never generated as SSR @@ -29,20 +32,24 @@ const DynamicImg = dynamic( * when path and locale come from static props (as here) * or set them manually. */ -const AccountPage = ({ page }) => ( - - - - - -) +const AccountPage = ({ page }) => { + const { t } = useTranslation(ns) + + return ( + + + + + + ) +} export default AccountPage export async function getStaticProps({ locale }) { return { props: { - ...(await serverSideTranslations(locale, namespaces)), + ...(await serverSideTranslations(locale, ns)), page: { locale, path: ['account', 'img'], diff --git a/sites/org/pages/account/index.mjs b/sites/org/pages/account/index.mjs index 5634f609957..d3bb8cac2c0 100644 --- a/sites/org/pages/account/index.mjs +++ b/sites/org/pages/account/index.mjs @@ -1,6 +1,7 @@ // Dependencies import dynamic from 'next/dynamic' import { serverSideTranslations } from 'next-i18next/serverSideTranslations' +import { nsMerge } from 'shared/utils.mjs' // Hooks import { useTranslation } from 'next-i18next' // Components @@ -8,7 +9,7 @@ import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs' import { ns as authNs } from 'shared/components/wrappers/auth/index.mjs' // Translation namespaces used on this page -const ns = [...new Set(['account', ...pageNs, ...authNs])] +const ns = nsMerge('account', 'status', pageNs, authNs) /* * Some things should never generated as SSR diff --git a/sites/org/pages/account/language.mjs b/sites/org/pages/account/language.mjs index 7f674acfc20..d9faab2b656 100644 --- a/sites/org/pages/account/language.mjs +++ b/sites/org/pages/account/language.mjs @@ -1,13 +1,16 @@ // Dependencies import dynamic from 'next/dynamic' import { serverSideTranslations } from 'next-i18next/serverSideTranslations' +import { nsMerge } from 'shared/utils.mjs' +// Hooks +import { useTranslation } from 'next-i18next' // Components import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs' import { ns as authNs } from 'shared/components/wrappers/auth/index.mjs' import { ns as languageNs } from 'shared/components/account/language.mjs' // Translation namespaces used on this page -const namespaces = [...new Set([...languageNs, ...authNs, ...pageNs])] +const ns = nsMerge(languageNs, authNs, pageNs) /* * Some things should never generated as SSR @@ -29,20 +32,24 @@ const DynamicLanguage = dynamic( * when path and locale come from static props (as here) * or set them manually. */ -const AccountLanguagePage = ({ page }) => ( - - - - - -) +const AccountLanguagePage = ({ page }) => { + const { t } = useTranslation(ns) + + return ( + + + + + + ) +} export default AccountLanguagePage export async function getStaticProps({ locale }) { return { props: { - ...(await serverSideTranslations(locale, namespaces)), + ...(await serverSideTranslations(locale, ns)), page: { locale, path: ['account', 'language'], diff --git a/sites/org/pages/account/mfa.mjs b/sites/org/pages/account/mfa.mjs index 43818c16b2b..bcebe204970 100644 --- a/sites/org/pages/account/mfa.mjs +++ b/sites/org/pages/account/mfa.mjs @@ -1,13 +1,16 @@ // Dependencies import dynamic from 'next/dynamic' import { serverSideTranslations } from 'next-i18next/serverSideTranslations' +import { nsMerge } from 'shared/utils.mjs' +// Hooks +import { useTranslation } from 'next-i18next' // Components import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs' import { ns as authNs } from 'shared/components/wrappers/auth/index.mjs' import { ns as mfaNs } from 'shared/components/account/mfa.mjs' // Translation namespaces used on this page -const namespaces = [...new Set([...mfaNs, ...authNs, ...pageNs])] +const ns = nsMerge(mfaNs, authNs, pageNs) /* * Some things should never generated as SSR @@ -29,20 +32,24 @@ const DynamicMfa = dynamic( * when path and locale come from static props (as here) * or set them manually. */ -const AccountMfaPage = ({ page }) => ( - - - - - -) +const AccountMfaPage = ({ page }) => { + const { t } = useTranslation(ns) + + return ( + + + + + + ) +} export default AccountMfaPage export async function getStaticProps({ locale }) { return { props: { - ...(await serverSideTranslations(locale, namespaces)), + ...(await serverSideTranslations(locale, ns)), page: { locale, path: ['account', 'mfa'], diff --git a/sites/org/pages/account/newsletter.mjs b/sites/org/pages/account/newsletter.mjs index bacd3ddf25a..8aa73438d92 100644 --- a/sites/org/pages/account/newsletter.mjs +++ b/sites/org/pages/account/newsletter.mjs @@ -1,13 +1,16 @@ // Dependencies import dynamic from 'next/dynamic' import { serverSideTranslations } from 'next-i18next/serverSideTranslations' +import { nsMerge } from 'shared/utils.mjs' +// Hooks +import { useTranslation } from 'next-i18next' // Components import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs' import { ns as authNs } from 'shared/components/wrappers/auth/index.mjs' import { ns as newsletterNs } from 'shared/components/account/newsletter.mjs' // Translation namespaces used on this page -const namespaces = [...new Set([...newsletterNs, ...authNs, ...pageNs])] +const ns = nsMerge(newsletterNs, authNs, pageNs) /* * Some things should never generated as SSR @@ -29,20 +32,24 @@ const DynamicNewsletter = dynamic( * when path and locale come from static props (as here) * or set them manually. */ -const AccountNewsletterPage = ({ page }) => ( - - - - - -) +const AccountNewsletterPage = ({ page }) => { + const { t } = useTranslation(ns) + + return ( + + + + + + ) +} export default AccountNewsletterPage export async function getStaticProps({ locale }) { return { props: { - ...(await serverSideTranslations(locale, namespaces)), + ...(await serverSideTranslations(locale, ns)), page: { locale, path: ['account', 'newsletter'], diff --git a/sites/org/pages/account/password.mjs b/sites/org/pages/account/password.mjs index 3a3160199a7..d9cb5e98f36 100644 --- a/sites/org/pages/account/password.mjs +++ b/sites/org/pages/account/password.mjs @@ -1,13 +1,16 @@ // Dependencies import dynamic from 'next/dynamic' import { serverSideTranslations } from 'next-i18next/serverSideTranslations' +import { nsMerge } from 'shared/utils.mjs' +// Hooks +import { useTranslation } from 'next-i18next' // Components import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs' import { ns as authNs } from 'shared/components/wrappers/auth/index.mjs' import { ns as passwordNs } from 'shared/components/account/password.mjs' // Translation namespaces used on this page -const namespaces = [...new Set([...passwordNs, ...authNs, ...pageNs])] +const ns = nsMerge(passwordNs, authNs, pageNs) /* * Some things should never generated as SSR @@ -29,20 +32,24 @@ const DynamicPassword = dynamic( * when path and locale come from static props (as here) * or set them manually. */ -const AccountPasswordPage = ({ page }) => ( - - - - - -) +const AccountPasswordPage = ({ page }) => { + const { t } = useTranslation(ns) + + return ( + + + + + + ) +} export default AccountPasswordPage export async function getStaticProps({ locale }) { return { props: { - ...(await serverSideTranslations(locale, namespaces)), + ...(await serverSideTranslations(locale, ns)), page: { locale, path: ['account', 'password'], diff --git a/sites/org/pages/account/reload.mjs b/sites/org/pages/account/reload.mjs index e7605865210..f96685d5336 100644 --- a/sites/org/pages/account/reload.mjs +++ b/sites/org/pages/account/reload.mjs @@ -1,13 +1,16 @@ // Dependencies import dynamic from 'next/dynamic' import { serverSideTranslations } from 'next-i18next/serverSideTranslations' +import { nsMerge } from 'shared/utils.mjs' +// Hooks +import { useTranslation } from 'next-i18next' // Components import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs' import { ns as authNs } from 'shared/components/wrappers/auth/index.mjs' import { ns as reloadNs } from 'shared/components/account/reload.mjs' // Translation namespaces used on this page -const namespaces = [...new Set([...reloadNs, ...authNs, ...pageNs])] +const ns = nsMerge(reloadNs, authNs, pageNs) /* * Some things should never generated as SSR @@ -29,20 +32,24 @@ const DynamicReload = dynamic( * when path and locale come from static props (as here) * or set them manually. */ -const AccountReloadPage = ({ page }) => ( - - - - - -) +const AccountReloadPage = ({ page }) => { + const { t } = useTranslation(ns) + + return ( + + + + + + ) +} export default AccountReloadPage export async function getStaticProps({ locale }) { return { props: { - ...(await serverSideTranslations(locale, namespaces)), + ...(await serverSideTranslations(locale, ns)), page: { locale, path: ['account', 'reload'], diff --git a/sites/org/pages/account/units.mjs b/sites/org/pages/account/units.mjs index 18495e4533f..6cc5d2f7b53 100644 --- a/sites/org/pages/account/units.mjs +++ b/sites/org/pages/account/units.mjs @@ -1,13 +1,16 @@ // Dependencies import dynamic from 'next/dynamic' import { serverSideTranslations } from 'next-i18next/serverSideTranslations' +import { nsMerge } from 'shared/utils.mjs' +// Hooks +import { useTranslation } from 'next-i18next' // Components import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs' import { ns as authNs } from 'shared/components/wrappers/auth/index.mjs' import { ns as unitsNs } from 'shared/components/account/imperial.mjs' // Translation namespaces used on this page -const namespaces = [...new Set([...unitsNs, ...authNs, ...pageNs])] +const namespaces = nsMerge(unitsNs, authNs, pageNs) /* * Some things should never generated as SSR @@ -29,13 +32,17 @@ const DynamicImperial = dynamic( * when path and locale come from static props (as here) * or set them manually. */ -const AccountUnitsPage = ({ page }) => ( - - - - - -) +const AccountUnitsPage = ({ page }) => { + const { t } = useTranslation(namespaces) + + return ( + + + + + + ) +} export default AccountUnitsPage diff --git a/sites/org/pages/account/username.mjs b/sites/org/pages/account/username.mjs index 6d61e7e84c1..aeadc56e3c4 100644 --- a/sites/org/pages/account/username.mjs +++ b/sites/org/pages/account/username.mjs @@ -1,13 +1,16 @@ // Dependencies import dynamic from 'next/dynamic' import { serverSideTranslations } from 'next-i18next/serverSideTranslations' +import { nsMerge } from 'shared/utils.mjs' +// Hooks +import { useTranslation } from 'next-i18next' // Components import { PageWrapper, ns as pageNs } from 'shared/components/wrappers/page.mjs' import { ns as authNs } from 'shared/components/wrappers/auth/index.mjs' import { ns as usernameNs } from 'shared/components/account/username.mjs' // Translation namespaces used on this page -const namespaces = [...new Set([...usernameNs, ...authNs, ...pageNs])] +const ns = nsMerge(usernameNs, authNs, pageNs) /* * Some things should never generated as SSR @@ -29,20 +32,24 @@ const DynamicUsername = dynamic( * when path and locale come from static props (as here) * or set them manually. */ -const AccountPage = ({ page }) => ( - - - - - -) +const AccountPage = ({ page }) => { + const { t } = useTranslation(ns) + + return ( + + + + + + ) +} export default AccountPage export async function getStaticProps({ locale }) { return { props: { - ...(await serverSideTranslations(locale, namespaces)), + ...(await serverSideTranslations(locale, ns)), page: { locale, path: ['account', 'username'], diff --git a/sites/shared/components/account/bio.mjs b/sites/shared/components/account/bio.mjs index 60cd5e7f639..2e9f842fd60 100644 --- a/sites/shared/components/account/bio.mjs +++ b/sites/shared/components/account/bio.mjs @@ -4,9 +4,7 @@ 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' +import { useLoadingStatus } from 'shared/hooks/use-loading-status.mjs' // Components import Markdown from 'react-markdown' import { Icons, welcomeSteps, BackToAccountButton } from './shared.mjs' @@ -14,7 +12,7 @@ import { Popout } from 'shared/components/popout/index.mjs' import { SaveSettingsButton } from 'shared/components/buttons/save-settings-button.mjs' import { ContinueButton } from 'shared/components/buttons/continue-button.mjs' -export const ns = ['account', 'toast'] +export const ns = ['account', 'status'] export const Tab = ({ id, activeTab, setActiveTab, t }) => ( )} - + ) } diff --git a/sites/shared/components/account/en.yaml b/sites/shared/components/account/en.yaml index c158265df9b..64e64395e9a 100644 --- a/sites/shared/components/account/en.yaml +++ b/sites/shared/components/account/en.yaml @@ -204,3 +204,6 @@ newBasic: The basics newAdvanced: Go further generateANewThing: "Generate a new { thing }" + +website: Website +linkedIdentities: Linked Identities diff --git a/sites/shared/components/account/img.mjs b/sites/shared/components/account/img.mjs index a91131b0c86..d6620f86de8 100644 --- a/sites/shared/components/account/img.mjs +++ b/sites/shared/components/account/img.mjs @@ -2,27 +2,27 @@ 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' +import { cloudflareImageUrl } from 'shared/utils.mjs' // 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' +import { useLoadingStatus } from 'shared/hooks/use-loading-status.mjs' // Components import { Icons, welcomeSteps, BackToAccountButton } from './shared.mjs' import { ContinueButton } from 'shared/components/buttons/continue-button.mjs' import { SaveSettingsButton } from 'shared/components/buttons/save-settings-button.mjs' +import { DownloadIcon } from 'shared/components/icons.mjs' -export const ns = ['account', 'toast'] +export const ns = ['account', 'status'] 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() + const { setLoadingStatus, LoadingStatus } = useLoadingStatus() const { t } = useTranslation(ns) const [img, setImg] = useState(false) + const [url, setUrl] = useState('') const onDrop = useCallback((acceptedFiles) => { const reader = new FileReader() @@ -32,26 +32,35 @@ export const ImgSettings = ({ title = false, welcome = false }) => { acceptedFiles.forEach((file) => reader.readAsDataURL(file)) }, []) + const imageFromUrl = async () => { + const result = await backend.uploadImage({ type, subId, slug, url }) + if (result.success) setImg(result.data.imgId) + } + const { getRootProps, getInputProps } = useDropzone({ onDrop }) const save = async () => { - startLoading() - const result = await backend.updateAccount({ img }) + setLoadingStatus([true, 'processingUpdate']) + const result = await backend.updateAccount({ img: url ? url : img }) if (result.success) { setAccount(result.data.account) - toast.for.settingsSaved() - } else toast.for.backendError() - stopLoading() + setLoadingStatus([true, 'settingsSaved', true, true]) + } else setLoadingStatus([true, 'backendError', true, false]) } const nextHref = '/docs/guide' return (
+ {title ?

{t('imgTitle')}

: null}
{!welcome || img !== false ? ( - img + img ) : null}
{ {t('imgSelectImage')}
+

{t('or')}

+
+ setUrl(evt.target.value)} + /> +
{welcome ? ( @@ -96,7 +115,7 @@ export const ImgSettings = ({ title = false, welcome = false }) => { ) : ( <> - + )}
diff --git a/sites/shared/components/account/imperial.mjs b/sites/shared/components/account/imperial.mjs index c503b6490d9..bcf08c4877d 100644 --- a/sites/shared/components/account/imperial.mjs +++ b/sites/shared/components/account/imperial.mjs @@ -4,23 +4,18 @@ 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' +import { useLoadingStatus } from 'shared/hooks/use-loading-status.mjs' // Components import { Choice, Icons, welcomeSteps, BackToAccountButton } from './shared.mjs' import { ContinueButton } from 'shared/components/buttons/continue-button.mjs' -export const ns = ['account', 'toast'] +export const ns = ['account', 'status'] export const ImperialSettings = ({ title = false, welcome = false }) => { - // Context - const { loading, startLoading, stopLoading } = useContext(LoadingContext) - // Hooks const { account, setAccount, token } = useAccount() + const { setLoadingStatus, LoadingStatus } = useLoadingStatus() const backend = useBackend(token) - const toast = useToast() const { t } = useTranslation(ns) // State @@ -29,14 +24,13 @@ export const ImperialSettings = ({ title = false, welcome = false }) => { // Helper method to update account const update = async (val) => { if (val !== selection) { - startLoading() + setLoadingStatus([true, 'processingUpdate']) 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() - stopLoading() + setLoadingStatus([true, 'settingsSaved', true, true]) + } else setLoadingStatus([true, 'backendError', true, true]) } } @@ -48,6 +42,7 @@ export const ImperialSettings = ({ title = false, welcome = false }) => { return (
+ {title ?

{t('unitsTitle')}

:

} {['metric', 'imperial'].map((val) => ( { ) : null} ) : ( - + )}
) diff --git a/sites/shared/components/account/language.mjs b/sites/shared/components/account/language.mjs index 430315183da..be74f1b7665 100644 --- a/sites/shared/components/account/language.mjs +++ b/sites/shared/components/account/language.mjs @@ -4,24 +4,19 @@ 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' +import { useLoadingStatus, ns as statusNs } from 'shared/hooks/use-loading-status.mjs' // Components import { BackToAccountButton, Choice } from './shared.mjs' // Config import { siteConfig as conf } from 'site/site.config.mjs' -export const ns = ['account', 'locales', 'toast'] +export const ns = ['account', 'locales', statusNs] export const LanguageSettings = ({ title = false }) => { - // Context - const { loading, startLoading, stopLoading } = useContext(LoadingContext) - // Hooks const { account, setAccount, token } = useAccount() + const { setLoadingStatus, LoadingStatus } = useLoadingStatus() const backend = useBackend(token) - const toast = useToast() const { t } = useTranslation(ns) // State @@ -30,26 +25,26 @@ export const LanguageSettings = ({ title = false }) => { // Helper method to update the account const update = async (lang) => { if (lang !== language) { - startLoading() + setLoadingStatus([true, 'processingUpdate']) setLanguage(lang) const result = await backend.updateAccount({ language: lang }) if (result.success) { setAccount(result.data.account) - toast.for.settingsSaved() - } else toast.for.backendError() - stopLoading() + setLoadingStatus([true, 'settingsSaved', true, true]) + } else setLoadingStatus([true, 'backendError', true, true]) } } return (
+ {title ?

{t('languageTitle')}

: null} {conf.languages.map((val) => ( {t(`locales:${val}`)} ))} - +
) } diff --git a/sites/shared/components/account/links.mjs b/sites/shared/components/account/links.mjs index 1347e1adeb1..eaf9817bc11 100644 --- a/sites/shared/components/account/links.mjs +++ b/sites/shared/components/account/links.mjs @@ -1,35 +1,48 @@ +import { useState, useEffect } from 'react' import { useAccount } from 'shared/hooks/use-account.mjs' +import { useBackend } from 'shared/hooks/use-backend.mjs' import { useTranslation } from 'next-i18next' import Link from 'next/link' import { PageLink } from 'shared/components/page-link.mjs' import { freeSewingConfig as conf } from 'shared/config/freesewing.config.mjs' -import { Fingerprint } from 'shared/components/fingerprint.mjs' +import { + DesignIcon, + MeasieIcon, + SignoutIcon, + UserIcon, + UnitsIcon, + I18nIcon, + ShowcaseIcon, + ChatIcon, + EmailIcon, + KeyIcon, + BookmarkIcon, + CompareIcon, + PrivacyIcon, + ControlIcon, + LockIcon, + NewsletterIcon, + ShieldIcon, + FingerprintIcon, + GitHubIcon, + InstagramIcon, + MastodonIcon, + TwitterIcon, + TwitchIcon, + TikTokIcon, + LinkIcon, + TrashIcon, + RedditIcon, + ExportIcon, + CloseIcon, + ReloadIcon, + OkIcon, + NoIcon, +} from 'shared/components/icons.mjs' +import { cloudflareImageUrl, capitalize } from 'shared/utils.mjs' +import { ControlScore } from 'shared/components/control/score.mjs' -export const ns = ['account'] - -const Li = ({ children }) =>
  • {children}
  • -const Spacer = () =>
  • |
  • - -const LinkList = ({ items, t, control, first = false }) => { - const output = [] - if (first) - output.push( -
  • - {first}: -
  • - ) - for (const [item, cscore] of Object.entries(items)) { - if (cscore <= control) - output.push( -
  • - -
  • , - - ) - } - - return output.length > 1 ?
      {output.slice(0, -1)}
    : null -} +export const ns = ['account', 'i18n'] const actions = { reload: 4, @@ -39,55 +52,220 @@ const actions = { remove: 2, } +const itemIcons = { + bookmarks: , + sets: , + patterns: , + apikeys: , + username: , + email: , + bio: , + img: , + language: , + units: , + compare: , + consent: , + control: , + mfa: , + newsletter: , + password: , + github: , + instagram: , + mastodon: , + twitter: , + twitch: , + tiktok: , + website: , + reddit: , +} + +const itemClasses = 'flex flex-row items-center justify-between bg-opacity-10 p-2 px-4 rounded mb-1' + +const AccountLink = ({ href, title, children }) => ( + + {children} + +) + +const YesNo = ({ check }) => + check ? ( + + ) : ( + + ) + export const AccountLinks = () => { const { account, signOut } = useAccount() const { t } = useTranslation(ns) + const backend = useBackend() + + const [bookmarks, setBookmarks] = useState([]) + const [sets, setSets] = useState([]) + const [patterns, setPatterns] = useState([]) + const [apikeys, setApikeys] = useState([]) + + useEffect(() => { + const getUserData = async () => { + const result = await backend.getUserData(account.id) + if (result.success) { + setApikeys(result.data.data.apikeys) + setBookmarks(result.data.data.bookmarks) + setPatterns(result.data.data.patterns) + setSets(result.data.data.sets) + } + } + getUserData() + }, [account.id]) const lprops = { t, control: account.control } + const btnClasses = 'btn capitalize flex flex-row justify-between' + const linkClasses = 'flex flex-row gap-2 text-lg py-2 items-center font-medium capitalize' + + const itemPreviews = { + apikeys: apikeys?.length || 0, + bookmarks: bookmarks?.length || 0, + sets: sets?.length || 0, + patterns: patterns?.length || 0, + username: account.username, + email: account.email, + bio: {account.bio.slice(0, 15)}…, + img: ( + + ), + language: t(`i18n:${account.language}`), + units: t(account.imperial ? 'imperialUnits' : 'metricUnits'), + newsletter: , + compare: , + consent: , + control: , + github: account.data.githubUsername || account.data.githubEmail || , + password: + account.passwordType === 'v3' ? ( + + ) : ( + + ), + mfa: , + } + + for (const social of Object.keys(conf.account.fields.identities).filter((i) => i !== 'github')) + itemPreviews[social] = account.data[social] || ( + + ) return ( -
    - - {t('newPattern')} - -
    - - {t('newSet')} +
    +
    +
    +

    {t('data')}

    + {Object.keys(conf.account.fields.data).map((item) => ( + +
    + {itemIcons[item]} + {t(`your${capitalize(item)}`)} +
    +
    {itemPreviews[item]}
    +
    + ))} +
    + +
    +

    {t('info')}

    + {Object.keys(conf.account.fields.info).map((item) => ( + +
    + {itemIcons[item]} + {t(item)} +
    +
    {itemPreviews[item]}
    +
    + ))} +
    +
    + + {t('userId')} +
    +
    {account.id}
    +
    +
    + +
    +

    {t('settings')}

    + {Object.keys(conf.account.fields.settings).map((item) => ( + +
    + {itemIcons[item]} + {t(item)} +
    +
    {itemPreviews[item]}
    +
    + ))} +
    + +
    +

    {t('linkedIdentities')}

    + {Object.keys(conf.account.fields.identities).map((item) => ( + +
    + {itemIcons[item]} + {t(item)} +
    +
    {itemPreviews[item]}
    +
    + ))} +
    + +
    +

    {t('security')}

    + {Object.keys(conf.account.fields.security).map((item) => ( + +
    + {itemIcons[item]} + {t(item)} +
    +
    {itemPreviews[item]}
    +
    + ))} +
    + +
    +

    {t('actions')}

    + + + {t('reload')} + + + + {t('export')} + + + + {t('restrict')} + + + + {t('remove')} + +
    +
    + +
    + + + {t('yourProfile')} -
    - -
      -
    • - Quick links: -
    • -
    • - {' '} -
    • - -
    • - {' '} -
    • - -
    • - {' '} -
    • -
    - - {Object.keys(conf.account.fields).map((section) => ( - - ))} - - - -
    ) } diff --git a/sites/shared/components/account/mfa.mjs b/sites/shared/components/account/mfa.mjs index 9ec0527c92a..ce76e72e930 100644 --- a/sites/shared/components/account/mfa.mjs +++ b/sites/shared/components/account/mfa.mjs @@ -4,9 +4,7 @@ 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' +import { useLoadingStatus } from 'shared/hooks/use-loading-status.mjs' // Components import { BackToAccountButton } from './shared.mjs' import { Popout } from 'shared/components/popout/index.mjs' @@ -25,14 +23,11 @@ const CodeInput = ({ code, setCode, t }) => ( ) 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() + const { setLoadingStatus, LoadingStatus } = useLoadingStatus() // State const [enable, setEnable] = useState(false) @@ -42,15 +37,17 @@ export const MfaSettings = ({ title = false, welcome = false }) => { // Helper method to enable MFA const enableMfa = async () => { - startLoading() + setLoadingStatus([true, 'processingUpdate']) const result = await backend.enableMfa() - if (result.success) setEnable(result.data.mfa) - stopLoading() + if (result.success) { + setEnable(result.data.mfa) + setLoadingStatus([true, 'settingsSaved', true, true]) + } else setLoadingStatus([true, 'backendError', true, false]) } // Helper method to disable MFA const disableMfa = async () => { - startLoading() + setLoadingStatus([true, 'processingUpdate']) const result = await backend.disableMfa({ mfa: false, password, @@ -59,19 +56,18 @@ export const MfaSettings = ({ title = false, welcome = false }) => { if (result) { if (result.success) { setAccount(result.data.account) - toast.warning({t('mfaDisabled')}) - } else toast.for.backendError() + setLoadingStatus([true, 'settingsSaved', true, true]) + } else setLoadingStatus([true, 'backendError', true, false]) setDisable(false) setEnable(false) setCode('') setPassword('') } - stopLoading() } // Helper method to confirm MFA const confirmMfa = async () => { - startLoading() + setLoadingStatus([true, 'processingUpdate']) const result = await backend.confirmMfa({ mfa: true, secret: enable.secret, @@ -79,11 +75,10 @@ export const MfaSettings = ({ title = false, welcome = false }) => { }) if (result.success) { setAccount(result.data.account) - toast.success({t('mfaEnabled')}) - } else toast.for.backendError() + setLoadingStatus([true, 'settingsSaved', true, true]) + } else setLoadingStatus([true, 'backendError', true, false]) setEnable(false) setCode('') - stopLoading() } // Figure out what title to use @@ -92,6 +87,7 @@ export const MfaSettings = ({ title = false, welcome = false }) => { return (
    + {title ?

    {titleText}

    : null} {enable ? ( <> @@ -156,7 +152,7 @@ export const MfaSettings = ({ title = false, welcome = false }) => {
    )}
    - {!welcome && } + {!welcome && }
    ) } diff --git a/sites/shared/components/account/newsletter.mjs b/sites/shared/components/account/newsletter.mjs index 56b433b463e..03883b76a31 100644 --- a/sites/shared/components/account/newsletter.mjs +++ b/sites/shared/components/account/newsletter.mjs @@ -4,39 +4,32 @@ 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' +import { useLoadingStatus } from 'shared/hooks/use-loading-status.mjs' // Components import { BackToAccountButton, Choice, Icons, welcomeSteps } from './shared.mjs' import { ContinueButton } from 'shared/components/buttons/continue-button.mjs' -export const ns = ['account', 'toast'] +export const ns = ['account', 'status'] 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) - + const { LoadingStatus, setLoadingStatus } = useLoadingStatus() // State const [selection, setSelection] = useState(account?.newsletter ? 'yes' : 'no') // Helper method to update account const update = async (val) => { if (val !== selection) { - startLoading() + setLoadingStatus([true, 'processingUpdate']) 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() - stopLoading() + setLoadingStatus([true, 'settingsSaved', true, true]) + } else setLoadingStatus([true, 'backendError', true, true]) } } @@ -48,6 +41,7 @@ export const NewsletterSettings = ({ title = false, welcome = false }) => { return (
    + {title ?

    {t('newsletterTitle')}

    : null} {['yes', 'no'].map((val) => ( @@ -83,7 +77,7 @@ export const NewsletterSettings = ({ title = false, welcome = false }) => { ) : null} ) : ( - + )}
    ) diff --git a/sites/shared/components/account/password.mjs b/sites/shared/components/account/password.mjs index 749affa4da0..3911202a01c 100644 --- a/sites/shared/components/account/password.mjs +++ b/sites/shared/components/account/password.mjs @@ -4,9 +4,7 @@ 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' +import { useLoadingStatus } from 'shared/hooks/use-loading-status.mjs' // Components import Link from 'next/link' import { BackToAccountButton } from './shared.mjs' @@ -14,17 +12,14 @@ import { SaveSettingsButton } from 'shared/components/buttons/save-settings-butt import { Popout } from 'shared/components/popout/index.mjs' import { RightIcon } from 'shared/components/icons.mjs' -export const ns = ['account', 'toast'] +export const ns = ['account', 'status'] 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 { account, setAccount } = useAccount() + const backend = useBackend() const { t } = useTranslation(ns) - const toast = useToast() + const { setLoadingStatus, LoadingStatus } = useLoadingStatus() // State const [password, setPassword] = useState('') @@ -32,17 +27,17 @@ export const PasswordSettings = ({ title = false, welcome = false }) => { // Helper method to save password to account const save = async () => { - startLoading() + setLoadingStatus([true, 'processingUpdate']) const result = await backend.updateAccount({ password }) if (result.success) { setAccount(result.data.account) - toast.for.settingsSaved() - } else toast.for.backendError() - stopLoading() + setLoadingStatus([true, 'settingsSaved', true, true]) + } else setLoadingStatus([true, 'backendError', true, false]) } return (
    + {title ?

    {t('passwordTitle')}

    : null}
    {
    - {!welcome && } + {!welcome && } {!account.mfaEnabled && (
    {t('mfaTipTitle')}
    diff --git a/sites/shared/components/account/reload.mjs b/sites/shared/components/account/reload.mjs index 10f8a8b62f4..3d590274d23 100644 --- a/sites/shared/components/account/reload.mjs +++ b/sites/shared/components/account/reload.mjs @@ -1,46 +1,40 @@ // 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' +import { useLoadingStatus } from 'shared/hooks/use-loading-status.mjs' // Components import { BackToAccountButton } from './shared.mjs' -export const ns = ['account', 'toast'] +export const ns = ['account', 'status'] 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() + const { setLoadingStatus, LoadingStatus } = useLoadingStatus() // Helper method to reload account const reload = async () => { - startLoading() + setLoadingStatus([true, 'processingUpdate']) const result = await backend.reloadAccount() if (result.success) { setAccount(result.data.account) - toast.success({t('nailedIt')}) - } else toast.for.backendError() - stopLoading() + setLoadingStatus([true, 'nailedIt', true, true]) + } else setLoadingStatus([true, 'backendError', true, false]) } return (
    + {title ?

    {t('reloadMsg1')}

    : null}

    {t('reloadMsg2')}

    - +
    ) } diff --git a/sites/shared/components/account/username.mjs b/sites/shared/components/account/username.mjs index cf009f690b2..eb2c4caa887 100644 --- a/sites/shared/components/account/username.mjs +++ b/sites/shared/components/account/username.mjs @@ -4,8 +4,7 @@ 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 { useLoadingStatus } from 'shared/hooks/use-loading-status.mjs' import { LoadingContext } from 'shared/context/loading-context.mjs' // Components import { Spinner } from 'shared/components/spinner.mjs' @@ -16,13 +15,10 @@ import { ContinueButton } from 'shared/components/buttons/continue-button.mjs' export const ns = ['account', 'toast'] 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() + const { setLoadingStatus, LoadingStatus } = useLoadingStatus() const { t } = useTranslation(ns) const [username, setUsername] = useState(account.username) const [available, setAvailable] = useState(true) @@ -38,13 +34,12 @@ export const UsernameSettings = ({ title = false, welcome = false }) => { } const save = async () => { - startLoading() + setLoadingStatus([true, 'processingUpdate']) const result = await backend.updateAccount({ username }) if (result.success) { setAccount(result.data.account) - toast.for.settingsSaved() - } else toast.for.backendError() - stopLoading() + setLoadingStatus([true, 'settingsSaved', true, true]) + } else setLoadingStatus([true, 'backendError', true, true]) } const nextHref = @@ -53,18 +48,12 @@ export const UsernameSettings = ({ title = false, welcome = false }) => { : '/docs/guide' let btnClasses = 'btn mt-4 capitalize ' - if (welcome) { - btnClasses += 'w-64 ' - if (loading) btnClasses += 'btn-accent ' - else btnClasses += 'btn-secondary ' - } else { - btnClasses += 'w-full ' - if (loading) btnClasses += 'btn-accent ' - else btnClasses += 'btn-primary ' - } + if (welcome) btnClasses += 'w-64 btn-secondary' + else btnClasses += 'w-full btn-primary' return (
    + {title ?

    {t('usernameTitle')}

    : null}
    {
    @@ -119,7 +99,7 @@ export const UsernameSettings = ({ title = false, welcome = false }) => { ) : null} ) : ( - + )}
    ) diff --git a/sites/shared/components/control/score.mjs b/sites/shared/components/control/score.mjs new file mode 100644 index 00000000000..d0f371cefb9 --- /dev/null +++ b/sites/shared/components/control/score.mjs @@ -0,0 +1,12 @@ +import { BulletIcon } from 'shared/components/icons.mjs' + +const scores = [1, 2, 3, 4, 5] + +export const ControlScore = ({ control, color = 'base-content' }) => + control ? ( +
    + {scores.map((score) => ( + = score ? true : false} className="w-6 h-6 -ml-1" /> + ))} +
    + ) : null diff --git a/sites/shared/components/icons.mjs b/sites/shared/components/icons.mjs index c612f49cb78..11fc5e07b97 100644 --- a/sites/shared/components/icons.mjs +++ b/sites/shared/components/icons.mjs @@ -36,6 +36,12 @@ export const BioIcon = (props) => ( ) +export const BookmarkIcon = (props) => ( + + + +) + export const BoxIcon = (props) => ( @@ -327,6 +333,18 @@ export const LinkIcon = (props) => ( ) +export const LockIcon = (props) => ( + + + +) + +export const MastodonIcon = (props) => ( + + + +) + export const MarginIcon = (props) => ( @@ -441,12 +459,24 @@ export const PrintIcon = (props) => ( ) +export const PrivacyIcon = (props) => ( + + + +) + export const RedditIcon = (props) => ( ) +export const ReloadIcon = (props) => ( + + + +) + export const RightIcon = (props) => ( @@ -493,6 +523,12 @@ export const SettingsIcon = (props) => ( ) +export const ShieldIcon = (props) => ( + + + +) + export const ShowcaseIcon = (props) => ( @@ -500,6 +536,18 @@ export const ShowcaseIcon = (props) => ( ) +export const SigninIcon = (props) => ( + + + +) + +export const SignoutIcon = (props) => ( + + + +) + export const StarIcon = (props) => ( @@ -512,6 +560,12 @@ export const ThemeIcon = (props) => ( ) +export const TikTokIcon = (props) => ( + + + +) + export const TipIcon = (props) => ( @@ -538,6 +592,12 @@ export const TrophyIcon = (props) => ( ) +export const TwitchIcon = (props) => ( + + + +) + export const TwitterIcon = (props) => ( diff --git a/sites/shared/components/page-link.mjs b/sites/shared/components/page-link.mjs index e647b34aae6..844b6b59ebd 100644 --- a/sites/shared/components/page-link.mjs +++ b/sites/shared/components/page-link.mjs @@ -1,11 +1,11 @@ import Link from 'next/link' -export const PageLink = ({ href, txt, className = '' }) => ( +export const PageLink = ({ href, txt, className = '', children = null }) => ( - {txt} + {children ? children : txt} ) diff --git a/sites/shared/config/freesewing.config.mjs b/sites/shared/config/freesewing.config.mjs index cff600b82d0..74e42d77f0e 100644 --- a/sites/shared/config/freesewing.config.mjs +++ b/sites/shared/config/freesewing.config.mjs @@ -11,29 +11,40 @@ export const freeSewingConfig = { account: { fields: { data: { + bookmarks: 1, sets: 1, patterns: 1, + apikeys: 4, }, info: { - bio: 1, - email: 3, - github: 3, - img: 2, - units: 2, - language: 2, username: 2, + bio: 1, + img: 2, + email: 3, }, settings: { - compare: 3, - consent: 2, - control: 1, - mfa: 4, + language: 2, + units: 2, newsletter: 2, - password: 2, + compare: 3, + control: 1, + consent: 2, }, - developer: { + security: { + password: 2, + mfa: 4, apikeys: 4, }, + identities: { + github: 3, + instagram: 3, + mastodon: 3, + reddit: 3, + twitter: 3, + twitch: 3, + tiktok: 3, + website: 3, + }, }, sets: { name: 1, diff --git a/sites/shared/config/tailwind-force.html b/sites/shared/config/tailwind-force.html index f59f1dc479f..f7f69807efe 100644 --- a/sites/shared/config/tailwind-force.html +++ b/sites/shared/config/tailwind-force.html @@ -6,6 +6,9 @@ file in it's scans. --> + +
    +

    diff --git a/sites/shared/hooks/use-backend.mjs b/sites/shared/hooks/use-backend.mjs index 560a3ba31fb..6cb78cb8633 100644 --- a/sites/shared/hooks/use-backend.mjs +++ b/sites/shared/hooks/use-backend.mjs @@ -164,6 +164,13 @@ Backend.prototype.reloadAccount = async function () { return responseHandler(await await api.get(`/whoami/jwt`, this.auth)) } +/* + * Load all user data + */ +Backend.prototype.getUserData = async function (uid) { + return responseHandler(await await api.get(`/users/${uid}/jwt`, this.auth)) +} + /* * Load user profile */ diff --git a/sites/shared/hooks/use-loading-status.mjs b/sites/shared/hooks/use-loading-status.mjs index 3c016ddd89b..208bbe7099d 100644 --- a/sites/shared/hooks/use-loading-status.mjs +++ b/sites/shared/hooks/use-loading-status.mjs @@ -1,20 +1,80 @@ -import { useState } from 'react' +import { useState, useEffect } from 'react' import { Spinner } from 'shared/components/spinner.mjs' +import { OkIcon, WarningIcon } from 'shared/components/icons.mjs' +import { useTranslation } from 'next-i18next' -const LoadingStatus = ({ loadingStatus }) => - loadingStatus[0] ? ( -

    +export const ns = ['status'] + +/* + * Timeout in seconds before the loading status dissapears + */ +const timeout = 2 + +const LoadingStatus = ({ loadingStatus }) => { + const { t } = useTranslation(ns) + + const [fade, setFade] = useState('opacity-100') + const [timer, setTimer] = useState(false) + + useEffect(() => { + if (loadingStatus[2]) { + if (timer) clearTimeout(timer) + setTimer( + window.setTimeout(() => { + setFade('opacity-0') + }, timeout * 1000 - 350) + ) + } + }, [loadingStatus[2]]) + + if (!loadingStatus[0]) return null + + let color = 'secondary' + let icon = + if (loadingStatus[2]) { + color = loadingStatus[3] ? 'success' : 'error' + icon = loadingStatus[3] ? ( + + ) : ( + + ) + } + + return ( +
    - {loadingStatus[1]} + {icon} + {t(loadingStatus[1])}
    - ) : null + ) +} export const useLoadingStatus = () => { + /* + * LoadingStatus should hold an array with 1 to 4 elements: + * 0 => Show loading status or not (true or false) + * 1 => Message to show + * 2 => Set this to true to make the loadingStatus dissapear after 2 seconds + * 3 => Set this to true to show success, false to show error (only when 2 is true) + */ const [loadingStatus, setLoadingStatus] = useState([false]) + const [timer, setTimer] = useState(false) + + useEffect(() => { + if (loadingStatus[2]) { + if (timer) clearTimeout(timer) + setTimer( + window.setTimeout(() => { + setLoadingStatus([false]) + }, timeout * 1000) + ) + } + }, [loadingStatus[2]]) return { setLoadingStatus, diff --git a/sites/shared/hooks/useGist.mjs b/sites/shared/hooks/useGist.mjs deleted file mode 100644 index 39d1de47b4b..00000000000 --- a/sites/shared/hooks/useGist.mjs +++ /dev/null @@ -1,100 +0,0 @@ -import { useState, useMemo, useCallback } from 'react' -import set from 'lodash.set' -import unset from 'lodash.unset' -import cloneDeep from 'lodash.clonedeep' -import { useLocalStorage } from './useLocalStorage' -import { defaultGist as baseGist } from 'shared/components/workbench/gist.mjs' - -// Generates a default design gist to start from -export const defaultGist = (design, locale = 'en') => { - const gist = { - design, - ...baseGist, - _state: { view: 'draft' }, - } - if (locale) gist.locale = locale - - return gist -} - -// generate the gist state and its handlers -export function useGist(design, locale) { - // memoize the initial gist for this design so that it doesn't change between renders and cause an infinite loop - const initialGist = useMemo(() => defaultGist(design, locale), [design, locale]) - - // get the localstorage state and setter - const [gist, _setGist, gistReady] = useLocalStorage(`${design}_gist`, initialGist) - const [gistHistory, setGistHistory] = useState([]) - const [gistFuture, setGistFuture] = useState([]) - - const setGist = useCallback( - (newGist, addToHistory = true) => { - let oldGist - _setGist((gistState) => { - // have to clone it or nested objects will be referenced instead of copied, which defeats the purpose - if (addToHistory) oldGist = cloneDeep(gistState) - - return typeof newGist === 'function' ? newGist(cloneDeep(gistState)) : newGist - }) - - if (addToHistory) { - setGistHistory((history) => { - return [...history, oldGist] - }) - setGistFuture([]) - } - }, - [_setGist, setGistFuture, setGistHistory] - ) - - /** update a single gist value */ - const updateGist = useCallback( - (path, value, addToHistory = true) => { - setGist((gistState) => { - const newGist = { ...gistState } - set(newGist, path, value) - return newGist - }, addToHistory) - }, - [setGist] - ) - - /** unset a single gist value */ - const unsetGist = useCallback( - (path, addToHistory = true) => { - setGist((gistState) => { - const newGist = { ...gistState } - unset(newGist, path) - return newGist - }, addToHistory) - }, - [setGist] - ) - - const undoGist = useCallback(() => { - _setGist((gistState) => { - let prevGist - setGistHistory((history) => { - const newHistory = [...history] - prevGist = newHistory.pop() || defaultGist(design, locale) - return newHistory - }) - setGistFuture((future) => [gistState, ...future]) - - return { ...prevGist } - }) - }, [_setGist, setGistFuture, setGistHistory]) - - const redoGist = useCallback(() => { - const newHistory = [...gistHistory, gist] - const newFuture = [...gistFuture] - const newGist = newFuture.shift() - setGistHistory(newHistory) - setGistFuture(newFuture) - _setGist(newGist) - }, [_setGist, setGistFuture, setGistHistory]) - - const resetGist = useCallback(() => setGist(defaultGist(design, locale)), [setGist]) - - return { gist, setGist, unsetGist, gistReady, updateGist, undoGist, redoGist, resetGist } -} diff --git a/sites/shared/hooks/useLocalStorage.mjs b/sites/shared/hooks/useLocalStorage.mjs deleted file mode 100644 index 1730c2e33d4..00000000000 --- a/sites/shared/hooks/useLocalStorage.mjs +++ /dev/null @@ -1,38 +0,0 @@ -import { useState, useEffect } from 'react' - -const prefix = 'fs_' - -// See: https://usehooks.com/useLocalStorage/ -export function useLocalStorage(key, initialValue) { - // use this to track whether it's mounted. useful for doing other effects outside this hook - // and for making sure we don't write the initial value over the current value - const [ready, setReady] = useState(false) - - // State to store our value - const [storedValue, setValue] = useState(initialValue) - - // set to localstorage every time the storedValue changes - // we do it this way instead of a callback because - // getting the current state inside `useCallback` didn't seem to be working - useEffect(() => { - if (ready) { - window.localStorage.setItem(prefix + key, JSON.stringify(storedValue)) - } - }, [storedValue, key, ready]) - - // read from local storage on mount - useEffect(() => { - try { - // Get from local storage by key - const item = window.localStorage.getItem(prefix + key) - // Parse stored json or if none return initialValue - const valToSet = item ? JSON.parse(item) : initialValue - setValue(valToSet) - setReady(true) - } catch (error) { - console.log(error) - } - }, [setReady, setValue, key, initialValue]) - - return [storedValue, setValue, ready] -} diff --git a/sites/shared/i18n/toast/de.yaml b/sites/shared/i18n/status/de.yaml similarity index 100% rename from sites/shared/i18n/toast/de.yaml rename to sites/shared/i18n/status/de.yaml diff --git a/sites/shared/i18n/toast/en.yaml b/sites/shared/i18n/status/en.yaml similarity index 60% rename from sites/shared/i18n/toast/en.yaml rename to sites/shared/i18n/status/en.yaml index 9546e0ac319..b5853b4ca5c 100644 --- a/sites/shared/i18n/toast/en.yaml +++ b/sites/shared/i18n/status/en.yaml @@ -1,3 +1,5 @@ +updatingSettings: Updating settings settingsSaved: Settings saved backendError: Backend returned an error copiedToClipboard: Copied to clipboard +processingUpdate: Processing update diff --git a/sites/shared/i18n/toast/es.yaml b/sites/shared/i18n/status/es.yaml similarity index 100% rename from sites/shared/i18n/toast/es.yaml rename to sites/shared/i18n/status/es.yaml diff --git a/sites/shared/i18n/toast/fr.yaml b/sites/shared/i18n/status/fr.yaml similarity index 100% rename from sites/shared/i18n/toast/fr.yaml rename to sites/shared/i18n/status/fr.yaml diff --git a/sites/shared/i18n/toast/nl.yaml b/sites/shared/i18n/status/nl.yaml similarity index 100% rename from sites/shared/i18n/toast/nl.yaml rename to sites/shared/i18n/status/nl.yaml diff --git a/sites/shared/i18n/toast/uk.yaml b/sites/shared/i18n/status/uk.yaml similarity index 100% rename from sites/shared/i18n/toast/uk.yaml rename to sites/shared/i18n/status/uk.yaml diff --git a/sites/shared/prebuild/sitenav-org.mjs b/sites/shared/prebuild/sitenav-org.mjs index 5c6023cba31..927ced11037 100644 --- a/sites/shared/prebuild/sitenav-org.mjs +++ b/sites/shared/prebuild/sitenav-org.mjs @@ -39,7 +39,7 @@ export const extendSiteNav = async (siteNav, lang) => { h: 1, t: t('sections:new'), apikey: { - c: conf.account.fields.developer.apikeys, + c: conf.account.fields.security.apikeys, s: 'new/apikey', t: t('newApikey'), o: 30,