diff --git a/config/dependencies.yaml b/config/dependencies.yaml index 04ee761ae09..d450026d6be 100644 --- a/config/dependencies.yaml +++ b/config/dependencies.yaml @@ -132,6 +132,7 @@ react: luxon: "^3.5.0" nuqs: "^1.17.6" react-markdown: "^9.0.1" + tlds: "^1.255.0" use-local-storage-state: "19.1.0" use-session-storage-state: "^19.0.0" peer: diff --git a/config/exceptions.yaml b/config/exceptions.yaml index 0e6738f33da..a81fbd25e02 100644 --- a/config/exceptions.yaml +++ b/config/exceptions.yaml @@ -109,6 +109,7 @@ packageJson: # Hooks "./hooks/useAccount": "./hooks/useAccount/index.mjs" "./hooks/useBackend": "./hooks/useBackend/index.mjs" + "./hooks/useControl": "./hooks/useControl/index.mjs" "./hooks/useSelection": "./hooks/useSelection/index.mjs" # Lib "./lib/RestClient": "./lib/RestClient/index.mjs" diff --git a/package-lock.json b/package-lock.json index e5b5ba477b1..415e54cd436 100644 --- a/package-lock.json +++ b/package-lock.json @@ -61565,6 +61565,7 @@ } }, "packages/i18n": { + "name": "@freesewing/i18n", "version": "3.3.0-rc.1", "license": "MIT", "devDependencies": {}, @@ -62042,6 +62043,9 @@ "name": "@freesewing/utils", "version": "3.3.0-rc.1", "license": "MIT", + "dependencies": { + "tlds": "^1.255.0" + }, "devDependencies": {}, "engines": { "node": ">= 20" @@ -62051,6 +62055,14 @@ "url": "https://freesewing.org/patrons/join" } }, + "packages/utils/node_modules/tlds": { + "version": "1.255.0", + "resolved": "https://registry.npmjs.org/tlds/-/tlds-1.255.0.tgz", + "integrity": "sha512-tcwMRIioTcF/FcxLev8MJWxCp+GUALRhFEqbDoZrnowmKSGqPrl5pqS+Sut2m8BgJ6S4FExCSSpGffZ0Tks6Aw==", + "bin": { + "tlds": "bin.js" + } + }, "plugins/core-plugins": { "name": "@freesewing/core-plugins", "version": "3.3.0-rc.1", diff --git a/packages/config/src/control.mjs b/packages/config/src/control.mjs index 2dc45ca7b77..1898719b7d9 100644 --- a/packages/config/src/control.mjs +++ b/packages/config/src/control.mjs @@ -26,7 +26,6 @@ const account = { bookmarks: 2, sets: 1, patterns: 1, - apikeys: 4, }, info: { username: 2, @@ -145,3 +144,26 @@ export const control = { views: editor.views, }, } + +export const controlDesc = { + 1: { + title: `Keep it as simple as possible`, + desc: `Hides all but the most crucial features.`, + }, + 2: { + title: `Keep it simple, but not too simple`, + desc: `Hides the majority of features.`, + }, + 3: { + title: `Balance simplicity with power`, + desc: `Reveals the majority of features, but not all.`, + }, + 4: { + title: `Give me all powers, but keep me safe`, + desc: `Reveals all features, keeps handrails and safety checks`, + }, + 5: { + title: `Get out of my way`, + desc: `Reveals all features, removes handrails and safety checks`, + }, +} diff --git a/packages/config/src/index.mjs b/packages/config/src/index.mjs index cb7925d0d99..271db7e1f39 100644 --- a/packages/config/src/index.mjs +++ b/packages/config/src/index.mjs @@ -1,5 +1,5 @@ import { cloudflare } from './cloudflare.mjs' -import { control } from './control.mjs' +import { control, controlDesc } from './control.mjs' import { logoPath } from './logo.mjs' import { measurements, degreeMeasurements, isDegreeMeasurement } from './measurements.mjs' import { sewingTechniques } from './sewing.mjs' @@ -14,6 +14,7 @@ export { apikeyLevels, cloudflare, control, + controlDesc, logoPath, measurements, degreeMeasurements, diff --git a/packages/react/components/Account/Avatar.mjs b/packages/react/components/Account/Avatar.mjs new file mode 100644 index 00000000000..c590a167d49 --- /dev/null +++ b/packages/react/components/Account/Avatar.mjs @@ -0,0 +1,103 @@ +// Dependencies +import { welcomeSteps } from './shared.mjs' +import { cloudflareImageUrl } from '@freesewing/utils' + +// 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 { PassiveImageInput } from '@freesewing/react/components/Input' + +/* + * 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 Avatar = ({ welcome = false, Link = false }) => { + if (!Link) Link = WebLink + + // Hooks + const { account, setAccount } = useAccount() + const backend = useBackend() + + // State + const [img, setImg] = useState('') + + // Context + const { setLoadingStatus } = useContext(LoadingStatusContext) + + // Save handler + const save = async () => { + setLoadingStatus([true, 'Uploading image']) + const [status, body] = await backend.updateAccount({ img }) + if (status === 200 && body.result === 'success') { + setAccount(body.account) + setLoadingStatus([true, 'Avatar saved', true, true]) + } else setLoadingStatus([true, 'Failed to save avatar image. Please report this', true, false]) + } + + // Next page in welcome flow + const nextHref = '/docs/about/guide' + + return ( +
+ {!welcome || img !== false ? ( + img + ) : null} + val.length > 0} + /> + {welcome ? ( + <> + + + {welcomeSteps[account.control].length > 0 ? ( + <> + + + 7 / {welcomeSteps[account.control].length} + + + + ) : null} + + ) : ( + <> +

+ +

+ + )} +
+ ) +} diff --git a/packages/react/components/Account/Bio.mjs b/packages/react/components/Account/Bio.mjs new file mode 100644 index 00000000000..f7c35a107c5 --- /dev/null +++ b/packages/react/components/Account/Bio.mjs @@ -0,0 +1,85 @@ +// Dependencies +import { welcomeSteps } from './shared.mjs' + +// 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 { MarkdownInput } from '@freesewing/react/components/Input' + +/* + * 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 Bio = ({ welcome = false, Link = false }) => { + if (!Link) Link = WebLink + + // 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} +
+ ) +} diff --git a/packages/react/components/Account/Bookmarks.mjs b/packages/react/components/Account/Bookmarks.mjs index bb5cc65a750..843032f75a0 100644 --- a/packages/react/components/Account/Bookmarks.mjs +++ b/packages/react/components/Account/Bookmarks.mjs @@ -88,7 +88,7 @@ export const Bookmarks = () => { for (const type in types) perType[type] = bookmarks.filter((b) => b.type === type) return ( -
+

+

+ + )} +
+ ) +} diff --git a/packages/react/components/Account/Github.mjs b/packages/react/components/Account/Github.mjs new file mode 100644 index 00000000000..a11896876f1 --- /dev/null +++ b/packages/react/components/Account/Github.mjs @@ -0,0 +1,61 @@ +// 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 { SaveIcon } from '@freesewing/react/components/Icon' +import { StringInput } from '@freesewing/react/components/Input' + +/* + * Component for the account/social/github page + */ +export const Github = () => { + // Hooks + const { account, setAccount } = useAccount() + const backend = useBackend() + 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, 'Saving bio']) + const [status, body] = await backend.updateAccount({ data: { githubUsername, githubEmail } }) + if (status === 200 && body.result === 'success') { + setAccount(body.account) + setLoadingStatus([true, 'GitHub info updated', true, true]) + } else setLoadingStatus([true, 'Something went wrong. Please report this', true, true]) + } + + return ( +
+ val.length > 0} + placeholder={'joost@joost.at'} + /> + val.length > 0} + placeholder={'joostdecock'} + /> +

