toga-ai 1.0.183 → 1.0.184
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# toga2-view (TOGa View Frontend) — 2.0 knowledge
|
|
2
|
+
|
|
3
|
+
| Doc | Summary | Files |
|
|
4
|
+
|-----|---------|-------|
|
|
5
|
+
| [TOGa View Frontend (toga2-view) Architecture](architecture.md) | `toga2-view` is the **React/TypeScript single-page frontend** for the TOGa 2.0 platform — the customer-facing web app (home warranty / tech-support portals). | toga2-view/src/main.tsx, toga2-view/src/App.tsx, toga2-view/src/routes.tsx, toga2-view/src/api/axiosInstance.ts, toga2-view/src/api/apiFunctions.ts, toga2-view/src/utils/queryHelpers.ts, toga2-view/src/contexts/AuthContext.tsx, toga2-view/src/contexts/useUserStore.ts, toga2-view/src/hooks/useAuthenticationFlow.ts, toga2-view/vite.config.ts, toga2-view/package.json |
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: TOGa View Frontend (toga2-view) Architecture
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: toga2-view
|
|
5
|
+
project: TOGa View Frontend
|
|
6
|
+
client: shared
|
|
7
|
+
type: architecture
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-06-24
|
|
10
|
+
owners: [apeterson]
|
|
11
|
+
files:
|
|
12
|
+
- toga2-view/src/main.tsx
|
|
13
|
+
- toga2-view/src/App.tsx
|
|
14
|
+
- toga2-view/src/routes.tsx
|
|
15
|
+
- toga2-view/src/api/axiosInstance.ts
|
|
16
|
+
- toga2-view/src/api/apiFunctions.ts
|
|
17
|
+
- toga2-view/src/utils/queryHelpers.ts
|
|
18
|
+
- toga2-view/src/contexts/AuthContext.tsx
|
|
19
|
+
- toga2-view/src/contexts/useUserStore.ts
|
|
20
|
+
- toga2-view/src/hooks/useAuthenticationFlow.ts
|
|
21
|
+
- toga2-view/vite.config.ts
|
|
22
|
+
- toga2-view/package.json
|
|
23
|
+
related:
|
|
24
|
+
- clients/rate/profile.md
|
|
25
|
+
- clients/rate/features/saml-sso.md
|
|
26
|
+
- clients/rate/features/service-card-entitlements.md
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Summary
|
|
30
|
+
|
|
31
|
+
`toga2-view` is the **React/TypeScript single-page frontend** for the TOGa 2.0
|
|
32
|
+
platform — the customer-facing web app (home warranty / tech-support portals). It is
|
|
33
|
+
a pure client: it holds no server logic and talks exclusively to the **`api2`** backend
|
|
34
|
+
(the `/v2/...` JSON API) for all data and auth. It is **not** a PHP app and does **not**
|
|
35
|
+
build on `_underscore` directly — its only runtime dependency is `api2` (declared in
|
|
36
|
+
`registry.json` as `dependsOn: ["api2"]`).
|
|
37
|
+
|
|
38
|
+
**Stack:** React 18 + TypeScript, built with **Vite 7**. Routing via **React Router v7**
|
|
39
|
+
(`createBrowserRouter`). Server state via **TanStack React Query v5** (persisted to
|
|
40
|
+
`localStorage`). Client/global state via **Zustand** (user) + **React Context** (auth).
|
|
41
|
+
HTTP via a single **Axios** instance with auth/transaction interceptors. Styling via
|
|
42
|
+
**Tailwind CSS v3 + SCSS**. Shared UI primitives come from the **`@agilant/toga-blox`**
|
|
43
|
+
component library (npm package, versioned). Errors report to **Sentry**. Forms use
|
|
44
|
+
`react-hook-form`; animation via `framer-motion`; icons from FontAwesome Pro.
|
|
45
|
+
|
|
46
|
+
## Project layout
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
src/
|
|
50
|
+
├── main.tsx # Vite entry; mounts <App/>, imports global styles
|
|
51
|
+
├── App.tsx # Providers: PersistQueryClientProvider + AuthProvider + RouterProvider
|
|
52
|
+
├── routes.tsx # createBrowserRouter route table + route guards
|
|
53
|
+
├── api/ # Shared HTTP layer
|
|
54
|
+
│ ├── axiosInstance.ts # Single axios client + interceptors (auth, transactionId, 401 refresh)
|
|
55
|
+
│ ├── apiFunctions.ts # Generic apiGet / apiPost / apiPut / apiDelete<TData> wrappers
|
|
56
|
+
│ └── genericApi.ts
|
|
57
|
+
├── contexts/
|
|
58
|
+
│ ├── AuthContext.tsx # isAuthenticated, user, login(), logout(); cross-tab sync
|
|
59
|
+
│ └── useUserStore.ts # Zustand user store (persisted to localStorage "zu-user")
|
|
60
|
+
├── hooks/ # Shared hooks reused across pages
|
|
61
|
+
│ ├── useAuthenticationFlow.ts # SAML + domain-SSO orchestration (see Auth)
|
|
62
|
+
│ ├── useApiQuery.ts / useApiMutation.ts # React Query wrappers
|
|
63
|
+
│ ├── useBundleServices.ts / useActiveServices.ts # shared service-data hooks
|
|
64
|
+
│ └── useBreakpoint.tsx, useAnnouncements.ts, ...
|
|
65
|
+
├── components/ # Reusable, non-page UI (BaseButton, BaseInputs, MobileNav, ...)
|
|
66
|
+
├── pages/<Page>/ # Feature pages — see "Page convention" below
|
|
67
|
+
├── utils/
|
|
68
|
+
│ └── queryHelpers.ts # assembleOptions(): JS query object → /v2 query string
|
|
69
|
+
├── services/ # External integrations (e.g. paypalService.ts)
|
|
70
|
+
└── styles/ # index.scss (source) → index.css (Tailwind + SCSS output)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Page convention (the dominant pattern)
|
|
74
|
+
|
|
75
|
+
Each feature lives under `src/pages/<PageName>/` with a **view / viewModel / api** split:
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
pages/<PageName>/
|
|
79
|
+
├── view/
|
|
80
|
+
│ ├── <PageName>.tsx # presentational component; consumes the viewModel hook
|
|
81
|
+
│ └── components/ # page-local subcomponents
|
|
82
|
+
├── viewModels/ # business-logic hooks (⚠ some pages use singular "viewModel/")
|
|
83
|
+
│ ├── use<PageName>ViewModel.ts
|
|
84
|
+
│ └── DUMMYFIELDS/*.json # static labels/copy for the page
|
|
85
|
+
├── api/<page>Api.ts # page-specific API calls (build options, call apiGet/apiPost)
|
|
86
|
+
├── types.ts # page-local TypeScript interfaces
|
|
87
|
+
└── index.ts # re-exports the view component
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**ViewModel-hook pattern.** The view component is "dumb": it calls
|
|
91
|
+
`use<PageName>ViewModel()` and renders what it returns. The hook owns all state,
|
|
92
|
+
data-fetching, and event handlers, and returns a typed object
|
|
93
|
+
(`<PageName>ViewModel` interface). It composes shared hooks (`useUserStore()`,
|
|
94
|
+
`useBundleServices()`, `useNavigate()`, …) and the page's own `api/` functions.
|
|
95
|
+
|
|
96
|
+
> **Convention drift to know:** most pages name the folder `viewModels/` (plural), but a
|
|
97
|
+
> few (e.g. `CheckOut`, `Login`) use `viewModel/` (singular). When adding a page, match the
|
|
98
|
+
> plural form unless editing one of the existing singular ones. The hook itself is always
|
|
99
|
+
> `use<PageName>ViewModel` (singular "ViewModel").
|
|
100
|
+
|
|
101
|
+
## API / data layer
|
|
102
|
+
|
|
103
|
+
All HTTP goes through the single axios instance in `src/api/axiosInstance.ts`. Never
|
|
104
|
+
create ad-hoc `fetch`/`axios` calls in components — go through `apiFunctions.ts`
|
|
105
|
+
(`apiGet`/`apiPost`/`apiPut`/`apiDelete`) and React Query.
|
|
106
|
+
|
|
107
|
+
- **Base URL is resolved per-hostname.** The instance reads the first label of
|
|
108
|
+
`window.location.hostname`, upper-cases it, and looks up `VITE_API_<LABEL>`, falling
|
|
109
|
+
back to `VITE_API`. E.g. `homewarranty.rate.com` → `VITE_API_HOMEWARRANTY`. This is why
|
|
110
|
+
there are many `.env.<mode>` files and per-subdomain `VITE_API_*` vars.
|
|
111
|
+
- **transactionId** — a fresh UUID is attached to every request's query params (request
|
|
112
|
+
interceptor) for end-to-end tracing.
|
|
113
|
+
- **Query builder** — `assembleOptions()` in `utils/queryHelpers.ts` converts a structured
|
|
114
|
+
JS object (`fields`, `where`, `join`/`ojoin`, `sort`) into the `api2` `/v2` query string.
|
|
115
|
+
Use `ojoin` (LEFT JOIN) when a related row may be absent — `join` (INNER JOIN) silently
|
|
116
|
+
drops parent rows that have no match.
|
|
117
|
+
|
|
118
|
+
## Auth & tokens
|
|
119
|
+
|
|
120
|
+
- **AuthContext** holds `isAuthenticated`, `user`, `login()`, `logout()`. It persists to
|
|
121
|
+
`localStorage` and syncs across browser tabs via the `storage` event.
|
|
122
|
+
- **`useAuthenticationFlow.ts`** orchestrates two entry paths:
|
|
123
|
+
1. **SAML landing** — a `?saml=<payload>` query param on load: the param is **stripped
|
|
124
|
+
immediately via `replaceState`** (leaving it in causes stale-payload replay on
|
|
125
|
+
refresh — this was a live bug), then exchanged at the API for tokens + user, after
|
|
126
|
+
which `login()` runs and the app navigates to `/landing`.
|
|
127
|
+
2. **Domain SSO check** — if not authenticated and no `?saml=`, it queries `/domains`
|
|
128
|
+
for the current host; if the client is SSO-type it redirects to the IdP, otherwise
|
|
129
|
+
it falls through to `/login`.
|
|
130
|
+
- **Token storage (localStorage):** `accessToken`, `refreshToken`, `user`. Unauthenticated
|
|
131
|
+
requests use a **public token** auto-fetched via `POST /auth/public`. A `401` triggers a
|
|
132
|
+
one-shot refresh via `POST /auth/refresh`; if refresh fails, a `UserLoginRequired` window
|
|
133
|
+
event is dispatched to force re-login.
|
|
134
|
+
|
|
135
|
+
## Routing & guards
|
|
136
|
+
|
|
137
|
+
Routes are defined in `src/routes.tsx` with `createBrowserRouter`:
|
|
138
|
+
- `/` → `AuthRedirect` (sends to `/login` or `/landing` by auth state).
|
|
139
|
+
- `/login` → public (redirects already-authenticated users away).
|
|
140
|
+
- Everything else sits behind **`PrivateRoute`** (checks `useAuth().isAuthenticated`,
|
|
141
|
+
shows a loading state while `useAuthenticationFlow` resolves) and is wrapped by
|
|
142
|
+
`MobileNavToggle`. Pages: `/home`, `/landing`, `/services`, `/activity`, `/checkout`,
|
|
143
|
+
`/zip-validation`, `/payment-success`, `/activation`, `/get-support`, `/select-service`,
|
|
144
|
+
`/create-ticket`, `/choose-plan`.
|
|
145
|
+
|
|
146
|
+
## Build, environments & deploy
|
|
147
|
+
|
|
148
|
+
- **Tooling:** `npm run dev` (Vite dev server, port 5173), `npm run build`
|
|
149
|
+
(`tsc && vite build --mode production`), plus `buildBeta` / `buildGamma`. `npm run lint`
|
|
150
|
+
runs ESLint with `--max-warnings 0`. E2E via Cypress (`npm run cypress`).
|
|
151
|
+
- **Many environments:** `.env.{development,alpha,beta,gamma,sprint,stage,test,production}`.
|
|
152
|
+
Each supplies the `VITE_API_*` base URLs consumed by `axiosInstance.ts`. A client/brand is
|
|
153
|
+
selected by hostname at runtime (not a separate build), via the `VITE_API_<SUBDOMAIN>`
|
|
154
|
+
lookup.
|
|
155
|
+
|
|
156
|
+
## Critical rules
|
|
157
|
+
|
|
158
|
+
- **All HTTP goes through `axiosInstance` + `apiFunctions`** — never raw `fetch`/`axios` in
|
|
159
|
+
components. The shared instance is what attaches auth, the transactionId, and 401-refresh.
|
|
160
|
+
- **Use `ojoin` (LEFT JOIN), not `join` (INNER JOIN), in `assembleOptions` queries** when a
|
|
161
|
+
joined row may be missing — an inner join silently drops parent records.
|
|
162
|
+
- **Strip the `?saml=` param before using it** (already handled in `useAuthenticationFlow`);
|
|
163
|
+
do not reintroduce code paths that leave it in the URL.
|
|
164
|
+
- **Append `T00:00:00` to date-only strings before parsing** to avoid timezone shift moving
|
|
165
|
+
a date back a day (a recurring display bug on service/entitlement dates).
|
|
166
|
+
- **Keep view components presentational** — logic, state, and side effects belong in the
|
|
167
|
+
`use<PageName>ViewModel` hook, not the `.tsx` view.
|
|
168
|
+
- **Shared hooks fan out.** A change to a shared hook (e.g. `useBundleServices`) affects every
|
|
169
|
+
page/viewModel that consumes it — check call sites before changing its shape.
|
|
170
|
+
- **Tokens live in `localStorage`** (`accessToken`/`refreshToken`/`user`). Treat them as
|
|
171
|
+
sensitive; never log token values or echo them into Sentry extras.
|
|
172
|
+
|
|
173
|
+
## Client notes
|
|
174
|
+
|
|
175
|
+
**Rate** is the primary consumer of this frontend (SAML-only auth via Azure AD; warranty
|
|
176
|
+
service cards show Address instead of Price). See `clients/rate/profile.md` and the Rate
|
|
177
|
+
feature docs in `related:` for client-specific behavior layered on top of this architecture.
|
|
178
|
+
|
|
179
|
+
## Gaps / not yet captured
|
|
180
|
+
|
|
181
|
+
- `2.0/standards/frontend.md` — no shared 2.0 React/TS standard doc exists yet; conventions
|
|
182
|
+
above are documented here per-repo until one is written.
|
|
183
|
+
- Per-page feature docs (Home, Activity, Checkout, etc.) are not individually captured; add
|
|
184
|
+
under `2.0/apps/toga2-view/features/` as they stabilize.
|
package/knowledge/INDEX.md
CHANGED
|
@@ -21,7 +21,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
21
21
|
- **dbchanges2** (Database Changes) _(framework core)_ — 2 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
|
|
22
22
|
- **toga2-supply** (TOGa Supply) — 3 doc(s) → [2.0/apps/toga2-supply/INDEX.md](2.0/apps/toga2-supply/INDEX.md)
|
|
23
23
|
- **saml** (SAML SSO Gateway) — 2 doc(s) → [2.0/apps/saml/INDEX.md](2.0/apps/saml/INDEX.md)
|
|
24
|
-
- **toga2-view** (TOGa View Frontend) —
|
|
24
|
+
- **toga2-view** (TOGa View Frontend) — 2 doc(s) → [2.0/apps/toga2-view/INDEX.md](2.0/apps/toga2-view/INDEX.md)
|
|
25
25
|
- **toga2-hub** (TOGa Hub) — 2 doc(s) → [2.0/apps/toga2-hub/INDEX.md](2.0/apps/toga2-hub/INDEX.md)
|
|
26
26
|
- **talos** (TOGa IQ) — 6 doc(s) → [2.0/apps/talos/INDEX.md](2.0/apps/talos/INDEX.md)
|
|
27
27
|
- **voice-to-voice** (TOGa Voice) — 4 doc(s) → [2.0/apps/voice-to-voice/INDEX.md](2.0/apps/voice-to-voice/INDEX.md)
|
package/package.json
CHANGED