add UserActivationEditForm component for user activation management
```
This commit is contained in:
louiscklaw
2025-05-15 09:24:41 +08:00
parent 8e3d463f78
commit d4fcc1dd8f
2 changed files with 211 additions and 12 deletions

View File

@@ -0,0 +1,193 @@
'use client';
//
// src/components/dashboard/user_meta/user-activation-edit-form.tsx
// RULES
// handle user change activation of other users
//
import * as React from 'react';
import RouterLink from 'next/link';
import { useParams, useRouter } from 'next/navigation';
//
import { COL_USERS } from '@/constants';
import { getUserById } from '@/db/Users/GetById';
import { zodResolver } from '@hookform/resolvers/zod';
import { LoadingButton } from '@mui/lab';
//
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Card from '@mui/material/Card';
import CardActions from '@mui/material/CardActions';
import CardContent from '@mui/material/CardContent';
import Divider from '@mui/material/Divider';
import FormControl from '@mui/material/FormControl';
import FormHelperText from '@mui/material/FormHelperText';
import InputLabel from '@mui/material/InputLabel';
import MenuItem from '@mui/material/MenuItem';
import Select from '@mui/material/Select';
import Stack from '@mui/material/Stack';
//
//
import { Controller, useForm } from 'react-hook-form';
import { useTranslation } from 'react-i18next';
import { z as zod } from 'zod';
import { paths } from '@/paths';
import isDevelopment from '@/lib/check-is-development';
import { logger } from '@/lib/default-logger';
import { pb } from '@/lib/pb';
import { toast } from '@/components/core/toaster';
import FormLoading from '@/components/loading';
// import ErrorDisplay from '../../error';
import ErrorDisplay from '../error';
// TODO: review this
const schema = zod.object({
verified: zod.string(),
});
type Values = zod.infer<typeof schema>;
const defaultValues = {
verified: 'false',
} satisfies Values;
export function UserActivationEditForm({ userId }: { userId: string }): React.JSX.Element {
const router = useRouter();
const { t } = useTranslation(['user_metas']);
const { id: userMetaId } = useParams<{ id: string }>();
//
const [isUpdating, setIsUpdating] = React.useState<boolean>(false);
const [showLoading, setShowLoading] = React.useState<boolean>(false);
//
const [showError, setShowError] = React.useState({ show: false, detail: '' });
const {
control,
handleSubmit,
formState: { errors },
setValue,
reset,
watch,
} = useForm<Values>({ defaultValues, resolver: zodResolver(schema) });
const onSubmit = React.useCallback(
async (values: Values): Promise<void> => {
setIsUpdating(true);
const updateData = {
verified: false,
};
try {
await pb.collection(COL_USERS).update(userId, updateData);
toast.success(t('user-updated-successfully'));
// router.push(paths.dashboard.user_metas.list);
} catch (error) {
logger.error(error);
toast.error(t('failed-to-update-user-meta'));
} finally {
setIsUpdating(false);
}
},
[userMetaId, router]
);
// TODO: need to align with save form
// use trycatch
const [textDescription, setTextDescription] = React.useState<string>('');
const [textRemarks, setTextRemarks] = React.useState<string>('');
// load existing data when user arrive
const loadExistingData = React.useCallback(
async (id: string) => {
try {
const result = await getUserById(userId);
reset({ verified: result.verified.toString() });
setShowLoading(false);
} catch (error) {
logger.error(error);
toast.error('failed-to-load-user-meta-data');
setShowError({ show: true, detail: JSON.stringify(error, null, 2) });
} finally {
setShowLoading(false);
}
},
[reset, setValue]
);
React.useEffect(() => {
void loadExistingData(userId);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [userId]);
if (showLoading) return <FormLoading />;
if (showError.show)
return (
<ErrorDisplay
message={t('error.unable-to-process-request')}
code="500"
details={showError.detail}
/>
);
return (
<form onSubmit={handleSubmit(onSubmit)}>
<Card>
<CardContent>
<Stack
divider={<Divider />}
spacing={4}
>
<Controller
control={control}
name="verified"
render={({ field }) => (
<FormControl
error={Boolean(errors.verified)}
fullWidth
>
<InputLabel required>
{t('user-activation')} {t('optional')}
</InputLabel>
<Select {...field}>
<MenuItem value="true">{t('activated')}</MenuItem>
<MenuItem value="false">{t('not-actviate')}</MenuItem>
</Select>
{errors.verified ? <FormHelperText>{errors.verified.message}</FormHelperText> : null}
</FormControl>
)}
/>
</Stack>
</CardContent>
<CardActions sx={{ justifyContent: 'flex-end' }}>
<div>
<Button
color="secondary"
component={RouterLink}
href={paths.dashboard.user_metas.list}
>
{t('edit.cancelButton')}
</Button>
<LoadingButton
disabled={isUpdating}
loading={isUpdating}
type="submit"
variant="contained"
>
{t('edit.updateButton')}
</LoadingButton>
</div>
</CardActions>
</Card>
<Box sx={{ display: isDevelopment ? 'block' : 'none' }}>
<pre>{JSON.stringify({ errors }, null, 2)}</pre>
</Box>
</form>
);
}

View File

@@ -40,12 +40,14 @@ import { paths } from '@/paths';
import isDevelopment from '@/lib/check-is-development';
import { logger } from '@/lib/default-logger';
import { base64ToFile, fileToBase64 } from '@/lib/file-to-base64';
import getImageUrlFromFile from '@/lib/get-image-url-from-file.ts';
import { pb } from '@/lib/pb';
import { toast } from '@/components/core/toaster';
import FormLoading from '@/components/loading';
// import ErrorDisplay from '../../error';
import ErrorDisplay from '../error';
import { UserMeta } from './type.d';
// TODO: review this
const schema = zod.object({
@@ -92,7 +94,7 @@ const defaultValues = {
export function UserMetaEditForm(): React.JSX.Element {
const router = useRouter();
const { t } = useTranslation(['lp_categories']);
const { t } = useTranslation(['user_metas']);
const { id: userMetaId } = useParams<{ id: string }>();
//
@@ -129,11 +131,12 @@ export function UserMetaEditForm(): React.JSX.Element {
try {
await pb.collection(COL_USER_METAS).update(userMetaId, updateData);
toast.success('Teacher updated successfully');
router.push(paths.dashboard.teachers.list);
toast.success(t('user-updated-successfully'));
router.push(paths.dashboard.user_metas.list);
} catch (error) {
logger.error(error);
toast.error('Failed to update teacher');
toast.error(t('failed-to-update-user-meta'));
} finally {
setIsUpdating(false);
}
@@ -161,27 +164,29 @@ export function UserMetaEditForm(): React.JSX.Element {
const [textDescription, setTextDescription] = React.useState<string>('');
const [textRemarks, setTextRemarks] = React.useState<string>('');
const [userId, setUserId] = React.useState<string>('');
// load existing data when user arrive
const loadExistingData = React.useCallback(
async (id: string) => {
setShowLoading(true);
try {
const result = await pb.collection(COL_USER_METAS).getOne(id);
// const result = await pb.collection(COL_USER_METAS).getOne(id);
const result = (await getUserMetaById(id)) as unknown as UserMeta;
setUserId(result.user_id);
reset({ ...defaultValues, ...result });
console.log({ result });
if (result.avatar) {
const fetchResult = await fetch(
`http://127.0.0.1:8090/api/files/${result.collectionId}/${result.id}/${result.avatar}`
);
const fetchResult = await fetch(getImageUrlFromFile(result.collectionId, result.id, result.avatar));
const blob = await fetchResult.blob();
const url = await fileToBase64(blob);
setValue('avatar', url);
}
} catch (error) {
logger.error(error);
toast.error('Failed to load teacher data');
toast.error('failed-to-load-user-meta-data');
setShowError({ show: true, detail: JSON.stringify(error, null, 2) });
} finally {
setShowLoading(false);
@@ -355,6 +360,7 @@ export function UserMetaEditForm(): React.JSX.Element {
)}
/>
</Grid>
{/* */}
</Grid>
</Stack>
{/* */}
@@ -499,7 +505,7 @@ export function UserMetaEditForm(): React.JSX.Element {
</Stack>
<Stack spacing={3}>
<Typography variant="h6">Additional Information</Typography>
<Typography variant="h6">{t('additional-information')}</Typography>
<Grid
container
spacing={3}
@@ -586,7 +592,7 @@ export function UserMetaEditForm(): React.JSX.Element {
<Button
color="secondary"
component={RouterLink}
href={paths.dashboard.teachers.list}
href={paths.dashboard.user_metas.list}
>
{t('edit.cancelButton')}
</Button>