@burdenoff/microfe-files 2026.626.1 → 2026.713.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.
@@ -1,64 +1,83 @@
1
- import { AlertCircle as e, AlertTriangle as t } from "lucide-react";
2
- import { jsx as n, jsxs as r } from "react/jsx-runtime";
1
+ import { QuotaMeter as e } from "@burdenoff/fe-libs/shared/components";
2
+ import { AlertCircle as t, AlertTriangle as n } from "lucide-react";
3
+ import { jsx as r, jsxs as i } from "react/jsx-runtime";
3
4
  //#region src/files/components/StorageBanner.tsx
4
- function i(e) {
5
+ function a(e) {
5
6
  return e >= 1073741824 ? `${(e / 1073741824).toFixed(1)} GB` : e >= 1048576 ? `${(e / 1048576).toFixed(1)} MB` : e >= 1024 ? `${(e / 1024).toFixed(1)} KB` : `${e} B`;
6
7
  }
7
- var a = ({ quota: a }) => {
8
- if (a.loading || a.noLimit || !a.isExhausted && !a.isNearLimit) return null;
9
- let o = i(a.usedBytes), s = a.limitBytes ? i(a.limitBytes) : null;
10
- return a.isExhausted ? /* @__PURE__ */ r("div", {
8
+ var o = ({ quota: o }) => {
9
+ if (o.loading || o.noLimit || !o.isExhausted && !o.isNearLimit) return null;
10
+ let s = a(o.usedBytes), c = o.limitBytes ? a(o.limitBytes) : null, l = Math.round(o.usedBytes / 1048576), u = o.limitBytes ? Math.round(o.limitBytes / 1048576) : l;
11
+ return o.isExhausted ? /* @__PURE__ */ i("div", {
11
12
  className: "flex items-center gap-3 rounded-lg border border-status-error-bg/30 bg-status-error-bg/10 px-4 py-3 text-sm",
12
13
  children: [
13
- /* @__PURE__ */ n(e, { className: "size-4 shrink-0 text-status-error-bg" }),
14
- /* @__PURE__ */ r("div", {
14
+ /* @__PURE__ */ r(t, { className: "size-4 shrink-0 text-status-error-bg" }),
15
+ /* @__PURE__ */ i("div", {
15
16
  className: "flex flex-1 items-center gap-1 text-text-primary",
16
- children: [/* @__PURE__ */ n("span", {
17
+ children: [/* @__PURE__ */ r("span", {
17
18
  className: "font-medium",
18
19
  children: "Storage full."
19
- }), /* @__PURE__ */ r("span", {
20
+ }), /* @__PURE__ */ i("span", {
20
21
  className: "text-text-secondary",
21
22
  children: [
22
- s ? `${o} of ${s} used — no space left.` : `${o} used — no space left.`,
23
+ c ? `${s} of ${c} used — no space left.` : `${s} used — no space left.`,
23
24
  " ",
24
25
  "Delete files to free up space or upgrade your plan."
25
26
  ]
26
27
  })]
27
28
  }),
28
- /* @__PURE__ */ n("div", {
29
+ /* @__PURE__ */ r("div", {
30
+ className: "min-w-[180px] shrink-0",
31
+ children: /* @__PURE__ */ r(e, {
32
+ label: "Storage",
33
+ used: l,
34
+ limit: u,
35
+ unit: "MB"
36
+ })
37
+ }),
38
+ /* @__PURE__ */ r("div", {
29
39
  className: "h-1.5 w-24 shrink-0 overflow-hidden rounded-full bg-surface-tertiary",
30
- children: /* @__PURE__ */ n("div", { className: "h-full w-full rounded-full bg-status-error-bg" })
40
+ children: /* @__PURE__ */ r("div", { className: "h-full w-full rounded-full bg-status-error-bg" })
31
41
  })
32
42
  ]
33
- }) : /* @__PURE__ */ r("div", {
43
+ }) : /* @__PURE__ */ i("div", {
34
44
  className: "flex items-center gap-3 rounded-lg border border-status-warning-bg/30 bg-status-warning-bg/10 px-4 py-3 text-sm",
35
45
  children: [
36
- /* @__PURE__ */ n(t, { className: "size-4 shrink-0 text-status-warning-bg" }),
37
- /* @__PURE__ */ r("div", {
46
+ /* @__PURE__ */ r(n, { className: "size-4 shrink-0 text-status-warning-bg" }),
47
+ /* @__PURE__ */ i("div", {
38
48
  className: "flex flex-1 items-center gap-1 text-text-primary",
39
- children: [/* @__PURE__ */ n("span", {
49
+ children: [/* @__PURE__ */ r("span", {
40
50
  className: "font-medium",
41
51
  children: "Storage almost full."
42
- }), /* @__PURE__ */ r("span", {
52
+ }), /* @__PURE__ */ i("span", {
43
53
  className: "text-text-secondary",
44
54
  children: [
45
- s ? `${o} of ${s} used (${Math.round(a.usedPercent)}%).` : `${o} used.`,
55
+ c ? `${s} of ${c} used (${Math.round(o.usedPercent)}%).` : `${s} used.`,
46
56
  " ",
47
57
  "Consider deleting files or upgrading your plan."
48
58
  ]
49
59
  })]
50
60
  }),
51
- /* @__PURE__ */ n("div", {
61
+ /* @__PURE__ */ r("div", {
62
+ className: "min-w-[180px] shrink-0",
63
+ children: /* @__PURE__ */ r(e, {
64
+ label: "Storage",
65
+ used: l,
66
+ limit: u,
67
+ unit: "MB"
68
+ })
69
+ }),
70
+ /* @__PURE__ */ r("div", {
52
71
  className: "h-1.5 w-24 shrink-0 overflow-hidden rounded-full bg-surface-tertiary",
53
- children: /* @__PURE__ */ n("div", {
72
+ children: /* @__PURE__ */ r("div", {
54
73
  className: "h-full rounded-full bg-status-warning-bg transition-all",
55
- style: { width: `${a.usedPercent}%` }
74
+ style: { width: `${o.usedPercent}%` }
56
75
  })
57
76
  })
58
77
  ]
59
78
  });
60
79
  };
61
80
  //#endregion
62
- export { a as StorageBanner };
81
+ export { o as StorageBanner };
63
82
 
64
83
  //# sourceMappingURL=StorageBanner.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"StorageBanner.js","names":[],"sources":["../../../src/files/components/StorageBanner.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { AlertCircle, AlertTriangle, HardDrive } from \"lucide-react\";\nimport type { StorageQuotaState } from \"../hooks/useStorageQuota\";\n\nfunction formatBytes(bytes: number): string {\n if (bytes >= 1073741824) return `${(bytes / 1073741824).toFixed(1)} GB`;\n if (bytes >= 1048576) return `${(bytes / 1048576).toFixed(1)} MB`;\n if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${bytes} B`;\n}\n\ninterface StorageBannerProps {\n quota: StorageQuotaState;\n}\n\nexport const StorageBanner: FC<StorageBannerProps> = ({ quota }) => {\n if (\n quota.loading ||\n quota.noLimit ||\n (!quota.isExhausted && !quota.isNearLimit)\n ) {\n return null;\n }\n\n const usedLabel = formatBytes(quota.usedBytes);\n const limitLabel = quota.limitBytes ? formatBytes(quota.limitBytes) : null;\n\n if (quota.isExhausted) {\n return (\n <div className=\"flex items-center gap-3 rounded-lg border border-status-error-bg/30 bg-status-error-bg/10 px-4 py-3 text-sm\">\n <AlertCircle className=\"size-4 shrink-0 text-status-error-bg\" />\n <div className=\"flex flex-1 items-center gap-1 text-text-primary\">\n <span className=\"font-medium\">Storage full.</span>\n <span className=\"text-text-secondary\">\n {limitLabel\n ? `${usedLabel} of ${limitLabel} used — no space left.`\n : `${usedLabel} used — no space left.`}{\" \"}\n Delete files to free up space or upgrade your plan.\n </span>\n </div>\n <div className=\"h-1.5 w-24 shrink-0 overflow-hidden rounded-full bg-surface-tertiary\">\n <div className=\"h-full w-full rounded-full bg-status-error-bg\" />\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"flex items-center gap-3 rounded-lg border border-status-warning-bg/30 bg-status-warning-bg/10 px-4 py-3 text-sm\">\n <AlertTriangle className=\"size-4 shrink-0 text-status-warning-bg\" />\n <div className=\"flex flex-1 items-center gap-1 text-text-primary\">\n <span className=\"font-medium\">Storage almost full.</span>\n <span className=\"text-text-secondary\">\n {limitLabel\n ? `${usedLabel} of ${limitLabel} used (${Math.round(quota.usedPercent)}%).`\n : `${usedLabel} used.`}{\" \"}\n Consider deleting files or upgrading your plan.\n </span>\n </div>\n <div className=\"h-1.5 w-24 shrink-0 overflow-hidden rounded-full bg-surface-tertiary\">\n <div\n className=\"h-full rounded-full bg-status-warning-bg transition-all\"\n style={{ width: `${quota.usedPercent}%` }}\n />\n </div>\n </div>\n );\n};\n\ninterface StorageIndicatorProps {\n quota: StorageQuotaState;\n}\n\nexport const StorageIndicator: FC<StorageIndicatorProps> = ({ quota }) => {\n if (quota.loading || quota.noLimit || !quota.limitBytes) return null;\n\n const usedLabel = formatBytes(quota.usedBytes);\n const limitLabel = formatBytes(quota.limitBytes);\n const color = quota.isExhausted\n ? \"bg-status-error-bg\"\n : quota.isNearLimit\n ? \"bg-status-warning-bg\"\n : \"bg-status-info-bg\";\n\n return (\n <div className=\"flex items-center gap-2 text-xs text-text-muted\">\n <HardDrive className=\"size-3.5 shrink-0\" />\n <span className=\"tabular-nums\">\n {usedLabel} / {limitLabel}\n </span>\n <div className=\"h-1 w-16 overflow-hidden rounded-full bg-surface-tertiary\">\n <div\n className={`h-full rounded-full transition-all ${color}`}\n style={{ width: `${quota.usedPercent}%` }}\n />\n </div>\n </div>\n );\n};\n"],"mappings":";;;AAIA,SAAS,EAAY,GAAuB;AAI1C,QAHI,KAAS,aAAmB,IAAI,IAAQ,YAAY,QAAQ,EAAE,CAAC,OAC/D,KAAS,UAAgB,IAAI,IAAQ,SAAS,QAAQ,EAAE,CAAC,OACzD,KAAS,OAAa,IAAI,IAAQ,MAAM,QAAQ,EAAE,CAAC,OAChD,GAAG,EAAM;;AAOlB,IAAa,KAAyC,EAAE,eAAY;AAClE,KACE,EAAM,WACN,EAAM,WACL,CAAC,EAAM,eAAe,CAAC,EAAM,YAE9B,QAAO;CAGT,IAAM,IAAY,EAAY,EAAM,UAAU,EACxC,IAAa,EAAM,aAAa,EAAY,EAAM,WAAW,GAAG;AAsBtE,QApBI,EAAM,cAEN,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD,EAAa,WAAU,wCAAyC,CAAA;GAChE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,QAAD;KAAM,WAAU;eAAc;KAAoB,CAAA,EAClD,kBAAC,QAAD;KAAM,WAAU;eAAhB;MACG,IACG,GAAG,EAAU,MAAM,EAAW,0BAC9B,GAAG,EAAU;MAAyB;MAAI;MAEzC;OACH;;GACN,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,OAAD,EAAK,WAAU,iDAAkD,CAAA;IAC7D,CAAA;GACF;MAKR,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD,EAAe,WAAU,0CAA2C,CAAA;GACpE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,QAAD;KAAM,WAAU;eAAc;KAA2B,CAAA,EACzD,kBAAC,QAAD;KAAM,WAAU;eAAhB;MACG,IACG,GAAG,EAAU,MAAM,EAAW,SAAS,KAAK,MAAM,EAAM,YAAY,CAAC,OACrE,GAAG,EAAU;MAAS;MAAI;MAEzB;OACH;;GACN,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,OAAD;KACE,WAAU;KACV,OAAO,EAAE,OAAO,GAAG,EAAM,YAAY,IAAI;KACzC,CAAA;IACE,CAAA;GACF"}
1
+ {"version":3,"file":"StorageBanner.js","names":[],"sources":["../../../src/files/components/StorageBanner.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { AlertCircle, AlertTriangle, HardDrive } from \"lucide-react\";\nimport { QuotaMeter } from \"@burdenoff/fe-libs/shared/components\";\nimport type { StorageQuotaState } from \"../hooks/useStorageQuota\";\n\nfunction formatBytes(bytes: number): string {\n if (bytes >= 1073741824) return `${(bytes / 1073741824).toFixed(1)} GB`;\n if (bytes >= 1048576) return `${(bytes / 1048576).toFixed(1)} MB`;\n if (bytes >= 1024) return `${(bytes / 1024).toFixed(1)} KB`;\n return `${bytes} B`;\n}\n\ninterface StorageBannerProps {\n quota: StorageQuotaState;\n}\n\nexport const StorageBanner: FC<StorageBannerProps> = ({ quota }) => {\n if (\n quota.loading ||\n quota.noLimit ||\n (!quota.isExhausted && !quota.isNearLimit)\n ) {\n return null;\n }\n\n const usedLabel = formatBytes(quota.usedBytes);\n const limitLabel = quota.limitBytes ? formatBytes(quota.limitBytes) : null;\n const quotaMeterUsed = Math.round(quota.usedBytes / 1048576);\n const quotaMeterLimit = quota.limitBytes ? Math.round(quota.limitBytes / 1048576) : quotaMeterUsed;\n\n if (quota.isExhausted) {\n return (\n <div className=\"flex items-center gap-3 rounded-lg border border-status-error-bg/30 bg-status-error-bg/10 px-4 py-3 text-sm\">\n <AlertCircle className=\"size-4 shrink-0 text-status-error-bg\" />\n <div className=\"flex flex-1 items-center gap-1 text-text-primary\">\n <span className=\"font-medium\">Storage full.</span>\n <span className=\"text-text-secondary\">\n {limitLabel\n ? `${usedLabel} of ${limitLabel} used — no space left.`\n : `${usedLabel} used — no space left.`}{\" \"}\n Delete files to free up space or upgrade your plan.\n </span>\n </div>\n <div className=\"min-w-[180px] shrink-0\">\n <QuotaMeter\n label=\"Storage\"\n used={quotaMeterUsed}\n limit={quotaMeterLimit}\n unit=\"MB\"\n />\n </div>\n <div className=\"h-1.5 w-24 shrink-0 overflow-hidden rounded-full bg-surface-tertiary\">\n <div className=\"h-full w-full rounded-full bg-status-error-bg\" />\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"flex items-center gap-3 rounded-lg border border-status-warning-bg/30 bg-status-warning-bg/10 px-4 py-3 text-sm\">\n <AlertTriangle className=\"size-4 shrink-0 text-status-warning-bg\" />\n <div className=\"flex flex-1 items-center gap-1 text-text-primary\">\n <span className=\"font-medium\">Storage almost full.</span>\n <span className=\"text-text-secondary\">\n {limitLabel\n ? `${usedLabel} of ${limitLabel} used (${Math.round(quota.usedPercent)}%).`\n : `${usedLabel} used.`}{\" \"}\n Consider deleting files or upgrading your plan.\n </span>\n </div>\n <div className=\"min-w-[180px] shrink-0\">\n <QuotaMeter\n label=\"Storage\"\n used={quotaMeterUsed}\n limit={quotaMeterLimit}\n unit=\"MB\"\n />\n </div>\n <div className=\"h-1.5 w-24 shrink-0 overflow-hidden rounded-full bg-surface-tertiary\">\n <div\n className=\"h-full rounded-full bg-status-warning-bg transition-all\"\n style={{ width: `${quota.usedPercent}%` }}\n />\n </div>\n </div>\n );\n};\n\ninterface StorageIndicatorProps {\n quota: StorageQuotaState;\n}\n\nexport const StorageIndicator: FC<StorageIndicatorProps> = ({ quota }) => {\n if (quota.loading || quota.noLimit || !quota.limitBytes) return null;\n\n const usedLabel = formatBytes(quota.usedBytes);\n const limitLabel = formatBytes(quota.limitBytes);\n const color = quota.isExhausted\n ? \"bg-status-error-bg\"\n : quota.isNearLimit\n ? \"bg-status-warning-bg\"\n : \"bg-status-info-bg\";\n\n return (\n <div className=\"flex items-center gap-2 text-xs text-text-muted\">\n <HardDrive className=\"size-3.5 shrink-0\" />\n <span className=\"tabular-nums\">\n {usedLabel} / {limitLabel}\n </span>\n <div className=\"h-1 w-16 overflow-hidden rounded-full bg-surface-tertiary\">\n <div\n className={`h-full rounded-full transition-all ${color}`}\n style={{ width: `${quota.usedPercent}%` }}\n />\n </div>\n </div>\n );\n};\n"],"mappings":";;;;AAKA,SAAS,EAAY,GAAuB;AAI1C,QAHI,KAAS,aAAmB,IAAI,IAAQ,YAAY,QAAQ,EAAE,CAAC,OAC/D,KAAS,UAAgB,IAAI,IAAQ,SAAS,QAAQ,EAAE,CAAC,OACzD,KAAS,OAAa,IAAI,IAAQ,MAAM,QAAQ,EAAE,CAAC,OAChD,GAAG,EAAM;;AAOlB,IAAa,KAAyC,EAAE,eAAY;AAClE,KACE,EAAM,WACN,EAAM,WACL,CAAC,EAAM,eAAe,CAAC,EAAM,YAE9B,QAAO;CAGT,IAAM,IAAY,EAAY,EAAM,UAAU,EACxC,IAAa,EAAM,aAAa,EAAY,EAAM,WAAW,GAAG,MAChE,IAAiB,KAAK,MAAM,EAAM,YAAY,QAAQ,EACtD,IAAkB,EAAM,aAAa,KAAK,MAAM,EAAM,aAAa,QAAQ,GAAG;AA8BpF,QA5BI,EAAM,cAEN,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD,EAAa,WAAU,wCAAyC,CAAA;GAChE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,QAAD;KAAM,WAAU;eAAc;KAAoB,CAAA,EAClD,kBAAC,QAAD;KAAM,WAAU;eAAhB;MACG,IACG,GAAG,EAAU,MAAM,EAAW,0BAC9B,GAAG,EAAU;MAAyB;MAAI;MAEzC;OACH;;GACN,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD;KACE,OAAM;KACN,MAAM;KACN,OAAO;KACP,MAAK;KACL,CAAA;IACE,CAAA;GACN,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,OAAD,EAAK,WAAU,iDAAkD,CAAA;IAC7D,CAAA;GACF;MAKR,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,GAAD,EAAe,WAAU,0CAA2C,CAAA;GACpE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,QAAD;KAAM,WAAU;eAAc;KAA2B,CAAA,EACzD,kBAAC,QAAD;KAAM,WAAU;eAAhB;MACG,IACG,GAAG,EAAU,MAAM,EAAW,SAAS,KAAK,MAAM,EAAM,YAAY,CAAC,OACrE,GAAG,EAAU;MAAS;MAAI;MAEzB;OACH;;GACN,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD;KACE,OAAM;KACN,MAAM;KACN,OAAO;KACP,MAAK;KACL,CAAA;IACE,CAAA;GACN,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,OAAD;KACE,WAAU;KACV,OAAO,EAAE,OAAO,GAAG,EAAM,YAAY,IAAI;KACzC,CAAA;IACE,CAAA;GACF"}
@@ -1,7 +1,7 @@
1
1
  import { useGetWorkspaceStorageStatusQuery as e } from "../../generated/wspace-operations.js";
2
2
  import { useMemo as t } from "react";
3
3
  //#region src/files/hooks/useStorageQuota.ts
4
- var n = "vibecontrols.files.storage_bytes";
4
+ var n = "platform.files.storage_bytes";
5
5
  function r(r) {
6
6
  let { data: i, loading: a } = e({
7
7
  variables: { workspaceId: r },
@@ -1 +1 @@
1
- {"version":3,"file":"useStorageQuota.js","names":[],"sources":["../../../src/files/hooks/useStorageQuota.ts"],"sourcesContent":["import { useMemo } from \"react\";\nimport { useGetWorkspaceStorageStatusQuery } from \"../../generated/wspace-operations\";\n\nconst STORAGE_QUOTA_NAME = \"vibecontrols.files.storage_bytes\";\n\nexport interface StorageQuotaState {\n usedBytes: number;\n limitBytes: number | null;\n noLimit: boolean;\n usedPercent: number;\n isExhausted: boolean;\n isNearLimit: boolean;\n loading: boolean;\n}\n\nexport function useStorageQuota(workspaceId: string): StorageQuotaState {\n const { data, loading } = useGetWorkspaceStorageStatusQuery({\n variables: { workspaceId },\n skip: !workspaceId,\n pollInterval: 60_000,\n });\n\n return useMemo(() => {\n const status = data?.workspaceQuotaStatuses.find(\n (s: { quotaName: string }) => s.quotaName === STORAGE_QUOTA_NAME,\n );\n\n if (!status) {\n return {\n usedBytes: 0,\n limitBytes: null,\n noLimit: true,\n usedPercent: 0,\n isExhausted: false,\n isNearLimit: false,\n loading,\n };\n }\n\n const usedBytes = status.currentValue;\n const limitBytes = status.noLimit ? null : (status.limit ?? null);\n const usedPercent =\n limitBytes && limitBytes > 0\n ? Math.min(100, (usedBytes / limitBytes) * 100)\n : 0;\n\n return {\n usedBytes,\n limitBytes,\n noLimit: status.noLimit,\n usedPercent,\n isExhausted: !status.noLimit && usedBytes >= (limitBytes ?? Infinity),\n isNearLimit: !status.noLimit && usedPercent >= 80,\n loading,\n };\n }, [data, loading]);\n}\n"],"mappings":";;;AAGA,IAAM,IAAqB;AAY3B,SAAgB,EAAgB,GAAwC;CACtE,IAAM,EAAE,SAAM,eAAY,EAAkC;EAC1D,WAAW,EAAE,gBAAa;EAC1B,MAAM,CAAC;EACP,cAAc;EACf,CAAC;AAEF,QAAO,QAAc;EACnB,IAAM,IAAS,GAAM,uBAAuB,MACzC,MAA6B,EAAE,cAAc,EAC/C;AAED,MAAI,CAAC,EACH,QAAO;GACL,WAAW;GACX,YAAY;GACZ,SAAS;GACT,aAAa;GACb,aAAa;GACb,aAAa;GACb;GACD;EAGH,IAAM,IAAY,EAAO,cACnB,IAAa,EAAO,UAAU,OAAQ,EAAO,SAAS,MACtD,IACJ,KAAc,IAAa,IACvB,KAAK,IAAI,KAAM,IAAY,IAAc,IAAI,GAC7C;AAEN,SAAO;GACL;GACA;GACA,SAAS,EAAO;GAChB;GACA,aAAa,CAAC,EAAO,WAAW,MAAc,KAAc;GAC5D,aAAa,CAAC,EAAO,WAAW,KAAe;GAC/C;GACD;IACA,CAAC,GAAM,EAAQ,CAAC"}
1
+ {"version":3,"file":"useStorageQuota.js","names":[],"sources":["../../../src/files/hooks/useStorageQuota.ts"],"sourcesContent":["import { useMemo } from \"react\";\nimport { useGetWorkspaceStorageStatusQuery } from \"../../generated/wspace-operations\";\n\nconst STORAGE_QUOTA_NAME = \"platform.files.storage_bytes\";\n\nexport interface StorageQuotaState {\n usedBytes: number;\n limitBytes: number | null;\n noLimit: boolean;\n usedPercent: number;\n isExhausted: boolean;\n isNearLimit: boolean;\n loading: boolean;\n}\n\nexport function useStorageQuota(workspaceId: string): StorageQuotaState {\n const { data, loading } = useGetWorkspaceStorageStatusQuery({\n variables: { workspaceId },\n skip: !workspaceId,\n pollInterval: 60_000,\n });\n\n return useMemo(() => {\n const status = data?.workspaceQuotaStatuses.find(\n (s: { quotaName: string }) => s.quotaName === STORAGE_QUOTA_NAME,\n );\n\n if (!status) {\n return {\n usedBytes: 0,\n limitBytes: null,\n noLimit: true,\n usedPercent: 0,\n isExhausted: false,\n isNearLimit: false,\n loading,\n };\n }\n\n const usedBytes = status.currentValue;\n const limitBytes = status.noLimit ? null : (status.limit ?? null);\n const usedPercent =\n limitBytes && limitBytes > 0\n ? Math.min(100, (usedBytes / limitBytes) * 100)\n : 0;\n\n return {\n usedBytes,\n limitBytes,\n noLimit: status.noLimit,\n usedPercent,\n isExhausted: !status.noLimit && usedBytes >= (limitBytes ?? Infinity),\n isNearLimit: !status.noLimit && usedPercent >= 80,\n loading,\n };\n }, [data, loading]);\n}\n"],"mappings":";;;AAGA,IAAM,IAAqB;AAY3B,SAAgB,EAAgB,GAAwC;CACtE,IAAM,EAAE,SAAM,eAAY,EAAkC;EAC1D,WAAW,EAAE,gBAAa;EAC1B,MAAM,CAAC;EACP,cAAc;EACf,CAAC;AAEF,QAAO,QAAc;EACnB,IAAM,IAAS,GAAM,uBAAuB,MACzC,MAA6B,EAAE,cAAc,EAC/C;AAED,MAAI,CAAC,EACH,QAAO;GACL,WAAW;GACX,YAAY;GACZ,SAAS;GACT,aAAa;GACb,aAAa;GACb,aAAa;GACb;GACD;EAGH,IAAM,IAAY,EAAO,cACnB,IAAa,EAAO,UAAU,OAAQ,EAAO,SAAS,MACtD,IACJ,KAAc,IAAa,IACvB,KAAK,IAAI,KAAM,IAAY,IAAc,IAAI,GAC7C;AAEN,SAAO;GACL;GACA;GACA,SAAS,EAAO;GAChB;GACA,aAAa,CAAC,EAAO,WAAW,MAAc,KAAc;GAC5D,aAAa,CAAC,EAAO,WAAW,KAAe;GAC/C;GACD;IACA,CAAC,GAAM,EAAQ,CAAC"}
@@ -1,9 +1,10 @@
1
1
  import { useFilesContext as e } from "../providers/FilesProvider.js";
2
2
  import { useCompleteUploadMutation as t, useInitiateUploadMutation as n } from "../../generated/wspace-operations.js";
3
- import { useCallback as r, useRef as i, useState as a } from "react";
3
+ import { isQuotaExhaustedError as r } from "../utils/quotaUtils.js";
4
+ import { useCallback as i, useRef as a, useState as o } from "react";
4
5
  //#region src/files/hooks/useUploadFile.ts
5
- var o = () => {
6
- let { workspaceId: o } = e(), [s, c] = a(!1), [l, u] = a(null), d = i(null), f = i(!1), [p] = n(), [m] = t({ refetchQueries: [
6
+ var s = () => {
7
+ let { workspaceId: s } = e(), [c, l] = o(!1), [u, d] = o(null), f = a(null), p = a(!1), [m] = n(), [h] = t({ refetchQueries: [
7
8
  "GetFilesRootBrowsePage",
8
9
  "GetFilesFolderBrowsePage",
9
10
  "GetFilesRecentPage",
@@ -11,73 +12,71 @@ var o = () => {
11
12
  ] });
12
13
  return {
13
14
  mutateAsync: async (e) => {
14
- let { file: t, folderId: n, onProgress: r } = e, i = t.name.split(".").pop() || "";
15
+ let { file: t, folderId: n, onProgress: i } = e, a = t.name.split(".").pop() || "";
15
16
  try {
16
- c(!0), u(null), f.current = !1, r?.(0);
17
- let e = await p({ variables: { input: {
17
+ l(!0), d(null), p.current = !1, i?.(0);
18
+ let e = await m({ variables: { input: {
18
19
  fileName: t.name,
19
20
  fileSize: t.size.toString(),
20
21
  mimeType: t.type || "application/octet-stream",
21
- extension: i,
22
+ extension: a,
22
23
  folderId: n || null
23
24
  } } });
24
25
  if (e.error) {
26
+ if (r(e.error)) throw e.error;
25
27
  let t = e.error.message || "";
26
- throw t.includes("STORAGE_QUOTA_EXCEEDED") || t.includes("quota") || t.toLowerCase().includes("quota exceeded") ? Error("QUOTA_EXHAUSTED") : Error(t || "Failed to initiate upload");
28
+ throw Error(t || "Failed to initiate upload");
27
29
  }
28
- let a = e.data?.initiateUpload, o = a?.session, s = a?.uploadUrl;
29
- if (!o || !s) throw Error("Failed to initiate upload session");
30
- if (r?.(10), !(await new Promise((e, n) => {
31
- let i = new XMLHttpRequest();
32
- d.current = i, i.upload.addEventListener("progress", (e) => {
33
- if (e.lengthComputable && !f.current) {
30
+ let o = e.data?.initiateUpload, s = o?.session, c = o?.uploadUrl;
31
+ if (!s || !c) throw Error("Failed to initiate upload session");
32
+ if (i?.(10), !(await new Promise((e, n) => {
33
+ let r = new XMLHttpRequest();
34
+ f.current = r, r.upload.addEventListener("progress", (e) => {
35
+ if (e.lengthComputable && !p.current) {
34
36
  let t = 10 + Math.round(e.loaded / e.total * 80);
35
- r?.(t);
37
+ i?.(t);
36
38
  }
37
- }), i.addEventListener("load", () => {
38
- i.status >= 200 && i.status < 300 ? (f.current || r?.(90), e(new Response(i.response, {
39
- status: i.status,
40
- statusText: i.statusText,
41
- headers: new Headers(i.getAllResponseHeaders().split("\r\n").reduce((e, t) => {
39
+ }), r.addEventListener("load", () => {
40
+ r.status >= 200 && r.status < 300 ? (p.current || i?.(90), e(new Response(r.response, {
41
+ status: r.status,
42
+ statusText: r.statusText,
43
+ headers: new Headers(r.getAllResponseHeaders().split("\r\n").reduce((e, t) => {
42
44
  let n = t.split(": ");
43
45
  return n[0] && (e[n[0]] = n[1] || ""), e;
44
46
  }, {}))
45
- }))) : n(/* @__PURE__ */ Error(`Upload failed with status ${i.status}`));
46
- }), i.addEventListener("error", () => {
47
- f.current || n(/* @__PURE__ */ Error("Upload failed"));
48
- }), i.addEventListener("abort", () => {
49
- f.current ? n(/* @__PURE__ */ Error("Upload paused")) : n(/* @__PURE__ */ Error("Upload aborted"));
50
- }), i.open("PUT", s), i.setRequestHeader("Content-Type", t.type), i.send(t);
47
+ }))) : n(/* @__PURE__ */ Error(`Upload failed with status ${r.status}`));
48
+ }), r.addEventListener("error", () => {
49
+ p.current || n(/* @__PURE__ */ Error("Upload failed"));
50
+ }), r.addEventListener("abort", () => {
51
+ p.current ? n(/* @__PURE__ */ Error("Upload paused")) : n(/* @__PURE__ */ Error("Upload aborted"));
52
+ }), r.open("PUT", c), r.setRequestHeader("Content-Type", t.type), r.send(t);
51
53
  })).ok) throw Error("Failed to upload file to S3");
52
- if (f.current) throw Error("Upload paused");
53
- let l = await m({ variables: { input: {
54
- sessionId: o.id,
54
+ if (p.current) throw Error("Upload paused");
55
+ let u = await h({ variables: { input: {
56
+ sessionId: s.id,
55
57
  fileSize: t.size.toString(),
56
58
  folderId: n || null
57
59
  } } });
58
- if (l.error) {
59
- let e = l.error.message || "";
60
- throw e.includes("STORAGE_QUOTA_EXCEEDED") || e.includes("quota") || e.toLowerCase().includes("quota exceeded") ? Error("QUOTA_EXHAUSTED") : Error(l.error.message || "Failed to complete upload");
61
- }
62
- let h = l.data?.completeUpload?.file;
63
- if (!h) throw Error("Upload completed but file data was not returned. The file may not have been created properly.");
64
- return r?.(100), h;
60
+ if (u.error) throw r(u.error) ? u.error : (u.error.message, Error(u.error.message || "Failed to complete upload"));
61
+ let g = u.data?.completeUpload?.file;
62
+ if (!g) throw Error("Upload completed but file data was not returned. The file may not have been created properly.");
63
+ return i?.(100), g;
65
64
  } catch (e) {
66
65
  let t = e instanceof Error ? e : /* @__PURE__ */ Error("Upload failed");
67
- throw u(t), t;
66
+ throw d(t), t;
68
67
  } finally {
69
- c(!1), d.current = null;
68
+ l(!1), f.current = null;
70
69
  }
71
70
  },
72
- isPending: s,
73
- error: l,
74
- pause: r(() => {
75
- d.current && !f.current && (f.current = !0, d.current.abort(), d.current = null);
71
+ isPending: c,
72
+ error: u,
73
+ pause: i(() => {
74
+ f.current && !p.current && (p.current = !0, f.current.abort(), f.current = null);
76
75
  }, []),
77
- isPaused: r(() => f.current, [])
76
+ isPaused: i(() => p.current, [])
78
77
  };
79
78
  };
80
79
  //#endregion
81
- export { o as useUploadFile };
80
+ export { s as useUploadFile };
82
81
 
83
82
  //# sourceMappingURL=useUploadFile.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useUploadFile.js","names":[],"sources":["../../../src/files/hooks/useUploadFile.ts"],"sourcesContent":["import { useState, useRef, useCallback } from \"react\";\nimport {\n useInitiateUploadMutation,\n useCompleteUploadMutation,\n} from \"../../generated/wspace-operations\";\nimport { useFilesContext } from \"../providers/FilesProvider\";\n\nexport interface InitiateUploadInput {\n fileName: string;\n fileSize: number;\n mimeType: string;\n folderId?: string;\n}\n\nexport interface CompleteUploadInput {\n sessionId: string;\n fileSize: string;\n}\n\nexport interface UploadController {\n pause: () => void;\n isPaused: () => boolean;\n}\n\nexport const useUploadFile = () => {\n const { workspaceId } = useFilesContext();\n const [isUploading, setIsUploading] = useState(false);\n const [uploadError, setUploadError] = useState<Error | null>(null);\n\n const activeXhrRef = useRef<XMLHttpRequest | null>(null);\n const isPausedRef = useRef(false);\n\n const [initiateUploadMutation] = useInitiateUploadMutation();\n const [completeUploadMutation] = useCompleteUploadMutation({\n refetchQueries: [\n \"GetFilesRootBrowsePage\",\n \"GetFilesFolderBrowsePage\",\n \"GetFilesRecentPage\",\n \"GetFiles\",\n ],\n });\n\n const pause = useCallback(() => {\n if (activeXhrRef.current && !isPausedRef.current) {\n isPausedRef.current = true;\n activeXhrRef.current.abort();\n activeXhrRef.current = null;\n }\n }, []);\n\n const isPaused = useCallback(() => isPausedRef.current, []);\n\n const mutateAsync = async (params: {\n file: File;\n folderId?: string;\n onProgress?: (progress: number) => void;\n }) => {\n const { file, folderId, onProgress } = params;\n const extension = file.name.split(\".\").pop() || \"\";\n\n try {\n setIsUploading(true);\n setUploadError(null);\n isPausedRef.current = false;\n\n onProgress?.(0);\n\n const initiateResult = await initiateUploadMutation({\n variables: {\n input: {\n fileName: file.name,\n fileSize: file.size.toString(),\n mimeType: file.type || \"application/octet-stream\",\n extension: extension,\n folderId: folderId || null,\n },\n },\n });\n\n // Check for GraphQL errors FIRST - before checking data\n // This ensures backend error messages are properly surfaced to the user\n if (initiateResult.error) {\n const errorMsg = initiateResult.error.message || \"\";\n if (\n errorMsg.includes(\"STORAGE_QUOTA_EXCEEDED\") ||\n errorMsg.includes(\"quota\") ||\n errorMsg.toLowerCase().includes(\"quota exceeded\")\n ) {\n throw new Error(\"QUOTA_EXHAUSTED\");\n }\n throw new Error(errorMsg || \"Failed to initiate upload\");\n }\n\n const result = initiateResult.data?.initiateUpload;\n const session = result?.session;\n const uploadUrl = result?.uploadUrl;\n\n if (!session || !uploadUrl) {\n throw new Error(\"Failed to initiate upload session\");\n }\n\n onProgress?.(10);\n\n const uploadResponse = await new Promise<Response>((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n activeXhrRef.current = xhr;\n\n xhr.upload.addEventListener(\"progress\", (e) => {\n if (e.lengthComputable && !isPausedRef.current) {\n const percentComplete = 10 + Math.round((e.loaded / e.total) * 80);\n onProgress?.(percentComplete);\n }\n });\n\n xhr.addEventListener(\"load\", () => {\n if (xhr.status >= 200 && xhr.status < 300) {\n if (!isPausedRef.current) {\n onProgress?.(90);\n }\n resolve(\n new Response(xhr.response, {\n status: xhr.status,\n statusText: xhr.statusText,\n headers: new Headers(\n xhr\n .getAllResponseHeaders()\n .split(\"\\r\\n\")\n .reduce(\n (acc, current) => {\n const parts = current.split(\": \");\n if (parts[0]) acc[parts[0]] = parts[1] || \"\";\n return acc;\n },\n {} as Record<string, string>,\n ),\n ),\n }),\n );\n } else {\n reject(new Error(`Upload failed with status ${xhr.status}`));\n }\n });\n\n xhr.addEventListener(\"error\", () => {\n if (!isPausedRef.current) {\n reject(new Error(\"Upload failed\"));\n }\n });\n\n xhr.addEventListener(\"abort\", () => {\n if (isPausedRef.current) {\n reject(new Error(\"Upload paused\"));\n } else {\n reject(new Error(\"Upload aborted\"));\n }\n });\n\n xhr.open(\"PUT\", uploadUrl);\n xhr.setRequestHeader(\"Content-Type\", file.type);\n xhr.send(file);\n });\n\n if (!uploadResponse.ok) {\n throw new Error(\"Failed to upload file to S3\");\n }\n\n if (isPausedRef.current) {\n throw new Error(\"Upload paused\");\n }\n\n const completeResult = await completeUploadMutation({\n variables: {\n input: {\n sessionId: session.id,\n fileSize: file.size.toString(),\n folderId: folderId || null,\n },\n },\n });\n\n if (completeResult.error) {\n const errorMsg = completeResult.error.message || \"\";\n if (\n errorMsg.includes(\"STORAGE_QUOTA_EXCEEDED\") ||\n errorMsg.includes(\"quota\") ||\n errorMsg.toLowerCase().includes(\"quota exceeded\")\n ) {\n throw new Error(\"QUOTA_EXHAUSTED\");\n }\n throw new Error(\n completeResult.error.message || \"Failed to complete upload\",\n );\n }\n\n const uploadedFile = completeResult.data?.completeUpload?.file;\n if (!uploadedFile) {\n throw new Error(\n \"Upload completed but file data was not returned. The file may not have been created properly.\",\n );\n }\n\n onProgress?.(100);\n return uploadedFile;\n } catch (error) {\n const err = error instanceof Error ? error : new Error(\"Upload failed\");\n setUploadError(err);\n throw err;\n } finally {\n setIsUploading(false);\n activeXhrRef.current = null;\n }\n };\n\n return {\n mutateAsync,\n isPending: isUploading,\n error: uploadError,\n pause,\n isPaused,\n };\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"useUploadFile.js","names":[],"sources":["../../../src/files/hooks/useUploadFile.ts"],"sourcesContent":["import { useState, useRef, useCallback } from \"react\";\nimport {\n useInitiateUploadMutation,\n useCompleteUploadMutation,\n} from \"../../generated/wspace-operations\";\nimport { useFilesContext } from \"../providers/FilesProvider\";\nimport { isQuotaExhaustedError } from \"../utils/quotaUtils\";\n\nexport interface InitiateUploadInput {\n fileName: string;\n fileSize: number;\n mimeType: string;\n folderId?: string;\n}\n\nexport interface CompleteUploadInput {\n sessionId: string;\n fileSize: string;\n}\n\nexport interface UploadController {\n pause: () => void;\n isPaused: () => boolean;\n}\n\nexport const useUploadFile = () => {\n const { workspaceId } = useFilesContext();\n const [isUploading, setIsUploading] = useState(false);\n const [uploadError, setUploadError] = useState<Error | null>(null);\n\n const activeXhrRef = useRef<XMLHttpRequest | null>(null);\n const isPausedRef = useRef(false);\n\n const [initiateUploadMutation] = useInitiateUploadMutation();\n const [completeUploadMutation] = useCompleteUploadMutation({\n refetchQueries: [\n \"GetFilesRootBrowsePage\",\n \"GetFilesFolderBrowsePage\",\n \"GetFilesRecentPage\",\n \"GetFiles\",\n ],\n });\n\n const pause = useCallback(() => {\n if (activeXhrRef.current && !isPausedRef.current) {\n isPausedRef.current = true;\n activeXhrRef.current.abort();\n activeXhrRef.current = null;\n }\n }, []);\n\n const isPaused = useCallback(() => isPausedRef.current, []);\n\n const mutateAsync = async (params: {\n file: File;\n folderId?: string;\n onProgress?: (progress: number) => void;\n }) => {\n const { file, folderId, onProgress } = params;\n const extension = file.name.split(\".\").pop() || \"\";\n\n try {\n setIsUploading(true);\n setUploadError(null);\n isPausedRef.current = false;\n\n onProgress?.(0);\n\n const initiateResult = await initiateUploadMutation({\n variables: {\n input: {\n fileName: file.name,\n fileSize: file.size.toString(),\n mimeType: file.type || \"application/octet-stream\",\n extension: extension,\n folderId: folderId || null,\n },\n },\n });\n\n // Check for GraphQL errors FIRST - before checking data\n // This ensures backend error messages are properly surfaced to the user\n if (initiateResult.error) {\n if (isQuotaExhaustedError(initiateResult.error)) {\n throw initiateResult.error;\n }\n const errorMsg = initiateResult.error.message || \"\";\n throw new Error(errorMsg || \"Failed to initiate upload\");\n }\n\n const result = initiateResult.data?.initiateUpload;\n const session = result?.session;\n const uploadUrl = result?.uploadUrl;\n\n if (!session || !uploadUrl) {\n throw new Error(\"Failed to initiate upload session\");\n }\n\n onProgress?.(10);\n\n const uploadResponse = await new Promise<Response>((resolve, reject) => {\n const xhr = new XMLHttpRequest();\n activeXhrRef.current = xhr;\n\n xhr.upload.addEventListener(\"progress\", (e) => {\n if (e.lengthComputable && !isPausedRef.current) {\n const percentComplete = 10 + Math.round((e.loaded / e.total) * 80);\n onProgress?.(percentComplete);\n }\n });\n\n xhr.addEventListener(\"load\", () => {\n if (xhr.status >= 200 && xhr.status < 300) {\n if (!isPausedRef.current) {\n onProgress?.(90);\n }\n resolve(\n new Response(xhr.response, {\n status: xhr.status,\n statusText: xhr.statusText,\n headers: new Headers(\n xhr\n .getAllResponseHeaders()\n .split(\"\\r\\n\")\n .reduce(\n (acc, current) => {\n const parts = current.split(\": \");\n if (parts[0]) acc[parts[0]] = parts[1] || \"\";\n return acc;\n },\n {} as Record<string, string>,\n ),\n ),\n }),\n );\n } else {\n reject(new Error(`Upload failed with status ${xhr.status}`));\n }\n });\n\n xhr.addEventListener(\"error\", () => {\n if (!isPausedRef.current) {\n reject(new Error(\"Upload failed\"));\n }\n });\n\n xhr.addEventListener(\"abort\", () => {\n if (isPausedRef.current) {\n reject(new Error(\"Upload paused\"));\n } else {\n reject(new Error(\"Upload aborted\"));\n }\n });\n\n xhr.open(\"PUT\", uploadUrl);\n xhr.setRequestHeader(\"Content-Type\", file.type);\n xhr.send(file);\n });\n\n if (!uploadResponse.ok) {\n throw new Error(\"Failed to upload file to S3\");\n }\n\n if (isPausedRef.current) {\n throw new Error(\"Upload paused\");\n }\n\n const completeResult = await completeUploadMutation({\n variables: {\n input: {\n sessionId: session.id,\n fileSize: file.size.toString(),\n folderId: folderId || null,\n },\n },\n });\n\n if (completeResult.error) {\n if (isQuotaExhaustedError(completeResult.error)) {\n throw completeResult.error;\n }\n const errorMsg = completeResult.error.message || \"\";\n throw new Error(\n completeResult.error.message || \"Failed to complete upload\",\n );\n }\n\n const uploadedFile = completeResult.data?.completeUpload?.file;\n if (!uploadedFile) {\n throw new Error(\n \"Upload completed but file data was not returned. The file may not have been created properly.\",\n );\n }\n\n onProgress?.(100);\n return uploadedFile;\n } catch (error) {\n const err = error instanceof Error ? error : new Error(\"Upload failed\");\n setUploadError(err);\n throw err;\n } finally {\n setIsUploading(false);\n activeXhrRef.current = null;\n }\n };\n\n return {\n mutateAsync,\n isPending: isUploading,\n error: uploadError,\n pause,\n isPaused,\n };\n};\n"],"mappings":"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"}