@burdenoff/microfe-devportal 2026.1013.1 → 2026.1014.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. package/dist/devportal/components/FormField.d.ts.map +1 -1
  2. package/dist/devportal/components/FormField.js +1 -1
  3. package/dist/devportal/components/FormField.js.map +1 -1
  4. package/dist/devportal/pages/AnalyticsDashboardPage.js +2 -2
  5. package/dist/devportal/pages/AnalyticsDashboardPage.js.map +1 -1
  6. package/dist/devportal/pages/ApiKeysPage.js +1 -1
  7. package/dist/devportal/pages/ApiKeysPage.js.map +1 -1
  8. package/dist/devportal/pages/AppLogsPage.js +1 -1
  9. package/dist/devportal/pages/AppLogsPage.js.map +1 -1
  10. package/dist/devportal/pages/ApplicationDetailPage.js +1 -1
  11. package/dist/devportal/pages/ApplicationDetailPage.js.map +1 -1
  12. package/dist/devportal/pages/ApplicationsPage.js +2 -2
  13. package/dist/devportal/pages/ApplicationsPage.js.map +1 -1
  14. package/dist/devportal/pages/CreateWebhookPage.js +1 -1
  15. package/dist/devportal/pages/CreateWebhookPage.js.map +1 -1
  16. package/dist/devportal/pages/OAuthClientsPage.js +1 -1
  17. package/dist/devportal/pages/OAuthClientsPage.js.map +1 -1
  18. package/dist/devportal/pages/OrganizationMembersPage.js +1 -1
  19. package/dist/devportal/pages/OrganizationMembersPage.js.map +1 -1
  20. package/dist/devportal/pages/RevenueDashboardPage.js +1 -1
  21. package/dist/devportal/pages/RevenueDashboardPage.js.map +1 -1
  22. package/dist/devportal/pages/SellerItemsPage.js +1 -1
  23. package/dist/devportal/pages/SellerItemsPage.js.map +1 -1
  24. package/dist/devportal/pages/SellerOrdersPage.js +2 -2
  25. package/dist/devportal/pages/SellerOrdersPage.js.map +1 -1
  26. package/dist/devportal/pages/WebhooksPage.js +1 -1
  27. package/dist/devportal/pages/WebhooksPage.js.map +1 -1
  28. package/dist/generated/global-types.d.ts +338 -8
  29. package/dist/generated/global-types.d.ts.map +1 -1
  30. package/dist/generated/global-types.js.map +1 -1
  31. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"FormField.d.ts","sourceRoot":"","sources":["../../../src/devportal/components/FormField.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,EAAE,EACF,mBAAmB,EACnB,sBAAsB,EACtB,oBAAoB,EACrB,MAAM,OAAO,CAAC;AAIf,UAAU,cAAc;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,UAAU,eACR,SACE,cAAc,EACd,IAAI,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,EAAE,WAAW,CAAC;IAC1D,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,UAAU,GAAG,QAAQ,GAAG,KAAK,GAAG,KAAK,CAAC;CACjE;AAED,UAAU,kBACR,SACE,cAAc,EACd,IAAI,CAAC,sBAAsB,CAAC,mBAAmB,CAAC,EAAE,WAAW,CAAC;CAAG;AAErE,UAAU,gBACR,SACE,cAAc,EACd,IAAI,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,WAAW,CAAC;IAC5D,OAAO,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,UAAU,kBACR,SACE,cAAc,EACd,IAAI,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,EAAE,WAAW,GAAG,MAAM,CAAC;IACnE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,eAAO,MAAM,UAAU,EAAE,EAAE,CAAC,eAAe,CAiC1C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,aAAa,EAAE,EAAE,CAAC,kBAAkB,CAiChD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,WAAW,EAAE,EAAE,CAAC,gBAAgB,CA0C5C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,aAAa,EAAE,EAAE,CAAC,kBAAkB,CAgChD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,aAAa,EAAE,EAAE,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACvE,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CA2BA,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,QAAQ,EAAE,EAAE,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAoDA,CAAC"}
