diff --git a/config/exceptions.yaml b/config/exceptions.yaml index a81fbd25e02..eb0365c5561 100644 --- a/config/exceptions.yaml +++ b/config/exceptions.yaml @@ -76,6 +76,7 @@ packageJson: # Components "./components/Account": "./components/Account/index.mjs" "./components/Breadcrumbs": "./components/Breadcrumbs/index.mjs" + "./components/Button": "./components/Button/index.mjs" "./components/Control": "./components/Control/index.mjs" "./components/CopyToClipboard": "./components/CopyToClipboard/index.mjs" "./components/Docusaurus": "./components/Docusaurus/index.mjs" diff --git a/packages/react/components/Account/Export.mjs b/packages/react/components/Account/Export.mjs new file mode 100644 index 00000000000..a25ce9cbedd --- /dev/null +++ b/packages/react/components/Account/Export.mjs @@ -0,0 +1,53 @@ +// Context +import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus' + +// Hooks +import React, { useState, useContext } from 'react' +import { useAccount } from '@freesewing/react/hooks/useAccount' +import { useBackend } from '@freesewing/react/hooks/useBackend' + +// Components +import { Link as WebLink } from '@freesewing/react/components/Link' +import { DownloadIcon } from '@freesewing/react/components/Icon' +import { Popout } from '@freesewing/react/components/Popout' +import { IconButton } from '@freesewing/react/components/Button' + +/* + * Component for the account/actions/export page + */ +export const Export = () => { + // Hooks + const backend = useBackend() + const { setLoadingStatus } = useContext(LoadingStatusContext) + + // State + const [link, setLink] = useState() + + // Helper method to export account + const exportData = async () => { + setLoadingStatus([true, 'Contacting backend']) + const [status, body] = await backend.exportAccount() + if (status === 200) { + setLink(body.data) + setLoadingStatus([true, 'All done', true, true]) + } else setLoadingStatus([true, 'Something went wrong, please report this', true, false]) + } + + return ( +
+ {link ? ( + +
Your data was exported and is available for download at the following location:
+

+ {link} +

+
+ ) : null} +

Click below to export your personal FreeSewing data

+ + + Export Your Data + +
+ ) +} diff --git a/packages/react/components/Account/Import.mjs b/packages/react/components/Account/Import.mjs new file mode 100644 index 00000000000..7b387de565e --- /dev/null +++ b/packages/react/components/Account/Import.mjs @@ -0,0 +1,181 @@ +// Context +import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus' + +// Hooks +import React, { useState, useContext } from 'react' +import { useAccount } from '@freesewing/react/hooks/useAccount' +import { useBackend } from '@freesewing/react/hooks/useBackend' + +// Components +import { Link as WebLink } from '@freesewing/react/components/Link' +import { SaveIcon } from '@freesewing/react/components/Icon' +import { FileInput } from '@freesewing/react/components/Input' +import { Popout } from '@freesewing/react/components/Popout' +import { Yaml } from '@freesewing/react/components/Yaml' + +/* + * Component for the account/bio page + * + * @params {object} props - All React props + * @params {bool} props.welcome - Set to true to use this component on the welcome page + * @params {function} props.Link - A framework specific Link component for client-side routing + */ +export const ImportSet = () => { + // Hooks + const backend = useBackend() + const { account } = useAccount() + const { setLoadingStatus } = useContext(LoadingStatusContext) + + // Helper method to upload/save a set + const uploadSet = async (upload) => { + setLoadingStatus([true, 'Uploading data']) + let data + try { + const chunks = upload.split(',') + if (chunks[0].includes('json')) data = JSON.parse(atob(chunks[1])) + else data = yaml.parse(atob(chunks[1])) + if (!Array.isArray(data)) data = [data] + /* + * Treat each set + */ + for (const set of data) { + if (set.measurements || set.measies) { + const name = set.name || 'J. Doe' + setLoadingStatus([true, `Importing ${name}`]) + const [status, body] = await backend.createSet({ + name: set.name || 'J. Doe', + units: set.units || 'metric', + notes: set.notes || '', + measies: set.measurements || set.measies, + userId: account.id, + }) + if (status === 200) setLoadingStatus([true, `Imported ${name}`, true, true]) + else setLoadingStatus([true, `Import of ${name} failed`, true, false]) + } else { + setLoadingStatus([true, `Invalid format`, true, false]) + } + } + } catch (err) { + console.log(err) + setLoadingStatus([true, `Import of ${name || 'file'} failed`, true, false]) + } + } + + return ( +
+ + +

+ To import a measurement set, you should have a JSON or YAML file that has the following + structure: +

+ +

+ Your file can either contain a single measurements set, or an array/list of multiple + measurements sets. +

+
+
+ ) +} + +/* + // Hooks + const { account, setAccount } = useAccount() + const backend = useBackend() + const { setLoadingStatus } = useContext(LoadingStatusContext) + + // State + const [bio, setBio] = useState(account.bio) + + // Helper method to save bio + const save = async () => { + setLoadingStatus([true, 'Saving bio']) + const [status, body] = await backend.updateAccount({ bio }) + if (status === 200 && body.result === 'success') { + setAccount(body.account) + setLoadingStatus([true, 'Bio updated', true, true]) + } else setLoadingStatus([true, 'Something went wrong. Please report this', true, true]) + } + + // Next step in the onboarding + const nextHref = + welcomeSteps[account.control].length > 5 + ? '/welcome/' + welcomeSteps[account.control][6] + : '/docs/about/guide' + + return ( +
+
Tell people a little bit about yourself.
+ +

+ +

+ + {welcome ? ( + <> + + {welcomeSteps[account.control].length > 0 ? ( + <> + + + 6 / {welcomeSteps[account.control].length} + + + + ) : null} + + ) : null} +
+ ) +} +// Dependencies +import { useContext } from 'react' +import { useTranslation } from 'next-i18next' +// Context +import { LoadingStatusContext } from 'shared/context/loading-status-context.mjs' +// Hooks +import { useAccount } from 'shared/hooks/use-account.mjs' +import { useBackend } from 'shared/hooks/use-backend.mjs' +// Components +import { FileInput } from 'shared/components/inputs.mjs' +import { Popout } from 'shared/components/popout/index.mjs' +import { linkClasses } from 'shared/components/link.mjs' +import yaml from 'yaml' + +export const ns = ['account', 'status'] + +export const Importer = () => { + // Hooks + */ diff --git a/packages/react/components/Account/Reload.mjs b/packages/react/components/Account/Reload.mjs new file mode 100644 index 00000000000..e9e2ddd4bea --- /dev/null +++ b/packages/react/components/Account/Reload.mjs @@ -0,0 +1,50 @@ +// Context +import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus' + +// Hooks +import React, { useState, useContext } from 'react' +import { useAccount } from '@freesewing/react/hooks/useAccount' +import { useBackend } from '@freesewing/react/hooks/useBackend' + +// Components +import { Link as WebLink } from '@freesewing/react/components/Link' +import { ReloadIcon } from '@freesewing/react/components/Icon' +import { Popout } from '@freesewing/react/components/Popout' +import { IconButton } from '@freesewing/react/components/Button' + +/* + * Component for the account/actions/export page + */ +export const Reload = () => { + // Hooks + const backend = useBackend() + const { setAccount } = useAccount() + const { setLoadingStatus } = useContext(LoadingStatusContext) + + // Helper method to reload account + const reload = async () => { + setLoadingStatus([true, 'Contacting backend']) + const [status, body] = await backend.reloadAccount() + if (status === 200) { + setAccount(body.account) + setLoadingStatus([true, 'All done', true, true]) + } else setLoadingStatus([true, 'This did not go as planned. Please report this.', true, false]) + } + + return ( +
+

+ The data stored in your browser can sometimes get out of sync with the data stored in our + backend. +

+

+ This lets you reload your account data from the backend. It has the same effect as signing + out, and then signing in again. +

+ + + Reload Account Data + +
+ ) +} diff --git a/packages/react/components/Account/Remove.mjs b/packages/react/components/Account/Remove.mjs new file mode 100644 index 00000000000..ee42ac98abf --- /dev/null +++ b/packages/react/components/Account/Remove.mjs @@ -0,0 +1,81 @@ +// Context +import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus' +import { ModalContext } from '@freesewing/react/context/Modal' + +// Hooks +import React, { useState, useContext } from 'react' +import { useAccount } from '@freesewing/react/hooks/useAccount' +import { useBackend } from '@freesewing/react/hooks/useBackend' + +// Components +import { Link as WebLink } from '@freesewing/react/components/Link' +import { BackIcon as ExitIcon, TrashIcon } from '@freesewing/react/components/Icon' +import { Popout } from '@freesewing/react/components/Popout' +import { IconButton } from '@freesewing/react/components/Button' +import { ModalWrapper } from '@freesewing/react/components/Modal' + +/* + * Component for the account/actions/remove page + */ +export const Remove = () => { + // Hooks + const backend = useBackend() + const { signOut, account } = useAccount() + + // Context + const { setLoadingStatus } = useContext(LoadingStatusContext) + const { setModal, clearModal } = useContext(ModalContext) + + // Helper method to remove the account + const removeAccount = async () => { + setLoadingStatus([true, 'Talking to the backend']) + const result = await backend.removeAccount() + if (result.success) { + setLoadingStatus([true, 'Done. Or rather, gone.', true, true]) + signOut() + } else setLoadingStatus([true, 'An error occured. Please report this', true, false]) + } + + if (account.control === 5) + return ( + <> +

This button is red for a reason.

