@dilipod/ui 0.4.28 → 0.4.30
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/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +164 -0
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +157 -1
- package/dist/index.mjs.map +1 -1
- package/dist/lib/email.d.ts +48 -0
- package/dist/lib/email.d.ts.map +1 -0
- package/dist/lib/slack.d.ts +83 -0
- package/dist/lib/slack.d.ts.map +1 -0
- package/package.json +2 -2
- package/src/index.ts +7 -0
- package/src/lib/email.ts +146 -0
- package/src/lib/slack.ts +142 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email Template Helpers
|
|
3
|
+
*
|
|
4
|
+
* Shared branded email template and HTML building blocks.
|
|
5
|
+
* Used by both platform and admin apps via their local `lib/email.ts` re-exports.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Wrap email body content in the standard Dilipod branded template.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* emailTemplate({
|
|
12
|
+
* body: '<h2>Hello</h2><p>Content here</p>',
|
|
13
|
+
* })
|
|
14
|
+
*
|
|
15
|
+
* Or with a preheader (hidden preview text in email clients):
|
|
16
|
+
* emailTemplate({
|
|
17
|
+
* preheader: 'Quick summary shown in inbox',
|
|
18
|
+
* body: '...',
|
|
19
|
+
* })
|
|
20
|
+
*/
|
|
21
|
+
export declare function emailTemplate({ body, preheader }: {
|
|
22
|
+
body: string;
|
|
23
|
+
preheader?: string;
|
|
24
|
+
}): string;
|
|
25
|
+
/**
|
|
26
|
+
* Render a CTA button for use inside emailTemplate body.
|
|
27
|
+
*
|
|
28
|
+
* buttonHtml({ text: 'Reset Password', href: 'https://...' })
|
|
29
|
+
* buttonHtml({ text: 'View Incident', href: '...', variant: 'danger' })
|
|
30
|
+
*/
|
|
31
|
+
export declare function buttonHtml({ text, href, variant, }: {
|
|
32
|
+
text: string;
|
|
33
|
+
href: string;
|
|
34
|
+
variant?: 'primary' | 'danger';
|
|
35
|
+
}): string;
|
|
36
|
+
/**
|
|
37
|
+
* Render an info box (grey background) for key details.
|
|
38
|
+
*
|
|
39
|
+
* infoBoxHtml('<strong>Status:</strong> Deployed')
|
|
40
|
+
*/
|
|
41
|
+
export declare function infoBoxHtml(innerHtml: string): string;
|
|
42
|
+
/**
|
|
43
|
+
* Render a warning/note box (yellow background).
|
|
44
|
+
*
|
|
45
|
+
* noteBoxHtml('This link expires in 1 hour.')
|
|
46
|
+
*/
|
|
47
|
+
export declare function noteBoxHtml(text: string): string;
|
|
48
|
+
//# sourceMappingURL=email.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"email.d.ts","sourceRoot":"","sources":["../../src/lib/email.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,MAAM,CA0E/F;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,EACzB,IAAI,EACJ,IAAI,EACJ,OAAmB,GACpB,EAAE;IACD,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAA;CAC/B,GAAG,MAAM,CAYT;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAIrD;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAIhD"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slack Block Kit Helpers
|
|
3
|
+
*
|
|
4
|
+
* Shared Slack message and block builders used by both platform and admin apps.
|
|
5
|
+
* Pure functions with zero dependencies — same pattern as email.ts.
|
|
6
|
+
*
|
|
7
|
+
* Transport layers (sendSlackMessage, sendSlackWebhook) stay local in each app.
|
|
8
|
+
*/
|
|
9
|
+
export interface SlackBlock {
|
|
10
|
+
type: string;
|
|
11
|
+
text?: {
|
|
12
|
+
type: string;
|
|
13
|
+
text: string;
|
|
14
|
+
};
|
|
15
|
+
elements?: SlackElement[];
|
|
16
|
+
fields?: SlackField[];
|
|
17
|
+
}
|
|
18
|
+
export interface SlackElement {
|
|
19
|
+
type: string;
|
|
20
|
+
text?: {
|
|
21
|
+
type: string;
|
|
22
|
+
text: string;
|
|
23
|
+
};
|
|
24
|
+
style?: string;
|
|
25
|
+
value?: string;
|
|
26
|
+
url?: string;
|
|
27
|
+
action_id?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface SlackField {
|
|
30
|
+
type: string;
|
|
31
|
+
text: string;
|
|
32
|
+
}
|
|
33
|
+
export interface SlackMessage {
|
|
34
|
+
text: string;
|
|
35
|
+
blocks: SlackBlock[];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Build a single mrkdwn section block.
|
|
39
|
+
*
|
|
40
|
+
* slackSection('*Hello* world')
|
|
41
|
+
*/
|
|
42
|
+
export declare function slackSection(mrkdwn: string): SlackBlock;
|
|
43
|
+
/**
|
|
44
|
+
* Build a key-value fields block.
|
|
45
|
+
*
|
|
46
|
+
* slackFields({ Worker: 'Invoice Bot', Status: 'Live' })
|
|
47
|
+
*/
|
|
48
|
+
export declare function slackFields(fields: Record<string, string | number>): SlackBlock;
|
|
49
|
+
/**
|
|
50
|
+
* Build an actions block with buttons.
|
|
51
|
+
*
|
|
52
|
+
* slackActions([
|
|
53
|
+
* { text: 'View', url: 'https://...' },
|
|
54
|
+
* { text: 'Approve', actionId: 'approve', style: 'primary', value: '123' },
|
|
55
|
+
* ])
|
|
56
|
+
*/
|
|
57
|
+
export declare function slackActions(buttons: {
|
|
58
|
+
text: string;
|
|
59
|
+
url?: string;
|
|
60
|
+
value?: string;
|
|
61
|
+
actionId?: string;
|
|
62
|
+
style?: 'primary' | 'danger';
|
|
63
|
+
}[]): SlackBlock;
|
|
64
|
+
/**
|
|
65
|
+
* Build a complete Slack message with optional details, note, and button.
|
|
66
|
+
* Covers 90% of notification use cases.
|
|
67
|
+
*
|
|
68
|
+
* slackMessage({
|
|
69
|
+
* title: '🚀 New signup',
|
|
70
|
+
* details: { Name: 'John', Email: 'john@acme.com' },
|
|
71
|
+
* note: 'First user from this company',
|
|
72
|
+
* buttonText: 'View Customer',
|
|
73
|
+
* buttonUrl: 'https://admin.dilipod.com/customers/123',
|
|
74
|
+
* })
|
|
75
|
+
*/
|
|
76
|
+
export declare function slackMessage(options: {
|
|
77
|
+
title: string;
|
|
78
|
+
details?: Record<string, string | number>;
|
|
79
|
+
note?: string;
|
|
80
|
+
buttonText?: string;
|
|
81
|
+
buttonUrl?: string;
|
|
82
|
+
}): SlackMessage;
|
|
83
|
+
//# sourceMappingURL=slack.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slack.d.ts","sourceRoot":"","sources":["../../src/lib/slack.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;IACrC,QAAQ,CAAC,EAAE,YAAY,EAAE,CAAA;IACzB,MAAM,CAAC,EAAE,UAAU,EAAE,CAAA;CACtB;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAA;IACrC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;CACb;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,UAAU,EAAE,CAAA;CACrB;AAMD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,UAAU,CAKvD;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,GAAG,UAAU,CAQ/E;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAC1B,OAAO,EAAE;IACP,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,SAAS,GAAG,QAAQ,CAAA;CAC7B,EAAE,GACF,UAAU,CAYZ;AAMD;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE;IACpC,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAA;IACzC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,GAAG,YAAY,CAmBf"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dilipod/ui",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.30",
|
|
4
4
|
"description": "Dilipod Design System - Shared UI components and styles",
|
|
5
5
|
"author": "Dilipod <hello@dilipod.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -111,7 +111,7 @@
|
|
|
111
111
|
"tailwindcss": "^4.1.8",
|
|
112
112
|
"tsup": "^8.5.0",
|
|
113
113
|
"typescript": "^5.8.3",
|
|
114
|
-
"vite": "^
|
|
114
|
+
"vite": "^6.0.0",
|
|
115
115
|
"vitest": "^1.2.0"
|
|
116
116
|
}
|
|
117
117
|
}
|
package/src/index.ts
CHANGED
|
@@ -315,6 +315,13 @@ export { useServiceWorker } from './hooks/use-service-worker'
|
|
|
315
315
|
// Formatting Utilities
|
|
316
316
|
export { formatCentsToEuros, formatEuros, formatDuration, formatRelativeTime } from './lib/formatting'
|
|
317
317
|
|
|
318
|
+
// Email Template Helpers
|
|
319
|
+
export { emailTemplate, buttonHtml, infoBoxHtml, noteBoxHtml } from './lib/email'
|
|
320
|
+
|
|
321
|
+
// Slack Block Kit Helpers
|
|
322
|
+
export { slackMessage, slackSection, slackFields, slackActions } from './lib/slack'
|
|
323
|
+
export type { SlackBlock, SlackElement, SlackField, SlackMessage } from './lib/slack'
|
|
324
|
+
|
|
318
325
|
// Utilities
|
|
319
326
|
export { cn } from './lib/utils'
|
|
320
327
|
|
package/src/lib/email.ts
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Email Template Helpers
|
|
3
|
+
*
|
|
4
|
+
* Shared branded email template and HTML building blocks.
|
|
5
|
+
* Used by both platform and admin apps via their local `lib/email.ts` re-exports.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Wrap email body content in the standard Dilipod branded template.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* emailTemplate({
|
|
13
|
+
* body: '<h2>Hello</h2><p>Content here</p>',
|
|
14
|
+
* })
|
|
15
|
+
*
|
|
16
|
+
* Or with a preheader (hidden preview text in email clients):
|
|
17
|
+
* emailTemplate({
|
|
18
|
+
* preheader: 'Quick summary shown in inbox',
|
|
19
|
+
* body: '...',
|
|
20
|
+
* })
|
|
21
|
+
*/
|
|
22
|
+
export function emailTemplate({ body, preheader }: { body: string; preheader?: string }): string {
|
|
23
|
+
const preheaderHtml = preheader
|
|
24
|
+
? `<div style="display:none;font-size:1px;color:#ffffff;line-height:1px;max-height:0;max-width:0;opacity:0;overflow:hidden;">${preheader}</div>`
|
|
25
|
+
: ''
|
|
26
|
+
|
|
27
|
+
return `<!DOCTYPE html>
|
|
28
|
+
<html lang="en">
|
|
29
|
+
<head>
|
|
30
|
+
<meta charset="utf-8" />
|
|
31
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
32
|
+
<meta name="color-scheme" content="light" />
|
|
33
|
+
<meta name="supported-color-schemes" content="light" />
|
|
34
|
+
<title>Dilipod</title>
|
|
35
|
+
</head>
|
|
36
|
+
<body style="margin: 0; padding: 0; background-color: #f0f0f0; -webkit-font-smoothing: antialiased;">
|
|
37
|
+
${preheaderHtml}
|
|
38
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background-color: #f0f0f0;">
|
|
39
|
+
<tr>
|
|
40
|
+
<td align="center" style="padding: 40px 16px;">
|
|
41
|
+
<!-- Main card -->
|
|
42
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="max-width: 560px;">
|
|
43
|
+
<!-- Logo header -->
|
|
44
|
+
<tr>
|
|
45
|
+
<td style="padding: 0 0 24px 0;">
|
|
46
|
+
<table role="presentation" cellpadding="0" cellspacing="0">
|
|
47
|
+
<tr>
|
|
48
|
+
<td style="width: 32px; height: 32px; background-color: #00e5cc; border-radius: 6px; text-align: center; vertical-align: middle; font-size: 16px; font-weight: 700; color: #111111; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;">
|
|
49
|
+
D
|
|
50
|
+
</td>
|
|
51
|
+
<td style="padding-left: 10px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 17px; font-weight: 600; color: #111111; letter-spacing: -0.2px;">
|
|
52
|
+
Dilipod
|
|
53
|
+
</td>
|
|
54
|
+
</tr>
|
|
55
|
+
</table>
|
|
56
|
+
</td>
|
|
57
|
+
</tr>
|
|
58
|
+
<!-- Content card -->
|
|
59
|
+
<tr>
|
|
60
|
+
<td>
|
|
61
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background-color: #ffffff; border-radius: 6px; overflow: hidden; border: 1px solid #e2e2e2;">
|
|
62
|
+
<!-- Accent bar -->
|
|
63
|
+
<tr>
|
|
64
|
+
<td style="height: 3px; background: linear-gradient(90deg, #00e5cc 0%, #00c8b5 100%); font-size: 0; line-height: 0;"> </td>
|
|
65
|
+
</tr>
|
|
66
|
+
<!-- Body content -->
|
|
67
|
+
<tr>
|
|
68
|
+
<td style="padding: 36px 40px 40px 40px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 15px; line-height: 1.7; color: #374151;">
|
|
69
|
+
${body}
|
|
70
|
+
</td>
|
|
71
|
+
</tr>
|
|
72
|
+
</table>
|
|
73
|
+
</td>
|
|
74
|
+
</tr>
|
|
75
|
+
<!-- Footer -->
|
|
76
|
+
<tr>
|
|
77
|
+
<td style="padding: 24px 4px 0 4px;">
|
|
78
|
+
<table role="presentation" width="100%" cellpadding="0" cellspacing="0">
|
|
79
|
+
<tr>
|
|
80
|
+
<td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 12px; color: #9ca3af;">
|
|
81
|
+
© ${new Date().getFullYear()} Dilipod — Your Digital Workforce
|
|
82
|
+
</td>
|
|
83
|
+
<td align="right" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 12px;">
|
|
84
|
+
<a href="https://dilipod.com" style="color: #9ca3af; text-decoration: none;">dilipod.com</a>
|
|
85
|
+
</td>
|
|
86
|
+
</tr>
|
|
87
|
+
</table>
|
|
88
|
+
</td>
|
|
89
|
+
</tr>
|
|
90
|
+
</table>
|
|
91
|
+
</td>
|
|
92
|
+
</tr>
|
|
93
|
+
</table>
|
|
94
|
+
</body>
|
|
95
|
+
</html>`
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Render a CTA button for use inside emailTemplate body.
|
|
100
|
+
*
|
|
101
|
+
* buttonHtml({ text: 'Reset Password', href: 'https://...' })
|
|
102
|
+
* buttonHtml({ text: 'View Incident', href: '...', variant: 'danger' })
|
|
103
|
+
*/
|
|
104
|
+
export function buttonHtml({
|
|
105
|
+
text,
|
|
106
|
+
href,
|
|
107
|
+
variant = 'primary',
|
|
108
|
+
}: {
|
|
109
|
+
text: string
|
|
110
|
+
href: string
|
|
111
|
+
variant?: 'primary' | 'danger'
|
|
112
|
+
}): string {
|
|
113
|
+
const bg = variant === 'danger' ? '#dc2626' : '#111111'
|
|
114
|
+
const color = '#ffffff'
|
|
115
|
+
return `<table role="presentation" cellpadding="0" cellspacing="0" style="margin-top: 24px;">
|
|
116
|
+
<tr>
|
|
117
|
+
<td style="background-color: ${bg}; border-radius: 6px;">
|
|
118
|
+
<a href="${href}" style="display: inline-block; padding: 12px 32px; color: ${color}; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 14px; font-weight: 600; text-decoration: none;">
|
|
119
|
+
${text} →
|
|
120
|
+
</a>
|
|
121
|
+
</td>
|
|
122
|
+
</tr>
|
|
123
|
+
</table>`
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Render an info box (grey background) for key details.
|
|
128
|
+
*
|
|
129
|
+
* infoBoxHtml('<strong>Status:</strong> Deployed')
|
|
130
|
+
*/
|
|
131
|
+
export function infoBoxHtml(innerHtml: string): string {
|
|
132
|
+
return `<div style="background-color: #f8fafb; padding: 16px 20px; border-radius: 6px; border: 1px solid #e5e7eb; margin: 20px 0; border-left: 3px solid #00e5cc;">
|
|
133
|
+
${innerHtml}
|
|
134
|
+
</div>`
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Render a warning/note box (yellow background).
|
|
139
|
+
*
|
|
140
|
+
* noteBoxHtml('This link expires in 1 hour.')
|
|
141
|
+
*/
|
|
142
|
+
export function noteBoxHtml(text: string): string {
|
|
143
|
+
return `<div style="background-color: #fffbeb; padding: 14px 18px; border-radius: 6px; border: 1px solid #fde68a; margin: 20px 0; font-size: 13px; color: #92400e; border-left: 3px solid #f59e0b;">
|
|
144
|
+
${text}
|
|
145
|
+
</div>`
|
|
146
|
+
}
|
package/src/lib/slack.ts
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slack Block Kit Helpers
|
|
3
|
+
*
|
|
4
|
+
* Shared Slack message and block builders used by both platform and admin apps.
|
|
5
|
+
* Pure functions with zero dependencies — same pattern as email.ts.
|
|
6
|
+
*
|
|
7
|
+
* Transport layers (sendSlackMessage, sendSlackWebhook) stay local in each app.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// ============================================
|
|
11
|
+
// Types
|
|
12
|
+
// ============================================
|
|
13
|
+
|
|
14
|
+
export interface SlackBlock {
|
|
15
|
+
type: string
|
|
16
|
+
text?: { type: string; text: string }
|
|
17
|
+
elements?: SlackElement[]
|
|
18
|
+
fields?: SlackField[]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface SlackElement {
|
|
22
|
+
type: string
|
|
23
|
+
text?: { type: string; text: string }
|
|
24
|
+
style?: string
|
|
25
|
+
value?: string
|
|
26
|
+
url?: string
|
|
27
|
+
action_id?: string
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface SlackField {
|
|
31
|
+
type: string
|
|
32
|
+
text: string
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface SlackMessage {
|
|
36
|
+
text: string
|
|
37
|
+
blocks: SlackBlock[]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ============================================
|
|
41
|
+
// Block Builders
|
|
42
|
+
// ============================================
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Build a single mrkdwn section block.
|
|
46
|
+
*
|
|
47
|
+
* slackSection('*Hello* world')
|
|
48
|
+
*/
|
|
49
|
+
export function slackSection(mrkdwn: string): SlackBlock {
|
|
50
|
+
return {
|
|
51
|
+
type: 'section',
|
|
52
|
+
text: { type: 'mrkdwn', text: mrkdwn },
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Build a key-value fields block.
|
|
58
|
+
*
|
|
59
|
+
* slackFields({ Worker: 'Invoice Bot', Status: 'Live' })
|
|
60
|
+
*/
|
|
61
|
+
export function slackFields(fields: Record<string, string | number>): SlackBlock {
|
|
62
|
+
return {
|
|
63
|
+
type: 'section',
|
|
64
|
+
fields: Object.entries(fields).map(([key, value]) => ({
|
|
65
|
+
type: 'mrkdwn',
|
|
66
|
+
text: `*${key}:*\n${value}`,
|
|
67
|
+
})),
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Build an actions block with buttons.
|
|
73
|
+
*
|
|
74
|
+
* slackActions([
|
|
75
|
+
* { text: 'View', url: 'https://...' },
|
|
76
|
+
* { text: 'Approve', actionId: 'approve', style: 'primary', value: '123' },
|
|
77
|
+
* ])
|
|
78
|
+
*/
|
|
79
|
+
export function slackActions(
|
|
80
|
+
buttons: {
|
|
81
|
+
text: string
|
|
82
|
+
url?: string
|
|
83
|
+
value?: string
|
|
84
|
+
actionId?: string
|
|
85
|
+
style?: 'primary' | 'danger'
|
|
86
|
+
}[]
|
|
87
|
+
): SlackBlock {
|
|
88
|
+
return {
|
|
89
|
+
type: 'actions',
|
|
90
|
+
elements: buttons.map((btn) => ({
|
|
91
|
+
type: 'button',
|
|
92
|
+
text: { type: 'plain_text', text: btn.text },
|
|
93
|
+
...(btn.url ? { url: btn.url } : {}),
|
|
94
|
+
...(btn.value ? { value: btn.value } : {}),
|
|
95
|
+
...(btn.actionId ? { action_id: btn.actionId } : {}),
|
|
96
|
+
...(btn.style ? { style: btn.style } : {}),
|
|
97
|
+
})),
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ============================================
|
|
102
|
+
// Message Builder
|
|
103
|
+
// ============================================
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Build a complete Slack message with optional details, note, and button.
|
|
107
|
+
* Covers 90% of notification use cases.
|
|
108
|
+
*
|
|
109
|
+
* slackMessage({
|
|
110
|
+
* title: '🚀 New signup',
|
|
111
|
+
* details: { Name: 'John', Email: 'john@acme.com' },
|
|
112
|
+
* note: 'First user from this company',
|
|
113
|
+
* buttonText: 'View Customer',
|
|
114
|
+
* buttonUrl: 'https://admin.dilipod.com/customers/123',
|
|
115
|
+
* })
|
|
116
|
+
*/
|
|
117
|
+
export function slackMessage(options: {
|
|
118
|
+
title: string
|
|
119
|
+
details?: Record<string, string | number>
|
|
120
|
+
note?: string
|
|
121
|
+
buttonText?: string
|
|
122
|
+
buttonUrl?: string
|
|
123
|
+
}): SlackMessage {
|
|
124
|
+
const blocks: SlackBlock[] = [slackSection(`*${options.title}*`)]
|
|
125
|
+
|
|
126
|
+
if (options.details && Object.keys(options.details).length > 0) {
|
|
127
|
+
blocks.push(slackFields(options.details))
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if (options.note) {
|
|
131
|
+
blocks.push(slackSection(options.note))
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (options.buttonUrl && options.buttonText) {
|
|
135
|
+
blocks.push(slackActions([{ text: options.buttonText, url: options.buttonUrl }]))
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
text: options.title,
|
|
140
|
+
blocks,
|
|
141
|
+
}
|
|
142
|
+
}
|