Files
nobat724_front/components/doctor/claim/index.js
T
hamedandClaude Opus 4.8 813225ff6d fix(doctor claim): mobile field is the account number, read-only
Mobile is prefilled from the logged-in user's cookie (userInfo.mobile_number)
and rendered read-only; removed it from editable form state/validation and
send that number in the claim body. Backend already enforces it must match
the account, so the field can't diverge.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 15:16:27 +03:30

264 lines
11 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, usePathname } 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 Cookies from "js-cookie";
import { convertToJalali, isUserLoggedIn } from "@/helper";
import { request } from "@/services/response";
import Altcha from "@/components/Altcha";
const initialForm = { national_code: "", birth_date: "", first_name: "", last_name: "" };
function currentUserMobile() {
try {
return JSON.parse(Cookies.get("userInfo") || "{}")?.mobile_number || "";
} catch {
return "";
}
}
/**
* احراز مالکیت پروفایل پزشک ایمپورت‌شده (unclaimed).
* کامپوننت مشترک برای دامنهٔ اصلی و همهٔ زیردامنه‌ها/دامنه‌های نمایندگان —
* منطق claim به دامنه وابسته نیست؛ احراز هویت کاملاً سمت سرور انجام می‌شود.
*/
function ClaimProfileSection({ doctor }) {
const router = useRouter();
const pathname = usePathname();
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);
const [altcha, setAltcha] = useState(null); // null=در حال آماده‌سازی، ""=خاموش، payload=حل‌شده
const [confirmDelete, setConfirmDelete] = useState(false);
const [deleting, setDeleting] = useState(false);
if (doctor?.owner_status !== "unclaimed" || claimed) {
return null;
}
const loggedIn = isUserLoggedIn();
const userMobile = currentUserMobile();
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, mobile: userMobile, altcha });
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");
};
const removeProfile = async () => {
if (deleting) return;
setDeleting(true);
setError("");
try {
await request.deleteDoctor(doctor.uuid);
setDeleting(false);
setOpen(false);
setClaimed(true);
router.push("/");
} catch (err) {
setDeleting(false);
const first = err?.response?.data?.errors?.[0];
setError(first?.message || "حذف پروفایل ممکن نشد.");
}
};
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>
{error && (
<p className="text-[#e0394a] text-[13px] leading-6">{error}</p>
)}
{confirmDelete ? (
<div className="w-full flex flex-col gap-[8px]">
<p className="text-[#e0394a] text-[13px] leading-6">
آیا از حذف کامل این پروفایل مطمئن هستید؟ این عمل قابل بازگشت نیست.
</p>
<div className="flex gap-[8px]">
<Button
variant="contained"
color="error"
className="!rounded-[12px] flex-1"
disabled={deleting}
onClick={removeProfile}
>
{deleting ? "در حال حذف…" : "بله، حذف کن"}
</Button>
<Button
variant="outlined"
className="!rounded-[12px] flex-1"
disabled={deleting}
onClick={() => setConfirmDelete(false)}
>
انصراف
</Button>
</div>
</div>
) : (
<button
type="button"
className="text-[#e0394a] text-[13px] hover:underline"
onClick={() => setConfirmDelete(true)}
>
حذف این پروفایل
</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?redirect=${encodeURIComponent(pathname)}`} 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
/>
<TextField
label="شماره موبایل"
value={userMobile}
helperText="شمارهٔ حساب کاربری شما (غیرقابل ویرایش)"
InputProps={{ readOnly: true }}
inputProps={{ dir: "ltr" }}
fullWidth
/>
<JalaliDatePicker
value={form.birth_date}
placeholder="تاریخ تولد (شمسی)"
error={fieldErrors.birth_date}
stepwise
onChange={(e) => setField("birth_date", convertToJalali(e._d))}
/>
<Altcha onVerified={setAltcha} />
{error && (
<p className="text-[#e0394a] text-[13px] leading-6 text-center">{error}</p>
)}
<Button
variant="contained"
className="!rounded-[12px]"
disabled={loading || altcha === null}
onClick={submit}
>
{loading ? "در حال بررسی هویت…" : "تأیید هویت و تصاحب پروفایل"}
</Button>
</div>
)}
</Box>
</Modal>
</>
);
}
export default ClaimProfileSection;