+ + + Remove your FreeSewing account + + + ) + + return ( +
+ + setModal( + +
+

There is no way back from this

+

If this is what you want, then go ahead.

+ + + Remove your FreeSewing account + + + + Back to safety + +
+
+ ) + } + > + + Remove your FreeSewing account +
+
+ ) +} diff --git a/packages/react/components/Account/Restrict.mjs b/packages/react/components/Account/Restrict.mjs new file mode 100644 index 00000000000..5a15294c097 --- /dev/null +++ b/packages/react/components/Account/Restrict.mjs @@ -0,0 +1,98 @@ +// Dependencies +import { linkClasses } from '@freesewing/utils' + +// Context +import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus' +import { ModalContext } from '@freesewing/react/context/Modal' + +// Hooks +import React, { useState, useContext } from 'react' +import { useAccount } from '@freesewing/react/hooks/useAccount' +import { useBackend } from '@freesewing/react/hooks/useBackend' + +// Components +import { Link as WebLink } from '@freesewing/react/components/Link' +import { BackIcon, NoIcon } from '@freesewing/react/components/Icon' +import { Popout } from '@freesewing/react/components/Popout' +import { IconButton } from '@freesewing/react/components/Button' +import { ModalWrapper } from '@freesewing/react/components/Modal' + +/* + * Component for the account/actions/restrict page + */ +export const Restrict = ({ Link = false }) => { + if (!Link) Link = WebLink + + // Hooks + const backend = useBackend() + const { signOut, account } = useAccount() + + // Context + const { setLoadingStatus } = useContext(LoadingStatusContext) + const { setModal, clearModal } = useContext(ModalContext) + + // Helper method to restrict the account + const restrictAccount = async () => { + setLoadingStatus([true, 'Talking to the backend']) + const [status, body] = await backend.restrictAccount() + if (status === 200) { + setLoadingStatus([true, 'Done. Consider yourself restrcited.', true, true]) + signOut() + } else setLoadingStatus([true, 'An error occured. Please report this', true, false]) + } + + if (account.control === 5) + return ( + <> +

This button is red for a reason.

+ + + Remove your FreeSewing account + + + ) + + return ( +
+

+ The GDPR guarantees{' '} + + your right to restrict processing + {' '} + of your personal data. +

+

This will disable your account, but not remove any data.

+ + setModal( + +
+

Proceed with caution

+

+ While no data will be removed, this will disable your account. Furthermore, you + can not undo this on your own, but will have to contact support when you want to + restore access to your account. +

+ + + Restrict processing of your FreeSewing data + + + + Back to safety + +
+
+ ) + } + > + + Restrict processing of your FreeSewing data +
+
+ ) +} diff --git a/packages/react/components/Account/apikeys.mjs b/packages/react/components/Account/apikeys.mjs deleted file mode 100644 index f181bb201d7..00000000000 --- a/packages/react/components/Account/apikeys.mjs +++ /dev/null @@ -1,408 +0,0 @@ -// Dependencies -import { useState, useEffect, useContext } from 'react' -import { useTranslation } from 'next-i18next' -import { DateTime } from 'luxon' -import { CopyToClipboard } from 'react-copy-to-clipboard' -import { shortDate, formatNumber } from 'shared/utils.mjs' -// Context -import { LoadingStatusContext } from 'shared/context/loading-status-context.mjs' -// Hooks -import { useAccount } from 'shared/hooks/use-account.mjs' -import { useBackend } from 'shared/hooks/use-backend.mjs' -import { useRouter } from 'next/router' -// Components -import { BackToAccountButton, DisplayRow, NumberBullet } from './shared.mjs' -import { Popout } from 'shared/components/popout/index.mjs' -import { LeftIcon, PlusIcon, CopyIcon, RightIcon, TrashIcon } from 'shared/components/icons.mjs' -import { Link, linkClasses } from 'shared/components/link.mjs' -import { StringInput, ListInput, FormControl } from 'shared/components/inputs.mjs' -import { DynamicMdx } from 'shared/components/mdx/dynamic.mjs' - -export const ns = ['account', 'status'] - -const ExpiryPicker = ({ t, expires, setExpires }) => { - const router = useRouter() - const { locale } = router - const [months, setMonths] = useState(1) - - // Run update when component mounts - useEffect(() => update(months), []) - - const update = (evt) => { - const value = typeof evt === 'number' ? evt : evt.target.value - setExpires(DateTime.now().plus({ months: value })) - setMonths(value) - } - - return ( - <> -
- -
- - {t('keyExpiresDesc')} - {shortDate(locale, expires)} - - - ) -} - -const CopyInput = ({ text }) => { - const { t } = useTranslation(['status']) - const { setLoadingStatus } = useContext(LoadingStatusContext) - - const [copied, setCopied] = useState(false) - - const showCopied = () => { - setCopied(true) - setLoadingStatus([true, t('copiedToClipboard'), true, true]) - window.setTimeout(() => setCopied(false), 2000) - } - - return ( -
- - - - -
- ) -} - -export const Apikey = ({ apikey, setId }) => { - const { t } = useTranslation(ns) - const router = useRouter() - const { locale } = router - - return apikey ? ( -
- {apikey.name} - {shortDate(locale, apikey.createdAt)} - {shortDate(locale, apikey.expiresAt)} - {apikey.key} -
- -
-
- ) : null -} - -const ShowKey = ({ apikey, t, clear }) => { - const router = useRouter() - const { locale } = router - - return ( -
- - {t('keySecretWarning')} - - {apikey.name} - {shortDate(locale, apikey.createdAt)} - {shortDate(locale, apikey.expiresAt)} - - - - - - -
- - -
-
- ) -} - -const NewKey = ({ account, setGenerate, backend }) => { - const [name, setName] = useState('') - const [level, setLevel] = useState(1) - const [expires, setExpires] = useState(Date.now()) - const [apikey, setApikey] = useState(false) - const { setLoadingStatus } = useContext(LoadingStatusContext) - const { t, i18n } = useTranslation(ns) - const docs = {} - for (const option of ['name', 'expiry', 'level']) { - docs[option] = ( - - ) - } - - const levels = account.role === 'admin' ? [0, 1, 2, 3, 4, 5, 6, 7, 8] : [0, 1, 2, 3, 4] - - const createKey = async () => { - setLoadingStatus([true, 'processingUpdate']) - const result = await backend.createApikey({ - name, - level, - expiresIn: Math.floor((expires.valueOf() - Date.now().valueOf()) / 1000), - }) - if (result.success) { - setLoadingStatus([true, 'nailedIt', true, true]) - setApikey(result.data.apikey) - } else setLoadingStatus([true, 'backendError', true, false]) - } - - const clear = () => { - setApikey(false) - setGenerate(false) - setName('') - setLevel(1) - } - - return ( -
- {apikey ? ( - - ) : ( - <> - val.length > 0} - placeholder={'Alicia Key'} - /> - - - - ({ - val: l, - label: ( -
- {t(`keyLevel${l}`)} - -
- ), - }))} - current={level} - update={setLevel} - /> -
- -
- - )} -
- ) -} - -// Component for the 'new/apikey' page -export const NewApikey = () => { - // Hooks - const { account } = useAccount() - const backend = useBackend() - - // State - 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 = ({ setId }) => { - const router = useRouter() - const { locale } = router - - // Hooks - const { account } = useAccount() - const backend = useBackend() - const { t } = useTranslation(ns) - const { setLoadingStatus, LoadingProgress } = useContext(LoadingStatusContext) - - // State - const [keys, setKeys] = useState([]) - const [selected, setSelected] = useState({}) - const [refresh, setRefresh] = useState(0) - - // Helper var to see how many are selected - const selCount = Object.keys(selected).length - - // Effects - useEffect(() => { - const getApikeys = async () => { - const result = await backend.getApikeys() - if (result.success) setKeys(result.data.apikeys) - } - getApikeys() - }, [refresh]) - - // Helper method to toggle single selection - const toggleSelect = (id) => { - const newSelected = { ...selected } - if (newSelected[id]) delete newSelected[id] - else newSelected[id] = 1 - setSelected(newSelected) - } - - // Helper method to toggle select all - const toggleSelectAll = () => { - if (selCount === keys.length) setSelected({}) - else { - const newSelected = {} - for (const key of keys) newSelected[key.id] = 1 - setSelected(newSelected) - } - } - - // Helper to delete one or more apikeys - const removeSelectedApikeys = async () => { - let i = 0 - for (const key in selected) { - i++ - await backend.removeApikey(key) - setLoadingStatus([ - true, - , - ]) - } - setSelected({}) - setRefresh(refresh + 1) - setLoadingStatus([true, 'nailedIt', true, true]) - } - - return ( -
-

- - - {t('newApikey')} - -

