@optifye/dashboard-core 4.2.1 → 4.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -212,6 +212,7 @@ interface EndpointsConfig {
212
212
  worstPerformingWorkspaces?: string;
213
213
  agnoApiUrl?: string;
214
214
  slackWebhookUrl?: string;
215
+ slackProxyEndpoint?: string;
215
216
  }
216
217
  interface DateTimeConfig {
217
218
  defaultTimezone?: string;
@@ -4344,9 +4345,63 @@ interface SupportTicket {
4344
4345
  }
4345
4346
  declare class SlackAPI {
4346
4347
  /**
4347
- * Sends a support ticket notification directly to Slack webhook
4348
+ * Sends a support ticket notification to Slack.
4349
+ * 1. If slackWebhookUrl is configured, send directly (preferred)
4350
+ * 2. Otherwise, if slackProxyEndpoint is configured, send to the proxy
4351
+ * If neither is configured, logs a warning.
4348
4352
  */
4349
4353
  static sendSupportTicketNotification(ticket: SupportTicket): Promise<void>;
4354
+ /**
4355
+ * Formats a support ticket into a Slack message format
4356
+ * This can be used by server-side implementations
4357
+ */
4358
+ static formatSlackMessage(ticket: SupportTicket): {
4359
+ text: string;
4360
+ blocks: ({
4361
+ type: string;
4362
+ text: {
4363
+ type: string;
4364
+ text: string;
4365
+ emoji: boolean;
4366
+ };
4367
+ fields?: undefined;
4368
+ elements?: undefined;
4369
+ } | {
4370
+ type: string;
4371
+ fields: {
4372
+ type: string;
4373
+ text: string;
4374
+ }[];
4375
+ text?: undefined;
4376
+ elements?: undefined;
4377
+ } | {
4378
+ type: string;
4379
+ text: {
4380
+ type: string;
4381
+ text: string;
4382
+ emoji?: undefined;
4383
+ };
4384
+ fields?: undefined;
4385
+ elements?: undefined;
4386
+ } | {
4387
+ type: string;
4388
+ text?: undefined;
4389
+ fields?: undefined;
4390
+ elements?: undefined;
4391
+ } | {
4392
+ type: string;
4393
+ elements: {
4394
+ type: string;
4395
+ text: string;
4396
+ }[];
4397
+ text?: undefined;
4398
+ fields?: undefined;
4399
+ })[];
4400
+ attachments: {
4401
+ color: string;
4402
+ fields: never[];
4403
+ }[];
4404
+ };
4350
4405
  }
4351
4406
 
4352
4407
  interface ThreadSidebarProps {
package/dist/index.d.ts CHANGED
@@ -212,6 +212,7 @@ interface EndpointsConfig {
212
212
  worstPerformingWorkspaces?: string;
213
213
  agnoApiUrl?: string;
214
214
  slackWebhookUrl?: string;
215
+ slackProxyEndpoint?: string;
215
216
  }
216
217
  interface DateTimeConfig {
217
218
  defaultTimezone?: string;
@@ -4344,9 +4345,63 @@ interface SupportTicket {
4344
4345
  }
4345
4346
  declare class SlackAPI {
4346
4347
  /**
4347
- * Sends a support ticket notification directly to Slack webhook
4348
+ * Sends a support ticket notification to Slack.
4349
+ * 1. If slackWebhookUrl is configured, send directly (preferred)
4350
+ * 2. Otherwise, if slackProxyEndpoint is configured, send to the proxy
4351
+ * If neither is configured, logs a warning.
4348
4352
  */
4349
4353
  static sendSupportTicketNotification(ticket: SupportTicket): Promise<void>;
4354
+ /**
4355
+ * Formats a support ticket into a Slack message format
4356
+ * This can be used by server-side implementations
4357
+ */
4358
+ static formatSlackMessage(ticket: SupportTicket): {
4359
+ text: string;
4360
+ blocks: ({
4361
+ type: string;
4362
+ text: {
4363
+ type: string;
4364
+ text: string;
4365
+ emoji: boolean;
4366
+ };
4367
+ fields?: undefined;
4368
+ elements?: undefined;
4369
+ } | {
4370
+ type: string;
4371
+ fields: {
4372
+ type: string;
4373
+ text: string;
4374
+ }[];
4375
+ text?: undefined;
4376
+ elements?: undefined;
4377
+ } | {
4378
+ type: string;
4379
+ text: {
4380
+ type: string;
4381
+ text: string;
4382
+ emoji?: undefined;
4383
+ };
4384
+ fields?: undefined;
4385
+ elements?: undefined;
4386
+ } | {
4387
+ type: string;
4388
+ text?: undefined;
4389
+ fields?: undefined;
4390
+ elements?: undefined;
4391
+ } | {
4392
+ type: string;
4393
+ elements: {
4394
+ type: string;
4395
+ text: string;
4396
+ }[];
4397
+ text?: undefined;
4398
+ fields?: undefined;
4399
+ })[];
4400
+ attachments: {
4401
+ color: string;
4402
+ fields: never[];
4403
+ }[];
4404
+ };
4350
4405
  }
4351
4406
 
4352
4407
  interface ThreadSidebarProps {
package/dist/index.js CHANGED
@@ -139,8 +139,10 @@ var DEFAULT_ENDPOINTS_CONFIG = {
139
139
  whatsapp: "/api/send-whatsapp-direct",
140
140
  agnoApiUrl: process.env.NEXT_PUBLIC_AGNO_URL || "https://optifye-agent-production.up.railway.app",
141
141
  // Default AGNO API URL
142
- slackWebhookUrl: process.env.NEXT_PUBLIC_SLACK_WEBHOOK_URL || void 0
143
- // Slack webhook URL from environment
142
+ // Use environment variable for Slack webhook URL for privacy/security
143
+ // Note: SLACK_WEBHOOK_URL is server-side only, NEXT_PUBLIC_SLACK_WEBHOOK_URL works client-side but is less secure
144
+ slackWebhookUrl: process.env.SLACK_WEBHOOK_URL || process.env.NEXT_PUBLIC_SLACK_WEBHOOK_URL || void 0,
145
+ slackProxyEndpoint: void 0
144
146
  };
145
147
  var DEFAULT_THEME_CONFIG = {
146
148
  // Sensible defaults for theme can be added here
@@ -23226,13 +23228,13 @@ var SideNavBar = React33.memo(({
23226
23228
  {
23227
23229
  onClick: handleKPIsClick,
23228
23230
  className: kpisButtonClasses,
23229
- "aria-label": "KPIs",
23231
+ "aria-label": "Lines",
23230
23232
  tabIndex: 0,
23231
23233
  role: "tab",
23232
23234
  "aria-selected": pathname === "/kpis" || pathname.startsWith("/kpis/"),
23233
23235
  children: [
23234
23236
  /* @__PURE__ */ jsxRuntime.jsx(outline.ChartBarIcon, { className: "w-5 h-5 mb-1" }),
23235
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] font-medium leading-tight", children: "KPIs" })
23237
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-[10px] font-medium leading-tight", children: "Lines" })
23236
23238
  ]
23237
23239
  }
23238
23240
  ),
@@ -25145,125 +25147,166 @@ var streamProxyConfig = {
25145
25147
  // src/lib/api/slackApi.ts
25146
25148
  var SlackAPI = class {
25147
25149
  /**
25148
- * Sends a support ticket notification directly to Slack webhook
25150
+ * Sends a support ticket notification to Slack.
25151
+ * 1. If slackWebhookUrl is configured, send directly (preferred)
25152
+ * 2. Otherwise, if slackProxyEndpoint is configured, send to the proxy
25153
+ * If neither is configured, logs a warning.
25149
25154
  */
25150
25155
  static async sendSupportTicketNotification(ticket) {
25151
25156
  try {
25152
25157
  const config = _getDashboardConfigInstance();
25153
25158
  const endpointsConfig = config.endpoints ?? DEFAULT_ENDPOINTS_CONFIG;
25154
- const slackWebhookUrl = endpointsConfig.slackWebhookUrl;
25155
- if (!slackWebhookUrl) {
25156
- console.log("Slack webhook URL not configured, skipping notification");
25159
+ const { slackWebhookUrl, slackProxyEndpoint } = endpointsConfig;
25160
+ console.log("SlackAPI Debug - Configuration check:", {
25161
+ hasConfig: !!config,
25162
+ hasEndpoints: !!config.endpoints,
25163
+ slackWebhookUrl: slackWebhookUrl ? "configured" : "not configured",
25164
+ slackProxyEndpoint: slackProxyEndpoint ? "configured" : "not configured",
25165
+ envVariable: process.env.SLACK_WEBHOOK_URL ? "set" : "not set",
25166
+ publicEnvVariable: process.env.NEXT_PUBLIC_SLACK_WEBHOOK_URL ? "set" : "not set"
25167
+ });
25168
+ if (process.env.NEXT_PUBLIC_SLACK_WEBHOOK_URL && !process.env.SLACK_WEBHOOK_URL) {
25169
+ console.warn("\u26A0\uFE0F SECURITY WARNING: Using NEXT_PUBLIC_SLACK_WEBHOOK_URL exposes your webhook URL to the client. Consider using a server-side proxy instead.");
25170
+ }
25171
+ if (slackWebhookUrl) {
25172
+ const slackMessage = this.formatSlackMessage(ticket);
25173
+ const response = await fetch(slackWebhookUrl, {
25174
+ method: "POST",
25175
+ headers: {
25176
+ // Use form-urlencoded + simple headers to avoid CORS preflight
25177
+ "Content-Type": "application/x-www-form-urlencoded"
25178
+ },
25179
+ body: `payload=${encodeURIComponent(JSON.stringify(slackMessage))}`
25180
+ });
25181
+ if (!response.ok) {
25182
+ const errorText = await response.text();
25183
+ console.error("Slack webhook error:", errorText);
25184
+ throw new Error(`Slack webhook failed with status: ${response.status}`);
25185
+ }
25186
+ console.log("Support ticket notification sent to Slack successfully via webhook");
25157
25187
  return;
25158
25188
  }
25159
- const timestamp = new Date(ticket.timestamp).toLocaleString("en-US", {
25160
- dateStyle: "medium",
25161
- timeStyle: "short",
25162
- timeZone: "UTC"
25163
- });
25164
- const maxDescriptionLength = 300;
25165
- const description = ticket.description.length > maxDescriptionLength ? `${ticket.description.substring(0, maxDescriptionLength)}...` : ticket.description;
25166
- const categoryConfig = {
25167
- general: { emoji: "\u{1F4AC}", color: "#36a64f" },
25168
- technical: { emoji: "\u{1F527}", color: "#ff6b6b" },
25169
- feature: { emoji: "\u2728", color: "#4ecdc4" },
25170
- billing: { emoji: "\u{1F4B3}", color: "#f7b731" }
25171
- };
25172
- const priorityConfig = {
25173
- low: { emoji: "\u{1F7E2}", color: "#36a64f" },
25174
- normal: { emoji: "\u{1F7E1}", color: "#ffc107" },
25175
- high: { emoji: "\u{1F7E0}", color: "#ff8c00" },
25176
- urgent: { emoji: "\u{1F534}", color: "#dc3545" }
25177
- };
25178
- const categoryInfo = categoryConfig[ticket.category] || categoryConfig.general;
25179
- const priorityInfo = priorityConfig[ticket.priority] || priorityConfig.normal;
25180
- const messageColor = ticket.priority === "high" || ticket.priority === "urgent" ? priorityInfo.color : categoryInfo.color;
25181
- const slackMessage = {
25182
- text: `New support ticket from ${ticket.email}`,
25183
- blocks: [
25184
- {
25185
- type: "header",
25186
- text: {
25187
- type: "plain_text",
25188
- text: "\u{1F3AB} New Support Ticket Submitted",
25189
- emoji: true
25190
- }
25189
+ if (slackProxyEndpoint) {
25190
+ const response = await fetch(slackProxyEndpoint, {
25191
+ method: "POST",
25192
+ headers: {
25193
+ "Content-Type": "application/json"
25191
25194
  },
25192
- {
25193
- type: "section",
25194
- fields: [
25195
- {
25196
- type: "mrkdwn",
25197
- text: `*From:*
25195
+ body: JSON.stringify(ticket)
25196
+ });
25197
+ if (!response.ok) {
25198
+ const errorData = await response.json().catch(() => ({ message: "Unknown error" }));
25199
+ throw new Error(errorData.message || `Proxy failed with status: ${response.status}`);
25200
+ }
25201
+ console.log("Support ticket notification sent to Slack successfully via proxy");
25202
+ return;
25203
+ }
25204
+ console.warn("Slack notification skipped: No webhook or proxy endpoint configured");
25205
+ console.info("To fix this, either:");
25206
+ console.info("1. Set up a server-side proxy endpoint and configure slackProxyEndpoint in your dashboard config");
25207
+ console.info("2. For development only: use NEXT_PUBLIC_SLACK_WEBHOOK_URL (not recommended for production)");
25208
+ console.info("3. Configure the webhook URL directly in your dashboard config at runtime");
25209
+ } catch (error) {
25210
+ console.error("Failed to send Slack notification:", error);
25211
+ throw error;
25212
+ }
25213
+ }
25214
+ /**
25215
+ * Formats a support ticket into a Slack message format
25216
+ * This can be used by server-side implementations
25217
+ */
25218
+ static formatSlackMessage(ticket) {
25219
+ const timestamp = new Date(ticket.timestamp).toLocaleString("en-US", {
25220
+ dateStyle: "medium",
25221
+ timeStyle: "short",
25222
+ timeZone: "UTC"
25223
+ });
25224
+ const maxDescriptionLength = 300;
25225
+ const description = ticket.description.length > maxDescriptionLength ? `${ticket.description.substring(0, maxDescriptionLength)}...` : ticket.description;
25226
+ const categoryConfig = {
25227
+ general: { emoji: "\u{1F4AC}", color: "#36a64f" },
25228
+ technical: { emoji: "\u{1F527}", color: "#ff6b6b" },
25229
+ feature: { emoji: "\u2728", color: "#4ecdc4" },
25230
+ billing: { emoji: "\u{1F4B3}", color: "#f7b731" }
25231
+ };
25232
+ const priorityConfig = {
25233
+ low: { emoji: "\u{1F7E2}", color: "#36a64f" },
25234
+ normal: { emoji: "\u{1F7E1}", color: "#ffc107" },
25235
+ high: { emoji: "\u{1F7E0}", color: "#ff8c00" },
25236
+ urgent: { emoji: "\u{1F534}", color: "#dc3545" }
25237
+ };
25238
+ const categoryInfo = categoryConfig[ticket.category] || categoryConfig.general;
25239
+ const priorityInfo = priorityConfig[ticket.priority] || priorityConfig.normal;
25240
+ const messageColor = ticket.priority === "high" || ticket.priority === "urgent" ? priorityInfo.color : categoryInfo.color;
25241
+ return {
25242
+ text: `New support ticket from ${ticket.email}`,
25243
+ blocks: [
25244
+ {
25245
+ type: "header",
25246
+ text: {
25247
+ type: "plain_text",
25248
+ text: "\u{1F3AB} New Support Ticket Submitted",
25249
+ emoji: true
25250
+ }
25251
+ },
25252
+ {
25253
+ type: "section",
25254
+ fields: [
25255
+ {
25256
+ type: "mrkdwn",
25257
+ text: `*From:*
25198
25258
  ${ticket.email}`
25199
- },
25200
- {
25201
- type: "mrkdwn",
25202
- text: `*Category:*
25259
+ },
25260
+ {
25261
+ type: "mrkdwn",
25262
+ text: `*Category:*
25203
25263
  ${categoryInfo.emoji} ${ticket.category.charAt(0).toUpperCase() + ticket.category.slice(1)}`
25204
- },
25205
- {
25206
- type: "mrkdwn",
25207
- text: `*Priority:*
25264
+ },
25265
+ {
25266
+ type: "mrkdwn",
25267
+ text: `*Priority:*
25208
25268
  ${priorityInfo.emoji} ${ticket.priority.charAt(0).toUpperCase() + ticket.priority.slice(1)}`
25209
- },
25210
- {
25211
- type: "mrkdwn",
25212
- text: `*Subject:*
25269
+ },
25270
+ {
25271
+ type: "mrkdwn",
25272
+ text: `*Subject:*
25213
25273
  ${ticket.subject}`
25214
- },
25215
- {
25216
- type: "mrkdwn",
25217
- text: `*Submitted:*
25218
- ${timestamp} UTC`
25219
- }
25220
- ]
25221
- },
25222
- {
25223
- type: "section",
25224
- text: {
25274
+ },
25275
+ {
25225
25276
  type: "mrkdwn",
25226
- text: `*Description:*
25227
- ${description}`
25277
+ text: `*Submitted:*
25278
+ ${timestamp} UTC`
25228
25279
  }
25229
- },
25230
- {
25231
- type: "divider"
25232
- },
25233
- {
25234
- type: "context",
25235
- elements: [
25236
- {
25237
- type: "mrkdwn",
25238
- text: `Category: \`${ticket.category}\` | Priority: ${priorityInfo.emoji} \`${ticket.priority.toUpperCase()}\` | ${ticket.priority === "urgent" ? "\u26A0\uFE0F Requires immediate attention" : "Standard processing"}`
25239
- }
25240
- ]
25241
- }
25242
- ],
25243
- attachments: [
25244
- {
25245
- color: messageColor,
25246
- fields: []
25280
+ ]
25281
+ },
25282
+ {
25283
+ type: "section",
25284
+ text: {
25285
+ type: "mrkdwn",
25286
+ text: `*Description:*
25287
+ ${description}`
25247
25288
  }
25248
- ]
25249
- };
25250
- const response = await fetch(slackWebhookUrl, {
25251
- method: "POST",
25252
- headers: {
25253
- "Content-Type": "application/json"
25254
25289
  },
25255
- body: JSON.stringify(slackMessage)
25256
- });
25257
- if (!response.ok) {
25258
- const errorText = await response.text();
25259
- console.error("Slack webhook error:", errorText);
25260
- throw new Error(`Slack webhook failed with status: ${response.status}`);
25261
- }
25262
- console.log("Support ticket notification sent to Slack successfully");
25263
- } catch (error) {
25264
- console.error("Failed to send Slack notification:", error);
25265
- throw error;
25266
- }
25290
+ {
25291
+ type: "divider"
25292
+ },
25293
+ {
25294
+ type: "context",
25295
+ elements: [
25296
+ {
25297
+ type: "mrkdwn",
25298
+ text: `Category: \`${ticket.category}\` | Priority: ${priorityInfo.emoji} \`${ticket.priority.toUpperCase()}\` | ${ticket.priority === "urgent" ? "\u26A0\uFE0F Requires immediate attention" : "Standard processing"}`
25299
+ }
25300
+ ]
25301
+ }
25302
+ ],
25303
+ attachments: [
25304
+ {
25305
+ color: messageColor,
25306
+ fields: []
25307
+ }
25308
+ ]
25309
+ };
25267
25310
  }
25268
25311
  };
25269
25312
  var HelpView = ({
@@ -26706,7 +26749,7 @@ var KPIsOverviewView = ({
26706
26749
  }
26707
26750
  ),
26708
26751
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 flex justify-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
26709
- /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-3xl font-bold text-gray-800 tracking-tight", children: "Production Line KPIs" }),
26752
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-3xl font-bold text-gray-800 tracking-tight", children: "Shop-floor overview" }),
26710
26753
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-2 w-2 rounded-full bg-green-500 animate-pulse ring-2 ring-green-500/30 ring-offset-1" })
26711
26754
  ] }) })
26712
26755
  ] }) }) }),
