// Dependencies import { useState, useEffect, useContext } from 'react' import { useTranslation } from 'next-i18next' import { DateTime } from 'luxon' import { CopyToClipboard } from 'react-copy-to-clipboard' // Context import { LoadingContext } from 'shared/context/loading-context.mjs' import { ModalContext } from 'shared/context/modal-context.mjs' // Hooks import { useAccount } from 'shared/hooks/use-account.mjs' import { useBackend } from 'shared/hooks/use-backend.mjs' import { useToast } from 'shared/hooks/use-toast.mjs' import { useRouter } from 'next/router' // Components import { BackToAccountButton, Choice } from './shared.mjs' import { Popout } from 'shared/components/popout/index.mjs' import { WebLink } from 'shared/components/web-link.mjs' import { CopyIcon } from 'shared/components/icons.mjs' import { Collapse, useCollapseButton } from 'shared/components/collapse.mjs' import { TrashIcon } from 'shared/components/icons.mjs' import { LeftIcon } from 'shared/components/icons.mjs' import { ModalWrapper } from 'shared/components/wrappers/modal.mjs' export const ns = ['account', 'toast'] const ExpiryPicker = ({ t, expires, setExpires }) => { 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')} {expires.toHTTP()} ) } const CopyInput = ({ text }) => { const { t } = useTranslation(['toast']) const toast = useToast() const [copied, setCopied] = useState(false) const showCopied = () => { setCopied(true) toast.success({t('copiedToClipboard')}) window.setTimeout(() => setCopied(false), 3000) } return (
) } const Row = ({ title, children }) => (
{title}
{children}
) const ShowKey = ({ apikey, t, clear, standalone }) => { const router = useRouter() return (
{t('keySecretWarning')} {apikey.name} {DateTime.fromISO(apikey.createdAt).toHTTP()} {DateTime.fromISO(apikey.expiresAt).toHTTP()}
) } const NewKey = ({ t, account, setGenerate, keyAdded, backend, toast, startLoading, stopLoading, closeCollapseButton, standalone = false, title = true, }) => { const [name, setName] = useState('') const [level, setLevel] = useState(1) const [expires, setExpires] = useState(DateTime.now()) const [apikey, setApikey] = useState(false) const levels = account.role === 'admin' ? [0, 1, 2, 3, 4, 5, 6, 7, 8] : [0, 1, 2, 3, 4] const createKey = async () => { startLoading() const result = await backend.createApikey({ name, level, expiresIn: Math.floor((expires.valueOf() - DateTime.now().valueOf()) / 1000), }) if (result.success) { toast.success({t('nailedIt')}) setApikey(result.data.apikey) keyAdded() } else toast.for.backendError() stopLoading() if (closeCollapseButton) closeCollapseButton() } const clear = () => { setApikey(false) setGenerate(false) } return (
{title ?

{t('newApikey')}

: null} {apikey ? ( <> ) : ( <>
{t('keyName')}

{t('keyNameDesc')}

setName(evt.target.value)} className="input w-full input-bordered flex flex-row" type="text" placeholder={'Alicia key'} />
{t('keyExpires')}
{t('keyLevel')}
{levels.map((l) => ( {t(`keyLevel${l}`)} ))}
)}
) } const Apikey = ({ apikey, t, account, backend, keyAdded, startLoading, stopLoading }) => { const { setModal } = useContext(ModalContext) const toast = useToast() const fields = { id: 'ID', name: t('keyName'), level: t('keyLevel'), expiresAt: t('expires'), createdAt: t('created'), } const expired = DateTime.fromISO(apikey.expiresAt).valueOf() < DateTime.now().valueOf() const remove = async () => { startLoading() const result = await backend.removeApikey(apikey.id) if (result) toast.success(t('gone')) else toast.for.backendError() // This just forces a refresh of the list from the server // We obviously did not add a key here, but rather removed one keyAdded() stopLoading() } const removeModal = () => { setModal(

{t('areYouCertain')}

{t('deleteKeyWarning')}

) } const title = (
{apikey.name} {t('expires')}: {DateTime.fromISO(apikey.expiresAt).toLocaleString()} | {t('keyLevel')}: {apikey.level}
) return ( 4 ? remove : removeModal} > , ]} > {expired ? ( {t('keyExpired')} ) : null} {Object.entries(fields).map(([key, title]) => ( {apikey[key]} ))} ) } // Component for the 'new/apikey' page export const NewApikey = ({ standalone = false }) => { // Context const { startLoading, stopLoading } = useContext(LoadingContext) // Hooks const { account } = useAccount() const backend = useBackend() const { t } = useTranslation(ns) const toast = useToast() // 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 = () => { // Context const { startLoading, stopLoading, loading } = useContext(LoadingContext) // Hooks const { account } = useAccount() const backend = useBackend() const { t } = useTranslation(ns) const toast = useToast() const { CollapseButton, closeCollapseButton } = useCollapseButton() // State const [keys, setKeys] = useState([]) const [generate, setGenerate] = useState(false) const [added, setAdded] = useState(0) // Effects useEffect(() => { const getApikeys = async () => { const result = await backend.getApikeys() if (result.success) setKeys(result.data.apikeys) } getApikeys() }, [added]) // Helper method to force refresh const keyAdded = () => setAdded(added + 1) return (
{generate ? ( ) : ( <>

{t('apikeys')}

{keys.map((apikey) => ( ))} {account.control < 5 ? (
Refer to FreeSewing.dev for details (English only)

This is an advanced feature aimed at developers or anyone who wants to interact with our backend directly. For details, please refer to{' '} {' '} on , our site for developers and contributors.

) : null} )}
) }