- - - - - - - - - - - - - {keys.map((apikey, i) => ( - - - - - - - - ))} - -
- - {t('keyName')} - {t('keyLevel')} - - 🔐 - - {t('keyExpires')}{t('apiCalls')}
- toggleSelect(apikey.id)} - /> - - - - {apikey.level} - - ({t(`keyLevel${apikey.level}`)}) - - - {shortDate(locale, apikey.expiresAt, false)} - - {formatNumber(apikey.calls)} -
- - {account.control < 5 ? ( - -
{t('keyDocsTitle')}
-

{t('keyDocsMsg')}

-

- - FreeSewing.dev - - -

-
- ) : null} -
- ) -} diff --git a/packages/react/components/Account/bio.mjs b/packages/react/components/Account/bio.mjs deleted file mode 100644 index 510e13e7cbf..00000000000 --- a/packages/react/components/Account/bio.mjs +++ /dev/null @@ -1,98 +0,0 @@ -// Dependencies -import { useState, useContext } from 'react' -import { useTranslation } from 'next-i18next' -// Context -import { LoadingStatusContext } from 'shared/context/loading-status-context.mjs' -// Hooks -import { useAccount } from 'shared/hooks/use-account.mjs' -import { useBackend } from 'shared/hooks/use-backend.mjs' -// Components -import { Icons, welcomeSteps, BackToAccountButton } from './shared.mjs' -import { SaveSettingsButton } from 'shared/components/buttons/save-settings-button.mjs' -import { ContinueButton } from 'shared/components/buttons/continue-button.mjs' -import { MarkdownInput } from 'shared/components/inputs.mjs' -import { DynamicMdx } from 'shared/components/mdx/dynamic.mjs' -import { TipIcon } from 'shared/components/icons.mjs' - -export const ns = ['account', 'status'] - -export const Tab = ({ id, activeTab, setActiveTab, t }) => ( - -) - -export const BioSettings = ({ welcome = false }) => { - // Hooks - const { account, setAccount } = useAccount() - const backend = useBackend() - const { t, i18n } = useTranslation(ns) - const { setLoadingStatus } = useContext(LoadingStatusContext) - - // State - const [bio, setBio] = useState(account.bio) - - // Helper method to save bio - const save = async () => { - setLoadingStatus([true, 'processingUpdate']) - const result = await backend.updateAccount({ bio }) - if (result.success) { - setAccount(result.data.account) - setLoadingStatus([true, 'settingsSaved', true, true]) - } else setLoadingStatus([true, 'backendError', true, false]) - } - - // Next step in the onboarding - const nextHref = - welcomeSteps[account.control].length > 5 - ? '/welcome/' + welcomeSteps[account.control][6] - : '/docs/about/guide' - - return ( -
- } - labelBL={ - - - {t('mdSupport')} - - } - /> - - {!welcome && } - - {welcome ? ( - <> - - {welcomeSteps[account.control].length > 0 ? ( - <> - - - 6 / {welcomeSteps[account.control].length} - - - - ) : null} - - ) : null} -
- ) -} diff --git a/packages/react/components/Account/compare.mjs b/packages/react/components/Account/compare.mjs deleted file mode 100644 index 49d664423a8..00000000000 --- a/packages/react/components/Account/compare.mjs +++ /dev/null @@ -1,98 +0,0 @@ -// Dependencies -import { useState, useContext } from 'react' -import { useTranslation } from 'next-i18next' -// Context -import { LoadingStatusContext } from 'shared/context/loading-status-context.mjs' -// Hooks -import { useAccount } from 'shared/hooks/use-account.mjs' -import { useBackend } from 'shared/hooks/use-backend.mjs' -// Components -import { Icons, welcomeSteps, BackToAccountButton } from './shared.mjs' -import { ContinueButton } from 'shared/components/buttons/continue-button.mjs' -import { ListInput } from 'shared/components/inputs.mjs' -import { OkIcon, NoIcon } from 'shared/components/icons.mjs' -import { DynamicMdx } from 'shared/components/mdx/dynamic.mjs' - -export const ns = ['account', 'status'] - -export const CompareSettings = ({ welcome = false }) => { - // Hooks - const { account, setAccount } = useAccount() - const backend = useBackend() - const { setLoadingStatus } = useContext(LoadingStatusContext) - const { t, i18n } = useTranslation(ns) - - // State - const [selection, setSelection] = useState(account?.compare ? 'yes' : 'no') - - // Helper method to update the account - const update = async (val) => { - if (val !== selection) { - setLoadingStatus([true, 'processingUpdate']) - const result = await backend.updateAccount({ - compare: val === 'yes' ? true : false, - }) - if (result.success) { - setLoadingStatus([true, 'settingsSaved', true, true]) - setAccount(result.data.account) - setSelection(val) - } else setLoadingStatus([true, 'backendError', true, true]) - } - } - - // Link to the next onboarding step - const nextHref = - welcomeSteps[account?.control].length > 3 - ? '/welcome/' + welcomeSteps[account?.control][4] - : '/docs/about/guide' - - return ( -
- ({ - val, - label: ( -
- {t(val === 'yes' ? 'compareYes' : 'compareNo')} - {val === 'yes' ? ( - - ) : ( - - )} -
- ), - desc: t(val === 'yes' ? 'compareYesd' : 'compareNod'), - }))} - current={selection} - update={update} - docs={} - /> - {welcome ? ( - <> - - {welcomeSteps[account?.control].length > 0 ? ( - <> - - - 4 / {welcomeSteps[account?.control].length} - - - - ) : null} - - ) : ( - - )} -
- ) -} diff --git a/packages/react/components/Account/consent.mjs b/packages/react/components/Account/consent.mjs deleted file mode 100644 index 9a3576c8d8f..00000000000 --- a/packages/react/components/Account/consent.mjs +++ /dev/null @@ -1,116 +0,0 @@ -// Dependencies -import { useState, useContext } from 'react' -import { useTranslation } from 'next-i18next' -import { nsMerge } from 'shared/utils.mjs' -// Context -import { LoadingStatusContext } from 'shared/context/loading-status-context.mjs' -// Hooks -import { useAccount } from 'shared/hooks/use-account.mjs' -import { useBackend } from 'shared/hooks/use-backend.mjs' -// Components -import Link from 'next/link' -import { Popout } from 'shared/components/popout/index.mjs' -import { BackToAccountButton } from './shared.mjs' -import { SaveSettingsButton } from 'shared/components/buttons/save-settings-button.mjs' -import { GdprAccountDetails, ns as gdprNs } from 'shared/components/gdpr/details.mjs' - -export const ns = nsMerge(gdprNs, 'account', 'status') - -const Checkbox = ({ value, setter, label, children = null }) => ( -
setter(value ? false : true)} - > -
- setter(value ? false : true)} - /> - {label} -
- {children} -
-) - -export const ConsentSettings = ({ title = false }) => { - // Hooks - const { account, setAccount, setToken } = useAccount() - const backend = useBackend() - const { setLoadingStatus } = useContext(LoadingStatusContext) - const { t } = useTranslation(ns) - - // State - const [consent1, setConsent1] = useState(account?.consent > 0) - const [consent2, setConsent2] = useState(account?.consent > 1) - - // Helper method to update the account - const update = async () => { - let newConsent = 0 - if (consent1) newConsent = 1 - if (consent1 && consent2) newConsent = 2 - if (newConsent !== account.consent) { - setLoadingStatus([true, 'processingUpdate']) - const result = await backend.updateAccount({ consent: newConsent }) - if (result.data?.result === 'success') { - setLoadingStatus([true, 'settingsSaved', true, true]) - setAccount(result.data.account) - } else setLoadingStatus([true, 'backendError', true, true]) - } - } - - // Helper method to remove the account - const removeAccount = async () => { - setLoadingStatus([true, 'processingUpdate']) - const result = await backend.removeAccount() - if (result === true) setLoadingStatus([true, 'settingsSaved', true, true]) - else setLoadingStatus([true, 'backendError', true, true]) - setToken(null) - setAccount({ username: false }) - } - - return ( -
- {title ?

{t('privacyMatters')}

: null} -

{t('compliant')}

-

{t('consentWhyAnswer')}

-
{t('accountQuestion')}
- - {consent1 ? ( - - ) : ( - - )} - {consent1 ? ( - - ) : null} - {consent1 && !consent2 ? {t('openDataInfo')} : null} - {!consent1 && {t('noConsentNoAccount')}} - {consent1 ? ( - - ) : ( - - )} - -

- - FreeSewing Privacy Notice - -

-
- ) -} diff --git a/packages/react/components/Account/control.mjs b/packages/react/components/Account/control.mjs deleted file mode 100644 index d996dac0953..00000000000 --- a/packages/react/components/Account/control.mjs +++ /dev/null @@ -1,105 +0,0 @@ -// __SDEFILE__ - This file is a dependency for the stand-alone environment -// Dependencies -import { useState, useContext } from 'react' -import { useTranslation } from 'next-i18next' -// Context -import { LoadingStatusContext } from 'shared/context/loading-status-context.mjs' -// Hooks -import { useAccount } from 'shared/hooks/use-account.mjs' -import { useBackend } from 'shared/hooks/use-backend.mjs' -// Components -import { BackToAccountButton, Icons, welcomeSteps } from './shared.mjs' -import { ContinueButton } from 'shared/components/buttons/continue-button.mjs' -import { ListInput } from 'shared/components/inputs.mjs' -import { ControlScore } from 'shared/components/control/score.mjs' -import { DynamicMdx } from 'shared/components/mdx/dynamic.mjs' - -export const ns = ['account', 'status'] - -/** state handlers for any input that changes the control setting */ -export const useControlState = () => { - // Hooks - const { account, setAccount, token } = useAccount() - const backend = useBackend() - const { setLoadingStatus } = useContext(LoadingStatusContext) - - // State - const [selection, setSelection] = useState(account.control) - - // Method to update the control setting - const update = async (control) => { - if (control !== selection) { - if (token) { - setLoadingStatus([true, 'processingUpdate']) - const result = await backend.updateAccount({ control }) - if (result.success) { - setSelection(control) - setAccount(result.data.account) - setLoadingStatus([true, 'settingsSaved', true, true]) - } else setLoadingStatus([true, 'backendError', true, true]) - } - //fallback for guest users - else { - setAccount({ ...account, control }) - setSelection(control) - } - } - } - - return { selection, update } -} - -export const ControlSettings = ({ welcome = false, noBack = false }) => { - const { t, i18n } = useTranslation(ns) - - const { selection, update } = useControlState() - - // Helper to get the link to the next onboarding step - const nextHref = welcome - ? welcomeSteps[selection].length > 1 - ? '/welcome/' + welcomeSteps[selection][1] - : '/docs/about/guide' - : false - - return ( -
- ({ - val, - label: ( -
- {t(`control${val}.t`)} - -
- ), - desc: t(`control${val}.d`), - }))} - current={selection} - update={update} - docs={} - /> - {welcome ? ( - <> - - {welcomeSteps[selection].length > 1 ? ( - <> - - - 1 / {welcomeSteps[selection].length} - - - - ) : null} - - ) : noBack ? null : ( - - )} -
- ) -} diff --git a/packages/react/components/Account/email.mjs b/packages/react/components/Account/email.mjs deleted file mode 100644 index e71603b1789..00000000000 --- a/packages/react/components/Account/email.mjs +++ /dev/null @@ -1,76 +0,0 @@ -// Dependencies -import { useState, useContext } from 'react' -import { useTranslation } from 'next-i18next' -// Context -import { LoadingStatusContext } from 'shared/context/loading-status-context.mjs' -// Hooks -import { useAccount } from 'shared/hooks/use-account.mjs' -import { useBackend } from 'shared/hooks/use-backend.mjs' -// Verification methods -import { validateEmail, validateTld } from 'shared/utils.mjs' -// Components -import { BackToAccountButton } from './shared.mjs' -import { Popout } from 'shared/components/popout/index.mjs' -import { EmailInput } from 'shared/components/inputs.mjs' -import { DynamicMdx } from 'shared/components/mdx/dynamic.mjs' - -export const ns = ['account', 'status'] - -export const EmailSettings = () => { - // Hooks - const { account, setAccount } = useAccount() - const backend = useBackend() - const { t, i18n } = useTranslation(ns) - const { setLoadingStatus } = useContext(LoadingStatusContext) - - // State - const [email, setEmail] = useState(account.email) - const [changed, setChanged] = useState(false) - - // Helper method to update account - const save = async () => { - setLoadingStatus([true, 'processingUpdate']) - const result = await backend.updateAccount({ email }) - if (result.success) { - setAccount(result.data.account) - setLoadingStatus([true, 'settingsSaved', true, true]) - } else setLoadingStatus([true, 'backendError', true, true]) - setChanged(true) - } - - // Is email valid? - const valid = (validateEmail(email) && validateTld(email)) || false - - return ( -
- {changed ? ( - -

{t('oneMoreThing')}

-

{t('emailChangeConfirmation')}

-
- ) : ( - <> - valid} - docs={} - /> - - - )} - -
- ) -} diff --git a/packages/react/components/Account/export.mjs b/packages/react/components/Account/export.mjs deleted file mode 100644 index 97ff787c52a..00000000000 --- a/packages/react/components/Account/export.mjs +++ /dev/null @@ -1,49 +0,0 @@ -// Context -import { LoadingStatusContext } from 'shared/context/loading-status-context.mjs' -// Hooks -import { useTranslation } from 'next-i18next' -import { useState, useContext } from 'react' -import { useBackend } from 'shared/hooks/use-backend.mjs' -// Components -import { BackToAccountButton } from './shared.mjs' -import { Popout } from 'shared/components/popout/index.mjs' -import { WebLink } from 'shared/components/link.mjs' - -export const ns = ['account', 'status'] - -export const ExportAccount = () => { - // Hooks - const backend = useBackend() - const { t } = useTranslation(ns) - const { setLoadingStatus } = useContext(LoadingStatusContext) - - const [link, setLink] = useState() - - // Helper method to export account - const exportData = async () => { - setLoadingStatus([true, 'processingUpdate']) - const result = await backend.exportAccount() - if (result.success) { - setLink(result.data.data) - setLoadingStatus([true, 'nailedIt', true, true]) - } else setLoadingStatus([true, 'backendError', true, false]) - } - - return ( -
- {link ? ( - -
{t('exportDownload')}
-

- -

-
- ) : null} -

