@burdenoff/microfe-devportal 2026.916.1 → 2026.916.3

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.
@@ -3,8 +3,9 @@ import { EmptyState as t } from "../components/EmptyState.js";
3
3
  import { PageLayout as n } from "../components/PageLayout.js";
4
4
  import { PermissionGuard as r } from "../components/PermissionGuard.js";
5
5
  import "../components/index.js";
6
- import { useEffect as i, useState as a } from "react";
7
- import { ArrowRight as o, Building2 as s, CheckCircle2 as c, Clock as l, DollarSign as u, Download as d, Loader2 as f, Package as p, Plus as m } from "lucide-react";
6
+ import { formatCurrency as i } from "../utils/currency.js";
7
+ import { useEffect as a, useState as o } from "react";
8
+ import { ArrowRight as s, Building2 as c, CheckCircle2 as l, Clock as u, DollarSign as d, Loader2 as f, Package as p, Plus as m } from "lucide-react";
8
9
  import { Fragment as h, jsx as g, jsxs as _ } from "react/jsx-runtime";
9
10
  import { useI18n as v } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
10
11
  import { GlassCard as y, GradientText as b } from "@burdenoff/fe-libs/ui";
@@ -40,16 +41,20 @@ function S(e, t) {
40
41
  maximumFractionDigits: 2
41
42
  }).format(e)} ${n}`;
42
43
  }
43
- var C = "\n query SellerDashboardBootstrap {\n myDeveloperOrganizations {\n id\n name\n slug\n logoUrl\n isVerified\n publishedApps\n totalInstalls\n totalRevenue\n kind\n }\n myPublisher {\n id\n }\n }\n", w = "\n query SellerDashboardProducts($publisherId: String!) {\n products(filter: { publisherId: $publisherId, nature: PHYSICAL }, limit: 3, offset: 0) {\n id\n name\n slug\n type\n status\n price\n currency\n stockQuantity\n icon\n }\n }\n", T = () => {
44
- let { navigate: u, basePath: d, apiGatewayUrl: y, authToken: x } = e(), { t: S } = v(), T = (e, t) => {
44
+ var C = "\n query SellerDashboardBootstrap {\n myDeveloperOrganizations {\n id\n name\n slug\n logoUrl\n isVerified\n publishedApps\n totalInstalls\n totalRevenue\n kind\n }\n myPublisher {\n id\n }\n }\n";
45
+ function w(e) {
46
+ return `query SellerDashboardOrgCurrencies {\n ${e.map((e, t) => `o${t}: developerOrgRevenueSummary(organizationId: ${JSON.stringify(e)}, period: "monthly") { currency }`).join("\n ")}\n }`;
47
+ }
48
+ var T = "\n query SellerDashboardProducts($publisherId: String!) {\n products(filter: { publisherId: $publisherId, nature: PHYSICAL }, limit: 3, offset: 0) {\n id\n name\n slug\n type\n status\n price\n currency\n stockQuantity\n icon\n }\n }\n", E = () => {
49
+ let { navigate: i, basePath: d, apiGatewayUrl: y, authToken: x } = e(), { t: S } = v(), E = (e, t) => {
45
50
  let n = S(e);
46
51
  return n === e ? t : n;
47
- }, A = y ?? "/global/graphql", [j, M] = a(!0), [N, P] = a(!1), [F, I] = a([]), [L, R] = a(null), [z, B] = a([]);
48
- i(() => {
52
+ }, j = y ?? "/global/graphql", [M, N] = o(!0), [P, F] = o(!1), [I, L] = o([]), [R, z] = o(null), [B, V] = o([]), [H, U] = o({});
53
+ a(() => {
49
54
  let e = !1;
50
- return M(!0), (async () => {
55
+ return N(!0), (async () => {
51
56
  try {
52
- let t = await fetch(A, {
57
+ let t = await fetch(j, {
53
58
  method: "POST",
54
59
  headers: {
55
60
  "Content-Type": "application/json",
@@ -60,27 +65,27 @@ var C = "\n query SellerDashboardBootstrap {\n myDeveloperOrganizations {\n
60
65
  if (e) return;
61
66
  let n = await t.json();
62
67
  if (e) return;
63
- I((n.data?.myDeveloperOrganizations ?? []).filter((e) => e.kind === "PHYSICAL")), R(n.data?.myPublisher?.id ?? null);
68
+ L((n.data?.myDeveloperOrganizations ?? []).filter((e) => e.kind === "PHYSICAL")), z(n.data?.myPublisher?.id ?? null);
64
69
  } finally {
65
- e || M(!1);
70
+ e || N(!1);
66
71
  }
67
72
  })(), () => {
68
73
  e = !0;
69
74
  };
70
- }, [A, x]), i(() => {
71
- if (!L) return;
75
+ }, [j, x]), a(() => {
76
+ if (!R) return;
72
77
  let e = !1;
73
- return P(!0), (async () => {
78
+ return F(!0), (async () => {
74
79
  try {
75
- let t = await fetch(A, {
80
+ let t = await fetch(j, {
76
81
  method: "POST",
77
82
  headers: {
78
83
  "Content-Type": "application/json",
79
84
  Authorization: `Bearer ${x ?? ""}`
80
85
  },
81
86
  body: JSON.stringify({
82
- query: w,
83
- variables: { publisherId: L }
87
+ query: T,
88
+ variables: { publisherId: R }
84
89
  })
85
90
  });
86
91
  if (e) return;
@@ -91,110 +96,141 @@ var C = "\n query SellerDashboardBootstrap {\n myDeveloperOrganizations {\n
91
96
  "NODE",
92
97
  "WORKFLOW"
93
98
  ]);
94
- B((n.data?.products ?? []).filter((e) => !r.has(e.type)).map((e) => ({
99
+ V((n.data?.products ?? []).filter((e) => !r.has(e.type)).map((e) => ({
95
100
  ...e,
96
101
  status: e.status
97
102
  })));
98
103
  } finally {
99
- e || P(!1);
104
+ e || F(!1);
100
105
  }
101
106
  })(), () => {
102
107
  e = !0;
103
108
  };
104
109
  }, [
105
- L,
106
- A,
110
+ R,
111
+ j,
112
+ x
113
+ ]), a(() => {
114
+ let e = I.slice(0, 3).map((e) => e.id);
115
+ if (e.length === 0) return;
116
+ let t = !1;
117
+ return (async () => {
118
+ try {
119
+ let n = await fetch(j, {
120
+ method: "POST",
121
+ headers: {
122
+ "Content-Type": "application/json",
123
+ Authorization: `Bearer ${x ?? ""}`
124
+ },
125
+ body: JSON.stringify({ query: w(e) })
126
+ });
127
+ if (t) return;
128
+ let r = await n.json();
129
+ if (t || !r.data) return;
130
+ let i = {};
131
+ e.forEach((e, t) => {
132
+ let n = r.data?.[`o${t}`]?.currency;
133
+ n && (i[e] = n);
134
+ }), U(i);
135
+ } catch {}
136
+ })(), () => {
137
+ t = !0;
138
+ };
139
+ }, [
140
+ I.map((e) => e.id).join(","),
141
+ j,
107
142
  x
108
143
  ]);
109
- let V = j, H = j || N, U = z.length, W = z.filter((e) => e.status === "PENDING_REVIEW").length, G = z.filter((e) => e.status === "PUBLISHED").length, K = (e) => {
110
- u?.(`${d}${e}`);
144
+ let W = M, G = M || P, K = B.length, q = B.filter((e) => e.status === "PENDING_REVIEW").length, J = B.filter((e) => e.status === "PUBLISHED").length, Y = (e) => {
145
+ i?.(`${d}${e}`);
111
146
  };
112
147
  return /* @__PURE__ */ g(r, {
113
148
  authOnly: !0,
114
149
  children: /* @__PURE__ */ g(n, {
115
150
  title: /* @__PURE__ */ _(h, { children: [
116
- T("devportal.seller", "Seller"),
151
+ E("devportal.seller", "Seller"),
117
152
  " ",
118
- /* @__PURE__ */ g(b, { children: T("devportal.dashboard", "Dashboard") })
153
+ /* @__PURE__ */ g(b, { children: E("devportal.dashboard", "Dashboard") })
119
154
  ] }),
120
- actions: V ? null : /* @__PURE__ */ _("button", {
121
- onClick: () => K("/seller/items/new"),
155
+ actions: W ? null : /* @__PURE__ */ _("button", {
156
+ onClick: () => Y("/seller/items/new"),
122
157
  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",
123
- children: [/* @__PURE__ */ g(m, { size: 16 }), T("devportal.listAnItem", "List an Item")]
158
+ children: [/* @__PURE__ */ g(m, { size: 16 }), E("devportal.listAnItem", "List an Item")]
124
159
  }),
125
160
  children: /* @__PURE__ */ _("div", {
126
161
  className: "flex flex-col gap-8",
127
162
  children: [
128
163
  /* @__PURE__ */ g("div", {
129
164
  className: "flex items-center gap-6 border-b border-border-seam pb-6",
130
- children: H ? /* @__PURE__ */ g(f, { className: "h-4 w-4 animate-spin text-text-secondary" }) : /* @__PURE__ */ _(h, { children: [
131
- /* @__PURE__ */ g(E, {
165
+ children: G ? /* @__PURE__ */ g(f, { className: "h-4 w-4 animate-spin text-text-secondary" }) : /* @__PURE__ */ _(h, { children: [
166
+ /* @__PURE__ */ g(D, {
132
167
  icon: /* @__PURE__ */ g(p, { size: 14 }),
133
- value: U,
134
- label: T("devportal.items", "Items")
168
+ value: K,
169
+ label: E("devportal.items", "Items")
135
170
  }),
136
171
  /* @__PURE__ */ g("div", { className: "h-4 w-px bg-border-seam" }),
137
- /* @__PURE__ */ g(E, {
138
- icon: /* @__PURE__ */ g(l, { size: 14 }),
139
- value: W,
140
- label: T("devportal.pendingReview", "Pending Review")
172
+ /* @__PURE__ */ g(D, {
173
+ icon: /* @__PURE__ */ g(u, { size: 14 }),
174
+ value: q,
175
+ label: E("devportal.pendingReview", "Pending Review")
141
176
  }),
142
177
  /* @__PURE__ */ g("div", { className: "h-4 w-px bg-border-seam" }),
143
- /* @__PURE__ */ g(E, {
144
- icon: /* @__PURE__ */ g(c, { size: 14 }),
145
- value: G,
146
- label: T("devportal.published", "Published")
178
+ /* @__PURE__ */ g(D, {
179
+ icon: /* @__PURE__ */ g(l, { size: 14 }),
180
+ value: J,
181
+ label: E("devportal.published", "Published")
147
182
  }),
148
183
  /* @__PURE__ */ g("div", { className: "h-4 w-px bg-border-seam" }),
149
- /* @__PURE__ */ g(E, {
150
- icon: /* @__PURE__ */ g(s, { size: 14 }),
151
- value: F.length,
152
- label: T("devportal.organizations", "Organizations")
184
+ /* @__PURE__ */ g(D, {
185
+ icon: /* @__PURE__ */ g(c, { size: 14 }),
186
+ value: I.length,
187
+ label: E("devportal.organizations", "Organizations")
153
188
  })
154
189
  ] })
155
190
  }),
156
- /* @__PURE__ */ g(D, {
157
- title: T("devportal.recentListings", "Recent Listings"),
158
- action: z.length > 0 ? /* @__PURE__ */ _("button", {
159
- onClick: () => K("/seller/items"),
191
+ /* @__PURE__ */ g(O, {
192
+ title: E("devportal.recentListings", "Recent Listings"),
193
+ action: B.length > 0 ? /* @__PURE__ */ _("button", {
194
+ onClick: () => Y("/seller/items"),
160
195
  className: "inline-flex items-center gap-1 text-sm font-medium text-primary hover:underline",
161
- children: [T("devportal.viewAll", "View all"), /* @__PURE__ */ g(o, { size: 14 })]
196
+ children: [E("devportal.viewAll", "View all"), /* @__PURE__ */ g(s, { size: 14 })]
162
197
  }) : void 0,
163
- children: V || N ? /* @__PURE__ */ g("div", {
198
+ children: W || P ? /* @__PURE__ */ g("div", {
164
199
  className: "flex items-center justify-center py-12",
165
200
  children: /* @__PURE__ */ g(f, { className: "h-6 w-6 animate-spin text-text-secondary" })
166
- }) : z.length > 0 ? /* @__PURE__ */ g("div", {
201
+ }) : B.length > 0 ? /* @__PURE__ */ g("div", {
167
202
  className: "grid gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3",
168
- children: z.slice(0, 3).map((e) => /* @__PURE__ */ g(O, {
203
+ children: B.slice(0, 3).map((e) => /* @__PURE__ */ g(k, {
169
204
  product: e,
170
- onClick: () => K("/seller/items")
205
+ onClick: () => Y("/seller/items")
171
206
  }, e.id))
172
207
  }) : /* @__PURE__ */ g(t, {
173
208
  illustration: "empty-data",
174
- title: T("devportal.listYourFirstItem", "List your first item"),
175
- description: T("devportal.addPhysicalProductsToYourStore", "Add physical products to your store and they will appear here. Your seller organization is set up automatically the first time you list an item."),
209
+ title: E("devportal.listYourFirstItem", "List your first item"),
210
+ description: E("devportal.addPhysicalProductsToYourStore", "Add physical products to your store and they will appear here. Your seller organization is set up automatically the first time you list an item."),
176
211
  action: /* @__PURE__ */ _("button", {
177
- onClick: () => K("/seller/items/new"),
212
+ onClick: () => Y("/seller/items/new"),
178
213
  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",
179
- children: [/* @__PURE__ */ g(m, { size: 16 }), T("devportal.listAnItem", "List an Item")]
214
+ children: [/* @__PURE__ */ g(m, { size: 16 }), E("devportal.listAnItem", "List an Item")]
180
215
  })
181
216
  })
182
217
  }),
183
- (V || F.length > 0) && /* @__PURE__ */ g(D, {
184
- title: T("devportal.yourOrganizations", "Your Organizations"),
185
- action: F.length > 0 ? /* @__PURE__ */ _("button", {
186
- onClick: () => K("/organizations"),
218
+ (W || I.length > 0) && /* @__PURE__ */ g(O, {
219
+ title: E("devportal.yourOrganizations", "Your Organizations"),
220
+ action: I.length > 0 ? /* @__PURE__ */ _("button", {
221
+ onClick: () => Y("/seller/organizations"),
187
222
  className: "inline-flex items-center gap-1 text-sm font-medium text-primary hover:underline",
188
- children: [T("devportal.viewAll", "View all"), /* @__PURE__ */ g(o, { size: 14 })]
223
+ children: [E("devportal.viewAll", "View all"), /* @__PURE__ */ g(s, { size: 14 })]
189
224
  }) : void 0,
190
- children: V ? /* @__PURE__ */ g("div", {
225
+ children: W ? /* @__PURE__ */ g("div", {
191
226
  className: "flex items-center justify-center py-12",
192
227
  children: /* @__PURE__ */ g(f, { className: "h-6 w-6 animate-spin text-text-secondary" })
193
228
  }) : /* @__PURE__ */ g("div", {
194
229
  className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-3",
195
- children: F.slice(0, 3).map((e) => /* @__PURE__ */ g(k, {
230
+ children: I.slice(0, 3).map((e) => /* @__PURE__ */ g(A, {
196
231
  org: e,
197
- onClick: () => K(`/organizations/${e.id}`)
232
+ currency: H[e.id] ?? "USD",
233
+ onClick: () => Y(`/seller/organizations/${e.id}`)
198
234
  }, e.id))
199
235
  })
200
236
  })
@@ -202,7 +238,7 @@ var C = "\n query SellerDashboardBootstrap {\n myDeveloperOrganizations {\n
202
238
  })
203
239
  })
204
240
  });
205
- }, E = ({ icon: e, value: t, label: n }) => /* @__PURE__ */ _("div", {
241
+ }, D = ({ icon: e, value: t, label: n }) => /* @__PURE__ */ _("div", {
206
242
  className: "flex items-center gap-1.5",
207
243
  children: [
208
244
  /* @__PURE__ */ g("span", {
@@ -218,7 +254,7 @@ var C = "\n query SellerDashboardBootstrap {\n myDeveloperOrganizations {\n
218
254
  children: n
219
255
  })
220
256
  ]
221
- }), D = ({ title: e, action: t, children: n }) => /* @__PURE__ */ _("section", {
257
+ }), O = ({ title: e, action: t, children: n }) => /* @__PURE__ */ _("section", {
222
258
  className: "flex flex-col gap-4",
223
259
  children: [/* @__PURE__ */ _("div", {
224
260
  className: "flex items-center justify-between",
@@ -227,7 +263,7 @@ var C = "\n query SellerDashboardBootstrap {\n myDeveloperOrganizations {\n
227
263
  children: e
228
264
  }), t]
229
265
  }), n]
230
- }), O = ({ product: e, onClick: t }) => {
266
+ }), k = ({ product: e, onClick: t }) => {
231
267
  let n = x[e.status] ?? x.DRAFT;
232
268
  return /* @__PURE__ */ _(y, {
233
269
  treatment: "insight",
@@ -275,19 +311,19 @@ var C = "\n query SellerDashboardBootstrap {\n myDeveloperOrganizations {\n
275
311
  })]
276
312
  })]
277
313
  });
278
- }, k = ({ org: e, onClick: t }) => {
279
- let { t: n } = v(), r = (e, t) => {
280
- let r = n(e);
281
- return r === e ? t : r;
282
- }, i = e.name.split(" ").map((e) => e[0]).join("").slice(0, 2).toUpperCase();
314
+ }, A = ({ org: e, currency: t, onClick: n }) => {
315
+ let { t: r } = v(), a = (e, t) => {
316
+ let n = r(e);
317
+ return n === e ? t : n;
318
+ }, o = e.name.split(" ").map((e) => e[0]).join("").slice(0, 2).toUpperCase();
283
319
  return /* @__PURE__ */ _(y, {
284
320
  treatment: "insight",
285
321
  role: "button",
286
322
  tabIndex: 0,
287
323
  className: "cursor-pointer p-5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
288
- onClick: t,
324
+ onClick: n,
289
325
  onKeyDown: (e) => {
290
- (e.key === "Enter" || e.key === " ") && (e.preventDefault(), t());
326
+ (e.key === "Enter" || e.key === " ") && (e.preventDefault(), n());
291
327
  },
292
328
  children: [/* @__PURE__ */ _("div", {
293
329
  className: "flex items-center gap-3 mb-4",
@@ -298,7 +334,7 @@ var C = "\n query SellerDashboardBootstrap {\n myDeveloperOrganizations {\n
298
334
  className: "h-10 w-10 rounded-xl object-cover"
299
335
  }) : /* @__PURE__ */ g("div", {
300
336
  className: "flex h-10 w-10 items-center justify-center rounded-xl bg-action-primary-bg/15 text-primary text-sm font-bold",
301
- children: i
337
+ children: o
302
338
  }),
303
339
  /* @__PURE__ */ _("div", {
304
340
  className: "min-w-0 flex-1",
@@ -310,46 +346,34 @@ var C = "\n query SellerDashboardBootstrap {\n myDeveloperOrganizations {\n
310
346
  children: [
311
347
  e.publishedApps ?? 0,
312
348
  " ",
313
- r("devportal.appsPublished", "apps published")
349
+ a("devportal.appsPublished", "apps published")
314
350
  ]
315
351
  })]
316
352
  }),
317
- /* @__PURE__ */ g(o, {
353
+ /* @__PURE__ */ g(s, {
318
354
  size: 15,
319
355
  className: "text-text-muted shrink-0"
320
356
  })
321
357
  ]
322
- }), /* @__PURE__ */ _("div", {
323
- className: "grid grid-cols-2 gap-3",
324
- children: [/* @__PURE__ */ _("div", {
358
+ }), /* @__PURE__ */ g("div", {
359
+ className: "grid grid-cols-1 gap-3",
360
+ children: /* @__PURE__ */ _("div", {
325
361
  className: "flex items-center gap-2 rounded-lg bg-bg-muted px-3 py-2",
326
362
  children: [/* @__PURE__ */ g(d, {
327
363
  size: 12,
328
364
  className: "text-text-muted shrink-0"
329
365
  }), /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("p", {
330
366
  className: "text-[11px] text-text-secondary leading-none mb-0.5",
331
- children: r("devportal.installs", "Installs")
367
+ children: a("devportal.revenue", "Revenue")
332
368
  }), /* @__PURE__ */ g("p", {
333
369
  className: "text-sm font-semibold tabular-nums",
334
- children: (e.totalInstalls ?? 0).toLocaleString()
370
+ children: i(e.totalRevenue ?? 0, t)
335
371
  })] })]
336
- }), /* @__PURE__ */ _("div", {
337
- className: "flex items-center gap-2 rounded-lg bg-bg-muted px-3 py-2",
338
- children: [/* @__PURE__ */ g(u, {
339
- size: 12,
340
- className: "text-text-muted shrink-0"
341
- }), /* @__PURE__ */ _("div", { children: [/* @__PURE__ */ g("p", {
342
- className: "text-[11px] text-text-secondary leading-none mb-0.5",
343
- children: r("devportal.revenue", "Revenue")
344
- }), /* @__PURE__ */ _("p", {
345
- className: "text-sm font-semibold tabular-nums",
346
- children: ["$", (e.totalRevenue ?? 0).toLocaleString()]
347
- })] })]
348
- })]
372
+ })
349
373
  })]
350
374
  });
351
375
  };
352
376
  //#endregion
353
- export { T as SellerDashboardPage };
377
+ export { E as SellerDashboardPage };
354
378
 
355
379
  //# sourceMappingURL=SellerDashboardPage.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"SellerDashboardPage.js","names":[],"sources":["../../../src/devportal/pages/SellerDashboardPage.tsx"],"sourcesContent":["import type { FC, ReactNode } from \"react\";\nimport { useEffect, useState } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n Package,\n Building2,\n Plus,\n ArrowRight,\n Loader2,\n Download,\n DollarSign,\n Clock,\n CheckCircle2,\n} from \"lucide-react\";\nimport { GlassCard, GradientText } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../components/PageLayout\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\n\n// ---------------------------------------------------------------------------\n// Local types\n// ---------------------------------------------------------------------------\n\ntype ProductStatus =\n \"DRAFT\" | \"PENDING_REVIEW\" | \"PUBLISHED\" | \"REJECTED\" | \"INACTIVE\";\n\ninterface PhysicalProduct {\n id: string;\n name: string;\n slug: string;\n type: string;\n status: ProductStatus;\n price: number;\n currency: string;\n stockQuantity: number;\n icon?: string | null;\n}\n\ninterface OrgSummary {\n id: string;\n name: string;\n slug: string;\n logoUrl?: string | null;\n isVerified: boolean;\n publishedApps?: number | null;\n totalInstalls?: number | null;\n totalRevenue?: number | null;\n kind: \"DIGITAL\" | \"PHYSICAL\";\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst STATUS_BADGE_STYLES: Record<\n ProductStatus,\n { label: string; className: string }\n> = {\n PUBLISHED: {\n label: \"Published\",\n className: \"text-status-success-text bg-status-success-bg-subtle\",\n },\n PENDING_REVIEW: {\n label: \"Pending Review\",\n className: \"text-status-warning-text bg-status-warning-bg-subtle\",\n },\n DRAFT: {\n label: \"Draft\",\n className: \"text-text-muted bg-bg-muted\",\n },\n REJECTED: {\n label: \"Rejected\",\n className: \"text-status-error-text bg-status-error-bg/10\",\n },\n INACTIVE: {\n label: \"Inactive\",\n className: \"text-text-muted bg-bg-muted\",\n },\n};\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction formatPrice(price: number, currency: string): string {\n const curr = currency || \"USD\";\n const formatted = new Intl.NumberFormat(\"en-US\", {\n style: \"currency\",\n currency: curr,\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n }).format(price);\n return `${formatted} ${curr}`;\n}\n\n// ---------------------------------------------------------------------------\n// GraphQL query strings\n// ---------------------------------------------------------------------------\n\nconst BOOTSTRAP_QUERY = `\n query SellerDashboardBootstrap {\n myDeveloperOrganizations {\n id\n name\n slug\n logoUrl\n isVerified\n publishedApps\n totalInstalls\n totalRevenue\n kind\n }\n myPublisher {\n id\n }\n }\n`;\n\nconst SELLER_PRODUCTS_QUERY = `\n query SellerDashboardProducts($publisherId: String!) {\n products(filter: { publisherId: $publisherId, nature: PHYSICAL }, limit: 3, offset: 0) {\n id\n name\n slug\n type\n status\n price\n currency\n stockQuantity\n icon\n }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Page component\n// ---------------------------------------------------------------------------\n\nexport const SellerDashboardPage: FC = () => {\n const { navigate, basePath, apiGatewayUrl, authToken } = 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\n const gqlUrl = apiGatewayUrl ?? \"/global/graphql\";\n\n const [bootstrapLoading, setBootstrapLoading] = useState(true);\n const [productsLoading, setProductsLoading] = useState(false);\n const [orgs, setOrgs] = useState<OrgSummary[]>([]);\n const [publisherId, setPublisherId] = useState<string | null>(null);\n const [products, setProducts] = useState<PhysicalProduct[]>([]);\n\n // Fetch orgs + publisher in one round-trip\n useEffect(() => {\n let cancelled = false;\n setBootstrapLoading(true);\n\n const run = async () => {\n try {\n const res = await fetch(gqlUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${authToken ?? \"\"}`,\n },\n body: JSON.stringify({ query: BOOTSTRAP_QUERY }),\n });\n if (cancelled) return;\n const json = (await res.json()) as {\n data?: {\n myDeveloperOrganizations?: OrgSummary[];\n myPublisher?: { id: string } | null;\n };\n };\n if (cancelled) return;\n // Seller portal only ever shows PHYSICAL-kind orgs — a DIGITAL org\n // (regular app-publishing) has no business here. See DeveloperOrgKind\n // (BOFF-7285).\n setOrgs(\n (json.data?.myDeveloperOrganizations ?? []).filter(\n (org) => org.kind === \"PHYSICAL\",\n ),\n );\n setPublisherId(json.data?.myPublisher?.id ?? null);\n } finally {\n if (!cancelled) setBootstrapLoading(false);\n }\n };\n\n void run();\n return () => {\n cancelled = true;\n };\n }, [gqlUrl, authToken]);\n\n // Fetch recent physical products once publisher is known\n useEffect(() => {\n if (!publisherId) return;\n let cancelled = false;\n setProductsLoading(true);\n\n const run = async () => {\n try {\n const res = await fetch(gqlUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${authToken ?? \"\"}`,\n },\n body: JSON.stringify({\n query: SELLER_PRODUCTS_QUERY,\n variables: { publisherId },\n }),\n });\n if (cancelled) return;\n const json = (await res.json()) as {\n data?: {\n products?: Array<\n Omit<PhysicalProduct, \"status\"> & { status: string }\n >;\n };\n };\n if (cancelled) return;\n const DIGITAL_TYPES = new Set([\"APP\", \"NODE\", \"WORKFLOW\"]);\n setProducts(\n (json.data?.products ?? [])\n .filter((p) => !DIGITAL_TYPES.has(p.type))\n .map((p) => ({\n ...p,\n status: p.status as ProductStatus,\n })),\n );\n } finally {\n if (!cancelled) setProductsLoading(false);\n }\n };\n\n void run();\n return () => {\n cancelled = true;\n };\n }, [publisherId, gqlUrl, authToken]);\n\n const isLoading = bootstrapLoading;\n const statsLoading = bootstrapLoading || productsLoading;\n\n const totalItems = products.length;\n const pendingCount = products.filter(\n (p) => p.status === \"PENDING_REVIEW\",\n ).length;\n const publishedCount = products.filter(\n (p) => p.status === \"PUBLISHED\",\n ).length;\n\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n return (\n <PermissionGuard authOnly>\n <PageLayout\n title={\n <>\n {tr(\"devportal.seller\", \"Seller\")}{\" \"}\n <GradientText>\n {tr(\"devportal.dashboard\", \"Dashboard\")}\n </GradientText>\n </>\n }\n actions={\n // Whether or not a PHYSICAL org exists yet, this always goes to the\n // same place — CreatePhysicalItemPage auto-provisions one via\n // createSellerOrganization on first listing (see\n // useSellerOrganization, BOFF-7285). Hidden only while loading.\n !isLoading ? (\n <button\n onClick={() => handleNavigate(\"/seller/items/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.listAnItem\", \"List an Item\")}\n </button>\n ) : null\n }\n >\n <div className=\"flex flex-col gap-8\">\n {/* Compact inline stats */}\n <div className=\"flex items-center gap-6 border-b border-border-seam pb-6\">\n {statsLoading ? (\n <Loader2 className=\"h-4 w-4 animate-spin text-text-secondary\" />\n ) : (\n <>\n <StatPill\n icon={<Package size={14} />}\n value={totalItems}\n label={tr(\"devportal.items\", \"Items\")}\n />\n <div className=\"h-4 w-px bg-border-seam\" />\n <StatPill\n icon={<Clock size={14} />}\n value={pendingCount}\n label={tr(\"devportal.pendingReview\", \"Pending Review\")}\n />\n <div className=\"h-4 w-px bg-border-seam\" />\n <StatPill\n icon={<CheckCircle2 size={14} />}\n value={publishedCount}\n label={tr(\"devportal.published\", \"Published\")}\n />\n <div className=\"h-4 w-px bg-border-seam\" />\n <StatPill\n icon={<Building2 size={14} />}\n value={orgs.length}\n label={tr(\"devportal.organizations\", \"Organizations\")}\n />\n </>\n )}\n </div>\n\n {/* Recent Listings */}\n <DashSection\n title={tr(\"devportal.recentListings\", \"Recent Listings\")}\n action={\n products.length > 0 ? (\n <button\n onClick={() => handleNavigate(\"/seller/items\")}\n className=\"inline-flex items-center gap-1 text-sm font-medium text-primary hover:underline\"\n >\n {tr(\"devportal.viewAll\", \"View all\")}\n <ArrowRight size={14} />\n </button>\n ) : undefined\n }\n >\n {isLoading || productsLoading ? (\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 className=\"h-6 w-6 animate-spin text-text-secondary\" />\n </div>\n ) : products.length > 0 ? (\n <div className=\"grid gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3\">\n {products.slice(0, 3).map((product) => (\n <ProductCard\n key={product.id}\n product={product}\n onClick={() => handleNavigate(\"/seller/items\")}\n />\n ))}\n </div>\n ) : (\n <EmptyState\n illustration=\"empty-data\"\n title={tr(\n \"devportal.listYourFirstItem\",\n \"List your first item\",\n )}\n description={tr(\n \"devportal.addPhysicalProductsToYourStore\",\n \"Add physical products to your store and they will appear here. Your seller organization is set up automatically the first time you list an item.\",\n )}\n action={\n <button\n onClick={() => handleNavigate(\"/seller/items/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.listAnItem\", \"List an Item\")}\n </button>\n }\n />\n )}\n </DashSection>\n\n {/* Organizations */}\n {(isLoading || orgs.length > 0) && (\n <DashSection\n title={tr(\"devportal.yourOrganizations\", \"Your Organizations\")}\n action={\n orgs.length > 0 ? (\n <button\n onClick={() => handleNavigate(\"/organizations\")}\n className=\"inline-flex items-center gap-1 text-sm font-medium text-primary hover:underline\"\n >\n {tr(\"devportal.viewAll\", \"View all\")}\n <ArrowRight size={14} />\n </button>\n ) : undefined\n }\n >\n {isLoading ? (\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 className=\"h-6 w-6 animate-spin text-text-secondary\" />\n </div>\n ) : (\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {orgs.slice(0, 3).map((org) => (\n <OrgCard\n key={org.id}\n org={org}\n onClick={() => handleNavigate(`/organizations/${org.id}`)}\n />\n ))}\n </div>\n )}\n </DashSection>\n )}\n </div>\n </PageLayout>\n </PermissionGuard>\n );\n};\n\n// ---------------------------------------------------------------------------\n// Sub-components\n// ---------------------------------------------------------------------------\n\nconst StatPill: FC<{ icon: ReactNode; value: number; label: string }> = ({\n icon,\n value,\n label,\n}) => (\n <div className=\"flex items-center gap-1.5\">\n <span className=\"text-text-muted\">{icon}</span>\n <span className=\"text-sm font-semibold tabular-nums\">{value}</span>\n <span className=\"text-sm text-text-secondary\">{label}</span>\n </div>\n);\n\nconst DashSection: FC<{\n title: string;\n action?: ReactNode;\n children: ReactNode;\n}> = ({ title, action, children }) => (\n <section className=\"flex flex-col gap-4\">\n <div className=\"flex items-center justify-between\">\n <h2 className=\"text-sm font-semibold uppercase tracking-wider text-text-secondary\">\n {title}\n </h2>\n {action}\n </div>\n {children}\n </section>\n);\n\nconst ProductCard: FC<{\n product: PhysicalProduct;\n onClick: () => void;\n}> = ({ product, onClick }) => {\n const badge =\n STATUS_BADGE_STYLES[product.status] ?? STATUS_BADGE_STYLES.DRAFT;\n\n return (\n <GlassCard\n treatment=\"insight\"\n role=\"button\"\n tabIndex={0}\n className=\"cursor-pointer p-5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\"\n onClick={onClick}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onClick();\n }\n }}\n >\n <div className=\"flex items-center gap-3 mb-4\">\n {product.icon ? (\n <img\n src={product.icon}\n alt={product.name}\n className=\"h-10 w-10 rounded-xl object-cover shrink-0\"\n />\n ) : (\n <div className=\"flex h-10 w-10 items-center justify-center rounded-xl bg-action-primary-bg/15 text-primary shrink-0\">\n <Package size={20} />\n </div>\n )}\n <div className=\"min-w-0 flex-1\">\n <p className=\"font-semibold truncate text-text-primary\">\n {product.name}\n </p>\n <p className=\"text-xs text-text-secondary mt-0.5\">\n {formatPrice(product.price, product.currency)}\n </p>\n </div>\n <span\n className={`shrink-0 rounded-full px-2.5 py-0.5 text-xs font-medium ${badge.className}`}\n >\n {badge.label}\n </span>\n </div>\n <div className=\"flex items-center gap-2 text-sm\">\n <Package size={12} className=\"text-text-muted shrink-0\" />\n <span\n className={\n product.stockQuantity === 0\n ? \"text-status-error-text\"\n : \"text-text-secondary\"\n }\n >\n {product.stockQuantity === 0\n ? \"Out of stock\"\n : `${product.stockQuantity} in stock`}\n </span>\n </div>\n </GlassCard>\n );\n};\n\nconst OrgCard: FC<{ org: OrgSummary; onClick: () => void }> = ({\n org,\n onClick,\n}) => {\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\n const initials = org.name\n .split(\" \")\n .map((w: string) => w[0])\n .join(\"\")\n .slice(0, 2)\n .toUpperCase();\n\n return (\n <GlassCard\n treatment=\"insight\"\n role=\"button\"\n tabIndex={0}\n className=\"cursor-pointer p-5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\"\n onClick={onClick}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onClick();\n }\n }}\n >\n <div className=\"flex items-center gap-3 mb-4\">\n {org.logoUrl ? (\n <img\n src={org.logoUrl}\n alt={org.name}\n className=\"h-10 w-10 rounded-xl object-cover\"\n />\n ) : (\n <div className=\"flex h-10 w-10 items-center justify-center rounded-xl bg-action-primary-bg/15 text-primary text-sm font-bold\">\n {initials}\n </div>\n )}\n <div className=\"min-w-0 flex-1\">\n <p className=\"font-semibold truncate text-text-primary\">{org.name}</p>\n <p className=\"text-xs text-text-secondary mt-0.5\">\n {org.publishedApps ?? 0}{\" \"}\n {tr(\"devportal.appsPublished\", \"apps published\")}\n </p>\n </div>\n <ArrowRight size={15} className=\"text-text-muted shrink-0\" />\n </div>\n\n <div className=\"grid grid-cols-2 gap-3\">\n <div className=\"flex items-center gap-2 rounded-lg bg-bg-muted px-3 py-2\">\n <Download size={12} className=\"text-text-muted shrink-0\" />\n <div>\n <p className=\"text-[11px] text-text-secondary leading-none mb-0.5\">\n {tr(\"devportal.installs\", \"Installs\")}\n </p>\n <p className=\"text-sm font-semibold tabular-nums\">\n {(org.totalInstalls ?? 0).toLocaleString()}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-2 rounded-lg bg-bg-muted px-3 py-2\">\n <DollarSign size={12} className=\"text-text-muted shrink-0\" />\n <div>\n <p className=\"text-[11px] text-text-secondary leading-none mb-0.5\">\n {tr(\"devportal.revenue\", \"Revenue\")}\n </p>\n <p className=\"text-sm font-semibold tabular-nums\">\n ${(org.totalRevenue ?? 0).toLocaleString()}\n </p>\n </div>\n </div>\n </div>\n </GlassCard>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"SellerDashboardPage.js","names":[],"sources":["../../../src/devportal/pages/SellerDashboardPage.tsx"],"sourcesContent":["import type { FC, ReactNode } from \"react\";\nimport { useEffect, useState } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n Package,\n Building2,\n Plus,\n ArrowRight,\n Loader2,\n DollarSign,\n Clock,\n CheckCircle2,\n} from \"lucide-react\";\nimport { GlassCard, GradientText } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../components/PageLayout\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\nimport { formatCurrency } from \"../utils/currency\";\n\n// ---------------------------------------------------------------------------\n// Local types\n// ---------------------------------------------------------------------------\n\ntype ProductStatus =\n \"DRAFT\" | \"PENDING_REVIEW\" | \"PUBLISHED\" | \"REJECTED\" | \"INACTIVE\";\n\ninterface PhysicalProduct {\n id: string;\n name: string;\n slug: string;\n type: string;\n status: ProductStatus;\n price: number;\n currency: string;\n stockQuantity: number;\n icon?: string | null;\n}\n\ninterface OrgSummary {\n id: string;\n name: string;\n slug: string;\n logoUrl?: string | null;\n isVerified: boolean;\n publishedApps?: number | null;\n totalInstalls?: number | null;\n totalRevenue?: number | null;\n kind: \"DIGITAL\" | \"PHYSICAL\";\n}\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst STATUS_BADGE_STYLES: Record<\n ProductStatus,\n { label: string; className: string }\n> = {\n PUBLISHED: {\n label: \"Published\",\n className: \"text-status-success-text bg-status-success-bg-subtle\",\n },\n PENDING_REVIEW: {\n label: \"Pending Review\",\n className: \"text-status-warning-text bg-status-warning-bg-subtle\",\n },\n DRAFT: {\n label: \"Draft\",\n className: \"text-text-muted bg-bg-muted\",\n },\n REJECTED: {\n label: \"Rejected\",\n className: \"text-status-error-text bg-status-error-bg/10\",\n },\n INACTIVE: {\n label: \"Inactive\",\n className: \"text-text-muted bg-bg-muted\",\n },\n};\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction formatPrice(price: number, currency: string): string {\n const curr = currency || \"USD\";\n const formatted = new Intl.NumberFormat(\"en-US\", {\n style: \"currency\",\n currency: curr,\n minimumFractionDigits: 2,\n maximumFractionDigits: 2,\n }).format(price);\n return `${formatted} ${curr}`;\n}\n\n// ---------------------------------------------------------------------------\n// GraphQL query strings\n// ---------------------------------------------------------------------------\n\nconst BOOTSTRAP_QUERY = `\n query SellerDashboardBootstrap {\n myDeveloperOrganizations {\n id\n name\n slug\n logoUrl\n isVerified\n publishedApps\n totalInstalls\n totalRevenue\n kind\n }\n myPublisher {\n id\n }\n }\n`;\n\n/**\n * org.totalRevenue has no currency field of its own on the backend model —\n * one aliased developerOrgRevenueSummary lookup per visible org card (at\n * most 3 here) gets the org's real dominant currency to label it with,\n * batched into a single request the same way buildIssueFlagsBatchQuery does\n * in SellerOrdersPage.tsx.\n */\nfunction buildOrgCurrencyBatchQuery(orgIds: string[]): string {\n const fields = orgIds\n .map(\n (id, i) =>\n `o${i}: developerOrgRevenueSummary(organizationId: ${JSON.stringify(id)}, period: \"monthly\") { currency }`,\n )\n .join(\"\\n \");\n return `query SellerDashboardOrgCurrencies {\\n ${fields}\\n }`;\n}\n\nconst SELLER_PRODUCTS_QUERY = `\n query SellerDashboardProducts($publisherId: String!) {\n products(filter: { publisherId: $publisherId, nature: PHYSICAL }, limit: 3, offset: 0) {\n id\n name\n slug\n type\n status\n price\n currency\n stockQuantity\n icon\n }\n }\n`;\n\n// ---------------------------------------------------------------------------\n// Page component\n// ---------------------------------------------------------------------------\n\nexport const SellerDashboardPage: FC = () => {\n const { navigate, basePath, apiGatewayUrl, authToken } = 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\n const gqlUrl = apiGatewayUrl ?? \"/global/graphql\";\n\n const [bootstrapLoading, setBootstrapLoading] = useState(true);\n const [productsLoading, setProductsLoading] = useState(false);\n const [orgs, setOrgs] = useState<OrgSummary[]>([]);\n const [publisherId, setPublisherId] = useState<string | null>(null);\n const [products, setProducts] = useState<PhysicalProduct[]>([]);\n const [orgCurrencies, setOrgCurrencies] = useState<Record<string, string>>(\n {},\n );\n\n // Fetch orgs + publisher in one round-trip\n useEffect(() => {\n let cancelled = false;\n setBootstrapLoading(true);\n\n const run = async () => {\n try {\n const res = await fetch(gqlUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${authToken ?? \"\"}`,\n },\n body: JSON.stringify({ query: BOOTSTRAP_QUERY }),\n });\n if (cancelled) return;\n const json = (await res.json()) as {\n data?: {\n myDeveloperOrganizations?: OrgSummary[];\n myPublisher?: { id: string } | null;\n };\n };\n if (cancelled) return;\n // Seller portal only ever shows PHYSICAL-kind orgs — a DIGITAL org\n // (regular app-publishing) has no business here. See DeveloperOrgKind\n // (BOFF-7285).\n setOrgs(\n (json.data?.myDeveloperOrganizations ?? []).filter(\n (org) => org.kind === \"PHYSICAL\",\n ),\n );\n setPublisherId(json.data?.myPublisher?.id ?? null);\n } finally {\n if (!cancelled) setBootstrapLoading(false);\n }\n };\n\n void run();\n return () => {\n cancelled = true;\n };\n }, [gqlUrl, authToken]);\n\n // Fetch recent physical products once publisher is known\n useEffect(() => {\n if (!publisherId) return;\n let cancelled = false;\n setProductsLoading(true);\n\n const run = async () => {\n try {\n const res = await fetch(gqlUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${authToken ?? \"\"}`,\n },\n body: JSON.stringify({\n query: SELLER_PRODUCTS_QUERY,\n variables: { publisherId },\n }),\n });\n if (cancelled) return;\n const json = (await res.json()) as {\n data?: {\n products?: Array<\n Omit<PhysicalProduct, \"status\"> & { status: string }\n >;\n };\n };\n if (cancelled) return;\n const DIGITAL_TYPES = new Set([\"APP\", \"NODE\", \"WORKFLOW\"]);\n setProducts(\n (json.data?.products ?? [])\n .filter((p) => !DIGITAL_TYPES.has(p.type))\n .map((p) => ({\n ...p,\n status: p.status as ProductStatus,\n })),\n );\n } finally {\n if (!cancelled) setProductsLoading(false);\n }\n };\n\n void run();\n return () => {\n cancelled = true;\n };\n }, [publisherId, gqlUrl, authToken]);\n\n // Fetch each visible org card's real currency once orgs are known\n useEffect(() => {\n const visibleOrgIds = orgs.slice(0, 3).map((org) => org.id);\n if (visibleOrgIds.length === 0) return;\n let cancelled = false;\n\n const run = async () => {\n try {\n const res = await fetch(gqlUrl, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${authToken ?? \"\"}`,\n },\n body: JSON.stringify({\n query: buildOrgCurrencyBatchQuery(visibleOrgIds),\n }),\n });\n if (cancelled) return;\n const json = (await res.json()) as {\n data?: Record<string, { currency: string } | null>;\n };\n if (cancelled || !json.data) return;\n const next: Record<string, string> = {};\n visibleOrgIds.forEach((id, i) => {\n const currency = json.data?.[`o${i}`]?.currency;\n if (currency) next[id] = currency;\n });\n setOrgCurrencies(next);\n } catch {\n // Non-critical — cards fall back to USD if this fails.\n }\n };\n\n void run();\n return () => {\n cancelled = true;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- refetch keyed on org id membership, not array identity\n }, [orgs.map((o) => o.id).join(\",\"), gqlUrl, authToken]);\n\n const isLoading = bootstrapLoading;\n const statsLoading = bootstrapLoading || productsLoading;\n\n const totalItems = products.length;\n const pendingCount = products.filter(\n (p) => p.status === \"PENDING_REVIEW\",\n ).length;\n const publishedCount = products.filter(\n (p) => p.status === \"PUBLISHED\",\n ).length;\n\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n return (\n <PermissionGuard authOnly>\n <PageLayout\n title={\n <>\n {tr(\"devportal.seller\", \"Seller\")}{\" \"}\n <GradientText>\n {tr(\"devportal.dashboard\", \"Dashboard\")}\n </GradientText>\n </>\n }\n actions={\n // Whether or not a PHYSICAL org exists yet, this always goes to the\n // same place — CreatePhysicalItemPage auto-provisions one via\n // createSellerOrganization on first listing (see\n // useSellerOrganization, BOFF-7285). Hidden only while loading.\n !isLoading ? (\n <button\n onClick={() => handleNavigate(\"/seller/items/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.listAnItem\", \"List an Item\")}\n </button>\n ) : null\n }\n >\n <div className=\"flex flex-col gap-8\">\n {/* Compact inline stats */}\n <div className=\"flex items-center gap-6 border-b border-border-seam pb-6\">\n {statsLoading ? (\n <Loader2 className=\"h-4 w-4 animate-spin text-text-secondary\" />\n ) : (\n <>\n <StatPill\n icon={<Package size={14} />}\n value={totalItems}\n label={tr(\"devportal.items\", \"Items\")}\n />\n <div className=\"h-4 w-px bg-border-seam\" />\n <StatPill\n icon={<Clock size={14} />}\n value={pendingCount}\n label={tr(\"devportal.pendingReview\", \"Pending Review\")}\n />\n <div className=\"h-4 w-px bg-border-seam\" />\n <StatPill\n icon={<CheckCircle2 size={14} />}\n value={publishedCount}\n label={tr(\"devportal.published\", \"Published\")}\n />\n <div className=\"h-4 w-px bg-border-seam\" />\n <StatPill\n icon={<Building2 size={14} />}\n value={orgs.length}\n label={tr(\"devportal.organizations\", \"Organizations\")}\n />\n </>\n )}\n </div>\n\n {/* Recent Listings */}\n <DashSection\n title={tr(\"devportal.recentListings\", \"Recent Listings\")}\n action={\n products.length > 0 ? (\n <button\n onClick={() => handleNavigate(\"/seller/items\")}\n className=\"inline-flex items-center gap-1 text-sm font-medium text-primary hover:underline\"\n >\n {tr(\"devportal.viewAll\", \"View all\")}\n <ArrowRight size={14} />\n </button>\n ) : undefined\n }\n >\n {isLoading || productsLoading ? (\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 className=\"h-6 w-6 animate-spin text-text-secondary\" />\n </div>\n ) : products.length > 0 ? (\n <div className=\"grid gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3\">\n {products.slice(0, 3).map((product) => (\n <ProductCard\n key={product.id}\n product={product}\n onClick={() => handleNavigate(\"/seller/items\")}\n />\n ))}\n </div>\n ) : (\n <EmptyState\n illustration=\"empty-data\"\n title={tr(\n \"devportal.listYourFirstItem\",\n \"List your first item\",\n )}\n description={tr(\n \"devportal.addPhysicalProductsToYourStore\",\n \"Add physical products to your store and they will appear here. Your seller organization is set up automatically the first time you list an item.\",\n )}\n action={\n <button\n onClick={() => handleNavigate(\"/seller/items/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.listAnItem\", \"List an Item\")}\n </button>\n }\n />\n )}\n </DashSection>\n\n {/* Organizations */}\n {(isLoading || orgs.length > 0) && (\n <DashSection\n title={tr(\"devportal.yourOrganizations\", \"Your Organizations\")}\n action={\n orgs.length > 0 ? (\n <button\n onClick={() => handleNavigate(\"/seller/organizations\")}\n className=\"inline-flex items-center gap-1 text-sm font-medium text-primary hover:underline\"\n >\n {tr(\"devportal.viewAll\", \"View all\")}\n <ArrowRight size={14} />\n </button>\n ) : undefined\n }\n >\n {isLoading ? (\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 className=\"h-6 w-6 animate-spin text-text-secondary\" />\n </div>\n ) : (\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {orgs.slice(0, 3).map((org) => (\n <OrgCard\n key={org.id}\n org={org}\n currency={orgCurrencies[org.id] ?? \"USD\"}\n onClick={() =>\n handleNavigate(`/seller/organizations/${org.id}`)\n }\n />\n ))}\n </div>\n )}\n </DashSection>\n )}\n </div>\n </PageLayout>\n </PermissionGuard>\n );\n};\n\n// ---------------------------------------------------------------------------\n// Sub-components\n// ---------------------------------------------------------------------------\n\nconst StatPill: FC<{ icon: ReactNode; value: number; label: string }> = ({\n icon,\n value,\n label,\n}) => (\n <div className=\"flex items-center gap-1.5\">\n <span className=\"text-text-muted\">{icon}</span>\n <span className=\"text-sm font-semibold tabular-nums\">{value}</span>\n <span className=\"text-sm text-text-secondary\">{label}</span>\n </div>\n);\n\nconst DashSection: FC<{\n title: string;\n action?: ReactNode;\n children: ReactNode;\n}> = ({ title, action, children }) => (\n <section className=\"flex flex-col gap-4\">\n <div className=\"flex items-center justify-between\">\n <h2 className=\"text-sm font-semibold uppercase tracking-wider text-text-secondary\">\n {title}\n </h2>\n {action}\n </div>\n {children}\n </section>\n);\n\nconst ProductCard: FC<{\n product: PhysicalProduct;\n onClick: () => void;\n}> = ({ product, onClick }) => {\n const badge =\n STATUS_BADGE_STYLES[product.status] ?? STATUS_BADGE_STYLES.DRAFT;\n\n return (\n <GlassCard\n treatment=\"insight\"\n role=\"button\"\n tabIndex={0}\n className=\"cursor-pointer p-5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\"\n onClick={onClick}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onClick();\n }\n }}\n >\n <div className=\"flex items-center gap-3 mb-4\">\n {product.icon ? (\n <img\n src={product.icon}\n alt={product.name}\n className=\"h-10 w-10 rounded-xl object-cover shrink-0\"\n />\n ) : (\n <div className=\"flex h-10 w-10 items-center justify-center rounded-xl bg-action-primary-bg/15 text-primary shrink-0\">\n <Package size={20} />\n </div>\n )}\n <div className=\"min-w-0 flex-1\">\n <p className=\"font-semibold truncate text-text-primary\">\n {product.name}\n </p>\n <p className=\"text-xs text-text-secondary mt-0.5\">\n {formatPrice(product.price, product.currency)}\n </p>\n </div>\n <span\n className={`shrink-0 rounded-full px-2.5 py-0.5 text-xs font-medium ${badge.className}`}\n >\n {badge.label}\n </span>\n </div>\n <div className=\"flex items-center gap-2 text-sm\">\n <Package size={12} className=\"text-text-muted shrink-0\" />\n <span\n className={\n product.stockQuantity === 0\n ? \"text-status-error-text\"\n : \"text-text-secondary\"\n }\n >\n {product.stockQuantity === 0\n ? \"Out of stock\"\n : `${product.stockQuantity} in stock`}\n </span>\n </div>\n </GlassCard>\n );\n};\n\nconst OrgCard: FC<{\n org: OrgSummary;\n currency: string;\n onClick: () => void;\n}> = ({ org, currency, onClick }) => {\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\n const initials = org.name\n .split(\" \")\n .map((w: string) => w[0])\n .join(\"\")\n .slice(0, 2)\n .toUpperCase();\n\n return (\n <GlassCard\n treatment=\"insight\"\n role=\"button\"\n tabIndex={0}\n className=\"cursor-pointer p-5 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\"\n onClick={onClick}\n onKeyDown={(e) => {\n if (e.key === \"Enter\" || e.key === \" \") {\n e.preventDefault();\n onClick();\n }\n }}\n >\n <div className=\"flex items-center gap-3 mb-4\">\n {org.logoUrl ? (\n <img\n src={org.logoUrl}\n alt={org.name}\n className=\"h-10 w-10 rounded-xl object-cover\"\n />\n ) : (\n <div className=\"flex h-10 w-10 items-center justify-center rounded-xl bg-action-primary-bg/15 text-primary text-sm font-bold\">\n {initials}\n </div>\n )}\n <div className=\"min-w-0 flex-1\">\n <p className=\"font-semibold truncate text-text-primary\">{org.name}</p>\n <p className=\"text-xs text-text-secondary mt-0.5\">\n {org.publishedApps ?? 0}{\" \"}\n {tr(\"devportal.appsPublished\", \"apps published\")}\n </p>\n </div>\n <ArrowRight size={15} className=\"text-text-muted shrink-0\" />\n </div>\n\n {/* Every org on this page is PHYSICAL (seller-only dashboard) —\n \"Installs\" is an app-marketplace concept that doesn't apply, so\n Revenue gets the full-width cell instead of sharing a 2-col grid. */}\n <div className=\"grid grid-cols-1 gap-3\">\n <div className=\"flex items-center gap-2 rounded-lg bg-bg-muted px-3 py-2\">\n <DollarSign size={12} className=\"text-text-muted shrink-0\" />\n <div>\n <p className=\"text-[11px] text-text-secondary leading-none mb-0.5\">\n {tr(\"devportal.revenue\", \"Revenue\")}\n </p>\n <p className=\"text-sm font-semibold tabular-nums\">\n {formatCurrency(org.totalRevenue ?? 0, currency)}\n </p>\n </div>\n </div>\n </div>\n </GlassCard>\n );\n};\n"],"mappings":"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"}
@@ -3250,6 +3250,23 @@ export type MyBillingAccountsQuery = {
3250
3250
  updatedAt?: string | null;
3251
3251
  }>;
3252
3252
  };