@@ -26728,7 +26771,7 @@ var KPIsOverviewView = ({
26728
26771
  }
26729
26772
  ),
26730
26773
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 flex justify-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
26731
- /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-3xl font-bold text-gray-800 tracking-tight", children: "Production Line KPIs" }),
26774
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-3xl font-bold text-gray-800 tracking-tight", children: "Shop-floor overview" }),
26732
26775
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-2 w-2 rounded-full bg-green-500 animate-pulse ring-2 ring-green-500/30 ring-offset-1" })
26733
26776
  ] }) })
26734
26777
  ] }) }) }),
@@ -26753,7 +26796,7 @@ var KPIsOverviewView = ({
26753
26796
  }
26754
26797
  ),
26755
26798
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex-1 flex justify-center", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [
26756
- /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-3xl font-bold text-gray-800 tracking-tight", children: "Production Line KPIs" }),
26799
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-3xl font-bold text-gray-800 tracking-tight", children: "Shop-floor overview" }),
26757
26800
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-2 w-2 rounded-full bg-green-500 animate-pulse ring-2 ring-green-500/30 ring-offset-1" })
26758
26801
  ] }) })
26759
26802
  ] }),
package/dist/index.mjs CHANGED
@@ -110,8 +110,10 @@ var DEFAULT_ENDPOINTS_CONFIG = {
110
110
  whatsapp: "/api/send-whatsapp-direct",
111
111
  agnoApiUrl: process.env.NEXT_PUBLIC_AGNO_URL || "https://optifye-agent-production.up.railway.app",
112
112
  // Default AGNO API URL
113
- slackWebhookUrl: process.env.NEXT_PUBLIC_SLACK_WEBHOOK_URL || void 0
114
- // Slack webhook URL from environment
113
+ // Use environment variable for Slack webhook URL for privacy/security
114
+ // Note: SLACK_WEBHOOK_URL is server-side only, NEXT_PUBLIC_SLACK_WEBHOOK_URL works client-side but is less secure
115
+ slackWebhookUrl: process.env.SLACK_WEBHOOK_URL || process.env.NEXT_PUBLIC_SLACK_WEBHOOK_URL || void 0,
116
+ slackProxyEndpoint: void 0
115
117
  };