{t('exportMsg')}

- - -
- ) -} diff --git a/packages/react/components/Account/force-account-check.mjs b/packages/react/components/Account/force-account-check.mjs deleted file mode 100644 index 4e5c0bd45d6..00000000000 --- a/packages/react/components/Account/force-account-check.mjs +++ /dev/null @@ -1,35 +0,0 @@ -// Dependencies -import { useState, useEffect } from 'react' -// Hooks -import { useAccount } from 'shared/hooks/use-account.mjs' -import { useBackend } from 'shared/hooks/use-backend.mjs' - -export const ForceAccountCheck = ({ trigger = null }) => { - // Hooks - const { account, setAccount, signOut } = useAccount() - const backend = useBackend() - - // State - const [lastCheck, setLastCheck] = useState(Date.now()) - - // The actual check - useEffect(() => { - const age = Date.now() - lastCheck - if (account.status && age < 500) { - const checkAccount = async () => { - const result = await backend.reloadAccount() - if (result.success) { - setAccount(result.data.account) - } else { - // Login expired. Logout user. - signOut() - } - setLastCheck(Date.now()) - } - checkAccount() - } - }, [trigger]) - - // Don't return anything. This is all about the useEffect hook. - return null -} diff --git a/packages/react/components/Account/github.mjs b/packages/react/components/Account/github.mjs deleted file mode 100644 index 0d8c1a13107..00000000000 --- a/packages/react/components/Account/github.mjs +++ /dev/null @@ -1,62 +0,0 @@ -// Context -import { LoadingStatusContext } from 'shared/context/loading-status-context.mjs' -// Hooks -import { useState, useContext } from 'react' -import { useTranslation } from 'next-i18next' -import { useAccount } from 'shared/hooks/use-account.mjs' -import { useBackend } from 'shared/hooks/use-backend.mjs' -// Components -import { BackToAccountButton } from './shared.mjs' -import { SaveSettingsButton } from 'shared/components/buttons/save-settings-button.mjs' -import { StringInput } from 'shared/components/inputs.mjs' -import { DynamicMdx } from 'shared/components/mdx/dynamic.mjs' - -export const ns = ['account', 'status'] - -export const GithubSettings = () => { - // Hooks - const { account, setAccount } = useAccount() - const backend = useBackend() - const { t, i18n } = useTranslation(ns) - const { setLoadingStatus } = useContext(LoadingStatusContext) - - // State - const [githubUsername, setGithubUsername] = useState(account.data.githubUsername || '') - const [githubEmail, setGithubEmail] = useState(account.data.githubEmail || '') - - // Helper method to save changes - const save = async () => { - setLoadingStatus([true, 'processingUpdate']) - const result = await backend.updateAccount({ data: { githubUsername, githubEmail } }) - if (result.success) { - setAccount(result.data.account) - setLoadingStatus([true, 'settingsSaved', true, true]) - } else setLoadingStatus([true, 'backendError', true, false]) - } - - return ( -
-

{t('githubTitle')}

- val.length > 0} - placeholder={'joost@joost.at'} - docs={} - /> - val.length > 0} - placeholder={'joostdecock'} - docs={} - /> - - -
- ) -} diff --git a/packages/react/components/Account/img.mjs b/packages/react/components/Account/img.mjs deleted file mode 100644 index 8fd76fc42d1..00000000000 --- a/packages/react/components/Account/img.mjs +++ /dev/null @@ -1,88 +0,0 @@ -// Dependencies -import { cloudflareImageUrl } from 'shared/utils.mjs' -// Context -import { LoadingStatusContext } from 'shared/context/loading-status-context.mjs' -// Hooks -import { useState, useContext } from 'react' -import { useTranslation } from 'next-i18next' -import { useAccount } from 'shared/hooks/use-account.mjs' -import { useBackend } from 'shared/hooks/use-backend.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 { PassiveImageInput } from 'shared/components/inputs.mjs' -import { DynamicMdx } from 'shared/components/mdx/dynamic.mjs' - -export const ns = ['account', 'status'] - -export const ImgSettings = ({ welcome = false }) => { - const { account, setAccount } = useAccount() - const backend = useBackend() - const { setLoadingStatus } = useContext(LoadingStatusContext) - const { t, i18n } = useTranslation(ns) - - const [img, setImg] = useState('') - - const save = async () => { - setLoadingStatus([true, 'processingUpdate']) - const result = await backend.updateAccount({ img }) - if (result.success) { - setAccount(result.data.account) - setLoadingStatus([true, 'settingsSaved', true, true]) - } else setLoadingStatus([true, 'backendError', true, false]) - } - - const nextHref = '/docs/about/guide' - - return ( -
- {!welcome || img !== false ? ( - img - ) : null} - val.length > 0} - docs={} - /> - {welcome ? ( - <> - - - {welcomeSteps[account.control].length > 0 ? ( - <> - - - 7 / {welcomeSteps[account.control].length} - - - - ) : null} - - ) : ( - <> - - - - )} -
- ) -} diff --git a/packages/react/components/Account/imperial.mjs b/packages/react/components/Account/imperial.mjs deleted file mode 100644 index 739c6ad74d3..00000000000 --- a/packages/react/components/Account/imperial.mjs +++ /dev/null @@ -1,90 +0,0 @@ -// Context -import { LoadingStatusContext } from 'shared/context/loading-status-context.mjs' -// Hooks -import { useState, useContext } from 'react' -import { useTranslation } from 'next-i18next' -import { useAccount } from 'shared/hooks/use-account.mjs' -import { useBackend } from 'shared/hooks/use-backend.mjs' -// Components -import { Icons, welcomeSteps, BackToAccountButton, NumberBullet } from './shared.mjs' -import { ContinueButton } from 'shared/components/buttons/continue-button.mjs' -import { ListInput } from 'shared/components/inputs.mjs' -import { DynamicMdx } from 'shared/components/mdx/dynamic.mjs' - -export const ns = ['account', 'status'] - -export const ImperialSettings = ({ welcome = false }) => { - // Hooks - const { account, setAccount } = useAccount() - const { setLoadingStatus } = useContext(LoadingStatusContext) - const backend = useBackend() - const { t, i18n } = useTranslation(ns) - - // State - const [selection, setSelection] = useState(account?.imperial === true ? 'imperial' : 'metric') - - // Helper method to update account - const update = async (val) => { - if (val !== selection) { - setLoadingStatus([true, 'processingUpdate']) - const result = await backend.updateAccount({ imperial: val === 'imperial' ? true : false }) - if (result.success) { - setAccount(result.data.account) - setSelection(val) - setLoadingStatus([true, 'settingsSaved', true, true]) - } else setLoadingStatus([true, 'backendError', true, true]) - } - } - - // Next step in the onboarding - const nextHref = - welcomeSteps[account?.control].length > 3 - ? '/welcome/' + welcomeSteps[account?.control][3] - : '/docs/about/guide' - - return ( -
- ({ - val, - label: ( -
- {t(`${val}Units`)} - -
- ), - desc: t(`${val}Unitsd`), - }))} - current={selection} - update={update} - docs={} - /> - {welcome ? ( - <> - - {welcomeSteps[account?.control].length > 0 ? ( - <> - - - 3 / {welcomeSteps[account?.control].length} - - - - ) : null} - - ) : ( - - )} -
- ) -} diff --git a/packages/react/components/Account/import.mjs b/packages/react/components/Account/import.mjs deleted file mode 100644 index 8892a26a56f..00000000000 --- a/packages/react/components/Account/import.mjs +++ /dev/null @@ -1,101 +0,0 @@ -// Dependencies -import { useContext } from 'react' -import { useTranslation } from 'next-i18next' -// Context -import { LoadingStatusContext } from 'shared/context/loading-status-context.mjs' -// Hooks -import { useAccount } from 'shared/hooks/use-account.mjs' -import { useBackend } from 'shared/hooks/use-backend.mjs' -// Components -import { FileInput } from 'shared/components/inputs.mjs' -import { Yaml } from 'shared/components/yaml.mjs' -import { Popout } from 'shared/components/popout/index.mjs' -import { linkClasses } from 'shared/components/link.mjs' -import yaml from 'yaml' - -export const ns = ['account', 'status'] - -export const Importer = () => { - // Hooks - const { account } = useAccount() - const backend = useBackend() - const { t } = useTranslation(ns) - const { setLoadingStatus } = useContext(LoadingStatusContext) - - // Helper method to upload/save a set - const uploadSet = async (upload) => { - setLoadingStatus([true, 'processingUpdate']) - let data - try { - const chunks = upload.split(',') - if (chunks[0].includes('json')) data = JSON.parse(atob(chunks[1])) - else data = yaml.parse(atob(chunks[1])) - if (!Array.isArray(data)) data = [data] - /* - * Treat each set - */ - for (const set of data) { - if (set.measurements || set.measies) { - const name = set.name || 'J. Doe' - setLoadingStatus([true, `Importing ${name}`]) - const result = await backend.createSet({ - name: set.name || 'J. Doe', - units: set.units || 'metric', - notes: set.notes || '', - measies: set.measurements || set.measies, - userId: account.id, - }) - if (result.success) setLoadingStatus([true, `Imported ${name}`, true, true]) - else setLoadingStatus([true, `Import of ${name} failed`, true, false]) - } else { - setLoadingStatus([true, `Invalid format`, true, false]) - } - } - } catch (err) { - console.log(err) - setLoadingStatus([true, `Import of ${name || 'file'} failed`, true, false]) - } - } - - return ( -
-

