@replicated/portal-components 0.0.24 → 0.0.26

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 (180) hide show
  1. package/components/metadata/registry.json +2 -2
  2. package/components/metadata/registry.md +2 -2
  3. package/dist/actions/branding-actions.js +6 -4
  4. package/dist/actions/branding-actions.js.map +1 -1
  5. package/dist/actions/change-team.js +1 -0
  6. package/dist/actions/change-team.js.map +1 -1
  7. package/dist/actions/index.d.mts +1 -1
  8. package/dist/actions/index.d.ts +1 -1
  9. package/dist/actions/index.js +1 -53
  10. package/dist/actions/index.js.map +1 -1
  11. package/dist/actions/install-actions.d.mts +1 -1
  12. package/dist/actions/install-actions.d.ts +1 -1
  13. package/dist/actions/install-actions.js +1 -0
  14. package/dist/actions/install-actions.js.map +1 -1
  15. package/dist/actions/invite-actions.js +6 -5
  16. package/dist/actions/invite-actions.js.map +1 -1
  17. package/dist/actions/magic-link-actions.js +6 -5
  18. package/dist/actions/magic-link-actions.js.map +1 -1
  19. package/dist/actions/service-account.d.mts +1 -1
  20. package/dist/actions/service-account.d.ts +1 -1
  21. package/dist/actions/service-account.js +1 -0
  22. package/dist/actions/service-account.js.map +1 -1
  23. package/dist/actions/support-bundles.d.mts +1 -1
  24. package/dist/actions/support-bundles.d.ts +1 -1
  25. package/dist/actions/support-bundles.js +1 -50
  26. package/dist/actions/support-bundles.js.map +1 -1
  27. package/dist/actions/team-settings.d.mts +1 -1
  28. package/dist/actions/team-settings.d.ts +1 -1
  29. package/dist/actions/team-settings.js +1 -0
  30. package/dist/actions/team-settings.js.map +1 -1
  31. package/dist/actions/trial-signup-actions.js +6 -5
  32. package/dist/actions/trial-signup-actions.js.map +1 -1
  33. package/dist/actions/trial-signup.js +6 -5
  34. package/dist/actions/trial-signup.js.map +1 -1
  35. package/dist/actions/user-settings.d.mts +1 -1
  36. package/dist/actions/user-settings.d.ts +1 -1
  37. package/dist/actions/user-settings.js +1 -0
  38. package/dist/actions/user-settings.js.map +1 -1
  39. package/dist/airgap-instances.d.mts +1 -1
  40. package/dist/airgap-instances.d.ts +1 -1
  41. package/dist/airgap-instances.js +25 -25
  42. package/dist/airgap-instances.js.map +1 -1
  43. package/dist/{saml-handlers.d.mts → api/saml.d.mts} +2 -2
  44. package/dist/{saml-handlers.d.ts → api/saml.d.ts} +2 -2
  45. package/dist/{saml-handlers.js → api/saml.js} +4 -3
  46. package/dist/api/saml.js.map +1 -0
  47. package/dist/api/support-bundles.d.mts +22 -0
  48. package/dist/api/support-bundles.d.ts +22 -0
  49. package/dist/api/support-bundles.js +245 -0
  50. package/dist/api/support-bundles.js.map +1 -0
  51. package/dist/{branding-BsMSywts.d.mts → branding-DmsrDTNE.d.mts} +2 -1
  52. package/dist/{branding-BsMSywts.d.ts → branding-DmsrDTNE.d.ts} +2 -1
  53. package/dist/esm/actions/branding-actions.js +6 -4
  54. package/dist/esm/actions/branding-actions.js.map +1 -1
  55. package/dist/esm/actions/change-team.js +1 -0
  56. package/dist/esm/actions/change-team.js.map +1 -1
  57. package/dist/esm/actions/index.js +2 -53
  58. package/dist/esm/actions/index.js.map +1 -1
  59. package/dist/esm/actions/install-actions.js +1 -0
  60. package/dist/esm/actions/install-actions.js.map +1 -1
  61. package/dist/esm/actions/invite-actions.js +6 -5
  62. package/dist/esm/actions/invite-actions.js.map +1 -1
  63. package/dist/esm/actions/magic-link-actions.js +6 -5
  64. package/dist/esm/actions/magic-link-actions.js.map +1 -1
  65. package/dist/esm/actions/service-account.js +1 -0
  66. package/dist/esm/actions/service-account.js.map +1 -1
  67. package/dist/esm/actions/support-bundles.js +2 -50
  68. package/dist/esm/actions/support-bundles.js.map +1 -1
  69. package/dist/esm/actions/team-settings.js +1 -0
  70. package/dist/esm/actions/team-settings.js.map +1 -1
  71. package/dist/esm/actions/trial-signup-actions.js +6 -5
  72. package/dist/esm/actions/trial-signup-actions.js.map +1 -1
  73. package/dist/esm/actions/trial-signup.js +6 -5
  74. package/dist/esm/actions/trial-signup.js.map +1 -1
  75. package/dist/esm/actions/user-settings.js +1 -0
  76. package/dist/esm/actions/user-settings.js.map +1 -1
  77. package/dist/esm/airgap-instances.js +25 -25
  78. package/dist/esm/airgap-instances.js.map +1 -1
  79. package/dist/esm/{saml-handlers.js → api/saml.js} +4 -3
  80. package/dist/esm/api/saml.js.map +1 -0
  81. package/dist/esm/api/support-bundles.js +243 -0
  82. package/dist/esm/api/support-bundles.js.map +1 -0
  83. package/dist/esm/helm-install-wizard.js +1 -0
  84. package/dist/esm/helm-install-wizard.js.map +1 -1
  85. package/dist/esm/index.js +262 -220
  86. package/dist/esm/index.js.map +1 -1
  87. package/dist/esm/install-actions.js +1 -0
  88. package/dist/esm/install-actions.js.map +1 -1
  89. package/dist/esm/instance-card.js +25 -25
  90. package/dist/esm/instance-card.js.map +1 -1
  91. package/dist/esm/license-details.js +1 -0
  92. package/dist/esm/license-details.js.map +1 -1
  93. package/dist/esm/linux-install-wizard.js +1 -0
  94. package/dist/esm/linux-install-wizard.js.map +1 -1
  95. package/dist/esm/logout-action.js +1 -1
  96. package/dist/esm/logout-action.js.map +1 -1
  97. package/dist/esm/logout-button.js +5 -7
  98. package/dist/esm/logout-button.js.map +1 -1
  99. package/dist/esm/online-instance-list.js +25 -25
  100. package/dist/esm/online-instance-list.js.map +1 -1
  101. package/dist/esm/saml-callback-client.js.map +1 -1
  102. package/dist/esm/support-card.js +1 -0
  103. package/dist/esm/support-card.js.map +1 -1
  104. package/dist/esm/top-nav.js +82 -79
  105. package/dist/esm/top-nav.js.map +1 -1
  106. package/dist/esm/update-layout.js +82 -79
  107. package/dist/esm/update-layout.js.map +1 -1
  108. package/dist/esm/upload-support-bundle-modal.js +46 -16
  109. package/dist/esm/upload-support-bundle-modal.js.map +1 -1
  110. package/dist/esm/utils/index.js +48 -6
  111. package/dist/esm/utils/index.js.map +1 -1
  112. package/dist/helm-install-wizard.d.mts +2 -2
  113. package/dist/helm-install-wizard.d.ts +2 -2
  114. package/dist/helm-install-wizard.js +1 -0
  115. package/dist/helm-install-wizard.js.map +1 -1
  116. package/dist/{index-DXy7RxOX.d.ts → index-Bcp17Mf3.d.ts} +1 -16
  117. package/dist/{index-JX9-CIMA.d.mts → index-DaH1bSuO.d.mts} +1 -16
  118. package/dist/index.d.mts +6 -5
  119. package/dist/index.d.ts +6 -5
  120. package/dist/index.js +262 -220
  121. package/dist/index.js.map +1 -1
  122. package/dist/install-actions.d.mts +2 -2
  123. package/dist/install-actions.d.ts +2 -2
  124. package/dist/install-actions.js +1 -0
  125. package/dist/install-actions.js.map +1 -1
  126. package/dist/instance-card.d.mts +1 -1
  127. package/dist/instance-card.d.ts +1 -1
  128. package/dist/instance-card.js +25 -25
  129. package/dist/instance-card.js.map +1 -1
  130. package/dist/license-details.js +1 -0
  131. package/dist/license-details.js.map +1 -1
  132. package/dist/linux-install-wizard.d.mts +2 -2
  133. package/dist/linux-install-wizard.d.ts +2 -2
  134. package/dist/linux-install-wizard.js +1 -0
  135. package/dist/linux-install-wizard.js.map +1 -1
  136. package/dist/logout-action.d.mts +1 -1
  137. package/dist/logout-action.d.ts +1 -1
  138. package/dist/logout-action.js +1 -1
  139. package/dist/logout-action.js.map +1 -1
  140. package/dist/logout-button.d.mts +1 -1
  141. package/dist/logout-button.d.ts +1 -1
  142. package/dist/logout-button.js +5 -7
  143. package/dist/logout-button.js.map +1 -1
  144. package/dist/online-instance-list.d.mts +1 -1
  145. package/dist/online-instance-list.d.ts +1 -1
  146. package/dist/online-instance-list.js +25 -25
  147. package/dist/online-instance-list.js.map +1 -1
  148. package/dist/pending-installations.d.mts +1 -1
  149. package/dist/pending-installations.d.ts +1 -1
  150. package/dist/saml-callback-client.d.mts +1 -1
  151. package/dist/saml-callback-client.d.ts +1 -1
  152. package/dist/saml-callback-client.js.map +1 -1
  153. package/dist/security-card.d.mts +1 -1
  154. package/dist/security-card.d.ts +1 -1
  155. package/dist/styles.css +4 -0
  156. package/dist/support-bundles-card.d.mts +1 -1
  157. package/dist/support-bundles-card.d.ts +1 -1
  158. package/dist/support-card.js +1 -0
  159. package/dist/support-card.js.map +1 -1
  160. package/dist/{top-nav-CEqw0KpO.d.ts → top-nav-8f2U69MF.d.ts} +1 -1
  161. package/dist/{top-nav-BUQAGoG1.d.mts → top-nav-B2yA3PC7.d.mts} +1 -1
  162. package/dist/top-nav.d.mts +2 -2
  163. package/dist/top-nav.d.ts +2 -2
  164. package/dist/top-nav.js +82 -79
  165. package/dist/top-nav.js.map +1 -1
  166. package/dist/update-layout.js +82 -79
  167. package/dist/update-layout.js.map +1 -1
  168. package/dist/upload-support-bundle-modal.d.mts +9 -9
  169. package/dist/upload-support-bundle-modal.d.ts +9 -9
  170. package/dist/upload-support-bundle-modal.js +46 -16
  171. package/dist/upload-support-bundle-modal.js.map +1 -1
  172. package/dist/upload-with-progress-D760HthX.d.mts +35 -0
  173. package/dist/upload-with-progress-D760HthX.d.ts +35 -0
  174. package/dist/utils/index.d.mts +4 -18
  175. package/dist/utils/index.d.ts +4 -18
  176. package/dist/utils/index.js +48 -6
  177. package/dist/utils/index.js.map +1 -1
  178. package/package.json +16 -6
  179. package/dist/esm/saml-handlers.js.map +0 -1
  180. package/dist/saml-handlers.js.map +0 -1
