- Implemented GET /api/v1/dashboard/clinic to return clinic stats and today's schedule for clinic owners. - Implemented GET /api/v1/dashboard/doctor to return doctor's stats and today's schedule for doctors. - Implemented GET /api/v1/dashboard/secretary to return stats and conditional appointments for secretaries. feat(migrations): create user_active_context and mobile_verification_otp tables - Added migration to create user_active_context table for tracking active user sessions. - Added migration to create mobile_verification_otp table for handling mobile number verification. feat(migrations): create site_config table for application settings - Added migration to create site_config table to store various site configuration settings. feat(appointments): create MyAppointmentsController for user-specific appointments - Added MyAppointmentsController to handle fetching user-specific appointments with pagination and filtering. feat(auth): implement NotificationMobileController for mobile number verification - Added NotificationMobileController to handle OTP requests and verification for mobile number changes. feat(auth): create MobileVerificationOtp entity for OTP management - Created MobileVerificationOtp entity to manage OTP records for mobile verification. feat(auth): create UserActiveContext entity for user session management - Created UserActiveContext entity to manage user active sessions. feat(config): implement SiteConfigController for managing site settings - Added SiteConfigController to handle fetching and updating site configuration settings. feat(config): create SiteConfig entity and repository for configuration management - Created SiteConfig entity and repository to manage site configuration data.
136 lines
3.6 KiB
TypeScript
136 lines
3.6 KiB
TypeScript
import { create } from 'zustand';
|
|
import { persist } from 'zustand/middleware';
|
|
|
|
export interface ContextItem {
|
|
type: 'doctor' | 'clinic';
|
|
db_uuid: string;
|
|
name: string;
|
|
role: 'admin' | 'clinic' | 'doctor' | 'secretary' | 'user';
|
|
permissions?: Record<string, any>;
|
|
}
|
|
|
|
interface AuthState {
|
|
token: string | null;
|
|
refreshToken: string | null;
|
|
isAuthenticated: boolean;
|
|
userUuid: string | null;
|
|
userName: string | null;
|
|
primaryRole: 'admin' | 'clinic' | 'doctor' | 'secretary' | 'user' | null;
|
|
dbUuid: string | null;
|
|
dbKey: string | null;
|
|
context: ContextItem | null;
|
|
availableContexts: ContextItem[];
|
|
|
|
login: (token: string, refreshToken: string) => void;
|
|
logout: () => void;
|
|
fetchMe: () => Promise<void>;
|
|
switchContext: (dbUuid: string) => Promise<void>;
|
|
}
|
|
|
|
function getToken(): string | null {
|
|
try {
|
|
const raw = localStorage.getItem('clinicpro-auth');
|
|
if (!raw) return null;
|
|
return JSON.parse(raw)?.state?.token ?? null;
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
async function apiFetch(path: string, options: RequestInit = {}) {
|
|
const token = getToken();
|
|
const res = await fetch(path, {
|
|
...options,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
...options.headers,
|
|
},
|
|
});
|
|
return res.json();
|
|
}
|
|
|
|
export const useAuthStore = create<AuthState>()(
|
|
persist(
|
|
(set, get) => ({
|
|
token: null,
|
|
refreshToken: null,
|
|
isAuthenticated: false,
|
|
userUuid: null,
|
|
userName: null,
|
|
primaryRole: null,
|
|
dbUuid: null,
|
|
dbKey: null,
|
|
context: null,
|
|
availableContexts: [],
|
|
|
|
login: (token, refreshToken) => {
|
|
set({ token, refreshToken, isAuthenticated: true });
|
|
get().fetchMe();
|
|
},
|
|
|
|
logout: () =>
|
|
set({
|
|
token: null,
|
|
refreshToken: null,
|
|
isAuthenticated: false,
|
|
userUuid: null,
|
|
userName: null,
|
|
primaryRole: null,
|
|
dbUuid: null,
|
|
dbKey: null,
|
|
context: null,
|
|
availableContexts: [],
|
|
}),
|
|
|
|
fetchMe: async () => {
|
|
try {
|
|
const res = await apiFetch('/oauth/userinfo');
|
|
if (!res.success) return;
|
|
const d = res.data;
|
|
set({
|
|
userUuid: d.uuid,
|
|
userName: d.realName,
|
|
primaryRole: d.primary_role ?? null,
|
|
dbUuid: d.db_uuid ?? null,
|
|
dbKey: d.db_key ?? null,
|
|
context: d.context ?? null,
|
|
availableContexts: d.available_contexts ?? [],
|
|
});
|
|
} catch {
|
|
// شبکه در دسترس نیست — state دستنخورده بماند
|
|
}
|
|
},
|
|
|
|
switchContext: async (dbUuid: string) => {
|
|
const res = await apiFetch('/api/v1/auth/switch-context', {
|
|
method: 'POST',
|
|
body: JSON.stringify({ db_uuid: dbUuid }),
|
|
});
|
|
if (res.success) {
|
|
set({
|
|
dbUuid: res.data.db_uuid,
|
|
dbKey: res.data.db_key,
|
|
context: res.data.context,
|
|
});
|
|
}
|
|
},
|
|
}),
|
|
{
|
|
name: 'clinicpro-auth',
|
|
partialize: (s) => ({
|
|
token: s.token,
|
|
refreshToken: s.refreshToken,
|
|
isAuthenticated: s.isAuthenticated,
|
|
userUuid: s.userUuid,
|
|
userName: s.userName,
|
|
primaryRole: s.primaryRole,
|
|
dbUuid: s.dbUuid,
|
|
dbKey: s.dbKey,
|
|
context: s.context,
|
|
availableContexts: s.availableContexts,
|
|
}),
|
|
}
|
|
)
|
|
);
|