{t('account:importHere')}

-

{t('account:importSupported')}

- -

{t('account:importSets')}

- - -

{t('account:importSetTip1')}

- -

{t('account:importSetTip2')}

-
-
- ) -} diff --git a/packages/react/components/Account/index.mjs b/packages/react/components/Account/index.mjs index 3915e2cf72a..6e5d0d0948d 100644 --- a/packages/react/components/Account/index.mjs +++ b/packages/react/components/Account/index.mjs @@ -20,6 +20,11 @@ import { Newsletter } from './Newsletter.mjs' import { Consent } from './Consent.mjs' import { Password } from './Password.mjs' import { Mfa } from './Mfa.mjs' +import { ImportSet } from './Import.mjs' +import { Export } from './Export.mjs' +import { Reload } from './Reload.mjs' +import { Remove } from './Remove.mjs' +import { Restrict } from './Restrict.mjs' export { Bookmarks, @@ -51,4 +56,9 @@ export { Consent, Password, Mfa, + ImportSet, + Export, + Reload, + Remove, + Restrict, } diff --git a/packages/react/components/Account/language.mjs b/packages/react/components/Account/language.mjs deleted file mode 100644 index f55869462b3..00000000000 --- a/packages/react/components/Account/language.mjs +++ /dev/null @@ -1,66 +0,0 @@ -// Context -import { LoadingStatusContext } from 'shared/context/loading-status-context.mjs' -// Hooks -import { useState, useContext } from 'react' -import { useTranslation } from 'next-i18next' -import { useAccount } from 'shared/hooks/use-account.mjs' -import { useBackend } from 'shared/hooks/use-backend.mjs' -// Components -import { BackToAccountButton, NumberBullet } from './shared.mjs' -import { ListInput } from 'shared/components/inputs.mjs' -import { DynamicMdx } from 'shared/components/mdx/dynamic.mjs' -// Config -import { siteConfig as conf } from 'site/site.config.mjs' - -export const ns = ['account', 'locales', 'status'] - -export const LanguageSettings = () => { - // Hooks - const { account, setAccount } = useAccount() - const { setLoadingStatus } = useContext(LoadingStatusContext) - const backend = useBackend() - const { t, i18n } = useTranslation(ns) - - // State - const [language, setLanguage] = useState(account.language || 'en') - - // Helper method to update the account - const update = async (lang) => { - if (lang !== language) { - setLoadingStatus([true, 'processingUpdate']) - setLanguage(lang) - const result = await backend.updateAccount({ language: lang }) - if (result.success) { - setAccount(result.data.account) - setLoadingStatus([true, 'settingsSaved', true, true]) - } else setLoadingStatus([true, 'backendError', true, true]) - } - } - - return ( -
- ({ - val, - label: ( -
- - {t(`locales:${val}`)} - | - {t(`locales:${val}`, { lng: val })} - - -
- ), - desc: t('languageTitle', { lng: val }), - }))} - current={language} - update={update} - docs={} - /> - -
- ) -} diff --git a/packages/react/components/Account/mfa.mjs b/packages/react/components/Account/mfa.mjs deleted file mode 100644 index a4258b235fe..00000000000 --- a/packages/react/components/Account/mfa.mjs +++ /dev/null @@ -1,187 +0,0 @@ -// Context -import { LoadingStatusContext } from 'shared/context/loading-status-context.mjs' -// Hooks -import { useState, useContext } from 'react' -import { useTranslation } from 'next-i18next' -import { useAccount } from 'shared/hooks/use-account.mjs' -import { useBackend } from 'shared/hooks/use-backend.mjs' -// Components -import { BackToAccountButton } from './shared.mjs' -import { Popout } from 'shared/components/popout/index.mjs' -import { Bullet } from 'shared/components/bullet.mjs' -import { PasswordInput } from 'shared/components/inputs.mjs' -import { CopyToClipboard } from 'shared/components/copy-to-clipboard.mjs' - -export const ns = ['account'] - -const CodeInput = ({ code, setCode, t }) => ( - setCode(evt.target.value)} - className="input w-full text-4xl input-bordered input-lg flex flex-row text-center mb-8 tracking-widest" - type="text" - placeholder={t('000000')} - /> -) - -export const MfaSettings = ({ title = false, welcome = false }) => { - // Hooks - const { account, setAccount } = useAccount() - const backend = useBackend() - const { t } = useTranslation(ns) - const { setLoadingStatus } = useContext(LoadingStatusContext) - - // State - const [enable, setEnable] = useState(false) - const [disable, setDisable] = useState(false) - const [code, setCode] = useState('') - const [password, setPassword] = useState('') - const [scratchCodes, setScratchCodes] = useState(false) - - // Helper method to enable MFA - const enableMfa = async () => { - setLoadingStatus([true, 'processingUpdate']) - const result = await backend.enableMfa() - 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 () => { - setLoadingStatus([true, 'processingUpdate']) - const result = await backend.disableMfa({ - mfa: false, - password, - token: code, - }) - if (result) { - if (result.success) { - setAccount(result.data.account) - setLoadingStatus([true, 'settingsSaved', true, true]) - } else setLoadingStatus([true, 'backendError', true, false]) - setDisable(false) - setEnable(false) - setCode('') - setPassword('') - } - } - - // Helper method to confirm MFA - const confirmMfa = async () => { - setLoadingStatus([true, 'processingUpdate']) - const result = await backend.confirmMfa({ - mfa: true, - secret: enable.secret, - token: code, - }) - if (result.success) { - setAccount(result.data.account) - setScratchCodes(result.data.scratchCodes) - setLoadingStatus([true, 'settingsSaved', true, true]) - } else setLoadingStatus([true, 'backendError', true, false]) - setEnable(false) - setCode('') - } - - // Figure out what title to use - let titleText = account.mfaEnabled ? t('mfaEnabled') : t('mfaDisabled') - if (enable) titleText = t('mfaSetup') - - return ( -
- {title ?

{titleText}

: null} - {enable ? ( - <> -
-
-
-

{enable.secret}

- {t('mfaAdd')} - {t('confirmWithMfa')} - setCode(evt.target.value)} - className="input w-64 m-auto text-4xl input-bordered input-lg flex flex-row text-center mb-8 tracking-widest" - type="text" - inputMode="numeric" - pattern="[0-9]{6}" - placeholder={t('000000')} - /> - - - ) : null} - {disable ? ( -
- -
{t('confirmWithPassword')}
- true} - /> -
- -
{t('confirmWithMfa')}
- -
- -
- ) : null} - {scratchCodes ? ( - <> -

{t('account:mfaScratchCodes')}

-

{t('account:mfaScratchCodesMsg1')}

-

{t('account:mfaScratchCodesMsg2')}

-
-
- {t('account:mfaScratchCodes')} - code + '\n').join('') - } - /> -
-
-              {scratchCodes.map((code) => code + '\n')}
-            
-
- - ) : ( -
- {account.mfaEnabled ? ( - disable ? null : ( - - ) - ) : enable ? null : ( -
- - -
{t('mfaTipTitle')}
-

{t('mfaTipMsg')}

-
-
- )} -
- )} - {!welcome && } -
- ) -} diff --git a/packages/react/components/Account/newsletter.mjs b/packages/react/components/Account/newsletter.mjs deleted file mode 100644 index bfd82d44a11..00000000000 --- a/packages/react/components/Account/newsletter.mjs +++ /dev/null @@ -1,107 +0,0 @@ -// Context -import { LoadingStatusContext } from 'shared/context/loading-status-context.mjs' -// Hooks -import { useState, useContext } from 'react' -import { useTranslation } from 'next-i18next' -import { useAccount } from 'shared/hooks/use-account.mjs' -import { useBackend } from 'shared/hooks/use-backend.mjs' -// Components -import { BackToAccountButton, Icons, welcomeSteps } from './shared.mjs' -import { ContinueButton } from 'shared/components/buttons/continue-button.mjs' -import { ListInput } from 'shared/components/inputs.mjs' -import { DynamicMdx } from 'shared/components/mdx/dynamic.mjs' -import { OkIcon, NoIcon } from 'shared/components/icons.mjs' -import { Popout } from 'shared/components/popout/index.mjs' -import { PageLink } from 'shared/components/link.mjs' - -export const ns = ['account', 'status', 'newsletter'] - -export const NewsletterSettings = ({ welcome = false, bare = false }) => { - // Hooks - const { account, setAccount } = useAccount() - const backend = useBackend() - const { t, i18n } = useTranslation(ns) - const { setLoadingStatus } = useContext(LoadingStatusContext) - // State - const [selection, setSelection] = useState(account?.newsletter ? 'yes' : 'no') - - // Helper method to update account - const update = async (val) => { - if (val !== selection) { - setLoadingStatus([true, 'processingUpdate']) - const result = await backend.updateAccount({ newsletter: val === 'yes' ? true : false }) - if (result.success) { - setAccount(result.data.account) - setSelection(val) - setLoadingStatus([true, 'settingsSaved', true, true]) - } else setLoadingStatus([true, 'backendError', true, true]) - } - } - - // Next step for onboarding - const nextHref = - welcomeSteps[account?.control].length > 2 - ? '/welcome/' + welcomeSteps[account?.control][2] - : '/docs/about/guide' - - return ( -
- ({ - val, - label: ( -
- {t(val === 'yes' ? 'newsletterYes' : 'noThanks')} - {val === 'yes' ? ( - - ) : ( - - )} -
- ), - desc: t(val === 'yes' ? 'newsletterYesd' : 'newsletterNod'), - }))} - current={selection} - update={update} - docs={} - /> - {welcome ? ( - <> - - {welcomeSteps[account?.control].length > 0 ? ( - <> - - - 2 / {welcomeSteps[account?.control].length} - - - - ) : null} - - ) : bare ? null : ( - - )} - -