@@ -1,23 +1,23 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { U as UploadSupportBundleResult } from './upload-with-progress-D760HthX.mjs';
2
3
 
3
4
  interface UploadSupportBundleModalProps {
4
5
  isOpen: boolean;
5
6
  onClose: () => void;
6
- /** Server action to upload the support bundle */
7
- uploadSupportBundle: (formData: FormData) => Promise<{
8
- success: boolean;
9
- error?: string;
10
- bundleId?: string;
11
- bundleSlug?: string;
12
- }>;
7
+ /**
8
+ * Upload function with progress tracking
9
+ * onProgress receives percentage (0-100)
10
+ * signal is an AbortSignal for cancellation
11
+ */
12
+ uploadWithProgress: (file: File, appId: string, onProgress: (percent: number) => void, signal: AbortSignal) => Promise<UploadSupportBundleResult>;
13
13
  /** Callback after successful upload - performs async polling to refresh bundle list */
14
14
  onUploadSuccess: () => Promise<void>;
15
15
  appId: string;
16
16
  primaryColor?: string;
17
17
  }
18
18
  declare const UploadSupportBundleModal: {
19
- ({ isOpen, onClose, uploadSupportBundle, onUploadSuccess, appId, primaryColor }: UploadSupportBundleModalProps): react_jsx_runtime.JSX.Element | null;
19
+ ({ isOpen, onClose, uploadWithProgress, onUploadSuccess, appId, primaryColor }: UploadSupportBundleModalProps): react_jsx_runtime.JSX.Element | null;
20
20
  displayName: string;
21
21
  };
22
22
 
23
- export { UploadSupportBundleModal };
23
+ export { UploadSupportBundleModal, UploadSupportBundleResult };
@@ -1,23 +1,23 @@
1
1
  import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import { U as UploadSupportBundleResult } from './upload-with-progress-D760HthX.js';
2
3
 
