@sudobility/entity_pages 0.0.10 → 0.0.13

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 SubscriptionPageLabels, type SubscriptionPageFormatters, } 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 o, jsx as i, Fragment as ie } from "react/jsx-runtime";
2
+ import { useState as S, useEffect as ne, useCallback as P } from "react";
3
+ import { Plus as re } from "lucide-react";
4
+ import { EntityList as B, InvitationForm as ae, InvitationList as $, MemberList as oe } from "@sudobility/entity-components";
5
+ import { useEntities as se, useCreateEntity as le, useEntityMembers as ce, useUpdateMemberRole as de, useRemoveMember as ue, useEntityInvitations as me, useCreateInvitation as he, useCancelInvitation as ye, useMyInvitations as ge, useAcceptInvitation as fe, useDeclineInvitation as pe } from "@sudobility/entity_client";
6
+ import { SubscriptionLayout as ve, SubscriptionTile as O, SegmentedControl as be } from "@sudobility/subscription-components";
7
+ function Ie({
8
+ client: c,
9
+ onSelectEntity: r
9
10
  }) {
10
- const [p, a] = f(!1), [s, c] = f({
11
+ const [t, a] = S(!1), [h, f] = S({
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
+ }), [y, p] = S(null), { data: d = [], isLoading: s } = se(c), m = le(c), x = d.filter((l) => l.entityType === "personal"), w = d.filter(
15
+ (l) => l.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__ */ o("div", { className: "space-y-8", children: [
18
+ /* @__PURE__ */ o("div", { className: "flex items-center justify-between", children: [
19
+ /* @__PURE__ */ o("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__ */ o(
23
24
  "button",
24
25
  {
25
26
  type: "button",
26
27
  onClick: () => a(!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(re, { 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
+ t && /* @__PURE__ */ i("div", { className: "fixed inset-0 z-50 flex items-center justify-center bg-black/50", children: /* @__PURE__ */ o("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__ */ o("form", { onSubmit: async (l) => {
39
+ if (l.preventDefault(), p(null), !h.displayName.trim()) {
40
+ p("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 m.mutateAsync({
45
+ displayName: h.displayName.trim(),
46
+ description: h.description.trim() || void 0
47
+ }), a(!1), f({ displayName: "", description: "" });
48
+ } catch (v) {
49
+ p(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__ */ o("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: h.displayName,
59
+ onChange: (l) => f((v) => ({
60
+ ...v,
61
+ displayName: l.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__ */ o("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: h.description,
74
+ onChange: (l) => f((v) => ({
75
+ ...v,
76
+ description: l.target.value
76
77
  })),
77
78
  placeholder: "What is this organization for?",
78
79
  rows: 3,
@@ -80,47 +81,47 @@ 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
+ y && /* @__PURE__ */ i("p", { className: "text-sm text-destructive", children: y }),
85
+ /* @__PURE__ */ o("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
+ a(!1), f({ displayName: "", description: "" }), p(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: m.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: m.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
+ x.length > 0 && /* @__PURE__ */ o("div", { children: [
110
+ /* @__PURE__ */ i("h2", { className: "text-lg font-semibold mb-3", children: "Personal Workspace" }),
111
+ /* @__PURE__ */ i(
112
+ B,
112
113
  {
113
- entities: b,
114
- onSelect: n,
115
- isLoading: r
114
+ entities: x,
115
+ onSelect: r,
116
+ isLoading: s
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__ */ o("div", { children: [
121
+ /* @__PURE__ */ i("h2", { className: "text-lg font-semibold mb-3", children: "Organizations" }),
122
+ w.length === 0 && !s ? /* @__PURE__ */ o("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",
@@ -129,151 +130,409 @@ function U({
129
130
  children: "Create your first organization"
130
131
  }
131
132
  )
132
- ] }) : /* @__PURE__ */ e(
133
- x,
133
+ ] }) : /* @__PURE__ */ i(
134
+ B,
134
135
  {
135
- entities: v,
136
- onSelect: n,
137
- isLoading: r
136
+ entities: w,
137
+ onSelect: r,
138
+ isLoading: s
138
139
  }
139
140
  )
140
141
  ] })
141
142
  ] });
142
143
  }
143
- function B({
144
- client: i,
145
- entity: n,
146
- currentUserId: p
144
+ function Te({
145
+ client: c,
146
+ entity: r,
147
+ currentUserId: t
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 a = r.userRole === "admin", { data: h = [], isLoading: f } = ce(
150
+ c,
151
+ r.entitySlug
152
+ ), y = de(c), p = ue(c), { data: d = [], isLoading: s } = me(c, a ? r.entitySlug : null), m = he(c), x = ye(c), w = async (N, g) => {
152
153
  try {
153
- await h.mutateAsync({
154
- entitySlug: n.entitySlug,
155
- memberId: g,
156
- role: y
154
+ await y.mutateAsync({
155
+ entitySlug: r.entitySlug,
156
+ memberId: N,
157
+ role: g
157
158
  });
158
- } catch (w) {
159
- console.error("Failed to update role:", w);
159
+ } catch (M) {
160
+ console.error("Failed to update role:", M);
160
161
  }
161
- }, N = async (g) => {
162
+ }, C = async (N) => {
162
163
  if (confirm("Are you sure you want to remove this member?"))
163
164
  try {
164
- await d.mutateAsync({
165
- entitySlug: n.entitySlug,
166
- memberId: g
165
+ await p.mutateAsync({
166
+ entitySlug: r.entitySlug,
167
+ memberId: N
167
168
  });
168
- } catch (y) {
169
- console.error("Failed to remove member:", y);
169
+ } catch (g) {
170
+ console.error("Failed to remove member:", g);
170
171
  }
171
- }, o = async (g) => {
172
- await l.mutateAsync({
173
- entitySlug: n.entitySlug,
174
- request: g
172
+ }, l = async (N) => {
173
+ await m.mutateAsync({
174
+ entitySlug: r.entitySlug,
175
+ request: N
175
176
  });
176
- }, u = async (g) => {
177
+ }, v = async (N) => {
177
178
  try {
178
- await b.mutateAsync({
179
- entitySlug: n.entitySlug,
180
- invitationId: g
179
+ await x.mutateAsync({
180
+ entitySlug: r.entitySlug,
181
+ invitationId: N
181
182
  });
182
- } catch (y) {
183
- console.error("Failed to cancel invitation:", y);
183
+ } catch (g) {
184
+ console.error("Failed to cancel invitation:", g);
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__ */ o("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__ */ o("div", { className: "space-y-8", children: [
191
+ /* @__PURE__ */ o("div", { children: [
192
+ /* @__PURE__ */ i("h1", { className: "text-2xl font-bold text-foreground", children: "Members" }),
193
+ /* @__PURE__ */ o("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
+ a && /* @__PURE__ */ o("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
+ ae,
201
202
  {
202
- onSubmit: o,
203
- isSubmitting: l.isPending
203
+ onSubmit: l,
204
+ isSubmitting: m.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
+ a && /* @__PURE__ */ o("div", { children: [
209
+ /* @__PURE__ */ i("h2", { className: "text-lg font-semibold mb-3", children: "Pending Invitations" }),
210
+ /* @__PURE__ */ i(
211
+ $,
211
212
  {
212
- invitations: m,
213
+ invitations: d,
213
214
  mode: "admin",
214
- onCancel: u,
215
- isLoading: r,
215
+ onCancel: v,
216
+ isLoading: s,
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__ */ o("div", { children: [
222
+ /* @__PURE__ */ o("h2", { className: "text-lg font-semibold mb-3", children: [
222
223
  "Current Members (",
223
- s.length,
224
+ h.length,
224
225
  ")"
225
226
  ] }),
226
- /* @__PURE__ */ e(
227
- S,
227
+ /* @__PURE__ */ i(
228
+ oe,
228
229
  {
229
- members: s,
230
- currentUserId: p,
230
+ members: h,
231
+ currentUserId: t,
231
232
  canManage: a,
232
- onRoleChange: v,
233
- onRemove: N,
234
- isLoading: c
233
+ onRoleChange: w,
234
+ onRemove: C,
235
+ isLoading: f
235
236
  }
236
237
  )
237
238
  ] })
238
239
  ] });
239
240
  }
240
- function G({
241
- client: i,
242
- onInvitationAccepted: n
241
+ function Fe({
242
+ client: c,
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: t = [], isLoading: a } = ge(c), h = fe(c), f = pe(c), y = async (s) => {
245
246
  try {
246
- await s.mutateAsync(r), n?.();
247
- } catch (l) {
248
- console.error("Failed to accept invitation:", l);
247
+ await h.mutateAsync(s), r?.();
248
+ } catch (m) {
249
+ console.error("Failed to accept invitation:", m);
249
250
  }
250
- }, d = async (r) => {
251
+ }, p = async (s) => {
251
252
  try {
252
- await c.mutateAsync(r);
253
- } catch (l) {
254
- console.error("Failed to decline invitation:", l);
253
+ await f.mutateAsync(s);
254
+ } catch (m) {
255
+ console.error("Failed to decline invitation:", m);
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
+ }, d = t.filter((s) => s.status === "pending").length;
258
+ return /* @__PURE__ */ o("div", { className: "space-y-6", children: [
259
+ /* @__PURE__ */ o("div", { children: [
260
+ /* @__PURE__ */ i("h1", { className: "text-2xl font-bold text-foreground", children: "Invitations" }),
261
+ /* @__PURE__ */ i("p", { className: "text-muted-foreground", children: d > 0 ? `You have ${d} pending invitation${d > 1 ? "s" : ""}` : "No pending invitations" })
261
262
  ] }),
262
- /* @__PURE__ */ e(
263
- C,
263
+ /* @__PURE__ */ i(
264
+ $,
264
265
  {
265
- invitations: p,
266
+ invitations: t,
266
267
  mode: "user",
267
- onAccept: h,
268
- onDecline: d,
268
+ onAccept: y,
269
+ onDecline: p,
269
270
  isLoading: a,
270
271
  emptyMessage: "You don't have any pending invitations"
271
272
  }
272
273
  )
273
274
  ] });
274
275
  }
276
+ const L = {
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 Ce({
285
+ subscription: c,
286
+ rateLimitsConfig: r,
287
+ labels: t,
288
+ formatters: a,
289
+ onPurchaseSuccess: h,
290
+ onRestoreSuccess: f,
291
+ onError: y,
292
+ onWarning: p
293
+ }) {
294
+ const {
295
+ products: d,
296
+ currentSubscription: s,
297
+ isLoading: m,
298
+ error: x,
299
+ purchase: w,
300
+ restore: C,
301
+ clearError: l
302
+ } = c, [v, N] = S("monthly"), [g, M] = S(null), [I, A] = S(!1), [T, E] = S(!1);
303
+ ne(() => {
304
+ x && (y?.(t.errorTitle, x), l());
305
+ }, [x, l, t.errorTitle, y]);
306
+ const R = d.filter((e) => {
307
+ if (!e.period) return !1;
308
+ const n = e.period.includes("Y") || e.period.includes("year");
309
+ return v === "yearly" ? n : !n;
310
+ }).sort((e, n) => parseFloat(e.price) - parseFloat(n.price)), U = (e) => {
311
+ N(e), M(null);
312
+ }, q = async () => {
313
+ if (g) {
314
+ A(!0), l();
315
+ try {
316
+ await w(g) && (h?.(), M(null));
317
+ } catch (e) {
318
+ y?.(
319
+ t.errorTitle,
320
+ e instanceof Error ? e.message : t.purchaseError
321
+ );
322
+ } finally {
323
+ A(!1);
324
+ }
325
+ }
326
+ }, H = async () => {
327
+ E(!0), l();
328
+ try {
329
+ await C() ? f?.() : p?.(t.errorTitle, t.restoreNoPurchases);
330
+ } catch (e) {
331
+ y?.(
332
+ t.errorTitle,
333
+ e instanceof Error ? e.message : t.restoreError
334
+ );
335
+ } finally {
336
+ E(!1);
337
+ }
338
+ }, V = P((e) => e ? new Intl.DateTimeFormat(void 0, {
339
+ year: "numeric",
340
+ month: "long",
341
+ day: "numeric"
342
+ }).format(e) : "", []), G = P(
343
+ (e) => e ? e.includes("Y") || e.includes("year") ? t.periodYear : e.includes("M") || e.includes("month") ? t.periodMonth : e.includes("W") || e.includes("week") ? t.periodWeek : "" : "",
344
+ [t]
345
+ ), K = P(
346
+ (e) => {
347
+ if (!e) return;
348
+ const n = parseInt(e.replace(/\D/g, "") || "1", 10);
349
+ return e.includes("W") ? a.formatTrialWeeks(n) : e.includes("M") ? a.formatTrialMonths(n) : a.formatTrialDays(n);
350
+ },
351
+ [a]
352
+ ), k = P(
353
+ (e) => {
354
+ if (!r?.tiers) return;
355
+ const n = L[e];
356
+ return n ? r.tiers.find(
357
+ (u) => u.entitlement === n
358
+ ) : r.tiers.find((u) => u.entitlement === "none");
359
+ },
360
+ [r]
361
+ ), b = P(
362
+ (e) => e === null ? t.unlimited : e.toLocaleString(),
363
+ [t.unlimited]
364
+ ), D = P(
365
+ (e) => {
366
+ const n = k(e);
367
+ if (!n) return [];
368
+ const u = [];
369
+ return n.limits.hourly !== null && u.push(
370
+ a.formatHourlyLimit(b(n.limits.hourly))
371
+ ), n.limits.daily !== null && u.push(
372
+ a.formatDailyLimit(b(n.limits.daily))
373
+ ), n.limits.monthly !== null && u.push(
374
+ a.formatMonthlyLimit(b(n.limits.monthly))
375
+ ), n.limits.hourly === null && n.limits.daily === null && n.limits.monthly === null && u.push(t.unlimitedRequests), u;
376
+ },
377
+ [k, b, a, t.unlimitedRequests]
378
+ ), J = P(
379
+ (e) => D(e),
380
+ [D]
381
+ ), Q = P(() => {
382
+ const e = [...t.freeTierFeatures];
383
+ if (r?.tiers) {
384
+ const n = r.tiers.find(
385
+ (u) => u.entitlement === "none"
386
+ );
387
+ n && (n.limits.hourly !== null && e.push(
388
+ a.formatHourlyLimit(b(n.limits.hourly))
389
+ ), n.limits.daily !== null && e.push(
390
+ a.formatDailyLimit(b(n.limits.daily))
391
+ ), n.limits.monthly !== null && e.push(
392
+ a.formatMonthlyLimit(b(n.limits.monthly))
393
+ ));
394
+ }
395
+ return e;
396
+ }, [r, b, a, t.freeTierFeatures]), X = P(
397
+ (e) => {
398
+ const n = L[e];
399
+ if (!n) return;
400
+ const u = d.find(
401
+ (F) => F.identifier === e
402
+ );
403
+ if (!u) return;
404
+ const z = Object.entries(L).find(
405
+ ([F, te]) => te === n && F.includes("monthly")
406
+ )?.[0];
407
+ if (!z) return;
408
+ const _ = d.find(
409
+ (F) => F.identifier === z
410
+ );
411
+ if (!_) return;
412
+ const Y = parseFloat(u.price), j = parseFloat(_.price);
413
+ if (j <= 0 || Y <= 0) return;
414
+ const W = j * 12, ee = (W - Y) / W * 100;
415
+ return Math.round(ee);
416
+ },
417
+ [d]
418
+ ), Z = [
419
+ { value: "monthly", label: t.billingMonthly },
420
+ { value: "yearly", label: t.billingYearly }
421
+ ];
422
+ return /* @__PURE__ */ i(
423
+ ve,
424
+ {
425
+ title: t.title,
426
+ error: x,
427
+ currentStatusLabel: t.currentStatusLabel,
428
+ currentStatus: {
429
+ isActive: s?.isActive ?? !1,
430
+ activeContent: s?.isActive ? {
431
+ title: t.statusActive,
432
+ fields: [
433
+ {
434
+ label: t.labelPlan,
435
+ value: s.productIdentifier || t.labelPremium
436
+ },
437
+ {
438
+ label: t.labelExpires,
439
+ value: V(
440
+ s.expirationDate
441
+ )
442
+ },
443
+ {
444
+ label: t.labelWillRenew,
445
+ value: s.willRenew ? t.yes : t.no
446
+ },
447
+ ...r ? [
448
+ {
449
+ label: t.labelMonthlyUsage,
450
+ value: `${r.currentUsage.monthly.toLocaleString()} / ${b(r.currentLimits.monthly)}`
451
+ },
452
+ {
453
+ label: t.labelDailyUsage,
454
+ value: `${r.currentUsage.daily.toLocaleString()} / ${b(r.currentLimits.daily)}`
455
+ }
456
+ ] : []
457
+ ]
458
+ } : void 0,
459
+ inactiveContent: s?.isActive ? void 0 : {
460
+ title: t.statusInactive,
461
+ message: t.statusInactiveMessage
462
+ }
463
+ },
464
+ aboveProducts: !m && d.length > 0 ? /* @__PURE__ */ i("div", { className: "flex justify-center mb-6", children: /* @__PURE__ */ i(
465
+ be,
466
+ {
467
+ options: Z,
468
+ value: v,
469
+ onChange: U
470
+ }
471
+ ) }) : null,
472
+ primaryAction: {
473
+ label: I ? t.buttonPurchasing : t.buttonSubscribe,
474
+ onClick: q,
475
+ disabled: !g || I || T,
476
+ loading: I
477
+ },
478
+ secondaryAction: {
479
+ label: T ? t.buttonRestoring : t.buttonRestore,
480
+ onClick: H,
481
+ disabled: I || T,
482
+ loading: T
483
+ },
484
+ children: m ? /* @__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" }) }) : d.length === 0 ? /* @__PURE__ */ i("div", { className: "text-center py-12 text-theme-text-secondary", children: t.noProducts }) : R.length === 0 ? /* @__PURE__ */ i("div", { className: "text-center py-12 text-theme-text-secondary", children: t.noProductsForPeriod }) : /* @__PURE__ */ o(ie, { children: [
485
+ /* @__PURE__ */ i(
486
+ O,
487
+ {
488
+ id: "free",
489
+ title: t.freeTierTitle,
490
+ price: t.freeTierPrice,
491
+ periodLabel: t.periodMonth,
492
+ features: Q(),
493
+ isSelected: !s?.isActive && g === null,
494
+ onSelect: () => M(null),
495
+ topBadge: s?.isActive ? void 0 : {
496
+ text: t.currentPlanBadge,
497
+ color: "green"
498
+ },
499
+ disabled: I || T,
500
+ hideSelectionIndicator: !0
501
+ },
502
+ "free"
503
+ ),
504
+ R.map((e) => /* @__PURE__ */ i(
505
+ O,
506
+ {
507
+ id: e.identifier,
508
+ title: e.title,
509
+ price: e.priceString,
510
+ periodLabel: G(e.period),
511
+ features: J(e.identifier),
512
+ isSelected: g === e.identifier,
513
+ onSelect: () => M(e.identifier),
514
+ isBestValue: e.identifier.includes("pro"),
515
+ discountBadge: e.period?.includes("Y") ? (() => {
516
+ const n = X(
517
+ e.identifier
518
+ );
519
+ return n && n > 0 ? {
520
+ text: a.formatSavePercent(n),
521
+ isBestValue: !0
522
+ } : void 0;
523
+ })() : void 0,
524
+ introPriceNote: e.freeTrialPeriod ? K(e.freeTrialPeriod) : e.introPrice ? a.formatIntroNote(e.introPrice) : void 0,
525
+ disabled: I || T
526
+ },
527
+ e.identifier
528
+ ))
529
+ ] })
530
+ }
531
+ );
532
+ }
275
533
  export {
276
- U as EntityListPage,
277
- G as InvitationsPage,
278
- B as MembersManagementPage
534
+ Ie as EntityListPage,
535
+ Ce as EntitySubscriptionsPage,
536
+ Fe as InvitationsPage,
537
+ Te as MembersManagementPage
279
538
  };
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(c,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):(c=typeof globalThis<"u"?globalThis:c||self,e(c.EntityPages={},c.jsxRuntime,c.React,c.LucideReact,c.SudobilityEntityComponents,c.SudobilityEntityClient,c.SudobilitySubscriptionComponents))})(this,(function(c,e,o,$,w,g,C){"use strict";function U({client:d,onSelectEntity:r}){const[i,a]=o.useState(!1),[m,v]=o.useState({displayName:"",description:""}),[f,b]=o.useState(null),{data:u=[],isLoading:s}=g.useEntities(d),h=g.useCreateEntity(d),S=u.filter(l=>l.entityType==="personal"),M=u.filter(l=>l.entityType==="organization"),k=async l=>{if(l.preventDefault(),b(null),!m.displayName.trim()){b("Display name is required");return}try{await h.mutateAsync({displayName:m.displayName.trim(),description:m.description.trim()||void 0}),a(!1),v({displayName:"",description:""})}catch(N){b(N.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:()=>a(!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"})]})]}),i&&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:k,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:m.displayName,onChange:l=>v(N=>({...N,displayName:l.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:m.description,onChange:l=>v(N=>({...N,description:l.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"})]}),f&&e.jsx("p",{className:"text-sm text-destructive",children:f}),e.jsxs("div",{className:"flex justify-end gap-2",children:[e.jsx("button",{type:"button",onClick:()=>{a(!1),v({displayName:"",description:""}),b(null)},className:"px-4 py-2 rounded-lg border hover:bg-muted transition-colors",children:"Cancel"}),e.jsx("button",{type:"submit",disabled:h.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:h.isPending?"Creating...":"Create"})]})]})]})}),S.length>0&&e.jsxs("div",{children:[e.jsx("h2",{className:"text-lg font-semibold mb-3",children:"Personal Workspace"}),e.jsx(w.EntityList,{entities:S,onSelect:r,isLoading:s})]}),e.jsxs("div",{children:[e.jsx("h2",{className:"text-lg font-semibold mb-3",children:"Organizations"}),M.length===0&&!s?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:()=>a(!0),className:"mt-2 text-primary hover:underline",children:"Create your first organization"})]}):e.jsx(w.EntityList,{entities:M,onSelect:r,isLoading:s})]})]})}function H({client:d,entity:r,currentUserId:i}){const a=r.userRole==="admin",{data:m=[],isLoading:v}=g.useEntityMembers(d,r.entitySlug),f=g.useUpdateMemberRole(d),b=g.useRemoveMember(d),{data:u=[],isLoading:s}=g.useEntityInvitations(d,a?r.entitySlug:null),h=g.useCreateInvitation(d),S=g.useCancelInvitation(d),M=async(P,p)=>{try{await f.mutateAsync({entitySlug:r.entitySlug,memberId:P,role:p})}catch(T){console.error("Failed to update role:",T)}},k=async P=>{if(confirm("Are you sure you want to remove this member?"))try{await b.mutateAsync({entitySlug:r.entitySlug,memberId:P})}catch(p){console.error("Failed to remove member:",p)}},l=async P=>{await h.mutateAsync({entitySlug:r.entitySlug,request:P})},N=async P=>{try{await S.mutateAsync({entitySlug:r.entitySlug,invitationId:P})}catch(p){console.error("Failed to cancel invitation:",p)}};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]})]}),a&&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(w.InvitationForm,{onSubmit:l,isSubmitting:h.isPending})]}),a&&e.jsxs("div",{children:[e.jsx("h2",{className:"text-lg font-semibold mb-3",children:"Pending Invitations"}),e.jsx(w.InvitationList,{invitations:u,mode:"admin",onCancel:N,isLoading:s,emptyMessage:"No pending invitations"})]}),e.jsxs("div",{children:[e.jsxs("h2",{className:"text-lg font-semibold mb-3",children:["Current Members (",m.length,")"]}),e.jsx(w.MemberList,{members:m,currentUserId:i,canManage:a,onRoleChange:M,onRemove:k,isLoading:v})]})]})}function V({client:d,onInvitationAccepted:r}){const{data:i=[],isLoading:a}=g.useMyInvitations(d),m=g.useAcceptInvitation(d),v=g.useDeclineInvitation(d),f=async s=>{try{await m.mutateAsync(s),r?.()}catch(h){console.error("Failed to accept invitation:",h)}},b=async s=>{try{await v.mutateAsync(s)}catch(h){console.error("Failed to decline invitation:",h)}},u=i.filter(s=>s.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:u>0?`You have ${u} pending invitation${u>1?"s":""}`:"No pending invitations"})]}),e.jsx(w.InvitationList,{invitations:i,mode:"user",onAccept:f,onDecline:b,isLoading:a,emptyMessage:"You don't have any pending invitations"})]})}const F={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:d,rateLimitsConfig:r,labels:i,formatters:a,onPurchaseSuccess:m,onRestoreSuccess:v,onError:f,onWarning:b}){const{products:u,currentSubscription:s,isLoading:h,error:S,purchase:M,restore:k,clearError:l}=d,[N,P]=o.useState("monthly"),[p,T]=o.useState(null),[I,A]=o.useState(!1),[L,D]=o.useState(!1);o.useEffect(()=>{S&&(f?.(i.errorTitle,S),l())},[S,l,i.errorTitle,f]);const z=u.filter(t=>{if(!t.period)return!1;const n=t.period.includes("Y")||t.period.includes("year");return N==="yearly"?n:!n}).sort((t,n)=>parseFloat(t.price)-parseFloat(n.price)),K=t=>{P(t),T(null)},J=async()=>{if(p){A(!0),l();try{await M(p)&&(m?.(),T(null))}catch(t){f?.(i.errorTitle,t instanceof Error?t.message:i.purchaseError)}finally{A(!1)}}},Q=async()=>{D(!0),l();try{await k()?v?.():b?.(i.errorTitle,i.restoreNoPurchases)}catch(t){f?.(i.errorTitle,t instanceof Error?t.message:i.restoreError)}finally{D(!1)}},X=o.useCallback(t=>t?new Intl.DateTimeFormat(void 0,{year:"numeric",month:"long",day:"numeric"}).format(t):"",[]),Z=o.useCallback(t=>t?t.includes("Y")||t.includes("year")?i.periodYear:t.includes("M")||t.includes("month")?i.periodMonth:t.includes("W")||t.includes("week")?i.periodWeek:"":"",[i]),R=o.useCallback(t=>{if(!t)return;const n=parseInt(t.replace(/\D/g,"")||"1",10);return t.includes("W")?a.formatTrialWeeks(n):t.includes("M")?a.formatTrialMonths(n):a.formatTrialDays(n)},[a]),j=o.useCallback(t=>{if(!r?.tiers)return;const n=F[t];return n?r.tiers.find(y=>y.entitlement===n):r.tiers.find(y=>y.entitlement==="none")},[r]),x=o.useCallback(t=>t===null?i.unlimited:t.toLocaleString(),[i.unlimited]),q=o.useCallback(t=>{const n=j(t);if(!n)return[];const y=[];return n.limits.hourly!==null&&y.push(a.formatHourlyLimit(x(n.limits.hourly))),n.limits.daily!==null&&y.push(a.formatDailyLimit(x(n.limits.daily))),n.limits.monthly!==null&&y.push(a.formatMonthlyLimit(x(n.limits.monthly))),n.limits.hourly===null&&n.limits.daily===null&&n.limits.monthly===null&&y.push(i.unlimitedRequests),y},[j,x,a,i.unlimitedRequests]),ee=o.useCallback(t=>q(t),[q]),te=o.useCallback(()=>{const t=[...i.freeTierFeatures];if(r?.tiers){const n=r.tiers.find(y=>y.entitlement==="none");n&&(n.limits.hourly!==null&&t.push(a.formatHourlyLimit(x(n.limits.hourly))),n.limits.daily!==null&&t.push(a.formatDailyLimit(x(n.limits.daily))),n.limits.monthly!==null&&t.push(a.formatMonthlyLimit(x(n.limits.monthly))))}return t},[r,x,a,i.freeTierFeatures]),ie=o.useCallback(t=>{const n=F[t];if(!n)return;const y=u.find(E=>E.identifier===t);if(!y)return;const Y=Object.entries(F).find(([E,ae])=>ae===n&&E.includes("monthly"))?.[0];if(!Y)return;const W=u.find(E=>E.identifier===Y);if(!W)return;const O=parseFloat(y.price),B=parseFloat(W.price);if(B<=0||O<=0)return;const _=B*12,re=(_-O)/_*100;return Math.round(re)},[u]),ne=[{value:"monthly",label:i.billingMonthly},{value:"yearly",label:i.billingYearly}];return e.jsx(C.SubscriptionLayout,{title:i.title,error:S,currentStatusLabel:i.currentStatusLabel,currentStatus:{isActive:s?.isActive??!1,activeContent:s?.isActive?{title:i.statusActive,fields:[{label:i.labelPlan,value:s.productIdentifier||i.labelPremium},{label:i.labelExpires,value:X(s.expirationDate)},{label:i.labelWillRenew,value:s.willRenew?i.yes:i.no},...r?[{label:i.labelMonthlyUsage,value:`${r.currentUsage.monthly.toLocaleString()} / ${x(r.currentLimits.monthly)}`},{label:i.labelDailyUsage,value:`${r.currentUsage.daily.toLocaleString()} / ${x(r.currentLimits.daily)}`}]:[]]}:void 0,inactiveContent:s?.isActive?void 0:{title:i.statusInactive,message:i.statusInactiveMessage}},aboveProducts:!h&&u.length>0?e.jsx("div",{className:"flex justify-center mb-6",children:e.jsx(C.SegmentedControl,{options:ne,value:N,onChange:K})}):null,primaryAction:{label:I?i.buttonPurchasing:i.buttonSubscribe,onClick:J,disabled:!p||I||L,loading:I},secondaryAction:{label:L?i.buttonRestoring:i.buttonRestore,onClick:Q,disabled:I||L,loading:L},children:h?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"})}):u.length===0?e.jsx("div",{className:"text-center py-12 text-theme-text-secondary",children:i.noProducts}):z.length===0?e.jsx("div",{className:"text-center py-12 text-theme-text-secondary",children:i.noProductsForPeriod}):e.jsxs(e.Fragment,{children:[e.jsx(C.SubscriptionTile,{id:"free",title:i.freeTierTitle,price:i.freeTierPrice,periodLabel:i.periodMonth,features:te(),isSelected:!s?.isActive&&p===null,onSelect:()=>T(null),topBadge:s?.isActive?void 0:{text:i.currentPlanBadge,color:"green"},disabled:I||L,hideSelectionIndicator:!0},"free"),z.map(t=>e.jsx(C.SubscriptionTile,{id:t.identifier,title:t.title,price:t.priceString,periodLabel:Z(t.period),features:ee(t.identifier),isSelected:p===t.identifier,onSelect:()=>T(t.identifier),isBestValue:t.identifier.includes("pro"),discountBadge:t.period?.includes("Y")?(()=>{const n=ie(t.identifier);return n&&n>0?{text:a.formatSavePercent(n),isBestValue:!0}:void 0})():void 0,introPriceNote:t.freeTrialPeriod?R(t.freeTrialPeriod):t.introPrice?a.formatIntroNote(t.introPrice):void 0,disabled:I||L},t.identifier))]})})}c.EntityListPage=U,c.EntitySubscriptionsPage=G,c.InvitationsPage=V,c.MembersManagementPage=H,Object.defineProperty(c,Symbol.toStringTag,{value:"Module"})}));
@@ -0,0 +1,106 @@
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
+ /** All localized labels for the subscription page */
30
+ export interface SubscriptionPageLabels {
31
+ title: string;
32
+ errorTitle: string;
33
+ purchaseError: string;
34
+ restoreError: string;
35
+ restoreNoPurchases: string;
36
+ periodYear: string;
37
+ periodMonth: string;
38
+ periodWeek: string;
39
+ billingMonthly: string;
40
+ billingYearly: string;
41
+ unlimited: string;
42
+ unlimitedRequests: string;
43
+ currentStatusLabel: string;
44
+ statusActive: string;
45
+ statusInactive: string;
46
+ statusInactiveMessage: string;
47
+ labelPlan: string;
48
+ labelPremium: string;
49
+ labelExpires: string;
50
+ labelWillRenew: string;
51
+ labelMonthlyUsage: string;
52
+ labelDailyUsage: string;
53
+ yes: string;
54
+ no: string;
55
+ buttonSubscribe: string;
56
+ buttonPurchasing: string;
57
+ buttonRestore: string;
58
+ buttonRestoring: string;
59
+ noProducts: string;
60
+ noProductsForPeriod: string;
61
+ freeTierTitle: string;
62
+ freeTierPrice: string;
63
+ freeTierFeatures: string[];
64
+ currentPlanBadge: string;
65
+ }
66
+ /** Formatter functions for dynamic strings */
67
+ export interface SubscriptionPageFormatters {
68
+ /** Format rate limit: "1,000 requests/hour" */
69
+ formatHourlyLimit: (limit: string) => string;
70
+ /** Format rate limit: "10,000 requests/day" */
71
+ formatDailyLimit: (limit: string) => string;
72
+ /** Format rate limit: "100,000 requests/month" */
73
+ formatMonthlyLimit: (limit: string) => string;
74
+ /** Format trial period: "7 days free trial" */
75
+ formatTrialDays: (count: number) => string;
76
+ /** Format trial period: "2 weeks free trial" */
77
+ formatTrialWeeks: (count: number) => string;
78
+ /** Format trial period: "1 month free trial" */
79
+ formatTrialMonths: (count: number) => string;
80
+ /** Format savings badge: "Save 20%" */
81
+ formatSavePercent: (percent: number) => string;
82
+ /** Format intro price note */
83
+ formatIntroNote: (price: string) => string;
84
+ }
85
+ export interface EntitySubscriptionsPageProps {
86
+ /** Subscription context value */
87
+ subscription: SubscriptionContextValue;
88
+ /** Rate limit configuration */
89
+ rateLimitsConfig?: RateLimitsConfigData | null;
90
+ /** All localized labels */
91
+ labels: SubscriptionPageLabels;
92
+ /** Formatter functions for dynamic strings */
93
+ formatters: SubscriptionPageFormatters;
94
+ /** Called when purchase succeeds */
95
+ onPurchaseSuccess?: () => void;
96
+ /** Called when restore succeeds */
97
+ onRestoreSuccess?: () => void;
98
+ /** Called on error */
99
+ onError?: (title: string, message: string) => void;
100
+ /** Called on warning */
101
+ onWarning?: (title: string, message: string) => void;
102
+ }
103
+ /**
104
+ * Page for managing entity subscriptions.
105
+ */
106
+ export declare function EntitySubscriptionsPage({ subscription, rateLimitsConfig, labels, formatters, 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 SubscriptionPageLabels, type SubscriptionPageFormatters, } 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.13",
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",