1
+ {"version":3,"file":"FormField.d.ts","sourceRoot":"","sources":["../../../src/devportal/components/FormField.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,EAAE,EACF,mBAAmB,EACnB,sBAAsB,EACtB,oBAAoB,EACrB,MAAM,OAAO,CAAC;AAIf,UAAU,cAAc;IACtB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,UAAU,eACR,SACE,cAAc,EACd,IAAI,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,EAAE,WAAW,CAAC;IAC1D,IAAI,CAAC,EAAE,MAAM,GAAG,OAAO,GAAG,UAAU,GAAG,QAAQ,GAAG,KAAK,GAAG,KAAK,CAAC;CACjE;AAED,UAAU,kBACR,SACE,cAAc,EACd,IAAI,CAAC,sBAAsB,CAAC,mBAAmB,CAAC,EAAE,WAAW,CAAC;CAAG;AAErE,UAAU,gBACR,SACE,cAAc,EACd,IAAI,CAAC,oBAAoB,CAAC,iBAAiB,CAAC,EAAE,WAAW,CAAC;IAC5D,OAAO,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,UAAU,kBACR,SACE,cAAc,EACd,IAAI,CAAC,mBAAmB,CAAC,gBAAgB,CAAC,EAAE,WAAW,GAAG,MAAM,CAAC;IACnE,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;GAEG;AACH,eAAO,MAAM,UAAU,EAAE,EAAE,CAAC,eAAe,CAiC1C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,aAAa,EAAE,EAAE,CAAC,kBAAkB,CAiChD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,WAAW,EAAE,EAAE,CAAC,gBAAgB,CA2C5C,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,aAAa,EAAE,EAAE,CAAC,kBAAkB,CAgChD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,aAAa,EAAE,EAAE,CAAC;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACvE,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACrC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CA2BA,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,QAAQ,EAAE,EAAE,CAAC;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,QAAQ,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,IAAI,CAAC;IACrC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAoDA,CAAC"}
@@ -75,7 +75,7 @@ var i = ({ label: e, error: i, hint: a, required: o, className: s, type: c = "te
75
75
  }),
76
76
  /* @__PURE__ */ r("select", {
77
77
  id: h,
78
- className: t("w-full rounded-md border bg-bg-surface px-3 py-2 text-sm", "focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent", a && "border-status-error-bg focus:ring-status-error-bg"),
78
+ className: t("w-full rounded-md border bg-bg-surface px-3 py-2 text-sm", "focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent", "scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary", a && "border-status-error-bg focus:ring-status-error-bg"),
79
79
  ...f,
80
80
  children: [/* @__PURE__ */ n("option", {
81
81
  value: "",
@@ -1 +1 @@
1
- {"version":3,"file":"FormField.js","names":[],"sources":["../../../src/devportal/components/FormField.tsx"],"sourcesContent":["import type {\n FC,\n InputHTMLAttributes,\n TextareaHTMLAttributes,\n SelectHTMLAttributes,\n} from \"react\";\nimport { cn } from \"../utils/cn\";\nimport { useTr } from \"../utils/i18n\";\n\ninterface BaseFieldProps {\n label: string;\n error?: string;\n hint?: string;\n required?: boolean;\n className?: string;\n}\n\ninterface InputFieldProps\n extends\n BaseFieldProps,\n Omit<InputHTMLAttributes<HTMLInputElement>, \"className\"> {\n type?: \"text\" | \"email\" | \"password\" | \"number\" | \"url\" | \"tel\";\n}\n\ninterface TextareaFieldProps\n extends\n BaseFieldProps,\n Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, \"className\"> {}\n\ninterface SelectFieldProps\n extends\n BaseFieldProps,\n Omit<SelectHTMLAttributes<HTMLSelectElement>, \"className\"> {\n options: Array<{ value: string; label: string }>;\n placeholder?: string;\n}\n\ninterface CheckboxFieldProps\n extends\n BaseFieldProps,\n Omit<InputHTMLAttributes<HTMLInputElement>, \"className\" | \"type\"> {\n description?: string;\n}\n\n/**\n * Text input field with label and error handling\n */\nexport const InputField: FC<InputFieldProps> = ({\n label,\n error,\n hint,\n required,\n className,\n type = \"text\",\n id,\n ...props\n}) => {\n const fieldId = id || label.toLowerCase().replace(/\\s+/g, \"-\");\n\n return (\n <div className={cn(\"space-y-1\", className)}>\n <label htmlFor={fieldId} className=\"block text-sm font-medium\">\n {label}\n {required && <span className=\"text-status-error-text ml-1\">*</span>}\n </label>\n <input\n id={fieldId}\n type={type}\n className={cn(\n \"w-full rounded-md border bg-bg-surface px-3 py-2 text-sm\",\n \"focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent\",\n \"placeholder:text-text-secondary\",\n error && \"border-status-error-bg focus:ring-status-error-bg\",\n )}\n {...props}\n />\n {hint && !error && <p className=\"text-xs text-text-secondary\">{hint}</p>}\n {error && <p className=\"text-xs text-status-error-text\">{error}</p>}\n </div>\n );\n};\n\n/**\n * Textarea field with label and error handling\n */\nexport const TextareaField: FC<TextareaFieldProps> = ({\n label,\n error,\n hint,\n required,\n className,\n id,\n rows = 4,\n ...props\n}) => {\n const fieldId = id || label.toLowerCase().replace(/\\s+/g, \"-\");\n\n return (\n <div className={cn(\"space-y-1\", className)}>\n <label htmlFor={fieldId} className=\"block text-sm font-medium\">\n {label}\n {required && <span className=\"text-status-error-text ml-1\">*</span>}\n </label>\n <textarea\n id={fieldId}\n rows={rows}\n className={cn(\n \"w-full rounded-md border bg-bg-surface px-3 py-2 text-sm\",\n \"focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent\",\n \"placeholder:text-text-secondary resize-none\",\n error && \"border-status-error-bg focus:ring-status-error-bg\",\n )}\n {...props}\n />\n {hint && !error && <p className=\"text-xs text-text-secondary\">{hint}</p>}\n {error && <p className=\"text-xs text-status-error-text\">{error}</p>}\n </div>\n );\n};\n\n/**\n * Select field with label and error handling\n */\nexport const SelectField: FC<SelectFieldProps> = ({\n label,\n error,\n hint,\n required,\n className,\n options,\n placeholder,\n id,\n ...props\n}) => {\n const tr = useTr();\n const resolvedPlaceholder =\n placeholder ?? tr(\"devportal.selectAnOption\", \"Select an option\");\n const fieldId = id || label.toLowerCase().replace(/\\s+/g, \"-\");\n\n return (\n <div className={cn(\"space-y-1\", className)}>\n <label htmlFor={fieldId} className=\"block text-sm font-medium\">\n {label}\n {required && <span className=\"text-status-error-text ml-1\">*</span>}\n </label>\n <select\n id={fieldId}\n className={cn(\n \"w-full rounded-md border bg-bg-surface px-3 py-2 text-sm\",\n \"focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent\",\n error && \"border-status-error-bg focus:ring-status-error-bg\",\n )}\n {...props}\n >\n <option value=\"\">{resolvedPlaceholder}</option>\n {options.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n {hint && !error && <p className=\"text-xs text-text-secondary\">{hint}</p>}\n {error && <p className=\"text-xs text-status-error-text\">{error}</p>}\n </div>\n );\n};\n\n/**\n * Checkbox field with label and description\n */\nexport const CheckboxField: FC<CheckboxFieldProps> = ({\n label,\n description,\n error,\n className,\n id,\n ...props\n}) => {\n const fieldId = id || label.toLowerCase().replace(/\\s+/g, \"-\");\n\n return (\n <div className={cn(\"flex items-start gap-3\", className)}>\n <input\n id={fieldId}\n type=\"checkbox\"\n className={cn(\n \"mt-1 h-4 w-4 rounded border-border-default text-primary\",\n \"focus:ring-primary\",\n )}\n {...props}\n />\n <div>\n <label htmlFor={fieldId} className=\"text-sm font-medium cursor-pointer\">\n {label}\n </label>\n {description && (\n <p className=\"text-xs text-text-secondary\">{description}</p>\n )}\n {error && <p className=\"text-xs text-status-error-text\">{error}</p>}\n </div>\n </div>\n );\n};\n\n/**\n * Multi-select checkbox group\n */\nexport const CheckboxGroup: FC<{\n label: string;\n options: Array<{ value: string; label: string; description?: string }>;\n value: string[];\n onChange: (values: string[]) => void;\n error?: string;\n hint?: string;\n className?: string;\n}> = ({ label, options, value, onChange, error, hint, className }) => {\n const handleChange = (optionValue: string, checked: boolean) => {\n if (checked) {\n onChange([...value, optionValue]);\n } else {\n onChange(value.filter((v) => v !== optionValue));\n }\n };\n\n return (\n <div className={cn(\"space-y-2\", className)}>\n <label className=\"block text-sm font-medium\">{label}</label>\n <div className=\"space-y-2\">\n {options.map((option) => (\n <CheckboxField\n key={option.value}\n label={option.label}\n description={option.description}\n checked={value.includes(option.value)}\n onChange={(e) => handleChange(option.value, e.target.checked)}\n />\n ))}\n </div>\n {hint && !error && <p className=\"text-xs text-text-secondary\">{hint}</p>}\n {error && <p className=\"text-xs text-status-error-text\">{error}</p>}\n </div>\n );\n};\n\n/**\n * Tag input for multiple values\n */\nexport const TagInput: FC<{\n label: string;\n value: string[];\n onChange: (values: string[]) => void;\n placeholder?: string;\n error?: string;\n hint?: string;\n className?: string;\n}> = ({ label, value, onChange, placeholder, error, hint, className }) => {\n const tr = useTr();\n const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (e.key === \"Enter\" || e.key === \",\") {\n e.preventDefault();\n const input = e.currentTarget;\n const newValue = input.value.trim();\n if (newValue && !value.includes(newValue)) {\n onChange([...value, newValue]);\n input.value = \"\";\n }\n }\n };\n\n const removeTag = (tagToRemove: string) => {\n onChange(value.filter((tag) => tag !== tagToRemove));\n };\n\n return (\n <div className={cn(\"space-y-1\", className)}>\n <label className=\"block text-sm font-medium\">{label}</label>\n <div className=\"flex flex-wrap gap-2 rounded-md border bg-bg-surface p-2\">\n {value.map((tag) => (\n <span\n key={tag}\n className=\"inline-flex items-center gap-1 rounded-full bg-action-primary-bg/10 px-2 py-1 text-xs\"\n >\n {tag}\n <button\n type=\"button\"\n onClick={() => removeTag(tag)}\n className=\"hover:text-status-error-text\"\n >\n &times;\n </button>\n </span>\n ))}\n <input\n type=\"text\"\n placeholder={\n value.length === 0\n ? placeholder\n : tr(\"devportal.addMore\", \"Add more...\")\n }\n onKeyDown={handleKeyDown}\n className=\"flex-1 min-w-[100px] bg-transparent text-sm outline-none\"\n />\n </div>\n {hint && !error && <p className=\"text-xs text-text-secondary\">{hint}</p>}\n {error && <p className=\"text-xs text-status-error-text\">{error}</p>}\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"FormField.js","names":[],"sources":["../../../src/devportal/components/FormField.tsx"],"sourcesContent":["import type {\n FC,\n InputHTMLAttributes,\n TextareaHTMLAttributes,\n SelectHTMLAttributes,\n} from \"react\";\nimport { cn } from \"../utils/cn\";\nimport { useTr } from \"../utils/i18n\";\n\ninterface BaseFieldProps {\n label: string;\n error?: string;\n hint?: string;\n required?: boolean;\n className?: string;\n}\n\ninterface InputFieldProps\n extends\n BaseFieldProps,\n Omit<InputHTMLAttributes<HTMLInputElement>, \"className\"> {\n type?: \"text\" | \"email\" | \"password\" | \"number\" | \"url\" | \"tel\";\n}\n\ninterface TextareaFieldProps\n extends\n BaseFieldProps,\n Omit<TextareaHTMLAttributes<HTMLTextAreaElement>, \"className\"> {}\n\ninterface SelectFieldProps\n extends\n BaseFieldProps,\n Omit<SelectHTMLAttributes<HTMLSelectElement>, \"className\"> {\n options: Array<{ value: string; label: string }>;\n placeholder?: string;\n}\n\ninterface CheckboxFieldProps\n extends\n BaseFieldProps,\n Omit<InputHTMLAttributes<HTMLInputElement>, \"className\" | \"type\"> {\n description?: string;\n}\n\n/**\n * Text input field with label and error handling\n */\nexport const InputField: FC<InputFieldProps> = ({\n label,\n error,\n hint,\n required,\n className,\n type = \"text\",\n id,\n ...props\n}) => {\n const fieldId = id || label.toLowerCase().replace(/\\s+/g, \"-\");\n\n return (\n <div className={cn(\"space-y-1\", className)}>\n <label htmlFor={fieldId} className=\"block text-sm font-medium\">\n {label}\n {required && <span className=\"text-status-error-text ml-1\">*</span>}\n </label>\n <input\n id={fieldId}\n type={type}\n className={cn(\n \"w-full rounded-md border bg-bg-surface px-3 py-2 text-sm\",\n \"focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent\",\n \"placeholder:text-text-secondary\",\n error && \"border-status-error-bg focus:ring-status-error-bg\",\n )}\n {...props}\n />\n {hint && !error && <p className=\"text-xs text-text-secondary\">{hint}</p>}\n {error && <p className=\"text-xs text-status-error-text\">{error}</p>}\n </div>\n );\n};\n\n/**\n * Textarea field with label and error handling\n */\nexport const TextareaField: FC<TextareaFieldProps> = ({\n label,\n error,\n hint,\n required,\n className,\n id,\n rows = 4,\n ...props\n}) => {\n const fieldId = id || label.toLowerCase().replace(/\\s+/g, \"-\");\n\n return (\n <div className={cn(\"space-y-1\", className)}>\n <label htmlFor={fieldId} className=\"block text-sm font-medium\">\n {label}\n {required && <span className=\"text-status-error-text ml-1\">*</span>}\n </label>\n <textarea\n id={fieldId}\n rows={rows}\n className={cn(\n \"w-full rounded-md border bg-bg-surface px-3 py-2 text-sm\",\n \"focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent\",\n \"placeholder:text-text-secondary resize-none\",\n error && \"border-status-error-bg focus:ring-status-error-bg\",\n )}\n {...props}\n />\n {hint && !error && <p className=\"text-xs text-text-secondary\">{hint}</p>}\n {error && <p className=\"text-xs text-status-error-text\">{error}</p>}\n </div>\n );\n};\n\n/**\n * Select field with label and error handling\n */\nexport const SelectField: FC<SelectFieldProps> = ({\n label,\n error,\n hint,\n required,\n className,\n options,\n placeholder,\n id,\n ...props\n}) => {\n const tr = useTr();\n const resolvedPlaceholder =\n placeholder ?? tr(\"devportal.selectAnOption\", \"Select an option\");\n const fieldId = id || label.toLowerCase().replace(/\\s+/g, \"-\");\n\n return (\n <div className={cn(\"space-y-1\", className)}>\n <label htmlFor={fieldId} className=\"block text-sm font-medium\">\n {label}\n {required && <span className=\"text-status-error-text ml-1\">*</span>}\n </label>\n <select\n id={fieldId}\n className={cn(\n \"w-full rounded-md border bg-bg-surface px-3 py-2 text-sm\",\n \"focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent\",\n \"scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\",\n error && \"border-status-error-bg focus:ring-status-error-bg\",\n )}\n {...props}\n >\n <option value=\"\">{resolvedPlaceholder}</option>\n {options.map((option) => (\n <option key={option.value} value={option.value}>\n {option.label}\n </option>\n ))}\n </select>\n {hint && !error && <p className=\"text-xs text-text-secondary\">{hint}</p>}\n {error && <p className=\"text-xs text-status-error-text\">{error}</p>}\n </div>\n );\n};\n\n/**\n * Checkbox field with label and description\n */\nexport const CheckboxField: FC<CheckboxFieldProps> = ({\n label,\n description,\n error,\n className,\n id,\n ...props\n}) => {\n const fieldId = id || label.toLowerCase().replace(/\\s+/g, \"-\");\n\n return (\n <div className={cn(\"flex items-start gap-3\", className)}>\n <input\n id={fieldId}\n type=\"checkbox\"\n className={cn(\n \"mt-1 h-4 w-4 rounded border-border-default text-primary\",\n \"focus:ring-primary\",\n )}\n {...props}\n />\n <div>\n <label htmlFor={fieldId} className=\"text-sm font-medium cursor-pointer\">\n {label}\n </label>\n {description && (\n <p className=\"text-xs text-text-secondary\">{description}</p>\n )}\n {error && <p className=\"text-xs text-status-error-text\">{error}</p>}\n </div>\n </div>\n );\n};\n\n/**\n * Multi-select checkbox group\n */\nexport const CheckboxGroup: FC<{\n label: string;\n options: Array<{ value: string; label: string; description?: string }>;\n value: string[];\n onChange: (values: string[]) => void;\n error?: string;\n hint?: string;\n className?: string;\n}> = ({ label, options, value, onChange, error, hint, className }) => {\n const handleChange = (optionValue: string, checked: boolean) => {\n if (checked) {\n onChange([...value, optionValue]);\n } else {\n onChange(value.filter((v) => v !== optionValue));\n }\n };\n\n return (\n <div className={cn(\"space-y-2\", className)}>\n <label className=\"block text-sm font-medium\">{label}</label>\n <div className=\"space-y-2\">\n {options.map((option) => (\n <CheckboxField\n key={option.value}\n label={option.label}\n description={option.description}\n checked={value.includes(option.value)}\n onChange={(e) => handleChange(option.value, e.target.checked)}\n />\n ))}\n </div>\n {hint && !error && <p className=\"text-xs text-text-secondary\">{hint}</p>}\n {error && <p className=\"text-xs text-status-error-text\">{error}</p>}\n </div>\n );\n};\n\n/**\n * Tag input for multiple values\n */\nexport const TagInput: FC<{\n label: string;\n value: string[];\n onChange: (values: string[]) => void;\n placeholder?: string;\n error?: string;\n hint?: string;\n className?: string;\n}> = ({ label, value, onChange, placeholder, error, hint, className }) => {\n const tr = useTr();\n const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (e.key === \"Enter\" || e.key === \",\") {\n e.preventDefault();\n const input = e.currentTarget;\n const newValue = input.value.trim();\n if (newValue && !value.includes(newValue)) {\n onChange([...value, newValue]);\n input.value = \"\";\n }\n }\n };\n\n const removeTag = (tagToRemove: string) => {\n onChange(value.filter((tag) => tag !== tagToRemove));\n };\n\n return (\n <div className={cn(\"space-y-1\", className)}>\n <label className=\"block text-sm font-medium\">{label}</label>\n <div className=\"flex flex-wrap gap-2 rounded-md border bg-bg-surface p-2\">\n {value.map((tag) => (\n <span\n key={tag}\n className=\"inline-flex items-center gap-1 rounded-full bg-action-primary-bg/10 px-2 py-1 text-xs\"\n >\n {tag}\n <button\n type=\"button\"\n onClick={() => removeTag(tag)}\n className=\"hover:text-status-error-text\"\n >\n &times;\n </button>\n </span>\n ))}\n <input\n type=\"text\"\n placeholder={\n value.length === 0\n ? placeholder\n : tr(\"devportal.addMore\", \"Add more...\")\n }\n onKeyDown={handleKeyDown}\n className=\"flex-1 min-w-[100px] bg-transparent text-sm outline-none\"\n />\n </div>\n {hint && !error && <p className=\"text-xs text-text-secondary\">{hint}</p>}\n {error && <p className=\"text-xs text-status-error-text\">{error}</p>}\n </div>\n );\n};\n"],"mappings":"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"}
@@ -131,7 +131,7 @@ var ce = {
131
131
  value: I,
132
132
  onChange: (e) => L(e.target.value),
133
133
  "aria-label": N("devportal.analytics.selectAppAria", "Select application"),
134
- className: "rounded-md border bg-bg-surface px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary",
134
+ className: "rounded-md border bg-bg-surface px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary",
135
135
  children: K.map((e) => /* @__PURE__ */ T("option", {
136
136
  value: e.id,
137
137
  children: e.name
@@ -140,7 +140,7 @@ var ce = {
140
140
  value: P,
141
141
  onChange: (e) => F(e.target.value),
142
142
  "aria-label": N("devportal.analytics.selectRangeAria", "Select time range"),
143
- className: "rounded-md border bg-bg-surface px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary",
143
+ className: "rounded-md border bg-bg-surface px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary",
144
144
  children: [
145
145
  /* @__PURE__ */ T("option", {
146
146
  value: "7d",
@@ -1 +1 @@
1
- {"version":3,"file":"AnalyticsDashboardPage.js","names":[],"sources":["../../../src/devportal/pages/AnalyticsDashboardPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useEffect, useMemo, useState } from \"react\";\nimport { useParams } from \"react-router\";\nimport {\n Activity,\n AlertCircle,\n AlertTriangle,\n BarChart3,\n Clock,\n Loader2,\n Users,\n} from \"lucide-react\";\nimport {\n Bar,\n BarChart,\n CartesianGrid,\n Line,\n LineChart,\n ResponsiveContainer,\n Tooltip,\n XAxis,\n YAxis,\n} from \"recharts\";\nimport { EmphasisPanel, GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport { DataTable } from \"../components/DataTable\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { StatCard, StatCardGrid } from \"../components/StatCard\";\nimport { AppListItem } from \"../components/AppCard\";\nimport type { Application } from \"../types\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport {\n type GetAnalyticsDashboardOptionsQuery,\n getErrorMessage,\n useGetAnalyticsDashboardOptionsQuery,\n useGetApplicationAnalyticsDashboardQuery,\n} from \"../hooks\";\nimport { useTr } from \"../utils/i18n\";\n\ntype TimeRange = \"7d\" | \"30d\" | \"90d\";\n\nconst daysByRange: Record<TimeRange, number> = {\n \"7d\": 7,\n \"30d\": 30,\n \"90d\": 90,\n};\n\nconst chartColors = {\n primary: \"var(--color-chart-1)\",\n secondary: \"var(--color-chart-2)\",\n tertiary: \"var(--color-chart-4)\",\n negative: \"var(--color-chart-negative)\",\n};\n\nexport const AnalyticsDashboardPage: FC = () => {\n const { appId } = useParams<{ appId: string }>();\n const { navigate, basePath, activeOrgId } = useDevportal();\n const tr = useTr();\n const [timeRange, setTimeRange] = useState<TimeRange>(\"30d\");\n const [selectedApplicationId, setSelectedApplicationId] = useState(\n appId || \"\",\n );\n\n const now = useMemo(() => new Date(), []);\n const startDate = useMemo(() => {\n const nextDate = new Date(now);\n nextDate.setDate(nextDate.getDate() - daysByRange[timeRange]);\n return nextDate.toISOString();\n }, [now, timeRange]);\n\n const {\n data: optionsData,\n loading: optionsLoading,\n error: optionsError,\n } = useGetAnalyticsDashboardOptionsQuery({\n skip: !!appId,\n fetchPolicy: \"cache-and-network\",\n });\n\n const { data, loading, error } = useGetApplicationAnalyticsDashboardQuery({\n variables: {\n applicationId: selectedApplicationId || appId || \"\",\n period: \"daily\",\n startDate,\n endDate: now.toISOString(),\n },\n skip: !(selectedApplicationId || appId),\n fetchPolicy: \"cache-and-network\",\n });\n\n const allApplications: Application[] =\n optionsData?.applications?.edges?.map(\n (\n edge: GetAnalyticsDashboardOptionsQuery[\"applications\"][\"edges\"][number],\n ) => edge.node as Application,\n ) ?? [];\n // Scope the app picker to the active org when one is selected\n const applications = activeOrgId\n ? allApplications.filter((a) => a.developerOrganizationId === activeOrgId)\n : allApplications;\n const selectedApplication = data?.application;\n\n useEffect(() => {\n const name = selectedApplication?.name;\n const id = appId || selectedApplicationId;\n if (!id || !name) return;\n const reg = (\n window as unknown as {\n __breadcrumbRegistry?: {\n register: (id: string, name: string) => void;\n unregister: (id: string) => void;\n };\n }\n ).__breadcrumbRegistry;\n reg?.register(id, name);\n return () => reg?.unregister(id);\n }, [appId, selectedApplicationId, selectedApplication?.name]);\n\n const analytics = [...(data?.applicationAnalytics ?? [])].sort(\n (left, right) =>\n new Date(left.date).getTime() - new Date(right.date).getTime(),\n );\n\n const latest = analytics[analytics.length - 1];\n const previous = analytics[analytics.length - 2];\n const chartData = analytics.map((entry) => ({\n date: new Date(entry.date).toLocaleDateString(undefined, {\n month: \"short\",\n day: \"numeric\",\n }),\n apiCalls: entry.apiCalls,\n errors: entry.errors,\n activeUsers: entry.activeUsers,\n newUsers: entry.newUsers,\n sessions: entry.sessions,\n avgResponseTime: entry.avgResponseTime ?? 0,\n }));\n\n const errorRate =\n latest && latest.apiCalls > 0 ? (latest.errors / latest.apiCalls) * 100 : 0;\n const previousErrorRate =\n previous && previous.apiCalls > 0\n ? (previous.errors / previous.apiCalls) * 100\n : 0;\n const responseTrend =\n latest?.avgResponseTime && previous?.avgResponseTime\n ? previous.avgResponseTime - latest.avgResponseTime\n : undefined;\n\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n if (!appId && optionsLoading && !optionsData) {\n return (\n <PageLayout\n title={tr(\"devportal.analytics.title\", \"Analytics\")}\n description={tr(\n \"devportal.analytics.description\",\n \"Explore live application usage and performance metrics\",\n )}\n >\n <div className=\"flex items-center justify-center py-16 text-text-secondary\">\n <Loader2 className=\"h-8 w-8 animate-spin\" />\n </div>\n </PageLayout>\n );\n }\n\n if (!appId && optionsError) {\n return (\n <PageLayout\n title={tr(\"devportal.analytics.title\", \"Analytics\")}\n description={tr(\n \"devportal.analytics.description\",\n \"Explore live application usage and performance metrics\",\n )}\n >\n <EmptyState\n icon={<AlertCircle size={32} className=\"text-status-error-text\" />}\n title={tr(\n \"devportal.analytics.errorLoadApps\",\n \"Unable to load applications\",\n )}\n description={getErrorMessage(optionsError)}\n />\n </PageLayout>\n );\n }\n\n if (!appId && !selectedApplicationId) {\n return (\n <PageLayout\n title={tr(\"devportal.analytics.title\", \"Analytics\")}\n description={tr(\n \"devportal.analytics.selectAppDescription\",\n \"Select an application to review live usage metrics from the public gateway\",\n )}\n >\n {applications.length > 0 ? (\n <div className=\"space-y-2\">\n {applications.map((application) => (\n <AppListItem\n key={application.id}\n app={application}\n onClick={() => setSelectedApplicationId(application.id)}\n />\n ))}\n </div>\n ) : (\n <EmptyState\n illustration=\"empty-data\"\n icon={<BarChart3 size={32} />}\n title={tr(\n \"devportal.analytics.noAppsTitle\",\n \"No applications available\",\n )}\n description={tr(\n \"devportal.analytics.noAppsDescription\",\n \"Create an application before viewing analytics.\",\n )}\n />\n )}\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout\n title={tr(\"devportal.analytics.title\", \"Analytics\")}\n description={tr(\n \"devportal.analytics.description\",\n \"Explore live application usage and performance metrics\",\n )}\n backLink={\n selectedApplicationId ? `/apps/${selectedApplicationId}` : undefined\n }\n backLabel={tr(\n \"devportal.analytics.backToApplication\",\n \"Back to Application\",\n )}\n >\n <EmptyState\n icon={<AlertCircle size={32} className=\"text-status-error-text\" />}\n title={tr(\n \"devportal.analytics.errorLoadAnalytics\",\n \"Unable to load analytics\",\n )}\n description={getErrorMessage(error)}\n />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title={\n appId\n ? tr(\"devportal.analytics.appTitle\", \"Application Analytics\")\n : tr(\"devportal.analytics.title\", \"Analytics\")\n }\n description={\n selectedApplication\n ? `${tr(\"devportal.analytics.liveUsageFor\", \"Live usage and performance for\")} ${selectedApplication.name}`\n : tr(\n \"devportal.analytics.description\",\n \"Explore live application usage and performance metrics\",\n )\n }\n purpose={tr(\n \"devportal.analytics.purpose\",\n \"Analytics shows how your apps are actually used — API call volume, active installs, latency, and error rates over time — so you can spot regressions, plan capacity, and prove value to your users. Pick an app and a time range to drill in.\",\n )}\n backLink={\n appId && selectedApplication\n ? `/apps/${selectedApplication.id}`\n : undefined\n }\n backLabel={tr(\n \"devportal.analytics.backToApplication\",\n \"Back to Application\",\n )}\n actions={\n <div className=\"flex flex-wrap gap-2\">\n {!appId && applications.length > 0 && (\n <select\n value={selectedApplicationId}\n onChange={(event) => setSelectedApplicationId(event.target.value)}\n aria-label={tr(\n \"devportal.analytics.selectAppAria\",\n \"Select application\",\n )}\n className=\"rounded-md border bg-bg-surface px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n {applications.map((application) => (\n <option key={application.id} value={application.id}>\n {application.name}\n </option>\n ))}\n </select>\n )}\n <select\n value={timeRange}\n onChange={(event) => setTimeRange(event.target.value as TimeRange)}\n aria-label={tr(\n \"devportal.analytics.selectRangeAria\",\n \"Select time range\",\n )}\n className=\"rounded-md border bg-bg-surface px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <option value=\"7d\">\n {tr(\"devportal.analytics.range.7d\", \"Last 7 days\")}\n </option>\n <option value=\"30d\">\n {tr(\"devportal.analytics.range.30d\", \"Last 30 days\")}\n </option>\n <option value=\"90d\">\n {tr(\"devportal.analytics.range.90d\", \"Last 90 days\")}\n </option>\n </select>\n </div>\n }\n >\n {loading && !analytics.length ? (\n <div className=\"flex items-center justify-center py-16 text-text-secondary\">\n <Loader2 className=\"h-8 w-8 animate-spin\" />\n </div>\n ) : analytics.length > 0 ? (\n <>\n <EmphasisPanel className=\"mb-6\">\n <StatCardGrid columns={4}>\n <StatCard\n title={tr(\"devportal.analytics.apiCalls\", \"API Calls\")}\n value={(latest?.apiCalls ?? 0).toLocaleString()}\n icon={<Activity size={20} />}\n />\n <StatCard\n title={tr(\"devportal.analytics.activeUsers\", \"Active Users\")}\n value={(latest?.activeUsers ?? 0).toLocaleString()}\n icon={<Users size={20} />}\n />\n <StatCard\n title={tr(\"devportal.analytics.errorRate\", \"Error Rate\")}\n value={`${errorRate.toFixed(2)}%`}\n icon={<AlertTriangle size={20} />}\n trend={\n previous\n ? {\n value: previousErrorRate - errorRate,\n label: tr(\n \"devportal.analytics.vsPrevious\",\n \"vs previous period\",\n ),\n }\n : undefined\n }\n />\n <StatCard\n title={tr(\n \"devportal.analytics.avgResponseTime\",\n \"Avg Response Time\",\n )}\n value={\n latest?.avgResponseTime\n ? `${latest.avgResponseTime}ms`\n : tr(\"devportal.common.na\", \"N/A\")\n }\n icon={<Clock size={20} />}\n trend={\n typeof responseTrend === \"number\"\n ? {\n value: responseTrend,\n label: tr(\n \"devportal.analytics.fasterThanPrevious\",\n \"faster than previous\",\n ),\n }\n : undefined\n }\n />\n </StatCardGrid>\n </EmphasisPanel>\n\n <div className=\"grid gap-6 xl:grid-cols-2\">\n <PageSection\n title={tr(\"devportal.analytics.trafficTrend\", \"Traffic trend\")}\n >\n <GlassCard treatment=\"insight\" className=\"p-4\">\n <ResponsiveContainer width=\"100%\" height={300}>\n <LineChart data={chartData}>\n <CartesianGrid\n strokeDasharray=\"3 3\"\n stroke=\"var(--color-chart-grid)\"\n />\n <XAxis\n dataKey=\"date\"\n stroke=\"var(--color-chart-axis)\"\n fontSize={12}\n />\n <YAxis stroke=\"var(--color-chart-axis)\" fontSize={12} />\n <Tooltip\n contentStyle={{\n backgroundColor: \"var(--color-card)\",\n border: \"1px solid var(--color-border-default)\",\n borderRadius: 12,\n }}\n />\n <Line\n type=\"monotone\"\n dataKey=\"apiCalls\"\n stroke={chartColors.primary}\n strokeWidth={2}\n dot={false}\n />\n <Line\n type=\"monotone\"\n dataKey=\"errors\"\n stroke={chartColors.negative}\n strokeWidth={2}\n dot={false}\n />\n </LineChart>\n </ResponsiveContainer>\n </GlassCard>\n </PageSection>\n\n <PageSection\n title={tr(\"devportal.analytics.userActivity\", \"User activity\")}\n >\n <GlassCard treatment=\"insight\" className=\"p-4\">\n <ResponsiveContainer width=\"100%\" height={300}>\n <BarChart data={chartData}>\n <CartesianGrid\n strokeDasharray=\"3 3\"\n stroke=\"var(--color-chart-grid)\"\n />\n <XAxis\n dataKey=\"date\"\n stroke=\"var(--color-chart-axis)\"\n fontSize={12}\n />\n <YAxis stroke=\"var(--color-chart-axis)\" fontSize={12} />\n <Tooltip\n contentStyle={{\n backgroundColor: \"var(--color-card)\",\n border: \"1px solid var(--color-border-default)\",\n borderRadius: 12,\n }}\n />\n <Bar\n dataKey=\"activeUsers\"\n fill={chartColors.primary}\n radius={[6, 6, 0, 0]}\n />\n <Bar\n dataKey=\"newUsers\"\n fill={chartColors.secondary}\n radius={[6, 6, 0, 0]}\n />\n </BarChart>\n </ResponsiveContainer>\n </GlassCard>\n </PageSection>\n </div>\n\n <PageSection\n title={tr(\"devportal.analytics.dailyBreakdown\", \"Daily breakdown\")}\n >\n <DataTable\n data={analytics}\n keyField=\"id\"\n columns={[\n {\n key: \"date\",\n header: tr(\"devportal.analytics.date\", \"Date\"),\n render: (entry) =>\n new Date(entry.date).toLocaleDateString(undefined, {\n month: \"short\",\n day: \"numeric\",\n year: \"numeric\",\n }),\n },\n {\n key: \"apiCalls\",\n header: tr(\"devportal.analytics.apiCalls\", \"API Calls\"),\n render: (entry) => entry.apiCalls.toLocaleString(),\n },\n {\n key: \"errors\",\n header: tr(\"devportal.analytics.errors\", \"Errors\"),\n render: (entry) => entry.errors.toLocaleString(),\n },\n {\n key: \"activeUsers\",\n header: tr(\"devportal.analytics.activeUsers\", \"Active Users\"),\n render: (entry) => entry.activeUsers.toLocaleString(),\n },\n {\n key: \"newUsers\",\n header: tr(\"devportal.analytics.newUsers\", \"New Users\"),\n render: (entry) => entry.newUsers.toLocaleString(),\n },\n {\n key: \"avgResponseTime\",\n header: tr(\"devportal.analytics.avgResponse\", \"Avg Response\"),\n render: (entry) =>\n entry.avgResponseTime\n ? `${entry.avgResponseTime}ms`\n : tr(\"devportal.common.na\", \"N/A\"),\n },\n {\n key: \"sessions\",\n header: tr(\"devportal.analytics.sessions\", \"Sessions\"),\n render: (entry) => entry.sessions.toLocaleString(),\n },\n ]}\n />\n </PageSection>\n </>\n ) : (\n <EmptyState\n illustration=\"empty-data\"\n icon={<BarChart3 size={32} />}\n title={tr(\n \"devportal.analytics.noAnalyticsTitle\",\n \"No analytics available yet\",\n )}\n description={tr(\n \"devportal.analytics.noAnalyticsDescription\",\n \"Analytics will appear after this application starts receiving traffic through the public gateway.\",\n )}\n action={\n !appId ? (\n <button\n type=\"button\"\n onClick={() => handleNavigate(\"/apps\")}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n >\n {tr(\n \"devportal.analytics.viewApplications\",\n \"View applications\",\n )}\n </button>\n ) : undefined\n }\n />\n )}\n </PageLayout>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"AnalyticsDashboardPage.js","names":[],"sources":["../../../src/devportal/pages/AnalyticsDashboardPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useEffect, useMemo, useState } from \"react\";\nimport { useParams } from \"react-router\";\nimport {\n Activity,\n AlertCircle,\n AlertTriangle,\n BarChart3,\n Clock,\n Loader2,\n Users,\n} from \"lucide-react\";\nimport {\n Bar,\n BarChart,\n CartesianGrid,\n Line,\n LineChart,\n ResponsiveContainer,\n Tooltip,\n XAxis,\n YAxis,\n} from \"recharts\";\nimport { EmphasisPanel, GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport { DataTable } from \"../components/DataTable\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { StatCard, StatCardGrid } from \"../components/StatCard\";\nimport { AppListItem } from \"../components/AppCard\";\nimport type { Application } from \"../types\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport {\n type GetAnalyticsDashboardOptionsQuery,\n getErrorMessage,\n useGetAnalyticsDashboardOptionsQuery,\n useGetApplicationAnalyticsDashboardQuery,\n} from \"../hooks\";\nimport { useTr } from \"../utils/i18n\";\n\ntype TimeRange = \"7d\" | \"30d\" | \"90d\";\n\nconst daysByRange: Record<TimeRange, number> = {\n \"7d\": 7,\n \"30d\": 30,\n \"90d\": 90,\n};\n\nconst chartColors = {\n primary: \"var(--color-chart-1)\",\n secondary: \"var(--color-chart-2)\",\n tertiary: \"var(--color-chart-4)\",\n negative: \"var(--color-chart-negative)\",\n};\n\nexport const AnalyticsDashboardPage: FC = () => {\n const { appId } = useParams<{ appId: string }>();\n const { navigate, basePath, activeOrgId } = useDevportal();\n const tr = useTr();\n const [timeRange, setTimeRange] = useState<TimeRange>(\"30d\");\n const [selectedApplicationId, setSelectedApplicationId] = useState(\n appId || \"\",\n );\n\n const now = useMemo(() => new Date(), []);\n const startDate = useMemo(() => {\n const nextDate = new Date(now);\n nextDate.setDate(nextDate.getDate() - daysByRange[timeRange]);\n return nextDate.toISOString();\n }, [now, timeRange]);\n\n const {\n data: optionsData,\n loading: optionsLoading,\n error: optionsError,\n } = useGetAnalyticsDashboardOptionsQuery({\n skip: !!appId,\n fetchPolicy: \"cache-and-network\",\n });\n\n const { data, loading, error } = useGetApplicationAnalyticsDashboardQuery({\n variables: {\n applicationId: selectedApplicationId || appId || \"\",\n period: \"daily\",\n startDate,\n endDate: now.toISOString(),\n },\n skip: !(selectedApplicationId || appId),\n fetchPolicy: \"cache-and-network\",\n });\n\n const allApplications: Application[] =\n optionsData?.applications?.edges?.map(\n (\n edge: GetAnalyticsDashboardOptionsQuery[\"applications\"][\"edges\"][number],\n ) => edge.node as Application,\n ) ?? [];\n // Scope the app picker to the active org when one is selected\n const applications = activeOrgId\n ? allApplications.filter((a) => a.developerOrganizationId === activeOrgId)\n : allApplications;\n const selectedApplication = data?.application;\n\n useEffect(() => {\n const name = selectedApplication?.name;\n const id = appId || selectedApplicationId;\n if (!id || !name) return;\n const reg = (\n window as unknown as {\n __breadcrumbRegistry?: {\n register: (id: string, name: string) => void;\n unregister: (id: string) => void;\n };\n }\n ).__breadcrumbRegistry;\n reg?.register(id, name);\n return () => reg?.unregister(id);\n }, [appId, selectedApplicationId, selectedApplication?.name]);\n\n const analytics = [...(data?.applicationAnalytics ?? [])].sort(\n (left, right) =>\n new Date(left.date).getTime() - new Date(right.date).getTime(),\n );\n\n const latest = analytics[analytics.length - 1];\n const previous = analytics[analytics.length - 2];\n const chartData = analytics.map((entry) => ({\n date: new Date(entry.date).toLocaleDateString(undefined, {\n month: \"short\",\n day: \"numeric\",\n }),\n apiCalls: entry.apiCalls,\n errors: entry.errors,\n activeUsers: entry.activeUsers,\n newUsers: entry.newUsers,\n sessions: entry.sessions,\n avgResponseTime: entry.avgResponseTime ?? 0,\n }));\n\n const errorRate =\n latest && latest.apiCalls > 0 ? (latest.errors / latest.apiCalls) * 100 : 0;\n const previousErrorRate =\n previous && previous.apiCalls > 0\n ? (previous.errors / previous.apiCalls) * 100\n : 0;\n const responseTrend =\n latest?.avgResponseTime && previous?.avgResponseTime\n ? previous.avgResponseTime - latest.avgResponseTime\n : undefined;\n\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n if (!appId && optionsLoading && !optionsData) {\n return (\n <PageLayout\n title={tr(\"devportal.analytics.title\", \"Analytics\")}\n description={tr(\n \"devportal.analytics.description\",\n \"Explore live application usage and performance metrics\",\n )}\n >\n <div className=\"flex items-center justify-center py-16 text-text-secondary\">\n <Loader2 className=\"h-8 w-8 animate-spin\" />\n </div>\n </PageLayout>\n );\n }\n\n if (!appId && optionsError) {\n return (\n <PageLayout\n title={tr(\"devportal.analytics.title\", \"Analytics\")}\n description={tr(\n \"devportal.analytics.description\",\n \"Explore live application usage and performance metrics\",\n )}\n >\n <EmptyState\n icon={<AlertCircle size={32} className=\"text-status-error-text\" />}\n title={tr(\n \"devportal.analytics.errorLoadApps\",\n \"Unable to load applications\",\n )}\n description={getErrorMessage(optionsError)}\n />\n </PageLayout>\n );\n }\n\n if (!appId && !selectedApplicationId) {\n return (\n <PageLayout\n title={tr(\"devportal.analytics.title\", \"Analytics\")}\n description={tr(\n \"devportal.analytics.selectAppDescription\",\n \"Select an application to review live usage metrics from the public gateway\",\n )}\n >\n {applications.length > 0 ? (\n <div className=\"space-y-2\">\n {applications.map((application) => (\n <AppListItem\n key={application.id}\n app={application}\n onClick={() => setSelectedApplicationId(application.id)}\n />\n ))}\n </div>\n ) : (\n <EmptyState\n illustration=\"empty-data\"\n icon={<BarChart3 size={32} />}\n title={tr(\n \"devportal.analytics.noAppsTitle\",\n \"No applications available\",\n )}\n description={tr(\n \"devportal.analytics.noAppsDescription\",\n \"Create an application before viewing analytics.\",\n )}\n />\n )}\n </PageLayout>\n );\n }\n\n if (error) {\n return (\n <PageLayout\n title={tr(\"devportal.analytics.title\", \"Analytics\")}\n description={tr(\n \"devportal.analytics.description\",\n \"Explore live application usage and performance metrics\",\n )}\n backLink={\n selectedApplicationId ? `/apps/${selectedApplicationId}` : undefined\n }\n backLabel={tr(\n \"devportal.analytics.backToApplication\",\n \"Back to Application\",\n )}\n >\n <EmptyState\n icon={<AlertCircle size={32} className=\"text-status-error-text\" />}\n title={tr(\n \"devportal.analytics.errorLoadAnalytics\",\n \"Unable to load analytics\",\n )}\n description={getErrorMessage(error)}\n />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title={\n appId\n ? tr(\"devportal.analytics.appTitle\", \"Application Analytics\")\n : tr(\"devportal.analytics.title\", \"Analytics\")\n }\n description={\n selectedApplication\n ? `${tr(\"devportal.analytics.liveUsageFor\", \"Live usage and performance for\")} ${selectedApplication.name}`\n : tr(\n \"devportal.analytics.description\",\n \"Explore live application usage and performance metrics\",\n )\n }\n purpose={tr(\n \"devportal.analytics.purpose\",\n \"Analytics shows how your apps are actually used — API call volume, active installs, latency, and error rates over time — so you can spot regressions, plan capacity, and prove value to your users. Pick an app and a time range to drill in.\",\n )}\n backLink={\n appId && selectedApplication\n ? `/apps/${selectedApplication.id}`\n : undefined\n }\n backLabel={tr(\n \"devportal.analytics.backToApplication\",\n \"Back to Application\",\n )}\n actions={\n <div className=\"flex flex-wrap gap-2\">\n {!appId && applications.length > 0 && (\n <select\n value={selectedApplicationId}\n onChange={(event) => setSelectedApplicationId(event.target.value)}\n aria-label={tr(\n \"devportal.analytics.selectAppAria\",\n \"Select application\",\n )}\n className=\"rounded-md border bg-bg-surface px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n {applications.map((application) => (\n <option key={application.id} value={application.id}>\n {application.name}\n </option>\n ))}\n </select>\n )}\n <select\n value={timeRange}\n onChange={(event) => setTimeRange(event.target.value as TimeRange)}\n aria-label={tr(\n \"devportal.analytics.selectRangeAria\",\n \"Select time range\",\n )}\n className=\"rounded-md border bg-bg-surface px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n <option value=\"7d\">\n {tr(\"devportal.analytics.range.7d\", \"Last 7 days\")}\n </option>\n <option value=\"30d\">\n {tr(\"devportal.analytics.range.30d\", \"Last 30 days\")}\n </option>\n <option value=\"90d\">\n {tr(\"devportal.analytics.range.90d\", \"Last 90 days\")}\n </option>\n </select>\n </div>\n }\n >\n {loading && !analytics.length ? (\n <div className=\"flex items-center justify-center py-16 text-text-secondary\">\n <Loader2 className=\"h-8 w-8 animate-spin\" />\n </div>\n ) : analytics.length > 0 ? (\n <>\n <EmphasisPanel className=\"mb-6\">\n <StatCardGrid columns={4}>\n <StatCard\n title={tr(\"devportal.analytics.apiCalls\", \"API Calls\")}\n value={(latest?.apiCalls ?? 0).toLocaleString()}\n icon={<Activity size={20} />}\n />\n <StatCard\n title={tr(\"devportal.analytics.activeUsers\", \"Active Users\")}\n value={(latest?.activeUsers ?? 0).toLocaleString()}\n icon={<Users size={20} />}\n />\n <StatCard\n title={tr(\"devportal.analytics.errorRate\", \"Error Rate\")}\n value={`${errorRate.toFixed(2)}%`}\n icon={<AlertTriangle size={20} />}\n trend={\n previous\n ? {\n value: previousErrorRate - errorRate,\n label: tr(\n \"devportal.analytics.vsPrevious\",\n \"vs previous period\",\n ),\n }\n : undefined\n }\n />\n <StatCard\n title={tr(\n \"devportal.analytics.avgResponseTime\",\n \"Avg Response Time\",\n )}\n value={\n latest?.avgResponseTime\n ? `${latest.avgResponseTime}ms`\n : tr(\"devportal.common.na\", \"N/A\")\n }\n icon={<Clock size={20} />}\n trend={\n typeof responseTrend === \"number\"\n ? {\n value: responseTrend,\n label: tr(\n \"devportal.analytics.fasterThanPrevious\",\n \"faster than previous\",\n ),\n }\n : undefined\n }\n />\n </StatCardGrid>\n </EmphasisPanel>\n\n <div className=\"grid gap-6 xl:grid-cols-2\">\n <PageSection\n title={tr(\"devportal.analytics.trafficTrend\", \"Traffic trend\")}\n >\n <GlassCard treatment=\"insight\" className=\"p-4\">\n <ResponsiveContainer width=\"100%\" height={300}>\n <LineChart data={chartData}>\n <CartesianGrid\n strokeDasharray=\"3 3\"\n stroke=\"var(--color-chart-grid)\"\n />\n <XAxis\n dataKey=\"date\"\n stroke=\"var(--color-chart-axis)\"\n fontSize={12}\n />\n <YAxis stroke=\"var(--color-chart-axis)\" fontSize={12} />\n <Tooltip\n contentStyle={{\n backgroundColor: \"var(--color-card)\",\n border: \"1px solid var(--color-border-default)\",\n borderRadius: 12,\n }}\n />\n <Line\n type=\"monotone\"\n dataKey=\"apiCalls\"\n stroke={chartColors.primary}\n strokeWidth={2}\n dot={false}\n />\n <Line\n type=\"monotone\"\n dataKey=\"errors\"\n stroke={chartColors.negative}\n strokeWidth={2}\n dot={false}\n />\n </LineChart>\n </ResponsiveContainer>\n </GlassCard>\n </PageSection>\n\n <PageSection\n title={tr(\"devportal.analytics.userActivity\", \"User activity\")}\n >\n <GlassCard treatment=\"insight\" className=\"p-4\">\n <ResponsiveContainer width=\"100%\" height={300}>\n <BarChart data={chartData}>\n <CartesianGrid\n strokeDasharray=\"3 3\"\n stroke=\"var(--color-chart-grid)\"\n />\n <XAxis\n dataKey=\"date\"\n stroke=\"var(--color-chart-axis)\"\n fontSize={12}\n />\n <YAxis stroke=\"var(--color-chart-axis)\" fontSize={12} />\n <Tooltip\n contentStyle={{\n backgroundColor: \"var(--color-card)\",\n border: \"1px solid var(--color-border-default)\",\n borderRadius: 12,\n }}\n />\n <Bar\n dataKey=\"activeUsers\"\n fill={chartColors.primary}\n radius={[6, 6, 0, 0]}\n />\n <Bar\n dataKey=\"newUsers\"\n fill={chartColors.secondary}\n radius={[6, 6, 0, 0]}\n />\n </BarChart>\n </ResponsiveContainer>\n </GlassCard>\n </PageSection>\n </div>\n\n <PageSection\n title={tr(\"devportal.analytics.dailyBreakdown\", \"Daily breakdown\")}\n >\n <DataTable\n data={analytics}\n keyField=\"id\"\n columns={[\n {\n key: \"date\",\n header: tr(\"devportal.analytics.date\", \"Date\"),\n render: (entry) =>\n new Date(entry.date).toLocaleDateString(undefined, {\n month: \"short\",\n day: \"numeric\",\n year: \"numeric\",\n }),\n },\n {\n key: \"apiCalls\",\n header: tr(\"devportal.analytics.apiCalls\", \"API Calls\"),\n render: (entry) => entry.apiCalls.toLocaleString(),\n },\n {\n key: \"errors\",\n header: tr(\"devportal.analytics.errors\", \"Errors\"),\n render: (entry) => entry.errors.toLocaleString(),\n },\n {\n key: \"activeUsers\",\n header: tr(\"devportal.analytics.activeUsers\", \"Active Users\"),\n render: (entry) => entry.activeUsers.toLocaleString(),\n },\n {\n key: \"newUsers\",\n header: tr(\"devportal.analytics.newUsers\", \"New Users\"),\n render: (entry) => entry.newUsers.toLocaleString(),\n },\n {\n key: \"avgResponseTime\",\n header: tr(\"devportal.analytics.avgResponse\", \"Avg Response\"),\n render: (entry) =>\n entry.avgResponseTime\n ? `${entry.avgResponseTime}ms`\n : tr(\"devportal.common.na\", \"N/A\"),\n },\n {\n key: \"sessions\",\n header: tr(\"devportal.analytics.sessions\", \"Sessions\"),\n render: (entry) => entry.sessions.toLocaleString(),\n },\n ]}\n />\n </PageSection>\n </>\n ) : (\n <EmptyState\n illustration=\"empty-data\"\n icon={<BarChart3 size={32} />}\n title={tr(\n \"devportal.analytics.noAnalyticsTitle\",\n \"No analytics available yet\",\n )}\n description={tr(\n \"devportal.analytics.noAnalyticsDescription\",\n \"Analytics will appear after this application starts receiving traffic through the public gateway.\",\n )}\n action={\n !appId ? (\n <button\n type=\"button\"\n onClick={() => handleNavigate(\"/apps\")}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary\"\n >\n {tr(\n \"devportal.analytics.viewApplications\",\n \"View applications\",\n )}\n </button>\n ) : undefined\n }\n />\n )}\n </PageLayout>\n );\n};\n"],"mappings":"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"}
@@ -292,7 +292,7 @@ var v = (e) => {
292
292
  }), /* @__PURE__ */ g("select", {
293
293
  value: C,
294
294
  onChange: (e) => ye(e.target.value),
295
- className: "rounded-md border bg-bg-surface px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary",
295
+ className: "rounded-md border bg-bg-surface px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary",
296
296
  children: [
297
297
  /* @__PURE__ */ h("option", {
298
298
  value: "all",
@@ -1 +1 @@
1
- {"version":3,"file":"ApiKeysPage.js","names":[],"sources":["../../../src/devportal/pages/ApiKeysPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useMemo, useState } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n Plus,\n Search,\n Key,\n Shield,\n Clock,\n AlertTriangle,\n Loader2,\n AlertCircle,\n RotateCw,\n Ban,\n} from \"lucide-react\";\nimport { EmphasisPanel, GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../components/PageLayout\";\nimport { DataTable } from \"../components/DataTable\";\nimport { ActiveIndicator } from \"../components/StatusBadge\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { ConfirmDialog } from \"../components/ConfirmDialog\";\nimport { NewSecretAlert } from \"../components/SecretDisplay\";\nimport { SelectionCheckbox } from \"../components/SelectionCheckbox\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport type { AuthApiKey } from \"../types\";\nimport { cn } from \"../utils/cn\";\nimport {\n useMyApiKeysQuery,\n useCurrentUserAppsQuery,\n useRevokeApiKeyAuthMutation,\n useRotateApiKeyAuthMutation,\n getErrorMessage,\n} from \"../hooks\";\n\n/**\n * Map GraphQL APIKey to local AuthApiKey type\n */\nfunction mapApiKey(key: {\n id: string;\n name: string;\n prefix: string;\n scopes: string[];\n appId: string;\n appName?: string | null;\n createdAt: string;\n expiresAt?: string | null;\n lastUsedAt?: string | null;\n}): AuthApiKey {\n return {\n id: key.id,\n name: key.name,\n prefix: key.prefix,\n scopes: key.scopes,\n appId: key.appId,\n appName: key.appName ?? undefined,\n createdAt: key.createdAt,\n expiresAt: key.expiresAt ?? undefined,\n lastUsedAt: key.lastUsedAt ?? undefined,\n };\n}\n\nconst isExpiringSoon = (expiresAt?: string): boolean => {\n if (!expiresAt) return false;\n const daysUntilExpiry = Math.ceil(\n (new Date(expiresAt).getTime() - Date.now()) / (1000 * 60 * 60 * 24),\n );\n return daysUntilExpiry <= 30 && daysUntilExpiry > 0;\n};\n\nconst isExpired = (expiresAt?: string): boolean => {\n if (!expiresAt) return false;\n return new Date(expiresAt).getTime() < Date.now();\n};\n\n/**\n * API Keys listing page\n *\n * Displays all API keys for the current user from auth-svc.\n * Previously showed devportal-svc ApiKeys per application;\n * now shows all user-owned API keys centrally.\n * Supports revoking and rotating API keys.\n */\nexport const ApiKeysPage: FC = () => {\n const { navigate, basePath, activeOrgId } = useDevportal();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const [searchQuery, setSearchQuery] = useState(\"\");\n const [statusFilter, setStatusFilter] = useState<\n \"all\" | \"active\" | \"expired\"\n >(\"all\");\n\n // Revoke / Rotate state\n const [revokeTarget, setRevokeTarget] = useState<AuthApiKey | null>(null);\n const [rotateTarget, setRotateTarget] = useState<AuthApiKey | null>(null);\n const [newSecret, setNewSecret] = useState<string | null>(null);\n const [selectedKeyIds, setSelectedKeyIds] = useState<Set<string>>(new Set());\n const [bulkFeedback, setBulkFeedback] = useState<{\n type: \"success\" | \"error\";\n message: string;\n } | null>(null);\n const [showBulkRevokeDialog, setShowBulkRevokeDialog] = useState(false);\n\n // Fetch API keys from auth-svc (user-scoped, no applicationId needed)\n const { data, loading, error, refetch } = useMyApiKeysQuery({\n fetchPolicy: \"cache-and-network\",\n });\n\n // Fetch OAuth apps to map appId → filter; key.appId is an OAuth client UUID\n const { data: oauthAppsData } = useCurrentUserAppsQuery({\n fetchPolicy: \"cache-first\",\n });\n const orgAppIds = useMemo(() => {\n const apps = oauthAppsData?.currentUserApps?.apps;\n if (!apps?.length) return null;\n return new Set(apps.map((a: { id: string }) => a.id));\n }, [oauthAppsData?.currentUserApps?.apps]);\n\n // Mutations\n const [revokeApiKey, { loading: revokeLoading }] =\n useRevokeApiKeyAuthMutation();\n const [rotateApiKey, { loading: rotateLoading }] =\n useRotateApiKeyAuthMutation();\n\n const apiKeys: AuthApiKey[] = useMemo(\n () => data?.myApiKeys?.apiKeys.map(mapApiKey) ?? [],\n [data?.myApiKeys?.apiKeys],\n );\n\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n // Revoke handler\n const handleRevoke = async () => {\n if (!revokeTarget) return;\n try {\n await revokeApiKey({\n variables: { keyId: revokeTarget.id },\n });\n setRevokeTarget(null);\n await refetch();\n } catch {\n // Error is surfaced by Apollo's error state on the mutation;\n // the ConfirmDialog stays open so the user can retry or cancel.\n }\n };\n\n // Rotate handler\n const handleRotate = async () => {\n if (!rotateTarget) return;\n try {\n const result = await rotateApiKey({\n variables: { keyId: rotateTarget.id },\n });\n const secret = result.data?.rotateAPIKey.apiSecret;\n if (secret) {\n setNewSecret(secret);\n }\n setRotateTarget(null);\n await refetch();\n } catch {\n // Error is surfaced by Apollo's error state on the mutation;\n // the ConfirmDialog stays open so the user can retry or cancel.\n }\n };\n\n // Filter keys: org scope + search + status\n const filteredKeys = apiKeys.filter((key: AuthApiKey) => {\n const matchesOrg = !orgAppIds || orgAppIds.has(key.appId);\n const matchesSearch =\n !searchQuery ||\n key.name.toLowerCase().includes(searchQuery.toLowerCase()) ||\n key.prefix.toLowerCase().includes(searchQuery.toLowerCase()) ||\n (key.appName?.toLowerCase().includes(searchQuery.toLowerCase()) ?? false);\n const matchesStatus =\n statusFilter === \"all\" ||\n (statusFilter === \"active\" && !isExpired(key.expiresAt)) ||\n (statusFilter === \"expired\" && isExpired(key.expiresAt));\n return matchesOrg && matchesSearch && matchesStatus;\n });\n\n const selectedKeys = useMemo(\n () => apiKeys.filter((key) => selectedKeyIds.has(key.id)),\n [apiKeys, selectedKeyIds],\n );\n\n const selectedActiveKeys = useMemo(\n () => selectedKeys.filter((key) => !isExpired(key.expiresAt)),\n [selectedKeys],\n );\n\n const allVisibleSelectableKeys = filteredKeys.filter(\n (key) => !isExpired(key.expiresAt),\n );\n const allVisibleSelected =\n allVisibleSelectableKeys.length > 0 &&\n allVisibleSelectableKeys.every((key) => selectedKeyIds.has(key.id));\n const partiallySelected =\n allVisibleSelectableKeys.some((key) => selectedKeyIds.has(key.id)) &&\n !allVisibleSelected;\n\n const toggleKeySelection = (keyId: string, checked: boolean) => {\n setSelectedKeyIds((prev) => {\n const next = new Set(prev);\n if (checked) {\n next.add(keyId);\n } else {\n next.delete(keyId);\n }\n return next;\n });\n };\n\n const toggleAllVisibleKeys = (checked: boolean) => {\n setSelectedKeyIds((prev) => {\n const next = new Set(prev);\n allVisibleSelectableKeys.forEach((key) => {\n if (checked) {\n next.add(key.id);\n } else {\n next.delete(key.id);\n }\n });\n return next;\n });\n };\n\n const clearSelection = () => setSelectedKeyIds(new Set());\n\n // Loading state\n if (loading && !data) {\n return (\n <PageLayout\n title={tr(\"devportal.apiKeys\", \"API Keys\")}\n description={tr(\n \"devportal.manageApiKeys\",\n \"Manage your API keys for programmatic access\",\n )}\n >\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 className=\"h-8 w-8 animate-spin text-text-secondary\" />\n <span className=\"ml-2 text-text-secondary\">\n {tr(\"devportal.loadingApiKeys\", \"Loading API keys...\")}\n </span>\n </div>\n </PageLayout>\n );\n }\n\n // Error state\n if (error) {\n return (\n <PageLayout\n title={tr(\"devportal.apiKeys\", \"API Keys\")}\n description={tr(\n \"devportal.manageApiKeys\",\n \"Manage your API keys for programmatic access\",\n )}\n >\n <div className=\"rounded-lg border border-status-error-border/50 bg-status-error-bg/10 p-6 text-center\">\n <AlertCircle className=\"mx-auto h-8 w-8 text-status-error-text\" />\n <h3 className=\"mt-2 font-medium text-status-error-text\">\n {tr(\"devportal.failedToLoadApiKeys\", \"Failed to load API keys\")}\n </h3>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {getErrorMessage(error)}\n </p>\n <button\n onClick={() => refetch()}\n className=\"mt-4 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n {tr(\"devportal.tryAgain\", \"Try Again\")}\n </button>\n </div>\n </PageLayout>\n );\n }\n\n const columns = [\n {\n key: \"select\",\n header: (\n <SelectionCheckbox\n checked={allVisibleSelected}\n indeterminate={partiallySelected}\n onChange={toggleAllVisibleKeys}\n ariaLabel=\"Select all visible API keys\"\n />\n ),\n className: \"w-12\",\n render: (key: AuthApiKey) => (\n <div onClick={(event) => event.stopPropagation()}>\n <SelectionCheckbox\n checked={selectedKeyIds.has(key.id)}\n disabled={isExpired(key.expiresAt)}\n onChange={(checked) => toggleKeySelection(key.id, checked)}\n ariaLabel={`Select API key ${key.name}`}\n />\n </div>\n ),\n },\n {\n key: \"name\",\n header: \"API Key\",\n render: (key: AuthApiKey) => (\n <div className=\"flex items-center gap-3\">\n <div\n className={cn(\n \"flex h-10 w-10 items-center justify-center rounded-lg\",\n isExpired(key.expiresAt)\n ? \"bg-status-error-bg-subtle text-status-error-text\"\n : \"bg-action-primary-bg/10 text-primary\",\n )}\n >\n <Key size={20} />\n </div>\n <div>\n <div className=\"flex items-center gap-2\">\n <p className=\"font-medium\">{key.name}</p>\n {isExpired(key.expiresAt) && (\n <span className=\"rounded bg-status-error-bg-subtle px-1.5 py-0.5 text-xs text-status-error-text\">\n Expired\n </span>\n )}\n {isExpiringSoon(key.expiresAt) && !isExpired(key.expiresAt) && (\n <span className=\"rounded bg-status-warning-bg-subtle px-1.5 py-0.5 text-xs text-status-warning-text\">\n Expiring Soon\n </span>\n )}\n </div>\n {key.appName && (\n <p className=\"text-xs text-text-secondary\">App: {key.appName}</p>\n )}\n </div>\n </div>\n ),\n },\n {\n key: \"prefix\",\n header: \"Key Prefix\",\n render: (key: AuthApiKey) => (\n <code className=\"text-sm text-text-secondary\">{key.prefix}...</code>\n ),\n },\n {\n key: \"scopes\",\n header: \"Scopes\",\n render: (key: AuthApiKey) => (\n <div className=\"flex flex-wrap gap-1\">\n {key.scopes.slice(0, 3).map((scope) => (\n <span\n key={scope}\n className=\"inline-flex rounded bg-bg-sunken px-1.5 py-0.5 text-xs\"\n >\n {scope}\n </span>\n ))}\n {key.scopes.length > 3 && (\n <span className=\"text-xs text-text-secondary\">\n +{key.scopes.length - 3}\n </span>\n )}\n </div>\n ),\n },\n {\n key: \"lastUsed\",\n header: \"Last Used\",\n render: (key: AuthApiKey) => (\n <span className=\"text-sm text-text-secondary\">\n {key.lastUsedAt\n ? new Date(key.lastUsedAt).toLocaleDateString()\n : \"Never\"}\n </span>\n ),\n },\n {\n key: \"expires\",\n header: \"Expires\",\n render: (key: AuthApiKey) => (\n <span\n className={cn(\n \"text-sm\",\n isExpired(key.expiresAt)\n ? \"text-status-error-text\"\n : isExpiringSoon(key.expiresAt)\n ? \"text-status-warning-text\"\n : \"text-text-secondary\",\n )}\n >\n {key.expiresAt\n ? new Date(key.expiresAt).toLocaleDateString()\n : \"Never\"}\n </span>\n ),\n },\n {\n key: \"status\",\n header: \"Status\",\n render: (key: AuthApiKey) => (\n <div className=\"flex items-center gap-2\">\n <ActiveIndicator active={!isExpired(key.expiresAt)} />\n <span className=\"text-sm\">\n {isExpired(key.expiresAt) ? \"Expired\" : \"Active\"}\n </span>\n </div>\n ),\n },\n {\n key: \"actions\",\n header: \"Actions\",\n className: \"w-28\",\n render: (key: AuthApiKey) => {\n if (isExpired(key.expiresAt)) {\n return (\n <span className=\"text-xs text-text-secondary\">No actions</span>\n );\n }\n\n return (\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n title=\"Rotate secret\"\n disabled={rotateLoading}\n onClick={(e) => {\n e.stopPropagation();\n setRotateTarget(key);\n }}\n className={cn(\n \"inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium transition-colors\",\n \"text-status-info-text hover:bg-status-info-bg-subtle\",\n \"disabled:opacity-50 disabled:cursor-not-allowed\",\n )}\n >\n <RotateCw size={14} />\n <span className=\"sr-only sm:not-sr-only\">Rotate</span>\n </button>\n <button\n type=\"button\"\n title=\"Revoke key\"\n disabled={revokeLoading}\n onClick={(e) => {\n e.stopPropagation();\n setRevokeTarget(key);\n }}\n className={cn(\n \"inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium transition-colors\",\n \"text-status-error-text hover:bg-status-error-bg-subtle\",\n \"disabled:opacity-50 disabled:cursor-not-allowed\",\n )}\n >\n <Ban size={14} />\n <span className=\"sr-only sm:not-sr-only\">Revoke</span>\n </button>\n </div>\n );\n },\n },\n ];\n\n // Stats scoped to the filtered set (org context)\n const stats = {\n total: filteredKeys.length,\n active: filteredKeys.filter((k) => !isExpired(k.expiresAt)).length,\n expiringSoon: filteredKeys.filter((k) => isExpiringSoon(k.expiresAt))\n .length,\n expired: filteredKeys.filter((k) => isExpired(k.expiresAt)).length,\n };\n\n const handleBulkRevoke = async () => {\n if (selectedActiveKeys.length === 0) return;\n\n const results = await Promise.allSettled(\n selectedActiveKeys.map((key) =>\n revokeApiKey({ variables: { keyId: key.id } }),\n ),\n );\n\n const succeeded = results.filter(\n (result) => result.status === \"fulfilled\",\n ).length;\n const failed = results.length - succeeded;\n\n setBulkFeedback({\n type: failed === 0 ? \"success\" : \"error\",\n message: `Revoked ${succeeded} API key${succeeded === 1 ? \"\" : \"s\"}${\n failed > 0 ? ` • ${failed} failed` : \"\"\n }`,\n });\n setShowBulkRevokeDialog(false);\n clearSelection();\n await refetch();\n };\n\n return (\n <PermissionGuard authOnly>\n <PageLayout\n title={tr(\"devportal.apiKeys\", \"API Keys\")}\n description={tr(\n \"devportal.manageApiKeys\",\n \"Manage your API keys for programmatic access\",\n )}\n purpose={tr(\n \"devportal.apiKeysPurpose\",\n \"API keys authenticate server-to-server calls — use them in scripts, CI jobs, or backend services that talk to the platform without a logged-in user. Generate a key here, scope it to an app, and rotate it if it is ever exposed.\",\n )}\n actions={\n <button\n onClick={() => handleNavigate(\"/api-keys/new\")}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 transition-colors\"\n >\n <Plus size={16} />\n {tr(\"devportal.generateApiKey\", \"Generate API Key\")}\n </button>\n }\n >\n {/* New secret alert — shown after a successful rotation */}\n {newSecret && (\n <NewSecretAlert\n title=\"API Key Rotated\"\n secret={newSecret}\n description=\"The previous secret has been invalidated. Copy the new secret below — it will not be shown again.\"\n onDismiss={() => setNewSecret(null)}\n />\n )}\n\n {bulkFeedback && (\n <div\n className={cn(\n \"rounded-lg border px-4 py-3 text-sm\",\n bulkFeedback.type === \"success\"\n ? \"border-border-subtle bg-status-success-bg-subtle text-status-success-text\"\n : \"border-status-error-border/40 bg-status-error-bg/10 text-status-error-text\",\n )}\n >\n <div className=\"flex items-center justify-between gap-3\">\n <span>{bulkFeedback.message}</span>\n <button\n onClick={() => setBulkFeedback(null)}\n className=\"text-xs font-medium underline underline-offset-2\"\n >\n Dismiss\n </button>\n </div>\n </div>\n )}\n\n {/* Filters */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex flex-1 items-center gap-4\">\n {/* Search */}\n <div className=\"relative flex-1 max-w-md\">\n <Search\n size={16}\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-text-secondary\"\n />\n <input\n type=\"text\"\n placeholder=\"Search by name, prefix, or app...\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"w-full rounded-md border bg-bg-surface pl-9 pr-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary\"\n />\n </div>\n\n {/* Status Filter */}\n <select\n value={statusFilter}\n onChange={(e) =>\n setStatusFilter(e.target.value as \"all\" | \"active\" | \"expired\")\n }\n className=\"rounded-md border bg-bg-surface px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <option value=\"all\">All Status</option>\n <option value=\"active\">Active</option>\n <option value=\"expired\">Expired</option>\n </select>\n </div>\n </div>\n\n {/* Summary Stats — primary emphasis zone */}\n <EmphasisPanel>\n <div className=\"grid grid-cols-2 gap-4 sm:grid-cols-4\">\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <div className=\"flex items-center gap-2 text-text-secondary\">\n <Key size={16} />\n <span className=\"text-sm\">Total Keys</span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {stats.total}\n </p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <div className=\"flex items-center gap-2 text-status-success-text\">\n <Shield size={16} />\n <span className=\"text-sm\">Active</span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {stats.active}\n </p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <div className=\"flex items-center gap-2 text-status-warning-text\">\n <Clock size={16} />\n <span className=\"text-sm\">Expiring Soon</span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {stats.expiringSoon}\n </p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <div className=\"flex items-center gap-2 text-status-error-text\">\n <AlertTriangle size={16} />\n <span className=\"text-sm\">Expired</span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {stats.expired}\n </p>\n </GlassCard>\n </div>\n </EmphasisPanel>\n\n {/* Results count */}\n <p className=\"text-sm text-text-secondary\">\n {filteredKeys.length} API key{filteredKeys.length !== 1 ? \"s\" : \"\"}{\" \"}\n found\n </p>\n\n {filteredKeys.length > 0 && (\n <div className=\"rounded-card border border-border-seam bg-bg-surface p-4\">\n <div className=\"flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <div className=\"flex items-center gap-2\">\n <SelectionCheckbox\n checked={allVisibleSelected}\n indeterminate={partiallySelected}\n onChange={toggleAllVisibleKeys}\n ariaLabel=\"Select all visible API keys\"\n />\n <span className=\"text-sm font-medium\">\n Select all active keys\n </span>\n </div>\n {selectedKeyIds.size > 0 && (\n <button\n onClick={clearSelection}\n className=\"text-sm text-text-secondary hover:text-text-primary\"\n >\n Clear selection\n </button>\n )}\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"rounded-full bg-bg-sunken px-3 py-1 text-sm font-medium text-text-secondary\">\n {selectedKeyIds.size} selected\n </span>\n <button\n onClick={() => setShowBulkRevokeDialog(true)}\n disabled={selectedActiveKeys.length === 0}\n className=\"inline-flex items-center gap-2 rounded-md border border-status-error-bg px-3 py-2 text-sm font-medium text-status-error-text transition-colors hover:bg-status-error-bg-subtle disabled:cursor-not-allowed disabled:opacity-50\"\n >\n <Ban size={16} />\n Revoke Selected\n {selectedActiveKeys.length > 0\n ? ` (${selectedActiveKeys.length})`\n : \"\"}\n </button>\n </div>\n </div>\n </div>\n )}\n\n {/* Keys Table */}\n {filteredKeys.length > 0 ? (\n <DataTable data={filteredKeys} columns={columns} keyField=\"id\" />\n ) : (\n <EmptyState\n illustration=\"empty-data\"\n title={tr(\"devportal.noApiKeysFound\", \"No API keys found\")}\n description={\n searchQuery || statusFilter !== \"all\"\n ? tr(\n \"devportal.tryAdjustingFilters\",\n \"Try adjusting your filters\",\n )\n : tr(\n \"devportal.generateFirstApiKey\",\n \"Generate your first API key to start making API calls\",\n )\n }\n action={\n !searchQuery && statusFilter === \"all\" ? (\n <button\n onClick={() => handleNavigate(\"/api-keys/new\")}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus size={16} />\n {tr(\"devportal.generateApiKey\", \"Generate API Key\")}\n </button>\n ) : undefined\n }\n />\n )}\n\n {/* Revoke confirmation dialog */}\n <ConfirmDialog\n open={revokeTarget !== null}\n onOpenChange={(open) => {\n if (!open) setRevokeTarget(null);\n }}\n onConfirm={handleRevoke}\n title=\"Revoke API Key\"\n description={`Are you sure you want to revoke \"${revokeTarget?.name ?? \"\"}\"? This action cannot be undone. Any application or service using this key will immediately lose access.`}\n confirmLabel=\"Revoke Key\"\n cancelLabel=\"Cancel\"\n variant=\"danger\"\n loading={revokeLoading}\n />\n\n {/* Rotate confirmation dialog */}\n <ConfirmDialog\n open={rotateTarget !== null}\n onOpenChange={(open) => {\n if (!open) setRotateTarget(null);\n }}\n onConfirm={handleRotate}\n title=\"Rotate API Key Secret\"\n description={`This will generate a new secret for \"${rotateTarget?.name ?? \"\"}\" and immediately invalidate the current one. Make sure to update all services using this key before the old secret stops working.`}\n confirmLabel=\"Rotate Secret\"\n cancelLabel=\"Cancel\"\n variant=\"warning\"\n loading={rotateLoading}\n />\n\n <ConfirmDialog\n open={showBulkRevokeDialog}\n onOpenChange={setShowBulkRevokeDialog}\n onConfirm={handleBulkRevoke}\n title=\"Revoke selected API keys\"\n description={`Revoke ${selectedActiveKeys.length} selected API key${selectedActiveKeys.length === 1 ? \"\" : \"s\"}? Any service using these keys will immediately lose access.`}\n confirmLabel=\"Revoke Keys\"\n cancelLabel=\"Cancel\"\n variant=\"danger\"\n loading={revokeLoading}\n />\n </PageLayout>\n </PermissionGuard>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"ApiKeysPage.js","names":[],"sources":["../../../src/devportal/pages/ApiKeysPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useMemo, useState } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n Plus,\n Search,\n Key,\n Shield,\n Clock,\n AlertTriangle,\n Loader2,\n AlertCircle,\n RotateCw,\n Ban,\n} from \"lucide-react\";\nimport { EmphasisPanel, GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../components/PageLayout\";\nimport { DataTable } from \"../components/DataTable\";\nimport { ActiveIndicator } from \"../components/StatusBadge\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { ConfirmDialog } from \"../components/ConfirmDialog\";\nimport { NewSecretAlert } from \"../components/SecretDisplay\";\nimport { SelectionCheckbox } from \"../components/SelectionCheckbox\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport type { AuthApiKey } from \"../types\";\nimport { cn } from \"../utils/cn\";\nimport {\n useMyApiKeysQuery,\n useCurrentUserAppsQuery,\n useRevokeApiKeyAuthMutation,\n useRotateApiKeyAuthMutation,\n getErrorMessage,\n} from \"../hooks\";\n\n/**\n * Map GraphQL APIKey to local AuthApiKey type\n */\nfunction mapApiKey(key: {\n id: string;\n name: string;\n prefix: string;\n scopes: string[];\n appId: string;\n appName?: string | null;\n createdAt: string;\n expiresAt?: string | null;\n lastUsedAt?: string | null;\n}): AuthApiKey {\n return {\n id: key.id,\n name: key.name,\n prefix: key.prefix,\n scopes: key.scopes,\n appId: key.appId,\n appName: key.appName ?? undefined,\n createdAt: key.createdAt,\n expiresAt: key.expiresAt ?? undefined,\n lastUsedAt: key.lastUsedAt ?? undefined,\n };\n}\n\nconst isExpiringSoon = (expiresAt?: string): boolean => {\n if (!expiresAt) return false;\n const daysUntilExpiry = Math.ceil(\n (new Date(expiresAt).getTime() - Date.now()) / (1000 * 60 * 60 * 24),\n );\n return daysUntilExpiry <= 30 && daysUntilExpiry > 0;\n};\n\nconst isExpired = (expiresAt?: string): boolean => {\n if (!expiresAt) return false;\n return new Date(expiresAt).getTime() < Date.now();\n};\n\n/**\n * API Keys listing page\n *\n * Displays all API keys for the current user from auth-svc.\n * Previously showed devportal-svc ApiKeys per application;\n * now shows all user-owned API keys centrally.\n * Supports revoking and rotating API keys.\n */\nexport const ApiKeysPage: FC = () => {\n const { navigate, basePath, activeOrgId } = useDevportal();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const [searchQuery, setSearchQuery] = useState(\"\");\n const [statusFilter, setStatusFilter] = useState<\n \"all\" | \"active\" | \"expired\"\n >(\"all\");\n\n // Revoke / Rotate state\n const [revokeTarget, setRevokeTarget] = useState<AuthApiKey | null>(null);\n const [rotateTarget, setRotateTarget] = useState<AuthApiKey | null>(null);\n const [newSecret, setNewSecret] = useState<string | null>(null);\n const [selectedKeyIds, setSelectedKeyIds] = useState<Set<string>>(new Set());\n const [bulkFeedback, setBulkFeedback] = useState<{\n type: \"success\" | \"error\";\n message: string;\n } | null>(null);\n const [showBulkRevokeDialog, setShowBulkRevokeDialog] = useState(false);\n\n // Fetch API keys from auth-svc (user-scoped, no applicationId needed)\n const { data, loading, error, refetch } = useMyApiKeysQuery({\n fetchPolicy: \"cache-and-network\",\n });\n\n // Fetch OAuth apps to map appId → filter; key.appId is an OAuth client UUID\n const { data: oauthAppsData } = useCurrentUserAppsQuery({\n fetchPolicy: \"cache-first\",\n });\n const orgAppIds = useMemo(() => {\n const apps = oauthAppsData?.currentUserApps?.apps;\n if (!apps?.length) return null;\n return new Set(apps.map((a: { id: string }) => a.id));\n }, [oauthAppsData?.currentUserApps?.apps]);\n\n // Mutations\n const [revokeApiKey, { loading: revokeLoading }] =\n useRevokeApiKeyAuthMutation();\n const [rotateApiKey, { loading: rotateLoading }] =\n useRotateApiKeyAuthMutation();\n\n const apiKeys: AuthApiKey[] = useMemo(\n () => data?.myApiKeys?.apiKeys.map(mapApiKey) ?? [],\n [data?.myApiKeys?.apiKeys],\n );\n\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n // Revoke handler\n const handleRevoke = async () => {\n if (!revokeTarget) return;\n try {\n await revokeApiKey({\n variables: { keyId: revokeTarget.id },\n });\n setRevokeTarget(null);\n await refetch();\n } catch {\n // Error is surfaced by Apollo's error state on the mutation;\n // the ConfirmDialog stays open so the user can retry or cancel.\n }\n };\n\n // Rotate handler\n const handleRotate = async () => {\n if (!rotateTarget) return;\n try {\n const result = await rotateApiKey({\n variables: { keyId: rotateTarget.id },\n });\n const secret = result.data?.rotateAPIKey.apiSecret;\n if (secret) {\n setNewSecret(secret);\n }\n setRotateTarget(null);\n await refetch();\n } catch {\n // Error is surfaced by Apollo's error state on the mutation;\n // the ConfirmDialog stays open so the user can retry or cancel.\n }\n };\n\n // Filter keys: org scope + search + status\n const filteredKeys = apiKeys.filter((key: AuthApiKey) => {\n const matchesOrg = !orgAppIds || orgAppIds.has(key.appId);\n const matchesSearch =\n !searchQuery ||\n key.name.toLowerCase().includes(searchQuery.toLowerCase()) ||\n key.prefix.toLowerCase().includes(searchQuery.toLowerCase()) ||\n (key.appName?.toLowerCase().includes(searchQuery.toLowerCase()) ?? false);\n const matchesStatus =\n statusFilter === \"all\" ||\n (statusFilter === \"active\" && !isExpired(key.expiresAt)) ||\n (statusFilter === \"expired\" && isExpired(key.expiresAt));\n return matchesOrg && matchesSearch && matchesStatus;\n });\n\n const selectedKeys = useMemo(\n () => apiKeys.filter((key) => selectedKeyIds.has(key.id)),\n [apiKeys, selectedKeyIds],\n );\n\n const selectedActiveKeys = useMemo(\n () => selectedKeys.filter((key) => !isExpired(key.expiresAt)),\n [selectedKeys],\n );\n\n const allVisibleSelectableKeys = filteredKeys.filter(\n (key) => !isExpired(key.expiresAt),\n );\n const allVisibleSelected =\n allVisibleSelectableKeys.length > 0 &&\n allVisibleSelectableKeys.every((key) => selectedKeyIds.has(key.id));\n const partiallySelected =\n allVisibleSelectableKeys.some((key) => selectedKeyIds.has(key.id)) &&\n !allVisibleSelected;\n\n const toggleKeySelection = (keyId: string, checked: boolean) => {\n setSelectedKeyIds((prev) => {\n const next = new Set(prev);\n if (checked) {\n next.add(keyId);\n } else {\n next.delete(keyId);\n }\n return next;\n });\n };\n\n const toggleAllVisibleKeys = (checked: boolean) => {\n setSelectedKeyIds((prev) => {\n const next = new Set(prev);\n allVisibleSelectableKeys.forEach((key) => {\n if (checked) {\n next.add(key.id);\n } else {\n next.delete(key.id);\n }\n });\n return next;\n });\n };\n\n const clearSelection = () => setSelectedKeyIds(new Set());\n\n // Loading state\n if (loading && !data) {\n return (\n <PageLayout\n title={tr(\"devportal.apiKeys\", \"API Keys\")}\n description={tr(\n \"devportal.manageApiKeys\",\n \"Manage your API keys for programmatic access\",\n )}\n >\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 className=\"h-8 w-8 animate-spin text-text-secondary\" />\n <span className=\"ml-2 text-text-secondary\">\n {tr(\"devportal.loadingApiKeys\", \"Loading API keys...\")}\n </span>\n </div>\n </PageLayout>\n );\n }\n\n // Error state\n if (error) {\n return (\n <PageLayout\n title={tr(\"devportal.apiKeys\", \"API Keys\")}\n description={tr(\n \"devportal.manageApiKeys\",\n \"Manage your API keys for programmatic access\",\n )}\n >\n <div className=\"rounded-lg border border-status-error-border/50 bg-status-error-bg/10 p-6 text-center\">\n <AlertCircle className=\"mx-auto h-8 w-8 text-status-error-text\" />\n <h3 className=\"mt-2 font-medium text-status-error-text\">\n {tr(\"devportal.failedToLoadApiKeys\", \"Failed to load API keys\")}\n </h3>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {getErrorMessage(error)}\n </p>\n <button\n onClick={() => refetch()}\n className=\"mt-4 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n {tr(\"devportal.tryAgain\", \"Try Again\")}\n </button>\n </div>\n </PageLayout>\n );\n }\n\n const columns = [\n {\n key: \"select\",\n header: (\n <SelectionCheckbox\n checked={allVisibleSelected}\n indeterminate={partiallySelected}\n onChange={toggleAllVisibleKeys}\n ariaLabel=\"Select all visible API keys\"\n />\n ),\n className: \"w-12\",\n render: (key: AuthApiKey) => (\n <div onClick={(event) => event.stopPropagation()}>\n <SelectionCheckbox\n checked={selectedKeyIds.has(key.id)}\n disabled={isExpired(key.expiresAt)}\n onChange={(checked) => toggleKeySelection(key.id, checked)}\n ariaLabel={`Select API key ${key.name}`}\n />\n </div>\n ),\n },\n {\n key: \"name\",\n header: \"API Key\",\n render: (key: AuthApiKey) => (\n <div className=\"flex items-center gap-3\">\n <div\n className={cn(\n \"flex h-10 w-10 items-center justify-center rounded-lg\",\n isExpired(key.expiresAt)\n ? \"bg-status-error-bg-subtle text-status-error-text\"\n : \"bg-action-primary-bg/10 text-primary\",\n )}\n >\n <Key size={20} />\n </div>\n <div>\n <div className=\"flex items-center gap-2\">\n <p className=\"font-medium\">{key.name}</p>\n {isExpired(key.expiresAt) && (\n <span className=\"rounded bg-status-error-bg-subtle px-1.5 py-0.5 text-xs text-status-error-text\">\n Expired\n </span>\n )}\n {isExpiringSoon(key.expiresAt) && !isExpired(key.expiresAt) && (\n <span className=\"rounded bg-status-warning-bg-subtle px-1.5 py-0.5 text-xs text-status-warning-text\">\n Expiring Soon\n </span>\n )}\n </div>\n {key.appName && (\n <p className=\"text-xs text-text-secondary\">App: {key.appName}</p>\n )}\n </div>\n </div>\n ),\n },\n {\n key: \"prefix\",\n header: \"Key Prefix\",\n render: (key: AuthApiKey) => (\n <code className=\"text-sm text-text-secondary\">{key.prefix}...</code>\n ),\n },\n {\n key: \"scopes\",\n header: \"Scopes\",\n render: (key: AuthApiKey) => (\n <div className=\"flex flex-wrap gap-1\">\n {key.scopes.slice(0, 3).map((scope) => (\n <span\n key={scope}\n className=\"inline-flex rounded bg-bg-sunken px-1.5 py-0.5 text-xs\"\n >\n {scope}\n </span>\n ))}\n {key.scopes.length > 3 && (\n <span className=\"text-xs text-text-secondary\">\n +{key.scopes.length - 3}\n </span>\n )}\n </div>\n ),\n },\n {\n key: \"lastUsed\",\n header: \"Last Used\",\n render: (key: AuthApiKey) => (\n <span className=\"text-sm text-text-secondary\">\n {key.lastUsedAt\n ? new Date(key.lastUsedAt).toLocaleDateString()\n : \"Never\"}\n </span>\n ),\n },\n {\n key: \"expires\",\n header: \"Expires\",\n render: (key: AuthApiKey) => (\n <span\n className={cn(\n \"text-sm\",\n isExpired(key.expiresAt)\n ? \"text-status-error-text\"\n : isExpiringSoon(key.expiresAt)\n ? \"text-status-warning-text\"\n : \"text-text-secondary\",\n )}\n >\n {key.expiresAt\n ? new Date(key.expiresAt).toLocaleDateString()\n : \"Never\"}\n </span>\n ),\n },\n {\n key: \"status\",\n header: \"Status\",\n render: (key: AuthApiKey) => (\n <div className=\"flex items-center gap-2\">\n <ActiveIndicator active={!isExpired(key.expiresAt)} />\n <span className=\"text-sm\">\n {isExpired(key.expiresAt) ? \"Expired\" : \"Active\"}\n </span>\n </div>\n ),\n },\n {\n key: \"actions\",\n header: \"Actions\",\n className: \"w-28\",\n render: (key: AuthApiKey) => {\n if (isExpired(key.expiresAt)) {\n return (\n <span className=\"text-xs text-text-secondary\">No actions</span>\n );\n }\n\n return (\n <div className=\"flex items-center gap-1\">\n <button\n type=\"button\"\n title=\"Rotate secret\"\n disabled={rotateLoading}\n onClick={(e) => {\n e.stopPropagation();\n setRotateTarget(key);\n }}\n className={cn(\n \"inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium transition-colors\",\n \"text-status-info-text hover:bg-status-info-bg-subtle\",\n \"disabled:opacity-50 disabled:cursor-not-allowed\",\n )}\n >\n <RotateCw size={14} />\n <span className=\"sr-only sm:not-sr-only\">Rotate</span>\n </button>\n <button\n type=\"button\"\n title=\"Revoke key\"\n disabled={revokeLoading}\n onClick={(e) => {\n e.stopPropagation();\n setRevokeTarget(key);\n }}\n className={cn(\n \"inline-flex items-center gap-1 rounded-md px-2 py-1 text-xs font-medium transition-colors\",\n \"text-status-error-text hover:bg-status-error-bg-subtle\",\n \"disabled:opacity-50 disabled:cursor-not-allowed\",\n )}\n >\n <Ban size={14} />\n <span className=\"sr-only sm:not-sr-only\">Revoke</span>\n </button>\n </div>\n );\n },\n },\n ];\n\n // Stats scoped to the filtered set (org context)\n const stats = {\n total: filteredKeys.length,\n active: filteredKeys.filter((k) => !isExpired(k.expiresAt)).length,\n expiringSoon: filteredKeys.filter((k) => isExpiringSoon(k.expiresAt))\n .length,\n expired: filteredKeys.filter((k) => isExpired(k.expiresAt)).length,\n };\n\n const handleBulkRevoke = async () => {\n if (selectedActiveKeys.length === 0) return;\n\n const results = await Promise.allSettled(\n selectedActiveKeys.map((key) =>\n revokeApiKey({ variables: { keyId: key.id } }),\n ),\n );\n\n const succeeded = results.filter(\n (result) => result.status === \"fulfilled\",\n ).length;\n const failed = results.length - succeeded;\n\n setBulkFeedback({\n type: failed === 0 ? \"success\" : \"error\",\n message: `Revoked ${succeeded} API key${succeeded === 1 ? \"\" : \"s\"}${\n failed > 0 ? ` • ${failed} failed` : \"\"\n }`,\n });\n setShowBulkRevokeDialog(false);\n clearSelection();\n await refetch();\n };\n\n return (\n <PermissionGuard authOnly>\n <PageLayout\n title={tr(\"devportal.apiKeys\", \"API Keys\")}\n description={tr(\n \"devportal.manageApiKeys\",\n \"Manage your API keys for programmatic access\",\n )}\n purpose={tr(\n \"devportal.apiKeysPurpose\",\n \"API keys authenticate server-to-server calls — use them in scripts, CI jobs, or backend services that talk to the platform without a logged-in user. Generate a key here, scope it to an app, and rotate it if it is ever exposed.\",\n )}\n actions={\n <button\n onClick={() => handleNavigate(\"/api-keys/new\")}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 transition-colors\"\n >\n <Plus size={16} />\n {tr(\"devportal.generateApiKey\", \"Generate API Key\")}\n </button>\n }\n >\n {/* New secret alert — shown after a successful rotation */}\n {newSecret && (\n <NewSecretAlert\n title=\"API Key Rotated\"\n secret={newSecret}\n description=\"The previous secret has been invalidated. Copy the new secret below — it will not be shown again.\"\n onDismiss={() => setNewSecret(null)}\n />\n )}\n\n {bulkFeedback && (\n <div\n className={cn(\n \"rounded-lg border px-4 py-3 text-sm\",\n bulkFeedback.type === \"success\"\n ? \"border-border-subtle bg-status-success-bg-subtle text-status-success-text\"\n : \"border-status-error-border/40 bg-status-error-bg/10 text-status-error-text\",\n )}\n >\n <div className=\"flex items-center justify-between gap-3\">\n <span>{bulkFeedback.message}</span>\n <button\n onClick={() => setBulkFeedback(null)}\n className=\"text-xs font-medium underline underline-offset-2\"\n >\n Dismiss\n </button>\n </div>\n </div>\n )}\n\n {/* Filters */}\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex flex-1 items-center gap-4\">\n {/* Search */}\n <div className=\"relative flex-1 max-w-md\">\n <Search\n size={16}\n className=\"absolute left-3 top-1/2 -translate-y-1/2 text-text-secondary\"\n />\n <input\n type=\"text\"\n placeholder=\"Search by name, prefix, or app...\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"w-full rounded-md border bg-bg-surface pl-9 pr-4 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary\"\n />\n </div>\n\n {/* Status Filter */}\n <select\n value={statusFilter}\n onChange={(e) =>\n setStatusFilter(e.target.value as \"all\" | \"active\" | \"expired\")\n }\n className=\"rounded-md border bg-bg-surface px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n <option value=\"all\">All Status</option>\n <option value=\"active\">Active</option>\n <option value=\"expired\">Expired</option>\n </select>\n </div>\n </div>\n\n {/* Summary Stats — primary emphasis zone */}\n <EmphasisPanel>\n <div className=\"grid grid-cols-2 gap-4 sm:grid-cols-4\">\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <div className=\"flex items-center gap-2 text-text-secondary\">\n <Key size={16} />\n <span className=\"text-sm\">Total Keys</span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {stats.total}\n </p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <div className=\"flex items-center gap-2 text-status-success-text\">\n <Shield size={16} />\n <span className=\"text-sm\">Active</span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {stats.active}\n </p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <div className=\"flex items-center gap-2 text-status-warning-text\">\n <Clock size={16} />\n <span className=\"text-sm\">Expiring Soon</span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {stats.expiringSoon}\n </p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <div className=\"flex items-center gap-2 text-status-error-text\">\n <AlertTriangle size={16} />\n <span className=\"text-sm\">Expired</span>\n </div>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {stats.expired}\n </p>\n </GlassCard>\n </div>\n </EmphasisPanel>\n\n {/* Results count */}\n <p className=\"text-sm text-text-secondary\">\n {filteredKeys.length} API key{filteredKeys.length !== 1 ? \"s\" : \"\"}{\" \"}\n found\n </p>\n\n {filteredKeys.length > 0 && (\n <div className=\"rounded-card border border-border-seam bg-bg-surface p-4\">\n <div className=\"flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between\">\n <div className=\"flex flex-wrap items-center gap-3\">\n <div className=\"flex items-center gap-2\">\n <SelectionCheckbox\n checked={allVisibleSelected}\n indeterminate={partiallySelected}\n onChange={toggleAllVisibleKeys}\n ariaLabel=\"Select all visible API keys\"\n />\n <span className=\"text-sm font-medium\">\n Select all active keys\n </span>\n </div>\n {selectedKeyIds.size > 0 && (\n <button\n onClick={clearSelection}\n className=\"text-sm text-text-secondary hover:text-text-primary\"\n >\n Clear selection\n </button>\n )}\n </div>\n\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"rounded-full bg-bg-sunken px-3 py-1 text-sm font-medium text-text-secondary\">\n {selectedKeyIds.size} selected\n </span>\n <button\n onClick={() => setShowBulkRevokeDialog(true)}\n disabled={selectedActiveKeys.length === 0}\n className=\"inline-flex items-center gap-2 rounded-md border border-status-error-bg px-3 py-2 text-sm font-medium text-status-error-text transition-colors hover:bg-status-error-bg-subtle disabled:cursor-not-allowed disabled:opacity-50\"\n >\n <Ban size={16} />\n Revoke Selected\n {selectedActiveKeys.length > 0\n ? ` (${selectedActiveKeys.length})`\n : \"\"}\n </button>\n </div>\n </div>\n </div>\n )}\n\n {/* Keys Table */}\n {filteredKeys.length > 0 ? (\n <DataTable data={filteredKeys} columns={columns} keyField=\"id\" />\n ) : (\n <EmptyState\n illustration=\"empty-data\"\n title={tr(\"devportal.noApiKeysFound\", \"No API keys found\")}\n description={\n searchQuery || statusFilter !== \"all\"\n ? tr(\n \"devportal.tryAdjustingFilters\",\n \"Try adjusting your filters\",\n )\n : tr(\n \"devportal.generateFirstApiKey\",\n \"Generate your first API key to start making API calls\",\n )\n }\n action={\n !searchQuery && statusFilter === \"all\" ? (\n <button\n onClick={() => handleNavigate(\"/api-keys/new\")}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus size={16} />\n {tr(\"devportal.generateApiKey\", \"Generate API Key\")}\n </button>\n ) : undefined\n }\n />\n )}\n\n {/* Revoke confirmation dialog */}\n <ConfirmDialog\n open={revokeTarget !== null}\n onOpenChange={(open) => {\n if (!open) setRevokeTarget(null);\n }}\n onConfirm={handleRevoke}\n title=\"Revoke API Key\"\n description={`Are you sure you want to revoke \"${revokeTarget?.name ?? \"\"}\"? This action cannot be undone. Any application or service using this key will immediately lose access.`}\n confirmLabel=\"Revoke Key\"\n cancelLabel=\"Cancel\"\n variant=\"danger\"\n loading={revokeLoading}\n />\n\n {/* Rotate confirmation dialog */}\n <ConfirmDialog\n open={rotateTarget !== null}\n onOpenChange={(open) => {\n if (!open) setRotateTarget(null);\n }}\n onConfirm={handleRotate}\n title=\"Rotate API Key Secret\"\n description={`This will generate a new secret for \"${rotateTarget?.name ?? \"\"}\" and immediately invalidate the current one. Make sure to update all services using this key before the old secret stops working.`}\n confirmLabel=\"Rotate Secret\"\n cancelLabel=\"Cancel\"\n variant=\"warning\"\n loading={rotateLoading}\n />\n\n <ConfirmDialog\n open={showBulkRevokeDialog}\n onOpenChange={setShowBulkRevokeDialog}\n onConfirm={handleBulkRevoke}\n title=\"Revoke selected API keys\"\n description={`Revoke ${selectedActiveKeys.length} selected API key${selectedActiveKeys.length === 1 ? \"\" : \"s\"}? Any service using these keys will immediately lose access.`}\n confirmLabel=\"Revoke Keys\"\n cancelLabel=\"Cancel\"\n variant=\"danger\"\n loading={revokeLoading}\n />\n </PageLayout>\n </PermissionGuard>\n );\n};\n"],"mappings":"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"}
@@ -105,7 +105,7 @@ var y = 25, b = (e) => o(new Date(e), "MMM d, yyyy HH:mm:ss"), x = (e) => e.repl
105
105
  }), /* @__PURE__ */ _("select", {
106
106
  value: O,
107
107
  onChange: (e) => k(e.target.value),
108
- className: "rounded-md border bg-bg-surface px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary",
108
+ className: "rounded-md border bg-bg-surface px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary",
109
109
  children: [/* @__PURE__ */ g("option", {
110
110
  value: "all",
111
111
  children: "All actions"
@@ -1 +1 @@
1
- {"version":3,"file":"AppLogsPage.js","names":[],"sources":["../../../src/devportal/pages/AppLogsPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useEffect, useMemo, useState } from \"react\";\nimport { useParams } from \"react-router\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n Download,\n Filter,\n Loader2,\n RefreshCw,\n AlertCircle,\n FileClock,\n} from \"lucide-react\";\nimport { format } from \"date-fns\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport {\n type GetApplicationAuditLogsQuery,\n getErrorMessage,\n useGetApplicationAuditLogsQuery,\n} from \"../hooks\";\n\nconst PAGE_SIZE = 25;\ntype AuditLogEntry =\n GetApplicationAuditLogsQuery[\"auditLogs\"][\"edges\"][number][\"node\"];\n\nconst formatTimestamp = (value: string) =>\n format(new Date(value), \"MMM d, yyyy HH:mm:ss\");\n\nconst formatAction = (value: string) =>\n value\n .replace(/_/g, \" \")\n .toLowerCase()\n .replace(/\\b\\w/g, (character) => character.toUpperCase());\n\nconst metadataPreview = (metadata: unknown): string => {\n if (!metadata) return \"\";\n\n try {\n return JSON.stringify(metadata, null, 2);\n } catch {\n return \"\";\n }\n};\n\nexport const AppLogsPage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const { appId } = useParams<{ appId: string }>();\n const { navigate, basePath, currentUser } = useDevportal();\n const resolveActorLabel = (actorId: string): string => {\n if (currentUser?.id === actorId && currentUser.email)\n return currentUser.email;\n return actorId;\n };\n const [actionFilter, setActionFilter] = useState(\"all\");\n const [searchQuery, setSearchQuery] = useState(\"\");\n const [page, setPage] = useState(1);\n\n const { data, loading, error, refetch } = useGetApplicationAuditLogsQuery({\n variables: {\n applicationId: appId || \"\",\n limit: PAGE_SIZE,\n offset: (page - 1) * PAGE_SIZE,\n },\n skip: !appId,\n fetchPolicy: \"cache-and-network\",\n });\n\n const application = data?.application;\n\n useEffect(() => {\n const name = application?.name;\n if (!appId || !name) return;\n const reg = (\n window as unknown as {\n __breadcrumbRegistry?: {\n register: (id: string, name: string) => void;\n unregister: (id: string) => void;\n };\n }\n ).__breadcrumbRegistry;\n reg?.register(appId, name);\n return () => reg?.unregister(appId);\n }, [appId, application?.name]);\n\n const totalCount = data?.auditLogs?.totalCount ?? 0;\n const auditLogs: AuditLogEntry[] =\n data?.auditLogs?.edges?.map(\n (edge: GetApplicationAuditLogsQuery[\"auditLogs\"][\"edges\"][number]) =>\n edge.node,\n ) ?? [];\n\n const availableActions = useMemo(\n () =>\n Array.from(\n new Set(auditLogs.map((entry: AuditLogEntry) => entry.action)),\n ).sort((left, right) => left.localeCompare(right)),\n [auditLogs],\n );\n\n const filteredLogs = useMemo(() => {\n const normalizedQuery = searchQuery.trim().toLowerCase();\n\n return auditLogs.filter((entry: AuditLogEntry) => {\n const matchesAction =\n actionFilter === \"all\" || entry.action === actionFilter;\n if (!matchesAction) return false;\n\n if (!normalizedQuery) return true;\n\n const haystack = [\n entry.action,\n entry.actorId,\n resolveActorLabel(entry.actorId),\n entry.resource,\n metadataPreview(entry.metadata),\n ]\n .join(\" \")\n .toLowerCase();\n\n return haystack.includes(normalizedQuery);\n });\n }, [actionFilter, auditLogs, searchQuery]);\n\n const exportPayload = () => {\n const blob = new Blob([JSON.stringify(filteredLogs, null, 2)], {\n type: \"application/json\",\n });\n const url = URL.createObjectURL(blob);\n const anchor = document.createElement(\"a\");\n anchor.href = url;\n anchor.download = `${application?.slug || appId || \"application\"}-audit-log.json`;\n anchor.click();\n URL.revokeObjectURL(url);\n };\n\n if (loading && !data) {\n return (\n <PageLayout\n title=\"Application Activity\"\n description=\"Loading application audit history\"\n backLink={appId ? `/apps/${appId}` : \"/apps\"}\n backLabel=\"Back to Application\"\n >\n <div className=\"flex items-center justify-center py-16 text-text-secondary\">\n <Loader2 className=\"h-8 w-8 animate-spin\" />\n </div>\n </PageLayout>\n );\n }\n\n if (error || !application) {\n return (\n <PageLayout\n title=\"Application Activity\"\n description=\"Review audit events for this application\"\n backLink={appId ? `/apps/${appId}` : \"/apps\"}\n backLabel=\"Back to Application\"\n >\n <EmptyState\n icon={<AlertCircle size={32} className=\"text-status-error-text\" />}\n title=\"Unable to load activity\"\n description={error ? getErrorMessage(error) : \"Application not found\"}\n />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Application Activity\"\n description={`Audit events for ${application.name}`}\n purpose=\"A chronological audit trail of everything that happened to this app — config changes, secret rotations, webhook deliveries, and access events. Use it to debug integrations and to answer 'who changed what, and when?'\"\n backLink={`/apps/${application.id}`}\n backLabel=\"Back to Application\"\n actions={\n <div className=\"flex flex-wrap gap-2\">\n <button\n type=\"button\"\n onClick={() => void refetch()}\n className=\"inline-flex items-center gap-2 rounded-md border px-3 py-2 text-sm font-medium hover:bg-accent\"\n >\n <RefreshCw size={16} />\n Refresh\n </button>\n <button\n type=\"button\"\n onClick={exportPayload}\n className=\"inline-flex items-center gap-2 rounded-md border px-3 py-2 text-sm font-medium hover:bg-accent\"\n >\n <Download size={16} />\n Export JSON\n </button>\n </div>\n }\n >\n <div className=\"grid gap-4 rounded-card border border-border-seam bg-bg-surface p-4 lg:grid-cols-[220px_minmax(0,1fr)_auto] lg:items-center\">\n <label className=\"grid gap-2 text-sm font-medium\">\n <span className=\"flex items-center gap-2 text-text-secondary\">\n <Filter size={16} />\n Filter by action\n </span>\n <select\n value={actionFilter}\n onChange={(event) => setActionFilter(event.target.value)}\n className=\"rounded-md border bg-bg-surface px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary\"\n >\n <option value=\"all\">All actions</option>\n {availableActions.map((action) => (\n <option key={action} value={action}>\n {formatAction(action)}\n </option>\n ))}\n </select>\n </label>\n\n <label className=\"grid gap-2 text-sm font-medium\">\n <span className=\"text-text-secondary\">Search metadata</span>\n <input\n type=\"text\"\n value={searchQuery}\n onChange={(event) => setSearchQuery(event.target.value)}\n placeholder=\"Search actor, action, or metadata\"\n className=\"rounded-md border bg-bg-surface px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary\"\n />\n </label>\n\n <div className=\"rounded-full bg-bg-sunken px-3 py-2 text-sm text-text-secondary\">\n {totalCount} total events\n </div>\n </div>\n\n <PageSection>\n {filteredLogs.length > 0 ? (\n <div className=\"space-y-3 rounded-card border border-border-seam bg-bg-surface p-4\">\n {filteredLogs.map((entry: AuditLogEntry) => (\n <article\n key={entry.id}\n className=\"rounded-card border border-border-seam bg-bg-surface p-4\"\n >\n <div className=\"flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between\">\n <div className=\"space-y-2\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"rounded-full bg-action-primary-bg/10 px-2.5 py-1 text-xs font-medium text-primary\">\n {formatAction(entry.action)}\n </span>\n <span className=\"text-xs text-text-secondary\">\n Actor {resolveActorLabel(entry.actorId)}\n </span>\n </div>\n <p className=\"text-sm text-text-secondary\">\n Resource: {entry.resource}\n {entry.resourceId ? ` · ${entry.resourceId}` : \"\"}\n </p>\n </div>\n\n <time className=\"text-xs text-text-secondary\">\n {formatTimestamp(entry.createdAt)}\n </time>\n </div>\n\n {entry.metadata && (\n <pre className=\"mt-3 overflow-x-auto rounded-md bg-bg-sunken p-3 text-xs text-text-primary\">\n {metadataPreview(entry.metadata)}\n </pre>\n )}\n\n {entry.changes && (\n <details className=\"mt-3 rounded-md border bg-bg-surface p-3\">\n <summary className=\"cursor-pointer text-sm font-medium text-text-primary\">\n View captured changes\n </summary>\n <pre className=\"mt-3 overflow-x-auto text-xs text-text-secondary\">\n {metadataPreview(entry.changes)}\n </pre>\n </details>\n )}\n </article>\n ))}\n </div>\n ) : (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No audit events found\"\n description=\"Events will appear here after application or webhook actions are performed through the public gateway.\"\n />\n )}\n </PageSection>\n\n {data.auditLogs?.pageInfo?.hasNextPage && (\n <div className=\"flex justify-center\">\n <button\n type=\"button\"\n onClick={() => setPage((previous) => previous + 1)}\n className=\"inline-flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium hover:bg-accent\"\n >\n <RefreshCw size={16} />\n Next page\n </button>\n </div>\n )}\n </PageLayout>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"AppLogsPage.js","names":[],"sources":["../../../src/devportal/pages/AppLogsPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useEffect, useMemo, useState } from \"react\";\nimport { useParams } from \"react-router\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n Download,\n Filter,\n Loader2,\n RefreshCw,\n AlertCircle,\n FileClock,\n} from \"lucide-react\";\nimport { format } from \"date-fns\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport {\n type GetApplicationAuditLogsQuery,\n getErrorMessage,\n useGetApplicationAuditLogsQuery,\n} from \"../hooks\";\n\nconst PAGE_SIZE = 25;\ntype AuditLogEntry =\n GetApplicationAuditLogsQuery[\"auditLogs\"][\"edges\"][number][\"node\"];\n\nconst formatTimestamp = (value: string) =>\n format(new Date(value), \"MMM d, yyyy HH:mm:ss\");\n\nconst formatAction = (value: string) =>\n value\n .replace(/_/g, \" \")\n .toLowerCase()\n .replace(/\\b\\w/g, (character) => character.toUpperCase());\n\nconst metadataPreview = (metadata: unknown): string => {\n if (!metadata) return \"\";\n\n try {\n return JSON.stringify(metadata, null, 2);\n } catch {\n return \"\";\n }\n};\n\nexport const AppLogsPage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const { appId } = useParams<{ appId: string }>();\n const { navigate, basePath, currentUser } = useDevportal();\n const resolveActorLabel = (actorId: string): string => {\n if (currentUser?.id === actorId && currentUser.email)\n return currentUser.email;\n return actorId;\n };\n const [actionFilter, setActionFilter] = useState(\"all\");\n const [searchQuery, setSearchQuery] = useState(\"\");\n const [page, setPage] = useState(1);\n\n const { data, loading, error, refetch } = useGetApplicationAuditLogsQuery({\n variables: {\n applicationId: appId || \"\",\n limit: PAGE_SIZE,\n offset: (page - 1) * PAGE_SIZE,\n },\n skip: !appId,\n fetchPolicy: \"cache-and-network\",\n });\n\n const application = data?.application;\n\n useEffect(() => {\n const name = application?.name;\n if (!appId || !name) return;\n const reg = (\n window as unknown as {\n __breadcrumbRegistry?: {\n register: (id: string, name: string) => void;\n unregister: (id: string) => void;\n };\n }\n ).__breadcrumbRegistry;\n reg?.register(appId, name);\n return () => reg?.unregister(appId);\n }, [appId, application?.name]);\n\n const totalCount = data?.auditLogs?.totalCount ?? 0;\n const auditLogs: AuditLogEntry[] =\n data?.auditLogs?.edges?.map(\n (edge: GetApplicationAuditLogsQuery[\"auditLogs\"][\"edges\"][number]) =>\n edge.node,\n ) ?? [];\n\n const availableActions = useMemo(\n () =>\n Array.from(\n new Set(auditLogs.map((entry: AuditLogEntry) => entry.action)),\n ).sort((left, right) => left.localeCompare(right)),\n [auditLogs],\n );\n\n const filteredLogs = useMemo(() => {\n const normalizedQuery = searchQuery.trim().toLowerCase();\n\n return auditLogs.filter((entry: AuditLogEntry) => {\n const matchesAction =\n actionFilter === \"all\" || entry.action === actionFilter;\n if (!matchesAction) return false;\n\n if (!normalizedQuery) return true;\n\n const haystack = [\n entry.action,\n entry.actorId,\n resolveActorLabel(entry.actorId),\n entry.resource,\n metadataPreview(entry.metadata),\n ]\n .join(\" \")\n .toLowerCase();\n\n return haystack.includes(normalizedQuery);\n });\n }, [actionFilter, auditLogs, searchQuery]);\n\n const exportPayload = () => {\n const blob = new Blob([JSON.stringify(filteredLogs, null, 2)], {\n type: \"application/json\",\n });\n const url = URL.createObjectURL(blob);\n const anchor = document.createElement(\"a\");\n anchor.href = url;\n anchor.download = `${application?.slug || appId || \"application\"}-audit-log.json`;\n anchor.click();\n URL.revokeObjectURL(url);\n };\n\n if (loading && !data) {\n return (\n <PageLayout\n title=\"Application Activity\"\n description=\"Loading application audit history\"\n backLink={appId ? `/apps/${appId}` : \"/apps\"}\n backLabel=\"Back to Application\"\n >\n <div className=\"flex items-center justify-center py-16 text-text-secondary\">\n <Loader2 className=\"h-8 w-8 animate-spin\" />\n </div>\n </PageLayout>\n );\n }\n\n if (error || !application) {\n return (\n <PageLayout\n title=\"Application Activity\"\n description=\"Review audit events for this application\"\n backLink={appId ? `/apps/${appId}` : \"/apps\"}\n backLabel=\"Back to Application\"\n >\n <EmptyState\n icon={<AlertCircle size={32} className=\"text-status-error-text\" />}\n title=\"Unable to load activity\"\n description={error ? getErrorMessage(error) : \"Application not found\"}\n />\n </PageLayout>\n );\n }\n\n return (\n <PageLayout\n title=\"Application Activity\"\n description={`Audit events for ${application.name}`}\n purpose=\"A chronological audit trail of everything that happened to this app — config changes, secret rotations, webhook deliveries, and access events. Use it to debug integrations and to answer 'who changed what, and when?'\"\n backLink={`/apps/${application.id}`}\n backLabel=\"Back to Application\"\n actions={\n <div className=\"flex flex-wrap gap-2\">\n <button\n type=\"button\"\n onClick={() => void refetch()}\n className=\"inline-flex items-center gap-2 rounded-md border px-3 py-2 text-sm font-medium hover:bg-accent\"\n >\n <RefreshCw size={16} />\n Refresh\n </button>\n <button\n type=\"button\"\n onClick={exportPayload}\n className=\"inline-flex items-center gap-2 rounded-md border px-3 py-2 text-sm font-medium hover:bg-accent\"\n >\n <Download size={16} />\n Export JSON\n </button>\n </div>\n }\n >\n <div className=\"grid gap-4 rounded-card border border-border-seam bg-bg-surface p-4 lg:grid-cols-[220px_minmax(0,1fr)_auto] lg:items-center\">\n <label className=\"grid gap-2 text-sm font-medium\">\n <span className=\"flex items-center gap-2 text-text-secondary\">\n <Filter size={16} />\n Filter by action\n </span>\n <select\n value={actionFilter}\n onChange={(event) => setActionFilter(event.target.value)}\n className=\"rounded-md border bg-bg-surface px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary\"\n >\n <option value=\"all\">All actions</option>\n {availableActions.map((action) => (\n <option key={action} value={action}>\n {formatAction(action)}\n </option>\n ))}\n </select>\n </label>\n\n <label className=\"grid gap-2 text-sm font-medium\">\n <span className=\"text-text-secondary\">Search metadata</span>\n <input\n type=\"text\"\n value={searchQuery}\n onChange={(event) => setSearchQuery(event.target.value)}\n placeholder=\"Search actor, action, or metadata\"\n className=\"rounded-md border bg-bg-surface px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-primary\"\n />\n </label>\n\n <div className=\"rounded-full bg-bg-sunken px-3 py-2 text-sm text-text-secondary\">\n {totalCount} total events\n </div>\n </div>\n\n <PageSection>\n {filteredLogs.length > 0 ? (\n <div className=\"space-y-3 rounded-card border border-border-seam bg-bg-surface p-4\">\n {filteredLogs.map((entry: AuditLogEntry) => (\n <article\n key={entry.id}\n className=\"rounded-card border border-border-seam bg-bg-surface p-4\"\n >\n <div className=\"flex flex-col gap-3 lg:flex-row lg:items-start lg:justify-between\">\n <div className=\"space-y-2\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"rounded-full bg-action-primary-bg/10 px-2.5 py-1 text-xs font-medium text-primary\">\n {formatAction(entry.action)}\n </span>\n <span className=\"text-xs text-text-secondary\">\n Actor {resolveActorLabel(entry.actorId)}\n </span>\n </div>\n <p className=\"text-sm text-text-secondary\">\n Resource: {entry.resource}\n {entry.resourceId ? ` · ${entry.resourceId}` : \"\"}\n </p>\n </div>\n\n <time className=\"text-xs text-text-secondary\">\n {formatTimestamp(entry.createdAt)}\n </time>\n </div>\n\n {entry.metadata && (\n <pre className=\"mt-3 overflow-x-auto rounded-md bg-bg-sunken p-3 text-xs text-text-primary\">\n {metadataPreview(entry.metadata)}\n </pre>\n )}\n\n {entry.changes && (\n <details className=\"mt-3 rounded-md border bg-bg-surface p-3\">\n <summary className=\"cursor-pointer text-sm font-medium text-text-primary\">\n View captured changes\n </summary>\n <pre className=\"mt-3 overflow-x-auto text-xs text-text-secondary\">\n {metadataPreview(entry.changes)}\n </pre>\n </details>\n )}\n </article>\n ))}\n </div>\n ) : (\n <EmptyState\n illustration=\"empty-data\"\n title=\"No audit events found\"\n description=\"Events will appear here after application or webhook actions are performed through the public gateway.\"\n />\n )}\n </PageSection>\n\n {data.auditLogs?.pageInfo?.hasNextPage && (\n <div className=\"flex justify-center\">\n <button\n type=\"button\"\n onClick={() => setPage((previous) => previous + 1)}\n className=\"inline-flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium hover:bg-accent\"\n >\n <RefreshCw size={16} />\n Next page\n </button>\n </div>\n )}\n </PageLayout>\n );\n};\n"],"mappings":"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"}
@@ -2247,7 +2247,7 @@ var Ye = [
2247
2247
  className: "flex-1 rounded-md border px-3 py-2 text-sm font-mono bg-bg-surface"
2248
2248
  }), /* @__PURE__ */ U("select", {
2249
2249
  onChange: (e) => g(e.target.value),
2250
- className: "rounded-md border px-3 py-2 text-sm bg-bg-surface",
2250
+ className: "rounded-md border px-3 py-2 text-sm bg-bg-surface scheme-normal dark:scheme-dark [&_option]:bg-bg-surface [&_option]:text-text-primary",
2251
2251
  value: h,
2252
2252
  children: [/* @__PURE__ */ H("option", {
2253
2253
  value: `${n || "https://gw-global.local.burdenoff.com"}/global/graphql`,