3
4
  interface UploadSupportBundleModalProps {
4
5
  isOpen: boolean;
5
6
  onClose: () => void;
6
- /** Server action to upload the support bundle */
7
- uploadSupportBundle: (formData: FormData) => Promise<{
8
- success: boolean;
9
- error?: string;
10
- bundleId?: string;
11
- bundleSlug?: string;
12
- }>;
7
+ /**
8
+ * Upload function with progress tracking
9
+ * onProgress receives percentage (0-100)
10
+ * signal is an AbortSignal for cancellation
11
+ */
12
+ uploadWithProgress: (file: File, appId: string, onProgress: (percent: number) => void, signal: AbortSignal) => Promise<UploadSupportBundleResult>;
13
13
  /** Callback after successful upload - performs async polling to refresh bundle list */
14
14
  onUploadSuccess: () => Promise<void>;
15
15
  appId: string;
16
16
  primaryColor?: string;
17
17
  }
18
18
  declare const UploadSupportBundleModal: {
19
- ({ isOpen, onClose, uploadSupportBundle, onUploadSuccess, appId, primaryColor }: UploadSupportBundleModalProps): react_jsx_runtime.JSX.Element | null;
19
+ ({ isOpen, onClose, uploadWithProgress, onUploadSuccess, appId, primaryColor }: UploadSupportBundleModalProps): react_jsx_runtime.JSX.Element | null;
20
20
  displayName: string;
21
21
  };
22
22
 
23
- export { UploadSupportBundleModal };
23
+ export { UploadSupportBundleModal, UploadSupportBundleResult };
@@ -24,20 +24,27 @@ var DEFAULT_PRIMARY_COLOR = "#4f46e5";
24
24
  var UploadSupportBundleModal = ({
25
25
  isOpen,
26
26
  onClose,
27
- uploadSupportBundle,
27
+ uploadWithProgress,
28
28
  onUploadSuccess,
29
29
  appId,
30
30
  primaryColor = DEFAULT_PRIMARY_COLOR
31
31
  }) => {
32
32
  const [selectedFile, setSelectedFile] = react.useState(null);
33
33
  const [isUploading, setIsUploading] = react.useState(false);
34
+ const [uploadProgress, setUploadProgress] = react.useState(0);
34
35
  const [error, setError] = react.useState(null);
36
+ const [abortController, setAbortController] = react.useState(null);
35
37
  const fileInputRef = react.useRef(null);
36
38
  react.useEffect(() => {
37
39
  if (!isOpen) {
38
40
  setSelectedFile(null);
39
41
  setIsUploading(false);
42
+ setUploadProgress(0);
40
43
  setError(null);
44
+ setAbortController((prev) => {
45
+ prev?.abort();
46
+ return null;
47
+ });
41
48
  }
42
49
  }, [isOpen]);
43
50
  const handleFileChange = react.useCallback((event) => {
@@ -54,30 +61,38 @@ var UploadSupportBundleModal = ({
54
61
  if (!selectedFile || !appId) return;
55
62
  setError(null);
56
63
  setIsUploading(true);
64
+ setUploadProgress(0);
57
65
  try {
58
- const formData = new FormData();
59
- formData.append("file", selectedFile);
60
- formData.append("appId", appId);
61
- const result = await uploadSupportBundle(formData);
66
+ const controller = new AbortController();
67
+ setAbortController(controller);
68
+ const result = await uploadWithProgress(
69
+ selectedFile,
70
+ appId,
71
+ setUploadProgress,
72
+ controller.signal
73
+ );
62
74
  if (!result.success) {
63
75
  throw new Error(result.error ?? "Failed to upload support bundle");
64
76
  }
65
77
  await onUploadSuccess();
66
78
  onClose();
67
79
  } catch (err) {
68
- console.error("Upload error:", err);
69
- setError(err instanceof Error ? err.message : "Upload failed");
80
+ if (err instanceof Error && err.message !== "Upload cancelled") {
81
+ console.error("Upload error:", err);
82
+ setError(err.message);
83
+ }
70
84
  } finally {
71
85
  setIsUploading(false);
86
+ setUploadProgress(0);
87
+ setAbortController(null);
72
88
  }
73
- }, [selectedFile, appId, uploadSupportBundle, onUploadSuccess, onClose]);
89
+ }, [selectedFile, appId, uploadWithProgress, onUploadSuccess, onClose]);
74
90
  const handleCancel = react.useCallback(() => {
75
- if (isUploading) {
76
- setError("Upload cannot be cancelled once started");
77
- return;
91
+ if (isUploading && abortController) {
92
+ abortController.abort();
78
93
  }
79
94
  onClose();
80
- }, [isUploading, onClose]);
95
+ }, [isUploading, abortController, onClose]);
81
96
  const handleBackdropClick = react.useCallback((e) => {
82
97
  if (e.target === e.currentTarget && !isUploading) {
83
98
  onClose();
@@ -113,9 +128,24 @@ var UploadSupportBundleModal = ({
113
128
  ] })
114
129
  ] }),
115
130
  error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-4 rounded-lg bg-red-50 px-3 py-2 text-sm text-red-700", children: error }),
116
- isUploading && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-6 mb-6 flex items-center justify-center gap-3", children: [
117
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-5 w-5 animate-spin rounded-full border-2 border-gray-300 border-t-transparent", style: { borderTopColor: primaryColor } }),
118
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium text-gray-700", children: "Uploading support bundle..." })
131
+ isUploading && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-6 mb-6", children: [
132
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-2", children: [
133
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-medium text-gray-700", children: "Uploading support bundle..." }),
134
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm font-medium text-gray-900", children: [
135
+ uploadProgress,
136
+ "%"
137
+ ] })
138
+ ] }),
139
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full bg-gray-200 rounded-full h-2", children: /* @__PURE__ */ jsxRuntime.jsx(
140
+ "div",
141
+ {
142
+ className: "h-2 rounded-full transition-all duration-300",
143
+ style: {
144
+ width: `${uploadProgress}%`,
145
+ backgroundColor: primaryColor || "#3B82F6"
146
+ }
147
+ }
148
+ ) })
119
149
  ] }),
120
150
  /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-6 flex justify-end gap-3", children: [
121
151
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -124,7 +154,7 @@ var UploadSupportBundleModal = ({
124
154
  type: "button",
125
155
  onClick: handleCancel,
126
156
  className: "rounded-lg px-4 py-2 text-sm font-medium text-gray-600 transition hover:bg-gray-100",
127
- children: isUploading ? "Cancel Upload" : "Cancel"
157
+ children: isUploading && abortController ? "Cancel Upload" : "Cancel"
128
158
  }
129
159
  ),
