@retiregolden/planner-ui 0.1.0 → 0.2.0

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.
@@ -1,7 +1,7 @@
1
1
  import { useCallback, useState } from 'react'
2
2
 
3
3
  import { readLocal, STORAGE_KEYS, writeLocal } from '../../data/localStore'
4
- import type { PlanSummary } from '../../data/planStore'
4
+ import type { PlanSummary } from '../../data/planStoreContext'
5
5
 
6
6
  export const WELCOME_DISMISSED_KEY = STORAGE_KEYS.homeWelcomeDismissed
7
7
 
@@ -0,0 +1,14 @@
1
+ import type { ReactNode } from 'react'
2
+
3
+ import { ReportBrandingContext } from './brandingContext'
4
+ import type { ReportBranding } from './reportHtml'
5
+
6
+ /**
7
+ * Supplies report branding to hosts that mount the exported route groups
8
+ * directly instead of `<PlannerApp/>` (whose `reportBranding` prop does the
9
+ * same thing). Omit `branding` (or the provider) and downloaded reports keep
10
+ * the RetireGolden defaults.
11
+ */
12
+ export function ReportBrandingProvider({ branding, children }: { branding?: ReportBranding; children: ReactNode }) {
13
+ return <ReportBrandingContext.Provider value={branding ?? null}>{children}</ReportBrandingContext.Provider>
14
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Route-level exports: the planner's route table split into host-mountable
3
+ * groups. `<PlannerApp/>` composes all three; a host that owns its own
4
+ * plans-management chrome mounts the workspace (and usually content) groups
5
+ * under its router and omits the home group entirely.
6
+ *
7
+ * The groups are react-router v7 `RouteObject[]` arrays — the shape that
8
+ * spreads into `useRoutes` or feeds `createBrowserRouter` — rather than
9
+ * `<Route>` fragments, which can't cross a component boundary (`<Routes>`
10
+ * only reads `<Route>` children it can see literally).
11
+ *
12
+ * **Mount the groups at the host router's root.** To serve the planner under
13
+ * a URL prefix, put the prefix in the router's `basename` — the planner's
14
+ * pages navigate with root-absolute paths (`/plan/:id/…`, `/compare`,
15
+ * `/learn/…`), which react-router resolves against the basename. Nesting the
16
+ * groups under a parent route path (e.g. `path: 'planner/*'`) is NOT
17
+ * supported: the initial deep link would render, but the first in-app
18
+ * navigation would escape the prefix.
19
+ *
20
+ * Everything here stays chrome-free: pages only, no header/nav/footer and no
21
+ * document.title management for non-plan routes (plan routes retitle
22
+ * themselves in PlanWorkspace). The host owns that, as `<PlannerApp/>` does
23
+ * for the web app.
24
+ */
25
+
26
+ import { Suspense, type ReactNode } from 'react'
27
+ import { Navigate, type RouteObject } from 'react-router-dom'
28
+
29
+ import { PlanPickerPage } from '../planner/PlanPickerPage'
30
+ import { DisclaimerPage } from '../planner/DisclaimerPage'
31
+ import { RouteFallback } from './RouteFallback'
32
+ import { ComparePlansPage, ExamplesPage, HowTestedPage, ImportPage, LearnRoutes, PlanRoutes } from './lazyPages'
33
+
34
+ function suspended(children: ReactNode) {
35
+ return <Suspense fallback={<RouteFallback />}>{children}</Suspense>
36
+ }
37
+
38
+ /**
39
+ * The plan workspace: everything under `/plan/:planId/*` (entry sections,
40
+ * results, Monte Carlo, scenarios, survivor, relocation, the optimizers, the
41
+ * report) plus cross-plan Compare. Deep links work when this group is
42
+ * mounted alone. Workspace pages link to `/` ("Your plans") and into the
43
+ * content group (`/disclaimer`, `/how-tested`, Learn) — a host mounts or
44
+ * redirects those paths as it sees fit.
45
+ */
46
+ export const plannerWorkspaceRoutes: RouteObject[] = [
47
+ { path: 'plan/*', element: suspended(<PlanRoutes />) },
48
+ { path: 'compare', element: suspended(<ComparePlansPage />) },
49
+ ]
50
+
51
+ /**
52
+ * Storage-independent content: the example library, the Learning Center,
53
+ * the disclaimer, and the "How RetireGolden is tested" trust page (linked
54
+ * from Results and the Disclaimer, so hosts mounting the workspace want it).
55
+ */
56
+ export const plannerContentRoutes: RouteObject[] = [
57
+ { path: 'examples', element: suspended(<ExamplesPage />) },
58
+ { path: 'learn/*', element: suspended(<LearnRoutes />) },
59
+ { path: 'disclaimer', element: <DisclaimerPage /> },
60
+ { path: 'how-tested', element: suspended(<HowTestedPage />) },
61
+ ]
62
+
63
+ /**
64
+ * The web app's plans-management surfaces: the home page (plan list, backup
65
+ * download/import, clear-all) and the file-import wizard, plus redirects for
66
+ * retired v1 routes. Hosts with their own library chrome omit this group.
67
+ */
68
+ export const plannerHomeRoutes: RouteObject[] = [
69
+ { index: true, element: <PlanPickerPage /> },
70
+ { path: 'import', element: suspended(<ImportPage />) },
71
+ // Retired v1 routes now redirect into the planner.
72
+ { path: 'legacy', element: <Navigate to="/" replace /> },
73
+ { path: 'longevity', element: <Navigate to="/" replace /> },
74
+ { path: 'social-security', element: <Navigate to="/" replace /> },
75
+ ]
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Lazy page bindings for the route groups (routes/groups.tsx), split out so
3
+ * each file exports only components (react-refresh boundary rule).
4
+ */
5
+
6
+ import { lazy } from 'react'
7
+
8
+ export const PlanRoutes = lazy(() => import('./PlanRoutes'))
9
+ export const LearnRoutes = lazy(() => import('./LearnRoutes'))
10
+ // Lazy like /plan and /learn: Examples pulls in all example builders + the Zod
11
+ // schema, Compare pulls in the whole engine via projectPlan — eager imports
12
+ // here would drag both into the landing entry chunk.
13
+ export const ExamplesPage = lazy(() => import('../planner/examples/ExamplesPage').then((m) => ({ default: m.ExamplesPage })))
14
+ export const ComparePlansPage = lazy(() => import('../planner/ComparePlansPage').then((m) => ({ default: m.ComparePlansPage })))
15
+ // Lazy so the glob-derived test-suite manifests it embeds stay out of the landing chunk.
16
+ export const HowTestedPage = lazy(() => import('../planner/HowTestedPage').then((m) => ({ default: m.HowTestedPage })))
17
+ // Lazy: the import wizard pulls in the per-source mappers and the Zod schema.
18
+ export const ImportPage = lazy(() => import('../import/ImportPage').then((m) => ({ default: m.ImportPage })))