+ +

+
+ ) +} diff --git a/packages/react/components/Account/Instagram.mjs b/packages/react/components/Account/Instagram.mjs new file mode 100644 index 00000000000..54f85435435 --- /dev/null +++ b/packages/react/components/Account/Instagram.mjs @@ -0,0 +1,61 @@ +// 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 { SaveIcon } from '@freesewing/react/components/Icon' +import { StringInput } from '@freesewing/react/components/Input' + +/* + * Component for the account/social/github page + */ +export const Instagram = () => { + // Hooks + const { account, setAccount } = useAccount() + const backend = useBackend() + 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, 'Saving bio']) + const [status, body] = await backend.updateAccount({ data: { githubUsername, githubEmail } }) + if (status === 200 && body.result === 'success') { + setAccount(body.account) + setLoadingStatus([true, 'GitHub info updated', true, true]) + } else setLoadingStatus([true, 'Something went wrong. Please report this', true, true]) + } + + return ( +
+ val.length > 0} + placeholder={'joost@joost.at'} + /> + val.length > 0} + placeholder={'joostdecock'} + /> +

+ +

+
+ ) +} diff --git a/packages/react/components/Account/Platform.mjs b/packages/react/components/Account/Platform.mjs new file mode 100644 index 00000000000..65def90b2f0 --- /dev/null +++ b/packages/react/components/Account/Platform.mjs @@ -0,0 +1,75 @@ +// 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 { SaveIcon } from '@freesewing/react/components/Icon' +import { StringInput } from '@freesewing/react/components/Input' + +const labels = { + instagram: 'Instagram', + mastodon: 'Mastodon', + reddit: 'Reddit', + twitch: 'Twitch', + tiktok: 'TikTok', + website: 'Website', +} + +export const Instagram = () => +export const Mastodon = () => +export const Reddit = () => +export const Twitch = () => +export const Tiktok = () => +export const Website = () => + +/* + * Component for the account/social/[platform] page + * + * @param {object} props - All React props + * @param {string} platform - One of the keys in the labels object above + */ +const Platform = ({ platform = false }) => { + // Hooks + const { account, setAccount } = useAccount() + const backend = useBackend() + const { setLoadingStatus } = useContext(LoadingStatusContext) + + // State + const [platformId, setPlatformId] = useState(account.data[platform] || '') + + if (!labels || !labels[platform]) return