130
160
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/utils/format.ts","../src/utils/constants.ts","../src/components/upload-support-bundle-modal.tsx"],"names":["useState","useRef","useEffect","useCallback","jsx","jsxs"],"mappings":"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","file":"upload-support-bundle-modal.js","sourcesContent":["/**\n * Format bytes to human-readable string\n * @param bytes - Number of bytes\n * @param decimals - Number of decimal places (default: 1)\n * @returns Formatted string (e.g., \"1.5 MB\")\n */\nexport function formatBytes(bytes: number, decimals = 1): string {\n if (bytes === 0) return \"0 Bytes\";\n\n const k = 1024;\n const dm = decimals < 0 ? 0 : decimals;\n const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\", \"TB\"];\n\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n\n return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;\n}\n\n/**\n * Format date string to short numeric date (no time)\n * @param dateString - ISO date string or null/undefined\n * @returns Formatted string (e.g., \"01/27/2026\") or \"Never\" if no date\n */\nexport function formatDateShort(dateString?: string | null): string {\n if (!dateString) return \"Never\";\n const date = new Date(dateString);\n if (isNaN(date.getTime())) {\n return dateString;\n }\n return date.toLocaleDateString(\"en-US\", {\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\"\n });\n}\n\n/**\n * Format date string to human-readable local timestamp\n * @param dateString - ISO date string or null/undefined\n * @returns Formatted string (e.g., \"Jan 27, 2026, 10:32 PM\") or \"Never\" if no date\n */\nexport function formatDate(dateString?: string | null): string {\n if (!dateString) return \"Never\";\n const date = new Date(dateString);\n if (isNaN(date.getTime())) {\n return dateString;\n }\n return date.toLocaleDateString(\"en-US\", {\n month: \"short\",\n day: \"numeric\",\n year: \"numeric\",\n hour: \"numeric\",\n minute: \"2-digit\",\n hour12: true\n });\n}\n\n/**\n * Format date string to human-readable UTC timestamp\n * @param dateString - ISO date string\n * @returns Formatted string (e.g., \"01/27/2026, 22:32:39 UTC\")\n */\nexport function formatDateTime(dateString: string): string {\n const date = new Date(dateString);\n if (isNaN(date.getTime())) {\n return dateString;\n }\n return date.toLocaleString(\"en-US\", {\n timeZone: \"UTC\",\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n hour12: false\n }) + \" UTC\";\n}\n\n/**\n * Format date string to user-friendly local timestamp with 12-hour time\n * @param dateString - ISO date string or null/undefined\n * @returns Formatted string (e.g., \"1/27/2026, 10:32 PM\") or \"N/A\" if not provided\n */\nexport function formatDateTimeLocal(dateString?: string | null): string {\n if (!dateString) return \"N/A\";\n\n try {\n const date = new Date(dateString);\n if (isNaN(date.getTime())) {\n return dateString;\n }\n return date.toLocaleDateString(\"en-US\", {\n month: \"numeric\",\n day: \"numeric\",\n year: \"numeric\",\n hour: \"numeric\",\n minute: \"2-digit\",\n hour12: true\n });\n } catch {\n return \"N/A\";\n }\n}\n","/**\n * Default globe favicon matching the CiGlobe icon (Circum Icons)\n * Used as fallback when custom branding doesn't provide a favicon\n */\nexport const DEFAULT_FAVICON = \"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%239ca3af' stroke-width='1.5'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cellipse cx='12' cy='12' rx='4' ry='10'/%3E%3Cpath d='M2 12h20'/%3E%3C/svg%3E\";\n\n/**\n * Default primary brand color\n */\nexport const DEFAULT_PRIMARY_COLOR = \"#4f46e5\";\n\n/**\n * Default secondary brand color\n */\nexport const DEFAULT_SECONDARY_COLOR = \"#6366f1\";\n\n/**\n * Check if the API origin is HTTP (used for repldev environments)\n * This determines cookie security settings for cross-origin iframes\n * @returns true if the API origin starts with http:// (not https://)\n */\nexport const isHttpApiOrigin = (): boolean => {\n return process.env.REPLICATED_APP_ORIGIN?.startsWith('http://') || false;\n};\n","\"use client\";\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { formatBytes } from \"../utils/format\";\nimport { DEFAULT_PRIMARY_COLOR } from \"../utils/constants\";\n\ninterface UploadSupportBundleModalProps {\n isOpen: boolean;\n onClose: () => void;\n /** Server action to upload the support bundle */\n uploadSupportBundle: (formData: FormData) => Promise<{ \n success: boolean; \n error?: string; \n bundleId?: string;\n bundleSlug?: string;\n }>;\n /** Callback after successful upload - performs async polling to refresh bundle list */\n onUploadSuccess: () => Promise<void>;\n appId: string;\n primaryColor?: string;\n}\n\nexport const UploadSupportBundleModal = ({\n isOpen,\n onClose,\n uploadSupportBundle,\n onUploadSuccess,\n appId,\n primaryColor = DEFAULT_PRIMARY_COLOR\n}: UploadSupportBundleModalProps) => {\n const [selectedFile, setSelectedFile] = useState<File | null>(null);\n const [isUploading, setIsUploading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const fileInputRef = useRef<HTMLInputElement>(null);\n\n // Reset state when modal opens/closes\n useEffect(() => {\n if (!isOpen) {\n setSelectedFile(null);\n setIsUploading(false);\n setError(null);\n }\n }, [isOpen]);\n\n const handleFileChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {\n const file = event.target.files?.[0];\n if (!file) return;\n\n if (!file.name.endsWith(\".tgz\") && !file.name.endsWith(\".tar.gz\")) {\n setError(\"Invalid file type. Please select a .tgz or .tar.gz file.\");\n return;\n }\n\n setError(null);\n setSelectedFile(file);\n }, []);\n\n const handleUpload = useCallback(async () => {\n if (!selectedFile || !appId) return;\n\n setError(null);\n setIsUploading(true);\n\n try {\n // Create FormData to send to server action\n const formData = new FormData();\n formData.append(\"file\", selectedFile);\n formData.append(\"appId\", appId);\n\n // Upload via server action (streams to Enterprise Portal API)\n const result = await uploadSupportBundle(formData);\n\n if (!result.success) {\n throw new Error(result.error ?? \"Failed to upload support bundle\");\n }\n\n // Success - notify parent and wait for polling to complete\n await onUploadSuccess();\n onClose();\n } catch (err) {\n console.error(\"Upload error:\", err);\n setError(err instanceof Error ? err.message : \"Upload failed\");\n } finally {\n setIsUploading(false);\n }\n }, [selectedFile, appId, uploadSupportBundle, onUploadSuccess, onClose]);\n\n const handleCancel = useCallback(() => {\n // Note: Cannot abort server action once started\n if (isUploading) {\n setError(\"Upload cannot be cancelled once started\");\n return;\n }\n onClose();\n }, [isUploading, onClose]);\n\n const handleBackdropClick = useCallback((e: React.MouseEvent) => {\n if (e.target === e.currentTarget && !isUploading) {\n onClose();\n }\n }, [isUploading, onClose]);\n\n if (!isOpen) return null;\n\n return (\n <div\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50\"\n onClick={handleBackdropClick}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"upload-modal-title\"\n >\n <div className=\"w-full max-w-md rounded-2xl bg-white p-6 shadow-xl\">\n <h2 id=\"upload-modal-title\" className=\"text-lg font-semibold text-gray-900\">\n Upload Support Bundle\n </h2>\n\n <div className=\"mt-4\">\n <label className=\"block text-sm font-medium text-gray-700\">\n Select Support Bundle\n </label>\n <input\n ref={fileInputRef}\n type=\"file\"\n accept=\".tgz,.tar.gz,application/gzip,application/x-gzip\"\n onChange={handleFileChange}\n disabled={isUploading}\n className=\"mt-2 block w-full text-sm text-gray-500 file:mr-4 file:rounded-lg file:border-0 file:bg-gray-100 file:px-4 file:py-2 file:text-sm file:font-medium file:text-gray-700 hover:file:bg-gray-200 disabled:opacity-50\"\n />\n {selectedFile && (\n <div className=\"mt-3 flex items-center justify-between rounded-lg bg-gray-50 px-3 py-2 text-sm\">\n <span className=\"truncate text-gray-700\">{selectedFile.name}</span>\n <span className=\"ml-2 shrink-0 text-gray-500\">{formatBytes(selectedFile.size)}</span>\n </div>\n )}\n </div>\n\n {error && (\n <div className=\"mt-4 rounded-lg bg-red-50 px-3 py-2 text-sm text-red-700\">\n {error}\n </div>\n )}\n\n {isUploading && (\n <div className=\"mt-6 mb-6 flex items-center justify-center gap-3\">\n <div className=\"h-5 w-5 animate-spin rounded-full border-2 border-gray-300 border-t-transparent\" style={{ borderTopColor: primaryColor }} />\n <span className=\"text-sm font-medium text-gray-700\">\n Uploading support bundle...\n </span>\n </div>\n )}\n\n <div className=\"mt-6 flex justify-end gap-3\">\n <button\n type=\"button\"\n onClick={handleCancel}\n className=\"rounded-lg px-4 py-2 text-sm font-medium text-gray-600 transition hover:bg-gray-100\"\n >\n {isUploading ? \"Cancel Upload\" : \"Cancel\"}\n </button>\n <button\n type=\"button\"\n onClick={handleUpload}\n disabled={!selectedFile || isUploading}\n className=\"rounded-lg px-4 py-2 text-sm font-medium text-white transition-opacity duration-200 hover:opacity-90 disabled:opacity-50\"\n style={{ backgroundColor: primaryColor }}\n >\n {isUploading ? \"Uploading...\" : \"Upload\"}\n </button>\n </div>\n </div>\n </div>\n );\n};\n\nUploadSupportBundleModal.displayName = \"UploadSupportBundleModal\";\n"]}
1
+ {"version":3,"sources":["../src/utils/format.ts","../src/utils/constants.ts","../src/components/upload-support-bundle-modal.tsx"],"names":["useState","useRef","useEffect","useCallback","jsx","jsxs"],"mappings":"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","file":"upload-support-bundle-modal.js","sourcesContent":["/**\n * Format bytes to human-readable string\n * @param bytes - Number of bytes\n * @param decimals - Number of decimal places (default: 1)\n * @returns Formatted string (e.g., \"1.5 MB\")\n */\nexport function formatBytes(bytes: number, decimals = 1): string {\n if (bytes === 0) return \"0 Bytes\";\n\n const k = 1024;\n const dm = decimals < 0 ? 0 : decimals;\n const sizes = [\"Bytes\", \"KB\", \"MB\", \"GB\", \"TB\"];\n\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n\n return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))} ${sizes[i]}`;\n}\n\n/**\n * Format date string to short numeric date (no time)\n * @param dateString - ISO date string or null/undefined\n * @returns Formatted string (e.g., \"01/27/2026\") or \"Never\" if no date\n */\nexport function formatDateShort(dateString?: string | null): string {\n if (!dateString) return \"Never\";\n const date = new Date(dateString);\n if (isNaN(date.getTime())) {\n return dateString;\n }\n return date.toLocaleDateString(\"en-US\", {\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\"\n });\n}\n\n/**\n * Format date string to human-readable local timestamp\n * @param dateString - ISO date string or null/undefined\n * @returns Formatted string (e.g., \"Jan 27, 2026, 10:32 PM\") or \"Never\" if no date\n */\nexport function formatDate(dateString?: string | null): string {\n if (!dateString) return \"Never\";\n const date = new Date(dateString);\n if (isNaN(date.getTime())) {\n return dateString;\n }\n return date.toLocaleDateString(\"en-US\", {\n month: \"short\",\n day: \"numeric\",\n year: \"numeric\",\n hour: \"numeric\",\n minute: \"2-digit\",\n hour12: true\n });\n}\n\n/**\n * Format date string to human-readable UTC timestamp\n * @param dateString - ISO date string\n * @returns Formatted string (e.g., \"01/27/2026, 22:32:39 UTC\")\n */\nexport function formatDateTime(dateString: string): string {\n const date = new Date(dateString);\n if (isNaN(date.getTime())) {\n return dateString;\n }\n return date.toLocaleString(\"en-US\", {\n timeZone: \"UTC\",\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n hour12: false\n }) + \" UTC\";\n}\n\n/**\n * Format date string to user-friendly local timestamp with 12-hour time\n * @param dateString - ISO date string or null/undefined\n * @returns Formatted string (e.g., \"1/27/2026, 10:32 PM\") or \"N/A\" if not provided\n */\nexport function formatDateTimeLocal(dateString?: string | null): string {\n if (!dateString) return \"N/A\";\n\n try {\n const date = new Date(dateString);\n if (isNaN(date.getTime())) {\n return dateString;\n }\n return date.toLocaleDateString(\"en-US\", {\n month: \"numeric\",\n day: \"numeric\",\n year: \"numeric\",\n hour: \"numeric\",\n minute: \"2-digit\",\n hour12: true\n });\n } catch {\n return \"N/A\";\n }\n}\n","/**\n * Default globe favicon matching the CiGlobe icon (Circum Icons)\n * Used as fallback when custom branding doesn't provide a favicon\n */\nexport const DEFAULT_FAVICON = \"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%239ca3af' stroke-width='1.5'%3E%3Ccircle cx='12' cy='12' r='10'/%3E%3Cellipse cx='12' cy='12' rx='4' ry='10'/%3E%3Cpath d='M2 12h20'/%3E%3C/svg%3E\";\n\n/**\n * Default primary brand color\n */\nexport const DEFAULT_PRIMARY_COLOR = \"#4f46e5\";\n\n/**\n * Default secondary brand color\n */\nexport const DEFAULT_SECONDARY_COLOR = \"#6366f1\";\n\n/**\n * Check if the API origin is HTTP (used for repldev environments)\n * This determines cookie security settings for cross-origin iframes\n * @returns true if the API origin starts with http:// (not https://)\n */\nexport const isHttpApiOrigin = (): boolean => {\n return process.env.REPLICATED_APP_ORIGIN?.startsWith('http://') || false;\n};\n","\"use client\";\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\nimport { formatBytes } from \"../utils/format\";\nimport { DEFAULT_PRIMARY_COLOR } from \"../utils/constants\";\nimport type { UploadSupportBundleResult } from \"../utils/upload-with-progress\";\n\ninterface UploadSupportBundleModalProps {\n isOpen: boolean;\n onClose: () => void;\n /**\n * Upload function with progress tracking\n * onProgress receives percentage (0-100)\n * signal is an AbortSignal for cancellation\n */\n uploadWithProgress: (\n file: File,\n appId: string,\n onProgress: (percent: number) => void,\n signal: AbortSignal\n ) => Promise<UploadSupportBundleResult>;\n /** Callback after successful upload - performs async polling to refresh bundle list */\n onUploadSuccess: () => Promise<void>;\n appId: string;\n primaryColor?: string;\n}\n\nexport const UploadSupportBundleModal = ({\n isOpen,\n onClose,\n uploadWithProgress,\n onUploadSuccess,\n appId,\n primaryColor = DEFAULT_PRIMARY_COLOR\n}: UploadSupportBundleModalProps) => {\n const [selectedFile, setSelectedFile] = useState<File | null>(null);\n const [isUploading, setIsUploading] = useState(false);\n const [uploadProgress, setUploadProgress] = useState<number>(0);\n const [error, setError] = useState<string | null>(null);\n const [abortController, setAbortController] = useState<AbortController | null>(null);\n const fileInputRef = useRef<HTMLInputElement>(null);\n\n // Reset state when modal opens/closes\n useEffect(() => {\n if (!isOpen) {\n setSelectedFile(null);\n setIsUploading(false);\n setUploadProgress(0);\n setError(null);\n // Abort any in-flight upload before clearing controller\n setAbortController(prev => {\n prev?.abort();\n return null;\n });\n }\n }, [isOpen]);\n\n const handleFileChange = useCallback((event: React.ChangeEvent<HTMLInputElement>) => {\n const file = event.target.files?.[0];\n if (!file) return;\n\n if (!file.name.endsWith(\".tgz\") && !file.name.endsWith(\".tar.gz\")) {\n setError(\"Invalid file type. Please select a .tgz or .tar.gz file.\");\n return;\n }\n\n setError(null);\n setSelectedFile(file);\n }, []);\n\n const handleUpload = useCallback(async () => {\n if (!selectedFile || !appId) return;\n\n setError(null);\n setIsUploading(true);\n setUploadProgress(0);\n\n try {\n // Upload with progress tracking\n const controller = new AbortController();\n setAbortController(controller);\n\n const result = await uploadWithProgress(\n selectedFile,\n appId,\n setUploadProgress,\n controller.signal\n );\n\n if (!result.success) {\n throw new Error(result.error ?? \"Failed to upload support bundle\");\n }\n\n // Success - notify parent and wait for polling to complete\n await onUploadSuccess();\n onClose();\n } catch (err) {\n if (err instanceof Error && err.message !== \"Upload cancelled\") {\n console.error(\"Upload error:\", err);\n setError(err.message);\n }\n } finally {\n setIsUploading(false);\n setUploadProgress(0);\n setAbortController(null);\n }\n }, [selectedFile, appId, uploadWithProgress, onUploadSuccess, onClose]);\n\n const handleCancel = useCallback(() => {\n if (isUploading && abortController) {\n abortController.abort();\n }\n onClose();\n }, [isUploading, abortController, onClose]);\n\n const handleBackdropClick = useCallback((e: React.MouseEvent) => {\n if (e.target === e.currentTarget && !isUploading) {\n onClose();\n }\n }, [isUploading, onClose]);\n\n if (!isOpen) return null;\n\n return (\n <div\n className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50\"\n onClick={handleBackdropClick}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"upload-modal-title\"\n >\n <div className=\"w-full max-w-md rounded-2xl bg-white p-6 shadow-xl\">\n <h2 id=\"upload-modal-title\" className=\"text-lg font-semibold text-gray-900\">\n Upload Support Bundle\n </h2>\n\n <div className=\"mt-4\">\n <label className=\"block text-sm font-medium text-gray-700\">\n Select Support Bundle\n </label>\n <input\n ref={fileInputRef}\n type=\"file\"\n accept=\".tgz,.tar.gz,application/gzip,application/x-gzip\"\n onChange={handleFileChange}\n disabled={isUploading}\n className=\"mt-2 block w-full text-sm text-gray-500 file:mr-4 file:rounded-lg file:border-0 file:bg-gray-100 file:px-4 file:py-2 file:text-sm file:font-medium file:text-gray-700 hover:file:bg-gray-200 disabled:opacity-50\"\n />\n {selectedFile && (\n <div className=\"mt-3 flex items-center justify-between rounded-lg bg-gray-50 px-3 py-2 text-sm\">\n <span className=\"truncate text-gray-700\">{selectedFile.name}</span>\n <span className=\"ml-2 shrink-0 text-gray-500\">{formatBytes(selectedFile.size)}</span>\n </div>\n )}\n </div>\n\n {error && (\n <div className=\"mt-4 rounded-lg bg-red-50 px-3 py-2 text-sm text-red-700\">\n {error}\n </div>\n )}\n\n {isUploading && (\n <div className=\"mt-6 mb-6\">\n <div className=\"flex items-center justify-between mb-2\">\n <span className=\"text-sm font-medium text-gray-700\">\n Uploading support bundle...\n </span>\n <span className=\"text-sm font-medium text-gray-900\">\n {uploadProgress}%\n </span>\n </div>\n <div className=\"w-full bg-gray-200 rounded-full h-2\">\n <div\n className=\"h-2 rounded-full transition-all duration-300\"\n style={{\n width: `${uploadProgress}%`,\n backgroundColor: primaryColor || \"#3B82F6\",\n }}\n />\n </div>\n </div>\n )}\n\n <div className=\"mt-6 flex justify-end gap-3\">\n <button\n type=\"button\"\n onClick={handleCancel}\n className=\"rounded-lg px-4 py-2 text-sm font-medium text-gray-600 transition hover:bg-gray-100\"\n >\n {isUploading && abortController ? \"Cancel Upload\" : \"Cancel\"}\n </button>\n <button\n type=\"button\"\n onClick={handleUpload}\n disabled={!selectedFile || isUploading}\n className=\"rounded-lg px-4 py-2 text-sm font-medium text-white transition-opacity duration-200 hover:opacity-90 disabled:opacity-50\"\n style={{ backgroundColor: primaryColor }}\n >\n {isUploading ? \"Uploading...\" : \"Upload\"}\n </button>\n </div>\n </div>\n </div>\n );\n};\n\nUploadSupportBundleModal.displayName = \"UploadSupportBundleModal\";\n"]}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Client-side upload function with progress tracking and streaming using XMLHttpRequest
3
+ *
4
+ * This sends the file directly (not as FormData) to avoid proxy issues and enable true streaming.
5
+ * The file is sent with Content-Type: application/gzip and appId as a query parameter.
6
+ *
7
+ * @param file - The file to upload
8
+ * @param appId - The application ID
9
+ * @param onProgress - Callback that receives progress percentage (0-100)
10
+ * @param signal - AbortSignal for cancellation
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * import { uploadSupportBundleWithProgress } from "@replicated/portal-components/utils";
15
+ *
16
+ * <UploadSupportBundleModal
17
+ * uploadWithProgress={uploadSupportBundleWithProgress}
18
+ * // ... other props
19
+ * />
20
+ * ```
21
+ *
22
+ * Note: This requires a corresponding API route in your Next.js app at:
23
+ * /api/support-bundles/upload
24
+ *
25
+ * The API route should stream the request body directly to the backend without buffering.
26
+ */
27
+ interface UploadSupportBundleResult {
28
+ success: boolean;
29
+ error?: string;
30
+ bundleId?: string;
31
+ bundleSlug?: string;
32
+ }
33
+ declare function uploadSupportBundleWithProgress(file: File, appId: string, onProgress: (percent: number) => void, signal: AbortSignal): Promise<UploadSupportBundleResult>;
34
+
35
+ export { type UploadSupportBundleResult as U, uploadSupportBundleWithProgress as u };
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Client-side upload function with progress tracking and streaming using XMLHttpRequest
3
+ *
4
+ * This sends the file directly (not as FormData) to avoid proxy issues and enable true streaming.
5
+ * The file is sent with Content-Type: application/gzip and appId as a query parameter.
6
+ *
7
+ * @param file - The file to upload
8
+ * @param appId - The application ID
9
+ * @param onProgress - Callback that receives progress percentage (0-100)
10
+ * @param signal - AbortSignal for cancellation
11
+ *
12
+ * @example
13
+ * ```tsx
14
+ * import { uploadSupportBundleWithProgress } from "@replicated/portal-components/utils";
15
+ *
16
+ * <UploadSupportBundleModal
17
+ * uploadWithProgress={uploadSupportBundleWithProgress}
18
+ * // ... other props
19
+ * />
20
+ * ```
21
+ *
22
+ * Note: This requires a corresponding API route in your Next.js app at:
23
+ * /api/support-bundles/upload
24
+ *
25
+ * The API route should stream the request body directly to the backend without buffering.
26
+ */
27
+ interface UploadSupportBundleResult {
28
+ success: boolean;
29
+ error?: string;
30
+ bundleId?: string;
31
+ bundleSlug?: string;
32
+ }
33
+ declare function uploadSupportBundleWithProgress(file: File, appId: string, onProgress: (percent: number) => void, signal: AbortSignal): Promise<UploadSupportBundleResult>;
34
+
35
+ export { type UploadSupportBundleResult as U, uploadSupportBundleWithProgress as u };
@@ -1,6 +1,7 @@
1
- export { d as decodeBranding, n as normalizeColor, s as sanitizeUrlForCss } from '../branding-BsMSywts.mjs';
2
- import { a1 as DownloadPortalRelease } from '../index-JX9-CIMA.mjs';
1
+ export { a as BackgroundType, B as BrandingResult, d as decodeBranding, n as normalizeColor, s as sanitizeUrlForCss } from '../branding-DmsrDTNE.mjs';
2
+ import { a1 as DownloadPortalRelease } from '../index-DaH1bSuO.mjs';
3
3
  import { ReleaseEntry } from '../release-history-panel.mjs';
4
+ export { U as UploadSupportBundleResult, u as uploadSupportBundleWithProgress } from '../upload-with-progress-D760HthX.mjs';
4
5
  import '../fetch-license-iTyF7_GY.mjs';
5
6
  import '../actions/change-team.mjs';
6
7
  import '../actions/trial-signup.mjs';
@@ -45,21 +46,6 @@ declare function authenticatedFetch(url: string, options?: ApiFetchOptions): Pro
45
46
  * the session cookie before rendering authenticated content.
46
47
  */
47
48
  declare function validateSession(token: string): Promise<boolean>;
48
- /**
49
- * Deletes the portal_session cookie.
50
- * This MUST be called from a Server Action or Route Handler, not from Server Component render.
51
- *
52
- * Usage example in a Server Action:
53
- * ```typescript
54
- * "use server";
55
- * import { deleteSessionCookie } from "@replicated/portal-components/utils";
56
- * export async function logout() {
57
- * await deleteSessionCookie();
58
- * redirect("/");
59
- * }
60
- * ```
61
- */
62
- declare function deleteSessionCookie(): Promise<void>;
63
49
 
64
50
  /**
65
51
  * Format bytes to human-readable string
@@ -126,4 +112,4 @@ declare const DEFAULT_SECONDARY_COLOR = "#6366f1";
126
112
  */
127
113
  declare const isHttpApiOrigin: () => boolean;
128
114
 
129
- export { type ConvertToReleaseEntryOptions, DEFAULT_FAVICON, DEFAULT_PRIMARY_COLOR, DEFAULT_SECONDARY_COLOR, type ReleaseNotesMode, UnauthorizedError, authenticatedFetch, convertToReleaseEntry, deleteSessionCookie, formatBytes, formatDate, formatDateShort, formatDateTime, formatDateTimeLocal, isHttpApiOrigin, isRedirectError, validateSession };
115
+ export { type ConvertToReleaseEntryOptions, DEFAULT_FAVICON, DEFAULT_PRIMARY_COLOR, DEFAULT_SECONDARY_COLOR, type ReleaseNotesMode, UnauthorizedError, authenticatedFetch, convertToReleaseEntry, formatBytes, formatDate, formatDateShort, formatDateTime, formatDateTimeLocal, isHttpApiOrigin, isRedirectError, validateSession };
@@ -1,6 +1,7 @@
1
- export { d as decodeBranding, n as normalizeColor, s as sanitizeUrlForCss } from '../branding-BsMSywts.js';
2
- import { a1 as DownloadPortalRelease } from '../index-DXy7RxOX.js';
1
+ export { a as BackgroundType, B as BrandingResult, d as decodeBranding, n as normalizeColor, s as sanitizeUrlForCss } from '../branding-DmsrDTNE.js';
2
+ import { a1 as DownloadPortalRelease } from '../index-Bcp17Mf3.js';
3
3
  import { ReleaseEntry } from '../release-history-panel.js';
4
+ export { U as UploadSupportBundleResult, u as uploadSupportBundleWithProgress } from '../upload-with-progress-D760HthX.js';
4
5
  import '../fetch-license-iTyF7_GY.js';
5
6
  import '../actions/change-team.js';
6
7
  import '../actions/trial-signup.js';
@@ -45,21 +46,6 @@ declare function authenticatedFetch(url: string, options?: ApiFetchOptions): Pro
45
46
  * the session cookie before rendering authenticated content.
46
47
  */
47
48
  declare function validateSession(token: string): Promise<boolean>;
48
- /**
49
- * Deletes the portal_session cookie.
50
- * This MUST be called from a Server Action or Route Handler, not from Server Component render.
51
- *
52
- * Usage example in a Server Action:
53
- * ```typescript
54
- * "use server";
55
- * import { deleteSessionCookie } from "@replicated/portal-components/utils";
56
- * export async function logout() {
57
- * await deleteSessionCookie();
58
- * redirect("/");
59
- * }
60
- * ```
61
- */
62
- declare function deleteSessionCookie(): Promise<void>;
63
49
 
64
50
  /**
65
51
  * Format bytes to human-readable string
@@ -126,4 +112,4 @@ declare const DEFAULT_SECONDARY_COLOR = "#6366f1";
126
112
  */
127
113
  declare const isHttpApiOrigin: () => boolean;
128
114
 
129
- export { type ConvertToReleaseEntryOptions, DEFAULT_FAVICON, DEFAULT_PRIMARY_COLOR, DEFAULT_SECONDARY_COLOR, type ReleaseNotesMode, UnauthorizedError, authenticatedFetch, convertToReleaseEntry, deleteSessionCookie, formatBytes, formatDate, formatDateShort, formatDateTime, formatDateTimeLocal, isHttpApiOrigin, isRedirectError, validateSession };
115
+ export { type ConvertToReleaseEntryOptions, DEFAULT_FAVICON, DEFAULT_PRIMARY_COLOR, DEFAULT_SECONDARY_COLOR, type ReleaseNotesMode, UnauthorizedError, authenticatedFetch, convertToReleaseEntry, formatBytes, formatDate, formatDateShort, formatDateTime, formatDateTimeLocal, isHttpApiOrigin, isRedirectError, validateSession };
@@ -150,6 +150,7 @@ var decodeBranding = ({ brandingData }) => {
150
150
  const secondaryColorRaw = parsed.secondaryColor ?? parsed.secondary_color;
151
151
  const primaryColor = normalizeColor(primaryColorRaw);
152
152
  const secondaryColor = normalizeColor(secondaryColorRaw);
153
+ const contact = typeof parsed.contact === "string" ? parsed.contact : void 0;
153
154
  const supportPortalLink = typeof parsed.supportPortalLink === "string" ? parsed.supportPortalLink : void 0;
154
155
  const backgroundRaw = parsed.background;
155
156
  const background = backgroundRaw === "minimal" || backgroundRaw === "custom" || backgroundRaw === "image" ? backgroundRaw : void 0;
@@ -162,6 +163,7 @@ var decodeBranding = ({ brandingData }) => {
162
163
  favicon,
163
164
  primaryColor: primaryColor || DEFAULT_PRIMARY_COLOR,
164
165
  secondaryColor: secondaryColor || DEFAULT_SECONDARY_COLOR,
166
+ contact,
165
167
  supportPortalLink,
166
168
  background,
167
169
  backgroundImage,
@@ -211,6 +213,7 @@ var fetchCustomBrandingImpl = async () => {
211
213
  title: payload.title,
212
214
  primaryColor: payload.primaryColor,
213
215
  secondaryColor: payload.secondaryColor,
216
+ contact: payload.contact,
214
217
  favicon: payload.faviconUrl,
215
218
  supportPortalLink: payload.supportPortalLink || "",
216
219
  background: payload.background,
@@ -288,11 +291,6 @@ async function validateSession(token) {
288
291
  }
289
292
  return true;
290
293
  }
291
- async function deleteSessionCookie() {
292
- const { cookies } = await import('next/headers');
293
- const cookieStore = await cookies();
294
- cookieStore.delete("portal_session");
295
- }
296
294
 
297
295
  // src/utils/format.ts
298
296
  function formatBytes(bytes, decimals = 1) {
@@ -400,6 +398,50 @@ function convertToReleaseEntry(release, channelName, options) {
400
398
  };
401
399
  }
402
400
 
401
+ // src/utils/upload-with-progress.ts
402
+ async function uploadSupportBundleWithProgress(file, appId, onProgress, signal) {
403
+ return new Promise((resolve, reject) => {
404
+ const xhr = new XMLHttpRequest();
405
+ xhr.upload.addEventListener("progress", (e) => {
406
+ if (e.lengthComputable) {
407
+ const percent = Math.round(e.loaded / e.total * 100);
408
+ onProgress(percent);
409
+ }
410
+ });
411
+ xhr.addEventListener("load", () => {
412
+ if (xhr.status === 200) {
413
+ try {
414
+ const response = JSON.parse(xhr.responseText);
415
+ resolve(response);
416
+ } catch (err) {
417
+ reject(new Error("Failed to parse server response"));
418
+ }
419
+ } else {
420
+ try {
421
+ const response = JSON.parse(xhr.responseText);
422
+ reject(new Error(response.error || `Upload failed: ${xhr.status}`));
423
+ } catch {
424
+ reject(new Error(`Upload failed: ${xhr.status} ${xhr.statusText}`));
425
+ }
426
+ }
427
+ });
428
+ xhr.addEventListener("error", () => {
429
+ reject(new Error("Network error. Please check your connection."));
430
+ });
431
+ xhr.addEventListener("abort", () => {
432
+ reject(new Error("Upload cancelled"));
433
+ });
434
+ signal.addEventListener("abort", () => {
435
+ xhr.abort();
436
+ });
437
+ const basePath = process.env.NEXT_PUBLIC_BASE_PATH || "";
438
+ const uploadUrl = `${basePath}/api/support-bundles/upload?appId=${encodeURIComponent(appId)}`;
439
+ xhr.open("POST", uploadUrl, true);
440
+ xhr.setRequestHeader("Content-Type", "application/gzip");
441
+ xhr.send(file);
442
+ });
443
+ }
444
+
403
445
  exports.DEFAULT_FAVICON = DEFAULT_FAVICON;
404
446
  exports.DEFAULT_PRIMARY_COLOR = DEFAULT_PRIMARY_COLOR;
405
447
  exports.DEFAULT_SECONDARY_COLOR = DEFAULT_SECONDARY_COLOR;
@@ -407,7 +449,6 @@ exports.UnauthorizedError = UnauthorizedError;
407
449
  exports.authenticatedFetch = authenticatedFetch;
408
450
  exports.convertToReleaseEntry = convertToReleaseEntry;
409
451
  exports.decodeBranding = decodeBranding;
410
- exports.deleteSessionCookie = deleteSessionCookie;
411
452
  exports.formatBytes = formatBytes;
412
453
  exports.formatDate = formatDate;
413
454
  exports.formatDateShort = formatDateShort;
@@ -417,6 +458,7 @@ exports.isHttpApiOrigin = isHttpApiOrigin;
417
458
  exports.isRedirectError = isRedirectError;
418
459
  exports.normalizeColor = normalizeColor;
419
460
  exports.sanitizeUrlForCss = sanitizeUrlForCss;
461
+ exports.uploadSupportBundleWithProgress = uploadSupportBundleWithProgress;
420
462
  exports.validateSession = validateSession;
421
463
  //# sourceMappingURL=index.js.map
422
464
  //# sourceMappingURL=index.js.map