feat(api): add dashboard endpoints for clinic, doctor, and secretary roles
- 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.
This commit is contained in:
@@ -1,25 +1,135 @@
|
||||
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) => ({
|
||||
(set, get) => ({
|
||||
token: null,
|
||||
refreshToken: null,
|
||||
isAuthenticated: false,
|
||||
login: (token, refreshToken) =>
|
||||
set({ token, refreshToken, isAuthenticated: true }),
|
||||
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 }),
|
||||
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' }
|
||||
{
|
||||
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,
|
||||
}),
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user