toga-ai 1.0.468 → 1.0.469
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.
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
| Doc | Summary | Files |
|
|
4
4
|
|-----|---------|-------|
|
|
5
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 |
|
|
6
|
+
| [Get Support — Subscription Details & Cancel Subscription Gating](features/get-support-cancel-subscription.md) | The Get Support page (`/get-support?itemUuid=…`) shows a **subscription details card** (plan · price · renewal date) for the service the user clicked, and — **o | toga2-view/src/pages/GetSupport/viewModels/getSupportPageViewModel.ts, toga2-view/src/pages/GetSupport/view/GetSupportPage.tsx, toga2-view/src/pages/GetSupport/viewModels/DUMMYFIELDS/SUPPORTDUMMYFIELDS.json, toga2-view/src/constants/featureFlags.ts, toga2-view/src/hooks/useBundleServices.ts |
|
|
6
7
|
| [Mobile Nav Header](features/mobile-nav-header.md) | The `MobileNavToggle` component renders the fixed 72 px header (hamburger + Rate logo) and the slide-in drawer nav. | toga2-view/src/components/MobileNav/MobileNav.tsx, toga2-view/public/assets/Rate_Logo.svg |
|
|
7
8
|
| [/v2 Query-String Builder (assembleOptions / where coercion)](features/query-string-builder.md) | `src/utils/queryHelpers.ts` converts a structured JS options object (`fields`, `where`, `join`/`ojoin`, `sort`, …) into the `api2` `/v2` query string. | toga2-view/src/utils/queryHelpers.ts, toga2-view/src/utils/queryHelpers.test.ts |
|
|
8
9
|
| [Service Card Component](features/service-card.md) | The `ServiceCard` component renders a single service subscription (tech support or home warranty) on both the Home and Services pages. | toga2-view/src/components/ServiceCard/ServiceCard.tsx, toga2-view/src/pages/Services/view/ServicesPage.tsx, toga2-view/src/pages/Home/view/HomePage.tsx, toga2-view/src/constants/bundleConstants.ts, toga2-view/src/pages/Services/viewModels/DUMMYFIELDS/SERVICESDUMMYFIELDS.json |
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: "Get Support — Subscription Details & Cancel Subscription Gating"
|
|
3
|
+
framework: "2.0"
|
|
4
|
+
repo: toga2-view
|
|
5
|
+
project: TOGa View Frontend
|
|
6
|
+
client: shared
|
|
7
|
+
type: feature
|
|
8
|
+
status: active
|
|
9
|
+
updated: 2026-07-28
|
|
10
|
+
owners: ["tcox"]
|
|
11
|
+
files:
|
|
12
|
+
- toga2-view/src/pages/GetSupport/viewModels/getSupportPageViewModel.ts
|
|
13
|
+
- toga2-view/src/pages/GetSupport/view/GetSupportPage.tsx
|
|
14
|
+
- toga2-view/src/pages/GetSupport/viewModels/DUMMYFIELDS/SUPPORTDUMMYFIELDS.json
|
|
15
|
+
- toga2-view/src/constants/featureFlags.ts
|
|
16
|
+
- toga2-view/src/hooks/useBundleServices.ts
|
|
17
|
+
related:
|
|
18
|
+
- ../architecture.md
|
|
19
|
+
- ../../../../clients/rate/features/service-card-entitlements.md
|
|
20
|
+
- ../../../../clients/rate/profile.md
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Summary
|
|
24
|
+
|
|
25
|
+
The Get Support page (`/get-support?itemUuid=…`) shows a **subscription details card**
|
|
26
|
+
(plan · price · renewal date) for the service the user clicked, and — **only for tech-support
|
|
27
|
+
services** — a **Cancel Subscription** button plus a confirmation modal. Whole Home (WH) warranty
|
|
28
|
+
services and unclassified services get the subscription details card **without** any cancel path.
|
|
29
|
+
|
|
30
|
+
Two things about this flow are easy to get wrong and are the reason it is documented:
|
|
31
|
+
|
|
32
|
+
- The cancel affordance exists **only on this page** — `ServiceCard` has none.
|
|
33
|
+
- **There is no backend cancellation endpoint yet.** The viewModel's `cancelSubscription()` is a
|
|
34
|
+
stub that always rejects, so confirming surfaces an error instead of faking success.
|
|
35
|
+
|
|
36
|
+
## Key files / entry points
|
|
37
|
+
|
|
38
|
+
- `pages/GetSupport/viewModels/getSupportPageViewModel.ts` — resolves `activeService`, builds the
|
|
39
|
+
`subscription` object, exposes `canCancelSubscription` and the `cancelSubscription()` stub
|
|
40
|
+
- `pages/GetSupport/view/GetSupportPage.tsx` — the **only** place a cancel affordance is rendered;
|
|
41
|
+
computes `showCancelUi` and owns the confirm-modal state (`showCancelModal`, `cancelError`)
|
|
42
|
+
- `constants/featureFlags.ts` — `CANCEL_SUBSCRIPTION_ENABLED` (currently `true`); flipping it to
|
|
43
|
+
`false` hides the whole cancel path in one place
|
|
44
|
+
- `viewModels/DUMMYFIELDS/SUPPORTDUMMYFIELDS.json` — copy: `renewsPrefix`, `cancelNote`
|
|
45
|
+
("You can cancel anytime. Coverage continues until the end of your current period."),
|
|
46
|
+
`confirm.*` (including `confirm.errorMessage`)
|
|
47
|
+
- `hooks/useBundleServices.ts` — `detectServiceType()`, the classification the gate reuses
|
|
48
|
+
|
|
49
|
+
## How it works
|
|
50
|
+
|
|
51
|
+
1. **`activeService` resolution.** The viewModel picks the bundle service whose `uuid` matches the
|
|
52
|
+
URL's `itemUuid`, falls back to the first service with `isActive`, else `null`:
|
|
53
|
+
```ts
|
|
54
|
+
const activeService =
|
|
55
|
+
bundleServices?.find((service) => service.uuid === itemUuid) ??
|
|
56
|
+
bundleServices?.find((service) => service.isActive) ??
|
|
57
|
+
null;
|
|
58
|
+
```
|
|
59
|
+
The subscription card **and** the cancel gate both read this same `activeService`, so older links
|
|
60
|
+
without a resolvable `itemUuid` behave consistently across both.
|
|
61
|
+
|
|
62
|
+
2. **Subscription details.** `subscription` = `{ plan, price, renewalDate, entitlementUuid }` from
|
|
63
|
+
`activeService`, else `null`. The card renders whenever `subscription` exists — **including for
|
|
64
|
+
warranty services**. Gating cancel does not hide the plan/price/renewal information.
|
|
65
|
+
|
|
66
|
+
3. **The gate (positive, not negative).**
|
|
67
|
+
```ts
|
|
68
|
+
const canCancelSubscription = activeService?.serviceType === "tech";
|
|
69
|
+
```
|
|
70
|
+
`'warranty'` **and** `'other'` therefore both hide it — an unclassified service fails closed.
|
|
71
|
+
`serviceType` comes from `detectServiceType(bundleName || itemTitle)` in `useBundleServices`
|
|
72
|
+
(title-substring classification: contains "warranty"/"whole home" and not "tech" → `'warranty'`;
|
|
73
|
+
contains "tech"/"support" → `'tech'`; else `'other'`). This is the same signal that switches
|
|
74
|
+
`ServiceCard`'s second data row between **Price** (tech) and **Address** (warranty), so the cancel
|
|
75
|
+
gate and the service cards agree by construction.
|
|
76
|
+
|
|
77
|
+
4. **The view gates three things with one boolean.**
|
|
78
|
+
```ts
|
|
79
|
+
const showCancelUi = CANCEL_SUBSCRIPTION_ENABLED && canCancelSubscription;
|
|
80
|
+
```
|
|
81
|
+
- the Cancel Subscription button
|
|
82
|
+
- the confirm modal (`showCancelUi && showCancelModal`)
|
|
83
|
+
- the `cancelNote` sentence appended to the `subscriptionDetails` string
|
|
84
|
+
|
|
85
|
+
The note is gated deliberately: leaving *"You can cancel anytime…"* on a card with no cancel
|
|
86
|
+
button is self-contradictory copy.
|
|
87
|
+
|
|
88
|
+
5. **Confirm flow.** `handleConfirmCancel()` awaits `cancelSubscription()`; it closes the modal
|
|
89
|
+
**only** on a genuine resolve, and on rejection keeps the modal open and renders
|
|
90
|
+
`subscriptionFields.confirm.errorMessage`. Since the stub always throws, today's real-world
|
|
91
|
+
outcome is always the error state — intentional, so nothing reports a cancellation that did not
|
|
92
|
+
happen.
|
|
93
|
+
|
|
94
|
+
## Decision — gate on `serviceType`, not on `isRateWarranty`
|
|
95
|
+
|
|
96
|
+
The page already carries a separate warranty signal: `isRateWarranty = bundleUuid ===
|
|
97
|
+
HOME_WARRANTY_BUNDLE_UUID`, used to route the **TICKETS** support method to the claims-style
|
|
98
|
+
create-ticket link. It was deliberately **not** reused for the cancel gate:
|
|
99
|
+
|
|
100
|
+
- `serviceType` is a *product classification*, so any service classified `'tech'` gets cancel —
|
|
101
|
+
it does not need a bundle uuid allowlist maintained per product.
|
|
102
|
+
- It keeps this page consistent with the service cards, which already branch on `serviceType`.
|
|
103
|
+
- A bundle-uuid check would silently fail open for any new warranty bundle uuid.
|
|
104
|
+
|
|
105
|
+
**Why WH is excluded at all:** Whole Home warranty contracts (the AIG-backed warranty product) are
|
|
106
|
+
not self-service cancellable from the portal; only tech-support subscriptions are.
|
|
107
|
+
|
|
108
|
+
## Gotchas / known issues
|
|
109
|
+
|
|
110
|
+
- **The cancel UI lives only on `GetSupportPage.tsx`.** `ServiceCard` has no cancel affordance —
|
|
111
|
+
do not go looking for one in `components/`, and do not add a second entry point without
|
|
112
|
+
reproducing the same `showCancelUi` gate.
|
|
113
|
+
- **No backend endpoint exists.** `cancelSubscription()` throws
|
|
114
|
+
`"Subscription cancellation is not available yet."` by design (reject, never resolve). Swap the
|
|
115
|
+
body for the real API call when the endpoint ships; do not "fix" it by resolving.
|
|
116
|
+
- **`detectServiceType` now has a second consumer with a policy consequence.** Widening its
|
|
117
|
+
`'tech'` keywords does not just change a card row any more — it **exposes the cancel button** for
|
|
118
|
+
whatever newly matches. Check both call sites before touching the classifier.
|
|
119
|
+
- **The gate fails closed on `'other'`.** If a legitimately cancellable service stops matching
|
|
120
|
+
"tech"/"support" in its bundle/item title, its cancel button silently disappears. The title is the
|
|
121
|
+
only input.
|
|
122
|
+
- **`cancelNote` is not standalone copy** — it is concatenated into `subscriptionDetails` and gated
|
|
123
|
+
by `showCancelUi`. Any new surface rendering that string must gate it the same way.
|
|
124
|
+
- **`CANCEL_SUBSCRIPTION_ENABLED = false` hides button + modal + note but keeps the subscription
|
|
125
|
+
details card** — that is the intended kill-switch behavior.
|
|
126
|
+
- **Never close the confirm modal on failure.** Closing it reads to the user as a successful
|
|
127
|
+
cancellation.
|
|
128
|
+
|
|
129
|
+
## Client variations
|
|
130
|
+
|
|
131
|
+
Rate is the consumer of this page today. Its product mix — Whole Home Warranty (AIG-backed) plus
|
|
132
|
+
tech support — is what makes the gate load-bearing: both products appear as active services for the
|
|
133
|
+
same user, so the page must distinguish them per service rather than per account. See
|
|
134
|
+
[Rate profile](../../../../clients/rate/profile.md) and
|
|
135
|
+
[Service Card Entitlement Display](../../../../clients/rate/features/service-card-entitlements.md).
|
|
136
|
+
|
|
137
|
+
## Change history
|
|
138
|
+
|
|
139
|
+
- 2026-07-28 — Cancel Subscription UI gated by service type: viewModel exposes
|
|
140
|
+
`canCancelSubscription` (`activeService?.serviceType === "tech"`), and the view's `showCancelUi`
|
|
141
|
+
now gates the button, the confirm modal, and the `cancelNote` sentence. WH warranty and
|
|
142
|
+
unclassified services keep the subscription details card with no cancel path. Also first capture of
|
|
143
|
+
the pre-existing flow: cancel exists only on `GetSupportPage`, is flagged by
|
|
144
|
+
`CANCEL_SUBSCRIPTION_ENABLED`, and `cancelSubscription()` is a stub that always rejects until the
|
|
145
|
+
backend endpoint ships. (tcox)
|
package/knowledge/INDEX.md
CHANGED
|
@@ -23,7 +23,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
|
|
|
23
23
|
- **dbchanges2** (Database Changes) _(framework core)_ — 3 doc(s) → [2.0/apps/dbchanges2/INDEX.md](2.0/apps/dbchanges2/INDEX.md)
|
|
24
24
|
- **toga2-supply** (TOGa Supply) — 3 doc(s) → [2.0/apps/toga2-supply/INDEX.md](2.0/apps/toga2-supply/INDEX.md)
|
|
25
25
|
- **saml** (SAML SSO Gateway) — 3 doc(s) → [2.0/apps/saml/INDEX.md](2.0/apps/saml/INDEX.md)
|
|
26
|
-
- **toga2-view** (TOGa View Frontend) —
|
|
26
|
+
- **toga2-view** (TOGa View Frontend) — 7 doc(s) → [2.0/apps/toga2-view/INDEX.md](2.0/apps/toga2-view/INDEX.md)
|
|
27
27
|
- **toga2-hub** (TOGa Hub) — 2 doc(s) → [2.0/apps/toga2-hub/INDEX.md](2.0/apps/toga2-hub/INDEX.md)
|
|
28
28
|
- **talos** (TOGa IQ) — 7 doc(s) → [2.0/apps/talos/INDEX.md](2.0/apps/talos/INDEX.md)
|
|
29
29
|
- **voice-to-voice** (TOGa Voice) — 4 doc(s) → [2.0/apps/voice-to-voice/INDEX.md](2.0/apps/voice-to-voice/INDEX.md)
|
|
@@ -6,7 +6,7 @@ project: TOGa View Frontend
|
|
|
6
6
|
client: rate
|
|
7
7
|
type: client-feature
|
|
8
8
|
status: active
|
|
9
|
-
updated: 2026-07-
|
|
9
|
+
updated: 2026-07-28
|
|
10
10
|
owners: ["bala", "tcox", "mhammontree"]
|
|
11
11
|
files:
|
|
12
12
|
- src/components/ServiceCard/ServiceCard.tsx
|
|
@@ -24,6 +24,7 @@ files:
|
|
|
24
24
|
related:
|
|
25
25
|
- clients/rate/profile.md
|
|
26
26
|
- whole-home-warranty-purchase-guard.md
|
|
27
|
+
- 2.0/apps/toga2-view/features/get-support-cancel-subscription.md
|
|
27
28
|
---
|
|
28
29
|
|
|
29
30
|
## Summary
|
|
@@ -101,6 +102,11 @@ This feature is Rate-specific. The warranty card showing Address instead of Pric
|
|
|
101
102
|
by Rate's product mix (Whole Home Warranty + Tech Support). Other clients with different
|
|
102
103
|
product types would need `detectServiceType` extended or overridden.
|
|
103
104
|
|
|
105
|
+
**`detectServiceType` is no longer display-only.** As of 2026-07-28 it also gates the
|
|
106
|
+
**Cancel Subscription** UI on the Get Support page (`serviceType === 'tech'` — WH warranty and
|
|
107
|
+
unclassified services get no cancel path). See
|
|
108
|
+
[Get Support — Subscription Details & Cancel Subscription Gating](../../../2.0/apps/toga2-view/features/get-support-cancel-subscription.md).
|
|
109
|
+
|
|
104
110
|
## Gotchas / known issues
|
|
105
111
|
|
|
106
112
|
- **`ojoin` is required for address** — using INNER JOIN (`join`) drops every entitlement
|
|
@@ -115,6 +121,10 @@ product types would need `detectServiceType` extended or overridden.
|
|
|
115
121
|
`AddSubscriptionSheet`) replaces the old "Get Started" inactive bundle cards.
|
|
116
122
|
- **`useBundleServices` is shared** — both `useHomePageViewModel` and `useServicePageViewModel`
|
|
117
123
|
call it. Changes to the hook affect both pages.
|
|
124
|
+
- **Widening `detectServiceType`'s `'tech'` keywords now changes entitlement *permissions*, not just
|
|
125
|
+
a card row.** The Get Support page gates its Cancel Subscription button on
|
|
126
|
+
`serviceType === 'tech'`, so anything newly classified as tech becomes self-service cancellable.
|
|
127
|
+
Review both consumers (`ServiceCard` row 2 and the cancel gate) before editing the classifier.
|
|
118
128
|
- **Show the entitlement's OWN pin only — never the contact's shared address.** The removed
|
|
119
129
|
`contact.primaryContactAddress` fallback made every unpinned card display the same (latest)
|
|
120
130
|
address because that field is one shared, purchase-overwritten value per contact. An unpinned
|
|
@@ -137,6 +147,9 @@ product types would need `detectServiceType` extended or overridden.
|
|
|
137
147
|
|
|
138
148
|
## Change history
|
|
139
149
|
|
|
150
|
+
- 2026-07-28 — `detectServiceType` gained a second, non-display consumer: the Get Support page gates
|
|
151
|
+
its Cancel Subscription UI on `serviceType === 'tech'`, so the classifier now decides which
|
|
152
|
+
entitlements are self-service cancellable. (tcox)
|
|
140
153
|
- 2026-07-27 — TRUE-79533: recorded that `fetchAddressesByIds`
|
|
141
154
|
(`GET /v2/addresses?fields=id,line1,...`) returned **403 `EZ-2`** because the **`Addresses.id`
|
|
142
155
|
field** had no read ACL grant in `Client_Rate` — the numeric `id` is ACL-gated like any field. Fixed
|
package/package.json
CHANGED