@remit/web-client 0.0.1
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.
- package/harness/build.mjs +84 -0
- package/harness/index.html +37 -0
- package/harness/vite-preset.ts +29 -0
- package/index.html +47 -0
- package/package.json +147 -0
- package/public/config.js +5 -0
- package/public/locales/de/common.json +1 -0
- package/public/locales/de/errors.json +1 -0
- package/public/locales/de/mail.json +13 -0
- package/public/locales/de/settings.json +1 -0
- package/public/locales/en/common.json +26 -0
- package/public/locales/en/errors.json +1 -0
- package/public/locales/en/mail.json +69 -0
- package/public/locales/en/settings.json +1 -0
- package/public/locales/fr/common.json +1 -0
- package/public/locales/fr/errors.json +1 -0
- package/public/locales/fr/mail.json +13 -0
- package/public/locales/fr/settings.json +1 -0
- package/public/locales/nl/common.json +1 -0
- package/public/locales/nl/errors.json +1 -0
- package/public/locales/nl/mail.json +13 -0
- package/public/locales/nl/settings.json +1 -0
- package/src/auth/AccountMenu.tsx +58 -0
- package/src/auth/AccountSession.tsx +19 -0
- package/src/auth/BetterAuthShell.tsx +198 -0
- package/src/auth/account-menu-mode.test.ts +64 -0
- package/src/auth/account-menu-mode.ts +28 -0
- package/src/auth/amplify-config.test.ts +158 -0
- package/src/auth/amplify-config.ts +60 -0
- package/src/auth/auth-error.test.ts +109 -0
- package/src/auth/auth-error.ts +107 -0
- package/src/auth/auth-interceptor.test.ts +86 -0
- package/src/auth/auth-interceptor.ts +23 -0
- package/src/auth/auth.css +334 -0
- package/src/auth/better-auth/BetterAuthAccount.tsx +25 -0
- package/src/auth/better-auth-config.ts +85 -0
- package/src/auth/better-auth-provider.tsx +20 -0
- package/src/auth/cognito/CognitoAccount.tsx +48 -0
- package/src/auth/cognito/CognitoShell.tsx +266 -0
- package/src/auth/cognito/cognito-token.test.ts +110 -0
- package/src/auth/cognito/cognito-token.ts +47 -0
- package/src/auth/cognito/cognito.css +21 -0
- package/src/auth/cognito-provider.tsx +22 -0
- package/src/auth/combined-provider.tsx +42 -0
- package/src/auth/composition.test.ts +136 -0
- package/src/auth/provider.tsx +63 -0
- package/src/auth/sign-out-visibility.test.ts +40 -0
- package/src/auth/sign-out-visibility.ts +10 -0
- package/src/auth/sign-up-disabled.test.ts +23 -0
- package/src/components/compose/AddressField.tsx +237 -0
- package/src/components/compose/ComposeBody.tsx +36 -0
- package/src/components/compose/ComposeForm.tsx +676 -0
- package/src/components/compose/ComposeProvider.tsx +133 -0
- package/src/components/compose/ComposeSmtpMissingBanner.tsx +49 -0
- package/src/components/compose/FromSelector.tsx +67 -0
- package/src/components/compose/FullCompose.tsx +82 -0
- package/src/components/compose/InlineCompose.tsx +29 -0
- package/src/components/compose/MobileComposeSheet.tsx +152 -0
- package/src/components/compose/PlateEditor.tsx +86 -0
- package/src/components/compose/PlateToolbar.tsx +110 -0
- package/src/components/compose/SubjectField.tsx +19 -0
- package/src/components/compose/draft-discard-banner.test.ts +76 -0
- package/src/components/compose/index.ts +6 -0
- package/src/components/compose/sanitize-quote-html.ts +23 -0
- package/src/components/layout/AppShellSkeleton.tsx +53 -0
- package/src/components/layout/ComposeFab.tsx +86 -0
- package/src/components/layout/Drawer.tsx +105 -0
- package/src/components/mail/AutoMovedIndicator.tsx +48 -0
- package/src/components/mail/BlockedImagesNotice.render.test.ts +65 -0
- package/src/components/mail/BlockedImagesNotice.tsx +41 -0
- package/src/components/mail/BriefPane.tsx +289 -0
- package/src/components/mail/ConversationView.tsx +462 -0
- package/src/components/mail/DailyBrief.tsx +451 -0
- package/src/components/mail/DraftsView.tsx +279 -0
- package/src/components/mail/FlaggedList.tsx +170 -0
- package/src/components/mail/FlaggedPane.tsx +217 -0
- package/src/components/mail/IntelligencePane.test.ts +123 -0
- package/src/components/mail/IntelligencePane.tsx +441 -0
- package/src/components/mail/MailListHeader.tsx +187 -0
- package/src/components/mail/MailNav.tsx +70 -0
- package/src/components/mail/MailSidebarAdapter.tsx +351 -0
- package/src/components/mail/MailViewChrome.tsx +95 -0
- package/src/components/mail/MailboxPane.tsx +1219 -0
- package/src/components/mail/MessageActionMenu.tsx +368 -0
- package/src/components/mail/MessageBody.tsx +207 -0
- package/src/components/mail/MessageBodyErrorBanner.render.test.ts +85 -0
- package/src/components/mail/MessageBodyErrorBanner.test.ts +86 -0
- package/src/components/mail/MessageBodyErrorBanner.tsx +90 -0
- package/src/components/mail/MessageCard.tsx +401 -0
- package/src/components/mail/MessageDetail.tsx +153 -0
- package/src/components/mail/MessageList.tsx +804 -0
- package/src/components/mail/MessageListItem.tsx +267 -0
- package/src/components/mail/MessageToolbar.render.test.ts +57 -0
- package/src/components/mail/MessageToolbar.tsx +166 -0
- package/src/components/mail/MobileMessageBar.tsx +142 -0
- package/src/components/mail/MoveToTrigger.tsx +250 -0
- package/src/components/mail/NavMenuButton.tsx +25 -0
- package/src/components/mail/OutboxPane.tsx +531 -0
- package/src/components/mail/PullToRefresh.tsx +24 -0
- package/src/components/mail/RawMessageView.tsx +84 -0
- package/src/components/mail/ReadingPaneEmpty.render.test.ts +42 -0
- package/src/components/mail/SelectionToolbar.tsx +139 -0
- package/src/components/mail/SpamRescue.render.test.ts +35 -0
- package/src/components/mail/SpamRescue.tsx +138 -0
- package/src/components/mail/SwipeableMessageRow.tsx +148 -0
- package/src/components/mail/message-body-error-banner-content.ts +87 -0
- package/src/components/mail/organize/OrganizeDialog.render.test.ts +37 -0
- package/src/components/mail/organize/OrganizeDialog.tsx +83 -0
- package/src/components/mail/organize/OrganizePanel.render.test.ts +102 -0
- package/src/components/mail/organize/OrganizePanel.tsx +344 -0
- package/src/components/onboarding/OnboardingWizard.tsx +1547 -0
- package/src/components/settings/AccountFormPanel.test.ts +72 -0
- package/src/components/settings/AccountFormPanel.tsx +942 -0
- package/src/components/settings/DangerZone.tsx +30 -0
- package/src/components/settings/DeleteAccountDialog.render.test.ts +47 -0
- package/src/components/settings/DeleteAccountDialog.tsx +224 -0
- package/src/components/settings/FiltersList.render.test.ts +71 -0
- package/src/components/settings/FiltersList.tsx +93 -0
- package/src/components/settings/account-form-helpers.test.ts +172 -0
- package/src/components/settings/account-form-helpers.ts +105 -0
- package/src/components/ui/AppVersion.tsx +50 -0
- package/src/components/ui/BugReportButton.tsx +45 -0
- package/src/components/ui/ConfirmDialog.tsx +125 -0
- package/src/components/ui/DropdownMenu.tsx +105 -0
- package/src/components/ui/EmptyState.tsx +11 -0
- package/src/components/ui/ErrorBanner.tsx +88 -0
- package/src/components/ui/ErrorBannerProvider.tsx +77 -0
- package/src/components/ui/ErrorBannerStack.tsx +34 -0
- package/src/components/ui/ErrorState.tsx +80 -0
- package/src/components/ui/FatalErrorOverlay.render.test.ts +66 -0
- package/src/components/ui/FatalErrorOverlay.tsx +151 -0
- package/src/components/ui/KeyboardShortcutsModal.tsx +120 -0
- package/src/components/ui/SlidePanel.tsx +72 -0
- package/src/components/ui/error-banners.test.ts +207 -0
- package/src/components/ui/error-banners.ts +111 -0
- package/src/hooks/queries/keys.ts +38 -0
- package/src/hooks/useArchiveMailbox.ts +104 -0
- package/src/hooks/useAutoMovedBadge.ts +77 -0
- package/src/hooks/useCurrentMailboxName.ts +99 -0
- package/src/hooks/useDebouncedValue.ts +12 -0
- package/src/hooks/useDeleteMessages.test.ts +103 -0
- package/src/hooks/useDeleteMessages.ts +226 -0
- package/src/hooks/useFilters.ts +112 -0
- package/src/hooks/useIntelligenceData.test.ts +269 -0
- package/src/hooks/useIntelligenceData.ts +284 -0
- package/src/hooks/useIsDark.ts +27 -0
- package/src/hooks/useKeyboardNavigation.ts +192 -0
- package/src/hooks/useLayoutTier.test.ts +47 -0
- package/src/hooks/useLayoutTier.ts +44 -0
- package/src/hooks/useLongPress.test.ts +291 -0
- package/src/hooks/useLongPress.ts +87 -0
- package/src/hooks/useMailboxAccount.ts +62 -0
- package/src/hooks/useMailboxNameIndex.ts +32 -0
- package/src/hooks/useMarkAsRead.test.ts +165 -0
- package/src/hooks/useMarkAsRead.ts +319 -0
- package/src/hooks/useMediaQuery.ts +39 -0
- package/src/hooks/useMessageBodyContent.test.ts +370 -0
- package/src/hooks/useMessageBodyContent.ts +380 -0
- package/src/hooks/useMoveMessages.test.ts +63 -0
- package/src/hooks/useMoveMessages.ts +221 -0
- package/src/hooks/useOrganizeJob.ts +90 -0
- package/src/hooks/useOrganizePreview.ts +40 -0
- package/src/hooks/useRescueCandidates.test.ts +217 -0
- package/src/hooks/useRescueCandidates.ts +48 -0
- package/src/hooks/useSaveDraft.ts +118 -0
- package/src/hooks/useSelection.test.ts +75 -0
- package/src/hooks/useSelection.ts +211 -0
- package/src/hooks/useSemanticSearch.ts +94 -0
- package/src/hooks/useSignature.ts +69 -0
- package/src/hooks/useStaleAccountSync.test.ts +211 -0
- package/src/hooks/useStaleAccountSync.ts +246 -0
- package/src/hooks/useSwipeNavigation.test.ts +35 -0
- package/src/hooks/useSwipeNavigation.ts +99 -0
- package/src/hooks/useToggleStar.ts +187 -0
- package/src/hooks/useToggleTrusted.test.ts +102 -0
- package/src/hooks/useToggleTrusted.ts +161 -0
- package/src/hooks/useTriageKeyboard.ts +103 -0
- package/src/hooks/useTriggerSync.test.ts +24 -0
- package/src/hooks/useTriggerSync.ts +58 -0
- package/src/hooks/useUpdateAddressFlags.ts +125 -0
- package/src/hooks/useVisualViewport.test.ts +127 -0
- package/src/hooks/useVisualViewport.ts +51 -0
- package/src/index.css +19 -0
- package/src/lib/account-order.test.ts +84 -0
- package/src/lib/account-order.ts +13 -0
- package/src/lib/adjacent-message.test.ts +43 -0
- package/src/lib/adjacent-message.ts +20 -0
- package/src/lib/api.ts +82 -0
- package/src/lib/app-info.ts +15 -0
- package/src/lib/auto-moved.test.ts +140 -0
- package/src/lib/auto-moved.ts +68 -0
- package/src/lib/autodiscovery.test.ts +94 -0
- package/src/lib/autodiscovery.ts +354 -0
- package/src/lib/brief.test.ts +366 -0
- package/src/lib/brief.ts +180 -0
- package/src/lib/bug-report.test.ts +213 -0
- package/src/lib/bug-report.ts +176 -0
- package/src/lib/client.ts +36 -0
- package/src/lib/console-errors.test.ts +64 -0
- package/src/lib/console-errors.ts +92 -0
- package/src/lib/conversation-target.test.ts +68 -0
- package/src/lib/conversation-target.ts +59 -0
- package/src/lib/display-category.ts +20 -0
- package/src/lib/drafts.test.ts +241 -0
- package/src/lib/drafts.ts +121 -0
- package/src/lib/error-classifier.test.ts +133 -0
- package/src/lib/error-classifier.ts +96 -0
- package/src/lib/fatal-error.test.ts +97 -0
- package/src/lib/fatal-error.ts +165 -0
- package/src/lib/folder-roles.test.ts +113 -0
- package/src/lib/folder-roles.ts +112 -0
- package/src/lib/format.test.ts +115 -0
- package/src/lib/format.ts +198 -0
- package/src/lib/i18n.ts +36 -0
- package/src/lib/intelligence-pref.test.ts +57 -0
- package/src/lib/intelligence-pref.ts +25 -0
- package/src/lib/keymap-dispatch.test.ts +178 -0
- package/src/lib/keymap-dispatch.ts +187 -0
- package/src/lib/keymap.test.ts +49 -0
- package/src/lib/keymap.ts +178 -0
- package/src/lib/mail-context.ts +64 -0
- package/src/lib/mail-route.test.ts +80 -0
- package/src/lib/mail-route.ts +44 -0
- package/src/lib/message-body-source.test.ts +98 -0
- package/src/lib/message-body-source.ts +48 -0
- package/src/lib/move-targets.test.ts +195 -0
- package/src/lib/move-targets.ts +74 -0
- package/src/lib/onboarding-completion.test.ts +64 -0
- package/src/lib/onboarding-completion.ts +23 -0
- package/src/lib/organize/filter-status.test.ts +99 -0
- package/src/lib/organize/filter-status.ts +86 -0
- package/src/lib/organize/organize-copy.test.ts +93 -0
- package/src/lib/organize/organize-copy.ts +61 -0
- package/src/lib/organize/organize-model.test.ts +104 -0
- package/src/lib/organize/organize-model.ts +105 -0
- package/src/lib/organize/organize-poll.test.ts +37 -0
- package/src/lib/organize/organize-poll.ts +28 -0
- package/src/lib/outbox-status.test.ts +64 -0
- package/src/lib/outbox-status.ts +75 -0
- package/src/lib/plate-serializer.ts +164 -0
- package/src/lib/provider-presets.test.ts +96 -0
- package/src/lib/provider-presets.ts +81 -0
- package/src/lib/query-error-handler.test.ts +144 -0
- package/src/lib/query-error-handler.ts +34 -0
- package/src/lib/query-escalation.integration.test.ts +138 -0
- package/src/lib/recent-searches.test.ts +53 -0
- package/src/lib/recent-searches.ts +34 -0
- package/src/lib/rescue-candidates.test.ts +94 -0
- package/src/lib/rescue-candidates.ts +35 -0
- package/src/lib/rescue-telemetry.test.ts +75 -0
- package/src/lib/rescue-telemetry.ts +55 -0
- package/src/lib/rum-adapter.test.ts +299 -0
- package/src/lib/rum-adapter.ts +107 -0
- package/src/lib/saved-searches.test.ts +80 -0
- package/src/lib/saved-searches.ts +52 -0
- package/src/lib/search-pending.test.ts +61 -0
- package/src/lib/search-pending.ts +24 -0
- package/src/lib/search-query.test.ts +35 -0
- package/src/lib/search-query.ts +11 -0
- package/src/lib/search-result.test.ts +160 -0
- package/src/lib/search-result.ts +102 -0
- package/src/lib/search-schemas.ts +34 -0
- package/src/lib/search-token-index.test.ts +112 -0
- package/src/lib/search-token-index.ts +56 -0
- package/src/lib/search-tokens.test.ts +218 -0
- package/src/lib/search-tokens.ts +133 -0
- package/src/lib/telemetry-context.tsx +9 -0
- package/src/lib/telemetry.ts +17 -0
- package/src/lib/theme-preference.ts +46 -0
- package/src/lib/theme.ts +33 -0
- package/src/lib/utils.ts +4 -0
- package/src/main.tsx +10 -0
- package/src/routeTree.gen.ts +392 -0
- package/src/router.test.ts +81 -0
- package/src/router.tsx +31 -0
- package/src/routes/__root.tsx +69 -0
- package/src/routes/index.tsx +7 -0
- package/src/routes/mail/$mailboxId.tsx +27 -0
- package/src/routes/mail/flagged.tsx +37 -0
- package/src/routes/mail/index.tsx +41 -0
- package/src/routes/mail/outbox.tsx +13 -0
- package/src/routes/mail.tsx +413 -0
- package/src/routes/onboarding.tsx +35 -0
- package/src/routes/settings/-accounts.test.ts +65 -0
- package/src/routes/settings/accounts.tsx +542 -0
- package/src/routes/settings/advanced.tsx +55 -0
- package/src/routes/settings/appearance.tsx +140 -0
- package/src/routes/settings/filters.tsx +141 -0
- package/src/routes/settings/folders.tsx +216 -0
- package/src/routes/settings/index.tsx +5 -0
- package/src/routes/settings/senders.tsx +466 -0
- package/src/routes/settings/suggested-vips.tsx +10 -0
- package/src/routes/settings.tsx +66 -0
- package/src/runtime-config.ts +75 -0
- package/src/shell/index.tsx +98 -0
- package/src/types/index.ts +73 -0
- package/src/vite-env.d.ts +4 -0
- package/vite.base.ts +43 -0
|
@@ -0,0 +1,392 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
|
|
3
|
+
// @ts-nocheck
|
|
4
|
+
|
|
5
|
+
// noinspection JSUnusedGlobalSymbols
|
|
6
|
+
|
|
7
|
+
// This file was automatically generated by TanStack Router.
|
|
8
|
+
// You should NOT make any changes in this file as it will be overwritten.
|
|
9
|
+
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
|
10
|
+
|
|
11
|
+
import { Route as rootRouteImport } from './routes/__root'
|
|
12
|
+
import { Route as IndexRouteImport } from './routes/index'
|
|
13
|
+
import { Route as MailRouteImport } from './routes/mail'
|
|
14
|
+
import { Route as OnboardingRouteImport } from './routes/onboarding'
|
|
15
|
+
import { Route as SettingsRouteImport } from './routes/settings'
|
|
16
|
+
import { Route as MailIndexRouteImport } from './routes/mail/index'
|
|
17
|
+
import { Route as MailMailboxIdRouteImport } from './routes/mail/$mailboxId'
|
|
18
|
+
import { Route as MailFlaggedRouteImport } from './routes/mail/flagged'
|
|
19
|
+
import { Route as MailOutboxRouteImport } from './routes/mail/outbox'
|
|
20
|
+
import { Route as SettingsIndexRouteImport } from './routes/settings/index'
|
|
21
|
+
import { Route as SettingsAccountsRouteImport } from './routes/settings/accounts'
|
|
22
|
+
import { Route as SettingsAdvancedRouteImport } from './routes/settings/advanced'
|
|
23
|
+
import { Route as SettingsAppearanceRouteImport } from './routes/settings/appearance'
|
|
24
|
+
import { Route as SettingsFiltersRouteImport } from './routes/settings/filters'
|
|
25
|
+
import { Route as SettingsFoldersRouteImport } from './routes/settings/folders'
|
|
26
|
+
import { Route as SettingsSendersRouteImport } from './routes/settings/senders'
|
|
27
|
+
import { Route as SettingsSuggestedVipsRouteImport } from './routes/settings/suggested-vips'
|
|
28
|
+
|
|
29
|
+
const IndexRoute = IndexRouteImport.update({
|
|
30
|
+
id: '/',
|
|
31
|
+
path: '/',
|
|
32
|
+
getParentRoute: () => rootRouteImport,
|
|
33
|
+
} as any)
|
|
34
|
+
const MailRoute = MailRouteImport.update({
|
|
35
|
+
id: '/mail',
|
|
36
|
+
path: '/mail',
|
|
37
|
+
getParentRoute: () => rootRouteImport,
|
|
38
|
+
} as any)
|
|
39
|
+
const OnboardingRoute = OnboardingRouteImport.update({
|
|
40
|
+
id: '/onboarding',
|
|
41
|
+
path: '/onboarding',
|
|
42
|
+
getParentRoute: () => rootRouteImport,
|
|
43
|
+
} as any)
|
|
44
|
+
const SettingsRoute = SettingsRouteImport.update({
|
|
45
|
+
id: '/settings',
|
|
46
|
+
path: '/settings',
|
|
47
|
+
getParentRoute: () => rootRouteImport,
|
|
48
|
+
} as any)
|
|
49
|
+
const MailIndexRoute = MailIndexRouteImport.update({
|
|
50
|
+
id: '/',
|
|
51
|
+
path: '/',
|
|
52
|
+
getParentRoute: () => MailRoute,
|
|
53
|
+
} as any)
|
|
54
|
+
const MailMailboxIdRoute = MailMailboxIdRouteImport.update({
|
|
55
|
+
id: '/$mailboxId',
|
|
56
|
+
path: '/$mailboxId',
|
|
57
|
+
getParentRoute: () => MailRoute,
|
|
58
|
+
} as any)
|
|
59
|
+
const MailFlaggedRoute = MailFlaggedRouteImport.update({
|
|
60
|
+
id: '/flagged',
|
|
61
|
+
path: '/flagged',
|
|
62
|
+
getParentRoute: () => MailRoute,
|
|
63
|
+
} as any)
|
|
64
|
+
const MailOutboxRoute = MailOutboxRouteImport.update({
|
|
65
|
+
id: '/outbox',
|
|
66
|
+
path: '/outbox',
|
|
67
|
+
getParentRoute: () => MailRoute,
|
|
68
|
+
} as any)
|
|
69
|
+
const SettingsIndexRoute = SettingsIndexRouteImport.update({
|
|
70
|
+
id: '/',
|
|
71
|
+
path: '/',
|
|
72
|
+
getParentRoute: () => SettingsRoute,
|
|
73
|
+
} as any)
|
|
74
|
+
const SettingsAccountsRoute = SettingsAccountsRouteImport.update({
|
|
75
|
+
id: '/accounts',
|
|
76
|
+
path: '/accounts',
|
|
77
|
+
getParentRoute: () => SettingsRoute,
|
|
78
|
+
} as any)
|
|
79
|
+
const SettingsAdvancedRoute = SettingsAdvancedRouteImport.update({
|
|
80
|
+
id: '/advanced',
|
|
81
|
+
path: '/advanced',
|
|
82
|
+
getParentRoute: () => SettingsRoute,
|
|
83
|
+
} as any)
|
|
84
|
+
const SettingsAppearanceRoute = SettingsAppearanceRouteImport.update({
|
|
85
|
+
id: '/appearance',
|
|
86
|
+
path: '/appearance',
|
|
87
|
+
getParentRoute: () => SettingsRoute,
|
|
88
|
+
} as any)
|
|
89
|
+
const SettingsFiltersRoute = SettingsFiltersRouteImport.update({
|
|
90
|
+
id: '/filters',
|
|
91
|
+
path: '/filters',
|
|
92
|
+
getParentRoute: () => SettingsRoute,
|
|
93
|
+
} as any)
|
|
94
|
+
const SettingsFoldersRoute = SettingsFoldersRouteImport.update({
|
|
95
|
+
id: '/folders',
|
|
96
|
+
path: '/folders',
|
|
97
|
+
getParentRoute: () => SettingsRoute,
|
|
98
|
+
} as any)
|
|
99
|
+
const SettingsSendersRoute = SettingsSendersRouteImport.update({
|
|
100
|
+
id: '/senders',
|
|
101
|
+
path: '/senders',
|
|
102
|
+
getParentRoute: () => SettingsRoute,
|
|
103
|
+
} as any)
|
|
104
|
+
const SettingsSuggestedVipsRoute = SettingsSuggestedVipsRouteImport.update({
|
|
105
|
+
id: '/suggested-vips',
|
|
106
|
+
path: '/suggested-vips',
|
|
107
|
+
getParentRoute: () => SettingsRoute,
|
|
108
|
+
} as any)
|
|
109
|
+
|
|
110
|
+
export interface FileRoutesByFullPath {
|
|
111
|
+
'/': typeof IndexRoute
|
|
112
|
+
'/mail': typeof MailRouteWithChildren
|
|
113
|
+
'/onboarding': typeof OnboardingRoute
|
|
114
|
+
'/settings': typeof SettingsRouteWithChildren
|
|
115
|
+
'/mail/$mailboxId': typeof MailMailboxIdRoute
|
|
116
|
+
'/mail/flagged': typeof MailFlaggedRoute
|
|
117
|
+
'/mail/outbox': typeof MailOutboxRoute
|
|
118
|
+
'/settings/accounts': typeof SettingsAccountsRoute
|
|
119
|
+
'/settings/advanced': typeof SettingsAdvancedRoute
|
|
120
|
+
'/settings/appearance': typeof SettingsAppearanceRoute
|
|
121
|
+
'/settings/filters': typeof SettingsFiltersRoute
|
|
122
|
+
'/settings/folders': typeof SettingsFoldersRoute
|
|
123
|
+
'/settings/senders': typeof SettingsSendersRoute
|
|
124
|
+
'/settings/suggested-vips': typeof SettingsSuggestedVipsRoute
|
|
125
|
+
'/mail/': typeof MailIndexRoute
|
|
126
|
+
'/settings/': typeof SettingsIndexRoute
|
|
127
|
+
}
|
|
128
|
+
export interface FileRoutesByTo {
|
|
129
|
+
'/': typeof IndexRoute
|
|
130
|
+
'/onboarding': typeof OnboardingRoute
|
|
131
|
+
'/mail/$mailboxId': typeof MailMailboxIdRoute
|
|
132
|
+
'/mail/flagged': typeof MailFlaggedRoute
|
|
133
|
+
'/mail/outbox': typeof MailOutboxRoute
|
|
134
|
+
'/settings/accounts': typeof SettingsAccountsRoute
|
|
135
|
+
'/settings/advanced': typeof SettingsAdvancedRoute
|
|
136
|
+
'/settings/appearance': typeof SettingsAppearanceRoute
|
|
137
|
+
'/settings/filters': typeof SettingsFiltersRoute
|
|
138
|
+
'/settings/folders': typeof SettingsFoldersRoute
|
|
139
|
+
'/settings/senders': typeof SettingsSendersRoute
|
|
140
|
+
'/settings/suggested-vips': typeof SettingsSuggestedVipsRoute
|
|
141
|
+
'/mail': typeof MailIndexRoute
|
|
142
|
+
'/settings': typeof SettingsIndexRoute
|
|
143
|
+
}
|
|
144
|
+
export interface FileRoutesById {
|
|
145
|
+
__root__: typeof rootRouteImport
|
|
146
|
+
'/': typeof IndexRoute
|
|
147
|
+
'/mail': typeof MailRouteWithChildren
|
|
148
|
+
'/onboarding': typeof OnboardingRoute
|
|
149
|
+
'/settings': typeof SettingsRouteWithChildren
|
|
150
|
+
'/mail/$mailboxId': typeof MailMailboxIdRoute
|
|
151
|
+
'/mail/flagged': typeof MailFlaggedRoute
|
|
152
|
+
'/mail/outbox': typeof MailOutboxRoute
|
|
153
|
+
'/settings/accounts': typeof SettingsAccountsRoute
|
|
154
|
+
'/settings/advanced': typeof SettingsAdvancedRoute
|
|
155
|
+
'/settings/appearance': typeof SettingsAppearanceRoute
|
|
156
|
+
'/settings/filters': typeof SettingsFiltersRoute
|
|
157
|
+
'/settings/folders': typeof SettingsFoldersRoute
|
|
158
|
+
'/settings/senders': typeof SettingsSendersRoute
|
|
159
|
+
'/settings/suggested-vips': typeof SettingsSuggestedVipsRoute
|
|
160
|
+
'/mail/': typeof MailIndexRoute
|
|
161
|
+
'/settings/': typeof SettingsIndexRoute
|
|
162
|
+
}
|
|
163
|
+
export interface FileRouteTypes {
|
|
164
|
+
fileRoutesByFullPath: FileRoutesByFullPath
|
|
165
|
+
fullPaths:
|
|
166
|
+
| '/'
|
|
167
|
+
| '/mail'
|
|
168
|
+
| '/onboarding'
|
|
169
|
+
| '/settings'
|
|
170
|
+
| '/mail/$mailboxId'
|
|
171
|
+
| '/mail/flagged'
|
|
172
|
+
| '/mail/outbox'
|
|
173
|
+
| '/settings/accounts'
|
|
174
|
+
| '/settings/advanced'
|
|
175
|
+
| '/settings/appearance'
|
|
176
|
+
| '/settings/filters'
|
|
177
|
+
| '/settings/folders'
|
|
178
|
+
| '/settings/senders'
|
|
179
|
+
| '/settings/suggested-vips'
|
|
180
|
+
| '/mail/'
|
|
181
|
+
| '/settings/'
|
|
182
|
+
fileRoutesByTo: FileRoutesByTo
|
|
183
|
+
to:
|
|
184
|
+
| '/'
|
|
185
|
+
| '/onboarding'
|
|
186
|
+
| '/mail/$mailboxId'
|
|
187
|
+
| '/mail/flagged'
|
|
188
|
+
| '/mail/outbox'
|
|
189
|
+
| '/settings/accounts'
|
|
190
|
+
| '/settings/advanced'
|
|
191
|
+
| '/settings/appearance'
|
|
192
|
+
| '/settings/filters'
|
|
193
|
+
| '/settings/folders'
|
|
194
|
+
| '/settings/senders'
|
|
195
|
+
| '/settings/suggested-vips'
|
|
196
|
+
| '/mail'
|
|
197
|
+
| '/settings'
|
|
198
|
+
id:
|
|
199
|
+
| '__root__'
|
|
200
|
+
| '/'
|
|
201
|
+
| '/mail'
|
|
202
|
+
| '/onboarding'
|
|
203
|
+
| '/settings'
|
|
204
|
+
| '/mail/$mailboxId'
|
|
205
|
+
| '/mail/flagged'
|
|
206
|
+
| '/mail/outbox'
|
|
207
|
+
| '/settings/accounts'
|
|
208
|
+
| '/settings/advanced'
|
|
209
|
+
| '/settings/appearance'
|
|
210
|
+
| '/settings/filters'
|
|
211
|
+
| '/settings/folders'
|
|
212
|
+
| '/settings/senders'
|
|
213
|
+
| '/settings/suggested-vips'
|
|
214
|
+
| '/mail/'
|
|
215
|
+
| '/settings/'
|
|
216
|
+
fileRoutesById: FileRoutesById
|
|
217
|
+
}
|
|
218
|
+
export interface RootRouteChildren {
|
|
219
|
+
IndexRoute: typeof IndexRoute
|
|
220
|
+
MailRoute: typeof MailRouteWithChildren
|
|
221
|
+
OnboardingRoute: typeof OnboardingRoute
|
|
222
|
+
SettingsRoute: typeof SettingsRouteWithChildren
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
declare module '@tanstack/react-router' {
|
|
226
|
+
interface FileRoutesByPath {
|
|
227
|
+
'/': {
|
|
228
|
+
id: '/'
|
|
229
|
+
path: '/'
|
|
230
|
+
fullPath: '/'
|
|
231
|
+
preLoaderRoute: typeof IndexRouteImport
|
|
232
|
+
parentRoute: typeof rootRouteImport
|
|
233
|
+
}
|
|
234
|
+
'/mail': {
|
|
235
|
+
id: '/mail'
|
|
236
|
+
path: '/mail'
|
|
237
|
+
fullPath: '/mail'
|
|
238
|
+
preLoaderRoute: typeof MailRouteImport
|
|
239
|
+
parentRoute: typeof rootRouteImport
|
|
240
|
+
}
|
|
241
|
+
'/onboarding': {
|
|
242
|
+
id: '/onboarding'
|
|
243
|
+
path: '/onboarding'
|
|
244
|
+
fullPath: '/onboarding'
|
|
245
|
+
preLoaderRoute: typeof OnboardingRouteImport
|
|
246
|
+
parentRoute: typeof rootRouteImport
|
|
247
|
+
}
|
|
248
|
+
'/settings': {
|
|
249
|
+
id: '/settings'
|
|
250
|
+
path: '/settings'
|
|
251
|
+
fullPath: '/settings'
|
|
252
|
+
preLoaderRoute: typeof SettingsRouteImport
|
|
253
|
+
parentRoute: typeof rootRouteImport
|
|
254
|
+
}
|
|
255
|
+
'/mail/': {
|
|
256
|
+
id: '/mail/'
|
|
257
|
+
path: '/'
|
|
258
|
+
fullPath: '/mail/'
|
|
259
|
+
preLoaderRoute: typeof MailIndexRouteImport
|
|
260
|
+
parentRoute: typeof MailRoute
|
|
261
|
+
}
|
|
262
|
+
'/mail/$mailboxId': {
|
|
263
|
+
id: '/mail/$mailboxId'
|
|
264
|
+
path: '/$mailboxId'
|
|
265
|
+
fullPath: '/mail/$mailboxId'
|
|
266
|
+
preLoaderRoute: typeof MailMailboxIdRouteImport
|
|
267
|
+
parentRoute: typeof MailRoute
|
|
268
|
+
}
|
|
269
|
+
'/mail/flagged': {
|
|
270
|
+
id: '/mail/flagged'
|
|
271
|
+
path: '/flagged'
|
|
272
|
+
fullPath: '/mail/flagged'
|
|
273
|
+
preLoaderRoute: typeof MailFlaggedRouteImport
|
|
274
|
+
parentRoute: typeof MailRoute
|
|
275
|
+
}
|
|
276
|
+
'/mail/outbox': {
|
|
277
|
+
id: '/mail/outbox'
|
|
278
|
+
path: '/outbox'
|
|
279
|
+
fullPath: '/mail/outbox'
|
|
280
|
+
preLoaderRoute: typeof MailOutboxRouteImport
|
|
281
|
+
parentRoute: typeof MailRoute
|
|
282
|
+
}
|
|
283
|
+
'/settings/': {
|
|
284
|
+
id: '/settings/'
|
|
285
|
+
path: '/'
|
|
286
|
+
fullPath: '/settings/'
|
|
287
|
+
preLoaderRoute: typeof SettingsIndexRouteImport
|
|
288
|
+
parentRoute: typeof SettingsRoute
|
|
289
|
+
}
|
|
290
|
+
'/settings/accounts': {
|
|
291
|
+
id: '/settings/accounts'
|
|
292
|
+
path: '/accounts'
|
|
293
|
+
fullPath: '/settings/accounts'
|
|
294
|
+
preLoaderRoute: typeof SettingsAccountsRouteImport
|
|
295
|
+
parentRoute: typeof SettingsRoute
|
|
296
|
+
}
|
|
297
|
+
'/settings/advanced': {
|
|
298
|
+
id: '/settings/advanced'
|
|
299
|
+
path: '/advanced'
|
|
300
|
+
fullPath: '/settings/advanced'
|
|
301
|
+
preLoaderRoute: typeof SettingsAdvancedRouteImport
|
|
302
|
+
parentRoute: typeof SettingsRoute
|
|
303
|
+
}
|
|
304
|
+
'/settings/appearance': {
|
|
305
|
+
id: '/settings/appearance'
|
|
306
|
+
path: '/appearance'
|
|
307
|
+
fullPath: '/settings/appearance'
|
|
308
|
+
preLoaderRoute: typeof SettingsAppearanceRouteImport
|
|
309
|
+
parentRoute: typeof SettingsRoute
|
|
310
|
+
}
|
|
311
|
+
'/settings/filters': {
|
|
312
|
+
id: '/settings/filters'
|
|
313
|
+
path: '/filters'
|
|
314
|
+
fullPath: '/settings/filters'
|
|
315
|
+
preLoaderRoute: typeof SettingsFiltersRouteImport
|
|
316
|
+
parentRoute: typeof SettingsRoute
|
|
317
|
+
}
|
|
318
|
+
'/settings/folders': {
|
|
319
|
+
id: '/settings/folders'
|
|
320
|
+
path: '/folders'
|
|
321
|
+
fullPath: '/settings/folders'
|
|
322
|
+
preLoaderRoute: typeof SettingsFoldersRouteImport
|
|
323
|
+
parentRoute: typeof SettingsRoute
|
|
324
|
+
}
|
|
325
|
+
'/settings/senders': {
|
|
326
|
+
id: '/settings/senders'
|
|
327
|
+
path: '/senders'
|
|
328
|
+
fullPath: '/settings/senders'
|
|
329
|
+
preLoaderRoute: typeof SettingsSendersRouteImport
|
|
330
|
+
parentRoute: typeof SettingsRoute
|
|
331
|
+
}
|
|
332
|
+
'/settings/suggested-vips': {
|
|
333
|
+
id: '/settings/suggested-vips'
|
|
334
|
+
path: '/suggested-vips'
|
|
335
|
+
fullPath: '/settings/suggested-vips'
|
|
336
|
+
preLoaderRoute: typeof SettingsSuggestedVipsRouteImport
|
|
337
|
+
parentRoute: typeof SettingsRoute
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
interface MailRouteChildren {
|
|
343
|
+
MailMailboxIdRoute: typeof MailMailboxIdRoute
|
|
344
|
+
MailFlaggedRoute: typeof MailFlaggedRoute
|
|
345
|
+
MailOutboxRoute: typeof MailOutboxRoute
|
|
346
|
+
MailIndexRoute: typeof MailIndexRoute
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const MailRouteChildren: MailRouteChildren = {
|
|
350
|
+
MailMailboxIdRoute: MailMailboxIdRoute,
|
|
351
|
+
MailFlaggedRoute: MailFlaggedRoute,
|
|
352
|
+
MailOutboxRoute: MailOutboxRoute,
|
|
353
|
+
MailIndexRoute: MailIndexRoute,
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const MailRouteWithChildren = MailRoute._addFileChildren(MailRouteChildren)
|
|
357
|
+
|
|
358
|
+
interface SettingsRouteChildren {
|
|
359
|
+
SettingsAccountsRoute: typeof SettingsAccountsRoute
|
|
360
|
+
SettingsAdvancedRoute: typeof SettingsAdvancedRoute
|
|
361
|
+
SettingsAppearanceRoute: typeof SettingsAppearanceRoute
|
|
362
|
+
SettingsFiltersRoute: typeof SettingsFiltersRoute
|
|
363
|
+
SettingsFoldersRoute: typeof SettingsFoldersRoute
|
|
364
|
+
SettingsSendersRoute: typeof SettingsSendersRoute
|
|
365
|
+
SettingsSuggestedVipsRoute: typeof SettingsSuggestedVipsRoute
|
|
366
|
+
SettingsIndexRoute: typeof SettingsIndexRoute
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const SettingsRouteChildren: SettingsRouteChildren = {
|
|
370
|
+
SettingsAccountsRoute: SettingsAccountsRoute,
|
|
371
|
+
SettingsAdvancedRoute: SettingsAdvancedRoute,
|
|
372
|
+
SettingsAppearanceRoute: SettingsAppearanceRoute,
|
|
373
|
+
SettingsFiltersRoute: SettingsFiltersRoute,
|
|
374
|
+
SettingsFoldersRoute: SettingsFoldersRoute,
|
|
375
|
+
SettingsSendersRoute: SettingsSendersRoute,
|
|
376
|
+
SettingsSuggestedVipsRoute: SettingsSuggestedVipsRoute,
|
|
377
|
+
SettingsIndexRoute: SettingsIndexRoute,
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
const SettingsRouteWithChildren = SettingsRoute._addFileChildren(
|
|
381
|
+
SettingsRouteChildren,
|
|
382
|
+
)
|
|
383
|
+
|
|
384
|
+
const rootRouteChildren: RootRouteChildren = {
|
|
385
|
+
IndexRoute: IndexRoute,
|
|
386
|
+
MailRoute: MailRouteWithChildren,
|
|
387
|
+
OnboardingRoute: OnboardingRoute,
|
|
388
|
+
SettingsRoute: SettingsRouteWithChildren,
|
|
389
|
+
}
|
|
390
|
+
export const routeTree = rootRouteImport
|
|
391
|
+
._addFileChildren(rootRouteChildren)
|
|
392
|
+
._addFileTypes<FileRouteTypes>()
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { describe, it } from "node:test";
|
|
3
|
+
import type { Telemetry } from "./lib/telemetry";
|
|
4
|
+
|
|
5
|
+
interface SubscribeCall {
|
|
6
|
+
eventType: string;
|
|
7
|
+
fn: (event: { toLocation: { pathname: string } }) => void;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface RouterStub {
|
|
11
|
+
subscribeCalls: SubscribeCall[];
|
|
12
|
+
subscribe(
|
|
13
|
+
eventType: string,
|
|
14
|
+
fn: (event: { toLocation: { pathname: string } }) => void,
|
|
15
|
+
): () => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function makeRouterStub(): RouterStub {
|
|
19
|
+
const stub: RouterStub = {
|
|
20
|
+
subscribeCalls: [],
|
|
21
|
+
subscribe(eventType, fn) {
|
|
22
|
+
stub.subscribeCalls.push({ eventType, fn });
|
|
23
|
+
return () => undefined;
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
return stub;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function makeTelemetryStub(): Telemetry & { pageViews: string[] } {
|
|
30
|
+
const stub = {
|
|
31
|
+
pageViews: [] as string[],
|
|
32
|
+
recordPageView(path: string) {
|
|
33
|
+
stub.pageViews.push(path);
|
|
34
|
+
},
|
|
35
|
+
recordError: () => undefined,
|
|
36
|
+
recordEvent: () => undefined,
|
|
37
|
+
recordTiming: () => undefined,
|
|
38
|
+
};
|
|
39
|
+
return stub;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function wirePageViews(router: RouterStub, telemetry: Telemetry): void {
|
|
43
|
+
router.subscribe("onResolved", (event) => {
|
|
44
|
+
telemetry.recordPageView(event.toLocation.pathname);
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
describe("router page-view telemetry wiring", () => {
|
|
49
|
+
it("subscribes to onResolved", () => {
|
|
50
|
+
const router = makeRouterStub();
|
|
51
|
+
const telemetry = makeTelemetryStub();
|
|
52
|
+
wirePageViews(router, telemetry);
|
|
53
|
+
assert.equal(router.subscribeCalls.length, 1);
|
|
54
|
+
assert.equal(router.subscribeCalls[0]?.eventType, "onResolved");
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it("calls recordPageView with pathname on each navigation", () => {
|
|
58
|
+
const router = makeRouterStub();
|
|
59
|
+
const telemetry = makeTelemetryStub();
|
|
60
|
+
wirePageViews(router, telemetry);
|
|
61
|
+
|
|
62
|
+
const handler = router.subscribeCalls[0]?.fn;
|
|
63
|
+
assert.ok(handler, "Expected a subscribe handler");
|
|
64
|
+
|
|
65
|
+
handler({ toLocation: { pathname: "/mail" } });
|
|
66
|
+
handler({ toLocation: { pathname: "/settings" } });
|
|
67
|
+
|
|
68
|
+
assert.deepEqual(telemetry.pageViews, ["/mail", "/settings"]);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("is a no-op when telemetry is noopTelemetry (does not throw)", () => {
|
|
72
|
+
const router = makeRouterStub();
|
|
73
|
+
const telemetry = makeTelemetryStub();
|
|
74
|
+
telemetry.recordPageView = () => undefined;
|
|
75
|
+
wirePageViews(router, telemetry);
|
|
76
|
+
|
|
77
|
+
const handler = router.subscribeCalls[0]?.fn;
|
|
78
|
+
assert.ok(handler);
|
|
79
|
+
assert.doesNotThrow(() => handler({ toLocation: { pathname: "/mail" } }));
|
|
80
|
+
});
|
|
81
|
+
});
|
package/src/router.tsx
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { QueryClient } from "@tanstack/react-query";
|
|
2
|
+
import { createRouter } from "@tanstack/react-router";
|
|
3
|
+
import type { Telemetry } from "./lib/telemetry";
|
|
4
|
+
import { routeTree } from "./routeTree.gen";
|
|
5
|
+
|
|
6
|
+
export interface RouterContext {
|
|
7
|
+
queryClient: QueryClient;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export const createAppRouter = (
|
|
11
|
+
queryClient: QueryClient,
|
|
12
|
+
telemetry: Telemetry,
|
|
13
|
+
) => {
|
|
14
|
+
const router = createRouter({
|
|
15
|
+
routeTree,
|
|
16
|
+
context: { queryClient },
|
|
17
|
+
defaultPreload: "intent",
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
router.subscribe("onResolved", (event) => {
|
|
21
|
+
telemetry.recordPageView(event.toLocation.pathname);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
return router;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
declare module "@tanstack/react-router" {
|
|
28
|
+
interface Register {
|
|
29
|
+
router: ReturnType<typeof createAppRouter>;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import {
|
|
2
|
+
createRootRouteWithContext,
|
|
3
|
+
type ErrorComponentProps,
|
|
4
|
+
Outlet,
|
|
5
|
+
} from "@tanstack/react-router";
|
|
6
|
+
import { Suspense } from "react";
|
|
7
|
+
import { useTranslation } from "react-i18next";
|
|
8
|
+
import { ComposeProvider } from "@/components/compose/ComposeProvider";
|
|
9
|
+
import { AppShellSkeleton } from "@/components/layout/AppShellSkeleton";
|
|
10
|
+
import { ErrorBannerProvider } from "@/components/ui/ErrorBannerProvider";
|
|
11
|
+
import {
|
|
12
|
+
FatalErrorOverlay,
|
|
13
|
+
FatalErrorScreen,
|
|
14
|
+
} from "@/components/ui/FatalErrorOverlay";
|
|
15
|
+
import { isServerError } from "@/lib/error-classifier";
|
|
16
|
+
import { reportFatalError } from "@/lib/fatal-error";
|
|
17
|
+
import type { RouterContext } from "@/router";
|
|
18
|
+
|
|
19
|
+
const RootErrorComponent = ({ error, reset, info }: ErrorComponentProps) => {
|
|
20
|
+
// Everything that bubbles to the route boundary escalates to the loud
|
|
21
|
+
// full-screen fatal page — never the soft grey "Something went wrong" that
|
|
22
|
+
// implied recovery. A bubbled 5xx is transient (Retry re-runs the loader via
|
|
23
|
+
// reset); a caught render exception is deterministic — retry re-crashes, so
|
|
24
|
+
// it is fatal with no Retry, offering a safe route out instead (issue #1231).
|
|
25
|
+
const recoverable = isServerError(error);
|
|
26
|
+
const fatal = reportFatalError(error, {
|
|
27
|
+
recoverable,
|
|
28
|
+
componentStack: info?.componentStack,
|
|
29
|
+
});
|
|
30
|
+
return (
|
|
31
|
+
<FatalErrorScreen fatal={fatal} onRetry={recoverable ? reset : undefined} />
|
|
32
|
+
);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export const Route = createRootRouteWithContext<RouterContext>()({
|
|
36
|
+
component: RootLayout,
|
|
37
|
+
errorComponent: RootErrorComponent,
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const SkipLink = () => {
|
|
41
|
+
// Provide a string fallback so Suspense doesn't render the raw i18n key
|
|
42
|
+
// while the common namespace is still loading over HTTP.
|
|
43
|
+
const { t } = useTranslation("common", { useSuspense: false });
|
|
44
|
+
|
|
45
|
+
return (
|
|
46
|
+
<a
|
|
47
|
+
href="#main-content"
|
|
48
|
+
className="sr-only focus:not-sr-only focus:absolute focus:z-50 focus:p-4 focus:bg-canvas focus:text-fg focus:rounded-md"
|
|
49
|
+
>
|
|
50
|
+
{t("accessibility.skipToContent", "Skip to main content")}
|
|
51
|
+
</a>
|
|
52
|
+
);
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
function RootLayout() {
|
|
56
|
+
return (
|
|
57
|
+
<ErrorBannerProvider>
|
|
58
|
+
<ComposeProvider>
|
|
59
|
+
<SkipLink />
|
|
60
|
+
<main id="main-content" className="h-dvh overflow-hidden">
|
|
61
|
+
<Suspense fallback={<AppShellSkeleton />}>
|
|
62
|
+
<Outlet />
|
|
63
|
+
</Suspense>
|
|
64
|
+
</main>
|
|
65
|
+
</ComposeProvider>
|
|
66
|
+
<FatalErrorOverlay />
|
|
67
|
+
</ErrorBannerProvider>
|
|
68
|
+
);
|
|
69
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /mail/$mailboxId route — shell activation only.
|
|
3
|
+
*
|
|
4
|
+
* The component returns null because rendering is lifted to `mail.tsx`
|
|
5
|
+
* which reads the active mailboxId via `useRouterState` and mounts
|
|
6
|
+
* `<MailboxPane>` directly into `<AppShellSlotted>` slots. TanStack Router
|
|
7
|
+
* still needs this file to:
|
|
8
|
+
* - Register the route so URL matching works
|
|
9
|
+
* - Validate the search params so selectedMessageId is properly typed
|
|
10
|
+
*/
|
|
11
|
+
import { createFileRoute } from "@tanstack/react-router";
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
|
|
14
|
+
// Search schema includes q from parent route for proper inheritance
|
|
15
|
+
const mailboxSearchSchema = z.object({
|
|
16
|
+
selectedMessageId: z.string().optional(),
|
|
17
|
+
// A tapped semantic "Related" hit can point at a message outside the loaded
|
|
18
|
+
// list; carrying its thread lets the mailbox open it directly (the mailbox is
|
|
19
|
+
// the route param). See `buildConversationTarget`.
|
|
20
|
+
selectedThreadId: z.string().optional(),
|
|
21
|
+
q: z.string().optional(),
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
export const Route = createFileRoute("/mail/$mailboxId")({
|
|
25
|
+
component: () => null,
|
|
26
|
+
validateSearch: mailboxSearchSchema,
|
|
27
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /mail/flagged route (Flagged virtual mailbox) — shell activation only.
|
|
3
|
+
*
|
|
4
|
+
* The component returns null because rendering is lifted to `mail.tsx`, which
|
|
5
|
+
* detects the flagged route via `useRouterState` and mounts `<FlaggedPane>`
|
|
6
|
+
* directly into `<AppShellSlotted>` slots — the same pattern the brief uses.
|
|
7
|
+
*/
|
|
8
|
+
import {
|
|
9
|
+
createFileRoute,
|
|
10
|
+
type ErrorComponentProps,
|
|
11
|
+
} from "@tanstack/react-router";
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
import { ErrorState } from "@/components/ui/ErrorState";
|
|
14
|
+
|
|
15
|
+
const FlaggedError = ({ error, reset }: ErrorComponentProps) => (
|
|
16
|
+
<div className="flex h-full items-center justify-center bg-canvas p-4">
|
|
17
|
+
<ErrorState
|
|
18
|
+
title="Couldn't load your flagged mail"
|
|
19
|
+
error={error}
|
|
20
|
+
onRetry={reset}
|
|
21
|
+
/>
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
// `q` is inherited from the parent /mail route; re-declared here so it survives
|
|
26
|
+
// this route's own search validation and isn't dropped when navigating with a
|
|
27
|
+
// functional search updater.
|
|
28
|
+
const flaggedSearchSchema = z.object({
|
|
29
|
+
selectedMessageId: z.string().optional(),
|
|
30
|
+
q: z.string().optional(),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
export const Route = createFileRoute("/mail/flagged")({
|
|
34
|
+
component: () => null,
|
|
35
|
+
validateSearch: flaggedSearchSchema,
|
|
36
|
+
errorComponent: FlaggedError,
|
|
37
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /mail/ route (Daily Brief) — shell activation only.
|
|
3
|
+
*
|
|
4
|
+
* The component returns null because rendering is lifted to `mail.tsx`
|
|
5
|
+
* which detects the brief route via `useRouterState` and mounts
|
|
6
|
+
* `<BriefPane>` directly into `<AppShellSlotted>` slots.
|
|
7
|
+
*/
|
|
8
|
+
import {
|
|
9
|
+
createFileRoute,
|
|
10
|
+
type ErrorComponentProps,
|
|
11
|
+
} from "@tanstack/react-router";
|
|
12
|
+
import { z } from "zod";
|
|
13
|
+
import { ErrorState } from "@/components/ui/ErrorState";
|
|
14
|
+
|
|
15
|
+
const MailIndexError = ({ error, reset }: ErrorComponentProps) => (
|
|
16
|
+
<div className="flex h-full items-center justify-center bg-canvas p-4">
|
|
17
|
+
<ErrorState
|
|
18
|
+
title="Couldn't load your mailboxes"
|
|
19
|
+
error={error}
|
|
20
|
+
onRetry={reset}
|
|
21
|
+
/>
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
|
|
25
|
+
// `q` is inherited from the parent /mail route; re-declared here so it
|
|
26
|
+
// survives this route's own search validation and isn't dropped when
|
|
27
|
+
// navigating with a functional search updater.
|
|
28
|
+
const briefSearchSchema = z.object({
|
|
29
|
+
selectedMessageId: z.string().optional(),
|
|
30
|
+
// A tapped semantic "Related" hit can point at a message outside the loaded
|
|
31
|
+
// brief list; carrying its thread + mailbox lets the brief open it directly.
|
|
32
|
+
selectedThreadId: z.string().optional(),
|
|
33
|
+
selectedMailboxId: z.string().optional(),
|
|
34
|
+
q: z.string().optional(),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export const Route = createFileRoute("/mail/")({
|
|
38
|
+
component: () => null,
|
|
39
|
+
validateSearch: briefSearchSchema,
|
|
40
|
+
errorComponent: MailIndexError,
|
|
41
|
+
});
|