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
Some checks are pending
Deploy Canina / deploy (push) Waiting to run

This commit is contained in:
parsa aghaei 2026-08-09 09:59:55 +03:30
parent b617331683
commit 4e304944ef
5 changed files with 103 additions and 17 deletions

View File

@ -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');

View File

@ -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);

View File

@ -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 },
});
}

View File

@ -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);

View File

@ -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 */}
<div className="bg-canina-blue text-white p-6 relative">
<button
onClick={onClose}
onClick={handleClose}
className="absolute left-5 top-5 text-white/80 hover:text-white bg-white/10 p-1.5 rounded-full transition-colors"
>
<X className="w-5 h-5" />
@ -103,7 +120,7 @@ export default function PrescriptionUploadModal({ isOpen, onClose }: Prescriptio
کارشناسان و دامپزشکان کانینا نسخه شما را بررسی کرده و ظرف حداکثر ۱۵ دقیقه جهت هماهنگی ارسال دارو با شما تماس خواهند گرفت.
</p>
<button
onClick={() => { setIsSuccess(false); onClose(); }}
onClick={handleClose}
className="px-6 py-2.5 bg-canina-blue text-white rounded-xl text-xs font-black hover:bg-canina-dark transition-all"
>
متوجه شدم
@ -172,7 +189,7 @@ export default function PrescriptionUploadModal({ isOpen, onClose }: Prescriptio
<div className="pt-2 flex items-center justify-end gap-3 border-t border-medical-gray-100">
<button
type="button"
onClick={onClose}
onClick={handleClose}
className="px-4 py-2.5 rounded-xl text-xs font-bold text-medical-gray-500 hover:bg-medical-gray-100 transition-colors"
>
انصراف