update customer edit form in the middle,
This commit is contained in:
@@ -11,6 +11,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
|
|
||||||
import { paths } from '@/paths';
|
import { paths } from '@/paths';
|
||||||
import { CrCategoryEditForm } from '@/components/dashboard/cr/categories/cr-category-edit-form';
|
import { CrCategoryEditForm } from '@/components/dashboard/cr/categories/cr-category-edit-form';
|
||||||
|
import { CustomerEditForm } from '@/components/dashboard/customer/customer-edit-form';
|
||||||
|
|
||||||
export default function Page(): React.JSX.Element {
|
export default function Page(): React.JSX.Element {
|
||||||
const { t } = useTranslation(['lp_categories']);
|
const { t } = useTranslation(['lp_categories']);
|
||||||
|
@@ -4,7 +4,7 @@ import * as React from 'react';
|
|||||||
import RouterLink from 'next/link';
|
import RouterLink from 'next/link';
|
||||||
import { useParams, useRouter } from 'next/navigation';
|
import { useParams, useRouter } from 'next/navigation';
|
||||||
//
|
//
|
||||||
import { COL_QUIZ_LP_CATEGORIES } from '@/constants';
|
import { COL_CUSTOMERS } from '@/constants';
|
||||||
import { zodResolver } from '@hookform/resolvers/zod';
|
import { zodResolver } from '@hookform/resolvers/zod';
|
||||||
import { LoadingButton } from '@mui/lab';
|
import { LoadingButton } from '@mui/lab';
|
||||||
//
|
//
|
||||||
@@ -35,64 +35,61 @@ import { paths } from '@/paths';
|
|||||||
import { logger } from '@/lib/default-logger';
|
import { logger } from '@/lib/default-logger';
|
||||||
import { base64ToFile, fileToBase64 } from '@/lib/file-to-base64';
|
import { base64ToFile, fileToBase64 } from '@/lib/file-to-base64';
|
||||||
import { pb } from '@/lib/pb';
|
import { pb } from '@/lib/pb';
|
||||||
import { TextEditor } from '@/components/core/text-editor/text-editor';
|
|
||||||
import { toast } from '@/components/core/toaster';
|
import { toast } from '@/components/core/toaster';
|
||||||
import FormLoading from '@/components/loading';
|
import FormLoading from '@/components/loading';
|
||||||
|
|
||||||
import ErrorDisplay from '../../error';
|
// import ErrorDisplay from '../../error';
|
||||||
import type { EditFormProps } from './type';
|
import ErrorDisplay from '../error';
|
||||||
|
import isDevelopment from '@/lib/check-is-development';
|
||||||
|
|
||||||
// TODO: review this
|
// TODO: review this
|
||||||
const schema = zod.object({
|
const schema = zod.object({
|
||||||
cat_name: zod.string().min(1, 'name-is-required').max(255),
|
name: zod.string().min(1, 'Name is required').max(255),
|
||||||
// accept file object when user change image
|
email: zod.string().email('Must be a valid email').min(1, 'Email is required').max(255),
|
||||||
// accept http string when user not changing image
|
phone: zod.string().min(1, 'Phone is required').max(15),
|
||||||
cat_image: zod.union([zod.array(zod.any()), zod.string()]).optional(),
|
company: zod.string().max(255).optional(),
|
||||||
|
billingAddress: zod.object({
|
||||||
// position
|
country: zod.string().min(1, 'Country is required').max(255),
|
||||||
pos: zod.number().min(1, 'position is required').max(99),
|
state: zod.string().min(1, 'State is required').max(255),
|
||||||
|
city: zod.string().min(1, 'City is required').max(255),
|
||||||
// it should be a valid JSON
|
zipCode: zod.string().min(1, 'Zip code is required').max(255),
|
||||||
init_answer: zod
|
line1: zod.string().min(1, 'Street line 1 is required').max(255),
|
||||||
.string()
|
line2: zod.string().max(255).optional(),
|
||||||
.refine(
|
}),
|
||||||
(value) => {
|
taxId: zod.string().max(255).optional(),
|
||||||
try {
|
timezone: zod.string().min(1, 'Timezone is required').max(255),
|
||||||
JSON.parse(value);
|
language: zod.string().min(1, 'Language is required').max(255),
|
||||||
return true;
|
currency: zod.string().min(1, 'Currency is required').max(255),
|
||||||
} catch (error) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{ message: 'init_answer must be a valid JSON' }
|
|
||||||
)
|
|
||||||
.optional(),
|
|
||||||
visible: zod.string(),
|
|
||||||
slug: zod.string().min(0, 'slug-is-required').max(255).optional(),
|
|
||||||
remarks: zod.string().optional(),
|
|
||||||
description: zod.string().optional(),
|
|
||||||
// NOTE: for image handling
|
|
||||||
avatar: zod.string().optional(),
|
avatar: zod.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
type Values = zod.infer<typeof schema>;
|
type Values = zod.infer<typeof schema>;
|
||||||
|
|
||||||
const defaultValues = {
|
const defaultValues = {
|
||||||
cat_name: '',
|
name: '',
|
||||||
cat_image: undefined,
|
email: '',
|
||||||
pos: 1,
|
phone: '',
|
||||||
init_answer: JSON.stringify({}),
|
company: '',
|
||||||
visible: 'hidden',
|
billingAddress: {
|
||||||
slug: '',
|
country: '',
|
||||||
remarks: '',
|
state: '',
|
||||||
description: '',
|
city: '',
|
||||||
|
zipCode: '',
|
||||||
|
line1: '',
|
||||||
|
line2: '',
|
||||||
|
},
|
||||||
|
taxId: '',
|
||||||
|
timezone: '',
|
||||||
|
language: '',
|
||||||
|
currency: '',
|
||||||
|
avatar: '',
|
||||||
} satisfies Values;
|
} satisfies Values;
|
||||||
|
|
||||||
export function CrQuestionEditForm(): React.JSX.Element {
|
export function CustomerEditForm(): React.JSX.Element {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { t } = useTranslation(['lp_categories']);
|
const { t } = useTranslation(['lp_categories']);
|
||||||
|
|
||||||
const { cat_id: catId } = useParams<{ cat_id: string }>();
|
const { customerId } = useParams<{ customerId: string }>();
|
||||||
//
|
//
|
||||||
const [isUpdating, setIsUpdating] = React.useState<boolean>(false);
|
const [isUpdating, setIsUpdating] = React.useState<boolean>(false);
|
||||||
const [showLoading, setShowLoading] = React.useState<boolean>(false);
|
const [showLoading, setShowLoading] = React.useState<boolean>(false);
|
||||||
@@ -112,37 +109,31 @@ export function CrQuestionEditForm(): React.JSX.Element {
|
|||||||
async (values: Values): Promise<void> => {
|
async (values: Values): Promise<void> => {
|
||||||
setIsUpdating(true);
|
setIsUpdating(true);
|
||||||
|
|
||||||
const tempUpdate: EditFormProps = {
|
const updateData = {
|
||||||
cat_name: values.cat_name,
|
name: values.name,
|
||||||
cat_image: values.avatar ? [await base64ToFile(values.avatar)] : null,
|
email: values.email,
|
||||||
pos: values.pos,
|
phone: values.phone,
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
company: values.company,
|
||||||
init_answer: JSON.parse(values.init_answer || '{}'),
|
billingAddress: values.billingAddress,
|
||||||
|
taxId: values.taxId,
|
||||||
visible: values.visible,
|
timezone: values.timezone,
|
||||||
slug: values.slug || 'not-defined',
|
language: values.language,
|
||||||
remarks: values.remarks,
|
currency: values.currency,
|
||||||
description: values.description,
|
avatar: values.avatar ? await base64ToFile(values.avatar) : null,
|
||||||
//
|
|
||||||
// TODO: remove below
|
|
||||||
type: '',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await pb.collection(COL_QUIZ_LP_CATEGORIES).update(catId, tempUpdate);
|
await pb.collection(COL_CUSTOMERS).update(customerId, updateData);
|
||||||
logger.debug(result);
|
toast.success('Customer updated successfully');
|
||||||
toast.success(t('edit.success'));
|
router.push(paths.dashboard.customers.list);
|
||||||
router.push(paths.dashboard.lp_categories.list);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
toast.error(t('update.failed'));
|
toast.error('Failed to update customer');
|
||||||
} finally {
|
} finally {
|
||||||
setIsUpdating(false);
|
setIsUpdating(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// t is not necessary here
|
[customerId, router]
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
[router]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const avatarInputRef = React.useRef<HTMLInputElement>(null);
|
const avatarInputRef = React.useRef<HTMLInputElement>(null);
|
||||||
@@ -171,41 +162,33 @@ export function CrQuestionEditForm(): React.JSX.Element {
|
|||||||
setShowLoading(true);
|
setShowLoading(true);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await pb.collection(COL_QUIZ_LP_CATEGORIES).getOne(id);
|
const result = await pb.collection(COL_CUSTOMERS).getOne(id);
|
||||||
|
reset({ ...defaultValues, ...result });
|
||||||
|
console.log({ result });
|
||||||
|
|
||||||
reset({ ...defaultValues, ...result, init_answer: JSON.stringify(result.init_answer) });
|
if (result.avatar_file) {
|
||||||
setTextDescription(result.description);
|
|
||||||
setTextRemarks(result.remarks);
|
|
||||||
|
|
||||||
if (result.cat_image !== '') {
|
|
||||||
const fetchResult = await fetch(
|
const fetchResult = await fetch(
|
||||||
`http://127.0.0.1:8090/api/files/${result.collectionId}/${result.id}/${result.cat_image}`
|
`http://127.0.0.1:8090/api/files/${result.collectionId}/${result.id}/${result.avatar_file}`
|
||||||
);
|
);
|
||||||
|
|
||||||
const blob = await fetchResult.blob();
|
const blob = await fetchResult.blob();
|
||||||
const url = await fileToBase64(blob);
|
const url = await fileToBase64(blob);
|
||||||
|
|
||||||
setValue('avatar', url);
|
setValue('avatar', url);
|
||||||
} else {
|
|
||||||
setValue('avatar', '');
|
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error(error);
|
logger.error(error);
|
||||||
toast(t('list.error'));
|
toast.error('Failed to load customer data');
|
||||||
|
|
||||||
setShowError({ show: true, detail: JSON.stringify(error, null, 2) });
|
setShowError({ show: true, detail: JSON.stringify(error, null, 2) });
|
||||||
} finally {
|
} finally {
|
||||||
setShowLoading(false);
|
setShowLoading(false);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
[reset, setValue]
|
||||||
[catId]
|
|
||||||
);
|
);
|
||||||
|
|
||||||
React.useEffect(() => {
|
React.useEffect(() => {
|
||||||
void loadExistingData(catId);
|
void loadExistingData(customerId);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [catId]);
|
}, [customerId]);
|
||||||
|
|
||||||
if (showLoading) return <FormLoading />;
|
if (showLoading) return <FormLoading />;
|
||||||
if (showError.show)
|
if (showError.show)
|
||||||
@@ -290,112 +273,79 @@ export function CrQuestionEditForm(): React.JSX.Element {
|
|||||||
xs={12}
|
xs={12}
|
||||||
>
|
>
|
||||||
<Controller
|
<Controller
|
||||||
disabled={isUpdating}
|
|
||||||
control={control}
|
control={control}
|
||||||
name="cat_name"
|
name="name"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormControl
|
<FormControl
|
||||||
disabled={isUpdating}
|
error={Boolean(errors.name)}
|
||||||
error={Boolean(errors.cat_name)}
|
|
||||||
fullWidth
|
fullWidth
|
||||||
>
|
>
|
||||||
<InputLabel required>{t('edit.cat_name')}</InputLabel>
|
<InputLabel required>Name</InputLabel>
|
||||||
<OutlinedInput {...field} />
|
<OutlinedInput {...field} />
|
||||||
{errors.cat_name ? <FormHelperText>{errors.cat_name.message}</FormHelperText> : null}
|
{errors.name ? <FormHelperText>{errors.name.message}</FormHelperText> : null}
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
{/* */}
|
|
||||||
<Grid
|
<Grid
|
||||||
md={6}
|
md={6}
|
||||||
xs={12}
|
xs={12}
|
||||||
>
|
>
|
||||||
<Controller
|
<Controller
|
||||||
disabled={isUpdating}
|
|
||||||
control={control}
|
control={control}
|
||||||
name="pos"
|
name="email"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormControl
|
<FormControl
|
||||||
error={Boolean(errors.pos)}
|
error={Boolean(errors.email)}
|
||||||
fullWidth
|
fullWidth
|
||||||
>
|
>
|
||||||
<InputLabel required>{t('edit.pos')}</InputLabel>
|
<InputLabel required>Email</InputLabel>
|
||||||
<OutlinedInput
|
<OutlinedInput
|
||||||
{...field}
|
{...field}
|
||||||
onChange={(e) => {
|
type="email"
|
||||||
field.onChange(parseInt(e.target.value));
|
|
||||||
}}
|
|
||||||
type="number"
|
|
||||||
/>
|
/>
|
||||||
{errors.pos ? <FormHelperText>{errors.pos.message}</FormHelperText> : null}
|
{errors.email ? <FormHelperText>{errors.email.message}</FormHelperText> : null}
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
{/* */}
|
|
||||||
<Grid
|
<Grid
|
||||||
md={6}
|
md={6}
|
||||||
xs={12}
|
xs={12}
|
||||||
>
|
>
|
||||||
<Controller
|
<Controller
|
||||||
disabled={isUpdating}
|
|
||||||
control={control}
|
control={control}
|
||||||
name="slug"
|
name="phone"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormControl
|
<FormControl
|
||||||
error={Boolean(errors.slug)}
|
error={Boolean(errors.phone)}
|
||||||
fullWidth
|
fullWidth
|
||||||
>
|
>
|
||||||
<InputLabel required>{t('edit.slug')}</InputLabel>
|
<InputLabel required>Phone</InputLabel>
|
||||||
<OutlinedInput {...field} />
|
<OutlinedInput {...field} />
|
||||||
{errors.slug ? <FormHelperText>{errors.slug.message}</FormHelperText> : null}
|
{errors.phone ? <FormHelperText>{errors.phone.message}</FormHelperText> : null}
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
{/* */}
|
|
||||||
<Grid
|
<Grid
|
||||||
md={6}
|
md={6}
|
||||||
xs={12}
|
xs={12}
|
||||||
>
|
>
|
||||||
<Controller
|
<Controller
|
||||||
disabled={isUpdating}
|
|
||||||
control={control}
|
control={control}
|
||||||
name="visible"
|
name="company"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormControl
|
<FormControl
|
||||||
error={Boolean(errors.visible)}
|
error={Boolean(errors.company)}
|
||||||
fullWidth
|
fullWidth
|
||||||
>
|
>
|
||||||
<InputLabel>{t('edit.visible')}</InputLabel>
|
<InputLabel>Company</InputLabel>
|
||||||
<Select {...field}>
|
<OutlinedInput
|
||||||
<MenuItem value="visible">{t('edit.visible')}</MenuItem>
|
{...field}
|
||||||
<MenuItem value="hidden">{t('edit.hidden')}</MenuItem>
|
placeholder="no company name"
|
||||||
</Select>
|
/>
|
||||||
|
{errors.company ? <FormHelperText>{errors.company.message}</FormHelperText> : null}
|
||||||
{errors.visible ? <FormHelperText>{errors.visible.message}</FormHelperText> : null}
|
|
||||||
</FormControl>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</Grid>
|
|
||||||
{/* */}
|
|
||||||
<Grid
|
|
||||||
md={6}
|
|
||||||
xs={12}
|
|
||||||
>
|
|
||||||
<Controller
|
|
||||||
disabled={isUpdating}
|
|
||||||
control={control}
|
|
||||||
name="init_answer"
|
|
||||||
render={({ field }) => (
|
|
||||||
<FormControl
|
|
||||||
error={Boolean(errors.init_answer)}
|
|
||||||
fullWidth
|
|
||||||
>
|
|
||||||
<InputLabel required>{t('edit.init_answer')}</InputLabel>
|
|
||||||
<OutlinedInput {...field} />
|
|
||||||
{errors.init_answer ? <FormHelperText>{errors.init_answer.message}</FormHelperText> : null}
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -404,7 +354,7 @@ export function CrQuestionEditForm(): React.JSX.Element {
|
|||||||
</Stack>
|
</Stack>
|
||||||
{/* */}
|
{/* */}
|
||||||
<Stack spacing={3}>
|
<Stack spacing={3}>
|
||||||
<Typography variant="h6">{t('edit.detail-information')}</Typography>
|
<Typography variant="h6">Billing Information</Typography>
|
||||||
<Grid
|
<Grid
|
||||||
container
|
container
|
||||||
spacing={3}
|
spacing={3}
|
||||||
@@ -414,31 +364,24 @@ export function CrQuestionEditForm(): React.JSX.Element {
|
|||||||
xs={12}
|
xs={12}
|
||||||
>
|
>
|
||||||
<Controller
|
<Controller
|
||||||
disabled={isUpdating}
|
|
||||||
control={control}
|
control={control}
|
||||||
name="description"
|
name="billingAddress.country"
|
||||||
render={({ field }) => {
|
render={({ field }) => (
|
||||||
return (
|
<FormControl
|
||||||
<Box>
|
error={Boolean(errors.billingAddress?.country)}
|
||||||
<Typography
|
fullWidth
|
||||||
variant="subtitle1"
|
>
|
||||||
color="text-secondary"
|
<InputLabel required>Country</InputLabel>
|
||||||
>
|
<Select {...field}>
|
||||||
{t('edit.description')}
|
<MenuItem value="US">United States</MenuItem>
|
||||||
</Typography>
|
<MenuItem value="UK">United Kingdom</MenuItem>
|
||||||
<Box sx={{ mt: '8px', '& .tiptap-container': { height: '200px' } }}>
|
<MenuItem value="CA">Canada</MenuItem>
|
||||||
<TextEditor
|
</Select>
|
||||||
{...field}
|
{errors.billingAddress?.country ? (
|
||||||
content={textDescription}
|
<FormHelperText>{errors.billingAddress.country.message}</FormHelperText>
|
||||||
onUpdate={({ editor }) => {
|
) : null}
|
||||||
field.onChange({ target: { value: editor.getHTML() } });
|
</FormControl>
|
||||||
}}
|
)}
|
||||||
placeholder={t('edit.description.default')}
|
|
||||||
/>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid
|
<Grid
|
||||||
@@ -446,41 +389,199 @@ export function CrQuestionEditForm(): React.JSX.Element {
|
|||||||
xs={12}
|
xs={12}
|
||||||
>
|
>
|
||||||
<Controller
|
<Controller
|
||||||
disabled={isUpdating}
|
|
||||||
control={control}
|
control={control}
|
||||||
name="remarks"
|
name="billingAddress.state"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<Box>
|
<FormControl
|
||||||
<Typography
|
error={Boolean(errors.billingAddress?.state)}
|
||||||
variant="subtitle1"
|
fullWidth
|
||||||
color="text.secondary"
|
>
|
||||||
>
|
<InputLabel required>State</InputLabel>
|
||||||
{t('edit.remarks')}
|
<OutlinedInput {...field} />
|
||||||
</Typography>
|
{errors.billingAddress?.state ? (
|
||||||
<Box sx={{ mt: '8px', '& .tiptap-container': { height: '200px' } }}>
|
<FormHelperText>{errors.billingAddress.state.message}</FormHelperText>
|
||||||
<TextEditor
|
) : null}
|
||||||
content={textRemarks}
|
</FormControl>
|
||||||
onUpdate={({ editor }) => {
|
)}
|
||||||
field.onChange({ target: { value: editor.getText() } });
|
/>
|
||||||
}}
|
</Grid>
|
||||||
hideToolbar
|
<Grid
|
||||||
placeholder={t('edit.remarks.default')}
|
md={6}
|
||||||
/>
|
xs={12}
|
||||||
</Box>
|
>
|
||||||
</Box>
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="billingAddress.city"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormControl
|
||||||
|
error={Boolean(errors.billingAddress?.city)}
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
<InputLabel required>City</InputLabel>
|
||||||
|
<OutlinedInput {...field} />
|
||||||
|
{errors.billingAddress?.city ? (
|
||||||
|
<FormHelperText>{errors.billingAddress.city.message}</FormHelperText>
|
||||||
|
) : null}
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid
|
||||||
|
md={6}
|
||||||
|
xs={12}
|
||||||
|
>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="billingAddress.zipCode"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormControl
|
||||||
|
error={Boolean(errors.billingAddress?.zipCode)}
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
<InputLabel required>Zip Code</InputLabel>
|
||||||
|
<OutlinedInput {...field} />
|
||||||
|
{errors.billingAddress?.zipCode ? (
|
||||||
|
<FormHelperText>{errors.billingAddress.zipCode.message}</FormHelperText>
|
||||||
|
) : null}
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid
|
||||||
|
md={6}
|
||||||
|
xs={12}
|
||||||
|
>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="billingAddress.line1"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormControl
|
||||||
|
error={Boolean(errors.billingAddress?.line1)}
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
<InputLabel required>Address Line 1</InputLabel>
|
||||||
|
<OutlinedInput {...field} />
|
||||||
|
{errors.billingAddress?.line1 ? (
|
||||||
|
<FormHelperText>{errors.billingAddress.line1.message}</FormHelperText>
|
||||||
|
) : null}
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid
|
||||||
|
md={6}
|
||||||
|
xs={12}
|
||||||
|
>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="taxId"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormControl
|
||||||
|
error={Boolean(errors.taxId)}
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
<InputLabel>Tax ID</InputLabel>
|
||||||
|
<OutlinedInput
|
||||||
|
{...field}
|
||||||
|
placeholder="no tax id..."
|
||||||
|
/>
|
||||||
|
{errors.taxId ? <FormHelperText>{errors.taxId.message}</FormHelperText> : null}
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Stack>
|
||||||
|
|
||||||
|
<Stack spacing={3}>
|
||||||
|
<Typography variant="h6">Additional Information</Typography>
|
||||||
|
<Grid
|
||||||
|
container
|
||||||
|
spacing={3}
|
||||||
|
>
|
||||||
|
<Grid
|
||||||
|
md={6}
|
||||||
|
xs={12}
|
||||||
|
>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="timezone"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormControl
|
||||||
|
error={Boolean(errors.timezone)}
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
<InputLabel required>Timezone</InputLabel>
|
||||||
|
<Select {...field}>
|
||||||
|
<MenuItem value="America/New_York">New York</MenuItem>
|
||||||
|
<MenuItem value="Europe/London">London</MenuItem>
|
||||||
|
<MenuItem value="Asia/Tokyo">Tokyo</MenuItem>
|
||||||
|
<MenuItem value="America/Boa_Vista">Boa Vista</MenuItem>
|
||||||
|
<MenuItem value="America/Grand_Turk">Grand Turk</MenuItem>
|
||||||
|
<MenuItem value="Asia/Manila">Manila</MenuItem>
|
||||||
|
<MenuItem value="Asia/Urumqi">Urumqi</MenuItem>
|
||||||
|
<MenuItem value="Africa/Tunis">Tunis</MenuItem>
|
||||||
|
</Select>
|
||||||
|
{errors.timezone ? <FormHelperText>{errors.timezone.message}</FormHelperText> : null}
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid
|
||||||
|
md={6}
|
||||||
|
xs={12}
|
||||||
|
>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="language"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormControl
|
||||||
|
error={Boolean(errors.language)}
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
<InputLabel required>Language</InputLabel>
|
||||||
|
<Select {...field}>
|
||||||
|
<MenuItem value="en">English</MenuItem>
|
||||||
|
<MenuItem value="es">Spanish</MenuItem>
|
||||||
|
<MenuItem value="fr">French</MenuItem>
|
||||||
|
</Select>
|
||||||
|
{errors.language ? <FormHelperText>{errors.language.message}</FormHelperText> : null}
|
||||||
|
</FormControl>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</Grid>
|
||||||
|
<Grid
|
||||||
|
md={6}
|
||||||
|
xs={12}
|
||||||
|
>
|
||||||
|
<Controller
|
||||||
|
control={control}
|
||||||
|
name="currency"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormControl
|
||||||
|
error={Boolean(errors.currency)}
|
||||||
|
fullWidth
|
||||||
|
>
|
||||||
|
<InputLabel required>Currency</InputLabel>
|
||||||
|
<Select {...field}>
|
||||||
|
<MenuItem value="USD">USD</MenuItem>
|
||||||
|
<MenuItem value="EUR">EUR</MenuItem>
|
||||||
|
<MenuItem value="GBP">GBP</MenuItem>
|
||||||
|
</Select>
|
||||||
|
{errors.currency ? <FormHelperText>{errors.currency.message}</FormHelperText> : null}
|
||||||
|
</FormControl>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Stack>
|
</Stack>
|
||||||
{/* */}
|
|
||||||
</Stack>
|
</Stack>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
<CardActions sx={{ justifyContent: 'flex-end' }}>
|
<CardActions sx={{ justifyContent: 'flex-end' }}>
|
||||||
<Button
|
<Button
|
||||||
color="secondary"
|
color="secondary"
|
||||||
component={RouterLink}
|
component={RouterLink}
|
||||||
href={paths.dashboard.lp_categories.list}
|
href={paths.dashboard.customers.list}
|
||||||
>
|
>
|
||||||
{t('edit.cancelButton')}
|
{t('edit.cancelButton')}
|
||||||
</Button>
|
</Button>
|
||||||
|
@@ -143,7 +143,7 @@ function columns(handleDeleteClick: (testId: string) => void): ColumnDef<Custome
|
|||||||
//
|
//
|
||||||
color="secondary"
|
color="secondary"
|
||||||
component={RouterLink}
|
component={RouterLink}
|
||||||
href={paths.dashboard.cr_questions.details(row.id)}
|
href={paths.dashboard.customers.details(row.id)}
|
||||||
>
|
>
|
||||||
<PencilSimpleIcon size={24} />
|
<PencilSimpleIcon size={24} />
|
||||||
</LoadingButton>
|
</LoadingButton>
|
||||||
|
@@ -1,7 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { SortDir } from './phone-filter-popover';
|
export type SortDir = 'asc' | 'desc';
|
||||||
import { Customer } from './type.d';
|
|
||||||
|
|
||||||
export interface Customer {
|
export interface Customer {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -26,11 +25,24 @@ export interface CreateFormProps {
|
|||||||
|
|
||||||
export interface EditFormProps {
|
export interface EditFormProps {
|
||||||
name: string;
|
name: string;
|
||||||
avatar?: string;
|
|
||||||
email: string;
|
email: string;
|
||||||
phone?: string;
|
phone?: string;
|
||||||
quota: number;
|
company?: string;
|
||||||
status: 'pending' | 'active' | 'blocked';
|
billingAddress?: {
|
||||||
|
country: string;
|
||||||
|
state: string;
|
||||||
|
city: string;
|
||||||
|
zipCode: string;
|
||||||
|
line1: string;
|
||||||
|
line2?: string;
|
||||||
|
};
|
||||||
|
taxId?: string;
|
||||||
|
timezone: string;
|
||||||
|
language: string;
|
||||||
|
currency: string;
|
||||||
|
avatar?: string;
|
||||||
|
// quota?: number;
|
||||||
|
// status?: 'pending' | 'active' | 'blocked';
|
||||||
}
|
}
|
||||||
export interface CustomersFiltersProps {
|
export interface CustomersFiltersProps {
|
||||||
filters?: Filters;
|
filters?: Filters;
|
||||||
@@ -42,5 +54,3 @@ export interface Filters {
|
|||||||
phone?: string;
|
phone?: string;
|
||||||
status?: string;
|
status?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SortDir = 'asc' | 'desc';
|
|
||||||
|
@@ -626,6 +626,19 @@
|
|||||||
"thumbs": [],
|
"thumbs": [],
|
||||||
"type": "file"
|
"type": "file"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"cascadeDelete": false,
|
||||||
|
"collectionId": "_pb_users_auth_",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "relation2809058197",
|
||||||
|
"maxSelect": 1,
|
||||||
|
"minSelect": 0,
|
||||||
|
"name": "user_id",
|
||||||
|
"presentable": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "relation"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"hidden": false,
|
"hidden": false,
|
||||||
"id": "autodate2990389176",
|
"id": "autodate2990389176",
|
||||||
@@ -2852,6 +2865,138 @@
|
|||||||
],
|
],
|
||||||
"system": true
|
"system": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "pbc_1509025625",
|
||||||
|
"listRule": null,
|
||||||
|
"viewRule": null,
|
||||||
|
"createRule": null,
|
||||||
|
"updateRule": null,
|
||||||
|
"deleteRule": null,
|
||||||
|
"name": "billingAddress",
|
||||||
|
"type": "base",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"autogeneratePattern": "[a-z0-9]{15}",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "text3208210256",
|
||||||
|
"max": 15,
|
||||||
|
"min": 15,
|
||||||
|
"name": "id",
|
||||||
|
"pattern": "^[a-z0-9]+$",
|
||||||
|
"presentable": false,
|
||||||
|
"primaryKey": true,
|
||||||
|
"required": true,
|
||||||
|
"system": true,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"autogeneratePattern": "",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "text1400097126",
|
||||||
|
"max": 0,
|
||||||
|
"min": 0,
|
||||||
|
"name": "country",
|
||||||
|
"pattern": "",
|
||||||
|
"presentable": false,
|
||||||
|
"primaryKey": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"autogeneratePattern": "",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "text2744374011",
|
||||||
|
"max": 0,
|
||||||
|
"min": 0,
|
||||||
|
"name": "state",
|
||||||
|
"pattern": "",
|
||||||
|
"presentable": false,
|
||||||
|
"primaryKey": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"autogeneratePattern": "",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "text760939060",
|
||||||
|
"max": 0,
|
||||||
|
"min": 0,
|
||||||
|
"name": "city",
|
||||||
|
"pattern": "",
|
||||||
|
"presentable": false,
|
||||||
|
"primaryKey": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"autogeneratePattern": "",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "text4114525948",
|
||||||
|
"max": 0,
|
||||||
|
"min": 0,
|
||||||
|
"name": "zipCode",
|
||||||
|
"pattern": "",
|
||||||
|
"presentable": false,
|
||||||
|
"primaryKey": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"autogeneratePattern": "",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "text3620973610",
|
||||||
|
"max": 0,
|
||||||
|
"min": 0,
|
||||||
|
"name": "line1",
|
||||||
|
"pattern": "",
|
||||||
|
"presentable": false,
|
||||||
|
"primaryKey": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"autogeneratePattern": "",
|
||||||
|
"hidden": false,
|
||||||
|
"id": "text1322974608",
|
||||||
|
"max": 0,
|
||||||
|
"min": 0,
|
||||||
|
"name": "line2",
|
||||||
|
"pattern": "",
|
||||||
|
"presentable": false,
|
||||||
|
"primaryKey": false,
|
||||||
|
"required": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": false,
|
||||||
|
"id": "autodate2990389176",
|
||||||
|
"name": "created",
|
||||||
|
"onCreate": true,
|
||||||
|
"onUpdate": false,
|
||||||
|
"presentable": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "autodate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"hidden": false,
|
||||||
|
"id": "autodate3332085495",
|
||||||
|
"name": "updated",
|
||||||
|
"onCreate": true,
|
||||||
|
"onUpdate": true,
|
||||||
|
"presentable": false,
|
||||||
|
"system": false,
|
||||||
|
"type": "autodate"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"indexes": [],
|
||||||
|
"system": false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "pbc_123408445",
|
"id": "pbc_123408445",
|
||||||
"listRule": "",
|
"listRule": "",
|
||||||
|
Reference in New Issue
Block a user