{t('newsletter:subscribePs')}

-

- -

-
-
- ) -} - -export default NewsletterSettings diff --git a/packages/react/components/Account/overview.mjs b/packages/react/components/Account/overview.mjs deleted file mode 100644 index 045fd7ebe5c..00000000000 --- a/packages/react/components/Account/overview.mjs +++ /dev/null @@ -1,3 +0,0 @@ -import { AccountLinks } from './links.mjs' - -export const AccountOverview = ({ app }) => diff --git a/packages/react/components/Account/password.mjs b/packages/react/components/Account/password.mjs deleted file mode 100644 index d41958f10f1..00000000000 --- a/packages/react/components/Account/password.mjs +++ /dev/null @@ -1,66 +0,0 @@ -// Dependencies -import { useState, useContext } from 'react' -import { useTranslation } from 'next-i18next' -// Context -import { LoadingStatusContext } from 'shared/context/loading-status-context.mjs' -// Hooks -import { useAccount } from 'shared/hooks/use-account.mjs' -import { useBackend } from 'shared/hooks/use-backend.mjs' -// Components -import Link from 'next/link' -import { BackToAccountButton } from './shared.mjs' -import { SaveSettingsButton } from 'shared/components/buttons/save-settings-button.mjs' -import { Popout } from 'shared/components/popout/index.mjs' -import { RightIcon } from 'shared/components/icons.mjs' -import { PasswordInput } from 'shared/components/inputs.mjs' -import { DynamicMdx } from 'shared/components/mdx/dynamic.mjs' - -export const ns = ['account', 'status'] - -export const PasswordSettings = ({ welcome = false }) => { - // Hooks - const { account, setAccount } = useAccount() - const backend = useBackend() - const { t, i18n } = useTranslation(ns) - const { setLoadingStatus } = useContext(LoadingStatusContext) - - // State - const [password, setPassword] = useState('') - - // Helper method to save password to account - const save = async () => { - setLoadingStatus([true, 'processingUpdate']) - const result = await backend.updateAccount({ password }) - if (result.success) { - setAccount(result.data.account) - setLoadingStatus([true, 'settingsSaved', true, true]) - } else setLoadingStatus([true, 'backendError', true, false]) - } - - return ( -
- val.length > 0} - placeholder={t('passwordTitle')} - docs={} - /> - - {!welcome && } - {!account.mfaEnabled && ( - -
{t('mfaTipTitle')}
-

{t('mfaTipMsg')}

-

- - {t('mfa')} - -

-
- )} -
- ) -} diff --git a/packages/react/components/Account/patterns.mjs b/packages/react/components/Account/patterns.mjs deleted file mode 100644 index d55b7cb0009..00000000000 --- a/packages/react/components/Account/patterns.mjs +++ /dev/null @@ -1,725 +0,0 @@ -// Dependencies -import { useState, useEffect, useContext } from 'react' -import { useTranslation } from 'next-i18next' -import { - capitalize, - shortDate, - cloudflareImageUrl, - horFlexClasses, - newPatternUrl, -} from 'shared/utils.mjs' -import orderBy from 'lodash.orderby' -import { freeSewingConfig as conf, controlLevels } from 'shared/config/freesewing.config.mjs' -// Context -import { LoadingStatusContext } from 'shared/context/loading-status-context.mjs' -// Hooks -import { useAccount } from 'shared/hooks/use-account.mjs' -import { useBackend } from 'shared/hooks/use-backend.mjs' -import { useRouter } from 'next/router' -// Context -import { ModalContext } from 'shared/context/modal-context.mjs' -// Components -import { PageLink, Link, AnchorLink } from 'shared/components/link.mjs' -import { BackToAccountButton } from './shared.mjs' -import { Popout } from 'shared/components/popout/index.mjs' -import { - StringInput, - MarkdownInput, - PassiveImageInput, - ListInput, -} from 'shared/components/inputs.mjs' -import { - OkIcon, - NoIcon, - TrashIcon, - PlusIcon, - CameraIcon, - EditIcon, - ResetIcon, - RightIcon, - UploadIcon, - FreeSewingIcon, - CloneIcon, - BoolYesIcon, - BoolNoIcon, - LockIcon, - PatternIcon, - BookmarkIcon, -} from 'shared/components/icons.mjs' -import { DisplayRow } from './shared.mjs' -import { ModalWrapper } from 'shared/components/wrappers/modal.mjs' -import { Mdx } from 'shared/components/mdx/dynamic.mjs' -import Timeago from 'react-timeago' -import { TableWrapper } from 'shared/components/wrappers/table.mjs' -import { PatternReactPreview } from 'shared/components/pattern/preview.mjs' -import { Lightbox } from 'shared/components/lightbox.mjs' -import { DynamicMdx } from 'shared/components/mdx/dynamic.mjs' - -export const ns = ['account', 'patterns', 'status'] - -export const ShowPattern = ({ id }) => { - // Hooks - const { setLoadingStatus } = useContext(LoadingStatusContext) - const backend = useBackend() - const { t, i18n } = useTranslation(ns) - const { account } = useAccount() - - // State - const [pattern, setPattern] = useState() - const [isOwn, setIsOwn] = useState(false) - - // Effect - useEffect(() => { - const getPattern = async () => { - setLoadingStatus([true, t('backendLoadingStarted')]) - let result - try { - result = await backend.getPattern(id) - if (result.success) { - setPattern(result.data.pattern) - if (result.data.pattern.userId === account.userId) setIsOwn(true) - setLoadingStatus([true, 'backendLoadingCompleted', true, true]) - } else { - result = await backend.getPublicPattern(id) - if (result.success) { - setPattern({ ...result.data, public: true }) - setLoadingStatus([true, 'backendLoadingCompleted', true, true]) - } else setLoadingStatus([true, 'backendError', true, false]) - } - } catch (err) { - console.log(err) - setLoadingStatus([true, 'backendError', true, false]) - } - } - if (id) getPattern() - }, [id]) - - const bookmarkPattern = async () => { - setLoadingStatus([true, 'creatingBookmark']) - const result = await backend.createBookmark({ - type: 'pattern', - title: pattern.name, - url: `/patterns?id=${pattern.id}`, - }) - if (result.success) { - const id = result.data.bookmark.id - setLoadingStatus([ - true, - <> - {t('status:bookmarkCreated')} [#{id}] - , - true, - true, - ]) - } else setLoadingStatus([true, 'backendError', true, false]) - } - - if (!pattern) return

loading

- - return ( - <> -
-
- - - -
-
- {pattern.name} - {pattern.id} - - - - - - - - - | - {shortDate(i18n.language, pattern.createdAt, false)} - - - - | - {shortDate(i18n.language, pattern.updatedAt, false)} - - - {pattern.public ? : } - - - - - - - {account.id ? ( - - ) : null} - - {t('clonePattern')} - - {isOwn ? ( - <> - -

{t('account:ownPublicPattern')}

- - {t('account:privateView')} - -
- - ) : null} -
-
-

{t('account:notes')}

- {isOwn ? 'is own' : 'is not own'} - - - ) -} - -export const Pattern = ({ id }) => { - // Hooks - const { account, control } = useAccount() - const { setLoadingStatus } = useContext(LoadingStatusContext) - const backend = useBackend() - const { t, i18n } = useTranslation(ns) - - // Context - const { setModal } = useContext(ModalContext) - - const [edit, setEdit] = useState(false) - const [pattern, setPattern] = useState() - // Set fields for editing - const [name, setName] = useState(pattern?.name) - const [image, setImage] = useState(pattern?.image) - const [isPublic, setIsPublic] = useState(pattern?.public ? true : false) - const [notes, setNotes] = useState(pattern?.notes || '') - - // Effect - useEffect(() => { - const getPattern = async () => { - setLoadingStatus([true, t('backendLoadingStarted')]) - const result = await backend.getPattern(id) - if (result.success) { - setPattern(result.data.pattern) - setName(result.data.pattern.name) - setImage(result.data.pattern.image) - setIsPublic(result.data.pattern.public ? true : false) - setNotes(result.data.pattern.notes) - setLoadingStatus([true, 'backendLoadingCompleted', true, true]) - } else setLoadingStatus([true, 'backendError', true, false]) - } - if (id) getPattern() - }, [id]) - - const save = async () => { - setLoadingStatus([true, 'gatheringInfo']) - // Compile data - const data = {} - if (name || name !== pattern.name) data.name = name - if (image || image !== pattern.image) data.img = image - if (notes || notes !== pattern.notes) data.notes = notes - if ([true, false].includes(isPublic) && isPublic !== pattern.public) data.public = isPublic - setLoadingStatus([true, 'savingPattern']) - const result = await backend.updatePattern(pattern.id, data) - if (result.success) { - setPattern(result.data.pattern) - setEdit(false) - setLoadingStatus([true, 'nailedIt', true, true]) - } else setLoadingStatus([true, 'backendError', true, false]) - } - - if (!pattern) return null - - const heading = ( - <> -
-
- -
-
- {account.control > 3 && pattern?.public ? ( - - ) : ( - - )} - - {pattern.userId === account.id && ( - <> - {edit ? ( - <> - - - - ) : ( - <> - - {t('updatePattern')} - - - {t('clonePattern')} - - - - )} - - )} -
-
-
- - ) - - if (!edit) - return ( -
- {heading} - {pattern.name} - {control >= controlLevels.sets.notes && ( - - - - )} - {control >= controlLevels.patterns.public && ( - <> - - {pattern.public ? : } - - {pattern.public && ( - - - - )} - - )} - {control >= controlLevels.sets.createdAt && ( - - - | - {shortDate(i18n.language, pattern.createdAt, false)} - - )} - {control >= controlLevels.patterns.updatedAt && ( - - - | - {shortDate(i18n.language, pattern.updatedAt, false)} - - )} - {control >= controlLevels.patterns.id && ( - {pattern.id} - )} - -

{t('account:ownPrivatePattern')}

- - - {t('account:publicView')} - -
-
- ) - - return ( -
- {heading} -
    -
  • - -
  • - {account.control >= conf.account.sets.img ? ( -
  • - -
  • - ) : null} - {['public', 'units', 'notes'].map((id) => - account.control >= conf.account.sets[id] ? ( -
  • - -
  • - ) : null - )} -
- - {/* Name is always shown */} - - val && val.length > 0} - docs={} - /> - - {/* img: Control level determines whether or not to show this */} - - {account.control >= conf.account.sets.img ? ( - val.length > 0} - docs={} - /> - ) : null} - - {/* public: Control level determines whether or not to show this */} - - {account.control >= conf.account.patterns.public ? ( - - {t('publicPattern')} - -
- ), - desc: t('publicPatternDesc'), - }, - { - val: false, - label: ( -
- {t('privatePattern')} - -
- ), - desc: t('privatePatternDesc'), - }, - ]} - current={isPublic} - docs={} - /> - ) : null} - - {/* notes: Control level determines whether or not to show this */} - - {account.control >= conf.account.patterns.notes ? ( - } - /> - ) : null} - -
- ) -} - -export const PatternCard = ({ - pattern, - href = false, - onClick = false, - useA = false, - size = 'md', -}) => { - const sizes = { - lg: 96, - md: 52, - sm: 36, - xs: 20, - } - const s = sizes[size] - - const wrapperProps = { - className: `bg-base-300 w-full mb-2 mx-auto flex flex-col items-start text-center justify-center rounded shadow py-4 h-${s} w-${s}`, - style: { - backgroundImage: `url(${cloudflareImageUrl({ type: 'w1000', id: pattern.img })})`, - backgroundSize: 'cover', - backgroundRepeat: 'no-repeat', - backgroundPosition: '50%', - }, - } - if (pattern.img === 'default-avatar') wrapperProps.style.backgroundPosition = 'bottom right' - - const inner = null - - // Is it a button with an onClick handler? - if (onClick) - return ( - - ) - - // Returns a link to an internal page - if (href && !useA) - return ( - - {inner} - - ) - - // Returns a link to an external page - if (href && useA) - return ( - - {inner} - - ) - - // Returns a div - return
{inner}
-} - -// Component to show the sort header in the pattern table -const SortButton = ({ field, label, order, orderAsc, updateOrder }) => ( - -) - -// Component for the account/patterns page -export const Patterns = () => { - const router = useRouter() - const { locale } = router - - // Hooks - const backend = useBackend() - const { t } = useTranslation(ns) - const { setLoadingStatus, LoadingProgress } = useContext(LoadingStatusContext) - - // State - const [patterns, setPatterns] = useState([]) - const [selected, setSelected] = useState({}) - const [refresh, setRefresh] = useState(0) - const [order, setOrder] = useState('id') - const [orderAsc, setOrderAsc] = useState(true) - - // Helper var to see how many are selected - const selCount = Object.keys(selected).length - - // Effects - useEffect(() => { - const getPatterns = async () => { - const result = await backend.getPatterns() - if (result.success) setPatterns(result.data.patterns) - } - getPatterns() - }, [refresh]) - - // Helper method to toggle single selection - const toggleSelect = (id) => { - const newSelected = { ...selected } - if (newSelected[id]) delete newSelected[id] - else newSelected[id] = 1 - setSelected(newSelected) - } - - // Helper method to toggle select all - const toggleSelectAll = () => { - if (selCount === patterns.length) setSelected({}) - else { - const newSelected = {} - for (const pattern of patterns) newSelected[pattern.id] = 1 - setSelected(newSelected) - } - } - - // Helper to delete one or more patterns - const removeSelectedPatterns = async () => { - let i = 0 - for (const pattern in selected) { - i++ - await backend.removePattern(pattern) - setLoadingStatus([ - true, - , - ]) - } - setSelected({}) - setRefresh(refresh + 1) - setLoadingStatus([true, 'nailedIt', true, true]) - } - - // Helper method to update the order state - const updateOrder = (field) => { - if (order !== field) { - setOrder(field) - setOrderAsc(true) - } else setOrderAsc(!orderAsc) - } - - return ( -
-

