feat: implement OTP login flow and enhance role-based access control
- Added OTP login functionality in driver.mjs to handle user authentication with a fixed code in dev environment. - Enhanced RoleRoute component in App.tsx to support clinic-scoped doctor roles and permissions. - Updated ClinicDoctorsManager component to include pagination and search functionality for better user experience. - Refactored tests for ClinicDoctorsManager to cover new features and ensure proper API mocking. - Adjusted permissions in settingsMenu.ts and PracticeDomainSettingsPage.tsx to align with updated backend requirements. - Created RoleRoute.test.tsx to validate role-based access logic for different user scenarios.
This commit is contained in:
@@ -4,7 +4,7 @@ import { PencilIcon } from '@heroicons/react/24/outline';
|
||||
import { useAuthStore } from '../stores/authStore';
|
||||
import { usePermissions } from '../hooks/usePermissions';
|
||||
import SettingsLayout from '../components/layout/SettingsLayout';
|
||||
import BackButton from '../components/ui/BackButton';
|
||||
import PageHeader from '../components/ui/PageHeader';
|
||||
import ClinicDoctorsManager from '../components/ClinicDoctorsManager';
|
||||
|
||||
/**
|
||||
@@ -45,18 +45,17 @@ function ClinicDoctorsContent() {
|
||||
|
||||
return (
|
||||
<div className="fade-in" style={{ display: 'flex', flexDirection: 'column', gap: 'var(--gap)' }}>
|
||||
<div>
|
||||
<BackButton fallback="/admin/settings-menu" />
|
||||
</div>
|
||||
<div className="card-title-row">
|
||||
<div>
|
||||
<h1 className="section-title">پزشکان کلینیک</h1>
|
||||
<div className="muted">مدیریت پزشکان و دعوتنامههای کلینیک</div>
|
||||
</div>
|
||||
<Link className="btn ghost sm" to={`/admin/clinics/${clinicUuid}`}>
|
||||
<PencilIcon style={{ width: 15, height: 15 }} /> ویرایش اطلاعات کلینیک
|
||||
</Link>
|
||||
</div>
|
||||
{/* عنوان و بازگشت و اکشن هر سه در PageHeader — همان قاعدهٔ بقیهٔ صفحات پنل. */}
|
||||
<PageHeader
|
||||
title="پزشکان کلینیک"
|
||||
description="مدیریت پزشکان و دعوتنامههای کلینیک"
|
||||
backTo="/admin/settings-menu"
|
||||
action={
|
||||
<Link className="btn secondary sm" to={`/admin/clinics/${clinicUuid}`}>
|
||||
<PencilIcon style={{ width: 15, height: 15 }} /> ویرایش اطلاعات کلینیک
|
||||
</Link>
|
||||
}
|
||||
/>
|
||||
|
||||
<ClinicDoctorsManager
|
||||
clinicUuid={clinicUuid}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { api, ApiError, type ApiResponse } from '../lib/api';
|
||||
import SettingsLayout from '../components/layout/SettingsLayout';
|
||||
import SearchableSelect from '../components/ui/SearchableSelect';
|
||||
import { useAuthStore } from '../stores/authStore';
|
||||
import { usePermissions } from '../hooks/usePermissions';
|
||||
import type { PracticeDomain } from '../types';
|
||||
|
||||
/**
|
||||
@@ -18,6 +19,8 @@ export default function PracticeDomainSettingsPage() {
|
||||
const qc = useQueryClient();
|
||||
// محیط جاری با `db_uuid` شناخته میشود؛ همان uuid کلینیک است.
|
||||
const clinicUuid = useAuthStore((s) => s.context?.db_uuid ?? null);
|
||||
const { can } = usePermissions();
|
||||
const canUpdate = can('clinic_info', 'update');
|
||||
|
||||
const { data: domainsData, isLoading } = useQuery({
|
||||
queryKey: ['practice-domains'],
|
||||
@@ -86,10 +89,18 @@ export default function PracticeDomainSettingsPage() {
|
||||
</span>
|
||||
)}
|
||||
|
||||
{/* پزشکِ عضو ممکن است «مشاهده» داشته باشد و «ویرایش» نه؛ دکمهٔ فعالی که
|
||||
حتماً ۴۰۳ میگیرد، خطای کاربر نیست — خطای رابط است. */}
|
||||
{!canUpdate && (
|
||||
<span className="muted" style={{ fontSize: 12 }}>
|
||||
برای تغییر حوزهٔ فعالیت، دسترسی «ویرایش اطلاعات کلینیک» لازم است.
|
||||
</span>
|
||||
)}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className="btn primary"
|
||||
disabled={save.isPending || !clinicUuid}
|
||||
disabled={save.isPending || !clinicUuid || !canUpdate}
|
||||
onClick={() => save.mutate(selected)}
|
||||
style={{ justifySelf: 'start' }}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user