Not a supported platform

+ + // Helper method to save changes + const save = async () => { + setLoadingStatus([true, 'Saving linked identity']) + const data = { data: {} } + data.data[platform] = platformId + const [status, body] = await backend.updateAccount(data) + if (status === 200 && body.result === 'success') { + setAccount(body.account) + setLoadingStatus([true, `Saved your ${labels[platform]} info`, true, true]) + } else setLoadingStatus([true, 'Something went wrong. Please report this', true, true]) + } + + return ( +
+ val.length > 0} + placeholder={'joostdecock'} + /> +

+ +

+
+ ) +} diff --git a/packages/react/components/Account/Set.mjs b/packages/react/components/Account/Set.mjs index 0c57065141d..0c47b4562be 100644 --- a/packages/react/components/Account/Set.mjs +++ b/packages/react/components/Account/Set.mjs @@ -200,7 +200,7 @@ export const Set = ({ id, publicOnly = false, Link = false }) => { const [status, body] = await backend.createSet(data) if (status === 201 && body.result === 'created') { setLoadingStatus([true, 'Loading newly created set', true, true]) - window.location = `/account/set/?id=${body.set.id}` + window.location = `/account/data/sets/set?id=${body.set.id}` } else setLoadingStatus([true, 'We failed to create this measurements set', true, false]) } diff --git a/packages/react/components/Account/Sets.mjs b/packages/react/components/Account/Sets.mjs index 667d0a72c70..2da62a4ff2c 100644 --- a/packages/react/components/Account/Sets.mjs +++ b/packages/react/components/Account/Sets.mjs @@ -3,6 +3,7 @@ import { measurements } from '@freesewing/config' import { cloudflareImageUrl, capitalize } from '@freesewing/utils' // Context import { LoadingStatusContext } from '@freesewing/react/context/LoadingStatus' +import { ModalContext } from '@freesewing/react/context/Modal' // Hooks import React, { useState, useEffect, Fragment, useContext } from 'react' import { useAccount } from '@freesewing/react/hooks/useAccount' @@ -10,6 +11,8 @@ import { useBackend } from '@freesewing/react/hooks/useBackend' // Components import { Link as WebLink } from '@freesewing/react/components/Link' import { NoIcon, OkIcon, PlusIcon, TrashIcon, UploadIcon } from '@freesewing/react/components/Icon' +import { ModalWrapper } from '@freesewing/react/components/Modal' +import { NewSet } from './Set.mjs' /* * The component for the an account/sets page @@ -23,13 +26,16 @@ export const Sets = ({ Link = false }) => { // Hooks const { control } = useAccount() const backend = useBackend() - const { setLoadingStatus, LoadingProgress } = useContext(LoadingStatusContext) // State const [sets, setSets] = useState([]) const [selected, setSelected] = useState({}) const [refresh, setRefresh] = useState(0) + // Context + const { setLoadingStatus, LoadingProgress } = useContext(LoadingStatusContext) + const { setModal } = useContext(ModalContext) + // Effects useEffect(() => { const getSets = async () => { @@ -90,15 +96,19 @@ export const Sets = ({ Link = false }) => { Import Measurements Sets - + setModal( + + + + ) + } > Create a new Measurements Set - +

{ />
- +
))} diff --git a/packages/react/components/Account/Username.mjs b/packages/react/components/Account/Username.mjs new file mode 100644 index 00000000000..818b1cd4afb --- /dev/null +++ b/packages/react/components/Account/Username.mjs @@ -0,0 +1,117 @@ +// Dependencies +import { welcomeSteps } from './shared.mjs' + +// 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 { NoIcon, OkIcon, SaveIcon } from '@freesewing/react/components/Icon' +import { StringInput } from '@freesewing/react/components/Input' + +/* + * Component for the account/username 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 Username = ({ welcome = false, Link = false }) => { + if (!Link) Link = WebLink + + // Hooks + const { account, setAccount } = useAccount() + const backend = useBackend() + const { setLoadingStatus } = useContext(LoadingStatusContext) + 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) + setAvailable(result.available ? true : false) + } + } + + const save = async () => { + setLoadingStatus([true, 'Saving username']) + const [status, body] = await backend.updateAccount({ username }) + if (status === 200 && body.result === 'success') { + setAccount(body.account) + setLoadingStatus([true, 'Username updated', true, true]) + } else setLoadingStatus([true, 'Something went wrong. Please report this', true, true]) + } + + const nextHref = + welcomeSteps[account.control].length > 5 + ? '/welcome/' + welcomeSteps[account.control][5] + : '/docs/about/guide' + + let btnClasses = 'daisy-btn mt-4 capitalize ' + if (welcome) btnClasses += 'w-64 daisy-btn-secondary' + else btnClasses += 'w-full daisy-btn-primary' + + return ( +
+ available} + placeholder={'Sorcha Ni Dhubghaill'} + labelBL={ + + {available ? ( + <> + Username is available + + ) : ( + <> + This username is taken + + )} + + } + /> +

