@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,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DraftsView — segmented Drafts folder view.
|
|
3
|
+
*
|
|
4
|
+
* Renders two labeled sections when the open mailbox is an account's IMAP
|
|
5
|
+
* \Drafts special-use folder (issue #505):
|
|
6
|
+
*
|
|
7
|
+
* 1. "Not yet sent (Remit)" — outbox rows with status === "draft" belonging
|
|
8
|
+
* to the account that owns the open \Drafts mailbox. Clicking a row opens
|
|
9
|
+
* compose pre-filled via openCompose({ mode: "new", outboxMessageId }).
|
|
10
|
+
*
|
|
11
|
+
* 2. "On the server" — IMAP \Drafts thread rows already loaded for the
|
|
12
|
+
* mailbox. Clicking a row opens the normal reading pane (read-only;
|
|
13
|
+
* editing IMAP drafts in compose is a known follow-up gap).
|
|
14
|
+
*
|
|
15
|
+
* Reuses the remit-ui ThreadSection / sectioned-list machinery that the Daily
|
|
16
|
+
* Brief also uses — sticky section headers, ComfortableRow / CompactRow with
|
|
17
|
+
* density. No new sectioned-list code is introduced here.
|
|
18
|
+
*
|
|
19
|
+
* Dedup note: Remit draft rows and IMAP \Drafts messages have no shared key
|
|
20
|
+
* today (no IMAP APPEND-as-draft path exists). Both sections render without
|
|
21
|
+
* client-side dedup; if appending Remit drafts into IMAP \Drafts ships in the
|
|
22
|
+
* future the dedup key would be the RFC822 Message-ID header (issue #505).
|
|
23
|
+
*/
|
|
24
|
+
import {
|
|
25
|
+
outboxDetailOperationsDeleteOutboxMessageMutation,
|
|
26
|
+
outboxOperationsListOutboxMessagesOptions,
|
|
27
|
+
outboxOperationsListOutboxMessagesQueryKey,
|
|
28
|
+
} from "@remit/api-http-client/@tanstack/react-query.gen.ts";
|
|
29
|
+
import type { RemitImapThreadMessageResponse } from "@remit/api-http-client/types.gen.ts";
|
|
30
|
+
import {
|
|
31
|
+
Avatar,
|
|
32
|
+
ComfortableRowTextContent,
|
|
33
|
+
cn,
|
|
34
|
+
comfortableRowClass,
|
|
35
|
+
type ThreadRowData,
|
|
36
|
+
type ThreadSection,
|
|
37
|
+
} from "@remit/ui";
|
|
38
|
+
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
39
|
+
import { useNavigate } from "@tanstack/react-router";
|
|
40
|
+
import { FileText, Inbox, Trash2 } from "lucide-react";
|
|
41
|
+
import { useMemo } from "react";
|
|
42
|
+
import { useCompose } from "@/components/compose/ComposeProvider";
|
|
43
|
+
import { groupDraftSections } from "@/lib/drafts";
|
|
44
|
+
import { NavMenuButton } from "./NavMenuButton";
|
|
45
|
+
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// Section header (mirrors DailyBrief SectionHeader)
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
const SectionHeader = ({ label, count }: { label: string; count: number }) => (
|
|
51
|
+
<div className="sticky top-0 z-10 flex items-baseline justify-between border-b border-line bg-surface-sunken px-row-inset py-1">
|
|
52
|
+
<span className="text-2xs font-semibold uppercase tracking-wider text-fg-subtle">
|
|
53
|
+
{label}
|
|
54
|
+
</span>
|
|
55
|
+
<span className="text-2xs text-fg-subtle tabular-nums">{count}</span>
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
// Remit-draft row — opens compose for editing
|
|
61
|
+
// ---------------------------------------------------------------------------
|
|
62
|
+
|
|
63
|
+
interface RemitDraftRowProps {
|
|
64
|
+
row: ThreadRowData;
|
|
65
|
+
isSelected: boolean;
|
|
66
|
+
onOpen: (outboxMessageId: string) => void;
|
|
67
|
+
onDelete: (outboxMessageId: string) => void;
|
|
68
|
+
isDeleting: boolean;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const RemitDraftRow = ({
|
|
72
|
+
row,
|
|
73
|
+
isSelected,
|
|
74
|
+
onOpen,
|
|
75
|
+
onDelete,
|
|
76
|
+
isDeleting,
|
|
77
|
+
}: RemitDraftRowProps) => (
|
|
78
|
+
<button
|
|
79
|
+
type="button"
|
|
80
|
+
onClick={() => onOpen(row.id)}
|
|
81
|
+
className={cn("group", comfortableRowClass({ active: isSelected }))}
|
|
82
|
+
>
|
|
83
|
+
<FileText className="size-7 shrink-0 text-fg-muted mt-0.5" />
|
|
84
|
+
<ComfortableRowTextContent thread={row} />
|
|
85
|
+
<div className="flex items-center gap-1 shrink-0">
|
|
86
|
+
<button
|
|
87
|
+
type="button"
|
|
88
|
+
onClick={(e) => {
|
|
89
|
+
e.stopPropagation();
|
|
90
|
+
onDelete(row.id);
|
|
91
|
+
}}
|
|
92
|
+
disabled={isDeleting}
|
|
93
|
+
className="p-1.5 rounded-md text-fg-muted hover:text-danger hover:bg-surface-raised transition-colors opacity-0 group-hover:opacity-100 focus:opacity-100"
|
|
94
|
+
title="Delete draft"
|
|
95
|
+
>
|
|
96
|
+
<Trash2 className="size-3.5" />
|
|
97
|
+
</button>
|
|
98
|
+
</div>
|
|
99
|
+
</button>
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
// IMAP-draft row — opens reading pane (read-only)
|
|
104
|
+
// ---------------------------------------------------------------------------
|
|
105
|
+
|
|
106
|
+
interface ImapDraftRowProps {
|
|
107
|
+
row: ThreadRowData;
|
|
108
|
+
isSelected: boolean;
|
|
109
|
+
onOpen: (messageId: string) => void;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const ImapDraftRow = ({ row, isSelected, onOpen }: ImapDraftRowProps) => {
|
|
113
|
+
const unread = !row.isRead;
|
|
114
|
+
return (
|
|
115
|
+
<button
|
|
116
|
+
type="button"
|
|
117
|
+
onClick={() => onOpen(row.id)}
|
|
118
|
+
className={cn("group", comfortableRowClass({ active: isSelected }))}
|
|
119
|
+
>
|
|
120
|
+
{unread && (
|
|
121
|
+
<span className="absolute left-1.5 top-1/2 size-1.5 -translate-y-1/2 rounded-full bg-accent" />
|
|
122
|
+
)}
|
|
123
|
+
<Avatar name={row.fromName} email={row.fromEmail} size="sm" />
|
|
124
|
+
<ComfortableRowTextContent thread={row} />
|
|
125
|
+
</button>
|
|
126
|
+
);
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// ---------------------------------------------------------------------------
|
|
130
|
+
// Main component
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
|
|
133
|
+
interface DraftsViewProps {
|
|
134
|
+
/** The mailbox id for the \Drafts mailbox being viewed. */
|
|
135
|
+
mailboxId: string;
|
|
136
|
+
/** The accountId that owns this \Drafts mailbox. */
|
|
137
|
+
accountId: string;
|
|
138
|
+
/** The currently selected message id (reading pane). */
|
|
139
|
+
selectedMessageId?: string;
|
|
140
|
+
/** IMAP thread rows already loaded for the \Drafts mailbox. */
|
|
141
|
+
imapThreads: RemitImapThreadMessageResponse[];
|
|
142
|
+
/** Mailbox display name (e.g. "Drafts"). */
|
|
143
|
+
title: string;
|
|
144
|
+
/** Total unread count for the header. */
|
|
145
|
+
unreadCount?: number;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function DraftsView({
|
|
149
|
+
mailboxId,
|
|
150
|
+
accountId,
|
|
151
|
+
selectedMessageId,
|
|
152
|
+
imapThreads,
|
|
153
|
+
title,
|
|
154
|
+
unreadCount,
|
|
155
|
+
}: DraftsViewProps) {
|
|
156
|
+
const { openCompose, state: composeState } = useCompose();
|
|
157
|
+
const navigate = useNavigate();
|
|
158
|
+
const queryClient = useQueryClient();
|
|
159
|
+
|
|
160
|
+
// Fetch the full outbox list — both sources are already fetched by the
|
|
161
|
+
// client; this is a pure client-side composition (issue #505).
|
|
162
|
+
const { data: outboxResponse } = useQuery(
|
|
163
|
+
outboxOperationsListOutboxMessagesOptions(),
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
const sections = useMemo<ThreadSection[]>(() => {
|
|
167
|
+
return groupDraftSections({
|
|
168
|
+
outboxMessages: outboxResponse?.items ?? [],
|
|
169
|
+
accountId,
|
|
170
|
+
imapThreads,
|
|
171
|
+
});
|
|
172
|
+
}, [outboxResponse?.items, accountId, imapThreads]);
|
|
173
|
+
|
|
174
|
+
const deleteMutation = useMutation({
|
|
175
|
+
...outboxDetailOperationsDeleteOutboxMessageMutation(),
|
|
176
|
+
onSuccess: () => {
|
|
177
|
+
queryClient.invalidateQueries({
|
|
178
|
+
queryKey: outboxOperationsListOutboxMessagesQueryKey(),
|
|
179
|
+
});
|
|
180
|
+
},
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
const handleRemitDraftDelete = (outboxMessageId: string) => {
|
|
184
|
+
deleteMutation.mutate({ path: { outboxMessageId } });
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const handleRemitDraftOpen = (outboxMessageId: string) => {
|
|
188
|
+
// Clear any open IMAP draft first. The route's detailPane only renders
|
|
189
|
+
// FullCompose when `composeState.isOpen && !selectedThread`; if an IMAP
|
|
190
|
+
// draft is open (selectedMessageId set) the reading pane would keep
|
|
191
|
+
// showing ConversationView and compose would never surface (#505).
|
|
192
|
+
if (selectedMessageId) {
|
|
193
|
+
navigate({
|
|
194
|
+
to: "/mail/$mailboxId",
|
|
195
|
+
params: { mailboxId },
|
|
196
|
+
search: { selectedMessageId: undefined },
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
openCompose({ mode: "new", outboxMessageId });
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const handleImapDraftOpen = (messageId: string) => {
|
|
203
|
+
navigate({
|
|
204
|
+
to: "/mail/$mailboxId",
|
|
205
|
+
params: { mailboxId },
|
|
206
|
+
search: { selectedMessageId: messageId },
|
|
207
|
+
});
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const isEmpty = sections.length === 0;
|
|
211
|
+
|
|
212
|
+
return (
|
|
213
|
+
<section className="flex h-full w-full flex-col bg-surface">
|
|
214
|
+
{/* List datum bar */}
|
|
215
|
+
<header className="flex h-pane-header shrink-0 items-center gap-2 border-b border-line px-row-inset">
|
|
216
|
+
<NavMenuButton />
|
|
217
|
+
<h1 className="min-w-0 flex-1 truncate text-sm font-semibold text-fg">
|
|
218
|
+
{title}
|
|
219
|
+
</h1>
|
|
220
|
+
{(unreadCount ?? 0) > 0 && (
|
|
221
|
+
<span className="shrink-0 text-2xs text-fg-subtle tabular-nums">
|
|
222
|
+
{unreadCount} unread
|
|
223
|
+
</span>
|
|
224
|
+
)}
|
|
225
|
+
</header>
|
|
226
|
+
|
|
227
|
+
{/* Scrollable body */}
|
|
228
|
+
<div className="flex-1 overflow-y-auto">
|
|
229
|
+
{isEmpty ? (
|
|
230
|
+
<div className="flex flex-col items-center justify-center h-full gap-2 text-center px-4">
|
|
231
|
+
<Inbox className="size-8 text-fg-subtle" />
|
|
232
|
+
<p className="text-sm font-medium text-fg">No drafts</p>
|
|
233
|
+
<p className="text-xs text-fg-subtle">
|
|
234
|
+
Messages you start composing will appear here.
|
|
235
|
+
</p>
|
|
236
|
+
</div>
|
|
237
|
+
) : (
|
|
238
|
+
sections.map((section) => (
|
|
239
|
+
<div key={section.id}>
|
|
240
|
+
{section.label && (
|
|
241
|
+
<SectionHeader
|
|
242
|
+
label={section.label}
|
|
243
|
+
count={section.threads.length}
|
|
244
|
+
/>
|
|
245
|
+
)}
|
|
246
|
+
<div className="divide-y divide-line">
|
|
247
|
+
{section.threads.map((thread) => {
|
|
248
|
+
if (section.id === "remit-drafts") {
|
|
249
|
+
return (
|
|
250
|
+
<RemitDraftRow
|
|
251
|
+
key={thread.id}
|
|
252
|
+
row={thread}
|
|
253
|
+
isSelected={
|
|
254
|
+
composeState.isOpen &&
|
|
255
|
+
thread.id === composeState.outboxMessageId
|
|
256
|
+
}
|
|
257
|
+
onOpen={handleRemitDraftOpen}
|
|
258
|
+
onDelete={handleRemitDraftDelete}
|
|
259
|
+
isDeleting={deleteMutation.isPending}
|
|
260
|
+
/>
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
return (
|
|
264
|
+
<ImapDraftRow
|
|
265
|
+
key={thread.id}
|
|
266
|
+
row={thread}
|
|
267
|
+
isSelected={thread.id === selectedMessageId}
|
|
268
|
+
onOpen={handleImapDraftOpen}
|
|
269
|
+
/>
|
|
270
|
+
);
|
|
271
|
+
})}
|
|
272
|
+
</div>
|
|
273
|
+
</div>
|
|
274
|
+
))
|
|
275
|
+
)}
|
|
276
|
+
</div>
|
|
277
|
+
</section>
|
|
278
|
+
);
|
|
279
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FlaggedList — a FLAT, cross-account inbox of starred mail.
|
|
3
|
+
*
|
|
4
|
+
* Reads the same unified GET /threads source as the daily brief, keeps only
|
|
5
|
+
* starred rows (`toThreadRowData(...).starred`), and renders them as one
|
|
6
|
+
* continuous list (no category sections). The shared `MailViewChrome` owns the
|
|
7
|
+
* `MailHeader` + filter expando; the kit `MessageListPane` (flat, no
|
|
8
|
+
* `briefFilters`) owns the loading / empty / error chrome and keyboard hints,
|
|
9
|
+
* with a consumer-supplied `listBody` so the real rows render at every width.
|
|
10
|
+
*/
|
|
11
|
+
import { unifiedThreadOperationsListAllThreadsOptions } from "@remit/api-http-client/@tanstack/react-query.gen.ts";
|
|
12
|
+
import {
|
|
13
|
+
ComfortableRow,
|
|
14
|
+
flaggedFilterConfig,
|
|
15
|
+
MessageListPane,
|
|
16
|
+
type ThreadRowData,
|
|
17
|
+
} from "@remit/ui";
|
|
18
|
+
import { useQuery } from "@tanstack/react-query";
|
|
19
|
+
import { useCallback, useMemo, useState } from "react";
|
|
20
|
+
import { formatErrorMessage } from "@/components/ui/ErrorState";
|
|
21
|
+
import { useIsDesktop } from "@/hooks/useMediaQuery";
|
|
22
|
+
import {
|
|
23
|
+
matchesBriefSearch,
|
|
24
|
+
matchesSearchTokens,
|
|
25
|
+
toThreadRowData,
|
|
26
|
+
} from "@/lib/brief";
|
|
27
|
+
import { buildBugReportContext, buildGitHubIssueUrl } from "@/lib/bug-report";
|
|
28
|
+
import { useMailContext } from "@/lib/mail-context";
|
|
29
|
+
import { rowToSearchResult } from "@/lib/search-result";
|
|
30
|
+
import { parseSearchTokens } from "@/lib/search-tokens";
|
|
31
|
+
import { MailViewChrome } from "./MailViewChrome";
|
|
32
|
+
|
|
33
|
+
const FILTER_PREDICATES: Record<string, (t: ThreadRowData) => boolean> = {
|
|
34
|
+
unread: (t) => !t.isRead,
|
|
35
|
+
attachment: (t) => t.hasAttachment === true,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
interface FlaggedListProps {
|
|
39
|
+
selectedMessageId?: string;
|
|
40
|
+
onSelectMessage?: (id: string) => void;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function FlaggedList({
|
|
44
|
+
selectedMessageId,
|
|
45
|
+
onSelectMessage,
|
|
46
|
+
}: FlaggedListProps) {
|
|
47
|
+
const { searchQuery, mailboxNameIndex, accountNameIndex } = useMailContext();
|
|
48
|
+
const isDesktop = useIsDesktop();
|
|
49
|
+
|
|
50
|
+
const [selectedCategory, setSelectedCategory] = useState("all");
|
|
51
|
+
const [activeFilters, setActiveFilters] = useState<ReadonlySet<string>>(
|
|
52
|
+
new Set(),
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
const toggleFilter = useCallback((id: string) => {
|
|
56
|
+
setActiveFilters((prev) => {
|
|
57
|
+
const next = new Set(prev);
|
|
58
|
+
if (next.has(id)) next.delete(id);
|
|
59
|
+
else next.add(id);
|
|
60
|
+
return next;
|
|
61
|
+
});
|
|
62
|
+
}, []);
|
|
63
|
+
|
|
64
|
+
const clearFilters = useCallback(() => {
|
|
65
|
+
setSelectedCategory("all");
|
|
66
|
+
setActiveFilters(new Set());
|
|
67
|
+
}, []);
|
|
68
|
+
|
|
69
|
+
const {
|
|
70
|
+
data: threadsData,
|
|
71
|
+
isLoading,
|
|
72
|
+
isError,
|
|
73
|
+
error,
|
|
74
|
+
refetch,
|
|
75
|
+
} = useQuery({
|
|
76
|
+
...unifiedThreadOperationsListAllThreadsOptions(),
|
|
77
|
+
staleTime: 60_000,
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const { freeText: sq, tokens: queryTokens } = parseSearchTokens(
|
|
81
|
+
searchQuery.trim().toLowerCase(),
|
|
82
|
+
{ mailboxesByName: mailboxNameIndex, accountsByName: accountNameIndex },
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const rows = useMemo<ThreadRowData[]>(() => {
|
|
86
|
+
const predicates = Array.from(activeFilters)
|
|
87
|
+
.map((id) => FILTER_PREDICATES[id])
|
|
88
|
+
.filter((p): p is (t: ThreadRowData) => boolean => p != null);
|
|
89
|
+
return (threadsData?.items ?? [])
|
|
90
|
+
.map(toThreadRowData)
|
|
91
|
+
.filter((t) => t.starred === true)
|
|
92
|
+
.filter(
|
|
93
|
+
(t) =>
|
|
94
|
+
(selectedCategory === "all" || t.category === selectedCategory) &&
|
|
95
|
+
predicates.every((p) => p(t)) &&
|
|
96
|
+
(!sq || matchesBriefSearch(t, sq)) &&
|
|
97
|
+
matchesSearchTokens(t, queryTokens),
|
|
98
|
+
);
|
|
99
|
+
}, [threadsData, selectedCategory, activeFilters, sq, queryTokens]);
|
|
100
|
+
|
|
101
|
+
const preset = useMemo(() => flaggedFilterConfig(), []);
|
|
102
|
+
|
|
103
|
+
const searchResults = useMemo(() => rows.map(rowToSearchResult), [rows]);
|
|
104
|
+
|
|
105
|
+
const unreadCount = useMemo(
|
|
106
|
+
() => rows.filter((t) => !t.isRead).length,
|
|
107
|
+
[rows],
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
const listState = isLoading
|
|
111
|
+
? "loading"
|
|
112
|
+
: isError
|
|
113
|
+
? "error"
|
|
114
|
+
: rows.length === 0
|
|
115
|
+
? "empty"
|
|
116
|
+
: "ready";
|
|
117
|
+
|
|
118
|
+
const handleReportError = useCallback(() => {
|
|
119
|
+
const url = buildGitHubIssueUrl(buildBugReportContext());
|
|
120
|
+
window.open(url, "_blank", "noopener,noreferrer");
|
|
121
|
+
}, []);
|
|
122
|
+
|
|
123
|
+
const listBody = (
|
|
124
|
+
<div className="flex-1 overflow-y-auto">
|
|
125
|
+
<div className="divide-y divide-line">
|
|
126
|
+
{rows.map((thread) => (
|
|
127
|
+
<ComfortableRow
|
|
128
|
+
key={thread.id}
|
|
129
|
+
thread={thread}
|
|
130
|
+
active={thread.id === selectedMessageId}
|
|
131
|
+
onClick={() => onSelectMessage?.(thread.id)}
|
|
132
|
+
/>
|
|
133
|
+
))}
|
|
134
|
+
</div>
|
|
135
|
+
</div>
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
return (
|
|
139
|
+
<MailViewChrome
|
|
140
|
+
title="Flagged"
|
|
141
|
+
unreadCount={unreadCount}
|
|
142
|
+
preset={preset}
|
|
143
|
+
selectedCategory={selectedCategory}
|
|
144
|
+
activeFilters={activeFilters}
|
|
145
|
+
onSelectCategory={setSelectedCategory}
|
|
146
|
+
onToggleFilter={toggleFilter}
|
|
147
|
+
onClearFilters={clearFilters}
|
|
148
|
+
searchResults={searchResults}
|
|
149
|
+
searchLoading={isLoading}
|
|
150
|
+
onSelectSearchResult={(result) => onSelectMessage?.(result.id)}
|
|
151
|
+
>
|
|
152
|
+
<MessageListPane
|
|
153
|
+
listTitle="Flagged"
|
|
154
|
+
sections={[{ id: "flagged", threads: rows }]}
|
|
155
|
+
flatList
|
|
156
|
+
hideHeader
|
|
157
|
+
listState={listState}
|
|
158
|
+
searchQuery={sq ? searchQuery : undefined}
|
|
159
|
+
errorMessage={isError ? formatErrorMessage(error) : undefined}
|
|
160
|
+
onRetry={() => refetch()}
|
|
161
|
+
onReportError={handleReportError}
|
|
162
|
+
selectedThreadId={selectedMessageId}
|
|
163
|
+
onSelectThread={onSelectMessage}
|
|
164
|
+
onSelectBriefCategory={() => undefined}
|
|
165
|
+
isDesktop={isDesktop}
|
|
166
|
+
listBody={listState === "ready" ? listBody : undefined}
|
|
167
|
+
/>
|
|
168
|
+
</MailViewChrome>
|
|
169
|
+
);
|
|
170
|
+
}
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FlaggedPane — compound component for the Flagged virtual mailbox
|
|
3
|
+
* (/mail/flagged route).
|
|
4
|
+
*
|
|
5
|
+
* Mirrors BriefPane: it reads the unified cross-account thread list to resolve
|
|
6
|
+
* the open thread and owns the list / reading / phone slots. The list itself is
|
|
7
|
+
* a FLAT inbox of starred mail (see `FlaggedList`), not the sectioned brief.
|
|
8
|
+
*
|
|
9
|
+
* <FlaggedPane selectedMessageId={...}>
|
|
10
|
+
* <AppShellSlotted
|
|
11
|
+
* list={<FlaggedPane.List />}
|
|
12
|
+
* reading={<FlaggedPane.Reading />}
|
|
13
|
+
* />
|
|
14
|
+
* </FlaggedPane>
|
|
15
|
+
*
|
|
16
|
+
* On phone, use `<FlaggedPane.Phone />` instead.
|
|
17
|
+
*/
|
|
18
|
+
import { unifiedThreadOperationsListAllThreadsOptions } from "@remit/api-http-client/@tanstack/react-query.gen.ts";
|
|
19
|
+
import type { RemitImapThreadMessageResponse } from "@remit/api-http-client/types.gen.ts";
|
|
20
|
+
import { ReadingPaneEmpty } from "@remit/ui";
|
|
21
|
+
import { useQuery } from "@tanstack/react-query";
|
|
22
|
+
import { useNavigate } from "@tanstack/react-router";
|
|
23
|
+
import {
|
|
24
|
+
createContext,
|
|
25
|
+
type ReactNode,
|
|
26
|
+
useCallback,
|
|
27
|
+
useContext,
|
|
28
|
+
useMemo,
|
|
29
|
+
} from "react";
|
|
30
|
+
import { Drawer } from "@/components/layout/Drawer";
|
|
31
|
+
import { ConversationView } from "@/components/mail/ConversationView";
|
|
32
|
+
import { FlaggedList } from "@/components/mail/FlaggedList";
|
|
33
|
+
import { IntelligencePane } from "@/components/mail/IntelligencePane";
|
|
34
|
+
import { MessageToolbar } from "@/components/mail/MessageToolbar";
|
|
35
|
+
import { useMailContext } from "@/lib/mail-context";
|
|
36
|
+
|
|
37
|
+
/* ------------------------------------------------------------------ */
|
|
38
|
+
/* Context */
|
|
39
|
+
/* ------------------------------------------------------------------ */
|
|
40
|
+
|
|
41
|
+
interface FlaggedPaneContextValue {
|
|
42
|
+
selectedMessageId: string | undefined;
|
|
43
|
+
selectedThread: RemitImapThreadMessageResponse | undefined;
|
|
44
|
+
onSelectMessage: (id: string) => void;
|
|
45
|
+
onCloseThread: () => void;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const FlaggedPaneCtx = createContext<FlaggedPaneContextValue | null>(null);
|
|
49
|
+
|
|
50
|
+
function useFlaggedPane(): FlaggedPaneContextValue {
|
|
51
|
+
const ctx = useContext(FlaggedPaneCtx);
|
|
52
|
+
if (!ctx) throw new Error("FlaggedPane.* must be used inside <FlaggedPane>");
|
|
53
|
+
return ctx;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/* ------------------------------------------------------------------ */
|
|
57
|
+
/* Provider */
|
|
58
|
+
/* ------------------------------------------------------------------ */
|
|
59
|
+
|
|
60
|
+
interface FlaggedPaneProps {
|
|
61
|
+
selectedMessageId: string | undefined;
|
|
62
|
+
children: ReactNode;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function FlaggedPaneProvider({
|
|
66
|
+
selectedMessageId,
|
|
67
|
+
children,
|
|
68
|
+
}: FlaggedPaneProps) {
|
|
69
|
+
const navigate = useNavigate();
|
|
70
|
+
|
|
71
|
+
const { data: threadsData } = useQuery({
|
|
72
|
+
...unifiedThreadOperationsListAllThreadsOptions(),
|
|
73
|
+
staleTime: 60_000,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
const selectedThread = useMemo(() => {
|
|
77
|
+
if (!selectedMessageId) return undefined;
|
|
78
|
+
return threadsData?.items.find((t) => t.messageId === selectedMessageId);
|
|
79
|
+
}, [threadsData, selectedMessageId]);
|
|
80
|
+
|
|
81
|
+
const handleSelectMessage = useCallback(
|
|
82
|
+
(id: string) => {
|
|
83
|
+
navigate({
|
|
84
|
+
to: "/mail/flagged",
|
|
85
|
+
search: (prev) => ({ ...prev, selectedMessageId: id }),
|
|
86
|
+
});
|
|
87
|
+
},
|
|
88
|
+
[navigate],
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const handleCloseThread = useCallback(() => {
|
|
92
|
+
navigate({
|
|
93
|
+
to: "/mail/flagged",
|
|
94
|
+
search: (prev) => ({ ...prev, selectedMessageId: undefined }),
|
|
95
|
+
});
|
|
96
|
+
}, [navigate]);
|
|
97
|
+
|
|
98
|
+
const ctx: FlaggedPaneContextValue = {
|
|
99
|
+
selectedMessageId,
|
|
100
|
+
selectedThread,
|
|
101
|
+
onSelectMessage: handleSelectMessage,
|
|
102
|
+
onCloseThread: handleCloseThread,
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<FlaggedPaneCtx.Provider value={ctx}>{children}</FlaggedPaneCtx.Provider>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* ------------------------------------------------------------------ */
|
|
111
|
+
/* Sub-views */
|
|
112
|
+
/* ------------------------------------------------------------------ */
|
|
113
|
+
|
|
114
|
+
/** Flat starred list. Mount in the `list` slot of `AppShellSlotted`. */
|
|
115
|
+
function FlaggedListSlot() {
|
|
116
|
+
const { selectedMessageId, onSelectMessage } = useFlaggedPane();
|
|
117
|
+
return (
|
|
118
|
+
<FlaggedList
|
|
119
|
+
selectedMessageId={selectedMessageId}
|
|
120
|
+
onSelectMessage={onSelectMessage}
|
|
121
|
+
/>
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Reading pane: toolbar + ConversationView.
|
|
127
|
+
* Mount in the `reading` slot of `AppShellSlotted`. Only rendered ≥ 1024px.
|
|
128
|
+
*/
|
|
129
|
+
function FlaggedReading() {
|
|
130
|
+
const { selectedThread } = useFlaggedPane();
|
|
131
|
+
const {
|
|
132
|
+
onToggleIntelligence,
|
|
133
|
+
searchInput,
|
|
134
|
+
onSearchChange,
|
|
135
|
+
onSearchClear,
|
|
136
|
+
onSearchClearQuery,
|
|
137
|
+
} = useMailContext();
|
|
138
|
+
|
|
139
|
+
return (
|
|
140
|
+
<section className="flex h-full w-full min-w-0 flex-col bg-canvas">
|
|
141
|
+
<MessageToolbar
|
|
142
|
+
hasThread={Boolean(selectedThread)}
|
|
143
|
+
onCompose={() => undefined}
|
|
144
|
+
intelligenceOpen={false}
|
|
145
|
+
showIntelligenceToggle={false}
|
|
146
|
+
onToggleIntelligence={onToggleIntelligence}
|
|
147
|
+
searchValue={searchInput}
|
|
148
|
+
onSearchChange={onSearchChange}
|
|
149
|
+
onSearchClear={onSearchClear}
|
|
150
|
+
onSearchClearQuery={onSearchClearQuery}
|
|
151
|
+
/>
|
|
152
|
+
<div className="min-h-0 flex-1 overflow-hidden">
|
|
153
|
+
{selectedThread ? (
|
|
154
|
+
<ConversationView
|
|
155
|
+
threadId={selectedThread.threadId}
|
|
156
|
+
mailboxId={selectedThread.mailboxId}
|
|
157
|
+
subject={selectedThread.subject}
|
|
158
|
+
authenticity={selectedThread.authenticity}
|
|
159
|
+
/>
|
|
160
|
+
) : (
|
|
161
|
+
<ReadingPaneEmpty />
|
|
162
|
+
)}
|
|
163
|
+
</div>
|
|
164
|
+
</section>
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** Phone view: ConversationView when a thread is open, else the flat list. */
|
|
169
|
+
function FlaggedPhone() {
|
|
170
|
+
const { selectedThread, onCloseThread } = useFlaggedPane();
|
|
171
|
+
const { intelligenceOpen, onToggleIntelligence } = useMailContext();
|
|
172
|
+
|
|
173
|
+
if (selectedThread) {
|
|
174
|
+
return (
|
|
175
|
+
<>
|
|
176
|
+
<ConversationView
|
|
177
|
+
threadId={selectedThread.threadId}
|
|
178
|
+
mailboxId={selectedThread.mailboxId}
|
|
179
|
+
subject={selectedThread.subject}
|
|
180
|
+
authenticity={selectedThread.authenticity}
|
|
181
|
+
onBack={onCloseThread}
|
|
182
|
+
onOpenIntelligence={onToggleIntelligence}
|
|
183
|
+
/>
|
|
184
|
+
<Drawer
|
|
185
|
+
isOpen={intelligenceOpen}
|
|
186
|
+
onClose={onToggleIntelligence}
|
|
187
|
+
ariaLabel="Message details"
|
|
188
|
+
side="right"
|
|
189
|
+
>
|
|
190
|
+
<IntelligencePane
|
|
191
|
+
onClose={onToggleIntelligence}
|
|
192
|
+
thread={selectedThread}
|
|
193
|
+
hideCloseButton
|
|
194
|
+
/>
|
|
195
|
+
</Drawer>
|
|
196
|
+
</>
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return (
|
|
201
|
+
<div className="h-full">
|
|
202
|
+
<FlaggedListSlot />
|
|
203
|
+
</div>
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/* ------------------------------------------------------------------ */
|
|
208
|
+
/* Compound component assembly */
|
|
209
|
+
/* ------------------------------------------------------------------ */
|
|
210
|
+
|
|
211
|
+
const FlaggedPane = Object.assign(FlaggedPaneProvider, {
|
|
212
|
+
List: FlaggedListSlot,
|
|
213
|
+
Reading: FlaggedReading,
|
|
214
|
+
Phone: FlaggedPhone,
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
export { FlaggedPane };
|