3253
+ export type GetOrgPhysicalItemsQueryVariables = Types.Exact<{
3254
+ publisherId: Types.Scalars['String']['input'];
3255
+ limit?: Types.InputMaybe<Types.Scalars['Int']['input']>;
3256
+ }>;
3257
+ export type GetOrgPhysicalItemsQuery = {
3258
+ __typename?: 'Query';
3259
+ products: Array<{
3260
+ __typename?: 'StoreProduct';
3261
+ id: string;
3262
+ name: string;
3263
+ slug: string;
3264
+ status: Types.ProductStatus;
3265
+ price: number;
3266
+ currency: string;
3267
+ stockQuantity?: number | null;
3268
+ }>;
3269
+ };
3253
3270
  export type DeveloperProfileFieldsFragment = {
3254
3271
  __typename?: 'DeveloperProfile';
3255
3272
  id: string;
@@ -7049,6 +7066,37 @@ export type MyBillingAccountsQueryHookResult = ReturnType<typeof useMyBillingAcc
7049
7066
  export type MyBillingAccountsLazyQueryHookResult = ReturnType<typeof useMyBillingAccountsLazyQuery>;
7050
7067
  export type MyBillingAccountsSuspenseQueryHookResult = ReturnType<typeof useMyBillingAccountsSuspenseQuery>;
7051
7068
  export type MyBillingAccountsQueryResult = Apollo.QueryResult<Types.MyBillingAccountsQuery, Types.MyBillingAccountsQueryVariables>;
7069
+ export declare const GetOrgPhysicalItemsDocument: Apollo.DocumentNode;
7070
+ /**
7071
+ * __useGetOrgPhysicalItemsQuery__
7072
+ *
7073
+ * To run a query within a React component, call `useGetOrgPhysicalItemsQuery` and pass it any options that fit your needs.
7074
+ * When your component renders, `useGetOrgPhysicalItemsQuery` returns an object from Apollo Client that contains loading, error, and data properties
7075
+ * you can use to render your UI.
7076
+ *
7077
+ * @param baseOptions options that will be passed into the query, supported options are listed on: https://www.apollographql.com/docs/react/api/react-hooks/#options;
7078
+ *
7079
+ * @example
7080
+ * const { data, loading, error } = useGetOrgPhysicalItemsQuery({
7081
+ * variables: {
7082
+ * publisherId: // value for 'publisherId'
7083
+ * limit: // value for 'limit'
7084
+ * },
7085
+ * });
7086
+ */
7087
+ export declare function useGetOrgPhysicalItemsQuery(baseOptions: ApolloReactHooks.QueryHookOptions<Types.GetOrgPhysicalItemsQuery, Types.GetOrgPhysicalItemsQueryVariables> & ({
7088
+ variables: Types.GetOrgPhysicalItemsQueryVariables;
7089
+ skip?: boolean;
7090
+ } | {
7091
+ skip: boolean;
7092
+ })): ApolloReactHooks.useQuery.Result<Types.GetOrgPhysicalItemsQuery, Types.GetOrgPhysicalItemsQueryVariables, "empty" | "complete" | "streaming", Types.GetOrgPhysicalItemsQueryVariables>;
7093
+ export declare function useGetOrgPhysicalItemsLazyQuery(baseOptions?: ApolloReactHooks.LazyQueryHookOptions<Types.GetOrgPhysicalItemsQuery, Types.GetOrgPhysicalItemsQueryVariables>): ApolloReactHooks.useLazyQuery.ResultTuple<Types.GetOrgPhysicalItemsQuery, Types.GetOrgPhysicalItemsQueryVariables, "empty" | "complete" | "streaming">;
7094
+ export declare function useGetOrgPhysicalItemsSuspenseQuery(baseOptions?: ApolloReactHooks.SuspenseQueryHookOptions<Types.GetOrgPhysicalItemsQuery, Types.GetOrgPhysicalItemsQueryVariables>): ApolloReactHooks.UseSuspenseQueryResult<Types.GetOrgPhysicalItemsQuery, Types.GetOrgPhysicalItemsQueryVariables>;
7095
+ export declare function useGetOrgPhysicalItemsSuspenseQuery(baseOptions?: ApolloReactHooks.SkipToken | ApolloReactHooks.SuspenseQueryHookOptions<Types.GetOrgPhysicalItemsQuery, Types.GetOrgPhysicalItemsQueryVariables>): ApolloReactHooks.UseSuspenseQueryResult<Types.GetOrgPhysicalItemsQuery | undefined, Types.GetOrgPhysicalItemsQueryVariables>;
7096
+ export type GetOrgPhysicalItemsQueryHookResult = ReturnType<typeof useGetOrgPhysicalItemsQuery>;
7097
+ export type GetOrgPhysicalItemsLazyQueryHookResult = ReturnType<typeof useGetOrgPhysicalItemsLazyQuery>;
7098
+ export type GetOrgPhysicalItemsSuspenseQueryHookResult = ReturnType<typeof useGetOrgPhysicalItemsSuspenseQuery>;
7099
+ export type GetOrgPhysicalItemsQueryResult = Apollo.QueryResult<Types.GetOrgPhysicalItemsQuery, Types.GetOrgPhysicalItemsQueryVariables>;
7052
7100
  export declare const GetMyDeveloperProfileDocument: Apollo.DocumentNode;
7053
7101
  /**
7054
7102
  * __useGetMyDeveloperProfileQuery__