- 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) <noreply@anthropic.com>
185 lines
7.8 KiB
JavaScript
185 lines
7.8 KiB
JavaScript
"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 (
|
||
<>
|
||
<div className="w-full mt-[16px] p-[20px] border border-solid border-[#E9ECEF] rounded-[16px] bg-[#FFF8ED]">
|
||
<p className="text-[#3B3B3B] text-[14px] leading-7">
|
||
این پروفایل بر اساس اطلاعات عمومی سازمان نظام پزشکی ایجاد شده و هنوز
|
||
توسط پزشک تأیید و مدیریت نمیشود؛ نوبتدهی آنلاین آن غیرفعال است.
|
||
</p>
|
||
<div className="flex items-center justify-between flex-wrap gap-3 mt-[12px]">
|
||
<p className="text-[#3B3B3B] text-[16px] font-bold">آیا شما این پزشک هستید؟</p>
|
||
<Button
|
||
variant="contained"
|
||
className="!rounded-[12px] !px-[20px]"
|
||
onClick={() => setOpen(true)}
|
||
>
|
||
تأیید و مدیریت این پروفایل
|
||
</Button>
|
||
</div>
|
||
</div>
|
||
|
||
<Modal open={open} onClose={() => !loading && setOpen(false)}>
|
||
<Box sx={styleDefault} className="!py-[24px] !px-[28px] !max-w-[440px] !w-[92vw]">
|
||
<div className="flex justify-between items-center">
|
||
<span></span>
|
||
<p className="text-[#3B3B3B] text-[18px] font-medium">احراز مالکیت پروفایل</p>
|
||
<div className="cursor-pointer" onClick={() => !loading && setOpen(false)}>
|
||
<CloseModalD />
|
||
</div>
|
||
</div>
|
||
|
||
{success ? (
|
||
<div className="flex flex-col items-center gap-[16px] mt-[24px] text-center">
|
||
<p className="text-[#15a35a] text-[18px] font-bold">
|
||
دکتر {form.first_name} {form.last_name}، به نوبت ۷۲۴ خوش آمدید 🎉
|
||
</p>
|
||
<p className="text-[#3B3B3B] text-[14px] leading-7">
|
||
پروفایل شما با موفقیت تأیید شد و اکنون میتوانید اطلاعات پروفایل و
|
||
تنظیمات نوبتدهی خود را مدیریت کنید.
|
||
</p>
|
||
<Button variant="contained" className="!rounded-[12px] w-full" onClick={closeAfterSuccess}>
|
||
ورود به پنل مدیریت
|
||
</Button>
|
||
</div>
|
||
) : !loggedIn ? (
|
||
<div className="flex flex-col items-center gap-[16px] mt-[24px] text-center">
|
||
<p className="text-[#3B3B3B] text-[14px] leading-7">
|
||
برای احراز مالکیت، ابتدا با شماره موبایل خود وارد شوید. پس از ورود،
|
||
به همین صفحه بازگردید و دوباره تلاش کنید.
|
||
</p>
|
||
<Link href="/login" className="w-full">
|
||
<Button variant="contained" className="!rounded-[12px] w-full">
|
||
ورود / ثبتنام با موبایل
|
||
</Button>
|
||
</Link>
|
||
</div>
|
||
) : (
|
||
<div className="flex flex-col gap-[14px] mt-[24px]">
|
||
<p className="text-[#6B7280] text-[13px] leading-6">
|
||
اطلاعات هویتی شما بهصورت امن با سامانهٔ ثبت احوال و اطلاعات سازمان
|
||
نظام پزشکی تطبیق داده میشود.
|
||
</p>
|
||
<TextField
|
||
label="نام"
|
||
value={form.first_name}
|
||
error={!!fieldErrors.first_name}
|
||
helperText={fieldErrors.first_name}
|
||
onChange={(e) => setField("first_name", e.target.value)}
|
||
fullWidth
|
||
/>
|
||
<TextField
|
||
label="نام خانوادگی"
|
||
value={form.last_name}
|
||
error={!!fieldErrors.last_name}
|
||
helperText={fieldErrors.last_name}
|
||
onChange={(e) => setField("last_name", e.target.value)}
|
||
fullWidth
|
||
/>
|
||
<TextField
|
||
label="کد ملی"
|
||
value={form.national_code}
|
||
error={!!fieldErrors.national_code}
|
||
helperText={fieldErrors.national_code}
|
||
onChange={(e) => setField("national_code", e.target.value)}
|
||
inputProps={{ maxLength: 10, dir: "ltr" }}
|
||
fullWidth
|
||
/>
|
||
<JalaliDatePicker
|
||
value={form.birth_date}
|
||
placeholder="تاریخ تولد (شمسی)"
|
||
error={fieldErrors.birth_date}
|
||
stepwise
|
||
onChange={(e) => setField("birth_date", convertToJalali(e._d))}
|
||
/>
|
||
{error && (
|
||
<p className="text-[#e0394a] text-[13px] leading-6 text-center">{error}</p>
|
||
)}
|
||
<Button
|
||
variant="contained"
|
||
className="!rounded-[12px]"
|
||
disabled={loading}
|
||
onClick={submit}
|
||
>
|
||
{loading ? "در حال بررسی هویت…" : "تأیید هویت و تصاحب پروفایل"}
|
||
</Button>
|
||
</div>
|
||
)}
|
||
</Box>
|
||
</Modal>
|
||
</>
|
||
);
|
||
}
|
||
|
||
export default ClaimProfileSection;
|