// 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 }) => (
)
const ShowKey = ({ apikey, t, clear, standalone }) => {
const router = useRouter()
return (
{t('keySecretWarning')}
{apikey.name}
{DateTime.fromISO(apikey.createdAt).toHTTP()}
{DateTime.fromISO(apikey.expiresAt).toHTTP()}
router.push('/account/apikeys') : clear}
>
{t('apikeys')}
)
}
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}`)}
))}
{t('newApikey')}
>
)}
)
}
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')}
{t('cancel')}
{t('delete')}
)
}
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}
>
)}
)
}