@company-semantics/contracts 27.13.1 → 28.0.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/package.json +1 -1
- package/src/email/render/__tests__/__snapshots__/render-snapshot.test.ts.snap +281 -221
- package/src/email/render/auth-otp.ts +31 -50
- package/src/email/render/blocks.ts +230 -179
- package/src/email/render/chat-shared.ts +19 -61
- package/src/email/render/company-md-access-approved.ts +16 -40
- package/src/email/render/company-md-access-denied.ts +23 -44
- package/src/email/render/company-md-access-requested.ts +23 -48
- package/src/email/render/constants.ts +0 -6
- package/src/email/render/index.ts +15 -58
- package/src/email/render/org-invite.ts +14 -37
- package/src/email/render/ownership-transfer-completed.ts +19 -17
- package/src/email/render/ownership-transfer.ts +36 -21
- package/src/email/render/render-email.ts +110 -100
- package/src/email/render/security-alert.ts +27 -27
- package/src/email/render/share-granted.ts +27 -58
- package/src/email/render/unit-owner-granted.ts +26 -50
- package/src/email/render/base-text.ts +0 -26
|
@@ -1,79 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Chat-shared email (someone shared a chat).
|
|
2
|
+
* Chat-shared email (someone shared a chat).
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { EmailPayloads } from "../types";
|
|
6
6
|
|
|
7
|
-
import { baseTextLayout } from "./base-text";
|
|
8
7
|
import {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
chatAssistantBoxText,
|
|
12
|
-
chatAssistantBubble,
|
|
8
|
+
type Block,
|
|
9
|
+
chatAssistant,
|
|
13
10
|
chatDots,
|
|
14
|
-
|
|
15
|
-
chatUserBoxText,
|
|
16
|
-
chatUserBubble,
|
|
11
|
+
chatUser,
|
|
17
12
|
ctaBox,
|
|
18
13
|
footer,
|
|
19
|
-
htmlShell,
|
|
20
|
-
monoParagraph,
|
|
21
14
|
NOTICE,
|
|
22
|
-
|
|
15
|
+
paragraph,
|
|
16
|
+
signature,
|
|
23
17
|
} from "./blocks";
|
|
24
|
-
import {
|
|
25
|
-
COMPANY_NAME,
|
|
26
|
-
PREVIEW_TRUNCATE_LENGTH,
|
|
27
|
-
TITLE_TRUNCATE_LENGTH,
|
|
28
|
-
} from "./constants";
|
|
29
|
-
import { escapeHtml } from "./escape-html";
|
|
18
|
+
import { COMPANY_NAME } from "./constants";
|
|
30
19
|
|
|
31
20
|
export type ChatSharedPayload = EmailPayloads["chat.shared"];
|
|
32
21
|
|
|
33
|
-
function
|
|
34
|
-
return text.length > max ? text.slice(0, max - 3) + "..." : text;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function renderChatSharedBody(payload: ChatSharedPayload): string {
|
|
22
|
+
export function renderChatShared(payload: ChatSharedPayload): Block[] {
|
|
38
23
|
const { chatTitle, shareUrl, previewText } = payload;
|
|
39
24
|
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
if (previewText) {
|
|
46
|
-
sections.push("");
|
|
47
|
-
sections.push(chatAssistantBoxText(previewText));
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
sections.push("");
|
|
51
|
-
sections.push(chatDotsText());
|
|
52
|
-
sections.push("");
|
|
53
|
-
sections.push(asciiCtaBox("SEE MORE"));
|
|
54
|
-
sections.push("");
|
|
55
|
-
sections.push(shareUrl);
|
|
56
|
-
sections.push("");
|
|
57
|
-
sections.push(`This share was sent via ${COMPANY_NAME}.`);
|
|
58
|
-
sections.push("");
|
|
59
|
-
sections.push(NOTICE);
|
|
25
|
+
const blocks: Block[] = [
|
|
26
|
+
paragraph("A chat has been shared with you."),
|
|
27
|
+
chatUser(chatTitle),
|
|
28
|
+
];
|
|
60
29
|
|
|
61
|
-
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export function renderChatSharedHtml(payload: ChatSharedPayload): string {
|
|
65
|
-
const { chatTitle, shareUrl, previewText } = payload;
|
|
30
|
+
if (previewText) blocks.push(chatAssistant(previewText));
|
|
66
31
|
|
|
67
|
-
|
|
68
|
-
const previewHtml = previewText
|
|
69
|
-
? chatAssistantBubble(
|
|
70
|
-
escapeHtml(truncate(previewText, PREVIEW_TRUNCATE_LENGTH)),
|
|
71
|
-
)
|
|
72
|
-
: "";
|
|
73
|
-
|
|
74
|
-
return htmlShell([
|
|
75
|
-
bodyParagraph("A chat has been shared with you."),
|
|
76
|
-
chatUserBubble(title) + previewHtml,
|
|
32
|
+
blocks.push(
|
|
77
33
|
chatDots(),
|
|
78
34
|
ctaBox({
|
|
79
35
|
label: "SEE MORE",
|
|
@@ -83,7 +39,9 @@ export function renderChatSharedHtml(payload: ChatSharedPayload): string {
|
|
|
83
39
|
maxWidth: "200px",
|
|
84
40
|
}),
|
|
85
41
|
footer(`This share was sent via ${COMPANY_NAME}.`),
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
42
|
+
paragraph(NOTICE, "tight"),
|
|
43
|
+
signature(),
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
return blocks;
|
|
89
47
|
}
|
|
@@ -1,56 +1,35 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Company.md access-approved email (sent to the requester).
|
|
2
|
+
* Company.md access-approved email (sent to the requester).
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { EmailPayloads } from "../types";
|
|
6
6
|
|
|
7
|
-
import { baseTextLayout } from "./base-text";
|
|
8
7
|
import {
|
|
9
8
|
ACCESS_PHRASE,
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
type Block,
|
|
10
|
+
bold,
|
|
12
11
|
ctaBox,
|
|
13
12
|
footer,
|
|
14
13
|
greeting,
|
|
15
|
-
|
|
14
|
+
paragraph,
|
|
15
|
+
signature,
|
|
16
16
|
} from "./blocks";
|
|
17
17
|
import { COMPANY_NAME } from "./constants";
|
|
18
|
-
import { escapeHtml } from "./escape-html";
|
|
19
18
|
|
|
20
19
|
export type AccessApprovedPayload =
|
|
21
20
|
EmailPayloads["companyMd.access_request_approved"];
|
|
22
21
|
|
|
23
|
-
export function
|
|
24
|
-
payload: AccessApprovedPayload,
|
|
25
|
-
): string {
|
|
22
|
+
export function renderAccessApproved(payload: AccessApprovedPayload): Block[] {
|
|
26
23
|
const { approverName, docTitle, accessLevel, docUrl } = payload;
|
|
27
24
|
|
|
28
|
-
|
|
29
|
-
sections.push("Hi,");
|
|
30
|
-
sections.push("");
|
|
31
|
-
sections.push(
|
|
32
|
-
`${approverName} approved your request to access "${docTitle}" — you ${ACCESS_PHRASE[accessLevel]}.`,
|
|
33
|
-
);
|
|
34
|
-
sections.push("");
|
|
35
|
-
sections.push(asciiCtaBox("OPEN"));
|
|
36
|
-
sections.push("");
|
|
37
|
-
sections.push(docUrl);
|
|
38
|
-
sections.push("");
|
|
39
|
-
sections.push(`This notification was sent via ${COMPANY_NAME}.`);
|
|
40
|
-
|
|
41
|
-
return baseTextLayout(sections.join("\n"));
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
export function renderAccessApprovedHtml(
|
|
45
|
-
payload: AccessApprovedPayload,
|
|
46
|
-
): string {
|
|
47
|
-
const { approverName, docTitle, accessLevel, docUrl } = payload;
|
|
48
|
-
|
|
49
|
-
return htmlShell([
|
|
25
|
+
return [
|
|
50
26
|
greeting(),
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
27
|
+
paragraph([
|
|
28
|
+
bold(approverName),
|
|
29
|
+
" approved your request to access ",
|
|
30
|
+
bold(`"${docTitle}"`),
|
|
31
|
+
` — you ${ACCESS_PHRASE[accessLevel]}.`,
|
|
32
|
+
]),
|
|
54
33
|
ctaBox({
|
|
55
34
|
label: "OPEN",
|
|
56
35
|
href: docUrl,
|
|
@@ -58,10 +37,7 @@ export function renderAccessApprovedHtml(
|
|
|
58
37
|
fontSize: "16px",
|
|
59
38
|
maxWidth: "220px",
|
|
60
39
|
}),
|
|
61
|
-
footer(
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
"none",
|
|
65
|
-
),
|
|
66
|
-
]);
|
|
40
|
+
footer(`This notification was sent via ${COMPANY_NAME}.`),
|
|
41
|
+
signature(),
|
|
42
|
+
];
|
|
67
43
|
}
|
|
@@ -1,63 +1,42 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Company.md access-denied email (sent to the requester). No CTA.
|
|
2
|
+
* Company.md access-denied email (sent to the requester). No CTA.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { EmailPayloads } from "../types";
|
|
6
6
|
|
|
7
|
-
import { baseTextLayout } from "./base-text";
|
|
8
7
|
import {
|
|
9
|
-
|
|
8
|
+
type Block,
|
|
9
|
+
bold,
|
|
10
|
+
chatUser,
|
|
10
11
|
footer,
|
|
11
12
|
greeting,
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
textQuote,
|
|
13
|
+
paragraph,
|
|
14
|
+
signature,
|
|
15
15
|
} from "./blocks";
|
|
16
16
|
import { COMPANY_NAME } from "./constants";
|
|
17
|
-
import { escapeHtml } from "./escape-html";
|
|
18
17
|
|
|
19
18
|
export type AccessDeniedPayload =
|
|
20
19
|
EmailPayloads["companyMd.access_request_denied"];
|
|
21
20
|
|
|
22
|
-
export function
|
|
21
|
+
export function renderAccessDenied(payload: AccessDeniedPayload): Block[] {
|
|
23
22
|
const { approverName, docTitle, reason } = payload;
|
|
24
23
|
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
24
|
+
const blocks: Block[] = [
|
|
25
|
+
greeting(),
|
|
26
|
+
paragraph([
|
|
27
|
+
bold(approverName),
|
|
28
|
+
" declined your request to access ",
|
|
29
|
+
bold(`"${docTitle}"`),
|
|
30
|
+
".",
|
|
31
|
+
]),
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
if (reason) blocks.push(chatUser(reason, `Reason from ${approverName}`));
|
|
35
|
+
|
|
36
|
+
blocks.push(
|
|
37
|
+
footer(`This notification was sent via ${COMPANY_NAME}.`),
|
|
38
|
+
signature(),
|
|
30
39
|
);
|
|
31
40
|
|
|
32
|
-
|
|
33
|
-
sections.push("");
|
|
34
|
-
sections.push(textQuote(`Reason from ${approverName}`, reason));
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
sections.push("");
|
|
38
|
-
sections.push(`This notification was sent via ${COMPANY_NAME}.`);
|
|
39
|
-
|
|
40
|
-
return baseTextLayout(sections.join("\n"));
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function renderAccessDeniedHtml(payload: AccessDeniedPayload): string {
|
|
44
|
-
const { approverName, docTitle, reason } = payload;
|
|
45
|
-
|
|
46
|
-
return htmlShell([
|
|
47
|
-
greeting(),
|
|
48
|
-
bodyParagraph(
|
|
49
|
-
`<b>${escapeHtml(approverName)}</b> declined your request to access <b>"${escapeHtml(docTitle)}"</b>.`,
|
|
50
|
-
),
|
|
51
|
-
reason
|
|
52
|
-
? quoteBlock(
|
|
53
|
-
`Reason from ${escapeHtml(approverName)}:`,
|
|
54
|
-
escapeHtml(reason),
|
|
55
|
-
)
|
|
56
|
-
: "",
|
|
57
|
-
footer(
|
|
58
|
-
`This notification was sent via ${COMPANY_NAME}.`,
|
|
59
|
-
undefined,
|
|
60
|
-
"none",
|
|
61
|
-
),
|
|
62
|
-
]);
|
|
41
|
+
return blocks;
|
|
63
42
|
}
|
|
@@ -1,71 +1,44 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Company.md access-requested email (sent to the doc owner).
|
|
2
|
+
* Company.md access-requested email (sent to the doc owner).
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { EmailPayloads } from "../types";
|
|
6
6
|
|
|
7
|
-
import { baseTextLayout } from "./base-text";
|
|
8
7
|
import {
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
type Block,
|
|
9
|
+
bold,
|
|
10
|
+
chatUser,
|
|
11
11
|
ctaBox,
|
|
12
12
|
footer,
|
|
13
13
|
greeting,
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
signatureLine,
|
|
17
|
-
textQuote,
|
|
14
|
+
paragraph,
|
|
15
|
+
signature,
|
|
18
16
|
} from "./blocks";
|
|
19
17
|
import { COMPANY_NAME } from "./constants";
|
|
20
|
-
import { escapeHtml } from "./escape-html";
|
|
21
18
|
|
|
22
19
|
export type AccessRequestedPayload =
|
|
23
20
|
EmailPayloads["companyMd.access_requested"];
|
|
24
21
|
|
|
25
22
|
const OWNER_NOTE = "You are receiving this because you own this document.";
|
|
26
23
|
|
|
27
|
-
export function
|
|
24
|
+
export function renderAccessRequested(
|
|
28
25
|
payload: AccessRequestedPayload,
|
|
29
|
-
):
|
|
26
|
+
): Block[] {
|
|
30
27
|
const { requesterName, docTitle, message, reviewUrl } = payload;
|
|
31
28
|
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
29
|
+
const blocks: Block[] = [
|
|
30
|
+
greeting(),
|
|
31
|
+
paragraph([
|
|
32
|
+
bold(requesterName),
|
|
33
|
+
" is requesting access to ",
|
|
34
|
+
bold(`"${docTitle}"`),
|
|
35
|
+
".",
|
|
36
|
+
]),
|
|
37
|
+
];
|
|
41
38
|
|
|
42
|
-
|
|
43
|
-
sections.push(asciiCtaBox("REVIEW"));
|
|
44
|
-
sections.push("");
|
|
45
|
-
sections.push(reviewUrl);
|
|
46
|
-
sections.push("");
|
|
47
|
-
sections.push(`This notification was sent via ${COMPANY_NAME}.`);
|
|
48
|
-
sections.push(OWNER_NOTE);
|
|
39
|
+
if (message) blocks.push(chatUser(message, `Message from ${requesterName}`));
|
|
49
40
|
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
export function renderAccessRequestedHtml(
|
|
54
|
-
payload: AccessRequestedPayload,
|
|
55
|
-
): string {
|
|
56
|
-
const { requesterName, docTitle, message, reviewUrl } = payload;
|
|
57
|
-
|
|
58
|
-
return htmlShell([
|
|
59
|
-
greeting(),
|
|
60
|
-
bodyParagraph(
|
|
61
|
-
`<b>${escapeHtml(requesterName)}</b> is requesting access to <b>"${escapeHtml(docTitle)}"</b>.`,
|
|
62
|
-
),
|
|
63
|
-
message
|
|
64
|
-
? quoteBlock(
|
|
65
|
-
`Message from ${escapeHtml(requesterName)}:`,
|
|
66
|
-
escapeHtml(message),
|
|
67
|
-
)
|
|
68
|
-
: "",
|
|
41
|
+
blocks.push(
|
|
69
42
|
ctaBox({
|
|
70
43
|
label: "REVIEW",
|
|
71
44
|
href: reviewUrl,
|
|
@@ -74,6 +47,8 @@ export function renderAccessRequestedHtml(
|
|
|
74
47
|
maxWidth: "220px",
|
|
75
48
|
}),
|
|
76
49
|
footer(`This notification was sent via ${COMPANY_NAME}.`, OWNER_NOTE),
|
|
77
|
-
|
|
78
|
-
|
|
50
|
+
signature(),
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
return blocks;
|
|
79
54
|
}
|
|
@@ -16,9 +16,3 @@ export const SUPPORT_EMAIL = "support@companysemantics.ai";
|
|
|
16
16
|
/** Monospace font stack for HTML emails. */
|
|
17
17
|
export const MONO_FONT_STACK =
|
|
18
18
|
"'SF Mono', SFMono-Regular, Consolas, 'Liberation Mono', Menlo, monospace";
|
|
19
|
-
|
|
20
|
-
/** Max chat-title length before truncation (chat-shared). */
|
|
21
|
-
export const TITLE_TRUNCATE_LENGTH = 30;
|
|
22
|
-
|
|
23
|
-
/** Max preview-text length before truncation (chat-shared). */
|
|
24
|
-
export const PREVIEW_TRUNCATE_LENGTH = 70;
|
|
@@ -6,40 +6,30 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
// Constants
|
|
9
|
-
export {
|
|
10
|
-
COMPANY_NAME,
|
|
11
|
-
SUPPORT_EMAIL,
|
|
12
|
-
MONO_FONT_STACK,
|
|
13
|
-
TITLE_TRUNCATE_LENGTH,
|
|
14
|
-
PREVIEW_TRUNCATE_LENGTH,
|
|
15
|
-
} from "./constants";
|
|
9
|
+
export { COMPANY_NAME, SUPPORT_EMAIL, MONO_FONT_STACK } from "./constants";
|
|
16
10
|
|
|
17
11
|
// Primitives
|
|
18
12
|
export { escapeHtml } from "./escape-html";
|
|
19
|
-
export { baseTextLayout } from "./base-text";
|
|
20
13
|
|
|
21
|
-
//
|
|
14
|
+
// Dual-output building blocks (each returns a `Block` = { html, text })
|
|
22
15
|
export {
|
|
23
16
|
htmlShell,
|
|
24
|
-
|
|
17
|
+
textShell,
|
|
18
|
+
paragraph,
|
|
25
19
|
greeting,
|
|
26
|
-
|
|
27
|
-
metaRow,
|
|
20
|
+
keyValue,
|
|
28
21
|
footer,
|
|
29
|
-
|
|
22
|
+
signature,
|
|
30
23
|
NOTICE,
|
|
31
24
|
ACCESS_PHRASE,
|
|
32
25
|
chatDots,
|
|
33
26
|
ctaBox,
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
chatDotsText,
|
|
41
|
-
chatUserBoxText,
|
|
42
|
-
chatAssistantBoxText,
|
|
27
|
+
chatUser,
|
|
28
|
+
chatAssistant,
|
|
29
|
+
bold,
|
|
30
|
+
type Block,
|
|
31
|
+
type Inline,
|
|
32
|
+
type InlineContent,
|
|
43
33
|
type Spacing,
|
|
44
34
|
type CtaBoxOptions,
|
|
45
35
|
} from "./blocks";
|
|
@@ -53,39 +43,6 @@ export {
|
|
|
53
43
|
type RenderEmailOptions,
|
|
54
44
|
} from "./render-email";
|
|
55
45
|
|
|
56
|
-
//
|
|
57
|
-
export {
|
|
58
|
-
|
|
59
|
-
renderAuthOtpHtml,
|
|
60
|
-
type AuthOtpPayload,
|
|
61
|
-
type RenderOptions,
|
|
62
|
-
} from "./auth-otp";
|
|
63
|
-
export { renderOrgInviteBody, renderOrgInviteHtml } from "./org-invite";
|
|
64
|
-
export { renderChatSharedBody, renderChatSharedHtml } from "./chat-shared";
|
|
65
|
-
export {
|
|
66
|
-
renderSecurityAlertBody,
|
|
67
|
-
SECURITY_ALERT_TYPES,
|
|
68
|
-
type SecurityAlertType,
|
|
69
|
-
} from "./security-alert";
|
|
70
|
-
export {
|
|
71
|
-
renderUnitOwnerGrantedBody,
|
|
72
|
-
renderUnitOwnerGrantedHtml,
|
|
73
|
-
} from "./unit-owner-granted";
|
|
74
|
-
export {
|
|
75
|
-
renderShareGrantedBody,
|
|
76
|
-
renderShareGrantedHtml,
|
|
77
|
-
} from "./share-granted";
|
|
78
|
-
export { renderOwnershipTransferBody } from "./ownership-transfer";
|
|
79
|
-
export { renderOwnershipTransferCompletedBody } from "./ownership-transfer-completed";
|
|
80
|
-
export {
|
|
81
|
-
renderAccessRequestedBody,
|
|
82
|
-
renderAccessRequestedHtml,
|
|
83
|
-
} from "./company-md-access-requested";
|
|
84
|
-
export {
|
|
85
|
-
renderAccessApprovedBody,
|
|
86
|
-
renderAccessApprovedHtml,
|
|
87
|
-
} from "./company-md-access-approved";
|
|
88
|
-
export {
|
|
89
|
-
renderAccessDeniedBody,
|
|
90
|
-
renderAccessDeniedHtml,
|
|
91
|
-
} from "./company-md-access-denied";
|
|
46
|
+
// Template payload types + alert vocabulary
|
|
47
|
+
export type { AuthOtpPayload, RenderOptions } from "./auth-otp";
|
|
48
|
+
export { SECURITY_ALERT_TYPES, type SecurityAlertType } from "./security-alert";
|
|
@@ -1,61 +1,38 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Org invite email (join a workspace).
|
|
2
|
+
* Org invite email (join a workspace).
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import type { EmailPayloads } from "../types";
|
|
6
6
|
|
|
7
|
-
import { baseTextLayout } from "./base-text";
|
|
8
7
|
import {
|
|
9
|
-
|
|
10
|
-
bodyParagraph,
|
|
8
|
+
type Block,
|
|
11
9
|
ctaBox,
|
|
12
10
|
footer,
|
|
13
|
-
|
|
14
|
-
metaRow,
|
|
11
|
+
keyValue,
|
|
15
12
|
NOTICE,
|
|
16
|
-
|
|
13
|
+
paragraph,
|
|
14
|
+
signature,
|
|
17
15
|
} from "./blocks";
|
|
18
16
|
import { COMPANY_NAME } from "./constants";
|
|
19
|
-
import { escapeHtml } from "./escape-html";
|
|
20
17
|
|
|
21
18
|
export type OrgInvitePayload = EmailPayloads["org.invite"];
|
|
22
19
|
|
|
23
|
-
export function
|
|
20
|
+
export function renderOrgInvite(payload: OrgInvitePayload): Block[] {
|
|
24
21
|
const { inviterName, orgName, role, acceptUrl, expiresInDays } = payload;
|
|
25
22
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
sections.push(asciiCtaBox("JOIN"));
|
|
29
|
-
sections.push("");
|
|
30
|
-
sections.push(`From: ${inviterName}`);
|
|
31
|
-
sections.push(`Workspace: ${orgName}`);
|
|
32
|
-
sections.push(`Role: ${role}`);
|
|
33
|
-
sections.push(`Expires in: ${expiresInDays} days`);
|
|
34
|
-
sections.push("");
|
|
35
|
-
sections.push(acceptUrl);
|
|
36
|
-
sections.push("");
|
|
37
|
-
sections.push(`This invitation was sent via ${COMPANY_NAME}.`);
|
|
38
|
-
sections.push(NOTICE);
|
|
39
|
-
|
|
40
|
-
return baseTextLayout(sections.join("\n"));
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export function renderOrgInviteHtml(payload: OrgInvitePayload): string {
|
|
44
|
-
const { inviterName, orgName, role, acceptUrl, expiresInDays } = payload;
|
|
45
|
-
|
|
46
|
-
return htmlShell([
|
|
47
|
-
bodyParagraph("Workspace invitation."),
|
|
23
|
+
return [
|
|
24
|
+
paragraph("Workspace invitation."),
|
|
48
25
|
ctaBox({
|
|
49
26
|
label: "JOIN",
|
|
50
27
|
href: acceptUrl,
|
|
51
28
|
padding: "20px 24px",
|
|
52
29
|
fontSize: "16px",
|
|
53
30
|
}),
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
31
|
+
keyValue("From", inviterName),
|
|
32
|
+
keyValue("Workspace", orgName),
|
|
33
|
+
keyValue("Role", role),
|
|
34
|
+
keyValue("Expires in", `${expiresInDays} days`, "normal"),
|
|
58
35
|
footer(`This invitation was sent via ${COMPANY_NAME}.`, NOTICE),
|
|
59
|
-
|
|
60
|
-
]
|
|
36
|
+
signature(),
|
|
37
|
+
];
|
|
61
38
|
}
|
|
@@ -4,28 +4,30 @@
|
|
|
4
4
|
|
|
5
5
|
import type { EmailPayloads } from "../types";
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import { type Block, bold, footer, paragraph, signature } from "./blocks";
|
|
8
8
|
|
|
9
9
|
export type OwnershipTransferCompletedPayload =
|
|
10
10
|
EmailPayloads["org.ownership_transfer_completed"];
|
|
11
11
|
|
|
12
|
-
export function
|
|
12
|
+
export function renderOwnershipTransferCompleted(
|
|
13
13
|
payload: OwnershipTransferCompletedPayload,
|
|
14
|
-
):
|
|
14
|
+
): Block[] {
|
|
15
15
|
const { orgName, newOwnerEmail } = payload;
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
17
|
+
return [
|
|
18
|
+
paragraph([
|
|
19
|
+
"The ownership of ",
|
|
20
|
+
bold(`"${orgName}"`),
|
|
21
|
+
" has been transferred to ",
|
|
22
|
+
bold(newOwnerEmail),
|
|
23
|
+
".",
|
|
24
|
+
]),
|
|
25
|
+
paragraph(
|
|
26
|
+
"You are no longer the owner of this workspace, but you remain an admin.",
|
|
27
|
+
),
|
|
28
|
+
footer(
|
|
29
|
+
"If you did not authorize this transfer, please contact support immediately.",
|
|
30
|
+
),
|
|
31
|
+
signature(),
|
|
32
|
+
];
|
|
31
33
|
}
|
|
@@ -4,33 +4,48 @@
|
|
|
4
4
|
|
|
5
5
|
import type { EmailPayloads } from "../types";
|
|
6
6
|
|
|
7
|
-
import {
|
|
8
|
-
|
|
7
|
+
import {
|
|
8
|
+
type Block,
|
|
9
|
+
bold,
|
|
10
|
+
chatUser,
|
|
11
|
+
ctaBox,
|
|
12
|
+
footer,
|
|
13
|
+
keyValue,
|
|
14
|
+
paragraph,
|
|
15
|
+
signature,
|
|
16
|
+
} from "./blocks";
|
|
9
17
|
|
|
10
18
|
export type OwnershipTransferPayload = EmailPayloads["org.ownership_transfer"];
|
|
11
19
|
|
|
12
|
-
export function
|
|
20
|
+
export function renderOwnershipTransfer(
|
|
13
21
|
payload: OwnershipTransferPayload,
|
|
14
|
-
):
|
|
22
|
+
): Block[] {
|
|
15
23
|
const { orgName, acceptUrl, expiresInDays, note } = payload;
|
|
16
24
|
|
|
17
|
-
const
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
25
|
+
const blocks: Block[] = [
|
|
26
|
+
paragraph([
|
|
27
|
+
"You have been invited to become the owner of ",
|
|
28
|
+
bold(`"${orgName}"`),
|
|
29
|
+
".",
|
|
30
|
+
]),
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
if (note) blocks.push(chatUser(note, "Message from the current owner"));
|
|
34
|
+
|
|
35
|
+
blocks.push(
|
|
36
|
+
ctaBox({
|
|
37
|
+
label: "ACCEPT",
|
|
38
|
+
href: acceptUrl,
|
|
39
|
+
padding: "16px 24px",
|
|
40
|
+
fontSize: "16px",
|
|
41
|
+
maxWidth: "220px",
|
|
42
|
+
}),
|
|
43
|
+
keyValue("Expires in", `${expiresInDays} days`, "normal"),
|
|
44
|
+
footer(
|
|
45
|
+
"If you did not expect this invitation, you can safely ignore this email.",
|
|
46
|
+
),
|
|
47
|
+
signature(),
|
|
33
48
|
);
|
|
34
49
|
|
|
35
|
-
return
|
|
50
|
+
return blocks;
|
|
36
51
|
}
|