From d8ab26b4b7a7dd13c8ca9f95e33ab026373256ed Mon Sep 17 00:00:00 2001 From: hamed <15238-genius.ha@users.noreply.drupalcode.org> Date: Sat, 11 Jul 2026 11:44:40 +0330 Subject: [PATCH] feat(doctor): claim-profile section + modal for unclaimed IRIMC-imported doctors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ClaimProfileSection (components/doctor/claim): shown only when doctor.owner_status === "unclaimed"; banner explains the profile is not yet managed by the doctor, button "تأیید و مدیریت این پروفایل" - Modal: login prompt when logged out; otherwise first/last name, national code, Jalali birth-date (existing JalaliDatePicker) — posts to POST api/v1/doctor/{uuid}/claim (identity verified server-side via API.ir; no client call to API.ir, no token exposure) - States: loading, per-field validation, server error (Persian envelope message), double-submit guard, success welcome message + redirect - Shared component across main domain and all representative subdomains - services/response.js: getDoctorClaimInfo / postDoctorClaim Co-Authored-By: Claude Opus 4.8 (1M context) --- components/doctor/claim/index.js | 184 +++++++++++++++++++++++++++++++ components/doctor/index.js | 2 + services/response.js | 3 + 3 files changed, 189 insertions(+) create mode 100644 components/doctor/claim/index.js diff --git a/components/doctor/claim/index.js b/components/doctor/claim/index.js new file mode 100644 index 0000000..951cfb7 --- /dev/null +++ b/components/doctor/claim/index.js @@ -0,0 +1,184 @@ +"use client"; + +import { useState } from "react"; +import { useRouter } from "next/navigation"; +import Link from "next/link"; +import { Box, Button, Modal, TextField } from "@mui/material"; +import { styleDefault } from "@/mui"; +import JalaliDatePicker from "@/components/common/JalaliDatePicker"; +import CloseModalD from "@/components/icons/CloseModalD"; +import { convertToJalali, isUserLoggedIn } from "@/helper"; +import { request } from "@/services/response"; + +const initialForm = { national_code: "", birth_date: "", first_name: "", last_name: "" }; + +/** + * احراز مالکیت پروفایل پزشک ایمپورت‌شده (unclaimed). + * کامپوننت مشترک برای دامنهٔ اصلی و همهٔ زیردامنه‌ها/دامنه‌های نمایندگان — + * منطق claim به دامنه وابسته نیست؛ احراز هویت کاملاً سمت سرور انجام می‌شود. + */ +function ClaimProfileSection({ doctor }) { + const router = useRouter(); + const [open, setOpen] = useState(false); + const [form, setForm] = useState(initialForm); + const [fieldErrors, setFieldErrors] = useState({}); + const [error, setError] = useState(""); + const [loading, setLoading] = useState(false); + const [claimed, setClaimed] = useState(false); + const [success, setSuccess] = useState(false); + + if (doctor?.owner_status !== "unclaimed" || claimed) { + return null; + } + + const loggedIn = isUserLoggedIn(); + + const setField = (key, value) => { + setForm((f) => ({ ...f, [key]: value })); + setFieldErrors((e) => ({ ...e, [key]: undefined })); + }; + + const validate = () => { + const errs = {}; + const nc = form.national_code.replace(/[۰-۹]/g, (d) => "۰۱۲۳۴۵۶۷۸۹".indexOf(d)); + if (!/^\d{10}$/.test(nc)) errs.national_code = "کد ملی باید ۱۰ رقم باشد"; + if (!form.birth_date) errs.birth_date = "تاریخ تولد را انتخاب کنید"; + if (!form.first_name.trim()) errs.first_name = "نام الزامی است"; + if (!form.last_name.trim()) errs.last_name = "نام خانوادگی الزامی است"; + setFieldErrors(errs); + return Object.keys(errs).length === 0; + }; + + const submit = async () => { + if (loading || !validate()) return; + setLoading(true); + setError(""); + try { + await request.postDoctorClaim(doctor.uuid, form); + setLoading(false); + setSuccess(true); + } catch (err) { + setLoading(false); + const first = err?.response?.data?.errors?.[0]; + setError(first?.message || "خطایی رخ داد. لطفاً دوباره تلاش کنید."); + } + }; + + const closeAfterSuccess = () => { + setOpen(false); + setClaimed(true); + router.push("/dashboard"); + }; + + return ( + <> +
+

+ این پروفایل بر اساس اطلاعات عمومی سازمان نظام پزشکی ایجاد شده و هنوز + توسط پزشک تأیید و مدیریت نمی‌شود؛ نوبت‌دهی آنلاین آن غیرفعال است. +

+
+

آیا شما این پزشک هستید؟

+ +
+
+ + !loading && setOpen(false)}> + +
+ +

احراز مالکیت پروفایل

+
!loading && setOpen(false)}> + +
+
+ + {success ? ( +
+

+ دکتر {form.first_name} {form.last_name}، به نوبت ۷۲۴ خوش آمدید 🎉 +

+

+ پروفایل شما با موفقیت تأیید شد و اکنون می‌توانید اطلاعات پروفایل و + تنظیمات نوبت‌دهی خود را مدیریت کنید. +

+ +
+ ) : !loggedIn ? ( +
+

+ برای احراز مالکیت، ابتدا با شماره موبایل خود وارد شوید. پس از ورود، + به همین صفحه بازگردید و دوباره تلاش کنید. +

+ + + +
+ ) : ( +
+

+ اطلاعات هویتی شما به‌صورت امن با سامانهٔ ثبت احوال و اطلاعات سازمان + نظام پزشکی تطبیق داده می‌شود. +

+ setField("first_name", e.target.value)} + fullWidth + /> + setField("last_name", e.target.value)} + fullWidth + /> + setField("national_code", e.target.value)} + inputProps={{ maxLength: 10, dir: "ltr" }} + fullWidth + /> + setField("birth_date", convertToJalali(e._d))} + /> + {error && ( +

{error}

+ )} + +
+ )} +
+
+ + ); +} + +export default ClaimProfileSection; diff --git a/components/doctor/index.js b/components/doctor/index.js index a85adc4..603c592 100644 --- a/components/doctor/index.js +++ b/components/doctor/index.js @@ -1,5 +1,6 @@ import Link from "next/link"; import AppointmentList from "./appointmentList"; +import ClaimProfileSection from "./claim"; import DetailDoctor from "./detailDoctor"; import Share from "./detailDoctor/Share"; import CustomLoading from "@/app/component/loading/Custom"; @@ -40,6 +41,7 @@ function DoctorPage({ doctor, comments, rateAggregate, slug }) { /> + ); } diff --git a/services/response.js b/services/response.js index 4fb4044..b194a37 100644 --- a/services/response.js +++ b/services/response.js @@ -76,6 +76,9 @@ export const request = { getDoctorServices: () => api.get("api/v1/categorys/doctor_services", removeTokenHead), postDoctorComment: (data) => api.post("api/v1/comment", data, { requireAuth: true }), + getDoctorClaimInfo: (uuid) => api.get(`api/v1/doctor/${uuid}/claim-info`), + postDoctorClaim: (uuid, data) => + api.post(`api/v1/doctor/${uuid}/claim`, data, { requireAuth: true }), getDoctorRate: (uuid) => api.get(`api/v1/rate/${uuid}`), getRateEligibility: (uuid) => api.get(`api/v1/rate/${uuid}/eligibility`, { requireAuth: true }),