@dilipod/ui 0.4.30 → 0.4.31

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.
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Server-safe exports from @dilipod/ui
3
+ *
4
+ * This entry point does NOT include the "use client" directive,
5
+ * so these utilities can be safely used in Next.js API routes,
6
+ * server actions, and other server-side code.
7
+ */
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';
11
+ export { formatCentsToEuros, formatEuros, formatDuration, formatRelativeTime } from './lib/formatting';
12
+ export { cn } from './lib/utils';
13
+ //# sourceMappingURL=server.d.ts.map
@@ -0,0 +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,EAAE,YAAY,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AACnF,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAGrF,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 ADDED
@@ -0,0 +1,195 @@
1
+ 'use strict';
2
+
3
+ var dateFns = require('date-fns');
4
+ var clsx = require('clsx');
5
+ var tailwindMerge = require('tailwind-merge');
6
+
7
+ // src/lib/email.ts
8
+ function emailTemplate({ body, preheader }) {
9
+ const preheaderHtml = preheader ? `<div style="display:none;font-size:1px;color:#ffffff;line-height:1px;max-height:0;max-width:0;opacity:0;overflow:hidden;">${preheader}</div>` : "";
10
+ return `<!DOCTYPE html>
11
+ <html lang="en">
12
+ <head>
13
+ <meta charset="utf-8" />
14
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
15
+ <meta name="color-scheme" content="light" />
16
+ <meta name="supported-color-schemes" content="light" />
17
+ <title>Dilipod</title>
18
+ </head>
19
+ <body style="margin: 0; padding: 0; background-color: #f0f0f0; -webkit-font-smoothing: antialiased;">
20
+ ${preheaderHtml}
21
+ <table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background-color: #f0f0f0;">
22
+ <tr>
23
+ <td align="center" style="padding: 40px 16px;">
24
+ <!-- Main card -->
25
+ <table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="max-width: 560px;">
26
+ <!-- Logo header -->
27
+ <tr>
28
+ <td style="padding: 0 0 24px 0;">
29
+ <table role="presentation" cellpadding="0" cellspacing="0">
30
+ <tr>
31
+ <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;">
32
+ D
33
+ </td>
34
+ <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;">
35
+ Dilipod
36
+ </td>
37
+ </tr>
38
+ </table>
39
+ </td>
40
+ </tr>
41
+ <!-- Content card -->
42
+ <tr>
43
+ <td>
44
+ <table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background-color: #ffffff; border-radius: 6px; overflow: hidden; border: 1px solid #e2e2e2;">
45
+ <!-- Accent bar -->
46
+ <tr>
47
+ <td style="height: 3px; background: linear-gradient(90deg, #00e5cc 0%, #00c8b5 100%); font-size: 0; line-height: 0;">&nbsp;</td>
48
+ </tr>
49
+ <!-- Body content -->
50
+ <tr>
51
+ <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;">
52
+ ${body}
53
+ </td>
54
+ </tr>
55
+ </table>
56
+ </td>
57
+ </tr>
58
+ <!-- Footer -->
59
+ <tr>
60
+ <td style="padding: 24px 4px 0 4px;">
61
+ <table role="presentation" width="100%" cellpadding="0" cellspacing="0">
62
+ <tr>
63
+ <td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 12px; color: #9ca3af;">
64
+ &copy; ${(/* @__PURE__ */ new Date()).getFullYear()} Dilipod &mdash; Your Digital Workforce
65
+ </td>
66
+ <td align="right" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 12px;">
67
+ <a href="https://dilipod.com" style="color: #9ca3af; text-decoration: none;">dilipod.com</a>
68
+ </td>
69
+ </tr>
70
+ </table>
71
+ </td>
72
+ </tr>
73
+ </table>
74
+ </td>
75
+ </tr>
76
+ </table>
77
+ </body>
78
+ </html>`;
79
+ }
80
+ function buttonHtml({
81
+ text,
82
+ href,
83
+ variant = "primary"
84
+ }) {
85
+ const bg = variant === "danger" ? "#dc2626" : "#111111";
86
+ const color = "#ffffff";
87
+ return `<table role="presentation" cellpadding="0" cellspacing="0" style="margin-top: 24px;">
88
+ <tr>
89
+ <td style="background-color: ${bg}; border-radius: 6px;">
90
+ <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;">
91
+ ${text} &rarr;
92
+ </a>
93
+ </td>
94
+ </tr>
95
+ </table>`;
96
+ }
97
+ function infoBoxHtml(innerHtml) {
98
+ return `<div style="background-color: #f8fafb; padding: 16px 20px; border-radius: 6px; border: 1px solid #e5e7eb; margin: 20px 0; border-left: 3px solid #00e5cc;">
99
+ ${innerHtml}
100
+ </div>`;
101
+ }
102
+ function noteBoxHtml(text) {
103
+ 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;">
104
+ ${text}
105
+ </div>`;
106
+ }
107
+
108
+ // src/lib/slack.ts
109
+ function slackSection(mrkdwn) {
110
+ return {
111
+ type: "section",
112
+ text: { type: "mrkdwn", text: mrkdwn }
113
+ };
114
+ }
115
+ function slackFields(fields) {
116
+ return {
117
+ type: "section",
118
+ fields: Object.entries(fields).map(([key, value]) => ({
119
+ type: "mrkdwn",
120
+ text: `*${key}:*
121
+ ${value}`
122
+ }))
123
+ };
124
+ }
125
+ function slackActions(buttons) {
126
+ return {
127
+ type: "actions",
128
+ elements: buttons.map((btn) => ({
129
+ type: "button",
130
+ text: { type: "plain_text", text: btn.text },
131
+ ...btn.url ? { url: btn.url } : {},
132
+ ...btn.value ? { value: btn.value } : {},
133
+ ...btn.actionId ? { action_id: btn.actionId } : {},
134
+ ...btn.style ? { style: btn.style } : {}
135
+ }))
136
+ };
137
+ }
138
+ function slackMessage(options) {
139
+ const blocks = [slackSection(`*${options.title}*`)];
140
+ if (options.details && Object.keys(options.details).length > 0) {
141
+ blocks.push(slackFields(options.details));
142
+ }
143
+ if (options.note) {
144
+ blocks.push(slackSection(options.note));
145
+ }
146
+ if (options.buttonUrl && options.buttonText) {
147
+ blocks.push(slackActions([{ text: options.buttonText, url: options.buttonUrl }]));
148
+ }
149
+ return {
150
+ text: options.title,
151
+ blocks
152
+ };
153
+ }
154
+ function formatCentsToEuros(cents) {
155
+ return `\u20AC${(cents / 100).toLocaleString()}`;
156
+ }
157
+ function formatEuros(euros, decimals) {
158
+ if (decimals !== void 0) {
159
+ return `\u20AC${euros.toFixed(decimals)}`;
160
+ }
161
+ return `\u20AC${euros.toLocaleString()}`;
162
+ }
163
+ function formatDuration(ms) {
164
+ return `${(ms / 1e3).toFixed(1)}s`;
165
+ }
166
+ function formatRelativeTime(date) {
167
+ if (!date) return "\u2014";
168
+ const d = typeof date === "string" ? new Date(date) : date;
169
+ const hours = dateFns.differenceInHours(/* @__PURE__ */ new Date(), d);
170
+ if (hours < 1) {
171
+ const mins = dateFns.differenceInMinutes(/* @__PURE__ */ new Date(), d);
172
+ return `${mins}m`;
173
+ }
174
+ if (hours < 48) return `${hours}h`;
175
+ return dateFns.formatDistanceToNow(d, { addSuffix: false });
176
+ }
177
+ function cn(...inputs) {
178
+ return tailwindMerge.twMerge(clsx.clsx(inputs));
179
+ }
180
+
181
+ exports.buttonHtml = buttonHtml;
182
+ exports.cn = cn;
183
+ exports.emailTemplate = emailTemplate;
184
+ exports.formatCentsToEuros = formatCentsToEuros;
185
+ exports.formatDuration = formatDuration;
186
+ exports.formatEuros = formatEuros;
187
+ exports.formatRelativeTime = formatRelativeTime;
188
+ exports.infoBoxHtml = infoBoxHtml;
189
+ exports.noteBoxHtml = noteBoxHtml;
190
+ exports.slackActions = slackActions;
191
+ exports.slackFields = slackFields;
192
+ exports.slackMessage = slackMessage;
193
+ exports.slackSection = slackSection;
194
+ //# sourceMappingURL=server.js.map
195
+ //# sourceMappingURL=server.js.map
@@ -0,0 +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;\">&nbsp;</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 &copy; ${new Date().getFullYear()} Dilipod &mdash; 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} &rarr;\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({ Worker: '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"]}
@@ -0,0 +1,181 @@
1
+ import { differenceInHours, differenceInMinutes, formatDistanceToNow } from 'date-fns';
2
+ import { clsx } from 'clsx';
3
+ import { twMerge } from 'tailwind-merge';
4
+
5
+ // src/lib/email.ts
6
+ function emailTemplate({ body, preheader }) {
7
+ const preheaderHtml = preheader ? `<div style="display:none;font-size:1px;color:#ffffff;line-height:1px;max-height:0;max-width:0;opacity:0;overflow:hidden;">${preheader}</div>` : "";
8
+ return `<!DOCTYPE html>
9
+ <html lang="en">
10
+ <head>
11
+ <meta charset="utf-8" />
12
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
13
+ <meta name="color-scheme" content="light" />
14
+ <meta name="supported-color-schemes" content="light" />
15
+ <title>Dilipod</title>
16
+ </head>
17
+ <body style="margin: 0; padding: 0; background-color: #f0f0f0; -webkit-font-smoothing: antialiased;">
18
+ ${preheaderHtml}
19
+ <table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background-color: #f0f0f0;">
20
+ <tr>
21
+ <td align="center" style="padding: 40px 16px;">
22
+ <!-- Main card -->
23
+ <table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="max-width: 560px;">
24
+ <!-- Logo header -->
25
+ <tr>
26
+ <td style="padding: 0 0 24px 0;">
27
+ <table role="presentation" cellpadding="0" cellspacing="0">
28
+ <tr>
29
+ <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;">
30
+ D
31
+ </td>
32
+ <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;">
33
+ Dilipod
34
+ </td>
35
+ </tr>
36
+ </table>
37
+ </td>
38
+ </tr>
39
+ <!-- Content card -->
40
+ <tr>
41
+ <td>
42
+ <table role="presentation" width="100%" cellpadding="0" cellspacing="0" style="background-color: #ffffff; border-radius: 6px; overflow: hidden; border: 1px solid #e2e2e2;">
43
+ <!-- Accent bar -->
44
+ <tr>
45
+ <td style="height: 3px; background: linear-gradient(90deg, #00e5cc 0%, #00c8b5 100%); font-size: 0; line-height: 0;">&nbsp;</td>
46
+ </tr>
47
+ <!-- Body content -->
48
+ <tr>
49
+ <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;">
50
+ ${body}
51
+ </td>
52
+ </tr>
53
+ </table>
54
+ </td>
55
+ </tr>
56
+ <!-- Footer -->
57
+ <tr>
58
+ <td style="padding: 24px 4px 0 4px;">
59
+ <table role="presentation" width="100%" cellpadding="0" cellspacing="0">
60
+ <tr>
61
+ <td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 12px; color: #9ca3af;">
62
+ &copy; ${(/* @__PURE__ */ new Date()).getFullYear()} Dilipod &mdash; Your Digital Workforce
63
+ </td>
64
+ <td align="right" style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; font-size: 12px;">
65
+ <a href="https://dilipod.com" style="color: #9ca3af; text-decoration: none;">dilipod.com</a>
66
+ </td>
67
+ </tr>
68
+ </table>
69
+ </td>
70
+ </tr>
71
+ </table>
72
+ </td>
73
+ </tr>
74
+ </table>
75
+ </body>
76
+ </html>`;
77
+ }
78
+ function buttonHtml({
79
+ text,
80
+ href,
81
+ variant = "primary"
82
+ }) {
83
+ const bg = variant === "danger" ? "#dc2626" : "#111111";
84
+ const color = "#ffffff";
85
+ return `<table role="presentation" cellpadding="0" cellspacing="0" style="margin-top: 24px;">
86
+ <tr>
87
+ <td style="background-color: ${bg}; border-radius: 6px;">
88
+ <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;">
89
+ ${text} &rarr;
90
+ </a>
91
+ </td>
92
+ </tr>
93
+ </table>`;
94
+ }
95
+ function infoBoxHtml(innerHtml) {
96
+ return `<div style="background-color: #f8fafb; padding: 16px 20px; border-radius: 6px; border: 1px solid #e5e7eb; margin: 20px 0; border-left: 3px solid #00e5cc;">
97
+ ${innerHtml}
98
+ </div>`;
99
+ }
100
+ function noteBoxHtml(text) {
101
+ 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;">
102
+ ${text}
103
+ </div>`;
104
+ }
105
+
106
+ // src/lib/slack.ts
107
+ function slackSection(mrkdwn) {
108
+ return {
109
+ type: "section",
110
+ text: { type: "mrkdwn", text: mrkdwn }
111
+ };
112
+ }
113
+ function slackFields(fields) {
114
+ return {
115
+ type: "section",
116
+ fields: Object.entries(fields).map(([key, value]) => ({
117
+ type: "mrkdwn",
118
+ text: `*${key}:*
119
+ ${value}`
120
+ }))
121
+ };
122
+ }
123
+ function slackActions(buttons) {
124
+ return {
125
+ type: "actions",
126
+ elements: buttons.map((btn) => ({
127
+ type: "button",
128
+ text: { type: "plain_text", text: btn.text },
129
+ ...btn.url ? { url: btn.url } : {},
130
+ ...btn.value ? { value: btn.value } : {},
131
+ ...btn.actionId ? { action_id: btn.actionId } : {},
132
+ ...btn.style ? { style: btn.style } : {}
133
+ }))
134
+ };
135
+ }
136
+ function slackMessage(options) {
137
+ const blocks = [slackSection(`*${options.title}*`)];
138
+ if (options.details && Object.keys(options.details).length > 0) {
139
+ blocks.push(slackFields(options.details));
140
+ }
141
+ if (options.note) {
142
+ blocks.push(slackSection(options.note));
143
+ }
144
+ if (options.buttonUrl && options.buttonText) {
145
+ blocks.push(slackActions([{ text: options.buttonText, url: options.buttonUrl }]));
146
+ }
147
+ return {
148
+ text: options.title,
149
+ blocks
150
+ };
151
+ }
152
+ function formatCentsToEuros(cents) {
153
+ return `\u20AC${(cents / 100).toLocaleString()}`;
154
+ }
155
+ function formatEuros(euros, decimals) {
156
+ if (decimals !== void 0) {
157
+ return `\u20AC${euros.toFixed(decimals)}`;
158
+ }
159
+ return `\u20AC${euros.toLocaleString()}`;
160
+ }
161
+ function formatDuration(ms) {
162
+ return `${(ms / 1e3).toFixed(1)}s`;
163
+ }
164
+ function formatRelativeTime(date) {
165
+ if (!date) return "\u2014";
166
+ const d = typeof date === "string" ? new Date(date) : date;
167
+ const hours = differenceInHours(/* @__PURE__ */ new Date(), d);
168
+ if (hours < 1) {
169
+ const mins = differenceInMinutes(/* @__PURE__ */ new Date(), d);
170
+ return `${mins}m`;
171
+ }
172
+ if (hours < 48) return `${hours}h`;
173
+ return formatDistanceToNow(d, { addSuffix: false });
174
+ }
175
+ function cn(...inputs) {
176
+ return twMerge(clsx(inputs));
177
+ }
178
+
179
+ export { buttonHtml, cn, emailTemplate, formatCentsToEuros, formatDuration, formatEuros, formatRelativeTime, infoBoxHtml, noteBoxHtml, slackActions, slackFields, slackMessage, slackSection };
180
+ //# sourceMappingURL=server.mjs.map
181
+ //# sourceMappingURL=server.mjs.map
@@ -0,0 +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;\">&nbsp;</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 &copy; ${new Date().getFullYear()} Dilipod &mdash; 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} &rarr;\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({ Worker: '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"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dilipod/ui",
3
- "version": "0.4.30",
3
+ "version": "0.4.31",
4
4
  "description": "Dilipod Design System - Shared UI components and styles",