+ +

+ + {welcome ? ( + <> + + {welcomeSteps[account.control].length > 0 ? ( + <> + + + 5 / {welcomeSteps[account.control].length} + + + + ) : null} + + ) : null} +
+ ) +} diff --git a/packages/react/components/Account/index.mjs b/packages/react/components/Account/index.mjs index e04b99a9186..a8913f42ac6 100644 --- a/packages/react/components/Account/index.mjs +++ b/packages/react/components/Account/index.mjs @@ -7,6 +7,14 @@ import { Sets, MsetCard } from './Sets.mjs' import { Patterns } from './Patterns.mjs' import { Pattern, PatternCard } from './Pattern.mjs' import { Apikeys } from './Apikeys.mjs' +import { Username } from './Username.mjs' +import { Bio } from './Bio.mjs' +import { Avatar } from './Avatar.mjs' +import { Email } from './Email.mjs' +import { Github } from './Github.mjs' +import { Instagram, Mastodon, Reddit, Twitch, Tiktok, Website } from './Platform.mjs' +import { Compare } from './Compare.mjs' +import { Control } from './Control.mjs' export { Bookmarks, @@ -20,4 +28,17 @@ export { Pattern, PatternCard, Apikeys, + Username, + Bio, + Avatar, + Email, + Github, + Instagram, + Mastodon, + Reddit, + Twitch, + Tiktok, + Website, + Compare, + Control, } diff --git a/packages/react/components/Account/shared.mjs b/packages/react/components/Account/shared.mjs index dcdc1de67c3..0fbc7bf1096 100644 --- a/packages/react/components/Account/shared.mjs +++ b/packages/react/components/Account/shared.mjs @@ -12,6 +12,14 @@ export const DisplayRow = ({ title, children, keyWidth = 'w-24' }) => (
) +export const welcomeSteps = { + 1: [''], + 2: ['', 'newsletter', 'units'], + 3: ['', 'newsletter', 'units', 'compare', 'username'], + 4: ['', 'newsletter', 'units', 'compare', 'username', 'bio', 'img'], + 5: [''], +} + /* import { Spinner } from 'shared/components/spinner.mjs' import Link from 'next/link' @@ -132,12 +140,5 @@ const icons = { img: UserIcon, } -export const welcomeSteps = { - 1: [''], - 2: ['', 'newsletter', 'units'], - 3: ['', 'newsletter', 'units', 'compare', 'username'], - 4: ['', 'newsletter', 'units', 'compare', 'username', 'bio', 'img'], - 5: [''], -} */ diff --git a/packages/react/components/Account/username.mjs b/packages/react/components/Account/username.mjs index 2bbd2376bcd..13bf0f57c52 100644 --- a/packages/react/components/Account/username.mjs +++ b/packages/react/components/Account/username.mjs @@ -51,7 +51,7 @@ export const UsernameSettings = ({ welcome = false }) => { else btnClasses += 'w-full btn-primary' return ( -
+
control ? (
- {scores.map((score) => ( + {Object.keys(controlDesc).map((score) => ( = score ? true : false} className="w-6 h-6 -ml-1" key={score} /> ))}
diff --git a/packages/react/components/Input/index.mjs b/packages/react/components/Input/index.mjs index 46a91c4c579..62b74848ab7 100644 --- a/packages/react/components/Input/index.mjs +++ b/packages/react/components/Input/index.mjs @@ -259,8 +259,12 @@ export const EmailInput = ({ placeholder={placeholder} value={current} onChange={(evt) => update(evt.target.value)} - className={`input w-full input-bordered ${ - current === original ? 'input-secondary' : valid(current) ? 'input-success' : 'input-error' + className={`daisy-input w-full daisy-input-bordered ${ + current === original + ? 'daisy-input-secondary' + : valid(current) + ? 'daisy-input-success' + : 'daisy-input-error' }`} /> @@ -390,7 +394,7 @@ export const ImageInput = ({ setUrl(evt.target.value) : (evt) => update(evt.target.value)} @@ -475,7 +479,7 @@ export const MarkdownInput = ({
-
+
{current}
diff --git a/packages/react/hooks/useControl/index.mjs b/packages/react/hooks/useControl/index.mjs new file mode 100644 index 00000000000..f109e296638 --- /dev/null +++ b/packages/react/hooks/useControl/index.mjs @@ -0,0 +1,46 @@ +// 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' + +/** + * Control can be updated from many places in the UI. + * So this shared state handler keeps this DRY + */ +export const useControl = () => { + // Hooks + const { account, setAccount, token } = useAccount() + const backend = useBackend() + const { setLoadingStatus } = useContext(LoadingStatusContext) + + // State + const [control, setControl] = useState(account.control) + + // Method to update the control setting + const updateControl = async (newControl) => { + if (newControl !== control) { + if (token) { + setLoadingStatus([true, 'Updating preferences']) + const [status, body] = await backend.updateAccount({ control: newControl }) + if (status === 200) { + setControl(newControl) + setAccount(body.account) + setLoadingStatus([true, 'Preferences updated', true, true]) + } else + setLoadingStatus([true, 'Failed to update preferences. Please report this', true, true]) + } else { + /* + * Control is used even when people are not logged in + * So this ensures control is always available, even if people are not authenticated + */ + setAccount({ ...account, control: newControl }) + setControl(newControl) + } + } + } + + return { control, updateControl } +} diff --git a/packages/react/package.json b/packages/react/package.json index dee2ce7928d..70c6def4ee4 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -60,6 +60,7 @@ "./context/Modal": "./context/Modal/index.mjs", "./hooks/useAccount": "./hooks/useAccount/index.mjs", "./hooks/useBackend": "./hooks/useBackend/index.mjs", + "./hooks/useControl": "./hooks/useControl/index.mjs", "./hooks/useSelection": "./hooks/useSelection/index.mjs", "./lib/RestClient": "./lib/RestClient/index.mjs", "./lib/logoPath": "./components/Logo/path.mjs" @@ -78,6 +79,7 @@ "luxon": "^3.5.0", "nuqs": "^1.17.6", "react-markdown": "^9.0.1", + "tlds": "^1.255.0", "use-local-storage-state": "19.1.0", "use-session-storage-state": "^19.0.0" }, diff --git a/packages/utils/src/index.mjs b/packages/utils/src/index.mjs index 4e801a8dbc7..1e175610301 100644 --- a/packages/utils/src/index.mjs +++ b/packages/utils/src/index.mjs @@ -1,3 +1,4 @@ +import tlds from 'tlds/index.json' with { type: 'json' } import { cloudflare as cloudflareConfig } from '@freesewing/config' /* @@ -343,3 +344,29 @@ export function timeAgo(timestamp, terse = true) { if (months < 25) return `${months}${terse ? 'M' : ' months'}${suffix}` return `${years}${terse ? 'Y' : ' years'}${suffix}` } + +/** + * Validates an email address for correct syntax + * + * @param {string} email - The email input to check + * @return {bool} valid - True if it's a valid email address + */ +export function validateEmail(email) { + /* eslint-disable */ + const re = + /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/ + /* eslint-enable */ + return re.test(email) +} + +/** + * Validates the top level domain (TLT) for an email address + * + * @param {string} email - The email input to check + * @return {bool} valid - True if it's a valid email address + */ +export function validateTld(email) { + const tld = email.split('@').pop().split('.').pop().toLowerCase() + if (tlds.indexOf(tld) === -1) return tld + else return true +} diff --git a/sites/org/docs/account/about/avatar/index.mdx b/sites/org/docs/account/about/avatar/index.mdx new file mode 100644 index 00000000000..a7b92c2b43d --- /dev/null +++ b/sites/org/docs/account/about/avatar/index.mdx @@ -0,0 +1,14 @@ +--- +title: Avatar +--- + +import { DocusaurusDoc } from '@freesewing/react/components/Docusaurus' +import { RoleBlock } from '@freesewing/react/components/Role' +import { Avatar } from '@freesewing/react/components/Account' +import Link from '@docusaurus/Link' + + + + + + diff --git a/sites/org/docs/account/about/bio/index.mdx b/sites/org/docs/account/about/bio/index.mdx new file mode 100644 index 00000000000..1e1fa05a727 --- /dev/null +++ b/sites/org/docs/account/about/bio/index.mdx @@ -0,0 +1,14 @@ +--- +title: Bio +--- + +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' + + + + + + diff --git a/sites/org/docs/account/about/email/index.mdx b/sites/org/docs/account/about/email/index.mdx new file mode 100644 index 00000000000..537d537652c --- /dev/null +++ b/sites/org/docs/account/about/email/index.mdx @@ -0,0 +1,14 @@ +--- +title: Email Address +--- + +import { DocusaurusDoc } from '@freesewing/react/components/Docusaurus' +import { RoleBlock } from '@freesewing/react/components/Role' +import { Email } from '@freesewing/react/components/Account' +import Link from '@docusaurus/Link' + + + + + + diff --git a/sites/org/docs/account/about/index.mdx b/sites/org/docs/account/about/index.mdx new file mode 100644 index 00000000000..1fca580176b --- /dev/null +++ b/sites/org/docs/account/about/index.mdx @@ -0,0 +1,32 @@ +--- +title: About you +sidebar_position: 2 +--- + +import { DocusaurusDoc } from '@freesewing/react/components/Docusaurus' +import { RoleBlock } from '@freesewing/react/components/Role' +import { Avatar, Bio, Email, Username } from '@freesewing/react/components/Account' +import Link from '@docusaurus/Link' + + + +These fields are tied to your identity: + +## Avatar + + + +## Bio + + + +## Email Address + + + +## Username + + + + + diff --git a/sites/org/docs/account/about/username/index.mdx b/sites/org/docs/account/about/username/index.mdx new file mode 100644 index 00000000000..24044ab479e --- /dev/null +++ b/sites/org/docs/account/about/username/index.mdx @@ -0,0 +1,14 @@ +--- +title: Username +--- + +import { DocusaurusDoc } from '@freesewing/react/components/Docusaurus' +import { RoleBlock } from '@freesewing/react/components/Role' +import { Username } from '@freesewing/react/components/Account' +import Link from '@docusaurus/Link' + + + + + + diff --git a/sites/org/docs/account/actions/import/index.mdx b/sites/org/docs/account/actions/import/index.mdx new file mode 100644 index 00000000000..b32fd5e5e99 --- /dev/null +++ b/sites/org/docs/account/actions/import/index.mdx @@ -0,0 +1,15 @@ +--- +title: Import data +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' + + + + + + diff --git a/sites/org/docs/account/actions/index.mdx b/sites/org/docs/account/actions/index.mdx new file mode 100644 index 00000000000..240c652740d --- /dev/null +++ b/sites/org/docs/account/actions/index.mdx @@ -0,0 +1,4 @@ +--- +title: Actions +sidebar_position: 6 +--- diff --git a/sites/org/docs/account/actions/reload/index.mdx b/sites/org/docs/account/actions/reload/index.mdx new file mode 100644 index 00000000000..9cf4a2219ba --- /dev/null +++ b/sites/org/docs/account/actions/reload/index.mdx @@ -0,0 +1,15 @@ +--- +title: Reload account data +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' + + + + + + diff --git a/sites/org/docs/account/actions/remove/index.mdx b/sites/org/docs/account/actions/remove/index.mdx new file mode 100644 index 00000000000..01784880740 --- /dev/null +++ b/sites/org/docs/account/actions/remove/index.mdx @@ -0,0 +1,15 @@ +--- +title: Remove your account +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' + + + + + + diff --git a/sites/org/docs/account/actions/restrict/index.mdx b/sites/org/docs/account/actions/restrict/index.mdx new file mode 100644 index 00000000000..118cbdb3712 --- /dev/null +++ b/sites/org/docs/account/actions/restrict/index.mdx @@ -0,0 +1,15 @@ +--- +title: Restrict processing of your data +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' + + + + + + diff --git a/sites/org/docs/account/bookmarks/index.mdx b/sites/org/docs/account/data/bookmarks/index.mdx similarity index 100% rename from sites/org/docs/account/bookmarks/index.mdx rename to sites/org/docs/account/data/bookmarks/index.mdx diff --git a/sites/org/docs/account/data/index.mdx b/sites/org/docs/account/data/index.mdx new file mode 100644 index 00000000000..6eaa0b64c78 --- /dev/null +++ b/sites/org/docs/account/data/index.mdx @@ -0,0 +1,28 @@ +--- +title: Your Data +sidebar_position: 1 +--- + +import { DocusaurusDoc } from '@freesewing/react/components/Docusaurus' +import { RoleBlock } from '@freesewing/react/components/Role' +import { Bookmarks, Patterns, Sets } from '@freesewing/react/components/Account' +import Link from '@docusaurus/Link' + + + + This is the main data stored in your FreeSewing account: + +## Your Bookmarks + + + +## Your Measurements Sets + + + +## Your Patterns + + + + + diff --git a/sites/org/docs/account/patterns/index.mdx b/sites/org/docs/account/data/patterns/index.mdx similarity index 100% rename from sites/org/docs/account/patterns/index.mdx rename to sites/org/docs/account/data/patterns/index.mdx diff --git a/sites/org/docs/account/sets/index.mdx b/sites/org/docs/account/data/sets/index.mdx similarity index 100% rename from sites/org/docs/account/sets/index.mdx rename to sites/org/docs/account/data/sets/index.mdx diff --git a/sites/org/docs/account/set/index.mdx b/sites/org/docs/account/data/sets/set.mdx similarity index 95% rename from sites/org/docs/account/set/index.mdx rename to sites/org/docs/account/data/sets/set.mdx index 336dcfbf7cb..2cd58287382 100644 --- a/sites/org/docs/account/set/index.mdx +++ b/sites/org/docs/account/data/sets/set.mdx @@ -1,7 +1,6 @@ --- title: Measurement Set sidebar_label: ' ' -sidebar_position: 99 --- import { getSearchParam } from '@freesewing/utils' diff --git a/sites/org/docs/account/preferences/compare/index.mdx b/sites/org/docs/account/preferences/compare/index.mdx new file mode 100644 index 00000000000..8aea2310925 --- /dev/null +++ b/sites/org/docs/account/preferences/compare/index.mdx @@ -0,0 +1,13 @@ +--- +title: Measurements Sets Comparison +--- + +import { DocusaurusDoc } from '@freesewing/react/components/Docusaurus' +import { RoleBlock } from '@freesewing/react/components/Role' +import { Compare } from '@freesewing/react/components/Account' + + + + + + diff --git a/sites/org/docs/account/preferences/consent/index.mdx b/sites/org/docs/account/preferences/consent/index.mdx new file mode 100644 index 00000000000..8a349e73b8d --- /dev/null +++ b/sites/org/docs/account/preferences/consent/index.mdx @@ -0,0 +1,14 @@ +--- +title: Consent & Privacy +--- + +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' + + + + + + diff --git a/sites/org/docs/account/preferences/control/index.mdx b/sites/org/docs/account/preferences/control/index.mdx new file mode 100644 index 00000000000..bb1b9d00bfc --- /dev/null +++ b/sites/org/docs/account/preferences/control/index.mdx @@ -0,0 +1,13 @@ +--- +title: User Experience +--- + +import { DocusaurusDoc } from '@freesewing/react/components/Docusaurus' +import { RoleBlock } from '@freesewing/react/components/Role' +import { Control } from '@freesewing/react/components/Account' + + + + + + diff --git a/sites/org/docs/account/preferences/index.mdx b/sites/org/docs/account/preferences/index.mdx new file mode 100644 index 00000000000..0cfb45604a8 --- /dev/null +++ b/sites/org/docs/account/preferences/index.mdx @@ -0,0 +1,12 @@ +--- +title: Preferences +sidebar_position: 3 +--- + +Here are the personal preferences stored in your account: + +- [Measurements Sets Comparison](/account/preferences/compare) +- [Consent & Privacy](/account/preferences/consent) +- [User Experience](/account/preferences/control) +- [Newsletter Subscription](/account/preferences/newsletter) +- [Units](/account/preferences/units) diff --git a/sites/org/docs/account/preferences/newsletter/index.mdx b/sites/org/docs/account/preferences/newsletter/index.mdx new file mode 100644 index 00000000000..e86255e1519 --- /dev/null +++ b/sites/org/docs/account/preferences/newsletter/index.mdx @@ -0,0 +1,14 @@ +--- +title: Newsletter Subscription +--- + +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' + + + + + + diff --git a/sites/org/docs/account/preferences/units/index.mdx b/sites/org/docs/account/preferences/units/index.mdx new file mode 100644 index 00000000000..bf9e3ab2fef --- /dev/null +++ b/sites/org/docs/account/preferences/units/index.mdx @@ -0,0 +1,14 @@ +--- +title: Units +--- + +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' + + + + + + diff --git a/sites/org/docs/account/apikeys/index.mdx b/sites/org/docs/account/security/apikeys/index.mdx similarity index 94% rename from sites/org/docs/account/apikeys/index.mdx rename to sites/org/docs/account/security/apikeys/index.mdx index 9f98c5454d9..0a94c5c6d5e 100644 --- a/sites/org/docs/account/apikeys/index.mdx +++ b/sites/org/docs/account/security/apikeys/index.mdx @@ -1,6 +1,5 @@ --- title: Your API Keys -sidebar_position: 3 --- import { DocusaurusDoc } from '@freesewing/react/components/Docusaurus' diff --git a/sites/org/docs/account/security/index.mdx b/sites/org/docs/account/security/index.mdx new file mode 100644 index 00000000000..1dcd0d9029e --- /dev/null +++ b/sites/org/docs/account/security/index.mdx @@ -0,0 +1,4 @@ +--- +title: Security +sidebar_position: 5 +--- diff --git a/sites/org/docs/account/security/mfa/index.mdx b/sites/org/docs/account/security/mfa/index.mdx new file mode 100644 index 00000000000..1af61bc77bb --- /dev/null +++ b/sites/org/docs/account/security/mfa/index.mdx @@ -0,0 +1,14 @@ +--- +title: Multi-Factor Authentication +--- + +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' + + + + + + diff --git a/sites/org/docs/account/security/password/index.mdx b/sites/org/docs/account/security/password/index.mdx new file mode 100644 index 00000000000..1e1fa05a727 --- /dev/null +++ b/sites/org/docs/account/security/password/index.mdx @@ -0,0 +1,14 @@ +--- +title: Bio +--- + +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' + + + + + + diff --git a/sites/org/docs/account/social/github/index.mdx b/sites/org/docs/account/social/github/index.mdx new file mode 100644 index 00000000000..b4e365efe7f --- /dev/null +++ b/sites/org/docs/account/social/github/index.mdx @@ -0,0 +1,14 @@ +--- +title: GitHub +--- + +import { DocusaurusDoc } from '@freesewing/react/components/Docusaurus' +import { RoleBlock } from '@freesewing/react/components/Role' +import { Github } from '@freesewing/react/components/Account' +import Link from '@docusaurus/Link' + + + + + + diff --git a/sites/org/docs/account/social/index.mdx b/sites/org/docs/account/social/index.mdx new file mode 100644 index 00000000000..c86b41ffaef --- /dev/null +++ b/sites/org/docs/account/social/index.mdx @@ -0,0 +1,53 @@ +--- +title: Linked Identities +sidebar_position: 4 +--- + +import { DocusaurusDoc } from '@freesewing/react/components/Docusaurus' +import { RoleBlock } from '@freesewing/react/components/Role' +import { + Github, + Instagram, + Mastodon, + Reddit, + Tiktok, + Twitch, + Website, +} from '@freesewing/react/components/Account' +import Link from '@docusaurus/Link' + + + + +Listing your other online identities allows visitors to follow you elsewhere. + +## GitHub + + + +## Instagram + + + +## Mastodon + + + +## Reddit + + + +## TikTok + + + +## Twitch + + + +## Website + + + + + diff --git a/sites/org/docs/account/social/instagram/index.mdx b/sites/org/docs/account/social/instagram/index.mdx new file mode 100644 index 00000000000..2805c48a313 --- /dev/null +++ b/sites/org/docs/account/social/instagram/index.mdx @@ -0,0 +1,14 @@ +--- +title: Instagram +--- + +import { DocusaurusDoc } from '@freesewing/react/components/Docusaurus' +import { RoleBlock } from '@freesewing/react/components/Role' +import { Instagram } from '@freesewing/react/components/Account' +import Link from '@docusaurus/Link' + + + + + + diff --git a/sites/org/docs/account/social/mastodon/index.mdx b/sites/org/docs/account/social/mastodon/index.mdx new file mode 100644 index 00000000000..7e22b6d4113 --- /dev/null +++ b/sites/org/docs/account/social/mastodon/index.mdx @@ -0,0 +1,14 @@ +--- +title: Mastodon +--- + +import { DocusaurusDoc } from '@freesewing/react/components/Docusaurus' +import { RoleBlock } from '@freesewing/react/components/Role' +import { Mastodon } from '@freesewing/react/components/Account' +import Link from '@docusaurus/Link' + + + + + + diff --git a/sites/org/docs/account/social/reddit/index.mdx b/sites/org/docs/account/social/reddit/index.mdx new file mode 100644 index 00000000000..b25d0d4f97b --- /dev/null +++ b/sites/org/docs/account/social/reddit/index.mdx @@ -0,0 +1,14 @@ +--- +title: Reddit +--- + +import { DocusaurusDoc } from '@freesewing/react/components/Docusaurus' +import { RoleBlock } from '@freesewing/react/components/Role' +import { Reddit } from '@freesewing/react/components/Account' +import Link from '@docusaurus/Link' + + + + + + diff --git a/sites/org/docs/account/social/tiktok/index.mdx b/sites/org/docs/account/social/tiktok/index.mdx new file mode 100644 index 00000000000..18a5c04773c --- /dev/null +++ b/sites/org/docs/account/social/tiktok/index.mdx @@ -0,0 +1,14 @@ +--- +title: TikTok +--- + +import { DocusaurusDoc } from '@freesewing/react/components/Docusaurus' +import { RoleBlock } from '@freesewing/react/components/Role' +import { Tiktok } from '@freesewing/react/components/Account' +import Link from '@docusaurus/Link' + + + + + + diff --git a/sites/org/docs/account/social/twitch/index.mdx b/sites/org/docs/account/social/twitch/index.mdx new file mode 100644 index 00000000000..50e5ba018ac --- /dev/null +++ b/sites/org/docs/account/social/twitch/index.mdx @@ -0,0 +1,14 @@ +--- +title: Twitch +--- + +import { DocusaurusDoc } from '@freesewing/react/components/Docusaurus' +import { RoleBlock } from '@freesewing/react/components/Role' +import { Twitch } from '@freesewing/react/components/Account' +import Link from '@docusaurus/Link' + + + + + + diff --git a/sites/org/docs/account/social/website/index.mdx b/sites/org/docs/account/social/website/index.mdx new file mode 100644 index 00000000000..efc4e0f6cf4 --- /dev/null +++ b/sites/org/docs/account/social/website/index.mdx @@ -0,0 +1,14 @@ +--- +title: Website +--- + +import { DocusaurusDoc } from '@freesewing/react/components/Docusaurus' +import { RoleBlock } from '@freesewing/react/components/Role' +import { Website } from '@freesewing/react/components/Account' +import Link from '@docusaurus/Link' + + + + + +