Files
nobat724_front/components/doctor/claim/index.js
T
hamedandClaude Opus 4.8 d8ab26b4b7 feat(doctor): claim-profile section + modal for unclaimed IRIMC-imported doctors
- 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>
2026-07-11 11:44:40 +03:30

185 lines
7.8 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"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;