5
5
  "author": "Dilipod <hello@dilipod.com>",
6
6
  "license": "MIT",
@@ -32,6 +32,11 @@
32
32
  "import": "./dist/index.mjs",
33
33
  "require": "./dist/index.js"
34
34
  },
35
+ "./server": {
36
+ "types": "./dist/server.d.ts",
37
+ "import": "./dist/server.mjs",
38
+ "require": "./dist/server.js"
39
+ },
35
40
  "./styles": "./src/styles/globals.css",
36
41
  "./styles.css": "./src/styles/globals.css"
37
42
  },
package/src/server.ts ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Server-safe exports from @dilipod/ui
3
+ *
4
+ * This entry point does NOT include the "use client" directive,
5
+ * so these utilities can be safely used in Next.js API routes,
6
+ * server actions, and other server-side code.
7
+ */
8
+
9
+ // Email Template Helpers
10
+ export { emailTemplate, buttonHtml, infoBoxHtml, noteBoxHtml } from './lib/email'
11
+
12
+ // Slack Block Kit Helpers
13
+ export { slackMessage, slackSection, slackFields, slackActions } from './lib/slack'
14
+ export type { SlackBlock, SlackElement, SlackField, SlackMessage } from './lib/slack'
15
+
16
+ // Formatting Utilities
17
+ export { formatCentsToEuros, formatEuros, formatDuration, formatRelativeTime } from './lib/formatting'
18
+
19
+ // General Utilities
20
+ export { cn } from './lib/utils'