@burdenoff/microfe-devportal 2026.629.5 → 2026.629.7
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.
|
@@ -15,15 +15,29 @@ import { Fragment as w, jsx as T, jsxs as E } from "react/jsx-runtime";
|
|
|
15
15
|
import { EmphasisPanel as D, GradientText as O, NextSteps as k } from "@burdenoff/fe-libs/ui";
|
|
16
16
|
//#region src/devportal/pages/DashboardPage.tsx
|
|
17
17
|
var A = () => {
|
|
18
|
-
let { navigate: A, basePath: N, activeOrgId: P } = e(), { t:
|
|
19
|
-
let n =
|
|
18
|
+
let { navigate: A, basePath: N, activeOrgId: P, setActiveOrgId: F } = e(), { t: I } = d(), L = (e, t) => {
|
|
19
|
+
let n = I(e);
|
|
20
20
|
return n === e ? t : n;
|
|
21
|
-
}, { data:
|
|
22
|
-
variables: {
|
|
21
|
+
}, { data: R, loading: z } = n({
|
|
22
|
+
variables: {},
|
|
23
23
|
fetchPolicy: "cache-and-network"
|
|
24
|
-
}),
|
|
24
|
+
}), B = R?.myDeveloperOrganizations ?? [], V = (() => {
|
|
25
|
+
if (B.length) return P && B.some((e) => e.id === P) ? P : B[0]?.id;
|
|
26
|
+
})();
|
|
25
27
|
u(() => {
|
|
26
|
-
|
|
28
|
+
V && V !== P && F(V);
|
|
29
|
+
}, [
|
|
30
|
+
V,
|
|
31
|
+
P,
|
|
32
|
+
F
|
|
33
|
+
]);
|
|
34
|
+
let { data: H, loading: U } = n({
|
|
35
|
+
variables: { organizationId: V },
|
|
36
|
+
skip: !V,
|
|
37
|
+
fetchPolicy: "cache-and-network"
|
|
38
|
+
}), W = z || U, G = H ?? R;
|
|
39
|
+
u(() => {
|
|
40
|
+
if (!(z || !R) && !(B.length > 0)) {
|
|
27
41
|
try {
|
|
28
42
|
if (localStorage.getItem("devportal_setup_dismissed")) return;
|
|
29
43
|
} catch {
|
|
@@ -32,45 +46,45 @@ var A = () => {
|
|
|
32
46
|
A?.(`${N}/setup`);
|
|
33
47
|
}
|
|
34
48
|
}, [
|
|
49
|
+
z,
|
|
35
50
|
R,
|
|
36
|
-
|
|
37
|
-
z.length,
|
|
51
|
+
B.length,
|
|
38
52
|
A,
|
|
39
53
|
N
|
|
40
54
|
]);
|
|
41
|
-
let
|
|
42
|
-
totalApps:
|
|
43
|
-
totalOrgs:
|
|
55
|
+
let K = H?.applications?.edges?.map((e) => e.node) ?? [], q = {
|
|
56
|
+
totalApps: G?.applications?.totalCount ?? 0,
|
|
57
|
+
totalOrgs: B.length,
|
|
44
58
|
totalApiKeys: 0,
|
|
45
59
|
totalWebhooks: 0,
|
|
46
|
-
totalApiCalls:
|
|
47
|
-
activeInstalls:
|
|
48
|
-
monthlyRevenue:
|
|
60
|
+
totalApiCalls: B.reduce((e, t) => e + parseInt(t.totalApiCalls || "0"), 0),
|
|
61
|
+
activeInstalls: B.reduce((e, t) => e + (t.activeInstalls || 0), 0),
|
|
62
|
+
monthlyRevenue: B.reduce((e, t) => e + (t.totalRevenue || 0), 0),
|
|
49
63
|
errorRate: 0
|
|
50
|
-
},
|
|
64
|
+
}, J = (e) => {
|
|
51
65
|
A?.(`${N}${e}`);
|
|
52
|
-
},
|
|
66
|
+
}, Y = W, X = !Y && B.length > 0;
|
|
53
67
|
return /* @__PURE__ */ T(c, {
|
|
54
68
|
permission: l.APP_READ,
|
|
55
69
|
children: /* @__PURE__ */ E(r, {
|
|
56
70
|
heroBackdrop: !0,
|
|
57
71
|
title: /* @__PURE__ */ E(w, { children: [
|
|
58
|
-
|
|
72
|
+
L("devportal.developer", "Developer"),
|
|
59
73
|
" ",
|
|
60
|
-
/* @__PURE__ */ T(O, { children:
|
|
74
|
+
/* @__PURE__ */ T(O, { children: L("devportal.portal", "Portal") })
|
|
61
75
|
] }),
|
|
62
|
-
description:
|
|
63
|
-
purpose:
|
|
64
|
-
actions:
|
|
76
|
+
description: L("devportal.buildManageMonitor", "Build, manage, and monitor your applications"),
|
|
77
|
+
purpose: L("devportal.dashboardPurpose", "This is your developer home base — a single view of the apps you ship, the organizations behind them, and how they are performing (installs, API usage, revenue). Start an organization, then create and publish apps from here."),
|
|
78
|
+
actions: X ? /* @__PURE__ */ E("button", {
|
|
65
79
|
"data-tour": "devportal-dashboard-create-app-button",
|
|
66
|
-
onClick: () =>
|
|
80
|
+
onClick: () => J("/apps/new"),
|
|
67
81
|
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",
|
|
68
|
-
children: [/* @__PURE__ */ T(y, { size: 16 }),
|
|
69
|
-
}) :
|
|
82
|
+
children: [/* @__PURE__ */ T(y, { size: 16 }), L("devportal.createApp", "Create App")]
|
|
83
|
+
}) : Y ? null : /* @__PURE__ */ E("button", {
|
|
70
84
|
"data-tour": "devportal-dashboard-create-org-button",
|
|
71
|
-
onClick: () =>
|
|
85
|
+
onClick: () => J("/organizations/new"),
|
|
72
86
|
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",
|
|
73
|
-
children: [/* @__PURE__ */ T(y, { size: 16 }),
|
|
87
|
+
children: [/* @__PURE__ */ T(y, { size: 16 }), L("devportal.createOrganization", "Create Organization")]
|
|
74
88
|
}),
|
|
75
89
|
children: [
|
|
76
90
|
/* @__PURE__ */ T(D, {
|
|
@@ -80,93 +94,93 @@ var A = () => {
|
|
|
80
94
|
children: [
|
|
81
95
|
/* @__PURE__ */ T(a, {
|
|
82
96
|
featured: !0,
|
|
83
|
-
title:
|
|
84
|
-
value:
|
|
97
|
+
title: L("devportal.totalApps", "Total Apps"),
|
|
98
|
+
value: Y ? /* @__PURE__ */ T(v, { className: "h-5 w-5 animate-spin" }) : q.totalApps,
|
|
85
99
|
icon: /* @__PURE__ */ T(p, { size: 20 }),
|
|
86
100
|
trend: void 0
|
|
87
101
|
}),
|
|
88
102
|
/* @__PURE__ */ T(a, {
|
|
89
|
-
title:
|
|
90
|
-
value:
|
|
103
|
+
title: L("devportal.organizations", "Organizations"),
|
|
104
|
+
value: Y ? /* @__PURE__ */ T(v, { className: "h-5 w-5 animate-spin" }) : q.totalOrgs,
|
|
91
105
|
icon: /* @__PURE__ */ T(h, { size: 20 })
|
|
92
106
|
}),
|
|
93
107
|
/* @__PURE__ */ T(a, {
|
|
94
|
-
title:
|
|
95
|
-
value:
|
|
108
|
+
title: L("devportal.apiKeys", "API Keys"),
|
|
109
|
+
value: q.totalApiKeys,
|
|
96
110
|
icon: /* @__PURE__ */ T(_, { size: 20 }),
|
|
97
|
-
description:
|
|
111
|
+
description: L("devportal.activeKeys", "Active keys")
|
|
98
112
|
}),
|
|
99
113
|
/* @__PURE__ */ T(a, {
|
|
100
|
-
title:
|
|
101
|
-
value:
|
|
114
|
+
title: L("devportal.webhooks", "Webhooks"),
|
|
115
|
+
value: q.totalWebhooks,
|
|
102
116
|
icon: /* @__PURE__ */ T(C, { size: 20 }),
|
|
103
|
-
description:
|
|
117
|
+
description: L("devportal.configured", "Configured")
|
|
104
118
|
})
|
|
105
119
|
]
|
|
106
120
|
})
|
|
107
121
|
}),
|
|
108
|
-
!
|
|
122
|
+
!Y && /* @__PURE__ */ T(k, {
|
|
109
123
|
storageKey: "devportal-dashboard",
|
|
110
124
|
steps: [
|
|
111
125
|
{
|
|
112
126
|
id: "create-org",
|
|
113
|
-
label:
|
|
114
|
-
description:
|
|
127
|
+
label: L("devportal.createOrganization", "Create Organization"),
|
|
128
|
+
description: L("devportal.setUpATeamToCollaborate", "Set up a team to collaborate"),
|
|
115
129
|
icon: /* @__PURE__ */ T(h, { size: 16 }),
|
|
116
|
-
onClick: () =>
|
|
117
|
-
done:
|
|
130
|
+
onClick: () => J("/organizations/new"),
|
|
131
|
+
done: X
|
|
118
132
|
},
|
|
119
133
|
{
|
|
120
134
|
id: "create-app",
|
|
121
|
-
label:
|
|
122
|
-
description:
|
|
135
|
+
label: L("devportal.createApp", "Create App"),
|
|
136
|
+
description: L("devportal.startBuildingANewApplication", "Start building a new application"),
|
|
123
137
|
icon: /* @__PURE__ */ T(p, { size: 16 }),
|
|
124
|
-
onClick: () =>
|
|
125
|
-
done:
|
|
138
|
+
onClick: () => J("/apps/new"),
|
|
139
|
+
done: q.totalApps > 0
|
|
126
140
|
},
|
|
127
141
|
{
|
|
128
142
|
id: "api-key",
|
|
129
|
-
label:
|
|
130
|
-
description:
|
|
143
|
+
label: L("devportal.generateApiKey", "Generate API Key"),
|
|
144
|
+
description: L("devportal.createCredentialsForYourApps", "Create credentials for your apps"),
|
|
131
145
|
icon: /* @__PURE__ */ T(_, { size: 16 }),
|
|
132
|
-
onClick: () =>
|
|
146
|
+
onClick: () => J("/api-keys")
|
|
133
147
|
},
|
|
134
148
|
{
|
|
135
149
|
id: "webhooks",
|
|
136
|
-
label:
|
|
137
|
-
description:
|
|
150
|
+
label: L("devportal.configureWebhooks", "Configure Webhooks"),
|
|
151
|
+
description: L("devportal.setUpEventNotifications", "Set up event notifications"),
|
|
138
152
|
icon: /* @__PURE__ */ T(C, { size: 16 }),
|
|
139
|
-
onClick: () =>
|
|
153
|
+
onClick: () => J("/webhooks")
|
|
140
154
|
}
|
|
141
155
|
]
|
|
142
156
|
}),
|
|
143
157
|
/* @__PURE__ */ T(i, {
|
|
144
|
-
title:
|
|
158
|
+
title: L("devportal.usageOverview", "Usage Overview"),
|
|
145
159
|
"data-tour": "devportal-dashboard-usage-overview",
|
|
146
160
|
children: /* @__PURE__ */ E(o, {
|
|
147
161
|
columns: 4,
|
|
148
162
|
children: [
|
|
149
163
|
/* @__PURE__ */ T(a, {
|
|
150
|
-
title:
|
|
151
|
-
value:
|
|
164
|
+
title: L("devportal.apiCalls", "API Calls"),
|
|
165
|
+
value: Y ? /* @__PURE__ */ T(v, { className: "h-5 w-5 animate-spin" }) : q.totalApiCalls > 1e6 ? `${(q.totalApiCalls / 1e6).toFixed(1)}M` : q.totalApiCalls > 1e3 ? `${(q.totalApiCalls / 1e3).toFixed(1)}K` : q.totalApiCalls.toString(),
|
|
152
166
|
icon: /* @__PURE__ */ T(f, { size: 20 }),
|
|
153
167
|
trend: void 0
|
|
154
168
|
}),
|
|
155
169
|
/* @__PURE__ */ T(a, {
|
|
156
|
-
title:
|
|
157
|
-
value:
|
|
170
|
+
title: L("devportal.activeInstalls", "Active Installs"),
|
|
171
|
+
value: Y ? /* @__PURE__ */ T(v, { className: "h-5 w-5 animate-spin" }) : q.activeInstalls > 1e3 ? `${(q.activeInstalls / 1e3).toFixed(1)}K` : q.activeInstalls.toString(),
|
|
158
172
|
icon: /* @__PURE__ */ T(S, { size: 20 }),
|
|
159
173
|
trend: void 0
|
|
160
174
|
}),
|
|
161
175
|
/* @__PURE__ */ T(a, {
|
|
162
|
-
title:
|
|
163
|
-
value:
|
|
176
|
+
title: L("devportal.monthlyRevenue", "Monthly Revenue"),
|
|
177
|
+
value: Y ? /* @__PURE__ */ T(v, { className: "h-5 w-5 animate-spin" }) : `$${q.monthlyRevenue.toLocaleString()}`,
|
|
164
178
|
icon: /* @__PURE__ */ T(g, { size: 20 }),
|
|
165
179
|
trend: void 0
|
|
166
180
|
}),
|
|
167
181
|
/* @__PURE__ */ T(a, {
|
|
168
|
-
title:
|
|
169
|
-
value: `${
|
|
182
|
+
title: L("devportal.errorRate", "Error Rate"),
|
|
183
|
+
value: `${q.errorRate}%`,
|
|
170
184
|
icon: /* @__PURE__ */ T(x, { size: 20 }),
|
|
171
185
|
trend: void 0
|
|
172
186
|
})
|
|
@@ -177,102 +191,102 @@ var A = () => {
|
|
|
177
191
|
className: "grid gap-6 lg:grid-cols-2",
|
|
178
192
|
children: [/* @__PURE__ */ T(i, {
|
|
179
193
|
"data-tour": "devportal-dashboard-recent-apps",
|
|
180
|
-
title:
|
|
194
|
+
title: L("devportal.recentApplications", "Recent Applications"),
|
|
181
195
|
actions: /* @__PURE__ */ E("button", {
|
|
182
|
-
onClick: () =>
|
|
196
|
+
onClick: () => J("/apps"),
|
|
183
197
|
className: "text-sm text-primary hover:underline flex items-center gap-1",
|
|
184
198
|
children: [
|
|
185
|
-
|
|
199
|
+
L("devportal.viewAll", "View all"),
|
|
186
200
|
" ",
|
|
187
201
|
/* @__PURE__ */ T(m, { size: 14 })
|
|
188
202
|
]
|
|
189
203
|
}),
|
|
190
|
-
children:
|
|
204
|
+
children: Y ? /* @__PURE__ */ T("div", {
|
|
191
205
|
className: "flex items-center justify-center py-8",
|
|
192
206
|
children: /* @__PURE__ */ T(v, { className: "h-6 w-6 animate-spin text-text-secondary" })
|
|
193
|
-
}) :
|
|
207
|
+
}) : K.length > 0 ? /* @__PURE__ */ T("div", {
|
|
194
208
|
className: "space-y-2",
|
|
195
|
-
children:
|
|
209
|
+
children: K.slice(0, 3).map((e) => /* @__PURE__ */ T(s, {
|
|
196
210
|
app: e,
|
|
197
|
-
onClick: () =>
|
|
211
|
+
onClick: () => J(`/apps/${e.id}`)
|
|
198
212
|
}, e.id))
|
|
199
213
|
}) : /* @__PURE__ */ T(t, {
|
|
200
214
|
illustration: "empty-generic",
|
|
201
|
-
title:
|
|
202
|
-
description:
|
|
203
|
-
action:
|
|
204
|
-
onClick: () =>
|
|
215
|
+
title: L("devportal.noApplicationsYet", "No applications yet"),
|
|
216
|
+
description: X ? L("devportal.createFirstApplicationToGetStarted", "Create your first application to get started") : L("devportal.createOrgFirstToPublishApps", "Create an organization first to start publishing apps"),
|
|
217
|
+
action: X ? /* @__PURE__ */ E("button", {
|
|
218
|
+
onClick: () => J("/apps/new"),
|
|
205
219
|
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",
|
|
206
|
-
children: [/* @__PURE__ */ T(y, { size: 16 }),
|
|
220
|
+
children: [/* @__PURE__ */ T(y, { size: 16 }), L("devportal.createApp", "Create App")]
|
|
207
221
|
}) : /* @__PURE__ */ E("button", {
|
|
208
|
-
onClick: () =>
|
|
222
|
+
onClick: () => J("/organizations/new"),
|
|
209
223
|
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",
|
|
210
|
-
children: [/* @__PURE__ */ T(y, { size: 16 }),
|
|
224
|
+
children: [/* @__PURE__ */ T(y, { size: 16 }), L("devportal.createOrganization", "Create Organization")]
|
|
211
225
|
})
|
|
212
226
|
})
|
|
213
227
|
}), /* @__PURE__ */ T(i, {
|
|
214
|
-
title:
|
|
228
|
+
title: L("devportal.quickActions", "Quick Actions"),
|
|
215
229
|
"data-tour": "devportal-dashboard-quick-actions",
|
|
216
230
|
children: /* @__PURE__ */ E("div", {
|
|
217
231
|
className: "grid gap-3",
|
|
218
232
|
children: [
|
|
219
|
-
|
|
220
|
-
title:
|
|
221
|
-
description:
|
|
233
|
+
X ? /* @__PURE__ */ T(j, {
|
|
234
|
+
title: L("devportal.createNewApp", "Create New App"),
|
|
235
|
+
description: L("devportal.startBuildingANewApplication", "Start building a new application"),
|
|
222
236
|
icon: /* @__PURE__ */ T(p, { size: 20 }),
|
|
223
|
-
onClick: () =>
|
|
237
|
+
onClick: () => J("/apps/new")
|
|
224
238
|
}) : /* @__PURE__ */ T(j, {
|
|
225
|
-
title:
|
|
226
|
-
description:
|
|
239
|
+
title: L("devportal.createOrganization", "Create Organization"),
|
|
240
|
+
description: L("devportal.setUpATeamToCollaborate", "Set up a team to collaborate"),
|
|
227
241
|
icon: /* @__PURE__ */ T(h, { size: 20 }),
|
|
228
|
-
onClick: () =>
|
|
242
|
+
onClick: () => J("/organizations/new"),
|
|
229
243
|
primary: !0
|
|
230
244
|
}),
|
|
231
|
-
|
|
232
|
-
title:
|
|
233
|
-
description:
|
|
245
|
+
X && /* @__PURE__ */ T(j, {
|
|
246
|
+
title: L("devportal.publisherProfile", "Publisher Profile"),
|
|
247
|
+
description: L("devportal.manageYourStorePresence", "Manage your store publisher presence"),
|
|
234
248
|
icon: /* @__PURE__ */ T(b, { size: 20 }),
|
|
235
|
-
onClick: () =>
|
|
249
|
+
onClick: () => J("/publisher/profile")
|
|
236
250
|
}),
|
|
237
251
|
/* @__PURE__ */ T(j, {
|
|
238
|
-
title:
|
|
239
|
-
description:
|
|
252
|
+
title: L("devportal.generateApiKey", "Generate API Key"),
|
|
253
|
+
description: L("devportal.createCredentialsForYourApps", "Create credentials for your apps"),
|
|
240
254
|
icon: /* @__PURE__ */ T(_, { size: 20 }),
|
|
241
|
-
onClick: () =>
|
|
255
|
+
onClick: () => J("/api-keys")
|
|
242
256
|
}),
|
|
243
257
|
/* @__PURE__ */ T(j, {
|
|
244
|
-
title:
|
|
245
|
-
description:
|
|
258
|
+
title: L("devportal.configureWebhooks", "Configure Webhooks"),
|
|
259
|
+
description: L("devportal.setUpEventNotifications", "Set up event notifications"),
|
|
246
260
|
icon: /* @__PURE__ */ T(C, { size: 20 }),
|
|
247
|
-
onClick: () =>
|
|
261
|
+
onClick: () => J("/webhooks")
|
|
248
262
|
})
|
|
249
263
|
]
|
|
250
264
|
})
|
|
251
265
|
})]
|
|
252
266
|
}),
|
|
253
|
-
|
|
267
|
+
Y ? /* @__PURE__ */ T(i, {
|
|
254
268
|
"data-tour": "devportal-dashboard-organizations",
|
|
255
|
-
title:
|
|
269
|
+
title: L("devportal.yourOrganizations", "Your Organizations"),
|
|
256
270
|
children: /* @__PURE__ */ T("div", {
|
|
257
271
|
className: "flex items-center justify-center py-8",
|
|
258
272
|
children: /* @__PURE__ */ T(v, { className: "h-6 w-6 animate-spin text-text-secondary" })
|
|
259
273
|
})
|
|
260
|
-
}) :
|
|
261
|
-
title:
|
|
274
|
+
}) : B.length > 0 ? /* @__PURE__ */ T(i, {
|
|
275
|
+
title: L("devportal.yourOrganizations", "Your Organizations"),
|
|
262
276
|
actions: /* @__PURE__ */ E("button", {
|
|
263
|
-
onClick: () =>
|
|
277
|
+
onClick: () => J("/organizations"),
|
|
264
278
|
className: "text-sm text-primary hover:underline flex items-center gap-1",
|
|
265
279
|
children: [
|
|
266
|
-
|
|
280
|
+
L("devportal.viewAll", "View all"),
|
|
267
281
|
" ",
|
|
268
282
|
/* @__PURE__ */ T(m, { size: 14 })
|
|
269
283
|
]
|
|
270
284
|
}),
|
|
271
285
|
children: /* @__PURE__ */ T("div", {
|
|
272
286
|
className: "grid gap-4 sm:grid-cols-2 lg:grid-cols-3",
|
|
273
|
-
children:
|
|
287
|
+
children: B.slice(0, 3).map((e) => /* @__PURE__ */ T(M, {
|
|
274
288
|
org: e,
|
|
275
|
-
onClick: () =>
|
|
289
|
+
onClick: () => J(`/organizations/${e.id}`)
|
|
276
290
|
}, e.id))
|
|
277
291
|
})
|
|
278
292
|
}) : null
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DashboardPage.js","names":[],"sources":["../../../src/devportal/pages/DashboardPage.tsx"],"sourcesContent":["import type { FC, ReactNode } from \"react\";\nimport { useEffect } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n AppWindow,\n Building2,\n Key,\n Webhook,\n TrendingUp,\n Users,\n DollarSign,\n Activity,\n Plus,\n ArrowRight,\n Loader2,\n Store,\n} from \"lucide-react\";\nimport { EmphasisPanel, GradientText, NextSteps } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport { StatCard, StatCardGrid } from \"../components/StatCard\";\nimport { AppListItem } from \"../components/AppCard\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport type { Application, DeveloperOrganization } from \"../types\";\nimport {\n useGetDevportalDashboardBootstrapQuery,\n type OrganizationFieldsFragment,\n type ApplicationFieldsFragment,\n} from \"../hooks\";\n\n/**\n * Developer Portal Dashboard Page\n *\n * Shows an overview of the developer's applications, organizations,\n * and key metrics using real data from the GraphQL API.\n */\nexport const DashboardPage: FC = () => {\n const { navigate, basePath, activeOrgId } = useDevportal();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n\n // Fetch dashboard bootstrap in a single global query — scope apps to the active org\n const { data, loading } = useGetDevportalDashboardBootstrapQuery({\n variables: { organizationId: activeOrgId ?? undefined },\n fetchPolicy: \"cache-and-network\",\n });\n\n const organizations: OrganizationFieldsFragment[] =\n data?.myDeveloperOrganizations ?? [];\n\n // First-time users: redirect to setup wizard when no org exists and they\n // haven't already dismissed the wizard in this browser.\n useEffect(() => {\n if (loading || !data) return;\n if (organizations.length > 0) return;\n try {\n if (localStorage.getItem(\"devportal_setup_dismissed\")) return;\n } catch {\n // localStorage unavailable — don't redirect\n return;\n }\n navigate?.(`${basePath}/setup`);\n }, [loading, data, organizations.length, navigate, basePath]);\n const applications: ApplicationFieldsFragment[] =\n data?.applications?.edges?.map(\n (e: { node: ApplicationFieldsFragment }) => e.node,\n ) ?? [];\n\n // Calculate aggregate stats\n const stats = {\n totalApps: data?.applications?.totalCount ?? 0,\n totalOrgs: organizations.length,\n totalApiKeys: 0,\n totalWebhooks: 0,\n totalApiCalls: organizations.reduce(\n (acc: number, org: OrganizationFieldsFragment) =>\n acc + parseInt(org.totalApiCalls || \"0\"),\n 0,\n ),\n activeInstalls: organizations.reduce(\n (acc: number, org: OrganizationFieldsFragment) =>\n acc + (org.activeInstalls || 0),\n 0,\n ),\n monthlyRevenue: organizations.reduce(\n (acc: number, org: OrganizationFieldsFragment) =>\n acc + (org.totalRevenue || 0),\n 0,\n ),\n errorRate: 0,\n };\n\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n const isLoading = loading;\n\n const hasOrgs = !isLoading && organizations.length > 0;\n\n return (\n <PermissionGuard permission={DEVPORTAL_PERMISSIONS.APP_READ}>\n <PageLayout\n heroBackdrop\n title={\n <>\n {tr(\"devportal.developer\", \"Developer\")}{\" \"}\n <GradientText>{tr(\"devportal.portal\", \"Portal\")}</GradientText>\n </>\n }\n description={tr(\n \"devportal.buildManageMonitor\",\n \"Build, manage, and monitor your applications\",\n )}\n purpose={tr(\n \"devportal.dashboardPurpose\",\n \"This is your developer home base — a single view of the apps you ship, the organizations behind them, and how they are performing (installs, API usage, revenue). Start an organization, then create and publish apps from here.\",\n )}\n actions={\n hasOrgs ? (\n <button\n data-tour=\"devportal-dashboard-create-app-button\"\n onClick={() => handleNavigate(\"/apps/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.createApp\", \"Create App\")}\n </button>\n ) : !isLoading ? (\n <button\n data-tour=\"devportal-dashboard-create-org-button\"\n onClick={() => handleNavigate(\"/organizations/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.createOrganization\", \"Create Organization\")}\n </button>\n ) : null\n }\n >\n {/* Stats Overview — primary emphasis zone */}\n <EmphasisPanel data-tour=\"devportal-dashboard-stats-overview\">\n <StatCardGrid columns={4}>\n <StatCard\n featured\n title={tr(\"devportal.totalApps\", \"Total Apps\")}\n value={\n isLoading ? (\n <Loader2 className=\"h-5 w-5 animate-spin\" />\n ) : (\n stats.totalApps\n )\n }\n icon={<AppWindow size={20} />}\n trend={undefined}\n />\n <StatCard\n title={tr(\"devportal.organizations\", \"Organizations\")}\n value={\n isLoading ? (\n <Loader2 className=\"h-5 w-5 animate-spin\" />\n ) : (\n stats.totalOrgs\n )\n }\n icon={<Building2 size={20} />}\n />\n <StatCard\n title={tr(\"devportal.apiKeys\", \"API Keys\")}\n value={stats.totalApiKeys}\n icon={<Key size={20} />}\n description={tr(\"devportal.activeKeys\", \"Active keys\")}\n />\n <StatCard\n title={tr(\"devportal.webhooks\", \"Webhooks\")}\n value={stats.totalWebhooks}\n icon={<Webhook size={20} />}\n description={tr(\"devportal.configured\", \"Configured\")}\n />\n </StatCardGrid>\n </EmphasisPanel>\n\n {/* Guided next steps for new developers (dismissable) */}\n {!isLoading && (\n <NextSteps\n storageKey=\"devportal-dashboard\"\n steps={[\n {\n id: \"create-org\",\n label: tr(\n \"devportal.createOrganization\",\n \"Create Organization\",\n ),\n description: tr(\n \"devportal.setUpATeamToCollaborate\",\n \"Set up a team to collaborate\",\n ),\n icon: <Building2 size={16} />,\n onClick: () => handleNavigate(\"/organizations/new\"),\n done: hasOrgs,\n },\n {\n id: \"create-app\",\n label: tr(\"devportal.createApp\", \"Create App\"),\n description: tr(\n \"devportal.startBuildingANewApplication\",\n \"Start building a new application\",\n ),\n icon: <AppWindow size={16} />,\n onClick: () => handleNavigate(\"/apps/new\"),\n done: stats.totalApps > 0,\n },\n {\n id: \"api-key\",\n label: tr(\"devportal.generateApiKey\", \"Generate API Key\"),\n description: tr(\n \"devportal.createCredentialsForYourApps\",\n \"Create credentials for your apps\",\n ),\n icon: <Key size={16} />,\n onClick: () => handleNavigate(\"/api-keys\"),\n },\n {\n id: \"webhooks\",\n label: tr(\"devportal.configureWebhooks\", \"Configure Webhooks\"),\n description: tr(\n \"devportal.setUpEventNotifications\",\n \"Set up event notifications\",\n ),\n icon: <Webhook size={16} />,\n onClick: () => handleNavigate(\"/webhooks\"),\n },\n ]}\n />\n )}\n\n {/* Usage Stats */}\n <PageSection\n title={tr(\"devportal.usageOverview\", \"Usage Overview\")}\n data-tour=\"devportal-dashboard-usage-overview\"\n >\n <StatCardGrid columns={4}>\n <StatCard\n title={tr(\"devportal.apiCalls\", \"API Calls\")}\n value={\n isLoading ? (\n <Loader2 className=\"h-5 w-5 animate-spin\" />\n ) : stats.totalApiCalls > 1000000 ? (\n `${(stats.totalApiCalls / 1000000).toFixed(1)}M`\n ) : stats.totalApiCalls > 1000 ? (\n `${(stats.totalApiCalls / 1000).toFixed(1)}K`\n ) : (\n stats.totalApiCalls.toString()\n )\n }\n icon={<Activity size={20} />}\n trend={undefined}\n />\n <StatCard\n title={tr(\"devportal.activeInstalls\", \"Active Installs\")}\n value={\n isLoading ? (\n <Loader2 className=\"h-5 w-5 animate-spin\" />\n ) : stats.activeInstalls > 1000 ? (\n `${(stats.activeInstalls / 1000).toFixed(1)}K`\n ) : (\n stats.activeInstalls.toString()\n )\n }\n icon={<Users size={20} />}\n trend={undefined}\n />\n <StatCard\n title={tr(\"devportal.monthlyRevenue\", \"Monthly Revenue\")}\n value={\n isLoading ? (\n <Loader2 className=\"h-5 w-5 animate-spin\" />\n ) : (\n `$${stats.monthlyRevenue.toLocaleString()}`\n )\n }\n icon={<DollarSign size={20} />}\n trend={undefined}\n />\n <StatCard\n title={tr(\"devportal.errorRate\", \"Error Rate\")}\n value={`${stats.errorRate}%`}\n icon={<TrendingUp size={20} />}\n trend={undefined}\n />\n </StatCardGrid>\n </PageSection>\n\n {/* Quick Actions & Recent Items */}\n <div className=\"grid gap-6 lg:grid-cols-2\">\n {/* Recent Apps */}\n <PageSection\n data-tour=\"devportal-dashboard-recent-apps\"\n title={tr(\"devportal.recentApplications\", \"Recent Applications\")}\n actions={\n <button\n onClick={() => handleNavigate(\"/apps\")}\n className=\"text-sm text-primary hover:underline flex items-center gap-1\"\n >\n {tr(\"devportal.viewAll\", \"View all\")} <ArrowRight size={14} />\n </button>\n }\n >\n {isLoading ? (\n <div className=\"flex items-center justify-center py-8\">\n <Loader2 className=\"h-6 w-6 animate-spin text-text-secondary\" />\n </div>\n ) : applications.length > 0 ? (\n <div className=\"space-y-2\">\n {applications\n .slice(0, 3)\n .map((app: ApplicationFieldsFragment) => (\n <AppListItem\n key={app.id}\n app={app as Application}\n onClick={() => handleNavigate(`/apps/${app.id}`)}\n />\n ))}\n </div>\n ) : (\n <EmptyState\n illustration=\"empty-generic\"\n title={tr(\"devportal.noApplicationsYet\", \"No applications yet\")}\n description={\n hasOrgs\n ? tr(\n \"devportal.createFirstApplicationToGetStarted\",\n \"Create your first application to get started\",\n )\n : tr(\n \"devportal.createOrgFirstToPublishApps\",\n \"Create an organization first to start publishing apps\",\n )\n }\n action={\n hasOrgs ? (\n <button\n onClick={() => handleNavigate(\"/apps/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.createApp\", \"Create App\")}\n </button>\n ) : (\n <button\n onClick={() => handleNavigate(\"/organizations/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(\n \"devportal.createOrganization\",\n \"Create Organization\",\n )}\n </button>\n )\n }\n />\n )}\n </PageSection>\n\n {/* Quick Actions */}\n <PageSection\n title={tr(\"devportal.quickActions\", \"Quick Actions\")}\n data-tour=\"devportal-dashboard-quick-actions\"\n >\n <div className=\"grid gap-3\">\n {!hasOrgs ? (\n <QuickActionCard\n title={tr(\n \"devportal.createOrganization\",\n \"Create Organization\",\n )}\n description={tr(\n \"devportal.setUpATeamToCollaborate\",\n \"Set up a team to collaborate\",\n )}\n icon={<Building2 size={20} />}\n onClick={() => handleNavigate(\"/organizations/new\")}\n primary\n />\n ) : (\n <QuickActionCard\n title={tr(\"devportal.createNewApp\", \"Create New App\")}\n description={tr(\n \"devportal.startBuildingANewApplication\",\n \"Start building a new application\",\n )}\n icon={<AppWindow size={20} />}\n onClick={() => handleNavigate(\"/apps/new\")}\n />\n )}\n {hasOrgs && (\n <QuickActionCard\n title={tr(\"devportal.publisherProfile\", \"Publisher Profile\")}\n description={tr(\n \"devportal.manageYourStorePresence\",\n \"Manage your store publisher presence\",\n )}\n icon={<Store size={20} />}\n onClick={() => handleNavigate(\"/publisher/profile\")}\n />\n )}\n <QuickActionCard\n title={tr(\"devportal.generateApiKey\", \"Generate API Key\")}\n description={tr(\n \"devportal.createCredentialsForYourApps\",\n \"Create credentials for your apps\",\n )}\n icon={<Key size={20} />}\n onClick={() => handleNavigate(\"/api-keys\")}\n />\n <QuickActionCard\n title={tr(\"devportal.configureWebhooks\", \"Configure Webhooks\")}\n description={tr(\n \"devportal.setUpEventNotifications\",\n \"Set up event notifications\",\n )}\n icon={<Webhook size={20} />}\n onClick={() => handleNavigate(\"/webhooks\")}\n />\n </div>\n </PageSection>\n </div>\n\n {/* Organizations */}\n {isLoading ? (\n <PageSection\n data-tour=\"devportal-dashboard-organizations\"\n title={tr(\"devportal.yourOrganizations\", \"Your Organizations\")}\n >\n <div className=\"flex items-center justify-center py-8\">\n <Loader2 className=\"h-6 w-6 animate-spin text-text-secondary\" />\n </div>\n </PageSection>\n ) : organizations.length > 0 ? (\n <PageSection\n title={tr(\"devportal.yourOrganizations\", \"Your Organizations\")}\n actions={\n <button\n onClick={() => handleNavigate(\"/organizations\")}\n className=\"text-sm text-primary hover:underline flex items-center gap-1\"\n >\n {tr(\"devportal.viewAll\", \"View all\")} <ArrowRight size={14} />\n </button>\n }\n >\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {organizations\n .slice(0, 3)\n .map((org: OrganizationFieldsFragment) => (\n <OrgCard\n key={org.id}\n org={org as DeveloperOrganization}\n onClick={() => handleNavigate(`/organizations/${org.id}`)}\n />\n ))}\n </div>\n </PageSection>\n ) : null}\n </PageLayout>\n </PermissionGuard>\n );\n};\n\n// Quick action card component\nconst QuickActionCard: FC<{\n title: string;\n description: string;\n icon: React.ReactNode;\n onClick: () => void;\n primary?: boolean;\n}> = ({ title, description, icon, onClick, primary }) => (\n <button\n onClick={onClick}\n className={[\n \"flex w-full items-center gap-4 rounded-card border p-4 text-left transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\",\n primary\n ? \"border-primary bg-action-primary-bg/5 hover:bg-action-primary-bg/10\"\n : \"border-border-seam bg-bg-surface shadow-[var(--shadow-pop)] hover:bg-accent\",\n ].join(\" \")}\n >\n <div\n className={[\n \"rounded-lg p-2\",\n primary\n ? \"bg-action-primary-bg/20 text-primary\"\n : \"bg-action-primary-bg/10 text-primary\",\n ].join(\" \")}\n >\n {icon}\n </div>\n <div>\n <p className=\"font-medium\">{title}</p>\n <p className=\"text-sm text-text-secondary\">{description}</p>\n </div>\n </button>\n);\n\n// Organization card component\nconst OrgCard: FC<{\n org: DeveloperOrganization;\n onClick: () => void;\n}> = ({ org, 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 return (\n <button\n onClick={onClick}\n className=\"w-full rounded-card border border-border-seam bg-bg-surface p-4 text-left shadow-[var(--shadow-pop)] transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\"\n >\n <div className=\"flex items-center gap-3\">\n {org.logoUrl ? (\n <img\n src={org.logoUrl}\n alt={org.name}\n className=\"h-10 w-10 rounded-lg object-cover\"\n />\n ) : (\n <div className=\"flex h-10 w-10 items-center justify-center rounded-lg bg-action-primary-bg/10 text-primary\">\n <Building2 size={20} />\n </div>\n )}\n <div className=\"flex-1 min-w-0\">\n <p className=\"font-medium truncate\">{org.name}</p>\n <p className=\"text-sm text-text-secondary\">\n {org.publishedApps}{\" \"}\n {tr(\"devportal.appsPublished\", \"apps published\")}\n </p>\n </div>\n </div>\n <div className=\"mt-3 flex gap-4 text-sm text-text-secondary\">\n <span>\n {(org.totalInstalls || 0).toLocaleString()}{\" \"}\n {tr(\"devportal.installs\", \"installs\")}\n </span>\n <span>\n ${(org.totalRevenue || 0).toLocaleString()}{\" \"}\n {tr(\"devportal.revenue\", \"revenue\")}\n </span>\n </div>\n </button>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DashboardPage.js","names":[],"sources":["../../../src/devportal/pages/DashboardPage.tsx"],"sourcesContent":["import type { FC, ReactNode } from \"react\";\nimport { useEffect } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n AppWindow,\n Building2,\n Key,\n Webhook,\n TrendingUp,\n Users,\n DollarSign,\n Activity,\n Plus,\n ArrowRight,\n Loader2,\n Store,\n} from \"lucide-react\";\nimport { EmphasisPanel, GradientText, NextSteps } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport { StatCard, StatCardGrid } from \"../components/StatCard\";\nimport { AppListItem } from \"../components/AppCard\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport type { Application, DeveloperOrganization } from \"../types\";\nimport {\n useGetDevportalDashboardBootstrapQuery,\n type OrganizationFieldsFragment,\n type ApplicationFieldsFragment,\n} from \"../hooks\";\n\n/**\n * Developer Portal Dashboard Page\n *\n * Shows an overview of the developer's applications, organizations,\n * and key metrics using real data from the GraphQL API.\n */\nexport const DashboardPage: FC = () => {\n const { navigate, basePath, activeOrgId, setActiveOrgId } = 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 // Phase 1: fetch orgs only (no apps yet) so we can resolve the active org\n const { data: orgsData, loading: orgsLoading } =\n useGetDevportalDashboardBootstrapQuery({\n variables: {},\n fetchPolicy: \"cache-and-network\",\n });\n\n const organizations: OrganizationFieldsFragment[] =\n orgsData?.myDeveloperOrganizations ?? [];\n\n // Resolve the org to scope apps to: prefer stored activeOrgId if valid,\n // otherwise fall back to the first org the user belongs to.\n const resolvedOrgId: string | undefined = (() => {\n if (!organizations.length) return undefined;\n if (activeOrgId && organizations.some((o) => o.id === activeOrgId))\n return activeOrgId;\n return organizations[0]?.id;\n })();\n\n // Sync activeOrgId when we auto-selected a fallback org\n useEffect(() => {\n if (resolvedOrgId && resolvedOrgId !== activeOrgId) {\n setActiveOrgId(resolvedOrgId);\n }\n }, [resolvedOrgId, activeOrgId, setActiveOrgId]);\n\n // Phase 2: fetch apps scoped to the resolved org (skip until org is known)\n const { data: appsData, loading: appsLoading } =\n useGetDevportalDashboardBootstrapQuery({\n variables: { organizationId: resolvedOrgId },\n skip: !resolvedOrgId,\n fetchPolicy: \"cache-and-network\",\n });\n\n const loading = orgsLoading || appsLoading;\n const data = appsData ?? orgsData;\n\n // First-time users: redirect to setup wizard when no org exists and they\n // haven't already dismissed the wizard in this browser.\n useEffect(() => {\n if (orgsLoading || !orgsData) return;\n if (organizations.length > 0) return;\n try {\n if (localStorage.getItem(\"devportal_setup_dismissed\")) return;\n } catch {\n // localStorage unavailable — don't redirect\n return;\n }\n navigate?.(`${basePath}/setup`);\n }, [orgsLoading, orgsData, organizations.length, navigate, basePath]);\n\n const applications: ApplicationFieldsFragment[] =\n appsData?.applications?.edges?.map(\n (e: { node: ApplicationFieldsFragment }) => e.node,\n ) ?? [];\n\n // Calculate aggregate stats\n const stats = {\n totalApps: data?.applications?.totalCount ?? 0,\n totalOrgs: organizations.length,\n totalApiKeys: 0,\n totalWebhooks: 0,\n totalApiCalls: organizations.reduce(\n (acc: number, org: OrganizationFieldsFragment) =>\n acc + parseInt(org.totalApiCalls || \"0\"),\n 0,\n ),\n activeInstalls: organizations.reduce(\n (acc: number, org: OrganizationFieldsFragment) =>\n acc + (org.activeInstalls || 0),\n 0,\n ),\n monthlyRevenue: organizations.reduce(\n (acc: number, org: OrganizationFieldsFragment) =>\n acc + (org.totalRevenue || 0),\n 0,\n ),\n errorRate: 0,\n };\n\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n const isLoading = loading;\n\n const hasOrgs = !isLoading && organizations.length > 0;\n\n return (\n <PermissionGuard permission={DEVPORTAL_PERMISSIONS.APP_READ}>\n <PageLayout\n heroBackdrop\n title={\n <>\n {tr(\"devportal.developer\", \"Developer\")}{\" \"}\n <GradientText>{tr(\"devportal.portal\", \"Portal\")}</GradientText>\n </>\n }\n description={tr(\n \"devportal.buildManageMonitor\",\n \"Build, manage, and monitor your applications\",\n )}\n purpose={tr(\n \"devportal.dashboardPurpose\",\n \"This is your developer home base — a single view of the apps you ship, the organizations behind them, and how they are performing (installs, API usage, revenue). Start an organization, then create and publish apps from here.\",\n )}\n actions={\n hasOrgs ? (\n <button\n data-tour=\"devportal-dashboard-create-app-button\"\n onClick={() => handleNavigate(\"/apps/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.createApp\", \"Create App\")}\n </button>\n ) : !isLoading ? (\n <button\n data-tour=\"devportal-dashboard-create-org-button\"\n onClick={() => handleNavigate(\"/organizations/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.createOrganization\", \"Create Organization\")}\n </button>\n ) : null\n }\n >\n {/* Stats Overview — primary emphasis zone */}\n <EmphasisPanel data-tour=\"devportal-dashboard-stats-overview\">\n <StatCardGrid columns={4}>\n <StatCard\n featured\n title={tr(\"devportal.totalApps\", \"Total Apps\")}\n value={\n isLoading ? (\n <Loader2 className=\"h-5 w-5 animate-spin\" />\n ) : (\n stats.totalApps\n )\n }\n icon={<AppWindow size={20} />}\n trend={undefined}\n />\n <StatCard\n title={tr(\"devportal.organizations\", \"Organizations\")}\n value={\n isLoading ? (\n <Loader2 className=\"h-5 w-5 animate-spin\" />\n ) : (\n stats.totalOrgs\n )\n }\n icon={<Building2 size={20} />}\n />\n <StatCard\n title={tr(\"devportal.apiKeys\", \"API Keys\")}\n value={stats.totalApiKeys}\n icon={<Key size={20} />}\n description={tr(\"devportal.activeKeys\", \"Active keys\")}\n />\n <StatCard\n title={tr(\"devportal.webhooks\", \"Webhooks\")}\n value={stats.totalWebhooks}\n icon={<Webhook size={20} />}\n description={tr(\"devportal.configured\", \"Configured\")}\n />\n </StatCardGrid>\n </EmphasisPanel>\n\n {/* Guided next steps for new developers (dismissable) */}\n {!isLoading && (\n <NextSteps\n storageKey=\"devportal-dashboard\"\n steps={[\n {\n id: \"create-org\",\n label: tr(\n \"devportal.createOrganization\",\n \"Create Organization\",\n ),\n description: tr(\n \"devportal.setUpATeamToCollaborate\",\n \"Set up a team to collaborate\",\n ),\n icon: <Building2 size={16} />,\n onClick: () => handleNavigate(\"/organizations/new\"),\n done: hasOrgs,\n },\n {\n id: \"create-app\",\n label: tr(\"devportal.createApp\", \"Create App\"),\n description: tr(\n \"devportal.startBuildingANewApplication\",\n \"Start building a new application\",\n ),\n icon: <AppWindow size={16} />,\n onClick: () => handleNavigate(\"/apps/new\"),\n done: stats.totalApps > 0,\n },\n {\n id: \"api-key\",\n label: tr(\"devportal.generateApiKey\", \"Generate API Key\"),\n description: tr(\n \"devportal.createCredentialsForYourApps\",\n \"Create credentials for your apps\",\n ),\n icon: <Key size={16} />,\n onClick: () => handleNavigate(\"/api-keys\"),\n },\n {\n id: \"webhooks\",\n label: tr(\"devportal.configureWebhooks\", \"Configure Webhooks\"),\n description: tr(\n \"devportal.setUpEventNotifications\",\n \"Set up event notifications\",\n ),\n icon: <Webhook size={16} />,\n onClick: () => handleNavigate(\"/webhooks\"),\n },\n ]}\n />\n )}\n\n {/* Usage Stats */}\n <PageSection\n title={tr(\"devportal.usageOverview\", \"Usage Overview\")}\n data-tour=\"devportal-dashboard-usage-overview\"\n >\n <StatCardGrid columns={4}>\n <StatCard\n title={tr(\"devportal.apiCalls\", \"API Calls\")}\n value={\n isLoading ? (\n <Loader2 className=\"h-5 w-5 animate-spin\" />\n ) : stats.totalApiCalls > 1000000 ? (\n `${(stats.totalApiCalls / 1000000).toFixed(1)}M`\n ) : stats.totalApiCalls > 1000 ? (\n `${(stats.totalApiCalls / 1000).toFixed(1)}K`\n ) : (\n stats.totalApiCalls.toString()\n )\n }\n icon={<Activity size={20} />}\n trend={undefined}\n />\n <StatCard\n title={tr(\"devportal.activeInstalls\", \"Active Installs\")}\n value={\n isLoading ? (\n <Loader2 className=\"h-5 w-5 animate-spin\" />\n ) : stats.activeInstalls > 1000 ? (\n `${(stats.activeInstalls / 1000).toFixed(1)}K`\n ) : (\n stats.activeInstalls.toString()\n )\n }\n icon={<Users size={20} />}\n trend={undefined}\n />\n <StatCard\n title={tr(\"devportal.monthlyRevenue\", \"Monthly Revenue\")}\n value={\n isLoading ? (\n <Loader2 className=\"h-5 w-5 animate-spin\" />\n ) : (\n `$${stats.monthlyRevenue.toLocaleString()}`\n )\n }\n icon={<DollarSign size={20} />}\n trend={undefined}\n />\n <StatCard\n title={tr(\"devportal.errorRate\", \"Error Rate\")}\n value={`${stats.errorRate}%`}\n icon={<TrendingUp size={20} />}\n trend={undefined}\n />\n </StatCardGrid>\n </PageSection>\n\n {/* Quick Actions & Recent Items */}\n <div className=\"grid gap-6 lg:grid-cols-2\">\n {/* Recent Apps */}\n <PageSection\n data-tour=\"devportal-dashboard-recent-apps\"\n title={tr(\"devportal.recentApplications\", \"Recent Applications\")}\n actions={\n <button\n onClick={() => handleNavigate(\"/apps\")}\n className=\"text-sm text-primary hover:underline flex items-center gap-1\"\n >\n {tr(\"devportal.viewAll\", \"View all\")} <ArrowRight size={14} />\n </button>\n }\n >\n {isLoading ? (\n <div className=\"flex items-center justify-center py-8\">\n <Loader2 className=\"h-6 w-6 animate-spin text-text-secondary\" />\n </div>\n ) : applications.length > 0 ? (\n <div className=\"space-y-2\">\n {applications\n .slice(0, 3)\n .map((app: ApplicationFieldsFragment) => (\n <AppListItem\n key={app.id}\n app={app as Application}\n onClick={() => handleNavigate(`/apps/${app.id}`)}\n />\n ))}\n </div>\n ) : (\n <EmptyState\n illustration=\"empty-generic\"\n title={tr(\"devportal.noApplicationsYet\", \"No applications yet\")}\n description={\n hasOrgs\n ? tr(\n \"devportal.createFirstApplicationToGetStarted\",\n \"Create your first application to get started\",\n )\n : tr(\n \"devportal.createOrgFirstToPublishApps\",\n \"Create an organization first to start publishing apps\",\n )\n }\n action={\n hasOrgs ? (\n <button\n onClick={() => handleNavigate(\"/apps/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.createApp\", \"Create App\")}\n </button>\n ) : (\n <button\n onClick={() => handleNavigate(\"/organizations/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(\n \"devportal.createOrganization\",\n \"Create Organization\",\n )}\n </button>\n )\n }\n />\n )}\n </PageSection>\n\n {/* Quick Actions */}\n <PageSection\n title={tr(\"devportal.quickActions\", \"Quick Actions\")}\n data-tour=\"devportal-dashboard-quick-actions\"\n >\n <div className=\"grid gap-3\">\n {!hasOrgs ? (\n <QuickActionCard\n title={tr(\n \"devportal.createOrganization\",\n \"Create Organization\",\n )}\n description={tr(\n \"devportal.setUpATeamToCollaborate\",\n \"Set up a team to collaborate\",\n )}\n icon={<Building2 size={20} />}\n onClick={() => handleNavigate(\"/organizations/new\")}\n primary\n />\n ) : (\n <QuickActionCard\n title={tr(\"devportal.createNewApp\", \"Create New App\")}\n description={tr(\n \"devportal.startBuildingANewApplication\",\n \"Start building a new application\",\n )}\n icon={<AppWindow size={20} />}\n onClick={() => handleNavigate(\"/apps/new\")}\n />\n )}\n {hasOrgs && (\n <QuickActionCard\n title={tr(\"devportal.publisherProfile\", \"Publisher Profile\")}\n description={tr(\n \"devportal.manageYourStorePresence\",\n \"Manage your store publisher presence\",\n )}\n icon={<Store size={20} />}\n onClick={() => handleNavigate(\"/publisher/profile\")}\n />\n )}\n <QuickActionCard\n title={tr(\"devportal.generateApiKey\", \"Generate API Key\")}\n description={tr(\n \"devportal.createCredentialsForYourApps\",\n \"Create credentials for your apps\",\n )}\n icon={<Key size={20} />}\n onClick={() => handleNavigate(\"/api-keys\")}\n />\n <QuickActionCard\n title={tr(\"devportal.configureWebhooks\", \"Configure Webhooks\")}\n description={tr(\n \"devportal.setUpEventNotifications\",\n \"Set up event notifications\",\n )}\n icon={<Webhook size={20} />}\n onClick={() => handleNavigate(\"/webhooks\")}\n />\n </div>\n </PageSection>\n </div>\n\n {/* Organizations */}\n {isLoading ? (\n <PageSection\n data-tour=\"devportal-dashboard-organizations\"\n title={tr(\"devportal.yourOrganizations\", \"Your Organizations\")}\n >\n <div className=\"flex items-center justify-center py-8\">\n <Loader2 className=\"h-6 w-6 animate-spin text-text-secondary\" />\n </div>\n </PageSection>\n ) : organizations.length > 0 ? (\n <PageSection\n title={tr(\"devportal.yourOrganizations\", \"Your Organizations\")}\n actions={\n <button\n onClick={() => handleNavigate(\"/organizations\")}\n className=\"text-sm text-primary hover:underline flex items-center gap-1\"\n >\n {tr(\"devportal.viewAll\", \"View all\")} <ArrowRight size={14} />\n </button>\n }\n >\n <div className=\"grid gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {organizations\n .slice(0, 3)\n .map((org: OrganizationFieldsFragment) => (\n <OrgCard\n key={org.id}\n org={org as DeveloperOrganization}\n onClick={() => handleNavigate(`/organizations/${org.id}`)}\n />\n ))}\n </div>\n </PageSection>\n ) : null}\n </PageLayout>\n </PermissionGuard>\n );\n};\n\n// Quick action card component\nconst QuickActionCard: FC<{\n title: string;\n description: string;\n icon: React.ReactNode;\n onClick: () => void;\n primary?: boolean;\n}> = ({ title, description, icon, onClick, primary }) => (\n <button\n onClick={onClick}\n className={[\n \"flex w-full items-center gap-4 rounded-card border p-4 text-left transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\",\n primary\n ? \"border-primary bg-action-primary-bg/5 hover:bg-action-primary-bg/10\"\n : \"border-border-seam bg-bg-surface shadow-[var(--shadow-pop)] hover:bg-accent\",\n ].join(\" \")}\n >\n <div\n className={[\n \"rounded-lg p-2\",\n primary\n ? \"bg-action-primary-bg/20 text-primary\"\n : \"bg-action-primary-bg/10 text-primary\",\n ].join(\" \")}\n >\n {icon}\n </div>\n <div>\n <p className=\"font-medium\">{title}</p>\n <p className=\"text-sm text-text-secondary\">{description}</p>\n </div>\n </button>\n);\n\n// Organization card component\nconst OrgCard: FC<{\n org: DeveloperOrganization;\n onClick: () => void;\n}> = ({ org, 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 return (\n <button\n onClick={onClick}\n className=\"w-full rounded-card border border-border-seam bg-bg-surface p-4 text-left shadow-[var(--shadow-pop)] transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\"\n >\n <div className=\"flex items-center gap-3\">\n {org.logoUrl ? (\n <img\n src={org.logoUrl}\n alt={org.name}\n className=\"h-10 w-10 rounded-lg object-cover\"\n />\n ) : (\n <div className=\"flex h-10 w-10 items-center justify-center rounded-lg bg-action-primary-bg/10 text-primary\">\n <Building2 size={20} />\n </div>\n )}\n <div className=\"flex-1 min-w-0\">\n <p className=\"font-medium truncate\">{org.name}</p>\n <p className=\"text-sm text-text-secondary\">\n {org.publishedApps}{\" \"}\n {tr(\"devportal.appsPublished\", \"apps published\")}\n </p>\n </div>\n </div>\n <div className=\"mt-3 flex gap-4 text-sm text-text-secondary\">\n <span>\n {(org.totalInstalls || 0).toLocaleString()}{\" \"}\n {tr(\"devportal.installs\", \"installs\")}\n </span>\n <span>\n ${(org.totalRevenue || 0).toLocaleString()}{\" \"}\n {tr(\"devportal.revenue\", \"revenue\")}\n </span>\n </div>\n </button>\n );\n};\n"],"mappings":"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"}
|