From e687e4502edad369c9cafc0b84fc23ac7bd31e76 Mon Sep 17 00:00:00 2001 From: Alireza Hassani Date: Sun, 27 Sep 2026 21:51:44 +0330 Subject: [PATCH] Add customer partnership form and admin requests list with detail modal. Co-authored-by: Cursor --- src/app/account/page.tsx | 36 +- src/app/account/partners/page.tsx | 410 +++++++++++++++++++ src/app/website/requests/page.tsx | 325 ++++++++++++++- src/components/RequireAuth.tsx | 9 +- src/components/auth/AuthForm.tsx | 5 + src/components/dashboard/DashboardHeader.tsx | 8 +- src/components/ui/SearchableMultiSelect.tsx | 179 ++++++++ src/lib/auth-handoff.ts | 42 ++ src/lib/partnership-api.ts | 99 +++++ 9 files changed, 1101 insertions(+), 12 deletions(-) create mode 100644 src/app/account/partners/page.tsx create mode 100644 src/components/ui/SearchableMultiSelect.tsx create mode 100644 src/lib/auth-handoff.ts create mode 100644 src/lib/partnership-api.ts diff --git a/src/app/account/page.tsx b/src/app/account/page.tsx index 0bd0c21..e02f832 100644 --- a/src/app/account/page.tsx +++ b/src/app/account/page.tsx @@ -30,6 +30,13 @@ export default function CustomerHomePage() { description: "اطلاعات حساب و آدرس‌ها", icon: , }, + { + href: "/account/partners", + title: "شروع همکاری", + titleEn: "Partnership", + description: "درخواست همکاری به‌عنوان تولیدکننده", + icon: , + }, ...(showAdmin ? [ { @@ -104,13 +111,13 @@ function OrdersIcon() { @@ -120,24 +127,43 @@ function OrdersIcon() { function ProfileIcon() { return ( - + ); } +function PartnersIcon() { + return ( + + + + + ); +} + function AdminIcon() { return ( diff --git a/src/app/account/partners/page.tsx b/src/app/account/partners/page.tsx new file mode 100644 index 0000000..be9d5f4 --- /dev/null +++ b/src/app/account/partners/page.tsx @@ -0,0 +1,410 @@ +"use client"; + +import { FormEvent, useEffect, useState } from "react"; +import ClothesShell from "@/components/clothes/ClothesShell"; +import SearchableMultiSelect from "@/components/ui/SearchableMultiSelect"; +import { ApiError } from "@/lib/api"; +import { fetchProfile } from "@/lib/profile-api"; +import { + createPartnershipRequest, + fetchPublicCategoryOptions, + uploadPartnershipImage, + type FlatCategoryOption, +} from "@/lib/partnership-api"; + +const PROVINCES = [ + "تهران", + "اصفهان", + "فارس", + "خراسان رضوی", + "آذربایجان شرقی", + "آذربایجان غربی", + "گیلان", + "مازندران", + "خوزستان", + "کرمان", + "یزد", + "قم", + "البرز", + "همدان", + "کردستان", + "کرمانشاه", + "هرمزگان", + "بوشهر", + "سیستان و بلوچستان", + "گلستان", + "قزوین", + "زنجان", + "اردبیل", + "لرستان", + "مرکزی", + "سمنان", + "چهارمحال و بختیاری", + "کهگیلویه و بویراحمد", + "ایلام", + "خراسان شمالی", + "خراسان جنوبی", +]; + +const MAX_IMAGES = 6; + +export default function AccountPartnersPage() { + const [categories, setCategories] = useState([]); + const [loading, setLoading] = useState(true); + const [cellNumber, setCellNumber] = useState(""); + const [businessNameFa, setBusinessNameFa] = useState(""); + const [businessNameEn, setBusinessNameEn] = useState(""); + const [selectedCategories, setSelectedCategories] = useState([]); + const [description, setDescription] = useState(""); + const [imageUrls, setImageUrls] = useState([]); + const [landline, setLandline] = useState(""); + const [instagram, setInstagram] = useState(""); + const [city, setCity] = useState(""); + const [province, setProvince] = useState(""); + const [address, setAddress] = useState(""); + const [postalCode, setPostalCode] = useState(""); + const [uploading, setUploading] = useState(false); + const [saving, setSaving] = useState(false); + const [error, setError] = useState(null); + const [sent, setSent] = useState(false); + + useEffect(() => { + let cancelled = false; + void (async () => { + setLoading(true); + try { + const [profile, cats] = await Promise.all([ + fetchProfile(), + fetchPublicCategoryOptions(), + ]); + if (cancelled) return; + setCellNumber(profile.cellNumber || ""); + if (profile.instagram) setInstagram(profile.instagram); + setCategories(cats); + } catch (err) { + if (!cancelled) { + setError( + err instanceof ApiError + ? err.message + : "بارگذاری فرم ناموفق بود", + ); + } + } finally { + if (!cancelled) setLoading(false); + } + })(); + return () => { + cancelled = true; + }; + }, []); + + async function handleImagesSelected( + event: React.ChangeEvent, + ) { + const files = Array.from(event.target.files ?? []); + event.target.value = ""; + if (!files.length) return; + + const remaining = MAX_IMAGES - imageUrls.length; + if (remaining <= 0) { + setError(`حداکثر ${MAX_IMAGES} تصویر می‌توانید بارگذاری کنید`); + return; + } + + setError(null); + setUploading(true); + try { + const uploaded: string[] = []; + for (const file of files.slice(0, remaining)) { + const result = await uploadPartnershipImage(file); + uploaded.push(result.url); + } + setImageUrls((prev) => [...prev, ...uploaded]); + } catch (err) { + setError( + err instanceof ApiError ? err.message : "بارگذاری تصویر ناموفق بود", + ); + } finally { + setUploading(false); + } + } + + async function handleSubmit(event: FormEvent) { + event.preventDefault(); + setError(null); + + if (!selectedCategories.length) { + setError("حداقل یک دسته‌بندی فعالیت انتخاب کنید"); + return; + } + + setSaving(true); + try { + await createPartnershipRequest({ + businessNameFa: businessNameFa.trim(), + businessNameEn: businessNameEn.trim(), + categoryIds: selectedCategories, + description: description.trim(), + imageUrls, + landline: landline.trim() || undefined, + instagram: instagram.trim() || undefined, + city: city.trim(), + province, + address: address.trim(), + postalCode: postalCode.trim(), + }); + setSent(true); + } catch (err) { + setError( + err instanceof ApiError ? err.message : "ارسال درخواست ناموفق بود", + ); + } finally { + setSaving(false); + } + } + + return ( + + {loading ? ( +

+ در حال بارگذاری… +

+ ) : sent ? ( +
+

درخواست شما ثبت شد

+

+ Partnership request received +

+

+ تیم مانا به‌زودی درخواست همکاری شما را بررسی می‌کند. +

+
+ ) : ( +
void handleSubmit(e)} + className="dash-enter space-y-3" + dir="rtl" + > + {error ? ( +

+ {error} +

+ ) : null} + +
+

اطلاعات کسب‌وکار

+
+
+ + setBusinessNameFa(e.target.value)} + placeholder="نام برند یا فروشگاه" + /> +
+
+ + setBusinessNameEn(e.target.value)} + placeholder="Brand or shop name" + /> +
+ +
+ + + + می‌توانید چند مورد انتخاب کنید + +
+ +
+ +