@namphuongtechnologi/np-hub 0.2.1 → 0.2.2
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/README.md +33 -23
- package/dist/{chunk-CIUOZK3Q.js → chunk-BO2GWI5I.js} +9 -11
- package/dist/chunk-BO2GWI5I.js.map +1 -0
- package/dist/index.cjs +8 -10
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/np-hub.min.global.js +3 -3
- package/dist/react.cjs +8 -10
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +1 -1
- package/dist/react.d.ts +1 -1
- package/dist/react.js +1 -1
- package/dist/register.cjs +8 -10
- package/dist/register.cjs.map +1 -1
- package/dist/register.js +1 -1
- package/dist/{support-widget.element-DewiQPzp.d.cts → support-widget.element-C6a1k-nt.d.cts} +8 -2
- package/dist/{support-widget.element-DewiQPzp.d.ts → support-widget.element-C6a1k-nt.d.ts} +8 -2
- package/docs/DEVELOPER_GUIDE.md +9 -8
- package/docs/USAGE_STATIC_REACT.md +13 -2
- package/package.json +1 -1
- package/dist/chunk-CIUOZK3Q.js.map +0 -1
package/README.md
CHANGED
|
@@ -121,14 +121,14 @@ export default function App() {
|
|
|
121
121
|
|
|
122
122
|
### `setConfig` / React props
|
|
123
123
|
|
|
124
|
-
| Name | Required | Default | Description
|
|
125
|
-
| --------------- | -------- | ------- |
|
|
126
|
-
| `projectId` | No | — | Project code forwarded to the API
|
|
127
|
-
| `isDev` | No | `false` | `true` → Development API; otherwise Production
|
|
128
|
-
| `priority` | No | `0` | Request priority
|
|
129
|
-
| `coordinators` | No | `[]` | Coordinator email list
|
|
130
|
-
| `emailContacts` | No | `[]` | Contact emails that receive the notification
|
|
131
|
-
| `toastDuration` | No | `4000` | Toast auto-close delay in
|
|
124
|
+
| Name | Required | Default | Description |
|
|
125
|
+
| --------------- | -------- | ------- | ---------------------------------------------------------------------------------------------------------------------- |
|
|
126
|
+
| `projectId` | No | — | Project code forwarded to the API |
|
|
127
|
+
| `isDev` | No | `false` | `true` → Development API; otherwise Production |
|
|
128
|
+
| `priority` | No | `0` | Request priority |
|
|
129
|
+
| `coordinators` | No | `[]` | Coordinator email list |
|
|
130
|
+
| `emailContacts` | No | `[]` | Contact emails that receive the notification |
|
|
131
|
+
| `toastDuration` | No | `4000` | Toast auto-close delay in ms. Can be a number or an object: `{ success?: number, error?: number }` |
|
|
132
132
|
|
|
133
133
|
### Prefill (optional)
|
|
134
134
|
|
|
@@ -192,23 +192,32 @@ widget.setAttribute("bottom", "24");
|
|
|
192
192
|
|
|
193
193
|
After a successful API submit or an API/exception failure, the widget shows a **centered toast** with the result message.
|
|
194
194
|
|
|
195
|
-
| Behavior | Detail
|
|
196
|
-
| ----------------- |
|
|
197
|
-
| When shown | Submit success, or API / runtime error
|
|
198
|
-
| When hidden | Missing required fields (only inline field highlighting)
|
|
199
|
-
| Auto-close | After `toastDuration` ms (default `4000`)
|
|
200
|
-
|
|
|
201
|
-
|
|
|
195
|
+
| Behavior | Detail |
|
|
196
|
+
| ----------------- | ----------------------------------------------------------------------------------------------------------------------- |
|
|
197
|
+
| When shown | Submit success, or API / runtime error |
|
|
198
|
+
| When hidden | Missing required fields (only inline field highlighting) |
|
|
199
|
+
| Auto-close | After `toastDuration` ms (default `4000`). Pauses countdown on cursor hover. |
|
|
200
|
+
| Copy Actions | Provides quick-copy buttons for the support request code and lookup URL directly on the success toast |
|
|
201
|
+
| Manual close | User can dismiss via the `×` button |
|
|
202
|
+
| Success follow-up | Modal closes automatically after success toast auto-closes (controlled by `toastSuccessDuration`) |
|
|
203
|
+
|
|
204
|
+
`toastDuration` can be a single number (applies to both success/error) or structured with separate values:
|
|
202
205
|
|
|
203
206
|
```js
|
|
204
207
|
widget.setConfig({
|
|
205
208
|
projectId: "NPP",
|
|
206
|
-
toastDuration:
|
|
209
|
+
toastDuration: {
|
|
210
|
+
success: 3000, // closes success toast after 3s
|
|
211
|
+
error: 8000 // keeps error toast open for 8s
|
|
212
|
+
}
|
|
207
213
|
});
|
|
208
214
|
```
|
|
209
215
|
|
|
210
216
|
```tsx
|
|
211
|
-
<SupportWidget
|
|
217
|
+
<SupportWidget
|
|
218
|
+
projectId="NPP"
|
|
219
|
+
toastDuration={{ success: 3000, error: 8000 }}
|
|
220
|
+
/>
|
|
212
221
|
```
|
|
213
222
|
|
|
214
223
|
---
|
|
@@ -228,12 +237,13 @@ React equivalent props: `onOpen`, `onClose`, `onSubmitSuccess`, `onSubmitError`.
|
|
|
228
237
|
|
|
229
238
|
### Methods (Web Component)
|
|
230
239
|
|
|
231
|
-
| Method
|
|
232
|
-
|
|
|
233
|
-
| `setConfig(config)`
|
|
234
|
-
| `setUser(user)`
|
|
235
|
-
| `setFormPrefill(data)`
|
|
236
|
-
| `open()` / `close()`
|
|
240
|
+
| Method | Description |
|
|
241
|
+
| ----------------------------- | ------------------------------------------------------------------- |
|
|
242
|
+
| `setConfig(config)` | Apply project / API settings |
|
|
243
|
+
| `setUser(user)` | Prefill requester fields |
|
|
244
|
+
| `setFormPrefill(data)` | Prefill content / attachments |
|
|
245
|
+
| `open()` / `close()` | Open or close the modal |
|
|
246
|
+
| `showDemoToast(type)` | Show a dummy toast for testing/dev (accepts `"success"` or `"error"`) |
|
|
237
247
|
|
|
238
248
|
---
|
|
239
249
|
|
|
@@ -165,7 +165,6 @@ var SupportWidgetElement = class extends HTMLElement {
|
|
|
165
165
|
this.prefilledAttachments = [];
|
|
166
166
|
this.previewUrls = [];
|
|
167
167
|
this.closeAfterSuccessTimer = null;
|
|
168
|
-
this.toastAutoCloseTimer = null;
|
|
169
168
|
this.toastCountdownInterval = null;
|
|
170
169
|
this.toastSuccessDuration = 4e3;
|
|
171
170
|
this.toastErrorDuration = 4e3;
|
|
@@ -196,7 +195,6 @@ var SupportWidgetElement = class extends HTMLElement {
|
|
|
196
195
|
disconnectedCallback() {
|
|
197
196
|
window.removeEventListener("resize", this.handleResize);
|
|
198
197
|
this.clearCloseAfterSuccessTimer();
|
|
199
|
-
this.clearToastAutoCloseTimer();
|
|
200
198
|
this.clearToastCountdown();
|
|
201
199
|
}
|
|
202
200
|
attributeChangedCallback() {
|
|
@@ -209,6 +207,10 @@ var SupportWidgetElement = class extends HTMLElement {
|
|
|
209
207
|
this.user = user;
|
|
210
208
|
this.prefillFromState();
|
|
211
209
|
}
|
|
210
|
+
/**
|
|
211
|
+
* Updates the widget configuration.
|
|
212
|
+
* Supports custom toast auto-close durations via `toastDuration`.
|
|
213
|
+
*/
|
|
212
214
|
setConfig(config) {
|
|
213
215
|
this.config = {
|
|
214
216
|
...this.config,
|
|
@@ -272,6 +274,10 @@ var SupportWidgetElement = class extends HTMLElement {
|
|
|
272
274
|
(_b = (_a = this.shadowRoot) == null ? void 0 : _a.getElementById("modal")) == null ? void 0 : _b.classList.remove("show");
|
|
273
275
|
this.dispatchEvent(new CustomEvent(WIDGET_EVENTS.CLOSE, { bubbles: true }));
|
|
274
276
|
}
|
|
277
|
+
/**
|
|
278
|
+
* Helper method to trigger a mock/demo toast popup for testing and development.
|
|
279
|
+
* @param type - Either "success" or "error"
|
|
280
|
+
*/
|
|
275
281
|
showDemoToast(type) {
|
|
276
282
|
if (type === "success") {
|
|
277
283
|
this.showToast({
|
|
@@ -780,7 +786,6 @@ var SupportWidgetElement = class extends HTMLElement {
|
|
|
780
786
|
if (!toastEl || !titleEl || !messageEl || !detailsEl || !requestCodeEl || !linkRowEl || !linkEl || !countdownBarEl) {
|
|
781
787
|
return;
|
|
782
788
|
}
|
|
783
|
-
this.clearToastAutoCloseTimer();
|
|
784
789
|
this.clearToastCountdown();
|
|
785
790
|
titleEl.textContent = payload.title;
|
|
786
791
|
messageEl.textContent = payload.message;
|
|
@@ -844,7 +849,6 @@ var SupportWidgetElement = class extends HTMLElement {
|
|
|
844
849
|
return;
|
|
845
850
|
}
|
|
846
851
|
const isSuccess = toastEl.classList.contains("success");
|
|
847
|
-
this.clearToastAutoCloseTimer();
|
|
848
852
|
this.clearToastCountdown();
|
|
849
853
|
this.toastRemainingMs = 0;
|
|
850
854
|
this.toastTotalMs = 0;
|
|
@@ -910,12 +914,6 @@ var SupportWidgetElement = class extends HTMLElement {
|
|
|
910
914
|
title: content.type === "success" ? "G\u1EEDi y\xEAu c\u1EA7u th\xE0nh c\xF4ng" : "G\u1EEDi y\xEAu c\u1EA7u th\u1EA5t b\u1EA1i"
|
|
911
915
|
};
|
|
912
916
|
}
|
|
913
|
-
clearToastAutoCloseTimer() {
|
|
914
|
-
if (this.toastAutoCloseTimer !== null) {
|
|
915
|
-
window.clearTimeout(this.toastAutoCloseTimer);
|
|
916
|
-
this.toastAutoCloseTimer = null;
|
|
917
|
-
}
|
|
918
|
-
}
|
|
919
917
|
normalizeToastDuration(value) {
|
|
920
918
|
if (!Number.isFinite(value) || value <= 0) {
|
|
921
919
|
return 4e3;
|
|
@@ -1176,4 +1174,4 @@ export {
|
|
|
1176
1174
|
SupportWidgetElement,
|
|
1177
1175
|
registerSupportWidget
|
|
1178
1176
|
};
|
|
1179
|
-
//# sourceMappingURL=chunk-
|
|
1177
|
+
//# sourceMappingURL=chunk-BO2GWI5I.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/core/config/endpoints.ts","../src/core/validation/request-validator.ts","../src/services/api/create-request-response.ts","../src/services/api/http-client.ts","../src/services/api/support-api.ts","../src/component/events/widget-events.ts","../src/component/support-widget.styles.css","../src/component/support-widget.template.html","../src/component/support-widget.element.ts","../src/register.ts"],"sourcesContent":["export const PROD_API_URL = \"https://namphuong-api.azurewebsites.net\";\nexport const DEV_API_URL = \"https://namphuong-api-dev.azurewebsites.net\";\n\nexport function resolveBaseUrl(input: { isDev?: boolean }): string {\n return input.isDev ? DEV_API_URL : PROD_API_URL;\n}\n","import type {\n SupportSubmissionInput,\n SupportUser,\n SupportWidgetConfig,\n} from \"../../types/public\";\n\nexport function validateWidgetConfig(config: SupportWidgetConfig): void {\n if (config.projectId !== undefined && config.projectId.trim().length === 0) {\n throw new Error(\"projectId cannot be empty.\");\n }\n}\n\nexport function validateUser(user: SupportUser): void {\n if (!user.name?.trim()) {\n throw new Error(\"user.name is required.\");\n }\n if (!user.email?.trim()) {\n throw new Error(\"user.email is required.\");\n }\n if (!user.phoneNumber?.trim()) {\n throw new Error(\"user.phoneNumber is required.\");\n }\n}\n\nexport function validateSubmission(input: SupportSubmissionInput): void {\n if (!input.content?.trim()) {\n throw new Error(\"Content is required.\");\n }\n}\n","export interface CreateRequestResponse {\n statusCode?: number;\n message?: string;\n totalRecord?: number;\n data?: {\n url?: string;\n requestCode?: string;\n } | null;\n}\n\nexport interface SubmitToastContent {\n type: \"success\" | \"error\";\n message: string;\n statusCode?: string;\n requestCode?: string;\n url?: string;\n}\n\nconst SUCCESS_FALLBACK_MESSAGE = \"Yêu cầu đã được tiếp nhận thành công.\";\nconst ERROR_FALLBACK_MESSAGE = \"Có lỗi xảy ra khi gửi yêu cầu.\";\n\nexport function isCreateRequestSuccess(\n result: CreateRequestResponse,\n): boolean {\n if (typeof result.statusCode === \"number\") {\n return result.statusCode >= 200 && result.statusCode < 300;\n }\n return true;\n}\n\nexport function mapCreateRequestSuccess(\n result: CreateRequestResponse,\n): SubmitToastContent {\n const statusCode =\n typeof result.statusCode === \"number\"\n ? String(result.statusCode)\n : undefined;\n const requestCode = result.data?.requestCode?.trim() ?? \"\";\n const url = result.data?.url?.trim() ?? \"\";\n\n return {\n type: \"success\",\n message: result.message?.trim() || SUCCESS_FALLBACK_MESSAGE,\n statusCode,\n requestCode: requestCode || undefined,\n url: url || undefined,\n };\n}\n\nexport function mapCreateRequestFailure(\n result: CreateRequestResponse,\n): SubmitToastContent {\n const statusCode =\n typeof result.statusCode === \"number\"\n ? String(result.statusCode)\n : undefined;\n\n return {\n type: \"error\",\n message: result.message?.trim() || ERROR_FALLBACK_MESSAGE,\n statusCode,\n };\n}\n\nexport function mapHttpClientError(error: unknown): SubmitToastContent {\n const fallbackMessage = ERROR_FALLBACK_MESSAGE;\n const rawMessage =\n error instanceof Error ? error.message : fallbackMessage;\n const matchedStatus = rawMessage.match(/Request failed \\((\\d+)\\):/);\n const httpStatus = matchedStatus ? matchedStatus[1] : \"\";\n const afterColon = rawMessage.match(/Request failed \\(\\d+\\):\\s*(.*)$/s);\n const payloadText = afterColon?.[1]?.trim() ?? \"\";\n\n let parsedMessage = \"\";\n let bodyStatus = \"\";\n if (payloadText.startsWith(\"{\")) {\n try {\n const parsed = JSON.parse(payloadText) as CreateRequestResponse;\n if (typeof parsed.statusCode === \"number\") {\n bodyStatus = String(parsed.statusCode);\n }\n if (typeof parsed.message === \"string\") {\n parsedMessage = parsed.message.trim();\n }\n } catch {\n // Ignore JSON parse error, fallback to message extraction below.\n }\n }\n\n if (!parsedMessage && payloadText) {\n parsedMessage = payloadText;\n }\n if (!parsedMessage) {\n parsedMessage = rawMessage || fallbackMessage;\n }\n\n return {\n type: \"error\",\n message: parsedMessage,\n statusCode: bodyStatus || httpStatus || undefined,\n };\n}\n","export async function postFormData<TResponse>(\n url: string,\n body: FormData,\n): Promise<TResponse> {\n const response = await fetch(url, {\n method: \"POST\",\n body,\n });\n\n if (!response.ok) {\n const message = await response.text();\n throw new Error(`Request failed (${response.status}): ${message}`);\n }\n\n return (await response.json()) as TResponse;\n}\n","import type { CreateRequestResponse } from \"./create-request-response\";\nimport { postFormData } from \"./http-client\";\n\nexport type { CreateRequestResponse };\n\nexport async function createSupportRequest(\n baseUrl: string,\n formData: FormData,\n): Promise<CreateRequestResponse> {\n const endpoint = `${baseUrl.replace(/\\/$/, \"\")}/api/supportcenter/create-request-anonymous`;\n return postFormData<CreateRequestResponse>(endpoint, formData);\n}\n\n","export const WIDGET_EVENTS = {\n OPEN: \"np-hub-open\",\n CLOSE: \"np-hub-close\",\n SUBMIT_SUCCESS: \"np-hub-submit-success\",\n SUBMIT_ERROR: \"np-hub-submit-error\",\n} as const;\n","@import url(\"https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&display=swap\");\n\n:host {\n --np-primary: #003c82;\n --np-primary-dark: #002a5c;\n --np-primary-soft: rgba(0, 60, 130, 0.08);\n --np-border: #e2e8f0;\n --np-text: #0f172a;\n --np-text-muted: #64748b;\n font-family: \"Outfit\", sans-serif;\n display: block;\n position: fixed;\n right: var(--np-hub-right, 20px);\n bottom: var(--np-hub-bottom, 20px);\n left: var(--np-hub-left, auto);\n top: var(--np-hub-top, auto);\n z-index: 10000;\n}\n\n.launcher {\n width: var(--np-hub-width, 65px);\n height: var(--np-hub-height, 65px);\n border: none;\n border-radius: 50%;\n cursor: grab;\n box-shadow: 0 8px 28px rgba(6, 6, 7, 0.2);\n background-color: #ffffff;\n background-image: var(--np-hub-icon);\n background-position: center;\n background-repeat: no-repeat;\n background-size: 75%;\n transition:\n transform 0.2s ease,\n box-shadow 0.2s ease;\n}\n\n.launcher:hover {\n transform: scale(1.05);\n box-shadow: 0 12px 32px rgba(0, 60, 130, 0.3);\n}\n\n.launcher:active {\n cursor: grabbing;\n}\n\n.modal {\n position: fixed;\n inset: 0;\n background: rgba(15, 23, 42, 0.55);\n backdrop-filter: blur(6px);\n display: none;\n align-items: center;\n justify-content: center;\n padding: 16px;\n opacity: 0;\n transition: opacity 0.25s ease-out;\n}\n\n.modal.show {\n display: flex;\n opacity: 1;\n}\n\n.dialog {\n width: min(540px, 100%);\n max-height: calc(100vh - 32px);\n overflow-y: auto;\n background: #ffffff;\n border-radius: 20px;\n box-shadow: 0 24px 60px rgba(15, 23, 42, 0.22);\n padding: 28px 28px 26px;\n box-sizing: border-box;\n position: relative;\n transform: translateY(24px) scale(0.98);\n transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);\n}\n\n.modal.show .dialog {\n transform: translateY(0) scale(1);\n}\n\n.dialog::-webkit-scrollbar {\n width: 8px;\n}\n\n.dialog::-webkit-scrollbar-thumb {\n background: #cbd5e1;\n border-radius: 8px;\n}\n\n.header {\n display: flex;\n align-items: center;\n justify-content: center;\n padding-bottom: 12px;\n margin-bottom: 16px;\n border-bottom: 1px solid var(--np-border);\n position: relative;\n}\n\n.title {\n margin: 0;\n font-size: 20px;\n font-weight: 700;\n color: var(--np-primary);\n text-align: center;\n letter-spacing: -0.01em;\n}\n\n.close-btn {\n border: none;\n background: #f1f5f9;\n color: #64748b;\n font-size: 20px;\n line-height: 1;\n cursor: pointer;\n position: absolute;\n right: 0;\n top: 0;\n bottom: 18px;\n margin: auto 0;\n width: 32px;\n height: 32px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: 50%;\n transition:\n background 0.15s ease,\n color 0.15s ease,\n transform 0.15s ease;\n}\n\n.close-btn:hover {\n background: #e2e8f0;\n color: #0f172a;\n transform: rotate(90deg);\n}\n\n.form-grid {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 20px;\n}\n\n.field {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.field-error {\n display: none;\n}\n\n.field.full {\n grid-column: 1 / -1;\n}\n\nlabel {\n font-size: 13px;\n font-weight: 600;\n color: #334155;\n}\n\n.required {\n color: #ef4444;\n margin-right: 3px;\n}\n\ninput,\ntextarea {\n width: 100%;\n box-sizing: border-box;\n border: 1px solid #d5dde7;\n border-radius: 10px;\n font-size: 14px;\n padding: 11px 13px;\n color: var(--np-text);\n outline: none;\n font-family: inherit;\n background: #fbfcfe;\n transition:\n border-color 0.15s ease,\n box-shadow 0.15s ease,\n background 0.15s ease;\n}\n\ntextarea {\n min-height: 100px;\n resize: vertical;\n line-height: 1.5;\n}\n\ninput::placeholder,\ntextarea::placeholder {\n color: #94a3b8;\n}\n\ninput:hover,\ntextarea:hover {\n border-color: #b6c2d1;\n}\n\ninput:focus,\ntextarea:focus {\n border-color: var(--np-primary);\n background: #ffffff;\n box-shadow: 0 0 0 4px var(--np-primary-soft);\n}\n\n.input-wrap.has-error input,\n.input-wrap.has-error textarea {\n border-color: #e45b5b;\n background: #ffffff;\n}\n\n.input-wrap.has-error input:focus,\n.input-wrap.has-error textarea:focus {\n border-color: #e45b5b;\n box-shadow: 0 0 0 3px rgba(228, 91, 91, 0.14);\n}\n\n.input-wrap {\n position: relative;\n}\n\n.input-wrap input,\n.input-wrap textarea {\n padding-right: 40px;\n}\n\n.btn-clear {\n position: absolute;\n right: 10px;\n top: 50%;\n transform: translateY(-50%);\n width: 24px;\n height: 24px;\n border: none;\n border-radius: 50%;\n background: #e2e8f0;\n color: #64748b;\n font-size: 16px;\n line-height: 1;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 0;\n opacity: 0;\n pointer-events: none;\n transition:\n opacity 0.15s ease,\n background 0.15s ease,\n color 0.15s ease;\n}\n\n.input-wrap.has-value:hover .btn-clear,\n.input-wrap.has-value:focus-within .btn-clear {\n opacity: 1;\n pointer-events: auto;\n}\n\n.btn-clear:hover {\n background: #cbd5e1;\n color: #0f172a;\n}\n\n.input-wrap--textarea .btn-clear {\n top: 12px;\n transform: none;\n}\n\n/* File upload styling */\n.upload-zone {\n display: flex;\n align-items: center;\n gap: 16px;\n background: #f8fafc;\n border: 1.5px dashed #cbd5e1;\n padding: 16px 16px;\n border-radius: 12px;\n cursor: pointer;\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n user-select: none;\n outline: none;\n}\n\n.upload-zone:hover {\n background: var(--np-primary-soft);\n border-color: var(--np-primary);\n}\n\n.upload-zone:focus-visible {\n border-color: var(--np-primary);\n box-shadow: 0 0 0 4px var(--np-primary-soft);\n}\n\n.upload-zone.dragover {\n background: rgba(0, 60, 130, 0.08);\n border-color: var(--np-primary);\n transform: scale(1.005);\n}\n\n.upload-icon-wrapper {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n border-radius: 10px;\n background: #ffffff;\n border: 1px solid #e2e8f0;\n color: #64748b;\n box-shadow: 0 2px 4px rgba(15, 23, 42, 0.04);\n transition: all 0.2s ease;\n flex-shrink: 0;\n}\n\n.upload-zone:hover .upload-icon-wrapper,\n.upload-zone.dragover .upload-icon-wrapper {\n color: var(--np-primary);\n border-color: rgba(0, 60, 130, 0.15);\n box-shadow: 0 4px 8px rgba(0, 60, 130, 0.08);\n}\n\n.upload-text-container {\n display: flex;\n flex-direction: column;\n gap: 3px;\n flex: 1;\n}\n\n.upload-main-text {\n font-size: 13px;\n font-weight: 600;\n color: #334155;\n transition: color 0.2s ease;\n}\n\n.upload-zone:hover .upload-main-text {\n color: var(--np-primary);\n}\n\n.upload-sub-text {\n font-size: 11px;\n color: #64748b;\n line-height: 1.4;\n}\n\n.file-list {\n margin-top: 8px;\n display: grid;\n grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));\n gap: 12px;\n}\n\n.file-item {\n position: relative;\n display: flex;\n flex-direction: column;\n background: #ffffff;\n border: 1px solid #e2e8f0;\n border-radius: 12px;\n font-size: 11px;\n color: #475569;\n box-shadow: 0 2px 5px rgba(15, 23, 42, 0.03);\n animation: file-in 0.2s cubic-bezier(0.16, 1, 0.3, 1);\n transition: all 0.2s ease;\n overflow: hidden;\n}\n\n.file-item:hover {\n border-color: #cbd5e1;\n box-shadow: 0 4px 12px rgba(15, 23, 42, 0.08);\n transform: translateY(-2px);\n}\n\n.file-preview-trigger {\n border: none;\n background: none;\n padding: 0;\n margin: 0;\n cursor: pointer;\n display: flex;\n flex-direction: column;\n width: 100%;\n color: inherit;\n font: inherit;\n}\n\n.file-thumb {\n width: 100%;\n aspect-ratio: 1 / 1;\n display: flex;\n align-items: center;\n justify-content: center;\n background: #f8fafc;\n border-bottom: 1px solid #f1f5f9;\n position: relative;\n overflow: hidden;\n transition: background 0.2s ease;\n}\n\n.file-thumb img {\n width: 100%;\n height: 100%;\n object-fit: cover;\n display: block;\n transition: transform 0.25s ease;\n}\n\n.file-item:hover .file-thumb img {\n transform: scale(1.06);\n}\n\n.file-thumb--icon {\n color: #64748b;\n background: #f1f5f9;\n}\n\n.file-item:hover .file-thumb--icon {\n background: #e2e8f0;\n color: var(--np-primary);\n}\n\n@keyframes file-in {\n from {\n opacity: 0;\n transform: translateY(6px);\n }\n to {\n opacity: 1;\n transform: translateY(0);\n }\n}\n\n.file-info-container {\n padding: 8px 6px;\n display: flex;\n align-items: center;\n justify-content: center;\n width: 100%;\n box-sizing: border-box;\n}\n\n.file-item .file-name {\n width: 100%;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n line-height: 1.2;\n color: #334155;\n font-size: 11px;\n font-weight: 500;\n text-align: center;\n}\n\n.file-badge {\n position: absolute;\n top: 6px;\n left: 6px;\n z-index: 2;\n padding: 3px 6px;\n border-radius: 6px;\n font-size: 9px;\n font-weight: 600;\n line-height: 1;\n color: #0369a1;\n background: rgba(224, 242, 254, 0.9);\n border: 1px solid rgba(186, 230, 253, 0.8);\n backdrop-filter: blur(4px);\n box-shadow: 0 1px 2px rgba(15, 23, 42, 0.05);\n}\n\n.file-item .btn-remove {\n position: absolute;\n top: 6px;\n right: 6px;\n z-index: 2;\n width: 20px;\n height: 20px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: rgba(255, 255, 255, 0.9);\n border: 1px solid #e2e8f0;\n border-radius: 50%;\n color: #64748b;\n cursor: pointer;\n padding: 0;\n box-shadow: 0 2px 4px rgba(15, 23, 42, 0.06);\n transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n backdrop-filter: blur(4px);\n}\n\n.file-item .btn-remove svg {\n width: 8px;\n height: 8px;\n stroke-width: 3px;\n transition: transform 0.2s ease;\n}\n\n.file-item .btn-remove:hover {\n color: #ffffff;\n background: #ef4444;\n border-color: #ef4444;\n transform: scale(1.1);\n box-shadow: 0 3px 8px rgba(239, 68, 68, 0.25);\n}\n\n.file-item .btn-remove:hover svg {\n transform: rotate(90deg);\n}\n\n/* File preview popup */\n.preview-overlay {\n position: fixed;\n inset: 0;\n z-index: 20;\n display: none;\n align-items: center;\n justify-content: center;\n padding: 24px;\n background: rgba(15, 23, 42, 0.72);\n backdrop-filter: blur(4px);\n opacity: 0;\n transition: opacity 0.2s ease-out;\n}\n\n.preview-overlay.show {\n display: flex;\n opacity: 1;\n}\n\n.preview-box {\n position: relative;\n max-width: min(720px, 100%);\n max-height: calc(100vh - 96px);\n background: #ffffff;\n border-radius: 16px;\n padding: 16px;\n box-shadow: 0 24px 60px rgba(15, 23, 42, 0.35);\n display: flex;\n flex-direction: column;\n}\n\n.preview-close {\n position: absolute;\n top: -14px;\n right: -14px;\n width: 34px;\n height: 34px;\n border-radius: 50%;\n border: none;\n background: #ffffff;\n color: #334155;\n font-size: 22px;\n line-height: 1;\n cursor: pointer;\n box-shadow: 0 4px 12px rgba(15, 23, 42, 0.25);\n transition:\n color 0.15s ease,\n transform 0.15s ease;\n}\n\n.preview-close:hover {\n color: #ef4444;\n transform: scale(1.05);\n}\n\n.preview-body {\n overflow: auto;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n.preview-body img {\n max-width: 100%;\n max-height: calc(100vh - 140px);\n object-fit: contain;\n border-radius: 8px;\n display: block;\n}\n\n.preview-file {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 12px;\n padding: 32px 40px;\n text-align: center;\n color: var(--np-text);\n}\n\n.preview-file svg {\n color: var(--np-primary);\n}\n\n.preview-file .preview-file-name {\n font-size: 14px;\n font-weight: 600;\n word-break: break-all;\n}\n\n.preview-file a {\n color: var(--np-primary);\n font-size: 13px;\n font-weight: 600;\n text-decoration: none;\n}\n\n.preview-file a:hover {\n text-decoration: underline;\n}\n\n.actions {\n margin-top: 20px;\n display: flex;\n justify-content: flex-end;\n gap: 12px;\n}\n\n.toast {\n position: fixed;\n inset: 0;\n z-index: 10002;\n display: none;\n align-items: flex-start;\n justify-content: center;\n background: transparent;\n pointer-events: auto; /* disable interaction underneath */\n}\n\n.toast.show {\n display: flex;\n animation: toast-fade-in 0.35s cubic-bezier(0.16, 1, 0.3, 1) forwards;\n}\n\n.toast.success .toast-card {\n --toast-status-color: #10b981;\n --toast-status-bg: rgba(16, 185, 129, 0.12);\n --toast-badge-text: #047857;\n --toast-badge-bg: #ecfdf5;\n --toast-badge-border: #d1fae5;\n}\n\n.toast.error .toast-card {\n --toast-status-color: #ef4444;\n --toast-status-bg: rgba(239, 68, 68, 0.12);\n --toast-badge-text: #b91c1c;\n --toast-badge-bg: #fef2f2;\n --toast-badge-border: #fee2e2;\n}\n\n.toast-card {\n position: relative;\n width: min(90vw, 420px);\n display: flex;\n flex-direction: column;\n gap: 12px;\n padding: 20px 24px 22px 24px;\n border-radius: 20px; /* matched to main dialog border-radius */\n background: #ffffff;\n border: 1px solid var(--np-border);\n box-shadow: 0 24px 60px rgba(15, 23, 42, 0.22); /* matched to main dialog shadow */\n pointer-events: auto;\n overflow: hidden;\n margin-top: 24px; /* spacing from top of viewport */\n animation: toast-in 0.35s cubic-bezier(0.16, 1, 0.3, 1);\n}\n\n.toast-main {\n display: flex;\n align-items: flex-start;\n gap: 12px;\n}\n\n.toast-icon {\n width: 32px;\n height: 32px;\n border-radius: 50%;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n background: var(--toast-status-bg);\n margin-top: 2px;\n}\n\n.toast-icon svg {\n display: none;\n width: 18px;\n height: 18px;\n margin: 0;\n}\n\n.toast.success .toast-icon svg.icon-success {\n display: block;\n color: var(--toast-status-color);\n}\n\n.toast.error .toast-icon svg.icon-error {\n display: block;\n color: var(--toast-status-color);\n}\n\n.toast-body {\n display: flex;\n flex-direction: column;\n gap: 4px;\n flex: 1;\n min-width: 0;\n padding-top: 4px;\n}\n\n.toast-title {\n margin: 0;\n font-size: 16px;\n font-weight: 700;\n color: #111827; /* dark charcoal black */\n letter-spacing: -0.01em;\n line-height: 1.3;\n}\n\n.toast-message {\n margin: 0;\n font-size: 13.5px;\n line-height: 1.45;\n color: var(--np-text-muted);\n}\n\n.toast.success .toast-message {\n text-transform: uppercase;\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.05em;\n color: var(--np-text-muted);\n margin-top: 2px;\n}\n\n.toast-close-wrapper {\n position: relative;\n width: 36px;\n height: 36px;\n display: flex;\n align-items: center;\n justify-content: center;\n flex-shrink: 0;\n margin-left: auto;\n}\n\n.toast-countdown-ring {\n position: absolute;\n top: 0;\n left: 0;\n pointer-events: none;\n}\n\n.ring-track {\n stroke: #f1f5f9; /* soft gray track */\n}\n\n.ring-bar {\n stroke-linecap: round;\n transition: stroke-dashoffset 0.1s linear;\n}\n\n.toast-close-btn {\n position: relative;\n z-index: 1;\n border: none;\n background: transparent;\n color: #94a3b8; /* subtle gray close icon */\n width: 28px;\n height: 28px;\n border-radius: 50%;\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n transition:\n background 0.15s ease,\n color 0.15s ease,\n transform 0.15s ease;\n}\n\n.toast-close-btn svg {\n width: 12px;\n height: 12px;\n display: block;\n}\n\n.toast-close-btn:hover {\n background: #f1f5f9;\n color: #0f172a;\n transform: rotate(90deg);\n}\n\n.toast-details {\n margin-left: 44px;\n margin-top: 4px;\n padding: 12px 0 0 0;\n border-top: 1px solid var(--np-border); /* separator line */\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.toast-detail-row {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n}\n\n.toast-detail-label {\n font-size: 11px;\n text-transform: uppercase;\n letter-spacing: 0.05em;\n font-weight: 600;\n color: var(--np-text-muted);\n}\n\n.toast-detail-value {\n min-width: 0;\n font-size: 13px;\n line-height: 1.4;\n color: var(--np-text);\n display: flex;\n justify-content: flex-end;\n}\n\n.toast-detail-code {\n font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;\n font-weight: 600;\n color: var(--np-primary);\n background: var(--np-primary-soft);\n border: 1px solid rgba(0, 60, 130, 0.15);\n padding: 2px 8px;\n border-radius: 8px;\n font-size: 12px;\n display: inline-block;\n text-align: right;\n}\n\n.toast-detail-link {\n color: var(--np-primary);\n text-decoration: none;\n font-weight: 600;\n display: inline-block;\n max-width: 220px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n border-bottom: 1px dashed rgba(0, 60, 130, 0.3);\n transition: all 0.15s ease;\n font-size: 12.5px;\n}\n\n.toast-detail-link:hover {\n color: var(--np-primary-dark);\n border-bottom-style: solid;\n border-bottom-color: var(--np-primary-dark);\n}\n\n.toast-detail-value-wrap {\n display: flex;\n align-items: center;\n gap: 6px;\n justify-content: flex-end;\n min-width: 0;\n}\n\n.toast-copy-btn {\n background: transparent;\n border: none;\n padding: 4px;\n margin: 0;\n cursor: pointer;\n color: var(--np-text-muted);\n border-radius: 6px;\n display: flex;\n align-items: center;\n justify-content: center;\n transition: all 0.15s ease;\n}\n\n.toast-copy-btn:hover {\n background: #f1f5f9;\n color: var(--np-primary);\n}\n\n.toast-copy-btn svg {\n width: 14px;\n height: 14px;\n display: block;\n}\n\n.toast-copy-btn .icon-copied {\n color: var(--np-primary);\n}\n\n.toast-copy-btn.copied {\n background: var(--np-primary-soft);\n}\n\n.toast-countdown {\n margin-left: 44px;\n margin-top: 14px;\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.toast-countdown::before {\n content: \"\";\n flex: 1;\n height: 1px;\n background: #f1f5f9; /* soft divider line */\n}\n\n.toast-countdown-row {\n font-size: 11px;\n color: var(--np-text-muted);\n font-weight: 500;\n white-space: nowrap;\n display: inline-block;\n}\n\n.toast.success .toast-countdown-row {\n text-transform: none;\n font-size: 12px;\n}\n\n.toast.error .toast-countdown-row {\n text-transform: uppercase;\n letter-spacing: 0.05em;\n}\n\n.toast .is-hidden {\n display: none !important;\n}\n\n@keyframes toast-in {\n from {\n opacity: 0;\n transform: translateY(-20px) scale(0.96);\n }\n to {\n opacity: 1;\n transform: translateY(0) scale(1);\n }\n}\n\n@keyframes toast-fade-in {\n from {\n background: rgba(15, 23, 42, 0);\n backdrop-filter: blur(0px);\n -webkit-backdrop-filter: blur(0px);\n }\n to {\n background: rgba(15, 23, 42, 0.35);\n backdrop-filter: blur(4px);\n -webkit-backdrop-filter: blur(4px);\n }\n}\n\n.btn {\n border-radius: 10px;\n padding: 10px 22px;\n font-size: 15px;\n font-weight: 600;\n font-family: inherit;\n cursor: pointer;\n transition: all 0.18s ease;\n min-width: 120px;\n}\n\n.btn:active {\n transform: translateY(1px);\n}\n\n.btn-secondary {\n background: #ffffff;\n border: 1px solid #d5dde7;\n color: #334155;\n}\n\n.btn-secondary:hover {\n background: #f1f5f9;\n border-color: #b6c2d1;\n}\n\n.btn-primary {\n background: linear-gradient(\n 135deg,\n var(--np-primary),\n var(--np-primary-dark)\n );\n border: 1px solid var(--np-primary-dark);\n color: #ffffff;\n box-shadow: 0 6px 16px rgba(0, 60, 130, 0.28);\n}\n\n.btn-primary:hover {\n box-shadow: 0 8px 22px rgba(0, 60, 130, 0.38);\n filter: brightness(1.06);\n}\n\n@media (max-width: 480px) {\n .dialog {\n padding: 24px 20px 24px;\n border-radius: 16px;\n }\n\n .form-grid {\n grid-template-columns: 1fr;\n }\n\n .actions {\n justify-content: stretch;\n }\n\n .actions .btn {\n flex: 1;\n }\n}\n","<button\n id=\"open-btn\"\n class=\"launcher\"\n type=\"button\"\n aria-label=\"Open support form\"\n></button>\n\n<div id=\"modal\" class=\"modal\">\n <div\n class=\"dialog\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Support request form\"\n >\n <div class=\"header\">\n <h3 class=\"title\">Tạo yêu cầu hỗ trợ</h3>\n <button id=\"close-btn\" class=\"close-btn\" type=\"button\" aria-label=\"Close\">\n ×\n </button>\n </div>\n\n <div class=\"form-grid\">\n <div class=\"field\">\n <label for=\"requester\"><span class=\"required\">*</span> Họ và tên</label>\n <div class=\"input-wrap\">\n <input id=\"requester\" type=\"text\" placeholder=\"Họ và tên\" />\n <button type=\"button\" class=\"btn-clear\" aria-label=\"Xóa\">×</button>\n </div>\n <p id=\"requester-error\" class=\"field-error\" aria-live=\"polite\"></p>\n </div>\n\n <div class=\"field\">\n <label for=\"phone-number\"\n ><span class=\"required\">*</span> Số điện thoại</label\n >\n <div class=\"input-wrap\">\n <input id=\"phone-number\" type=\"text\" placeholder=\"Số điện thoại\" />\n <button type=\"button\" class=\"btn-clear\" aria-label=\"Xóa\">×</button>\n </div>\n <p id=\"phone-number-error\" class=\"field-error\" aria-live=\"polite\"></p>\n </div>\n\n <div class=\"field full\">\n <label for=\"email\"><span class=\"required\">*</span> Email</label>\n <div class=\"input-wrap\">\n <input id=\"email\" type=\"email\" placeholder=\"Email\" />\n <button type=\"button\" class=\"btn-clear\" aria-label=\"Xóa\">×</button>\n </div>\n <p id=\"email-error\" class=\"field-error\" aria-live=\"polite\"></p>\n </div>\n\n <div class=\"field full\">\n <label for=\"content\"><span class=\"required\">*</span> Nội dung</label>\n <div class=\"input-wrap input-wrap--textarea\">\n <textarea\n id=\"content\"\n placeholder=\"Anh/Chị vui lòng nhập thông tin cần hỗ trợ\"\n ></textarea>\n <button type=\"button\" class=\"btn-clear\" aria-label=\"Xóa\">×</button>\n </div>\n <p id=\"content-error\" class=\"field-error\" aria-live=\"polite\"></p>\n </div>\n\n <div class=\"field full\">\n <label>File đính kèm</label>\n <div\n id=\"upload-trigger\"\n class=\"upload-zone\"\n role=\"button\"\n tabindex=\"0\"\n aria-label=\"Tải lên tệp\"\n >\n <input id=\"file-input\" type=\"file\" multiple style=\"display: none\" />\n <div class=\"upload-icon-wrapper\">\n <svg\n class=\"upload-icon\"\n xmlns=\"http://www.w3.org/2000/svg\"\n width=\"20\"\n height=\"20\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2.2\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <path d=\"M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4\"></path>\n <polyline points=\"17 8 12 3 7 8\"></polyline>\n <line x1=\"12\" y1=\"3\" x2=\"12\" y2=\"15\"></line>\n </svg>\n </div>\n <div class=\"upload-text-container\">\n <span class=\"upload-main-text\"\n >Chọn tệp tải lên hoặc kéo thả vào đây</span\n >\n <span class=\"upload-sub-text\"\n >Hỗ trợ dán ảnh hoặc tệp (Ctrl+V / Cmd+V) để đính kèm nhanh</span\n >\n </div>\n </div>\n <div id=\"file-list\" class=\"file-list\"></div>\n </div>\n </div>\n\n <div id=\"preview-overlay\" class=\"preview-overlay\">\n <div\n class=\"preview-box\"\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Xem trước tệp\"\n >\n <button\n id=\"preview-close\"\n class=\"preview-close\"\n type=\"button\"\n aria-label=\"Đóng\"\n >\n ×\n </button>\n <div id=\"preview-body\" class=\"preview-body\"></div>\n </div>\n </div>\n\n <div class=\"actions\">\n <button id=\"cancel-btn\" class=\"btn btn-secondary\" type=\"button\">\n Hủy\n </button>\n <button id=\"submit-btn\" class=\"btn btn-primary\" type=\"button\">\n Tạo yêu cầu\n </button>\n </div>\n </div>\n</div>\n\n<div\n id=\"toast\"\n class=\"toast\"\n role=\"status\"\n aria-live=\"polite\"\n aria-atomic=\"true\"\n>\n <div class=\"toast-card\">\n <div class=\"toast-main\">\n <div class=\"toast-icon\" aria-hidden=\"true\">\n <svg\n class=\"icon-success\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"3\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <polyline points=\"20 6 9 17 4 12\"></polyline>\n </svg>\n <svg\n class=\"icon-error\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"3\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <circle cx=\"12\" cy=\"12\" r=\"10\"></circle>\n <line x1=\"12\" y1=\"8\" x2=\"12\" y2=\"12\"></line>\n <line x1=\"12\" y1=\"16\" x2=\"12.01\" y2=\"16\"></line>\n </svg>\n </div>\n <div class=\"toast-body\">\n <h4 id=\"toast-title\" class=\"toast-title\"></h4>\n <p id=\"toast-message\" class=\"toast-message\"></p>\n </div>\n <div class=\"toast-close-wrapper\">\n <svg\n class=\"toast-countdown-ring\"\n width=\"36\"\n height=\"36\"\n viewBox=\"0 0 36 36\"\n >\n <circle\n class=\"ring-track\"\n cx=\"18\"\n cy=\"18\"\n r=\"15\"\n fill=\"none\"\n stroke=\"#e2e8f0\"\n stroke-width=\"2.5\"\n ></circle>\n <circle\n id=\"toast-countdown-bar\"\n class=\"ring-bar\"\n cx=\"18\"\n cy=\"18\"\n r=\"15\"\n fill=\"none\"\n stroke=\"var(--toast-status-color)\"\n stroke-width=\"2.5\"\n stroke-dasharray=\"94.25\"\n stroke-dashoffset=\"0\"\n transform=\"rotate(-90 18 18)\"\n ></circle>\n </svg>\n <button\n id=\"toast-close-btn\"\n class=\"toast-close-btn\"\n type=\"button\"\n aria-label=\"Đóng thông báo\"\n >\n <svg\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line>\n </svg>\n </button>\n </div>\n </div>\n <div id=\"toast-details\" class=\"toast-details is-hidden\">\n <div class=\"toast-detail-row\">\n <span class=\"toast-detail-label\">Mã hỗ trợ</span>\n <div class=\"toast-detail-value-wrap\">\n <span\n id=\"toast-request-code\"\n class=\"toast-detail-value toast-detail-code\"\n ></span>\n <button\n id=\"toast-copy-code-btn\"\n class=\"toast-copy-btn\"\n type=\"button\"\n title=\"Sao chép mã hỗ trợ\"\n >\n <svg\n class=\"icon-copy\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\" ry=\"2\"></rect>\n <path\n d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\"\n ></path>\n </svg>\n <svg\n class=\"icon-copied is-hidden\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <polyline points=\"20 6 9 17 4 12\"></polyline>\n </svg>\n </button>\n </div>\n </div>\n <div id=\"toast-link-row\" class=\"toast-detail-row is-hidden\">\n <span class=\"toast-detail-label\">Tra cứu</span>\n <div class=\"toast-detail-value-wrap\">\n <a\n id=\"toast-link\"\n class=\"toast-detail-value toast-detail-link\"\n href=\"#\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n ></a>\n <button\n id=\"toast-copy-link-btn\"\n class=\"toast-copy-btn\"\n type=\"button\"\n title=\"Sao chép liên kết\"\n >\n <svg\n class=\"icon-copy\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <rect x=\"9\" y=\"9\" width=\"13\" height=\"13\" rx=\"2\" ry=\"2\"></rect>\n <path\n d=\"M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1\"\n ></path>\n </svg>\n <svg\n class=\"icon-copied is-hidden\"\n xmlns=\"http://www.w3.org/2000/svg\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n >\n <polyline points=\"20 6 9 17 4 12\"></polyline>\n </svg>\n </button>\n </div>\n </div>\n </div>\n </div>\n</div>\n","import { resolveBaseUrl } from \"../core/config/endpoints\";\nimport {\n validateSubmission,\n validateUser,\n validateWidgetConfig,\n} from \"../core/validation/request-validator\";\nimport {\n isCreateRequestSuccess,\n mapCreateRequestFailure,\n mapCreateRequestSuccess,\n mapHttpClientError,\n type SubmitToastContent,\n} from \"../services/api/create-request-response\";\nimport { createSupportRequest } from \"../services/api/support-api\";\nimport type {\n SupportSubmissionInput,\n SupportUser,\n SupportUserPrefill,\n SupportWidgetConfig,\n} from \"../types/public\";\nimport { WIDGET_EVENTS } from \"./events/widget-events\";\nimport npSupportLogo from \"./assets/np-support-logo.png\";\nimport styles from \"./support-widget.styles.css\";\nimport markup from \"./support-widget.template.html\";\n\nlet sharedTemplate: HTMLTemplateElement | null = null;\n\nfunction getTemplate(): HTMLTemplateElement {\n if (!sharedTemplate) {\n sharedTemplate = document.createElement(\"template\");\n sharedTemplate.innerHTML = `<style>${styles}</style>${markup}`;\n }\n return sharedTemplate;\n}\n\nasync function urlToFile(url: string): Promise<File | null> {\n try {\n const response = await fetch(url);\n if (!response.ok) return null;\n const blob = await response.blob();\n const name = url.substring(url.lastIndexOf(\"/\") + 1) || \"attachment\";\n return new File([blob], name, { type: blob.type });\n } catch (e) {\n console.warn(\"Failed to fetch prefilled attachment\", url, e);\n return null;\n }\n}\n\ninterface FormPrefillInput {\n content?: string;\n attachments?: string[];\n}\n\ninterface ToastPayload extends SubmitToastContent {\n title: string;\n}\n\ntype RequiredFieldId = \"requester\" | \"phone-number\" | \"email\" | \"content\";\n\nexport class SupportWidgetElement extends HTMLElement {\n static observedAttributes = [\n \"project-id\",\n \"is-dev\",\n \"width\",\n \"height\",\n \"right\",\n \"bottom\",\n \"left\",\n \"top\",\n \"toast-duration\",\n ];\n\n private config: SupportWidgetConfig = {\n projectId: undefined,\n };\n\n private user?: SupportUserPrefill;\n private dragOccurred = false;\n\n // Internal states for fields mapped behind the scenes\n private selectedFiles: File[] = [];\n private prefilledAttachments: string[] = [];\n private previewUrls: string[] = [];\n private closeAfterSuccessTimer: number | null = null;\n private toastCountdownInterval: number | null = null;\n private toastSuccessDuration = 4000;\n private toastErrorDuration = 4000;\n private toastRemainingMs = 0;\n private toastTotalMs = 0;\n private isToastPaused = false;\n public priority: number | undefined;\n public coordinators: string[] = [];\n public emailContacts: string[] = [];\n\n constructor() {\n super();\n const root = this.attachShadow({ mode: \"open\" });\n root.appendChild(getTemplate().content.cloneNode(true));\n this.bindActions();\n }\n\n connectedCallback(): void {\n this.syncConfigFromAttributes();\n this.prefillFromState();\n this.applyLauncherStyle();\n this.applyPositionStyle();\n this.restorePosition();\n window.addEventListener(\"resize\", this.handleResize);\n }\n\n disconnectedCallback(): void {\n window.removeEventListener(\"resize\", this.handleResize);\n this.clearCloseAfterSuccessTimer();\n this.clearToastCountdown();\n }\n\n attributeChangedCallback(): void {\n this.syncConfigFromAttributes();\n this.prefillFromState();\n this.applyLauncherStyle();\n this.applyPositionStyle();\n }\n\n setUser(user: SupportUserPrefill): void {\n this.user = user;\n this.prefillFromState();\n }\n\n /**\n * Updates the widget configuration.\n * Supports custom toast auto-close durations via `toastDuration`.\n */\n setConfig(config: Partial<SupportWidgetConfig>): void {\n this.config = {\n ...this.config,\n ...config,\n };\n if (config.projectId !== undefined) {\n this.setAttribute(\"project-id\", config.projectId);\n }\n if (config.isDev !== undefined) {\n if (config.isDev) {\n this.setAttribute(\"is-dev\", \"\");\n } else {\n this.removeAttribute(\"is-dev\");\n }\n }\n if (config.priority !== undefined) {\n this.priority = config.priority;\n }\n if (config.coordinators !== undefined) {\n this.coordinators = config.coordinators;\n }\n if (config.emailContacts !== undefined) {\n this.emailContacts = config.emailContacts;\n }\n if (config.toastDuration !== undefined) {\n const duration = config.toastDuration;\n if (typeof duration === \"number\") {\n const val = this.normalizeToastDuration(duration);\n this.toastSuccessDuration = val;\n this.toastErrorDuration = val;\n } else if (duration && typeof duration === \"object\") {\n if (duration.success !== undefined) {\n this.toastSuccessDuration = this.normalizeToastDuration(duration.success);\n } else {\n this.toastSuccessDuration = 4000;\n }\n if (duration.error !== undefined) {\n this.toastErrorDuration = this.normalizeToastDuration(duration.error);\n } else {\n this.toastErrorDuration = 4000;\n }\n }\n }\n }\n\n setFormPrefill(data: FormPrefillInput): void {\n this.setInputValue(\"content\", data.content ?? \"\");\n this.prefilledAttachments = data.attachments ?? [];\n this.renderFileList();\n }\n\n open(): void {\n this.shadowRoot?.getElementById(\"modal\")?.classList.add(\"show\");\n this.dispatchEvent(new CustomEvent(WIDGET_EVENTS.OPEN, { bubbles: true }));\n }\n\n close(): void {\n this.clearCloseAfterSuccessTimer();\n this.resetFormData();\n this.hideToast(false);\n this.shadowRoot?.getElementById(\"modal\")?.classList.remove(\"show\");\n this.dispatchEvent(new CustomEvent(WIDGET_EVENTS.CLOSE, { bubbles: true }));\n }\n\n /**\n * Helper method to trigger a mock/demo toast popup for testing and development.\n * @param type - Either \"success\" or \"error\"\n */\n public showDemoToast(type: \"success\" | \"error\"): void {\n if (type === \"success\") {\n this.showToast({\n type: \"success\",\n title: \"Gửi yêu cầu thành công\",\n message: \"Successful.\",\n requestCode: \"2607-230-1322\",\n url: \"https://hotro.azurewebsites.net/yeu-cau/2607-230-1322\",\n });\n } else {\n this.showToast({\n type: \"error\",\n title: \"Gửi yêu cầu thất bại\",\n message: \"Phone number is invalid\",\n });\n }\n }\n\n private syncConfigFromAttributes(): void {\n this.config = {\n ...this.config,\n projectId: this.getAttribute(\"project-id\") ?? \"\",\n isDev: this.hasAttribute(\"is-dev\"),\n };\n\n const toastDurationAttr = this.getAttribute(\"toast-duration\");\n if (toastDurationAttr !== null) {\n const trimmed = toastDurationAttr.trim();\n if (trimmed.startsWith(\"{\")) {\n try {\n const parsed = JSON.parse(trimmed);\n if (parsed && typeof parsed === \"object\") {\n if (parsed.success !== undefined) {\n this.toastSuccessDuration = this.normalizeToastDuration(Number(parsed.success));\n }\n if (parsed.error !== undefined) {\n this.toastErrorDuration = this.normalizeToastDuration(Number(parsed.error));\n }\n }\n } catch {\n // ignore parsing error\n }\n } else {\n const parsed = parseInt(trimmed, 10);\n if (!isNaN(parsed)) {\n const val = this.normalizeToastDuration(parsed);\n this.toastSuccessDuration = val;\n this.toastErrorDuration = val;\n }\n }\n }\n }\n\n private bindActions(): void {\n const root = this.shadowRoot;\n if (!root) return;\n\n const launcher = root.getElementById(\"open-btn\");\n if (launcher) {\n launcher.addEventListener(\"click\", () => {\n if (this.dragOccurred) {\n this.dragOccurred = false;\n return;\n }\n this.open();\n });\n this.setupDrag(launcher);\n }\n\n root\n .getElementById(\"close-btn\")\n ?.addEventListener(\"click\", () => this.close());\n root\n .getElementById(\"cancel-btn\")\n ?.addEventListener(\"click\", () => this.close());\n root\n .getElementById(\"submit-btn\")\n ?.addEventListener(\"click\", () => void this.submit());\n root.getElementById(\"toast-close-btn\")?.addEventListener(\"click\", () => {\n this.hideToast();\n });\n\n const toastCardEl = root.querySelector(\".toast-card\");\n if (toastCardEl) {\n toastCardEl.addEventListener(\"mouseenter\", () => {\n this.isToastPaused = true;\n });\n toastCardEl.addEventListener(\"mouseleave\", () => {\n this.isToastPaused = false;\n });\n }\n\n this.setupClearableInputs();\n\n // File selection UI handlers\n const uploadTrigger = root.getElementById(\"upload-trigger\");\n const fileInput = root.getElementById(\n \"file-input\",\n ) as HTMLInputElement | null;\n\n if (uploadTrigger && fileInput) {\n uploadTrigger.addEventListener(\"click\", () => {\n fileInput.click();\n });\n\n uploadTrigger.addEventListener(\"keydown\", (e: KeyboardEvent) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n fileInput.click();\n }\n });\n\n uploadTrigger.addEventListener(\"dragover\", (e: DragEvent) => {\n e.preventDefault();\n if (e.dataTransfer) {\n e.dataTransfer.dropEffect = \"copy\";\n }\n uploadTrigger.classList.add(\"dragover\");\n });\n\n uploadTrigger.addEventListener(\"dragleave\", () => {\n uploadTrigger.classList.remove(\"dragover\");\n });\n\n uploadTrigger.addEventListener(\"drop\", (e: DragEvent) => {\n e.preventDefault();\n uploadTrigger.classList.remove(\"dragover\");\n if (e.dataTransfer?.files && e.dataTransfer.files.length > 0) {\n this.addSelectedFiles(Array.from(e.dataTransfer.files));\n }\n });\n\n fileInput.addEventListener(\"change\", () => {\n if (fileInput.files) {\n this.addSelectedFiles(Array.from(fileInput.files));\n // Reset file input value to allow selecting the same file again\n fileInput.value = \"\";\n }\n });\n }\n\n this.setupToastCopy();\n this.setupClipboardPaste();\n this.setupPreviewControls();\n }\n\n private setupPreviewControls(): void {\n const root = this.shadowRoot;\n if (!root) return;\n\n const overlay = root.getElementById(\"preview-overlay\");\n root\n .getElementById(\"preview-close\")\n ?.addEventListener(\"click\", () => this.closePreview());\n\n overlay?.addEventListener(\"click\", (event) => {\n if (event.target === overlay) this.closePreview();\n });\n\n root.addEventListener(\"keydown\", (event) => {\n if ((event as KeyboardEvent).key === \"Escape\") this.closePreview();\n });\n }\n\n private setupToastCopy(): void {\n const root = this.shadowRoot;\n if (!root) return;\n\n const copyCodeBtn = root.getElementById(\"toast-copy-code-btn\");\n const copyLinkBtn = root.getElementById(\"toast-copy-link-btn\");\n\n const copyText = async (text: string, btn: HTMLElement | null) => {\n if (!text || !btn) return;\n\n const doSuccessFeedback = () => {\n btn.classList.add(\"copied\");\n const copyIcon = btn.querySelector(\".icon-copy\");\n const copiedIcon = btn.querySelector(\".icon-copied\");\n if (copyIcon && copiedIcon) {\n copyIcon.classList.add(\"is-hidden\");\n copiedIcon.classList.remove(\"is-hidden\");\n }\n\n window.setTimeout(() => {\n btn.classList.remove(\"copied\");\n if (copyIcon && copiedIcon) {\n copyIcon.classList.remove(\"is-hidden\");\n copiedIcon.classList.add(\"is-hidden\");\n }\n }, 1500);\n };\n\n if (navigator.clipboard && window.isSecureContext) {\n try {\n await navigator.clipboard.writeText(text);\n doSuccessFeedback();\n return;\n } catch (err) {\n console.warn(\"navigator.clipboard.writeText failed, trying fallback: \", err);\n }\n }\n\n const textarea = document.createElement(\"textarea\");\n textarea.value = text;\n textarea.style.position = \"fixed\";\n textarea.style.opacity = \"0\";\n document.body.appendChild(textarea);\n textarea.select();\n try {\n document.execCommand(\"copy\");\n doSuccessFeedback();\n } catch (err) {\n console.error(\"Fallback copy failed: \", err);\n } finally {\n document.body.removeChild(textarea);\n }\n };\n\n copyCodeBtn?.addEventListener(\"click\", () => {\n const codeEl = root.getElementById(\"toast-request-code\");\n const text = codeEl?.textContent || \"\";\n void copyText(text, copyCodeBtn);\n });\n\n copyLinkBtn?.addEventListener(\"click\", () => {\n const linkEl = root.getElementById(\"toast-link\") as HTMLAnchorElement | null;\n const text = linkEl?.href || \"\";\n void copyText(text, copyLinkBtn);\n });\n }\n\n private setupClipboardPaste(): void {\n const dialog = this.shadowRoot?.querySelector(\".dialog\");\n if (!dialog) return;\n\n dialog.addEventListener(\"paste\", (event) => {\n const clipboard = (event as ClipboardEvent).clipboardData;\n if (!clipboard) return;\n\n const files = Array.from(clipboard.items)\n .filter((item: DataTransferItem) => item.kind === \"file\")\n .map((item: DataTransferItem) => item.getAsFile())\n .filter((file): file is File => file !== null);\n\n if (files.length === 0) return;\n\n event.preventDefault();\n this.addSelectedFiles(files);\n });\n }\n\n private addSelectedFiles(files: File[]): void {\n if (files.length === 0) return;\n this.selectedFiles.push(...files);\n this.renderFileList();\n }\n\n private renderFileList(): void {\n const fileListEl = this.shadowRoot?.getElementById(\"file-list\");\n if (!fileListEl) return;\n fileListEl.innerHTML = \"\";\n\n // Release object URLs created in the previous render to avoid leaks\n this.previewUrls.forEach((url) => URL.revokeObjectURL(url));\n this.previewUrls = [];\n\n // Prefilled URLs\n this.prefilledAttachments.forEach((url, index) => {\n const name = url.substring(url.lastIndexOf(\"/\") + 1) || url;\n const isImage = this.isImageName(name);\n const previewUrl = isImage ? url : null;\n fileListEl.appendChild(\n this.buildFileItem({\n name,\n title: url,\n previewUrl,\n type: \"prefilled\",\n index,\n isImage,\n openUrl: url,\n badge: \"Đã gán\",\n }),\n );\n });\n\n // Selected local files\n this.selectedFiles.forEach((file, index) => {\n let previewUrl: string | null = null;\n const isImage = file.type.startsWith(\"image/\");\n if (isImage) {\n previewUrl = URL.createObjectURL(file);\n this.previewUrls.push(previewUrl);\n }\n fileListEl.appendChild(\n this.buildFileItem({\n name: file.name,\n title: file.name,\n previewUrl,\n type: \"selected\",\n index,\n isImage,\n openUrl: null,\n badge: null,\n }),\n );\n });\n\n // Hook remove event\n fileListEl.querySelectorAll(\".btn-remove\").forEach((btn) => {\n btn.addEventListener(\"click\", (e) => {\n const target = e.currentTarget as HTMLElement;\n const type = target.getAttribute(\"data-type\");\n const index = parseInt(target.getAttribute(\"data-index\") || \"0\", 10);\n if (type === \"prefilled\") {\n this.prefilledAttachments.splice(index, 1);\n } else {\n this.selectedFiles.splice(index, 1);\n }\n this.renderFileList();\n });\n });\n }\n\n private buildFileItem(opts: {\n name: string;\n title: string;\n previewUrl: string | null;\n type: \"prefilled\" | \"selected\";\n index: number;\n isImage: boolean;\n openUrl: string | null;\n badge: string | null;\n }): HTMLDivElement {\n const item = document.createElement(\"div\");\n item.className = \"file-item\";\n\n const trigger = document.createElement(\"button\");\n trigger.type = \"button\";\n trigger.className = \"file-preview-trigger\";\n trigger.title = \"Nhấn để xem trước\";\n trigger.addEventListener(\"click\", () => {\n this.openPreview({\n name: opts.title,\n previewUrl: opts.previewUrl,\n isImage: opts.isImage,\n openUrl: opts.openUrl,\n });\n });\n\n const thumb = document.createElement(\"div\");\n thumb.className = \"file-thumb\";\n if (opts.previewUrl) {\n const img = document.createElement(\"img\");\n img.src = opts.previewUrl;\n img.alt = opts.name;\n // Fall back to a file icon if the image fails to load\n img.addEventListener(\"error\", () => this.fillWithFileIcon(thumb));\n thumb.appendChild(img);\n } else {\n this.fillWithFileIcon(thumb);\n }\n\n const infoContainer = document.createElement(\"div\");\n infoContainer.className = \"file-info-container\";\n\n const name = document.createElement(\"span\");\n name.className = \"file-name\";\n name.title = opts.title;\n name.textContent = opts.name;\n\n infoContainer.appendChild(name);\n trigger.append(thumb, infoContainer);\n\n const removeBtn = document.createElement(\"button\");\n removeBtn.type = \"button\";\n removeBtn.className = \"btn-remove\";\n removeBtn.setAttribute(\"aria-label\", \"Xóa tệp\");\n removeBtn.setAttribute(\"data-type\", opts.type);\n removeBtn.setAttribute(\"data-index\", String(opts.index));\n removeBtn.innerHTML = `\n <svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line>\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line>\n </svg>\n `;\n\n item.append(trigger, removeBtn);\n\n if (opts.badge) {\n const badge = document.createElement(\"span\");\n badge.className = \"file-badge\";\n badge.textContent = opts.badge;\n item.appendChild(badge);\n }\n\n return item;\n }\n\n private fillWithFileIcon(thumb: HTMLElement): void {\n thumb.innerHTML = \"\";\n thumb.classList.add(\"file-thumb--icon\");\n thumb.innerHTML = `\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"22\" height=\"22\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\"></path>\n <polyline points=\"14 2 14 8 20 8\"></polyline>\n </svg>`;\n }\n\n private openPreview(opts: {\n name: string;\n previewUrl: string | null;\n isImage: boolean;\n openUrl: string | null;\n }): void {\n const overlay = this.shadowRoot?.getElementById(\"preview-overlay\");\n const body = this.shadowRoot?.getElementById(\"preview-body\");\n if (!overlay || !body) return;\n\n body.innerHTML = \"\";\n\n if (opts.isImage && opts.previewUrl) {\n const img = document.createElement(\"img\");\n img.src = opts.previewUrl;\n img.alt = opts.name;\n body.appendChild(img);\n } else {\n const info = document.createElement(\"div\");\n info.className = \"preview-file\";\n const openLink = opts.openUrl\n ? `<a href=\"${opts.openUrl}\" target=\"_blank\" rel=\"noopener noreferrer\">Mở trong tab mới</a>`\n : `<span>Không có bản xem trước cho tệp này</span>`;\n info.innerHTML = `\n <svg xmlns=\"http://www.w3.org/2000/svg\" width=\"56\" height=\"56\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"1.5\" stroke-linecap=\"round\" stroke-linejoin=\"round\">\n <path d=\"M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z\"></path>\n <polyline points=\"14 2 14 8 20 8\"></polyline>\n </svg>\n <span class=\"preview-file-name\"></span>\n ${openLink}`;\n const nameEl = info.querySelector(\".preview-file-name\");\n if (nameEl) nameEl.textContent = opts.name;\n body.appendChild(info);\n }\n\n overlay.classList.add(\"show\");\n }\n\n private closePreview(): void {\n const overlay = this.shadowRoot?.getElementById(\"preview-overlay\");\n if (!overlay) return;\n overlay.classList.remove(\"show\");\n const body = this.shadowRoot?.getElementById(\"preview-body\");\n if (body) body.innerHTML = \"\";\n }\n\n private isImageName(name: string): boolean {\n return /\\.(png|jpe?g|gif|webp|bmp|svg|avif)$/i.test(name);\n }\n\n private prefillFromState(): void {\n this.setInputValue(\"requester\", this.user?.name ?? \"\");\n this.setInputValue(\"email\", this.user?.email ?? \"\");\n this.setInputValue(\"phone-number\", this.user?.phoneNumber ?? \"\");\n }\n\n private applyLauncherStyle(): void {\n const launcher = this.shadowRoot?.getElementById(\"open-btn\");\n if (!launcher) return;\n\n const launcherStyles = [`--np-hub-icon: url(\"${npSupportLogo}\")`];\n const width = this.normalizeSize(this.getAttribute(\"width\"));\n const height = this.normalizeSize(this.getAttribute(\"height\"));\n if (width) launcherStyles.push(`--np-hub-width: ${width}`);\n if (height) launcherStyles.push(`--np-hub-height: ${height}`);\n\n launcher.setAttribute(\"style\", `${launcherStyles.join(\"; \")};`);\n }\n\n private applyPositionStyle(): void {\n // Skip when user already dragged the launcher (inline left/top set).\n if (this.style.left && this.style.top) return;\n\n const right = this.normalizeSize(this.getAttribute(\"right\"));\n const bottom = this.normalizeSize(this.getAttribute(\"bottom\"));\n const left = this.normalizeSize(this.getAttribute(\"left\"));\n const top = this.normalizeSize(this.getAttribute(\"top\"));\n\n this.setPositionVar(\"--np-hub-left\", left);\n this.setPositionVar(\"--np-hub-top\", top);\n this.setPositionVar(\"--np-hub-right\", right);\n this.setPositionVar(\"--np-hub-bottom\", bottom);\n\n // Avoid conflicting edges when only one side of a pair is given.\n if (left && !right) this.style.setProperty(\"--np-hub-right\", \"auto\");\n if (right && !left) this.style.setProperty(\"--np-hub-left\", \"auto\");\n if (top && !bottom) this.style.setProperty(\"--np-hub-bottom\", \"auto\");\n if (bottom && !top) this.style.setProperty(\"--np-hub-top\", \"auto\");\n }\n\n private setPositionVar(name: string, value: string | null): void {\n if (value) {\n this.style.setProperty(name, value);\n } else {\n this.style.removeProperty(name);\n }\n }\n\n private normalizeSize(value: string | null): string | null {\n const trimmed = value?.trim();\n if (!trimmed) return null;\n return /^\\d+(\\.\\d+)?$/.test(trimmed) ? `${trimmed}px` : trimmed;\n }\n\n private setupClearableInputs(): void {\n const root = this.shadowRoot;\n if (!root) return;\n\n root.querySelectorAll(\".input-wrap\").forEach((wrap) => {\n const input = wrap.querySelector(\"input, textarea\") as\n | HTMLInputElement\n | HTMLTextAreaElement\n | null;\n const clearBtn = wrap.querySelector(\n \".btn-clear\",\n ) as HTMLButtonElement | null;\n if (!input || !clearBtn) return;\n\n const updateVisibility = () => this.updateClearButton(input);\n\n input.addEventListener(\"input\", updateVisibility);\n input.addEventListener(\"input\", () => this.clearFieldError(input.id));\n clearBtn.addEventListener(\"click\", () => {\n input.value = \"\";\n input.focus();\n updateVisibility();\n this.clearFieldError(input.id);\n });\n\n updateVisibility();\n });\n }\n\n private updateClearButton(\n input: HTMLInputElement | HTMLTextAreaElement,\n ): void {\n const wrap = input.parentElement;\n if (wrap?.classList.contains(\"input-wrap\")) {\n wrap.classList.toggle(\"has-value\", input.value.length > 0);\n }\n }\n\n private setInputValue(id: string, value: string): void {\n const el = this.shadowRoot?.getElementById(id) as\n | HTMLInputElement\n | HTMLTextAreaElement\n | null;\n if (!el) return;\n el.value = value;\n this.updateClearButton(el);\n }\n\n private getInputValue(id: string): string {\n const el = this.shadowRoot?.getElementById(id) as\n | HTMLInputElement\n | HTMLTextAreaElement\n | null;\n return el?.value?.trim() ?? \"\";\n }\n\n private setFieldError(id: RequiredFieldId): void {\n const errorEl = this.shadowRoot?.getElementById(`${id}-error`);\n if (errorEl) {\n errorEl.textContent = \"\";\n }\n\n const input = this.shadowRoot?.getElementById(id) as\n | HTMLInputElement\n | HTMLTextAreaElement\n | null;\n const wrap = input?.parentElement;\n if (wrap?.classList.contains(\"input-wrap\")) {\n wrap.classList.add(\"has-error\");\n }\n }\n\n private clearFieldError(id: string): void {\n const errorEl = this.shadowRoot?.getElementById(`${id}-error`);\n if (errorEl) {\n errorEl.textContent = \"\";\n }\n\n const input = this.shadowRoot?.getElementById(id) as\n | HTMLInputElement\n | HTMLTextAreaElement\n | null;\n const wrap = input?.parentElement;\n if (wrap?.classList.contains(\"input-wrap\")) {\n wrap.classList.remove(\"has-error\");\n }\n }\n\n private clearAllFieldErrors(): void {\n this.clearFieldError(\"requester\");\n this.clearFieldError(\"phone-number\");\n this.clearFieldError(\"email\");\n this.clearFieldError(\"content\");\n }\n\n private showToast(\n payload: ToastPayload,\n autoCloseMs?: number,\n ): void {\n const toastEl = this.shadowRoot?.getElementById(\"toast\");\n const titleEl = this.shadowRoot?.getElementById(\"toast-title\");\n const messageEl = this.shadowRoot?.getElementById(\"toast-message\");\n const detailsEl = this.shadowRoot?.getElementById(\"toast-details\");\n const requestCodeEl = this.shadowRoot?.getElementById(\"toast-request-code\");\n const linkRowEl = this.shadowRoot?.getElementById(\"toast-link-row\");\n const linkEl = this.shadowRoot?.getElementById(\n \"toast-link\",\n ) as HTMLAnchorElement | null;\n const countdownEl = this.shadowRoot?.getElementById(\"toast-countdown\");\n const countdownSecondsEl = this.shadowRoot?.getElementById(\n \"toast-countdown-seconds\",\n );\n const countdownBarEl = this.shadowRoot?.getElementById(\n \"toast-countdown-bar\",\n );\n if (\n !toastEl ||\n !titleEl ||\n !messageEl ||\n !detailsEl ||\n !requestCodeEl ||\n !linkRowEl ||\n !linkEl ||\n !countdownBarEl\n ) {\n return;\n }\n\n this.clearToastCountdown();\n\n titleEl.textContent = payload.title;\n messageEl.textContent = payload.message;\n\n const hasRequestCode = Boolean(payload.requestCode);\n const hasUrl = Boolean(payload.url);\n const showDetails =\n payload.type === \"success\" && (hasRequestCode || hasUrl);\n\n if (showDetails) {\n requestCodeEl.textContent = payload.requestCode ?? \"—\";\n if (hasUrl && payload.url) {\n linkEl.href = payload.url;\n linkEl.textContent = payload.url;\n linkEl.title = payload.url;\n linkRowEl.classList.remove(\"is-hidden\");\n } else {\n linkEl.removeAttribute(\"href\");\n linkEl.textContent = \"\";\n linkEl.removeAttribute(\"title\");\n linkRowEl.classList.add(\"is-hidden\");\n }\n detailsEl.classList.remove(\"is-hidden\");\n } else {\n requestCodeEl.textContent = \"\";\n linkEl.removeAttribute(\"href\");\n linkEl.textContent = \"\";\n linkEl.removeAttribute(\"title\");\n linkRowEl.classList.add(\"is-hidden\");\n detailsEl.classList.add(\"is-hidden\");\n }\n\n const duration = autoCloseMs !== undefined\n ? autoCloseMs\n : (payload.type === \"success\" ? this.toastSuccessDuration : this.toastErrorDuration);\n\n this.toastRemainingMs = duration;\n this.toastTotalMs = duration;\n const toastCardEl = this.shadowRoot?.querySelector(\".toast-card\");\n this.isToastPaused = Boolean(toastCardEl?.matches(\":hover\"));\n\n toastEl.classList.remove(\"success\", \"error\");\n toastEl.classList.add(payload.type, \"show\");\n this.startToastCountdown(\n countdownEl ?? null,\n countdownSecondsEl ?? null,\n countdownBarEl,\n );\n }\n\n private hideToast(shouldCloseModal = true): void {\n const toastEl = this.shadowRoot?.getElementById(\"toast\");\n const titleEl = this.shadowRoot?.getElementById(\"toast-title\");\n const messageEl = this.shadowRoot?.getElementById(\"toast-message\");\n const detailsEl = this.shadowRoot?.getElementById(\"toast-details\");\n const requestCodeEl = this.shadowRoot?.getElementById(\"toast-request-code\");\n const linkRowEl = this.shadowRoot?.getElementById(\"toast-link-row\");\n const linkEl = this.shadowRoot?.getElementById(\n \"toast-link\",\n ) as HTMLAnchorElement | null;\n const countdownEl = this.shadowRoot?.getElementById(\"toast-countdown\");\n const countdownSecondsEl = this.shadowRoot?.getElementById(\n \"toast-countdown-seconds\",\n );\n const countdownBarEl = this.shadowRoot?.getElementById(\n \"toast-countdown-bar\",\n );\n if (\n !toastEl ||\n !titleEl ||\n !messageEl ||\n !detailsEl ||\n !requestCodeEl ||\n !linkRowEl ||\n !linkEl ||\n !countdownBarEl\n ) {\n return;\n }\n\n const isSuccess = toastEl.classList.contains(\"success\");\n\n this.clearToastCountdown();\n this.toastRemainingMs = 0;\n this.toastTotalMs = 0;\n this.isToastPaused = false;\n titleEl.textContent = \"\";\n messageEl.textContent = \"\";\n requestCodeEl.textContent = \"\";\n linkEl.removeAttribute(\"href\");\n linkEl.textContent = \"\";\n linkEl.removeAttribute(\"title\");\n linkRowEl.classList.add(\"is-hidden\");\n detailsEl.classList.add(\"is-hidden\");\n if (countdownSecondsEl) {\n countdownSecondsEl.textContent = \"\";\n }\n countdownBarEl.style.strokeDashoffset = \"0\";\n if (countdownEl) {\n countdownEl.classList.add(\"is-hidden\");\n }\n toastEl.classList.remove(\"success\", \"error\", \"show\");\n\n if (isSuccess && shouldCloseModal) {\n this.close();\n }\n }\n\n private startToastCountdown(\n countdownEl: HTMLElement | null,\n secondsEl: HTMLElement | null,\n barEl: HTMLElement,\n ): void {\n const updateCountdown = (): void => {\n const seconds = Math.max(1, Math.ceil(this.toastRemainingMs / 1000));\n if (secondsEl) {\n secondsEl.textContent = `${seconds}s`;\n }\n \n const percent = Math.max(0, Math.min(1, this.toastRemainingMs / this.toastTotalMs));\n const offset = 94.25 * (1 - percent);\n barEl.style.strokeDashoffset = `${offset}`;\n };\n\n updateCountdown();\n if (countdownEl) {\n countdownEl.classList.remove(\"is-hidden\");\n }\n\n this.toastCountdownInterval = window.setInterval(() => {\n if (this.isToastPaused) return;\n\n this.toastRemainingMs -= 100;\n if (this.toastRemainingMs <= 0) {\n this.toastRemainingMs = 0;\n barEl.style.strokeDashoffset = \"94.25\";\n if (secondsEl) {\n secondsEl.textContent = \"0s\";\n }\n this.hideToast();\n return;\n }\n updateCountdown();\n }, 100);\n }\n\n private clearToastCountdown(): void {\n if (this.toastCountdownInterval !== null) {\n window.clearInterval(this.toastCountdownInterval);\n this.toastCountdownInterval = null;\n }\n }\n\n private toToastPayload(content: SubmitToastContent): ToastPayload {\n return {\n ...content,\n title:\n content.type === \"success\"\n ? \"Gửi yêu cầu thành công\"\n : \"Gửi yêu cầu thất bại\",\n };\n }\n\n\n private normalizeToastDuration(value: number): number {\n if (!Number.isFinite(value) || value <= 0) {\n return 4000;\n }\n return Math.floor(value);\n }\n\n private clearCloseAfterSuccessTimer(): void {\n if (this.closeAfterSuccessTimer !== null) {\n window.clearTimeout(this.closeAfterSuccessTimer);\n this.closeAfterSuccessTimer = null;\n }\n }\n\n private validateRequiredFields(): boolean {\n this.clearAllFieldErrors();\n\n const requiredFields: RequiredFieldId[] = [\n \"requester\",\n \"phone-number\",\n \"email\",\n \"content\",\n ];\n\n let firstInvalidId: RequiredFieldId | null = null;\n\n requiredFields.forEach((id) => {\n if (!this.getInputValue(id)) {\n this.setFieldError(id);\n if (!firstInvalidId) {\n firstInvalidId = id;\n }\n }\n });\n\n if (firstInvalidId) {\n const firstInvalid = this.shadowRoot?.getElementById(firstInvalidId) as\n | HTMLInputElement\n | HTMLTextAreaElement\n | null;\n firstInvalid?.focus();\n return false;\n }\n\n return true;\n }\n\n private resetFormData(): void {\n this.setInputValue(\"requester\", \"\");\n this.setInputValue(\"email\", \"\");\n this.setInputValue(\"phone-number\", \"\");\n this.setInputValue(\"content\", \"\");\n this.clearAllFieldErrors();\n\n this.selectedFiles = [];\n this.prefilledAttachments = [];\n this.renderFileList();\n this.closePreview();\n }\n\n private async submit(): Promise<void> {\n this.clearCloseAfterSuccessTimer();\n this.hideToast(false);\n\n if (!this.validateRequiredFields()) {\n this.dispatchEvent(\n new CustomEvent(WIDGET_EVENTS.SUBMIT_ERROR, {\n bubbles: true,\n detail: { message: \"Please fill in all required fields.\" },\n }),\n );\n return;\n }\n\n const user: SupportUser = {\n name: this.getInputValue(\"requester\"),\n email: this.getInputValue(\"email\"),\n phoneNumber: this.getInputValue(\"phone-number\"),\n };\n\n this.user = user;\n\n try {\n validateWidgetConfig(this.config);\n validateUser(user);\n\n const content = this.getInputValue(\"content\");\n const submission: SupportSubmissionInput = {\n content,\n attachments: [], // dummy value for local validation of required content field\n };\n validateSubmission(submission);\n } catch (error) {\n const message = error instanceof Error ? error.message : \"Invalid input.\";\n this.dispatchEvent(\n new CustomEvent(WIDGET_EVENTS.SUBMIT_ERROR, {\n bubbles: true,\n detail: { message },\n }),\n );\n return;\n }\n\n const content = this.getInputValue(\"content\");\n\n // Create FormData to send request as multipart/form-data\n const formData = new FormData();\n formData.append(\"Requester\", user.name);\n formData.append(\"PhoneNumber\", user.phoneNumber);\n formData.append(\"Email\", user.email);\n formData.append(\"Content\", content);\n if (this.config.projectId?.trim()) {\n formData.append(\"ProjectId\", this.config.projectId.trim());\n }\n if (typeof this.priority === \"number\") {\n formData.append(\"Priority\", String(this.priority));\n }\n\n // Fetch prefilled attachments and convert them to Files\n for (const url of this.prefilledAttachments) {\n const file = await urlToFile(url);\n if (file) {\n formData.append(\"Attachments\", file);\n }\n }\n\n // Add newly selected files\n for (const file of this.selectedFiles) {\n formData.append(\"Attachments\", file);\n }\n\n // Add coordinators and emailContacts\n if (this.coordinators.length > 0) {\n this.coordinators.forEach((coord) =>\n formData.append(\"Coordinators\", coord),\n );\n }\n if (this.emailContacts.length > 0) {\n this.emailContacts.forEach((contact) =>\n formData.append(\"EmailContacts\", contact),\n );\n }\n\n try {\n const baseUrl = resolveBaseUrl(this.config);\n const result = await createSupportRequest(baseUrl, formData);\n\n if (!isCreateRequestSuccess(result)) {\n const failureContent = mapCreateRequestFailure(result);\n this.showToast(this.toToastPayload(failureContent));\n this.dispatchEvent(\n new CustomEvent(WIDGET_EVENTS.SUBMIT_ERROR, {\n bubbles: true,\n detail: { message: failureContent.message, response: result },\n }),\n );\n return;\n }\n\n const successContent = mapCreateRequestSuccess(result);\n this.showToast(this.toToastPayload(successContent));\n this.dispatchEvent(\n new CustomEvent(WIDGET_EVENTS.SUBMIT_SUCCESS, {\n bubbles: true,\n detail: result,\n }),\n );\n this.closeAfterSuccessTimer = window.setTimeout(() => {\n this.hideToast();\n }, this.toastSuccessDuration);\n } catch (error) {\n const errorContent = mapHttpClientError(error);\n this.showToast(this.toToastPayload(errorContent));\n this.dispatchEvent(\n new CustomEvent(WIDGET_EVENTS.SUBMIT_ERROR, {\n bubbles: true,\n detail: { message: errorContent.message },\n }),\n );\n }\n }\n\n private setupDrag(launcher: HTMLElement): void {\n launcher.addEventListener(\"pointerdown\", (e: PointerEvent) => {\n if (e.button !== 0) return;\n\n this.dragOccurred = false;\n const rect = this.getBoundingClientRect();\n const startX = e.clientX;\n const startY = e.clientY;\n const initialLeft = rect.left;\n const initialTop = rect.top;\n\n try {\n launcher.setPointerCapture(e.pointerId);\n } catch {\n // ignore if pointer capture fails\n }\n\n const onPointerMove = (moveEvent: PointerEvent) => {\n const dx = moveEvent.clientX - startX;\n const dy = moveEvent.clientY - startY;\n\n if (!this.dragOccurred && (Math.abs(dx) > 4 || Math.abs(dy) > 4)) {\n this.dragOccurred = true;\n launcher.style.cursor = \"grabbing\";\n }\n\n if (this.dragOccurred) {\n const leftVal = initialLeft + dx;\n const topVal = initialTop + dy;\n this.adjustPosition(leftVal, topVal);\n }\n };\n\n const onPointerUp = (upEvent: PointerEvent) => {\n try {\n launcher.releasePointerCapture(upEvent.pointerId);\n } catch {\n // ignore\n }\n launcher.removeEventListener(\"pointermove\", onPointerMove);\n launcher.removeEventListener(\"pointerup\", onPointerUp);\n launcher.removeEventListener(\"pointercancel\", onPointerUp);\n launcher.style.cursor = \"\";\n\n if (this.dragOccurred) {\n localStorage.setItem(\n \"np-hub-widget-position\",\n JSON.stringify({\n left: this.style.left,\n top: this.style.top,\n }),\n );\n }\n };\n\n launcher.addEventListener(\"pointermove\", onPointerMove);\n launcher.addEventListener(\"pointerup\", onPointerUp);\n launcher.addEventListener(\"pointercancel\", onPointerUp);\n });\n }\n\n private adjustPosition(leftVal: number, topVal: number): void {\n const rect = this.getBoundingClientRect();\n const width = rect.width || 72;\n const height = rect.height || 72;\n\n const minLeft = 0;\n const maxLeft = window.innerWidth - width;\n const minTop = 0;\n const maxTop = window.innerHeight - height;\n\n const clampedLeft = Math.max(minLeft, Math.min(maxLeft, leftVal));\n const clampedTop = Math.max(minTop, Math.min(maxTop, topVal));\n\n this.style.right = \"auto\";\n this.style.bottom = \"auto\";\n this.style.left = `${clampedLeft}px`;\n this.style.top = `${clampedTop}px`;\n }\n\n private restorePosition(): void {\n const savedPosition = localStorage.getItem(\"np-hub-widget-position\");\n if (savedPosition) {\n try {\n const { left, top } = JSON.parse(savedPosition);\n if (left && top) {\n const leftVal = parseFloat(left);\n const topVal = parseFloat(top);\n this.adjustPosition(leftVal, topVal);\n }\n } catch {\n // Ignore parsing errors\n }\n }\n }\n\n private handleResize = (): void => {\n if (this.style.left && this.style.top) {\n const leftVal = parseFloat(this.style.left);\n const topVal = parseFloat(this.style.top);\n this.adjustPosition(leftVal, topVal);\n }\n };\n}\n","import { SupportWidgetElement } from \"./component/support-widget.element\";\n\nconst TAG_NAME = \"np-hub\";\n\nexport function registerSupportWidget(): void {\n if (!customElements.get(TAG_NAME)) {\n customElements.define(TAG_NAME, SupportWidgetElement);\n }\n}\n\nregisterSupportWidget();\n"],"mappings":"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","names":["_a","content","e"]}
|
package/dist/index.cjs
CHANGED
|
@@ -193,7 +193,6 @@ var SupportWidgetElement = class extends HTMLElement {
|
|
|
193
193
|
this.prefilledAttachments = [];
|
|
194
194
|
this.previewUrls = [];
|
|
195
195
|
this.closeAfterSuccessTimer = null;
|
|
196
|
-
this.toastAutoCloseTimer = null;
|
|
197
196
|
this.toastCountdownInterval = null;
|
|
198
197
|
this.toastSuccessDuration = 4e3;
|
|
199
198
|
this.toastErrorDuration = 4e3;
|
|
@@ -224,7 +223,6 @@ var SupportWidgetElement = class extends HTMLElement {
|
|
|
224
223
|
disconnectedCallback() {
|
|
225
224
|
window.removeEventListener("resize", this.handleResize);
|
|
226
225
|
this.clearCloseAfterSuccessTimer();
|
|
227
|
-
this.clearToastAutoCloseTimer();
|
|
228
226
|
this.clearToastCountdown();
|
|
229
227
|
}
|
|
230
228
|
attributeChangedCallback() {
|
|
@@ -237,6 +235,10 @@ var SupportWidgetElement = class extends HTMLElement {
|
|
|
237
235
|
this.user = user;
|
|
238
236
|
this.prefillFromState();
|
|
239
237
|
}
|
|
238
|
+
/**
|
|
239
|
+
* Updates the widget configuration.
|
|
240
|
+
* Supports custom toast auto-close durations via `toastDuration`.
|
|
241
|
+
*/
|
|
240
242
|
setConfig(config) {
|
|
241
243
|
this.config = {
|
|
242
244
|
...this.config,
|
|
@@ -300,6 +302,10 @@ var SupportWidgetElement = class extends HTMLElement {
|
|
|
300
302
|
(_b = (_a = this.shadowRoot) == null ? void 0 : _a.getElementById("modal")) == null ? void 0 : _b.classList.remove("show");
|
|
301
303
|
this.dispatchEvent(new CustomEvent(WIDGET_EVENTS.CLOSE, { bubbles: true }));
|
|
302
304
|
}
|
|
305
|
+
/**
|
|
306
|
+
* Helper method to trigger a mock/demo toast popup for testing and development.
|
|
307
|
+
* @param type - Either "success" or "error"
|
|
308
|
+
*/
|
|
303
309
|
showDemoToast(type) {
|
|
304
310
|
if (type === "success") {
|
|
305
311
|
this.showToast({
|
|
@@ -808,7 +814,6 @@ var SupportWidgetElement = class extends HTMLElement {
|
|
|
808
814
|
if (!toastEl || !titleEl || !messageEl || !detailsEl || !requestCodeEl || !linkRowEl || !linkEl || !countdownBarEl) {
|
|
809
815
|
return;
|
|
810
816
|
}
|
|
811
|
-
this.clearToastAutoCloseTimer();
|
|
812
817
|
this.clearToastCountdown();
|
|
813
818
|
titleEl.textContent = payload.title;
|
|
814
819
|
messageEl.textContent = payload.message;
|
|
@@ -872,7 +877,6 @@ var SupportWidgetElement = class extends HTMLElement {
|
|
|
872
877
|
return;
|
|
873
878
|
}
|
|
874
879
|
const isSuccess = toastEl.classList.contains("success");
|
|
875
|
-
this.clearToastAutoCloseTimer();
|
|
876
880
|
this.clearToastCountdown();
|
|
877
881
|
this.toastRemainingMs = 0;
|
|
878
882
|
this.toastTotalMs = 0;
|
|
@@ -938,12 +942,6 @@ var SupportWidgetElement = class extends HTMLElement {
|
|
|
938
942
|
title: content.type === "success" ? "G\u1EEDi y\xEAu c\u1EA7u th\xE0nh c\xF4ng" : "G\u1EEDi y\xEAu c\u1EA7u th\u1EA5t b\u1EA1i"
|
|
939
943
|
};
|
|
940
944
|
}
|
|
941
|
-
clearToastAutoCloseTimer() {
|
|
942
|
-
if (this.toastAutoCloseTimer !== null) {
|
|
943
|
-
window.clearTimeout(this.toastAutoCloseTimer);
|
|
944
|
-
this.toastAutoCloseTimer = null;
|
|
945
|
-
}
|
|
946
|
-
}
|
|
947
945
|
normalizeToastDuration(value) {
|
|
948
946
|
if (!Number.isFinite(value) || value <= 0) {
|
|
949
947
|
return 4e3;
|