@sudobility/entity_pages 0.0.10 → 0.0.11

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/index.d.ts CHANGED
@@ -20,4 +20,4 @@
20
20
  * }
21
21
  * ```
22
22
  */
23
- export { EntityListPage, type EntityListPageProps, MembersManagementPage, type MembersManagementPageProps, InvitationsPage, type InvitationsPageProps, } from './pages';
23
+ export { EntityListPage, type EntityListPageProps, MembersManagementPage, type MembersManagementPageProps, InvitationsPage, type InvitationsPageProps, EntitySubscriptionsPage, type EntitySubscriptionsPageProps, type SubscriptionProduct, type CurrentSubscription, type SubscriptionContextValue, type TranslationFunction, } from './pages';
package/dist/index.esm.js CHANGED
@@ -1,78 +1,79 @@
1
- import { jsxs as t, jsx as e } from "react/jsx-runtime";
2
- import { useState as f } from "react";
3
- import { Plus as I } from "lucide-react";
4
- import { EntityList as x, InvitationForm as M, InvitationList as C, MemberList as S } from "@sudobility/entity-components";
5
- import { useEntities as z, useCreateEntity as k, useEntityMembers as L, useUpdateMemberRole as A, useRemoveMember as F, useEntityInvitations as E, useCreateInvitation as P, useCancelInvitation as D, useMyInvitations as R, useAcceptInvitation as j, useDeclineInvitation as O } from "@sudobility/entity_client";
6
- function U({
7
- client: i,
8
- onSelectEntity: n
1
+ import { jsxs as a, jsx as i, Fragment as te } from "react/jsx-runtime";
2
+ import { useState as w, useEffect as ie, useCallback as x } from "react";
3
+ import { Plus as ne } from "lucide-react";
4
+ import { EntityList as j, InvitationForm as re, InvitationList as B, MemberList as ae } from "@sudobility/entity-components";
5
+ import { useEntities as se, useCreateEntity as le, useEntityMembers as oe, useUpdateMemberRole as ce, useRemoveMember as de, useEntityInvitations as ue, useCreateInvitation as me, useCancelInvitation as he, useMyInvitations as ye, useAcceptInvitation as ge, useDeclineInvitation as pe } from "@sudobility/entity_client";
6
+ import { SubscriptionLayout as fe, SubscriptionTile as O, SegmentedControl as be } from "@sudobility/subscription-components";
7
+ function Ie({
8
+ client: l,
9
+ onSelectEntity: r
9
10
  }) {
10
- const [p, a] = f(!1), [s, c] = f({
11
+ const [e, h] = w(!1), [p, y] = w({
11
12
  displayName: "",
12
13
  description: ""
13
- }), [h, d] = f(null), { data: m = [], isLoading: r } = z(i), l = k(i), b = m.filter((o) => o.entityType === "personal"), v = m.filter(
14
- (o) => o.entityType === "organization"
14
+ }), [N, o] = w(null), { data: s = [], isLoading: d } = se(l), c = le(l), I = s.filter((u) => u.entityType === "personal"), L = s.filter(
15
+ (u) => u.entityType === "organization"
15
16
  );
16
- return /* @__PURE__ */ t("div", { className: "space-y-8", children: [
17
- /* @__PURE__ */ t("div", { className: "flex items-center justify-between", children: [
18
- /* @__PURE__ */ t("div", { children: [
19
- /* @__PURE__ */ e("h1", { className: "text-2xl font-bold text-foreground", children: "Workspaces" }),
20
- /* @__PURE__ */ e("p", { className: "text-muted-foreground", children: "Manage your personal and organization workspaces" })
17
+ return /* @__PURE__ */ a("div", { className: "space-y-8", children: [
18
+ /* @__PURE__ */ a("div", { className: "flex items-center justify-between", children: [
19
+ /* @__PURE__ */ a("div", { children: [
20
+ /* @__PURE__ */ i("h1", { className: "text-2xl font-bold text-foreground", children: "Workspaces" }),
21
+ /* @__PURE__ */ i("p", { className: "text-muted-foreground", children: "Manage your personal and organization workspaces" })
21
22
  ] }),
22
- /* @__PURE__ */ t(
23
+ /* @__PURE__ */ a(
23
24
  "button",
24
25
  {
25
26
  type: "button",
26
- onClick: () => a(!0),
27
+ onClick: () => h(!0),
27
28
  className: "flex items-center gap-2 px-4 py-2 rounded-lg bg-primary text-primary-foreground font-medium hover:bg-primary/90 transition-colors",
28
29
  children: [
29
- /* @__PURE__ */ e(I, { className: "h-4 w-4" }),
30
- /* @__PURE__ */ e("span", { children: "New Organization" })
30
+ /* @__PURE__ */ i(ne, { className: "h-4 w-4" }),
31
+ /* @__PURE__ */ i("span", { children: "New Organization" })
31
32
  ]
32
33
  }
33
34
  )
34
35
  ] }),
35
- p && /* @__PURE__ */ e("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50", children: /* @__PURE__ */ t("div", { className: "w-full max-w-md rounded-lg bg-background p-6 shadow-lg", children: [
36
- /* @__PURE__ */ e("h2", { className: "text-lg font-semibold mb-4", children: "Create Organization" }),
37
- /* @__PURE__ */ t("form", { onSubmit: async (o) => {
38
- if (o.preventDefault(), d(null), !s.displayName.trim()) {
39
- d("Display name is required");
36
+ e && /* @__PURE__ */ i("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50", children: /* @__PURE__ */ a("div", { className: "w-full max-w-md rounded-lg bg-background p-6 shadow-lg", children: [
37
+ /* @__PURE__ */ i("h2", { className: "text-lg font-semibold mb-4", children: "Create Organization" }),
38
+ /* @__PURE__ */ a("form", { onSubmit: async (u) => {
39
+ if (u.preventDefault(), o(null), !p.displayName.trim()) {
40
+ o("Display name is required");
40
41
  return;
41
42
  }
42
43
  try {
43
- await l.mutateAsync({
44
- displayName: s.displayName.trim(),
45
- description: s.description.trim() || void 0
46
- }), a(!1), c({ displayName: "", description: "" });
47
- } catch (u) {
48
- d(u.message || "Failed to create organization");
44
+ await c.mutateAsync({
45
+ displayName: p.displayName.trim(),
46
+ description: p.description.trim() || void 0
47
+ }), h(!1), y({ displayName: "", description: "" });
48
+ } catch (v) {
49
+ o(v.message || "Failed to create organization");
49
50
  }
50
51
  }, className: "space-y-4", children: [
51
- /* @__PURE__ */ t("div", { children: [
52
- /* @__PURE__ */ e("label", { className: "block text-sm font-medium mb-1", children: "Display Name" }),
53
- /* @__PURE__ */ e(
52
+ /* @__PURE__ */ a("div", { children: [
53
+ /* @__PURE__ */ i("label", { className: "block text-sm font-medium mb-1", children: "Display Name" }),
54
+ /* @__PURE__ */ i(
54
55
  "input",
55
56
  {
56
57
  type: "text",
57
- value: s.displayName,
58
- onChange: (o) => c((u) => ({
59
- ...u,
60
- displayName: o.target.value
58
+ value: p.displayName,
59
+ onChange: (u) => y((v) => ({
60
+ ...v,
61
+ displayName: u.target.value
61
62
  })),
62
63
  placeholder: "My Organization",
63
64
  className: "w-full px-3 py-2 rounded-lg border bg-background focus:outline-none focus:ring-2 focus:ring-primary"
64
65
  }
65
66
  )
66
67
  ] }),
67
- /* @__PURE__ */ t("div", { children: [
68
- /* @__PURE__ */ e("label", { className: "block text-sm font-medium mb-1", children: "Description (optional)" }),
69
- /* @__PURE__ */ e(
68
+ /* @__PURE__ */ a("div", { children: [
69
+ /* @__PURE__ */ i("label", { className: "block text-sm font-medium mb-1", children: "Description (optional)" }),
70
+ /* @__PURE__ */ i(
70
71
  "textarea",
71
72
  {
72
- value: s.description,
73
- onChange: (o) => c((u) => ({
74
- ...u,
75
- description: o.target.value
73
+ value: p.description,
74
+ onChange: (u) => y((v) => ({
75
+ ...v,
76
+ description: u.target.value
76
77
  })),
77
78
  placeholder: "What is this organization for?",
78
79
  rows: 3,
@@ -80,200 +81,481 @@ function U({
80
81
  }
81
82
  )
82
83
  ] }),
83
- h && /* @__PURE__ */ e("p", { className: "text-sm text-destructive", children: h }),
84
- /* @__PURE__ */ t("div", { className: "flex justify-end gap-2", children: [
85
- /* @__PURE__ */ e(
84
+ N && /* @__PURE__ */ i("p", { className: "text-sm text-destructive", children: N }),
85
+ /* @__PURE__ */ a("div", { className: "flex justify-end gap-2", children: [
86
+ /* @__PURE__ */ i(
86
87
  "button",
87
88
  {
88
89
  type: "button",
89
90
  onClick: () => {
90
- a(!1), c({ displayName: "", description: "" }), d(null);
91
+ h(!1), y({ displayName: "", description: "" }), o(null);
91
92
  },
92
93
  className: "px-4 py-2 rounded-lg border hover:bg-muted transition-colors",
93
94
  children: "Cancel"
94
95
  }
95
96
  ),
96
- /* @__PURE__ */ e(
97
+ /* @__PURE__ */ i(
97
98
  "button",
98
99
  {
99
100
  type: "submit",
100
- disabled: l.isPending,
101
+ disabled: c.isPending,
101
102
  className: "px-4 py-2 rounded-lg bg-primary text-primary-foreground font-medium hover:bg-primary/90 transition-colors disabled:opacity-50",
102
- children: l.isPending ? "Creating..." : "Create"
103
+ children: c.isPending ? "Creating..." : "Create"
103
104
  }
104
105
  )
105
106
  ] })
106
107
  ] })
107
108
  ] }) }),
108
- b.length > 0 && /* @__PURE__ */ t("div", { children: [
109
- /* @__PURE__ */ e("h2", { className: "text-lg font-semibold mb-3", children: "Personal Workspace" }),
110
- /* @__PURE__ */ e(
111
- x,
109
+ I.length > 0 && /* @__PURE__ */ a("div", { children: [
110
+ /* @__PURE__ */ i("h2", { className: "text-lg font-semibold mb-3", children: "Personal Workspace" }),
111
+ /* @__PURE__ */ i(
112
+ j,
112
113
  {
113
- entities: b,
114
- onSelect: n,
115
- isLoading: r
114
+ entities: I,
115
+ onSelect: r,
116
+ isLoading: d
116
117
  }
117
118
  )
118
119
  ] }),
119
- /* @__PURE__ */ t("div", { children: [
120
- /* @__PURE__ */ e("h2", { className: "text-lg font-semibold mb-3", children: "Organizations" }),
121
- v.length === 0 && !r ? /* @__PURE__ */ t("div", { className: "text-center py-8 text-muted-foreground border border-dashed rounded-lg", children: [
122
- /* @__PURE__ */ e("p", { children: "No organizations yet" }),
123
- /* @__PURE__ */ e(
120
+ /* @__PURE__ */ a("div", { children: [
121
+ /* @__PURE__ */ i("h2", { className: "text-lg font-semibold mb-3", children: "Organizations" }),
122
+ L.length === 0 && !d ? /* @__PURE__ */ a("div", { className: "text-center py-8 text-muted-foreground border border-dashed rounded-lg", children: [
123
+ /* @__PURE__ */ i("p", { children: "No organizations yet" }),
124
+ /* @__PURE__ */ i(
124
125
  "button",
125
126
  {
126
127
  type: "button",
127
- onClick: () => a(!0),
128
+ onClick: () => h(!0),
128
129
  className: "mt-2 text-primary hover:underline",
129
130
  children: "Create your first organization"
130
131
  }
131
132
  )
132
- ] }) : /* @__PURE__ */ e(
133
- x,
133
+ ] }) : /* @__PURE__ */ i(
134
+ j,
134
135
  {
135
- entities: v,
136
- onSelect: n,
137
- isLoading: r
136
+ entities: L,
137
+ onSelect: r,
138
+ isLoading: d
138
139
  }
139
140
  )
140
141
  ] })
141
142
  ] });
142
143
  }
143
- function B({
144
- client: i,
145
- entity: n,
146
- currentUserId: p
144
+ function Le({
145
+ client: l,
146
+ entity: r,
147
+ currentUserId: e
147
148
  }) {
148
- const a = n.userRole === "admin", { data: s = [], isLoading: c } = L(
149
- i,
150
- n.entitySlug
151
- ), h = A(i), d = F(i), { data: m = [], isLoading: r } = E(i, a ? n.entitySlug : null), l = P(i), b = D(i), v = async (g, y) => {
149
+ const h = r.userRole === "admin", { data: p = [], isLoading: y } = oe(
150
+ l,
151
+ r.entitySlug
152
+ ), N = ce(l), o = de(l), { data: s = [], isLoading: d } = ue(l, h ? r.entitySlug : null), c = me(l), I = he(l), L = async (g, f) => {
152
153
  try {
153
- await h.mutateAsync({
154
- entitySlug: n.entitySlug,
154
+ await N.mutateAsync({
155
+ entitySlug: r.entitySlug,
155
156
  memberId: g,
156
- role: y
157
+ role: f
157
158
  });
158
- } catch (w) {
159
- console.error("Failed to update role:", w);
159
+ } catch (S) {
160
+ console.error("Failed to update role:", S);
160
161
  }
161
- }, N = async (g) => {
162
+ }, P = async (g) => {
162
163
  if (confirm("Are you sure you want to remove this member?"))
163
164
  try {
164
- await d.mutateAsync({
165
- entitySlug: n.entitySlug,
165
+ await o.mutateAsync({
166
+ entitySlug: r.entitySlug,
166
167
  memberId: g
167
168
  });
168
- } catch (y) {
169
- console.error("Failed to remove member:", y);
169
+ } catch (f) {
170
+ console.error("Failed to remove member:", f);
170
171
  }
171
- }, o = async (g) => {
172
- await l.mutateAsync({
173
- entitySlug: n.entitySlug,
172
+ }, u = async (g) => {
173
+ await c.mutateAsync({
174
+ entitySlug: r.entitySlug,
174
175
  request: g
175
176
  });
176
- }, u = async (g) => {
177
+ }, v = async (g) => {
177
178
  try {
178
- await b.mutateAsync({
179
- entitySlug: n.entitySlug,
179
+ await I.mutateAsync({
180
+ entitySlug: r.entitySlug,
180
181
  invitationId: g
181
182
  });
182
- } catch (y) {
183
- console.error("Failed to cancel invitation:", y);
183
+ } catch (f) {
184
+ console.error("Failed to cancel invitation:", f);
184
185
  }
185
186
  };
186
- return n.entityType === "personal" ? /* @__PURE__ */ t("div", { className: "text-center py-12 text-muted-foreground", children: [
187
- /* @__PURE__ */ e("p", { children: "Personal workspaces cannot have additional members." }),
188
- /* @__PURE__ */ e("p", { className: "mt-2", children: "Create an organization to collaborate with others." })
189
- ] }) : /* @__PURE__ */ t("div", { className: "space-y-8", children: [
190
- /* @__PURE__ */ t("div", { children: [
191
- /* @__PURE__ */ e("h1", { className: "text-2xl font-bold text-foreground", children: "Members" }),
192
- /* @__PURE__ */ t("p", { className: "text-muted-foreground", children: [
187
+ return r.entityType === "personal" ? /* @__PURE__ */ a("div", { className: "text-center py-12 text-muted-foreground", children: [
188
+ /* @__PURE__ */ i("p", { children: "Personal workspaces cannot have additional members." }),
189
+ /* @__PURE__ */ i("p", { className: "mt-2", children: "Create an organization to collaborate with others." })
190
+ ] }) : /* @__PURE__ */ a("div", { className: "space-y-8", children: [
191
+ /* @__PURE__ */ a("div", { children: [
192
+ /* @__PURE__ */ i("h1", { className: "text-2xl font-bold text-foreground", children: "Members" }),
193
+ /* @__PURE__ */ a("p", { className: "text-muted-foreground", children: [
193
194
  "Manage members and invitations for ",
194
- n.displayName
195
+ r.displayName
195
196
  ] })
196
197
  ] }),
197
- a && /* @__PURE__ */ t("div", { className: "rounded-lg border p-4", children: [
198
- /* @__PURE__ */ e("h2", { className: "text-lg font-semibold mb-4", children: "Invite Members" }),
199
- /* @__PURE__ */ e(
200
- M,
198
+ h && /* @__PURE__ */ a("div", { className: "rounded-lg border p-4", children: [
199
+ /* @__PURE__ */ i("h2", { className: "text-lg font-semibold mb-4", children: "Invite Members" }),
200
+ /* @__PURE__ */ i(
201
+ re,
201
202
  {
202
- onSubmit: o,
203
- isSubmitting: l.isPending
203
+ onSubmit: u,
204
+ isSubmitting: c.isPending
204
205
  }
205
206
  )
206
207
  ] }),
207
- a && /* @__PURE__ */ t("div", { children: [
208
- /* @__PURE__ */ e("h2", { className: "text-lg font-semibold mb-3", children: "Pending Invitations" }),
209
- /* @__PURE__ */ e(
210
- C,
208
+ h && /* @__PURE__ */ a("div", { children: [
209
+ /* @__PURE__ */ i("h2", { className: "text-lg font-semibold mb-3", children: "Pending Invitations" }),
210
+ /* @__PURE__ */ i(
211
+ B,
211
212
  {
212
- invitations: m,
213
+ invitations: s,
213
214
  mode: "admin",
214
- onCancel: u,
215
- isLoading: r,
215
+ onCancel: v,
216
+ isLoading: d,
216
217
  emptyMessage: "No pending invitations"
217
218
  }
218
219
  )
219
220
  ] }),
220
- /* @__PURE__ */ t("div", { children: [
221
- /* @__PURE__ */ t("h2", { className: "text-lg font-semibold mb-3", children: [
221
+ /* @__PURE__ */ a("div", { children: [
222
+ /* @__PURE__ */ a("h2", { className: "text-lg font-semibold mb-3", children: [
222
223
  "Current Members (",
223
- s.length,
224
+ p.length,
224
225
  ")"
225
226
  ] }),
226
- /* @__PURE__ */ e(
227
- S,
227
+ /* @__PURE__ */ i(
228
+ ae,
228
229
  {
229
- members: s,
230
- currentUserId: p,
231
- canManage: a,
232
- onRoleChange: v,
233
- onRemove: N,
234
- isLoading: c
230
+ members: p,
231
+ currentUserId: e,
232
+ canManage: h,
233
+ onRoleChange: L,
234
+ onRemove: P,
235
+ isLoading: y
235
236
  }
236
237
  )
237
238
  ] })
238
239
  ] });
239
240
  }
240
- function G({
241
- client: i,
242
- onInvitationAccepted: n
241
+ function Me({
242
+ client: l,
243
+ onInvitationAccepted: r
243
244
  }) {
244
- const { data: p = [], isLoading: a } = R(i), s = j(i), c = O(i), h = async (r) => {
245
+ const { data: e = [], isLoading: h } = ye(l), p = ge(l), y = pe(l), N = async (d) => {
245
246
  try {
246
- await s.mutateAsync(r), n?.();
247
- } catch (l) {
248
- console.error("Failed to accept invitation:", l);
247
+ await p.mutateAsync(d), r?.();
248
+ } catch (c) {
249
+ console.error("Failed to accept invitation:", c);
249
250
  }
250
- }, d = async (r) => {
251
+ }, o = async (d) => {
251
252
  try {
252
- await c.mutateAsync(r);
253
- } catch (l) {
254
- console.error("Failed to decline invitation:", l);
253
+ await y.mutateAsync(d);
254
+ } catch (c) {
255
+ console.error("Failed to decline invitation:", c);
255
256
  }
256
- }, m = p.filter((r) => r.status === "pending").length;
257
- return /* @__PURE__ */ t("div", { className: "space-y-6", children: [
258
- /* @__PURE__ */ t("div", { children: [
259
- /* @__PURE__ */ e("h1", { className: "text-2xl font-bold text-foreground", children: "Invitations" }),
260
- /* @__PURE__ */ e("p", { className: "text-muted-foreground", children: m > 0 ? `You have ${m} pending invitation${m > 1 ? "s" : ""}` : "No pending invitations" })
257
+ }, s = e.filter((d) => d.status === "pending").length;
258
+ return /* @__PURE__ */ a("div", { className: "space-y-6", children: [
259
+ /* @__PURE__ */ a("div", { children: [
260
+ /* @__PURE__ */ i("h1", { className: "text-2xl font-bold text-foreground", children: "Invitations" }),
261
+ /* @__PURE__ */ i("p", { className: "text-muted-foreground", children: s > 0 ? `You have ${s} pending invitation${s > 1 ? "s" : ""}` : "No pending invitations" })
261
262
  ] }),
262
- /* @__PURE__ */ e(
263
- C,
263
+ /* @__PURE__ */ i(
264
+ B,
264
265
  {
265
- invitations: p,
266
+ invitations: e,
266
267
  mode: "user",
267
- onAccept: h,
268
- onDecline: d,
269
- isLoading: a,
268
+ onAccept: N,
269
+ onDecline: o,
270
+ isLoading: h,
270
271
  emptyMessage: "You don't have any pending invitations"
271
272
  }
272
273
  )
273
274
  ] });
274
275
  }
276
+ const C = {
277
+ ultra_yearly: "bandwidth_ultra",
278
+ ultra_monthly: "bandwidth_ultra",
279
+ pro_yearly: "bandwidth_pro",
280
+ pro_monthly: "bandwidth_pro",
281
+ dev_yearly: "bandwidth_dev",
282
+ dev_monthly: "bandwidth_dev"
283
+ };
284
+ function Ae({
285
+ subscription: l,
286
+ rateLimitsConfig: r,
287
+ t: e,
288
+ onPurchaseSuccess: h,
289
+ onRestoreSuccess: p,
290
+ onError: y,
291
+ onWarning: N
292
+ }) {
293
+ const {
294
+ products: o,
295
+ currentSubscription: s,
296
+ isLoading: d,
297
+ error: c,
298
+ purchase: I,
299
+ restore: L,
300
+ clearError: P
301
+ } = l, [u, v] = w("monthly"), [g, f] = w(null), [S, F] = w(!1), [M, T] = w(!1);
302
+ ie(() => {
303
+ c && (y?.(e("common.error"), c), P());
304
+ }, [c, P, e, y]);
305
+ const k = o.filter((t) => {
306
+ if (!t.period) return !1;
307
+ const n = t.period.includes("Y") || t.period.includes("year");
308
+ return u === "yearly" ? n : !n;
309
+ }).sort((t, n) => parseFloat(t.price) - parseFloat(n.price)), Y = (t) => {
310
+ v(t), f(null);
311
+ }, $ = async () => {
312
+ if (g) {
313
+ F(!0), P();
314
+ try {
315
+ await I(g) && (h?.(), f(null));
316
+ } catch (t) {
317
+ y?.(
318
+ e("common.error"),
319
+ t instanceof Error ? t.message : e("purchase.error")
320
+ );
321
+ } finally {
322
+ F(!1);
323
+ }
324
+ }
325
+ }, W = async () => {
326
+ T(!0), P();
327
+ try {
328
+ await L() ? p?.() : N?.(e("common.error"), e("restore.noPurchases"));
329
+ } catch (t) {
330
+ y?.(
331
+ e("common.error"),
332
+ t instanceof Error ? t.message : e("restore.error")
333
+ );
334
+ } finally {
335
+ T(!1);
336
+ }
337
+ }, V = x((t) => t ? new Intl.DateTimeFormat(void 0, {
338
+ year: "numeric",
339
+ month: "long",
340
+ day: "numeric"
341
+ }).format(t) : "", []), G = x(
342
+ (t) => t ? t.includes("Y") || t.includes("year") ? e("periods.year") : t.includes("M") || t.includes("month") ? e("periods.month") : t.includes("W") || t.includes("week") ? e("periods.week") : "" : "",
343
+ [e]
344
+ ), J = x(
345
+ (t) => {
346
+ if (!t) return;
347
+ const n = t.replace(/\D/g, "") || "1";
348
+ return t.includes("W") ? e("trial.weeks", { count: parseInt(n, 10) }) : t.includes("M") ? e("trial.months", { count: parseInt(n, 10) }) : e("trial.days", { count: parseInt(n, 10) });
349
+ },
350
+ [e]
351
+ ), E = x(
352
+ (t) => {
353
+ if (!r?.tiers) return;
354
+ const n = C[t];
355
+ return n ? r.tiers.find(
356
+ (m) => m.entitlement === n
357
+ ) : r.tiers.find((m) => m.entitlement === "none");
358
+ },
359
+ [r]
360
+ ), b = x(
361
+ (t) => t === null ? e("rateLimits.unlimited", "Unlimited") : t.toLocaleString(),
362
+ [e]
363
+ ), R = x(
364
+ (t) => {
365
+ const n = E(t);
366
+ if (!n) return [];
367
+ const m = [];
368
+ return n.limits.hourly !== null && m.push(
369
+ e("rateLimits.hourly", "{{limit}} requests/hour", {
370
+ limit: b(n.limits.hourly)
371
+ })
372
+ ), n.limits.daily !== null && m.push(
373
+ e("rateLimits.daily", "{{limit}} requests/day", {
374
+ limit: b(n.limits.daily)
375
+ })
376
+ ), n.limits.monthly !== null && m.push(
377
+ e("rateLimits.monthly", "{{limit}} requests/month", {
378
+ limit: b(n.limits.monthly)
379
+ })
380
+ ), n.limits.hourly === null && n.limits.daily === null && n.limits.monthly === null && m.push(
381
+ e("rateLimits.unlimitedRequests", "Unlimited API requests")
382
+ ), m;
383
+ },
384
+ [E, b, e]
385
+ ), K = x(
386
+ (t) => R(t),
387
+ [R]
388
+ ), H = x(() => {
389
+ const t = [
390
+ e("freeTier.schemaValidation", "JSON Schema-validated outputs"),
391
+ e(
392
+ "freeTier.allProviders",
393
+ "All LLM providers (OpenAI, Anthropic, Google)"
394
+ ),
395
+ e("freeTier.endpointTesting", "Built-in endpoint testing"),
396
+ e("freeTier.analytics", "Basic usage analytics")
397
+ ];
398
+ if (r?.tiers) {
399
+ const n = r.tiers.find(
400
+ (m) => m.entitlement === "none"
401
+ );
402
+ n && (n.limits.hourly !== null && t.push(
403
+ e("rateLimits.hourly", "{{limit}} requests/hour", {
404
+ limit: b(n.limits.hourly)
405
+ })
406
+ ), n.limits.daily !== null && t.push(
407
+ e("rateLimits.daily", "{{limit}} requests/day", {
408
+ limit: b(n.limits.daily)
409
+ })
410
+ ), n.limits.monthly !== null && t.push(
411
+ e("rateLimits.monthly", "{{limit}} requests/month", {
412
+ limit: b(n.limits.monthly)
413
+ })
414
+ ));
415
+ }
416
+ return t;
417
+ }, [r, b, e]), Q = x(
418
+ (t) => {
419
+ const n = C[t];
420
+ if (!n) return;
421
+ const m = o.find(
422
+ (A) => A.identifier === t
423
+ );
424
+ if (!m) return;
425
+ const z = Object.entries(C).find(
426
+ ([A, ee]) => ee === n && A.includes("monthly")
427
+ )?.[0];
428
+ if (!z) return;
429
+ const D = o.find(
430
+ (A) => A.identifier === z
431
+ );
432
+ if (!D) return;
433
+ const _ = parseFloat(m.price), q = parseFloat(D.price);
434
+ if (q <= 0 || _ <= 0) return;
435
+ const U = q * 12, Z = (U - _) / U * 100;
436
+ return Math.round(Z);
437
+ },
438
+ [o]
439
+ ), X = [
440
+ { value: "monthly", label: e("billingPeriod.monthly") },
441
+ { value: "yearly", label: e("billingPeriod.yearly") }
442
+ ];
443
+ return /* @__PURE__ */ i(
444
+ fe,
445
+ {
446
+ title: e("title"),
447
+ error: c,
448
+ currentStatusLabel: e("currentStatus.label"),
449
+ currentStatus: {
450
+ isActive: s?.isActive ?? !1,
451
+ activeContent: s?.isActive ? {
452
+ title: e("currentStatus.active"),
453
+ fields: [
454
+ {
455
+ label: e("currentStatus.plan"),
456
+ value: s.productIdentifier || e("currentStatus.premium")
457
+ },
458
+ {
459
+ label: e("currentStatus.expires"),
460
+ value: V(
461
+ s.expirationDate
462
+ )
463
+ },
464
+ {
465
+ label: e("currentStatus.willRenew"),
466
+ value: s.willRenew ? e("common.yes") : e("common.no")
467
+ },
468
+ ...r ? [
469
+ {
470
+ label: e("currentStatus.monthlyUsage", "Monthly Usage"),
471
+ value: `${r.currentUsage.monthly.toLocaleString()} / ${b(r.currentLimits.monthly)}`
472
+ },
473
+ {
474
+ label: e("currentStatus.dailyUsage", "Daily Usage"),
475
+ value: `${r.currentUsage.daily.toLocaleString()} / ${b(r.currentLimits.daily)}`
476
+ }
477
+ ] : []
478
+ ]
479
+ } : void 0,
480
+ inactiveContent: s?.isActive ? void 0 : {
481
+ title: e("currentStatus.inactive"),
482
+ message: e("currentStatus.inactiveMessage")
483
+ }
484
+ },
485
+ aboveProducts: !d && o.length > 0 ? /* @__PURE__ */ i("div", { className: "flex justify-center mb-6", children: /* @__PURE__ */ i(
486
+ be,
487
+ {
488
+ options: X,
489
+ value: u,
490
+ onChange: Y
491
+ }
492
+ ) }) : null,
493
+ primaryAction: {
494
+ label: e(S ? "buttons.purchasing" : "buttons.subscribe"),
495
+ onClick: $,
496
+ disabled: !g || S || M,
497
+ loading: S
498
+ },
499
+ secondaryAction: {
500
+ label: e(M ? "buttons.restoring" : "buttons.restore"),
501
+ onClick: W,
502
+ disabled: S || M,
503
+ loading: M
504
+ },
505
+ children: d ? /* @__PURE__ */ i("div", { className: "flex items-center justify-center py-12", children: /* @__PURE__ */ i("div", { className: "animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600" }) }) : o.length === 0 ? /* @__PURE__ */ i("div", { className: "text-center py-12 text-theme-text-secondary", children: e("noProducts") }) : k.length === 0 ? /* @__PURE__ */ i("div", { className: "text-center py-12 text-theme-text-secondary", children: e("noProductsForPeriod") }) : /* @__PURE__ */ a(te, { children: [
506
+ /* @__PURE__ */ i(
507
+ O,
508
+ {
509
+ id: "free",
510
+ title: e("freeTier.title"),
511
+ price: e("freeTier.price"),
512
+ periodLabel: e("periods.month"),
513
+ features: H(),
514
+ isSelected: !s?.isActive && g === null,
515
+ onSelect: () => f(null),
516
+ topBadge: s?.isActive ? void 0 : {
517
+ text: e("badges.currentPlan", "Current Plan"),
518
+ color: "green"
519
+ },
520
+ disabled: S || M,
521
+ hideSelectionIndicator: !0
522
+ },
523
+ "free"
524
+ ),
525
+ k.map((t) => /* @__PURE__ */ i(
526
+ O,
527
+ {
528
+ id: t.identifier,
529
+ title: t.title,
530
+ price: t.priceString,
531
+ periodLabel: G(t.period),
532
+ features: K(t.identifier),
533
+ isSelected: g === t.identifier,
534
+ onSelect: () => f(t.identifier),
535
+ isBestValue: t.identifier.includes("pro"),
536
+ discountBadge: t.period?.includes("Y") ? (() => {
537
+ const n = Q(
538
+ t.identifier
539
+ );
540
+ return n && n > 0 ? {
541
+ text: e("badges.savePercent", "Save {{percent}}%", {
542
+ percent: n
543
+ }),
544
+ isBestValue: !0
545
+ } : void 0;
546
+ })() : void 0,
547
+ introPriceNote: t.freeTrialPeriod ? J(t.freeTrialPeriod) : t.introPrice ? e("intro.note", { price: t.introPrice }) : void 0,
548
+ disabled: S || M
549
+ },
550
+ t.identifier
551
+ ))
552
+ ] })
553
+ }
554
+ );
555
+ }
275
556
  export {
276
- U as EntityListPage,
277
- G as InvitationsPage,
278
- B as MembersManagementPage
557
+ Ie as EntityListPage,
558
+ Ae as EntitySubscriptionsPage,
559
+ Me as InvitationsPage,
560
+ Le as MembersManagementPage
279
561
  };
package/dist/index.umd.js CHANGED
@@ -1 +1 @@
1
- (function(a,e){typeof exports=="object"&&typeof module<"u"?e(exports,require("react/jsx-runtime"),require("react"),require("lucide-react"),require("@sudobility/entity-components"),require("@sudobility/entity_client")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","react","lucide-react","@sudobility/entity-components","@sudobility/entity_client"],e):(a=typeof globalThis<"u"?globalThis:a||self,e(a.EntityPages={},a.jsxRuntime,a.React,a.LucideReact,a.SudobilityEntityComponents,a.SudobilityEntityClient))})(this,(function(a,e,x,w,h,s){"use strict";function C({client:n,onSelectEntity:t}){const[y,i]=x.useState(!1),[d,c]=x.useState({displayName:"",description:""}),[b,g]=x.useState(null),{data:m=[],isLoading:r}=s.useEntities(n),l=s.useCreateEntity(n),f=m.filter(o=>o.entityType==="personal"),N=m.filter(o=>o.entityType==="organization"),S=async o=>{if(o.preventDefault(),g(null),!d.displayName.trim()){g("Display name is required");return}try{await l.mutateAsync({displayName:d.displayName.trim(),description:d.description.trim()||void 0}),i(!1),c({displayName:"",description:""})}catch(u){g(u.message||"Failed to create organization")}};return e.jsxs("div",{className:"space-y-8",children:[e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsxs("div",{children:[e.jsx("h1",{className:"text-2xl font-bold text-foreground",children:"Workspaces"}),e.jsx("p",{className:"text-muted-foreground",children:"Manage your personal and organization workspaces"})]}),e.jsxs("button",{type:"button",onClick:()=>i(!0),className:"flex items-center gap-2 px-4 py-2 rounded-lg bg-primary text-primary-foreground font-medium hover:bg-primary/90 transition-colors",children:[e.jsx(w.Plus,{className:"h-4 w-4"}),e.jsx("span",{children:"New Organization"})]})]}),y&&e.jsx("div",{className:"fixed inset-0 z-50 flex items-center justify-center bg-black/50",children:e.jsxs("div",{className:"w-full max-w-md rounded-lg bg-background p-6 shadow-lg",children:[e.jsx("h2",{className:"text-lg font-semibold mb-4",children:"Create Organization"}),e.jsxs("form",{onSubmit:S,className:"space-y-4",children:[e.jsxs("div",{children:[e.jsx("label",{className:"block text-sm font-medium mb-1",children:"Display Name"}),e.jsx("input",{type:"text",value:d.displayName,onChange:o=>c(u=>({...u,displayName:o.target.value})),placeholder:"My Organization",className:"w-full px-3 py-2 rounded-lg border bg-background focus:outline-none focus:ring-2 focus:ring-primary"})]}),e.jsxs("div",{children:[e.jsx("label",{className:"block text-sm font-medium mb-1",children:"Description (optional)"}),e.jsx("textarea",{value:d.description,onChange:o=>c(u=>({...u,description:o.target.value})),placeholder:"What is this organization for?",rows:3,className:"w-full px-3 py-2 rounded-lg border bg-background focus:outline-none focus:ring-2 focus:ring-primary resize-none"})]}),b&&e.jsx("p",{className:"text-sm text-destructive",children:b}),e.jsxs("div",{className:"flex justify-end gap-2",children:[e.jsx("button",{type:"button",onClick:()=>{i(!1),c({displayName:"",description:""}),g(null)},className:"px-4 py-2 rounded-lg border hover:bg-muted transition-colors",children:"Cancel"}),e.jsx("button",{type:"submit",disabled:l.isPending,className:"px-4 py-2 rounded-lg bg-primary text-primary-foreground font-medium hover:bg-primary/90 transition-colors disabled:opacity-50",children:l.isPending?"Creating...":"Create"})]})]})]})}),f.length>0&&e.jsxs("div",{children:[e.jsx("h2",{className:"text-lg font-semibold mb-3",children:"Personal Workspace"}),e.jsx(h.EntityList,{entities:f,onSelect:t,isLoading:r})]}),e.jsxs("div",{children:[e.jsx("h2",{className:"text-lg font-semibold mb-3",children:"Organizations"}),N.length===0&&!r?e.jsxs("div",{className:"text-center py-8 text-muted-foreground border border-dashed rounded-lg",children:[e.jsx("p",{children:"No organizations yet"}),e.jsx("button",{type:"button",onClick:()=>i(!0),className:"mt-2 text-primary hover:underline",children:"Create your first organization"})]}):e.jsx(h.EntityList,{entities:N,onSelect:t,isLoading:r})]})]})}function M({client:n,entity:t,currentUserId:y}){const i=t.userRole==="admin",{data:d=[],isLoading:c}=s.useEntityMembers(n,t.entitySlug),b=s.useUpdateMemberRole(n),g=s.useRemoveMember(n),{data:m=[],isLoading:r}=s.useEntityInvitations(n,i?t.entitySlug:null),l=s.useCreateInvitation(n),f=s.useCancelInvitation(n),N=async(p,v)=>{try{await b.mutateAsync({entitySlug:t.entitySlug,memberId:p,role:v})}catch(L){console.error("Failed to update role:",L)}},S=async p=>{if(confirm("Are you sure you want to remove this member?"))try{await g.mutateAsync({entitySlug:t.entitySlug,memberId:p})}catch(v){console.error("Failed to remove member:",v)}},o=async p=>{await l.mutateAsync({entitySlug:t.entitySlug,request:p})},u=async p=>{try{await f.mutateAsync({entitySlug:t.entitySlug,invitationId:p})}catch(v){console.error("Failed to cancel invitation:",v)}};return t.entityType==="personal"?e.jsxs("div",{className:"text-center py-12 text-muted-foreground",children:[e.jsx("p",{children:"Personal workspaces cannot have additional members."}),e.jsx("p",{className:"mt-2",children:"Create an organization to collaborate with others."})]}):e.jsxs("div",{className:"space-y-8",children:[e.jsxs("div",{children:[e.jsx("h1",{className:"text-2xl font-bold text-foreground",children:"Members"}),e.jsxs("p",{className:"text-muted-foreground",children:["Manage members and invitations for ",t.displayName]})]}),i&&e.jsxs("div",{className:"rounded-lg border p-4",children:[e.jsx("h2",{className:"text-lg font-semibold mb-4",children:"Invite Members"}),e.jsx(h.InvitationForm,{onSubmit:o,isSubmitting:l.isPending})]}),i&&e.jsxs("div",{children:[e.jsx("h2",{className:"text-lg font-semibold mb-3",children:"Pending Invitations"}),e.jsx(h.InvitationList,{invitations:m,mode:"admin",onCancel:u,isLoading:r,emptyMessage:"No pending invitations"})]}),e.jsxs("div",{children:[e.jsxs("h2",{className:"text-lg font-semibold mb-3",children:["Current Members (",d.length,")"]}),e.jsx(h.MemberList,{members:d,currentUserId:y,canManage:i,onRoleChange:N,onRemove:S,isLoading:c})]})]})}function I({client:n,onInvitationAccepted:t}){const{data:y=[],isLoading:i}=s.useMyInvitations(n),d=s.useAcceptInvitation(n),c=s.useDeclineInvitation(n),b=async r=>{try{await d.mutateAsync(r),t?.()}catch(l){console.error("Failed to accept invitation:",l)}},g=async r=>{try{await c.mutateAsync(r)}catch(l){console.error("Failed to decline invitation:",l)}},m=y.filter(r=>r.status==="pending").length;return e.jsxs("div",{className:"space-y-6",children:[e.jsxs("div",{children:[e.jsx("h1",{className:"text-2xl font-bold text-foreground",children:"Invitations"}),e.jsx("p",{className:"text-muted-foreground",children:m>0?`You have ${m} pending invitation${m>1?"s":""}`:"No pending invitations"})]}),e.jsx(h.InvitationList,{invitations:y,mode:"user",onAccept:b,onDecline:g,isLoading:i,emptyMessage:"You don't have any pending invitations"})]})}a.EntityListPage=C,a.InvitationsPage=I,a.MembersManagementPage=M,Object.defineProperty(a,Symbol.toStringTag,{value:"Module"})}));
1
+ (function(l,e){typeof exports=="object"&&typeof module<"u"?e(exports,require("react/jsx-runtime"),require("react"),require("lucide-react"),require("@sudobility/entity-components"),require("@sudobility/entity_client"),require("@sudobility/subscription-components")):typeof define=="function"&&define.amd?define(["exports","react/jsx-runtime","react","lucide-react","@sudobility/entity-components","@sudobility/entity_client","@sudobility/subscription-components"],e):(l=typeof globalThis<"u"?globalThis:l||self,e(l.EntityPages={},l.jsxRuntime,l.React,l.LucideReact,l.SudobilityEntityComponents,l.SudobilityEntityClient,l.SudobilitySubscriptionComponents))})(this,(function(l,e,s,_,I,b,A){"use strict";function $({client:o,onSelectEntity:r}){const[t,y]=s.useState(!1),[f,g]=s.useState({displayName:"",description:""}),[N,c]=s.useState(null),{data:a=[],isLoading:u}=b.useEntities(o),d=b.useCreateEntity(o),L=a.filter(m=>m.entityType==="personal"),M=a.filter(m=>m.entityType==="organization"),P=async m=>{if(m.preventDefault(),c(null),!f.displayName.trim()){c("Display name is required");return}try{await d.mutateAsync({displayName:f.displayName.trim(),description:f.description.trim()||void 0}),y(!1),g({displayName:"",description:""})}catch(x){c(x.message||"Failed to create organization")}};return e.jsxs("div",{className:"space-y-8",children:[e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsxs("div",{children:[e.jsx("h1",{className:"text-2xl font-bold text-foreground",children:"Workspaces"}),e.jsx("p",{className:"text-muted-foreground",children:"Manage your personal and organization workspaces"})]}),e.jsxs("button",{type:"button",onClick:()=>y(!0),className:"flex items-center gap-2 px-4 py-2 rounded-lg bg-primary text-primary-foreground font-medium hover:bg-primary/90 transition-colors",children:[e.jsx(_.Plus,{className:"h-4 w-4"}),e.jsx("span",{children:"New Organization"})]})]}),t&&e.jsx("div",{className:"fixed inset-0 z-50 flex items-center justify-center bg-black/50",children:e.jsxs("div",{className:"w-full max-w-md rounded-lg bg-background p-6 shadow-lg",children:[e.jsx("h2",{className:"text-lg font-semibold mb-4",children:"Create Organization"}),e.jsxs("form",{onSubmit:P,className:"space-y-4",children:[e.jsxs("div",{children:[e.jsx("label",{className:"block text-sm font-medium mb-1",children:"Display Name"}),e.jsx("input",{type:"text",value:f.displayName,onChange:m=>g(x=>({...x,displayName:m.target.value})),placeholder:"My Organization",className:"w-full px-3 py-2 rounded-lg border bg-background focus:outline-none focus:ring-2 focus:ring-primary"})]}),e.jsxs("div",{children:[e.jsx("label",{className:"block text-sm font-medium mb-1",children:"Description (optional)"}),e.jsx("textarea",{value:f.description,onChange:m=>g(x=>({...x,description:m.target.value})),placeholder:"What is this organization for?",rows:3,className:"w-full px-3 py-2 rounded-lg border bg-background focus:outline-none focus:ring-2 focus:ring-primary resize-none"})]}),N&&e.jsx("p",{className:"text-sm text-destructive",children:N}),e.jsxs("div",{className:"flex justify-end gap-2",children:[e.jsx("button",{type:"button",onClick:()=>{y(!1),g({displayName:"",description:""}),c(null)},className:"px-4 py-2 rounded-lg border hover:bg-muted transition-colors",children:"Cancel"}),e.jsx("button",{type:"submit",disabled:d.isPending,className:"px-4 py-2 rounded-lg bg-primary text-primary-foreground font-medium hover:bg-primary/90 transition-colors disabled:opacity-50",children:d.isPending?"Creating...":"Create"})]})]})]})}),L.length>0&&e.jsxs("div",{children:[e.jsx("h2",{className:"text-lg font-semibold mb-3",children:"Personal Workspace"}),e.jsx(I.EntityList,{entities:L,onSelect:r,isLoading:u})]}),e.jsxs("div",{children:[e.jsx("h2",{className:"text-lg font-semibold mb-3",children:"Organizations"}),M.length===0&&!u?e.jsxs("div",{className:"text-center py-8 text-muted-foreground border border-dashed rounded-lg",children:[e.jsx("p",{children:"No organizations yet"}),e.jsx("button",{type:"button",onClick:()=>y(!0),className:"mt-2 text-primary hover:underline",children:"Create your first organization"})]}):e.jsx(I.EntityList,{entities:M,onSelect:r,isLoading:u})]})]})}function W({client:o,entity:r,currentUserId:t}){const y=r.userRole==="admin",{data:f=[],isLoading:g}=b.useEntityMembers(o,r.entitySlug),N=b.useUpdateMemberRole(o),c=b.useRemoveMember(o),{data:a=[],isLoading:u}=b.useEntityInvitations(o,y?r.entitySlug:null),d=b.useCreateInvitation(o),L=b.useCancelInvitation(o),M=async(p,v)=>{try{await N.mutateAsync({entitySlug:r.entitySlug,memberId:p,role:v})}catch(w){console.error("Failed to update role:",w)}},P=async p=>{if(confirm("Are you sure you want to remove this member?"))try{await c.mutateAsync({entitySlug:r.entitySlug,memberId:p})}catch(v){console.error("Failed to remove member:",v)}},m=async p=>{await d.mutateAsync({entitySlug:r.entitySlug,request:p})},x=async p=>{try{await L.mutateAsync({entitySlug:r.entitySlug,invitationId:p})}catch(v){console.error("Failed to cancel invitation:",v)}};return r.entityType==="personal"?e.jsxs("div",{className:"text-center py-12 text-muted-foreground",children:[e.jsx("p",{children:"Personal workspaces cannot have additional members."}),e.jsx("p",{className:"mt-2",children:"Create an organization to collaborate with others."})]}):e.jsxs("div",{className:"space-y-8",children:[e.jsxs("div",{children:[e.jsx("h1",{className:"text-2xl font-bold text-foreground",children:"Members"}),e.jsxs("p",{className:"text-muted-foreground",children:["Manage members and invitations for ",r.displayName]})]}),y&&e.jsxs("div",{className:"rounded-lg border p-4",children:[e.jsx("h2",{className:"text-lg font-semibold mb-4",children:"Invite Members"}),e.jsx(I.InvitationForm,{onSubmit:m,isSubmitting:d.isPending})]}),y&&e.jsxs("div",{children:[e.jsx("h2",{className:"text-lg font-semibold mb-3",children:"Pending Invitations"}),e.jsx(I.InvitationList,{invitations:a,mode:"admin",onCancel:x,isLoading:u,emptyMessage:"No pending invitations"})]}),e.jsxs("div",{children:[e.jsxs("h2",{className:"text-lg font-semibold mb-3",children:["Current Members (",f.length,")"]}),e.jsx(I.MemberList,{members:f,currentUserId:t,canManage:y,onRoleChange:M,onRemove:P,isLoading:g})]})]})}function V({client:o,onInvitationAccepted:r}){const{data:t=[],isLoading:y}=b.useMyInvitations(o),f=b.useAcceptInvitation(o),g=b.useDeclineInvitation(o),N=async u=>{try{await f.mutateAsync(u),r?.()}catch(d){console.error("Failed to accept invitation:",d)}},c=async u=>{try{await g.mutateAsync(u)}catch(d){console.error("Failed to decline invitation:",d)}},a=t.filter(u=>u.status==="pending").length;return e.jsxs("div",{className:"space-y-6",children:[e.jsxs("div",{children:[e.jsx("h1",{className:"text-2xl font-bold text-foreground",children:"Invitations"}),e.jsx("p",{className:"text-muted-foreground",children:a>0?`You have ${a} pending invitation${a>1?"s":""}`:"No pending invitations"})]}),e.jsx(I.InvitationList,{invitations:t,mode:"user",onAccept:N,onDecline:c,isLoading:y,emptyMessage:"You don't have any pending invitations"})]})}const T={ultra_yearly:"bandwidth_ultra",ultra_monthly:"bandwidth_ultra",pro_yearly:"bandwidth_pro",pro_monthly:"bandwidth_pro",dev_yearly:"bandwidth_dev",dev_monthly:"bandwidth_dev"};function G({subscription:o,rateLimitsConfig:r,t,onPurchaseSuccess:y,onRestoreSuccess:f,onError:g,onWarning:N}){const{products:c,currentSubscription:a,isLoading:u,error:d,purchase:L,restore:M,clearError:P}=o,[m,x]=s.useState("monthly"),[p,v]=s.useState(null),[w,E]=s.useState(!1),[k,F]=s.useState(!1);s.useEffect(()=>{d&&(g?.(t("common.error"),d),P())},[d,P,t,g]);const q=c.filter(i=>{if(!i.period)return!1;const n=i.period.includes("Y")||i.period.includes("year");return m==="yearly"?n:!n}).sort((i,n)=>parseFloat(i.price)-parseFloat(n.price)),J=i=>{x(i),v(null)},K=async()=>{if(p){E(!0),P();try{await L(p)&&(y?.(),v(null))}catch(i){g?.(t("common.error"),i instanceof Error?i.message:t("purchase.error"))}finally{E(!1)}}},H=async()=>{F(!0),P();try{await M()?f?.():N?.(t("common.error"),t("restore.noPurchases"))}catch(i){g?.(t("common.error"),i instanceof Error?i.message:t("restore.error"))}finally{F(!1)}},Q=s.useCallback(i=>i?new Intl.DateTimeFormat(void 0,{year:"numeric",month:"long",day:"numeric"}).format(i):"",[]),X=s.useCallback(i=>i?i.includes("Y")||i.includes("year")?t("periods.year"):i.includes("M")||i.includes("month")?t("periods.month"):i.includes("W")||i.includes("week")?t("periods.week"):"":"",[t]),Z=s.useCallback(i=>{if(!i)return;const n=i.replace(/\D/g,"")||"1";return i.includes("W")?t("trial.weeks",{count:parseInt(n,10)}):i.includes("M")?t("trial.months",{count:parseInt(n,10)}):t("trial.days",{count:parseInt(n,10)})},[t]),z=s.useCallback(i=>{if(!r?.tiers)return;const n=T[i];return n?r.tiers.find(h=>h.entitlement===n):r.tiers.find(h=>h.entitlement==="none")},[r]),S=s.useCallback(i=>i===null?t("rateLimits.unlimited","Unlimited"):i.toLocaleString(),[t]),D=s.useCallback(i=>{const n=z(i);if(!n)return[];const h=[];return n.limits.hourly!==null&&h.push(t("rateLimits.hourly","{{limit}} requests/hour",{limit:S(n.limits.hourly)})),n.limits.daily!==null&&h.push(t("rateLimits.daily","{{limit}} requests/day",{limit:S(n.limits.daily)})),n.limits.monthly!==null&&h.push(t("rateLimits.monthly","{{limit}} requests/month",{limit:S(n.limits.monthly)})),n.limits.hourly===null&&n.limits.daily===null&&n.limits.monthly===null&&h.push(t("rateLimits.unlimitedRequests","Unlimited API requests")),h},[z,S,t]),R=s.useCallback(i=>D(i),[D]),ee=s.useCallback(()=>{const i=[t("freeTier.schemaValidation","JSON Schema-validated outputs"),t("freeTier.allProviders","All LLM providers (OpenAI, Anthropic, Google)"),t("freeTier.endpointTesting","Built-in endpoint testing"),t("freeTier.analytics","Basic usage analytics")];if(r?.tiers){const n=r.tiers.find(h=>h.entitlement==="none");n&&(n.limits.hourly!==null&&i.push(t("rateLimits.hourly","{{limit}} requests/hour",{limit:S(n.limits.hourly)})),n.limits.daily!==null&&i.push(t("rateLimits.daily","{{limit}} requests/day",{limit:S(n.limits.daily)})),n.limits.monthly!==null&&i.push(t("rateLimits.monthly","{{limit}} requests/month",{limit:S(n.limits.monthly)})))}return i},[r,S,t]),te=s.useCallback(i=>{const n=T[i];if(!n)return;const h=c.find(C=>C.identifier===i);if(!h)return;const j=Object.entries(T).find(([C,re])=>re===n&&C.includes("monthly"))?.[0];if(!j)return;const O=c.find(C=>C.identifier===j);if(!O)return;const U=parseFloat(h.price),B=parseFloat(O.price);if(B<=0||U<=0)return;const Y=B*12,ne=(Y-U)/Y*100;return Math.round(ne)},[c]),ie=[{value:"monthly",label:t("billingPeriod.monthly")},{value:"yearly",label:t("billingPeriod.yearly")}];return e.jsx(A.SubscriptionLayout,{title:t("title"),error:d,currentStatusLabel:t("currentStatus.label"),currentStatus:{isActive:a?.isActive??!1,activeContent:a?.isActive?{title:t("currentStatus.active"),fields:[{label:t("currentStatus.plan"),value:a.productIdentifier||t("currentStatus.premium")},{label:t("currentStatus.expires"),value:Q(a.expirationDate)},{label:t("currentStatus.willRenew"),value:a.willRenew?t("common.yes"):t("common.no")},...r?[{label:t("currentStatus.monthlyUsage","Monthly Usage"),value:`${r.currentUsage.monthly.toLocaleString()} / ${S(r.currentLimits.monthly)}`},{label:t("currentStatus.dailyUsage","Daily Usage"),value:`${r.currentUsage.daily.toLocaleString()} / ${S(r.currentLimits.daily)}`}]:[]]}:void 0,inactiveContent:a?.isActive?void 0:{title:t("currentStatus.inactive"),message:t("currentStatus.inactiveMessage")}},aboveProducts:!u&&c.length>0?e.jsx("div",{className:"flex justify-center mb-6",children:e.jsx(A.SegmentedControl,{options:ie,value:m,onChange:J})}):null,primaryAction:{label:t(w?"buttons.purchasing":"buttons.subscribe"),onClick:K,disabled:!p||w||k,loading:w},secondaryAction:{label:t(k?"buttons.restoring":"buttons.restore"),onClick:H,disabled:w||k,loading:k},children:u?e.jsx("div",{className:"flex items-center justify-center py-12",children:e.jsx("div",{className:"animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"})}):c.length===0?e.jsx("div",{className:"text-center py-12 text-theme-text-secondary",children:t("noProducts")}):q.length===0?e.jsx("div",{className:"text-center py-12 text-theme-text-secondary",children:t("noProductsForPeriod")}):e.jsxs(e.Fragment,{children:[e.jsx(A.SubscriptionTile,{id:"free",title:t("freeTier.title"),price:t("freeTier.price"),periodLabel:t("periods.month"),features:ee(),isSelected:!a?.isActive&&p===null,onSelect:()=>v(null),topBadge:a?.isActive?void 0:{text:t("badges.currentPlan","Current Plan"),color:"green"},disabled:w||k,hideSelectionIndicator:!0},"free"),q.map(i=>e.jsx(A.SubscriptionTile,{id:i.identifier,title:i.title,price:i.priceString,periodLabel:X(i.period),features:R(i.identifier),isSelected:p===i.identifier,onSelect:()=>v(i.identifier),isBestValue:i.identifier.includes("pro"),discountBadge:i.period?.includes("Y")?(()=>{const n=te(i.identifier);return n&&n>0?{text:t("badges.savePercent","Save {{percent}}%",{percent:n}),isBestValue:!0}:void 0})():void 0,introPriceNote:i.freeTrialPeriod?Z(i.freeTrialPeriod):i.introPrice?t("intro.note",{price:i.introPrice}):void 0,disabled:w||k},i.identifier))]})})}l.EntityListPage=$,l.EntitySubscriptionsPage=G,l.InvitationsPage=V,l.MembersManagementPage=W,Object.defineProperty(l,Symbol.toStringTag,{value:"Module"})}));
@@ -0,0 +1,50 @@
1
+ import { RateLimitsConfigData } from '@sudobility/types';
2
+ /** Product from subscription provider */
3
+ export interface SubscriptionProduct {
4
+ identifier: string;
5
+ title: string;
6
+ price: string;
7
+ priceString: string;
8
+ period?: string;
9
+ freeTrialPeriod?: string;
10
+ introPrice?: string;
11
+ }
12
+ /** Current subscription state */
13
+ export interface CurrentSubscription {
14
+ isActive: boolean;
15
+ productIdentifier?: string;
16
+ expirationDate?: Date;
17
+ willRenew?: boolean;
18
+ }
19
+ /** Subscription context value passed from consumer */
20
+ export interface SubscriptionContextValue {
21
+ products: SubscriptionProduct[];
22
+ currentSubscription: CurrentSubscription | null;
23
+ isLoading: boolean;
24
+ error: string | null;
25
+ purchase: (productId: string) => Promise<boolean>;
26
+ restore: () => Promise<boolean>;
27
+ clearError: () => void;
28
+ }
29
+ /** Translation function type */
30
+ export type TranslationFunction = (key: string, defaultValueOrOptions?: string | Record<string, unknown>, options?: Record<string, unknown>) => string;
31
+ export interface EntitySubscriptionsPageProps {
32
+ /** Subscription context value */
33
+ subscription: SubscriptionContextValue;
34
+ /** Rate limit configuration */
35
+ rateLimitsConfig?: RateLimitsConfigData | null;
36
+ /** Translation function */
37
+ t: TranslationFunction;
38
+ /** Called when purchase succeeds */
39
+ onPurchaseSuccess?: () => void;
40
+ /** Called when restore succeeds */
41
+ onRestoreSuccess?: () => void;
42
+ /** Called on error */
43
+ onError?: (title: string, message: string) => void;
44
+ /** Called on warning */
45
+ onWarning?: (title: string, message: string) => void;
46
+ }
47
+ /**
48
+ * Page for managing entity subscriptions.
49
+ */
50
+ export declare function EntitySubscriptionsPage({ subscription, rateLimitsConfig, t, onPurchaseSuccess, onRestoreSuccess, onError, onWarning, }: EntitySubscriptionsPageProps): import("react/jsx-runtime").JSX.Element;
@@ -4,3 +4,4 @@
4
4
  export { EntityListPage, type EntityListPageProps } from './EntityListPage';
5
5
  export { MembersManagementPage, type MembersManagementPageProps, } from './MembersManagementPage';
6
6
  export { InvitationsPage, type InvitationsPageProps } from './InvitationsPage';
7
+ export { EntitySubscriptionsPage, type EntitySubscriptionsPageProps, type SubscriptionProduct, type CurrentSubscription, type SubscriptionContextValue, type TranslationFunction, } from './EntitySubscriptionsPage';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sudobility/entity_pages",
3
- "version": "0.0.10",
3
+ "version": "0.0.11",
4
4
  "description": "Page containers for entity/organization management",
5
5
  "type": "module",
6
6
  "main": "dist/index.umd.js",
@@ -44,12 +44,19 @@
44
44
  "@tanstack/react-query": "^5.0.0",
45
45
  "@sudobility/types": "^1.9.43",
46
46
  "@sudobility/entity_client": "^0.0.9",
47
- "@sudobility/entity-components": "^1.0.4"
47
+ "@sudobility/entity-components": "^1.0.4",
48
+ "@sudobility/subscription-components": "^1.0.13"
49
+ },
50
+ "peerDependenciesMeta": {
51
+ "@sudobility/subscription-components": {
52
+ "optional": true
53
+ }
48
54
  },
49
55
  "devDependencies": {
50
56
  "@eslint/js": "^9.38.0",
51
57
  "@sudobility/entity-components": "^1.0.4",
52
58
  "@sudobility/entity_client": "^0.0.9",
59
+ "@sudobility/subscription-components": "^1.0.13",
53
60
  "@sudobility/types": "^1.9.43",
54
61
  "@tanstack/react-query": "^5.0.0",
55
62
  "@testing-library/dom": "^10.4.0",
@@ -69,6 +76,7 @@
69
76
  "prettier": "^3.6.2",
70
77
  "react": "^19.0.0",
71
78
  "react-dom": "^19.0.0",
79
+ "react-helmet-async": "^2.0.5",
72
80
  "typescript": "^5.9.3",
73
81
  "typescript-eslint": "^8.32.1",
74
82
  "vite": "^7.1.12",