Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" }); const require_trigger_css = require("./trigger.css-CuTndetp.cjs"); let react = require("react"); let _tanstack_react_query = require("@tanstack/react-query"); let react_jsx_runtime = require("react/jsx-runtime"); let react_dom = require("react-dom"); let _reqdesk_sdk_react = require("@reqdesk/sdk-react"); //#region src/auth/widget-auth.ts const STORAGE_KEY = "reqdesk-widget-auth"; const EXPIRY_MARGIN_S = 30; const POPUP_FEATURES = "width=520,height=680,menubar=no,toolbar=no,location=no,status=no"; const POPUP_NAME = "reqdesk-widget-login"; let config = null; let listeners = []; let initPromise = null; let refreshTimer = null; let tokens = readStoredSync(); let authState = tokens && Date.now() < tokens.expiresAt - EXPIRY_MARGIN_S * 1e3 ? { isAuthenticated: true, isLoading: false, userEmail: tokens.userEmail, userName: tokens.userName } : { isAuthenticated: false, isLoading: false }; function readStoredSync() { try { if (typeof localStorage === "undefined") return null; const raw = localStorage.getItem(STORAGE_KEY); if (!raw) return null; const parsed = JSON.parse(raw); if (!parsed.accessToken || !parsed.expiresAt) return null; return parsed; } catch { return null; } } function notify() { for (const listener of listeners) try { listener(authState); } catch {} } function setState(update) { authState = { ...authState, ...update }; notify(); } function getAuthState() { return authState; } function onAuthStateChange(listener) { listeners.push(listener); return () => { listeners = listeners.filter((l) => l !== listener); }; } function isAuthConfigured() { return config !== null; } function base64UrlEncode(buffer) { const bytes = new Uint8Array(buffer); let binary = ""; for (const byte of bytes) binary += String.fromCharCode(byte); return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=/g, ""); } function randomBase64Url(bytes = 32) { const arr = new Uint8Array(bytes); crypto.getRandomValues(arr); return base64UrlEncode(arr.buffer); } async function sha256(input) { const data = new TextEncoder().encode(input); return base64UrlEncode(await crypto.subtle.digest("SHA-256", data)); } function decodeJwtPayload(token) { try { const parts = token.split("."); if (parts.length < 2) return null; const payload = parts[1] + "===".slice((parts[1].length + 3) % 4); return JSON.parse(atob(payload.replace(/-/g, "+").replace(/_/g, "/"))); } catch { return null; } } function saveStored(t) { try { if (t) { localStorage.setItem(STORAGE_KEY, JSON.stringify(t)); console.debug("[reqdesk-widget] tokens saved to localStorage", { expiresAt: new Date(t.expiresAt).toISOString() }); } else { localStorage.removeItem(STORAGE_KEY); console.debug("[reqdesk-widget] tokens removed from localStorage"); } } catch (err) { console.warn("[reqdesk-widget] localStorage write failed:", err); } } function authEndpoint(cfg) { return `${cfg.issuerUri}/protocol/openid-connect/auth`; } function tokenEndpoint(cfg) { return `${cfg.issuerUri}/protocol/openid-connect/token`; } function logoutEndpoint(cfg) { return `${cfg.issuerUri}/protocol/openid-connect/logout`; } function redirectUri() { return `${window.location.origin}/reqdesk-widget-auth-callback`; } function scheduleRefresh() { if (refreshTimer) clearTimeout(refreshTimer); if (!tokens?.refreshToken) return; const msUntilRefresh = Math.max(0, tokens.expiresAt - Date.now() - EXPIRY_MARGIN_S * 1e3); refreshTimer = setTimeout(() => { refresh(); }, msUntilRefresh); } function applyTokens(t) { tokens = t; saveStored(t); setState({ isAuthenticated: true, isLoading: false, userEmail: t.userEmail, userName: t.userName }); scheduleRefresh(); } function clearTokens() { tokens = null; saveStored(null); if (refreshTimer) { clearTimeout(refreshTimer); refreshTimer = null; } setState({ isAuthenticated: false, isLoading: false, userEmail: void 0, userName: void 0 }); } function parseTokenResponse(data) { const accessToken = String(data.access_token); const refreshToken = data.refresh_token ? String(data.refresh_token) : null; const expiresIn = Number(data.expires_in ?? 300); const expiresAt = Date.now() + expiresIn * 1e3; const payload = decodeJwtPayload(accessToken) ?? {}; return { accessToken, refreshToken, expiresAt, userEmail: typeof payload.email === "string" ? payload.email : void 0, userName: typeof payload.name === "string" ? payload.name : typeof payload.preferred_username === "string" ? payload.preferred_username : void 0 }; } async function refresh() { if (!config || !tokens?.refreshToken) { clearTokens(); return; } try { const res = await fetch(tokenEndpoint(config), { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ grant_type: "refresh_token", client_id: config.clientId, refresh_token: tokens.refreshToken }) }); if (!res.ok) { const body = await res.text().catch(() => ""); throw new Error(`refresh failed: ${res.status} ${body}`); } applyTokens(parseTokenResponse(await res.json())); } catch (err) { console.warn("[reqdesk-widget] token refresh failed:", err); clearTokens(); } } async function getAccessToken() { if (!tokens) return null; if (Date.now() < tokens.expiresAt - EXPIRY_MARGIN_S * 1e3) return tokens.accessToken; if (tokens.refreshToken) { await refresh(); return tokens?.accessToken ?? null; } clearTokens(); return null; } function initWidgetAuth(authConfig) { config = authConfig; initPromise = _initWidgetAuth(); return initPromise; } async function _initWidgetAuth() { setState({ isLoading: true }); require_trigger_css.setOidcTokenProvider(async () => { return { accessToken: await getAccessToken() ?? "" }; }); const stored = readStoredSync(); console.debug("[reqdesk-widget] init: stored tokens?", stored ? "yes" : "no", stored ? { expiresAt: new Date(stored.expiresAt).toISOString(), hasRefresh: !!stored.refreshToken } : null); if (stored) { tokens = stored; if (Date.now() >= stored.expiresAt - EXPIRY_MARGIN_S * 1e3) { console.debug("[reqdesk-widget] init: tokens expired, attempting refresh"); await refresh(); } else { console.debug("[reqdesk-widget] init: tokens valid, using as-is"); setState({ isAuthenticated: true, isLoading: false, userEmail: stored.userEmail, userName: stored.userName }); scheduleRefresh(); } } else setState({ isLoading: false }); } async function login() { if (!config) { console.warn("[reqdesk-widget] login() called before initWidgetAuth()"); return; } const cfg = config; const state = randomBase64Url(16); const codeVerifier = randomBase64Url(32); const codeChallenge = await sha256(codeVerifier); const authUrl = new URL(authEndpoint(cfg)); authUrl.searchParams.set("response_type", "code"); authUrl.searchParams.set("client_id", cfg.clientId); authUrl.searchParams.set("redirect_uri", redirectUri()); authUrl.searchParams.set("scope", "openid email profile"); authUrl.searchParams.set("state", state); authUrl.searchParams.set("code_challenge", codeChallenge); authUrl.searchParams.set("code_challenge_method", "S256"); const popup = window.open(authUrl.toString(), POPUP_NAME, POPUP_FEATURES); if (!popup) { console.warn("[reqdesk-widget] login popup was blocked by the browser"); return; } const code = await new Promise((resolve) => { let settled = false; const finish = (result, err) => { if (settled) return; settled = true; clearInterval(poll); try { if (!popup.closed) popup.close(); } catch {} if (err) console.warn("[reqdesk-widget] login failed:", err); resolve(result); }; const poll = setInterval(() => { if (popup.closed) { finish(null, "popup closed"); return; } let href = null; try { href = popup.location.href; } catch { return; } if (!href || !href.startsWith(redirectUri())) return; const url = new URL(href); const returnedState = url.searchParams.get("state"); const returnedCode = url.searchParams.get("code"); const error = url.searchParams.get("error"); if (error) { finish(null, error); return; } if (returnedState !== state) { finish(null, "state mismatch"); return; } finish(returnedCode, null); }, 400); }); if (!code) return; setState({ isLoading: true }); try { const res = await fetch(tokenEndpoint(cfg), { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ grant_type: "authorization_code", client_id: cfg.clientId, code, redirect_uri: redirectUri(), code_verifier: codeVerifier }) }); if (!res.ok) throw new Error(`token exchange failed: ${res.status}`); applyTokens(parseTokenResponse(await res.json())); } catch (err) { console.warn("[reqdesk-widget] token exchange failed:", err); setState({ isLoading: false }); } } async function logout() { if (!config) { clearTokens(); return; } const cfg = config; const refreshToken = tokens?.refreshToken; clearTokens(); if (refreshToken) try { await fetch(logoutEndpoint(cfg), { method: "POST", headers: { "Content-Type": "application/x-www-form-urlencoded" }, body: new URLSearchParams({ client_id: cfg.clientId, refresh_token: refreshToken }) }); } catch {} } //#endregion //#region src/auth/identity-resolver.ts const KNOWN_MODES = [ "sso", "email", "signed" ]; const SAFE_DEFAULT = ["sso", "email"]; /** * Single source of truth that decides which identity UX the widget should show next. Pure function; * trivially unit-testable. See `sdk/widget/src/auth/identity-resolver.test.ts` for coverage of every * branch in the decision table below. * * Precedence: * 1. If the host user is already SSO-authenticated, always use SSO. * 2. effectiveAllowed = intersection(serverAllowed, override ?? serverAllowed). If the result is * empty we fall back to the safe default so the widget is never completely blocked. * 3. signed identity present and allowed → `signed`. * 4. plain email (no userHash) present and email mode allowed → `unsigned`. * 5. SSO allowed → `sso`. * 6. email allowed → `email-prompt`. * 7. otherwise → `error: no-auth-configured`. */ function resolveIdentityMode(args) { if (args.isSsoAuthenticated) return { kind: "sso" }; const sanitizedServer = sanitizeModes(args.serverAllowed); const baseServer = sanitizedServer.length > 0 ? sanitizedServer : [...SAFE_DEFAULT]; const override = normalizeOverride(args.override); const effective = override.length > 0 ? baseServer.filter((mode) => override.includes(mode)) : baseServer; const allowed = effective.length > 0 ? effective : baseServer; const email = args.customer?.email?.trim(); const name = args.customer?.name; const userHash = args.customer?.userHash; const userHashTimestamp = args.customer?.userHashTimestamp; if (email && userHash && typeof userHashTimestamp === "number" && allowed.includes("signed")) return { kind: "signed", email, name, userHash, userHashTimestamp }; if (email && allowed.includes("email")) return { kind: "unsigned", email, name }; if (allowed.includes("sso")) return { kind: "sso" }; if (allowed.includes("email")) return { kind: "email-prompt" }; return { kind: "error", reason: "no-auth-configured" }; } function sanitizeModes(modes) { if (!Array.isArray(modes)) return []; const unique = /* @__PURE__ */ new Set(); for (const mode of modes) if (KNOWN_MODES.includes(mode)) unique.add(mode); return Array.from(unique); } function normalizeOverride(override) { if (!override) return []; if (Array.isArray(override)) return sanitizeModes(override); return KNOWN_MODES.includes(override) ? [override] : []; } //#endregion //#region src/react/registry-context.tsx const RegistryContext = (0, react.createContext)(null); function RegistryProvider({ registry, children }) { const instance = registry ?? require_trigger_css.getRegistry(); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RegistryContext.Provider, { value: instance, children }); } function useRegistry() { const ctx = (0, react.useContext)(RegistryContext); if (ctx) return ctx; return require_trigger_css.getRegistry(); } function useRegistrySnapshot(selector) { const registry = useRegistry(); return (0, react.useSyncExternalStore)(registry.subscribe, () => selector(registry.getSnapshot()), () => selector(registry.getSnapshot())); } //#endregion //#region src/react/notifications.tsx const NotificationContext = (0, react.createContext)(null); let currentPush = null; let currentDismiss = null; let currentClear = null; /** * Push a notification from outside React (e.g. from a TanStack `QueryCache.onError`). Silently * no-ops when no `` is mounted — error handlers call this eagerly, the * absence of a mounted provider is not itself a failure. */ function pushNotificationExternal(n) { if (!currentPush) return null; return currentPush(n); } const DEFAULT_AUTO_DISMISS_MS = { success: 4e3, info: 6e3, warning: 8e3, error: null }; const MAX_VISIBLE = 3; function NotificationProvider({ children }) { const [notifications, setNotifications] = (0, react.useState)([]); const timeoutsRef = (0, react.useRef)(/* @__PURE__ */ new Map()); const dismiss = (0, react.useCallback)((id) => { const handle = timeoutsRef.current.get(id); if (handle) { clearTimeout(handle); timeoutsRef.current.delete(id); } setNotifications((prev) => prev.filter((n) => n.id !== id)); }, []); const push = (0, react.useCallback)((incoming) => { const id = incoming.id ?? (typeof crypto !== "undefined" && crypto.randomUUID ? crypto.randomUUID() : `rqd-notif-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`); const autoDismissMs = incoming.autoDismissMs === void 0 ? DEFAULT_AUTO_DISMISS_MS[incoming.kind] : incoming.autoDismissMs; const next = { id, kind: incoming.kind, title: incoming.title, detail: incoming.detail, autoDismissMs }; setNotifications((prev) => { const queue = [...prev.filter((n) => !(n.kind === next.kind && n.title === next.title)), next]; while (queue.length > MAX_VISIBLE) { const dropped = queue.shift(); if (dropped) { const h = timeoutsRef.current.get(dropped.id); if (h) { clearTimeout(h); timeoutsRef.current.delete(dropped.id); } } } return queue; }); if (typeof autoDismissMs === "number" && autoDismissMs > 0) { const handle = setTimeout(() => dismiss(id), autoDismissMs); timeoutsRef.current.set(id, handle); } return id; }, [dismiss]); const clear = (0, react.useCallback)(() => { for (const handle of timeoutsRef.current.values()) clearTimeout(handle); timeoutsRef.current.clear(); setNotifications([]); }, []); (0, react.useEffect)(() => { currentPush = push; currentDismiss = dismiss; currentClear = clear; return () => { if (currentPush === push) currentPush = null; if (currentDismiss === dismiss) currentDismiss = null; if (currentClear === clear) currentClear = null; }; }, [ push, dismiss, clear ]); (0, react.useEffect)(() => { const timeouts = timeoutsRef.current; return () => { for (const handle of timeouts.values()) clearTimeout(handle); timeouts.clear(); }; }, []); const value = (0, react.useMemo)(() => ({ notifications, push, dismiss, clear }), [ notifications, push, dismiss, clear ]); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NotificationContext.Provider, { value, children }); } /** * Push / dismiss / clear notifications. Usable from any component inside `` — * hosts can reuse the same stack for their own in-widget messages via `useReqdesk().` wrappers. */ function useNotify() { const ctx = (0, react.useContext)(NotificationContext); if (!ctx) return { push: () => "", dismiss: () => {}, clear: () => {} }; return { push: ctx.push, dismiss: ctx.dismiss, clear: ctx.clear }; } function useNotifications() { return (0, react.useContext)(NotificationContext)?.notifications ?? []; } /** * Render the notification queue at the top of the widget panel. Accessible: * - `aria-live="polite"` so screen readers announce new items without interrupting. * - Per-item `role="alert"` on errors (critical), `role="status"` on others. * - Keyboard-dismissible via the ✕ button. * * Place inside the widget's shadow DOM — no host-page leakage. */ function NotificationStack({ dismissLabel = "Dismiss" }) { const notifications = useNotifications(); const ctx = (0, react.useContext)(NotificationContext); if (!ctx || notifications.length === 0) return null; return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-notification-stack", "aria-live": "polite", "aria-atomic": "false", children: notifications.map((n) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: `rqd-notification rqd-notification-${n.kind}`, role: n.kind === "error" ? "alert" : "status", children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-notification-icon", "aria-hidden": "true", children: iconFor(n.kind) }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-notification-body", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { className: "rqd-notification-title", children: n.title }), n.detail && n.detail.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", { className: "rqd-notification-list", children: n.detail.map((d, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("li", { children: d }, i)) })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { type: "button", className: "rqd-notification-dismiss", onClick: () => ctx.dismiss(n.id), "aria-label": dismissLabel, children: "✕" }) ] }, n.id)) }); } function iconFor(kind) { switch (kind) { case "error": return "⚠"; case "warning": return "⚠"; case "info": return "ℹ"; case "success": return "✓"; } } //#endregion //#region src/react/ReqdeskProvider.tsx const LOCALE_TABLES = { en: require_trigger_css.en, ar: require_trigger_css.ar }; const SAFE_DEFAULT_MODES = ["sso", "email"]; const ReqdeskContext = (0, react.createContext)(null); function useReqdeskContext() { const ctx = (0, react.useContext)(ReqdeskContext); if (!ctx) throw new Error("useReqdesk must be used within a "); return ctx; } /** Internal singletons so every `` mount reuses the same query cache while * still letting tests reset the module with a fresh QueryClient if they import this directly. */ function translate(locale, key, translations) { if (translations?.[key]) return translations[key]; return (LOCALE_TABLES[locale ?? "en"] ?? LOCALE_TABLES.en)[key] ?? key; } /** * Build the widget's QueryClient with a `QueryCache` + `MutationCache` that funnels every * unhandled error into the notification stack. Per-query / per-mutation opt-outs flow through * `meta: WidgetQueryMeta | WidgetMutationMeta`: * - `meta.silent` — suppress the toast entirely. * - `meta.allow404` — skip the 404 toast (probe-style queries). * - `meta.form` — skip the 422 toast (the view renders per-field errors inline via * ``). Non-422 errors still toast so network / 5xx failures never hide. * * `translate` is passed in so the cache callbacks can localize copy against the current locale * without reaching into a stale module-level snapshot. */ function buildWidgetQueryClient(getLocale) { const notifyFromError = (error, meta, source) => { const widgetErr = asWidgetError(error); if (shouldSuppressToast(widgetErr, meta, source)) return; const { locale, translations } = getLocale(); const t = (k) => translate(locale, k, translations); pushNotificationExternal({ kind: "error", title: widgetErr.message || defaultTitleFor(widgetErr, t), detail: widgetErr.errors && widgetErr.errors.length > 0 ? widgetErr.errors.filter((fe) => !fe.field).map((fe) => fe.detail || fe.code).filter(Boolean) : void 0, autoDismissMs: null }); }; return new _tanstack_react_query.QueryClient({ queryCache: new _tanstack_react_query.QueryCache({ onError: (error, query) => notifyFromError(error, query.meta, "query") }), mutationCache: new _tanstack_react_query.MutationCache({ onError: (error, _vars, _ctx, mutation) => notifyFromError(error, mutation.meta, "mutation") }), defaultOptions: { queries: { staleTime: 300 * 1e3, gcTime: 600 * 1e3, retry: (failureCount, error) => { const status = error?.status; if (status && status >= 400 && status < 500 && status !== 408) return false; if (status && status >= 500) return false; return failureCount < 2; }, retryDelay: (attemptIndex) => Math.min(1e3 * 2 ** attemptIndex, 3e4), refetchOnWindowFocus: false }, mutations: { retry: false } } }); } function asWidgetError(error) { if (error instanceof Error && "code" in error) return error; if (error && typeof error === "object" && "code" in error && "message" in error) return error; return { code: "UNKNOWN", message: error instanceof Error ? error.message : "Something went wrong." }; } function shouldSuppressToast(err, meta, source) { if (!meta) return false; if (meta.silent) return true; if (source === "query") { if (meta.allow404 && err.status === 404) return true; } if (source === "mutation") { if (meta.form && err.status === 422) return true; } return false; } function defaultTitleFor(err, t) { if (!err.status) return t("error.network"); if (err.status === 401) return t("error.unauthenticated"); if (err.status === 403) return t("error.forbidden"); if (err.status === 404) return t("error.notFound"); if (err.status >= 500) return t("error.server"); if (err.status === 422) return t("error.validation"); return t("error.generic"); } let sharedQueryClient = null; const localeGetterRef = { current: () => ({ locale: "en", translations: void 0 }) }; function getWidgetQueryClient() { if (!sharedQueryClient) sharedQueryClient = buildWidgetQueryClient(() => localeGetterRef.current()); return sharedQueryClient; } function ReqdeskProvider({ apiKey, apiUrl, auth, theme, language, customer, translations, authMode, display, actions, initialPreferences, userPreferences, onPreferencesChange, menuCloseOnAction, hideFab, hideDisplayModePicker, fabIcon, children }) { const initialized = (0, react.useRef)(false); const [authState, setAuthState] = (0, react.useState)(getAuthState); const [serverConfig, setServerConfig] = (0, react.useState)(null); const isWorkspaceKey = apiKey.startsWith("rqd_ws_"); const [projects, setProjects] = (0, react.useState)([]); const [selectedProjectId, setSelectedProjectId] = (0, react.useState)(null); const [isProjectLoading, setIsProjectLoading] = (0, react.useState)(false); const saved = initialized.current ? null : require_trigger_css.loadWidgetConfig(apiKey); const resolvedLanguage = language ?? saved?.language ?? "en"; const resolvedTheme = theme ?? saved?.theme; const registryInitialized = (0, react.useRef)(false); if (!registryInitialized.current) { const registry = require_trigger_css.getRegistry(); registry.setApiKeyForStorage(apiKey); registry.setInitialPreferences(initialPreferences ?? {}); registry.setConfigDisplay(display); registry.setMenuCloseOnAction(menuCloseOnAction ?? true); registry.setHostPreferencesCallback(onPreferencesChange); const resolvedPrefs = require_trigger_css.resolvePreferences({ apiKey, initialPreferences, userPreferences }); registry.initPreferencesSnapshot(resolvedPrefs); if (actions) for (const action of actions) registry.addAction(action); registryInitialized.current = true; } (0, react.useEffect)(() => { require_trigger_css.getRegistry().setHostPreferencesCallback(onPreferencesChange); }, [onPreferencesChange]); (0, react.useEffect)(() => { require_trigger_css.getRegistry().setConfigDisplay(display); }, [display]); (0, react.useEffect)(() => { require_trigger_css.getRegistry().setMenuCloseOnAction(menuCloseOnAction ?? true); }, [menuCloseOnAction]); (0, react.useEffect)(() => { if (!initialized.current) { require_trigger_css.configureWidgetClient(apiUrl || window.location.origin, apiKey); require_trigger_css.setWidgetCustomer(customer ?? null); initialized.current = true; if (!isWorkspaceKey) require_trigger_css.getWidgetConfig("_current").then((config) => { setServerConfig(config); if (!auth && config.authIssuerUri && config.authClientId) initWidgetAuth({ issuerUri: config.authIssuerUri, clientId: config.authClientId }); }).catch(() => {}); if (auth) initWidgetAuth(auth); } }, [ apiKey, apiUrl, auth, isWorkspaceKey ]); (0, react.useEffect)(() => { setAuthState(getAuthState()); return onAuthStateChange(setAuthState); }, []); (0, react.useEffect)(() => { require_trigger_css.setWidgetCustomer(customer ?? null); }, [customer]); (0, react.useEffect)(() => { require_trigger_css.saveWidgetConfig(apiKey, { language: resolvedLanguage, theme: resolvedTheme }); }, [ apiKey, resolvedLanguage, resolvedTheme ]); (0, react.useEffect)(() => { if (!authState.isAuthenticated) { setSelectedProjectId(isWorkspaceKey ? null : "_current"); setProjects([]); setIsProjectLoading(false); return; } setIsProjectLoading(true); require_trigger_css.listProjects(serverConfig?.workspaceId ?? void 0).then((projectList) => { setProjects(projectList); const userKey = authState.userEmail ?? "anon"; const saved = require_trigger_css.loadSelectedProject(apiKey, userKey); if (saved && projectList.some((p) => p.id === saved)) setSelectedProjectId(saved); else if (serverConfig?.id && projectList.some((p) => p.id === serverConfig.id)) { setSelectedProjectId(serverConfig.id); require_trigger_css.saveSelectedProject(apiKey, userKey, serverConfig.id); } else if (projectList.length === 1) { setSelectedProjectId(projectList[0].id); require_trigger_css.saveSelectedProject(apiKey, userKey, projectList[0].id); } else if (projectList.length > 1) setSelectedProjectId(null); else setSelectedProjectId(isWorkspaceKey ? null : "_current"); }).catch(() => { setSelectedProjectId(isWorkspaceKey ? null : "_current"); }).finally(() => setIsProjectLoading(false)); }, [ authState.isAuthenticated, serverConfig, apiKey, authState.userEmail, isWorkspaceKey ]); const selectProject = (0, react.useCallback)((id) => { setSelectedProjectId(id); require_trigger_css.saveSelectedProject(apiKey, authState.userEmail ?? "anon", id); }, [apiKey, authState.userEmail]); const mergedTheme = (0, react.useMemo)(() => { const base = { ...resolvedTheme ?? {} }; if (serverConfig) { if (!base.brandName) base.brandName = serverConfig.brandName || serverConfig.projectName; if (!base.logo && serverConfig.logoUrl) base.logo = serverConfig.logoUrl; } return base; }, [resolvedTheme, serverConfig]); const mergedAuth = auth ?? (serverConfig?.authIssuerUri && serverConfig?.authClientId ? { issuerUri: serverConfig.authIssuerUri, clientId: serverConfig.authClientId } : void 0); const resolvedProjectId = selectedProjectId ?? serverConfig?.id ?? "_current"; const isMultiProject = authState.isAuthenticated && projects.length > 1; const serverAllowedModes = (0, react.useMemo)(() => { const raw = serverConfig?.allowedAuthModes; if (!Array.isArray(raw) || raw.length === 0) return SAFE_DEFAULT_MODES; return raw.filter((m) => m === "sso" || m === "email" || m === "signed"); }, [serverConfig?.allowedAuthModes]); const resolvedIdentityMode = (0, react.useMemo)(() => resolveIdentityMode({ customer, serverAllowed: serverAllowedModes, override: authMode, isSsoAuthenticated: authState.isAuthenticated }), [ customer, serverAllowedModes, authMode, authState.isAuthenticated ]); const value = (0, react.useMemo)(() => ({ apiKey, auth: mergedAuth, theme: mergedTheme, language: resolvedLanguage, customer, translations, isAuthenticated: authState.isAuthenticated, userEmail: authState.userEmail, userName: authState.userName, showAllTickets: serverConfig?.showAllTickets ?? false, projectId: resolvedProjectId, projects, isMultiProject, selectProject, isProjectLoading, serverAllowedModes, resolvedIdentityMode, hideFab, hideDisplayModePicker, fabIcon }), [ apiKey, mergedAuth, mergedTheme, resolvedLanguage, customer, translations, authState, serverConfig, resolvedProjectId, projects, isMultiProject, selectProject, isProjectLoading, serverAllowedModes, resolvedIdentityMode, hideFab, hideDisplayModePicker, fabIcon ]); (0, react.useEffect)(() => { require_trigger_css.getRegistry().setContext({ user: customer ? { email: customer.email, name: customer.name } : null, projectId: resolvedProjectId, locale: resolvedLanguage }); }, [ customer, resolvedProjectId, resolvedLanguage ]); (0, react.useEffect)(() => { localeGetterRef.current = () => ({ locale: resolvedLanguage, translations }); }, [resolvedLanguage, translations]); const queryClient = getWidgetQueryClient(); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ReqdeskContext.Provider, { value, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(_tanstack_react_query.QueryClientProvider, { client: queryClient, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(NotificationProvider, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(RegistryProvider, { children }) }) }) }); } //#endregion //#region src/react/useReqdesk.ts function useReqdesk() { const ctx = useReqdeskContext(); const registry = useRegistry(); const [isLoading, setIsLoading] = (0, react.useState)(false); const [error, setError] = (0, react.useState)(null); return { submitTicket: (0, react.useCallback)(async (data) => { setIsLoading(true); setError(null); try { return await require_trigger_css.submitTicket(ctx.projectId, data); } catch (err) { const widgetError = err; setError(widgetError); throw widgetError; } finally { setIsLoading(false); } }, [ctx.projectId]), trackTicket: (0, react.useCallback)(async (token) => { setIsLoading(true); setError(null); try { return await require_trigger_css.trackTicket(token); } catch (err) { const widgetError = err; setError(widgetError); throw widgetError; } finally { setIsLoading(false); } }, []), submitTrackingReply: (0, react.useCallback)(async (token, body) => { setIsLoading(true); setError(null); try { await require_trigger_css.submitTrackingReply(token, body); } catch (err) { const widgetError = err; setError(widgetError); throw widgetError; } finally { setIsLoading(false); } }, []), isLoading, error, isOpen: useRegistrySnapshot((s) => s.isOpen), currentView: useRegistrySnapshot((s) => s.currentView), currentDisplayMode: useRegistrySnapshot((s) => s.currentDisplayMode), preferences: useRegistrySnapshot((s) => s.preferences), ...(0, react.useMemo)(() => ({ open: registry.open, close: registry.close, toggle: registry.toggle, openMenu: registry.openMenu, openAction: registry.openAction, setDisplayMode: registry.setDisplayMode, setPreferences: registry.setPreferences, addAction: registry.addAction, removeAction: registry.removeAction, on: registry.on }), [registry]) }; } //#endregion //#region src/react/shadow-root.tsx function ShadowRoot({ children }) { const hostRef = (0, react.useRef)(null); const [mountPoint, setMountPoint] = (0, react.useState)(null); (0, react.useEffect)(() => { const host = hostRef.current; if (!host || host.shadowRoot) return; const shadow = host.attachShadow({ mode: "open" }); const style = document.createElement("style"); style.textContent = require_trigger_css.getWidgetStyles(); shadow.appendChild(style); const mount = document.createElement("div"); mount.className = "rqd-root"; shadow.appendChild(mount); setMountPoint(mount); }, []); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { ref: hostRef, children: mountPoint && (0, react_dom.createPortal)(children, mountPoint) }); } //#endregion //#region src/react/TicketForm.tsx const translations$5 = { en: require_trigger_css.en, ar: require_trigger_css.ar }; function TicketForm({ mode = "inline", onTicketCreated, onError, className, style }) { const ctx = useReqdeskContext(); const { submitTicket, isLoading } = useReqdesk(); const [success, setSuccess] = (0, react.useState)(null); const [errors, setErrors] = (0, react.useState)({}); const t = (0, react.useCallback)((key) => { if (ctx.translations?.[key]) return ctx.translations[key]; return (translations$5[ctx.language] ?? translations$5.en)[key] ?? key; }, [ctx.language, ctx.translations]); const handleSubmit = (0, react.useCallback)(async (e) => { e.preventDefault(); const form = e.currentTarget; const formData = new FormData(form); const title = formData.get("title")?.trim() ?? ""; const email = formData.get("email")?.trim() ?? ""; const newErrors = {}; if (!title) newErrors.title = t("error.required"); else if (title.length < 5) newErrors.title = t("error.titleMin"); if (!email) newErrors.email = t("error.required"); else if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) newErrors.email = t("error.emailInvalid"); if (Object.keys(newErrors).length > 0) { setErrors(newErrors); return; } setErrors({}); const data = { title, description: formData.get("description")?.trim() || void 0, email, priority: formData.get("priority") ?? "medium" }; try { const result = await submitTicket(data); if (result.trackingToken) require_trigger_css.saveTrackingToken(ctx.apiKey, result.trackingToken); setSuccess(result); onTicketCreated?.(result); } catch (err) { onError?.(err); } }, [ submitTicket, ctx.apiKey, ctx.translations, ctx.language, onTicketCreated, onError, t ]); const cssVars = require_trigger_css.themeToStyle(ctx.theme); const content = success ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-success", style: { textAlign: "center", padding: "24px 0" }, children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: { fontSize: 48, marginBottom: 12 }, children: "✅" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { style: { margin: "0 0 8px", fontSize: 18 }, children: t("success.title") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", { children: [t("success.ticketNumber"), success.ticketNumber] }), success.trackingToken && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { style: { fontSize: 13, color: "var(--rqd-text-secondary)" }, children: t("success.trackingHint") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-token-box", children: success.trackingToken }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { className: "rqd-btn rqd-btn-secondary", onClick: () => navigator.clipboard.writeText(success.trackingToken), children: t("success.copyToken") }) ] }) ] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", { className: "rqd-form", onSubmit: handleSubmit, noValidate: true, children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-form-group", children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", { className: "rqd-label", children: t("form.title") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", { className: "rqd-input", name: "title", placeholder: t("form.titlePlaceholder"), required: true }), errors.title && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-error-text", children: errors.title }) ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-form-group", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", { className: "rqd-label", children: t("form.description") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", { className: "rqd-textarea", name: "description", placeholder: t("form.descriptionPlaceholder") })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-form-group", children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", { className: "rqd-label", children: t("form.email") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", { className: "rqd-input", name: "email", type: "email", placeholder: t("form.emailPlaceholder"), required: true }), errors.email && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-error-text", children: errors.email }) ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-form-group", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", { className: "rqd-label", children: t("form.priority") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", { className: "rqd-select", name: "priority", defaultValue: "medium", children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", { value: "low", children: t("form.priorityLow") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", { value: "medium", children: t("form.priorityMedium") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", { value: "high", children: t("form.priorityHigh") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", { value: "urgent", children: t("form.priorityUrgent") }) ] })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { className: "rqd-btn rqd-btn-primary", type: "submit", disabled: isLoading, children: isLoading ? t("form.submitting") : t("form.submit") }) ] }); if (mode === "floating") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ShadowRoot, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className, style: { ...style, ...cssVars }, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-body", children: content }) }) }); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ShadowRoot, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: `rqd-inline ${className ?? ""}`, style: cssVars, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-body", children: content }) }) }); } //#endregion //#region src/react/SupportPortal.tsx function SupportPortal({ className }) { const ctx = useReqdeskContext(); const { isLoading } = useReqdesk(); const cssVars = require_trigger_css.themeToStyle(ctx.theme); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ShadowRoot, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: `rqd-inline ${className ?? ""}`, style: cssVars, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-body", children: isLoading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { style: { textAlign: "center", color: "var(--rqd-text-secondary)" }, children: "Loading..." }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { style: { textAlign: "center", color: "var(--rqd-text-secondary)", padding: "24px 0" }, children: "Support Portal — coming in a future update." }) }) }) }); } //#endregion //#region src/react/queries.ts const widgetTicketDetailOptions = (ticketId) => (0, _tanstack_react_query.queryOptions)({ queryKey: ["widget-ticket", ticketId], queryFn: () => require_trigger_css.getTicketDetail(ticketId), staleTime: 6e4, enabled: !!ticketId }); const widgetMyTicketsOptions = (projectId, userId, showAll = false) => (0, _tanstack_react_query.queryOptions)({ queryKey: [ "widget-tickets", projectId, userId, showAll ], queryFn: () => require_trigger_css.listMyTickets(projectId, userId, 1, 20, showAll), staleTime: 3e4, placeholderData: _tanstack_react_query.keepPreviousData, enabled: showAll || !!userId }); const widgetUserOptions = (projectId, email) => (0, _tanstack_react_query.queryOptions)({ queryKey: [ "widget-user", projectId, email ], queryFn: () => require_trigger_css.resolveWidgetUser(projectId, email), staleTime: 5 * 6e4, enabled: !!email, meta: { allow404: true, silent: true } }); const widgetCategoriesOptions = (projectId, parentId) => (0, _tanstack_react_query.queryOptions)({ queryKey: [ "widget-categories", projectId, parentId ?? "root" ], queryFn: () => require_trigger_css.getCategories(projectId, parentId), staleTime: 5 * 6e4 }); const widgetTagsOptions = (projectId) => (0, _tanstack_react_query.queryOptions)({ queryKey: ["widget-tags", projectId], queryFn: () => require_trigger_css.listProjectTags(projectId), staleTime: 5 * 6e4, enabled: !!projectId, meta: { silent: true } }); //#endregion //#region src/client-metadata.ts const STORAGE_PREFIX = "reqdesk_diag_"; const DEFAULT_PREFS = { screenResolution: false, deviceType: false, timezone: false, referrerUrl: false, language: false, platform: false }; /** Always collected — minimal, non-sensitive */ function collectMinimalMetadata() { const meta = {}; try { meta.pageUrl = window.location.href; meta.userAgent = navigator.userAgent; } catch {} return meta; } /** Full diagnostic set — only included for opted-in fields */ function collectDiagnosticMetadata(prefs) { const meta = {}; try { if (prefs.screenResolution) meta.screenResolution = `${screen.width}x${screen.height}`; if (prefs.deviceType) meta.deviceType = detectDeviceType(); if (prefs.timezone) meta.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; if (prefs.referrerUrl && document.referrer) meta.referrerUrl = document.referrer; if (prefs.language) meta.language = navigator.language; if (prefs.platform) meta.platform = navigator.platform; } catch {} return meta; } /** Combine minimal + opted-in diagnostic metadata */ function collectAllMetadata(apiKey) { const prefs = getMetadataPreferences(apiKey); return { ...collectMinimalMetadata(), ...collectDiagnosticMetadata(prefs) }; } function getMetadataPreferences(apiKey) { try { const raw = localStorage.getItem(`${STORAGE_PREFIX}${apiKey}`); if (raw) return { ...DEFAULT_PREFS, ...JSON.parse(raw) }; } catch {} return { ...DEFAULT_PREFS }; } function saveMetadataPreferences(apiKey, prefs) { try { localStorage.setItem(`${STORAGE_PREFIX}${apiKey}`, JSON.stringify(prefs)); } catch {} } function detectDeviceType() { const ua = navigator.userAgent.toLowerCase(); if (/tablet|ipad|playbook|silk/i.test(ua)) return "tablet"; if (/mobile|iphone|ipod|android.*mobile|windows phone/i.test(ua)) return "mobile"; return "desktop"; } const DIAGNOSTIC_FIELDS = [ { key: "screenResolution", labelKey: "diag.screenResolution" }, { key: "deviceType", labelKey: "diag.deviceType" }, { key: "timezone", labelKey: "diag.timezone" }, { key: "referrerUrl", labelKey: "diag.referrerUrl" }, { key: "language", labelKey: "diag.language" }, { key: "platform", labelKey: "diag.platform" } ]; //#endregion //#region src/react/form-errors.tsx const FormErrorContext = (0, react.createContext)(null); function FormErrorProvider({ mutation, clientErrors, children }) { const idPrefix = (0, react.useId)(); const serverErrors = useServerErrors(mutation); const errors = (0, react.useMemo)(() => { const merged = { ...serverErrors }; if (clientErrors) { for (const [field, detail] of Object.entries(clientErrors)) if (detail) merged[field] = detail; } return merged; }, [serverErrors, clientErrors]); const errorId = (0, react.useCallback)((name) => `${idPrefix}-err-${name}`, [idPrefix]); const value = (0, react.useMemo)(() => ({ errors, errorId }), [errors, errorId]); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FormErrorContext.Provider, { value, children }); } /** * Extract a per-field error map from a `useMutation`'s current error object. Clears automatically * as soon as the mutation leaves the `'error'` state (i.e. on retry or success) so the user never * sees stale "priority invalid" red after they've fixed it and re-submitted. */ function useServerErrors(mutation) { const [serverErrors, setServerErrors] = (0, react.useState)({}); const lastSeenAttemptRef = (0, react.useRef)(void 0); (0, react.useEffect)(() => { if (!mutation) { setServerErrors({}); return; } if (mutation.status !== "error") { if (Object.keys(serverErrors).length > 0) setServerErrors({}); return; } if (lastSeenAttemptRef.current === mutation.submittedAt) return; lastSeenAttemptRef.current = mutation.submittedAt; const err = mutation.error; if (!err || !err.errors || err.errors.length === 0) { setServerErrors({}); return; } const next = {}; for (const fe of err.errors) { const key = fe.field; if (!key) continue; if (!(key in next)) next[key] = fe.detail || fe.code; } setServerErrors(next); }, [ mutation, mutation?.status, mutation?.submittedAt, mutation?.error, serverErrors ]); return serverErrors; } /** * Reads the current error for a named field. Returns the detail + pre-computed a11y props so the * caller can spread them onto any native input/select/textarea with zero branching. */ function useFieldError(name) { const ctx = (0, react.useContext)(FormErrorContext); const error = ctx?.errors[name] ?? null; const invalid = error !== null; const id = ctx?.errorId(name) ?? `rqd-err-${name}`; return { error, invalid, inputProps: invalid ? { "aria-invalid": "true", "aria-describedby": id, className: "rqd-field-error" } : {}, errorId: id }; } /** * Renders the per-field error message under an input. Uses `role="alert"` so screen readers * announce it when it appears (e.g. after a failed submission). Renders nothing when there's no * error — safe to leave in the markup unconditionally. */ function FieldError({ name, as = "div", className }) { const ctx = (0, react.useContext)(FormErrorContext); const detail = ctx?.errors[name]; if (!detail) return null; const commonProps = { id: ctx.errorId(name), role: "alert", className: className ? `rqd-error-text ${className}` : "rqd-error-text" }; switch (as) { case "span": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { ...commonProps, children: detail }); case "p": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { ...commonProps, children: detail }); default: return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { ...commonProps, children: detail }); } } /** * Common label + input + `` layout for a form field inside a ``. * The child input MUST carry a `name` attribute matching `props.name`; the group injects the * `aria-invalid` / `aria-describedby` / error-class props by cloning the child. * * For layouts that don't fit this shape (e.g. file drop zones, category tree, diagnostic checkbox * blocks), skip this component and call `useFieldError(name)` directly. */ function FormFieldGroup({ name, label, hint, className, children }) { const { inputProps } = useFieldError(name); const rootCls = className ? `rqd-form-group ${className}` : "rqd-form-group"; const merged = (0, react.isValidElement)(children) ? cloneWithMergedProps(children, inputProps) : children; return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: rootCls, children: [ label && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", { className: "rqd-label", children: label }), merged, hint && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-field-hint", children: hint }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldError, { name }) ] }); } function cloneWithMergedProps(child, extra) { const existing = child.props ?? {}; const mergedClass = [typeof existing.className === "string" ? existing.className : "", extra.className].filter(Boolean).join(" ").trim() || void 0; return (0, react.cloneElement)(child, { ...existing, "aria-invalid": extra["aria-invalid"] ?? existing["aria-invalid"], "aria-describedby": extra["aria-describedby"] ?? existing["aria-describedby"], className: mergedClass }); } //#endregion //#region src/react/views/attachments.ts /** * Shared file-attachment validation + types for SubmitTicketView (initial attachments) and * TicketDetailView (reply attachments). Centralizing the MIME allow-list + blocked extensions * means a future admin-driven policy ships in one file, not per-view. */ const ATTACHMENT_ALLOWED_MIME_TYPES = new Set([ "image/jpeg", "image/png", "image/gif", "image/webp", "application/pdf", "text/plain", "text/csv", "application/zip", "application/x-zip-compressed", "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "application/vnd.ms-excel", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "application/vnd.ms-powerpoint", "application/vnd.openxmlformats-officedocument.presentationml.presentation" ]); const ATTACHMENT_BLOCKED_EXTENSIONS = new Set([ ".exe", ".bat", ".cmd", ".sh", ".ps1", ".msi", ".dll", ".scr" ]); /** * Validate a single file against the widget's allow-list + size cap. Callers pass a `t()` * localizer so error messages respect the widget's locale. Returns `{ error: null }` for * valid files or `{ error: '' }` otherwise. */ function validateAttachment(file, t, maxSizeMb = 10) { const ext = "." + (file.name.split(".").pop()?.toLowerCase() ?? ""); if (ATTACHMENT_BLOCKED_EXTENSIONS.has(ext)) return { error: t("attach.invalidType") }; const officeOpenXml = file.type.startsWith("application/vnd.openxmlformats-officedocument."); if (!ATTACHMENT_ALLOWED_MIME_TYPES.has(file.type) && !officeOpenXml) return { error: t("attach.invalidType") }; if (file.size > maxSizeMb * 1024 * 1024) return { error: t("attach.tooLarge") }; return { error: null }; } function formatFileSize(bytes) { if (bytes < 1024) return `${bytes} B`; if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`; return `${(bytes / (1024 * 1024)).toFixed(1)} MB`; } let queuedFileCounter = 0; /** * Stable client-side id for a queued file. `crypto.randomUUID` is available in modern browsers * but not in older WebViews; the counter fallback guarantees uniqueness within a page load. */ function queuedFileId() { if (typeof crypto !== "undefined" && typeof crypto.randomUUID === "function") return crypto.randomUUID(); return `qf-${Date.now()}-${++queuedFileCounter}`; } //#endregion //#region src/react/views/SubmitTicketView.tsx const translations$4 = { en: require_trigger_css.en, ar: require_trigger_css.ar }; const MAX_FILES = 5; function SubmitTicketView$1({ projectId, onSuccess, onError, isAuthenticated, userEmail }) { const ctx = useReqdeskContext(); const queryClient = (0, _tanstack_react_query.useQueryClient)(); const fileInputRef = (0, react.useRef)(null); const savedEmail = require_trigger_css.loadWidgetEmail(ctx.apiKey); const preAuthedEmail = ctx.resolvedIdentityMode.kind === "signed" || ctx.resolvedIdentityMode.kind === "unsigned" ? ctx.resolvedIdentityMode.email : void 0; const hideEmailField = preAuthedEmail !== void 0 || isAuthenticated === true; const [clientErrors, setClientErrors] = (0, react.useState)({}); const [files, setFiles] = (0, react.useState)([]); const [isDragOver, setIsDragOver] = (0, react.useState)(false); const [uploadProgress, setUploadProgress] = (0, react.useState)(null); const [success, setSuccess] = (0, react.useState)(null); const [rememberEmail, setRememberEmail] = (0, react.useState)(!!savedEmail); const [categoryPath, setCategoryPath] = (0, react.useState)([]); const [selectedCategory, setSelectedCategory] = (0, react.useState)(null); const currentParentId = categoryPath.length > 0 ? categoryPath[categoryPath.length - 1].id : null; const { data: categories = [] } = (0, _tanstack_react_query.useQuery)(widgetCategoriesOptions(projectId, currentParentId)); const [selectedTagIds, setSelectedTagIds] = (0, react.useState)(() => /* @__PURE__ */ new Set()); const { data: availableTags = [] } = (0, _tanstack_react_query.useQuery)(widgetTagsOptions(projectId)); const toggleTag = (0, react.useCallback)((tagId) => { setSelectedTagIds((prev) => { const next = new Set(prev); if (next.has(tagId)) next.delete(tagId); else next.add(tagId); return next; }); }, []); const [newTagName, setNewTagName] = (0, react.useState)(""); const [showNewTag, setShowNewTag] = (0, react.useState)(false); const [newTagError, setNewTagError] = (0, react.useState)(null); const newTagMutation = (0, _tanstack_react_query.useMutation)({ meta: { form: "tag-create" }, mutationFn: () => require_trigger_css.createTag(projectId, newTagName.trim()), onSuccess: (created) => { setNewTagName(""); setShowNewTag(false); setNewTagError(null); setSelectedTagIds((prev) => { const next = new Set(prev); next.add(created.id); return next; }); queryClient.invalidateQueries({ queryKey: ["widget-tags", projectId] }); }, onError: (err) => { if ((err?.code ?? "") === "TAG_EXISTS") setNewTagError(t("tag.exists")); else setNewTagError(t("error.generic")); } }); const [newCatName, setNewCatName] = (0, react.useState)(""); const [showNewCat, setShowNewCat] = (0, react.useState)(false); const [newCatError, setNewCatError] = (0, react.useState)(null); const [catSearch, setCatSearch] = (0, react.useState)(""); const [catActiveIndex, setCatActiveIndex] = (0, react.useState)(0); const filteredCategories = catSearch.trim() ? categories.filter((c) => c.name.toLowerCase().includes(catSearch.trim().toLowerCase())) : categories; const catListKey = `${currentParentId ?? "root"}|${catSearch}`; const prevCatListKey = (0, react.useRef)(catListKey); if (prevCatListKey.current !== catListKey) { prevCatListKey.current = catListKey; if (catActiveIndex !== 0) setCatActiveIndex(0); } const newCatMutation = (0, _tanstack_react_query.useMutation)({ meta: { form: "category-create" }, mutationFn: () => require_trigger_css.createCategory(projectId, newCatName.trim(), currentParentId), onSuccess: (created) => { setNewCatName(""); setShowNewCat(false); setNewCatError(null); setSelectedCategory(created); queryClient.invalidateQueries({ queryKey: ["widget-categories", projectId] }); }, onError: (err) => { const code = err?.code ?? ""; if (code === "CATEGORY_EXISTS") setNewCatError(t("category.exists")); else if (code === "HTTP_402" || code === "SUBSCRIPTION_LIMIT_EXCEEDED") setNewCatError(t("category.limitReached")); else setNewCatError(t("error.generic")); } }); const [diagOpen, setDiagOpen] = (0, react.useState)(false); const [diagPrefs, setDiagPrefs] = (0, react.useState)(getMetadataPreferences(ctx.apiKey)); const diagAllChecked = Object.values(diagPrefs).every(Boolean); const diagValues = collectDiagnosticMetadata({ screenResolution: true, deviceType: true, timezone: true, referrerUrl: true, language: true, platform: true }); const t = (0, react.useCallback)((key) => { if (ctx.translations?.[key]) return ctx.translations[key]; return (translations$4[ctx.language] ?? translations$4.en)[key] ?? key; }, [ctx.language, ctx.translations]); const submitMutation = (0, _tanstack_react_query.useMutation)({ meta: { form: "ticket-submit" }, mutationFn: async ({ data, validFiles }) => { const result = await require_trigger_css.submitTicket(projectId, data); if (result.trackingToken) require_trigger_css.saveTrackingToken(ctx.apiKey, result.trackingToken); for (const queued of validFiles) { setUploadProgress({ fileId: queued.id, fileName: queued.file.name, percent: 0 }); await require_trigger_css.uploadAttachment(result.id, queued.file, (percent) => { setUploadProgress({ fileId: queued.id, fileName: queued.file.name, percent }); }); } setUploadProgress(null); return result; }, onSuccess: (result) => { setSuccess(result); setClientErrors({}); onSuccess?.(result); }, onError: (err) => { setUploadProgress(null); onError?.(err); } }); function addFiles(newFiles) { const toAdd = []; for (const file of Array.from(newFiles)) { if (files.length + toAdd.length >= MAX_FILES) break; const { error } = validateAttachment(file, t); toAdd.push({ file, id: queuedFileId(), error: error ?? void 0 }); } setFiles((prev) => [...prev, ...toAdd]); } function removeFile(id) { setFiles((prev) => prev.filter((f) => f.id !== id)); } function handleDragOver(e) { e.preventDefault(); setIsDragOver(true); } function handleDragLeave(e) { e.preventDefault(); setIsDragOver(false); } function handleDrop(e) { e.preventDefault(); setIsDragOver(false); if (e.dataTransfer.files.length > 0) addFiles(e.dataTransfer.files); } function handleSubmit(e) { e.preventDefault(); const form = e.currentTarget; const formData = new FormData(form); const title = formData.get("title")?.trim() ?? ""; const email = preAuthedEmail ?? userEmail ?? formData.get("email")?.trim() ?? savedEmail ?? ""; const newErrors = {}; if (!title) newErrors.title = t("error.required"); else if (title.length < 5) newErrors.title = t("error.titleMin"); if (!hideEmailField && email && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) newErrors.email = t("error.emailInvalid"); if (Object.keys(newErrors).length > 0) { setClientErrors(newErrors); return; } setClientErrors({}); if (rememberEmail && email) require_trigger_css.saveWidgetEmail(ctx.apiKey, email); saveMetadataPreferences(ctx.apiKey, diagPrefs); const validFiles = files.filter((f) => !f.error); const data = { title, description: formData.get("description")?.trim() || void 0, email, priority: formData.get("priority") ?? "medium", categoryId: selectedCategory?.id, clientMetadata: collectAllMetadata(ctx.apiKey), tagIds: selectedTagIds.size > 0 ? Array.from(selectedTagIds) : void 0 }; submitMutation.mutate({ data, validFiles }); } if (ctx.resolvedIdentityMode.kind === "error") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-placeholder", style: { textAlign: "center", padding: "24px 0" }, children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { style: { marginBottom: 8 }, children: t("auth.noAuthConfigured") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { style: { fontSize: 13, color: "var(--rqd-text-secondary)" }, children: t("auth.noAuthConfiguredHint") })] }); if (success) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-success", style: { textAlign: "center", padding: "24px 0" }, children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { style: { fontSize: 48, marginBottom: 12 }, children: "✅" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { style: { margin: "0 0 8px", fontSize: 18 }, children: t("success.title") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("p", { children: [t("success.ticketNumber"), success.ticketNumber] }), success.trackingToken && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { style: { fontSize: 13, color: "var(--rqd-text-secondary)" }, children: t("success.trackingHint") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-token-box", children: success.trackingToken }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { className: "rqd-btn rqd-btn-secondary", onClick: () => navigator.clipboard.writeText(success.trackingToken), children: t("success.copyToken") }) ] }) ] }); if (submitMutation.isPending && uploadProgress) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { style: { padding: "24px 0", textAlign: "center" }, children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { style: { marginBottom: 12, fontWeight: 500 }, children: t("attach.uploading") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-file-item", style: { marginBottom: 8 }, children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-file-item-info", children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-file-item-name", children: uploadProgress.fileName }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { style: { fontSize: 12, color: "var(--rqd-text-secondary)" }, children: [uploadProgress.percent, "%"] })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-progress", children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-progress-bar", style: { width: `${uploadProgress.percent}%` } }) }) ] }); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FormErrorProvider, { mutation: submitMutation, clientErrors, children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", { className: "rqd-form", onSubmit: handleSubmit, noValidate: true, children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FormFieldGroup, { name: "title", label: t("form.title"), children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", { className: "rqd-input", name: "title", placeholder: t("form.titlePlaceholder"), required: true }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FormFieldGroup, { name: "description", label: t("form.description"), children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", { className: "rqd-textarea", name: "description", placeholder: t("form.descriptionPlaceholder") }) }), !hideEmailField && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FormFieldGroup, { name: "email", label: t("form.email"), children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", { className: "rqd-input", name: "email", type: "email", placeholder: t("form.emailPlaceholder"), defaultValue: userEmail ?? savedEmail ?? "" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { className: "rqd-checkbox-label", style: { marginTop: 6 }, children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", { type: "checkbox", checked: rememberEmail, onChange: (e) => setRememberEmail(e.target.checked) }), t("mytickets.rememberMe")] })] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FormFieldGroup, { name: "priority", label: t("form.priority"), children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("select", { className: "rqd-select", name: "priority", defaultValue: "medium", children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", { value: "low", children: t("form.priorityLow") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", { value: "medium", children: t("form.priorityMedium") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", { value: "high", children: t("form.priorityHigh") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("option", { value: "critical", children: t("form.priorityCritical") }) ] }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-form-group", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", { className: "rqd-label", children: t("form.categorySelected") }), selectedCategory ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-category-selected", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { className: "rqd-category-selected-path", children: [ categoryPath.map((c) => c.name).join(" › "), categoryPath.length > 0 ? " › " : "", selectedCategory.name ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { type: "button", className: "rqd-category-selected-clear", "aria-label": t("form.categoryClear"), onClick: () => { setSelectedCategory(null); setCategoryPath([]); setCatSearch(""); }, children: "×" })] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-category-picker", children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-category-picker-header", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-category-breadcrumb", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { type: "button", className: "rqd-category-breadcrumb-crumb", onClick: () => { setCategoryPath([]); setCatSearch(""); }, "aria-current": categoryPath.length === 0 ? "location" : void 0, children: t("form.categoryRoot") }), categoryPath.map((c, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { className: "rqd-category-breadcrumb-segment", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-category-breadcrumb-sep", "aria-hidden": "true", children: "›" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { type: "button", className: "rqd-category-breadcrumb-crumb", onClick: () => { setCategoryPath((prev) => prev.slice(0, i + 1)); setCatSearch(""); }, "aria-current": i === categoryPath.length - 1 ? "location" : void 0, children: c.name })] }, c.id))] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", { type: "search", className: "rqd-input rqd-category-search", value: catSearch, onChange: (e) => setCatSearch(e.target.value), placeholder: t("form.categorySearch"), "aria-label": t("form.categorySearch"), onKeyDown: (e) => { if (e.key === "ArrowDown") { e.preventDefault(); setCatActiveIndex((i) => Math.min(i + 1, Math.max(filteredCategories.length - 1, 0))); } else if (e.key === "ArrowUp") { e.preventDefault(); setCatActiveIndex((i) => Math.max(i - 1, 0)); } else if (e.key === "Enter") { const cat = filteredCategories[catActiveIndex]; if (!cat) return; e.preventDefault(); if (cat.hasChildren) { setCategoryPath((prev) => [...prev, cat]); setCatSearch(""); } else setSelectedCategory(cat); } else if (e.key === "Escape" && catSearch) { e.preventDefault(); setCatSearch(""); } } })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-category-list", role: "listbox", "aria-label": t("form.categorySelected"), children: [filteredCategories.length === 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-category-empty", children: catSearch ? t("form.categoryNoMatch") : categoryPath.length === 0 ? t("form.categoryPlaceholder") : t("form.categoryLeaf") }), filteredCategories.map((cat, i) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", { type: "button", role: "option", "aria-selected": i === catActiveIndex, className: `rqd-category-item${i === catActiveIndex ? " rqd-category-item-active" : ""}`, onMouseEnter: () => setCatActiveIndex(i), onClick: () => { if (cat.hasChildren) { setCategoryPath((prev) => [...prev, cat]); setCatSearch(""); } else setSelectedCategory(cat); }, children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-category-item-name", children: cat.name }), cat.hasChildren && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-category-item-chevron", "aria-hidden": "true", children: "›" })] }, cat.id))] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-category-add-row", children: showNewCat ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-category-add-inline", children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", { type: "text", className: "rqd-input rqd-category-add-input", value: newCatName, onChange: (e) => { setNewCatName(e.target.value); setNewCatError(null); }, placeholder: categoryPath.length > 0 ? `${t("category.namePlaceholder")} \u2014 ${categoryPath[categoryPath.length - 1].name}` : t("category.namePlaceholder"), autoFocus: true, onKeyDown: (e) => { if (e.key === "Enter") { e.preventDefault(); if (newCatName.trim()) newCatMutation.mutate(); } if (e.key === "Escape") { setShowNewCat(false); setNewCatName(""); setNewCatError(null); } } }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { type: "button", className: "rqd-category-add-confirm", disabled: !newCatName.trim() || newCatMutation.isPending, onClick: () => newCatMutation.mutate(), "aria-label": t("category.addNew"), children: newCatMutation.isPending ? "…" : "✓" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { type: "button", className: "rqd-category-add-cancel", onClick: () => { setShowNewCat(false); setNewCatName(""); setNewCatError(null); }, "aria-label": t("form.categoryClear"), children: "✕" }), newCatError && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-category-add-error", children: newCatError }) ] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", { type: "button", className: "rqd-category-add-btn", onClick: () => setShowNewCat(true), children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { "aria-hidden": "true", style: { fontSize: 16, lineHeight: 1 }, children: "+" }), categoryPath.length > 0 ? `${t("category.addNew")} \u2014 ${t("category.addUnder")} ${categoryPath[categoryPath.length - 1].name}` : t("category.addNew")] }) }) ] })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-form-group", children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", { className: "rqd-label", children: t("form.tags") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-tag-picker", role: "group", "aria-label": t("form.tags"), children: [availableTags.map((tag) => { const selected = selectedTagIds.has(tag.id); return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", { type: "button", className: `rqd-tag-chip${selected ? " rqd-tag-chip-selected" : ""}`, "aria-pressed": selected, onClick: () => toggleTag(tag.id), style: { ["--rqd-tag-color"]: tag.color }, children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-tag-chip-dot", "aria-hidden": "true" }), tag.name] }, tag.id); }), showNewTag ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { className: "rqd-tag-add-inline", role: "group", "aria-label": t("tag.addNew"), children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", { type: "text", className: "rqd-input rqd-tag-add-input", value: newTagName, onChange: (e) => { setNewTagName(e.target.value); setNewTagError(null); }, placeholder: t("tag.namePlaceholder"), maxLength: 60, autoFocus: true, onKeyDown: (e) => { if (e.key === "Enter") { e.preventDefault(); if (newTagName.trim()) newTagMutation.mutate(); } if (e.key === "Escape") { e.preventDefault(); setShowNewTag(false); setNewTagName(""); setNewTagError(null); } } }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { type: "button", className: "rqd-tag-add-confirm", disabled: !newTagName.trim() || newTagMutation.isPending, onClick: () => newTagMutation.mutate(), "aria-label": t("tag.addNew"), children: newTagMutation.isPending ? "…" : "✓" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { type: "button", className: "rqd-tag-add-cancel", onClick: () => { setShowNewTag(false); setNewTagName(""); setNewTagError(null); }, "aria-label": t("form.categoryClear"), children: "×" }) ] }) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", { type: "button", className: "rqd-tag-chip rqd-tag-chip-add", onClick: () => setShowNewTag(true), children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { "aria-hidden": "true", style: { fontSize: 14, lineHeight: 1 }, children: "+" }), availableTags.length === 0 ? t("tag.addFirst") : t("tag.addNew")] })] }), newTagError && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-tag-add-error", children: newTagError }) ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-form-group", children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { className: "rqd-diag-master", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", { type: "checkbox", checked: diagAllChecked, onChange: (e) => { const next = e.target.checked; setDiagPrefs({ screenResolution: next, deviceType: next, timezone: next, referrerUrl: next, language: next, platform: next }); } }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { className: "rqd-diag-master-label", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-diag-master-title", children: t("diag.shareAll") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-diag-master-hint", children: t("diag.hint") })] })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", { type: "button", className: "rqd-diag-details-toggle", onClick: () => setDiagOpen((prev) => !prev), "aria-expanded": diagOpen, children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: diagOpen ? t("diag.hideIncluded") : t("diag.showIncluded") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { "aria-hidden": "true", children: diagOpen ? "▲" : "▼" })] }), diagOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", { className: "rqd-diag-preview", children: DIAGNOSTIC_FIELDS.map((field) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", { className: "rqd-diag-preview-item", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-diag-preview-label", children: t(field.labelKey) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-diag-preview-value", children: diagValues[field.key] ?? "—" })] }, field.key)) }) ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-form-group", children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("label", { className: "rqd-label", children: t("form.attachment") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: `rqd-dropzone${isDragOver ? " rqd-dropzone-active" : ""}`, onDragOver: handleDragOver, onDragLeave: handleDragLeave, onDrop: handleDrop, onClick: () => fileInputRef.current?.click(), children: [isDragOver ? t("attach.dropzoneActive") : t("attach.dropzone"), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", { ref: fileInputRef, type: "file", multiple: true, style: { display: "none" }, onChange: (e) => { if (e.target.files) addFiles(e.target.files); e.target.value = ""; } })] }), files.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-file-list", children: files.map((f) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-file-item", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-file-item-info", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-file-item-name", children: f.file.name }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { className: "rqd-file-item-size", children: [formatFileSize(f.file.size), f.error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { style: { color: "#e74c3c", marginLeft: 6 }, children: f.error })] })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { className: "rqd-file-remove", onClick: () => removeFile(f.id), type: "button", children: "×" })] }, f.id)) }) ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { className: "rqd-btn rqd-btn-primary", type: "submit", disabled: submitMutation.isPending, children: submitMutation.isPending ? t("form.submitting") : t("form.submit") }) ] }) }); } //#endregion //#region src/react/views/MyTicketsView.tsx const translations$3 = { en: require_trigger_css.en, ar: require_trigger_css.ar }; function MyTicketsView$1({ projectId, onSelectTicket, isAuthenticated, userEmail }) { const ctx = useReqdeskContext(); const savedEmail = require_trigger_css.loadWidgetEmail(ctx.apiKey); const preAuthedEmail = ctx.resolvedIdentityMode.kind === "signed" || ctx.resolvedIdentityMode.kind === "unsigned" ? ctx.resolvedIdentityMode.email : void 0; const effectiveUserEmail = userEmail ?? preAuthedEmail; const isHostAuthenticated = isAuthenticated === true || preAuthedEmail !== void 0; const [email, setEmail] = (0, react.useState)(effectiveUserEmail ?? savedEmail ?? ""); const [rememberMe, setRememberMe] = (0, react.useState)(!!savedEmail); const [submittedEmail, setSubmittedEmail] = (0, react.useState)(isHostAuthenticated && effectiveUserEmail ? effectiveUserEmail : savedEmail); const [tokenTickets, setTokenTickets] = (0, react.useState)([]); const t = (0, react.useCallback)((key) => { if (ctx.translations?.[key]) return ctx.translations[key]; return (translations$3[ctx.language] ?? translations$3.en)[key] ?? key; }, [ctx.language, ctx.translations]); const showAllTickets = ctx.showAllTickets ?? false; const skipWidgetUserResolve = isHostAuthenticated && !!effectiveUserEmail; const { data: widgetUser } = (0, _tanstack_react_query.useQuery)(widgetUserOptions(projectId, skipWidgetUserResolve ? "" : showAllTickets ? "" : submittedEmail ?? "")); const { data: emailTickets = [], isLoading } = (0, _tanstack_react_query.useQuery)(widgetMyTicketsOptions(projectId, skipWidgetUserResolve ? "" : widgetUser?.userId ?? "", showAllTickets || skipWidgetUserResolve)); const [tokensFetched, setTokensFetched] = (0, react.useState)(false); (0, react.useEffect)(() => { if (tokensFetched) return; const tokens = require_trigger_css.getTrackingTokens(ctx.apiKey); if (tokens.length === 0) { setTokensFetched(true); return; } setTokensFetched(true); Promise.allSettled(tokens.slice(0, 3).map((tk) => require_trigger_css.trackTicket(tk))).then((results) => { const tracked = []; for (const r of results) if (r.status === "fulfilled") { const t = r.value; tracked.push({ id: t.id, ticketNumber: t.ticketNumber, title: t.title, status: t.status, priority: t.priority, createdAt: t.createdAt }); } setTokenTickets(tracked); }); }, [ctx.apiKey, tokensFetched]); const allTickets = [...emailTickets, ...tokenTickets]; function handleSubmit(e) { e.preventDefault(); const trimmed = email.trim(); if (!trimmed || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed)) return; if (rememberMe) require_trigger_css.saveWidgetEmail(ctx.apiKey, trimmed); setSubmittedEmail(trimmed); } function formatDate(iso) { try { return new Date(iso).toLocaleDateString(); } catch { return iso; } } if (!submittedEmail && !isHostAuthenticated) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-email-form", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("mytickets.emailPrompt") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", { onSubmit: handleSubmit, children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-form-group", children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", { className: "rqd-input", type: "email", value: email, onChange: (e) => setEmail(e.target.value), placeholder: t("mytickets.emailPlaceholder"), required: true }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", { className: "rqd-checkbox-label", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", { type: "checkbox", checked: rememberMe, onChange: (e) => setRememberMe(e.target.checked) }), t("mytickets.rememberMe")] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { className: "rqd-btn rqd-btn-primary", type: "submit", style: { marginTop: 12 }, children: t("mytickets.lookup") }) ] })] }); if (isLoading) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-loading", children: t("mytickets.lookingUp") }); if (allTickets.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-placeholder", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: submittedEmail && !widgetUser ? t("mytickets.noAccount") : t("mytickets.noTickets") }), submittedEmail && !isHostAuthenticated && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { className: "rqd-btn rqd-btn-secondary", style: { width: "auto", padding: "8px 20px" }, onClick: () => setSubmittedEmail(null), children: t("tracker.back") })] }); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-ticket-list", children: allTickets.map((ticket) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-ticket-card", onClick: () => onSelectTicket(ticket.id), children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-ticket-card-top", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-ticket-card-number", children: ticket.ticketNumber }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-badge", children: ticket.status })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-ticket-card-title", children: ticket.title }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-ticket-card-date", children: formatDate(ticket.createdAt) }) ] }, ticket.id)) }); } //#endregion //#region src/react/mentions.ts /** * Normalize mention markup to plain @Name text for widget rendering. * * The dashboard reply composer emits two mention formats that need to survive * round-trip into the widget thread: * 1. TipTap HTML: @Name * 2. Plaintext: @[Name](user:ULID) * * The widget renders replies as React text nodes (no HTML parsing, no TipTap) * so without this helper customers see literal span markup or markdown-style * brackets. Strip both forms down to a bare "@Name" — the ULID is internal * plumbing and has no display value on the customer side. */ const PLAINTEXT_MENTION = /@\[([^\]]+)\]\(user:[0-9A-HJKMNP-TV-Z]{26}\)/gi; const HTML_MENTION = /]*data-mention-user=["'][0-9A-HJKMNP-TV-Z]{26}["'][^>]*>([^<]*)<\/span>/gi; function normalizeMentionsForDisplay(body) { if (!body) return ""; return body.replace(HTML_MENTION, (_, label) => label.trim() || "@").replace(PLAINTEXT_MENTION, (_, label) => `@${label}`); } //#endregion //#region src/react/views/TicketDetailView.tsx const translations$2 = { en: require_trigger_css.en, ar: require_trigger_css.ar }; /** Cap reply attachments lower than the ticket-level cap — replies are inline edits, not the * primary context-delivery vehicle; keeps the UI tight and matches the backend per-reply limit. */ const MAX_REPLY_FILES = 3; function formatDate(iso) { try { return new Date(iso).toLocaleString(); } catch { return iso; } } /** * Short, human-friendly time label for reply cards. Uses relative copy for recent replies * (≤ 59s, ≤ 59m, ≤ 23h) and absolute date + time thereafter. The full timestamp is still * exposed via `title` for users who need the exact value. */ function formatReplyTime(iso, locale) { if (!iso) return { label: "", title: "" }; const parsed = new Date(iso); if (Number.isNaN(parsed.getTime())) return { label: iso, title: iso }; const now = Date.now(); const diffSec = Math.floor((now - parsed.getTime()) / 1e3); const title = parsed.toLocaleString(); if (diffSec < 60) return { label: "just now", title }; if (diffSec < 3600) return { label: `${Math.floor(diffSec / 60)}m ago`, title }; if (diffSec < 86400) return { label: `${Math.floor(diffSec / 3600)}h ago`, title }; const sameYear = parsed.getFullYear() === new Date(now).getFullYear(); return { label: parsed.toLocaleString(locale, { month: "short", day: "numeric", year: sameYear ? void 0 : "numeric", hour: "2-digit", minute: "2-digit" }), title }; } /** First letter of the author name (or `@` of email-like strings) as an avatar-safe initial. */ function initialsFor(name) { const trimmed = (name ?? "").trim(); if (!trimmed) return "?"; return (trimmed.replace(/^[^\p{L}\p{N}]+/u, "").charAt(0) || "?").toUpperCase(); } function TicketDetailView$1({ ticketId, onBack }) { const ctx = useReqdeskContext(); const queryClient = (0, _tanstack_react_query.useQueryClient)(); const [replyBody, setReplyBody] = (0, react.useState)(""); const [originalOpen, setOriginalOpen] = (0, react.useState)(false); const [replyFiles, setReplyFiles] = (0, react.useState)([]); const [replyDragOver, setReplyDragOver] = (0, react.useState)(false); const [replyUploadProgress, setReplyUploadProgress] = (0, react.useState)(null); const replyFileInputRef = (0, react.useRef)(null); const t = (0, react.useCallback)((key) => { if (ctx.translations?.[key]) return ctx.translations[key]; return (translations$2[ctx.language] ?? translations$2.en)[key] ?? key; }, [ctx.language, ctx.translations]); const { data: ticket, isLoading, error } = (0, _tanstack_react_query.useQuery)(widgetTicketDetailOptions(ticketId)); const replyMutation = (0, _tanstack_react_query.useMutation)({ meta: { form: "reply-send" }, mutationFn: async ({ body, validFiles }) => { const result = await require_trigger_css.submitReply(ticketId, body); for (const queued of validFiles) { setReplyUploadProgress({ fileName: queued.file.name, percent: 0 }); try { await require_trigger_css.uploadReplyAttachment(ticketId, result.id, queued.file, (percent) => { setReplyUploadProgress({ fileName: queued.file.name, percent }); }); } catch {} } setReplyUploadProgress(null); return result; }, onMutate: async ({ body }) => { await queryClient.cancelQueries({ queryKey: ["widget-ticket", ticketId] }); const previous = queryClient.getQueryData(["widget-ticket", ticketId]); if (previous) queryClient.setQueryData(["widget-ticket", ticketId], { ...previous, replies: [...previous.replies, { id: `optimistic-${Date.now()}`, body, authorName: ctx.userName ?? ctx.userEmail ?? "You", isStaff: false, createdAt: (/* @__PURE__ */ new Date()).toISOString() }] }); return { previous }; }, onSuccess: () => { setReplyBody(""); setReplyFiles([]); setReplyUploadProgress(null); queryClient.invalidateQueries({ queryKey: ["widget-ticket", ticketId] }); }, onError: (_err, _vars, context) => { setReplyUploadProgress(null); if (context?.previous) queryClient.setQueryData(["widget-ticket", ticketId], context.previous); } }); function addReplyFiles(newFiles) { const toAdd = []; for (const file of Array.from(newFiles)) { if (replyFiles.length + toAdd.length >= MAX_REPLY_FILES) break; const { error } = validateAttachment(file, t); toAdd.push({ file, id: queuedFileId(), error: error ?? void 0 }); } setReplyFiles((prev) => [...prev, ...toAdd]); } function removeReplyFile(id) { setReplyFiles((prev) => prev.filter((f) => f.id !== id)); } function handleReplyDragOver(e) { e.preventDefault(); setReplyDragOver(true); } function handleReplyDragLeave(e) { e.preventDefault(); setReplyDragOver(false); } function handleReplyDrop(e) { e.preventDefault(); setReplyDragOver(false); if (e.dataTransfer.files.length > 0) addReplyFiles(e.dataTransfer.files); } function handleSendReply() { const body = replyBody.trim(); if (!body) return; const validFiles = replyFiles.filter((f) => !f.error); replyMutation.mutate({ body, validFiles }); } const resolveMutation = (0, _tanstack_react_query.useMutation)({ mutationFn: () => require_trigger_css.closeTicket(ticketId), onMutate: async () => { await queryClient.cancelQueries({ queryKey: ["widget-ticket", ticketId] }); const previous = queryClient.getQueryData(["widget-ticket", ticketId]); if (previous) queryClient.setQueryData(["widget-ticket", ticketId], { ...previous, status: "resolved" }); return { previous }; }, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ["widget-ticket", ticketId] }); }, onError: (_err, _vars, context) => { if (context?.previous) queryClient.setQueryData(["widget-ticket", ticketId], context.previous); } }); if (isLoading) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-loading", children: t("detail.loading") }); if (error || !ticket) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-placeholder", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("error.generic") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { className: "rqd-btn rqd-btn-secondary", style: { width: "auto", padding: "8px 20px" }, onClick: onBack, children: t("tracker.back") })] }); const isActive = ticket.status !== "resolved" && ticket.status !== "closed"; return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-detail", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-detail-main", children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-detail-headline", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("h3", { className: "rqd-detail-title", children: ticket.title }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-detail-meta-row", children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-ticket-card-number", children: ticket.ticketNumber }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-badge", children: ticket.status }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-badge rqd-badge-muted", children: ticket.priority }), ticket.tags.length > 0 && ticket.tags.map((tag) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { className: "rqd-tag-chip rqd-tag-chip-readonly", style: { ["--rqd-tag-color"]: tag.color }, children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-tag-chip-dot", "aria-hidden": "true" }), tag.name] }, tag.id)) ] })] }), (ticket.description || ticket.attachments.length > 0) && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: `rqd-detail-original${originalOpen ? " rqd-open" : ""}`, children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", { type: "button", className: "rqd-detail-original-summary", onClick: () => setOriginalOpen((v) => !v), "aria-expanded": originalOpen, children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-detail-original-label", children: t("detail.originalRequest") ?? "Original request" }), ticket.attachments.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { className: "rqd-detail-original-count", children: [ ticket.attachments.length, " ", ticket.attachments.length === 1 ? "file" : "files" ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", { className: "rqd-detail-original-chevron", width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("polyline", { points: "6 9 12 15 18 9" }) }) ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-detail-original-body", children: [ticket.description && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-ticket-desc", children: ticket.description }), ticket.attachments.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-detail-attachments-inline", children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-attachment-list", children: ticket.attachments.map((a) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-attachment-item", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-file-item-info", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-file-item-name", children: a.fileName }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-file-item-size", children: formatFileSize(a.fileSize) })] }), a.downloadUrl && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", { href: a.downloadUrl, target: "_blank", rel: "noopener noreferrer", children: t("attach.download") })] }, a.id)) }) })] })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-section-title", children: t("detail.replies") }), ticket.replies.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { className: "rqd-detail-empty", children: t("detail.noReplies") }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-conversation", children: ticket.replies.map((reply) => { const time = formatReplyTime(reply.createdAt, ctx.language); return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: `rqd-bubble ${reply.isStaff ? "rqd-bubble-staff" : "rqd-bubble-customer"}`, children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-bubble-header", children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-bubble-avatar", "aria-hidden": "true", children: initialsFor(reply.authorName) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-bubble-author", children: reply.authorName }), reply.isStaff && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-bubble-staff-badge", children: t("detail.staff") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-bubble-time", title: time.title, children: time.label }) ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-bubble-body", children: normalizeMentionsForDisplay(reply.body) }), reply.attachments && reply.attachments.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-bubble-attachments", children: reply.attachments.map((a) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-bubble-attachment", children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-bubble-attachment-icon", "aria-hidden": "true", children: "📎" }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-file-item-info", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-file-item-name", children: a.fileName }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-file-item-size", children: formatFileSize(a.fileSize) })] }), a.downloadUrl && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", { href: a.downloadUrl, target: "_blank", rel: "noopener noreferrer", children: t("attach.download") }) ] }, a.id)) }) ] }, reply.id); }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FormErrorProvider, { mutation: replyMutation, children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-reply-compose", children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FormFieldGroup, { name: "body", children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("textarea", { className: "rqd-textarea", name: "body", value: replyBody, onChange: (e) => setReplyBody(e.target.value), placeholder: t("detail.replyPlaceholder"), rows: 3 }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: `rqd-dropzone rqd-dropzone-compact${replyDragOver ? " rqd-dropzone-active" : ""}`, onDragOver: handleReplyDragOver, onDragLeave: handleReplyDragLeave, onDrop: handleReplyDrop, onClick: () => replyFileInputRef.current?.click(), role: "button", tabIndex: 0, "aria-label": t("detail.replyAttachLabel"), children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-dropzone-icon", "aria-hidden": "true", children: "📎" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: replyDragOver ? t("attach.dropzoneActive") : t("detail.replyAttachHint") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", { ref: replyFileInputRef, type: "file", multiple: true, style: { display: "none" }, onChange: (e) => { if (e.target.files) addReplyFiles(e.target.files); e.target.value = ""; } }) ] }), replyFiles.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-file-list", children: replyFiles.map((f) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: `rqd-file-item${f.error ? " rqd-file-item-error" : ""}`, children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-file-item-info", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-file-item-name", children: f.file.name }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { className: "rqd-file-item-size", children: [formatFileSize(f.file.size), f.error ? ` \u00b7 ${f.error}` : ""] })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { type: "button", className: "rqd-file-remove", onClick: () => removeReplyFile(f.id), "aria-label": t("attach.remove"), children: "×" })] }, f.id)) }), replyUploadProgress && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-upload-status", children: [ t("attach.uploading"), " — ", replyUploadProgress.fileName, " · ", replyUploadProgress.percent, "%", /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-progress", style: { marginTop: 4 }, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-progress-bar", style: { width: `${replyUploadProgress.percent}%` } }) }) ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-compose-actions", children: [isActive && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { className: "rqd-btn rqd-btn-ghost rqd-resolve-link", onClick: () => resolveMutation.mutate(), disabled: resolveMutation.isPending, title: t("detail.resolve"), children: resolveMutation.isPending ? t("detail.resolving") : t("detail.resolve") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { className: "rqd-btn rqd-btn-primary rqd-compose-send", onClick: handleSendReply, disabled: !replyBody.trim() || replyMutation.isPending, children: replyMutation.isPending ? t("detail.sending") : t("detail.sendReply") })] }) ] }) }) ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("aside", { className: "rqd-detail-sidebar", children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-sidebar-section", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-sidebar-label", children: t("detail.status") ?? "Status" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-sidebar-value", children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-badge", children: ticket.status }) })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-sidebar-section", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-sidebar-label", children: t("detail.priority") ?? "Priority" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-sidebar-value", children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-badge rqd-badge-muted", children: ticket.priority }) })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-sidebar-section", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-sidebar-label", children: t("detail.created") ?? "Created" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-sidebar-value rqd-sidebar-text", children: formatDate(ticket.createdAt) })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-sidebar-section", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-sidebar-label", children: t("detail.reference") ?? "Reference" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-sidebar-value rqd-sidebar-text rqd-sidebar-mono", children: ticket.ticketNumber })] }), ticket.attachments.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-sidebar-section rqd-sidebar-attachments", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-sidebar-label", children: [ t("detail.attachments"), " (", ticket.attachments.length, ")" ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-attachment-list", children: ticket.attachments.map((a) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-attachment-item", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-file-item-info", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-file-item-name", children: a.fileName }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-file-item-size", children: formatFileSize(a.fileSize) })] }), a.downloadUrl && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", { href: a.downloadUrl, target: "_blank", rel: "noopener noreferrer", children: t("attach.download") })] }, a.id)) })] }), isActive && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-sidebar-section", children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { className: "rqd-btn rqd-btn-secondary", style: { width: "100%" }, onClick: () => resolveMutation.mutate(), disabled: resolveMutation.isPending, children: resolveMutation.isPending ? t("detail.resolving") : t("detail.resolve") }) }) ] })] }); } //#endregion //#region src/react/views/TrackTicketView.tsx const translations$1 = { en: require_trigger_css.en, ar: require_trigger_css.ar }; /** * Normalize a pasted tracking token. Copy-paste from terminals, email clients, and PDF * viewers commonly brings trailing whitespace, smart quotes, or BOM marks. Tokens are pure * ASCII lowercase-hex after the `trk_` prefix, so we can canonicalize aggressively before the * network round-trip without risking a false-positive normalization of a valid value. */ function canonicalizeToken(raw) { let s = raw.trim(); s = s.replace(/^["'\u201C\u201D\u2018\u2019]+/, "").replace(/["'\u201C\u201D\u2018\u2019]+$/, ""); if (s.charCodeAt(0) === 65279) s = s.slice(1); return s.toLowerCase(); } function TrackTicketView$1({ onTrackSuccess }) { const ctx = useReqdeskContext(); const [token, setToken] = (0, react.useState)(""); const savedTokens = require_trigger_css.getTrackingTokens(ctx.apiKey); const t = (0, react.useCallback)((key) => { if (ctx.translations?.[key]) return ctx.translations[key]; return (translations$1[ctx.language] ?? translations$1.en)[key] ?? key; }, [ctx.language, ctx.translations]); const trackMutation = (0, _tanstack_react_query.useMutation)({ meta: { silent: true }, mutationFn: (trackToken) => require_trigger_css.trackTicket(canonicalizeToken(trackToken)), onSuccess: (result) => onTrackSuccess(result.id) }); function handleSubmit(e) { e.preventDefault(); const canonical = canonicalizeToken(token); if (canonical) trackMutation.mutate(canonical); } return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("form", { onSubmit: handleSubmit, children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-form-group", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", { className: "rqd-input", value: token, onChange: (e) => setToken(e.target.value), placeholder: t("track.tokenPlaceholder"), required: true }), trackMutation.isError && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-error-text", children: t("track.invalidToken") })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { className: "rqd-btn rqd-btn-primary", type: "submit", disabled: trackMutation.isPending || !token.trim(), children: trackMutation.isPending ? t("track.tracking") : t("track.submit") })] }), savedTokens.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { style: { marginTop: 20 }, children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-section-title", children: t("track.recentTickets") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-ticket-list", children: savedTokens.slice(0, 5).map((tk) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { className: "rqd-ticket-card", style: { border: "none", textAlign: "start", width: "100%" }, onClick: () => trackMutation.mutate(tk), children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", { className: "rqd-ticket-card-number", style: { fontFamily: "monospace", fontSize: 12 }, children: [tk.slice(0, 20), "..."] }) }, tk)) })] })] }); } //#endregion //#region src/react/views/keyboard-nav.ts /** * Resolve a keydown event into an abstract nav action. * * ArrowDown → focus next (wraps) * ArrowUp → focus previous (wraps) * Home → focus first * End → focus last * Enter → activate current * Space → activate current * Escape → close * * @param key `KeyboardEvent.key` string * @param currentIndex current focused index * @param itemCount total number of visible menuitems */ function resolveMenuNav(key, currentIndex, itemCount) { if (itemCount <= 0) { if (key === "Escape") return { kind: "close" }; return { kind: "noop" }; } switch (key) { case "ArrowDown": return { kind: "focus", index: (currentIndex + 1) % itemCount }; case "ArrowUp": return { kind: "focus", index: (currentIndex - 1 + itemCount) % itemCount }; case "Home": return { kind: "focus", index: 0 }; case "End": return { kind: "focus", index: itemCount - 1 }; case "Enter": case " ": case "Space": return { kind: "activate", index: currentIndex }; case "Escape": return { kind: "close" }; default: return { kind: "noop" }; } } //#endregion //#region src/react/views/MenuList.tsx function resolveLocalizedString(value, locale) { if (value === void 0 || value === null) return ""; if (typeof value === "string") return value; const direct = value[locale]; if (direct) return direct; const en = value.en; if (en) return en; return Object.values(value)[0] ?? ""; } function SvgPath({ path }) { return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", { width: 22, height: 22, viewBox: "0 0 24 24", fill: "currentColor", style: { display: "block", flexShrink: 0 }, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: path }) }); } function renderActionIcon(icon) { if (icon === void 0 || icon === null) return null; if (typeof icon === "string") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SvgPath, { path: icon }); if (typeof icon === "object" && "kind" in icon) { if (icon.kind === "path") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SvgPath, { path: icon.path }); if (icon.kind === "url") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", { src: icon.src, width: 22, height: 22, alt: "", "aria-hidden": "true" }); return null; } return icon; } function renderBadge(value) { if (value === null || value === void 0 || value === "") return null; return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { "aria-label": `${value}`, style: { marginInlineStart: "auto", padding: "2px 8px", borderRadius: 999, background: "var(--rqd-primary-light, rgba(15,94,86,0.12))", color: "var(--rqd-primary-dark, #0F5E56)", fontSize: 11, fontWeight: 700, lineHeight: 1.2 }, children: String(value) }); } /** * WAI-ARIA menu pattern implementation. Single tab stop (roving tabindex), arrow-key traversal, * Home/End jumps, Enter/Space activation, Escape closes the widget. Custom actions merged with * built-ins via registry.resolveRenderOrder; visibility predicates re-run each render. */ function MenuList({ builtIns }) { const registry = useRegistry(); const actions = useRegistrySnapshot((s) => s.actions); const locale = useRegistrySnapshot((s) => s.locale); const user = useRegistrySnapshot((s) => s.user); const projectId = useRegistrySnapshot((s) => s.projectId); const predicateCtx = (0, react.useMemo)(() => ({ actionId: "", input: void 0, close: registry.close, keepOpen: () => {}, setBadge: () => {}, setLoading: () => {}, user, projectId, locale, openAction: registry.openAction }), [ registry, user, projectId, locale ]); const visibleItems = (0, react.useMemo)(() => { const builtInMap = new Map(builtIns.map((b) => [b.id, b])); const actionMap = new Map(actions.map((a) => [a.id, a])); const order = registry.resolveRenderOrder(builtIns.map((b) => b.id)); const items = []; for (const entry of order) if (entry.kind === "built-in") { const b = builtInMap.get(entry.id); if (!b || b.hidden) continue; items.push({ kind: "built-in", item: b }); } else { const a = actionMap.get(entry.id); if (!a) continue; if (typeof a.visible === "boolean" && !a.visible) continue; if (typeof a.visible === "function") try { if (!a.visible({ ...predicateCtx, actionId: a.id })) continue; } catch (cause) { registry.emitEvent("error", { code: "action-visibility-failed", message: `Visibility predicate for "${a.id}" threw`, actionId: a.id, cause }); continue; } items.push({ kind: "custom", item: a }); } return items; }, [ actions, builtIns, registry, predicateCtx ]); const [focusedIndex, setFocusedIndex] = (0, react.useState)(0); const refs = (0, react.useRef)([]); (0, react.useEffect)(() => { if (focusedIndex >= visibleItems.length) setFocusedIndex(0); }, [visibleItems.length, focusedIndex]); const focusAt = (0, react.useCallback)((index) => { const el = refs.current[index]; if (el) { setFocusedIndex(index); el.focus(); } }, []); const handleActivate = (0, react.useCallback)((index) => { const entry = visibleItems[index]; if (!entry) return; const el = refs.current[index]; if (el) registry.recordActivator(el); if (entry.kind === "built-in") entry.item.onActivate(); else registry.openAction(entry.item.id); }, [visibleItems, registry]); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { role: "menu", className: "rqd-menu", onKeyDown: (0, react.useCallback)((e) => { const action = resolveMenuNav(e.key, focusedIndex, visibleItems.length); switch (action.kind) { case "focus": e.preventDefault(); focusAt(action.index); break; case "activate": e.preventDefault(); handleActivate(action.index); break; case "close": e.preventDefault(); registry.close(); break; default: break; } }, [ visibleItems.length, focusedIndex, focusAt, handleActivate, registry ]), "aria-orientation": "vertical", children: visibleItems.map((entry, index) => { const isCustom = entry.kind === "custom"; const id = isCustom ? entry.item.id : entry.item.id; const label = isCustom ? resolveLocalizedString(entry.item.label, locale) : entry.item.label; const description = isCustom ? resolveLocalizedString(entry.item.description, locale) : entry.item.description; const iconNode = isCustom ? renderActionIcon(entry.item.icon) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SvgPath, { path: entry.item.iconPath }); const badge = isCustom ? renderBadge(entry.item.badge) : null; return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", { ref: (el) => { refs.current[index] = el; }, type: "button", role: "menuitem", tabIndex: index === focusedIndex ? 0 : -1, className: "rqd-menu-item", onClick: () => handleActivate(index), onFocus: () => setFocusedIndex(index), children: [ iconNode && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-menu-icon", children: iconNode }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-menu-text", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-menu-label", children: label }), description && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-menu-desc", children: description })] }), badge ] }, id); }) }); } //#endregion //#region src/react/FloatingWidget.tsx const translations = { en: require_trigger_css.en, ar: require_trigger_css.ar }; const CORNER_LABEL_KEY = { "top-start": "prefs.topStart", "top-end": "prefs.topEnd", "bottom-start": "prefs.bottomStart", "bottom-end": "prefs.bottomEnd" }; const CORNERS = [ "top-start", "top-end", "bottom-start", "bottom-end" ]; const ICONS = { close: "M19 6.41L17.59 5 12 10.59 6.41 5 5 6.41 10.59 12 5 17.59 6.41 19 12 13.41 17.59 19 19 17.59 13.41 12z", newTicket: "M19 13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z", tickets: "M14 2H6c-1.1 0-2 .9-2 2v16c0 1.1.9 2 2 2h12c1.1 0 2-.9 2-2V8l-6-6zm4 18H6V4h7v5h5v11zM8 15.01l1.41 1.41L11 14.84V19h2v-4.16l1.59 1.59L16 15.01 12.01 11z", track: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z", kb: "M21 5c-1.11-.35-2.33-.5-3.5-.5-1.95 0-4.05.4-5.5 1.5-1.45-1.1-3.55-1.5-5.5-1.5S2.45 4.9 1 6v14.65c0 .25.25.5.5.5.1 0 .15-.05.25-.05C3.1 20.45 5.05 20 6.5 20c1.95 0 4.05.4 5.5 1.5 1.35-.85 3.8-1.5 5.5-1.5 1.65 0 3.35.3 4.75 1.05.1.05.15.05.25.05.25 0 .5-.25.5-.5V6c-.6-.45-1.25-.75-2-1zm0 13.5c-1.1-.35-2.3-.5-3.5-.5-1.7 0-4.15.65-5.5 1.5V8c1.35-.85 3.8-1.5 5.5-1.5 1.2 0 2.4.15 3.5.5v11.5z", back: "M20 11H7.83l5.59-5.59L12 4l-8 8 8 8 1.41-1.41L7.83 13H20v-2z", backRtl: "M4 11h12.17l-5.59-5.59L12 4l8 8-8 8-1.41-1.41L16.17 13H4v-2z", settings: "M19.14 12.94c.04-.3.06-.61.06-.94 0-.32-.02-.64-.07-.94l2.03-1.58a.49.49 0 00.12-.61l-1.92-3.32a.49.49 0 00-.59-.22l-2.39.96c-.5-.38-1.03-.7-1.62-.94l-.36-2.54a.484.484 0 00-.48-.41h-3.84c-.24 0-.43.17-.47.41l-.36 2.54c-.59.24-1.13.57-1.62.94l-2.39-.96a.49.49 0 00-.59.22L2.74 8.87c-.12.21-.08.47.12.61l2.03 1.58c-.05.3-.07.62-.07.94s.02.64.07.94l-2.03 1.58a.49.49 0 00-.12.61l1.92 3.32c.12.22.37.29.59.22l2.39-.96c.5.38 1.03.7 1.62.94l.36 2.54c.05.24.24.41.48.41h3.84c.24 0 .44-.17.47-.41l.36-2.54c.59-.24 1.13-.56 1.62-.94l2.39.96c.22.08.47 0 .59-.22l1.92-3.32c.12-.22.07-.47-.12-.61l-2.01-1.58zM12 15.6A3.6 3.6 0 1115.6 12 3.6 3.6 0 0112 15.6z", switchProject: "M6.99 11L3 15l3.99 4v-3H14v-2H6.99v-3zM21 9l-3.99-4v3H10v2h7.01v3L21 9z", expand: "M5 5h5V3H3v7h2V5zm5 14H5v-5H3v7h7v-2zm4-16v2h5v5h2V3h-7zm5 16h-5v2h7v-7h-2v5z", collapse: "M5 16h3v3h2v-5H5v2zm3-8H5v2h5V5H8v3zm6 11h2v-3h3v-2h-5v5zm2-11V5h-2v5h5V8h-3z", logout: "M17 7l-1.41 1.41L18.17 11H8v2h10.17l-2.58 2.58L17 17l5-5-5-5zM4 5h8V3H4c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h8v-2H4V5z" }; function SvgIcon({ path, size = 20 }) { return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "currentColor", style: { display: "block", flexShrink: 0 }, children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: path }) }); } /** * Resolves the host-supplied `fabIcon` value. A `ReactNode` renders as-is so hosts can drop in * any JSX (SVG, icon-font , ). A string is treated as a preset key or raw SVG path and * routed through the shared resolver so vanilla + React bundles stay visually identical. */ function renderFabIcon(fabIcon, size) { if (fabIcon != null && typeof fabIcon !== "string") { if ((0, react.isValidElement)(fabIcon) || Array.isArray(fabIcon) || typeof fabIcon === "number") return fabIcon; } return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SvgIcon, { path: require_trigger_css.resolveFabIconPath(typeof fabIcon === "string" ? fabIcon : void 0), size }); } function buildPanelClassName(args) { const parts = ["rqd-panel"]; if (args.mode === "popover") parts.push(args.posClass); else if (args.mode === "side-sheet") parts.push("rqd-mode-side-sheet", `rqd-side-${args.side}`); else parts.push("rqd-mode-bottom-sheet"); if (args.contained) parts.push("rqd-contained"); if (!args.isOpen) parts.push("rqd-hidden"); if (args.isExpanded && args.mode === "popover") parts.push("rqd-expanded"); if (args.isRepositioning) parts.push("rqd-repositioning"); return parts.join(" "); } function buildPanelStyle(baseVars, display) { const style = { ...baseVars }; if (display.mode === "side-sheet" && display.width) style["--rqd-sheet-width"] = display.width; if (display.mode === "bottom-sheet" && display.height) style["--rqd-sheet-height"] = display.height; return style; } function FloatingWidget({ position: propPosition = "bottom-end", contained = false, expandedWidth, hideFab: hideFabProp, onTicketCreated, onError }) { const ctx = useReqdeskContext(); const registry = useRegistry(); const hideFab = hideFabProp ?? ctx.hideFab ?? false; const currentDisplayMode = useRegistrySnapshot((s) => s.currentDisplayMode); const [isOpen, setIsOpen] = (0, react.useState)(false); const [isExpanded, setIsExpanded] = (0, react.useState)(false); const [view, setView] = (0, react.useState)("home"); const [selectedTicketId, setSelectedTicketId] = (0, react.useState)(null); const [emailCleared, setEmailCleared] = (0, react.useState)(false); const savedPrefs = require_trigger_css.loadWidgetConfig(ctx.apiKey); const [userLang, setUserLang] = (0, react.useState)(savedPrefs?.language ?? null); const [userThemeMode, setUserThemeMode] = (0, react.useState)(savedPrefs?.theme?.mode ?? null); const [userColor, setUserColor] = (0, react.useState)(savedPrefs?.theme?.primaryColor ?? null); const [position, setPosition] = (0, react.useState)(() => require_trigger_css.normalizePosition(savedPrefs?.position ?? propPosition)); const [isRepositioning, setIsRepositioning] = (0, react.useState)(false); const repositionTimer = (0, react.useRef)(null); (0, react.useEffect)(() => { if (!isExpanded) return; const onKey = (e) => { if (e.key === "Escape") setIsExpanded(false); }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [isExpanded]); (0, react.useEffect)(() => { if (!isOpen) return; if (currentDisplayMode.mode === "popover") return; const onKey = (e) => { if (e.key === "Escape") { setIsOpen(false); registry.close(); } }; window.addEventListener("keydown", onKey); return () => window.removeEventListener("keydown", onKey); }, [ isOpen, currentDisplayMode.mode, registry ]); const registryIsOpen = useRegistrySnapshot((s) => s.isOpen); (0, react.useEffect)(() => { if (registryIsOpen && !isOpen) setIsOpen(true); if (!registryIsOpen && isOpen) setIsOpen(false); }, [registryIsOpen, isOpen]); (0, react.useEffect)(() => () => { if (repositionTimer.current) clearTimeout(repositionTimer.current); }, []); const activeLang = userLang ?? ctx.language ?? "en"; const activeThemeMode = userThemeMode ?? ctx.theme?.mode ?? "auto"; const activeColor = userColor || ctx.theme?.primaryColor || "#0F5E56"; const activeTheme = { ...ctx.theme, mode: activeThemeMode, primaryColor: activeColor }; const t = (0, react.useCallback)((key) => { if (ctx.translations?.[key]) return ctx.translations[key]; return (translations[activeLang] ?? translations.en)[key] ?? key; }, [activeLang, ctx.translations]); const isRtl = activeLang === "ar"; const cssVars = { ...require_trigger_css.themeToStyle(activeTheme), ...expandedWidth ? { "--rqd-expanded-width": expandedWidth } : {} }; const posClass = `rqd-${position}`; const sideClass = `rqd-fab-side-${require_trigger_css.positionSide(position)}`; const repositionClass = isRepositioning ? " rqd-repositioning" : ""; const containedClass = contained ? " rqd-contained" : ""; const brandName = ctx.theme?.brandName; const brandLogo = ctx.theme?.logo; const hideBranding = ctx.theme?.hideBranding === true; const fabLabel = brandName ?? t("widget.title"); const projectId = ctx.projectId; const isWorkspaceKey = ctx.apiKey.startsWith("rqd_ws_"); const needsProjectSelection = !ctx.isProjectLoading && ctx.projectId === "_current" && (ctx.isAuthenticated && ctx.projects.length > 1 || isWorkspaceKey); function toggleOpen() { registry.toggle(); if (!isOpen) { setView(needsProjectSelection ? "select-project" : "home"); setSelectedTicketId(null); } } function goHome() { setView("home"); setSelectedTicketId(null); setEmailCleared(false); } function handleLangChange(lang) { setUserLang(lang); require_trigger_css.saveWidgetConfig(ctx.apiKey, { language: lang }); } function handleThemeChange(mode) { setUserThemeMode(mode); require_trigger_css.saveWidgetConfig(ctx.apiKey, { theme: { ...ctx.theme, mode, primaryColor: activeColor } }); } function handleColorChange(color) { setUserColor(color); require_trigger_css.saveWidgetConfig(ctx.apiKey, { theme: { ...ctx.theme, mode: activeThemeMode, primaryColor: color } }); } function handleCornerChange(corner) { if (corner === position) return; if (repositionTimer.current) clearTimeout(repositionTimer.current); setIsRepositioning(true); repositionTimer.current = setTimeout(() => { setPosition(corner); require_trigger_css.saveWidgetConfig(ctx.apiKey, { position: corner }); repositionTimer.current = setTimeout(() => { setIsRepositioning(false); repositionTimer.current = null; }, 20); }, 120); } const activeDisplayMode = currentDisplayMode.mode; const activeSheetSide = currentDisplayMode.side ?? "end"; function handleDisplayModeChange(mode) { if (mode === activeDisplayMode) return; registry.setPreferences({ display: { mode, side: mode === "side-sheet" ? activeSheetSide : void 0 } }); } function handleSheetSideChange(side) { if (side === activeSheetSide) return; registry.setPreferences({ display: { mode: activeDisplayMode, side } }); } const COLOR_PRESETS = [ { color: "#0F5E56", label: "Green" }, { color: "#3b82f6", label: "Blue" }, { color: "#8b5cf6", label: "Purple" }, { color: "#f59e0b", label: "Orange" }, { color: "#ef4444", label: "Red" } ]; function handleClearEmail() { require_trigger_css.clearWidgetEmail(ctx.apiKey); setEmailCleared(true); setTimeout(() => setEmailCleared(false), 2e3); } function openTicketDetail(ticketId) { setSelectedTicketId(ticketId); setView("ticket-detail"); } const currentProjectName = ctx.projects.find((p) => p.id === ctx.projectId)?.name; const menuItems = [ { key: "new-ticket", icon: ICONS.newTicket, label: t("menu.newTicket"), desc: t("menu.newTicketDesc") }, { key: "my-tickets", icon: ICONS.tickets, label: t("menu.myTickets"), desc: t("menu.myTicketsDesc") }, { key: "track", icon: ICONS.track, label: t("menu.trackTicket"), desc: t("menu.trackTicketDesc") }, { key: "preferences", icon: ICONS.settings, label: t("menu.preferences"), desc: t("menu.preferencesDesc") } ]; if (ctx.isMultiProject) menuItems.splice(-1, 0, { key: "select-project", icon: ICONS.switchProject, label: t("project.switch"), desc: currentProjectName ?? t("project.switchDesc") }); function renderProjectPicker() { if (ctx.isProjectLoading) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-loading", children: t("project.loading") }); if (isWorkspaceKey && !ctx.isAuthenticated) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-placeholder", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("auth.loginRequired") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { className: "rqd-primary-btn", onClick: () => login(), children: t("auth.login") })] }); if (ctx.projects.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-placeholder", children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("project.noProjects") }) }); return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-menu", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { style: { padding: "0 16px 8px", margin: 0, fontSize: 13, opacity: .7 }, children: t("project.select") }), ctx.projects.map((project) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", { className: `rqd-menu-item${project.id === ctx.projectId ? " rqd-active" : ""}`, onClick: () => { ctx.selectProject(project.id); setView("home"); }, children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-menu-icon", children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SvgIcon, { path: ICONS.switchProject, size: 22 }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-menu-text", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-menu-label", children: project.name }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-menu-desc", children: project.slug })] })] }, project.id))] }); } function renderViewContent() { if (needsProjectSelection && view !== "select-project") return renderProjectPicker(); switch (view) { case "home": { const builtIns = menuItems.map((item) => ({ id: item.key, iconPath: item.icon, label: item.label, description: item.desc, onActivate: () => setView(item.key) })); return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [ctx.isMultiProject && currentProjectName && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-project-badge", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SvgIcon, { path: ICONS.switchProject, size: 14 }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: currentProjectName })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MenuList, { builtIns })] }); } case "select-project": return renderProjectPicker(); case "new-ticket": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SubmitTicketView$1, { projectId, onSuccess: (result) => { onTicketCreated?.(result); }, onError, isAuthenticated: ctx.isAuthenticated, userEmail: ctx.userEmail }); case "my-tickets": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(MyTicketsView$1, { projectId, onSelectTicket: openTicketDetail, isAuthenticated: ctx.isAuthenticated, userEmail: ctx.userEmail }); case "ticket-detail": if (!selectedTicketId) return null; return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TicketDetailView$1, { ticketId: selectedTicketId, onBack: () => setView("my-tickets") }); case "track": return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(TrackTicketView$1, { onTrackSuccess: openTicketDetail }); case "kb": return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-placeholder", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SvgIcon, { path: ICONS.kb, size: 40 }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", { children: t("menu.kbPlaceholder") })] }); case "preferences": return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-prefs", children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-prefs-group", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-prefs-label", children: t("prefs.language") }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-prefs-options", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { className: `rqd-prefs-option${activeLang === "en" ? " rqd-active" : ""}`, onClick: () => handleLangChange("en"), children: "English" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { className: `rqd-prefs-option${activeLang === "ar" ? " rqd-active" : ""}`, onClick: () => handleLangChange("ar"), children: "العربية" })] })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-prefs-group", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-prefs-label", children: t("prefs.theme") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-prefs-options", children: [ "light", "dark", "auto" ].map((mode) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { className: `rqd-prefs-option${activeThemeMode === mode ? " rqd-active" : ""}`, onClick: () => handleThemeChange(mode), children: t(`prefs.${mode}`) }, mode)) })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-prefs-group", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-prefs-label", children: t("prefs.accentColor") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-color-presets", children: COLOR_PRESETS.map((preset) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { className: `rqd-color-preset${activeColor.toLowerCase() === preset.color.toLowerCase() ? " rqd-active" : ""}`, style: { background: preset.color, color: preset.color }, onClick: () => handleColorChange(preset.color), "aria-label": preset.label, title: preset.label }, preset.color)) })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-prefs-group", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-prefs-label", children: t("prefs.position") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-corner-picker", children: CORNERS.map((corner) => { const label = t(CORNER_LABEL_KEY[corner]); return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { type: "button", className: `rqd-corner-tile rqd-${corner}${position === corner ? " rqd-active" : ""}`, onClick: () => handleCornerChange(corner), "aria-label": label, title: label, "aria-pressed": position === corner }, corner); }) })] }), !ctx.hideDisplayModePicker && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-prefs-group", children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-prefs-label", children: t("prefs.displayMode") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-prefs-options", children: [ "popover", "side-sheet", "bottom-sheet" ].map((mode) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { type: "button", className: `rqd-prefs-option${activeDisplayMode === mode ? " rqd-active" : ""}`, onClick: () => handleDisplayModeChange(mode), "aria-pressed": activeDisplayMode === mode, children: t(`prefs.display.${mode}`) }, mode)) }), activeDisplayMode === "side-sheet" && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-prefs-label", style: { marginTop: 8, fontSize: 12 }, children: t("prefs.sheetSide") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-prefs-options", children: ["start", "end"].map((side) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { type: "button", className: `rqd-prefs-option${activeSheetSide === side ? " rqd-active" : ""}`, onClick: () => handleSheetSideChange(side), "aria-pressed": activeSheetSide === side, children: t(`prefs.side.${side}`) }, side)) })] }) ] }), require_trigger_css.loadWidgetEmail(ctx.apiKey) && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-prefs-group", children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { className: "rqd-btn rqd-btn-secondary", onClick: handleClearEmail, style: { width: "auto", padding: "8px 16px" }, children: emailCleared ? t("prefs.emailCleared") : t("prefs.clearEmail") }) }), registry.hasInitialPreferences() && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-prefs-group", children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { className: "rqd-btn rqd-btn-secondary", onClick: () => { if (typeof window !== "undefined" && window.confirm?.(t("prefs.resetConfirm")) === false) return; registry.resetToInitialPreferences(); const next = registry.getPreferences(); if (next.language) setUserLang(next.language); if (next.themeMode) setUserThemeMode(next.themeMode); if (next.primaryColor) setUserColor(next.primaryColor); if (next.position) setPosition(next.position); }, style: { width: "auto", padding: "8px 16px" }, children: t("prefs.resetToDefaults") }) }) ] }); } } const viewTitle = { home: brandName ?? t("widget.title"), "select-project": t("project.select"), "new-ticket": t("widget.newTicket"), "my-tickets": t("menu.myTickets"), "ticket-detail": t("menu.myTickets"), track: t("widget.trackTicket"), kb: t("menu.knowledgeBase"), preferences: t("prefs.title") }; const canGoBack = view !== "home"; const goBackTarget = view === "ticket-detail" ? "my-tickets" : "home"; return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ShadowRoot, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { style: cssVars, ...isRtl ? { dir: "rtl" } : {}, children: [ !hideFab && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", { className: `rqd-fab ${posClass}${containedClass}${!isOpen ? ` rqd-fab-labeled ${sideClass}` : ""}${repositionClass}`, style: cssVars, onClick: toggleOpen, "aria-label": isOpen ? t("widget.close") : fabLabel, children: [isOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SvgIcon, { path: ICONS.close, size: 24 }) : renderFabIcon(ctx.fabIcon, 24), !isOpen && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-fab-label", "aria-hidden": "true", children: fabLabel })] }), isOpen && isExpanded && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-backdrop", onClick: () => setIsExpanded(false), "aria-hidden": "true" }), isOpen && currentDisplayMode.mode !== "popover" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { className: "rqd-backdrop-sheet", "aria-hidden": "true", onClick: () => { if (currentDisplayMode.dismissOnBackdrop !== false) { setIsOpen(false); registry.close(); } } }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: buildPanelClassName({ mode: currentDisplayMode.mode, side: currentDisplayMode.side, posClass, contained, isOpen, isExpanded, isRepositioning }), style: buildPanelStyle(cssVars, currentDisplayMode), children: [ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-header", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-header-brand", children: [ canGoBack && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { className: "rqd-header-close", onClick: () => goBackTarget === "home" ? goHome() : setView(goBackTarget), "aria-label": t("tracker.back"), children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SvgIcon, { path: isRtl ? ICONS.backRtl : ICONS.back, size: 18 }) }), view === "home" && brandLogo && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", { src: brandLogo, alt: "", className: "rqd-header-logo" }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-header-title", children: viewTitle[view] }) ] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { style: { display: "flex", alignItems: "center", gap: 4 }, children: [ (ctx.auth || isAuthConfigured()) && view === "home" && ctx.serverAllowedModes.includes("sso") && (ctx.isAuthenticated ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-auth-user", title: ctx.userEmail ?? ctx.userName, children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-auth-user-name", children: ctx.userName ?? ctx.userEmail }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { className: "rqd-header-close", onClick: () => logout(), "aria-label": t("auth.logout"), title: t("auth.logout"), children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SvgIcon, { path: ICONS.logout, size: 16 }) })] }) : ctx.resolvedIdentityMode.kind !== "signed" && ctx.resolvedIdentityMode.kind !== "unsigned" && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { className: "rqd-auth-btn", onClick: () => login(), "aria-label": t("auth.login"), children: t("auth.login") })), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { className: "rqd-header-close", onClick: () => setIsExpanded((v) => !v), "aria-label": t(isExpanded ? "widget.collapse" : "widget.expand"), title: t(isExpanded ? "widget.collapse" : "widget.expand"), children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SvgIcon, { path: isExpanded ? ICONS.collapse : ICONS.expand, size: 18 }) }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", { className: "rqd-header-close", onClick: toggleOpen, "aria-label": t("widget.close"), children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SvgIcon, { path: ICONS.close, size: 18 }) }) ] })] }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-body", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(NotificationStack, { dismissLabel: t("notification.dismiss") }), renderViewContent()] }), !hideBranding && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { className: "rqd-footer", children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: t("branding.poweredBy") }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("a", { href: "https://reqdesk.com", target: "_blank", rel: "noopener noreferrer", children: "Reqdesk" })] }) ] }) ] }) }); } //#endregion //#region src/react/useReqdeskTrigger.ts function applyDisplayOverride(setDisplayMode, display) { if (!display) return; if (typeof display === "string") setDisplayMode(display); else if (display.mode) setDisplayMode(display.mode, display.side); } function useReqdeskTrigger(opts = {}) { const registry = useRegistry(); const isOpen = useRegistrySnapshot((s) => s.isOpen); const controlsId = (0, react.useId)(); const targetRef = opts.target; const displayRef = opts.display; return { onClick: (0, react.useCallback)((e) => { if (e?.currentTarget) registry.recordActivator(e.currentTarget); applyDisplayOverride(registry.setDisplayMode, displayRef); if (!targetRef || targetRef === "menu") registry.openMenu(); else registry.openAction(targetRef.actionId); }, [ registry, targetRef, displayRef ]), isOpen, ariaProps: (0, react.useMemo)(() => ({ "aria-haspopup": "menu", "aria-expanded": isOpen, "aria-controls": `rqd-panel-${controlsId}` }), [isOpen, controlsId]) }; } //#endregion //#region src/react/Trigger.tsx const CHAT_PATH = "M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2zm0 14H6l-2 2V4h16v12z"; function DefaultChatIcon() { return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", { width: 18, height: 18, viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: CHAT_PATH }) }); } function renderIcon(icon) { if (icon === null) return null; if (icon === void 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(DefaultChatIcon, {}); if (typeof icon === "string") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", { width: 18, height: 18, viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: icon }) }); if (typeof icon === "object" && "kind" in icon) { if (icon.kind === "path") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", { width: 18, height: 18, viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", { d: icon.path }) }); if (icon.kind === "url") return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("img", { src: icon.src, width: 18, height: 18, alt: "", "aria-hidden": "true" }); return null; } return icon; } function ReqdeskTrigger({ variant = "pill", icon, className, style, children, render, target, display }) { (0, react.useEffect)(() => { require_trigger_css.injectTriggerStyles(); }, []); const { onClick, isOpen, ariaProps } = useReqdeskTrigger({ target, display }); if (render) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(react_jsx_runtime.Fragment, { children: render({ onClick, isOpen, ariaProps }) }); const classes = `rqd-trigger rqd-trigger--${variant}${className ? ` ${className}` : ""}`; const iconNode = renderIcon(icon); const labelNode = variant === "icon" ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "rqd-trigger-label", children: children ?? "Open support" }) : children ?? null; return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", { type: "button", className: classes, style, onClick, ...ariaProps, children: [iconNode, labelNode] }); } //#endregion exports.FieldError = FieldError; exports.FloatingWidget = FloatingWidget; exports.FormErrorProvider = FormErrorProvider; exports.FormFieldGroup = FormFieldGroup; Object.defineProperty(exports, "MyTicketsView", { enumerable: true, get: function() { return _reqdesk_sdk_react.MyTicketsView; } }); exports.NotificationProvider = NotificationProvider; exports.NotificationStack = NotificationStack; exports.RegistryProvider = RegistryProvider; exports.ReqdeskProvider = ReqdeskProvider; exports.ReqdeskTrigger = ReqdeskTrigger; exports.ShadowRoot = ShadowRoot; Object.defineProperty(exports, "SubmitTicketView", { enumerable: true, get: function() { return _reqdesk_sdk_react.SubmitTicketView; } }); exports.SupportPortal = SupportPortal; Object.defineProperty(exports, "TicketDetailView", { enumerable: true, get: function() { return _reqdesk_sdk_react.TicketDetailView; } }); exports.TicketForm = TicketForm; Object.defineProperty(exports, "TrackTicketView", { enumerable: true, get: function() { return _reqdesk_sdk_react.TrackTicketView; } }); exports.WidgetFetchError = require_trigger_css.WidgetFetchError; exports.getWidgetDefaults = require_trigger_css.loadWidgetConfig; exports.useFieldError = useFieldError; exports.useNotify = useNotify; exports.useRegistry = useRegistry; exports.useRegistrySnapshot = useRegistrySnapshot; exports.useReqdesk = useReqdesk; exports.useReqdeskTrigger = useReqdeskTrigger;