@nocobase/portal-template-default 1.0.15 → 1.0.17
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/AGENTS.md +8 -0
- package/README.MD +4 -0
- package/dist/assets/{LineDraw-iG-ADes-.js → LineDraw-BBB5hwuJ.js} +1 -1
- package/dist/assets/{SymbolDraw-DXoSw1tM.js → SymbolDraw-CKS3uUb_.js} +1 -1
- package/dist/assets/{VisualMapping-C_ee4Eht.js → VisualMapping-BCwaW3TD.js} +1 -1
- package/dist/assets/auth-method-demo-BdCCvEEz.js +1 -0
- package/dist/assets/{auto-login-provider-5Uxza9Ms.js → auto-login-provider-DO5Il6Dp.js} +2 -2
- package/dist/assets/auto-login-provider-TQ08B6-i.js +1 -0
- package/dist/assets/auto-redirect-provider-ButXp0A3.js +1 -0
- package/dist/assets/auto-redirect-provider-DnGGP2Vn.js +1 -0
- package/dist/assets/auto-redirect-provider-ja-vFPaF.js +1 -0
- package/dist/assets/calendar-C7xnRgOT.js +1 -0
- package/dist/assets/{cas-sign-in-button-CyqG1QME.js → cas-sign-in-button-BZCa4lR6.js} +1 -1
- package/dist/assets/{components-D-ptYjX_.js → components-_g-GjQeV.js} +3 -3
- package/dist/assets/{configuration-gate-C08mkkiX.js → configuration-gate-B4XVz7tS.js} +1 -1
- package/dist/assets/{createClipPathFromCoordSys-wj34-b1B.js → createClipPathFromCoordSys-z9Kmh-tu.js} +1 -1
- package/dist/assets/createSeriesDataSimply-CPqysF0K.js +1 -0
- package/dist/assets/{customGraphicKeyframeAnimation-DWrhrHL_.js → customGraphicKeyframeAnimation-DMU5crAu.js} +1 -1
- package/dist/assets/demo-BPlae2bp.js +1 -0
- package/dist/assets/demo-Bj_Y0RrW.js +1 -0
- package/dist/assets/demo-CKPAQOib.js +1 -0
- package/dist/assets/demo-CXRTPvIx.js +1 -0
- package/dist/assets/demo-DJuVDu1n.js +1 -0
- package/dist/assets/demo-DUmWU7ik.js +1 -0
- package/dist/assets/demo-NhIzFRE1.js +1 -0
- package/dist/assets/{dingtalk-sign-in-button-DSWsSruv.js → dingtalk-sign-in-button-DMPgE5kT.js} +1 -1
- package/dist/assets/{echarts-preview-DxHNfq1C.js → echarts-preview-R96Pbqmz.js} +1 -1
- package/dist/assets/{echarts-runtime-D8kXn6ME.js → echarts-runtime-Ce_z2eX_.js} +3 -3
- package/dist/assets/{echarts-runtime-advanced-QY8v95qK.js → echarts-runtime-advanced-Dr80KHJR.js} +1 -1
- package/dist/assets/{echarts-runtime-common-D-yqAK-r.js → echarts-runtime-common-DB95Yfc1.js} +1 -1
- package/dist/assets/{echarts-runtime-components-P4-i8jPU.js → echarts-runtime-components-DCJEaTJk.js} +1 -1
- package/dist/assets/{echarts-runtime-hierarchy-ByQZ-DbP.js → echarts-runtime-hierarchy-CezgZs9O.js} +1 -1
- package/dist/assets/{floating-BFVEyPEp.js → floating-O9WmiDXk.js} +2 -2
- package/dist/assets/index-BBKV6O5d.css +1 -0
- package/dist/assets/{index-Btk35Nx-.js → index-CPzehPGe.js} +3 -3
- package/dist/assets/index-Cc6awvdg.js +1064 -0
- package/dist/assets/index-ChA0z9ke.js +38 -0
- package/dist/assets/{index-um5hdKdZ.js → index-DgB7uTMs.js} +2 -2
- package/dist/assets/{index-CwNxd66J.js → index-DroUAK80.js} +1 -1
- package/dist/assets/index-WKvygTBp.js +59 -0
- package/dist/assets/{labelHelper-DQqRy3a_.js → labelHelper-Dk5jkiD-.js} +1 -1
- package/dist/assets/ldap-sign-in-form-CKN4l7qM.js +1 -0
- package/dist/assets/{oidc-sign-in-button-CQdRcR_p.js → oidc-sign-in-button-BIRonrpC.js} +1 -1
- package/dist/assets/{page-context-CnQ4GYM7.js → page-context-DAiW_dId.js} +3 -3
- package/dist/assets/prompt-card-rGZqpqkF.js +1 -0
- package/dist/assets/prompt-output-B4h3uLWf.js +1 -0
- package/dist/assets/saml-sign-in-button-B4ugaLIb.js +1 -0
- package/dist/assets/{sectorHelper-DN_Vv6OL.js → sectorHelper-BuxrrJrA.js} +1 -1
- package/dist/assets/shortcut-BhKXxJLs.js +47 -0
- package/dist/assets/{sms-sign-in-form-DTX9GV2I.js → sms-sign-in-form-DKpxRDdZ.js} +1 -1
- package/dist/assets/{static.browser-jI1psK0d.js → static.browser-kzzpH0Xt.js} +8 -8
- package/dist/assets/switch-BdVxPIVU.js +1 -0
- package/dist/assets/{tool-cards-DFttLAfo.js → tool-cards-CpczIke-.js} +3 -3
- package/dist/assets/{use-authenticator-sign-in-CRGjzXju.js → use-authenticator-sign-in-CIj210ry.js} +1 -1
- package/dist/assets/use-dingtalk-sign-in-BW4kkXD3.js +1 -0
- package/dist/assets/{use-wecom-sign-in-C9j8wfCD.js → use-wecom-sign-in-dIqMtcN9.js} +1 -1
- package/dist/assets/wecom-sign-in-button-Dn_2zDab.js +1 -0
- package/dist/index.html +2 -2
- package/dist/index.raw.html +2 -2
- package/package.json +9 -1
- package/src/App.tsx +5 -2
- package/src/app/extension.ts +2 -0
- package/src/app/extensions.tsx +18 -6
- package/src/app/route-access-guard.tsx +22 -0
- package/src/app/route-runtime.ts +173 -0
- package/src/app/routes.tsx +2 -2
- package/src/components/app-shell/header.tsx +56 -1
- package/src/components/app-shell/sidebar.tsx +14 -2
- package/src/components/auth/sign-up-form.tsx +287 -59
- package/src/components/auth/types.ts +23 -1
- package/src/extensions/nocobase-acl/README.md +28 -14
- package/src/extensions/nocobase-acl/demo/index.tsx +87 -15
- package/src/extensions/nocobase-acl/demo/prompt-generator.tsx +1 -0
- package/src/extensions/nocobase-mail/README.md +74 -0
- package/src/extensions/nocobase-mail/components/index.ts +79 -0
- package/src/extensions/nocobase-mail/components/mail-api.ts +423 -0
- package/src/extensions/nocobase-mail/components/mail-attachment-list.tsx +67 -0
- package/src/extensions/nocobase-mail/components/mail-compose-attachments.tsx +166 -0
- package/src/extensions/nocobase-mail/components/mail-compose.tsx +1071 -0
- package/src/extensions/nocobase-mail/components/mail-detail.tsx +653 -0
- package/src/extensions/nocobase-mail/components/mail-draft-autosave.ts +26 -0
- package/src/extensions/nocobase-mail/components/mail-empty.tsx +48 -0
- package/src/extensions/nocobase-mail/components/mail-filters.tsx +277 -0
- package/src/extensions/nocobase-mail/components/mail-inbox.tsx +544 -0
- package/src/extensions/nocobase-mail/components/mail-inline-images.ts +69 -0
- package/src/extensions/nocobase-mail/components/mail-label-badge.tsx +25 -0
- package/src/extensions/nocobase-mail/components/mail-label-manager.tsx +271 -0
- package/src/extensions/nocobase-mail/components/mail-labels-editor.tsx +267 -0
- package/src/extensions/nocobase-mail/components/mail-mass-tracking.tsx +271 -0
- package/src/extensions/nocobase-mail/components/mail-note-editor.tsx +99 -0
- package/src/extensions/nocobase-mail/components/mail-recipient-input.tsx +260 -0
- package/src/extensions/nocobase-mail/components/mail-reply-quote.ts +28 -0
- package/src/extensions/nocobase-mail/components/mail-rich-editor.tsx +287 -0
- package/src/extensions/nocobase-mail/components/mail-send-actions.tsx +238 -0
- package/src/extensions/nocobase-mail/components/mail-senders.ts +61 -0
- package/src/extensions/nocobase-mail/components/mail-settings-drawer.tsx +708 -0
- package/src/extensions/nocobase-mail/components/mail-showcase-layout.tsx +64 -0
- package/src/extensions/nocobase-mail/components/mail-signature-manager.tsx +258 -0
- package/src/extensions/nocobase-mail/components/mail-signatures.ts +25 -0
- package/src/extensions/nocobase-mail/components/mail-table.tsx +476 -0
- package/src/extensions/nocobase-mail/components/mail-template-manager.tsx +204 -0
- package/src/extensions/nocobase-mail/components/mail-toolbar.tsx +155 -0
- package/src/extensions/nocobase-mail/components/mail-unread.tsx +111 -0
- package/src/extensions/nocobase-mail/components/types.ts +297 -0
- package/src/extensions/nocobase-mail/components/use-mail-compose.tsx +262 -0
- package/src/extensions/nocobase-mail/components/use-mail-mass-messages.ts +54 -0
- package/src/extensions/nocobase-mail/components/use-mail-messages.ts +113 -0
- package/src/extensions/nocobase-mail/components/use-mail-signatures.ts +122 -0
- package/src/extensions/nocobase-mail/components/use-mail-templates.ts +54 -0
- package/src/extensions/nocobase-mail/extension.tsx +105 -0
- package/src/extensions/nocobase-mail/index.ts +1 -0
- package/src/extensions/nocobase-mail/mail-demo-pages.tsx +503 -0
- package/src/extensions/nocobase-mail/mail-pages.tsx +201 -0
- package/src/extensions/nocobase-mail/tests/mail-quote-html-regression.mjs +51 -0
- package/src/extensions/nocobase-mail/tests/mail-regression.mjs +649 -0
- package/src/extensions/nocobase-users-example/app-routes.tsx +121 -0
- package/src/extensions/nocobase-users-example/extension.tsx +2 -100
- package/src/extensions/nocobase-users-example/index.ts +1 -0
- package/src/lib/nocobase/acl/menu.ts +17 -3
- package/src/lib/nocobase/acl/types.ts +4 -0
- package/src/locales/en-US.ts +2 -1
- package/src/locales/zh-CN.ts +2 -1
- package/src/providers/runtime-config.ts +18 -0
- package/src/routes.tsx +10 -0
- package/src/vite-env.d.ts +3 -0
- package/vite.config.ts +8 -0
- package/dist/assets/auth-method-demo-DaOCuvsq.js +0 -1
- package/dist/assets/auto-login-provider-DyaPGK2D.js +0 -1
- package/dist/assets/auto-redirect-provider-CfwRvYOI.js +0 -1
- package/dist/assets/auto-redirect-provider-Cr3jmYts.js +0 -1
- package/dist/assets/auto-redirect-provider-QkJQRBbG.js +0 -1
- package/dist/assets/calendar-DjDRfD5t.js +0 -1
- package/dist/assets/card-DuQBOS6A.js +0 -1
- package/dist/assets/createSeriesDataSimply-D0A6SAVQ.js +0 -1
- package/dist/assets/demo-BYWHeKUe.js +0 -1
- package/dist/assets/demo-CPWoNtx8.js +0 -1
- package/dist/assets/demo-CyvJk5zV.js +0 -1
- package/dist/assets/demo-D4OiKplc.js +0 -1
- package/dist/assets/demo-DBueJg0m.js +0 -1
- package/dist/assets/demo-DkIhyiRK.js +0 -1
- package/dist/assets/demo-tpTjO0sC.js +0 -1
- package/dist/assets/index-BgCbGDMc.js +0 -43
- package/dist/assets/index-MB-jfQld.js +0 -686
- package/dist/assets/index-NH3aqqQz.css +0 -1
- package/dist/assets/index-gFtCXH3o.js +0 -58
- package/dist/assets/ldap-sign-in-form--6Gt3yGq.js +0 -1
- package/dist/assets/prompt-card-B2RS4nt2.js +0 -1
- package/dist/assets/prompt-output-DZHRM2ft.js +0 -1
- package/dist/assets/saml-sign-in-button-BHL5a0JV.js +0 -1
- package/dist/assets/shortcut-C5x5LQWU.js +0 -47
- package/dist/assets/switch-CRL8PmRI.js +0 -1
- package/dist/assets/use-dingtalk-sign-in-8WYzJ2jf.js +0 -1
- package/dist/assets/wecom-sign-in-button-mIS1zGSU.js +0 -1
|
@@ -0,0 +1,708 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from "react";
|
|
2
|
+
import { useGetIdentity } from "@refinedev/core";
|
|
3
|
+
import {
|
|
4
|
+
AtSign,
|
|
5
|
+
FileText,
|
|
6
|
+
Loader2,
|
|
7
|
+
Mail,
|
|
8
|
+
MoreHorizontal,
|
|
9
|
+
PenLine,
|
|
10
|
+
Plus,
|
|
11
|
+
RefreshCw,
|
|
12
|
+
Settings,
|
|
13
|
+
Star,
|
|
14
|
+
Tags,
|
|
15
|
+
Trash2,
|
|
16
|
+
} from "lucide-react";
|
|
17
|
+
import { toast } from "sonner";
|
|
18
|
+
import { mailApi } from "./mail-api";
|
|
19
|
+
import { MailLabelManager } from "./mail-label-manager";
|
|
20
|
+
import { MailTemplateManager } from "./mail-template-manager";
|
|
21
|
+
import { MailSignatureManager } from "./mail-signature-manager";
|
|
22
|
+
import { useMailTemplates } from "./use-mail-templates";
|
|
23
|
+
import { useMailSignatures } from "./use-mail-signatures";
|
|
24
|
+
import type { MailAccount, MailLabel } from "./types";
|
|
25
|
+
import { Button } from "@/components/ui/button";
|
|
26
|
+
import { Badge } from "@/components/ui/badge";
|
|
27
|
+
import { Input } from "@/components/ui/input";
|
|
28
|
+
import { Label } from "@/components/ui/label";
|
|
29
|
+
import {
|
|
30
|
+
Sheet,
|
|
31
|
+
SheetContent,
|
|
32
|
+
SheetDescription,
|
|
33
|
+
SheetHeader,
|
|
34
|
+
SheetTitle,
|
|
35
|
+
} from "@/components/ui/sheet";
|
|
36
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|
37
|
+
import {
|
|
38
|
+
Select,
|
|
39
|
+
SelectContent,
|
|
40
|
+
SelectItem,
|
|
41
|
+
SelectTrigger,
|
|
42
|
+
SelectValue,
|
|
43
|
+
} from "@/components/ui/select";
|
|
44
|
+
import {
|
|
45
|
+
DropdownMenu,
|
|
46
|
+
DropdownMenuContent,
|
|
47
|
+
DropdownMenuItem,
|
|
48
|
+
DropdownMenuSeparator,
|
|
49
|
+
DropdownMenuTrigger,
|
|
50
|
+
} from "@/components/ui/dropdown-menu";
|
|
51
|
+
import {
|
|
52
|
+
Dialog,
|
|
53
|
+
DialogContent,
|
|
54
|
+
DialogFooter,
|
|
55
|
+
DialogHeader,
|
|
56
|
+
DialogTitle,
|
|
57
|
+
} from "@/components/ui/dialog";
|
|
58
|
+
import {
|
|
59
|
+
AlertDialog,
|
|
60
|
+
AlertDialogAction,
|
|
61
|
+
AlertDialogCancel,
|
|
62
|
+
AlertDialogContent,
|
|
63
|
+
AlertDialogDescription,
|
|
64
|
+
AlertDialogFooter,
|
|
65
|
+
AlertDialogHeader,
|
|
66
|
+
AlertDialogTitle,
|
|
67
|
+
} from "@/components/ui/alert-dialog";
|
|
68
|
+
|
|
69
|
+
export interface MailSettingsDrawerProps {
|
|
70
|
+
open: boolean;
|
|
71
|
+
onOpenChange: (open: boolean) => void;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function EmptySetting({ children }: { children: string }) {
|
|
75
|
+
return (
|
|
76
|
+
<div className="rounded-xl border border-dashed px-4 py-10 text-center text-sm text-muted-foreground">
|
|
77
|
+
{children}
|
|
78
|
+
</div>
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function MailSettingsDrawer({
|
|
83
|
+
open,
|
|
84
|
+
onOpenChange,
|
|
85
|
+
}: MailSettingsDrawerProps) {
|
|
86
|
+
const { data: identity } = useGetIdentity<{ id: number | string }>();
|
|
87
|
+
const [accounts, setAccounts] = useState<MailAccount[]>([]);
|
|
88
|
+
const [labels, setLabels] = useState<MailLabel[]>([]);
|
|
89
|
+
const [loading, setLoading] = useState(false);
|
|
90
|
+
const [syncingEmail, setSyncingEmail] = useState<string>();
|
|
91
|
+
const [selectedAccountId, setSelectedAccountId] = useState<number>();
|
|
92
|
+
const [deleteAccount, setDeleteAccount] = useState<MailAccount>();
|
|
93
|
+
const [resyncAccount, setResyncAccount] = useState<MailAccount>();
|
|
94
|
+
const [senderAccount, setSenderAccount] = useState<MailAccount>();
|
|
95
|
+
const [aliasAccount, setAliasAccount] = useState<MailAccount>();
|
|
96
|
+
const [syncingAliases, setSyncingAliases] = useState(false);
|
|
97
|
+
const [senderName, setSenderName] = useState("");
|
|
98
|
+
const [savingSender, setSavingSender] = useState(false);
|
|
99
|
+
const templates = useMailTemplates();
|
|
100
|
+
const selectedAccount = accounts.find(
|
|
101
|
+
(account) => account.id === selectedAccountId
|
|
102
|
+
);
|
|
103
|
+
const signatures = useMailSignatures(selectedAccount, (updated) => {
|
|
104
|
+
setAccounts((current) =>
|
|
105
|
+
current.map((account) => (account.id === updated.id ? updated : account))
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
const loadSettings = useCallback(async () => {
|
|
110
|
+
setLoading(true);
|
|
111
|
+
try {
|
|
112
|
+
const [nextAccounts, nextLabels] = await Promise.all([
|
|
113
|
+
mailApi.getAccounts(),
|
|
114
|
+
mailApi.getLabels(identity?.id),
|
|
115
|
+
]);
|
|
116
|
+
setAccounts(nextAccounts);
|
|
117
|
+
setLabels(nextLabels);
|
|
118
|
+
setSelectedAccountId((current) =>
|
|
119
|
+
nextAccounts.some((account) => account.id === current)
|
|
120
|
+
? current
|
|
121
|
+
: nextAccounts[0]?.id
|
|
122
|
+
);
|
|
123
|
+
} catch (error) {
|
|
124
|
+
toast.error(
|
|
125
|
+
error instanceof Error ? error.message : "Failed to load mail settings"
|
|
126
|
+
);
|
|
127
|
+
} finally {
|
|
128
|
+
setLoading(false);
|
|
129
|
+
}
|
|
130
|
+
}, [identity?.id]);
|
|
131
|
+
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
if (open) void loadSettings();
|
|
134
|
+
}, [loadSettings, open]);
|
|
135
|
+
|
|
136
|
+
useEffect(() => {
|
|
137
|
+
if (!open) return;
|
|
138
|
+
const refreshOnFocus = () => void loadSettings();
|
|
139
|
+
window.addEventListener("focus", refreshOnFocus);
|
|
140
|
+
return () => window.removeEventListener("focus", refreshOnFocus);
|
|
141
|
+
}, [loadSettings, open]);
|
|
142
|
+
|
|
143
|
+
const openOAuth = async (
|
|
144
|
+
type: "google" | "microsoft",
|
|
145
|
+
options?: { email?: string; reauthorize?: boolean }
|
|
146
|
+
) => {
|
|
147
|
+
try {
|
|
148
|
+
const url = await mailApi.getOAuthUrl(type, options);
|
|
149
|
+
if (!url) throw new Error("Authorization URL was not returned");
|
|
150
|
+
window.open(url, "_blank", "noopener,noreferrer");
|
|
151
|
+
} catch (error) {
|
|
152
|
+
toast.error(
|
|
153
|
+
error instanceof Error ? error.message : "Failed to start authorization"
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const reauthorize = (account: MailAccount) => {
|
|
159
|
+
if (account.type !== "google" && account.type !== "microsoft") {
|
|
160
|
+
toast.error(`Unsupported mailbox type: ${account.type}`);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
void openOAuth(account.type, { email: account.email, reauthorize: true });
|
|
164
|
+
};
|
|
165
|
+
|
|
166
|
+
const syncAccount = async (account: MailAccount) => {
|
|
167
|
+
setSyncingEmail(account.email);
|
|
168
|
+
try {
|
|
169
|
+
await mailApi.sync([account.email]);
|
|
170
|
+
toast.success(`${account.email} synced`);
|
|
171
|
+
} catch (error) {
|
|
172
|
+
toast.error(
|
|
173
|
+
error instanceof Error ? error.message : "Mailbox sync failed"
|
|
174
|
+
);
|
|
175
|
+
} finally {
|
|
176
|
+
setSyncingEmail(undefined);
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const confirmDeleteAccount = async () => {
|
|
181
|
+
if (!deleteAccount) return;
|
|
182
|
+
setSyncingEmail(deleteAccount.email);
|
|
183
|
+
try {
|
|
184
|
+
await mailApi.deleteAccount(deleteAccount.email);
|
|
185
|
+
toast.success("Mailbox deleted");
|
|
186
|
+
setDeleteAccount(undefined);
|
|
187
|
+
await loadSettings();
|
|
188
|
+
} catch (error) {
|
|
189
|
+
toast.error(
|
|
190
|
+
error instanceof Error ? error.message : "Mailbox deletion failed"
|
|
191
|
+
);
|
|
192
|
+
} finally {
|
|
193
|
+
setSyncingEmail(undefined);
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
const confirmResyncAccount = async () => {
|
|
198
|
+
if (!resyncAccount) return;
|
|
199
|
+
setSyncingEmail(resyncAccount.email);
|
|
200
|
+
try {
|
|
201
|
+
await mailApi.resyncAccount(resyncAccount.email);
|
|
202
|
+
toast.success("Mailbox resync completed");
|
|
203
|
+
setResyncAccount(undefined);
|
|
204
|
+
} catch (error) {
|
|
205
|
+
toast.error(
|
|
206
|
+
error instanceof Error ? error.message : "Mailbox resync failed"
|
|
207
|
+
);
|
|
208
|
+
} finally {
|
|
209
|
+
setSyncingEmail(undefined);
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const editSenderName = (account: MailAccount) => {
|
|
214
|
+
setSenderAccount(account);
|
|
215
|
+
setSenderName(
|
|
216
|
+
typeof account.config?.displayName === "string"
|
|
217
|
+
? account.config.displayName
|
|
218
|
+
: ""
|
|
219
|
+
);
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
const syncAliases = async () => {
|
|
223
|
+
if (!aliasAccount) return;
|
|
224
|
+
setSyncingAliases(true);
|
|
225
|
+
try {
|
|
226
|
+
const identities = await mailApi.syncAliases(aliasAccount.email);
|
|
227
|
+
const updated = { ...aliasAccount, identities };
|
|
228
|
+
setAliasAccount(updated);
|
|
229
|
+
setAccounts((current) =>
|
|
230
|
+
current.map((account) =>
|
|
231
|
+
account.id === updated.id ? updated : account
|
|
232
|
+
)
|
|
233
|
+
);
|
|
234
|
+
toast.success("Aliases synced");
|
|
235
|
+
} catch (error) {
|
|
236
|
+
toast.error(
|
|
237
|
+
error instanceof Error ? error.message : "Failed to sync aliases"
|
|
238
|
+
);
|
|
239
|
+
} finally {
|
|
240
|
+
setSyncingAliases(false);
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const saveSenderName = async () => {
|
|
245
|
+
if (!senderAccount) return;
|
|
246
|
+
setSavingSender(true);
|
|
247
|
+
try {
|
|
248
|
+
const config = {
|
|
249
|
+
...(senderAccount.config ?? {}),
|
|
250
|
+
displayName: senderName.trim(),
|
|
251
|
+
};
|
|
252
|
+
const updated = await mailApi.updateAccount(senderAccount.id, { config });
|
|
253
|
+
setAccounts((current) =>
|
|
254
|
+
current.map((account) =>
|
|
255
|
+
account.id === senderAccount.id
|
|
256
|
+
? { ...account, ...updated, config: updated.config ?? config }
|
|
257
|
+
: account
|
|
258
|
+
)
|
|
259
|
+
);
|
|
260
|
+
setSenderAccount(undefined);
|
|
261
|
+
toast.success("Sender name saved");
|
|
262
|
+
} catch (error) {
|
|
263
|
+
toast.error(
|
|
264
|
+
error instanceof Error ? error.message : "Failed to save sender name"
|
|
265
|
+
);
|
|
266
|
+
} finally {
|
|
267
|
+
setSavingSender(false);
|
|
268
|
+
}
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
return (
|
|
272
|
+
<>
|
|
273
|
+
<Sheet open={open} onOpenChange={onOpenChange}>
|
|
274
|
+
<SheetContent
|
|
275
|
+
side="right"
|
|
276
|
+
className="w-full gap-0 p-0 data-[side=right]:sm:max-w-3xl"
|
|
277
|
+
>
|
|
278
|
+
<SheetHeader className="border-b px-6 py-5">
|
|
279
|
+
<div className="flex items-center gap-2">
|
|
280
|
+
<Settings className="size-4 text-muted-foreground" />
|
|
281
|
+
<SheetTitle>Mail settings</SheetTitle>
|
|
282
|
+
</div>
|
|
283
|
+
<SheetDescription>
|
|
284
|
+
Manage personal mailboxes and reusable message content.
|
|
285
|
+
</SheetDescription>
|
|
286
|
+
</SheetHeader>
|
|
287
|
+
|
|
288
|
+
<Tabs defaultValue="mailbox" className="min-h-0 flex-1 gap-0">
|
|
289
|
+
<div className="border-b px-6 py-3">
|
|
290
|
+
<TabsList variant="line" className="h-9">
|
|
291
|
+
<TabsTrigger value="mailbox">
|
|
292
|
+
<Mail /> Mailbox
|
|
293
|
+
</TabsTrigger>
|
|
294
|
+
<TabsTrigger value="labels">
|
|
295
|
+
<Tags /> Labels
|
|
296
|
+
</TabsTrigger>
|
|
297
|
+
<TabsTrigger value="templates">
|
|
298
|
+
<FileText /> Templates
|
|
299
|
+
</TabsTrigger>
|
|
300
|
+
<TabsTrigger value="signature">
|
|
301
|
+
<Star /> Signature
|
|
302
|
+
</TabsTrigger>
|
|
303
|
+
</TabsList>
|
|
304
|
+
</div>
|
|
305
|
+
|
|
306
|
+
<div className="min-h-0 flex-1 overflow-y-auto p-6">
|
|
307
|
+
<TabsContent value="mailbox" className="space-y-4">
|
|
308
|
+
<div className="flex items-center justify-between gap-3">
|
|
309
|
+
<div>
|
|
310
|
+
<h3 className="font-semibold">Connected mailboxes</h3>
|
|
311
|
+
<p className="mt-1 text-xs text-muted-foreground">
|
|
312
|
+
View and sync the mailboxes available to your account.
|
|
313
|
+
</p>
|
|
314
|
+
</div>
|
|
315
|
+
<div className="flex items-center gap-2">
|
|
316
|
+
<Button
|
|
317
|
+
variant="outline"
|
|
318
|
+
size="sm"
|
|
319
|
+
onClick={() => void loadSettings()}
|
|
320
|
+
disabled={loading}
|
|
321
|
+
>
|
|
322
|
+
<RefreshCw
|
|
323
|
+
className={loading ? "animate-spin" : undefined}
|
|
324
|
+
/>{" "}
|
|
325
|
+
Refresh
|
|
326
|
+
</Button>
|
|
327
|
+
<DropdownMenu>
|
|
328
|
+
<DropdownMenuTrigger
|
|
329
|
+
render={
|
|
330
|
+
<Button size="sm">
|
|
331
|
+
<Plus /> Link email
|
|
332
|
+
</Button>
|
|
333
|
+
}
|
|
334
|
+
/>
|
|
335
|
+
<DropdownMenuContent align="end" className="w-44">
|
|
336
|
+
<DropdownMenuItem
|
|
337
|
+
onClick={() => void openOAuth("google")}
|
|
338
|
+
>
|
|
339
|
+
<Mail /> Gmail
|
|
340
|
+
</DropdownMenuItem>
|
|
341
|
+
<DropdownMenuItem
|
|
342
|
+
onClick={() => void openOAuth("microsoft")}
|
|
343
|
+
>
|
|
344
|
+
<Mail /> Outlook
|
|
345
|
+
</DropdownMenuItem>
|
|
346
|
+
</DropdownMenuContent>
|
|
347
|
+
</DropdownMenu>
|
|
348
|
+
</div>
|
|
349
|
+
</div>
|
|
350
|
+
{loading && !accounts.length ? (
|
|
351
|
+
<div className="flex justify-center py-12">
|
|
352
|
+
<Loader2 className="animate-spin text-muted-foreground" />
|
|
353
|
+
</div>
|
|
354
|
+
) : accounts.length ? (
|
|
355
|
+
<div className="divide-y overflow-hidden rounded-xl border bg-card">
|
|
356
|
+
{accounts.map((account) => (
|
|
357
|
+
<div
|
|
358
|
+
key={account.id}
|
|
359
|
+
className="flex items-center gap-4 px-4 py-3.5"
|
|
360
|
+
>
|
|
361
|
+
<span className="flex size-9 items-center justify-center rounded-lg bg-muted">
|
|
362
|
+
<Mail className="size-4" />
|
|
363
|
+
</span>
|
|
364
|
+
<span className="min-w-0 flex-1">
|
|
365
|
+
<span className="block truncate text-sm font-medium">
|
|
366
|
+
{account.email}
|
|
367
|
+
</span>
|
|
368
|
+
<span className="mt-0.5 block text-xs text-muted-foreground">
|
|
369
|
+
{account.identities?.filter(
|
|
370
|
+
(identity) => !identity.isPrimary
|
|
371
|
+
).length ?? 0}{" "}
|
|
372
|
+
aliases
|
|
373
|
+
</span>
|
|
374
|
+
</span>
|
|
375
|
+
<Badge variant="outline" className="capitalize">
|
|
376
|
+
{account.type}
|
|
377
|
+
</Badge>
|
|
378
|
+
<Button
|
|
379
|
+
variant="outline"
|
|
380
|
+
size="sm"
|
|
381
|
+
disabled={syncingEmail === account.email}
|
|
382
|
+
onClick={() => void syncAccount(account)}
|
|
383
|
+
>
|
|
384
|
+
<RefreshCw
|
|
385
|
+
className={
|
|
386
|
+
syncingEmail === account.email
|
|
387
|
+
? "animate-spin"
|
|
388
|
+
: undefined
|
|
389
|
+
}
|
|
390
|
+
/>
|
|
391
|
+
Sync
|
|
392
|
+
</Button>
|
|
393
|
+
<DropdownMenu>
|
|
394
|
+
<DropdownMenuTrigger
|
|
395
|
+
render={
|
|
396
|
+
<Button
|
|
397
|
+
variant="ghost"
|
|
398
|
+
size="icon-sm"
|
|
399
|
+
title={`Manage ${account.email}`}
|
|
400
|
+
>
|
|
401
|
+
<MoreHorizontal />
|
|
402
|
+
</Button>
|
|
403
|
+
}
|
|
404
|
+
/>
|
|
405
|
+
<DropdownMenuContent align="end" className="w-44">
|
|
406
|
+
<DropdownMenuItem
|
|
407
|
+
onClick={() => reauthorize(account)}
|
|
408
|
+
>
|
|
409
|
+
<RefreshCw /> Reauthorize
|
|
410
|
+
</DropdownMenuItem>
|
|
411
|
+
<DropdownMenuItem
|
|
412
|
+
onClick={() => setAliasAccount(account)}
|
|
413
|
+
>
|
|
414
|
+
<AtSign /> Aliases
|
|
415
|
+
</DropdownMenuItem>
|
|
416
|
+
<DropdownMenuItem
|
|
417
|
+
disabled={account.type === "microsoft"}
|
|
418
|
+
onClick={() => editSenderName(account)}
|
|
419
|
+
>
|
|
420
|
+
<PenLine /> Sender name
|
|
421
|
+
</DropdownMenuItem>
|
|
422
|
+
<DropdownMenuItem
|
|
423
|
+
onClick={() => setResyncAccount(account)}
|
|
424
|
+
>
|
|
425
|
+
<RefreshCw /> Resync
|
|
426
|
+
</DropdownMenuItem>
|
|
427
|
+
<DropdownMenuSeparator />
|
|
428
|
+
<DropdownMenuItem
|
|
429
|
+
variant="destructive"
|
|
430
|
+
onClick={() => setDeleteAccount(account)}
|
|
431
|
+
>
|
|
432
|
+
<Trash2 /> Delete
|
|
433
|
+
</DropdownMenuItem>
|
|
434
|
+
</DropdownMenuContent>
|
|
435
|
+
</DropdownMenu>
|
|
436
|
+
</div>
|
|
437
|
+
))}
|
|
438
|
+
</div>
|
|
439
|
+
) : (
|
|
440
|
+
<EmptySetting>No connected mailboxes</EmptySetting>
|
|
441
|
+
)}
|
|
442
|
+
</TabsContent>
|
|
443
|
+
|
|
444
|
+
<TabsContent value="labels" className="space-y-4">
|
|
445
|
+
<div>
|
|
446
|
+
<h3 className="font-semibold">Labels</h3>
|
|
447
|
+
<p className="mt-1 text-xs text-muted-foreground">
|
|
448
|
+
Create and organize reusable message labels.
|
|
449
|
+
</p>
|
|
450
|
+
</div>
|
|
451
|
+
<MailLabelManager
|
|
452
|
+
embedded
|
|
453
|
+
open
|
|
454
|
+
onOpenChange={() => undefined}
|
|
455
|
+
labels={labels}
|
|
456
|
+
onChange={setLabels}
|
|
457
|
+
/>
|
|
458
|
+
</TabsContent>
|
|
459
|
+
|
|
460
|
+
<TabsContent value="templates" className="space-y-4">
|
|
461
|
+
<div>
|
|
462
|
+
<h3 className="font-semibold">Templates</h3>
|
|
463
|
+
<p className="mt-1 text-xs text-muted-foreground">
|
|
464
|
+
Manage reusable content for new messages.
|
|
465
|
+
</p>
|
|
466
|
+
</div>
|
|
467
|
+
<MailTemplateManager
|
|
468
|
+
embedded
|
|
469
|
+
open
|
|
470
|
+
onOpenChange={() => undefined}
|
|
471
|
+
templates={templates.templates}
|
|
472
|
+
onCreate={templates.create}
|
|
473
|
+
onUpdate={templates.update}
|
|
474
|
+
onRemove={templates.remove}
|
|
475
|
+
/>
|
|
476
|
+
</TabsContent>
|
|
477
|
+
|
|
478
|
+
<TabsContent value="signature" className="space-y-4">
|
|
479
|
+
<div className="flex flex-wrap items-end justify-between gap-3">
|
|
480
|
+
<div className="min-w-56 space-y-1.5">
|
|
481
|
+
<h3 className="font-semibold">Signature</h3>
|
|
482
|
+
<Select
|
|
483
|
+
value={
|
|
484
|
+
selectedAccountId
|
|
485
|
+
? String(selectedAccountId)
|
|
486
|
+
: undefined
|
|
487
|
+
}
|
|
488
|
+
onValueChange={(value) =>
|
|
489
|
+
setSelectedAccountId(Number(value))
|
|
490
|
+
}
|
|
491
|
+
>
|
|
492
|
+
<SelectTrigger>
|
|
493
|
+
<SelectValue placeholder="Select mailbox" />
|
|
494
|
+
</SelectTrigger>
|
|
495
|
+
<SelectContent>
|
|
496
|
+
{accounts.map((account) => (
|
|
497
|
+
<SelectItem
|
|
498
|
+
key={account.id}
|
|
499
|
+
value={String(account.id)}
|
|
500
|
+
>
|
|
501
|
+
{account.email}
|
|
502
|
+
</SelectItem>
|
|
503
|
+
))}
|
|
504
|
+
</SelectContent>
|
|
505
|
+
</Select>
|
|
506
|
+
</div>
|
|
507
|
+
</div>
|
|
508
|
+
{selectedAccount ? (
|
|
509
|
+
<MailSignatureManager
|
|
510
|
+
embedded
|
|
511
|
+
open
|
|
512
|
+
onOpenChange={() => undefined}
|
|
513
|
+
signatures={signatures.signatures}
|
|
514
|
+
onCreate={signatures.create}
|
|
515
|
+
onUpdate={signatures.update}
|
|
516
|
+
onRemove={signatures.remove}
|
|
517
|
+
onSetDefault={signatures.setDefault}
|
|
518
|
+
/>
|
|
519
|
+
) : (
|
|
520
|
+
<EmptySetting>Select a mailbox</EmptySetting>
|
|
521
|
+
)}
|
|
522
|
+
</TabsContent>
|
|
523
|
+
</div>
|
|
524
|
+
</Tabs>
|
|
525
|
+
</SheetContent>
|
|
526
|
+
</Sheet>
|
|
527
|
+
|
|
528
|
+
<Dialog
|
|
529
|
+
open={Boolean(senderAccount)}
|
|
530
|
+
onOpenChange={(nextOpen) => !nextOpen && setSenderAccount(undefined)}
|
|
531
|
+
>
|
|
532
|
+
<DialogContent className="sm:max-w-md">
|
|
533
|
+
<DialogHeader>
|
|
534
|
+
<DialogTitle>Sender name</DialogTitle>
|
|
535
|
+
</DialogHeader>
|
|
536
|
+
<div className="space-y-2">
|
|
537
|
+
<Label htmlFor="mail-sender-name">Display name</Label>
|
|
538
|
+
<Input
|
|
539
|
+
id="mail-sender-name"
|
|
540
|
+
value={senderName}
|
|
541
|
+
onChange={(event) => setSenderName(event.target.value)}
|
|
542
|
+
placeholder="Name shown to recipients"
|
|
543
|
+
/>
|
|
544
|
+
<p className="text-xs text-muted-foreground">
|
|
545
|
+
Applies to {senderAccount?.email}. Outlook does not support this
|
|
546
|
+
setting.
|
|
547
|
+
</p>
|
|
548
|
+
</div>
|
|
549
|
+
<DialogFooter>
|
|
550
|
+
<Button
|
|
551
|
+
variant="outline"
|
|
552
|
+
onClick={() => setSenderAccount(undefined)}
|
|
553
|
+
>
|
|
554
|
+
Cancel
|
|
555
|
+
</Button>
|
|
556
|
+
<Button
|
|
557
|
+
disabled={savingSender}
|
|
558
|
+
onClick={() => void saveSenderName()}
|
|
559
|
+
>
|
|
560
|
+
{savingSender && <Loader2 className="animate-spin" />} Save
|
|
561
|
+
</Button>
|
|
562
|
+
</DialogFooter>
|
|
563
|
+
</DialogContent>
|
|
564
|
+
</Dialog>
|
|
565
|
+
|
|
566
|
+
<Dialog
|
|
567
|
+
open={Boolean(aliasAccount)}
|
|
568
|
+
onOpenChange={(nextOpen) => !nextOpen && setAliasAccount(undefined)}
|
|
569
|
+
>
|
|
570
|
+
<DialogContent className="sm:max-w-lg">
|
|
571
|
+
<DialogHeader>
|
|
572
|
+
<DialogTitle>Mailbox aliases</DialogTitle>
|
|
573
|
+
</DialogHeader>
|
|
574
|
+
<div className="space-y-4">
|
|
575
|
+
<div className="flex items-center justify-between gap-3 rounded-lg border bg-muted/30 p-3">
|
|
576
|
+
<div className="min-w-0">
|
|
577
|
+
<p className="truncate text-sm font-medium">
|
|
578
|
+
{aliasAccount?.email}
|
|
579
|
+
</p>
|
|
580
|
+
<p className="mt-0.5 text-xs text-muted-foreground">
|
|
581
|
+
Primary sender address
|
|
582
|
+
</p>
|
|
583
|
+
</div>
|
|
584
|
+
<Badge variant="outline">Primary</Badge>
|
|
585
|
+
</div>
|
|
586
|
+
|
|
587
|
+
{aliasAccount?.type === "microsoft" ? (
|
|
588
|
+
<div className="rounded-lg border border-dashed p-4 text-sm text-muted-foreground">
|
|
589
|
+
Outlook does not expose sender aliases through this integration.
|
|
590
|
+
</div>
|
|
591
|
+
) : (
|
|
592
|
+
<>
|
|
593
|
+
<div className="flex items-center justify-between gap-3">
|
|
594
|
+
<div>
|
|
595
|
+
<h4 className="text-sm font-semibold">Available aliases</h4>
|
|
596
|
+
<p className="mt-0.5 text-xs text-muted-foreground">
|
|
597
|
+
Synced from Gmail and available in the sender selector.
|
|
598
|
+
</p>
|
|
599
|
+
</div>
|
|
600
|
+
<Button
|
|
601
|
+
variant="outline"
|
|
602
|
+
size="sm"
|
|
603
|
+
disabled={syncingAliases}
|
|
604
|
+
onClick={() => void syncAliases()}
|
|
605
|
+
>
|
|
606
|
+
<RefreshCw
|
|
607
|
+
className={syncingAliases ? "animate-spin" : undefined}
|
|
608
|
+
/>
|
|
609
|
+
Sync aliases
|
|
610
|
+
</Button>
|
|
611
|
+
</div>
|
|
612
|
+
{aliasAccount?.identities?.filter(
|
|
613
|
+
(mailIdentity) =>
|
|
614
|
+
!mailIdentity.isPrimary &&
|
|
615
|
+
mailIdentity.email.toLocaleLowerCase() !==
|
|
616
|
+
aliasAccount.email.toLocaleLowerCase()
|
|
617
|
+
).length ? (
|
|
618
|
+
<div className="divide-y overflow-hidden rounded-lg border">
|
|
619
|
+
{aliasAccount.identities
|
|
620
|
+
.filter(
|
|
621
|
+
(mailIdentity) =>
|
|
622
|
+
!mailIdentity.isPrimary &&
|
|
623
|
+
mailIdentity.email.toLocaleLowerCase() !==
|
|
624
|
+
aliasAccount.email.toLocaleLowerCase()
|
|
625
|
+
)
|
|
626
|
+
.map((mailIdentity) => (
|
|
627
|
+
<div
|
|
628
|
+
key={mailIdentity.id}
|
|
629
|
+
className="flex items-center gap-3 px-3 py-2.5"
|
|
630
|
+
>
|
|
631
|
+
<AtSign className="size-4 text-muted-foreground" />
|
|
632
|
+
<span className="min-w-0 flex-1 truncate text-sm">
|
|
633
|
+
{mailIdentity.email}
|
|
634
|
+
</span>
|
|
635
|
+
{mailIdentity.name ? (
|
|
636
|
+
<span className="truncate text-xs text-muted-foreground">
|
|
637
|
+
{mailIdentity.name}
|
|
638
|
+
</span>
|
|
639
|
+
) : null}
|
|
640
|
+
</div>
|
|
641
|
+
))}
|
|
642
|
+
</div>
|
|
643
|
+
) : (
|
|
644
|
+
<EmptySetting>No aliases found</EmptySetting>
|
|
645
|
+
)}
|
|
646
|
+
</>
|
|
647
|
+
)}
|
|
648
|
+
</div>
|
|
649
|
+
<DialogFooter>
|
|
650
|
+
<Button variant="outline" onClick={() => setAliasAccount(undefined)}>
|
|
651
|
+
Close
|
|
652
|
+
</Button>
|
|
653
|
+
</DialogFooter>
|
|
654
|
+
</DialogContent>
|
|
655
|
+
</Dialog>
|
|
656
|
+
|
|
657
|
+
<AlertDialog
|
|
658
|
+
open={Boolean(resyncAccount)}
|
|
659
|
+
onOpenChange={(nextOpen) => !nextOpen && setResyncAccount(undefined)}
|
|
660
|
+
>
|
|
661
|
+
<AlertDialogContent>
|
|
662
|
+
<AlertDialogHeader>
|
|
663
|
+
<AlertDialogTitle>Resync this mailbox?</AlertDialogTitle>
|
|
664
|
+
<AlertDialogDescription>
|
|
665
|
+
This performs a full sync for {resyncAccount?.email}, may take
|
|
666
|
+
some time, and rebuilds its conversation threads.
|
|
667
|
+
</AlertDialogDescription>
|
|
668
|
+
</AlertDialogHeader>
|
|
669
|
+
<AlertDialogFooter>
|
|
670
|
+
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
|
671
|
+
<AlertDialogAction
|
|
672
|
+
disabled={Boolean(syncingEmail)}
|
|
673
|
+
onClick={() => void confirmResyncAccount()}
|
|
674
|
+
>
|
|
675
|
+
Resync
|
|
676
|
+
</AlertDialogAction>
|
|
677
|
+
</AlertDialogFooter>
|
|
678
|
+
</AlertDialogContent>
|
|
679
|
+
</AlertDialog>
|
|
680
|
+
|
|
681
|
+
<AlertDialog
|
|
682
|
+
open={Boolean(deleteAccount)}
|
|
683
|
+
onOpenChange={(nextOpen) => !nextOpen && setDeleteAccount(undefined)}
|
|
684
|
+
>
|
|
685
|
+
<AlertDialogContent>
|
|
686
|
+
<AlertDialogHeader>
|
|
687
|
+
<AlertDialogTitle>Delete this mailbox?</AlertDialogTitle>
|
|
688
|
+
<AlertDialogDescription>
|
|
689
|
+
This removes {deleteAccount?.email} and its locally stored
|
|
690
|
+
messages. The operation may take some time and cannot be undone
|
|
691
|
+
here.
|
|
692
|
+
</AlertDialogDescription>
|
|
693
|
+
</AlertDialogHeader>
|
|
694
|
+
<AlertDialogFooter>
|
|
695
|
+
<AlertDialogCancel>Cancel</AlertDialogCancel>
|
|
696
|
+
<AlertDialogAction
|
|
697
|
+
variant="destructive"
|
|
698
|
+
disabled={Boolean(syncingEmail)}
|
|
699
|
+
onClick={() => void confirmDeleteAccount()}
|
|
700
|
+
>
|
|
701
|
+
Delete
|
|
702
|
+
</AlertDialogAction>
|
|
703
|
+
</AlertDialogFooter>
|
|
704
|
+
</AlertDialogContent>
|
|
705
|
+
</AlertDialog>
|
|
706
|
+
</>
|
|
707
|
+
);
|
|
708
|
+
}
|