// Depdendencies import { cloudflareConfig } from 'shared/config/cloudflare.mjs' import { freeSewingConfig } from 'shared/config/freesewing.config.mjs' // Context import { LoadingStatusContext } from 'shared/context/loading-status-context.mjs' // Hooks import { useState, useEffect, useContext } from 'react' import { useBackend } from 'shared/hooks/use-backend.mjs' import { useAccount } from 'shared/hooks/use-account.mjs' // Components import Link from 'next/link' import { Json } from 'shared/components/json.mjs' import { Mdx } from 'shared/components/mdx/dynamic.mjs' import { AccountRole } from 'shared/components/account/role.mjs' import { AccountStatus } from 'shared/components/account/status.mjs' import { Loading } from 'shared/components/spinner.mjs' const roles = ['user', 'curator', 'bughunter', 'support', 'admin'] export const ImpersonateButton = ({ userId }) => { const backend = useBackend() const { setLoadingStatus } = useContext(LoadingStatusContext) const { impersonate } = useAccount() if (!userId) return null const impersonateUser = async () => { setLoadingStatus([true, 'status:contactingBackend']) const result = await backend.adminImpersonateUser(userId) if (result.success) { impersonate(result.data) setLoadingStatus([true, 'status:settingsSaved', true, true]) } else setLoadingStatus([true, 'status:backendError', true, false]) } return ( <button className="btn btn-primary" onClick={impersonateUser}> Impersonate </button> ) } export const Row = ({ title, val }) => ( <tr className="py-1"> <td className="text-sm px-2 text-right font-bold">{title}</td> <td className="text-sm">{val}</td> </tr> ) export const Hits = ({ results }) => ( <> {results && results.username && results.username.length > 0 && ( <> <h2>Results based on username</h2> {results.username.map((user) => ( <User user={user} key={user.id} /> ))} </> )} {results && results.email && results.email.length > 0 && ( <> <h2>Results based on E-mail address</h2> {results.email.map((user) => ( <User user={user} key={user.id} /> ))} </> )} </> ) export const ShowUser = ({ user, button = null }) => ( <div className="flex flex-row w-full gap-4"> <div className="w-52 h-52 bg-base-100 rounded-lg shadow shrink-0" style={{ backgroundImage: `url(${cloudflareConfig.url}uid-${user.ihash}/sq500)`, backgroundSize: 'cover', backgroundColor: '#ccc', }} ></div> <div className="w-full"> <h6 className="flex flex-row items-center gap-2 flex-wrap"> {user.username} <span className="font-light">|</span> <AccountRole role={user.role} /> <span className="font-light">|</span> <AccountStatus status={user.status} /> <span className="font-light">|</span> {user.id} </h6> <div className="flex flex-row flex-wrap gap-4 w-full"> <div className="max-w-xs"> <table> <tbody> <Row title="Email" val={user.email} /> <Row title="Initial" val={user.initial} /> <Row title="GitHub" val={user.github} /> <Row title="MFA" val={user.mfaEnabled ? 'Yes' : 'No'} /> <Row title="Passhash" val={user.passwordType} /> </tbody> </table> </div> <div className="max-w-xs"> <table> <tbody> <Row title="Patron" val={user.patron} /> <Row title="Consent" val={user.consent} /> <Row title="Control" val={user.control} /> <Row title="Calls (jwt)" val={user.jwtCalls} /> <Row title="Calls (key)" val={user.keyCalls} /> </tbody> </table> </div> <div className="max-w-xs flex flex-col gap-2">{button}</div> </div> <div className="max-w-full truncate"> <Mdx md={user.bio} /> </div> </div> </div> ) export const User = ({ user }) => ( <div className="my-8"> <ShowUser user={user} button={ <Link href={`/admin/user?id=${user.id}`} className="btn btn-primary"> Manage user </Link> } /> </div> ) export const ManageUser = ({ userId }) => { // Hooks const backend = useBackend() const { setLoadingStatus } = useContext(LoadingStatusContext) // State const [user, setUser] = useState({}) // Effect useEffect(() => { const loadUser = async () => { const result = await backend.adminLoadUser(userId) if (result.success) setUser(result.data.user) } loadUser() }, [userId]) const updateUser = async (data) => { setLoadingStatus([true, 'status:contactingBackend']) const result = await backend.adminUpdateUser({ id: userId, data }) if (result.success) { setLoadingStatus([true, 'status:settingsSaved', true, true]) setUser(result.data.user) } else setLoadingStatus([true, 'status:backendError', true, false]) } return user.id ? ( <div className="my-8"> <ShowUser user={user} button={<ImpersonateButton userId={user.id} />} /> <div className="flex flex-row flex-wrap gap-2 my-2"> {roles.map((role) => ( <button key={role} className="btn btn-primary btn-outline btn-sm" onClick={() => updateUser({ role })} disabled={role === user.role} > Assign {role} role </button> ))} </div> <div className="flex flex-row flex-wrap gap-2 my-2"> {user.mfaEnabled && ( <button className="btn btn-warning btn-outline btn-sm" onClick={() => updateUser({ mfaEnabled: false })} > Disable MFA </button> )} {Object.keys(freeSewingConfig.statuses).map((status) => ( <button key={status} className="btn btn-warning btn-outline btn-sm" onClick={() => updateUser({ status })} disabled={Number(status) === user.status} > Set {freeSewingConfig.statuses[status].name.toUpperCase()} status </button> ))} </div> {user.id ? <Json js={user} /> : null} </div> ) : ( <Loading /> ) }