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.
+
+
+
+ Save Bio
+
+
+
+ {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}
-
- setId(null)}
- >
-
- {t('apikeys')}
-
-
-
- ) : 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)}
-
-
-
-
-
-
-
-
router.push('/account/apikeys')}
- >
-
- {t('apikeys')}
-
-
-
- {t('newApikey')}
-
-
-
- )
-}
-
-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}
- />
-
-
- {t('newApikey')}
-
-
- >
- )}
-
- )
-}
-
-// 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')}
-
-
-
- {selCount} {t('apikeys')}
-
-
-
- {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 }) => (
- setActiveTab(id)}
- >
- {t(id)}
-
-)
-
-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 ? (
-
- ) : (
-
setConsent1(!consent1)}
- >
- {t('clickHere')}
-
- )}
- {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={ }
- />
-
- {t('save')}
-
- >
- )}
-
-
- )
-}
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')}
-
- {t('export')}
-
-
-
- )
-}
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 ? (
-
- ) : null}
-
val.length > 0}
- docs={ }
- />
- {welcome ? (
- <>
-
- {t('save')}
-
-
- {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')}
- />
-
- {t('enableMfa')}
-
- >
- ) : null}
- {disable ? (
-
-
- {t('confirmWithPassword')}
- true}
- />
-
-
- {t('confirmWithMfa')}
-
-
-
- {t('disableMfa')}
-
-
- ) : 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 : (
-
setDisable(true)}>
- {t('disableMfa')}
-
- )
- ) : enable ? null : (
-
-
- {t('mfaSetup')}
-
-
- {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 ? (
-
-
- {t('bookmark')}
-
-
- ) : 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 ? (
-
- ) : (
-
- )}
-
- setModal(
-
-
-
- )
- }
- className={`btn btn-secondary btn-outline ${horFlexClasses}`}
- >
-
- {t('showImage')}
-
- {pattern.userId === account.id && (
- <>
- {edit ? (
- <>
-
setEdit(false)}
- className={`btn btn-primary btn-outline ${horFlexClasses}`}
- >
-
- {t('cancel')}
-
-
-
- {t('saveThing', { thing: t('account:pattern') })}
-
- >
- ) : (
- <>
-
-
{t('updatePattern')}
-
-
-
{t('clonePattern')}
-
-
setEdit(true)}
- className={`btn btn-primary ${horFlexClasses}`}
- >
- {t('editThing', { thing: t('account:patternMetadata') })}
-
- >
- )}
- >
- )}
-
-
-
- >
- )
-
- 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}
-
-
- {t('saveThing', { thing: t('account:pattern') })}
-
-
- )
-}
-
-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 (
-
- {inner}
-
- )
-
- // 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 }) => (
- updateOrder(field)}
- className="btn-link text-secondary flex flex-row gap-2 items-center decoration-0 no-underline"
- >
- {label}
- {order === field ? (
-
- ) : null}
-
-)
-
-// 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')}
-
-
-
- {selCount} {t('patterns')}
-
-
-
-
-
-
- )
-}
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')}
-
- {t('reload')}
-
-
-
- )
-}
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')}
-
- {t('remove')}
-
-
-
-
- )
-}
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')}
-
- {t('restrict')}
-
-
-
-
- )
-}
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 (
- update(val)}
- >
- {bool ? boolChoices[val] : }
- {children}
-
- )
-}
-
-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={ }
- />
-
-
- {available ? t('save') : t('usernameNotAvailable')}
-
-
-
- {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} : {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'
-
+