Contexts
Two React Contexts hold everything the app needs to know globally. They're both mounted in App.js:
<TenantProvider>
<AuthProvider>
<App />
</AuthProvider>
</TenantProvider>
AuthContext
ui/src/contexts/AuthContext.js
State
| Field | Type | Source |
|---|---|---|
currentUser | { id, name, email, role, accountType, tenantId } | null | localStorage vetty.session on mount, or login() response |
loading | boolean | Async auth ops |
isAuthenticated | boolean | Derived from currentUser |
Methods
login(email, password)— callsservices.auth.login(), normalizes the user, pushes tenants intoTenantContext, stores the session in localStorage (vetty.session) and the token inaccess_token.logout()— clears localStorage (vetty.session,vetty.currentTenant,vetty.tenants,access_token), callsservices.auth.logout(), nulls out state.setSession(user, tenants, currentTenant, token)— internal helper used by login/register flows.
LocalStorage keys it owns
| Key | Contents |
|---|---|
vetty.session | JSON blob: { user, tenants, currentTenant, token } |
access_token | The bearer token used by HttpClient |
Remember: the browser's localStorage is readable by same-origin JavaScript. Sensitive info shouldn't land there. Today only the token + normalized user live there; roles are derived from the tenant pivot, not spoofable by the client.
TenantContext
ui/src/contexts/TenantContext.js
State
| Field | Type | Purpose |
|---|---|---|
currentTenant | Tenant | null | Active clinic |
tenants | Tenant[] | All clinics the user can pick from |
Methods
switchTenant(tenantId)— finds the tenant, sets it ascurrentTenant, persists it tovetty.currentTenant.syncTenants(incomingTenants, preferredTenantId?)— called fromAuthContext.login(); replaces the list and optionally selects a tenant. If no preferred id is provided, the tenant markedis_default = truewins; otherwise the first in the list.clearTenantState()— used on logout.
LocalStorage keys it owns
| Key | Contents |
|---|---|
vetty.currentTenant | JSON serialized Tenant |
vetty.tenants | JSON serialized Tenant[] |
Usage from components
import { useAuth } from '../../contexts/AuthContext';
import { useTenant } from '../../contexts/TenantContext';
function SomeComponent() {
const { currentUser, logout } = useAuth();
const { currentTenant, tenants, switchTenant } = useTenant();
// … render
}
Both contexts expose a use* hook that throws if consumed outside the provider — a pattern that catches forgotten wrappers early.
Keeping context in sync with the API
- After every
login()the user's full tenant list is synced toTenantContextviasyncTenants(). This overwrites anything stale from a prior session. switchTenant()only touchesTenantContext— it does not call the backend. The next tenant-scoped API call will use the newcurrentTenant.idnaturally.logout()sweeps both contexts so no residual state remains if a different user logs in on the same machine.