@dilipod/ui 0.4.32 → 0.4.34
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/components/settings-nav.d.ts.map +1 -1
- package/dist/components/sheet.d.ts +1 -0
- package/dist/components/sheet.d.ts.map +1 -1
- package/dist/index.js +7 -13
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +7 -13
- package/dist/index.mjs.map +1 -1
- package/dist/lib/slack.d.ts +98 -0
- package/dist/lib/slack.d.ts.map +1 -1
- package/dist/server.d.ts +2 -2
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +66 -0
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +62 -1
- package/dist/server.mjs.map +1 -1
- package/package.json +6 -6
- package/src/components/settings-nav.tsx +4 -8
- package/src/components/sheet.tsx +11 -7
- package/src/lib/slack.ts +151 -0
- package/src/server.ts +19 -2
package/dist/lib/slack.d.ts
CHANGED
|
@@ -61,6 +61,104 @@ export declare function slackActions(buttons: {
|
|
|
61
61
|
actionId?: string;
|
|
62
62
|
style?: 'primary' | 'danger';
|
|
63
63
|
}[]): SlackBlock;
|
|
64
|
+
export interface SlackModalView {
|
|
65
|
+
type: 'modal';
|
|
66
|
+
callback_id: string;
|
|
67
|
+
title: {
|
|
68
|
+
type: 'plain_text';
|
|
69
|
+
text: string;
|
|
70
|
+
};
|
|
71
|
+
submit?: {
|
|
72
|
+
type: 'plain_text';
|
|
73
|
+
text: string;
|
|
74
|
+
};
|
|
75
|
+
close?: {
|
|
76
|
+
type: 'plain_text';
|
|
77
|
+
text: string;
|
|
78
|
+
};
|
|
79
|
+
blocks: SlackBlock[];
|
|
80
|
+
private_metadata?: string;
|
|
81
|
+
}
|
|
82
|
+
export interface SlackOptionGroup {
|
|
83
|
+
label: {
|
|
84
|
+
type: 'plain_text';
|
|
85
|
+
text: string;
|
|
86
|
+
};
|
|
87
|
+
options: Array<{
|
|
88
|
+
text: {
|
|
89
|
+
type: 'plain_text';
|
|
90
|
+
text: string;
|
|
91
|
+
};
|
|
92
|
+
value: string;
|
|
93
|
+
}>;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Build a modal view for Slack.
|
|
97
|
+
*
|
|
98
|
+
* slackModal({
|
|
99
|
+
* callbackId: 'run_process',
|
|
100
|
+
* title: 'Run a Process',
|
|
101
|
+
* submitText: 'Run',
|
|
102
|
+
* blocks: [...],
|
|
103
|
+
* })
|
|
104
|
+
*/
|
|
105
|
+
export declare function slackModal(options: {
|
|
106
|
+
callbackId: string;
|
|
107
|
+
title: string;
|
|
108
|
+
submitText?: string;
|
|
109
|
+
closeText?: string;
|
|
110
|
+
blocks: SlackBlock[];
|
|
111
|
+
privateMetadata?: string;
|
|
112
|
+
}): SlackModalView;
|
|
113
|
+
/**
|
|
114
|
+
* Build a static select input block (for modals or messages).
|
|
115
|
+
*
|
|
116
|
+
* slackStaticSelect({
|
|
117
|
+
* blockId: 'process_select',
|
|
118
|
+
* actionId: 'process_id',
|
|
119
|
+
* label: 'Process',
|
|
120
|
+
* placeholder: 'Select a process',
|
|
121
|
+
* optionGroups: [
|
|
122
|
+
* { label: 'Invoice Bot', options: [{ text: 'Send Invoices', value: 'uuid' }] },
|
|
123
|
+
* ],
|
|
124
|
+
* })
|
|
125
|
+
*/
|
|
126
|
+
export declare function slackStaticSelect(options: {
|
|
127
|
+
blockId: string;
|
|
128
|
+
actionId: string;
|
|
129
|
+
label: string;
|
|
130
|
+
placeholder?: string;
|
|
131
|
+
options?: Array<{
|
|
132
|
+
text: string;
|
|
133
|
+
value: string;
|
|
134
|
+
}>;
|
|
135
|
+
optionGroups?: Array<{
|
|
136
|
+
label: string;
|
|
137
|
+
options: Array<{
|
|
138
|
+
text: string;
|
|
139
|
+
value: string;
|
|
140
|
+
}>;
|
|
141
|
+
}>;
|
|
142
|
+
}): SlackBlock;
|
|
143
|
+
/**
|
|
144
|
+
* Build a plain text input block (for modals).
|
|
145
|
+
*/
|
|
146
|
+
export declare function slackTextInput(options: {
|
|
147
|
+
blockId: string;
|
|
148
|
+
actionId: string;
|
|
149
|
+
label: string;
|
|
150
|
+
placeholder?: string;
|
|
151
|
+
optional?: boolean;
|
|
152
|
+
multiline?: boolean;
|
|
153
|
+
}): SlackBlock;
|
|
154
|
+
/**
|
|
155
|
+
* Build a divider block.
|
|
156
|
+
*/
|
|
157
|
+
export declare function slackDivider(): SlackBlock;
|
|
158
|
+
/**
|
|
159
|
+
* Build a context block (small muted text).
|
|
160
|
+
*/
|
|
161
|
+
export declare function slackContext(text: string): SlackBlock;
|
|
64
162
|
/**
|
|
65
163
|
* Build a complete Slack message with optional details, note, and button.
|
|
66
164
|
* Covers 90% of notification use cases.
|
package/dist/lib/slack.d.ts.map
CHANGED
|
@@ -1 +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"}
|
|
1
|
+
{"version":3,"file":"slack.d.ts","sourceRoot":"","sources":["../../src/lib/slack.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts
CHANGED
|
@@ -6,8 +6,8 @@
|
|
|
6
6
|
* server actions, and other server-side code.
|
|
7
7
|
*/
|
|
8
8
|
export { emailTemplate, buttonHtml, infoBoxHtml, noteBoxHtml } from './lib/email';
|
|
9
|
-
export { slackMessage, slackSection, slackFields, slackActions } from './lib/slack';
|
|
10
|
-
export type { SlackBlock, SlackElement, SlackField, SlackMessage } from './lib/slack';
|
|
9
|
+
export { slackMessage, slackSection, slackFields, slackActions, slackModal, slackStaticSelect, slackTextInput, slackDivider, slackContext, } from './lib/slack';
|
|
10
|
+
export type { SlackBlock, SlackElement, SlackField, SlackMessage, SlackModalView, SlackOptionGroup, } from './lib/slack';
|
|
11
11
|
export { formatCentsToEuros, formatEuros, formatDuration, formatRelativeTime } from './lib/formatting';
|
|
12
12
|
export { cn } from './lib/utils';
|
|
13
13
|
//# sourceMappingURL=server.d.ts.map
|
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAGjF,OAAO,
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AAGjF,OAAO,EACL,YAAY,EACZ,YAAY,EACZ,WAAW,EACX,YAAY,EACZ,UAAU,EACV,iBAAiB,EACjB,cAAc,EACd,YAAY,EACZ,YAAY,GACb,MAAM,aAAa,CAAA;AACpB,YAAY,EACV,UAAU,EACV,YAAY,EACZ,UAAU,EACV,YAAY,EACZ,cAAc,EACd,gBAAgB,GACjB,MAAM,aAAa,CAAA;AAGpB,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAA;AAGtG,OAAO,EAAE,EAAE,EAAE,MAAM,aAAa,CAAA"}
|
package/dist/server.js
CHANGED
|
@@ -135,6 +135,67 @@ function slackActions(buttons) {
|
|
|
135
135
|
}))
|
|
136
136
|
};
|
|
137
137
|
}
|
|
138
|
+
function slackModal(options) {
|
|
139
|
+
return {
|
|
140
|
+
type: "modal",
|
|
141
|
+
callback_id: options.callbackId,
|
|
142
|
+
title: { type: "plain_text", text: options.title },
|
|
143
|
+
...options.submitText ? { submit: { type: "plain_text", text: options.submitText } } : {},
|
|
144
|
+
...options.closeText ? { close: { type: "plain_text", text: options.closeText } } : {},
|
|
145
|
+
blocks: options.blocks,
|
|
146
|
+
...options.privateMetadata ? { private_metadata: options.privateMetadata } : {}
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
function slackStaticSelect(options) {
|
|
150
|
+
const element = {
|
|
151
|
+
type: "static_select",
|
|
152
|
+
action_id: options.actionId,
|
|
153
|
+
placeholder: { type: "plain_text", text: options.placeholder || "Select..." }
|
|
154
|
+
};
|
|
155
|
+
if (options.optionGroups) {
|
|
156
|
+
element.option_groups = options.optionGroups.map((group) => ({
|
|
157
|
+
label: { type: "plain_text", text: group.label },
|
|
158
|
+
options: group.options.map((opt) => ({
|
|
159
|
+
text: { type: "plain_text", text: opt.text },
|
|
160
|
+
value: opt.value
|
|
161
|
+
}))
|
|
162
|
+
}));
|
|
163
|
+
} else if (options.options) {
|
|
164
|
+
element.options = options.options.map((opt) => ({
|
|
165
|
+
text: { type: "plain_text", text: opt.text },
|
|
166
|
+
value: opt.value
|
|
167
|
+
}));
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
type: "input",
|
|
171
|
+
block_id: options.blockId,
|
|
172
|
+
label: { type: "plain_text", text: options.label },
|
|
173
|
+
element
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
function slackTextInput(options) {
|
|
177
|
+
return {
|
|
178
|
+
type: "input",
|
|
179
|
+
block_id: options.blockId,
|
|
180
|
+
optional: options.optional ?? false,
|
|
181
|
+
label: { type: "plain_text", text: options.label },
|
|
182
|
+
element: {
|
|
183
|
+
type: "plain_text_input",
|
|
184
|
+
action_id: options.actionId,
|
|
185
|
+
...options.placeholder ? { placeholder: { type: "plain_text", text: options.placeholder } } : {},
|
|
186
|
+
...options.multiline ? { multiline: true } : {}
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
function slackDivider() {
|
|
191
|
+
return { type: "divider" };
|
|
192
|
+
}
|
|
193
|
+
function slackContext(text) {
|
|
194
|
+
return {
|
|
195
|
+
type: "context",
|
|
196
|
+
elements: [{ type: "mrkdwn", text }]
|
|
197
|
+
};
|
|
198
|
+
}
|
|
138
199
|
function slackMessage(options) {
|
|
139
200
|
const blocks = [slackSection(`*${options.title}*`)];
|
|
140
201
|
if (options.details && Object.keys(options.details).length > 0) {
|
|
@@ -188,8 +249,13 @@ exports.formatRelativeTime = formatRelativeTime;
|
|
|
188
249
|
exports.infoBoxHtml = infoBoxHtml;
|
|
189
250
|
exports.noteBoxHtml = noteBoxHtml;
|
|
190
251
|
exports.slackActions = slackActions;
|
|
252
|
+
exports.slackContext = slackContext;
|
|
253
|
+
exports.slackDivider = slackDivider;
|
|
191
254
|
exports.slackFields = slackFields;
|
|
192
255
|
exports.slackMessage = slackMessage;
|
|
256
|
+
exports.slackModal = slackModal;
|
|
193
257
|
exports.slackSection = slackSection;
|
|
258
|
+
exports.slackStaticSelect = slackStaticSelect;
|
|
259
|
+
exports.slackTextInput = slackTextInput;
|
|
194
260
|
//# sourceMappingURL=server.js.map
|
|
195
261
|
//# sourceMappingURL=server.js.map
|
package/dist/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/email.ts","../src/lib/slack.ts","../src/lib/formatting.ts","../src/lib/utils.ts"],"names":["differenceInHours","differenceInMinutes","formatDistanceToNow","twMerge","clsx"],"mappings":"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","file":"server.js","sourcesContent":["/**\n * Email Template Helpers\n *\n * Shared branded email template and HTML building blocks.\n * Used by both platform and admin apps via their local `lib/email.ts` re-exports.\n */\n\n/**\n * Wrap email body content in the standard Dilipod branded template.\n *\n * Usage:\n * emailTemplate({\n * body: '<h2>Hello</h2><p>Content here</p>',\n * })\n *\n * Or with a preheader (hidden preview text in email clients):\n * emailTemplate({\n * preheader: 'Quick summary shown in inbox',\n * body: '...',\n * })\n */\nexport function emailTemplate({ body, preheader }: { body: string; preheader?: string }): string {\n const preheaderHtml = preheader\n ? `<div style=\"display:none;font-size:1px;color:#ffffff;line-height:1px;max-height:0;max-width:0;opacity:0;overflow:hidden;\">${preheader}</div>`\n : ''\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <meta name=\"color-scheme\" content=\"light\" />\n <meta name=\"supported-color-schemes\" content=\"light\" />\n <title>Dilipod</title>\n</head>\n<body style=\"margin: 0; padding: 0; background-color: #f0f0f0; -webkit-font-smoothing: antialiased;\">\n ${preheaderHtml}\n <table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"background-color: #f0f0f0;\">\n <tr>\n <td align=\"center\" style=\"padding: 40px 16px;\">\n <!-- Main card -->\n <table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"max-width: 560px;\">\n <!-- Logo header -->\n <tr>\n <td style=\"padding: 0 0 24px 0;\">\n <table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\">\n <tr>\n <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;\">\n D\n </td>\n <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;\">\n Dilipod\n </td>\n </tr>\n </table>\n </td>\n </tr>\n <!-- Content card -->\n <tr>\n <td>\n <table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"background-color: #ffffff; border-radius: 6px; overflow: hidden; border: 1px solid #e2e2e2;\">\n <!-- Accent bar -->\n <tr>\n <td style=\"height: 3px; background: linear-gradient(90deg, #00e5cc 0%, #00c8b5 100%); font-size: 0; line-height: 0;\"> </td>\n </tr>\n <!-- Body content -->\n <tr>\n <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;\">\n ${body}\n </td>\n </tr>\n </table>\n </td>\n </tr>\n <!-- Footer -->\n <tr>\n <td style=\"padding: 24px 4px 0 4px;\">\n <table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\">\n <tr>\n <td style=\"font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 12px; color: #9ca3af;\">\n © ${new Date().getFullYear()} Dilipod — Your Digital Workforce\n </td>\n <td align=\"right\" style=\"font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 12px;\">\n <a href=\"https://dilipod.com\" style=\"color: #9ca3af; text-decoration: none;\">dilipod.com</a>\n </td>\n </tr>\n </table>\n </td>\n </tr>\n </table>\n </td>\n </tr>\n </table>\n</body>\n</html>`\n}\n\n/**\n * Render a CTA button for use inside emailTemplate body.\n *\n * buttonHtml({ text: 'Reset Password', href: 'https://...' })\n * buttonHtml({ text: 'View Incident', href: '...', variant: 'danger' })\n */\nexport function buttonHtml({\n text,\n href,\n variant = 'primary',\n}: {\n text: string\n href: string\n variant?: 'primary' | 'danger'\n}): string {\n const bg = variant === 'danger' ? '#dc2626' : '#111111'\n const color = '#ffffff'\n return `<table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" style=\"margin-top: 24px;\">\n <tr>\n <td style=\"background-color: ${bg}; border-radius: 6px;\">\n <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;\">\n ${text} →\n </a>\n </td>\n </tr>\n</table>`\n}\n\n/**\n * Render an info box (grey background) for key details.\n *\n * infoBoxHtml('<strong>Status:</strong> Deployed')\n */\nexport function infoBoxHtml(innerHtml: string): string {\n return `<div style=\"background-color: #f8fafb; padding: 16px 20px; border-radius: 6px; border: 1px solid #e5e7eb; margin: 20px 0; border-left: 3px solid #00e5cc;\">\n ${innerHtml}\n</div>`\n}\n\n/**\n * Render a warning/note box (yellow background).\n *\n * noteBoxHtml('This link expires in 1 hour.')\n */\nexport function noteBoxHtml(text: string): string {\n 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;\">\n ${text}\n</div>`\n}\n","/**\n * Slack Block Kit Helpers\n *\n * Shared Slack message and block builders used by both platform and admin apps.\n * Pure functions with zero dependencies — same pattern as email.ts.\n *\n * Transport layers (sendSlackMessage, sendSlackWebhook) stay local in each app.\n */\n\n// ============================================\n// Types\n// ============================================\n\nexport interface SlackBlock {\n type: string\n text?: { type: string; text: string }\n elements?: SlackElement[]\n fields?: SlackField[]\n}\n\nexport interface SlackElement {\n type: string\n text?: { type: string; text: string }\n style?: string\n value?: string\n url?: string\n action_id?: string\n}\n\nexport interface SlackField {\n type: string\n text: string\n}\n\nexport interface SlackMessage {\n text: string\n blocks: SlackBlock[]\n}\n\n// ============================================\n// Block Builders\n// ============================================\n\n/**\n * Build a single mrkdwn section block.\n *\n * slackSection('*Hello* world')\n */\nexport function slackSection(mrkdwn: string): SlackBlock {\n return {\n type: 'section',\n text: { type: 'mrkdwn', text: mrkdwn },\n }\n}\n\n/**\n * Build a key-value fields block.\n *\n * slackFields({ Process: 'Invoice Bot', Status: 'Live' })\n */\nexport function slackFields(fields: Record<string, string | number>): SlackBlock {\n return {\n type: 'section',\n fields: Object.entries(fields).map(([key, value]) => ({\n type: 'mrkdwn',\n text: `*${key}:*\\n${value}`,\n })),\n }\n}\n\n/**\n * Build an actions block with buttons.\n *\n * slackActions([\n * { text: 'View', url: 'https://...' },\n * { text: 'Approve', actionId: 'approve', style: 'primary', value: '123' },\n * ])\n */\nexport function slackActions(\n buttons: {\n text: string\n url?: string\n value?: string\n actionId?: string\n style?: 'primary' | 'danger'\n }[]\n): SlackBlock {\n return {\n type: 'actions',\n elements: buttons.map((btn) => ({\n type: 'button',\n text: { type: 'plain_text', text: btn.text },\n ...(btn.url ? { url: btn.url } : {}),\n ...(btn.value ? { value: btn.value } : {}),\n ...(btn.actionId ? { action_id: btn.actionId } : {}),\n ...(btn.style ? { style: btn.style } : {}),\n })),\n }\n}\n\n// ============================================\n// Message Builder\n// ============================================\n\n/**\n * Build a complete Slack message with optional details, note, and button.\n * Covers 90% of notification use cases.\n *\n * slackMessage({\n * title: '🚀 New signup',\n * details: { Name: 'John', Email: 'john@acme.com' },\n * note: 'First user from this company',\n * buttonText: 'View Customer',\n * buttonUrl: 'https://admin.dilipod.com/customers/123',\n * })\n */\nexport function slackMessage(options: {\n title: string\n details?: Record<string, string | number>\n note?: string\n buttonText?: string\n buttonUrl?: string\n}): SlackMessage {\n const blocks: SlackBlock[] = [slackSection(`*${options.title}*`)]\n\n if (options.details && Object.keys(options.details).length > 0) {\n blocks.push(slackFields(options.details))\n }\n\n if (options.note) {\n blocks.push(slackSection(options.note))\n }\n\n if (options.buttonUrl && options.buttonText) {\n blocks.push(slackActions([{ text: options.buttonText, url: options.buttonUrl }]))\n }\n\n return {\n text: options.title,\n blocks,\n }\n}\n","/**\n * Formatting Utilities\n *\n * Shared text, currency, duration, and date formatting used across apps.\n */\n\nimport { differenceInHours, differenceInMinutes, formatDistanceToNow } from 'date-fns'\n\n/**\n * Convert a cent value to euros (numeric).\n * e.g. 2900 → 29\n */\nexport function formatCentsToEuros(cents: number): string {\n return `€${(cents / 100).toLocaleString()}`\n}\n\n/**\n * Format a euro value as a display string.\n * e.g. 299 → \"€299\" or 299.5 with decimals=2 → \"€299.50\"\n */\nexport function formatEuros(euros: number, decimals?: number): string {\n if (decimals !== undefined) {\n return `€${euros.toFixed(decimals)}`\n }\n return `€${euros.toLocaleString()}`\n}\n\n/**\n * Format milliseconds as a human-readable duration.\n * e.g. 1500 → \"1.5s\"\n */\nexport function formatDuration(ms: number): string {\n return `${(ms / 1000).toFixed(1)}s`\n}\n\n/**\n * Format a date into a compact relative-time string.\n * Returns \"—\" for null/undefined, \"5m\" for < 1 hour, \"2h\" for < 48 hours,\n * or a relative distance like \"3 days\" for older dates.\n */\nexport function formatRelativeTime(date: Date | string | null): string {\n if (!date) return '—'\n const d = typeof date === 'string' ? new Date(date) : date\n const hours = differenceInHours(new Date(), d)\n if (hours < 1) {\n const mins = differenceInMinutes(new Date(), d)\n return `${mins}m`\n }\n if (hours < 48) return `${hours}h`\n return formatDistanceToNow(d, { addSuffix: false })\n}\n","import { type ClassValue, clsx } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/lib/email.ts","../src/lib/slack.ts","../src/lib/formatting.ts","../src/lib/utils.ts"],"names":["differenceInHours","differenceInMinutes","formatDistanceToNow","twMerge","clsx"],"mappings":"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","file":"server.js","sourcesContent":["/**\n * Email Template Helpers\n *\n * Shared branded email template and HTML building blocks.\n * Used by both platform and admin apps via their local `lib/email.ts` re-exports.\n */\n\n/**\n * Wrap email body content in the standard Dilipod branded template.\n *\n * Usage:\n * emailTemplate({\n * body: '<h2>Hello</h2><p>Content here</p>',\n * })\n *\n * Or with a preheader (hidden preview text in email clients):\n * emailTemplate({\n * preheader: 'Quick summary shown in inbox',\n * body: '...',\n * })\n */\nexport function emailTemplate({ body, preheader }: { body: string; preheader?: string }): string {\n const preheaderHtml = preheader\n ? `<div style=\"display:none;font-size:1px;color:#ffffff;line-height:1px;max-height:0;max-width:0;opacity:0;overflow:hidden;\">${preheader}</div>`\n : ''\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <meta name=\"color-scheme\" content=\"light\" />\n <meta name=\"supported-color-schemes\" content=\"light\" />\n <title>Dilipod</title>\n</head>\n<body style=\"margin: 0; padding: 0; background-color: #f0f0f0; -webkit-font-smoothing: antialiased;\">\n ${preheaderHtml}\n <table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"background-color: #f0f0f0;\">\n <tr>\n <td align=\"center\" style=\"padding: 40px 16px;\">\n <!-- Main card -->\n <table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"max-width: 560px;\">\n <!-- Logo header -->\n <tr>\n <td style=\"padding: 0 0 24px 0;\">\n <table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\">\n <tr>\n <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;\">\n D\n </td>\n <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;\">\n Dilipod\n </td>\n </tr>\n </table>\n </td>\n </tr>\n <!-- Content card -->\n <tr>\n <td>\n <table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"background-color: #ffffff; border-radius: 6px; overflow: hidden; border: 1px solid #e2e2e2;\">\n <!-- Accent bar -->\n <tr>\n <td style=\"height: 3px; background: linear-gradient(90deg, #00e5cc 0%, #00c8b5 100%); font-size: 0; line-height: 0;\"> </td>\n </tr>\n <!-- Body content -->\n <tr>\n <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;\">\n ${body}\n </td>\n </tr>\n </table>\n </td>\n </tr>\n <!-- Footer -->\n <tr>\n <td style=\"padding: 24px 4px 0 4px;\">\n <table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\">\n <tr>\n <td style=\"font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 12px; color: #9ca3af;\">\n © ${new Date().getFullYear()} Dilipod — Your Digital Workforce\n </td>\n <td align=\"right\" style=\"font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 12px;\">\n <a href=\"https://dilipod.com\" style=\"color: #9ca3af; text-decoration: none;\">dilipod.com</a>\n </td>\n </tr>\n </table>\n </td>\n </tr>\n </table>\n </td>\n </tr>\n </table>\n</body>\n</html>`\n}\n\n/**\n * Render a CTA button for use inside emailTemplate body.\n *\n * buttonHtml({ text: 'Reset Password', href: 'https://...' })\n * buttonHtml({ text: 'View Incident', href: '...', variant: 'danger' })\n */\nexport function buttonHtml({\n text,\n href,\n variant = 'primary',\n}: {\n text: string\n href: string\n variant?: 'primary' | 'danger'\n}): string {\n const bg = variant === 'danger' ? '#dc2626' : '#111111'\n const color = '#ffffff'\n return `<table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" style=\"margin-top: 24px;\">\n <tr>\n <td style=\"background-color: ${bg}; border-radius: 6px;\">\n <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;\">\n ${text} →\n </a>\n </td>\n </tr>\n</table>`\n}\n\n/**\n * Render an info box (grey background) for key details.\n *\n * infoBoxHtml('<strong>Status:</strong> Deployed')\n */\nexport function infoBoxHtml(innerHtml: string): string {\n return `<div style=\"background-color: #f8fafb; padding: 16px 20px; border-radius: 6px; border: 1px solid #e5e7eb; margin: 20px 0; border-left: 3px solid #00e5cc;\">\n ${innerHtml}\n</div>`\n}\n\n/**\n * Render a warning/note box (yellow background).\n *\n * noteBoxHtml('This link expires in 1 hour.')\n */\nexport function noteBoxHtml(text: string): string {\n 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;\">\n ${text}\n</div>`\n}\n","/**\n * Slack Block Kit Helpers\n *\n * Shared Slack message and block builders used by both platform and admin apps.\n * Pure functions with zero dependencies — same pattern as email.ts.\n *\n * Transport layers (sendSlackMessage, sendSlackWebhook) stay local in each app.\n */\n\n// ============================================\n// Types\n// ============================================\n\nexport interface SlackBlock {\n type: string\n text?: { type: string; text: string }\n elements?: SlackElement[]\n fields?: SlackField[]\n}\n\nexport interface SlackElement {\n type: string\n text?: { type: string; text: string }\n style?: string\n value?: string\n url?: string\n action_id?: string\n}\n\nexport interface SlackField {\n type: string\n text: string\n}\n\nexport interface SlackMessage {\n text: string\n blocks: SlackBlock[]\n}\n\n// ============================================\n// Block Builders\n// ============================================\n\n/**\n * Build a single mrkdwn section block.\n *\n * slackSection('*Hello* world')\n */\nexport function slackSection(mrkdwn: string): SlackBlock {\n return {\n type: 'section',\n text: { type: 'mrkdwn', text: mrkdwn },\n }\n}\n\n/**\n * Build a key-value fields block.\n *\n * slackFields({ Process: 'Invoice Bot', Status: 'Live' })\n */\nexport function slackFields(fields: Record<string, string | number>): SlackBlock {\n return {\n type: 'section',\n fields: Object.entries(fields).map(([key, value]) => ({\n type: 'mrkdwn',\n text: `*${key}:*\\n${value}`,\n })),\n }\n}\n\n/**\n * Build an actions block with buttons.\n *\n * slackActions([\n * { text: 'View', url: 'https://...' },\n * { text: 'Approve', actionId: 'approve', style: 'primary', value: '123' },\n * ])\n */\nexport function slackActions(\n buttons: {\n text: string\n url?: string\n value?: string\n actionId?: string\n style?: 'primary' | 'danger'\n }[]\n): SlackBlock {\n return {\n type: 'actions',\n elements: buttons.map((btn) => ({\n type: 'button',\n text: { type: 'plain_text', text: btn.text },\n ...(btn.url ? { url: btn.url } : {}),\n ...(btn.value ? { value: btn.value } : {}),\n ...(btn.actionId ? { action_id: btn.actionId } : {}),\n ...(btn.style ? { style: btn.style } : {}),\n })),\n }\n}\n\n// ============================================\n// Modal & Select Builders\n// ============================================\n\nexport interface SlackModalView {\n type: 'modal'\n callback_id: string\n title: { type: 'plain_text'; text: string }\n submit?: { type: 'plain_text'; text: string }\n close?: { type: 'plain_text'; text: string }\n blocks: SlackBlock[]\n private_metadata?: string\n}\n\nexport interface SlackOptionGroup {\n label: { type: 'plain_text'; text: string }\n options: Array<{\n text: { type: 'plain_text'; text: string }\n value: string\n }>\n}\n\n/**\n * Build a modal view for Slack.\n *\n * slackModal({\n * callbackId: 'run_process',\n * title: 'Run a Process',\n * submitText: 'Run',\n * blocks: [...],\n * })\n */\nexport function slackModal(options: {\n callbackId: string\n title: string\n submitText?: string\n closeText?: string\n blocks: SlackBlock[]\n privateMetadata?: string\n}): SlackModalView {\n return {\n type: 'modal',\n callback_id: options.callbackId,\n title: { type: 'plain_text', text: options.title },\n ...(options.submitText\n ? { submit: { type: 'plain_text', text: options.submitText } }\n : {}),\n ...(options.closeText\n ? { close: { type: 'plain_text', text: options.closeText } }\n : {}),\n blocks: options.blocks,\n ...(options.privateMetadata\n ? { private_metadata: options.privateMetadata }\n : {}),\n }\n}\n\n/**\n * Build a static select input block (for modals or messages).\n *\n * slackStaticSelect({\n * blockId: 'process_select',\n * actionId: 'process_id',\n * label: 'Process',\n * placeholder: 'Select a process',\n * optionGroups: [\n * { label: 'Invoice Bot', options: [{ text: 'Send Invoices', value: 'uuid' }] },\n * ],\n * })\n */\nexport function slackStaticSelect(options: {\n blockId: string\n actionId: string\n label: string\n placeholder?: string\n options?: Array<{ text: string; value: string }>\n optionGroups?: Array<{ label: string; options: Array<{ text: string; value: string }> }>\n}): SlackBlock {\n const element: Record<string, unknown> = {\n type: 'static_select',\n action_id: options.actionId,\n placeholder: { type: 'plain_text', text: options.placeholder || 'Select...' },\n }\n\n if (options.optionGroups) {\n element.option_groups = options.optionGroups.map((group) => ({\n label: { type: 'plain_text', text: group.label },\n options: group.options.map((opt) => ({\n text: { type: 'plain_text', text: opt.text },\n value: opt.value,\n })),\n }))\n } else if (options.options) {\n element.options = options.options.map((opt) => ({\n text: { type: 'plain_text', text: opt.text },\n value: opt.value,\n }))\n }\n\n return {\n type: 'input',\n block_id: options.blockId,\n label: { type: 'plain_text', text: options.label },\n element,\n } as unknown as SlackBlock\n}\n\n/**\n * Build a plain text input block (for modals).\n */\nexport function slackTextInput(options: {\n blockId: string\n actionId: string\n label: string\n placeholder?: string\n optional?: boolean\n multiline?: boolean\n}): SlackBlock {\n return {\n type: 'input',\n block_id: options.blockId,\n optional: options.optional ?? false,\n label: { type: 'plain_text', text: options.label },\n element: {\n type: 'plain_text_input',\n action_id: options.actionId,\n ...(options.placeholder\n ? { placeholder: { type: 'plain_text', text: options.placeholder } }\n : {}),\n ...(options.multiline ? { multiline: true } : {}),\n },\n } as unknown as SlackBlock\n}\n\n/**\n * Build a divider block.\n */\nexport function slackDivider(): SlackBlock {\n return { type: 'divider' } as SlackBlock\n}\n\n/**\n * Build a context block (small muted text).\n */\nexport function slackContext(text: string): SlackBlock {\n return {\n type: 'context',\n elements: [{ type: 'mrkdwn', text }],\n } as unknown as SlackBlock\n}\n\n// ============================================\n// Message Builder\n// ============================================\n\n/**\n * Build a complete Slack message with optional details, note, and button.\n * Covers 90% of notification use cases.\n *\n * slackMessage({\n * title: '🚀 New signup',\n * details: { Name: 'John', Email: 'john@acme.com' },\n * note: 'First user from this company',\n * buttonText: 'View Customer',\n * buttonUrl: 'https://admin.dilipod.com/customers/123',\n * })\n */\nexport function slackMessage(options: {\n title: string\n details?: Record<string, string | number>\n note?: string\n buttonText?: string\n buttonUrl?: string\n}): SlackMessage {\n const blocks: SlackBlock[] = [slackSection(`*${options.title}*`)]\n\n if (options.details && Object.keys(options.details).length > 0) {\n blocks.push(slackFields(options.details))\n }\n\n if (options.note) {\n blocks.push(slackSection(options.note))\n }\n\n if (options.buttonUrl && options.buttonText) {\n blocks.push(slackActions([{ text: options.buttonText, url: options.buttonUrl }]))\n }\n\n return {\n text: options.title,\n blocks,\n }\n}\n","/**\n * Formatting Utilities\n *\n * Shared text, currency, duration, and date formatting used across apps.\n */\n\nimport { differenceInHours, differenceInMinutes, formatDistanceToNow } from 'date-fns'\n\n/**\n * Convert a cent value to euros (numeric).\n * e.g. 2900 → 29\n */\nexport function formatCentsToEuros(cents: number): string {\n return `€${(cents / 100).toLocaleString()}`\n}\n\n/**\n * Format a euro value as a display string.\n * e.g. 299 → \"€299\" or 299.5 with decimals=2 → \"€299.50\"\n */\nexport function formatEuros(euros: number, decimals?: number): string {\n if (decimals !== undefined) {\n return `€${euros.toFixed(decimals)}`\n }\n return `€${euros.toLocaleString()}`\n}\n\n/**\n * Format milliseconds as a human-readable duration.\n * e.g. 1500 → \"1.5s\"\n */\nexport function formatDuration(ms: number): string {\n return `${(ms / 1000).toFixed(1)}s`\n}\n\n/**\n * Format a date into a compact relative-time string.\n * Returns \"—\" for null/undefined, \"5m\" for < 1 hour, \"2h\" for < 48 hours,\n * or a relative distance like \"3 days\" for older dates.\n */\nexport function formatRelativeTime(date: Date | string | null): string {\n if (!date) return '—'\n const d = typeof date === 'string' ? new Date(date) : date\n const hours = differenceInHours(new Date(), d)\n if (hours < 1) {\n const mins = differenceInMinutes(new Date(), d)\n return `${mins}m`\n }\n if (hours < 48) return `${hours}h`\n return formatDistanceToNow(d, { addSuffix: false })\n}\n","import { type ClassValue, clsx } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n"]}
|
package/dist/server.mjs
CHANGED
|
@@ -133,6 +133,67 @@ function slackActions(buttons) {
|
|
|
133
133
|
}))
|
|
134
134
|
};
|
|
135
135
|
}
|
|
136
|
+
function slackModal(options) {
|
|
137
|
+
return {
|
|
138
|
+
type: "modal",
|
|
139
|
+
callback_id: options.callbackId,
|
|
140
|
+
title: { type: "plain_text", text: options.title },
|
|
141
|
+
...options.submitText ? { submit: { type: "plain_text", text: options.submitText } } : {},
|
|
142
|
+
...options.closeText ? { close: { type: "plain_text", text: options.closeText } } : {},
|
|
143
|
+
blocks: options.blocks,
|
|
144
|
+
...options.privateMetadata ? { private_metadata: options.privateMetadata } : {}
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
function slackStaticSelect(options) {
|
|
148
|
+
const element = {
|
|
149
|
+
type: "static_select",
|
|
150
|
+
action_id: options.actionId,
|
|
151
|
+
placeholder: { type: "plain_text", text: options.placeholder || "Select..." }
|
|
152
|
+
};
|
|
153
|
+
if (options.optionGroups) {
|
|
154
|
+
element.option_groups = options.optionGroups.map((group) => ({
|
|
155
|
+
label: { type: "plain_text", text: group.label },
|
|
156
|
+
options: group.options.map((opt) => ({
|
|
157
|
+
text: { type: "plain_text", text: opt.text },
|
|
158
|
+
value: opt.value
|
|
159
|
+
}))
|
|
160
|
+
}));
|
|
161
|
+
} else if (options.options) {
|
|
162
|
+
element.options = options.options.map((opt) => ({
|
|
163
|
+
text: { type: "plain_text", text: opt.text },
|
|
164
|
+
value: opt.value
|
|
165
|
+
}));
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
type: "input",
|
|
169
|
+
block_id: options.blockId,
|
|
170
|
+
label: { type: "plain_text", text: options.label },
|
|
171
|
+
element
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
function slackTextInput(options) {
|
|
175
|
+
return {
|
|
176
|
+
type: "input",
|
|
177
|
+
block_id: options.blockId,
|
|
178
|
+
optional: options.optional ?? false,
|
|
179
|
+
label: { type: "plain_text", text: options.label },
|
|
180
|
+
element: {
|
|
181
|
+
type: "plain_text_input",
|
|
182
|
+
action_id: options.actionId,
|
|
183
|
+
...options.placeholder ? { placeholder: { type: "plain_text", text: options.placeholder } } : {},
|
|
184
|
+
...options.multiline ? { multiline: true } : {}
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function slackDivider() {
|
|
189
|
+
return { type: "divider" };
|
|
190
|
+
}
|
|
191
|
+
function slackContext(text) {
|
|
192
|
+
return {
|
|
193
|
+
type: "context",
|
|
194
|
+
elements: [{ type: "mrkdwn", text }]
|
|
195
|
+
};
|
|
196
|
+
}
|
|
136
197
|
function slackMessage(options) {
|
|
137
198
|
const blocks = [slackSection(`*${options.title}*`)];
|
|
138
199
|
if (options.details && Object.keys(options.details).length > 0) {
|
|
@@ -176,6 +237,6 @@ function cn(...inputs) {
|
|
|
176
237
|
return twMerge(clsx(inputs));
|
|
177
238
|
}
|
|
178
239
|
|
|
179
|
-
export { buttonHtml, cn, emailTemplate, formatCentsToEuros, formatDuration, formatEuros, formatRelativeTime, infoBoxHtml, noteBoxHtml, slackActions, slackFields, slackMessage, slackSection };
|
|
240
|
+
export { buttonHtml, cn, emailTemplate, formatCentsToEuros, formatDuration, formatEuros, formatRelativeTime, infoBoxHtml, noteBoxHtml, slackActions, slackContext, slackDivider, slackFields, slackMessage, slackModal, slackSection, slackStaticSelect, slackTextInput };
|
|
180
241
|
//# sourceMappingURL=server.mjs.map
|
|
181
242
|
//# sourceMappingURL=server.mjs.map
|
package/dist/server.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/email.ts","../src/lib/slack.ts","../src/lib/formatting.ts","../src/lib/utils.ts"],"names":[],"mappings":"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","file":"server.mjs","sourcesContent":["/**\n * Email Template Helpers\n *\n * Shared branded email template and HTML building blocks.\n * Used by both platform and admin apps via their local `lib/email.ts` re-exports.\n */\n\n/**\n * Wrap email body content in the standard Dilipod branded template.\n *\n * Usage:\n * emailTemplate({\n * body: '<h2>Hello</h2><p>Content here</p>',\n * })\n *\n * Or with a preheader (hidden preview text in email clients):\n * emailTemplate({\n * preheader: 'Quick summary shown in inbox',\n * body: '...',\n * })\n */\nexport function emailTemplate({ body, preheader }: { body: string; preheader?: string }): string {\n const preheaderHtml = preheader\n ? `<div style=\"display:none;font-size:1px;color:#ffffff;line-height:1px;max-height:0;max-width:0;opacity:0;overflow:hidden;\">${preheader}</div>`\n : ''\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <meta name=\"color-scheme\" content=\"light\" />\n <meta name=\"supported-color-schemes\" content=\"light\" />\n <title>Dilipod</title>\n</head>\n<body style=\"margin: 0; padding: 0; background-color: #f0f0f0; -webkit-font-smoothing: antialiased;\">\n ${preheaderHtml}\n <table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"background-color: #f0f0f0;\">\n <tr>\n <td align=\"center\" style=\"padding: 40px 16px;\">\n <!-- Main card -->\n <table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"max-width: 560px;\">\n <!-- Logo header -->\n <tr>\n <td style=\"padding: 0 0 24px 0;\">\n <table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\">\n <tr>\n <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;\">\n D\n </td>\n <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;\">\n Dilipod\n </td>\n </tr>\n </table>\n </td>\n </tr>\n <!-- Content card -->\n <tr>\n <td>\n <table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"background-color: #ffffff; border-radius: 6px; overflow: hidden; border: 1px solid #e2e2e2;\">\n <!-- Accent bar -->\n <tr>\n <td style=\"height: 3px; background: linear-gradient(90deg, #00e5cc 0%, #00c8b5 100%); font-size: 0; line-height: 0;\"> </td>\n </tr>\n <!-- Body content -->\n <tr>\n <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;\">\n ${body}\n </td>\n </tr>\n </table>\n </td>\n </tr>\n <!-- Footer -->\n <tr>\n <td style=\"padding: 24px 4px 0 4px;\">\n <table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\">\n <tr>\n <td style=\"font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 12px; color: #9ca3af;\">\n © ${new Date().getFullYear()} Dilipod — Your Digital Workforce\n </td>\n <td align=\"right\" style=\"font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 12px;\">\n <a href=\"https://dilipod.com\" style=\"color: #9ca3af; text-decoration: none;\">dilipod.com</a>\n </td>\n </tr>\n </table>\n </td>\n </tr>\n </table>\n </td>\n </tr>\n </table>\n</body>\n</html>`\n}\n\n/**\n * Render a CTA button for use inside emailTemplate body.\n *\n * buttonHtml({ text: 'Reset Password', href: 'https://...' })\n * buttonHtml({ text: 'View Incident', href: '...', variant: 'danger' })\n */\nexport function buttonHtml({\n text,\n href,\n variant = 'primary',\n}: {\n text: string\n href: string\n variant?: 'primary' | 'danger'\n}): string {\n const bg = variant === 'danger' ? '#dc2626' : '#111111'\n const color = '#ffffff'\n return `<table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" style=\"margin-top: 24px;\">\n <tr>\n <td style=\"background-color: ${bg}; border-radius: 6px;\">\n <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;\">\n ${text} →\n </a>\n </td>\n </tr>\n</table>`\n}\n\n/**\n * Render an info box (grey background) for key details.\n *\n * infoBoxHtml('<strong>Status:</strong> Deployed')\n */\nexport function infoBoxHtml(innerHtml: string): string {\n return `<div style=\"background-color: #f8fafb; padding: 16px 20px; border-radius: 6px; border: 1px solid #e5e7eb; margin: 20px 0; border-left: 3px solid #00e5cc;\">\n ${innerHtml}\n</div>`\n}\n\n/**\n * Render a warning/note box (yellow background).\n *\n * noteBoxHtml('This link expires in 1 hour.')\n */\nexport function noteBoxHtml(text: string): string {\n 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;\">\n ${text}\n</div>`\n}\n","/**\n * Slack Block Kit Helpers\n *\n * Shared Slack message and block builders used by both platform and admin apps.\n * Pure functions with zero dependencies — same pattern as email.ts.\n *\n * Transport layers (sendSlackMessage, sendSlackWebhook) stay local in each app.\n */\n\n// ============================================\n// Types\n// ============================================\n\nexport interface SlackBlock {\n type: string\n text?: { type: string; text: string }\n elements?: SlackElement[]\n fields?: SlackField[]\n}\n\nexport interface SlackElement {\n type: string\n text?: { type: string; text: string }\n style?: string\n value?: string\n url?: string\n action_id?: string\n}\n\nexport interface SlackField {\n type: string\n text: string\n}\n\nexport interface SlackMessage {\n text: string\n blocks: SlackBlock[]\n}\n\n// ============================================\n// Block Builders\n// ============================================\n\n/**\n * Build a single mrkdwn section block.\n *\n * slackSection('*Hello* world')\n */\nexport function slackSection(mrkdwn: string): SlackBlock {\n return {\n type: 'section',\n text: { type: 'mrkdwn', text: mrkdwn },\n }\n}\n\n/**\n * Build a key-value fields block.\n *\n * slackFields({ Process: 'Invoice Bot', Status: 'Live' })\n */\nexport function slackFields(fields: Record<string, string | number>): SlackBlock {\n return {\n type: 'section',\n fields: Object.entries(fields).map(([key, value]) => ({\n type: 'mrkdwn',\n text: `*${key}:*\\n${value}`,\n })),\n }\n}\n\n/**\n * Build an actions block with buttons.\n *\n * slackActions([\n * { text: 'View', url: 'https://...' },\n * { text: 'Approve', actionId: 'approve', style: 'primary', value: '123' },\n * ])\n */\nexport function slackActions(\n buttons: {\n text: string\n url?: string\n value?: string\n actionId?: string\n style?: 'primary' | 'danger'\n }[]\n): SlackBlock {\n return {\n type: 'actions',\n elements: buttons.map((btn) => ({\n type: 'button',\n text: { type: 'plain_text', text: btn.text },\n ...(btn.url ? { url: btn.url } : {}),\n ...(btn.value ? { value: btn.value } : {}),\n ...(btn.actionId ? { action_id: btn.actionId } : {}),\n ...(btn.style ? { style: btn.style } : {}),\n })),\n }\n}\n\n// ============================================\n// Message Builder\n// ============================================\n\n/**\n * Build a complete Slack message with optional details, note, and button.\n * Covers 90% of notification use cases.\n *\n * slackMessage({\n * title: '🚀 New signup',\n * details: { Name: 'John', Email: 'john@acme.com' },\n * note: 'First user from this company',\n * buttonText: 'View Customer',\n * buttonUrl: 'https://admin.dilipod.com/customers/123',\n * })\n */\nexport function slackMessage(options: {\n title: string\n details?: Record<string, string | number>\n note?: string\n buttonText?: string\n buttonUrl?: string\n}): SlackMessage {\n const blocks: SlackBlock[] = [slackSection(`*${options.title}*`)]\n\n if (options.details && Object.keys(options.details).length > 0) {\n blocks.push(slackFields(options.details))\n }\n\n if (options.note) {\n blocks.push(slackSection(options.note))\n }\n\n if (options.buttonUrl && options.buttonText) {\n blocks.push(slackActions([{ text: options.buttonText, url: options.buttonUrl }]))\n }\n\n return {\n text: options.title,\n blocks,\n }\n}\n","/**\n * Formatting Utilities\n *\n * Shared text, currency, duration, and date formatting used across apps.\n */\n\nimport { differenceInHours, differenceInMinutes, formatDistanceToNow } from 'date-fns'\n\n/**\n * Convert a cent value to euros (numeric).\n * e.g. 2900 → 29\n */\nexport function formatCentsToEuros(cents: number): string {\n return `€${(cents / 100).toLocaleString()}`\n}\n\n/**\n * Format a euro value as a display string.\n * e.g. 299 → \"€299\" or 299.5 with decimals=2 → \"€299.50\"\n */\nexport function formatEuros(euros: number, decimals?: number): string {\n if (decimals !== undefined) {\n return `€${euros.toFixed(decimals)}`\n }\n return `€${euros.toLocaleString()}`\n}\n\n/**\n * Format milliseconds as a human-readable duration.\n * e.g. 1500 → \"1.5s\"\n */\nexport function formatDuration(ms: number): string {\n return `${(ms / 1000).toFixed(1)}s`\n}\n\n/**\n * Format a date into a compact relative-time string.\n * Returns \"—\" for null/undefined, \"5m\" for < 1 hour, \"2h\" for < 48 hours,\n * or a relative distance like \"3 days\" for older dates.\n */\nexport function formatRelativeTime(date: Date | string | null): string {\n if (!date) return '—'\n const d = typeof date === 'string' ? new Date(date) : date\n const hours = differenceInHours(new Date(), d)\n if (hours < 1) {\n const mins = differenceInMinutes(new Date(), d)\n return `${mins}m`\n }\n if (hours < 48) return `${hours}h`\n return formatDistanceToNow(d, { addSuffix: false })\n}\n","import { type ClassValue, clsx } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/lib/email.ts","../src/lib/slack.ts","../src/lib/formatting.ts","../src/lib/utils.ts"],"names":[],"mappings":";;;;;AAqBO,SAAS,aAAA,CAAc,EAAE,IAAA,EAAM,SAAA,EAAU,EAAiD;AAC/F,EAAA,MAAM,aAAA,GAAgB,SAAA,GAClB,CAAA,0HAAA,EAA6H,SAAS,CAAA,MAAA,CAAA,GACtI,EAAA;AAEJ,EAAA,OAAO,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAAA,EAUL,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,oBAAA,EAgCK,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,2BAAA,EAAA,iBAYG,IAAI,IAAA,EAAK,EAAE,WAAA,EAAa,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,OAAA,CAAA;AAerD;AAQO,SAAS,UAAA,CAAW;AAAA,EACzB,IAAA;AAAA,EACA,IAAA;AAAA,EACA,OAAA,GAAU;AACZ,CAAA,EAIW;AACT,EAAA,MAAM,EAAA,GAAK,OAAA,KAAY,QAAA,GAAW,SAAA,GAAY,SAAA;AAC9C,EAAA,MAAM,KAAA,GAAQ,SAAA;AACd,EAAA,OAAO,CAAA;AAAA;AAAA,iCAAA,EAE0B,EAAE,CAAA;AAAA,eAAA,EACpB,IAAI,8DAA8D,KAAK,CAAA;AAAA,QAAA,EAC9E,IAAI,CAAA;AAAA;AAAA;AAAA;AAAA,QAAA,CAAA;AAKd;AAOO,SAAS,YAAY,SAAA,EAA2B;AACrD,EAAA,OAAO,CAAA;AAAA,EAAA,EACL,SAAS;AAAA,MAAA,CAAA;AAEb;AAOO,SAAS,YAAY,IAAA,EAAsB;AAChD,EAAA,OAAO,CAAA;AAAA,EAAA,EACL,IAAI;AAAA,MAAA,CAAA;AAER;;;ACjGO,SAAS,aAAa,MAAA,EAA4B;AACvD,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,SAAA;AAAA,IACN,IAAA,EAAM,EAAE,IAAA,EAAM,QAAA,EAAU,MAAM,MAAA;AAAO,GACvC;AACF;AAOO,SAAS,YAAY,MAAA,EAAqD;AAC/E,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,SAAA;AAAA,IACN,MAAA,EAAQ,MAAA,CAAO,OAAA,CAAQ,MAAM,CAAA,CAAE,IAAI,CAAC,CAAC,GAAA,EAAK,KAAK,CAAA,MAAO;AAAA,MACpD,IAAA,EAAM,QAAA;AAAA,MACN,IAAA,EAAM,IAAI,GAAG,CAAA;AAAA,EAAO,KAAK,CAAA;AAAA,KAC3B,CAAE;AAAA,GACJ;AACF;AAUO,SAAS,aACd,OAAA,EAOY;AACZ,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,SAAA;AAAA,IACN,QAAA,EAAU,OAAA,CAAQ,GAAA,CAAI,CAAC,GAAA,MAAS;AAAA,MAC9B,IAAA,EAAM,QAAA;AAAA,MACN,MAAM,EAAE,IAAA,EAAM,YAAA,EAAc,IAAA,EAAM,IAAI,IAAA,EAAK;AAAA,MAC3C,GAAI,IAAI,GAAA,GAAM,EAAE,KAAK,GAAA,CAAI,GAAA,KAAQ,EAAC;AAAA,MAClC,GAAI,IAAI,KAAA,GAAQ,EAAE,OAAO,GAAA,CAAI,KAAA,KAAU,EAAC;AAAA,MACxC,GAAI,IAAI,QAAA,GAAW,EAAE,WAAW,GAAA,CAAI,QAAA,KAAa,EAAC;AAAA,MAClD,GAAI,IAAI,KAAA,GAAQ,EAAE,OAAO,GAAA,CAAI,KAAA,KAAU;AAAC,KAC1C,CAAE;AAAA,GACJ;AACF;AAkCO,SAAS,WAAW,OAAA,EAOR;AACjB,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,OAAA;AAAA,IACN,aAAa,OAAA,CAAQ,UAAA;AAAA,IACrB,OAAO,EAAE,IAAA,EAAM,YAAA,EAAc,IAAA,EAAM,QAAQ,KAAA,EAAM;AAAA,IACjD,GAAI,OAAA,CAAQ,UAAA,GACR,EAAE,MAAA,EAAQ,EAAE,IAAA,EAAM,YAAA,EAAc,IAAA,EAAM,OAAA,CAAQ,UAAA,EAAW,KACzD,EAAC;AAAA,IACL,GAAI,OAAA,CAAQ,SAAA,GACR,EAAE,KAAA,EAAO,EAAE,IAAA,EAAM,YAAA,EAAc,IAAA,EAAM,OAAA,CAAQ,SAAA,EAAU,KACvD,EAAC;AAAA,IACL,QAAQ,OAAA,CAAQ,MAAA;AAAA,IAChB,GAAI,QAAQ,eAAA,GACR,EAAE,kBAAkB,OAAA,CAAQ,eAAA,KAC5B;AAAC,GACP;AACF;AAeO,SAAS,kBAAkB,OAAA,EAOnB;AACb,EAAA,MAAM,OAAA,GAAmC;AAAA,IACvC,IAAA,EAAM,eAAA;AAAA,IACN,WAAW,OAAA,CAAQ,QAAA;AAAA,IACnB,aAAa,EAAE,IAAA,EAAM,cAAc,IAAA,EAAM,OAAA,CAAQ,eAAe,WAAA;AAAY,GAC9E;AAEA,EAAA,IAAI,QAAQ,YAAA,EAAc;AACxB,IAAA,OAAA,CAAQ,aAAA,GAAgB,OAAA,CAAQ,YAAA,CAAa,GAAA,CAAI,CAAC,KAAA,MAAW;AAAA,MAC3D,OAAO,EAAE,IAAA,EAAM,YAAA,EAAc,IAAA,EAAM,MAAM,KAAA,EAAM;AAAA,MAC/C,OAAA,EAAS,KAAA,CAAM,OAAA,CAAQ,GAAA,CAAI,CAAC,GAAA,MAAS;AAAA,QACnC,MAAM,EAAE,IAAA,EAAM,YAAA,EAAc,IAAA,EAAM,IAAI,IAAA,EAAK;AAAA,QAC3C,OAAO,GAAA,CAAI;AAAA,OACb,CAAE;AAAA,KACJ,CAAE,CAAA;AAAA,EACJ,CAAA,MAAA,IAAW,QAAQ,OAAA,EAAS;AAC1B,IAAA,OAAA,CAAQ,OAAA,GAAU,OAAA,CAAQ,OAAA,CAAQ,GAAA,CAAI,CAAC,GAAA,MAAS;AAAA,MAC9C,MAAM,EAAE,IAAA,EAAM,YAAA,EAAc,IAAA,EAAM,IAAI,IAAA,EAAK;AAAA,MAC3C,OAAO,GAAA,CAAI;AAAA,KACb,CAAE,CAAA;AAAA,EACJ;AAEA,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,OAAA;AAAA,IACN,UAAU,OAAA,CAAQ,OAAA;AAAA,IAClB,OAAO,EAAE,IAAA,EAAM,YAAA,EAAc,IAAA,EAAM,QAAQ,KAAA,EAAM;AAAA,IACjD;AAAA,GACF;AACF;AAKO,SAAS,eAAe,OAAA,EAOhB;AACb,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,OAAA;AAAA,IACN,UAAU,OAAA,CAAQ,OAAA;AAAA,IAClB,QAAA,EAAU,QAAQ,QAAA,IAAY,KAAA;AAAA,IAC9B,OAAO,EAAE,IAAA,EAAM,YAAA,EAAc,IAAA,EAAM,QAAQ,KAAA,EAAM;AAAA,IACjD,OAAA,EAAS;AAAA,MACP,IAAA,EAAM,kBAAA;AAAA,MACN,WAAW,OAAA,CAAQ,QAAA;AAAA,MACnB,GAAI,OAAA,CAAQ,WAAA,GACR,EAAE,WAAA,EAAa,EAAE,IAAA,EAAM,YAAA,EAAc,IAAA,EAAM,OAAA,CAAQ,WAAA,EAAY,KAC/D,EAAC;AAAA,MACL,GAAI,OAAA,CAAQ,SAAA,GAAY,EAAE,SAAA,EAAW,IAAA,KAAS;AAAC;AACjD,GACF;AACF;AAKO,SAAS,YAAA,GAA2B;AACzC,EAAA,OAAO,EAAE,MAAM,SAAA,EAAU;AAC3B;AAKO,SAAS,aAAa,IAAA,EAA0B;AACrD,EAAA,OAAO;AAAA,IACL,IAAA,EAAM,SAAA;AAAA,IACN,UAAU,CAAC,EAAE,IAAA,EAAM,QAAA,EAAU,MAAM;AAAA,GACrC;AACF;AAkBO,SAAS,aAAa,OAAA,EAMZ;AACf,EAAA,MAAM,SAAuB,CAAC,YAAA,CAAa,IAAI,OAAA,CAAQ,KAAK,GAAG,CAAC,CAAA;AAEhE,EAAA,IAAI,OAAA,CAAQ,WAAW,MAAA,CAAO,IAAA,CAAK,QAAQ,OAAO,CAAA,CAAE,SAAS,CAAA,EAAG;AAC9D,IAAA,MAAA,CAAO,IAAA,CAAK,WAAA,CAAY,OAAA,CAAQ,OAAO,CAAC,CAAA;AAAA,EAC1C;AAEA,EAAA,IAAI,QAAQ,IAAA,EAAM;AAChB,IAAA,MAAA,CAAO,IAAA,CAAK,YAAA,CAAa,OAAA,CAAQ,IAAI,CAAC,CAAA;AAAA,EACxC;AAEA,EAAA,IAAI,OAAA,CAAQ,SAAA,IAAa,OAAA,CAAQ,UAAA,EAAY;AAC3C,IAAA,MAAA,CAAO,IAAA,CAAK,YAAA,CAAa,CAAC,EAAE,IAAA,EAAM,OAAA,CAAQ,UAAA,EAAY,GAAA,EAAK,OAAA,CAAQ,SAAA,EAAW,CAAC,CAAC,CAAA;AAAA,EAClF;AAEA,EAAA,OAAO;AAAA,IACL,MAAM,OAAA,CAAQ,KAAA;AAAA,IACd;AAAA,GACF;AACF;ACxRO,SAAS,mBAAmB,KAAA,EAAuB;AACxD,EAAA,OAAO,CAAA,MAAA,EAAA,CAAK,KAAA,GAAQ,GAAA,EAAK,cAAA,EAAgB,CAAA,CAAA;AAC3C;AAMO,SAAS,WAAA,CAAY,OAAe,QAAA,EAA2B;AACpE,EAAA,IAAI,aAAa,MAAA,EAAW;AAC1B,IAAA,OAAO,CAAA,MAAA,EAAI,KAAA,CAAM,OAAA,CAAQ,QAAQ,CAAC,CAAA,CAAA;AAAA,EACpC;AACA,EAAA,OAAO,CAAA,MAAA,EAAI,KAAA,CAAM,cAAA,EAAgB,CAAA,CAAA;AACnC;AAMO,SAAS,eAAe,EAAA,EAAoB;AACjD,EAAA,OAAO,CAAA,EAAA,CAAI,EAAA,GAAK,GAAA,EAAM,OAAA,CAAQ,CAAC,CAAC,CAAA,CAAA,CAAA;AAClC;AAOO,SAAS,mBAAmB,IAAA,EAAoC;AACrE,EAAA,IAAI,CAAC,MAAM,OAAO,QAAA;AAClB,EAAA,MAAM,IAAI,OAAO,IAAA,KAAS,WAAW,IAAI,IAAA,CAAK,IAAI,CAAA,GAAI,IAAA;AACtD,EAAA,MAAM,KAAA,GAAQ,iBAAA,iBAAkB,IAAI,IAAA,IAAQ,CAAC,CAAA;AAC7C,EAAA,IAAI,QAAQ,CAAA,EAAG;AACb,IAAA,MAAM,IAAA,GAAO,mBAAA,iBAAoB,IAAI,IAAA,IAAQ,CAAC,CAAA;AAC9C,IAAA,OAAO,GAAG,IAAI,CAAA,CAAA,CAAA;AAAA,EAChB;AACA,EAAA,IAAI,KAAA,GAAQ,EAAA,EAAI,OAAO,CAAA,EAAG,KAAK,CAAA,CAAA,CAAA;AAC/B,EAAA,OAAO,mBAAA,CAAoB,CAAA,EAAG,EAAE,SAAA,EAAW,OAAO,CAAA;AACpD;AC/CO,SAAS,MAAM,MAAA,EAAsB;AAC1C,EAAA,OAAO,OAAA,CAAQ,IAAA,CAAK,MAAM,CAAC,CAAA;AAC7B","file":"server.mjs","sourcesContent":["/**\n * Email Template Helpers\n *\n * Shared branded email template and HTML building blocks.\n * Used by both platform and admin apps via their local `lib/email.ts` re-exports.\n */\n\n/**\n * Wrap email body content in the standard Dilipod branded template.\n *\n * Usage:\n * emailTemplate({\n * body: '<h2>Hello</h2><p>Content here</p>',\n * })\n *\n * Or with a preheader (hidden preview text in email clients):\n * emailTemplate({\n * preheader: 'Quick summary shown in inbox',\n * body: '...',\n * })\n */\nexport function emailTemplate({ body, preheader }: { body: string; preheader?: string }): string {\n const preheaderHtml = preheader\n ? `<div style=\"display:none;font-size:1px;color:#ffffff;line-height:1px;max-height:0;max-width:0;opacity:0;overflow:hidden;\">${preheader}</div>`\n : ''\n\n return `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <meta charset=\"utf-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <meta name=\"color-scheme\" content=\"light\" />\n <meta name=\"supported-color-schemes\" content=\"light\" />\n <title>Dilipod</title>\n</head>\n<body style=\"margin: 0; padding: 0; background-color: #f0f0f0; -webkit-font-smoothing: antialiased;\">\n ${preheaderHtml}\n <table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"background-color: #f0f0f0;\">\n <tr>\n <td align=\"center\" style=\"padding: 40px 16px;\">\n <!-- Main card -->\n <table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"max-width: 560px;\">\n <!-- Logo header -->\n <tr>\n <td style=\"padding: 0 0 24px 0;\">\n <table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\">\n <tr>\n <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;\">\n D\n </td>\n <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;\">\n Dilipod\n </td>\n </tr>\n </table>\n </td>\n </tr>\n <!-- Content card -->\n <tr>\n <td>\n <table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\" style=\"background-color: #ffffff; border-radius: 6px; overflow: hidden; border: 1px solid #e2e2e2;\">\n <!-- Accent bar -->\n <tr>\n <td style=\"height: 3px; background: linear-gradient(90deg, #00e5cc 0%, #00c8b5 100%); font-size: 0; line-height: 0;\"> </td>\n </tr>\n <!-- Body content -->\n <tr>\n <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;\">\n ${body}\n </td>\n </tr>\n </table>\n </td>\n </tr>\n <!-- Footer -->\n <tr>\n <td style=\"padding: 24px 4px 0 4px;\">\n <table role=\"presentation\" width=\"100%\" cellpadding=\"0\" cellspacing=\"0\">\n <tr>\n <td style=\"font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 12px; color: #9ca3af;\">\n © ${new Date().getFullYear()} Dilipod — Your Digital Workforce\n </td>\n <td align=\"right\" style=\"font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 12px;\">\n <a href=\"https://dilipod.com\" style=\"color: #9ca3af; text-decoration: none;\">dilipod.com</a>\n </td>\n </tr>\n </table>\n </td>\n </tr>\n </table>\n </td>\n </tr>\n </table>\n</body>\n</html>`\n}\n\n/**\n * Render a CTA button for use inside emailTemplate body.\n *\n * buttonHtml({ text: 'Reset Password', href: 'https://...' })\n * buttonHtml({ text: 'View Incident', href: '...', variant: 'danger' })\n */\nexport function buttonHtml({\n text,\n href,\n variant = 'primary',\n}: {\n text: string\n href: string\n variant?: 'primary' | 'danger'\n}): string {\n const bg = variant === 'danger' ? '#dc2626' : '#111111'\n const color = '#ffffff'\n return `<table role=\"presentation\" cellpadding=\"0\" cellspacing=\"0\" style=\"margin-top: 24px;\">\n <tr>\n <td style=\"background-color: ${bg}; border-radius: 6px;\">\n <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;\">\n ${text} →\n </a>\n </td>\n </tr>\n</table>`\n}\n\n/**\n * Render an info box (grey background) for key details.\n *\n * infoBoxHtml('<strong>Status:</strong> Deployed')\n */\nexport function infoBoxHtml(innerHtml: string): string {\n return `<div style=\"background-color: #f8fafb; padding: 16px 20px; border-radius: 6px; border: 1px solid #e5e7eb; margin: 20px 0; border-left: 3px solid #00e5cc;\">\n ${innerHtml}\n</div>`\n}\n\n/**\n * Render a warning/note box (yellow background).\n *\n * noteBoxHtml('This link expires in 1 hour.')\n */\nexport function noteBoxHtml(text: string): string {\n 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;\">\n ${text}\n</div>`\n}\n","/**\n * Slack Block Kit Helpers\n *\n * Shared Slack message and block builders used by both platform and admin apps.\n * Pure functions with zero dependencies — same pattern as email.ts.\n *\n * Transport layers (sendSlackMessage, sendSlackWebhook) stay local in each app.\n */\n\n// ============================================\n// Types\n// ============================================\n\nexport interface SlackBlock {\n type: string\n text?: { type: string; text: string }\n elements?: SlackElement[]\n fields?: SlackField[]\n}\n\nexport interface SlackElement {\n type: string\n text?: { type: string; text: string }\n style?: string\n value?: string\n url?: string\n action_id?: string\n}\n\nexport interface SlackField {\n type: string\n text: string\n}\n\nexport interface SlackMessage {\n text: string\n blocks: SlackBlock[]\n}\n\n// ============================================\n// Block Builders\n// ============================================\n\n/**\n * Build a single mrkdwn section block.\n *\n * slackSection('*Hello* world')\n */\nexport function slackSection(mrkdwn: string): SlackBlock {\n return {\n type: 'section',\n text: { type: 'mrkdwn', text: mrkdwn },\n }\n}\n\n/**\n * Build a key-value fields block.\n *\n * slackFields({ Process: 'Invoice Bot', Status: 'Live' })\n */\nexport function slackFields(fields: Record<string, string | number>): SlackBlock {\n return {\n type: 'section',\n fields: Object.entries(fields).map(([key, value]) => ({\n type: 'mrkdwn',\n text: `*${key}:*\\n${value}`,\n })),\n }\n}\n\n/**\n * Build an actions block with buttons.\n *\n * slackActions([\n * { text: 'View', url: 'https://...' },\n * { text: 'Approve', actionId: 'approve', style: 'primary', value: '123' },\n * ])\n */\nexport function slackActions(\n buttons: {\n text: string\n url?: string\n value?: string\n actionId?: string\n style?: 'primary' | 'danger'\n }[]\n): SlackBlock {\n return {\n type: 'actions',\n elements: buttons.map((btn) => ({\n type: 'button',\n text: { type: 'plain_text', text: btn.text },\n ...(btn.url ? { url: btn.url } : {}),\n ...(btn.value ? { value: btn.value } : {}),\n ...(btn.actionId ? { action_id: btn.actionId } : {}),\n ...(btn.style ? { style: btn.style } : {}),\n })),\n }\n}\n\n// ============================================\n// Modal & Select Builders\n// ============================================\n\nexport interface SlackModalView {\n type: 'modal'\n callback_id: string\n title: { type: 'plain_text'; text: string }\n submit?: { type: 'plain_text'; text: string }\n close?: { type: 'plain_text'; text: string }\n blocks: SlackBlock[]\n private_metadata?: string\n}\n\nexport interface SlackOptionGroup {\n label: { type: 'plain_text'; text: string }\n options: Array<{\n text: { type: 'plain_text'; text: string }\n value: string\n }>\n}\n\n/**\n * Build a modal view for Slack.\n *\n * slackModal({\n * callbackId: 'run_process',\n * title: 'Run a Process',\n * submitText: 'Run',\n * blocks: [...],\n * })\n */\nexport function slackModal(options: {\n callbackId: string\n title: string\n submitText?: string\n closeText?: string\n blocks: SlackBlock[]\n privateMetadata?: string\n}): SlackModalView {\n return {\n type: 'modal',\n callback_id: options.callbackId,\n title: { type: 'plain_text', text: options.title },\n ...(options.submitText\n ? { submit: { type: 'plain_text', text: options.submitText } }\n : {}),\n ...(options.closeText\n ? { close: { type: 'plain_text', text: options.closeText } }\n : {}),\n blocks: options.blocks,\n ...(options.privateMetadata\n ? { private_metadata: options.privateMetadata }\n : {}),\n }\n}\n\n/**\n * Build a static select input block (for modals or messages).\n *\n * slackStaticSelect({\n * blockId: 'process_select',\n * actionId: 'process_id',\n * label: 'Process',\n * placeholder: 'Select a process',\n * optionGroups: [\n * { label: 'Invoice Bot', options: [{ text: 'Send Invoices', value: 'uuid' }] },\n * ],\n * })\n */\nexport function slackStaticSelect(options: {\n blockId: string\n actionId: string\n label: string\n placeholder?: string\n options?: Array<{ text: string; value: string }>\n optionGroups?: Array<{ label: string; options: Array<{ text: string; value: string }> }>\n}): SlackBlock {\n const element: Record<string, unknown> = {\n type: 'static_select',\n action_id: options.actionId,\n placeholder: { type: 'plain_text', text: options.placeholder || 'Select...' },\n }\n\n if (options.optionGroups) {\n element.option_groups = options.optionGroups.map((group) => ({\n label: { type: 'plain_text', text: group.label },\n options: group.options.map((opt) => ({\n text: { type: 'plain_text', text: opt.text },\n value: opt.value,\n })),\n }))\n } else if (options.options) {\n element.options = options.options.map((opt) => ({\n text: { type: 'plain_text', text: opt.text },\n value: opt.value,\n }))\n }\n\n return {\n type: 'input',\n block_id: options.blockId,\n label: { type: 'plain_text', text: options.label },\n element,\n } as unknown as SlackBlock\n}\n\n/**\n * Build a plain text input block (for modals).\n */\nexport function slackTextInput(options: {\n blockId: string\n actionId: string\n label: string\n placeholder?: string\n optional?: boolean\n multiline?: boolean\n}): SlackBlock {\n return {\n type: 'input',\n block_id: options.blockId,\n optional: options.optional ?? false,\n label: { type: 'plain_text', text: options.label },\n element: {\n type: 'plain_text_input',\n action_id: options.actionId,\n ...(options.placeholder\n ? { placeholder: { type: 'plain_text', text: options.placeholder } }\n : {}),\n ...(options.multiline ? { multiline: true } : {}),\n },\n } as unknown as SlackBlock\n}\n\n/**\n * Build a divider block.\n */\nexport function slackDivider(): SlackBlock {\n return { type: 'divider' } as SlackBlock\n}\n\n/**\n * Build a context block (small muted text).\n */\nexport function slackContext(text: string): SlackBlock {\n return {\n type: 'context',\n elements: [{ type: 'mrkdwn', text }],\n } as unknown as SlackBlock\n}\n\n// ============================================\n// Message Builder\n// ============================================\n\n/**\n * Build a complete Slack message with optional details, note, and button.\n * Covers 90% of notification use cases.\n *\n * slackMessage({\n * title: '🚀 New signup',\n * details: { Name: 'John', Email: 'john@acme.com' },\n * note: 'First user from this company',\n * buttonText: 'View Customer',\n * buttonUrl: 'https://admin.dilipod.com/customers/123',\n * })\n */\nexport function slackMessage(options: {\n title: string\n details?: Record<string, string | number>\n note?: string\n buttonText?: string\n buttonUrl?: string\n}): SlackMessage {\n const blocks: SlackBlock[] = [slackSection(`*${options.title}*`)]\n\n if (options.details && Object.keys(options.details).length > 0) {\n blocks.push(slackFields(options.details))\n }\n\n if (options.note) {\n blocks.push(slackSection(options.note))\n }\n\n if (options.buttonUrl && options.buttonText) {\n blocks.push(slackActions([{ text: options.buttonText, url: options.buttonUrl }]))\n }\n\n return {\n text: options.title,\n blocks,\n }\n}\n","/**\n * Formatting Utilities\n *\n * Shared text, currency, duration, and date formatting used across apps.\n */\n\nimport { differenceInHours, differenceInMinutes, formatDistanceToNow } from 'date-fns'\n\n/**\n * Convert a cent value to euros (numeric).\n * e.g. 2900 → 29\n */\nexport function formatCentsToEuros(cents: number): string {\n return `€${(cents / 100).toLocaleString()}`\n}\n\n/**\n * Format a euro value as a display string.\n * e.g. 299 → \"€299\" or 299.5 with decimals=2 → \"€299.50\"\n */\nexport function formatEuros(euros: number, decimals?: number): string {\n if (decimals !== undefined) {\n return `€${euros.toFixed(decimals)}`\n }\n return `€${euros.toLocaleString()}`\n}\n\n/**\n * Format milliseconds as a human-readable duration.\n * e.g. 1500 → \"1.5s\"\n */\nexport function formatDuration(ms: number): string {\n return `${(ms / 1000).toFixed(1)}s`\n}\n\n/**\n * Format a date into a compact relative-time string.\n * Returns \"—\" for null/undefined, \"5m\" for < 1 hour, \"2h\" for < 48 hours,\n * or a relative distance like \"3 days\" for older dates.\n */\nexport function formatRelativeTime(date: Date | string | null): string {\n if (!date) return '—'\n const d = typeof date === 'string' ? new Date(date) : date\n const hours = differenceInHours(new Date(), d)\n if (hours < 1) {\n const mins = differenceInMinutes(new Date(), d)\n return `${mins}m`\n }\n if (hours < 48) return `${hours}h`\n return formatDistanceToNow(d, { addSuffix: false })\n}\n","import { type ClassValue, clsx } from 'clsx'\nimport { twMerge } from 'tailwind-merge'\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dilipod/ui",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.34",
|
|
4
4
|
"description": "Dilipod Design System - Shared UI components and styles",
|
|
5
5
|
"author": "Dilipod <hello@dilipod.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -71,9 +71,9 @@
|
|
|
71
71
|
"@phosphor-icons/react": "^2.1.7",
|
|
72
72
|
"@radix-ui/react-accordion": "^1.2.12",
|
|
73
73
|
"@radix-ui/react-alert-dialog": "^1.1.15",
|
|
74
|
-
"@radix-ui/react-avatar": "^1.1.
|
|
75
|
-
"@radix-ui/react-dialog": "^1.1.
|
|
76
|
-
"@radix-ui/react-dropdown-menu": "^2.1.
|
|
74
|
+
"@radix-ui/react-avatar": "^1.1.11",
|
|
75
|
+
"@radix-ui/react-dialog": "^1.1.15",
|
|
76
|
+
"@radix-ui/react-dropdown-menu": "^2.1.16",
|
|
77
77
|
"@radix-ui/react-navigation-menu": "^1.2.14",
|
|
78
78
|
"@radix-ui/react-popover": "^1.1.15",
|
|
79
79
|
"@radix-ui/react-radio-group": "^1.3.8",
|
|
@@ -105,7 +105,7 @@
|
|
|
105
105
|
"@testing-library/user-event": "^14.0.0",
|
|
106
106
|
"@types/react": "^19.0.0",
|
|
107
107
|
"@types/react-dom": "^19.0.0",
|
|
108
|
-
"@vitejs/plugin-react": "^
|
|
108
|
+
"@vitejs/plugin-react": "^5.1.2",
|
|
109
109
|
"autoprefixer": "^10.4.21",
|
|
110
110
|
"jsdom": "^23.0.0",
|
|
111
111
|
"playwright": "^1.57.0",
|
|
@@ -117,6 +117,6 @@
|
|
|
117
117
|
"tsup": "^8.5.0",
|
|
118
118
|
"typescript": "^5.8.3",
|
|
119
119
|
"vite": "^6.0.0",
|
|
120
|
-
"vitest": "^
|
|
120
|
+
"vitest": "^4.0.17"
|
|
121
121
|
}
|
|
122
122
|
}
|
|
@@ -37,14 +37,14 @@ const SettingsNav = React.forwardRef<HTMLDivElement, SettingsNavProps>(
|
|
|
37
37
|
<h2 className="text-sm font-medium text-muted-foreground uppercase tracking-wide mb-3 px-1">
|
|
38
38
|
{group.title}
|
|
39
39
|
</h2>
|
|
40
|
-
<div className="
|
|
40
|
+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
|
|
41
41
|
{group.items.map((item) => (
|
|
42
42
|
<Link
|
|
43
43
|
key={item.href}
|
|
44
44
|
href={item.href}
|
|
45
|
-
className="group flex items-
|
|
45
|
+
className="group flex items-start gap-4 p-5 rounded-lg transition-all bg-white border border-gray-200 hover:border-[var(--cyan)]/40 hover:shadow-sm"
|
|
46
46
|
>
|
|
47
|
-
<div className="flex items-center justify-center w-10 h-10 rounded-
|
|
47
|
+
<div className="flex items-center justify-center w-10 h-10 rounded-lg bg-gray-100 group-hover:bg-[var(--cyan)]/10 transition-colors shrink-0">
|
|
48
48
|
<span className="text-gray-600 group-hover:text-[var(--cyan)] transition-colors [&>svg]:w-5 [&>svg]:h-5">
|
|
49
49
|
{item.icon}
|
|
50
50
|
</span>
|
|
@@ -53,14 +53,10 @@ const SettingsNav = React.forwardRef<HTMLDivElement, SettingsNavProps>(
|
|
|
53
53
|
<h3 className="font-medium text-[var(--black)] group-hover:text-[var(--cyan)] transition-colors">
|
|
54
54
|
{item.title}
|
|
55
55
|
</h3>
|
|
56
|
-
<p className="text-sm text-muted-foreground">
|
|
56
|
+
<p className="text-sm text-muted-foreground mt-0.5 leading-snug">
|
|
57
57
|
{item.description}
|
|
58
58
|
</p>
|
|
59
59
|
</div>
|
|
60
|
-
<CaretRight
|
|
61
|
-
size={18}
|
|
62
|
-
className="text-gray-300 group-hover:text-[var(--cyan)] group-hover:translate-x-0.5 transition-all shrink-0"
|
|
63
|
-
/>
|
|
64
60
|
</Link>
|
|
65
61
|
))}
|
|
66
62
|
</div>
|
package/src/components/sheet.tsx
CHANGED
|
@@ -53,12 +53,14 @@ const sheetVariants = cva(
|
|
|
53
53
|
|
|
54
54
|
interface SheetContentProps
|
|
55
55
|
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
|
|
56
|
-
VariantProps<typeof sheetVariants> {
|
|
56
|
+
VariantProps<typeof sheetVariants> {
|
|
57
|
+
hideClose?: boolean
|
|
58
|
+
}
|
|
57
59
|
|
|
58
60
|
const SheetContent = React.forwardRef<
|
|
59
61
|
React.ElementRef<typeof SheetPrimitive.Content>,
|
|
60
62
|
SheetContentProps
|
|
61
|
-
>(({ side = 'right', className, children, ...props }, ref) => (
|
|
63
|
+
>(({ side = 'right', className, children, hideClose, ...props }, ref) => (
|
|
62
64
|
<SheetPortal>
|
|
63
65
|
<SheetOverlay />
|
|
64
66
|
<SheetPrimitive.Content
|
|
@@ -67,11 +69,13 @@ const SheetContent = React.forwardRef<
|
|
|
67
69
|
{...props}
|
|
68
70
|
>
|
|
69
71
|
{children}
|
|
70
|
-
{
|
|
71
|
-
|
|
72
|
-
<
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
{!hideClose && (
|
|
73
|
+
// @ts-expect-error - Radix Dialog Close accepts className and children at runtime
|
|
74
|
+
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none disabled:pointer-events-none data-[state=open]:bg-gray-100">
|
|
75
|
+
<X className="h-4 w-4" />
|
|
76
|
+
<span className="sr-only">Close</span>
|
|
77
|
+
</SheetPrimitive.Close>
|
|
78
|
+
)}
|
|
75
79
|
</SheetPrimitive.Content>
|
|
76
80
|
</SheetPortal>
|
|
77
81
|
))
|