116
118
  var DEFAULT_THEME_CONFIG = {
117
119
  // Sensible defaults for theme can be added here
@@ -23197,13 +23199,13 @@ var SideNavBar = memo(({
23197
23199
  {
23198
23200
  onClick: handleKPIsClick,
23199
23201
  className: kpisButtonClasses,
23200
- "aria-label": "KPIs",
23202
+ "aria-label": "Lines",
23201
23203
  tabIndex: 0,
23202
23204
  role: "tab",
23203
23205
  "aria-selected": pathname === "/kpis" || pathname.startsWith("/kpis/"),
23204
23206
  children: [
23205
23207
  /* @__PURE__ */ jsx(ChartBarIcon, { className: "w-5 h-5 mb-1" }),
23206
- /* @__PURE__ */ jsx("span", { className: "text-[10px] font-medium leading-tight", children: "KPIs" })
23208
+ /* @__PURE__ */ jsx("span", { className: "text-[10px] font-medium leading-tight", children: "Lines" })
23207
23209
  ]
23208
23210
  }
23209
23211
  ),
@@ -25116,125 +25118,166 @@ var streamProxyConfig = {
25116
25118
  // src/lib/api/slackApi.ts
25117
25119
  var SlackAPI = class {
25118
25120
  /**
25119
- * Sends a support ticket notification directly to Slack webhook
25121
+ * Sends a support ticket notification to Slack.
25122
+ * 1. If slackWebhookUrl is configured, send directly (preferred)
25123
+ * 2. Otherwise, if slackProxyEndpoint is configured, send to the proxy
25124
+ * If neither is configured, logs a warning.
25120
25125
  */
25121
25126
  static async sendSupportTicketNotification(ticket) {
25122
25127
  try {
25123
25128
  const config = _getDashboardConfigInstance();
25124
25129
  const endpointsConfig = config.endpoints ?? DEFAULT_ENDPOINTS_CONFIG;
25125
- const slackWebhookUrl = endpointsConfig.slackWebhookUrl;
25126
- if (!slackWebhookUrl) {
25127
- console.log("Slack webhook URL not configured, skipping notification");
25130
+ const { slackWebhookUrl, slackProxyEndpoint } = endpointsConfig;
25131
+ console.log("SlackAPI Debug - Configuration check:", {
25132
+ hasConfig: !!config,
25133
+ hasEndpoints: !!config.endpoints,
25134
+ slackWebhookUrl: slackWebhookUrl ? "configured" : "not configured",
25135
+ slackProxyEndpoint: slackProxyEndpoint ? "configured" : "not configured",
25136
+ envVariable: process.env.SLACK_WEBHOOK_URL ? "set" : "not set",
25137
+ publicEnvVariable: process.env.NEXT_PUBLIC_SLACK_WEBHOOK_URL ? "set" : "not set"
25138
+ });
25139
+ if (process.env.NEXT_PUBLIC_SLACK_WEBHOOK_URL && !process.env.SLACK_WEBHOOK_URL) {
25140
+ console.warn("\u26A0\uFE0F SECURITY WARNING: Using NEXT_PUBLIC_SLACK_WEBHOOK_URL exposes your webhook URL to the client. Consider using a server-side proxy instead.");
25141
+ }
25142
+ if (slackWebhookUrl) {
25143
+ const slackMessage = this.formatSlackMessage(ticket);
25144
+ const response = await fetch(slackWebhookUrl, {
25145
+ method: "POST",
25146
+ headers: {
25147
+ // Use form-urlencoded + simple headers to avoid CORS preflight
25148
+ "Content-Type": "application/x-www-form-urlencoded"
25149
+ },
25150
+ body: `payload=${encodeURIComponent(JSON.stringify(slackMessage))}`
25151
+ });
25152
+ if (!response.ok) {
25153
+ const errorText = await response.text();
25154
+ console.error("Slack webhook error:", errorText);
25155
+ throw new Error(`Slack webhook failed with status: ${response.status}`);
25156
+ }
25157
+ console.log("Support ticket notification sent to Slack successfully via webhook");
25128
25158
  return;
25129
25159
  }
25130
- const timestamp = new Date(ticket.timestamp).toLocaleString("en-US", {
25131
- dateStyle: "medium",
25132
- timeStyle: "short",
25133
- timeZone: "UTC"
25134
- });
25135
- const maxDescriptionLength = 300;
25136
- const description = ticket.description.length > maxDescriptionLength ? `${ticket.description.substring(0, maxDescriptionLength)}...` : ticket.description;
25137
- const categoryConfig = {
25138
- general: { emoji: "\u{1F4AC}", color: "#36a64f" },
25139
- technical: { emoji: "\u{1F527}", color: "#ff6b6b" },
25140
- feature: { emoji: "\u2728", color: "#4ecdc4" },
25141
- billing: { emoji: "\u{1F4B3}", color: "#f7b731" }
25142
- };
25143
- const priorityConfig = {
25144
- low: { emoji: "\u{1F7E2}", color: "#36a64f" },
25145
- normal: { emoji: "\u{1F7E1}", color: "#ffc107" },
25146
- high: { emoji: "\u{1F7E0}", color: "#ff8c00" },
25147
- urgent: { emoji: "\u{1F534}", color: "#dc3545" }
25148
- };
25149
- const categoryInfo = categoryConfig[ticket.category] || categoryConfig.general;
25150
- const priorityInfo = priorityConfig[ticket.priority] || priorityConfig.normal;
25151
- const messageColor = ticket.priority === "high" || ticket.priority === "urgent" ? priorityInfo.color : categoryInfo.color;
25152
- const slackMessage = {
25153
- text: `New support ticket from ${ticket.email}`,
25154
- blocks: [
25155
- {
25156
- type: "header",
25157
- text: {
25158
- type: "plain_text",
25159
- text: "\u{1F3AB} New Support Ticket Submitted",
25160
- emoji: true
25161
- }
25160
+ if (slackProxyEndpoint) {
25161
+ const response = await fetch(slackProxyEndpoint, {
25162
+ method: "POST",
25163
+ headers: {
25164
+ "Content-Type": "application/json"
25162
25165
  },
25163
- {
25164
- type: "section",
25165
- fields: [
25166
- {
25167
- type: "mrkdwn",
25168
- text: `*From:*
25166
+ body: JSON.stringify(ticket)
25167
+ });
25168
+ if (!response.ok) {
25169
+ const errorData = await response.json().catch(() => ({ message: "Unknown error" }));
25170
+ throw new Error(errorData.message || `Proxy failed with status: ${response.status}`);
25171
+ }
25172
+ console.log("Support ticket notification sent to Slack successfully via proxy");
25173
+ return;
25174
+ }
25175
+ console.warn("Slack notification skipped: No webhook or proxy endpoint configured");
25176
+ console.info("To fix this, either:");
25177
+ console.info("1. Set up a server-side proxy endpoint and configure slackProxyEndpoint in your dashboard config");
25178
+ console.info("2. For development only: use NEXT_PUBLIC_SLACK_WEBHOOK_URL (not recommended for production)");
25179
+ console.info("3. Configure the webhook URL directly in your dashboard config at runtime");
25180
+ } catch (error) {
25181
+ console.error("Failed to send Slack notification:", error);
25182
+ throw error;
25183
+ }
25184
+ }
25185
+ /**
25186
+ * Formats a support ticket into a Slack message format
25187
+ * This can be used by server-side implementations
25188
+ */
25189
+ static formatSlackMessage(ticket) {
25190
+ const timestamp = new Date(ticket.timestamp).toLocaleString("en-US", {
25191
+ dateStyle: "medium",
25192
+ timeStyle: "short",
25193
+ timeZone: "UTC"
25194
+ });
25195
+ const maxDescriptionLength = 300;
25196
+ const description = ticket.description.length > maxDescriptionLength ? `${ticket.description.substring(0, maxDescriptionLength)}...` : ticket.description;
25197
+ const categoryConfig = {
25198
+ general: { emoji: "\u{1F4AC}", color: "#36a64f" },
25199
+ technical: { emoji: "\u{1F527}", color: "#ff6b6b" },
25200
+ feature: { emoji: "\u2728", color: "#4ecdc4" },
25201
+ billing: { emoji: "\u{1F4B3}", color: "#f7b731" }
25202
+ };
25203
+ const priorityConfig = {
25204
+ low: { emoji: "\u{1F7E2}", color: "#36a64f" },
25205
+ normal: { emoji: "\u{1F7E1}", color: "#ffc107" },
25206
+ high: { emoji: "\u{1F7E0}", color: "#ff8c00" },
25207
+ urgent: { emoji: "\u{1F534}", color: "#dc3545" }
25208
+ };
25209
+ const categoryInfo = categoryConfig[ticket.category] || categoryConfig.general;
25210
+ const priorityInfo = priorityConfig[ticket.priority] || priorityConfig.normal;
25211
+ const messageColor = ticket.priority === "high" || ticket.priority === "urgent" ? priorityInfo.color : categoryInfo.color;
25212
+ return {
25213
+ text: `New support ticket from ${ticket.email}`,
25214
+ blocks: [
25215
+ {
25216
+ type: "header",
25217
+ text: {
25218
+ type: "plain_text",
25219
+ text: "\u{1F3AB} New Support Ticket Submitted",
25220
+ emoji: true
25221
+ }
25222
+ },
25223
+ {
25224
+ type: "section",
25225
+ fields: [
25226
+ {
25227
+ type: "mrkdwn",
25228
+ text: `*From:*
25169
25229
  ${ticket.email}`
25170
- },
25171
- {
25172
- type: "mrkdwn",
25173
- text: `*Category:*
25230
+ },
25231
+ {
25232
+ type: "mrkdwn",
25233
+ text: `*Category:*
25174
25234
  ${categoryInfo.emoji} ${ticket.category.charAt(0).toUpperCase() + ticket.category.slice(1)}`
25175
- },
25176
- {
25177
- type: "mrkdwn",
25178
- text: `*Priority:*
25235
+ },
25236
+ {
25237
+ type: "mrkdwn",
25238
+ text: `*Priority:*
25179
25239
  ${priorityInfo.emoji} ${ticket.priority.charAt(0).toUpperCase() + ticket.priority.slice(1)}`
25180
- },
25181
- {
25182
- type: "mrkdwn",
25183
- text: `*Subject:*
25240
+ },
25241
+ {
25242
+ type: "mrkdwn",
25243
+ text: `*Subject:*
25184
25244
  ${ticket.subject}`
25185
- },
25186
- {
25187
- type: "mrkdwn",
25188
- text: `*Submitted:*
25189
- ${timestamp} UTC`
25190
- }
25191
- ]
25192
- },
25193
- {
25194
- type: "section",
25195
- text: {
25245
+ },
25246
+ {
25196
25247
  type: "mrkdwn",
25197
- text: `*Description:*
25198
- ${description}`
25248
+ text: `*Submitted:*
25249
+ ${timestamp} UTC`
25199
25250
  }
25200
- },
25201
- {
25202
- type: "divider"
25203
- },
25204
- {
25205
- type: "context",
25206
- elements: [
25207
- {
25208
- type: "mrkdwn",
25209
- text: `Category: \`${ticket.category}\` | Priority: ${priorityInfo.emoji} \`${ticket.priority.toUpperCase()}\` | ${ticket.priority === "urgent" ? "\u26A0\uFE0F Requires immediate attention" : "Standard processing"}`
25210
- }
25211
- ]
25212
- }
25213
- ],
25214
- attachments: [
25215
- {
25216
- color: messageColor,
25217
- fields: []
25251
+ ]
25252
+ },
25253
+ {
25254
+ type: "section",
25255
+ text: {
25256
+ type: "mrkdwn",
25257
+ text: `*Description:*
25258
+ ${description}`
25218
25259
  }
25219
- ]
25220
- };
25221
- const response = await fetch(slackWebhookUrl, {
25222
- method: "POST",
25223
- headers: {
25224
- "Content-Type": "application/json"
25225
25260
  },
25226
- body: JSON.stringify(slackMessage)
25227
- });
25228
- if (!response.ok) {
25229
- const errorText = await response.text();
25230
- console.error("Slack webhook error:", errorText);
25231
- throw new Error(`Slack webhook failed with status: ${response.status}`);
25232
- }
25233
- console.log("Support ticket notification sent to Slack successfully");
25234
- } catch (error) {
25235
- console.error("Failed to send Slack notification:", error);
25236
- throw error;
25237
- }
25261
+ {
25262
+ type: "divider"
25263
+ },
25264
+ {
25265
+ type: "context",
25266
+ elements: [
25267
+ {
25268
+ type: "mrkdwn",
25269
+ text: `Category: \`${ticket.category}\` | Priority: ${priorityInfo.emoji} \`${ticket.priority.toUpperCase()}\` | ${ticket.priority === "urgent" ? "\u26A0\uFE0F Requires immediate attention" : "Standard processing"}`
25270
+ }
25271
+ ]
25272
+ }
25273
+ ],
25274
+ attachments: [
25275
+ {
25276
+ color: messageColor,
25277
+ fields: []
25278
+ }
25279
+ ]
25280
+ };
25238
25281
  }
25239
25282
  };
25240
25283
  var HelpView = ({
@@ -26677,7 +26720,7 @@ var KPIsOverviewView = ({
26677
26720
  }
26678
26721
  ),
26679
26722
  /* @__PURE__ */ jsx("div", { className: "flex-1 flex justify-center", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
26680
- /* @__PURE__ */ jsx("h1", { className: "text-3xl font-bold text-gray-800 tracking-tight", children: "Production Line KPIs" }),
26723
+ /* @__PURE__ */ jsx("h1", { className: "text-3xl font-bold text-gray-800 tracking-tight", children: "Shop-floor overview" }),
26681
26724
  /* @__PURE__ */ jsx("div", { className: "h-2 w-2 rounded-full bg-green-500 animate-pulse ring-2 ring-green-500/30 ring-offset-1" })
26682
26725
  ] }) })
26683
26726
  ] }) }) }),
