State & Data Flow
FT Configs UI separates state into three layers:
-
Server state — managed by TanStack Query (
@tanstack/react-query) over the sharedapiServiceaxios wrapper. -
Global app state — React Context providers (
I18nProvider,AuthProvider,AdminRolesProvider). -
Local/form state — component-local
useStateplusreact-hook-form+ Zod for validated forms.
Provider hierarchy
Mounted in src/main.tsx / App.tsx:
-
I18nProvider— outermost (Auth and Query error messages needt()). -
QueryProvider— supplies the TanStack Query client. -
AuthProvider— session state and derived flags. -
RouterProvider— TanStack Router (createRouter({ routeTree, defaultPreload: 'intent' })fromsrc/router.tsx). -
<Toaster/>— shadcn/ui toast host.
AdminRolesProvider is mounted locally in src/routes/_protected/settings/_admin.tsx, not globally, so the admin role list is only fetched when entering admin pages.
React Query configuration
Location: src/providers/query-provider.tsx.
function makeQueryClient() {
return new QueryClient({
defaultOptions: {
queries: {
staleTime: 30_000, // 30s — avoid duplicate fetches on rapid navigation
gcTime: 5 * 60_000, // 5m — cached data retained after last observer unmounts
retry: 1, // one automatic retry on transient failure
refetchOnWindowFocus: false,
},
},
});
}
// Browser singleton — prevents re-creation across HMR / React Strict double-render.
let browserQueryClient: QueryClient | undefined;
export function getQueryClient() {
if (!browserQueryClient) browserQueryClient = makeQueryClient();
return browserQueryClient;
}
<ReactQueryDevtools initialIsOpen={false} /> is mounted inside QueryProvider for development.
Feature hooks wrap apiService calls with useQuery / useMutation. React Query handles dedup, stale-while-revalidate, cancellation on unmount, and cache invalidation — do not add ad-hoc AbortController plumbing unless you need cross-effect coordination.
|
Auth / session flow
-
App mounts
AuthProvider(src/contexts/auth-provider.tsx). -
On mount, the provider calls
refreshSession()which invokesapi.getSession()(src/lib/api.ts) →GET /auth/me. A 401 is swallowed and yields{ user: null, isAuthenticated: false }. -
Provider exposes:
-
user,isAuthenticated,isLoading -
isReadOnly— computed asuser?.role === 'VIEWER' -
methods:
login,firstLogin,changePassword,register,updateProfile,updateLocale,logout,refreshSession
-
-
Provider calls
setCurrentUserRole(user.role)(src/lib/permissions.ts) so the axios request interceptor can block write calls for read-only users. -
Side effects on session load: emits a read-only toast (with a dedup flag) and syncs locale when frontend/backend disagree.
-
logout()is best-effort:api.logout()insidetry/catch→ clear state →useNavigate(LOGIN).
The route guard in _protected.tsx consumes useAuth() and redirects unauthenticated users to /login. See Routing & Layouts for why the guard runs in the component tree and not in beforeLoad.
Localization flow
-
I18nProviderstatically importslocales/en.jsonandlocales/ru.jsonat build time (no async loading). -
detectLocale()resolves in order: cookielocale=(en|ru)→navigator.language(startsWithru) →'en'. -
setLocale(locale)updates state, writeslocalStorage, sets cookielocale=…; max-age=31536000, and syncsdocument.documentElement.lang. -
t(key, fallbackOrParams?, params?)— nested lookup with plural support ({count {one … other …}}) and{{placeholder}}interpolation. -
Context value:
{ locale, setLocale, t, loading: false }.
Form validation schemas are built via useLocalizedSchema() so Zod messages re-render on locale change.
AdminRolesProvider
Mounted lazily in _admin.tsx:
-
State:
roles: AdminRole[],isLoading. -
Module-level
cachedRolesandloadingPromisededupe parallel refresh calls. -
refresh(force?)→adminUsersApi.getRoles(). -
Errors flow through
handleError()→ localized toast. -
useAdminRoles()hook includes a null-check to catch misuse outside the admin layout.
Data access flow
Component
│ useXxxQuery() / useXxxMutation() ← TanStack Query
▼
apiService (src/lib/axios.ts wrapper) ← get/post/put/patch/delete
│ axios request interceptors ← baseURL, locale, read-only check, CSRF
▼
Backend (/configs-service/*)
│
▼
axios response interceptor ← 401 refresh-token retry
│
▼
React Query cache ← dedup + stale-while-revalidate
│
▼
Component re-render ← typed DTOs from src/lib/definitions.ts
Error handling is unified via useErrorHandler().handleError(error, options), which extracts a UnifiedError (server / network / validation / frontend / unknown), shows a localized toast, and logs a grouped console entry. See API Integration for the full error classification.