@ezcoder.dev/sdk 1.0.0
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/analytics/index.d.ts +18 -0
- package/dist/analytics/index.js +76 -0
- package/dist/analytics/index.js.map +1 -0
- package/dist/animation/index.d.ts +172 -0
- package/dist/animation/index.js +81 -0
- package/dist/animation/index.js.map +1 -0
- package/dist/auth/index.d.ts +80 -0
- package/dist/auth/index.js +463 -0
- package/dist/auth/index.js.map +1 -0
- package/dist/chunk-5XIZHBKE.js +372 -0
- package/dist/chunk-5XIZHBKE.js.map +1 -0
- package/dist/chunk-G7XDUN3Z.js +141 -0
- package/dist/chunk-G7XDUN3Z.js.map +1 -0
- package/dist/chunk-YNDCD53D.js +212 -0
- package/dist/chunk-YNDCD53D.js.map +1 -0
- package/dist/cms/index.d.ts +44 -0
- package/dist/cms/index.js +106 -0
- package/dist/cms/index.js.map +1 -0
- package/dist/errors/index.d.ts +20 -0
- package/dist/errors/index.js +61 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/index.d.ts +30 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/notifications/index.d.ts +30 -0
- package/dist/notifications/index.js +191 -0
- package/dist/notifications/index.js.map +1 -0
- package/dist/payments/index.d.ts +89 -0
- package/dist/payments/index.js +408 -0
- package/dist/payments/index.js.map +1 -0
- package/dist/roles/index.d.ts +37 -0
- package/dist/roles/index.js +120 -0
- package/dist/roles/index.js.map +1 -0
- package/dist/seo/index.d.ts +39 -0
- package/dist/seo/index.js +89 -0
- package/dist/seo/index.js.map +1 -0
- package/dist/server/index.d.ts +72 -0
- package/dist/server/index.js +191 -0
- package/dist/server/index.js.map +1 -0
- package/dist/storage/index.d.ts +52 -0
- package/dist/storage/index.js +212 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/types-DtY5lp3P.d.ts +90 -0
- package/package.json +105 -0
|
@@ -0,0 +1,408 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AuthContext
|
|
3
|
+
} from "../chunk-YNDCD53D.js";
|
|
4
|
+
import {
|
|
5
|
+
ezcoder
|
|
6
|
+
} from "../chunk-5XIZHBKE.js";
|
|
7
|
+
import {
|
|
8
|
+
features,
|
|
9
|
+
supabase
|
|
10
|
+
} from "../chunk-G7XDUN3Z.js";
|
|
11
|
+
|
|
12
|
+
// src/payments/useSubscription.ts
|
|
13
|
+
import { useState, useEffect, useCallback, useContext } from "react";
|
|
14
|
+
var TIER_LEVELS = {
|
|
15
|
+
free: 0,
|
|
16
|
+
starter: 1,
|
|
17
|
+
creator: 2,
|
|
18
|
+
pro: 3,
|
|
19
|
+
business: 4,
|
|
20
|
+
enterprise: 5
|
|
21
|
+
};
|
|
22
|
+
function useSubscription() {
|
|
23
|
+
const auth = useContext(AuthContext);
|
|
24
|
+
const [subscription, setSubscription] = useState(null);
|
|
25
|
+
const [loading, setLoading] = useState(true);
|
|
26
|
+
const fetchSubscription = useCallback(async () => {
|
|
27
|
+
if (!auth?.profile) {
|
|
28
|
+
setSubscription(null);
|
|
29
|
+
setLoading(false);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
const sub = {
|
|
33
|
+
customerId: auth.profile.stripe_customer_id || null,
|
|
34
|
+
tier: auth.profile.subscription_tier || "free",
|
|
35
|
+
status: auth.profile.subscription_status || null,
|
|
36
|
+
currentPeriodEnd: auth.profile.subscription_period_end || null
|
|
37
|
+
};
|
|
38
|
+
setSubscription(sub);
|
|
39
|
+
setLoading(false);
|
|
40
|
+
}, [auth?.profile]);
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
fetchSubscription();
|
|
43
|
+
}, [fetchSubscription]);
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (!features.auth || !auth?.user?.id) return;
|
|
46
|
+
const channel = supabase.channel(`subscription_${auth.user.id}`).on(
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
48
|
+
"postgres_changes",
|
|
49
|
+
{
|
|
50
|
+
event: "UPDATE",
|
|
51
|
+
schema: "public",
|
|
52
|
+
table: "user_profiles",
|
|
53
|
+
filter: `id=eq.${auth.user.id}`
|
|
54
|
+
},
|
|
55
|
+
() => {
|
|
56
|
+
auth.refetchProfile(auth.user.id);
|
|
57
|
+
}
|
|
58
|
+
).subscribe();
|
|
59
|
+
return () => {
|
|
60
|
+
supabase.removeChannel(channel);
|
|
61
|
+
};
|
|
62
|
+
}, [auth?.user?.id, auth]);
|
|
63
|
+
const tier = subscription?.tier || "free";
|
|
64
|
+
const status = subscription?.status || null;
|
|
65
|
+
const isActive = !status || status === "active" || status === "trialing";
|
|
66
|
+
const tierLevel = TIER_LEVELS[tier] ?? 0;
|
|
67
|
+
return {
|
|
68
|
+
subscription,
|
|
69
|
+
tier,
|
|
70
|
+
status,
|
|
71
|
+
isActive,
|
|
72
|
+
isPro: tierLevel >= TIER_LEVELS.pro,
|
|
73
|
+
isBusiness: tierLevel >= TIER_LEVELS.business,
|
|
74
|
+
isEnterprise: tierLevel >= TIER_LEVELS.enterprise,
|
|
75
|
+
canAccess: (requiredTier) => tierLevel >= (TIER_LEVELS[requiredTier] ?? 0),
|
|
76
|
+
loading: loading || (auth?.loading ?? false),
|
|
77
|
+
isConfigured: features.payments,
|
|
78
|
+
user: auth?.user ?? null,
|
|
79
|
+
profile: auth?.profile ?? null,
|
|
80
|
+
isAuthenticated: Boolean(auth?.user),
|
|
81
|
+
refetch: fetchSubscription
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// src/payments/useCustomerAccess.ts
|
|
86
|
+
import { useState as useState2, useEffect as useEffect2, useCallback as useCallback2 } from "react";
|
|
87
|
+
function useCustomerAccess() {
|
|
88
|
+
const [loading, setLoading] = useState2(true);
|
|
89
|
+
const [hasAccess, setHasAccess] = useState2(false);
|
|
90
|
+
const [customer, setCustomer] = useState2(null);
|
|
91
|
+
const [subscription, setSubscription] = useState2(null);
|
|
92
|
+
const [error, setError] = useState2(null);
|
|
93
|
+
const checkAccess = useCallback2(async () => {
|
|
94
|
+
setLoading(true);
|
|
95
|
+
setError(null);
|
|
96
|
+
try {
|
|
97
|
+
const customerId = typeof localStorage !== "undefined" ? localStorage.getItem("stripeCustomerId") : null;
|
|
98
|
+
const email = typeof localStorage !== "undefined" ? localStorage.getItem("customerEmail") : null;
|
|
99
|
+
if (!customerId && !email) {
|
|
100
|
+
setHasAccess(false);
|
|
101
|
+
setLoading(false);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
const result = await ezcoder.stripe.getCustomerStatus({ customerId: customerId || void 0, email: email || void 0 });
|
|
105
|
+
if (result.success) {
|
|
106
|
+
setHasAccess(Boolean(result.hasAccess));
|
|
107
|
+
setCustomer(result.customer || null);
|
|
108
|
+
setSubscription(result.subscription || null);
|
|
109
|
+
} else {
|
|
110
|
+
setHasAccess(false);
|
|
111
|
+
setError(result.error || "Failed to check access");
|
|
112
|
+
}
|
|
113
|
+
} catch (err) {
|
|
114
|
+
setError(err instanceof Error ? err.message : "Unknown error");
|
|
115
|
+
setHasAccess(false);
|
|
116
|
+
} finally {
|
|
117
|
+
setLoading(false);
|
|
118
|
+
}
|
|
119
|
+
}, []);
|
|
120
|
+
useEffect2(() => {
|
|
121
|
+
checkAccess();
|
|
122
|
+
}, [checkAccess]);
|
|
123
|
+
const logout = useCallback2(() => {
|
|
124
|
+
if (typeof localStorage !== "undefined") {
|
|
125
|
+
localStorage.removeItem("stripeCustomerId");
|
|
126
|
+
localStorage.removeItem("customerEmail");
|
|
127
|
+
}
|
|
128
|
+
setHasAccess(false);
|
|
129
|
+
setCustomer(null);
|
|
130
|
+
setSubscription(null);
|
|
131
|
+
}, []);
|
|
132
|
+
return { loading, hasAccess, customer, subscription, error, refresh: checkAccess, logout };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// src/payments/BuyButton.tsx
|
|
136
|
+
import { useState as useState3 } from "react";
|
|
137
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
138
|
+
function BuyButton({
|
|
139
|
+
priceId,
|
|
140
|
+
productName,
|
|
141
|
+
className = "",
|
|
142
|
+
children,
|
|
143
|
+
disabled = false,
|
|
144
|
+
customerEmail,
|
|
145
|
+
quantity,
|
|
146
|
+
successUrl,
|
|
147
|
+
cancelUrl
|
|
148
|
+
}) {
|
|
149
|
+
const [loading, setLoading] = useState3(false);
|
|
150
|
+
const [error, setError] = useState3(null);
|
|
151
|
+
const handleClick = async () => {
|
|
152
|
+
setError(null);
|
|
153
|
+
setLoading(true);
|
|
154
|
+
try {
|
|
155
|
+
const result = await ezcoder.stripe.createCheckout(priceId, {
|
|
156
|
+
customerEmail,
|
|
157
|
+
quantity,
|
|
158
|
+
successUrl,
|
|
159
|
+
cancelUrl,
|
|
160
|
+
redirect: true
|
|
161
|
+
});
|
|
162
|
+
if (!result.success) {
|
|
163
|
+
setError(result.error || "Checkout failed");
|
|
164
|
+
}
|
|
165
|
+
} catch (err) {
|
|
166
|
+
setError(err instanceof Error ? err.message : "Checkout failed");
|
|
167
|
+
} finally {
|
|
168
|
+
setLoading(false);
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
172
|
+
/* @__PURE__ */ jsx(
|
|
173
|
+
"button",
|
|
174
|
+
{
|
|
175
|
+
onClick: handleClick,
|
|
176
|
+
disabled: disabled || loading,
|
|
177
|
+
className,
|
|
178
|
+
style: !className ? {
|
|
179
|
+
padding: "10px 20px",
|
|
180
|
+
backgroundColor: disabled || loading ? "#9ca3af" : "#3b82f6",
|
|
181
|
+
color: "white",
|
|
182
|
+
border: "none",
|
|
183
|
+
borderRadius: "6px",
|
|
184
|
+
fontSize: "14px",
|
|
185
|
+
fontWeight: 500,
|
|
186
|
+
cursor: disabled || loading ? "not-allowed" : "pointer"
|
|
187
|
+
} : void 0,
|
|
188
|
+
children: loading ? "Processing..." : children || `Buy ${productName || "Now"}`
|
|
189
|
+
}
|
|
190
|
+
),
|
|
191
|
+
error && /* @__PURE__ */ jsx("p", { style: { color: "#dc2626", fontSize: "12px", marginTop: "4px" }, children: error })
|
|
192
|
+
] });
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// src/payments/PricingTable.tsx
|
|
196
|
+
import { useState as useState4 } from "react";
|
|
197
|
+
import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
198
|
+
var DEFAULT_PLANS = [
|
|
199
|
+
{
|
|
200
|
+
name: "Starter",
|
|
201
|
+
description: "For individuals getting started",
|
|
202
|
+
monthlyPrice: 0,
|
|
203
|
+
features: ["Basic features", "Community support"],
|
|
204
|
+
cta: "Get Started"
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
name: "Creator",
|
|
208
|
+
description: "For growing projects",
|
|
209
|
+
monthlyPrice: 25,
|
|
210
|
+
yearlyPrice: 270,
|
|
211
|
+
features: ["All Starter features", "Priority support", "Advanced analytics"],
|
|
212
|
+
highlighted: true,
|
|
213
|
+
cta: "Subscribe"
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
name: "Business",
|
|
217
|
+
description: "For teams and businesses",
|
|
218
|
+
monthlyPrice: 50,
|
|
219
|
+
yearlyPrice: 540,
|
|
220
|
+
features: ["All Creator features", "Team collaboration", "Custom integrations"],
|
|
221
|
+
cta: "Subscribe"
|
|
222
|
+
},
|
|
223
|
+
{
|
|
224
|
+
name: "Enterprise",
|
|
225
|
+
description: "For large organizations",
|
|
226
|
+
monthlyPrice: -1,
|
|
227
|
+
features: ["All Business features", "Dedicated support", "SLA guarantee", "Custom contracts"],
|
|
228
|
+
cta: "Contact Sales"
|
|
229
|
+
}
|
|
230
|
+
];
|
|
231
|
+
function PricingTable({ plans = DEFAULT_PLANS, className = "", customerEmail }) {
|
|
232
|
+
const [yearly, setYearly] = useState4(false);
|
|
233
|
+
return /* @__PURE__ */ jsxs2("div", { className, children: [
|
|
234
|
+
/* @__PURE__ */ jsxs2("div", { style: { display: "flex", justifyContent: "center", marginBottom: "32px", gap: "8px", alignItems: "center" }, children: [
|
|
235
|
+
/* @__PURE__ */ jsx2("span", { style: { fontSize: "14px", color: !yearly ? "#111" : "#6b7280", fontWeight: !yearly ? 600 : 400 }, children: "Monthly" }),
|
|
236
|
+
/* @__PURE__ */ jsx2(
|
|
237
|
+
"button",
|
|
238
|
+
{
|
|
239
|
+
onClick: () => setYearly(!yearly),
|
|
240
|
+
style: {
|
|
241
|
+
width: "44px",
|
|
242
|
+
height: "24px",
|
|
243
|
+
borderRadius: "12px",
|
|
244
|
+
backgroundColor: yearly ? "#3b82f6" : "#d1d5db",
|
|
245
|
+
border: "none",
|
|
246
|
+
cursor: "pointer",
|
|
247
|
+
position: "relative"
|
|
248
|
+
},
|
|
249
|
+
children: /* @__PURE__ */ jsx2("span", { style: {
|
|
250
|
+
width: "18px",
|
|
251
|
+
height: "18px",
|
|
252
|
+
borderRadius: "50%",
|
|
253
|
+
backgroundColor: "white",
|
|
254
|
+
position: "absolute",
|
|
255
|
+
top: "3px",
|
|
256
|
+
left: yearly ? "23px" : "3px",
|
|
257
|
+
transition: "left 0.2s"
|
|
258
|
+
} })
|
|
259
|
+
}
|
|
260
|
+
),
|
|
261
|
+
/* @__PURE__ */ jsxs2("span", { style: { fontSize: "14px", color: yearly ? "#111" : "#6b7280", fontWeight: yearly ? 600 : 400 }, children: [
|
|
262
|
+
"Yearly ",
|
|
263
|
+
/* @__PURE__ */ jsx2("span", { style: { color: "#059669", fontSize: "12px" }, children: "Save 10%" })
|
|
264
|
+
] })
|
|
265
|
+
] }),
|
|
266
|
+
/* @__PURE__ */ jsx2("div", { style: { display: "grid", gridTemplateColumns: `repeat(${Math.min(plans.length, 4)}, 1fr)`, gap: "24px", maxWidth: "1200px", margin: "0 auto" }, children: plans.map((plan) => {
|
|
267
|
+
const price = yearly && plan.yearlyPrice !== void 0 ? plan.yearlyPrice / 12 : plan.monthlyPrice;
|
|
268
|
+
const priceId = yearly ? plan.yearlyPriceId : plan.monthlyPriceId;
|
|
269
|
+
return /* @__PURE__ */ jsxs2(
|
|
270
|
+
"div",
|
|
271
|
+
{
|
|
272
|
+
style: {
|
|
273
|
+
border: plan.highlighted ? "2px solid #3b82f6" : "1px solid #e5e7eb",
|
|
274
|
+
borderRadius: "12px",
|
|
275
|
+
padding: "24px",
|
|
276
|
+
backgroundColor: plan.highlighted ? "#eff6ff" : "white"
|
|
277
|
+
},
|
|
278
|
+
children: [
|
|
279
|
+
/* @__PURE__ */ jsx2("h3", { style: { fontSize: "1.25rem", fontWeight: 600, marginBottom: "4px" }, children: plan.name }),
|
|
280
|
+
/* @__PURE__ */ jsx2("p", { style: { color: "#6b7280", fontSize: "14px", marginBottom: "16px" }, children: plan.description }),
|
|
281
|
+
/* @__PURE__ */ jsx2("div", { style: { marginBottom: "24px" }, children: plan.monthlyPrice === 0 ? /* @__PURE__ */ jsx2("span", { style: { fontSize: "2rem", fontWeight: 700 }, children: "Free" }) : plan.monthlyPrice === -1 ? /* @__PURE__ */ jsx2("span", { style: { fontSize: "1.5rem", fontWeight: 600 }, children: "Custom" }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
|
|
282
|
+
/* @__PURE__ */ jsxs2("span", { style: { fontSize: "2rem", fontWeight: 700 }, children: [
|
|
283
|
+
"$",
|
|
284
|
+
Math.round(price)
|
|
285
|
+
] }),
|
|
286
|
+
/* @__PURE__ */ jsx2("span", { style: { color: "#6b7280", fontSize: "14px" }, children: "/mo" })
|
|
287
|
+
] }) }),
|
|
288
|
+
/* @__PURE__ */ jsx2("ul", { style: { listStyle: "none", padding: 0, marginBottom: "24px" }, children: plan.features.map((feature) => /* @__PURE__ */ jsxs2("li", { style: { display: "flex", alignItems: "center", gap: "8px", marginBottom: "8px", fontSize: "14px" }, children: [
|
|
289
|
+
/* @__PURE__ */ jsx2("span", { style: { color: "#059669" }, children: "\u2713" }),
|
|
290
|
+
" ",
|
|
291
|
+
feature
|
|
292
|
+
] }, feature)) }),
|
|
293
|
+
priceId ? /* @__PURE__ */ jsx2(BuyButton, { priceId, productName: plan.name, customerEmail, children: plan.cta || "Subscribe" }) : plan.monthlyPrice === 0 ? /* @__PURE__ */ jsx2("button", { style: {
|
|
294
|
+
width: "100%",
|
|
295
|
+
padding: "10px",
|
|
296
|
+
border: "1px solid #d1d5db",
|
|
297
|
+
borderRadius: "6px",
|
|
298
|
+
background: "white",
|
|
299
|
+
cursor: "pointer",
|
|
300
|
+
fontSize: "14px"
|
|
301
|
+
}, children: plan.cta || "Get Started" }) : /* @__PURE__ */ jsx2("button", { style: {
|
|
302
|
+
width: "100%",
|
|
303
|
+
padding: "10px",
|
|
304
|
+
border: "1px solid #d1d5db",
|
|
305
|
+
borderRadius: "6px",
|
|
306
|
+
background: "white",
|
|
307
|
+
cursor: "pointer",
|
|
308
|
+
fontSize: "14px"
|
|
309
|
+
}, children: plan.cta || "Contact Sales" })
|
|
310
|
+
]
|
|
311
|
+
},
|
|
312
|
+
plan.name
|
|
313
|
+
);
|
|
314
|
+
}) })
|
|
315
|
+
] });
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// src/payments/ManageSubscriptionButton.tsx
|
|
319
|
+
import { useState as useState5 } from "react";
|
|
320
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
321
|
+
function ManageSubscriptionButton({ children, className = "", customerId }) {
|
|
322
|
+
const [loading, setLoading] = useState5(false);
|
|
323
|
+
const handleClick = async () => {
|
|
324
|
+
const id = customerId || (typeof localStorage !== "undefined" ? localStorage.getItem("stripeCustomerId") : null);
|
|
325
|
+
if (!id) return;
|
|
326
|
+
setLoading(true);
|
|
327
|
+
await ezcoder.stripe.createPortalSession(id, { redirect: true });
|
|
328
|
+
setLoading(false);
|
|
329
|
+
};
|
|
330
|
+
return /* @__PURE__ */ jsx3(
|
|
331
|
+
"button",
|
|
332
|
+
{
|
|
333
|
+
onClick: handleClick,
|
|
334
|
+
disabled: loading,
|
|
335
|
+
className,
|
|
336
|
+
style: !className ? {
|
|
337
|
+
padding: "8px 16px",
|
|
338
|
+
border: "1px solid #d1d5db",
|
|
339
|
+
borderRadius: "6px",
|
|
340
|
+
background: "white",
|
|
341
|
+
cursor: loading ? "not-allowed" : "pointer",
|
|
342
|
+
fontSize: "14px"
|
|
343
|
+
} : void 0,
|
|
344
|
+
children: loading ? "Loading..." : children || "Manage Subscription"
|
|
345
|
+
}
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// src/payments/SubscriptionManager.tsx
|
|
350
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
351
|
+
var STATUS_COLORS = {
|
|
352
|
+
active: "#059669",
|
|
353
|
+
trialing: "#3b82f6",
|
|
354
|
+
past_due: "#d97706",
|
|
355
|
+
canceled: "#dc2626",
|
|
356
|
+
unpaid: "#dc2626"
|
|
357
|
+
};
|
|
358
|
+
function SubscriptionManager({ className = "" }) {
|
|
359
|
+
const { subscription, tier, status, isActive, loading } = useSubscription();
|
|
360
|
+
if (loading) {
|
|
361
|
+
return /* @__PURE__ */ jsx4("div", { style: { padding: "20px", color: "#6b7280" }, children: "Loading subscription..." });
|
|
362
|
+
}
|
|
363
|
+
return /* @__PURE__ */ jsxs3("div", { className, style: { border: "1px solid #e5e7eb", borderRadius: "12px", padding: "24px" }, children: [
|
|
364
|
+
/* @__PURE__ */ jsx4("h3", { style: { fontSize: "1.125rem", fontWeight: 600, marginBottom: "16px" }, children: "Subscription" }),
|
|
365
|
+
/* @__PURE__ */ jsxs3("div", { style: { display: "grid", gap: "12px", marginBottom: "24px" }, children: [
|
|
366
|
+
/* @__PURE__ */ jsxs3("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
|
|
367
|
+
/* @__PURE__ */ jsx4("span", { style: { color: "#6b7280" }, children: "Plan" }),
|
|
368
|
+
/* @__PURE__ */ jsx4("span", { style: { fontWeight: 500, textTransform: "capitalize" }, children: tier })
|
|
369
|
+
] }),
|
|
370
|
+
status && /* @__PURE__ */ jsxs3("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
|
|
371
|
+
/* @__PURE__ */ jsx4("span", { style: { color: "#6b7280" }, children: "Status" }),
|
|
372
|
+
/* @__PURE__ */ jsx4("span", { style: {
|
|
373
|
+
fontWeight: 500,
|
|
374
|
+
color: STATUS_COLORS[status] || "#6b7280",
|
|
375
|
+
textTransform: "capitalize"
|
|
376
|
+
}, children: status === "past_due" ? "Past Due" : status })
|
|
377
|
+
] }),
|
|
378
|
+
subscription?.currentPeriodEnd && /* @__PURE__ */ jsxs3("div", { style: { display: "flex", justifyContent: "space-between" }, children: [
|
|
379
|
+
/* @__PURE__ */ jsx4("span", { style: { color: "#6b7280" }, children: isActive ? "Renews" : "Expires" }),
|
|
380
|
+
/* @__PURE__ */ jsx4("span", { children: new Date(subscription.currentPeriodEnd).toLocaleDateString() })
|
|
381
|
+
] })
|
|
382
|
+
] }),
|
|
383
|
+
subscription?.customerId && /* @__PURE__ */ jsx4(ManageSubscriptionButton, { customerId: subscription.customerId })
|
|
384
|
+
] });
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// src/payments/ProtectedContent.tsx
|
|
388
|
+
import { Fragment as Fragment2, jsx as jsx5 } from "react/jsx-runtime";
|
|
389
|
+
function ProtectedContent({ children, fallback, loadingComponent }) {
|
|
390
|
+
const { loading, hasAccess } = useCustomerAccess();
|
|
391
|
+
if (loading) {
|
|
392
|
+
return /* @__PURE__ */ jsx5(Fragment2, { children: loadingComponent || /* @__PURE__ */ jsx5("div", { style: { padding: "20px", textAlign: "center", color: "#6b7280" }, children: "Loading..." }) });
|
|
393
|
+
}
|
|
394
|
+
if (!hasAccess) {
|
|
395
|
+
return /* @__PURE__ */ jsx5(Fragment2, { children: fallback || /* @__PURE__ */ jsx5("div", { style: { padding: "20px", textAlign: "center", color: "#6b7280" }, children: "This content requires a subscription." }) });
|
|
396
|
+
}
|
|
397
|
+
return /* @__PURE__ */ jsx5(Fragment2, { children });
|
|
398
|
+
}
|
|
399
|
+
export {
|
|
400
|
+
BuyButton,
|
|
401
|
+
ManageSubscriptionButton,
|
|
402
|
+
PricingTable,
|
|
403
|
+
ProtectedContent,
|
|
404
|
+
SubscriptionManager,
|
|
405
|
+
useCustomerAccess,
|
|
406
|
+
useSubscription
|
|
407
|
+
};
|
|
408
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/payments/useSubscription.ts","../../src/payments/useCustomerAccess.ts","../../src/payments/BuyButton.tsx","../../src/payments/PricingTable.tsx","../../src/payments/ManageSubscriptionButton.tsx","../../src/payments/SubscriptionManager.tsx","../../src/payments/ProtectedContent.tsx"],"sourcesContent":["import { useState, useEffect, useCallback, useContext } from 'react';\nimport { AuthContext } from '../auth/AuthProvider';\nimport { supabase } from '../core/supabase';\nimport { features } from '../core/config';\nimport type { SubscriptionTier, SubscriptionStatus } from '../core/types';\n\ninterface SubscriptionState {\n customerId: string | null;\n tier: SubscriptionTier;\n status: SubscriptionStatus | null;\n currentPeriodEnd: string | null;\n}\n\ninterface UseSubscriptionReturn {\n subscription: SubscriptionState | null;\n tier: SubscriptionTier;\n status: SubscriptionStatus | null;\n isActive: boolean;\n isPro: boolean;\n isBusiness: boolean;\n isEnterprise: boolean;\n canAccess: (requiredTier: SubscriptionTier) => boolean;\n loading: boolean;\n isConfigured: boolean;\n user: unknown;\n profile: unknown;\n isAuthenticated: boolean;\n refetch: () => Promise<void>;\n}\n\nconst TIER_LEVELS: Record<string, number> = {\n free: 0, starter: 1, creator: 2, pro: 3, business: 4, enterprise: 5,\n};\n\nexport function useSubscription(): UseSubscriptionReturn {\n const auth = useContext(AuthContext);\n const [subscription, setSubscription] = useState<SubscriptionState | null>(null);\n const [loading, setLoading] = useState(true);\n\n const fetchSubscription = useCallback(async () => {\n if (!auth?.profile) {\n setSubscription(null);\n setLoading(false);\n return;\n }\n\n const sub: SubscriptionState = {\n customerId: auth.profile.stripe_customer_id || null,\n tier: (auth.profile.subscription_tier as SubscriptionTier) || 'free',\n status: (auth.profile.subscription_status as SubscriptionStatus) || null,\n currentPeriodEnd: auth.profile.subscription_period_end || null,\n };\n\n setSubscription(sub);\n setLoading(false);\n }, [auth?.profile]);\n\n useEffect(() => {\n fetchSubscription();\n }, [fetchSubscription]);\n\n useEffect(() => {\n if (!features.auth || !auth?.user?.id) return;\n\n const channel = supabase\n .channel(`subscription_${auth.user.id}`)\n .on(\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n 'postgres_changes' as any,\n {\n event: 'UPDATE',\n schema: 'public',\n table: 'user_profiles',\n filter: `id=eq.${auth.user.id}`,\n },\n () => {\n auth.refetchProfile(auth.user!.id);\n }\n )\n .subscribe();\n\n return () => {\n supabase.removeChannel(channel);\n };\n }, [auth?.user?.id, auth]);\n\n const tier = subscription?.tier || 'free';\n const status = subscription?.status || null;\n const isActive = !status || status === 'active' || status === 'trialing';\n const tierLevel = TIER_LEVELS[tier] ?? 0;\n\n return {\n subscription,\n tier,\n status,\n isActive,\n isPro: tierLevel >= TIER_LEVELS.pro,\n isBusiness: tierLevel >= TIER_LEVELS.business,\n isEnterprise: tierLevel >= TIER_LEVELS.enterprise,\n canAccess: (requiredTier: SubscriptionTier) => tierLevel >= (TIER_LEVELS[requiredTier] ?? 0),\n loading: loading || (auth?.loading ?? false),\n isConfigured: features.payments,\n user: auth?.user ?? null,\n profile: auth?.profile ?? null,\n isAuthenticated: Boolean(auth?.user),\n refetch: fetchSubscription,\n };\n}\n","import { useState, useEffect, useCallback } from 'react';\nimport { ezcoder } from '../core/platform';\n\ninterface CustomerAccessReturn {\n loading: boolean;\n hasAccess: boolean;\n customer: Record<string, unknown> | null;\n subscription: Record<string, unknown> | null;\n error: string | null;\n refresh: () => Promise<void>;\n logout: () => void;\n}\n\nexport function useCustomerAccess(): CustomerAccessReturn {\n const [loading, setLoading] = useState(true);\n const [hasAccess, setHasAccess] = useState(false);\n const [customer, setCustomer] = useState<Record<string, unknown> | null>(null);\n const [subscription, setSubscription] = useState<Record<string, unknown> | null>(null);\n const [error, setError] = useState<string | null>(null);\n\n const checkAccess = useCallback(async () => {\n setLoading(true);\n setError(null);\n\n try {\n const customerId = typeof localStorage !== 'undefined'\n ? localStorage.getItem('stripeCustomerId')\n : null;\n const email = typeof localStorage !== 'undefined'\n ? localStorage.getItem('customerEmail')\n : null;\n\n if (!customerId && !email) {\n setHasAccess(false);\n setLoading(false);\n return;\n }\n\n const result = await ezcoder.stripe.getCustomerStatus({ customerId: customerId || undefined, email: email || undefined });\n\n if (result.success) {\n setHasAccess(Boolean(result.hasAccess));\n setCustomer((result.customer as Record<string, unknown>) || null);\n setSubscription((result.subscription as Record<string, unknown>) || null);\n } else {\n setHasAccess(false);\n setError(result.error || 'Failed to check access');\n }\n } catch (err: unknown) {\n setError(err instanceof Error ? err.message : 'Unknown error');\n setHasAccess(false);\n } finally {\n setLoading(false);\n }\n }, []);\n\n useEffect(() => {\n checkAccess();\n }, [checkAccess]);\n\n const logout = useCallback(() => {\n if (typeof localStorage !== 'undefined') {\n localStorage.removeItem('stripeCustomerId');\n localStorage.removeItem('customerEmail');\n }\n setHasAccess(false);\n setCustomer(null);\n setSubscription(null);\n }, []);\n\n return { loading, hasAccess, customer, subscription, error, refresh: checkAccess, logout };\n}\n","import { useState } from 'react';\nimport { ezcoder } from '../core/platform';\n\ninterface BuyButtonProps {\n priceId: string;\n productName?: string;\n className?: string;\n children?: React.ReactNode;\n disabled?: boolean;\n customerEmail?: string;\n quantity?: number;\n successUrl?: string;\n cancelUrl?: string;\n}\n\nexport function BuyButton({\n priceId,\n productName,\n className = '',\n children,\n disabled = false,\n customerEmail,\n quantity,\n successUrl,\n cancelUrl,\n}: BuyButtonProps) {\n const [loading, setLoading] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const handleClick = async () => {\n setError(null);\n setLoading(true);\n\n try {\n const result = await ezcoder.stripe.createCheckout(priceId, {\n customerEmail,\n quantity,\n successUrl,\n cancelUrl,\n redirect: true,\n });\n\n if (!result.success) {\n setError(result.error || 'Checkout failed');\n }\n } catch (err: unknown) {\n setError(err instanceof Error ? err.message : 'Checkout failed');\n } finally {\n setLoading(false);\n }\n };\n\n return (\n <div>\n <button\n onClick={handleClick}\n disabled={disabled || loading}\n className={className}\n style={!className ? {\n padding: '10px 20px',\n backgroundColor: disabled || loading ? '#9ca3af' : '#3b82f6',\n color: 'white',\n border: 'none',\n borderRadius: '6px',\n fontSize: '14px',\n fontWeight: 500,\n cursor: disabled || loading ? 'not-allowed' : 'pointer',\n } : undefined}\n >\n {loading ? 'Processing...' : children || `Buy ${productName || 'Now'}`}\n </button>\n {error && (\n <p style={{ color: '#dc2626', fontSize: '12px', marginTop: '4px' }}>{error}</p>\n )}\n </div>\n );\n}\n","import { useState } from 'react';\nimport { BuyButton } from './BuyButton';\n\ninterface PricingPlan {\n name: string;\n description: string;\n monthlyPriceId?: string;\n yearlyPriceId?: string;\n monthlyPrice: number;\n yearlyPrice?: number;\n features: string[];\n highlighted?: boolean;\n cta?: string;\n}\n\ninterface PricingTableProps {\n plans?: PricingPlan[];\n className?: string;\n customerEmail?: string;\n}\n\nconst DEFAULT_PLANS: PricingPlan[] = [\n {\n name: 'Starter',\n description: 'For individuals getting started',\n monthlyPrice: 0,\n features: ['Basic features', 'Community support'],\n cta: 'Get Started',\n },\n {\n name: 'Creator',\n description: 'For growing projects',\n monthlyPrice: 25,\n yearlyPrice: 270,\n features: ['All Starter features', 'Priority support', 'Advanced analytics'],\n highlighted: true,\n cta: 'Subscribe',\n },\n {\n name: 'Business',\n description: 'For teams and businesses',\n monthlyPrice: 50,\n yearlyPrice: 540,\n features: ['All Creator features', 'Team collaboration', 'Custom integrations'],\n cta: 'Subscribe',\n },\n {\n name: 'Enterprise',\n description: 'For large organizations',\n monthlyPrice: -1,\n features: ['All Business features', 'Dedicated support', 'SLA guarantee', 'Custom contracts'],\n cta: 'Contact Sales',\n },\n];\n\nexport function PricingTable({ plans = DEFAULT_PLANS, className = '', customerEmail }: PricingTableProps) {\n const [yearly, setYearly] = useState(false);\n\n return (\n <div className={className}>\n <div style={{ display: 'flex', justifyContent: 'center', marginBottom: '32px', gap: '8px', alignItems: 'center' }}>\n <span style={{ fontSize: '14px', color: !yearly ? '#111' : '#6b7280', fontWeight: !yearly ? 600 : 400 }}>Monthly</span>\n <button\n onClick={() => setYearly(!yearly)}\n style={{\n width: '44px', height: '24px', borderRadius: '12px',\n backgroundColor: yearly ? '#3b82f6' : '#d1d5db',\n border: 'none', cursor: 'pointer', position: 'relative',\n }}\n >\n <span style={{\n width: '18px', height: '18px', borderRadius: '50%', backgroundColor: 'white',\n position: 'absolute', top: '3px', left: yearly ? '23px' : '3px',\n transition: 'left 0.2s',\n }} />\n </button>\n <span style={{ fontSize: '14px', color: yearly ? '#111' : '#6b7280', fontWeight: yearly ? 600 : 400 }}>\n Yearly <span style={{ color: '#059669', fontSize: '12px' }}>Save 10%</span>\n </span>\n </div>\n\n <div style={{ display: 'grid', gridTemplateColumns: `repeat(${Math.min(plans.length, 4)}, 1fr)`, gap: '24px', maxWidth: '1200px', margin: '0 auto' }}>\n {plans.map((plan) => {\n const price = yearly && plan.yearlyPrice !== undefined ? plan.yearlyPrice / 12 : plan.monthlyPrice;\n const priceId = yearly ? plan.yearlyPriceId : plan.monthlyPriceId;\n\n return (\n <div\n key={plan.name}\n style={{\n border: plan.highlighted ? '2px solid #3b82f6' : '1px solid #e5e7eb',\n borderRadius: '12px', padding: '24px',\n backgroundColor: plan.highlighted ? '#eff6ff' : 'white',\n }}\n >\n <h3 style={{ fontSize: '1.25rem', fontWeight: 600, marginBottom: '4px' }}>{plan.name}</h3>\n <p style={{ color: '#6b7280', fontSize: '14px', marginBottom: '16px' }}>{plan.description}</p>\n\n <div style={{ marginBottom: '24px' }}>\n {plan.monthlyPrice === 0 ? (\n <span style={{ fontSize: '2rem', fontWeight: 700 }}>Free</span>\n ) : plan.monthlyPrice === -1 ? (\n <span style={{ fontSize: '1.5rem', fontWeight: 600 }}>Custom</span>\n ) : (\n <>\n <span style={{ fontSize: '2rem', fontWeight: 700 }}>${Math.round(price)}</span>\n <span style={{ color: '#6b7280', fontSize: '14px' }}>/mo</span>\n </>\n )}\n </div>\n\n <ul style={{ listStyle: 'none', padding: 0, marginBottom: '24px' }}>\n {plan.features.map((feature) => (\n <li key={feature} style={{ display: 'flex', alignItems: 'center', gap: '8px', marginBottom: '8px', fontSize: '14px' }}>\n <span style={{ color: '#059669' }}>✓</span> {feature}\n </li>\n ))}\n </ul>\n\n {priceId ? (\n <BuyButton priceId={priceId} productName={plan.name} customerEmail={customerEmail}>\n {plan.cta || 'Subscribe'}\n </BuyButton>\n ) : plan.monthlyPrice === 0 ? (\n <button style={{\n width: '100%', padding: '10px', border: '1px solid #d1d5db',\n borderRadius: '6px', background: 'white', cursor: 'pointer', fontSize: '14px',\n }}>\n {plan.cta || 'Get Started'}\n </button>\n ) : (\n <button style={{\n width: '100%', padding: '10px', border: '1px solid #d1d5db',\n borderRadius: '6px', background: 'white', cursor: 'pointer', fontSize: '14px',\n }}>\n {plan.cta || 'Contact Sales'}\n </button>\n )}\n </div>\n );\n })}\n </div>\n </div>\n );\n}\n","import { useState } from 'react';\nimport { ezcoder } from '../core/platform';\n\ninterface ManageSubscriptionButtonProps {\n children?: React.ReactNode;\n className?: string;\n customerId?: string;\n}\n\nexport function ManageSubscriptionButton({ children, className = '', customerId }: ManageSubscriptionButtonProps) {\n const [loading, setLoading] = useState(false);\n\n const handleClick = async () => {\n const id = customerId || (typeof localStorage !== 'undefined' ? localStorage.getItem('stripeCustomerId') : null);\n if (!id) return;\n\n setLoading(true);\n await ezcoder.stripe.createPortalSession(id, { redirect: true });\n setLoading(false);\n };\n\n return (\n <button\n onClick={handleClick}\n disabled={loading}\n className={className}\n style={!className ? {\n padding: '8px 16px',\n border: '1px solid #d1d5db',\n borderRadius: '6px',\n background: 'white',\n cursor: loading ? 'not-allowed' : 'pointer',\n fontSize: '14px',\n } : undefined}\n >\n {loading ? 'Loading...' : children || 'Manage Subscription'}\n </button>\n );\n}\n","import { useSubscription } from './useSubscription';\nimport { ManageSubscriptionButton } from './ManageSubscriptionButton';\n\ninterface SubscriptionManagerProps {\n className?: string;\n}\n\nconst STATUS_COLORS: Record<string, string> = {\n active: '#059669',\n trialing: '#3b82f6',\n past_due: '#d97706',\n canceled: '#dc2626',\n unpaid: '#dc2626',\n};\n\nexport function SubscriptionManager({ className = '' }: SubscriptionManagerProps) {\n const { subscription, tier, status, isActive, loading } = useSubscription();\n\n if (loading) {\n return <div style={{ padding: '20px', color: '#6b7280' }}>Loading subscription...</div>;\n }\n\n return (\n <div className={className} style={{ border: '1px solid #e5e7eb', borderRadius: '12px', padding: '24px' }}>\n <h3 style={{ fontSize: '1.125rem', fontWeight: 600, marginBottom: '16px' }}>Subscription</h3>\n\n <div style={{ display: 'grid', gap: '12px', marginBottom: '24px' }}>\n <div style={{ display: 'flex', justifyContent: 'space-between' }}>\n <span style={{ color: '#6b7280' }}>Plan</span>\n <span style={{ fontWeight: 500, textTransform: 'capitalize' }}>{tier}</span>\n </div>\n\n {status && (\n <div style={{ display: 'flex', justifyContent: 'space-between' }}>\n <span style={{ color: '#6b7280' }}>Status</span>\n <span style={{\n fontWeight: 500,\n color: STATUS_COLORS[status] || '#6b7280',\n textTransform: 'capitalize',\n }}>\n {status === 'past_due' ? 'Past Due' : status}\n </span>\n </div>\n )}\n\n {subscription?.currentPeriodEnd && (\n <div style={{ display: 'flex', justifyContent: 'space-between' }}>\n <span style={{ color: '#6b7280' }}>{isActive ? 'Renews' : 'Expires'}</span>\n <span>{new Date(subscription.currentPeriodEnd).toLocaleDateString()}</span>\n </div>\n )}\n </div>\n\n {subscription?.customerId && (\n <ManageSubscriptionButton customerId={subscription.customerId} />\n )}\n </div>\n );\n}\n","import { useCustomerAccess } from './useCustomerAccess';\n\ninterface ProtectedContentProps {\n children: React.ReactNode;\n fallback?: React.ReactNode;\n loadingComponent?: React.ReactNode;\n}\n\nexport function ProtectedContent({ children, fallback, loadingComponent }: ProtectedContentProps) {\n const { loading, hasAccess } = useCustomerAccess();\n\n if (loading) {\n return <>{loadingComponent || <div style={{ padding: '20px', textAlign: 'center', color: '#6b7280' }}>Loading...</div>}</>;\n }\n\n if (!hasAccess) {\n return <>{fallback || <div style={{ padding: '20px', textAlign: 'center', color: '#6b7280' }}>This content requires a subscription.</div>}</>;\n }\n\n return <>{children}</>;\n}\n"],"mappings":"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","names":["useState","useEffect","useCallback","useState","useCallback","useEffect","useState","useState","useState","jsx","jsxs","useState","useState","jsx","useState","jsx","jsxs","Fragment","jsx"]}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
|
|
3
|
+
interface UseRolesReturn {
|
|
4
|
+
roles: string[];
|
|
5
|
+
permissions: string[];
|
|
6
|
+
accessibleRoutes: string[];
|
|
7
|
+
loading: boolean;
|
|
8
|
+
error: string | null;
|
|
9
|
+
hasRole: (name: string) => boolean;
|
|
10
|
+
hasAnyRole: (names: string[]) => boolean;
|
|
11
|
+
hasAllRoles: (names: string[]) => boolean;
|
|
12
|
+
hasPermission: (perm: string) => boolean;
|
|
13
|
+
hasAnyPermission: (perms: string[]) => boolean;
|
|
14
|
+
hasAllPermissions: (perms: string[]) => boolean;
|
|
15
|
+
canAccessRoute: (path: string) => boolean;
|
|
16
|
+
refreshRoles: () => Promise<void>;
|
|
17
|
+
}
|
|
18
|
+
declare function useRoles(): UseRolesReturn;
|
|
19
|
+
|
|
20
|
+
interface RoleGateProps {
|
|
21
|
+
children: React.ReactNode;
|
|
22
|
+
roles?: string[];
|
|
23
|
+
permissions?: string[];
|
|
24
|
+
requireAll?: boolean;
|
|
25
|
+
fallback?: React.ReactNode;
|
|
26
|
+
loadingFallback?: React.ReactNode;
|
|
27
|
+
}
|
|
28
|
+
interface RouteGateProps {
|
|
29
|
+
children: React.ReactNode;
|
|
30
|
+
route: string;
|
|
31
|
+
fallback?: React.ReactNode;
|
|
32
|
+
loadingFallback?: React.ReactNode;
|
|
33
|
+
}
|
|
34
|
+
declare function RoleGate({ children, roles: requiredRoles, permissions: requiredPerms, requireAll, fallback, loadingFallback, }: RoleGateProps): react_jsx_runtime.JSX.Element;
|
|
35
|
+
declare function RouteGate({ children, route, fallback, loadingFallback }: RouteGateProps): react_jsx_runtime.JSX.Element;
|
|
36
|
+
|
|
37
|
+
export { RoleGate, RouteGate, useRoles };
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import {
|
|
2
|
+
AuthContext
|
|
3
|
+
} from "../chunk-YNDCD53D.js";
|
|
4
|
+
import "../chunk-5XIZHBKE.js";
|
|
5
|
+
import {
|
|
6
|
+
features,
|
|
7
|
+
supabase
|
|
8
|
+
} from "../chunk-G7XDUN3Z.js";
|
|
9
|
+
|
|
10
|
+
// src/roles/useRoles.ts
|
|
11
|
+
import { useState, useEffect, useCallback, useContext } from "react";
|
|
12
|
+
function useRoles() {
|
|
13
|
+
const auth = useContext(AuthContext);
|
|
14
|
+
const [roles, setRoles] = useState([]);
|
|
15
|
+
const [permissions, setPermissions] = useState([]);
|
|
16
|
+
const [accessibleRoutes, setAccessibleRoutes] = useState([]);
|
|
17
|
+
const [loading, setLoading] = useState(true);
|
|
18
|
+
const [error, setError] = useState(null);
|
|
19
|
+
const fetchRoles = useCallback(async () => {
|
|
20
|
+
if (!auth?.user?.id || !features.auth) {
|
|
21
|
+
setRoles([]);
|
|
22
|
+
setPermissions([]);
|
|
23
|
+
setAccessibleRoutes([]);
|
|
24
|
+
setLoading(false);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const { data, error: rpcError } = await supabase.rpc("get_user_roles", {
|
|
29
|
+
user_uuid: auth.user.id
|
|
30
|
+
});
|
|
31
|
+
if (rpcError) {
|
|
32
|
+
setError(rpcError.message);
|
|
33
|
+
setLoading(false);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const roleData = data || [];
|
|
37
|
+
const allRoles = roleData.map((r) => r.role_name);
|
|
38
|
+
const allPerms = [...new Set(roleData.flatMap((r) => r.permissions || []))];
|
|
39
|
+
const allRoutes = [...new Set(roleData.flatMap((r) => r.can_access_routes || []))];
|
|
40
|
+
setRoles(allRoles);
|
|
41
|
+
setPermissions(allPerms);
|
|
42
|
+
setAccessibleRoutes(allRoutes);
|
|
43
|
+
setError(null);
|
|
44
|
+
} catch (err) {
|
|
45
|
+
setError(err instanceof Error ? err.message : "Failed to fetch roles");
|
|
46
|
+
} finally {
|
|
47
|
+
setLoading(false);
|
|
48
|
+
}
|
|
49
|
+
}, [auth?.user?.id]);
|
|
50
|
+
useEffect(() => {
|
|
51
|
+
fetchRoles();
|
|
52
|
+
}, [fetchRoles]);
|
|
53
|
+
const hasRole = useCallback((name) => roles.includes(name), [roles]);
|
|
54
|
+
const hasAnyRole = useCallback((names) => names.some((n) => roles.includes(n)), [roles]);
|
|
55
|
+
const hasAllRoles = useCallback((names) => names.every((n) => roles.includes(n)), [roles]);
|
|
56
|
+
const hasPermission = useCallback((perm) => permissions.includes(perm), [permissions]);
|
|
57
|
+
const hasAnyPermission = useCallback((perms) => perms.some((p) => permissions.includes(p)), [permissions]);
|
|
58
|
+
const hasAllPermissions = useCallback((perms) => perms.every((p) => permissions.includes(p)), [permissions]);
|
|
59
|
+
const canAccessRoute = useCallback((path) => {
|
|
60
|
+
return accessibleRoutes.some((route) => {
|
|
61
|
+
if (route.endsWith("/*")) {
|
|
62
|
+
return path.startsWith(route.slice(0, -2));
|
|
63
|
+
}
|
|
64
|
+
return route === path;
|
|
65
|
+
});
|
|
66
|
+
}, [accessibleRoutes]);
|
|
67
|
+
return {
|
|
68
|
+
roles,
|
|
69
|
+
permissions,
|
|
70
|
+
accessibleRoutes,
|
|
71
|
+
loading,
|
|
72
|
+
error,
|
|
73
|
+
hasRole,
|
|
74
|
+
hasAnyRole,
|
|
75
|
+
hasAllRoles,
|
|
76
|
+
hasPermission,
|
|
77
|
+
hasAnyPermission,
|
|
78
|
+
hasAllPermissions,
|
|
79
|
+
canAccessRoute,
|
|
80
|
+
refreshRoles: fetchRoles
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// src/roles/RoleGate.tsx
|
|
85
|
+
import { Fragment, jsx } from "react/jsx-runtime";
|
|
86
|
+
function RoleGate({
|
|
87
|
+
children,
|
|
88
|
+
roles: requiredRoles,
|
|
89
|
+
permissions: requiredPerms,
|
|
90
|
+
requireAll = false,
|
|
91
|
+
fallback = null,
|
|
92
|
+
loadingFallback = null
|
|
93
|
+
}) {
|
|
94
|
+
const { hasRole, hasAnyRole, hasAllRoles, hasPermission, hasAnyPermission, hasAllPermissions, loading } = useRoles();
|
|
95
|
+
if (loading) return /* @__PURE__ */ jsx(Fragment, { children: loadingFallback });
|
|
96
|
+
let hasAccess = true;
|
|
97
|
+
if (requiredRoles?.length) {
|
|
98
|
+
hasAccess = requireAll ? hasAllRoles(requiredRoles) : hasAnyRole(requiredRoles);
|
|
99
|
+
}
|
|
100
|
+
if (hasAccess && requiredPerms?.length) {
|
|
101
|
+
hasAccess = requireAll ? hasAllPermissions(requiredPerms) : hasAnyPermission(requiredPerms);
|
|
102
|
+
}
|
|
103
|
+
if (!hasAccess && !requiredRoles?.length && !requiredPerms?.length) {
|
|
104
|
+
hasAccess = true;
|
|
105
|
+
}
|
|
106
|
+
void hasRole;
|
|
107
|
+
void hasPermission;
|
|
108
|
+
return hasAccess ? /* @__PURE__ */ jsx(Fragment, { children }) : /* @__PURE__ */ jsx(Fragment, { children: fallback });
|
|
109
|
+
}
|
|
110
|
+
function RouteGate({ children, route, fallback = null, loadingFallback = null }) {
|
|
111
|
+
const { canAccessRoute, loading } = useRoles();
|
|
112
|
+
if (loading) return /* @__PURE__ */ jsx(Fragment, { children: loadingFallback });
|
|
113
|
+
return canAccessRoute(route) ? /* @__PURE__ */ jsx(Fragment, { children }) : /* @__PURE__ */ jsx(Fragment, { children: fallback });
|
|
114
|
+
}
|
|
115
|
+
export {
|
|
116
|
+
RoleGate,
|
|
117
|
+
RouteGate,
|
|
118
|
+
useRoles
|
|
119
|
+
};
|
|
120
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/roles/useRoles.ts","../../src/roles/RoleGate.tsx"],"sourcesContent":["import { useState, useEffect, useCallback, useContext } from 'react';\nimport { supabase } from '../core/supabase';\nimport { features } from '../core/config';\nimport { AuthContext } from '../auth/AuthProvider';\n\ninterface RoleData {\n role_name: string;\n display_name: string;\n permissions: string[];\n can_access_routes: string[];\n}\n\ninterface UseRolesReturn {\n roles: string[];\n permissions: string[];\n accessibleRoutes: string[];\n loading: boolean;\n error: string | null;\n hasRole: (name: string) => boolean;\n hasAnyRole: (names: string[]) => boolean;\n hasAllRoles: (names: string[]) => boolean;\n hasPermission: (perm: string) => boolean;\n hasAnyPermission: (perms: string[]) => boolean;\n hasAllPermissions: (perms: string[]) => boolean;\n canAccessRoute: (path: string) => boolean;\n refreshRoles: () => Promise<void>;\n}\n\nexport function useRoles(): UseRolesReturn {\n const auth = useContext(AuthContext);\n const [roles, setRoles] = useState<string[]>([]);\n const [permissions, setPermissions] = useState<string[]>([]);\n const [accessibleRoutes, setAccessibleRoutes] = useState<string[]>([]);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n\n const fetchRoles = useCallback(async () => {\n if (!auth?.user?.id || !features.auth) {\n setRoles([]);\n setPermissions([]);\n setAccessibleRoutes([]);\n setLoading(false);\n return;\n }\n\n try {\n const { data, error: rpcError } = await supabase.rpc('get_user_roles', {\n user_uuid: auth.user.id,\n });\n\n if (rpcError) {\n setError(rpcError.message);\n setLoading(false);\n return;\n }\n\n const roleData = (data || []) as RoleData[];\n const allRoles = roleData.map((r) => r.role_name);\n const allPerms = [...new Set(roleData.flatMap((r) => r.permissions || []))];\n const allRoutes = [...new Set(roleData.flatMap((r) => r.can_access_routes || []))];\n\n setRoles(allRoles);\n setPermissions(allPerms);\n setAccessibleRoutes(allRoutes);\n setError(null);\n } catch (err: unknown) {\n setError(err instanceof Error ? err.message : 'Failed to fetch roles');\n } finally {\n setLoading(false);\n }\n }, [auth?.user?.id]);\n\n useEffect(() => {\n fetchRoles();\n }, [fetchRoles]);\n\n const hasRole = useCallback((name: string) => roles.includes(name), [roles]);\n const hasAnyRole = useCallback((names: string[]) => names.some((n) => roles.includes(n)), [roles]);\n const hasAllRoles = useCallback((names: string[]) => names.every((n) => roles.includes(n)), [roles]);\n const hasPermission = useCallback((perm: string) => permissions.includes(perm), [permissions]);\n const hasAnyPermission = useCallback((perms: string[]) => perms.some((p) => permissions.includes(p)), [permissions]);\n const hasAllPermissions = useCallback((perms: string[]) => perms.every((p) => permissions.includes(p)), [permissions]);\n\n const canAccessRoute = useCallback((path: string) => {\n return accessibleRoutes.some((route) => {\n if (route.endsWith('/*')) {\n return path.startsWith(route.slice(0, -2));\n }\n return route === path;\n });\n }, [accessibleRoutes]);\n\n return {\n roles, permissions, accessibleRoutes, loading, error,\n hasRole, hasAnyRole, hasAllRoles, hasPermission, hasAnyPermission, hasAllPermissions,\n canAccessRoute, refreshRoles: fetchRoles,\n };\n}\n","import { useRoles } from './useRoles';\n\ninterface RoleGateProps {\n children: React.ReactNode;\n roles?: string[];\n permissions?: string[];\n requireAll?: boolean;\n fallback?: React.ReactNode;\n loadingFallback?: React.ReactNode;\n}\n\ninterface RouteGateProps {\n children: React.ReactNode;\n route: string;\n fallback?: React.ReactNode;\n loadingFallback?: React.ReactNode;\n}\n\nexport function RoleGate({\n children,\n roles: requiredRoles,\n permissions: requiredPerms,\n requireAll = false,\n fallback = null,\n loadingFallback = null,\n}: RoleGateProps) {\n const { hasRole, hasAnyRole, hasAllRoles, hasPermission, hasAnyPermission, hasAllPermissions, loading } = useRoles();\n\n if (loading) return <>{loadingFallback}</>;\n\n let hasAccess = true;\n\n if (requiredRoles?.length) {\n hasAccess = requireAll ? hasAllRoles(requiredRoles) : hasAnyRole(requiredRoles);\n }\n\n if (hasAccess && requiredPerms?.length) {\n hasAccess = requireAll ? hasAllPermissions(requiredPerms) : hasAnyPermission(requiredPerms);\n }\n\n if (!hasAccess && !requiredRoles?.length && !requiredPerms?.length) {\n hasAccess = true;\n }\n\n // Use individual checks to satisfy linter\n void hasRole;\n void hasPermission;\n\n return hasAccess ? <>{children}</> : <>{fallback}</>;\n}\n\nexport function RouteGate({ children, route, fallback = null, loadingFallback = null }: RouteGateProps) {\n const { canAccessRoute, loading } = useRoles();\n\n if (loading) return <>{loadingFallback}</>;\n\n return canAccessRoute(route) ? <>{children}</> : <>{fallback}</>;\n}\n"],"mappings":"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","names":[]}
|