clean code

This commit is contained in:
hamid zarghami
2026-06-07 14:23:51 +03:30
parent 0138261643
commit 17717eb195
4 changed files with 230 additions and 141 deletions
+6 -141
View File
@@ -1,152 +1,17 @@
import Button from "@/components/Button";
import DatePicker from "@/components/DatePicker";
import Error from "@/components/Error";
import Input from "@/components/Input";
import Textarea from "@/components/Textarea";
import { toast } from "@/components/Toast";
import UploadBoxDraggble from "@/components/UploadBoxDraggble";
import { extractErrorMessage } from "@/helpers/utils";
import DesignerRequestForm from "@/pages/designer/components/DesignerRequestForm";
import { useSubmitDesignRequest } from "@/pages/designer/hooks/useDesignerData";
import { usePageTitle } from "@/hooks/usePageTitle";
import { useInitiateDesignRequest } from "@/pages/designer/hooks/useDesignerData";
import type { DesignerRequestFormValues } from "@/pages/designer/types/Types";
import { useMultipleUpload } from "@/pages/uploader/hooks/useUploaderData";
import { Form, Formik, type FormikHelpers, type FormikProps } from "formik";
import { type FC } from "react";
import * as Yup from "yup";
const PRICE_PER_PAGE = 1000;
const initialValues: DesignerRequestFormValues = {
title: "",
count: "",
desc: "",
expectedDate: "",
attachments: [],
};
const validationSchema = Yup.object({
title: Yup.string().trim().required("عنوان کاتالوگ الزامی است"),
count: Yup.number()
.transform((value, originalValue) => (originalValue === "" ? undefined : value))
.optional()
.min(1, "حداقل تعداد صفحات ۱ است"),
desc: Yup.string().trim(),
expectedDate: Yup.string().required("زمان تحویل الزامی است"),
attachments: Yup.array(),
});
type DesignerRequestFormFieldsProps = FormikProps<DesignerRequestFormValues>;
const DesignerRequestFormFields: FC<DesignerRequestFormFieldsProps> = ({ values, errors, touched, isSubmitting, setFieldValue, setFieldTouched, handleChange, handleBlur }) => {
const totalPrice = (values.count === "" ? 0 : values.count) * PRICE_PER_PAGE;
const handleAttachmentChange = (files: File[]) => {
setFieldValue("attachments", files);
setFieldTouched("attachments", true);
};
return (
<Form className="mt-6 space-y-5">
<Input label="عنوان کاتالوگ" name="title" value={values.title} onChange={handleChange} onBlur={handleBlur} error_text={touched.title ? errors.title : undefined} />
<div className="grid grid-cols-1 gap-4 lg:grid-cols-2">
<DatePicker
label="زمان تحویل موردنظر"
placeholder="تاریخ را انتخاب کنید"
defaulValue={values.expectedDate}
onChange={(date) => {
setFieldValue("expectedDate", date);
setFieldTouched("expectedDate", true);
}}
error_text={touched.expectedDate ? errors.expectedDate : undefined}
/>
<Input
label="تعداد صفحات"
name="count"
type="number"
isNotRequired
value={values.count}
onChange={(event) => {
const value = event.target.value;
setFieldValue("count", value === "" ? "" : Number(value));
}}
onBlur={handleBlur}
error_text={touched.count ? errors.count : undefined}
/>
</div>
<Textarea label="توضیحات" name="desc" value={values.desc} onChange={handleChange} onBlur={handleBlur} className="min-h-[96px]" error_text={touched.desc ? errors.desc : undefined} />
<div>
<div className="mb-2 text-sm">فایل های ضمیمه</div>
<UploadBoxDraggble label="فایل مورد نظر را آپلود کنید" onChange={handleAttachmentChange} isMultiple isFile isLoading={isSubmitting} loadingLabel="در حال ثبت درخواست..." />
{touched.attachments && errors.attachments ? <Error errorText={String(errors.attachments)} /> : null}
</div>
<div className="flex">
<div className="flex-1"></div>
<div className="flex-1 flex gap-4">
<div className="w-[250px]">
<Input label="قیمت به ازای هر صفحه" value={`${PRICE_PER_PAGE.toLocaleString("fa-IR")} تومان`} readOnly />
</div>
<Input label="مبلغ قابل پرداخت برای شما" value={`${totalPrice.toLocaleString("fa-IR")} تومان`} readOnly />
</div>
</div>
<div className="flex justify-end mt-5">
<Button type="submit" className="w-fit px-6" disabled={isSubmitting}>
ثبت درخواست
</Button>
</div>
</Form>
);
};
const DesignerRequest: FC = () => {
usePageTitle("درخواست طراحی");
const { mutateAsync: uploadFiles } = useMultipleUpload();
const { mutateAsync: initiateDesignRequest } = useInitiateDesignRequest();
const handleSubmit = async (values: DesignerRequestFormValues, { setSubmitting, resetForm }: FormikHelpers<DesignerRequestFormValues>) => {
let attachmentUrls: string[] = [];
if (values.attachments.length > 0) {
const uploadResponse = await uploadFiles(values.attachments);
attachmentUrls = uploadResponse.data.map((item) => item.url);
}
const { attachments, count, ...rest } = values;
await initiateDesignRequest(
{
...rest,
count: count === "" ? undefined : count,
attachments: attachmentUrls,
},
{
onSuccess: (data) => {
toast("صورت حساب با موفقیت ثبت شد", "success");
window.open(`${import.meta.env.VITE_INVOICE_URL}${data?.data?.invoiceId}`, "_blank");
setSubmitting(false);
resetForm();
},
onError: (error) => {
toast(extractErrorMessage(error), "error");
},
},
);
toast("درخواست با موفقیت ثبت شد", "success");
resetForm();
};
const handleSubmit = useSubmitDesignRequest();
return (
<div className="mt-4 w-full">
<h1 className="">درخواست طراحی کاتالوگ</h1>
<div className="rounded-[32px] bg-white p-4 md:p-6 lg:p-8 mt-6">
<Formik initialValues={initialValues} validationSchema={validationSchema} onSubmit={handleSubmit}>
{(formikProps) => <DesignerRequestFormFields {...formikProps} />}
</Formik>
<h1>درخواست طراحی کاتالوگ</h1>
<div className="mt-6 rounded-[32px] bg-white p-4 md:p-6 lg:p-8">
<DesignerRequestForm onSubmit={handleSubmit} />
</div>
</div>
);
@@ -0,0 +1,142 @@
import Button from "@/components/Button";
import DatePicker from "@/components/DatePicker";
import Error from "@/components/Error";
import Input from "@/components/Input";
import Textarea from "@/components/Textarea";
import UploadBoxDraggble from "@/components/UploadBoxDraggble";
import {
designerRequestInitialValues,
designerRequestValidationSchema,
getTotalPrice,
PRICE_PER_PAGE,
} from "@/pages/designer/designerRequestForm";
import type { DesignerRequestFormValues } from "@/pages/designer/types/Types";
import { Form, Formik, useFormikContext, type FormikHelpers } from "formik";
import { type FC } from "react";
type Props = {
onSubmit: (
values: DesignerRequestFormValues,
helpers: FormikHelpers<DesignerRequestFormValues>,
) => void | Promise<void>;
};
const DesignerRequestFormFields: FC = () => {
const {
values,
errors,
touched,
isSubmitting,
setFieldValue,
setFieldTouched,
handleChange,
handleBlur,
} = useFormikContext<DesignerRequestFormValues>();
const totalPrice = getTotalPrice(values.count);
const handleAttachmentChange = (files: File[]) => {
setFieldValue("attachments", files);
setFieldTouched("attachments", true);
};
return (
<Form className="mt-6 space-y-5">
<Input
label="عنوان کاتالوگ"
name="title"
value={values.title}
onChange={handleChange}
onBlur={handleBlur}
error_text={touched.title ? errors.title : undefined}
/>
<div className="grid grid-cols-1 gap-4 lg:grid-cols-2">
<DatePicker
label="زمان تحویل موردنظر"
placeholder="تاریخ را انتخاب کنید"
defaulValue={values.expectedDate}
onChange={(date) => {
setFieldValue("expectedDate", date);
setFieldTouched("expectedDate", true);
}}
error_text={touched.expectedDate ? errors.expectedDate : undefined}
/>
<Input
label="تعداد صفحات"
name="count"
type="number"
isNotRequired
value={values.count}
onChange={(event) => {
const value = event.target.value;
setFieldValue("count", value === "" ? "" : Number(value));
}}
onBlur={handleBlur}
error_text={touched.count ? errors.count : undefined}
/>
</div>
<Textarea
label="توضیحات"
name="desc"
value={values.desc}
onChange={handleChange}
onBlur={handleBlur}
className="min-h-[96px]"
error_text={touched.desc ? errors.desc : undefined}
/>
<div>
<div className="mb-2 text-sm">فایل های ضمیمه</div>
<UploadBoxDraggble
label="فایل مورد نظر را آپلود کنید"
onChange={handleAttachmentChange}
isMultiple
isFile
isLoading={isSubmitting}
loadingLabel="در حال ثبت درخواست..."
/>
{touched.attachments && errors.attachments ? (
<Error errorText={String(errors.attachments)} />
) : null}
</div>
<div className="flex">
<div className="flex-1" />
<div className="flex flex-1 gap-4">
<div className="w-[250px]">
<Input
label="قیمت به ازای هر صفحه"
value={`${PRICE_PER_PAGE.toLocaleString("fa-IR")} تومان`}
readOnly
/>
</div>
<Input
label="مبلغ قابل پرداخت برای شما"
value={`${totalPrice.toLocaleString("fa-IR")} تومان`}
readOnly
/>
</div>
</div>
<div className="mt-5 flex justify-end">
<Button type="submit" className="w-fit px-6" disabled={isSubmitting}>
ثبت درخواست
</Button>
</div>
</Form>
);
};
const DesignerRequestForm: FC<Props> = ({ onSubmit }) => (
<Formik
initialValues={designerRequestInitialValues}
validationSchema={designerRequestValidationSchema}
onSubmit={onSubmit}
>
<DesignerRequestFormFields />
</Formik>
);
export default DesignerRequestForm;
+44
View File
@@ -0,0 +1,44 @@
import type {
DesignRequestInitiateParams,
DesignerRequestFormValues,
} from "@/pages/designer/types/Types";
import * as Yup from "yup";
export const PRICE_PER_PAGE = 1000;
export const designerRequestInitialValues: DesignerRequestFormValues = {
title: "",
count: "",
desc: "",
expectedDate: "",
attachments: [],
};
export const designerRequestValidationSchema = Yup.object({
title: Yup.string().trim().required("عنوان کاتالوگ الزامی است"),
count: Yup.number()
.transform((value, originalValue) =>
originalValue === "" ? undefined : value,
)
.optional()
.min(1, "حداقل تعداد صفحات ۱ است"),
desc: Yup.string().trim(),
expectedDate: Yup.string().required("زمان تحویل الزامی است"),
attachments: Yup.array(),
});
export const getTotalPrice = (count: DesignerRequestFormValues["count"]) =>
(count === "" ? 0 : count) * PRICE_PER_PAGE;
export const toDesignRequestParams = (
values: DesignerRequestFormValues,
attachmentUrls: string[],
): DesignRequestInitiateParams => {
const { attachments: _attachments, count, ...rest } = values;
return {
...rest,
count: count === "" ? undefined : count,
attachments: attachmentUrls,
};
};
@@ -1,4 +1,10 @@
import { toast } from "@/components/Toast";
import { extractErrorMessage } from "@/helpers/utils";
import { toDesignRequestParams } from "@/pages/designer/designerRequestForm";
import type { DesignerRequestFormValues } from "@/pages/designer/types/Types";
import { useMultipleUpload } from "@/pages/uploader/hooks/useUploaderData";
import { useMutation } from "@tanstack/react-query";
import type { FormikHelpers } from "formik";
import * as api from "../service/DesignerService";
export const useInitiateDesignRequest = () => {
@@ -6,3 +12,35 @@ export const useInitiateDesignRequest = () => {
mutationFn: api.initiateDesignRequest,
});
};
export const useSubmitDesignRequest = () => {
const { mutateAsync: uploadFiles } = useMultipleUpload();
const { mutateAsync: initiateDesignRequest } = useInitiateDesignRequest();
return async (
values: DesignerRequestFormValues,
{ setSubmitting, resetForm }: FormikHelpers<DesignerRequestFormValues>,
) => {
try {
const attachmentUrls =
values.attachments.length > 0
? (await uploadFiles(values.attachments)).data.map((item) => item.url)
: [];
const data = await initiateDesignRequest(
toDesignRequestParams(values, attachmentUrls),
);
toast("صورت حساب با موفقیت ثبت شد", "success");
window.open(
`${import.meta.env.VITE_INVOICE_URL}${data?.data?.invoiceId}`,
"_blank",
);
resetForm();
} catch (error) {
toast(extractErrorMessage(error), "error");
} finally {
setSubmitting(false);
}
};
};