import { Button } from '@affine/admin/components/ui/button'; import { Input } from '@affine/admin/components/ui/input'; import { Label } from '@affine/admin/components/ui/label'; import { Separator } from '@affine/admin/components/ui/separator'; import type { FeatureType } from '@affine/graphql'; import { ChevronRightIcon } from 'lucide-react'; import type { ChangeEvent } from 'react'; import { useCallback, useEffect, useMemo, useState } from 'react'; import { toast } from 'sonner'; import { FeatureToggleList } from '../../../components/shared/feature-toggle-list'; import { useServerConfig } from '../../common'; import { RightPanelHeader } from '../../header'; import type { UserInput, UserType } from '../schema'; import { validateEmails, validatePassword } from '../utils/csv-utils'; import { useCreateUser, useUpdateUser } from './use-user-management'; type UserFormProps = { title: string; defaultValue?: Partial; onClose: () => void; onConfirm: (user: UserInput) => void; onValidate: (user: Partial) => boolean; actions?: React.ReactNode; showOption?: boolean; onDirtyChange?: (dirty: boolean) => void; }; function UserForm({ title, defaultValue, onClose, onConfirm, onValidate, actions, showOption, onDirtyChange, }: UserFormProps) { const serverConfig = useServerConfig(); const defaultUser: Partial = useMemo( () => ({ name: defaultValue?.name ?? '', email: defaultValue?.email ?? '', password: defaultValue?.password ?? '', features: defaultValue?.features ?? [], }), [defaultValue] ); const [changes, setChanges] = useState>(defaultUser); const setField = useCallback( ( field: K, value: UserInput[K] | ((prev: UserInput[K] | undefined) => UserInput[K]) ) => { setChanges(changes => ({ ...changes, [field]: typeof value === 'function' ? value(changes[field] as any) : value, })); }, [] ); const canSave = useMemo(() => { return onValidate(changes); }, [onValidate, changes]); useEffect(() => { const normalize = (value: Partial) => ({ name: value.name ?? '', email: value.email ?? '', password: value.password ?? '', features: [...(value.features ?? [])].sort(), }); const current = normalize(changes); const baseline = normalize(defaultUser); const dirty = (current.name !== baseline.name || current.email !== baseline.email || current.password !== baseline.password || current.features.join(',') !== baseline.features.join(',')) && !!onDirtyChange; onDirtyChange?.(dirty); }, [changes, defaultUser, onDirtyChange]); const handleConfirm = useCallback(() => { if (!canSave) { return; } // @ts-expect-error checked onConfirm(changes); setChanges(defaultUser); }, [canSave, changes, defaultUser, onConfirm]); const handleFeaturesChange = useCallback( (features: FeatureType[]) => { setField('features', features); }, [setField] ); const handleClose = useCallback(() => { setChanges(defaultUser); onClose(); }, [defaultUser, onClose]); useEffect(() => { setChanges(defaultUser); }, [defaultUser]); return (
{showOption && ( <> )}
{actions}
); } function InputItem({ label, field, optional, value, onChange, placeholder, }: { label: string; field: keyof UserInput; optional?: boolean; value?: string; onChange: (field: keyof UserInput, value: string) => void; placeholder?: string; }) { const onValueChange = useCallback( (e: ChangeEvent) => { onChange(field, e.target.value); }, [field, onChange] ); return (
); } const validateCreateUser = (user: Partial) => { return !!user.name && !!user.email && !!user.features; }; const validateUpdateUser = (user: Partial) => { return !!user.name || !!user.email; }; export function CreateUserForm({ onComplete, onDirtyChange, }: { onComplete: () => void; onDirtyChange?: (dirty: boolean) => void; }) { const { create, creating } = useCreateUser(); const serverConfig = useServerConfig(); const passwordLimits = serverConfig.credentialsRequirement.password; useEffect(() => { if (creating) { return () => { onComplete(); }; } return; }, [creating, onComplete]); const handleCreateUser = useCallback( (user: UserInput) => { const emailValidation = validateEmails([user]); const passwordValidation = validatePassword( user.password, passwordLimits ); if (!passwordValidation.valid || !emailValidation[0].valid) { toast.error(passwordValidation.error || emailValidation[0].error); return; } create(user); }, [create, passwordLimits] ); return ( ); } export function UpdateUserForm({ user, onResetPassword, onDeleteAccount, onComplete, onDirtyChange, }: { user: UserType; onResetPassword: () => void; onDeleteAccount: () => void; onComplete: () => void; onDirtyChange?: (dirty: boolean) => void; }) { const { update, updating } = useUpdateUser(); const onUpdateUser = useCallback( (updates: UserInput) => { update({ ...updates, userId: user.id, }); }, [user, update] ); useEffect(() => { if (updating) { return () => { onComplete(); }; } return; }, [updating, onComplete]); return ( } /> ); }