toga-ai 1.0.168 → 1.0.169

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.
@@ -21,7 +21,7 @@ _Auto-generated by `knowledge.js index`. Do not hand-edit._
21
21
  - **dbchanges2** (Database Changes) _(framework core)_ — 1 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) — 0 doc(s) → [2.0/apps/toga2-view/INDEX.md](2.0/apps/toga2-view/INDEX.md)
24
+ - **toga2-view** (TOGa View Frontend) — 1 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)
@@ -4,4 +4,5 @@
4
4
  |-----|-----------|---------|-------|
5
5
  | [Rate Monthly Reconciliation Report](features/monthly-reconciliation-report.md) | 1.0 | A monthly cron that emails an Excel reconciliation report covering all Rate subscription sales orders and their linked PayPal payments for the prior calendar mo | worker/crons/notifications/reports/rate/send_monthly_rate_purchases_report.php, worker/schedules/cron.worker.notification.json |
6
6
  | [Rate SAML SSO](features/saml-sso.md) | 2.0 | Rate uses Azure AD as its IdP (`login.rate.com`). | _underscore/Model/Rate/ClientAuthentication.php, saml/Controller/Index.php, toga2-view/src/hooks/useAuthenticationFlow.ts |
7
+ | [Service Card Entitlement Display](features/service-card-entitlements.md) | 2.0 | Rate's home and services pages display one service card per purchased entitlement. | src/components/ServiceCard/ServiceCard.tsx, src/components/ServiceCard/index.ts, src/hooks/useBundleServices.ts, src/pages/Home/api/homeApi.ts, src/pages/Home/view/HomePage.tsx, src/pages/Home/viewModels/useHomePageViewModel.ts, src/pages/Services/view/ServicesPage.tsx, src/pages/Services/viewModels/useServicePageViewModel.ts |
7
8
  | [Rate](profile.md) | 2.0 | Rate is a mortgage/lending client. | |
