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:
hamed
2026-08-09 12:41:11 +03:30
parent cfeb447645
commit 60ccd5cc1d
8 changed files with 515 additions and 121 deletions
@@ -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' }}
>