@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 +1 -1
- package/dist/index.esm.js +412 -153
- package/dist/index.umd.js +1 -1
- package/dist/pages/EntitySubscriptionsPage.d.ts +106 -0
- package/dist/pages/index.d.ts +1 -0
- package/package.json +10 -2
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
|
|
2
|
-
import { useState as
|
|
3
|
-
import { Plus as
|
|
4
|
-
import { EntityList as
|
|
5
|
-
import { useEntities as
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
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 [
|
|
11
|
+
const [t, a] = S(!1), [h, f] = S({
|
|
11
12
|
displayName: "",
|
|
12
13
|
description: ""
|
|
13
|
-
}), [
|
|
14
|
-
(
|
|
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__ */
|
|
17
|
-
/* @__PURE__ */
|
|
18
|
-
/* @__PURE__ */
|
|
19
|
-
/* @__PURE__ */
|
|
20
|
-
/* @__PURE__ */
|
|
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__ */
|
|
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__ */
|
|
30
|
-
/* @__PURE__ */
|
|
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
|
-
|
|
36
|
-
/* @__PURE__ */
|
|
37
|
-
/* @__PURE__ */
|
|
38
|
-
if (
|
|
39
|
-
|
|
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
|
|
44
|
-
displayName:
|
|
45
|
-
description:
|
|
46
|
-
}), a(!1),
|
|
47
|
-
} catch (
|
|
48
|
-
|
|
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__ */
|
|
52
|
-
/* @__PURE__ */
|
|
53
|
-
/* @__PURE__ */
|
|
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:
|
|
58
|
-
onChange: (
|
|
59
|
-
...
|
|
60
|
-
displayName:
|
|
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__ */
|
|
68
|
-
/* @__PURE__ */
|
|
69
|
-
/* @__PURE__ */
|
|
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:
|
|
73
|
-
onChange: (
|
|
74
|
-
...
|
|
75
|
-
description:
|
|
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
|
-
|
|
84
|
-
/* @__PURE__ */
|
|
85
|
-
/* @__PURE__ */
|
|
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),
|
|
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__ */
|
|
97
|
+
/* @__PURE__ */ i(
|
|
97
98
|
"button",
|
|
98
99
|
{
|
|
99
100
|
type: "submit",
|
|
100
|
-
disabled:
|
|
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:
|
|
103
|
+
children: m.isPending ? "Creating..." : "Create"
|
|
103
104
|
}
|
|
104
105
|
)
|
|
105
106
|
] })
|
|
106
107
|
] })
|
|
107
108
|
] }) }),
|
|
108
|
-
|
|
109
|
-
/* @__PURE__ */
|
|
110
|
-
/* @__PURE__ */
|
|
111
|
-
|
|
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:
|
|
114
|
-
onSelect:
|
|
115
|
-
isLoading:
|
|
114
|
+
entities: x,
|
|
115
|
+
onSelect: r,
|
|
116
|
+
isLoading: s
|
|
116
117
|
}
|
|
117
118
|
)
|
|
118
119
|
] }),
|
|
119
|
-
/* @__PURE__ */
|
|
120
|
-
/* @__PURE__ */
|
|
121
|
-
|
|
122
|
-
/* @__PURE__ */
|
|
123
|
-
/* @__PURE__ */
|
|
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__ */
|
|
133
|
-
|
|
133
|
+
] }) : /* @__PURE__ */ i(
|
|
134
|
+
B,
|
|
134
135
|
{
|
|
135
|
-
entities:
|
|
136
|
-
onSelect:
|
|
137
|
-
isLoading:
|
|
136
|
+
entities: w,
|
|
137
|
+
onSelect: r,
|
|
138
|
+
isLoading: s
|
|
138
139
|
}
|
|
139
140
|
)
|
|
140
141
|
] })
|
|
141
142
|
] });
|
|
142
143
|
}
|
|
143
|
-
function
|
|
144
|
-
client:
|
|
145
|
-
entity:
|
|
146
|
-
currentUserId:
|
|
144
|
+
function Te({
|
|
145
|
+
client: c,
|
|
146
|
+
entity: r,
|
|
147
|
+
currentUserId: t
|
|
147
148
|
}) {
|
|
148
|
-
const a =
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
),
|
|
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
|
|
154
|
-
entitySlug:
|
|
155
|
-
memberId:
|
|
156
|
-
role:
|
|
154
|
+
await y.mutateAsync({
|
|
155
|
+
entitySlug: r.entitySlug,
|
|
156
|
+
memberId: N,
|
|
157
|
+
role: g
|
|
157
158
|
});
|
|
158
|
-
} catch (
|
|
159
|
-
console.error("Failed to update role:",
|
|
159
|
+
} catch (M) {
|
|
160
|
+
console.error("Failed to update role:", M);
|
|
160
161
|
}
|
|
161
|
-
},
|
|
162
|
+
}, C = async (N) => {
|
|
162
163
|
if (confirm("Are you sure you want to remove this member?"))
|
|
163
164
|
try {
|
|
164
|
-
await
|
|
165
|
-
entitySlug:
|
|
166
|
-
memberId:
|
|
165
|
+
await p.mutateAsync({
|
|
166
|
+
entitySlug: r.entitySlug,
|
|
167
|
+
memberId: N
|
|
167
168
|
});
|
|
168
|
-
} catch (
|
|
169
|
-
console.error("Failed to remove member:",
|
|
169
|
+
} catch (g) {
|
|
170
|
+
console.error("Failed to remove member:", g);
|
|
170
171
|
}
|
|
171
|
-
},
|
|
172
|
-
await
|
|
173
|
-
entitySlug:
|
|
174
|
-
request:
|
|
172
|
+
}, l = async (N) => {
|
|
173
|
+
await m.mutateAsync({
|
|
174
|
+
entitySlug: r.entitySlug,
|
|
175
|
+
request: N
|
|
175
176
|
});
|
|
176
|
-
},
|
|
177
|
+
}, v = async (N) => {
|
|
177
178
|
try {
|
|
178
|
-
await
|
|
179
|
-
entitySlug:
|
|
180
|
-
invitationId:
|
|
179
|
+
await x.mutateAsync({
|
|
180
|
+
entitySlug: r.entitySlug,
|
|
181
|
+
invitationId: N
|
|
181
182
|
});
|
|
182
|
-
} catch (
|
|
183
|
-
console.error("Failed to cancel invitation:",
|
|
183
|
+
} catch (g) {
|
|
184
|
+
console.error("Failed to cancel invitation:", g);
|
|
184
185
|
}
|
|
185
186
|
};
|
|
186
|
-
return
|
|
187
|
-
/* @__PURE__ */
|
|
188
|
-
/* @__PURE__ */
|
|
189
|
-
] }) : /* @__PURE__ */
|
|
190
|
-
/* @__PURE__ */
|
|
191
|
-
/* @__PURE__ */
|
|
192
|
-
/* @__PURE__ */
|
|
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
|
-
|
|
195
|
+
r.displayName
|
|
195
196
|
] })
|
|
196
197
|
] }),
|
|
197
|
-
a && /* @__PURE__ */
|
|
198
|
-
/* @__PURE__ */
|
|
199
|
-
/* @__PURE__ */
|
|
200
|
-
|
|
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:
|
|
203
|
-
isSubmitting:
|
|
203
|
+
onSubmit: l,
|
|
204
|
+
isSubmitting: m.isPending
|
|
204
205
|
}
|
|
205
206
|
)
|
|
206
207
|
] }),
|
|
207
|
-
a && /* @__PURE__ */
|
|
208
|
-
/* @__PURE__ */
|
|
209
|
-
/* @__PURE__ */
|
|
210
|
-
|
|
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:
|
|
213
|
+
invitations: d,
|
|
213
214
|
mode: "admin",
|
|
214
|
-
onCancel:
|
|
215
|
-
isLoading:
|
|
215
|
+
onCancel: v,
|
|
216
|
+
isLoading: s,
|
|
216
217
|
emptyMessage: "No pending invitations"
|
|
217
218
|
}
|
|
218
219
|
)
|
|
219
220
|
] }),
|
|
220
|
-
/* @__PURE__ */
|
|
221
|
-
/* @__PURE__ */
|
|
221
|
+
/* @__PURE__ */ o("div", { children: [
|
|
222
|
+
/* @__PURE__ */ o("h2", { className: "text-lg font-semibold mb-3", children: [
|
|
222
223
|
"Current Members (",
|
|
223
|
-
|
|
224
|
+
h.length,
|
|
224
225
|
")"
|
|
225
226
|
] }),
|
|
226
|
-
/* @__PURE__ */
|
|
227
|
-
|
|
227
|
+
/* @__PURE__ */ i(
|
|
228
|
+
oe,
|
|
228
229
|
{
|
|
229
|
-
members:
|
|
230
|
-
currentUserId:
|
|
230
|
+
members: h,
|
|
231
|
+
currentUserId: t,
|
|
231
232
|
canManage: a,
|
|
232
|
-
onRoleChange:
|
|
233
|
-
onRemove:
|
|
234
|
-
isLoading:
|
|
233
|
+
onRoleChange: w,
|
|
234
|
+
onRemove: C,
|
|
235
|
+
isLoading: f
|
|
235
236
|
}
|
|
236
237
|
)
|
|
237
238
|
] })
|
|
238
239
|
] });
|
|
239
240
|
}
|
|
240
|
-
function
|
|
241
|
-
client:
|
|
242
|
-
onInvitationAccepted:
|
|
241
|
+
function Fe({
|
|
242
|
+
client: c,
|
|
243
|
+
onInvitationAccepted: r
|
|
243
244
|
}) {
|
|
244
|
-
const { data:
|
|
245
|
+
const { data: t = [], isLoading: a } = ge(c), h = fe(c), f = pe(c), y = async (s) => {
|
|
245
246
|
try {
|
|
246
|
-
await
|
|
247
|
-
} catch (
|
|
248
|
-
console.error("Failed to accept invitation:",
|
|
247
|
+
await h.mutateAsync(s), r?.();
|
|
248
|
+
} catch (m) {
|
|
249
|
+
console.error("Failed to accept invitation:", m);
|
|
249
250
|
}
|
|
250
|
-
},
|
|
251
|
+
}, p = async (s) => {
|
|
251
252
|
try {
|
|
252
|
-
await
|
|
253
|
-
} catch (
|
|
254
|
-
console.error("Failed to decline invitation:",
|
|
253
|
+
await f.mutateAsync(s);
|
|
254
|
+
} catch (m) {
|
|
255
|
+
console.error("Failed to decline invitation:", m);
|
|
255
256
|
}
|
|
256
|
-
},
|
|
257
|
-
return /* @__PURE__ */
|
|
258
|
-
/* @__PURE__ */
|
|
259
|
-
/* @__PURE__ */
|
|
260
|
-
/* @__PURE__ */
|
|
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__ */
|
|
263
|
-
|
|
263
|
+
/* @__PURE__ */ i(
|
|
264
|
+
$,
|
|
264
265
|
{
|
|
265
|
-
invitations:
|
|
266
|
+
invitations: t,
|
|
266
267
|
mode: "user",
|
|
267
|
-
onAccept:
|
|
268
|
-
onDecline:
|
|
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
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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;
|
package/dist/pages/index.d.ts
CHANGED
|
@@ -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.
|
|
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",
|