@@ -0,0 +1,103 @@
1
+ ---
2
+ title: "Service Card Entitlement Display"
3
+ framework: "2.0"
4
+ repo: toga2-view
5
+ project: TOGa View
6
+ client: rate
7
+ type: client-feature
8
+ status: active
9
+ updated: 2026-06-23
10
+ owners: ["bala"]
11
+ files:
12
+ - src/components/ServiceCard/ServiceCard.tsx
13
+ - src/components/ServiceCard/index.ts
14
+ - src/hooks/useBundleServices.ts
15
+ - src/pages/Home/api/homeApi.ts
16
+ - src/pages/Home/view/HomePage.tsx
17
+ - src/pages/Home/viewModels/useHomePageViewModel.ts
18
+ - src/pages/Services/view/ServicesPage.tsx
19
+ - src/pages/Services/viewModels/useServicePageViewModel.ts
20
+ related:
21
+ - clients/rate/profile.md
22
+ ---
23
+
24
+ ## Summary
25
+
26
+ Rate's home and services pages display one service card per purchased entitlement. The card
27
+ shows a hero image, title, Active badge, contract number, plan, price (or address for warranty
28
+ cards), renewal date, and a Manage button. Previously the UI showed one card per bundle type
29
+ regardless of how many entitlements the user had purchased.
30
+
31
+ ## Key files / entry points
32
+
33
+ - `ServiceCard.tsx` — the card component; accepts `serviceType` to switch the second data row
34
+ between Price (tech) and Address (warranty)
35
+ - `useBundleServices.ts` — shared hook returning one `BundleService` per active entitlement;
36
+ used by both `useHomePageViewModel` and `useServicePageViewModel`
37
+ - `homeApi.ts` — `getActiveServices()` fetches entitlements with subscription and address fields
38
+ - `HomePage.tsx` — shows max 3 cards; "View All Services" tertiary text button links to `/services`
39
+ - `ServicesPage.tsx` — shows all entitlements in a 2-column grid, scrollable
40
+
41
+ ## How it works
42
+
43
+ 1. `getActiveServices(borrowerId)` queries `Entitlements` with:
44
+ - INNER JOIN: `Contacts`, `Customers`, `Subscriptions`
45
+ - LEFT JOIN (`ojoin`): `ContactAddresses`, `Addresses` — keeps entitlements whose contact
46
+ has no address instead of dropping the row
47
+ - Fields: `uuid`, `number`, `saleItem.title/uuid`, `subscription.isActive/amount/frequency/dateEnd`,
48
+ `contact.primaryContactAddress.address.line1`
49
+
50
+ 2. `useBundleServices` filters `subscription.isActive === true` and builds one `BundleService`
51
+ per entitlement. It also fetches bundles to resolve `bundleUuid` for navigation.
52
+
53
+ 3. `detectServiceType(title)` classifies each card:
54
+ - `'warranty'` — title includes "warranty" or "whole home" (and NOT "tech")
55
+ - `'tech'` — title includes "tech" or "support"
56
+ - `'other'` — everything else
57
+
58
+ 4. `ServiceCard` renders the data rows based on `serviceType`:
59
+ - Tech / other → row 2: **Price** (`$XX.XX`)
60
+ - Warranty → row 2: **Address** (`contact.primaryContactAddress.address.line1`)
61
+
62
+ 5. Home page slices `allServices` to 3 (`HOME_SERVICE_LIMIT`). If more exist, a tertiary
63
+ "View All Services" text link appears below the cards pointing to `/services`.
64
+
65
+ 6. Services page renders all entitlements with no cap in a `grid-cols-1 md:grid-cols-2` grid.
66
+
67
+ ## Data model
68
+
69
+ Address path in DB:
70
+ ```
71
+ Entitlements.contactId
72
+ → Contacts.primaryContactAddressId
73
+ → ContactAddresses.id → ContactAddresses.addressId
74
+ → Addresses.line1
75
+ ```
76
+
77
+ Contract number comes from `Entitlements.number` (auto-incremented with ET prefix, e.g. `ET100017`).
78
+
79
+ ## Client variations
80
+
81
+ This feature is Rate-specific. The warranty card showing Address instead of Price is driven
82
+ by Rate's product mix (Whole Home Warranty + Tech Support). Other clients with different
83
+ product types would need `detectServiceType` extended or overridden.
84
+
85
+ ## Gotchas / known issues
86
+
87
+ - **`ojoin` is required for address** — using INNER JOIN (`join`) drops every entitlement
88
+ whose contact has no `primaryContactAddressId`, silently hiding those cards.
89
+ - **`T00:00:00` in date formatting** — `formatDate` appends this before parsing to avoid
90
+ timezone offset shifting the renewal date back by one day.
91
+ - **Per-entitlement, not per-bundle** — `useBundleServices` intentionally does not dedup.
92
+ A user with 8 entitlements of the same product sees 8 cards. The old dedup-by-bundle
93
+ behavior (1 card per service type) was the previous design.
94
+ - **Inactive bundles are not shown** — the new design only renders active entitlements.
95
+ The "Add a subscription" dashed button (handled by `useAddSubscription` hook +
96
+ `AddSubscriptionSheet`) replaces the old "Get Started" inactive bundle cards.
97
+ - **`useBundleServices` is shared** — both `useHomePageViewModel` and `useServicePageViewModel`
98
+ call it. Changes to the hook affect both pages.
99
+
100
+ ## Change history
101
+
102
+ - 2026-06-23 — Initial implementation: per-entitlement card display, new ServiceCard component,
103
+ address via LEFT JOIN, 3-card home limit with View All Services tertiary button (bala)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toga-ai",
3
- "version": "1.0.168",
3
+ "version": "1.0.169",
4
4
  "description": "TOGA Technology Team Claude Knowledge System — shared AI coding harness with skills, knowledge base CLI, and project installer for Claude Code.",
5
5
  "keywords": [
6
6
  "claude",