- - - {t('patternNew')} - -

- - - - - - - - - - - - - - - - {orderBy(patterns, order, orderAsc ? 'asc' : 'desc').map((pattern, i) => ( - - - - - - - - - - ))} - -
- - - - {t('account:img')} - - - - - - - -
- toggleSelect(pattern.id)} - /> - {pattern.id} - - - - - - - {shortDate(locale, pattern.createdAt, false)} - - {pattern.public ? : } -
-
- -
- ) -} diff --git a/packages/react/components/Account/platform.mjs b/packages/react/components/Account/platform.mjs deleted file mode 100644 index 8c0609f0b06..00000000000 --- a/packages/react/components/Account/platform.mjs +++ /dev/null @@ -1,55 +0,0 @@ -// Context -import { LoadingStatusContext } from 'shared/context/loading-status-context.mjs' -// Hooks -import { useState, useContext } from 'react' -import { useTranslation } from 'next-i18next' -import { useAccount } from 'shared/hooks/use-account.mjs' -import { useBackend } from 'shared/hooks/use-backend.mjs' -// Components -import { BackToAccountButton } from './shared.mjs' -import { SaveSettingsButton } from 'shared/components/buttons/save-settings-button.mjs' -import { StringInput } from 'shared/components/inputs.mjs' -import { DynamicMdx } from 'shared/components/mdx/dynamic.mjs' - -export const ns = ['account', 'status'] - -export const PlatformSettings = ({ platform }) => { - // Hooks - const { account, setAccount } = useAccount() - const backend = useBackend() - const { t, i18n } = useTranslation(ns) - const { setLoadingStatus } = useContext(LoadingStatusContext) - - // State - const [platformId, setPlatformId] = useState(account.data[platform] || '') - - // Helper method to save changes - const save = async () => { - setLoadingStatus([true, 'processingUpdate']) - const data = { data: {} } - data.data[platform] = platformId - const result = await backend.updateAccount(data) - if (result.success) { - setAccount(result.data.account) - setLoadingStatus([true, 'settingsSaved', true, true]) - } else setLoadingStatus([true, 'backendError', true, false]) - } - - return ( -
- val.length > 0} - placeholder={'joostdecock'} - docs={} - /> - - -
- ) -} diff --git a/packages/react/components/Account/reload.mjs b/packages/react/components/Account/reload.mjs deleted file mode 100644 index d82b43e7188..00000000000 --- a/packages/react/components/Account/reload.mjs +++ /dev/null @@ -1,40 +0,0 @@ -// Context -import { LoadingStatusContext } from 'shared/context/loading-status-context.mjs' -// Hooks -import { useTranslation } from 'next-i18next' -import { useContext } from 'react' -import { useAccount } from 'shared/hooks/use-account.mjs' -import { useBackend } from 'shared/hooks/use-backend.mjs' -// Components -import { BackToAccountButton } from './shared.mjs' - -export const ns = ['account', 'status'] - -export const ReloadAccount = ({ title = false }) => { - // Hooks - const { setAccount } = useAccount() - const backend = useBackend() - const { t } = useTranslation(ns) - const { setLoadingStatus } = useContext(LoadingStatusContext) - - // Helper method to reload account - const reload = async () => { - setLoadingStatus([true, 'processingUpdate']) - const result = await backend.reloadAccount() - if (result.success) { - setAccount(result.data.account) - setLoadingStatus([true, 'nailedIt', true, true]) - } else setLoadingStatus([true, 'backendError', true, false]) - } - - return ( -
- {title ?

{t('reloadMsg1')}

: null} -

{t('reloadMsg2')}

- - -
- ) -} diff --git a/packages/react/components/Account/remove.mjs b/packages/react/components/Account/remove.mjs deleted file mode 100644 index b22536c21db..00000000000 --- a/packages/react/components/Account/remove.mjs +++ /dev/null @@ -1,42 +0,0 @@ -// Context -import { LoadingStatusContext } from 'shared/context/loading-status-context.mjs' -// Hooks -import { useContext } from 'react' -import { useTranslation } from 'next-i18next' -import { useAccount } from 'shared/hooks/use-account.mjs' -import { useBackend } from 'shared/hooks/use-backend.mjs' -// Components -import { BackToAccountButton } from './shared.mjs' -import { Popout } from 'shared/components/popout/index.mjs' - -export const ns = ['account', 'status'] - -export const RemoveAccount = () => { - // Hooks - const { signOut } = useAccount() - const backend = useBackend() - const { t } = useTranslation(ns) - const { setLoadingStatus } = useContext(LoadingStatusContext) - - // Helper method to export account - const removeAccount = async () => { - setLoadingStatus([true, 'processingUpdate']) - const result = await backend.removeAccount() - if (result.success) { - setLoadingStatus([true, 'nailedIt', true, true]) - signOut() - } else setLoadingStatus([true, 'backendError', true, false]) - } - - return ( -
- -

{t('noWayBack')}

- -
- -
- ) -} diff --git a/packages/react/components/Account/restrict.mjs b/packages/react/components/Account/restrict.mjs deleted file mode 100644 index 255b5d64aee..00000000000 --- a/packages/react/components/Account/restrict.mjs +++ /dev/null @@ -1,43 +0,0 @@ -// Context -import { LoadingStatusContext } from 'shared/context/loading-status-context.mjs' -// Hooks -import { useContext } from 'react' -import { useTranslation } from 'next-i18next' -import { useAccount } from 'shared/hooks/use-account.mjs' -import { useBackend } from 'shared/hooks/use-backend.mjs' -// Components -import { BackToAccountButton } from './shared.mjs' -import { Popout } from 'shared/components/popout/index.mjs' - -export const ns = ['account', 'status'] - -export const RestrictAccount = () => { - // Hooks - const { signOut } = useAccount() - const backend = useBackend() - const { t } = useTranslation(ns) - const { setLoadingStatus } = useContext(LoadingStatusContext) - - // Helper method to export account - const restrictAccount = async () => { - setLoadingStatus([true, 'processingUpdate']) - const result = await backend.restrictAccount() - if (result.success) { - setLoadingStatus([true, 'nailedIt', true, true]) - signOut() - } else setLoadingStatus([true, 'backendError', true, false]) - } - - return ( -
- -
{t('proceedWithCaution')}
-

{t('restrictWarning')}

