@agent-native/dispatch 0.17.2 → 0.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/actions/provider-api-catalog.d.ts +1 -1
- package/dist/actions/provider-api-register.d.ts +11 -11
- package/dist/client/transactional-emails.d.ts +57 -0
- package/dist/client/transactional-emails.d.ts.map +1 -0
- package/dist/client/transactional-emails.js +116 -0
- package/dist/client/transactional-emails.js.map +1 -0
- package/dist/components/layout/Layout.d.ts.map +1 -1
- package/dist/components/layout/Layout.js +8 -1
- package/dist/components/layout/Layout.js.map +1 -1
- package/dist/components/transactional-email-activity.d.ts +24 -0
- package/dist/components/transactional-email-activity.d.ts.map +1 -0
- package/dist/components/transactional-email-activity.js +31 -0
- package/dist/components/transactional-email-activity.js.map +1 -0
- package/dist/components/transactional-email-metrics.d.ts +40 -0
- package/dist/components/transactional-email-metrics.d.ts.map +1 -0
- package/dist/components/transactional-email-metrics.js +44 -0
- package/dist/components/transactional-email-metrics.js.map +1 -0
- package/dist/components/transactional-email-preview.d.ts +7 -0
- package/dist/components/transactional-email-preview.d.ts.map +1 -0
- package/dist/components/transactional-email-preview.js +41 -0
- package/dist/components/transactional-email-preview.js.map +1 -0
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/routes/index.js +2 -0
- package/dist/routes/index.js.map +1 -1
- package/dist/routes/pages/transactional-email.$appId.$id.d.ts +5 -0
- package/dist/routes/pages/transactional-email.$appId.$id.d.ts.map +1 -0
- package/dist/routes/pages/transactional-email.$appId.$id.js +144 -0
- package/dist/routes/pages/transactional-email.$appId.$id.js.map +1 -0
- package/dist/routes/pages/transactional-email.d.ts +5 -0
- package/dist/routes/pages/transactional-email.d.ts.map +1 -0
- package/dist/routes/pages/transactional-email.js +139 -0
- package/dist/routes/pages/transactional-email.js.map +1 -0
- package/dist/server/lib/app-creation-store.d.ts.map +1 -1
- package/dist/server/lib/app-creation-store.js +1 -1
- package/dist/server/lib/app-creation-store.js.map +1 -1
- package/dist/server/lib/provider-api.d.ts +2 -2
- package/dist/server/lib/provider-api.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/client/transactional-emails.spec.ts +91 -0
- package/src/client/transactional-emails.ts +201 -0
- package/src/components/layout/Layout.tsx +8 -0
- package/src/components/transactional-email-activity.tsx +102 -0
- package/src/components/transactional-email-metrics.tsx +127 -0
- package/src/components/transactional-email-preview.tsx +88 -0
- package/src/routes/index.ts +5 -0
- package/src/routes/pages/transactional-email.$appId.$id.tsx +362 -0
- package/src/routes/pages/transactional-email.tsx +571 -0
- package/src/server/lib/app-creation-store.ts +2 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transactional-email-activity.d.ts","sourceRoot":"","sources":["../../src/components/transactional-email-activity.tsx"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAY3E,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;CACvB;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,EAC5B,MAAM,EACN,SAAS,EACT,OAAO,EACP,KAAK,EACL,OAAO,GACR,EAAE;IACD,MAAM,EAAE,qBAAqB,CAAC,kBAAkB,EAAE,CAAC,GAAG,SAAS,CAAC;IAChE,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;IACf,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB,0DAyDA"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useT } from "@agent-native/core/client/i18n";
|
|
3
|
+
import { IconInfoCircle } from "@tabler/icons-react";
|
|
4
|
+
import { ActionQueryError } from "./action-query-error.js";
|
|
5
|
+
import { Alert, AlertDescription, AlertTitle } from "./ui/alert.js";
|
|
6
|
+
import { Skeleton } from "./ui/skeleton.js";
|
|
7
|
+
import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow, } from "./ui/table.js";
|
|
8
|
+
/**
|
|
9
|
+
* Renders one email's activity feed. Used both inline on the detail page and
|
|
10
|
+
* inside the list page's activity dialog, so the "unavailable" and "empty"
|
|
11
|
+
* states only need to be written once.
|
|
12
|
+
*/
|
|
13
|
+
export function ActivityTable({ result, isLoading, isError, error, onRetry, }) {
|
|
14
|
+
const t = useT();
|
|
15
|
+
if (isError) {
|
|
16
|
+
return _jsx(ActionQueryError, { error: error, onRetry: onRetry });
|
|
17
|
+
}
|
|
18
|
+
if (isLoading) {
|
|
19
|
+
return _jsx(Skeleton, { className: "h-40 w-full" });
|
|
20
|
+
}
|
|
21
|
+
if (result && !result.available) {
|
|
22
|
+
return (_jsxs(Alert, { children: [_jsx(IconInfoCircle, { className: "size-4" }), _jsx(AlertTitle, { children: t("dispatch.transactionalEmail.activityUnavailable") }), _jsx(AlertDescription, { children: result.reason })] }));
|
|
23
|
+
}
|
|
24
|
+
if (result && result.data.length === 0) {
|
|
25
|
+
return (_jsx("div", { className: "rounded-xl border border-dashed px-4 py-8 text-sm text-muted-foreground", children: t("dispatch.transactionalEmail.activityEmpty") }));
|
|
26
|
+
}
|
|
27
|
+
if (!result)
|
|
28
|
+
return null;
|
|
29
|
+
return (_jsx("div", { className: "max-h-96 overflow-auto", children: _jsxs(Table, { children: [_jsx(TableHeader, { children: _jsxs(TableRow, { children: [_jsx(TableHead, { children: t("dispatch.transactionalEmail.recipient") }), _jsx(TableHead, { children: t("dispatch.transactionalEmail.subject") }), _jsx(TableHead, { children: t("dispatch.transactionalEmail.status") }), _jsx(TableHead, { children: t("dispatch.transactionalEmail.opens") }), _jsx(TableHead, { children: t("dispatch.transactionalEmail.lastEvent") })] }) }), _jsx(TableBody, { children: result.data.map((entry) => (_jsxs(TableRow, { children: [_jsx(TableCell, { className: "text-xs", children: entry.toEmail }), _jsx(TableCell, { className: "text-xs", children: entry.subject }), _jsx(TableCell, { className: "text-xs", children: entry.status }), _jsx(TableCell, { className: "text-xs tabular-nums", children: entry.opensCount }), _jsx(TableCell, { className: "text-xs", children: entry.lastEventTime })] }, entry.msgId))) })] }) }));
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=transactional-email-activity.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transactional-email-activity.js","sourceRoot":"","sources":["../../src/components/transactional-email-activity.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,gCAAgC,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD,OAAO,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAExD,OAAO,EAAE,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACjE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,EACL,KAAK,EACL,SAAS,EACT,SAAS,EACT,SAAS,EACT,WAAW,EACX,QAAQ,GACT,MAAM,YAAY,CAAC;AAapB;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,EAC5B,MAAM,EACN,SAAS,EACT,OAAO,EACP,KAAK,EACL,OAAO,GAOR;IACC,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC;IAEjB,IAAI,OAAO,EAAE,CAAC;QACZ,OAAO,KAAC,gBAAgB,IAAC,KAAK,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,GAAI,CAAC;IAC9D,CAAC;IACD,IAAI,SAAS,EAAE,CAAC;QACd,OAAO,KAAC,QAAQ,IAAC,SAAS,EAAC,aAAa,GAAG,CAAC;IAC9C,CAAC;IACD,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,CAAC;QAChC,OAAO,CACL,MAAC,KAAK,eACJ,KAAC,cAAc,IAAC,SAAS,EAAC,QAAQ,GAAG,EACrC,KAAC,UAAU,cACR,CAAC,CAAC,iDAAiD,CAAC,GAC1C,EACb,KAAC,gBAAgB,cAAE,MAAM,CAAC,MAAM,GAAoB,IAC9C,CACT,CAAC;IACJ,CAAC;IACD,IAAI,MAAM,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACvC,OAAO,CACL,cAAK,SAAS,EAAC,yEAAyE,YACrF,CAAC,CAAC,2CAA2C,CAAC,GAC3C,CACP,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IAEzB,OAAO,CACL,cAAK,SAAS,EAAC,wBAAwB,YACrC,MAAC,KAAK,eACJ,KAAC,WAAW,cACV,MAAC,QAAQ,eACP,KAAC,SAAS,cAAE,CAAC,CAAC,uCAAuC,CAAC,GAAa,EACnE,KAAC,SAAS,cAAE,CAAC,CAAC,qCAAqC,CAAC,GAAa,EACjE,KAAC,SAAS,cAAE,CAAC,CAAC,oCAAoC,CAAC,GAAa,EAChE,KAAC,SAAS,cAAE,CAAC,CAAC,mCAAmC,CAAC,GAAa,EAC/D,KAAC,SAAS,cAAE,CAAC,CAAC,uCAAuC,CAAC,GAAa,IAC1D,GACC,EACd,KAAC,SAAS,cACP,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAC1B,MAAC,QAAQ,eACP,KAAC,SAAS,IAAC,SAAS,EAAC,SAAS,YAAE,KAAK,CAAC,OAAO,GAAa,EAC1D,KAAC,SAAS,IAAC,SAAS,EAAC,SAAS,YAAE,KAAK,CAAC,OAAO,GAAa,EAC1D,KAAC,SAAS,IAAC,SAAS,EAAC,SAAS,YAAE,KAAK,CAAC,MAAM,GAAa,EACzD,KAAC,SAAS,IAAC,SAAS,EAAC,sBAAsB,YACxC,KAAK,CAAC,UAAU,GACP,EACZ,KAAC,SAAS,IAAC,SAAS,EAAC,SAAS,YAAE,KAAK,CAAC,aAAa,GAAa,KAPnD,KAAK,CAAC,KAAK,CAQf,CACZ,CAAC,GACQ,IACN,GACJ,CACP,CAAC;AACJ,CAAC","sourcesContent":["import { useT } from \"@agent-native/core/client/i18n\";\nimport { IconInfoCircle } from \"@tabler/icons-react\";\n\nimport { ActionQueryError } from \"./action-query-error\";\nimport { type ProviderMetricsResult } from \"./transactional-email-metrics\";\nimport { Alert, AlertDescription, AlertTitle } from \"./ui/alert\";\nimport { Skeleton } from \"./ui/skeleton\";\nimport {\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeader,\n TableRow,\n} from \"./ui/table\";\n\nexport interface EmailActivityEntry {\n msgId: string;\n toEmail: string;\n fromEmail: string;\n subject: string;\n status: string;\n opensCount: number;\n clicksCount: number;\n lastEventTime: string;\n}\n\n/**\n * Renders one email's activity feed. Used both inline on the detail page and\n * inside the list page's activity dialog, so the \"unavailable\" and \"empty\"\n * states only need to be written once.\n */\nexport function ActivityTable({\n result,\n isLoading,\n isError,\n error,\n onRetry,\n}: {\n result: ProviderMetricsResult<EmailActivityEntry[]> | undefined;\n isLoading: boolean;\n isError: boolean;\n error: unknown;\n onRetry: () => void;\n}) {\n const t = useT();\n\n if (isError) {\n return <ActionQueryError error={error} onRetry={onRetry} />;\n }\n if (isLoading) {\n return <Skeleton className=\"h-40 w-full\" />;\n }\n if (result && !result.available) {\n return (\n <Alert>\n <IconInfoCircle className=\"size-4\" />\n <AlertTitle>\n {t(\"dispatch.transactionalEmail.activityUnavailable\")}\n </AlertTitle>\n <AlertDescription>{result.reason}</AlertDescription>\n </Alert>\n );\n }\n if (result && result.data.length === 0) {\n return (\n <div className=\"rounded-xl border border-dashed px-4 py-8 text-sm text-muted-foreground\">\n {t(\"dispatch.transactionalEmail.activityEmpty\")}\n </div>\n );\n }\n if (!result) return null;\n\n return (\n <div className=\"max-h-96 overflow-auto\">\n <Table>\n <TableHeader>\n <TableRow>\n <TableHead>{t(\"dispatch.transactionalEmail.recipient\")}</TableHead>\n <TableHead>{t(\"dispatch.transactionalEmail.subject\")}</TableHead>\n <TableHead>{t(\"dispatch.transactionalEmail.status\")}</TableHead>\n <TableHead>{t(\"dispatch.transactionalEmail.opens\")}</TableHead>\n <TableHead>{t(\"dispatch.transactionalEmail.lastEvent\")}</TableHead>\n </TableRow>\n </TableHeader>\n <TableBody>\n {result.data.map((entry) => (\n <TableRow key={entry.msgId}>\n <TableCell className=\"text-xs\">{entry.toEmail}</TableCell>\n <TableCell className=\"text-xs\">{entry.subject}</TableCell>\n <TableCell className=\"text-xs\">{entry.status}</TableCell>\n <TableCell className=\"text-xs tabular-nums\">\n {entry.opensCount}\n </TableCell>\n <TableCell className=\"text-xs\">{entry.lastEventTime}</TableCell>\n </TableRow>\n ))}\n </TableBody>\n </Table>\n </div>\n );\n}\n"]}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared between the transactional email list and detail pages so both
|
|
3
|
+
* render the same "unknown vs. zero" and provider-availability rules.
|
|
4
|
+
*/
|
|
5
|
+
export interface EmailEngagement {
|
|
6
|
+
templateId: string;
|
|
7
|
+
delivered: number;
|
|
8
|
+
uniqueOpens: number;
|
|
9
|
+
uniqueClicks: number;
|
|
10
|
+
/** null when nothing was delivered in the window, so there is no rate yet. */
|
|
11
|
+
openRate: number | null;
|
|
12
|
+
}
|
|
13
|
+
export type ProviderMetricsResult<T> = {
|
|
14
|
+
available: true;
|
|
15
|
+
data: T;
|
|
16
|
+
} | {
|
|
17
|
+
available: false;
|
|
18
|
+
reason: string;
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Renders a metric the backend could not read. "Unknown" and "zero" must stay
|
|
22
|
+
* visibly different — a dash with a reason is the only honest rendering.
|
|
23
|
+
*/
|
|
24
|
+
export declare function UnknownMetric({ reason }: {
|
|
25
|
+
reason: string;
|
|
26
|
+
}): import("node_modules/@types/react").JSX.Element;
|
|
27
|
+
export declare function SendsCell({ sent, failed, }: {
|
|
28
|
+
sent: number | null;
|
|
29
|
+
failed: number | null;
|
|
30
|
+
}): import("node_modules/@types/react").JSX.Element;
|
|
31
|
+
export declare function OpenRateCell({ engagement, unavailableReason, loading, }: {
|
|
32
|
+
engagement: EmailEngagement | undefined;
|
|
33
|
+
unavailableReason: string | null;
|
|
34
|
+
loading: boolean;
|
|
35
|
+
}): import("node_modules/@types/react").JSX.Element;
|
|
36
|
+
export declare function LastSentCell({ lastSentAt, sent, }: {
|
|
37
|
+
lastSentAt: number | null;
|
|
38
|
+
sent: number | null;
|
|
39
|
+
}): import("node_modules/@types/react").JSX.Element;
|
|
40
|
+
//# sourceMappingURL=transactional-email-metrics.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transactional-email-metrics.d.ts","sourceRoot":"","sources":["../../src/components/transactional-email-metrics.tsx"],"names":[],"mappings":"AAKA;;;GAGG;AAEH,MAAM,WAAW,eAAe;IAC9B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,8EAA8E;IAC9E,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;CACzB;AAED,MAAM,MAAM,qBAAqB,CAAC,CAAC,IAC/B;IAAE,SAAS,EAAE,IAAI,CAAC;IAAC,IAAI,EAAE,CAAC,CAAA;CAAE,GAC5B;IAAE,SAAS,EAAE,KAAK,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzC;;;GAGG;AACH,wBAAgB,aAAa,CAAC,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,MAAM,CAAA;CAAE,mDAW3D;AAED,wBAAgB,SAAS,CAAC,EACxB,IAAI,EACJ,MAAM,GACP,EAAE;IACD,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB,mDAsBA;AAED,wBAAgB,YAAY,CAAC,EAC3B,UAAU,EACV,iBAAiB,EACjB,OAAO,GACR,EAAE;IACD,UAAU,EAAE,eAAe,GAAG,SAAS,CAAC;IACxC,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,OAAO,EAAE,OAAO,CAAC;CAClB,mDAyBA;AAED,wBAAgB,YAAY,CAAC,EAC3B,UAAU,EACV,IAAI,GACL,EAAE;IACD,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;CACrB,mDAeA"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useT } from "@agent-native/core/client/i18n";
|
|
3
|
+
import { Skeleton } from "./ui/skeleton.js";
|
|
4
|
+
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip.js";
|
|
5
|
+
/**
|
|
6
|
+
* Renders a metric the backend could not read. "Unknown" and "zero" must stay
|
|
7
|
+
* visibly different — a dash with a reason is the only honest rendering.
|
|
8
|
+
*/
|
|
9
|
+
export function UnknownMetric({ reason }) {
|
|
10
|
+
return (_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("span", { className: "cursor-help text-muted-foreground underline decoration-dotted underline-offset-2", children: "\u2014" }) }), _jsx(TooltipContent, { className: "max-w-64", children: reason })] }));
|
|
11
|
+
}
|
|
12
|
+
export function SendsCell({ sent, failed, }) {
|
|
13
|
+
const t = useT();
|
|
14
|
+
if (sent === null) {
|
|
15
|
+
return (_jsx(UnknownMetric, { reason: t("dispatch.transactionalEmail.sendLogUnread") }));
|
|
16
|
+
}
|
|
17
|
+
return (_jsxs("span", { className: "tabular-nums", children: [sent, failed !== null && failed > 0 ? (_jsx("span", { className: "ml-2 text-xs text-destructive", children: t("dispatch.transactionalEmail.failedCount", { count: failed }) })) : null, failed === null ? (_jsx("span", { className: "ml-2 text-xs text-muted-foreground", children: t("dispatch.transactionalEmail.failuresUnknown") })) : null] }));
|
|
18
|
+
}
|
|
19
|
+
export function OpenRateCell({ engagement, unavailableReason, loading, }) {
|
|
20
|
+
const t = useT();
|
|
21
|
+
if (unavailableReason) {
|
|
22
|
+
return _jsx(UnknownMetric, { reason: unavailableReason });
|
|
23
|
+
}
|
|
24
|
+
if (loading)
|
|
25
|
+
return _jsx(Skeleton, { className: "h-4 w-10" });
|
|
26
|
+
if (!engagement) {
|
|
27
|
+
return (_jsx(UnknownMetric, { reason: t("dispatch.transactionalEmail.noProviderRecord") }));
|
|
28
|
+
}
|
|
29
|
+
if (engagement.openRate === null) {
|
|
30
|
+
return (_jsx("span", { className: "text-xs text-muted-foreground", children: t("dispatch.transactionalEmail.noDeliveredMail") }));
|
|
31
|
+
}
|
|
32
|
+
return (_jsx("span", { className: "tabular-nums", children: `${(engagement.openRate * 100).toFixed(1)}%` }));
|
|
33
|
+
}
|
|
34
|
+
export function LastSentCell({ lastSentAt, sent, }) {
|
|
35
|
+
const t = useT();
|
|
36
|
+
if (lastSentAt !== null) {
|
|
37
|
+
return _jsx(_Fragment, { children: new Date(lastSentAt).toLocaleString() });
|
|
38
|
+
}
|
|
39
|
+
if (sent === 0) {
|
|
40
|
+
return (_jsx("span", { className: "text-xs text-muted-foreground", children: t("dispatch.transactionalEmail.neverSent") }));
|
|
41
|
+
}
|
|
42
|
+
return (_jsx(UnknownMetric, { reason: t("dispatch.transactionalEmail.lastSentUnknown") }));
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=transactional-email-metrics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transactional-email-metrics.js","sourceRoot":"","sources":["../../src/components/transactional-email-metrics.tsx"],"names":[],"mappings":"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","sourcesContent":["import { useT } from \"@agent-native/core/client/i18n\";\n\nimport { Skeleton } from \"./ui/skeleton\";\nimport { Tooltip, TooltipContent, TooltipTrigger } from \"./ui/tooltip\";\n\n/**\n * Shared between the transactional email list and detail pages so both\n * render the same \"unknown vs. zero\" and provider-availability rules.\n */\n\nexport interface EmailEngagement {\n templateId: string;\n delivered: number;\n uniqueOpens: number;\n uniqueClicks: number;\n /** null when nothing was delivered in the window, so there is no rate yet. */\n openRate: number | null;\n}\n\nexport type ProviderMetricsResult<T> =\n | { available: true; data: T }\n | { available: false; reason: string };\n\n/**\n * Renders a metric the backend could not read. \"Unknown\" and \"zero\" must stay\n * visibly different — a dash with a reason is the only honest rendering.\n */\nexport function UnknownMetric({ reason }: { reason: string }) {\n return (\n <Tooltip>\n <TooltipTrigger asChild>\n <span className=\"cursor-help text-muted-foreground underline decoration-dotted underline-offset-2\">\n —\n </span>\n </TooltipTrigger>\n <TooltipContent className=\"max-w-64\">{reason}</TooltipContent>\n </Tooltip>\n );\n}\n\nexport function SendsCell({\n sent,\n failed,\n}: {\n sent: number | null;\n failed: number | null;\n}) {\n const t = useT();\n if (sent === null) {\n return (\n <UnknownMetric reason={t(\"dispatch.transactionalEmail.sendLogUnread\")} />\n );\n }\n return (\n <span className=\"tabular-nums\">\n {sent}\n {failed !== null && failed > 0 ? (\n <span className=\"ml-2 text-xs text-destructive\">\n {t(\"dispatch.transactionalEmail.failedCount\", { count: failed })}\n </span>\n ) : null}\n {failed === null ? (\n <span className=\"ml-2 text-xs text-muted-foreground\">\n {t(\"dispatch.transactionalEmail.failuresUnknown\")}\n </span>\n ) : null}\n </span>\n );\n}\n\nexport function OpenRateCell({\n engagement,\n unavailableReason,\n loading,\n}: {\n engagement: EmailEngagement | undefined;\n unavailableReason: string | null;\n loading: boolean;\n}) {\n const t = useT();\n if (unavailableReason) {\n return <UnknownMetric reason={unavailableReason} />;\n }\n if (loading) return <Skeleton className=\"h-4 w-10\" />;\n if (!engagement) {\n return (\n <UnknownMetric\n reason={t(\"dispatch.transactionalEmail.noProviderRecord\")}\n />\n );\n }\n if (engagement.openRate === null) {\n return (\n <span className=\"text-xs text-muted-foreground\">\n {t(\"dispatch.transactionalEmail.noDeliveredMail\")}\n </span>\n );\n }\n return (\n <span className=\"tabular-nums\">\n {`${(engagement.openRate * 100).toFixed(1)}%`}\n </span>\n );\n}\n\nexport function LastSentCell({\n lastSentAt,\n sent,\n}: {\n lastSentAt: number | null;\n sent: number | null;\n}) {\n const t = useT();\n if (lastSentAt !== null) {\n return <>{new Date(lastSentAt).toLocaleString()}</>;\n }\n if (sent === 0) {\n return (\n <span className=\"text-xs text-muted-foreground\">\n {t(\"dispatch.transactionalEmail.neverSent\")}\n </span>\n );\n }\n return (\n <UnknownMetric reason={t(\"dispatch.transactionalEmail.lastSentUnknown\")} />\n );\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transactional-email-preview.d.ts","sourceRoot":"","sources":["../../src/components/transactional-email-preview.tsx"],"names":[],"mappings":"AAkCA,wBAAgB,gBAAgB,CAAC,EAC/B,KAAK,EACL,OAAO,EACP,EAAE,EACF,IAAI,GACL,EAAE;IACD,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;CACd,mDA2CA"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useActionQuery } from "@agent-native/core/client/hooks";
|
|
3
|
+
import { useT } from "@agent-native/core/client/i18n";
|
|
4
|
+
import { IconAlertTriangle } from "@tabler/icons-react";
|
|
5
|
+
import { useQuery } from "@tanstack/react-query";
|
|
6
|
+
import { fetchEmailPreview, } from "../client/transactional-emails.js";
|
|
7
|
+
import { Alert, AlertDescription, AlertTitle } from "./ui/alert.js";
|
|
8
|
+
import { Skeleton } from "./ui/skeleton.js";
|
|
9
|
+
/**
|
|
10
|
+
* The "core" app id means the email was read from Dispatch's own local
|
|
11
|
+
* catalog (see transactional-email.tsx), not a cross-app fetch, so its
|
|
12
|
+
* preview must render the same way — Dispatch always has the definition
|
|
13
|
+
* registered locally and a cross-app fetch would have no real appPath to hit.
|
|
14
|
+
*/
|
|
15
|
+
function useEmailPreviewQuery(appId, appPath, id) {
|
|
16
|
+
const isCore = appId === "core";
|
|
17
|
+
const local = useActionQuery("render-transactional-email-preview", { id }, { enabled: isCore, retry: false });
|
|
18
|
+
const remote = useQuery({
|
|
19
|
+
queryKey: ["transactional-email-preview", appPath, id],
|
|
20
|
+
queryFn: () => fetchEmailPreview(appPath, id),
|
|
21
|
+
enabled: !isCore,
|
|
22
|
+
retry: false,
|
|
23
|
+
});
|
|
24
|
+
return isCore ? local : remote;
|
|
25
|
+
}
|
|
26
|
+
export function EmailPreviewPane({ appId, appPath, id, name, }) {
|
|
27
|
+
const t = useT();
|
|
28
|
+
const preview = useEmailPreviewQuery(appId, appPath, id);
|
|
29
|
+
if (preview.isError) {
|
|
30
|
+
return (_jsxs(Alert, { variant: "destructive", children: [_jsx(IconAlertTriangle, { className: "size-4" }), _jsx(AlertTitle, { children: t("dispatch.transactionalEmail.previewFailed") }), _jsx(AlertDescription, { children: preview.error instanceof Error
|
|
31
|
+
? preview.error.message
|
|
32
|
+
: String(preview.error) })] }));
|
|
33
|
+
}
|
|
34
|
+
if (preview.isLoading || !preview.data) {
|
|
35
|
+
return _jsx(Skeleton, { className: "h-96 w-full" });
|
|
36
|
+
}
|
|
37
|
+
return (_jsxs("div", { className: "space-y-3", children: [_jsxs("div", { children: [_jsx("div", { className: "text-xs text-muted-foreground", children: t("dispatch.transactionalEmail.subject") }), _jsx("div", { className: "text-sm font-medium text-foreground", children: preview.data.subject })] }), _jsx("iframe", { title: t("dispatch.transactionalEmail.previewFrameTitle", { name }), sandbox: "", srcDoc: preview.data.html,
|
|
38
|
+
// guard:allow-raw-color — the frame previews email HTML, which renders on white in mail clients regardless of app theme.
|
|
39
|
+
className: "h-96 w-full rounded-xl border bg-white" })] }));
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=transactional-email-preview.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transactional-email-preview.js","sourceRoot":"","sources":["../../src/components/transactional-email-preview.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AACjE,OAAO,EAAE,IAAI,EAAE,MAAM,gCAAgC,CAAC;AACtD,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD,OAAO,EACL,iBAAiB,GAElB,MAAM,gCAAgC,CAAC;AACxC,OAAO,EAAE,KAAK,EAAE,gBAAgB,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AACjE,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAEzC;;;;;GAKG;AACH,SAAS,oBAAoB,CAAC,KAAa,EAAE,OAAe,EAAE,EAAU;IACtE,MAAM,MAAM,GAAG,KAAK,KAAK,MAAM,CAAC;IAChC,MAAM,KAAK,GAAG,cAAc,CAC1B,oCAAoC,EACpC,EAAE,EAAE,EAAE,EACN,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,CAClC,CAAC;IACF,MAAM,MAAM,GAAG,QAAQ,CAAC;QACtB,QAAQ,EAAE,CAAC,6BAA6B,EAAE,OAAO,EAAE,EAAE,CAAC;QACtD,OAAO,EAAE,GAAG,EAAE,CAAC,iBAAiB,CAAC,OAAO,EAAE,EAAE,CAAC;QAC7C,OAAO,EAAE,CAAC,MAAM;QAChB,KAAK,EAAE,KAAK;KACb,CAAC,CAAC;IACH,OAAO,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC;AACjC,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,EAC/B,KAAK,EACL,OAAO,EACP,EAAE,EACF,IAAI,GAML;IACC,MAAM,CAAC,GAAG,IAAI,EAAE,CAAC;IACjB,MAAM,OAAO,GAAG,oBAAoB,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE,CAAC,CAAC;IAEzD,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;QACpB,OAAO,CACL,MAAC,KAAK,IAAC,OAAO,EAAC,aAAa,aAC1B,KAAC,iBAAiB,IAAC,SAAS,EAAC,QAAQ,GAAG,EACxC,KAAC,UAAU,cACR,CAAC,CAAC,2CAA2C,CAAC,GACpC,EACb,KAAC,gBAAgB,cACd,OAAO,CAAC,KAAK,YAAY,KAAK;wBAC7B,CAAC,CAAC,OAAO,CAAC,KAAK,CAAC,OAAO;wBACvB,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,GACR,IACb,CACT,CAAC;IACJ,CAAC;IACD,IAAI,OAAO,CAAC,SAAS,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;QACvC,OAAO,KAAC,QAAQ,IAAC,SAAS,EAAC,aAAa,GAAG,CAAC;IAC9C,CAAC;IACD,OAAO,CACL,eAAK,SAAS,EAAC,WAAW,aACxB,0BACE,cAAK,SAAS,EAAC,+BAA+B,YAC3C,CAAC,CAAC,qCAAqC,CAAC,GACrC,EACN,cAAK,SAAS,EAAC,qCAAqC,YACjD,OAAO,CAAC,IAAI,CAAC,OAAO,GACjB,IACF,EAGN,iBACE,KAAK,EAAE,CAAC,CAAC,+CAA+C,EAAE,EAAE,IAAI,EAAE,CAAC,EACnE,OAAO,EAAC,EAAE,EACV,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,IAAI;gBACzB,yHAAyH;gBACzH,SAAS,EAAC,wCAAwC,GAClD,IACE,CACP,CAAC;AACJ,CAAC","sourcesContent":["import { useActionQuery } from \"@agent-native/core/client/hooks\";\nimport { useT } from \"@agent-native/core/client/i18n\";\nimport { IconAlertTriangle } from \"@tabler/icons-react\";\nimport { useQuery } from \"@tanstack/react-query\";\n\nimport {\n fetchEmailPreview,\n type EmailPreview,\n} from \"../client/transactional-emails\";\nimport { Alert, AlertDescription, AlertTitle } from \"./ui/alert\";\nimport { Skeleton } from \"./ui/skeleton\";\n\n/**\n * The \"core\" app id means the email was read from Dispatch's own local\n * catalog (see transactional-email.tsx), not a cross-app fetch, so its\n * preview must render the same way — Dispatch always has the definition\n * registered locally and a cross-app fetch would have no real appPath to hit.\n */\nfunction useEmailPreviewQuery(appId: string, appPath: string, id: string) {\n const isCore = appId === \"core\";\n const local = useActionQuery<EmailPreview>(\n \"render-transactional-email-preview\",\n { id },\n { enabled: isCore, retry: false },\n );\n const remote = useQuery({\n queryKey: [\"transactional-email-preview\", appPath, id],\n queryFn: () => fetchEmailPreview(appPath, id),\n enabled: !isCore,\n retry: false,\n });\n return isCore ? local : remote;\n}\n\nexport function EmailPreviewPane({\n appId,\n appPath,\n id,\n name,\n}: {\n appId: string;\n appPath: string;\n id: string;\n name: string;\n}) {\n const t = useT();\n const preview = useEmailPreviewQuery(appId, appPath, id);\n\n if (preview.isError) {\n return (\n <Alert variant=\"destructive\">\n <IconAlertTriangle className=\"size-4\" />\n <AlertTitle>\n {t(\"dispatch.transactionalEmail.previewFailed\")}\n </AlertTitle>\n <AlertDescription>\n {preview.error instanceof Error\n ? preview.error.message\n : String(preview.error)}\n </AlertDescription>\n </Alert>\n );\n }\n if (preview.isLoading || !preview.data) {\n return <Skeleton className=\"h-96 w-full\" />;\n }\n return (\n <div className=\"space-y-3\">\n <div>\n <div className=\"text-xs text-muted-foreground\">\n {t(\"dispatch.transactionalEmail.subject\")}\n </div>\n <div className=\"text-sm font-medium text-foreground\">\n {preview.data.subject}\n </div>\n </div>\n {/* sandbox=\"\" (no allow-scripts) keeps arbitrary email HTML from\n running script in the Dispatch origin. */}\n <iframe\n title={t(\"dispatch.transactionalEmail.previewFrameTitle\", { name })}\n sandbox=\"\"\n srcDoc={preview.data.html}\n // guard:allow-raw-color — the frame previews email HTML, which renders on white in mail clients regardless of app theme.\n className=\"h-96 w-full rounded-xl border bg-white\"\n />\n </div>\n );\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/routes/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,WAAW,EAAgB,MAAM,0BAA0B,CAAC;AAE1E;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,cAAc,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/routes/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,WAAW,EAAgB,MAAM,0BAA0B,CAAC;AAE1E;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,eAAO,MAAM,cAAc,EAAE,WAwC5B,CAAC"}
|
package/dist/routes/index.js
CHANGED
|
@@ -46,6 +46,8 @@ export const dispatchRoutes = [
|
|
|
46
46
|
route("workspace", "./pages/workspace.js"),
|
|
47
47
|
route("messaging", "./pages/messaging.js"),
|
|
48
48
|
route("destinations", "./pages/destinations.js"),
|
|
49
|
+
route("transactional-email", "./pages/transactional-email.js"),
|
|
50
|
+
route("transactional-email/:appId/:id", "./pages/transactional-email.$appId.$id.js"),
|
|
49
51
|
route("identities", "./pages/identities.js"),
|
|
50
52
|
route("approval", "./pages/approval.js"),
|
|
51
53
|
route("approvals", "./pages/approvals.js"),
|
package/dist/routes/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/routes/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AAE1E;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,CAAC,MAAM,cAAc,GAAgB;IACzC,KAAK,CAAC,mBAAmB,CAAC;IAC1B,KAAK,CAAC,iBAAiB,EAAE,4BAA4B,CAAC;IACtD,KAAK,CAAC,cAAc,EAAE,yBAAyB,CAAC;IAChD,KAAK,CAAC,MAAM,EAAE,iBAAiB,CAAC;IAChC,KAAK,CAAC,gBAAgB,EAAE,iBAAiB,CAAC;IAC1C,KAAK,CAAC,UAAU,EAAE,qBAAqB,CAAC;IACxC,KAAK,CAAC,SAAS,EAAE,oBAAoB,CAAC;IACtC,KAAK,CAAC,YAAY,EAAE,uBAAuB,CAAC;IAC5C,KAAK,CAAC,MAAM,EAAE,iBAAiB,CAAC;IAChC,KAAK,CAAC,aAAa,EAAE,wBAAwB,CAAC;IAC9C,KAAK,CAAC,SAAS,EAAE,oBAAoB,CAAC;IACtC,KAAK,CAAC,OAAO,EAAE,kBAAkB,CAAC;IAClC,KAAK,CAAC,cAAc,EAAE,yBAAyB,CAAC;IAChD,KAAK,CAAC,QAAQ,EAAE,mBAAmB,CAAC;IACpC,KAAK,CAAC,WAAW,EAAE,sBAAsB,CAAC;IAC1C,KAAK,CAAC,WAAW,EAAE,sBAAsB,CAAC;IAC1C,KAAK,CAAC,cAAc,EAAE,yBAAyB,CAAC;IAChD,KAAK,CAAC,YAAY,EAAE,uBAAuB,CAAC;IAC5C,KAAK,CAAC,UAAU,EAAE,qBAAqB,CAAC;IACxC,KAAK,CAAC,WAAW,EAAE,sBAAsB,CAAC;IAC1C,KAAK,CAAC,aAAa,EAAE,wBAAwB,CAAC;IAC9C,KAAK,CAAC,OAAO,EAAE,kBAAkB,CAAC;IAClC,KAAK,CAAC,UAAU,EAAE,qBAAqB,CAAC;IACxC,KAAK,CAAC,QAAQ,EAAE,mBAAmB,CAAC;IACpC,KAAK,CAAC,cAAc,EAAE,yBAAyB,CAAC;IAChD,KAAK,CAAC,MAAM,EAAE,iBAAiB,CAAC;IAChC,KAAK,CAAC,YAAY,EAAE,8BAA8B,CAAC;IACnD,KAAK,CAAC,gBAAgB,EAAE,2BAA2B,CAAC;IACpD,KAAK,CAAC,sBAAsB,EAAE,2BAA2B,CAAC;IAC1D,yEAAyE;IACzE,0EAA0E;IAC1E,2EAA2E;IAC3E,wEAAwE;IACxE,KAAK,CAAC,QAAQ,EAAE,mBAAmB,CAAC;CACrC,CAAC","sourcesContent":["import { type RouteConfig, route, index } from \"@react-router/dev/routes\";\n\n/**\n * Dispatch's routes as a programmatic `RouteConfig[]`. Splat into the\n * consumer's `app/routes.ts`:\n *\n * ```ts\n * import { type RouteConfig } from \"@react-router/dev/routes\";\n * import { dispatchRoutes } from \"@agent-native/dispatch/routes\";\n *\n * export default [\n * ...localRoutes, // consumer's own routes win on collision\n * ...dispatchRoutes, // dispatch fills in everything else\n * ] satisfies RouteConfig;\n * ```\n *\n * Route precedence: React Router 7 matches in declaration order, so\n * placing `dispatchRoutes` LAST means consumer-defined routes with the\n * same path take precedence. To override a single dispatch route, define\n * it in your local routes; to keep it, omit it.\n *\n * The `file` paths below resolve relative to this file at runtime — they\n * point into `packages/dispatch/dist/routes/pages/*.js` after build.\n *\n * Naming maps the original flatRoutes file conventions:\n * `_index.tsx` → `index(...)`\n * `<name>.tsx` → `route(\"<name>\", ...)`\n * `<a>.$<param>.tsx` → `route(\"<a>/:<param>\", ...)`\n * `<a>._index.tsx` → flattened as `route(\"<a>\", ...)` (workspace\n * versions are bare and don't wrap a parent layout)\n */\nexport const dispatchRoutes: RouteConfig = [\n index(\"./pages/_index.js\"),\n route(\"browser-connect\", \"./pages/browser-connect.js\"),\n route(\"browser-chat\", \"./pages/browser-chat.js\"),\n route(\"chat\", \"./pages/chat.js\"),\n route(\"chat/:threadId\", \"./pages/chat.js\"),\n route(\"overview\", \"./pages/overview.js\"),\n route(\"metrics\", \"./pages/metrics.js\"),\n route(\"operations\", \"./pages/operations.js\"),\n route(\"apps\", \"./pages/apps.js\"),\n route(\"apps/:appId\", \"./pages/apps.$appId.js\"),\n route(\"new-app\", \"./pages/new-app.js\"),\n route(\"vault\", \"./pages/vault.js\"),\n route(\"integrations\", \"./pages/integrations.js\"),\n route(\"agents\", \"./pages/agents.js\"),\n route(\"workspace\", \"./pages/workspace.js\"),\n route(\"messaging\", \"./pages/messaging.js\"),\n route(\"destinations\", \"./pages/destinations.js\"),\n route(\"identities\", \"./pages/identities.js\"),\n route(\"approval\", \"./pages/approval.js\"),\n route(\"approvals\", \"./pages/approvals.js\"),\n route(\"automations\", \"./pages/automations.js\"),\n route(\"audit\", \"./pages/audit.js\"),\n route(\"settings\", \"./pages/settings.js\"),\n route(\"dreams\", \"./pages/dreams.js\"),\n route(\"thread-debug\", \"./pages/thread-debug.js\"),\n route(\"team\", \"./pages/team.js\"),\n route(\"extensions\", \"./pages/extensions._index.js\"),\n route(\"extensions/:id\", \"./pages/extensions.$id.js\"),\n route(\"extensions/:id/:slug\", \"./pages/extensions.$id.js\"),\n // Catch-all for /:appId — bounces /dispatch/<appId> to /<appId> when the\n // segment names a workspace app sibling (e.g. Builder.io routing a \"go to\n // /todo\" call through Dispatch's mount). Declared last so React Router 7's\n // specificity ranking still matches explicit static routes above first.\n route(\":appId\", \"./pages/$appId.js\"),\n];\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/routes/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAoB,KAAK,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AAE1E;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,CAAC,MAAM,cAAc,GAAgB;IACzC,KAAK,CAAC,mBAAmB,CAAC;IAC1B,KAAK,CAAC,iBAAiB,EAAE,4BAA4B,CAAC;IACtD,KAAK,CAAC,cAAc,EAAE,yBAAyB,CAAC;IAChD,KAAK,CAAC,MAAM,EAAE,iBAAiB,CAAC;IAChC,KAAK,CAAC,gBAAgB,EAAE,iBAAiB,CAAC;IAC1C,KAAK,CAAC,UAAU,EAAE,qBAAqB,CAAC;IACxC,KAAK,CAAC,SAAS,EAAE,oBAAoB,CAAC;IACtC,KAAK,CAAC,YAAY,EAAE,uBAAuB,CAAC;IAC5C,KAAK,CAAC,MAAM,EAAE,iBAAiB,CAAC;IAChC,KAAK,CAAC,aAAa,EAAE,wBAAwB,CAAC;IAC9C,KAAK,CAAC,SAAS,EAAE,oBAAoB,CAAC;IACtC,KAAK,CAAC,OAAO,EAAE,kBAAkB,CAAC;IAClC,KAAK,CAAC,cAAc,EAAE,yBAAyB,CAAC;IAChD,KAAK,CAAC,QAAQ,EAAE,mBAAmB,CAAC;IACpC,KAAK,CAAC,WAAW,EAAE,sBAAsB,CAAC;IAC1C,KAAK,CAAC,WAAW,EAAE,sBAAsB,CAAC;IAC1C,KAAK,CAAC,cAAc,EAAE,yBAAyB,CAAC;IAChD,KAAK,CAAC,qBAAqB,EAAE,gCAAgC,CAAC;IAC9D,KAAK,CACH,gCAAgC,EAChC,2CAA2C,CAC5C;IACD,KAAK,CAAC,YAAY,EAAE,uBAAuB,CAAC;IAC5C,KAAK,CAAC,UAAU,EAAE,qBAAqB,CAAC;IACxC,KAAK,CAAC,WAAW,EAAE,sBAAsB,CAAC;IAC1C,KAAK,CAAC,aAAa,EAAE,wBAAwB,CAAC;IAC9C,KAAK,CAAC,OAAO,EAAE,kBAAkB,CAAC;IAClC,KAAK,CAAC,UAAU,EAAE,qBAAqB,CAAC;IACxC,KAAK,CAAC,QAAQ,EAAE,mBAAmB,CAAC;IACpC,KAAK,CAAC,cAAc,EAAE,yBAAyB,CAAC;IAChD,KAAK,CAAC,MAAM,EAAE,iBAAiB,CAAC;IAChC,KAAK,CAAC,YAAY,EAAE,8BAA8B,CAAC;IACnD,KAAK,CAAC,gBAAgB,EAAE,2BAA2B,CAAC;IACpD,KAAK,CAAC,sBAAsB,EAAE,2BAA2B,CAAC;IAC1D,yEAAyE;IACzE,0EAA0E;IAC1E,2EAA2E;IAC3E,wEAAwE;IACxE,KAAK,CAAC,QAAQ,EAAE,mBAAmB,CAAC;CACrC,CAAC","sourcesContent":["import { type RouteConfig, route, index } from \"@react-router/dev/routes\";\n\n/**\n * Dispatch's routes as a programmatic `RouteConfig[]`. Splat into the\n * consumer's `app/routes.ts`:\n *\n * ```ts\n * import { type RouteConfig } from \"@react-router/dev/routes\";\n * import { dispatchRoutes } from \"@agent-native/dispatch/routes\";\n *\n * export default [\n * ...localRoutes, // consumer's own routes win on collision\n * ...dispatchRoutes, // dispatch fills in everything else\n * ] satisfies RouteConfig;\n * ```\n *\n * Route precedence: React Router 7 matches in declaration order, so\n * placing `dispatchRoutes` LAST means consumer-defined routes with the\n * same path take precedence. To override a single dispatch route, define\n * it in your local routes; to keep it, omit it.\n *\n * The `file` paths below resolve relative to this file at runtime — they\n * point into `packages/dispatch/dist/routes/pages/*.js` after build.\n *\n * Naming maps the original flatRoutes file conventions:\n * `_index.tsx` → `index(...)`\n * `<name>.tsx` → `route(\"<name>\", ...)`\n * `<a>.$<param>.tsx` → `route(\"<a>/:<param>\", ...)`\n * `<a>._index.tsx` → flattened as `route(\"<a>\", ...)` (workspace\n * versions are bare and don't wrap a parent layout)\n */\nexport const dispatchRoutes: RouteConfig = [\n index(\"./pages/_index.js\"),\n route(\"browser-connect\", \"./pages/browser-connect.js\"),\n route(\"browser-chat\", \"./pages/browser-chat.js\"),\n route(\"chat\", \"./pages/chat.js\"),\n route(\"chat/:threadId\", \"./pages/chat.js\"),\n route(\"overview\", \"./pages/overview.js\"),\n route(\"metrics\", \"./pages/metrics.js\"),\n route(\"operations\", \"./pages/operations.js\"),\n route(\"apps\", \"./pages/apps.js\"),\n route(\"apps/:appId\", \"./pages/apps.$appId.js\"),\n route(\"new-app\", \"./pages/new-app.js\"),\n route(\"vault\", \"./pages/vault.js\"),\n route(\"integrations\", \"./pages/integrations.js\"),\n route(\"agents\", \"./pages/agents.js\"),\n route(\"workspace\", \"./pages/workspace.js\"),\n route(\"messaging\", \"./pages/messaging.js\"),\n route(\"destinations\", \"./pages/destinations.js\"),\n route(\"transactional-email\", \"./pages/transactional-email.js\"),\n route(\n \"transactional-email/:appId/:id\",\n \"./pages/transactional-email.$appId.$id.js\",\n ),\n route(\"identities\", \"./pages/identities.js\"),\n route(\"approval\", \"./pages/approval.js\"),\n route(\"approvals\", \"./pages/approvals.js\"),\n route(\"automations\", \"./pages/automations.js\"),\n route(\"audit\", \"./pages/audit.js\"),\n route(\"settings\", \"./pages/settings.js\"),\n route(\"dreams\", \"./pages/dreams.js\"),\n route(\"thread-debug\", \"./pages/thread-debug.js\"),\n route(\"team\", \"./pages/team.js\"),\n route(\"extensions\", \"./pages/extensions._index.js\"),\n route(\"extensions/:id\", \"./pages/extensions.$id.js\"),\n route(\"extensions/:id/:slug\", \"./pages/extensions.$id.js\"),\n // Catch-all for /:appId — bounces /dispatch/<appId> to /<appId> when the\n // segment names a workspace app sibling (e.g. Builder.io routing a \"go to\n // /todo\" call through Dispatch's mount). Declared last so React Router 7's\n // specificity ranking still matches explicit static routes above first.\n route(\":appId\", \"./pages/$appId.js\"),\n];\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transactional-email.$appId.$id.d.ts","sourceRoot":"","sources":["../../../src/routes/pages/transactional-email.$appId.$id.tsx"],"names":[],"mappings":"AAiCA,wBAAgB,IAAI;;IAEnB;AAwHD,MAAM,CAAC,OAAO,UAAU,6BAA6B,oDA8MpD"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useActionQuery } from "@agent-native/core/client/hooks";
|
|
3
|
+
import { useT } from "@agent-native/core/client/i18n";
|
|
4
|
+
import { IconAlertTriangle, IconArrowLeft } from "@tabler/icons-react";
|
|
5
|
+
import { useQueries, useQuery } from "@tanstack/react-query";
|
|
6
|
+
import { useMemo } from "react";
|
|
7
|
+
import { Link, useParams } from "react-router";
|
|
8
|
+
import { aggregateSharedEmails, callAppAction, fetchAppEmailCatalog, } from "../../client/transactional-emails.js";
|
|
9
|
+
import { ActionQueryError } from "../../components/action-query-error.js";
|
|
10
|
+
import { DispatchShell } from "../../components/dispatch-shell.js";
|
|
11
|
+
import { ActivityTable, } from "../../components/transactional-email-activity.js";
|
|
12
|
+
import { OpenRateCell, SendsCell, LastSentCell, } from "../../components/transactional-email-metrics.js";
|
|
13
|
+
import { EmailPreviewPane } from "../../components/transactional-email-preview.js";
|
|
14
|
+
import { Alert, AlertDescription, AlertTitle } from "../../components/ui/alert.js";
|
|
15
|
+
import { Button } from "../../components/ui/button.js";
|
|
16
|
+
import { Skeleton } from "../../components/ui/skeleton.js";
|
|
17
|
+
export function meta() {
|
|
18
|
+
return [{ title: "Transactional email detail — Dispatch" }];
|
|
19
|
+
}
|
|
20
|
+
const WINDOW_DAYS = 30;
|
|
21
|
+
const ACTIVITY_LIMIT = 50;
|
|
22
|
+
/**
|
|
23
|
+
* Resolves the one email this page shows, plus the app name/path to display.
|
|
24
|
+
* The "core" appId is Dispatch's own registry, not a cross-app fetch — see
|
|
25
|
+
* the same split in transactional-email.tsx.
|
|
26
|
+
*/
|
|
27
|
+
function useEmailDetail(appId, id) {
|
|
28
|
+
const isCore = appId === "core";
|
|
29
|
+
const t = useT();
|
|
30
|
+
const localQuery = useActionQuery("list-transactional-emails", { windowDays: WINDOW_DAYS }, { enabled: isCore });
|
|
31
|
+
const appsQuery = useActionQuery("list-workspace-apps", {
|
|
32
|
+
includeAgentCards: false,
|
|
33
|
+
});
|
|
34
|
+
const readyApps = useMemo(() => (appsQuery.data ?? []).filter((candidate) => candidate.status !== "pending"), [appsQuery.data]);
|
|
35
|
+
const app = useMemo(() => readyApps.find((candidate) => candidate.id === appId), [readyApps, appId]);
|
|
36
|
+
const coreCatalogQueries = useQueries({
|
|
37
|
+
queries: readyApps.map((candidate) => ({
|
|
38
|
+
queryKey: ["transactional-email-catalog", candidate.id, WINDOW_DAYS],
|
|
39
|
+
queryFn: () => fetchAppEmailCatalog(candidate, WINDOW_DAYS),
|
|
40
|
+
enabled: isCore,
|
|
41
|
+
staleTime: Infinity,
|
|
42
|
+
})),
|
|
43
|
+
});
|
|
44
|
+
const remoteQuery = useQuery({
|
|
45
|
+
queryKey: ["transactional-email-catalog", appId, WINDOW_DAYS],
|
|
46
|
+
queryFn: () => fetchAppEmailCatalog(app, WINDOW_DAYS),
|
|
47
|
+
enabled: !isCore && !!app,
|
|
48
|
+
staleTime: Infinity,
|
|
49
|
+
});
|
|
50
|
+
if (isCore) {
|
|
51
|
+
const appCatalogs = coreCatalogQueries
|
|
52
|
+
.map((query) => query.data)
|
|
53
|
+
.filter((catalog) => Boolean(catalog));
|
|
54
|
+
const aggregate = localQuery.data
|
|
55
|
+
? aggregateSharedEmails(localQuery.data, appCatalogs)
|
|
56
|
+
: undefined;
|
|
57
|
+
const email = aggregate?.emails.find((candidate) => candidate.id === id);
|
|
58
|
+
const catalogError = coreCatalogQueries.find((query) => query.data?.error || query.data?.statsError)?.data;
|
|
59
|
+
const queryError = coreCatalogQueries.find((query) => query.isError)?.error;
|
|
60
|
+
const isLoading = localQuery.isLoading ||
|
|
61
|
+
appsQuery.isLoading ||
|
|
62
|
+
coreCatalogQueries.some((query) => query.isLoading);
|
|
63
|
+
const isError = localQuery.isError || appsQuery.isError || Boolean(queryError);
|
|
64
|
+
return {
|
|
65
|
+
isLoading,
|
|
66
|
+
isError,
|
|
67
|
+
error: localQuery.error ?? appsQuery.error ?? queryError,
|
|
68
|
+
onRetry: () => {
|
|
69
|
+
void localQuery.refetch();
|
|
70
|
+
void appsQuery.refetch();
|
|
71
|
+
for (const query of coreCatalogQueries)
|
|
72
|
+
void query.refetch();
|
|
73
|
+
},
|
|
74
|
+
appName: t("dispatch.transactionalEmail.sharedTitle"),
|
|
75
|
+
appPath: t("dispatch.transactionalEmail.sharedSubtitle"),
|
|
76
|
+
email,
|
|
77
|
+
catalogError: aggregate?.statsError ??
|
|
78
|
+
catalogError?.error ??
|
|
79
|
+
catalogError?.statsError ??
|
|
80
|
+
null,
|
|
81
|
+
notFound: !isLoading && !isError && !email,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
const notFound = !appsQuery.isLoading &&
|
|
85
|
+
!appsQuery.isError &&
|
|
86
|
+
(!app ||
|
|
87
|
+
(!remoteQuery.isLoading &&
|
|
88
|
+
!remoteQuery.isError &&
|
|
89
|
+
!remoteQuery.data?.error &&
|
|
90
|
+
!remoteQuery.data?.emails.some((candidate) => candidate.id === id)));
|
|
91
|
+
return {
|
|
92
|
+
isLoading: appsQuery.isLoading || remoteQuery.isLoading,
|
|
93
|
+
isError: appsQuery.isError || remoteQuery.isError,
|
|
94
|
+
error: appsQuery.error ?? remoteQuery.error,
|
|
95
|
+
onRetry: () => {
|
|
96
|
+
void appsQuery.refetch();
|
|
97
|
+
void remoteQuery.refetch();
|
|
98
|
+
},
|
|
99
|
+
appName: app?.name ?? appId,
|
|
100
|
+
appPath: app?.path ?? "",
|
|
101
|
+
email: remoteQuery.data?.emails.find((candidate) => candidate.id === id),
|
|
102
|
+
catalogError: remoteQuery.data?.error ?? null,
|
|
103
|
+
notFound,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
export default function TransactionalEmailDetailRoute() {
|
|
107
|
+
const t = useT();
|
|
108
|
+
const params = useParams();
|
|
109
|
+
const appId = params.appId ?? "";
|
|
110
|
+
const id = params.id ?? "";
|
|
111
|
+
const detail = useEmailDetail(appId, id);
|
|
112
|
+
const sharedProviderReason = t("dispatch.transactionalEmail.sharedProviderMetricsUnavailable");
|
|
113
|
+
const engagementQuery = useQuery({
|
|
114
|
+
queryKey: ["list-email-engagement", detail.appPath, id, WINDOW_DAYS],
|
|
115
|
+
queryFn: () => callAppAction(detail.appPath, "list-email-engagement", { templateIds: [id], windowDays: WINDOW_DAYS }, "POST"),
|
|
116
|
+
enabled: appId !== "core" && id.length > 0 && detail.appPath.length > 0,
|
|
117
|
+
});
|
|
118
|
+
const engagementResult = engagementQuery.data;
|
|
119
|
+
const engagementUnavailable = appId === "core"
|
|
120
|
+
? sharedProviderReason
|
|
121
|
+
: engagementResult && !engagementResult.available
|
|
122
|
+
? engagementResult.reason
|
|
123
|
+
: engagementQuery.isError
|
|
124
|
+
? engagementQuery.error instanceof Error
|
|
125
|
+
? engagementQuery.error.message
|
|
126
|
+
: String(engagementQuery.error)
|
|
127
|
+
: null;
|
|
128
|
+
const engagement = engagementResult?.available
|
|
129
|
+
? engagementResult.data.find((entry) => entry.templateId === id)
|
|
130
|
+
: undefined;
|
|
131
|
+
const activityQuery = useQuery({
|
|
132
|
+
queryKey: ["list-email-activity", detail.appPath, id, ACTIVITY_LIMIT],
|
|
133
|
+
queryFn: () => callAppAction(detail.appPath, "list-email-activity", { templateId: id, limit: ACTIVITY_LIMIT }, "GET"),
|
|
134
|
+
enabled: appId !== "core" &&
|
|
135
|
+
id.length > 0 &&
|
|
136
|
+
detail.appPath.length > 0 &&
|
|
137
|
+
!engagementUnavailable,
|
|
138
|
+
});
|
|
139
|
+
return (_jsx(DispatchShell, { title: detail.email?.name ?? t("dispatch.transactionalEmail.title"), description: t("dispatch.transactionalEmail.description"), children: _jsxs("div", { className: "max-w-3xl space-y-4", children: [_jsx(Button, { asChild: true, size: "sm", variant: "ghost", className: "-ml-2", children: _jsxs(Link, { to: "/transactional-email", children: [_jsx(IconArrowLeft, { size: 15, className: "mr-1.5" }), t("dispatch.transactionalEmail.title")] }) }), detail.isError ? (_jsx(ActionQueryError, { error: detail.error, onRetry: detail.onRetry })) : detail.isLoading ? (_jsxs("div", { className: "space-y-3", children: [_jsx(Skeleton, { className: "h-6 w-64" }), _jsx(Skeleton, { className: "h-32 w-full" })] })) : detail.notFound || !detail.email ? (_jsxs(Alert, { variant: "destructive", children: [_jsx(IconAlertTriangle, { className: "size-4" }), _jsx(AlertTitle, { children: t("dispatch.transactionalEmail.emailNotFound") }), _jsx(AlertDescription, { children: detail.catalogError ??
|
|
140
|
+
t("dispatch.transactionalEmail.emailNotFoundDescription") })] })) : (_jsxs(_Fragment, { children: [_jsxs("section", { className: "rounded-2xl bg-card p-5", children: [_jsx("div", { className: "text-lg font-semibold text-foreground", children: detail.email.name }), _jsx("div", { className: "mt-1 font-mono text-xs text-muted-foreground", children: detail.email.id }), _jsxs("div", { className: "mt-1 text-xs text-muted-foreground", children: [detail.appName, detail.appPath ? ` · ${detail.appPath}` : ""] }), _jsxs("dl", { className: "mt-4 space-y-3 text-sm", children: [_jsxs("div", { children: [_jsx("dt", { className: "text-xs font-medium text-muted-foreground", children: t("dispatch.transactionalEmail.trigger") }), _jsx("dd", { className: "mt-1 text-foreground", children: detail.email.trigger })] }), _jsxs("div", { children: [_jsx("dt", { className: "text-xs font-medium text-muted-foreground", children: t("dispatch.transactionalEmail.recipient") }), _jsx("dd", { className: "mt-1 text-foreground", children: detail.email.recipient })] }), _jsxs("div", { children: [_jsx("dt", { className: "text-xs font-medium text-muted-foreground", children: t("dispatch.transactionalEmail.sender") }), _jsx("dd", { className: "mt-1 text-foreground", children: detail.email.sender })] })] }), _jsxs("div", { className: "mt-4 flex flex-wrap gap-6 border-t pt-4 text-sm", children: [_jsxs("div", { children: [_jsx("div", { className: "text-xs font-medium text-muted-foreground", children: t("dispatch.transactionalEmail.sends") }), _jsx("div", { className: "mt-1", children: _jsx(SendsCell, { sent: detail.email.sent, failed: detail.email.failed }) })] }), _jsxs("div", { children: [_jsx("div", { className: "text-xs font-medium text-muted-foreground", children: t("dispatch.transactionalEmail.openRate") }), _jsx("div", { className: "mt-1", children: _jsx(OpenRateCell, { engagement: engagement, unavailableReason: engagementUnavailable, loading: engagementQuery.isLoading }) })] }), _jsxs("div", { children: [_jsx("div", { className: "text-xs font-medium text-muted-foreground", children: t("dispatch.transactionalEmail.lastSent") }), _jsx("div", { className: "mt-1", children: _jsx(LastSentCell, { lastSentAt: detail.email.lastSentAt, sent: detail.email.sent }) })] })] })] }), _jsxs("section", { className: "rounded-2xl bg-card p-5", children: [_jsx("h2", { className: "text-sm font-medium text-foreground", children: t("dispatch.transactionalEmail.preview") }), _jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: t("dispatch.transactionalEmail.previewDescription") }), _jsx("div", { className: "mt-4", children: _jsx(EmailPreviewPane, { appId: appId, appPath: detail.appPath, id: detail.email.id, name: detail.email.name }) })] }), _jsxs("section", { className: "rounded-2xl bg-card p-5", children: [_jsx("h2", { className: "text-sm font-medium text-foreground", children: t("dispatch.transactionalEmail.activityLink") }), _jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: t("dispatch.transactionalEmail.retentionNote") }), _jsx("div", { className: "mt-4", children: _jsx(ActivityTable, { result: engagementUnavailable
|
|
141
|
+
? { available: false, reason: engagementUnavailable }
|
|
142
|
+
: activityQuery.data, isLoading: activityQuery.isLoading, isError: activityQuery.isError, error: activityQuery.error, onRetry: () => void activityQuery.refetch() }) })] })] }))] }) }));
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=transactional-email.$appId.$id.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transactional-email.$appId.$id.js","sourceRoot":"","sources":["../../../src/routes/pages/transactional-email.$appId.$id.tsx"],"names":[],"mappings":"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","sourcesContent":["import { callAction, useActionQuery } from \"@agent-native/core/client/hooks\";\nimport { useT } from \"@agent-native/core/client/i18n\";\nimport { IconAlertTriangle, IconArrowLeft } from \"@tabler/icons-react\";\nimport { useQueries, useQuery } from \"@tanstack/react-query\";\nimport { useMemo } from \"react\";\nimport { Link, useParams } from \"react-router\";\n\nimport {\n aggregateSharedEmails,\n callAppAction,\n fetchAppEmailCatalog,\n type AppEmailCatalog,\n type AppTransactionalEmail,\n type LocalTransactionalEmailCatalog,\n} from \"../../client/transactional-emails\";\nimport { ActionQueryError } from \"../../components/action-query-error\";\nimport { DispatchShell } from \"../../components/dispatch-shell\";\nimport {\n ActivityTable,\n type EmailActivityEntry,\n} from \"../../components/transactional-email-activity\";\nimport {\n OpenRateCell,\n SendsCell,\n LastSentCell,\n type EmailEngagement,\n type ProviderMetricsResult,\n} from \"../../components/transactional-email-metrics\";\nimport { EmailPreviewPane } from \"../../components/transactional-email-preview\";\nimport { Alert, AlertDescription, AlertTitle } from \"../../components/ui/alert\";\nimport { Button } from \"../../components/ui/button\";\nimport { Skeleton } from \"../../components/ui/skeleton\";\n\nexport function meta() {\n return [{ title: \"Transactional email detail — Dispatch\" }];\n}\n\nconst WINDOW_DAYS = 30;\nconst ACTIVITY_LIMIT = 50;\n\ninterface WorkspaceAppRef {\n id: string;\n name: string;\n path: string;\n status?: \"ready\" | \"pending\";\n}\n\n/**\n * Resolves the one email this page shows, plus the app name/path to display.\n * The \"core\" appId is Dispatch's own registry, not a cross-app fetch — see\n * the same split in transactional-email.tsx.\n */\nfunction useEmailDetail(appId: string, id: string) {\n const isCore = appId === \"core\";\n const t = useT();\n\n const localQuery = useActionQuery<LocalTransactionalEmailCatalog>(\n \"list-transactional-emails\",\n { windowDays: WINDOW_DAYS },\n { enabled: isCore },\n );\n\n const appsQuery = useActionQuery<WorkspaceAppRef[]>(\"list-workspace-apps\", {\n includeAgentCards: false,\n });\n const readyApps = useMemo(\n () =>\n (appsQuery.data ?? []).filter(\n (candidate) => candidate.status !== \"pending\",\n ),\n [appsQuery.data],\n );\n const app = useMemo(\n () => readyApps.find((candidate) => candidate.id === appId),\n [readyApps, appId],\n );\n const coreCatalogQueries = useQueries({\n queries: readyApps.map((candidate) => ({\n queryKey: [\"transactional-email-catalog\", candidate.id, WINDOW_DAYS],\n queryFn: () => fetchAppEmailCatalog(candidate, WINDOW_DAYS),\n enabled: isCore,\n staleTime: Infinity,\n })),\n });\n const remoteQuery = useQuery({\n queryKey: [\"transactional-email-catalog\", appId, WINDOW_DAYS],\n queryFn: () => fetchAppEmailCatalog(app!, WINDOW_DAYS),\n enabled: !isCore && !!app,\n staleTime: Infinity,\n });\n\n if (isCore) {\n const appCatalogs = coreCatalogQueries\n .map((query) => query.data)\n .filter((catalog): catalog is AppEmailCatalog => Boolean(catalog));\n const aggregate = localQuery.data\n ? aggregateSharedEmails(localQuery.data, appCatalogs)\n : undefined;\n const email = aggregate?.emails.find((candidate) => candidate.id === id);\n const catalogError = coreCatalogQueries.find(\n (query) => query.data?.error || query.data?.statsError,\n )?.data;\n const queryError = coreCatalogQueries.find((query) => query.isError)?.error;\n const isLoading =\n localQuery.isLoading ||\n appsQuery.isLoading ||\n coreCatalogQueries.some((query) => query.isLoading);\n const isError =\n localQuery.isError || appsQuery.isError || Boolean(queryError);\n return {\n isLoading,\n isError,\n error: localQuery.error ?? appsQuery.error ?? queryError,\n onRetry: () => {\n void localQuery.refetch();\n void appsQuery.refetch();\n for (const query of coreCatalogQueries) void query.refetch();\n },\n appName: t(\"dispatch.transactionalEmail.sharedTitle\"),\n appPath: t(\"dispatch.transactionalEmail.sharedSubtitle\"),\n email,\n catalogError:\n aggregate?.statsError ??\n catalogError?.error ??\n catalogError?.statsError ??\n null,\n notFound: !isLoading && !isError && !email,\n };\n }\n\n const notFound =\n !appsQuery.isLoading &&\n !appsQuery.isError &&\n (!app ||\n (!remoteQuery.isLoading &&\n !remoteQuery.isError &&\n !remoteQuery.data?.error &&\n !remoteQuery.data?.emails.some((candidate) => candidate.id === id)));\n\n return {\n isLoading: appsQuery.isLoading || remoteQuery.isLoading,\n isError: appsQuery.isError || remoteQuery.isError,\n error: appsQuery.error ?? remoteQuery.error,\n onRetry: () => {\n void appsQuery.refetch();\n void remoteQuery.refetch();\n },\n appName: app?.name ?? appId,\n appPath: app?.path ?? \"\",\n email: remoteQuery.data?.emails.find((candidate) => candidate.id === id),\n catalogError: remoteQuery.data?.error ?? null,\n notFound,\n };\n}\n\nexport default function TransactionalEmailDetailRoute() {\n const t = useT();\n const params = useParams<{ appId: string; id: string }>();\n const appId = params.appId ?? \"\";\n const id = params.id ?? \"\";\n\n const detail = useEmailDetail(appId, id);\n\n const sharedProviderReason = t(\n \"dispatch.transactionalEmail.sharedProviderMetricsUnavailable\",\n );\n const engagementQuery = useQuery({\n queryKey: [\"list-email-engagement\", detail.appPath, id, WINDOW_DAYS],\n queryFn: () =>\n callAppAction<ProviderMetricsResult<EmailEngagement[]>>(\n detail.appPath,\n \"list-email-engagement\",\n { templateIds: [id], windowDays: WINDOW_DAYS },\n \"POST\",\n ),\n enabled: appId !== \"core\" && id.length > 0 && detail.appPath.length > 0,\n });\n const engagementResult = engagementQuery.data;\n const engagementUnavailable =\n appId === \"core\"\n ? sharedProviderReason\n : engagementResult && !engagementResult.available\n ? engagementResult.reason\n : engagementQuery.isError\n ? engagementQuery.error instanceof Error\n ? engagementQuery.error.message\n : String(engagementQuery.error)\n : null;\n const engagement = engagementResult?.available\n ? engagementResult.data.find((entry) => entry.templateId === id)\n : undefined;\n\n const activityQuery = useQuery({\n queryKey: [\"list-email-activity\", detail.appPath, id, ACTIVITY_LIMIT],\n queryFn: () =>\n callAppAction<ProviderMetricsResult<EmailActivityEntry[]>>(\n detail.appPath,\n \"list-email-activity\",\n { templateId: id, limit: ACTIVITY_LIMIT },\n \"GET\",\n ),\n enabled:\n appId !== \"core\" &&\n id.length > 0 &&\n detail.appPath.length > 0 &&\n !engagementUnavailable,\n });\n\n return (\n <DispatchShell\n title={detail.email?.name ?? t(\"dispatch.transactionalEmail.title\")}\n description={t(\"dispatch.transactionalEmail.description\")}\n >\n <div className=\"max-w-3xl space-y-4\">\n <Button asChild size=\"sm\" variant=\"ghost\" className=\"-ml-2\">\n <Link to=\"/transactional-email\">\n <IconArrowLeft size={15} className=\"mr-1.5\" />\n {t(\"dispatch.transactionalEmail.title\")}\n </Link>\n </Button>\n\n {detail.isError ? (\n <ActionQueryError error={detail.error} onRetry={detail.onRetry} />\n ) : detail.isLoading ? (\n <div className=\"space-y-3\">\n <Skeleton className=\"h-6 w-64\" />\n <Skeleton className=\"h-32 w-full\" />\n </div>\n ) : detail.notFound || !detail.email ? (\n <Alert variant=\"destructive\">\n <IconAlertTriangle className=\"size-4\" />\n <AlertTitle>\n {t(\"dispatch.transactionalEmail.emailNotFound\")}\n </AlertTitle>\n <AlertDescription>\n {detail.catalogError ??\n t(\"dispatch.transactionalEmail.emailNotFoundDescription\")}\n </AlertDescription>\n </Alert>\n ) : (\n <>\n <section className=\"rounded-2xl bg-card p-5\">\n <div className=\"text-lg font-semibold text-foreground\">\n {detail.email.name}\n </div>\n <div className=\"mt-1 font-mono text-xs text-muted-foreground\">\n {detail.email.id}\n </div>\n <div className=\"mt-1 text-xs text-muted-foreground\">\n {detail.appName}\n {detail.appPath ? ` · ${detail.appPath}` : \"\"}\n </div>\n\n <dl className=\"mt-4 space-y-3 text-sm\">\n <div>\n <dt className=\"text-xs font-medium text-muted-foreground\">\n {t(\"dispatch.transactionalEmail.trigger\")}\n </dt>\n <dd className=\"mt-1 text-foreground\">\n {detail.email.trigger}\n </dd>\n </div>\n <div>\n <dt className=\"text-xs font-medium text-muted-foreground\">\n {t(\"dispatch.transactionalEmail.recipient\")}\n </dt>\n <dd className=\"mt-1 text-foreground\">\n {detail.email.recipient}\n </dd>\n </div>\n <div>\n <dt className=\"text-xs font-medium text-muted-foreground\">\n {t(\"dispatch.transactionalEmail.sender\")}\n </dt>\n <dd className=\"mt-1 text-foreground\">\n {detail.email.sender}\n </dd>\n </div>\n </dl>\n\n <div className=\"mt-4 flex flex-wrap gap-6 border-t pt-4 text-sm\">\n <div>\n <div className=\"text-xs font-medium text-muted-foreground\">\n {t(\"dispatch.transactionalEmail.sends\")}\n </div>\n <div className=\"mt-1\">\n <SendsCell\n sent={detail.email.sent}\n failed={detail.email.failed}\n />\n </div>\n </div>\n <div>\n <div className=\"text-xs font-medium text-muted-foreground\">\n {t(\"dispatch.transactionalEmail.openRate\")}\n </div>\n <div className=\"mt-1\">\n <OpenRateCell\n engagement={engagement}\n unavailableReason={engagementUnavailable}\n loading={engagementQuery.isLoading}\n />\n </div>\n </div>\n <div>\n <div className=\"text-xs font-medium text-muted-foreground\">\n {t(\"dispatch.transactionalEmail.lastSent\")}\n </div>\n <div className=\"mt-1\">\n <LastSentCell\n lastSentAt={detail.email.lastSentAt}\n sent={detail.email.sent}\n />\n </div>\n </div>\n </div>\n </section>\n\n <section className=\"rounded-2xl bg-card p-5\">\n <h2 className=\"text-sm font-medium text-foreground\">\n {t(\"dispatch.transactionalEmail.preview\")}\n </h2>\n <p className=\"mt-1 text-xs text-muted-foreground\">\n {t(\"dispatch.transactionalEmail.previewDescription\")}\n </p>\n <div className=\"mt-4\">\n <EmailPreviewPane\n appId={appId}\n appPath={detail.appPath}\n id={detail.email.id}\n name={detail.email.name}\n />\n </div>\n </section>\n\n <section className=\"rounded-2xl bg-card p-5\">\n <h2 className=\"text-sm font-medium text-foreground\">\n {t(\"dispatch.transactionalEmail.activityLink\")}\n </h2>\n <p className=\"mt-1 text-xs text-muted-foreground\">\n {t(\"dispatch.transactionalEmail.retentionNote\")}\n </p>\n <div className=\"mt-4\">\n <ActivityTable\n result={\n engagementUnavailable\n ? { available: false, reason: engagementUnavailable }\n : activityQuery.data\n }\n isLoading={activityQuery.isLoading}\n isError={activityQuery.isError}\n error={activityQuery.error}\n onRetry={() => void activityQuery.refetch()}\n />\n </div>\n </section>\n </>\n )}\n </div>\n </DispatchShell>\n );\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transactional-email.d.ts","sourceRoot":"","sources":["../../../src/routes/pages/transactional-email.tsx"],"names":[],"mappings":"AA4DA,wBAAgB,IAAI;;IAEnB;AAyUD,MAAM,CAAC,OAAO,UAAU,uBAAuB,oDAmL9C"}
|