@dilipod/ui 0.4.33 → 0.4.35
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/file-preview.d.ts +1 -0
- package/dist/components/file-preview.d.ts.map +1 -1
- package/dist/components/flowchart-diagram.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 +358 -163
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +359 -164
- 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 +1 -1
- package/src/components/file-preview.tsx +29 -15
- package/src/components/flowchart-diagram.tsx +446 -216
- 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":";;;;;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;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;ACjIO,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// 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":"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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// 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
|
@@ -22,6 +22,7 @@ export interface UploadedFile {
|
|
|
22
22
|
type: string
|
|
23
23
|
size: number
|
|
24
24
|
url?: string
|
|
25
|
+
processing?: boolean
|
|
25
26
|
}
|
|
26
27
|
|
|
27
28
|
export interface FilePreviewProps {
|
|
@@ -157,11 +158,12 @@ export function FilePreview({
|
|
|
157
158
|
const FileIcon = getFileIcon(file.type)
|
|
158
159
|
const typeLabel = getTypeLabel(file.type)
|
|
159
160
|
const sizeLabel = formatSize(file.size)
|
|
160
|
-
const
|
|
161
|
+
const isProcessing = file.processing === true
|
|
162
|
+
const isPreviewable = !isProcessing && canPreview(file)
|
|
161
163
|
const previewType = getPreviewType(file)
|
|
162
|
-
|
|
164
|
+
|
|
163
165
|
return (
|
|
164
|
-
<div
|
|
166
|
+
<div
|
|
165
167
|
key={i}
|
|
166
168
|
className={`flex items-center justify-between p-3 rounded-md bg-gray-50 transition-colors ${
|
|
167
169
|
isPreviewable ? 'hover:bg-gray-100 cursor-pointer' : ''
|
|
@@ -171,29 +173,41 @@ export function FilePreview({
|
|
|
171
173
|
<div className="flex items-center gap-3 min-w-0">
|
|
172
174
|
<div className="relative shrink-0">
|
|
173
175
|
<div className="w-10 h-10 rounded-sm bg-white border border-gray-200 flex items-center justify-center">
|
|
174
|
-
|
|
176
|
+
{isProcessing ? (
|
|
177
|
+
<CircleNotch className="w-5 h-5 text-[var(--cyan)] animate-spin" />
|
|
178
|
+
) : (
|
|
179
|
+
<FileIcon className="w-5 h-5 text-[var(--cyan)]" weight="fill" />
|
|
180
|
+
)}
|
|
175
181
|
</div>
|
|
176
|
-
{previewType === 'video' && (
|
|
182
|
+
{!isProcessing && previewType === 'video' && (
|
|
177
183
|
<Play className="absolute -bottom-0.5 -right-0.5 w-3.5 h-3.5 text-[var(--cyan)] bg-white rounded-full" weight="fill" />
|
|
178
184
|
)}
|
|
179
185
|
</div>
|
|
180
186
|
<div className="min-w-0">
|
|
181
187
|
<p className="text-sm font-medium text-[var(--black)] truncate">{file.filename}</p>
|
|
182
188
|
<p className="text-xs text-muted-foreground">
|
|
183
|
-
{
|
|
184
|
-
|
|
185
|
-
|
|
189
|
+
{isProcessing ? (
|
|
190
|
+
<span className="text-amber-600">Processing video — this takes about a minute</span>
|
|
191
|
+
) : (
|
|
192
|
+
<>
|
|
193
|
+
{typeLabel} · {sizeLabel}
|
|
194
|
+
{isPreviewable && (
|
|
195
|
+
<span className="text-[var(--cyan)] ml-1">· Click to preview</span>
|
|
196
|
+
)}
|
|
197
|
+
</>
|
|
186
198
|
)}
|
|
187
199
|
</p>
|
|
188
200
|
</div>
|
|
189
201
|
</div>
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
202
|
+
{!isProcessing && (
|
|
203
|
+
<button
|
|
204
|
+
onClick={(e) => handleDownload(e, file)}
|
|
205
|
+
className="p-2 rounded-sm hover:bg-gray-200 transition-colors shrink-0"
|
|
206
|
+
title="Download"
|
|
207
|
+
>
|
|
208
|
+
<Download className="w-4 h-4 text-muted-foreground" />
|
|
209
|
+
</button>
|
|
210
|
+
)}
|
|
197
211
|
</div>
|
|
198
212
|
)
|
|
199
213
|
})}
|