2025-04-01 16:15:20 +02:00
|
|
|
// Config
|
|
|
|
import { cloudflareImageUrl, capitalize } from '@freesewing/utils'
|
|
|
|
import { control as controlConfig } from '@freesewing/config'
|
|
|
|
// Hooks
|
|
|
|
import React, { useState, useEffect } from 'react'
|
|
|
|
import { useAccount } from '@freesewing/react/hooks/useAccount'
|
|
|
|
import { useBackend } from '@freesewing/react/hooks/useBackend'
|
|
|
|
// Components
|
|
|
|
import { Link as DefautLink } from '@freesewing/react/components/Link'
|
|
|
|
import { ControlScore } from '@freesewing/react/components/Control'
|
|
|
|
import {
|
|
|
|
MeasurementsSetIcon,
|
|
|
|
SignoutIcon,
|
|
|
|
UserIcon,
|
|
|
|
UnitsIcon,
|
|
|
|
ShowcaseIcon,
|
|
|
|
ChatIcon,
|
|
|
|
EmailIcon,
|
|
|
|
KeyIcon,
|
|
|
|
BookmarkIcon,
|
|
|
|
CompareIcon,
|
|
|
|
PrivacyIcon,
|
|
|
|
ControlIcon,
|
|
|
|
LockIcon,
|
|
|
|
NewsletterIcon,
|
|
|
|
ShieldIcon,
|
|
|
|
FingerprintIcon,
|
|
|
|
GitHubIcon,
|
|
|
|
InstagramIcon,
|
|
|
|
MastodonIcon,
|
|
|
|
TwitchIcon,
|
|
|
|
TikTokIcon,
|
|
|
|
LinkIcon,
|
|
|
|
TrashIcon,
|
|
|
|
RedditIcon,
|
|
|
|
CloseIcon,
|
|
|
|
ReloadIcon,
|
|
|
|
NoIcon,
|
|
|
|
PatternIcon,
|
|
|
|
BoolYesIcon,
|
|
|
|
BoolNoIcon,
|
|
|
|
OkIcon,
|
|
|
|
WrenchIcon,
|
|
|
|
UploadIcon,
|
|
|
|
DownloadIcon,
|
|
|
|
} from '@freesewing/react/components/Icon'
|
|
|
|
|
|
|
|
const itemIcons = {
|
|
|
|
bookmarks: <BookmarkIcon />,
|
|
|
|
sets: <MeasurementsSetIcon />,
|
|
|
|
patterns: <PatternIcon />,
|
|
|
|
apikeys: <KeyIcon />,
|
|
|
|
username: <UserIcon />,
|
|
|
|
email: <EmailIcon />,
|
|
|
|
bio: <ChatIcon />,
|
|
|
|
img: <ShowcaseIcon />,
|
|
|
|
language: <ShowcaseIcon />,
|
|
|
|
units: <UnitsIcon />,
|
|
|
|
compare: <CompareIcon />,
|
|
|
|
consent: <PrivacyIcon />,
|
|
|
|
control: <ControlIcon />,
|
|
|
|
mfa: <ShieldIcon />,
|
|
|
|
newsletter: <NewsletterIcon />,
|
|
|
|
password: <LockIcon />,
|
|
|
|
github: <GitHubIcon />,
|
|
|
|
instagram: <InstagramIcon />,
|
|
|
|
mastodon: <MastodonIcon />,
|
|
|
|
twitter: <InstagramIcon />,
|
|
|
|
twitch: <TwitchIcon />,
|
|
|
|
tiktok: <TikTokIcon />,
|
|
|
|
website: <LinkIcon />,
|
|
|
|
reddit: <RedditIcon />,
|
|
|
|
}
|
|
|
|
|
2025-04-18 08:07:13 +00:00
|
|
|
const btnClasses = 'tw:daisy-btn tw:capitalize tw:flex tw:flex-row tw:justify-between'
|
2025-04-01 16:15:20 +02:00
|
|
|
const itemClasses =
|
2025-04-18 08:07:13 +00:00
|
|
|
'tw:flex tw:flex-row tw:items-center tw:justify-between tw:p-2 tw:px-4 tw:rounded tw:mb-1'
|
|
|
|
const linkClasses = `tw:hover:bg-secondary/10 tw:max-w-md tw:hover:no-underline tw:text-base-content no-hover-decoration`
|
2025-04-01 16:15:20 +02:00
|
|
|
|
|
|
|
const titles = {
|
|
|
|
apikeys: 'API Keys',
|
|
|
|
sets: 'Measurements Sets',
|
|
|
|
patterns: 'Patterns',
|
|
|
|
img: 'Avatar',
|
|
|
|
email: 'E-mail Address',
|
|
|
|
newsletter: 'Newsletter Subscription',
|
|
|
|
compare: 'Measurements Comparison',
|
|
|
|
consent: 'Consent & Privacy',
|
|
|
|
control: 'User Experience',
|
|
|
|
github: 'GitHub',
|
|
|
|
mfa: 'Multi-Factor Authentication',
|
|
|
|
}
|
|
|
|
|
|
|
|
const YesNo = ({ check }) => (check ? <BoolYesIcon /> : <BoolNoIcon />)
|
|
|
|
|
|
|
|
const t = (input) => input
|
|
|
|
|
|
|
|
/**
|
|
|
|
* The Links component shows all of the links to manage your account
|
|
|
|
*
|
|
|
|
* @param {object} props - All the React props
|
|
|
|
* @param {function} Link - A custom Link component, typically the Docusaurus one, but it's optional
|
|
|
|
*/
|
|
|
|
export const Links = ({ Link = false }) => {
|
|
|
|
// Use custom Link component if available
|
|
|
|
if (!Link) Link = DefaultLink
|
|
|
|
|
|
|
|
// Hooks
|
|
|
|
const { account, signOut, control } = useAccount()
|
|
|
|
const backend = useBackend()
|
|
|
|
|
|
|
|
// State
|
|
|
|
const [bookmarks, setBookmarks] = useState([])
|
|
|
|
const [sets, setSets] = useState([])
|
|
|
|
const [patterns, setPatterns] = useState([])
|
|
|
|
const [apikeys, setApikeys] = useState([])
|
|
|
|
|
|
|
|
// Effects
|
|
|
|
useEffect(() => {
|
|
|
|
const getUserData = async () => {
|
|
|
|
const [status, body] = await backend.getUserData(account.id)
|
|
|
|
if (status === 200 && body.result === 'success') {
|
|
|
|
setApikeys(body.data.apikeys)
|
|
|
|
setBookmarks(body.data.bookmarks)
|
|
|
|
setPatterns(body.data.patterns)
|
|
|
|
setSets(body.data.sets)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
getUserData()
|
|
|
|
}, [account.id])
|
|
|
|
|
|
|
|
if (!account.username) return null
|
|
|
|
|
|
|
|
const itemPreviews = {
|
|
|
|
apikeys: apikeys?.length || 0,
|
|
|
|
bookmarks: bookmarks?.length || 0,
|
|
|
|
sets: sets?.length || 0,
|
|
|
|
patterns: patterns?.length || 0,
|
|
|
|
username: account.username,
|
|
|
|
email: account.email,
|
|
|
|
bio: account.bio ? <span>{account.bio.slice(0, 15)}…</span> : '',
|
|
|
|
img: (
|
|
|
|
<img
|
|
|
|
src={cloudflareImageUrl({ type: 'sq100', id: `uid-${account.ihash}` })}
|
2025-04-18 08:07:13 +00:00
|
|
|
className="tw:w-8 tw:h-8 tw:aspect-square tw:rounded-full shadow"
|
2025-04-01 16:15:20 +02:00
|
|
|
/>
|
|
|
|
),
|
|
|
|
units: account.imperial ? 'Imperial' : 'Metric',
|
|
|
|
newsletter: <YesNo check={account.newsletter} />,
|
|
|
|
compare: <YesNo check={account.compare} />,
|
|
|
|
consent: <YesNo check={account.consent} />,
|
|
|
|
control: <ControlScore control={account.control} />,
|
|
|
|
github: account.data.githubUsername || account.data.githubEmail || <NoIcon />,
|
|
|
|
password: account.passwordType === 'v3' ? <BoolYesIcon /> : <NoIcon />,
|
|
|
|
mfa: <YesNo check={account.mfaEnabled} />,
|
|
|
|
}
|
|
|
|
for (const social of Object.keys(controlConfig.account.fields.identities).filter(
|
|
|
|
(i) => i !== 'github'
|
|
|
|
))
|
|
|
|
itemPreviews[social] = account.data[social] || (
|
2025-04-18 08:07:13 +00:00
|
|
|
<NoIcon className="tw:text-base-content tw:w-6 tw:h-6" stroke={2} />
|
2025-04-01 16:15:20 +02:00
|
|
|
)
|
|
|
|
|
|
|
|
return (
|
2025-04-18 08:07:13 +00:00
|
|
|
<div className="tw:w-full">
|
|
|
|
<div className="tw:grid tw:grid-cols-1 tw:xl:grid-cols-2 tw:gap-4 tw:mb-8">
|
2025-04-01 16:15:20 +02:00
|
|
|
<div>
|
2025-04-18 08:07:13 +00:00
|
|
|
<h4 className="tw:my-2">Your Data</h4>
|
2025-04-01 16:15:20 +02:00
|
|
|
{Object.keys(controlConfig.account.fields.data).map((item) =>
|
|
|
|
controlConfig.flat[item] > control ? null : (
|
|
|
|
<Link
|
|
|
|
key={item}
|
|
|
|
title={titles[item]}
|
|
|
|
href={`/account/data/${item}/`}
|
|
|
|
className={`${itemClasses} ${linkClasses}`}
|
|
|
|
>
|
2025-04-18 08:07:13 +00:00
|
|
|
<div className="tw:flex tw:flex-row tw:items-center tw:gap-3 tw:font-medium tw:text-base-content">
|
2025-04-01 16:15:20 +02:00
|
|
|
{itemIcons[item]}
|
|
|
|
{titles[item] ? titles[item] : capitalize(item)}
|
|
|
|
</div>
|
2025-04-18 08:07:13 +00:00
|
|
|
<div className="tw:text-base-content">{itemPreviews[item]}</div>
|
2025-04-01 16:15:20 +02:00
|
|
|
</Link>
|
|
|
|
)
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{control > 1 && (
|
|
|
|
<div>
|
2025-04-18 08:07:13 +00:00
|
|
|
<h4 className="tw:my-2">About You</h4>
|
2025-04-01 16:15:20 +02:00
|
|
|
{Object.keys(controlConfig.account.fields.info).map((item) =>
|
|
|
|
controlConfig.flat[item] > control ? null : (
|
|
|
|
<Link
|
|
|
|
key={item}
|
|
|
|
title={titles[item] || capitalize(item)}
|
|
|
|
href={`/account/about/${item === 'img' ? 'avatar' : item}/`}
|
|
|
|
className={`${itemClasses} ${linkClasses}`}
|
|
|
|
>
|
2025-04-18 08:07:13 +00:00
|
|
|
<div className="tw:flex tw:flex-row tw:items-center tw:gap-3 tw:font-medium tw:text-base-content">
|
2025-04-01 16:15:20 +02:00
|
|
|
{itemIcons[item]}
|
|
|
|
{titles[item] ? titles[item] : capitalize(item)}
|
|
|
|
</div>
|
2025-04-18 08:07:13 +00:00
|
|
|
<div className="tw:text-base-content">{itemPreviews[item]}</div>
|
2025-04-01 16:15:20 +02:00
|
|
|
</Link>
|
|
|
|
)
|
|
|
|
)}
|
2025-04-18 08:07:13 +00:00
|
|
|
<div className={`${itemClasses} tw:opacity-60 tw:max-w-md`}>
|
|
|
|
<div className="tw:flex tw:flex-row tw:items-center tw:gap-3 tw:font-medium">
|
2025-04-01 16:15:20 +02:00
|
|
|
<OkIcon stroke={3} />
|
|
|
|
<span>Role</span>
|
|
|
|
</div>
|
2025-04-18 08:07:13 +00:00
|
|
|
<div className="tw:capitalize">{account.role}</div>
|
2025-04-01 16:15:20 +02:00
|
|
|
</div>
|
2025-04-18 08:07:13 +00:00
|
|
|
<div className={`${itemClasses} tw:opacity-60 tw:max-w-md`}>
|
|
|
|
<div className="tw:flex tw:flex-row tw:items-center tw:gap-3 tw:font-medium">
|
2025-04-01 16:15:20 +02:00
|
|
|
<FingerprintIcon />
|
|
|
|
<span>ID</span>
|
|
|
|
</div>
|
|
|
|
<div>{account.id}</div>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
|
|
|
|
<div>
|
2025-04-18 08:07:13 +00:00
|
|
|
<h4 className="tw:my-2">Preferences</h4>
|
2025-04-01 16:15:20 +02:00
|
|
|
{Object.keys(controlConfig.account.fields.settings).map((item) =>
|
|
|
|
controlConfig.flat[item] > control ? null : (
|
|
|
|
<Link
|
|
|
|
key={item}
|
|
|
|
title={titles[item] || capitalize(item)}
|
|
|
|
href={`/account/preferences/${item}/`}
|
|
|
|
className={`${itemClasses} ${linkClasses}`}
|
|
|
|
>
|
2025-04-18 08:07:13 +00:00
|
|
|
<div className="tw:flex tw:flex-row tw:items-center tw:gap-3 tw:font-medium tw:text-base-content">
|
2025-04-01 16:15:20 +02:00
|
|
|
{itemIcons[item]}
|
|
|
|
{titles[item] ? titles[item] : capitalize(item)}
|
|
|
|
</div>
|
2025-04-18 08:07:13 +00:00
|
|
|
<div className="tw:text-base-content">{itemPreviews[item]}</div>
|
2025-04-01 16:15:20 +02:00
|
|
|
</Link>
|
|
|
|
)
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{control > 2 && (
|
|
|
|
<div>
|
2025-04-18 08:07:13 +00:00
|
|
|
<h4 className="tw:my-2">Linked Identities</h4>
|
2025-04-01 16:15:20 +02:00
|
|
|
{Object.keys(controlConfig.account.fields.identities).map((item) =>
|
|
|
|
controlConfig.flat[item] > control ? null : (
|
|
|
|
<Link
|
|
|
|
key={item}
|
|
|
|
title={titles[item] || capitalize(item)}
|
|
|
|
href={`/account/social/${item}/`}
|
|
|
|
className={`${itemClasses} ${linkClasses}`}
|
|
|
|
>
|
2025-04-18 08:07:13 +00:00
|
|
|
<div className="tw:flex tw:flex-row tw:items-center tw:gap-3 tw:font-medium tw:text-base-content">
|
2025-04-01 16:15:20 +02:00
|
|
|
{itemIcons[item]}
|
|
|
|
{titles[item] ? titles[item] : capitalize(item)}
|
|
|
|
</div>
|
2025-04-18 08:07:13 +00:00
|
|
|
<div className="tw:text-base-content">{itemPreviews[item]}</div>
|
2025-04-01 16:15:20 +02:00
|
|
|
</Link>
|
|
|
|
)
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
|
|
|
|
{control > 1 && (
|
|
|
|
<div>
|
2025-04-18 08:07:13 +00:00
|
|
|
<h4 className="tw:my-2">Security</h4>
|
2025-04-01 16:15:20 +02:00
|
|
|
{Object.keys(controlConfig.account.fields.security).map((item) =>
|
|
|
|
controlConfig.flat[item] > control ? null : (
|
|
|
|
<Link
|
|
|
|
key={item}
|
|
|
|
title={titles[item] || capitalize(item)}
|
|
|
|
href={`/account/security/${item}/`}
|
|
|
|
className={`${itemClasses} ${linkClasses}`}
|
|
|
|
>
|
2025-04-18 08:07:13 +00:00
|
|
|
<div className="tw:flex tw:flex-row tw:items-center tw:gap-3 tw:font-medium tw:text-base-content">
|
2025-04-01 16:15:20 +02:00
|
|
|
{itemIcons[item]}
|
|
|
|
{titles[item] ? titles[item] : capitalize(item)}
|
|
|
|
</div>
|
2025-04-18 08:07:13 +00:00
|
|
|
<div className="tw:text-base-content">{itemPreviews[item]}</div>
|
2025-04-01 16:15:20 +02:00
|
|
|
</Link>
|
|
|
|
)
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
|
|
|
|
{control > 1 && (
|
|
|
|
<div>
|
2025-04-18 08:07:13 +00:00
|
|
|
<h4 className="tw:my-2">Actions</h4>
|
2025-04-01 16:15:20 +02:00
|
|
|
{control > 2 && (
|
|
|
|
<Link
|
|
|
|
className={`${itemClasses} ${linkClasses}`}
|
|
|
|
title="Import data"
|
|
|
|
href="/account/actions/import/"
|
|
|
|
>
|
2025-04-18 08:07:13 +00:00
|
|
|
<UploadIcon className="tw:w-6 tw:h-6 tw:text-base-content" />
|
|
|
|
<span className="tw:font-medium tw:text-base-content">Import data</span>
|
2025-04-01 16:15:20 +02:00
|
|
|
</Link>
|
|
|
|
)}
|
|
|
|
{control > 2 && (
|
|
|
|
<Link
|
|
|
|
className={`${itemClasses} ${linkClasses}`}
|
|
|
|
title="Export your data"
|
|
|
|
href="/account/actions/export/"
|
|
|
|
>
|
2025-04-18 08:07:13 +00:00
|
|
|
<DownloadIcon className="tw:w-6 tw:h-6 tw:text-base-content" />
|
|
|
|
<span className="tw:font-medium tw:text-base-content">Export your data</span>
|
2025-04-01 16:15:20 +02:00
|
|
|
</Link>
|
|
|
|
)}
|
|
|
|
{control > 2 && (
|
|
|
|
<Link
|
|
|
|
className={`${itemClasses} ${linkClasses}`}
|
|
|
|
title="Reload account data"
|
|
|
|
href="/account/actions/reload/"
|
|
|
|
>
|
2025-04-18 08:07:13 +00:00
|
|
|
<ReloadIcon className="tw:w-6 tw:h-6 tw:text-base-content" />
|
|
|
|
<span className="tw:font-medium tw:text-base-content">Reload account data</span>
|
2025-04-01 16:15:20 +02:00
|
|
|
</Link>
|
|
|
|
)}
|
|
|
|
{control > 3 && (
|
|
|
|
<Link
|
|
|
|
className={`${itemClasses} ${linkClasses}`}
|
|
|
|
title="Restrict processing of your data"
|
|
|
|
href="/account/actions/restrict/"
|
|
|
|
>
|
2025-04-18 08:07:13 +00:00
|
|
|
<CloseIcon className="tw:w-6 tw:h-6 tw:text-warning" stroke={3} />
|
|
|
|
<span className="tw:font-medium tw:text-base-content">
|
|
|
|
Restrict processing of your data
|
|
|
|
</span>
|
2025-04-01 16:15:20 +02:00
|
|
|
</Link>
|
|
|
|
)}
|
|
|
|
<Link
|
|
|
|
className={`${itemClasses} ${linkClasses}`}
|
|
|
|
title="Remove your account"
|
|
|
|
href="/account/actions/remove/"
|
|
|
|
>
|
2025-04-18 08:07:13 +00:00
|
|
|
<TrashIcon className="tw:w-6 tw:h-6 tw:text-warning" />
|
|
|
|
<span className="tw:font-medium tw:text-base-content">Remove your account</span>
|
2025-04-01 16:15:20 +02:00
|
|
|
</Link>
|
|
|
|
</div>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
|
2025-04-18 08:07:13 +00:00
|
|
|
<div className="tw:flex tw:flex-row tw:flex-wrap tw:gap-2 tw:md:gap-4 tw:justify-end">
|
2025-04-01 16:15:20 +02:00
|
|
|
{account.role === 'admin' && (
|
2025-04-18 08:07:13 +00:00
|
|
|
<Link
|
|
|
|
className={`${btnClasses} tw:daisy-btn-accent tw:md:w-64 tw:text-accent-content`}
|
|
|
|
href="/admin"
|
|
|
|
>
|
|
|
|
<WrenchIcon className="tw:w-6 tw:h-6 tw:text-accent-content" />
|
|
|
|
<span className="tw:text-accent-content">Administration</span>
|
2025-04-01 16:15:20 +02:00
|
|
|
</Link>
|
|
|
|
)}
|
|
|
|
{control > 1 && (
|
2025-04-18 08:07:13 +00:00
|
|
|
<Link className={`${btnClasses} tw:daisy-btn-secondary tw:md:w-64`} href="/profile">
|
|
|
|
<UserIcon className="tw:w-6 tw:h-6 tw:text-accent-content" />
|
|
|
|
<span className="tw:text-accent-content">Your Profile</span>
|
2025-04-01 16:15:20 +02:00
|
|
|
</Link>
|
|
|
|
)}
|
|
|
|
<button
|
2025-04-18 08:07:13 +00:00
|
|
|
className={`${btnClasses} tw:daisy-btn-neutral tw:md:w-64`}
|
2025-04-01 16:15:20 +02:00
|
|
|
onClick={() => signOut()}
|
|
|
|
>
|
|
|
|
<SignoutIcon />
|
|
|
|
Sign Out
|
|
|
|
</button>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
)
|
|
|
|
}
|