@@ -26699,7 +26742,7 @@ var KPIsOverviewView = ({
26699
26742
  }
26700
26743
  ),
26701
26744
  /* @__PURE__ */ jsx("div", { className: "flex-1 flex justify-center", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
26702
- /* @__PURE__ */ jsx("h1", { className: "text-3xl font-bold text-gray-800 tracking-tight", children: "Production Line KPIs" }),
26745
+ /* @__PURE__ */ jsx("h1", { className: "text-3xl font-bold text-gray-800 tracking-tight", children: "Shop-floor overview" }),
26703
26746
  /* @__PURE__ */ jsx("div", { className: "h-2 w-2 rounded-full bg-green-500 animate-pulse ring-2 ring-green-500/30 ring-offset-1" })
26704
26747
  ] }) })
26705
26748
  ] }) }) }),
@@ -26724,7 +26767,7 @@ var KPIsOverviewView = ({
26724
26767
  }
26725
26768
  ),
26726
26769
  /* @__PURE__ */ jsx("div", { className: "flex-1 flex justify-center", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
26727
- /* @__PURE__ */ jsx("h1", { className: "text-3xl font-bold text-gray-800 tracking-tight", children: "Production Line KPIs" }),
26770
+ /* @__PURE__ */ jsx("h1", { className: "text-3xl font-bold text-gray-800 tracking-tight", children: "Shop-floor overview" }),
26728
26771
  /* @__PURE__ */ jsx("div", { className: "h-2 w-2 rounded-full bg-green-500 animate-pulse ring-2 ring-green-500/30 ring-offset-1" })
26729
26772
  ] }) })
26730
26773
  ] }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@optifye/dashboard-core",
3
- "version": "4.2.1",
3
+ "version": "4.2.3",
4
4
  "description": "Reusable UI & logic for Optifye dashboard",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",