Files
clinicpro/assets/admin/stores/authStore.ts
T

176 lines
4.9 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' | 'representation' | 'user';
scope?: string | null;
doctor_uuid?: string;
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' | 'representation' | 'user' | null;
dbUuid: string | null;
dbKey: string | null;
doctorUuid: string | null;
context: ContextItem | null;
availableContexts: ContextItem[];
login: (token: string, refreshToken?: string | null) => void;
logout: () => void;
refresh: () => Promise<string | null>;
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,
doctorUuid: null,
context: null,
availableContexts: [],
login: (token, refreshToken = null) => {
set({ token, refreshToken, isAuthenticated: true });
get().fetchMe();
},
logout: () => {
const rt = get().refreshToken;
if (rt) {
apiFetch('/oauth/logout', {
method: 'POST',
body: JSON.stringify({ refresh_token: rt }),
}).catch(() => {});
}
set({
token: null,
refreshToken: null,
isAuthenticated: false,
userUuid: null,
userName: null,
primaryRole: null,
dbUuid: null,
dbKey: null,
doctorUuid: null,
context: null,
availableContexts: [],
});
},
refresh: async () => {
const rt = get().refreshToken;
if (!rt) return null;
try {
const res = await fetch('/oauth/token/refresh', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refresh_token: rt }),
});
const json = await res.json();
if (!res.ok || !json?.access_token) return null;
set({
token: json.access_token,
refreshToken: json.refresh_token ?? rt,
isAuthenticated: true,
});
return json.access_token as string;
} catch {
return null;
}
},
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,
doctorUuid: d.doctor_uuid ?? 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,
doctorUuid: res.data.context?.doctor_uuid ?? get().doctorUuid,
context: res.data.context,
primaryRole: res.data.context?.role ?? null,
});
}
},
}),
{
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,
doctorUuid: s.doctorUuid,
context: s.context,
availableContexts: s.availableContexts,
}),
}
)
);