From 4e304944efcd6e70f00e477257826e6e7610319b Mon Sep 17 00:00:00 2001
From: parsa aghaei
Date: Sun, 9 Aug 2026 09:59:55 +0330
Subject: [PATCH] feat(prescriptions & auth): auto-create user and pet profile
on prescription submit, validate mobile numbers with regex, allow public
upload and auto-reset form state
---
backend/src/admin/media.controller.ts | 3 +-
.../prescriptions/prescriptions.controller.ts | 9 ++-
.../prescriptions/prescriptions.service.ts | 64 +++++++++++++++++--
.../components/ContactFormClient.tsx | 9 ++-
.../components/PrescriptionUploadModal.tsx | 35 +++++++---
5 files changed, 103 insertions(+), 17 deletions(-)
diff --git a/backend/src/admin/media.controller.ts b/backend/src/admin/media.controller.ts
index 44ebc6d..0cbc2c1 100644
--- a/backend/src/admin/media.controller.ts
+++ b/backend/src/admin/media.controller.ts
@@ -30,9 +30,8 @@ export class MediaController {
return { success: true, data };
}
- @UseGuards(JwtAuthGuard)
@Post('upload')
- @ApiOperation({ summary: 'آپلود فایل جدید' })
+ @ApiOperation({ summary: 'آپلود فایل جدید (عمومی/ادمین)' })
@UseInterceptors(FileInterceptor('file'))
async uploadFile(@UploadedFile() file: Express.Multer.File) {
if (!file) throw new BadRequestException('File is missing');
diff --git a/backend/src/prescriptions/prescriptions.controller.ts b/backend/src/prescriptions/prescriptions.controller.ts
index f9f0fef..3b47d89 100644
--- a/backend/src/prescriptions/prescriptions.controller.ts
+++ b/backend/src/prescriptions/prescriptions.controller.ts
@@ -31,7 +31,14 @@ export class PrescriptionsController {
@ApiOperation({ summary: 'بارگذاری نسخه جدید توسط کاربر' })
create(
@Req() req: UserReqPayload,
- @Body() body: { petId?: string; fileUrl: string; notes?: string },
+ @Body()
+ body: {
+ petId?: string;
+ petName?: string;
+ phone?: string;
+ fileUrl: string;
+ notes?: string;
+ },
) {
const userId = req.user?.id || req.user?.userId || null;
return this.prescriptionsService.create(userId, body);
diff --git a/backend/src/prescriptions/prescriptions.service.ts b/backend/src/prescriptions/prescriptions.service.ts
index b0e262f..034dda1 100644
--- a/backend/src/prescriptions/prescriptions.service.ts
+++ b/backend/src/prescriptions/prescriptions.service.ts
@@ -11,17 +11,73 @@ export class PrescriptionsService {
async create(
userId: string | null,
- data: { petId?: string; fileUrl: string; notes?: string },
+ data: {
+ petId?: string;
+ petName?: string;
+ phone?: string;
+ fileUrl: string;
+ notes?: string;
+ },
) {
+ let finalUserId = userId;
+ let finalPetId = data.petId || null;
+
+ // 1. If phone is provided, find or create user automatically
+ if (data.phone) {
+ const cleanPhone = data.phone.trim();
+ let user = await this.prisma.user.findFirst({
+ where: { mobile: cleanPhone },
+ });
+
+ if (!user) {
+ // Create user with default role Customer
+ user = await this.prisma.user.create({
+ data: {
+ mobile: cleanPhone,
+ role: 'Customer',
+ firstName: data.petName ? `سرپرست ${data.petName}` : 'کاربر',
+ lastName: 'کانینا',
+ },
+ });
+ }
+ finalUserId = user.id;
+
+ // 2. If petName is provided and user exists, find or create pet
+ if (data.petName && !finalPetId) {
+ const cleanPetName = data.petName.trim();
+ let pet = await this.prisma.pet.findFirst({
+ where: {
+ userId: user.id,
+ name: cleanPetName,
+ },
+ });
+
+ if (!pet) {
+ pet = await this.prisma.pet.create({
+ data: {
+ userId: user.id,
+ name: cleanPetName,
+ type: 'سگ', // Default pet type for prescription consultation
+ breed: 'نامشخص',
+ age: 1,
+ weight: 5.0,
+ activityLevel: 'متوسط',
+ },
+ });
+ }
+ finalPetId = pet.id;
+ }
+ }
+
return this.prisma.prescription.create({
data: {
- userId: userId || undefined,
- petId: data.petId,
+ userId: finalUserId || undefined,
+ petId: finalPetId || undefined,
fileUrl: data.fileUrl,
notes: data.notes,
status: 'PENDING',
},
- include: { pet: true },
+ include: { user: true, pet: true },
});
}
diff --git a/frontend/application/components/ContactFormClient.tsx b/frontend/application/components/ContactFormClient.tsx
index b542240..46fbb26 100644
--- a/frontend/application/components/ContactFormClient.tsx
+++ b/frontend/application/components/ContactFormClient.tsx
@@ -35,10 +35,17 @@ export default function ContactFormClient() {
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
- if (!name.trim() || !phone.trim() || !message.trim()) {
+ const cleanPhone = phone.trim();
+ if (!name.trim() || !cleanPhone || !message.trim()) {
setError("لطفاً نام، شماره موبایل و متن پیام را وارد کنید.");
return;
}
+
+ if (!/^09\d{9}$/.test(cleanPhone)) {
+ setError("شماره همراه وارد شده معتبر نیست. (مثال: ۰۹۱۲۳۴۵۶۷۸۹)");
+ return;
+ }
+
setError("");
setLoading(true);
diff --git a/frontend/application/components/PrescriptionUploadModal.tsx b/frontend/application/components/PrescriptionUploadModal.tsx
index fe63205..6cc1734 100644
--- a/frontend/application/components/PrescriptionUploadModal.tsx
+++ b/frontend/application/components/PrescriptionUploadModal.tsx
@@ -25,14 +25,30 @@ export default function PrescriptionUploadModal({ isOpen, onClose }: Prescriptio
}
};
+ const resetForm = () => {
+ setFile(null);
+ setPetName("");
+ setPhone("");
+ setNotes("");
+ setIsSubmitting(false);
+ setIsSuccess(false);
+ };
+
+ const handleClose = () => {
+ resetForm();
+ onClose();
+ };
+
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!file) {
toast.error("لطفاً تصویر یا فایل نسخه پزشکی را انتخاب کنید.");
return;
}
- if (!phone) {
- toast.error("لطفاً شماره تماس جهت پیگیری را وارد نمایید.");
+
+ const cleanPhone = phone.trim();
+ if (!/^09\d{9}$/.test(cleanPhone)) {
+ toast.error("شماره همراه وارد شده معتبر نیست. (مثال: ۰۹۱۲۳۴۵۶۷۸۹)");
return;
}
@@ -46,10 +62,12 @@ export default function PrescriptionUploadModal({ isOpen, onClose }: Prescriptio
});
const fileUrl = uploadRes.data?.data?.url || uploadRes.data?.url || uploadRes.data?.filename || file.name;
- // Submit prescription to backend prescriptions endpoint
+ // Submit prescription to backend with petName and phone for automatic user & pet profile creation
await api.post("/prescriptions", {
fileUrl,
- notes: `نام پت: ${petName || "ثبت نشده"} | شماره تماس: ${phone} | توضیحات: ${notes || "ندارد"}`
+ phone: cleanPhone,
+ petName: petName.trim() || undefined,
+ notes: notes.trim() || undefined
});
setIsSubmitting(false);
@@ -58,8 +76,7 @@ export default function PrescriptionUploadModal({ isOpen, onClose }: Prescriptio
} catch (error) {
console.error("[PrescriptionUpload] Submission error:", error);
setIsSubmitting(false);
- setIsSuccess(true); // Fallback friendly UX
- toast.success("نسخه پزشکی دریافت شد و جهت بررسی در صف کارشناسان قرار گرفت.");
+ toast.error("خطا در ارسال نسخه پزشکی. لطفاً مجدداً تلاش کنید.");
}
};
@@ -75,7 +92,7 @@ export default function PrescriptionUploadModal({ isOpen, onClose }: Prescriptio
{/* Header */}