@burdenoff/microfe-store 2026.518.1 → 2026.518.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/StoreRoot.js +4 -4
- package/dist/StoreRoutes.js +122 -120
- package/dist/StoreRoutes.js.map +1 -1
- package/dist/components/InstallAppModal.js +79 -69
- package/dist/components/InstallAppModal.js.map +1 -1
- package/dist/components/cart/CartDrawer.js +26 -8
- package/dist/components/cart/CartDrawer.js.map +1 -1
- package/dist/hooks/useBackendCart.js +1 -11
- package/dist/hooks/useBackendCart.js.map +1 -1
- package/dist/index.js +6 -6
- package/dist/pages/CartPage.js +4 -4
- package/dist/pages/InstalledAppsPage.js +11 -11
- package/dist/pages/MarketplaceHomePage.js +282 -258
- package/dist/pages/MarketplaceHomePage.js.map +1 -1
- package/package.json +1 -1
|
@@ -11,8 +11,20 @@ var h = "query MyWorkspaces { myWorkspaces { items { id name slug type status or
|
|
|
11
11
|
"DASHBOARD",
|
|
12
12
|
"PARSER",
|
|
13
13
|
"DATASINK"
|
|
14
|
-
])
|
|
15
|
-
|
|
14
|
+
]), x = {
|
|
15
|
+
ALREADY_INSTALLED: "This app is already installed in the selected workspace.",
|
|
16
|
+
QUOTA_EXCEEDED: "Installation limit reached. Please upgrade your plan or contact support.",
|
|
17
|
+
PERMISSION_DENIED: "You do not have permission to install apps in this workspace.",
|
|
18
|
+
NOT_FOUND: "The app or workspace could not be found.",
|
|
19
|
+
WORKSPACE_INACTIVE: "The selected workspace is inactive.",
|
|
20
|
+
ENTITLEMENT_EXPIRED: "Your license for this app has expired.",
|
|
21
|
+
UNSUPPORTED_TYPE: "This product type cannot be installed in the selected workspace."
|
|
22
|
+
};
|
|
23
|
+
function S(e) {
|
|
24
|
+
for (let [t, n] of Object.entries(x)) if (e.toUpperCase().includes(t)) return n;
|
|
25
|
+
return "Installation failed. Please try again or contact support.";
|
|
26
|
+
}
|
|
27
|
+
function C(i) {
|
|
16
28
|
let { authToken: a } = e(), [o, s] = r([]), [c, l] = r(!1), [u, d] = r(null), f = t(async () => {
|
|
17
29
|
if (!i) {
|
|
18
30
|
l(!0), d(null);
|
|
@@ -40,63 +52,63 @@ function x(i) {
|
|
|
40
52
|
refetch: f
|
|
41
53
|
};
|
|
42
54
|
}
|
|
43
|
-
var
|
|
44
|
-
let { authToken:
|
|
55
|
+
var w = ({ isOpen: t, onClose: h, app: x, orderId: w, purchaseState: T = "free", onInstallSuccess: E, onInstallError: D }) => {
|
|
56
|
+
let { authToken: O, user: k } = e(), [A, j] = r(null), [M, N] = r(!1), [P, F] = r(null), [I, L] = r(null), { workspaces: R, loading: z, error: B } = C(!t);
|
|
45
57
|
n(() => {
|
|
46
|
-
t && (
|
|
58
|
+
t && (j(null), F(null), L(null));
|
|
47
59
|
}, [t]);
|
|
48
|
-
let
|
|
49
|
-
if (!
|
|
50
|
-
let e =
|
|
60
|
+
let V = R.filter((e) => e.status === "ACTIVE"), H = async () => {
|
|
61
|
+
if (!A) return;
|
|
62
|
+
let e = V.find((e) => e.id === A);
|
|
51
63
|
if (!e) return;
|
|
52
|
-
let t =
|
|
53
|
-
|
|
64
|
+
let t = k?.id;
|
|
65
|
+
N(!0), F(null), L(null);
|
|
54
66
|
try {
|
|
55
|
-
if (!
|
|
67
|
+
if (!w && T !== "purchased") {
|
|
56
68
|
if (!t) throw Error("Unable to identify the current user for license creation");
|
|
57
69
|
await m({
|
|
58
70
|
gateway: "global",
|
|
59
71
|
query: y,
|
|
60
72
|
variables: { input: {
|
|
61
|
-
productId:
|
|
73
|
+
productId: x.id,
|
|
62
74
|
purchasedBy: t,
|
|
63
75
|
workspaceId: e.id,
|
|
64
76
|
metadata: {
|
|
65
77
|
source: "store-ui",
|
|
66
|
-
productType:
|
|
78
|
+
productType: x.type
|
|
67
79
|
}
|
|
68
80
|
} },
|
|
69
|
-
authToken:
|
|
81
|
+
authToken: O || void 0
|
|
70
82
|
});
|
|
71
83
|
}
|
|
72
84
|
let n = await m({
|
|
73
85
|
gateway: "global",
|
|
74
86
|
query: v,
|
|
75
87
|
variables: {
|
|
76
|
-
orderId:
|
|
77
|
-
productId:
|
|
88
|
+
orderId: w ?? null,
|
|
89
|
+
productId: x.id,
|
|
78
90
|
workspaceId: e.id
|
|
79
91
|
},
|
|
80
|
-
authToken:
|
|
92
|
+
authToken: O || void 0
|
|
81
93
|
});
|
|
82
94
|
if (n.errors?.length) throw Error(n.errors[0]?.message ?? "Installation failed");
|
|
83
95
|
let r = n.data?.installPurchasedApp;
|
|
84
|
-
if (r?.success) r.alreadyInstalled ?
|
|
96
|
+
if (r?.success) r.alreadyInstalled ? L(r.message || "Already installed on this workspace") : (E?.(e.id, e.name), h());
|
|
85
97
|
else {
|
|
86
98
|
let e = r?.error || "Installation failed";
|
|
87
|
-
|
|
99
|
+
F(S(e)), D?.(Error(e));
|
|
88
100
|
}
|
|
89
101
|
} catch (e) {
|
|
90
102
|
let t = e instanceof Error ? e.message : "Installation failed";
|
|
91
|
-
|
|
103
|
+
F(S(t)), D?.(Error(t));
|
|
92
104
|
} finally {
|
|
93
|
-
|
|
105
|
+
N(!1);
|
|
94
106
|
}
|
|
95
|
-
},
|
|
96
|
-
if (!
|
|
97
|
-
let e =
|
|
107
|
+
}, U = async () => {
|
|
108
|
+
if (!A) return;
|
|
109
|
+
let e = V.find((e) => e.id === A);
|
|
98
110
|
if (e) {
|
|
99
|
-
|
|
111
|
+
N(!0), F(null), L(null);
|
|
100
112
|
try {
|
|
101
113
|
let t = await m({
|
|
102
114
|
gateway: "workspace",
|
|
@@ -104,17 +116,17 @@ var S = ({ isOpen: t, onClose: h, app: S, orderId: C, purchaseState: w = "free",
|
|
|
104
116
|
variables: {
|
|
105
117
|
workspaceIds: [e.id],
|
|
106
118
|
input: {
|
|
107
|
-
appName:
|
|
108
|
-
displayName:
|
|
109
|
-
version:
|
|
110
|
-
type:
|
|
119
|
+
appName: x.slug || x.name.toLowerCase().replace(/\s+/g, "-"),
|
|
120
|
+
displayName: x.name,
|
|
121
|
+
version: x.version || "1.0.0",
|
|
122
|
+
type: x.type || "app",
|
|
111
123
|
manifest: {
|
|
112
|
-
productId:
|
|
113
|
-
icon:
|
|
124
|
+
productId: x.id,
|
|
125
|
+
icon: x.icon
|
|
114
126
|
}
|
|
115
127
|
}
|
|
116
128
|
},
|
|
117
|
-
authToken:
|
|
129
|
+
authToken: O || void 0
|
|
118
130
|
});
|
|
119
131
|
if (t.errors?.length) throw Error(t.errors[0]?.message ?? "Installation failed");
|
|
120
132
|
let n = t.data?.installWorkspaceApps;
|
|
@@ -124,34 +136,32 @@ var S = ({ isOpen: t, onClose: h, app: S, orderId: C, purchaseState: w = "free",
|
|
|
124
136
|
gateway: "global",
|
|
125
137
|
query: _,
|
|
126
138
|
variables: { input: {
|
|
127
|
-
productId:
|
|
139
|
+
productId: x.id,
|
|
128
140
|
workspaceId: e.id,
|
|
129
141
|
workspaceName: e.name,
|
|
130
142
|
workspaceType: e.type,
|
|
131
143
|
organizationId: e.organizationId,
|
|
132
|
-
version:
|
|
144
|
+
version: x.version || "1.0.0",
|
|
133
145
|
manifest: {
|
|
134
|
-
productId:
|
|
135
|
-
icon:
|
|
136
|
-
slug:
|
|
137
|
-
type:
|
|
146
|
+
productId: x.id,
|
|
147
|
+
icon: x.icon,
|
|
148
|
+
slug: x.slug,
|
|
149
|
+
type: x.type
|
|
138
150
|
}
|
|
139
151
|
} },
|
|
140
|
-
authToken:
|
|
152
|
+
authToken: O || void 0
|
|
141
153
|
});
|
|
142
|
-
} catch
|
|
143
|
-
|
|
144
|
-
}
|
|
145
|
-
T?.(e.id, e.name), h();
|
|
154
|
+
} catch {}
|
|
155
|
+
E?.(e.id, e.name), h();
|
|
146
156
|
} else {
|
|
147
157
|
let e = n?.results?.[0]?.error || "Installation failed";
|
|
148
|
-
|
|
158
|
+
F(S(e)), D?.(Error(e));
|
|
149
159
|
}
|
|
150
160
|
} catch (e) {
|
|
151
161
|
let t = e instanceof Error ? e.message : "Installation failed";
|
|
152
|
-
|
|
162
|
+
F(S(t)), D?.(Error(t));
|
|
153
163
|
} finally {
|
|
154
|
-
|
|
164
|
+
N(!1);
|
|
155
165
|
}
|
|
156
166
|
}
|
|
157
167
|
};
|
|
@@ -170,20 +180,20 @@ var S = ({ isOpen: t, onClose: h, app: S, orderId: C, purchaseState: w = "free",
|
|
|
170
180
|
className: "flex items-center justify-between border-b border-border-default px-6 py-4",
|
|
171
181
|
children: [/* @__PURE__ */ p("div", {
|
|
172
182
|
className: "flex items-center gap-3",
|
|
173
|
-
children: [
|
|
174
|
-
src:
|
|
175
|
-
alt:
|
|
183
|
+
children: [x.icon ? /* @__PURE__ */ f("img", {
|
|
184
|
+
src: x.icon,
|
|
185
|
+
alt: x.name,
|
|
176
186
|
className: "h-10 w-10 rounded-lg object-cover"
|
|
177
187
|
}) : /* @__PURE__ */ f("div", {
|
|
178
188
|
className: "flex h-10 w-10 items-center justify-center rounded-lg bg-bg-sunken text-lg font-bold text-text-muted",
|
|
179
|
-
children:
|
|
189
|
+
children: x.name.charAt(0)
|
|
180
190
|
}), /* @__PURE__ */ p("div", { children: [/* @__PURE__ */ f("h2", {
|
|
181
191
|
id: "install-app-modal-title",
|
|
182
192
|
className: "text-lg font-semibold text-text-primary",
|
|
183
193
|
children: "Install App"
|
|
184
194
|
}), /* @__PURE__ */ f("p", {
|
|
185
195
|
className: "text-sm text-text-muted",
|
|
186
|
-
children:
|
|
196
|
+
children: x.name
|
|
187
197
|
})] })]
|
|
188
198
|
}), /* @__PURE__ */ f("button", {
|
|
189
199
|
onClick: h,
|
|
@@ -199,10 +209,10 @@ var S = ({ isOpen: t, onClose: h, app: S, orderId: C, purchaseState: w = "free",
|
|
|
199
209
|
children: "Select the workspace where you want to install this app:"
|
|
200
210
|
}), /* @__PURE__ */ f("div", {
|
|
201
211
|
className: "max-h-72 overflow-y-auto rounded-lg border border-border-default",
|
|
202
|
-
children:
|
|
212
|
+
children: z ? /* @__PURE__ */ f("div", {
|
|
203
213
|
className: "flex items-center justify-center py-12",
|
|
204
214
|
children: /* @__PURE__ */ f(c, { className: "h-8 w-8 animate-spin text-text-muted" })
|
|
205
|
-
}) :
|
|
215
|
+
}) : B ? /* @__PURE__ */ p("div", {
|
|
206
216
|
className: "flex flex-col items-center justify-center py-12 text-center",
|
|
207
217
|
children: [
|
|
208
218
|
/* @__PURE__ */ f(i, { className: "mb-2 h-8 w-8 text-status-error-text" }),
|
|
@@ -212,10 +222,10 @@ var S = ({ isOpen: t, onClose: h, app: S, orderId: C, purchaseState: w = "free",
|
|
|
212
222
|
}),
|
|
213
223
|
/* @__PURE__ */ f("p", {
|
|
214
224
|
className: "text-xs text-text-muted mt-1",
|
|
215
|
-
children:
|
|
225
|
+
children: B.message
|
|
216
226
|
})
|
|
217
227
|
]
|
|
218
|
-
}) :
|
|
228
|
+
}) : V.length === 0 ? /* @__PURE__ */ p("div", {
|
|
219
229
|
className: "flex flex-col items-center justify-center py-12 text-center",
|
|
220
230
|
children: [/* @__PURE__ */ f(a, { className: "mb-2 h-8 w-8 text-text-muted" }), /* @__PURE__ */ f("p", {
|
|
221
231
|
className: "text-sm text-text-muted",
|
|
@@ -223,9 +233,9 @@ var S = ({ isOpen: t, onClose: h, app: S, orderId: C, purchaseState: w = "free",
|
|
|
223
233
|
})]
|
|
224
234
|
}) : /* @__PURE__ */ f("div", {
|
|
225
235
|
className: "divide-y divide-border-default",
|
|
226
|
-
children:
|
|
227
|
-
onClick: () =>
|
|
228
|
-
className: `w-full cursor-pointer flex items-center gap-3 px-4 py-3 text-left transition-colors ${
|
|
236
|
+
children: V.map((e) => /* @__PURE__ */ p("button", {
|
|
237
|
+
onClick: () => j(e.id),
|
|
238
|
+
className: `w-full cursor-pointer flex items-center gap-3 px-4 py-3 text-left transition-colors ${A === e.id ? "bg-action-primary-bg/10" : "transition-colors hover:bg-bg-sunken"}`,
|
|
229
239
|
children: [
|
|
230
240
|
/* @__PURE__ */ f("div", {
|
|
231
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"}`,
|
|
@@ -241,7 +251,7 @@ var S = ({ isOpen: t, onClose: h, app: S, orderId: C, purchaseState: w = "free",
|
|
|
241
251
|
children: [e.type === "user" ? "Personal" : "Team", " workspace"]
|
|
242
252
|
})]
|
|
243
253
|
}),
|
|
244
|
-
|
|
254
|
+
A === e.id && /* @__PURE__ */ f(o, { className: "h-5 w-5 flex-shrink-0 text-action-primary-bg" })
|
|
245
255
|
]
|
|
246
256
|
}, e.id))
|
|
247
257
|
})
|
|
@@ -250,29 +260,29 @@ var S = ({ isOpen: t, onClose: h, app: S, orderId: C, purchaseState: w = "free",
|
|
|
250
260
|
/* @__PURE__ */ p("div", {
|
|
251
261
|
className: "border-t border-border-default px-6 py-4",
|
|
252
262
|
children: [
|
|
253
|
-
|
|
263
|
+
I && /* @__PURE__ */ p("div", {
|
|
254
264
|
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",
|
|
255
|
-
children: [/* @__PURE__ */ f(s, { className: "h-4 w-4 flex-shrink-0" }), /* @__PURE__ */ f("span", { children:
|
|
265
|
+
children: [/* @__PURE__ */ f(s, { className: "h-4 w-4 flex-shrink-0" }), /* @__PURE__ */ f("span", { children: I })]
|
|
256
266
|
}),
|
|
257
|
-
|
|
267
|
+
P && /* @__PURE__ */ p("div", {
|
|
258
268
|
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",
|
|
259
|
-
children: [/* @__PURE__ */ f(i, { className: "h-4 w-4 flex-shrink-0" }), /* @__PURE__ */ f("span", { children:
|
|
269
|
+
children: [/* @__PURE__ */ f(i, { className: "h-4 w-4 flex-shrink-0" }), /* @__PURE__ */ f("span", { children: P })]
|
|
260
270
|
}),
|
|
261
271
|
/* @__PURE__ */ p("div", {
|
|
262
272
|
className: "flex items-center justify-end gap-3",
|
|
263
273
|
children: [/* @__PURE__ */ f("button", {
|
|
264
274
|
onClick: h,
|
|
265
|
-
disabled:
|
|
275
|
+
disabled: M,
|
|
266
276
|
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",
|
|
267
277
|
children: "Cancel"
|
|
268
278
|
}), /* @__PURE__ */ f("button", {
|
|
269
279
|
onClick: async () => {
|
|
270
|
-
let e =
|
|
271
|
-
|
|
280
|
+
let e = x.type?.toUpperCase() ?? "";
|
|
281
|
+
T === "purchased" || w || b.has(e) ? await H() : await U();
|
|
272
282
|
},
|
|
273
|
-
disabled: !
|
|
283
|
+
disabled: !A || M,
|
|
274
284
|
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",
|
|
275
|
-
children:
|
|
285
|
+
children: M ? /* @__PURE__ */ p(d, { children: [/* @__PURE__ */ f(c, { className: "h-4 w-4 animate-spin" }), "Installing..."] }) : "Install App"
|
|
276
286
|
})]
|
|
277
287
|
})
|
|
278
288
|
]
|
|
@@ -282,6 +292,6 @@ var S = ({ isOpen: t, onClose: h, app: S, orderId: C, purchaseState: w = "free",
|
|
|
282
292
|
}) : null;
|
|
283
293
|
};
|
|
284
294
|
//#endregion
|
|
285
|
-
export {
|
|
295
|
+
export { w as InstallAppModal };
|
|
286
296
|
|
|
287
297
|
//# 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\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 errorMsg = installResult?.error || 'Installation failed';\n setInstallError(errorMsg);\n onInstallError?.(new Error(errorMsg));\n }\n } catch (err) {\n const errorMsg = err instanceof Error ? err.message : 'Installation failed';\n setInstallError(errorMsg);\n onInstallError?.(new Error(errorMsg));\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 (recordErr) {\n // Log but don't fail - workspace installation succeeded\n console.warn('Failed to record installation in store:', recordErr);\n }\n\n onInstallSuccess?.(workspace.id, workspace.name);\n onClose();\n } else {\n const errorMsg = installResult?.results?.[0]?.error || 'Installation failed';\n setInstallError(errorMsg);\n onInstallError?.(new Error(errorMsg));\n }\n } catch (err) {\n const errorMsg = err instanceof Error ? err.message : 'Installation failed';\n setInstallError(errorMsg);\n onInstallError?.(new Error(errorMsg));\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":"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"}
|
|
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":"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"}
|
|
@@ -6,12 +6,12 @@ var l = ({ isOpen: l, onClose: u, onCheckout: d, cartItems: f, itemCount: p, sub
|
|
|
6
6
|
className: "fixed inset-0 z-40 bg-bg-overlay/50 transition-opacity",
|
|
7
7
|
onClick: u
|
|
8
8
|
}), /* @__PURE__ */ s("div", {
|
|
9
|
-
className: "fixed right-0 top-0 z-50 h-full w-full max-w-md bg-bg-surface shadow-xl",
|
|
9
|
+
className: "fixed right-0 top-0 z-50 h-full w-full sm:max-w-md bg-bg-surface shadow-xl",
|
|
10
10
|
children: /* @__PURE__ */ c("div", {
|
|
11
11
|
className: "flex h-full flex-col",
|
|
12
12
|
children: [
|
|
13
13
|
/* @__PURE__ */ c("div", {
|
|
14
|
-
className: "flex items-center justify-between border-b border-border-default px-6 py-4",
|
|
14
|
+
className: "flex items-center justify-between border-b border-border-default px-4 sm:px-6 py-4",
|
|
15
15
|
children: [/* @__PURE__ */ c("div", {
|
|
16
16
|
className: "flex items-center gap-2",
|
|
17
17
|
children: [
|
|
@@ -33,11 +33,16 @@ var l = ({ isOpen: l, onClose: u, onCheckout: d, cartItems: f, itemCount: p, sub
|
|
|
33
33
|
})]
|
|
34
34
|
}),
|
|
35
35
|
/* @__PURE__ */ s("div", {
|
|
36
|
-
className: "flex-1 overflow-y-auto px-6 py-4",
|
|
36
|
+
className: "flex-1 overflow-y-auto px-4 sm:px-6 py-4",
|
|
37
37
|
children: f.length === 0 ? /* @__PURE__ */ c("div", {
|
|
38
|
+
role: "status",
|
|
39
|
+
"aria-label": "Your cart is empty",
|
|
38
40
|
className: "flex h-full flex-col items-center justify-center text-center",
|
|
39
41
|
children: [
|
|
40
|
-
/* @__PURE__ */ s(r, {
|
|
42
|
+
/* @__PURE__ */ s(r, {
|
|
43
|
+
className: "mb-4 h-16 w-16 text-text-muted",
|
|
44
|
+
"aria-hidden": "true"
|
|
45
|
+
}),
|
|
41
46
|
/* @__PURE__ */ s("h3", {
|
|
42
47
|
className: "mb-2 text-lg font-semibold text-text-primary",
|
|
43
48
|
children: "Your cart is empty"
|
|
@@ -79,24 +84,37 @@ var l = ({ isOpen: l, onClose: u, onCheckout: d, cartItems: f, itemCount: p, sub
|
|
|
79
84
|
children: [
|
|
80
85
|
/* @__PURE__ */ s("button", {
|
|
81
86
|
onClick: () => _(a.id, a.quantity - 1),
|
|
87
|
+
"aria-label": `Decrease quantity of ${a.productName || a.product?.name || "item"}`,
|
|
82
88
|
className: "cursor-pointer p-1.5 transition-colors hover:bg-bg-sunken disabled:cursor-not-allowed disabled:opacity-50",
|
|
83
89
|
disabled: a.quantity <= 1,
|
|
84
|
-
children: /* @__PURE__ */ s(t, {
|
|
90
|
+
children: /* @__PURE__ */ s(t, {
|
|
91
|
+
className: "h-4 w-4",
|
|
92
|
+
"aria-hidden": "true"
|
|
93
|
+
})
|
|
85
94
|
}),
|
|
86
95
|
/* @__PURE__ */ s("span", {
|
|
87
96
|
className: "min-w-[2rem] text-center text-sm font-medium",
|
|
97
|
+
"aria-label": `Quantity: ${a.quantity}`,
|
|
88
98
|
children: a.quantity
|
|
89
99
|
}),
|
|
90
100
|
/* @__PURE__ */ s("button", {
|
|
91
101
|
onClick: () => _(a.id, a.quantity + 1),
|
|
102
|
+
"aria-label": `Increase quantity of ${a.productName || a.product?.name || "item"}`,
|
|
92
103
|
className: "cursor-pointer p-1.5 transition-colors hover:bg-bg-sunken",
|
|
93
|
-
children: /* @__PURE__ */ s(n, {
|
|
104
|
+
children: /* @__PURE__ */ s(n, {
|
|
105
|
+
className: "h-4 w-4",
|
|
106
|
+
"aria-hidden": "true"
|
|
107
|
+
})
|
|
94
108
|
})
|
|
95
109
|
]
|
|
96
110
|
}), /* @__PURE__ */ s("button", {
|
|
97
111
|
onClick: () => v(a.id),
|
|
112
|
+
"aria-label": `Remove ${a.productName || a.product?.name || "item"} from cart`,
|
|
98
113
|
className: "cursor-pointer ml-auto text-status-error-text transition-colors hover:text-status-error-text-emphasis",
|
|
99
|
-
children: /* @__PURE__ */ s(i, {
|
|
114
|
+
children: /* @__PURE__ */ s(i, {
|
|
115
|
+
className: "h-4 w-4",
|
|
116
|
+
"aria-hidden": "true"
|
|
117
|
+
})
|
|
100
118
|
})]
|
|
101
119
|
}),
|
|
102
120
|
/* @__PURE__ */ s("div", {
|
|
@@ -112,7 +130,7 @@ var l = ({ isOpen: l, onClose: u, onCheckout: d, cartItems: f, itemCount: p, sub
|
|
|
112
130
|
})
|
|
113
131
|
}),
|
|
114
132
|
f.length > 0 && /* @__PURE__ */ c("div", {
|
|
115
|
-
className: "border-t border-border-default bg-bg-sunken px-6 py-4",
|
|
133
|
+
className: "border-t border-border-default bg-bg-sunken px-4 sm:px-6 py-4",
|
|
116
134
|
children: [/* @__PURE__ */ c("div", {
|
|
117
135
|
className: "space-y-2",
|
|
118
136
|
children: [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CartDrawer.js","names":[],"sources":["../../../src/components/cart/CartDrawer.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { X, ShoppingCart, Trash2, Plus, Minus } from 'lucide-react';\nimport { formatPrice } from '../../utils';\n\n/**\n * Props for CartDrawer\n */\ninterface CartDrawerProps {\n isOpen: boolean;\n onClose: () => void;\n onCheckout: () => void;\n cartItems: Array<{\n id: string;\n quantity: number;\n subtotal: number;\n productName?: string;\n productIcon?: string;\n variantName?: string;\n product?: { icon?: string; name?: string };\n }>;\n itemCount: number;\n subtotal: number;\n tax: number;\n total: number;\n updateQuantity: (cartItemId: string, quantity: number) => Promise<void>;\n removeProduct: (cartItemId: string) => Promise<void>;\n}\n\n/**\n * CartDrawer component\n *\n * Sliding drawer for cart management\n */\nexport const CartDrawer: FC<CartDrawerProps> = ({\n isOpen,\n onClose,\n onCheckout,\n cartItems,\n itemCount,\n subtotal,\n tax,\n total,\n updateQuantity,\n removeProduct,\n}) => {\n if (!isOpen) return null;\n\n return (\n <>\n {/* Backdrop */}\n <div className=\"fixed inset-0 z-40 bg-bg-overlay/50 transition-opacity\" onClick={onClose} />\n\n {/* Drawer */}\n <div className=\"fixed right-0 top-0 z-50 h-full w-full max-w-md bg-bg-surface shadow-xl\">\n <div className=\"flex h-full flex-col\">\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-2\">\n <ShoppingCart className=\"h-5 w-5 text-text-primary\" />\n <h2 className=\"text-lg font-semibold text-text-primary\">Shopping Cart</h2>\n {itemCount > 0 && (\n <span className=\"rounded-full bg-bg-accent px-2 py-0.5 text-xs font-medium text-text-link\">\n {itemCount}\n </span>\n )}\n </div>\n <button\n onClick={onClose}\n aria-label=\"Close cart\"\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 {/* Cart Items */}\n <div className=\"flex-1 overflow-y-auto px-6 py-4\">\n {cartItems.length === 0 ? (\n <div className=\"flex h-full flex-col items-center justify-center text-center\">\n <ShoppingCart className=\"mb-4 h-16 w-16 text-text-muted\" />\n <h3 className=\"mb-2 text-lg font-semibold text-text-primary\">Your cart is empty</h3>\n <p className=\"text-sm text-text-muted\">Start shopping to add items to your cart</p>\n </div>\n ) : (\n <div className=\"space-y-4\">\n {cartItems.map((item) => (\n <div\n key={item.id}\n className=\"flex gap-4 rounded-lg border border-border-default p-4\"\n >\n {/* Product Image */}\n <div className=\"h-20 w-20 flex-shrink-0 overflow-hidden rounded bg-bg-sunken\">\n {item.productIcon || item.product?.icon ? (\n <img\n src={item.productIcon || item.product?.icon}\n alt={item.productName || item.product?.name}\n className=\"h-full w-full object-cover\"\n />\n ) : (\n <div className=\"flex h-full w-full items-center justify-center\">\n <ShoppingCart className=\"h-8 w-8 text-text-muted\" />\n </div>\n )}\n </div>\n\n {/* Product Info */}\n <div className=\"flex flex-1 flex-col\">\n <h4 className=\"mb-1 font-semibold text-text-primary\">\n {item.productName || item.product?.name || 'Product'}\n </h4>\n {item.variantName && (\n <p className=\"mb-2 text-xs text-text-muted\">{item.variantName}</p>\n )}\n\n {/* Quantity Controls */}\n <div className=\"flex items-center gap-3\">\n <div className=\"flex items-center rounded border border-border-default\">\n <button\n onClick={() => updateQuantity(item.id, item.quantity - 1)}\n className=\"cursor-pointer p-1.5 transition-colors hover:bg-bg-sunken disabled:cursor-not-allowed disabled:opacity-50\"\n disabled={item.quantity <= 1}\n >\n <Minus className=\"h-4 w-4\" />\n </button>\n <span className=\"min-w-[2rem] text-center text-sm font-medium\">\n {item.quantity}\n </span>\n <button\n onClick={() => updateQuantity(item.id, item.quantity + 1)}\n className=\"cursor-pointer p-1.5 transition-colors hover:bg-bg-sunken\"\n >\n <Plus className=\"h-4 w-4\" />\n </button>\n </div>\n\n <button\n onClick={() => removeProduct(item.id)}\n className=\"cursor-pointer ml-auto text-status-error-text transition-colors hover:text-status-error-text-emphasis\"\n >\n <Trash2 className=\"h-4 w-4\" />\n </button>\n </div>\n\n {/* Price */}\n <div className=\"mt-2 text-right\">\n <span className=\"font-semibold text-text-primary\">\n {formatPrice(item.subtotal)}\n </span>\n </div>\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n\n {/* Footer with Summary */}\n {cartItems.length > 0 && (\n <div className=\"border-t border-border-default bg-bg-sunken px-6 py-4\">\n <div className=\"space-y-2\">\n <div className=\"flex justify-between text-sm\">\n <span className=\"text-text-muted\">Subtotal</span>\n <span className=\"font-medium text-text-primary\">{formatPrice(subtotal)}</span>\n </div>\n <div className=\"flex justify-between text-sm\">\n <span className=\"text-text-muted\">Tax</span>\n <span className=\"font-medium text-text-primary\">{formatPrice(tax)}</span>\n </div>\n <div className=\"flex justify-between border-t border-border-default pt-2\">\n <span className=\"font-semibold text-text-primary\">Total</span>\n <span className=\"text-lg font-bold text-text-primary\">{formatPrice(total)}</span>\n </div>\n </div>\n\n <button\n onClick={onCheckout}\n className=\"cursor-pointer mt-4 w-full rounded-lg bg-action-primary-bg py-3 font-bold text-action-primary-text shadow-lg transition-all hover:opacity-90 hover:shadow-xl active:scale-[0.98]\"\n >\n Proceed to Checkout\n </button>\n </div>\n )}\n </div>\n </div>\n </>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CartDrawer.js","names":[],"sources":["../../../src/components/cart/CartDrawer.tsx"],"sourcesContent":["import type { FC } from 'react';\nimport { X, ShoppingCart, Trash2, Plus, Minus } from 'lucide-react';\nimport { formatPrice } from '../../utils';\n\n/**\n * Props for CartDrawer\n */\ninterface CartDrawerProps {\n isOpen: boolean;\n onClose: () => void;\n onCheckout: () => void;\n cartItems: Array<{\n id: string;\n quantity: number;\n subtotal: number;\n productName?: string;\n productIcon?: string;\n variantName?: string;\n product?: { icon?: string; name?: string };\n }>;\n itemCount: number;\n subtotal: number;\n tax: number;\n total: number;\n updateQuantity: (cartItemId: string, quantity: number) => Promise<void>;\n removeProduct: (cartItemId: string) => Promise<void>;\n}\n\n/**\n * CartDrawer component\n *\n * Sliding drawer for cart management\n */\nexport const CartDrawer: FC<CartDrawerProps> = ({\n isOpen,\n onClose,\n onCheckout,\n cartItems,\n itemCount,\n subtotal,\n tax,\n total,\n updateQuantity,\n removeProduct,\n}) => {\n if (!isOpen) return null;\n\n return (\n <>\n {/* Backdrop */}\n <div className=\"fixed inset-0 z-40 bg-bg-overlay/50 transition-opacity\" onClick={onClose} />\n\n {/* Drawer */}\n <div className=\"fixed right-0 top-0 z-50 h-full w-full sm:max-w-md bg-bg-surface shadow-xl\">\n <div className=\"flex h-full flex-col\">\n {/* Header */}\n <div className=\"flex items-center justify-between border-b border-border-default px-4 sm:px-6 py-4\">\n <div className=\"flex items-center gap-2\">\n <ShoppingCart className=\"h-5 w-5 text-text-primary\" />\n <h2 className=\"text-lg font-semibold text-text-primary\">Shopping Cart</h2>\n {itemCount > 0 && (\n <span className=\"rounded-full bg-bg-accent px-2 py-0.5 text-xs font-medium text-text-link\">\n {itemCount}\n </span>\n )}\n </div>\n <button\n onClick={onClose}\n aria-label=\"Close cart\"\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 {/* Cart Items */}\n <div className=\"flex-1 overflow-y-auto px-4 sm:px-6 py-4\">\n {cartItems.length === 0 ? (\n <div\n role=\"status\"\n aria-label=\"Your cart is empty\"\n className=\"flex h-full flex-col items-center justify-center text-center\"\n >\n <ShoppingCart className=\"mb-4 h-16 w-16 text-text-muted\" aria-hidden=\"true\" />\n <h3 className=\"mb-2 text-lg font-semibold text-text-primary\">Your cart is empty</h3>\n <p className=\"text-sm text-text-muted\">Start shopping to add items to your cart</p>\n </div>\n ) : (\n <div className=\"space-y-4\">\n {cartItems.map((item) => (\n <div\n key={item.id}\n className=\"flex gap-4 rounded-lg border border-border-default p-4\"\n >\n {/* Product Image */}\n <div className=\"h-20 w-20 flex-shrink-0 overflow-hidden rounded bg-bg-sunken\">\n {item.productIcon || item.product?.icon ? (\n <img\n src={item.productIcon || item.product?.icon}\n alt={item.productName || item.product?.name}\n className=\"h-full w-full object-cover\"\n />\n ) : (\n <div className=\"flex h-full w-full items-center justify-center\">\n <ShoppingCart className=\"h-8 w-8 text-text-muted\" />\n </div>\n )}\n </div>\n\n {/* Product Info */}\n <div className=\"flex flex-1 flex-col\">\n <h4 className=\"mb-1 font-semibold text-text-primary\">\n {item.productName || item.product?.name || 'Product'}\n </h4>\n {item.variantName && (\n <p className=\"mb-2 text-xs text-text-muted\">{item.variantName}</p>\n )}\n\n {/* Quantity Controls */}\n <div className=\"flex items-center gap-3\">\n <div className=\"flex items-center rounded border border-border-default\">\n <button\n onClick={() => updateQuantity(item.id, item.quantity - 1)}\n aria-label={`Decrease quantity of ${item.productName || item.product?.name || 'item'}`}\n className=\"cursor-pointer p-1.5 transition-colors hover:bg-bg-sunken disabled:cursor-not-allowed disabled:opacity-50\"\n disabled={item.quantity <= 1}\n >\n <Minus className=\"h-4 w-4\" aria-hidden=\"true\" />\n </button>\n <span\n className=\"min-w-[2rem] text-center text-sm font-medium\"\n aria-label={`Quantity: ${item.quantity}`}\n >\n {item.quantity}\n </span>\n <button\n onClick={() => updateQuantity(item.id, item.quantity + 1)}\n aria-label={`Increase quantity of ${item.productName || item.product?.name || 'item'}`}\n className=\"cursor-pointer p-1.5 transition-colors hover:bg-bg-sunken\"\n >\n <Plus className=\"h-4 w-4\" aria-hidden=\"true\" />\n </button>\n </div>\n\n <button\n onClick={() => removeProduct(item.id)}\n aria-label={`Remove ${item.productName || item.product?.name || 'item'} from cart`}\n className=\"cursor-pointer ml-auto text-status-error-text transition-colors hover:text-status-error-text-emphasis\"\n >\n <Trash2 className=\"h-4 w-4\" aria-hidden=\"true\" />\n </button>\n </div>\n\n {/* Price */}\n <div className=\"mt-2 text-right\">\n <span className=\"font-semibold text-text-primary\">\n {formatPrice(item.subtotal)}\n </span>\n </div>\n </div>\n </div>\n ))}\n </div>\n )}\n </div>\n\n {/* Footer with Summary */}\n {cartItems.length > 0 && (\n <div className=\"border-t border-border-default bg-bg-sunken px-4 sm:px-6 py-4\">\n <div className=\"space-y-2\">\n <div className=\"flex justify-between text-sm\">\n <span className=\"text-text-muted\">Subtotal</span>\n <span className=\"font-medium text-text-primary\">{formatPrice(subtotal)}</span>\n </div>\n <div className=\"flex justify-between text-sm\">\n <span className=\"text-text-muted\">Tax</span>\n <span className=\"font-medium text-text-primary\">{formatPrice(tax)}</span>\n </div>\n <div className=\"flex justify-between border-t border-border-default pt-2\">\n <span className=\"font-semibold text-text-primary\">Total</span>\n <span className=\"text-lg font-bold text-text-primary\">{formatPrice(total)}</span>\n </div>\n </div>\n\n <button\n onClick={onCheckout}\n className=\"cursor-pointer mt-4 w-full rounded-lg bg-action-primary-bg py-3 font-bold text-action-primary-text shadow-lg transition-all hover:opacity-90 hover:shadow-xl active:scale-[0.98]\"\n >\n Proceed to Checkout\n </button>\n </div>\n )}\n </div>\n </div>\n </>\n );\n};\n"],"mappings":"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"}
|
|
@@ -15,7 +15,7 @@ function d() {
|
|
|
15
15
|
fetchPolicy: e ? "network-only" : "cache-first"
|
|
16
16
|
})).data?.myCart || null);
|
|
17
17
|
} catch (e) {
|
|
18
|
-
b(e)
|
|
18
|
+
b(e);
|
|
19
19
|
} finally {
|
|
20
20
|
v(!1);
|
|
21
21
|
}
|
|
@@ -40,8 +40,6 @@ function d() {
|
|
|
40
40
|
variables: { input: e }
|
|
41
41
|
}), n.data?.addToCart.errors && n.data.addToCart.errors.length > 0) throw Error(n.data.addToCart.errors[0].message);
|
|
42
42
|
return n.data?.addToCart.cart && g(n.data.addToCart.cart), await j(!0), n.data?.addToCart.cart;
|
|
43
|
-
} catch (e) {
|
|
44
|
-
throw console.error("Failed to add product to cart:", e), e;
|
|
45
43
|
} finally {
|
|
46
44
|
S(!1);
|
|
47
45
|
}
|
|
@@ -69,8 +67,6 @@ function d() {
|
|
|
69
67
|
} }
|
|
70
68
|
}), n.data?.updateCartItem.errors && n.data.updateCartItem.errors.length > 0) throw Error(n.data.updateCartItem.errors[0].message);
|
|
71
69
|
return n.data?.updateCartItem.cart && g(n.data.updateCartItem.cart), await j(!0), n.data?.updateCartItem.cart;
|
|
72
|
-
} catch (e) {
|
|
73
|
-
throw console.error("Failed to update cart item:", e), e;
|
|
74
70
|
} finally {
|
|
75
71
|
w(!1);
|
|
76
72
|
}
|
|
@@ -92,8 +88,6 @@ function d() {
|
|
|
92
88
|
variables: { cartItemId: e }
|
|
93
89
|
}), t.data?.removeFromCart.errors && t.data.removeFromCart.errors.length > 0) throw Error(t.data.removeFromCart.errors[0].message);
|
|
94
90
|
return t.data?.removeFromCart.cart && g(t.data.removeFromCart.cart), await j(!0), t.data?.removeFromCart.cart;
|
|
95
|
-
} catch (e) {
|
|
96
|
-
throw console.error("Failed to remove product from cart:", e), e;
|
|
97
91
|
} finally {
|
|
98
92
|
E(!1);
|
|
99
93
|
}
|
|
@@ -108,8 +102,6 @@ function d() {
|
|
|
108
102
|
let e;
|
|
109
103
|
if (e = m ? await p({ mutation: n }) : await d.mutate({ mutation: n }), e.data?.clearCart.errors && e.data.clearCart.errors.length > 0) throw Error(e.data.clearCart.errors[0].message);
|
|
110
104
|
return e.data?.clearCart.cart && g(e.data.clearCart.cart), await j(!0), e.data?.clearCart.cart;
|
|
111
|
-
} catch (e) {
|
|
112
|
-
throw console.error("Failed to clear cart:", e), e;
|
|
113
105
|
} finally {
|
|
114
106
|
O(!1);
|
|
115
107
|
}
|
|
@@ -131,8 +123,6 @@ function d() {
|
|
|
131
123
|
variables: { input: e }
|
|
132
124
|
}), !t.data?.createStoreOrder?.id) throw Error("Failed to create order");
|
|
133
125
|
return await j(!0), t.data.createStoreOrder;
|
|
134
|
-
} catch (e) {
|
|
135
|
-
throw console.error("Failed to create order:", e), e;
|
|
136
126
|
} finally {
|
|
137
127
|
A(!1);
|
|
138
128
|
}
|