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>
264 lines
11 KiB
JavaScript
264 lines
11 KiB
JavaScript
"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;
|