@burdenoff/microfe-store 2026.525.3 → 2026.528.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/StoreAdminRoutes.js +10 -9
- package/dist/StoreAdminRoutes.js.map +1 -1
- package/dist/StoreRoutes.js +2 -1
- package/dist/StoreRoutes.js.map +1 -1
- package/dist/components/AccessDenied.js +23 -0
- package/dist/components/AccessDenied.js.map +1 -0
- package/dist/components/InstallAppModal.js +183 -129
- package/dist/components/InstallAppModal.js.map +1 -1
- package/dist/components/PermissionGuard.js +8 -0
- package/dist/components/PermissionGuard.js.map +1 -0
- package/dist/components/RouteGuards.js +7 -21
- package/dist/components/RouteGuards.js.map +1 -1
- package/dist/components/cart/CartDrawer.js +20 -14
- package/dist/components/cart/CartDrawer.js.map +1 -1
- package/dist/hooks/useStoreGraphQL.js +22 -20
- package/dist/hooks/useStoreGraphQL.js.map +1 -1
- package/dist/hooks/useStoreGraphQLWithContext.js.map +1 -1
- package/dist/hooks/useStorePermissions.js +4 -1
- package/dist/hooks/useStorePermissions.js.map +1 -1
- package/dist/index.js +4 -4
- package/dist/pages/AdminDashboardPage.js +182 -183
- package/dist/pages/AdminDashboardPage.js.map +1 -1
- package/dist/pages/AdminReviewModerationPage.js +7 -6
- package/dist/pages/AdminReviewModerationPage.js.map +1 -1
- package/dist/pages/AdminStoresPage.js +193 -185
- package/dist/pages/AdminStoresPage.js.map +1 -1
- package/dist/pages/AdminSubmissionsQueuePage.js +140 -142
- package/dist/pages/AdminSubmissionsQueuePage.js.map +1 -1
- package/dist/pages/AppDetailPage.js +6 -3
- package/dist/pages/AppDetailPage.js.map +1 -1
- package/dist/pages/AppSettingsPage.js +6 -3
- package/dist/pages/AppSettingsPage.js.map +1 -1
- package/dist/pages/CartPage.js +23 -15
- package/dist/pages/CartPage.js.map +1 -1
- package/dist/pages/InstalledAppsPage.js +26 -15
- package/dist/pages/InstalledAppsPage.js.map +1 -1
- package/dist/pages/MarketplaceHomePage.js +275 -247
- package/dist/pages/MarketplaceHomePage.js.map +1 -1
- package/dist/pages/MyLicensesPage.js +10 -8
- package/dist/pages/MyLicensesPage.js.map +1 -1
- package/dist/pages/OrderConfirmationPage.js +72 -75
- package/dist/pages/OrderConfirmationPage.js.map +1 -1
- package/dist/pages/OrdersPage.js +22 -20
- package/dist/pages/OrdersPage.js.map +1 -1
- package/dist/pages/ProductCategoryPage.js +3 -1
- package/dist/pages/ProductCategoryPage.js.map +1 -1
- package/dist/pages/ProductDetailPage.js +458 -443
- package/dist/pages/ProductDetailPage.js.map +1 -1
- package/dist/pages/ProductReviewsPage.js +4 -3
- package/dist/pages/ProductReviewsPage.js.map +1 -1
- package/dist/pages/PublisherDashboardPage.js +13 -7
- package/dist/pages/PublisherDashboardPage.js.map +1 -1
- package/dist/pages/PublisherRevenuePage.js +7 -5
- package/dist/pages/PublisherRevenuePage.js.map +1 -1
- package/dist/pages/PublisherSubmissionsPage.js +12 -7
- package/dist/pages/PublisherSubmissionsPage.js.map +1 -1
- package/dist/pages/PublisherSubmitPage.js +6 -0
- package/dist/pages/PublisherSubmitPage.js.map +1 -1
- package/dist/pages/SearchResultsPage.js +6 -3
- package/dist/pages/SearchResultsPage.js.map +1 -1
- package/dist/pages/StoresPage.js +73 -74
- package/dist/pages/StoresPage.js.map +1 -1
- package/dist/providers/StoreProvider.js +25 -25
- package/dist/providers/StoreProvider.js.map +1 -1
- package/dist/utils/index.js +29 -19
- package/dist/utils/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/components/index.js +0 -3
- package/dist/components/marketplace/ProductCard.js +0 -2
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import { useStore as e } from "../providers/StoreProvider.js";
|
|
2
|
-
import { useCallback as t, useEffect as n,
|
|
3
|
-
import { AlertCircle as
|
|
4
|
-
import { Fragment as
|
|
5
|
-
import { graphqlFetch as
|
|
2
|
+
import { useCallback as t, useEffect as n, useReducer as r, useState as i } from "react";
|
|
3
|
+
import { AlertCircle as a, Building2 as o, CheckCircle as s, Info as c, Loader2 as l, User as u, X as d } from "lucide-react";
|
|
4
|
+
import { Fragment as f, jsx as p, jsxs as m } from "react/jsx-runtime";
|
|
5
|
+
import { graphqlFetch as h } from "@burdenoff/fe-libs/shared/graphql";
|
|
6
6
|
//#region src/components/InstallAppModal.tsx
|
|
7
|
-
var
|
|
7
|
+
var g = "query MyWorkspaces { myWorkspaces { items { id name slug type status organizationId } total } }", _ = "mutation InstallWorkspaceApps($workspaceIds: [ID!]!, $input: InstallWorkspaceAppInput!) { installWorkspaceApps(workspaceIds: $workspaceIds, input: $input) { successCount failedCount results { success workspaceId error } } }", v = "mutation RecordAppInstallation($input: RecordAppInstallationInput!) { recordAppInstallation(input: $input) { id status workspaceId version } }", y = "mutation InstallPurchasedApp($orderId: ID, $productId: String!, $workspaceId: String!) { installPurchasedApp(orderId: $orderId, productId: $productId, workspaceId: $workspaceId) { success alreadyInstalled installation { id productId workspaceId status version } error message } }", b = "mutation CreateProductEntitlement($input: CreateProductEntitlementInput!) { createProductEntitlement(input: $input) { id status productId workspaceId } }", x = new Set([
|
|
8
8
|
"VIBEMODULE",
|
|
9
9
|
"NODE",
|
|
10
10
|
"CONSOLE",
|
|
11
11
|
"DASHBOARD",
|
|
12
12
|
"PARSER",
|
|
13
13
|
"DATASINK"
|
|
14
|
-
]),
|
|
14
|
+
]), S = {
|
|
15
15
|
ALREADY_INSTALLED: "This app is already installed in the selected workspace.",
|
|
16
16
|
QUOTA_EXCEEDED: "Installation limit reached. Please upgrade your plan or contact support.",
|
|
17
17
|
PERMISSION_DENIED: "You do not have permission to install apps in this workspace.",
|
|
@@ -20,18 +20,52 @@ var h = "query MyWorkspaces { myWorkspaces { items { id name slug type status or
|
|
|
20
20
|
ENTITLEMENT_EXPIRED: "Your license for this app has expired.",
|
|
21
21
|
UNSUPPORTED_TYPE: "This product type cannot be installed in the selected workspace."
|
|
22
22
|
};
|
|
23
|
-
function
|
|
24
|
-
for (let [t, n] of Object.entries(
|
|
23
|
+
function C(e) {
|
|
24
|
+
for (let [t, n] of Object.entries(S)) if (e.toUpperCase().includes(t)) return n;
|
|
25
25
|
return "Installation failed. Please try again or contact support.";
|
|
26
26
|
}
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
var w = {
|
|
28
|
+
selectedWorkspace: null,
|
|
29
|
+
installing: !1,
|
|
30
|
+
installError: null,
|
|
31
|
+
installInfo: null
|
|
32
|
+
};
|
|
33
|
+
function T(e, t) {
|
|
34
|
+
switch (t.type) {
|
|
35
|
+
case "reset": return w;
|
|
36
|
+
case "selectWorkspace": return {
|
|
37
|
+
...e,
|
|
38
|
+
selectedWorkspace: t.workspaceId
|
|
39
|
+
};
|
|
40
|
+
case "startInstall": return {
|
|
41
|
+
...e,
|
|
42
|
+
installing: !0,
|
|
43
|
+
installError: null,
|
|
44
|
+
installInfo: null
|
|
45
|
+
};
|
|
46
|
+
case "finishInstall": return {
|
|
47
|
+
...e,
|
|
48
|
+
installing: !1
|
|
49
|
+
};
|
|
50
|
+
case "setError": return {
|
|
51
|
+
...e,
|
|
52
|
+
installError: t.error
|
|
53
|
+
};
|
|
54
|
+
case "setInfo": return {
|
|
55
|
+
...e,
|
|
56
|
+
installInfo: t.info
|
|
57
|
+
};
|
|
58
|
+
default: return e;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
function E(r) {
|
|
62
|
+
let { authToken: a } = e(), [o, s] = i([]), [c, l] = i(!1), [u, d] = i(null), f = t(async () => {
|
|
63
|
+
if (!r) {
|
|
30
64
|
l(!0), d(null);
|
|
31
65
|
try {
|
|
32
|
-
let e = await
|
|
66
|
+
let e = await h({
|
|
33
67
|
gateway: "global",
|
|
34
|
-
query:
|
|
68
|
+
query: g,
|
|
35
69
|
authToken: a || void 0
|
|
36
70
|
});
|
|
37
71
|
if (e.errors?.length) throw Error(e.errors[0]?.message ?? "GraphQL error");
|
|
@@ -42,7 +76,7 @@ function C(i) {
|
|
|
42
76
|
l(!1);
|
|
43
77
|
}
|
|
44
78
|
}
|
|
45
|
-
}, [a,
|
|
79
|
+
}, [a, r]);
|
|
46
80
|
return n(() => {
|
|
47
81
|
f();
|
|
48
82
|
}, [f]), {
|
|
@@ -52,237 +86,257 @@ function C(i) {
|
|
|
52
86
|
refetch: f
|
|
53
87
|
};
|
|
54
88
|
}
|
|
55
|
-
var
|
|
56
|
-
let { authToken:
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}, [t]);
|
|
60
|
-
let V = R.filter((e) => e.status === "ACTIVE"), H = async () => {
|
|
61
|
-
if (!A) return;
|
|
62
|
-
let e = V.find((e) => e.id === A);
|
|
89
|
+
var D = ({ isOpen: t, onClose: n, app: i, orderId: g, purchaseState: S = "free", onInstallSuccess: D, onInstallError: O }) => {
|
|
90
|
+
let { authToken: k, user: A } = e(), [j, M] = r(T, w), { selectedWorkspace: N, installing: P, installError: F, installInfo: I } = j, { workspaces: L, loading: R, error: z } = E(!t), B = L.filter((e) => e.status === "ACTIVE"), V = async () => {
|
|
91
|
+
if (!N) return;
|
|
92
|
+
let e = B.find((e) => e.id === N);
|
|
63
93
|
if (!e) return;
|
|
64
|
-
let t =
|
|
65
|
-
|
|
94
|
+
let t = A?.id;
|
|
95
|
+
M({ type: "startInstall" });
|
|
66
96
|
try {
|
|
67
|
-
if (!
|
|
97
|
+
if (!g && S !== "purchased") {
|
|
68
98
|
if (!t) throw Error("Unable to identify the current user for license creation");
|
|
69
|
-
await
|
|
99
|
+
await h({
|
|
70
100
|
gateway: "global",
|
|
71
|
-
query:
|
|
101
|
+
query: b,
|
|
72
102
|
variables: { input: {
|
|
73
|
-
productId:
|
|
103
|
+
productId: i.id,
|
|
74
104
|
purchasedBy: t,
|
|
75
105
|
workspaceId: e.id,
|
|
76
106
|
metadata: {
|
|
77
107
|
source: "store-ui",
|
|
78
|
-
productType:
|
|
108
|
+
productType: i.type
|
|
79
109
|
}
|
|
80
110
|
} },
|
|
81
|
-
authToken:
|
|
111
|
+
authToken: k || void 0
|
|
82
112
|
});
|
|
83
113
|
}
|
|
84
|
-
let
|
|
114
|
+
let r = await h({
|
|
85
115
|
gateway: "global",
|
|
86
|
-
query:
|
|
116
|
+
query: y,
|
|
87
117
|
variables: {
|
|
88
|
-
orderId:
|
|
89
|
-
productId:
|
|
118
|
+
orderId: g ?? null,
|
|
119
|
+
productId: i.id,
|
|
90
120
|
workspaceId: e.id
|
|
91
121
|
},
|
|
92
|
-
authToken:
|
|
122
|
+
authToken: k || void 0
|
|
93
123
|
});
|
|
94
|
-
if (
|
|
95
|
-
let
|
|
96
|
-
if (
|
|
124
|
+
if (r.errors?.length) throw Error(r.errors[0]?.message ?? "Installation failed");
|
|
125
|
+
let a = r.data?.installPurchasedApp;
|
|
126
|
+
if (a?.success) a.alreadyInstalled ? M({
|
|
127
|
+
type: "setInfo",
|
|
128
|
+
info: a.message || "Already installed on this workspace"
|
|
129
|
+
}) : (D?.(e.id, e.name), n());
|
|
97
130
|
else {
|
|
98
|
-
let e =
|
|
99
|
-
|
|
131
|
+
let e = a?.error || "Installation failed";
|
|
132
|
+
M({
|
|
133
|
+
type: "setError",
|
|
134
|
+
error: C(e)
|
|
135
|
+
}), O?.(Error(e));
|
|
100
136
|
}
|
|
101
137
|
} catch (e) {
|
|
102
138
|
let t = e instanceof Error ? e.message : "Installation failed";
|
|
103
|
-
|
|
139
|
+
M({
|
|
140
|
+
type: "setError",
|
|
141
|
+
error: C(t)
|
|
142
|
+
}), O?.(Error(t));
|
|
104
143
|
} finally {
|
|
105
|
-
|
|
144
|
+
M({ type: "finishInstall" });
|
|
106
145
|
}
|
|
107
|
-
},
|
|
108
|
-
if (!
|
|
109
|
-
let e =
|
|
146
|
+
}, H = async () => {
|
|
147
|
+
if (!N) return;
|
|
148
|
+
let e = B.find((e) => e.id === N);
|
|
110
149
|
if (e) {
|
|
111
|
-
|
|
150
|
+
M({ type: "startInstall" });
|
|
112
151
|
try {
|
|
113
|
-
let t = await
|
|
152
|
+
let t = await h({
|
|
114
153
|
gateway: "workspace",
|
|
115
|
-
query:
|
|
154
|
+
query: _,
|
|
116
155
|
variables: {
|
|
117
156
|
workspaceIds: [e.id],
|
|
118
157
|
input: {
|
|
119
|
-
appName:
|
|
120
|
-
displayName:
|
|
121
|
-
version:
|
|
122
|
-
type:
|
|
158
|
+
appName: i.slug || i.name.toLowerCase().replace(/\s+/g, "-"),
|
|
159
|
+
displayName: i.name,
|
|
160
|
+
version: i.version || "1.0.0",
|
|
161
|
+
type: i.type || "app",
|
|
123
162
|
manifest: {
|
|
124
|
-
productId:
|
|
125
|
-
icon:
|
|
163
|
+
productId: i.id,
|
|
164
|
+
icon: i.icon
|
|
126
165
|
}
|
|
127
166
|
}
|
|
128
167
|
},
|
|
129
|
-
authToken:
|
|
168
|
+
authToken: k || void 0
|
|
130
169
|
});
|
|
131
170
|
if (t.errors?.length) throw Error(t.errors[0]?.message ?? "Installation failed");
|
|
132
|
-
let
|
|
133
|
-
if (
|
|
171
|
+
let r = t.data?.installWorkspaceApps;
|
|
172
|
+
if (r?.successCount && r.successCount > 0) {
|
|
134
173
|
try {
|
|
135
|
-
await
|
|
174
|
+
await h({
|
|
136
175
|
gateway: "global",
|
|
137
|
-
query:
|
|
176
|
+
query: v,
|
|
138
177
|
variables: { input: {
|
|
139
|
-
productId:
|
|
178
|
+
productId: i.id,
|
|
140
179
|
workspaceId: e.id,
|
|
141
180
|
workspaceName: e.name,
|
|
142
181
|
workspaceType: e.type,
|
|
143
182
|
organizationId: e.organizationId,
|
|
144
|
-
version:
|
|
183
|
+
version: i.version || "1.0.0",
|
|
145
184
|
manifest: {
|
|
146
|
-
productId:
|
|
147
|
-
icon:
|
|
148
|
-
slug:
|
|
149
|
-
type:
|
|
185
|
+
productId: i.id,
|
|
186
|
+
icon: i.icon,
|
|
187
|
+
slug: i.slug,
|
|
188
|
+
type: i.type
|
|
150
189
|
}
|
|
151
190
|
} },
|
|
152
|
-
authToken:
|
|
191
|
+
authToken: k || void 0
|
|
153
192
|
});
|
|
154
193
|
} catch {}
|
|
155
|
-
|
|
194
|
+
D?.(e.id, e.name), n();
|
|
156
195
|
} else {
|
|
157
|
-
let e =
|
|
158
|
-
|
|
196
|
+
let e = r?.results?.[0]?.error || "Installation failed";
|
|
197
|
+
M({
|
|
198
|
+
type: "setError",
|
|
199
|
+
error: C(e)
|
|
200
|
+
}), O?.(Error(e));
|
|
159
201
|
}
|
|
160
202
|
} catch (e) {
|
|
161
203
|
let t = e instanceof Error ? e.message : "Installation failed";
|
|
162
|
-
|
|
204
|
+
M({
|
|
205
|
+
type: "setError",
|
|
206
|
+
error: C(t)
|
|
207
|
+
}), O?.(Error(t));
|
|
163
208
|
} finally {
|
|
164
|
-
|
|
209
|
+
M({ type: "finishInstall" });
|
|
165
210
|
}
|
|
166
211
|
}
|
|
167
212
|
};
|
|
168
|
-
return t ? /* @__PURE__ */
|
|
213
|
+
return t ? /* @__PURE__ */ m("div", {
|
|
169
214
|
className: "fixed inset-0 z-50 flex items-center justify-center",
|
|
170
|
-
children: [/* @__PURE__ */
|
|
215
|
+
children: [/* @__PURE__ */ p("button", {
|
|
216
|
+
type: "button",
|
|
217
|
+
"aria-label": "Close modal",
|
|
171
218
|
className: "absolute inset-0 bg-bg-overlay/50 backdrop-blur-sm",
|
|
172
|
-
onClick:
|
|
173
|
-
}), /* @__PURE__ */
|
|
174
|
-
|
|
219
|
+
onClick: n
|
|
220
|
+
}), /* @__PURE__ */ m("dialog", {
|
|
221
|
+
open: !0,
|
|
175
222
|
"aria-modal": "true",
|
|
176
223
|
"aria-labelledby": "install-app-modal-title",
|
|
177
224
|
className: "relative z-10 mx-4 w-full max-w-lg rounded-2xl border border-border-default bg-bg-surface shadow-2xl",
|
|
178
225
|
children: [
|
|
179
|
-
/* @__PURE__ */
|
|
226
|
+
/* @__PURE__ */ m("div", {
|
|
180
227
|
className: "flex items-center justify-between border-b border-border-default px-6 py-4",
|
|
181
|
-
children: [/* @__PURE__ */
|
|
228
|
+
children: [/* @__PURE__ */ m("div", {
|
|
182
229
|
className: "flex items-center gap-3",
|
|
183
|
-
children: [
|
|
184
|
-
src:
|
|
185
|
-
alt:
|
|
186
|
-
className: "
|
|
187
|
-
}) : /* @__PURE__ */
|
|
188
|
-
className: "flex
|
|
189
|
-
children:
|
|
190
|
-
}), /* @__PURE__ */
|
|
230
|
+
children: [i.icon ? /* @__PURE__ */ p("img", {
|
|
231
|
+
src: i.icon,
|
|
232
|
+
alt: i.name,
|
|
233
|
+
className: "size-10 rounded-lg object-cover"
|
|
234
|
+
}) : /* @__PURE__ */ p("div", {
|
|
235
|
+
className: "flex size-10 items-center justify-center rounded-lg bg-bg-sunken text-lg font-bold text-text-muted",
|
|
236
|
+
children: i.name.charAt(0)
|
|
237
|
+
}), /* @__PURE__ */ m("div", { children: [/* @__PURE__ */ p("h2", {
|
|
191
238
|
id: "install-app-modal-title",
|
|
192
239
|
className: "text-lg font-semibold text-text-primary",
|
|
193
240
|
children: "Install App"
|
|
194
|
-
}), /* @__PURE__ */
|
|
241
|
+
}), /* @__PURE__ */ p("p", {
|
|
195
242
|
className: "text-sm text-text-muted",
|
|
196
|
-
children:
|
|
243
|
+
children: i.name
|
|
197
244
|
})] })]
|
|
198
|
-
}), /* @__PURE__ */
|
|
199
|
-
|
|
245
|
+
}), /* @__PURE__ */ p("button", {
|
|
246
|
+
type: "button",
|
|
247
|
+
onClick: n,
|
|
200
248
|
"aria-label": "Close install dialog",
|
|
201
249
|
className: "cursor-pointer rounded-lg p-2 text-text-muted transition-colors hover:bg-bg-sunken hover:text-text-primary",
|
|
202
|
-
children: /* @__PURE__ */
|
|
250
|
+
children: /* @__PURE__ */ p(d, { className: "size-5" })
|
|
203
251
|
})]
|
|
204
252
|
}),
|
|
205
|
-
/* @__PURE__ */
|
|
253
|
+
/* @__PURE__ */ m("div", {
|
|
206
254
|
className: "px-6 py-4",
|
|
207
|
-
children: [/* @__PURE__ */
|
|
255
|
+
children: [/* @__PURE__ */ p("p", {
|
|
208
256
|
className: "mb-4 text-text-secondary",
|
|
209
257
|
children: "Select the workspace where you want to install this app:"
|
|
210
|
-
}), /* @__PURE__ */
|
|
258
|
+
}), /* @__PURE__ */ p("div", {
|
|
211
259
|
className: "max-h-72 overflow-y-auto rounded-lg border border-border-default",
|
|
212
|
-
children:
|
|
260
|
+
children: R ? /* @__PURE__ */ p("div", {
|
|
213
261
|
className: "flex items-center justify-center py-12",
|
|
214
|
-
children: /* @__PURE__ */
|
|
215
|
-
}) :
|
|
262
|
+
children: /* @__PURE__ */ p(l, { className: "size-8 animate-spin text-text-muted" })
|
|
263
|
+
}) : z ? /* @__PURE__ */ m("div", {
|
|
216
264
|
className: "flex flex-col items-center justify-center py-12 text-center",
|
|
217
265
|
children: [
|
|
218
|
-
/* @__PURE__ */
|
|
219
|
-
/* @__PURE__ */
|
|
266
|
+
/* @__PURE__ */ p(a, { className: "mb-2 size-8 text-status-error-text" }),
|
|
267
|
+
/* @__PURE__ */ p("p", {
|
|
220
268
|
className: "text-sm text-status-error-text",
|
|
221
269
|
children: "Failed to load workspaces"
|
|
222
270
|
}),
|
|
223
|
-
/* @__PURE__ */
|
|
271
|
+
/* @__PURE__ */ p("p", {
|
|
224
272
|
className: "text-xs text-text-muted mt-1",
|
|
225
|
-
children:
|
|
273
|
+
children: z.message
|
|
226
274
|
})
|
|
227
275
|
]
|
|
228
|
-
}) :
|
|
276
|
+
}) : B.length === 0 ? /* @__PURE__ */ m("div", {
|
|
229
277
|
className: "flex flex-col items-center justify-center py-12 text-center",
|
|
230
|
-
children: [/* @__PURE__ */
|
|
278
|
+
children: [/* @__PURE__ */ p(o, { className: "mb-2 size-8 text-text-muted" }), /* @__PURE__ */ p("p", {
|
|
231
279
|
className: "text-sm text-text-muted",
|
|
232
280
|
children: "No workspaces available"
|
|
233
281
|
})]
|
|
234
|
-
}) : /* @__PURE__ */
|
|
282
|
+
}) : /* @__PURE__ */ p("div", {
|
|
235
283
|
className: "divide-y divide-border-default",
|
|
236
|
-
children:
|
|
237
|
-
|
|
238
|
-
|
|
284
|
+
children: B.map((e) => /* @__PURE__ */ m("button", {
|
|
285
|
+
type: "button",
|
|
286
|
+
onClick: () => M({
|
|
287
|
+
type: "selectWorkspace",
|
|
288
|
+
workspaceId: e.id
|
|
289
|
+
}),
|
|
290
|
+
className: `w-full cursor-pointer flex items-center gap-3 px-4 py-3 text-left transition-colors ${N === e.id ? "bg-action-primary-bg/10" : "transition-colors hover:bg-bg-sunken"}`,
|
|
239
291
|
children: [
|
|
240
|
-
/* @__PURE__ */ f("div", {
|
|
241
|
-
className: `flex h-10 w-10 items-center justify-center rounded-lg ${e.type === "user" ? "bg-status-info-bg-subtle text-status-info-text" : "bg-action-primary-bg/10 text-text-link"}`,
|
|
242
|
-
children: e.type === "user" ? /* @__PURE__ */ f(l, { className: "h-5 w-5" }) : /* @__PURE__ */ f(a, { className: "h-5 w-5" })
|
|
243
|
-
}),
|
|
244
292
|
/* @__PURE__ */ p("div", {
|
|
293
|
+
className: `flex size-10 items-center justify-center rounded-lg ${e.type === "user" ? "bg-status-info-bg-subtle text-status-info-text" : "bg-action-primary-bg/10 text-text-link"}`,
|
|
294
|
+
children: e.type === "user" ? /* @__PURE__ */ p(u, { className: "size-5" }) : /* @__PURE__ */ p(o, { className: "size-5" })
|
|
295
|
+
}),
|
|
296
|
+
/* @__PURE__ */ m("div", {
|
|
245
297
|
className: "flex-1 min-w-0",
|
|
246
|
-
children: [/* @__PURE__ */
|
|
298
|
+
children: [/* @__PURE__ */ p("p", {
|
|
247
299
|
className: "font-medium text-text-primary truncate",
|
|
248
300
|
children: e.name
|
|
249
|
-
}), /* @__PURE__ */
|
|
301
|
+
}), /* @__PURE__ */ m("p", {
|
|
250
302
|
className: "text-xs text-text-muted",
|
|
251
303
|
children: [e.type === "user" ? "Personal" : "Team", " workspace"]
|
|
252
304
|
})]
|
|
253
305
|
}),
|
|
254
|
-
|
|
306
|
+
N === e.id && /* @__PURE__ */ p(s, { className: "size-5 flex-shrink-0 text-action-primary-bg" })
|
|
255
307
|
]
|
|
256
308
|
}, e.id))
|
|
257
309
|
})
|
|
258
310
|
})]
|
|
259
311
|
}),
|
|
260
|
-
/* @__PURE__ */
|
|
312
|
+
/* @__PURE__ */ m("div", {
|
|
261
313
|
className: "border-t border-border-default px-6 py-4",
|
|
262
314
|
children: [
|
|
263
|
-
I && /* @__PURE__ */
|
|
315
|
+
I && /* @__PURE__ */ m("div", {
|
|
264
316
|
className: "mb-3 flex items-center gap-2 rounded-lg bg-status-info-bg-subtle px-3 py-2 text-sm text-status-info-text",
|
|
265
|
-
children: [/* @__PURE__ */
|
|
317
|
+
children: [/* @__PURE__ */ p(c, { className: "size-4 flex-shrink-0" }), /* @__PURE__ */ p("span", { children: I })]
|
|
266
318
|
}),
|
|
267
|
-
|
|
319
|
+
F && /* @__PURE__ */ m("div", {
|
|
268
320
|
className: "mb-3 flex items-center gap-2 rounded-lg bg-status-error-bg-subtle/10 px-3 py-2 text-sm text-status-error-text",
|
|
269
|
-
children: [/* @__PURE__ */
|
|
321
|
+
children: [/* @__PURE__ */ p(a, { className: "size-4 flex-shrink-0" }), /* @__PURE__ */ p("span", { children: F })]
|
|
270
322
|
}),
|
|
271
|
-
/* @__PURE__ */
|
|
323
|
+
/* @__PURE__ */ m("div", {
|
|
272
324
|
className: "flex items-center justify-end gap-3",
|
|
273
|
-
children: [/* @__PURE__ */
|
|
274
|
-
|
|
275
|
-
|
|
325
|
+
children: [/* @__PURE__ */ p("button", {
|
|
326
|
+
type: "button",
|
|
327
|
+
onClick: n,
|
|
328
|
+
disabled: P,
|
|
276
329
|
className: "cursor-pointer rounded-lg border border-border-default px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken disabled:opacity-50",
|
|
277
330
|
children: "Cancel"
|
|
278
|
-
}), /* @__PURE__ */
|
|
331
|
+
}), /* @__PURE__ */ p("button", {
|
|
332
|
+
type: "button",
|
|
279
333
|
onClick: async () => {
|
|
280
|
-
let e =
|
|
281
|
-
|
|
334
|
+
let e = i.type?.toUpperCase() ?? "";
|
|
335
|
+
S === "purchased" || g || x.has(e) ? await V() : await H();
|
|
282
336
|
},
|
|
283
|
-
disabled: !
|
|
337
|
+
disabled: !N || P,
|
|
284
338
|
className: "cursor-pointer rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-opacity hover:opacity-90 disabled:opacity-50 disabled:cursor-not-allowed inline-flex items-center gap-2",
|
|
285
|
-
children:
|
|
339
|
+
children: P ? /* @__PURE__ */ m(f, { children: [/* @__PURE__ */ p(l, { className: "size-4 animate-spin" }), "Installing…"] }) : "Install App"
|
|
286
340
|
})]
|
|
287
341
|
})
|
|
288
342
|
]
|
|
@@ -292,6 +346,6 @@ var w = ({ isOpen: t, onClose: h, app: x, orderId: w, purchaseState: T = "free",
|
|
|
292
346
|
}) : null;
|
|
293
347
|
};
|
|
294
348
|
//#endregion
|
|
295
|
-
export {
|
|
349
|
+
export { D as InstallAppModal };
|
|
296
350
|
|
|
297
351
|
//# sourceMappingURL=InstallAppModal.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InstallAppModal.js","names":[],"sources":["../../src/components/InstallAppModal.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState, useEffect, useCallback } from 'react';\nimport { X, Loader2, Building2, User, CheckCircle, AlertCircle, Info } from 'lucide-react';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\nimport { useStore } from '../providers/StoreProvider';\n\n// Local type definitions for workspace operations\ninterface Workspace {\n id: string;\n name: string;\n slug: string;\n type?: string;\n status?: string;\n organizationId?: string;\n}\n\ninterface WorkspaceList {\n items: Workspace[];\n total: number;\n}\n\ninterface InstallWorkspaceAppsResult {\n successCount: number;\n failedCount: number;\n results: Array<{\n success: boolean;\n workspaceId: string;\n app?: unknown;\n error?: string;\n }>;\n}\n\n// String queries for graphqlFetch\nconst MY_WORKSPACES_STRING = `query MyWorkspaces { myWorkspaces { items { id name slug type status organizationId } total } }`;\nconst INSTALL_WORKSPACE_APPS_STRING = `mutation InstallWorkspaceApps($workspaceIds: [ID!]!, $input: InstallWorkspaceAppInput!) { installWorkspaceApps(workspaceIds: $workspaceIds, input: $input) { successCount failedCount results { success workspaceId error } } }`;\nconst RECORD_APP_INSTALLATION_STRING = `mutation RecordAppInstallation($input: RecordAppInstallationInput!) { recordAppInstallation(input: $input) { id status workspaceId version } }`;\nconst INSTALL_PURCHASED_APP_STRING = `mutation InstallPurchasedApp($orderId: ID, $productId: String!, $workspaceId: String!) { installPurchasedApp(orderId: $orderId, productId: $productId, workspaceId: $workspaceId) { success alreadyInstalled installation { id productId workspaceId status version } error message } }`;\nconst CREATE_PRODUCT_ENTITLEMENT_STRING = `mutation CreateProductEntitlement($input: CreateProductEntitlementInput!) { createProductEntitlement(input: $input) { id status productId workspaceId } }`;\nconst STORE_DISTRIBUTED_TYPES = new Set([\n 'VIBEMODULE',\n 'NODE',\n 'CONSOLE',\n 'DASHBOARD',\n 'PARSER',\n 'DATASINK',\n]);\n\nconst INSTALL_ERROR_MESSAGES: Record<string, string> = {\n ALREADY_INSTALLED: 'This app is already installed in the selected workspace.',\n QUOTA_EXCEEDED: 'Installation limit reached. Please upgrade your plan or contact support.',\n PERMISSION_DENIED: 'You do not have permission to install apps in this workspace.',\n NOT_FOUND: 'The app or workspace could not be found.',\n WORKSPACE_INACTIVE: 'The selected workspace is inactive.',\n ENTITLEMENT_EXPIRED: 'Your license for this app has expired.',\n UNSUPPORTED_TYPE: 'This product type cannot be installed in the selected workspace.',\n};\n\nfunction mapInstallError(raw: string): string {\n for (const [code, message] of Object.entries(INSTALL_ERROR_MESSAGES)) {\n if (raw.toUpperCase().includes(code)) return message;\n }\n return 'Installation failed. Please try again or contact support.';\n}\n\nexport interface AppInfo {\n id: string;\n name: string;\n slug?: string;\n icon?: string;\n type?: string;\n version?: string;\n}\n\ninterface InstallAppModalProps {\n isOpen: boolean;\n onClose: () => void;\n app: AppInfo;\n orderId?: string | null; // For purchased apps from orders page\n purchaseState?: 'free' | 'purchased';\n onInstallSuccess?: (workspaceId: string, workspaceName: string) => void;\n onInstallError?: (error: Error) => void;\n}\n\ninterface UseWorkspacesResult {\n workspaces: Workspace[];\n loading: boolean;\n error: Error | null;\n refetch: () => void;\n}\n\n// NOTE: Inline Apollo clients removed. All GraphQL calls now use graphqlFetch\n// from fe-libs which automatically resolves gateway URLs and injects auth + locale headers.\n\n/**\n * Hook to fetch user's workspaces using graphqlFetch from fe-libs\n */\nfunction useWorkspaces(skip: boolean): UseWorkspacesResult {\n const { authToken } = useStore();\n const [workspaces, setWorkspaces] = useState<Workspace[]>([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n const fetchWorkspaces = useCallback(async () => {\n if (skip) return;\n\n setLoading(true);\n setError(null);\n\n try {\n const result = await graphqlFetch<{ myWorkspaces: WorkspaceList }>({\n gateway: 'global',\n query: MY_WORKSPACES_STRING,\n authToken: authToken || undefined,\n });\n\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'GraphQL error');\n }\n\n setWorkspaces(result.data?.myWorkspaces?.items || []);\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Failed to fetch workspaces'));\n } finally {\n setLoading(false);\n }\n }, [authToken, skip]);\n\n useEffect(() => {\n fetchWorkspaces();\n }, [fetchWorkspaces]);\n\n return {\n workspaces,\n loading,\n error,\n refetch: fetchWorkspaces,\n };\n}\n\n/**\n * InstallAppModal component\n *\n * Modal for selecting a workspace to install an app into\n */\nexport const InstallAppModal: FC<InstallAppModalProps> = ({\n isOpen,\n onClose,\n app,\n orderId,\n purchaseState = 'free',\n onInstallSuccess,\n onInstallError,\n}) => {\n const { authToken, user } = useStore();\n const [selectedWorkspace, setSelectedWorkspace] = useState<string | null>(null);\n const [installing, setInstalling] = useState(false);\n const [installError, setInstallError] = useState<string | null>(null);\n const [installInfo, setInstallInfo] = useState<string | null>(null);\n\n const { workspaces, loading, error } = useWorkspaces(!isOpen);\n\n // Reset selection when modal opens\n useEffect(() => {\n if (isOpen) {\n setSelectedWorkspace(null);\n setInstallError(null);\n setInstallInfo(null);\n }\n }, [isOpen]);\n\n // Filter workspaces by status (only show active)\n const filteredWorkspaces = workspaces.filter((ws) => ws.status === 'ACTIVE');\n\n /**\n * Install purchased app via Store Service (NEW FLOW)\n * Used when installing from orders page or any already-purchased product\n */\n const handleInstallFromStore = async () => {\n if (!selectedWorkspace) return;\n\n const workspace = filteredWorkspaces.find((ws) => ws.id === selectedWorkspace);\n if (!workspace) return;\n const purchasedBy = user?.id;\n\n setInstalling(true);\n setInstallError(null);\n setInstallInfo(null);\n\n try {\n if (!orderId && purchaseState !== 'purchased') {\n if (!purchasedBy) {\n throw new Error('Unable to identify the current user for license creation');\n }\n\n await graphqlFetch({\n gateway: 'global',\n query: CREATE_PRODUCT_ENTITLEMENT_STRING,\n variables: {\n input: {\n productId: app.id,\n purchasedBy,\n workspaceId: workspace.id,\n metadata: {\n source: 'store-ui',\n productType: app.type,\n },\n },\n },\n authToken: authToken || undefined,\n });\n }\n\n // Call Store Service to install the purchased app\n const result = await graphqlFetch<{\n installPurchasedApp: {\n success: boolean;\n alreadyInstalled?: boolean;\n installation?: {\n id: string;\n productId: string;\n workspaceId: string;\n status: string;\n version: string;\n };\n error?: string;\n message?: string;\n };\n }>({\n gateway: 'global', // Store Service\n query: INSTALL_PURCHASED_APP_STRING,\n variables: {\n orderId: orderId ?? null,\n productId: app.id,\n workspaceId: workspace.id,\n },\n authToken: authToken || undefined,\n });\n\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Installation failed');\n }\n\n const installResult = result.data?.installPurchasedApp;\n\n if (installResult?.success) {\n if (installResult.alreadyInstalled) {\n setInstallInfo(installResult.message || 'Already installed on this workspace');\n } else {\n onInstallSuccess?.(workspace.id, workspace.name);\n onClose();\n }\n } else {\n const rawMsg = installResult?.error || 'Installation failed';\n const errorMsg = mapInstallError(rawMsg);\n setInstallError(errorMsg);\n onInstallError?.(new Error(rawMsg));\n }\n } catch (err) {\n const rawMsg = err instanceof Error ? err.message : 'Installation failed';\n const errorMsg = mapInstallError(rawMsg);\n setInstallError(errorMsg);\n onInstallError?.(new Error(rawMsg));\n } finally {\n setInstalling(false);\n }\n };\n\n /**\n * Install app via Workspace Service (LEGACY FLOW)\n * Used for free apps or when installing without order\n */\n const handleInstallViaWorkspace = async () => {\n if (!selectedWorkspace) return;\n\n const workspace = filteredWorkspaces.find((ws) => ws.id === selectedWorkspace);\n if (!workspace) return;\n\n setInstalling(true);\n setInstallError(null);\n setInstallInfo(null);\n\n try {\n // Step 1: Install to workspace service via graphqlFetch\n const installVars = {\n workspaceIds: [workspace.id],\n input: {\n appName: app.slug || app.name.toLowerCase().replace(/\\s+/g, '-'),\n displayName: app.name,\n version: app.version || '1.0.0',\n type: app.type || 'app',\n manifest: {\n productId: app.id,\n icon: app.icon,\n },\n },\n };\n\n const result = await graphqlFetch<{\n installWorkspaceApps: InstallWorkspaceAppsResult;\n }>({\n gateway: 'workspace',\n query: INSTALL_WORKSPACE_APPS_STRING,\n variables: installVars,\n authToken: authToken || undefined,\n });\n\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Installation failed');\n }\n\n const installResult = result.data?.installWorkspaceApps;\n if (installResult?.successCount && installResult.successCount > 0) {\n // Step 2: Record installation in store service (for tracking)\n try {\n await graphqlFetch({\n gateway: 'global',\n query: RECORD_APP_INSTALLATION_STRING,\n variables: {\n input: {\n productId: app.id,\n workspaceId: workspace.id,\n workspaceName: workspace.name,\n workspaceType: workspace.type,\n organizationId: workspace.organizationId,\n version: app.version || '1.0.0',\n manifest: {\n productId: app.id,\n icon: app.icon,\n slug: app.slug,\n type: app.type,\n },\n },\n },\n authToken: authToken || undefined,\n });\n } catch {\n // Non-fatal: workspace installation succeeded; store record will sync later.\n }\n\n onInstallSuccess?.(workspace.id, workspace.name);\n onClose();\n } else {\n const rawMsg = installResult?.results?.[0]?.error || 'Installation failed';\n const errorMsg = mapInstallError(rawMsg);\n setInstallError(errorMsg);\n onInstallError?.(new Error(rawMsg));\n }\n } catch (err) {\n const rawMsg = err instanceof Error ? err.message : 'Installation failed';\n const errorMsg = mapInstallError(rawMsg);\n setInstallError(errorMsg);\n onInstallError?.(new Error(rawMsg));\n } finally {\n setInstalling(false);\n }\n };\n\n /**\n * Main install handler - routes to appropriate install function\n */\n const handleInstall = async () => {\n const appType = app.type?.toUpperCase() ?? '';\n\n if (purchaseState === 'purchased' || orderId || STORE_DISTRIBUTED_TYPES.has(appType)) {\n // Purchased products install through the store service.\n await handleInstallFromStore();\n } else {\n // Free products install through the workspace service.\n await handleInstallViaWorkspace();\n }\n };\n\n if (!isOpen) return null;\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center\">\n {/* Backdrop */}\n <div className=\"absolute inset-0 bg-bg-overlay/50 backdrop-blur-sm\" onClick={onClose} />\n\n {/* Modal */}\n <div\n role=\"dialog\"\n aria-modal=\"true\"\n aria-labelledby=\"install-app-modal-title\"\n className=\"relative z-10 mx-4 w-full max-w-lg rounded-2xl border border-border-default bg-bg-surface shadow-2xl\"\n >\n {/* Header */}\n <div className=\"flex items-center justify-between border-b border-border-default px-6 py-4\">\n <div className=\"flex items-center gap-3\">\n {app.icon ? (\n <img src={app.icon} alt={app.name} className=\"h-10 w-10 rounded-lg object-cover\" />\n ) : (\n <div className=\"flex h-10 w-10 items-center justify-center rounded-lg bg-bg-sunken text-lg font-bold text-text-muted\">\n {app.name.charAt(0)}\n </div>\n )}\n <div>\n <h2 id=\"install-app-modal-title\" className=\"text-lg font-semibold text-text-primary\">\n Install App\n </h2>\n <p className=\"text-sm text-text-muted\">{app.name}</p>\n </div>\n </div>\n <button\n onClick={onClose}\n aria-label=\"Close install dialog\"\n className=\"cursor-pointer rounded-lg p-2 text-text-muted transition-colors hover:bg-bg-sunken hover:text-text-primary\"\n >\n <X className=\"h-5 w-5\" />\n </button>\n </div>\n\n {/* Content */}\n <div className=\"px-6 py-4\">\n <p className=\"mb-4 text-text-secondary\">\n Select the workspace where you want to install this app:\n </p>\n\n {/* Workspace List */}\n <div className=\"max-h-72 overflow-y-auto rounded-lg border border-border-default\">\n {loading ? (\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 className=\"h-8 w-8 animate-spin text-text-muted\" />\n </div>\n ) : error ? (\n <div className=\"flex flex-col items-center justify-center py-12 text-center\">\n <AlertCircle className=\"mb-2 h-8 w-8 text-status-error-text\" />\n <p className=\"text-sm text-status-error-text\">Failed to load workspaces</p>\n <p className=\"text-xs text-text-muted mt-1\">{error.message}</p>\n </div>\n ) : filteredWorkspaces.length === 0 ? (\n <div className=\"flex flex-col items-center justify-center py-12 text-center\">\n <Building2 className=\"mb-2 h-8 w-8 text-text-muted\" />\n <p className=\"text-sm text-text-muted\">No workspaces available</p>\n </div>\n ) : (\n <div className=\"divide-y divide-border-default\">\n {filteredWorkspaces.map((workspace) => (\n <button\n key={workspace.id}\n onClick={() => setSelectedWorkspace(workspace.id)}\n className={`w-full cursor-pointer flex items-center gap-3 px-4 py-3 text-left transition-colors ${\n selectedWorkspace === workspace.id\n ? 'bg-action-primary-bg/10'\n : 'transition-colors hover:bg-bg-sunken'\n }`}\n >\n {/* Workspace Icon */}\n <div\n className={`flex h-10 w-10 items-center justify-center rounded-lg ${\n workspace.type === 'user'\n ? 'bg-status-info-bg-subtle text-status-info-text'\n : 'bg-action-primary-bg/10 text-text-link'\n }`}\n >\n {workspace.type === 'user' ? (\n <User className=\"h-5 w-5\" />\n ) : (\n <Building2 className=\"h-5 w-5\" />\n )}\n </div>\n\n {/* Workspace Info */}\n <div className=\"flex-1 min-w-0\">\n <p className=\"font-medium text-text-primary truncate\">{workspace.name}</p>\n <p className=\"text-xs text-text-muted\">\n {workspace.type === 'user' ? 'Personal' : 'Team'} workspace\n </p>\n </div>\n\n {/* Selection Indicator */}\n {selectedWorkspace === workspace.id && (\n <CheckCircle className=\"h-5 w-5 flex-shrink-0 text-action-primary-bg\" />\n )}\n </button>\n ))}\n </div>\n )}\n </div>\n </div>\n\n {/* Footer */}\n <div className=\"border-t border-border-default px-6 py-4\">\n {/* Already-installed info */}\n {installInfo && (\n <div className=\"mb-3 flex items-center gap-2 rounded-lg bg-status-info-bg-subtle px-3 py-2 text-sm text-status-info-text\">\n <Info className=\"h-4 w-4 flex-shrink-0\" />\n <span>{installInfo}</span>\n </div>\n )}\n {/* Error message */}\n {installError && (\n <div className=\"mb-3 flex items-center gap-2 rounded-lg bg-status-error-bg-subtle/10 px-3 py-2 text-sm text-status-error-text\">\n <AlertCircle className=\"h-4 w-4 flex-shrink-0\" />\n <span>{installError}</span>\n </div>\n )}\n <div className=\"flex items-center justify-end gap-3\">\n <button\n onClick={onClose}\n disabled={installing}\n className=\"cursor-pointer rounded-lg border border-border-default px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken disabled:opacity-50\"\n >\n Cancel\n </button>\n <button\n onClick={handleInstall}\n disabled={!selectedWorkspace || installing}\n className=\"cursor-pointer rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-opacity hover:opacity-90 disabled:opacity-50 disabled:cursor-not-allowed inline-flex items-center gap-2\"\n >\n {installing ? (\n <>\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n Installing...\n </>\n ) : (\n 'Install App'\n )}\n </button>\n </div>\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":";;;;;;AAiCA,IAAM,IAAuB,mGACvB,IAAgC,mOAChC,IAAiC,kJACjC,IAA+B,2RAC/B,IAAoC,6JACpC,IAA0B,IAAI,IAAI;CACtC;CACA;CACA;CACA;CACA;CACA;CACD,CAAC,EAEI,IAAiD;CACrD,mBAAmB;CACnB,gBAAgB;CAChB,mBAAmB;CACnB,WAAW;CACX,oBAAoB;CACpB,qBAAqB;CACrB,kBAAkB;CACnB;AAED,SAAS,EAAgB,GAAqB;AAC5C,MAAK,IAAM,CAAC,GAAM,MAAY,OAAO,QAAQ,EAAuB,CAClE,KAAI,EAAI,aAAa,CAAC,SAAS,EAAK,CAAE,QAAO;AAE/C,QAAO;;AAmCT,SAAS,EAAc,GAAoC;CACzD,IAAM,EAAE,iBAAc,GAAU,EAC1B,CAAC,GAAY,KAAiB,EAAsB,EAAE,CAAC,EACvD,CAAC,GAAS,KAAc,EAAS,GAAM,EACvC,CAAC,GAAO,KAAY,EAAuB,KAAK,EAEhD,IAAkB,EAAY,YAAY;AAC1C,UAGJ;GADA,EAAW,GAAK,EAChB,EAAS,KAAK;AAEd,OAAI;IACF,IAAM,IAAS,MAAM,EAA8C;KACjE,SAAS;KACT,OAAO;KACP,WAAW,KAAa,KAAA;KACzB,CAAC;AAEF,QAAI,EAAO,QAAQ,OACjB,OAAU,MAAM,EAAO,OAAO,IAAI,WAAW,gBAAgB;AAG/D,MAAc,EAAO,MAAM,cAAc,SAAS,EAAE,CAAC;YAC9C,GAAK;AACZ,MAAS,aAAe,QAAQ,IAAM,gBAAI,MAAM,6BAA6B,CAAC;aACtE;AACR,MAAW,GAAM;;;IAElB,CAAC,GAAW,EAAK,CAAC;AAMrB,QAJA,QAAgB;AACd,KAAiB;IAChB,CAAC,EAAgB,CAAC,EAEd;EACL;EACA;EACA;EACA,SAAS;EACV;;AAQH,IAAa,KAA6C,EACxD,WACA,YACA,QACA,YACA,mBAAgB,QAChB,qBACA,wBACI;CACJ,IAAM,EAAE,cAAW,YAAS,GAAU,EAChC,CAAC,GAAmB,KAAwB,EAAwB,KAAK,EACzE,CAAC,GAAY,KAAiB,EAAS,GAAM,EAC7C,CAAC,GAAc,KAAmB,EAAwB,KAAK,EAC/D,CAAC,GAAa,KAAkB,EAAwB,KAAK,EAE7D,EAAE,eAAY,YAAS,aAAU,EAAc,CAAC,EAAO;AAG7D,SAAgB;AACd,EAAI,MACF,EAAqB,KAAK,EAC1B,EAAgB,KAAK,EACrB,EAAe,KAAK;IAErB,CAAC,EAAO,CAAC;CAGZ,IAAM,IAAqB,EAAW,QAAQ,MAAO,EAAG,WAAW,SAAS,EAMtE,IAAyB,YAAY;AACzC,MAAI,CAAC,EAAmB;EAExB,IAAM,IAAY,EAAmB,MAAM,MAAO,EAAG,OAAO,EAAkB;AAC9E,MAAI,CAAC,EAAW;EAChB,IAAM,IAAc,GAAM;AAI1B,EAFA,EAAc,GAAK,EACnB,EAAgB,KAAK,EACrB,EAAe,KAAK;AAEpB,MAAI;AACF,OAAI,CAAC,KAAW,MAAkB,aAAa;AAC7C,QAAI,CAAC,EACH,OAAU,MAAM,2DAA2D;AAG7E,UAAM,EAAa;KACjB,SAAS;KACT,OAAO;KACP,WAAW,EACT,OAAO;MACL,WAAW,EAAI;MACf;MACA,aAAa,EAAU;MACvB,UAAU;OACR,QAAQ;OACR,aAAa,EAAI;OAClB;MACF,EACF;KACD,WAAW,KAAa,KAAA;KACzB,CAAC;;GAIJ,IAAM,IAAS,MAAM,EAclB;IACD,SAAS;IACT,OAAO;IACP,WAAW;KACT,SAAS,KAAW;KACpB,WAAW,EAAI;KACf,aAAa,EAAU;KACxB;IACD,WAAW,KAAa,KAAA;IACzB,CAAC;AAEF,OAAI,EAAO,QAAQ,OACjB,OAAU,MAAM,EAAO,OAAO,IAAI,WAAW,sBAAsB;GAGrE,IAAM,IAAgB,EAAO,MAAM;AAEnC,OAAI,GAAe,QACjB,CAAI,EAAc,mBAChB,EAAe,EAAc,WAAW,sCAAsC,IAE9E,IAAmB,EAAU,IAAI,EAAU,KAAK,EAChD,GAAS;QAEN;IACL,IAAM,IAAS,GAAe,SAAS;AAGvC,IADA,EADiB,EAAgB,EAAO,CACf,EACzB,IAAqB,MAAM,EAAO,CAAC;;WAE9B,GAAK;GACZ,IAAM,IAAS,aAAe,QAAQ,EAAI,UAAU;AAGpD,GADA,EADiB,EAAgB,EAAO,CACf,EACzB,IAAqB,MAAM,EAAO,CAAC;YAC3B;AACR,KAAc,GAAM;;IAQlB,IAA4B,YAAY;AAC5C,MAAI,CAAC,EAAmB;EAExB,IAAM,IAAY,EAAmB,MAAM,MAAO,EAAG,OAAO,EAAkB;AACzE,SAIL;GAFA,EAAc,GAAK,EACnB,EAAgB,KAAK,EACrB,EAAe,KAAK;AAEpB,OAAI;IAgBF,IAAM,IAAS,MAAM,EAElB;KACD,SAAS;KACT,OAAO;KACP,WAnBkB;MAClB,cAAc,CAAC,EAAU,GAAG;MAC5B,OAAO;OACL,SAAS,EAAI,QAAQ,EAAI,KAAK,aAAa,CAAC,QAAQ,QAAQ,IAAI;OAChE,aAAa,EAAI;OACjB,SAAS,EAAI,WAAW;OACxB,MAAM,EAAI,QAAQ;OAClB,UAAU;QACR,WAAW,EAAI;QACf,MAAM,EAAI;QACX;OACF;MACF;KAQC,WAAW,KAAa,KAAA;KACzB,CAAC;AAEF,QAAI,EAAO,QAAQ,OACjB,OAAU,MAAM,EAAO,OAAO,IAAI,WAAW,sBAAsB;IAGrE,IAAM,IAAgB,EAAO,MAAM;AACnC,QAAI,GAAe,gBAAgB,EAAc,eAAe,GAAG;AAEjE,SAAI;AACF,YAAM,EAAa;OACjB,SAAS;OACT,OAAO;OACP,WAAW,EACT,OAAO;QACL,WAAW,EAAI;QACf,aAAa,EAAU;QACvB,eAAe,EAAU;QACzB,eAAe,EAAU;QACzB,gBAAgB,EAAU;QAC1B,SAAS,EAAI,WAAW;QACxB,UAAU;SACR,WAAW,EAAI;SACf,MAAM,EAAI;SACV,MAAM,EAAI;SACV,MAAM,EAAI;SACX;QACF,EACF;OACD,WAAW,KAAa,KAAA;OACzB,CAAC;aACI;AAKR,KADA,IAAmB,EAAU,IAAI,EAAU,KAAK,EAChD,GAAS;WACJ;KACL,IAAM,IAAS,GAAe,UAAU,IAAI,SAAS;AAGrD,KADA,EADiB,EAAgB,EAAO,CACf,EACzB,IAAqB,MAAM,EAAO,CAAC;;YAE9B,GAAK;IACZ,IAAM,IAAS,aAAe,QAAQ,EAAI,UAAU;AAGpD,IADA,EADiB,EAAgB,EAAO,CACf,EACzB,IAAqB,MAAM,EAAO,CAAC;aAC3B;AACR,MAAc,GAAM;;;;AAqBxB,QAFK,IAGH,kBAAC,OAAD;EAAK,WAAU;YAAf,CAEE,kBAAC,OAAD;GAAK,WAAU;GAAqD,SAAS;GAAW,CAAA,EAGxF,kBAAC,OAAD;GACE,MAAK;GACL,cAAW;GACX,mBAAgB;GAChB,WAAU;aAJZ;IAOE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACG,EAAI,OACH,kBAAC,OAAD;OAAK,KAAK,EAAI;OAAM,KAAK,EAAI;OAAM,WAAU;OAAsC,CAAA,GAEnF,kBAAC,OAAD;OAAK,WAAU;iBACZ,EAAI,KAAK,OAAO,EAAE;OACf,CAAA,EAER,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,MAAD;OAAI,IAAG;OAA0B,WAAU;iBAA0C;OAEhF,CAAA,EACL,kBAAC,KAAD;OAAG,WAAU;iBAA2B,EAAI;OAAS,CAAA,CACjD,EAAA,CAAA,CACF;SACN,kBAAC,UAAD;MACE,SAAS;MACT,cAAW;MACX,WAAU;gBAEV,kBAAC,GAAD,EAAG,WAAU,WAAY,CAAA;MAClB,CAAA,CACL;;IAGN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,KAAD;MAAG,WAAU;gBAA2B;MAEpC,CAAA,EAGJ,kBAAC,OAAD;MAAK,WAAU;gBACZ,IACC,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,GAAD,EAAS,WAAU,wCAAyC,CAAA;OACxD,CAAA,GACJ,IACF,kBAAC,OAAD;OAAK,WAAU;iBAAf;QACE,kBAAC,GAAD,EAAa,WAAU,uCAAwC,CAAA;QAC/D,kBAAC,KAAD;SAAG,WAAU;mBAAiC;SAA6B,CAAA;QAC3E,kBAAC,KAAD;SAAG,WAAU;mBAAgC,EAAM;SAAY,CAAA;QAC3D;WACJ,EAAmB,WAAW,IAChC,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAW,WAAU,gCAAiC,CAAA,EACtD,kBAAC,KAAD;QAAG,WAAU;kBAA0B;QAA2B,CAAA,CAC9D;WAEN,kBAAC,OAAD;OAAK,WAAU;iBACZ,EAAmB,KAAK,MACvB,kBAAC,UAAD;QAEE,eAAe,EAAqB,EAAU,GAAG;QACjD,WAAW,uFACT,MAAsB,EAAU,KAC5B,4BACA;kBANR;SAUE,kBAAC,OAAD;UACE,WAAW,yDACT,EAAU,SAAS,SACf,mDACA;oBAGL,EAAU,SAAS,SAClB,kBAAC,GAAD,EAAM,WAAU,WAAY,CAAA,GAE5B,kBAAC,GAAD,EAAW,WAAU,WAAY,CAAA;UAE/B,CAAA;SAGN,kBAAC,OAAD;UAAK,WAAU;oBAAf,CACE,kBAAC,KAAD;WAAG,WAAU;qBAA0C,EAAU;WAAS,CAAA,EAC1E,kBAAC,KAAD;WAAG,WAAU;qBAAb,CACG,EAAU,SAAS,SAAS,aAAa,QAAO,aAC/C;aACA;;SAGL,MAAsB,EAAU,MAC/B,kBAAC,GAAD,EAAa,WAAU,gDAAiD,CAAA;SAEnE;UAnCF,EAAU,GAmCR,CACT;OACE,CAAA;MAEJ,CAAA,CACF;;IAGN,kBAAC,OAAD;KAAK,WAAU;eAAf;MAEG,KACC,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAM,WAAU,yBAA0B,CAAA,EAC1C,kBAAC,QAAD,EAAA,UAAO,GAAmB,CAAA,CACtB;;MAGP,KACC,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,GAAD,EAAa,WAAU,yBAA0B,CAAA,EACjD,kBAAC,QAAD,EAAA,UAAO,GAAoB,CAAA,CACvB;;MAER,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,UAAD;QACE,SAAS;QACT,UAAU;QACV,WAAU;kBACX;QAEQ,CAAA,EACT,kBAAC,UAAD;QACE,SAlJU,YAAY;SAChC,IAAM,IAAU,EAAI,MAAM,aAAa,IAAI;AAE3C,SAAI,MAAkB,eAAe,KAAW,EAAwB,IAAI,EAAQ,GAElF,MAAM,GAAwB,GAG9B,MAAM,GAA2B;;QA2IzB,UAAU,CAAC,KAAqB;QAChC,WAAU;kBAET,IACC,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD,EAAS,WAAU,wBAAyB,CAAA,EAAA,gBAE3C,EAAA,CAAA,GAEH;QAEK,CAAA,CACL;;MACF;;IACF;KACF;MAtJY"}
|
|
1
|
+
{"version":3,"file":"InstallAppModal.js","names":[],"sources":["../../src/components/InstallAppModal.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { useState, useEffect, useCallback, useReducer } from 'react';\nimport { X, Loader2, Building2, User, CheckCircle, AlertCircle, Info } from 'lucide-react';\nimport { graphqlFetch } from '@burdenoff/fe-libs/shared/graphql';\nimport { useStore } from '../providers/StoreProvider';\n\n// Local type definitions for workspace operations\ninterface Workspace {\n id: string;\n name: string;\n slug: string;\n type?: string;\n status?: string;\n organizationId?: string;\n}\n\ninterface WorkspaceList {\n items: Workspace[];\n total: number;\n}\n\ninterface InstallWorkspaceAppsResult {\n successCount: number;\n failedCount: number;\n results: Array<{\n success: boolean;\n workspaceId: string;\n app?: unknown;\n error?: string;\n }>;\n}\n\n// String queries for graphqlFetch\nconst MY_WORKSPACES_STRING = `query MyWorkspaces { myWorkspaces { items { id name slug type status organizationId } total } }`;\nconst INSTALL_WORKSPACE_APPS_STRING = `mutation InstallWorkspaceApps($workspaceIds: [ID!]!, $input: InstallWorkspaceAppInput!) { installWorkspaceApps(workspaceIds: $workspaceIds, input: $input) { successCount failedCount results { success workspaceId error } } }`;\nconst RECORD_APP_INSTALLATION_STRING = `mutation RecordAppInstallation($input: RecordAppInstallationInput!) { recordAppInstallation(input: $input) { id status workspaceId version } }`;\nconst INSTALL_PURCHASED_APP_STRING = `mutation InstallPurchasedApp($orderId: ID, $productId: String!, $workspaceId: String!) { installPurchasedApp(orderId: $orderId, productId: $productId, workspaceId: $workspaceId) { success alreadyInstalled installation { id productId workspaceId status version } error message } }`;\nconst CREATE_PRODUCT_ENTITLEMENT_STRING = `mutation CreateProductEntitlement($input: CreateProductEntitlementInput!) { createProductEntitlement(input: $input) { id status productId workspaceId } }`;\nconst STORE_DISTRIBUTED_TYPES = new Set([\n 'VIBEMODULE',\n 'NODE',\n 'CONSOLE',\n 'DASHBOARD',\n 'PARSER',\n 'DATASINK',\n]);\n\nconst INSTALL_ERROR_MESSAGES: Record<string, string> = {\n ALREADY_INSTALLED: 'This app is already installed in the selected workspace.',\n QUOTA_EXCEEDED: 'Installation limit reached. Please upgrade your plan or contact support.',\n PERMISSION_DENIED: 'You do not have permission to install apps in this workspace.',\n NOT_FOUND: 'The app or workspace could not be found.',\n WORKSPACE_INACTIVE: 'The selected workspace is inactive.',\n ENTITLEMENT_EXPIRED: 'Your license for this app has expired.',\n UNSUPPORTED_TYPE: 'This product type cannot be installed in the selected workspace.',\n};\n\nfunction mapInstallError(raw: string): string {\n for (const [code, message] of Object.entries(INSTALL_ERROR_MESSAGES)) {\n if (raw.toUpperCase().includes(code)) return message;\n }\n return 'Installation failed. Please try again or contact support.';\n}\n\ninterface InstallState {\n selectedWorkspace: string | null;\n installing: boolean;\n installError: string | null;\n installInfo: string | null;\n}\n\ntype InstallAction =\n | { type: 'reset' }\n | { type: 'selectWorkspace'; workspaceId: string }\n | { type: 'startInstall' }\n | { type: 'finishInstall' }\n | { type: 'setError'; error: string }\n | { type: 'setInfo'; info: string };\n\nconst initialInstallState: InstallState = {\n selectedWorkspace: null,\n installing: false,\n installError: null,\n installInfo: null,\n};\n\nfunction installReducer(state: InstallState, action: InstallAction): InstallState {\n switch (action.type) {\n case 'reset':\n return initialInstallState;\n case 'selectWorkspace':\n return { ...state, selectedWorkspace: action.workspaceId };\n case 'startInstall':\n return { ...state, installing: true, installError: null, installInfo: null };\n case 'finishInstall':\n return { ...state, installing: false };\n case 'setError':\n return { ...state, installError: action.error };\n case 'setInfo':\n return { ...state, installInfo: action.info };\n default:\n return state;\n }\n}\n\nexport interface AppInfo {\n id: string;\n name: string;\n slug?: string;\n icon?: string;\n type?: string;\n version?: string;\n}\n\ninterface InstallAppModalProps {\n isOpen: boolean;\n onClose: () => void;\n app: AppInfo;\n orderId?: string | null; // For purchased apps from orders page\n purchaseState?: 'free' | 'purchased';\n onInstallSuccess?: (workspaceId: string, workspaceName: string) => void;\n onInstallError?: (error: Error) => void;\n}\n\ninterface UseWorkspacesResult {\n workspaces: Workspace[];\n loading: boolean;\n error: Error | null;\n refetch: () => void;\n}\n\n// NOTE: Inline Apollo clients removed. All GraphQL calls now use graphqlFetch\n// from fe-libs which automatically resolves gateway URLs and injects auth + locale headers.\n\n/**\n * Hook to fetch user's workspaces using graphqlFetch from fe-libs\n */\nfunction useWorkspaces(skip: boolean): UseWorkspacesResult {\n const { authToken } = useStore();\n const [workspaces, setWorkspaces] = useState<Workspace[]>([]);\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n const fetchWorkspaces = useCallback(async () => {\n if (skip) return;\n\n setLoading(true);\n setError(null);\n\n try {\n const result = await graphqlFetch<{ myWorkspaces: WorkspaceList }>({\n gateway: 'global',\n query: MY_WORKSPACES_STRING,\n authToken: authToken || undefined,\n });\n\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'GraphQL error');\n }\n\n setWorkspaces(result.data?.myWorkspaces?.items || []);\n } catch (err) {\n setError(err instanceof Error ? err : new Error('Failed to fetch workspaces'));\n } finally {\n setLoading(false);\n }\n }, [authToken, skip]);\n\n useEffect(() => {\n fetchWorkspaces();\n }, [fetchWorkspaces]);\n\n return {\n workspaces,\n loading,\n error,\n refetch: fetchWorkspaces,\n };\n}\n\n/**\n * InstallAppModal component\n *\n * Modal for selecting a workspace to install an app into\n */\nexport const InstallAppModal: FC<InstallAppModalProps> = ({\n isOpen,\n onClose,\n app,\n orderId,\n purchaseState = 'free',\n onInstallSuccess,\n onInstallError,\n}) => {\n const { authToken, user } = useStore();\n const [installState, dispatchInstall] = useReducer(installReducer, initialInstallState);\n const { selectedWorkspace, installing, installError, installInfo } = installState;\n\n const { workspaces, loading, error } = useWorkspaces(!isOpen);\n\n // Filter workspaces by status (only show active)\n const filteredWorkspaces = workspaces.filter((ws) => ws.status === 'ACTIVE');\n\n /**\n * Install purchased app via Store Service (NEW FLOW)\n * Used when installing from orders page or any already-purchased product\n */\n const handleInstallFromStore = async () => {\n if (!selectedWorkspace) return;\n\n const workspace = filteredWorkspaces.find((ws) => ws.id === selectedWorkspace);\n if (!workspace) return;\n const purchasedBy = user?.id;\n\n dispatchInstall({ type: 'startInstall' });\n\n try {\n if (!orderId && purchaseState !== 'purchased') {\n if (!purchasedBy) {\n throw new Error('Unable to identify the current user for license creation');\n }\n\n await graphqlFetch({\n gateway: 'global',\n query: CREATE_PRODUCT_ENTITLEMENT_STRING,\n variables: {\n input: {\n productId: app.id,\n purchasedBy,\n workspaceId: workspace.id,\n metadata: {\n source: 'store-ui',\n productType: app.type,\n },\n },\n },\n authToken: authToken || undefined,\n });\n }\n\n // Call Store Service to install the purchased app\n const result = await graphqlFetch<{\n installPurchasedApp: {\n success: boolean;\n alreadyInstalled?: boolean;\n installation?: {\n id: string;\n productId: string;\n workspaceId: string;\n status: string;\n version: string;\n };\n error?: string;\n message?: string;\n };\n }>({\n gateway: 'global', // Store Service\n query: INSTALL_PURCHASED_APP_STRING,\n variables: {\n orderId: orderId ?? null,\n productId: app.id,\n workspaceId: workspace.id,\n },\n authToken: authToken || undefined,\n });\n\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Installation failed');\n }\n\n const installResult = result.data?.installPurchasedApp;\n\n if (installResult?.success) {\n if (installResult.alreadyInstalled) {\n dispatchInstall({\n type: 'setInfo',\n info: installResult.message || 'Already installed on this workspace',\n });\n } else {\n onInstallSuccess?.(workspace.id, workspace.name);\n onClose();\n }\n } else {\n const rawMsg = installResult?.error || 'Installation failed';\n const errorMsg = mapInstallError(rawMsg);\n dispatchInstall({ type: 'setError', error: errorMsg });\n onInstallError?.(new Error(rawMsg));\n }\n } catch (err) {\n const rawMsg = err instanceof Error ? err.message : 'Installation failed';\n const errorMsg = mapInstallError(rawMsg);\n dispatchInstall({ type: 'setError', error: errorMsg });\n onInstallError?.(new Error(rawMsg));\n } finally {\n dispatchInstall({ type: 'finishInstall' });\n }\n };\n\n /**\n * Install app via Workspace Service (LEGACY FLOW)\n * Used for free apps or when installing without order\n */\n const handleInstallViaWorkspace = async () => {\n if (!selectedWorkspace) return;\n\n const workspace = filteredWorkspaces.find((ws) => ws.id === selectedWorkspace);\n if (!workspace) return;\n\n dispatchInstall({ type: 'startInstall' });\n\n try {\n // Step 1: Install to workspace service via graphqlFetch\n const installVars = {\n workspaceIds: [workspace.id],\n input: {\n appName: app.slug || app.name.toLowerCase().replace(/\\s+/g, '-'),\n displayName: app.name,\n version: app.version || '1.0.0',\n type: app.type || 'app',\n manifest: {\n productId: app.id,\n icon: app.icon,\n },\n },\n };\n\n const result = await graphqlFetch<{\n installWorkspaceApps: InstallWorkspaceAppsResult;\n }>({\n gateway: 'workspace',\n query: INSTALL_WORKSPACE_APPS_STRING,\n variables: installVars,\n authToken: authToken || undefined,\n });\n\n if (result.errors?.length) {\n throw new Error(result.errors[0]?.message ?? 'Installation failed');\n }\n\n const installResult = result.data?.installWorkspaceApps;\n if (installResult?.successCount && installResult.successCount > 0) {\n // Step 2: Record installation in store service (for tracking)\n try {\n await graphqlFetch({\n gateway: 'global',\n query: RECORD_APP_INSTALLATION_STRING,\n variables: {\n input: {\n productId: app.id,\n workspaceId: workspace.id,\n workspaceName: workspace.name,\n workspaceType: workspace.type,\n organizationId: workspace.organizationId,\n version: app.version || '1.0.0',\n manifest: {\n productId: app.id,\n icon: app.icon,\n slug: app.slug,\n type: app.type,\n },\n },\n },\n authToken: authToken || undefined,\n });\n } catch {\n // Non-fatal: workspace installation succeeded; store record will sync later.\n }\n\n onInstallSuccess?.(workspace.id, workspace.name);\n onClose();\n } else {\n const rawMsg = installResult?.results?.[0]?.error || 'Installation failed';\n const errorMsg = mapInstallError(rawMsg);\n dispatchInstall({ type: 'setError', error: errorMsg });\n onInstallError?.(new Error(rawMsg));\n }\n } catch (err) {\n const rawMsg = err instanceof Error ? err.message : 'Installation failed';\n const errorMsg = mapInstallError(rawMsg);\n dispatchInstall({ type: 'setError', error: errorMsg });\n onInstallError?.(new Error(rawMsg));\n } finally {\n dispatchInstall({ type: 'finishInstall' });\n }\n };\n\n /**\n * Main install handler - routes to appropriate install function\n */\n const handleInstall = async () => {\n const appType = app.type?.toUpperCase() ?? '';\n\n if (purchaseState === 'purchased' || orderId || STORE_DISTRIBUTED_TYPES.has(appType)) {\n // Purchased products install through the store service.\n await handleInstallFromStore();\n } else {\n // Free products install through the workspace service.\n await handleInstallViaWorkspace();\n }\n };\n\n if (!isOpen) return null;\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center\">\n {/* Backdrop */}\n <button\n type=\"button\"\n aria-label=\"Close modal\"\n className=\"absolute inset-0 bg-bg-overlay/50 backdrop-blur-sm\"\n onClick={onClose}\n />\n\n {/* Modal */}\n <dialog\n open\n aria-modal=\"true\"\n aria-labelledby=\"install-app-modal-title\"\n className=\"relative z-10 mx-4 w-full max-w-lg rounded-2xl border border-border-default bg-bg-surface shadow-2xl\"\n >\n {/* Header */}\n <div className=\"flex items-center justify-between border-b border-border-default px-6 py-4\">\n <div className=\"flex items-center gap-3\">\n {app.icon ? (\n <img src={app.icon} alt={app.name} className=\"size-10 rounded-lg object-cover\" />\n ) : (\n <div className=\"flex size-10 items-center justify-center rounded-lg bg-bg-sunken text-lg font-bold text-text-muted\">\n {app.name.charAt(0)}\n </div>\n )}\n <div>\n <h2 id=\"install-app-modal-title\" className=\"text-lg font-semibold text-text-primary\">\n Install App\n </h2>\n <p className=\"text-sm text-text-muted\">{app.name}</p>\n </div>\n </div>\n <button\n type=\"button\"\n onClick={onClose}\n aria-label=\"Close install dialog\"\n className=\"cursor-pointer rounded-lg p-2 text-text-muted transition-colors hover:bg-bg-sunken hover:text-text-primary\"\n >\n <X className=\"size-5\" />\n </button>\n </div>\n\n {/* Content */}\n <div className=\"px-6 py-4\">\n <p className=\"mb-4 text-text-secondary\">\n Select the workspace where you want to install this app:\n </p>\n\n {/* Workspace List */}\n <div className=\"max-h-72 overflow-y-auto rounded-lg border border-border-default\">\n {loading ? (\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 className=\"size-8 animate-spin text-text-muted\" />\n </div>\n ) : error ? (\n <div className=\"flex flex-col items-center justify-center py-12 text-center\">\n <AlertCircle className=\"mb-2 size-8 text-status-error-text\" />\n <p className=\"text-sm text-status-error-text\">Failed to load workspaces</p>\n <p className=\"text-xs text-text-muted mt-1\">{error.message}</p>\n </div>\n ) : filteredWorkspaces.length === 0 ? (\n <div className=\"flex flex-col items-center justify-center py-12 text-center\">\n <Building2 className=\"mb-2 size-8 text-text-muted\" />\n <p className=\"text-sm text-text-muted\">No workspaces available</p>\n </div>\n ) : (\n <div className=\"divide-y divide-border-default\">\n {filteredWorkspaces.map((workspace) => (\n <button\n type=\"button\"\n key={workspace.id}\n onClick={() =>\n dispatchInstall({ type: 'selectWorkspace', workspaceId: workspace.id })\n }\n className={`w-full cursor-pointer flex items-center gap-3 px-4 py-3 text-left transition-colors ${\n selectedWorkspace === workspace.id\n ? 'bg-action-primary-bg/10'\n : 'transition-colors hover:bg-bg-sunken'\n }`}\n >\n {/* Workspace Icon */}\n <div\n className={`flex size-10 items-center justify-center rounded-lg ${\n workspace.type === 'user'\n ? 'bg-status-info-bg-subtle text-status-info-text'\n : 'bg-action-primary-bg/10 text-text-link'\n }`}\n >\n {workspace.type === 'user' ? (\n <User className=\"size-5\" />\n ) : (\n <Building2 className=\"size-5\" />\n )}\n </div>\n\n {/* Workspace Info */}\n <div className=\"flex-1 min-w-0\">\n <p className=\"font-medium text-text-primary truncate\">{workspace.name}</p>\n <p className=\"text-xs text-text-muted\">\n {workspace.type === 'user' ? 'Personal' : 'Team'} workspace\n </p>\n </div>\n\n {/* Selection Indicator */}\n {selectedWorkspace === workspace.id && (\n <CheckCircle className=\"size-5 flex-shrink-0 text-action-primary-bg\" />\n )}\n </button>\n ))}\n </div>\n )}\n </div>\n </div>\n\n {/* Footer */}\n <div className=\"border-t border-border-default px-6 py-4\">\n {/* Already-installed info */}\n {installInfo && (\n <div className=\"mb-3 flex items-center gap-2 rounded-lg bg-status-info-bg-subtle px-3 py-2 text-sm text-status-info-text\">\n <Info className=\"size-4 flex-shrink-0\" />\n <span>{installInfo}</span>\n </div>\n )}\n {/* Error message */}\n {installError && (\n <div className=\"mb-3 flex items-center gap-2 rounded-lg bg-status-error-bg-subtle/10 px-3 py-2 text-sm text-status-error-text\">\n <AlertCircle className=\"size-4 flex-shrink-0\" />\n <span>{installError}</span>\n </div>\n )}\n <div className=\"flex items-center justify-end gap-3\">\n <button\n type=\"button\"\n onClick={onClose}\n disabled={installing}\n className=\"cursor-pointer rounded-lg border border-border-default px-4 py-2 text-sm font-medium text-text-primary transition-colors hover:bg-bg-sunken disabled:opacity-50\"\n >\n Cancel\n </button>\n <button\n type=\"button\"\n onClick={handleInstall}\n disabled={!selectedWorkspace || installing}\n className=\"cursor-pointer rounded-lg bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text transition-opacity hover:opacity-90 disabled:opacity-50 disabled:cursor-not-allowed inline-flex items-center gap-2\"\n >\n {installing ? (\n <>\n <Loader2 className=\"size-4 animate-spin\" />\n Installing…\n </>\n ) : (\n 'Install App'\n )}\n </button>\n </div>\n </div>\n </dialog>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { AccessDenied as e } from "./AccessDenied.js";
|
|
2
|
+
import { Fragment as t, jsx as n } from "react/jsx-runtime";
|
|
3
|
+
//#region src/components/PermissionGuard.tsx
|
|
4
|
+
var r = ({ allowed: r, children: i }) => r ? /* @__PURE__ */ n(t, { children: i }) : /* @__PURE__ */ n(e, {});
|
|
5
|
+
//#endregion
|
|
6
|
+
export { r as PermissionGuard };
|
|
7
|
+
|
|
8
|
+
//# sourceMappingURL=PermissionGuard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PermissionGuard.js","names":[],"sources":["../../src/components/PermissionGuard.tsx"],"sourcesContent":["import type { FC, ReactNode } from 'react';\nimport { AccessDenied } from './AccessDenied';\n\n/**\n * Route-level permission guard that shows AccessDenied when permission check fails\n */\nexport const PermissionGuard: FC<{ allowed: boolean; children: ReactNode }> = ({\n allowed,\n children,\n}) => {\n if (!allowed) {\n return <AccessDenied />;\n }\n return <>{children}</>;\n};\n"],"mappings":";;;AAMA,IAAa,KAAkE,EAC7E,YACA,kBAEK,IAGE,kBAAA,GAAA,EAAG,aAAY,CAAA,GAFb,kBAAC,GAAD,EAAgB,CAAA"}
|