- -
- -
- ) -} diff --git a/packages/react/components/Account/role.mjs b/packages/react/components/Account/role.mjs deleted file mode 100644 index e0391325c08..00000000000 --- a/packages/react/components/Account/role.mjs +++ /dev/null @@ -1,28 +0,0 @@ -export const ns = ['roles'] - -const colors = { - user: 'primary', - curator: 'secondary', - bughunter: 'accent', - support: 'warning', - admin: 'error', -} - -export const AccountRole = ({ role }) => { - const color = colors[role] - - return ( - - - role - - - {role} - - - ) -} diff --git a/packages/react/components/Account/shared.mjs b/packages/react/components/Account/shared.mjs index 0fbc7bf1096..a5a9d9ccf80 100644 --- a/packages/react/components/Account/shared.mjs +++ b/packages/react/components/Account/shared.mjs @@ -19,126 +19,3 @@ export const welcomeSteps = { 4: ['', 'newsletter', 'units', 'compare', 'username', 'bio', 'img'], 5: [''], } - -/* -import { Spinner } from 'shared/components/spinner.mjs' -import Link from 'next/link' -import { useTranslation } from 'next-i18next' -import { - CogIcon, - FingerprintIcon as ControlIcon, - NewsletterIcon, - UnitsIcon, - CompareIcon, - LabelIcon, - BioIcon, - UserIcon, - LeftIcon, - OkIcon, - NoIcon, -} from 'shared/components/icons.mjs' - -const btnClasses = { - dflt: - 'btn w-full mt-2 btn-secondary ' + - 'flex flex-row flex-nowrap items-center gap-4 py-4 h-auto ' + - 'border border-secondary justify-start text-left bg-opacity-30', - active: - 'btn-ghost bg-secondary hover:bg-secondary ' + 'hover:bg-opacity-30 hover:border-secondary', - inactive: - 'hover:bg-opacity-20 hover:bg-secondary btn-ghost ' + - 'border border-secondary hover:border hover:border-secondary', -} - -export const NumberBullet = ({ nr, color = 'secondary' }) => ( - - {nr} - -) - -export const BackToAccountButton = ({ loading = false }) => { - const { t } = useTranslation(['account']) - - return ( - - - {loading ? : } - {t('yourAccount')} - - - ) -} - -export const Choice = ({ - val, - update, - current, - children, - bool = false, - boolChoices = { - yes: , - no: , - }, -}) => { - const active = val === current - - return ( - - ) -} - -export const DoneIcon = ({ href }) => ( - - - -) -export const TodoIcon = ({ href }) => ( - - - -) - -const TopicIcon = (props) => { - const Icon = - props.href === '' || props.href === 'control' - ? ControlIcon - : icons[props.href] - ? icons[props.href] - : CogIcon - - return -} - -const DoingIcon = ({ href }) => - -export const Icons = ({ done = [], todo = [], current = '' }) => ( -
- {done.map((href) => ( - - ))} - - {todo.map((href) => ( - - ))} -
-) - -const icons = { - newsletter: NewsletterIcon, - units: UnitsIcon, - compare: CompareIcon, - username: LabelIcon, - bio: BioIcon, - img: UserIcon, -} - - -*/ diff --git a/packages/react/components/Account/status.mjs b/packages/react/components/Account/status.mjs deleted file mode 100644 index cfc9d623d16..00000000000 --- a/packages/react/components/Account/status.mjs +++ /dev/null @@ -1,22 +0,0 @@ -import { freeSewingConfig } from 'shared/config/freesewing.config.mjs' - -export const ns = ['status'] - -export const AccountStatus = ({ status }) => { - const { name, color } = freeSewingConfig.statuses[status] - - return ( - - - status - - - {name} - - - ) -} diff --git a/packages/react/components/Account/username.mjs b/packages/react/components/Account/username.mjs deleted file mode 100644 index 13bf0f57c52..00000000000 --- a/packages/react/components/Account/username.mjs +++ /dev/null @@ -1,109 +0,0 @@ -// Context -import { LoadingStatusContext } from 'shared/context/loading-status-context.mjs' -// Hooks -import { useState, useContext } from 'react' -import { useTranslation } from 'next-i18next' -import { useAccount } from 'shared/hooks/use-account.mjs' -import { useBackend } from 'shared/hooks/use-backend.mjs' -// Components -import { Icons, welcomeSteps, BackToAccountButton } from './shared.mjs' -import { OkIcon, NoIcon } from 'shared/components/icons.mjs' -import { ContinueButton } from 'shared/components/buttons/continue-button.mjs' -import { StringInput } from 'shared/components/inputs.mjs' -import { DynamicMdx } from 'shared/components/mdx/dynamic.mjs' - -export const ns = ['account', 'status'] - -export const UsernameSettings = ({ welcome = false }) => { - // Hooks - const { account, setAccount } = useAccount() - const backend = useBackend() - const { setLoadingStatus } = useContext(LoadingStatusContext) - const { t, i18n } = useTranslation(ns) - const [username, setUsername] = useState(account.username) - const [available, setAvailable] = useState(true) - - const update = async (value) => { - if (value !== username) { - setUsername(value) - const result = await backend.isUsernameAvailable(value) - if (result?.response?.response?.status === 404) setAvailable(true) - else setAvailable(false) - } - } - - const save = async () => { - setLoadingStatus([true, 'processingUpdate']) - const result = await backend.updateAccount({ username }) - if (result.success) { - setAccount(result.data.account) - setLoadingStatus([true, 'settingsSaved', true, true]) - } else setLoadingStatus([true, 'backendError', true, true]) - } - - const nextHref = - welcomeSteps[account.control].length > 5 - ? '/welcome/' + welcomeSteps[account.control][5] - : '/docs/about/guide' - - let btnClasses = 'btn mt-4 capitalize ' - if (welcome) btnClasses += 'w-64 btn-secondary' - else btnClasses += 'w-full btn-primary' - - return ( -
- available} - placeholder={'Sorcha Ni Dhubghaill'} - labelBL={ - - {available ? ( - <> - {t('usernameAvailable')} - - ) : ( - <> - {t('usernameNotAvailable')} - - )} - - } - docs={} - /> - - - {welcome ? ( - <> - - {welcomeSteps[account.control].length > 0 ? ( - <> - - - 5 / {welcomeSteps[account.control].length} - - - - ) : null} - - ) : ( - - )} -
- ) -} diff --git a/packages/react/components/Button/index.mjs b/packages/react/components/Button/index.mjs new file mode 100644 index 00000000000..ea2f31490c9 --- /dev/null +++ b/packages/react/components/Button/index.mjs @@ -0,0 +1,30 @@ +import React from 'react' + +/** + * A button with an icon and a label. Common across our UI + * + * @param {object} props - All React props + * @param {string} title - The button title + * @param {string} className - Any EXTRA classes to add + * @param {string} color - The main button color + * @param {string} href - Set this to make it a link + */ +export const IconButton = ({ + title = '', + className = '', + onClick = false, + href = false, + color = 'primary', + children = [], + btnProps = {}, +}) => { + const allProps = { + className: `flex flex-row gap-2 lg:gap-12 items-center justify-between w-full lg:w-auto daisy-btn daisy-btn-${color} capitalize my-2 ${className}`, + title: title, + ...btnProps, + } + if (onClick) allProps.onClick = onClick + else if (href) allProps.href = href + + return onClick ? : {children} +} diff --git a/packages/react/package.json b/packages/react/package.json index 70c6def4ee4..31e24d6edc7 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -29,6 +29,7 @@ "./xray": "./src/pattern-xray/index.mjs", "./components/Account": "./components/Account/index.mjs", "./components/Breadcrumbs": "./components/Breadcrumbs/index.mjs", + "./components/Button": "./components/Button/index.mjs", "./components/Control": "./components/Control/index.mjs", "./components/CopyToClipboard": "./components/CopyToClipboard/index.mjs", "./components/Docusaurus": "./components/Docusaurus/index.mjs", diff --git a/sites/org/docs/account/actions/import/index.mdx b/sites/org/docs/account/actions/import/index.mdx index b32fd5e5e99..0fe1062f71b 100644 --- a/sites/org/docs/account/actions/import/index.mdx +++ b/sites/org/docs/account/actions/import/index.mdx @@ -5,11 +5,20 @@ sidebar_position: 43 import { DocusaurusDoc } from '@freesewing/react/components/Docusaurus' import { RoleBlock } from '@freesewing/react/components/Role' -import { Bio } from '@freesewing/react/components/Account' -import Link from '@docusaurus/Link' +import { ImportSet } from '@freesewing/react/components/Account' - + +This page allows you to import data into your FreeSewing account. + +Currently, we support importing the following types of data: + +- Measurements Sets + +## Import Measurements Sets {#sets} + + + diff --git a/sites/org/docs/account/actions/reload/index.mdx b/sites/org/docs/account/actions/reload/index.mdx index 9cf4a2219ba..260acac1f6e 100644 --- a/sites/org/docs/account/actions/reload/index.mdx +++ b/sites/org/docs/account/actions/reload/index.mdx @@ -5,11 +5,10 @@ sidebar_position: 43 import { DocusaurusDoc } from '@freesewing/react/components/Docusaurus' import { RoleBlock } from '@freesewing/react/components/Role' -import { Bio } from '@freesewing/react/components/Account' -import Link from '@docusaurus/Link' +import { Reload } from '@freesewing/react/components/Account' - + diff --git a/sites/org/docs/account/actions/remove/index.mdx b/sites/org/docs/account/actions/remove/index.mdx index 01784880740..04c23aa36fa 100644 --- a/sites/org/docs/account/actions/remove/index.mdx +++ b/sites/org/docs/account/actions/remove/index.mdx @@ -5,11 +5,11 @@ sidebar_position: 43 import { DocusaurusDoc } from '@freesewing/react/components/Docusaurus' import { RoleBlock } from '@freesewing/react/components/Role' -import { Bio } from '@freesewing/react/components/Account' +import { Remove } from '@freesewing/react/components/Account' import Link from '@docusaurus/Link' - + diff --git a/sites/org/docs/account/actions/restrict/index.mdx b/sites/org/docs/account/actions/restrict/index.mdx index 118cbdb3712..baad560cfa6 100644 --- a/sites/org/docs/account/actions/restrict/index.mdx +++ b/sites/org/docs/account/actions/restrict/index.mdx @@ -5,11 +5,11 @@ sidebar_position: 43 import { DocusaurusDoc } from '@freesewing/react/components/Docusaurus' import { RoleBlock } from '@freesewing/react/components/Role' -import { Bio } from '@freesewing/react/components/Account' +import { Restrict } from '@freesewing/react/components/Account' import Link from '@docusaurus/Link' - +