305 lines
9.9 KiB
TypeScript
305 lines
9.9 KiB
TypeScript
import { zodResolver } from '@hookform/resolvers/zod';
|
|
import Box from '@mui/material/Box';
|
|
import Button from '@mui/material/Button';
|
|
import Card from '@mui/material/Card';
|
|
import FormControlLabel from '@mui/material/FormControlLabel';
|
|
import Grid from '@mui/material/Grid';
|
|
import Stack from '@mui/material/Stack';
|
|
import Switch from '@mui/material/Switch';
|
|
import Typography from '@mui/material/Typography';
|
|
import { useState } from 'react';
|
|
import { Controller, useForm } from 'react-hook-form';
|
|
import { useTranslation } from 'react-i18next';
|
|
import { isValidPhoneNumber } from 'react-phone-number-input/input';
|
|
import { createPartyUser, deletePartyUser, updatePartyUser } from 'src/actions/party-user';
|
|
import { Field, Form, schemaHelper } from 'src/components/hook-form';
|
|
import { Label } from 'src/components/label';
|
|
import { toast } from 'src/components/snackbar';
|
|
import { useRouter } from 'src/routes/hooks';
|
|
import { paths } from 'src/routes/paths';
|
|
import type { IPartyUserItem } from 'src/types/party-user';
|
|
import { fileToBase64 } from 'src/utils/file-to-base64';
|
|
import { fData } from 'src/utils/format-number';
|
|
import { z as zod } from 'zod';
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
export type NewUserSchemaType = zod.infer<typeof NewUserSchema>;
|
|
|
|
export const NewUserSchema = zod.object({
|
|
name: zod.string().min(1, { message: 'Name is required!' }).optional().or(zod.literal('')),
|
|
city: zod.string().min(1, { message: 'City is required!' }).optional().or(zod.literal('')),
|
|
role: zod.string().min(1, { message: 'Role is required!' }),
|
|
email: zod
|
|
.string()
|
|
.min(1, { message: 'Email is required!' })
|
|
.email({ message: 'Email must be a valid email address!' }),
|
|
state: zod.string().min(1, { message: 'State is required!' }).optional().or(zod.literal('')),
|
|
status: zod.string(),
|
|
address: zod.string().min(1, { message: 'Address is required!' }).optional().or(zod.literal('')),
|
|
country: schemaHelper
|
|
.nullableInput(zod.string().min(1, { message: 'Country is required!' }), {
|
|
// message for null value
|
|
message: 'Country is required!',
|
|
})
|
|
.optional()
|
|
.or(zod.literal('')),
|
|
zipCode: zod.string().min(1, { message: 'Zip code is required!' }).optional().or(zod.literal('')),
|
|
company: zod.string().min(1, { message: 'Company is required!' }).optional().or(zod.literal('')),
|
|
avatarUrl: zod.string().optional().or(zod.literal('')),
|
|
phoneNumber: zod.string().optional().or(zod.literal('')),
|
|
isVerified: zod.boolean().default(true),
|
|
//
|
|
username: zod.string().optional().or(zod.literal('')),
|
|
password: zod.string().optional().or(zod.literal('')),
|
|
});
|
|
|
|
// ----------------------------------------------------------------------
|
|
|
|
type Props = {
|
|
currentUser?: IPartyUserItem;
|
|
};
|
|
|
|
export function PartyUserNewEditForm({ currentUser }: Props) {
|
|
const { t } = useTranslation();
|
|
|
|
const router = useRouter();
|
|
|
|
const defaultValues: NewUserSchemaType = {
|
|
status: '',
|
|
avatarUrl: '',
|
|
name: '新用戶名字',
|
|
email: 'user@123.com',
|
|
phoneNumber: '',
|
|
country: '',
|
|
state: '',
|
|
city: '',
|
|
address: '',
|
|
zipCode: '',
|
|
company: '',
|
|
role: 'user',
|
|
// Email is verified
|
|
isVerified: true,
|
|
//
|
|
username: '',
|
|
password: '',
|
|
};
|
|
|
|
const methods = useForm<NewUserSchemaType>({
|
|
mode: 'onSubmit',
|
|
resolver: zodResolver(NewUserSchema),
|
|
defaultValues,
|
|
values: currentUser,
|
|
});
|
|
|
|
const {
|
|
reset,
|
|
watch,
|
|
control,
|
|
handleSubmit,
|
|
formState: { errors, isSubmitting },
|
|
} = methods;
|
|
|
|
const values = watch();
|
|
|
|
const [disableDeleteUserButton, setDisableDeleteUserButton] = useState<boolean>(false);
|
|
const handleDeleteUserClick = async () => {
|
|
setDisableDeleteUserButton(true);
|
|
try {
|
|
if (currentUser) {
|
|
await deletePartyUser(currentUser.id);
|
|
toast.success(t('party user deleted'));
|
|
router.push(paths.dashboard.partyUser.list);
|
|
}
|
|
} catch (error) {
|
|
console.error(error);
|
|
}
|
|
|
|
setDisableDeleteUserButton(false);
|
|
};
|
|
|
|
const onSubmit = handleSubmit(async (data: any) => {
|
|
try {
|
|
const temp: any = data.avatarUrl;
|
|
if (temp instanceof File) {
|
|
data.avatarUrl = await fileToBase64(temp);
|
|
}
|
|
|
|
const sanitizedValues: IPartyUserItem = values as unknown as IPartyUserItem;
|
|
|
|
if (currentUser) {
|
|
// perform
|
|
await updatePartyUser(sanitizedValues);
|
|
} else {
|
|
// perform create
|
|
await createPartyUser(sanitizedValues);
|
|
}
|
|
|
|
toast.success(currentUser ? t('Update success!') : t('Create success!'));
|
|
|
|
router.push(paths.dashboard.partyUser.list);
|
|
|
|
// console.info('DATA', data);
|
|
} catch (error) {
|
|
console.error(error);
|
|
}
|
|
});
|
|
|
|
return (
|
|
<Form methods={methods} onSubmit={onSubmit}>
|
|
<Grid container spacing={3}>
|
|
<Grid size={{ xs: 12, md: 4 }}>
|
|
<Card sx={{ pt: 10, pb: 5, px: 3 }}>
|
|
{currentUser && (
|
|
<Label
|
|
color={
|
|
(values.status === 'active' && 'success') ||
|
|
(values.status === 'banned' && 'error') ||
|
|
'warning'
|
|
}
|
|
sx={{ position: 'absolute', top: 24, right: 24 }}
|
|
>
|
|
{values.status}
|
|
</Label>
|
|
)}
|
|
|
|
<Box sx={{ mb: 5 }}>
|
|
<Field.UploadAvatar
|
|
name="avatarUrl"
|
|
maxSize={3145728}
|
|
helperText={
|
|
<Typography
|
|
variant="caption"
|
|
sx={{
|
|
mt: 3,
|
|
mx: 'auto',
|
|
display: 'block',
|
|
textAlign: 'center',
|
|
color: 'text.disabled',
|
|
}}
|
|
>
|
|
{t('Allowed')} *.jpeg, *.jpg, *.png, *.gif
|
|
<br /> {t('max size of')} {fData(3145728)}
|
|
</Typography>
|
|
}
|
|
/>
|
|
</Box>
|
|
|
|
{currentUser && (
|
|
<FormControlLabel
|
|
labelPlacement="start"
|
|
control={
|
|
<Controller
|
|
name="status"
|
|
control={control}
|
|
render={({ field }) => (
|
|
<Switch
|
|
{...field}
|
|
checked={field.value !== 'active'}
|
|
onChange={(event) =>
|
|
field.onChange(event.target.checked ? 'banned' : 'active')
|
|
}
|
|
/>
|
|
)}
|
|
/>
|
|
}
|
|
label={
|
|
<>
|
|
<Typography variant="subtitle2" sx={{ mb: 0.5 }}>
|
|
{t('Banned')}
|
|
</Typography>
|
|
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
|
{t('Apply disable account')}
|
|
</Typography>
|
|
</>
|
|
}
|
|
sx={{
|
|
mx: 0,
|
|
mb: 3,
|
|
width: 1,
|
|
justifyContent: 'space-between',
|
|
}}
|
|
/>
|
|
)}
|
|
|
|
<Field.Switch
|
|
name="isVerified"
|
|
labelPlacement="start"
|
|
label={
|
|
<>
|
|
<Typography variant="subtitle2" sx={{ mb: 0.5 }}>
|
|
{t('Email verified')}
|
|
</Typography>
|
|
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
|
|
{t('Disabling this will automatically send the user a verification email')}
|
|
</Typography>
|
|
</>
|
|
}
|
|
sx={{ mx: 0, width: 1, justifyContent: 'space-between' }}
|
|
/>
|
|
|
|
{currentUser && (
|
|
<Stack sx={{ mt: 3, alignItems: 'center', justifyContent: 'center' }}>
|
|
<Button
|
|
disabled={disableDeleteUserButton}
|
|
loading={disableDeleteUserButton}
|
|
variant="soft"
|
|
color="error"
|
|
onClick={handleDeleteUserClick}
|
|
>
|
|
{t('Delete user')}
|
|
</Button>
|
|
</Stack>
|
|
)}
|
|
</Card>
|
|
</Grid>
|
|
|
|
<Grid size={{ xs: 12, md: 8 }}>
|
|
<Card sx={{ p: 3 }}>
|
|
<Box
|
|
sx={{
|
|
rowGap: 3,
|
|
columnGap: 2,
|
|
display: 'grid',
|
|
gridTemplateColumns: { xs: 'repeat(1, 1fr)', sm: 'repeat(2, 1fr)' },
|
|
}}
|
|
>
|
|
<Field.Text name="username" label={t('username')} />
|
|
<Field.Text name="password" label={t('password')} />
|
|
|
|
<Field.Text name="name" label={t('Full name')} />
|
|
<Field.Text name="email" label={t('Email address')} />
|
|
<Field.Phone name="phoneNumber" label={t('Phone number')} country="HK" />
|
|
|
|
<Field.CountrySelect
|
|
fullWidth
|
|
name="country"
|
|
label={t('Country')}
|
|
placeholder={t('Choose a country')}
|
|
/>
|
|
|
|
<Field.Text name="state" label={t('State/region')} />
|
|
<Field.Text name="city" label={t('City')} />
|
|
<Field.Text name="address" label={t('Address')} />
|
|
<Field.Text name="zipCode" label={t('Zip/code')} required={false} />
|
|
<Field.Text name="company" label={t('Company')} />
|
|
<Field.Text name="role" label={t('Role')} />
|
|
</Box>
|
|
|
|
<Stack sx={{ mt: 3, alignItems: 'flex-end' }}>
|
|
<Button
|
|
disabled={isSubmitting}
|
|
loading={isSubmitting}
|
|
type="submit"
|
|
variant="contained"
|
|
>
|
|
{!currentUser ? t('create user') : t('save changes')}
|
|
</Button>
|
|
</Stack>
|
|
</Card>
|
|
</Grid>
|
|
</Grid>
|
|
</Form>
|
|
);
|
|
}
|