@agent-native/core 0.141.3 → 0.141.4
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/corpus/README.md +1 -1
- package/corpus/templates/analytics/.agents/skills/analysis-workspace/SKILL.md +1 -1
- package/corpus/templates/analytics/actions/gong-calls.ts +56 -18
- package/corpus/templates/analytics/changelog/2026-08-05-analytics-queries-keep-every-table-tenant-scoped.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-05-fixed-sign-in-and-dashboard-requests-failing-when-background.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-05-historical-rollups-stay-complete-while-live-events-arrive.md +6 -0
- package/corpus/templates/analytics/server/db/schema.ts +1 -0
- package/corpus/templates/analytics/server/lib/dashboard-report.ts +2 -0
- package/corpus/templates/analytics/server/lib/emails.ts +51 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics-health.ts +8 -12
- package/corpus/templates/analytics/server/lib/first-party-analytics-rollups.ts +9 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +262 -13
- package/corpus/templates/analytics/server/lib/gong.ts +132 -33
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/analytics/server/plugins/auth.ts +21 -2
- package/corpus/templates/analytics/server/plugins/db.ts +11 -3
- package/corpus/templates/analytics/server/plugins/transactional-emails.ts +5 -0
- package/corpus/templates/calendar/app/components/calendar/InlineEventPickers.tsx +17 -7
- package/corpus/templates/calendar/server/lib/booking-emails.ts +154 -70
- package/corpus/templates/calendar/server/lib/emails.ts +167 -0
- package/corpus/templates/calendar/server/lib/event-guest-notifications.ts +64 -28
- package/corpus/templates/calendar/server/plugins/transactional-emails.ts +5 -0
- package/corpus/templates/clips/actions/invite-member.ts +39 -16
- package/corpus/templates/clips/app/components/player/share-dialog.tsx +73 -1
- package/corpus/templates/clips/app/i18n/en-US.ts +3 -0
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +10 -25
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +2 -0
- package/corpus/templates/clips/changelog/2026-08-05-activity-is-now-the-default-clips-recording-tab-and-ai-setup.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-05-clips-can-copy-a-linked-email-preview-for-public-recordings.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-05-preparing-recording-now-uses-a-clean-shadow-free-status-card.md +6 -0
- package/corpus/templates/clips/desktop/src/styles.css +0 -3
- package/corpus/templates/clips/server/lib/emails.ts +256 -0
- package/corpus/templates/clips/server/lib/transactional-email-templates.ts +24 -0
- package/corpus/templates/clips/server/plugins/transactional-email-catalog.ts +5 -0
- package/corpus/templates/clips/server/routes/api/thumbnail/[recordingId].get.ts +4 -1
- package/corpus/templates/clips/shared/email-preview.ts +61 -0
- package/corpus/templates/content/server/lib/comment-notifications.ts +57 -26
- package/corpus/templates/content/server/lib/emails.ts +70 -0
- package/corpus/templates/content/server/plugins/transactional-emails.ts +5 -0
- package/corpus/templates/design/actions/import-design-tokens.ts +11 -8
- package/corpus/templates/design/actions/index-design-tokens.ts +13 -6
- package/corpus/templates/design/changelog/2026-08-05-design-token-imports-classify-typography-dimensions-correctly.md +6 -0
- package/corpus/templates/dispatch/app/routes/transactional-email.$appId.$id.tsx +4 -0
- package/corpus/templates/dispatch/app/routes/transactional-email.tsx +4 -0
- package/corpus/templates/dispatch/changelog/2026-08-05-transactional-email-catalogs-and-delivery-activity-are-available-again.md +6 -0
- package/corpus/templates/factory/AGENTS.md +18 -17
- package/corpus/templates/factory/actions/get-factory-automation-health.ts +50 -0
- package/corpus/templates/factory/actions/get-triage-config.ts +8 -0
- package/corpus/templates/factory/actions/save-triage-config.ts +26 -0
- package/corpus/templates/factory/app/i18n/en-US.ts +27 -0
- package/corpus/templates/factory/app/routes/factory.tsx +376 -206
- package/corpus/templates/factory/changelog/2026-08-05-factory-now-keeps-production-automations-running-shows-sched.md +6 -0
- package/corpus/templates/factory/server/db/schema.ts +6 -0
- package/corpus/templates/factory/server/plugins/factory-migrations.ts +10 -0
- package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +141 -2
- package/corpus/templates/forms/app/pages/FormsListPage.tsx +41 -21
- package/corpus/templates/forms/changelog/2026-08-05-form-links-can-now-be-cmd-clicked-to-open-in-a-new-tab.md +6 -0
- package/corpus/templates/forms/server/lib/emails.ts +52 -0
- package/corpus/templates/forms/server/lib/response-email.ts +2 -0
- package/corpus/templates/forms/server/plugins/transactional-emails.ts +5 -0
- package/corpus/templates/plan/actions/request-plan-access.ts +33 -12
- package/corpus/templates/plan/server/lib/comment-notifications.ts +53 -27
- package/corpus/templates/plan/server/lib/emails.ts +67 -0
- package/corpus/templates/plan/server/plugins/transactional-emails.ts +5 -0
- package/corpus/templates/slides/actions/import-from-url.ts +1 -0
- package/corpus/templates/slides/app/components/editor/NewDeckReferenceStep.tsx +492 -381
- package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +8 -7
- package/corpus/templates/slides/app/components/layout/Layout.tsx +27 -23
- package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +313 -0
- package/corpus/templates/slides/app/components/ui/command.tsx +1 -0
- package/corpus/templates/slides/app/i18n/en-US.ts +8 -2
- package/corpus/templates/slides/app/lib/create-deck-generation.ts +305 -0
- package/corpus/templates/slides/app/pages/DeckEditor.tsx +12 -12
- package/corpus/templates/slides/app/pages/Index.tsx +127 -137
- package/corpus/templates/slides/app/root.tsx +8 -0
- package/corpus/templates/slides/changelog/2026-08-05-deck-browsing-keeps-all-and-mine-in-the-header-with-a-tighte.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-05-new-deck-creation-now-flows-through-a-clean-full-screen-refe.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-05-website-imports-now-include-linked-stylesheets-for-more-accu.md +6 -0
- package/corpus/templates/slides/server/lib/comment-notifications.ts +44 -21
- package/corpus/templates/slides/server/lib/emails.ts +40 -0
- package/corpus/templates/slides/server/plugins/transactional-emails.ts +5 -0
- package/dist/brand-kit/index.d.ts +1 -0
- package/dist/brand-kit/index.js +1 -0
- package/dist/brand-kit/tokens.d.ts +91 -0
- package/dist/brand-kit/tokens.js +279 -0
- package/dist/brand-kit/types.d.ts +25 -0
- package/dist/client/AssistantChat.js +7 -7
- package/dist/client/onboarding/FirstRunOnboarding.js +26 -2
- package/dist/client/onboarding/first-run-registry.d.ts +25 -0
- package/dist/client/onboarding/first-run-registry.js +18 -0
- package/dist/client/onboarding/index.d.ts +2 -0
- package/dist/client/onboarding/index.js +1 -0
- package/dist/collab/routes.d.ts +1 -1
- package/dist/deploy/build.d.ts +8 -0
- package/dist/deploy/build.js +92 -3
- package/dist/deploy/workspace-deploy.js +86 -3
- package/dist/email-catalog/actions/list-email-activity.d.ts +5 -0
- package/dist/email-catalog/actions/list-email-activity.js +19 -0
- package/dist/email-catalog/actions/list-email-engagement.d.ts +5 -0
- package/dist/email-catalog/actions/list-email-engagement.js +15 -0
- package/dist/email-catalog/actions/list-email-log.d.ts +7 -0
- package/dist/email-catalog/actions/list-email-log.js +23 -0
- package/dist/email-catalog/actions/list-transactional-emails.d.ts +22 -0
- package/dist/email-catalog/actions/list-transactional-emails.js +67 -0
- package/dist/email-catalog/actions/render-transactional-email-preview.d.ts +9 -0
- package/dist/email-catalog/actions/render-transactional-email-preview.js +25 -0
- package/dist/email-catalog/authorize.d.ts +1 -0
- package/dist/email-catalog/authorize.js +9 -0
- package/dist/email-catalog/log.d.ts +60 -0
- package/dist/email-catalog/log.js +142 -0
- package/dist/email-catalog/provider-metrics.d.ts +34 -0
- package/dist/email-catalog/provider-metrics.js +182 -0
- package/dist/email-catalog/registry.d.ts +78 -0
- package/dist/email-catalog/registry.js +70 -0
- package/dist/email-catalog/schema.d.ts +231 -0
- package/dist/email-catalog/schema.js +53 -0
- package/dist/email-catalog/system-emails.d.ts +13 -0
- package/dist/email-catalog/system-emails.js +67 -0
- package/dist/jobs/index.d.ts +2 -0
- package/dist/jobs/index.js +2 -0
- package/dist/jobs/run-history.js +48 -1
- package/dist/jobs/scheduler-dispatch.d.ts +7 -0
- package/dist/jobs/scheduler-dispatch.js +7 -0
- package/dist/jobs/scheduler-health.d.ts +22 -0
- package/dist/jobs/scheduler-health.js +172 -0
- package/dist/jobs/scheduler.js +67 -2
- package/dist/localization/default-messages.d.ts +45 -0
- package/dist/localization/default-messages.js +45 -0
- package/dist/mcp/screen-memory-stdio.d.ts +7 -7
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +5 -5
- package/dist/org/handlers.js +9 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +10 -10
- package/dist/provider-api/actions/provider-api.d.ts +8 -8
- package/dist/secrets/routes.d.ts +9 -9
- package/dist/server/action-discovery.js +22 -0
- package/dist/server/agent-chat/recurring-jobs-runtime.d.ts +7 -1
- package/dist/server/agent-chat/recurring-jobs-runtime.js +12 -0
- package/dist/server/agent-chat-plugin.d.ts +2 -1
- package/dist/server/agent-chat-plugin.js +39 -1
- package/dist/server/better-auth-instance.js +17 -2
- package/dist/server/design-token-utils.d.ts +6 -1
- package/dist/server/design-token-utils.js +252 -59
- package/dist/server/email.d.ts +11 -0
- package/dist/server/email.js +54 -3
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +1 -0
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/vite/client.js +20 -14
- package/package.json +2 -1
|
@@ -24,6 +24,7 @@ import { and, eq, sql } from "drizzle-orm";
|
|
|
24
24
|
import { z } from "zod";
|
|
25
25
|
|
|
26
26
|
import { getDb } from "../server/db/index.js";
|
|
27
|
+
import { CLIPS_ORGANIZATION_INVITE_EMAIL_ID } from "../server/lib/emails.js";
|
|
27
28
|
import {
|
|
28
29
|
getCurrentOwnerEmail,
|
|
29
30
|
nanoid,
|
|
@@ -67,6 +68,36 @@ async function fetchOrgName(orgId: string): Promise<string> {
|
|
|
67
68
|
return row?.name ?? "Organization";
|
|
68
69
|
}
|
|
69
70
|
|
|
71
|
+
export function renderClipsInviteEmail({
|
|
72
|
+
appName,
|
|
73
|
+
orgName,
|
|
74
|
+
inviter,
|
|
75
|
+
role,
|
|
76
|
+
inviteUrl,
|
|
77
|
+
}: {
|
|
78
|
+
appName: string;
|
|
79
|
+
orgName: string;
|
|
80
|
+
inviter: string;
|
|
81
|
+
role: "admin" | "member";
|
|
82
|
+
inviteUrl: string;
|
|
83
|
+
}) {
|
|
84
|
+
return {
|
|
85
|
+
subject: `You're invited to ${orgName} on ${appName}`,
|
|
86
|
+
...renderEmail({
|
|
87
|
+
brandName: appName,
|
|
88
|
+
preheader: `${inviter} invited you to ${orgName} on ${appName}.`,
|
|
89
|
+
heading: `You're invited to join ${orgName}`,
|
|
90
|
+
paragraphs: [
|
|
91
|
+
`${emailStrong(inviter)} invited you to the ${emailStrong(orgName)} organization on ${emailStrong(appName)} as ${emailStrong(role)}.`,
|
|
92
|
+
`Click the button below to accept the invite and start collaborating.`,
|
|
93
|
+
],
|
|
94
|
+
cta: { label: "Accept invite", url: inviteUrl },
|
|
95
|
+
// guard:allow-raw-color - email HTML cannot reference CSS theme tokens.
|
|
96
|
+
brandColor: "#18181B",
|
|
97
|
+
}),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
70
101
|
export default defineAction({
|
|
71
102
|
description:
|
|
72
103
|
"Invite someone to the active organization by email. Creates a pending invitation. Role 'admin' maps to admin; all other Clips roles collapse to 'member'. Sends an email when a provider is configured.",
|
|
@@ -123,25 +154,17 @@ export default defineAction({
|
|
|
123
154
|
const orgName = await fetchOrgName(organizationId);
|
|
124
155
|
const inviteUrl = `${baseUrl()}/invite/${token}`;
|
|
125
156
|
|
|
126
|
-
const appName = getAppName();
|
|
127
|
-
const { html, text } = renderEmail({
|
|
128
|
-
brandName: appName,
|
|
129
|
-
preheader: `${inviter} invited you to ${orgName} on ${appName}.`,
|
|
130
|
-
heading: `You're invited to join ${orgName}`,
|
|
131
|
-
paragraphs: [
|
|
132
|
-
`${emailStrong(inviter)} invited you to the ${emailStrong(orgName)} organization on ${emailStrong(appName)} as ${emailStrong(role)}.`,
|
|
133
|
-
`Click the button below to accept the invite and start collaborating.`,
|
|
134
|
-
],
|
|
135
|
-
cta: { label: "Accept invite", url: inviteUrl },
|
|
136
|
-
// guard:allow-raw-color - email HTML cannot reference CSS theme tokens.
|
|
137
|
-
brandColor: "#18181B",
|
|
138
|
-
});
|
|
139
157
|
try {
|
|
140
158
|
await sendEmail({
|
|
159
|
+
...renderClipsInviteEmail({
|
|
160
|
+
appName: getAppName(),
|
|
161
|
+
orgName,
|
|
162
|
+
inviter,
|
|
163
|
+
role,
|
|
164
|
+
inviteUrl,
|
|
165
|
+
}),
|
|
141
166
|
to: args.email,
|
|
142
|
-
|
|
143
|
-
html,
|
|
144
|
-
text,
|
|
167
|
+
templateId: CLIPS_ORGANIZATION_INVITE_EMAIL_ID,
|
|
145
168
|
});
|
|
146
169
|
} catch (err) {
|
|
147
170
|
console.warn("[invite-member] email send failed:", err);
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { appPath } from "@agent-native/core/client/api-path";
|
|
2
|
+
import { writeClipboardText } from "@agent-native/core/client/clipboard";
|
|
2
3
|
import {
|
|
3
4
|
useActionMutation,
|
|
4
5
|
useActionQuery,
|
|
@@ -19,6 +20,7 @@ import {
|
|
|
19
20
|
useState,
|
|
20
21
|
type ReactNode,
|
|
21
22
|
} from "react";
|
|
23
|
+
import { toast } from "sonner";
|
|
22
24
|
|
|
23
25
|
import {
|
|
24
26
|
CopyField,
|
|
@@ -44,6 +46,7 @@ import {
|
|
|
44
46
|
import { Switch } from "@/components/ui/switch";
|
|
45
47
|
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@/components/ui/tabs";
|
|
46
48
|
|
|
49
|
+
import { buildEmailPreviewMarkup } from "../../../shared/email-preview";
|
|
47
50
|
import { isLoomEmbedUrl } from "../../../shared/loom";
|
|
48
51
|
import { withShareAttribution } from "../../../shared/share-attribution";
|
|
49
52
|
|
|
@@ -58,6 +61,7 @@ export interface ShareRecordingPopoverProps {
|
|
|
58
61
|
initialVisibility?: Visibility | null;
|
|
59
62
|
initialRole?: "owner" | "admin" | "editor" | "viewer";
|
|
60
63
|
videoUrl?: string | null;
|
|
64
|
+
thumbnailUrl?: string | null;
|
|
61
65
|
animatedThumbnailUrl?: string | null;
|
|
62
66
|
isLoomRecording?: boolean;
|
|
63
67
|
hasPassword?: boolean;
|
|
@@ -88,8 +92,10 @@ export function ShareRecordingPopover({
|
|
|
88
92
|
initialVisibility,
|
|
89
93
|
initialRole,
|
|
90
94
|
videoUrl,
|
|
95
|
+
thumbnailUrl,
|
|
91
96
|
animatedThumbnailUrl,
|
|
92
97
|
isLoomRecording = false,
|
|
98
|
+
hasPassword,
|
|
93
99
|
children,
|
|
94
100
|
open,
|
|
95
101
|
onOpenChange,
|
|
@@ -108,8 +114,10 @@ export function ShareRecordingPopover({
|
|
|
108
114
|
initialVisibility={initialVisibility}
|
|
109
115
|
initialRole={initialRole}
|
|
110
116
|
videoUrl={videoUrl}
|
|
117
|
+
thumbnailUrl={thumbnailUrl}
|
|
111
118
|
animatedThumbnailUrl={animatedThumbnailUrl}
|
|
112
119
|
isLoomRecording={isLoomRecording}
|
|
120
|
+
hasPassword={hasPassword}
|
|
113
121
|
/>
|
|
114
122
|
</PopoverContent>
|
|
115
123
|
</Popover>
|
|
@@ -127,10 +135,12 @@ export function ShareRecordingDialog({
|
|
|
127
135
|
initialVisibility,
|
|
128
136
|
initialRole,
|
|
129
137
|
videoUrl,
|
|
138
|
+
thumbnailUrl,
|
|
130
139
|
animatedThumbnailUrl,
|
|
131
140
|
isLoomRecording = false,
|
|
132
141
|
open,
|
|
133
142
|
onOpenChange,
|
|
143
|
+
hasPassword,
|
|
134
144
|
}: ShareRecordingDialogProps) {
|
|
135
145
|
const t = useT();
|
|
136
146
|
return (
|
|
@@ -147,8 +157,10 @@ export function ShareRecordingDialog({
|
|
|
147
157
|
initialVisibility={initialVisibility}
|
|
148
158
|
initialRole={initialRole}
|
|
149
159
|
videoUrl={videoUrl}
|
|
160
|
+
thumbnailUrl={thumbnailUrl}
|
|
150
161
|
animatedThumbnailUrl={animatedThumbnailUrl}
|
|
151
162
|
isLoomRecording={isLoomRecording}
|
|
163
|
+
hasPassword={hasPassword}
|
|
152
164
|
reserveCloseButton
|
|
153
165
|
/>
|
|
154
166
|
</DialogContent>
|
|
@@ -162,18 +174,22 @@ function ShareRecordingContent({
|
|
|
162
174
|
initialVisibility,
|
|
163
175
|
initialRole,
|
|
164
176
|
videoUrl,
|
|
177
|
+
thumbnailUrl,
|
|
165
178
|
animatedThumbnailUrl,
|
|
166
179
|
isLoomRecording = false,
|
|
167
180
|
reserveCloseButton = false,
|
|
181
|
+
hasPassword,
|
|
168
182
|
}: {
|
|
169
183
|
recordingId: string;
|
|
170
184
|
recordingTitle?: string;
|
|
171
185
|
initialVisibility?: Visibility | null;
|
|
172
186
|
initialRole?: "owner" | "admin" | "editor" | "viewer";
|
|
173
187
|
videoUrl?: string | null;
|
|
188
|
+
thumbnailUrl?: string | null;
|
|
174
189
|
animatedThumbnailUrl?: string | null;
|
|
175
190
|
isLoomRecording?: boolean;
|
|
176
191
|
reserveCloseButton?: boolean;
|
|
192
|
+
hasPassword?: boolean;
|
|
177
193
|
}) {
|
|
178
194
|
const t = useT();
|
|
179
195
|
const sharesQuery = useActionQuery<SharesResponse>("list-resource-shares", {
|
|
@@ -236,13 +252,16 @@ function ShareRecordingContent({
|
|
|
236
252
|
<TabsContent value="link" className="mt-3">
|
|
237
253
|
<LinkTab
|
|
238
254
|
recordingId={recordingId}
|
|
255
|
+
recordingTitle={recordingTitle}
|
|
239
256
|
shareUrl={shareUrl}
|
|
240
257
|
sharesQuery={sharesQuery}
|
|
241
258
|
visibility={visibility}
|
|
242
259
|
canManage={canManage}
|
|
243
260
|
videoUrl={videoUrl}
|
|
261
|
+
thumbnailUrl={thumbnailUrl}
|
|
244
262
|
animatedThumbnailUrl={animatedThumbnailUrl}
|
|
245
263
|
isLoomRecording={isLoomRecording}
|
|
264
|
+
hasPassword={hasPassword}
|
|
246
265
|
/>
|
|
247
266
|
</TabsContent>
|
|
248
267
|
|
|
@@ -276,22 +295,28 @@ function ShareRecordingContent({
|
|
|
276
295
|
|
|
277
296
|
function LinkTab({
|
|
278
297
|
recordingId,
|
|
298
|
+
recordingTitle,
|
|
279
299
|
shareUrl,
|
|
280
300
|
sharesQuery,
|
|
281
301
|
visibility,
|
|
282
302
|
canManage,
|
|
283
303
|
videoUrl,
|
|
304
|
+
thumbnailUrl,
|
|
284
305
|
animatedThumbnailUrl,
|
|
285
306
|
isLoomRecording: isLoomRecordingProp,
|
|
307
|
+
hasPassword,
|
|
286
308
|
}: {
|
|
287
309
|
recordingId: string;
|
|
310
|
+
recordingTitle?: string;
|
|
288
311
|
shareUrl: string;
|
|
289
312
|
sharesQuery: SharesQuery;
|
|
290
313
|
visibility: Visibility | null;
|
|
291
314
|
canManage: boolean;
|
|
292
315
|
videoUrl?: string | null;
|
|
316
|
+
thumbnailUrl?: string | null;
|
|
293
317
|
animatedThumbnailUrl?: string | null;
|
|
294
318
|
isLoomRecording?: boolean;
|
|
319
|
+
hasPassword?: boolean;
|
|
295
320
|
}) {
|
|
296
321
|
const t = useT();
|
|
297
322
|
const { setResourceVisibility, isPending } = useResourceVisibilityMutation(
|
|
@@ -303,6 +328,44 @@ function LinkTab({
|
|
|
303
328
|
const sharesLoaded = visibility !== null;
|
|
304
329
|
const visibilityPending = isPending || sharesQuery.isLoading;
|
|
305
330
|
const isLoomRecording = isLoomRecordingProp || isLoomEmbedUrl(videoUrl);
|
|
331
|
+
const emailPreviewThumbnailUrl = useMemo(() => {
|
|
332
|
+
if (!isPublic || hasPassword !== false || !sharesLoaded) return null;
|
|
333
|
+
const candidate = animatedThumbnailUrl || thumbnailUrl;
|
|
334
|
+
if (!candidate) return null;
|
|
335
|
+
|
|
336
|
+
const query = animatedThumbnailUrl ? "?animated=1" : "";
|
|
337
|
+
return absoluteAppUrl(
|
|
338
|
+
`/api/thumbnail/${encodeURIComponent(recordingId)}${query}`,
|
|
339
|
+
);
|
|
340
|
+
}, [
|
|
341
|
+
animatedThumbnailUrl,
|
|
342
|
+
hasPassword,
|
|
343
|
+
isPublic,
|
|
344
|
+
recordingId,
|
|
345
|
+
sharesLoaded,
|
|
346
|
+
thumbnailUrl,
|
|
347
|
+
]);
|
|
348
|
+
const copyEmailPreview = useCallback(async () => {
|
|
349
|
+
if (!emailPreviewThumbnailUrl || !shareUrl) return;
|
|
350
|
+
|
|
351
|
+
try {
|
|
352
|
+
const markup = buildEmailPreviewMarkup({
|
|
353
|
+
title: recordingTitle?.trim() || t("recordingPage.untitledClip"),
|
|
354
|
+
shareUrl,
|
|
355
|
+
thumbnailUrl: emailPreviewThumbnailUrl,
|
|
356
|
+
});
|
|
357
|
+
const copied = await writeClipboardText(markup.plainText, {
|
|
358
|
+
html: markup.html,
|
|
359
|
+
});
|
|
360
|
+
if (copied) {
|
|
361
|
+
toast.success(t("shareDialog.emailPreviewCopied"));
|
|
362
|
+
} else {
|
|
363
|
+
toast.error(t("shareDialog.emailPreviewCopyFailed"));
|
|
364
|
+
}
|
|
365
|
+
} catch {
|
|
366
|
+
toast.error(t("shareDialog.emailPreviewCopyFailed"));
|
|
367
|
+
}
|
|
368
|
+
}, [emailPreviewThumbnailUrl, recordingTitle, shareUrl, t]);
|
|
306
369
|
const createAgentLink = useActionMutation(
|
|
307
370
|
"create-recording-agent-link" as any,
|
|
308
371
|
);
|
|
@@ -439,8 +502,17 @@ function LinkTab({
|
|
|
439
502
|
/>
|
|
440
503
|
) : null}
|
|
441
504
|
|
|
442
|
-
{videoUrl || animatedThumbnailUrl ? (
|
|
505
|
+
{emailPreviewThumbnailUrl || videoUrl || animatedThumbnailUrl ? (
|
|
443
506
|
<div className="flex flex-wrap gap-2">
|
|
507
|
+
{emailPreviewThumbnailUrl ? (
|
|
508
|
+
<Button
|
|
509
|
+
variant="outline"
|
|
510
|
+
size="sm"
|
|
511
|
+
onClick={() => void copyEmailPreview()}
|
|
512
|
+
>
|
|
513
|
+
{t("shareDialog.copyEmailPreview")}
|
|
514
|
+
</Button>
|
|
515
|
+
) : null}
|
|
444
516
|
{animatedThumbnailUrl ? (
|
|
445
517
|
<Button
|
|
446
518
|
variant="outline"
|
|
@@ -452,6 +452,9 @@ const messages = {
|
|
|
452
452
|
agentLinkUnavailable: "Couldn't create the agent link.",
|
|
453
453
|
retryAgentLink: "Retry",
|
|
454
454
|
gifPreview: "GIF preview",
|
|
455
|
+
copyEmailPreview: "Copy email preview",
|
|
456
|
+
emailPreviewCopied: "Email preview copied",
|
|
457
|
+
emailPreviewCopyFailed: "Couldn't copy email preview",
|
|
455
458
|
openPlayer: "Open player",
|
|
456
459
|
chooseFile: "Choose file",
|
|
457
460
|
remove: "Remove",
|
|
@@ -235,7 +235,7 @@ export default function RecordingPage() {
|
|
|
235
235
|
const { session, isLoading: sessionLoading } = useSession();
|
|
236
236
|
const playerRef = useRef<VideoPlayerHandle | null>(null);
|
|
237
237
|
|
|
238
|
-
const [panel, setPanel] = useState<SidePanel>("
|
|
238
|
+
const [panel, setPanel] = useState<SidePanel>("comments");
|
|
239
239
|
const [theaterMode, setTheaterMode] = useState(false);
|
|
240
240
|
const [editing, setEditing] = useState(false);
|
|
241
241
|
const [currentMs, setCurrentMs] = useState(0);
|
|
@@ -311,15 +311,6 @@ export default function RecordingPage() {
|
|
|
311
311
|
}
|
|
312
312
|
}, [panelParam]);
|
|
313
313
|
|
|
314
|
-
const appliedCompactDefaultRef = useRef(false);
|
|
315
|
-
useEffect(() => {
|
|
316
|
-
if (appliedCompactDefaultRef.current) return;
|
|
317
|
-
if (!panelParam && isCompactLayout) {
|
|
318
|
-
appliedCompactDefaultRef.current = true;
|
|
319
|
-
setPanel("comments");
|
|
320
|
-
}
|
|
321
|
-
}, [isCompactLayout, panelParam]);
|
|
322
|
-
|
|
323
314
|
const playerDataQ = useActionQuery<any>(
|
|
324
315
|
"get-recording-player-data",
|
|
325
316
|
{
|
|
@@ -1154,21 +1145,13 @@ export default function RecordingPage() {
|
|
|
1154
1145
|
{label}
|
|
1155
1146
|
</TabsTrigger>
|
|
1156
1147
|
);
|
|
1157
|
-
const triggers =
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
]
|
|
1165
|
-
: [
|
|
1166
|
-
trigger("agent", t("recordingPage.agent")),
|
|
1167
|
-
trigger("comments", t("recordingPage.activity")),
|
|
1168
|
-
trigger("transcript", t("recordingPage.transcript")),
|
|
1169
|
-
trigger("insights", t("recordingPage.insights")),
|
|
1170
|
-
canEdit ? trigger("settings", t("recordingPage.settings")) : null,
|
|
1171
|
-
];
|
|
1148
|
+
const triggers = [
|
|
1149
|
+
trigger("comments", t("recordingPage.activity")),
|
|
1150
|
+
trigger("transcript", t("recordingPage.transcript")),
|
|
1151
|
+
trigger("agent", t("recordingPage.agent")),
|
|
1152
|
+
trigger("insights", t("recordingPage.insights")),
|
|
1153
|
+
canEdit ? trigger("settings", t("recordingPage.settings")) : null,
|
|
1154
|
+
];
|
|
1172
1155
|
|
|
1173
1156
|
return (
|
|
1174
1157
|
<Tabs
|
|
@@ -1197,6 +1180,7 @@ export default function RecordingPage() {
|
|
|
1197
1180
|
scope={recordingScope}
|
|
1198
1181
|
showHeader={false}
|
|
1199
1182
|
showTabBar={false}
|
|
1183
|
+
missingApiKeySetupLayout="sidebar"
|
|
1200
1184
|
emptyStateText={t("recordingPage.askAboutClip")}
|
|
1201
1185
|
dynamicSuggestions={false}
|
|
1202
1186
|
suggestions={
|
|
@@ -1553,6 +1537,7 @@ export default function RecordingPage() {
|
|
|
1553
1537
|
initialVisibility={recording.visibility}
|
|
1554
1538
|
initialRole={role}
|
|
1555
1539
|
videoUrl={recording.videoUrl}
|
|
1540
|
+
thumbnailUrl={recording.thumbnailUrl}
|
|
1556
1541
|
animatedThumbnailUrl={recording.animatedThumbnailUrl}
|
|
1557
1542
|
isLoomRecording={isLoomEmbedBacked}
|
|
1558
1543
|
hasPassword={Boolean(recording.hasPassword)}
|
|
@@ -985,6 +985,7 @@ export default function ShareRoute() {
|
|
|
985
985
|
initialVisibility={recording.visibility}
|
|
986
986
|
initialRole={viewerIsOwner ? "owner" : undefined}
|
|
987
987
|
videoUrl={recording.videoUrl}
|
|
988
|
+
thumbnailUrl={recording.thumbnailUrl}
|
|
988
989
|
animatedThumbnailUrl={recording.animatedThumbnailUrl}
|
|
989
990
|
isLoomRecording={isLoomEmbedBacked}
|
|
990
991
|
hasPassword={Boolean(recording.hasPassword)}
|
|
@@ -1128,6 +1129,7 @@ export default function ShareRoute() {
|
|
|
1128
1129
|
<AgentPanel
|
|
1129
1130
|
emptyStateText={t("recordingPage.askAboutClip")}
|
|
1130
1131
|
dynamicSuggestions={false}
|
|
1132
|
+
missingApiKeySetupLayout="sidebar"
|
|
1131
1133
|
suggestions={[
|
|
1132
1134
|
t("recordingPage.summarizeClip"),
|
|
1133
1135
|
t("recordingPage.findKeyMoments"),
|
|
@@ -3634,9 +3634,6 @@ body[data-clips-route="recording-pill"] #root {
|
|
|
3634
3634
|
backdrop-filter: blur(20px);
|
|
3635
3635
|
-webkit-backdrop-filter: blur(20px);
|
|
3636
3636
|
border: 1px solid rgba(255, 255, 255, 0.08);
|
|
3637
|
-
box-shadow:
|
|
3638
|
-
0 14px 40px rgba(0, 0, 0, 0.45),
|
|
3639
|
-
0 2px 6px rgba(0, 0, 0, 0.3);
|
|
3640
3637
|
color: white;
|
|
3641
3638
|
font-size: 13px;
|
|
3642
3639
|
font-weight: 600;
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Catalog entries for the transactional emails Clips sends.
|
|
3
|
+
*
|
|
4
|
+
* Registered from `server/plugins/transactional-emails.ts` so Dispatch can list
|
|
5
|
+
* and preview them without the app having sent anything yet.
|
|
6
|
+
*
|
|
7
|
+
* Every entry except the organization invite renders through
|
|
8
|
+
* `renderClipsTransactionalEmail`, so a preview shows the real template rather
|
|
9
|
+
* than a copy of it.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { defineTransactionalEmail } from "@agent-native/core/email-catalog";
|
|
13
|
+
|
|
14
|
+
import { renderClipsInviteEmail } from "../../actions/invite-member.js";
|
|
15
|
+
import {
|
|
16
|
+
CLIPS_ACTIVITY_COMMENT_EMAIL_ID,
|
|
17
|
+
CLIPS_ACTIVITY_REACTION_EMAIL_ID,
|
|
18
|
+
CLIPS_FIRST_AGENT_VIEW_EMAIL_ID,
|
|
19
|
+
CLIPS_FIRST_IMPORT_EMAIL_ID,
|
|
20
|
+
CLIPS_FIRST_VIEW_EMAIL_ID,
|
|
21
|
+
CLIPS_MONTHLY_RECAP_EMAIL_ID,
|
|
22
|
+
CLIPS_TWO_CLIPS_EMAIL_ID,
|
|
23
|
+
CLIPS_UNVIEWED_REMINDER_EMAIL_ID,
|
|
24
|
+
renderClipsTransactionalEmail,
|
|
25
|
+
type ClipsTransactionalEmailInput,
|
|
26
|
+
type ClipsTransactionalEmailRenderOptions,
|
|
27
|
+
} from "./transactional-email-templates.js";
|
|
28
|
+
|
|
29
|
+
/** Obviously-fake sample data — these render in a preview pane, never send. */
|
|
30
|
+
const PREVIEW_OPTIONS: ClipsTransactionalEmailRenderOptions = {
|
|
31
|
+
appUrl: "https://example.com",
|
|
32
|
+
};
|
|
33
|
+
const SAMPLE_RECORDING_ID = "rec_sample";
|
|
34
|
+
const SAMPLE_TITLE = "Onboarding walkthrough";
|
|
35
|
+
const SAMPLE_TO = "sam.rivera@example.com";
|
|
36
|
+
|
|
37
|
+
function preview(input: ClipsTransactionalEmailInput) {
|
|
38
|
+
return renderClipsTransactionalEmail(input, PREVIEW_OPTIONS);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const CLIPS_ORGANIZATION_INVITE_EMAIL_ID = "clips.organization-invite";
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* How the shared Clips sender resolves From and Reply-To for every kind it
|
|
45
|
+
* renders, so each entry can say so without restating the mechanism.
|
|
46
|
+
*/
|
|
47
|
+
const CLIPS_SENDER =
|
|
48
|
+
'From is the configured EMAIL_FROM with the display name "Agent-Native Clips"; on first-party agent-native.com deployments it becomes clips@agent-native.com. Reply-to is hello@agent-native.com.';
|
|
49
|
+
|
|
50
|
+
let registered = false;
|
|
51
|
+
|
|
52
|
+
export function registerClipsEmails(): void {
|
|
53
|
+
if (registered) return;
|
|
54
|
+
registered = true;
|
|
55
|
+
|
|
56
|
+
defineTransactionalEmail({
|
|
57
|
+
id: CLIPS_FIRST_VIEW_EMAIL_ID,
|
|
58
|
+
name: "First view on a Clip",
|
|
59
|
+
trigger:
|
|
60
|
+
"The background transactional-email sweep finds the first counted view of a Clip by someone other than its owner, recorded after transactional email was switched on. One per Clip.",
|
|
61
|
+
recipientLabel: "Clip owner",
|
|
62
|
+
recipient:
|
|
63
|
+
"The Clip's owner. Suppressed recipient addresses are skipped, and the send is dropped if the owner is no longer the recording's owner at send time.",
|
|
64
|
+
senderLabel: "Agent-Native Clips",
|
|
65
|
+
sender: CLIPS_SENDER,
|
|
66
|
+
preview: () =>
|
|
67
|
+
preview({
|
|
68
|
+
kind: "first-view",
|
|
69
|
+
to: SAMPLE_TO,
|
|
70
|
+
recordingId: SAMPLE_RECORDING_ID,
|
|
71
|
+
title: SAMPLE_TITLE,
|
|
72
|
+
viewerEmail: "alex.chen@example.com",
|
|
73
|
+
}),
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
defineTransactionalEmail({
|
|
77
|
+
id: CLIPS_UNVIEWED_REMINDER_EMAIL_ID,
|
|
78
|
+
name: "Unviewed Clip reminder",
|
|
79
|
+
trigger:
|
|
80
|
+
"A direct share is 48 hours old and the recipient still has no counted view of that Clip. Re-checked at send time, so a view in the meantime cancels it.",
|
|
81
|
+
recipientLabel: "Shared-with address",
|
|
82
|
+
recipient:
|
|
83
|
+
"The address the Clip was directly shared with. Requires the share to still exist.",
|
|
84
|
+
senderLabel: "Sharer via Clips",
|
|
85
|
+
sender:
|
|
86
|
+
'Same as the other Clips emails, except the display name becomes "<sharer> (via Agent-Native Clips)" and reply-to is the sharer\'s own address when it is a valid address, falling back to hello@agent-native.com.',
|
|
87
|
+
preview: () =>
|
|
88
|
+
preview({
|
|
89
|
+
kind: "unviewed-reminder",
|
|
90
|
+
to: SAMPLE_TO,
|
|
91
|
+
recordingId: SAMPLE_RECORDING_ID,
|
|
92
|
+
title: SAMPLE_TITLE,
|
|
93
|
+
senderEmail: "dana.hill@example.com",
|
|
94
|
+
senderName: "Dana Hill",
|
|
95
|
+
}),
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
defineTransactionalEmail({
|
|
99
|
+
id: CLIPS_FIRST_AGENT_VIEW_EMAIL_ID,
|
|
100
|
+
name: "First agent read of a Clip",
|
|
101
|
+
trigger:
|
|
102
|
+
"An AI agent reads one of an owner's Clips for the first time since transactional email was switched on. One per owner, not per Clip.",
|
|
103
|
+
recipientLabel: "Clip owner",
|
|
104
|
+
recipient:
|
|
105
|
+
"The owner of the Clip the agent read. Re-checked at send time against the owner's actual first agent view.",
|
|
106
|
+
senderLabel: "Agent-Native Clips",
|
|
107
|
+
sender: CLIPS_SENDER,
|
|
108
|
+
preview: () =>
|
|
109
|
+
preview({
|
|
110
|
+
kind: "first-agent-view",
|
|
111
|
+
to: SAMPLE_TO,
|
|
112
|
+
recordingId: SAMPLE_RECORDING_ID,
|
|
113
|
+
title: SAMPLE_TITLE,
|
|
114
|
+
agentName: "Claude Code",
|
|
115
|
+
}),
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
defineTransactionalEmail({
|
|
119
|
+
id: CLIPS_FIRST_IMPORT_EMAIL_ID,
|
|
120
|
+
name: "First imported video is ready",
|
|
121
|
+
trigger:
|
|
122
|
+
"An owner's first imported (rather than recorded) video finishes processing and becomes ready. One per owner.",
|
|
123
|
+
recipientLabel: "Clip owner",
|
|
124
|
+
recipient:
|
|
125
|
+
"The owner of the imported recording, re-checked at send time so a later import does not resend.",
|
|
126
|
+
senderLabel: "Agent-Native Clips",
|
|
127
|
+
sender: CLIPS_SENDER,
|
|
128
|
+
preview: () =>
|
|
129
|
+
preview({
|
|
130
|
+
kind: "first-import",
|
|
131
|
+
to: SAMPLE_TO,
|
|
132
|
+
recordingId: SAMPLE_RECORDING_ID,
|
|
133
|
+
title: "Quarterly demo recording",
|
|
134
|
+
}),
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
defineTransactionalEmail({
|
|
138
|
+
id: CLIPS_MONTHLY_RECAP_EMAIL_ID,
|
|
139
|
+
name: "Monthly Clips recap",
|
|
140
|
+
trigger:
|
|
141
|
+
"Once per owner per calendar month, from 14:00 UTC on the 1st, for owners whose Clips had any human views or agent reads in the closed month. Metrics and the top Clip are recomputed at send time.",
|
|
142
|
+
recipientLabel: "Clip owner",
|
|
143
|
+
recipient:
|
|
144
|
+
"The Clip owner whose audience the recap reports. Suppressed addresses are skipped.",
|
|
145
|
+
senderLabel: "Agent-Native Clips",
|
|
146
|
+
sender: CLIPS_SENDER,
|
|
147
|
+
preview: () =>
|
|
148
|
+
preview({
|
|
149
|
+
kind: "monthly-recap",
|
|
150
|
+
to: SAMPLE_TO,
|
|
151
|
+
month: "2025-02",
|
|
152
|
+
humanViews: 42,
|
|
153
|
+
agentSessions: 7,
|
|
154
|
+
topClip: {
|
|
155
|
+
recordingId: SAMPLE_RECORDING_ID,
|
|
156
|
+
title: SAMPLE_TITLE,
|
|
157
|
+
thumbnailUrl: "https://example.com/thumbnails/sample.png",
|
|
158
|
+
durationMs: 214_000,
|
|
159
|
+
recordedAt: "2025-02-11T16:00:00.000Z",
|
|
160
|
+
humanViews: 18,
|
|
161
|
+
agentSessions: 4,
|
|
162
|
+
},
|
|
163
|
+
copy: {
|
|
164
|
+
heroLine: "Your clips were watched 42 times. 7 agents read them.",
|
|
165
|
+
completionNote: "68% average completion · most stopped at 2:31",
|
|
166
|
+
agentBreakdown: "4 from Claude Code · 3 unidentified",
|
|
167
|
+
},
|
|
168
|
+
}),
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
defineTransactionalEmail({
|
|
172
|
+
id: CLIPS_TWO_CLIPS_EMAIL_ID,
|
|
173
|
+
name: "Two Clips received",
|
|
174
|
+
trigger:
|
|
175
|
+
"A one-time nudge once someone who owns no Clips of their own has had two distinct Clips directly shared with them. The summary line is AI-generated before the job is released to send.",
|
|
176
|
+
recipientLabel: "Shared-with address",
|
|
177
|
+
recipient:
|
|
178
|
+
"The shared-with address. Dropped at send time if they now own a Clip or no longer hold both shares.",
|
|
179
|
+
senderLabel: "Agent-Native Clips",
|
|
180
|
+
sender: CLIPS_SENDER,
|
|
181
|
+
preview: () =>
|
|
182
|
+
preview({
|
|
183
|
+
kind: "two-clips",
|
|
184
|
+
to: SAMPLE_TO,
|
|
185
|
+
generatedSummary:
|
|
186
|
+
"Two teammates walked you through the new onboarding flow.",
|
|
187
|
+
}),
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
defineTransactionalEmail({
|
|
191
|
+
id: CLIPS_ACTIVITY_COMMENT_EMAIL_ID,
|
|
192
|
+
name: "Clip comment",
|
|
193
|
+
trigger:
|
|
194
|
+
"Someone comments or replies on a Clip. Subject and copy differ slightly for a reply; both send under this id.",
|
|
195
|
+
recipientLabel: "Owner and thread authors",
|
|
196
|
+
recipient:
|
|
197
|
+
"The recording owner, plus every prior author in the thread when the comment is a reply. The list is re-checked against the recording's live ACL and filtered by each user's `emailNotifications` preference; the comment's own author never receives it.",
|
|
198
|
+
senderLabel: "Agent-Native Clips",
|
|
199
|
+
sender: CLIPS_SENDER,
|
|
200
|
+
preview: () =>
|
|
201
|
+
preview({
|
|
202
|
+
kind: "activity-comment",
|
|
203
|
+
to: SAMPLE_TO,
|
|
204
|
+
recordingId: SAMPLE_RECORDING_ID,
|
|
205
|
+
title: SAMPLE_TITLE,
|
|
206
|
+
authorEmail: "alex.chen@example.com",
|
|
207
|
+
authorName: "Alex Chen",
|
|
208
|
+
content: "The step at 1:20 is the part new hires always miss.",
|
|
209
|
+
videoTimestampMs: 80_000,
|
|
210
|
+
isReply: false,
|
|
211
|
+
}),
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
defineTransactionalEmail({
|
|
215
|
+
id: CLIPS_ACTIVITY_REACTION_EMAIL_ID,
|
|
216
|
+
name: "Clip reaction",
|
|
217
|
+
trigger: "A viewer reacts with an emoji on a Clip.",
|
|
218
|
+
recipientLabel: "Clip owner",
|
|
219
|
+
recipient:
|
|
220
|
+
"The recording owner plus any extra recipients the caller passes, re-checked against the recording's live ACL and filtered by each user's `emailNotifications` preference. The reacting viewer never receives it.",
|
|
221
|
+
senderLabel: "Agent-Native Clips",
|
|
222
|
+
sender: CLIPS_SENDER,
|
|
223
|
+
preview: () =>
|
|
224
|
+
preview({
|
|
225
|
+
kind: "activity-reaction",
|
|
226
|
+
to: SAMPLE_TO,
|
|
227
|
+
recordingId: SAMPLE_RECORDING_ID,
|
|
228
|
+
title: SAMPLE_TITLE,
|
|
229
|
+
emoji: "🎉",
|
|
230
|
+
authorEmail: "alex.chen@example.com",
|
|
231
|
+
authorName: "Alex Chen",
|
|
232
|
+
videoTimestampMs: 45_000,
|
|
233
|
+
}),
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
defineTransactionalEmail({
|
|
237
|
+
id: CLIPS_ORGANIZATION_INVITE_EMAIL_ID,
|
|
238
|
+
name: "Organization invitation",
|
|
239
|
+
trigger:
|
|
240
|
+
"An organization admin runs `invite-member`. Any earlier pending invite for the same address is cancelled first, so a re-invite sends a fresh email with a new token.",
|
|
241
|
+
recipientLabel: "Invited address",
|
|
242
|
+
recipient:
|
|
243
|
+
"The address passed to the action, exactly as typed. One email per invite.",
|
|
244
|
+
senderLabel: "Default sender",
|
|
245
|
+
sender:
|
|
246
|
+
"The configured default sender. This call site sets no `from`, `fromName`, `replyTo`, or `appSender`, so unlike the other Clips emails it does not send as Agent-Native Clips.",
|
|
247
|
+
preview: () =>
|
|
248
|
+
renderClipsInviteEmail({
|
|
249
|
+
appName: "Clips",
|
|
250
|
+
orgName: "Northwind Design",
|
|
251
|
+
inviter: "dana.hill@example.com",
|
|
252
|
+
role: "member",
|
|
253
|
+
inviteUrl: "https://example.com/invite/sample-token",
|
|
254
|
+
}),
|
|
255
|
+
});
|
|
256
|
+
}
|