@burdenoff/microfe-devportal 2026.720.1 → 2026.720.2
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/devportal/pages/CreateOrganizationPage.d.ts.map +1 -1
- package/dist/devportal/pages/CreateOrganizationPage.js +97 -98
- package/dist/devportal/pages/CreateOrganizationPage.js.map +1 -1
- package/dist/devportal/pages/OrganizationMembersPage.js +1 -1
- package/dist/devportal/pages/OrganizationMembersPage.js.map +1 -1
- package/dist/devportal/pages/OrganizationsPage.d.ts.map +1 -1
- package/dist/devportal/pages/OrganizationsPage.js +97 -98
- package/dist/devportal/pages/OrganizationsPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CreateOrganizationPage.d.ts","sourceRoot":"","sources":["../../../src/devportal/pages/CreateOrganizationPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"CreateOrganizationPage.d.ts","sourceRoot":"","sources":["../../../src/devportal/pages/CreateOrganizationPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AA+BhC;;;;GAIG;AACH,eAAO,MAAM,sBAAsB,EAAE,EA2TpC,CAAC"}
|
|
@@ -6,82 +6,81 @@ import { InputField as s, TextareaField as c } from "../components/FormField.js"
|
|
|
6
6
|
import { PermissionGuard as l } from "../components/PermissionGuard.js";
|
|
7
7
|
import "../components/index.js";
|
|
8
8
|
import "../hooks/index.js";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import { Fragment as g, jsx as _, jsxs as v } from "react/jsx-runtime";
|
|
9
|
+
import { useState as u } from "react";
|
|
10
|
+
import { useI18n as d } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
11
|
+
import { Building2 as f, Info as p, X as m } from "lucide-react";
|
|
12
|
+
import { Fragment as h, jsx as g, jsxs as _ } from "react/jsx-runtime";
|
|
14
13
|
//#region src/devportal/pages/CreateOrganizationPage.tsx
|
|
15
|
-
var
|
|
16
|
-
let { navigate:
|
|
17
|
-
let n =
|
|
14
|
+
var v = () => {
|
|
15
|
+
let { navigate: v, basePath: y, currentUser: b } = e(), { t: x } = d(), S = (e, t) => {
|
|
16
|
+
let n = x(e);
|
|
18
17
|
return n === e ? t : n;
|
|
19
|
-
}, [
|
|
18
|
+
}, [C, w] = u({}), [T, { loading: E }] = r(), [D] = n(), [O] = i(), k = t(), [A, j] = u({
|
|
20
19
|
name: "",
|
|
21
20
|
slug: "",
|
|
22
21
|
description: "",
|
|
23
22
|
websiteUrl: "",
|
|
24
23
|
contactEmail: "",
|
|
25
24
|
supportEmail: ""
|
|
26
|
-
}),
|
|
27
|
-
|
|
28
|
-
},
|
|
25
|
+
}), M = (e) => {
|
|
26
|
+
v?.(`${y}${e}`);
|
|
27
|
+
}, N = (e) => e.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, ""), P = (e) => {
|
|
29
28
|
let t = e.target.value;
|
|
30
|
-
|
|
29
|
+
j((e) => ({
|
|
31
30
|
...e,
|
|
32
31
|
name: t,
|
|
33
|
-
slug: e.slug ||
|
|
34
|
-
})),
|
|
32
|
+
slug: e.slug || N(t)
|
|
33
|
+
})), C.name && w((e) => ({
|
|
35
34
|
...e,
|
|
36
35
|
name: void 0
|
|
37
36
|
}));
|
|
38
|
-
},
|
|
39
|
-
|
|
37
|
+
}, F = (e, t) => {
|
|
38
|
+
j((n) => ({
|
|
40
39
|
...n,
|
|
41
40
|
[e]: t
|
|
42
|
-
})),
|
|
41
|
+
})), C[e] && w((t) => ({
|
|
43
42
|
...t,
|
|
44
43
|
[e]: void 0
|
|
45
44
|
}));
|
|
46
|
-
},
|
|
45
|
+
}, I = () => {
|
|
47
46
|
let e = {};
|
|
48
|
-
return
|
|
47
|
+
return A.name.trim() ? A.name.length < 2 ? e.name = S("devportal.orgNameTooShort", "Name must be at least 2 characters") : A.name.length > 100 && (e.name = S("devportal.nameTooLong", "Name must be less than 100 characters")) : e.name = S("devportal.orgNameRequired", "Organization name is required"), A.slug.trim() ? /^[a-z0-9-]+$/.test(A.slug) || (e.slug = S("devportal.slugInvalid", "Slug can only contain lowercase letters, numbers, and hyphens")) : e.slug = S("devportal.slugRequired", "Slug is required"), A.contactEmail && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(A.contactEmail) && (e.contactEmail = S("devportal.invalidEmailAddress", "Invalid email address")), A.supportEmail && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(A.supportEmail) && (e.supportEmail = S("devportal.invalidEmailAddress", "Invalid email address")), w(e), Object.keys(e).length === 0;
|
|
49
48
|
};
|
|
50
|
-
return /* @__PURE__ */
|
|
51
|
-
|
|
52
|
-
children: /* @__PURE__ */
|
|
53
|
-
title:
|
|
54
|
-
description:
|
|
55
|
-
purpose:
|
|
56
|
-
backLink: `${
|
|
57
|
-
backLabel:
|
|
58
|
-
children: /* @__PURE__ */
|
|
49
|
+
return /* @__PURE__ */ g(l, {
|
|
50
|
+
authOnly: !0,
|
|
51
|
+
children: /* @__PURE__ */ g(a, {
|
|
52
|
+
title: S("devportal.createOrganization", "Create Organization"),
|
|
53
|
+
description: S("devportal.createOrganizationDescription", "Set up a developer organization to collaborate with your team"),
|
|
54
|
+
purpose: S("devportal.createOrganizationPurpose", "An organization is the shared owner of your apps, members, and revenue. Create one here so your team works under a single identity — pick a name and tier now; you can invite members and verify it afterwards."),
|
|
55
|
+
backLink: `${y}/organizations`,
|
|
56
|
+
backLabel: S("devportal.backToOrganizations", "Back to Organizations"),
|
|
57
|
+
children: /* @__PURE__ */ _("form", {
|
|
59
58
|
onSubmit: async (e) => {
|
|
60
|
-
if (e.preventDefault(),
|
|
61
|
-
let e = (await
|
|
62
|
-
name:
|
|
63
|
-
slug:
|
|
64
|
-
description:
|
|
65
|
-
websiteUrl:
|
|
66
|
-
contactEmail:
|
|
67
|
-
supportEmail:
|
|
59
|
+
if (e.preventDefault(), I()) try {
|
|
60
|
+
let e = (await T({ variables: { input: {
|
|
61
|
+
name: A.name,
|
|
62
|
+
slug: A.slug || void 0,
|
|
63
|
+
description: A.description || void 0,
|
|
64
|
+
websiteUrl: A.websiteUrl || void 0,
|
|
65
|
+
contactEmail: A.contactEmail || void 0,
|
|
66
|
+
supportEmail: A.supportEmail || void 0
|
|
68
67
|
} } })).data?.createDeveloperOrganization?.id;
|
|
69
68
|
if (e) {
|
|
70
|
-
|
|
69
|
+
k.emit("devportal.org.created", {
|
|
71
70
|
organizationId: e,
|
|
72
|
-
name:
|
|
71
|
+
name: A.name
|
|
73
72
|
});
|
|
74
73
|
try {
|
|
75
|
-
let t = (await
|
|
76
|
-
name:
|
|
77
|
-
email:
|
|
74
|
+
let t = (await D({ variables: { input: {
|
|
75
|
+
name: A.name,
|
|
76
|
+
email: b?.email ?? ""
|
|
78
77
|
} } })).data?.createBillingAccount?.id;
|
|
79
|
-
t && await
|
|
78
|
+
t && await O({ variables: {
|
|
80
79
|
organizationId: e,
|
|
81
80
|
billingAccountId: t
|
|
82
81
|
} });
|
|
83
82
|
} catch {}
|
|
84
|
-
|
|
83
|
+
M(`/organizations/${e}`);
|
|
85
84
|
}
|
|
86
85
|
} catch (e) {
|
|
87
86
|
console.error("Failed to create organization:", e);
|
|
@@ -89,96 +88,96 @@ var y = () => {
|
|
|
89
88
|
},
|
|
90
89
|
className: "space-y-8 max-w-3xl",
|
|
91
90
|
children: [
|
|
92
|
-
/* @__PURE__ */
|
|
93
|
-
title:
|
|
94
|
-
description:
|
|
95
|
-
children: /* @__PURE__ */
|
|
91
|
+
/* @__PURE__ */ g(o, {
|
|
92
|
+
title: S("devportal.organizationDetails", "Organization Details"),
|
|
93
|
+
description: S("devportal.organizationDetailsDescription", "Basic information about your organization"),
|
|
94
|
+
children: /* @__PURE__ */ _("div", {
|
|
96
95
|
className: "space-y-4",
|
|
97
96
|
children: [
|
|
98
|
-
/* @__PURE__ */
|
|
99
|
-
label:
|
|
100
|
-
value:
|
|
101
|
-
onChange:
|
|
102
|
-
placeholder:
|
|
97
|
+
/* @__PURE__ */ g(s, {
|
|
98
|
+
label: S("devportal.organizationName", "Organization Name"),
|
|
99
|
+
value: A.name,
|
|
100
|
+
onChange: P,
|
|
101
|
+
placeholder: S("devportal.organizationNamePlaceholder", "Acme Development"),
|
|
103
102
|
required: !0,
|
|
104
|
-
error:
|
|
105
|
-
hint:
|
|
103
|
+
error: C.name,
|
|
104
|
+
hint: S("devportal.organizationNameHint", "Your organization's display name")
|
|
106
105
|
}),
|
|
107
|
-
/* @__PURE__ */
|
|
108
|
-
label:
|
|
109
|
-
value:
|
|
110
|
-
onChange: (e) =>
|
|
111
|
-
placeholder:
|
|
106
|
+
/* @__PURE__ */ g(s, {
|
|
107
|
+
label: S("devportal.slug", "Slug"),
|
|
108
|
+
value: A.slug,
|
|
109
|
+
onChange: (e) => F("slug", e.target.value.toLowerCase()),
|
|
110
|
+
placeholder: S("devportal.orgSlugPlaceholder", "acme-dev"),
|
|
112
111
|
required: !0,
|
|
113
|
-
error:
|
|
114
|
-
hint:
|
|
112
|
+
error: C.slug,
|
|
113
|
+
hint: S("devportal.slugHint", "URL-friendly identifier (auto-generated from name)")
|
|
115
114
|
}),
|
|
116
|
-
/* @__PURE__ */
|
|
117
|
-
label:
|
|
118
|
-
value:
|
|
119
|
-
onChange: (e) =>
|
|
120
|
-
placeholder:
|
|
115
|
+
/* @__PURE__ */ g(c, {
|
|
116
|
+
label: S("devportal.description", "Description"),
|
|
117
|
+
value: A.description,
|
|
118
|
+
onChange: (e) => F("description", e.target.value),
|
|
119
|
+
placeholder: S("devportal.orgDescriptionPlaceholder", "Tell us about your organization..."),
|
|
121
120
|
rows: 3
|
|
122
121
|
}),
|
|
123
|
-
/* @__PURE__ */
|
|
124
|
-
label:
|
|
122
|
+
/* @__PURE__ */ g(s, {
|
|
123
|
+
label: S("devportal.websiteUrl", "Website URL"),
|
|
125
124
|
type: "url",
|
|
126
|
-
value:
|
|
127
|
-
onChange: (e) =>
|
|
125
|
+
value: A.websiteUrl,
|
|
126
|
+
onChange: (e) => F("websiteUrl", e.target.value),
|
|
128
127
|
placeholder: "https://example.com"
|
|
129
128
|
}),
|
|
130
|
-
/* @__PURE__ */
|
|
129
|
+
/* @__PURE__ */ _("div", {
|
|
131
130
|
className: "grid gap-4 sm:grid-cols-2",
|
|
132
|
-
children: [/* @__PURE__ */
|
|
133
|
-
label:
|
|
131
|
+
children: [/* @__PURE__ */ g(s, {
|
|
132
|
+
label: S("devportal.contactEmail", "Contact Email"),
|
|
134
133
|
type: "email",
|
|
135
|
-
value:
|
|
136
|
-
onChange: (e) =>
|
|
134
|
+
value: A.contactEmail,
|
|
135
|
+
onChange: (e) => F("contactEmail", e.target.value),
|
|
137
136
|
placeholder: "contact@example.com",
|
|
138
|
-
error:
|
|
139
|
-
}), /* @__PURE__ */
|
|
140
|
-
label:
|
|
137
|
+
error: C.contactEmail
|
|
138
|
+
}), /* @__PURE__ */ g(s, {
|
|
139
|
+
label: S("devportal.supportEmail", "Support Email"),
|
|
141
140
|
type: "email",
|
|
142
|
-
value:
|
|
143
|
-
onChange: (e) =>
|
|
141
|
+
value: A.supportEmail,
|
|
142
|
+
onChange: (e) => F("supportEmail", e.target.value),
|
|
144
143
|
placeholder: "support@example.com",
|
|
145
|
-
error:
|
|
144
|
+
error: C.supportEmail
|
|
146
145
|
})]
|
|
147
146
|
})
|
|
148
147
|
]
|
|
149
148
|
})
|
|
150
149
|
}),
|
|
151
|
-
/* @__PURE__ */
|
|
150
|
+
/* @__PURE__ */ _("div", {
|
|
152
151
|
className: "rounded-lg border border-border-subtle bg-status-info-bg-subtle p-4 flex gap-3",
|
|
153
|
-
children: [/* @__PURE__ */
|
|
152
|
+
children: [/* @__PURE__ */ g(p, {
|
|
154
153
|
size: 20,
|
|
155
154
|
className: "text-accent-blue flex-shrink-0 mt-0.5"
|
|
156
|
-
}), /* @__PURE__ */
|
|
155
|
+
}), /* @__PURE__ */ _("div", {
|
|
157
156
|
className: "text-sm text-status-info-text",
|
|
158
|
-
children: [/* @__PURE__ */
|
|
157
|
+
children: [/* @__PURE__ */ g("p", {
|
|
159
158
|
className: "font-medium",
|
|
160
|
-
children:
|
|
161
|
-
}), /* @__PURE__ */
|
|
159
|
+
children: S("devportal.billingAutoSetup", "A billing account will be automatically set up")
|
|
160
|
+
}), /* @__PURE__ */ g("p", {
|
|
162
161
|
className: "mt-1",
|
|
163
|
-
children:
|
|
162
|
+
children: S("devportal.billingAutoSetupNote", "Revenue from app sales will be credited to it. You can complete legal and tax details for payouts later in Organization Settings.")
|
|
164
163
|
})]
|
|
165
164
|
})]
|
|
166
165
|
}),
|
|
167
|
-
/* @__PURE__ */
|
|
166
|
+
/* @__PURE__ */ _("div", {
|
|
168
167
|
className: "flex items-center justify-end gap-3 border-t pt-6",
|
|
169
|
-
children: [/* @__PURE__ */
|
|
168
|
+
children: [/* @__PURE__ */ _("button", {
|
|
170
169
|
type: "button",
|
|
171
170
|
onClick: () => {
|
|
172
|
-
|
|
171
|
+
M("/organizations");
|
|
173
172
|
},
|
|
174
|
-
disabled:
|
|
173
|
+
disabled: E,
|
|
175
174
|
className: "inline-flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium hover:bg-bg-sunken transition-colors disabled:opacity-50",
|
|
176
|
-
children: [/* @__PURE__ */
|
|
177
|
-
}), /* @__PURE__ */
|
|
175
|
+
children: [/* @__PURE__ */ g(m, { size: 16 }), S("devportal.cancel", "Cancel")]
|
|
176
|
+
}), /* @__PURE__ */ g("button", {
|
|
178
177
|
type: "submit",
|
|
179
|
-
disabled:
|
|
178
|
+
disabled: E,
|
|
180
179
|
className: "inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 transition-colors disabled:opacity-50",
|
|
181
|
-
children:
|
|
180
|
+
children: E ? /* @__PURE__ */ _(h, { children: [/* @__PURE__ */ g("span", { className: "h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent" }), S("devportal.creating", "Creating...")] }) : /* @__PURE__ */ _(h, { children: [/* @__PURE__ */ g(f, { size: 16 }), S("devportal.createOrganization", "Create Organization")] })
|
|
182
181
|
})]
|
|
183
182
|
})
|
|
184
183
|
]
|
|
@@ -187,6 +186,6 @@ var y = () => {
|
|
|
187
186
|
});
|
|
188
187
|
};
|
|
189
188
|
//#endregion
|
|
190
|
-
export {
|
|
189
|
+
export { v as CreateOrganizationPage };
|
|
191
190
|
|
|
192
191
|
//# sourceMappingURL=CreateOrganizationPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CreateOrganizationPage.js","names":[],"sources":["../../../src/devportal/pages/CreateOrganizationPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useState } from \"react\";\nimport { X, Info, Building2 } from \"lucide-react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport { InputField, TextareaField } from \"../components/FormField\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport {\n useCreateOrganizationMutation,\n useLinkDevOrgBillingAccountMutation,\n} from \"../../generated/global-operations\";\nimport { useCreateBillingAccountMutation } from \"../hooks\";\nimport { useDevportalEventBus } from \"../events\";\n\ninterface CreateOrganizationFormData {\n name: string;\n slug: string;\n description: string;\n websiteUrl: string;\n contactEmail: string;\n supportEmail: string;\n}\n\ninterface FormErrors {\n name?: string;\n slug?: string;\n contactEmail?: string;\n supportEmail?: string;\n}\n\n/**\n * Create new organization page — single step.\n * Billing account is auto-created and linked after org creation.\n * Legal/tax details are completed later from organization settings.\n */\nexport const CreateOrganizationPage: FC = () => {\n const { navigate, basePath, currentUser } = useDevportal();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const [errors, setErrors] = useState<FormErrors>({});\n const [createOrganization, { loading: isSubmitting }] =\n useCreateOrganizationMutation();\n const [createBillingAccountMutation] = useCreateBillingAccountMutation();\n const [linkBillingAccount] = useLinkDevOrgBillingAccountMutation();\n const bus = useDevportalEventBus();\n\n const [formData, setFormData] = useState<CreateOrganizationFormData>({\n name: \"\",\n slug: \"\",\n description: \"\",\n websiteUrl: \"\",\n contactEmail: \"\",\n supportEmail: \"\",\n });\n\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n const generateSlug = (name: string): string => {\n return name\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n };\n\n const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const name = e.target.value;\n setFormData((prev) => ({\n ...prev,\n name,\n slug: prev.slug || generateSlug(name),\n }));\n if (errors.name) {\n setErrors((prev) => ({ ...prev, name: undefined }));\n }\n };\n\n const handleChange = (\n field: keyof CreateOrganizationFormData,\n value: string,\n ) => {\n setFormData((prev) => ({ ...prev, [field]: value }));\n if (errors[field as keyof FormErrors]) {\n setErrors((prev) => ({ ...prev, [field]: undefined }));\n }\n };\n\n const validate = (): boolean => {\n const newErrors: FormErrors = {};\n\n if (!formData.name.trim()) {\n newErrors.name = tr(\n \"devportal.orgNameRequired\",\n \"Organization name is required\",\n );\n } else if (formData.name.length < 2) {\n newErrors.name = tr(\n \"devportal.orgNameTooShort\",\n \"Name must be at least 2 characters\",\n );\n } else if (formData.name.length > 100) {\n newErrors.name = tr(\n \"devportal.nameTooLong\",\n \"Name must be less than 100 characters\",\n );\n }\n\n if (!formData.slug.trim()) {\n newErrors.slug = tr(\"devportal.slugRequired\", \"Slug is required\");\n } else if (!/^[a-z0-9-]+$/.test(formData.slug)) {\n newErrors.slug = tr(\n \"devportal.slugInvalid\",\n \"Slug can only contain lowercase letters, numbers, and hyphens\",\n );\n }\n\n if (\n formData.contactEmail &&\n !/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(formData.contactEmail)\n ) {\n newErrors.contactEmail = tr(\n \"devportal.invalidEmailAddress\",\n \"Invalid email address\",\n );\n }\n\n if (\n formData.supportEmail &&\n !/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(formData.supportEmail)\n ) {\n newErrors.supportEmail = tr(\n \"devportal.invalidEmailAddress\",\n \"Invalid email address\",\n );\n }\n\n setErrors(newErrors);\n return Object.keys(newErrors).length === 0;\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n\n if (!validate()) return;\n\n try {\n const result = await createOrganization({\n variables: {\n input: {\n name: formData.name,\n slug: formData.slug || undefined,\n description: formData.description || undefined,\n websiteUrl: formData.websiteUrl || undefined,\n contactEmail: formData.contactEmail || undefined,\n supportEmail: formData.supportEmail || undefined,\n },\n },\n });\n\n const orgId = result.data?.createDeveloperOrganization?.id;\n if (orgId) {\n bus.emit(\"devportal.org.created\", {\n organizationId: orgId,\n name: formData.name,\n });\n\n // Auto-create and link a billing account for revenue tracking\n try {\n const billingResult = await createBillingAccountMutation({\n variables: {\n input: {\n name: formData.name,\n email: currentUser?.email ?? \"\",\n },\n },\n });\n const billingAccountId = billingResult.data?.createBillingAccount?.id;\n if (billingAccountId) {\n await linkBillingAccount({\n variables: { organizationId: orgId, billingAccountId },\n });\n }\n } catch {\n // non-blocking — can be linked later from the Revenue tab\n }\n\n handleNavigate(`/organizations/${orgId}`);\n }\n } catch (error) {\n console.error(\"Failed to create organization:\", error);\n }\n };\n\n const handleCancel = () => {\n handleNavigate(\"/organizations\");\n };\n\n return (\n <PermissionGuard permission={DEVPORTAL_PERMISSIONS.ORG_CREATE}>\n <PageLayout\n title={tr(\"devportal.createOrganization\", \"Create Organization\")}\n description={tr(\n \"devportal.createOrganizationDescription\",\n \"Set up a developer organization to collaborate with your team\",\n )}\n purpose={tr(\n \"devportal.createOrganizationPurpose\",\n \"An organization is the shared owner of your apps, members, and revenue. Create one here so your team works under a single identity — pick a name and tier now; you can invite members and verify it afterwards.\",\n )}\n backLink={`${basePath}/organizations`}\n backLabel={tr(\"devportal.backToOrganizations\", \"Back to Organizations\")}\n >\n <form onSubmit={handleSubmit} className=\"space-y-8 max-w-3xl\">\n {/* Organization Details */}\n <PageSection\n title={tr(\"devportal.organizationDetails\", \"Organization Details\")}\n description={tr(\n \"devportal.organizationDetailsDescription\",\n \"Basic information about your organization\",\n )}\n >\n <div className=\"space-y-4\">\n <InputField\n label={tr(\"devportal.organizationName\", \"Organization Name\")}\n value={formData.name}\n onChange={handleNameChange}\n placeholder={tr(\n \"devportal.organizationNamePlaceholder\",\n \"Acme Development\",\n )}\n required\n error={errors.name}\n hint={tr(\n \"devportal.organizationNameHint\",\n \"Your organization's display name\",\n )}\n />\n\n <InputField\n label={tr(\"devportal.slug\", \"Slug\")}\n value={formData.slug}\n onChange={(e) =>\n handleChange(\"slug\", e.target.value.toLowerCase())\n }\n placeholder={tr(\"devportal.orgSlugPlaceholder\", \"acme-dev\")}\n required\n error={errors.slug}\n hint={tr(\n \"devportal.slugHint\",\n \"URL-friendly identifier (auto-generated from name)\",\n )}\n />\n\n <TextareaField\n label={tr(\"devportal.description\", \"Description\")}\n value={formData.description}\n onChange={(e) => handleChange(\"description\", e.target.value)}\n placeholder={tr(\n \"devportal.orgDescriptionPlaceholder\",\n \"Tell us about your organization...\",\n )}\n rows={3}\n />\n\n <InputField\n label={tr(\"devportal.websiteUrl\", \"Website URL\")}\n type=\"url\"\n value={formData.websiteUrl}\n onChange={(e) => handleChange(\"websiteUrl\", e.target.value)}\n placeholder=\"https://example.com\"\n />\n\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <InputField\n label={tr(\"devportal.contactEmail\", \"Contact Email\")}\n type=\"email\"\n value={formData.contactEmail}\n onChange={(e) => handleChange(\"contactEmail\", e.target.value)}\n placeholder=\"contact@example.com\"\n error={errors.contactEmail}\n />\n\n <InputField\n label={tr(\"devportal.supportEmail\", \"Support Email\")}\n type=\"email\"\n value={formData.supportEmail}\n onChange={(e) => handleChange(\"supportEmail\", e.target.value)}\n placeholder=\"support@example.com\"\n error={errors.supportEmail}\n />\n </div>\n </div>\n </PageSection>\n\n {/* Billing info note */}\n <div className=\"rounded-lg border border-border-subtle bg-status-info-bg-subtle p-4 flex gap-3\">\n <Info size={20} className=\"text-accent-blue flex-shrink-0 mt-0.5\" />\n <div className=\"text-sm text-status-info-text\">\n <p className=\"font-medium\">\n {tr(\n \"devportal.billingAutoSetup\",\n \"A billing account will be automatically set up\",\n )}\n </p>\n <p className=\"mt-1\">\n {tr(\n \"devportal.billingAutoSetupNote\",\n \"Revenue from app sales will be credited to it. You can complete legal and tax details for payouts later in Organization Settings.\",\n )}\n </p>\n </div>\n </div>\n\n {/* Actions */}\n <div className=\"flex items-center justify-end gap-3 border-t pt-6\">\n <button\n type=\"button\"\n onClick={handleCancel}\n disabled={isSubmitting}\n className=\"inline-flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium hover:bg-bg-sunken transition-colors disabled:opacity-50\"\n >\n <X size={16} />\n {tr(\"devportal.cancel\", \"Cancel\")}\n </button>\n <button\n type=\"submit\"\n disabled={isSubmitting}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 transition-colors disabled:opacity-50\"\n >\n {isSubmitting ? (\n <>\n <span className=\"h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent\" />\n {tr(\"devportal.creating\", \"Creating...\")}\n </>\n ) : (\n <>\n <Building2 size={16} />\n {tr(\"devportal.createOrganization\", \"Create Organization\")}\n </>\n )}\n </button>\n </div>\n </form>\n </PageLayout>\n </PermissionGuard>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"CreateOrganizationPage.js","names":[],"sources":["../../../src/devportal/pages/CreateOrganizationPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useState } from \"react\";\nimport { X, Info, Building2 } from \"lucide-react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport { InputField, TextareaField } from \"../components/FormField\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\nimport {\n useCreateOrganizationMutation,\n useLinkDevOrgBillingAccountMutation,\n} from \"../../generated/global-operations\";\nimport { useCreateBillingAccountMutation } from \"../hooks\";\nimport { useDevportalEventBus } from \"../events\";\n\ninterface CreateOrganizationFormData {\n name: string;\n slug: string;\n description: string;\n websiteUrl: string;\n contactEmail: string;\n supportEmail: string;\n}\n\ninterface FormErrors {\n name?: string;\n slug?: string;\n contactEmail?: string;\n supportEmail?: string;\n}\n\n/**\n * Create new organization page — single step.\n * Billing account is auto-created and linked after org creation.\n * Legal/tax details are completed later from organization settings.\n */\nexport const CreateOrganizationPage: FC = () => {\n const { navigate, basePath, currentUser } = useDevportal();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const [errors, setErrors] = useState<FormErrors>({});\n const [createOrganization, { loading: isSubmitting }] =\n useCreateOrganizationMutation();\n const [createBillingAccountMutation] = useCreateBillingAccountMutation();\n const [linkBillingAccount] = useLinkDevOrgBillingAccountMutation();\n const bus = useDevportalEventBus();\n\n const [formData, setFormData] = useState<CreateOrganizationFormData>({\n name: \"\",\n slug: \"\",\n description: \"\",\n websiteUrl: \"\",\n contactEmail: \"\",\n supportEmail: \"\",\n });\n\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n const generateSlug = (name: string): string => {\n return name\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n };\n\n const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n const name = e.target.value;\n setFormData((prev) => ({\n ...prev,\n name,\n slug: prev.slug || generateSlug(name),\n }));\n if (errors.name) {\n setErrors((prev) => ({ ...prev, name: undefined }));\n }\n };\n\n const handleChange = (\n field: keyof CreateOrganizationFormData,\n value: string,\n ) => {\n setFormData((prev) => ({ ...prev, [field]: value }));\n if (errors[field as keyof FormErrors]) {\n setErrors((prev) => ({ ...prev, [field]: undefined }));\n }\n };\n\n const validate = (): boolean => {\n const newErrors: FormErrors = {};\n\n if (!formData.name.trim()) {\n newErrors.name = tr(\n \"devportal.orgNameRequired\",\n \"Organization name is required\",\n );\n } else if (formData.name.length < 2) {\n newErrors.name = tr(\n \"devportal.orgNameTooShort\",\n \"Name must be at least 2 characters\",\n );\n } else if (formData.name.length > 100) {\n newErrors.name = tr(\n \"devportal.nameTooLong\",\n \"Name must be less than 100 characters\",\n );\n }\n\n if (!formData.slug.trim()) {\n newErrors.slug = tr(\"devportal.slugRequired\", \"Slug is required\");\n } else if (!/^[a-z0-9-]+$/.test(formData.slug)) {\n newErrors.slug = tr(\n \"devportal.slugInvalid\",\n \"Slug can only contain lowercase letters, numbers, and hyphens\",\n );\n }\n\n if (\n formData.contactEmail &&\n !/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(formData.contactEmail)\n ) {\n newErrors.contactEmail = tr(\n \"devportal.invalidEmailAddress\",\n \"Invalid email address\",\n );\n }\n\n if (\n formData.supportEmail &&\n !/^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/.test(formData.supportEmail)\n ) {\n newErrors.supportEmail = tr(\n \"devportal.invalidEmailAddress\",\n \"Invalid email address\",\n );\n }\n\n setErrors(newErrors);\n return Object.keys(newErrors).length === 0;\n };\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n\n if (!validate()) return;\n\n try {\n const result = await createOrganization({\n variables: {\n input: {\n name: formData.name,\n slug: formData.slug || undefined,\n description: formData.description || undefined,\n websiteUrl: formData.websiteUrl || undefined,\n contactEmail: formData.contactEmail || undefined,\n supportEmail: formData.supportEmail || undefined,\n },\n },\n });\n\n const orgId = result.data?.createDeveloperOrganization?.id;\n if (orgId) {\n bus.emit(\"devportal.org.created\", {\n organizationId: orgId,\n name: formData.name,\n });\n\n // Auto-create and link a billing account for revenue tracking\n try {\n const billingResult = await createBillingAccountMutation({\n variables: {\n input: {\n name: formData.name,\n email: currentUser?.email ?? \"\",\n },\n },\n });\n const billingAccountId = billingResult.data?.createBillingAccount?.id;\n if (billingAccountId) {\n await linkBillingAccount({\n variables: { organizationId: orgId, billingAccountId },\n });\n }\n } catch {\n // non-blocking — can be linked later from the Revenue tab\n }\n\n handleNavigate(`/organizations/${orgId}`);\n }\n } catch (error) {\n console.error(\"Failed to create organization:\", error);\n }\n };\n\n const handleCancel = () => {\n handleNavigate(\"/organizations\");\n };\n\n return (\n <PermissionGuard authOnly>\n <PageLayout\n title={tr(\"devportal.createOrganization\", \"Create Organization\")}\n description={tr(\n \"devportal.createOrganizationDescription\",\n \"Set up a developer organization to collaborate with your team\",\n )}\n purpose={tr(\n \"devportal.createOrganizationPurpose\",\n \"An organization is the shared owner of your apps, members, and revenue. Create one here so your team works under a single identity — pick a name and tier now; you can invite members and verify it afterwards.\",\n )}\n backLink={`${basePath}/organizations`}\n backLabel={tr(\"devportal.backToOrganizations\", \"Back to Organizations\")}\n >\n <form onSubmit={handleSubmit} className=\"space-y-8 max-w-3xl\">\n {/* Organization Details */}\n <PageSection\n title={tr(\"devportal.organizationDetails\", \"Organization Details\")}\n description={tr(\n \"devportal.organizationDetailsDescription\",\n \"Basic information about your organization\",\n )}\n >\n <div className=\"space-y-4\">\n <InputField\n label={tr(\"devportal.organizationName\", \"Organization Name\")}\n value={formData.name}\n onChange={handleNameChange}\n placeholder={tr(\n \"devportal.organizationNamePlaceholder\",\n \"Acme Development\",\n )}\n required\n error={errors.name}\n hint={tr(\n \"devportal.organizationNameHint\",\n \"Your organization's display name\",\n )}\n />\n\n <InputField\n label={tr(\"devportal.slug\", \"Slug\")}\n value={formData.slug}\n onChange={(e) =>\n handleChange(\"slug\", e.target.value.toLowerCase())\n }\n placeholder={tr(\"devportal.orgSlugPlaceholder\", \"acme-dev\")}\n required\n error={errors.slug}\n hint={tr(\n \"devportal.slugHint\",\n \"URL-friendly identifier (auto-generated from name)\",\n )}\n />\n\n <TextareaField\n label={tr(\"devportal.description\", \"Description\")}\n value={formData.description}\n onChange={(e) => handleChange(\"description\", e.target.value)}\n placeholder={tr(\n \"devportal.orgDescriptionPlaceholder\",\n \"Tell us about your organization...\",\n )}\n rows={3}\n />\n\n <InputField\n label={tr(\"devportal.websiteUrl\", \"Website URL\")}\n type=\"url\"\n value={formData.websiteUrl}\n onChange={(e) => handleChange(\"websiteUrl\", e.target.value)}\n placeholder=\"https://example.com\"\n />\n\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <InputField\n label={tr(\"devportal.contactEmail\", \"Contact Email\")}\n type=\"email\"\n value={formData.contactEmail}\n onChange={(e) => handleChange(\"contactEmail\", e.target.value)}\n placeholder=\"contact@example.com\"\n error={errors.contactEmail}\n />\n\n <InputField\n label={tr(\"devportal.supportEmail\", \"Support Email\")}\n type=\"email\"\n value={formData.supportEmail}\n onChange={(e) => handleChange(\"supportEmail\", e.target.value)}\n placeholder=\"support@example.com\"\n error={errors.supportEmail}\n />\n </div>\n </div>\n </PageSection>\n\n {/* Billing info note */}\n <div className=\"rounded-lg border border-border-subtle bg-status-info-bg-subtle p-4 flex gap-3\">\n <Info size={20} className=\"text-accent-blue flex-shrink-0 mt-0.5\" />\n <div className=\"text-sm text-status-info-text\">\n <p className=\"font-medium\">\n {tr(\n \"devportal.billingAutoSetup\",\n \"A billing account will be automatically set up\",\n )}\n </p>\n <p className=\"mt-1\">\n {tr(\n \"devportal.billingAutoSetupNote\",\n \"Revenue from app sales will be credited to it. You can complete legal and tax details for payouts later in Organization Settings.\",\n )}\n </p>\n </div>\n </div>\n\n {/* Actions */}\n <div className=\"flex items-center justify-end gap-3 border-t pt-6\">\n <button\n type=\"button\"\n onClick={handleCancel}\n disabled={isSubmitting}\n className=\"inline-flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium hover:bg-bg-sunken transition-colors disabled:opacity-50\"\n >\n <X size={16} />\n {tr(\"devportal.cancel\", \"Cancel\")}\n </button>\n <button\n type=\"submit\"\n disabled={isSubmitting}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 transition-colors disabled:opacity-50\"\n >\n {isSubmitting ? (\n <>\n <span className=\"h-4 w-4 animate-spin rounded-full border-2 border-current border-t-transparent\" />\n {tr(\"devportal.creating\", \"Creating...\")}\n </>\n ) : (\n <>\n <Building2 size={16} />\n {tr(\"devportal.createOrganization\", \"Create Organization\")}\n </>\n )}\n </button>\n </div>\n </form>\n </PageLayout>\n </PermissionGuard>\n );\n};\n"],"mappings":"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"}
|
|
@@ -207,7 +207,7 @@ var we = {
|
|
|
207
207
|
description: ze?.message || Ze?.message || b("devportal.anErrorOccurredWhileLoadingTeamData", "An error occurred while loading team data")
|
|
208
208
|
})
|
|
209
209
|
}) : /* @__PURE__ */ h(pe, {
|
|
210
|
-
|
|
210
|
+
authOnly: !0,
|
|
211
211
|
children: /* @__PURE__ */ g(i, {
|
|
212
212
|
title: b("devportal.teamMembers", "Team Members"),
|
|
213
213
|
description: b("devportal.manageYourOrganizationsTeam", "Manage your organization's team"),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OrganizationMembersPage.js","names":[],"sources":["../../../src/devportal/pages/OrganizationMembersPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useEffect, useMemo, useState } from \"react\";\nimport { useParams } from \"react-router-dom\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n Plus,\n Users,\n Mail,\n Shield,\n UserMinus,\n RefreshCw,\n Loader2,\n AlertCircle,\n} from \"lucide-react\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport { DataTable } from \"../components/DataTable\";\nimport { StatusBadge } from \"../components/StatusBadge\";\nimport { ConfirmDialog } from \"../components/ConfirmDialog\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport {\n InputField,\n SelectField,\n TextareaField,\n} from \"../components/FormField\";\nimport { SelectionCheckbox } from \"../components/SelectionCheckbox\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport { useDevportalPermission } from \"../hooks/usePermission\";\nimport type { DeveloperOrgRole } from \"../types\";\nimport { ORG_ROLE_LABELS } from \"../utils/constants\";\nimport { format } from \"date-fns\";\nimport {\n useGetOrganizationQuery,\n useGetOrganizationMembersQuery,\n useGetOrganizationInvitationsQuery,\n useInviteMemberMutation,\n useRemoveMemberMutation,\n useSuspendMemberMutation,\n useReactivateMemberMutation,\n useUpdateMemberRoleMutation,\n useTransferOwnershipMutation,\n useCancelInvitationMutation,\n useResendInvitationMutation,\n type OrgMemberFieldsFragment,\n type OrgInvitationFieldsFragment,\n} from \"../hooks\";\n\nconst ORG_ROLE_PRIORITY: Record<DeveloperOrgRole, number> = {\n VIEWER: 0,\n DEVELOPER: 1,\n ADMIN: 2,\n OWNER: 3,\n};\n\nexport const OrganizationMembersPage: FC = () => {\n const { orgId } = useParams<{ orgId: string }>();\n const { currentUser } = useDevportal();\n const { allowed: canInviteByPermission } = useDevportalPermission(\n DEVPORTAL_PERMISSIONS.ORG_INVITATION_CREATE,\n );\n const { allowed: canManageMembersByPermission } = useDevportalPermission(\n DEVPORTAL_PERMISSIONS.ORG_MEMBER_UPDATE,\n );\n const { allowed: canRemoveMembersByPermission } = useDevportalPermission(\n DEVPORTAL_PERMISSIONS.ORG_MEMBER_DELETE,\n );\n const { allowed: canActivateMembersByPermission } = useDevportalPermission(\n DEVPORTAL_PERMISSIONS.ORG_MEMBER_ACTIVATE,\n );\n const { allowed: canDeactivateMembersByPermission } = useDevportalPermission(\n DEVPORTAL_PERMISSIONS.ORG_MEMBER_DEACTIVATE,\n );\n const resolveMemberLabel = (member: {\n userId: string;\n email?: string | null;\n }): string => {\n if (member.email) return member.email;\n if (currentUser?.id === member.userId && currentUser.email)\n return currentUser.email;\n return member.userId;\n };\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const [showInviteDialog, setShowInviteDialog] = useState(false);\n const [inviteEmail, setInviteEmail] = useState(\"\");\n const [inviteRole, setInviteRole] = useState<DeveloperOrgRole>(\"DEVELOPER\");\n const [inviteMessage, setInviteMessage] = useState(\"\");\n const [actionError, setActionError] = useState<string | null>(null);\n const [actionSuccess, setActionSuccess] = useState<string | null>(null);\n const [selectedMemberIds, setSelectedMemberIds] = useState<Set<string>>(\n new Set(),\n );\n const [selectedInvitationIds, setSelectedInvitationIds] = useState<\n Set<string>\n >(new Set());\n const [showBulkRemoveMembersDialog, setShowBulkRemoveMembersDialog] =\n useState(false);\n const [showBulkCancelInvitationsDialog, setShowBulkCancelInvitationsDialog] =\n useState(false);\n const [transferOwnershipMember, setTransferOwnershipMember] =\n useState<OrgMemberFieldsFragment | null>(null);\n\n const { data: orgData } = useGetOrganizationQuery({\n variables: { id: orgId ?? \"\" },\n skip: !orgId,\n fetchPolicy: \"cache-first\",\n });\n useEffect(() => {\n const name = orgData?.developerOrganization?.name;\n if (!orgId || !name) return;\n const reg = (\n window as unknown as {\n __breadcrumbRegistry?: {\n register: (id: string, name: string) => void;\n unregister: (id: string) => void;\n };\n }\n ).__breadcrumbRegistry;\n reg?.register(orgId, name);\n return () => reg?.unregister(orgId);\n }, [orgId, orgData?.developerOrganization?.name]);\n\n // Queries\n const {\n data: membersData,\n loading: membersLoading,\n error: membersError,\n } = useGetOrganizationMembersQuery({\n variables: { organizationId: orgId! },\n skip: !orgId,\n fetchPolicy: \"cache-and-network\",\n });\n\n // Mutations\n const [inviteMember, { loading: inviting }] = useInviteMemberMutation({\n refetchQueries: [\"GetOrganizationInvitations\"],\n });\n\n const [removeMember] = useRemoveMemberMutation({\n refetchQueries: [\"GetOrganizationMembers\"],\n });\n\n const [suspendMember] = useSuspendMemberMutation({\n refetchQueries: [\"GetOrganizationMembers\"],\n });\n\n const [reactivateMember] = useReactivateMemberMutation({\n refetchQueries: [\"GetOrganizationMembers\"],\n });\n\n const [updateMemberRole] = useUpdateMemberRoleMutation({\n refetchQueries: [\"GetOrganizationMembers\", \"GetOrganization\"],\n });\n\n const [transferOwnership] = useTransferOwnershipMutation({\n refetchQueries: [\"GetOrganizationMembers\", \"GetOrganization\"],\n });\n\n const [cancelInvitation] = useCancelInvitationMutation({\n refetchQueries: [\"GetOrganizationInvitations\"],\n });\n\n const [resendInvitation] = useResendInvitationMutation();\n\n // Derived data\n const members: OrgMemberFieldsFragment[] = useMemo(\n () =>\n membersData?.developerOrgMembers?.edges?.map(\n (e: { node: OrgMemberFieldsFragment }) => e.node,\n ) ?? [],\n [membersData?.developerOrgMembers?.edges],\n );\n\n const currentMembership = currentUser\n ? members.find((member) => member.userId === currentUser.id)\n : undefined;\n const currentRole = currentMembership?.role as DeveloperOrgRole | undefined;\n const canManageOrg =\n currentRole !== undefined &&\n ORG_ROLE_PRIORITY[currentRole] >= ORG_ROLE_PRIORITY.ADMIN;\n const canInvite = canManageOrg && canInviteByPermission;\n const canManageMembers = canManageOrg && canManageMembersByPermission;\n const canRemoveMembers = canManageOrg && canRemoveMembersByPermission;\n const canActivateMembers = canManageOrg && canActivateMembersByPermission;\n const canDeactivateMembers = canManageOrg && canDeactivateMembersByPermission;\n\n const {\n data: invitationsData,\n loading: invitationsLoading,\n error: invitationsError,\n } = useGetOrganizationInvitationsQuery({\n variables: { organizationId: orgId!, status: \"PENDING\" as never },\n skip: !orgId || !canManageOrg,\n fetchPolicy: \"cache-and-network\",\n });\n\n const invitations: OrgInvitationFieldsFragment[] = useMemo(\n () =>\n invitationsData?.developerOrgInvitations?.edges?.map(\n (e: { node: OrgInvitationFieldsFragment }) => e.node,\n ) ?? [],\n [invitationsData?.developerOrgInvitations?.edges],\n );\n\n const membersCount = membersData?.developerOrgMembers?.totalCount ?? 0;\n const invitationsCount =\n invitationsData?.developerOrgInvitations?.totalCount ?? 0;\n\n const selectedMembers = useMemo(\n () => members.filter((member) => selectedMemberIds.has(member.id)),\n [members, selectedMemberIds],\n );\n const selectedInvitations = useMemo(\n () =>\n invitations.filter((invitation) =>\n selectedInvitationIds.has(invitation.id),\n ),\n [invitations, selectedInvitationIds],\n );\n\n const selectableMembers = members.filter((member) => member.role !== \"OWNER\");\n const allMembersSelected =\n selectableMembers.length > 0 &&\n selectableMembers.every((member) => selectedMemberIds.has(member.id));\n const partiallySelectedMembers =\n selectableMembers.some((member) => selectedMemberIds.has(member.id)) &&\n !allMembersSelected;\n\n const allInvitationsSelected =\n invitations.length > 0 &&\n invitations.every((invitation) => selectedInvitationIds.has(invitation.id));\n const partiallySelectedInvitations =\n invitations.some((invitation) =>\n selectedInvitationIds.has(invitation.id),\n ) && !allInvitationsSelected;\n\n const suspendableMembers = selectedMembers.filter(\n (member) => member.role !== \"OWNER\" && !member.isSuspended,\n );\n const reactivatableMembers = selectedMembers.filter(\n (member) => member.isSuspended,\n );\n const removableMembers = selectedMembers.filter(\n (member) => member.role !== \"OWNER\",\n );\n\n const handleInvite = async () => {\n if (!orgId || !inviteEmail.trim()) return;\n setActionError(null);\n\n try {\n await inviteMember({\n variables: {\n organizationId: orgId,\n input: {\n email: inviteEmail,\n role: inviteRole as never,\n message: inviteMessage || undefined,\n },\n },\n });\n setShowInviteDialog(false);\n setInviteEmail(\"\");\n setInviteRole(\"DEVELOPER\");\n setInviteMessage(\"\");\n setActionSuccess(\n tr(\n \"devportal.invitationSent\",\n \"Invitation created and email sent to the team member.\",\n ),\n );\n } catch (err) {\n setActionError(\n err instanceof Error\n ? err.message\n : tr(\"devportal.failedToSendInvitation\", \"Failed to send invitation\"),\n );\n }\n };\n\n const handleRemoveMember = async (memberId: string) => {\n setActionError(null);\n try {\n await removeMember({ variables: { id: memberId } });\n } catch (err) {\n setActionError(\n err instanceof Error\n ? err.message\n : tr(\"devportal.failedToRemoveMember\", \"Failed to remove member\"),\n );\n }\n };\n\n const handleSuspendMember = async (memberId: string, reason?: string) => {\n setActionError(null);\n try {\n await suspendMember({\n variables: { id: memberId, reason: reason || undefined },\n });\n } catch (err) {\n setActionError(\n err instanceof Error\n ? err.message\n : tr(\"devportal.failedToSuspendMember\", \"Failed to suspend member\"),\n );\n }\n };\n\n const handleReactivateMember = async (memberId: string) => {\n setActionError(null);\n try {\n await reactivateMember({ variables: { id: memberId } });\n } catch (err) {\n setActionError(\n err instanceof Error\n ? err.message\n : tr(\n \"devportal.failedToReactivateMember\",\n \"Failed to reactivate member\",\n ),\n );\n }\n };\n\n const handleUpdateMemberRole = async (\n memberId: string,\n role: DeveloperOrgRole,\n ) => {\n setActionError(null);\n try {\n await updateMemberRole({\n variables: {\n id: memberId,\n role: role as never,\n permissions: undefined,\n },\n });\n setActionSuccess(\n tr(\"devportal.memberRoleUpdated\", \"Member role updated successfully\"),\n );\n } catch (err) {\n setActionError(\n err instanceof Error\n ? err.message\n : tr(\n \"devportal.failedToUpdateMemberRole\",\n \"Failed to update member role\",\n ),\n );\n }\n };\n\n const handleTransferOwnership = async () => {\n const member = transferOwnershipMember;\n if (!orgId || !member) return;\n setActionError(null);\n\n try {\n await transferOwnership({\n variables: { organizationId: orgId, newOwnerId: member.userId },\n });\n setTransferOwnershipMember(null);\n setActionSuccess(\n tr(\n \"devportal.organizationOwnershipTransferred\",\n \"Organization ownership transferred successfully\",\n ),\n );\n } catch (err) {\n setActionError(\n err instanceof Error\n ? err.message\n : tr(\n \"devportal.failedToTransferOwnership\",\n \"Failed to transfer organization ownership\",\n ),\n );\n }\n };\n\n const handleCancelInvitation = async (invitationId: string) => {\n setActionError(null);\n try {\n await cancelInvitation({ variables: { id: invitationId } });\n } catch (err) {\n setActionError(\n err instanceof Error\n ? err.message\n : tr(\n \"devportal.failedToCancelInvitation\",\n \"Failed to cancel invitation\",\n ),\n );\n }\n };\n\n const handleResendInvitation = async (invitationId: string) => {\n setActionError(null);\n try {\n await resendInvitation({ variables: { id: invitationId } });\n } catch (err) {\n setActionError(\n err instanceof Error\n ? err.message\n : tr(\n \"devportal.failedToResendInvitation\",\n \"Failed to resend invitation\",\n ),\n );\n }\n };\n\n const clearSelections = () => {\n setSelectedMemberIds(new Set());\n setSelectedInvitationIds(new Set());\n };\n\n const toggleMemberSelection = (memberId: string, checked: boolean) => {\n setSelectedMemberIds((prev) => {\n const next = new Set(prev);\n if (checked) {\n next.add(memberId);\n } else {\n next.delete(memberId);\n }\n return next;\n });\n };\n\n const toggleAllMembers = (checked: boolean) => {\n setSelectedMemberIds((prev) => {\n const next = new Set(prev);\n selectableMembers.forEach((member) => {\n if (checked) {\n next.add(member.id);\n } else {\n next.delete(member.id);\n }\n });\n return next;\n });\n };\n\n const toggleInvitationSelection = (\n invitationId: string,\n checked: boolean,\n ) => {\n setSelectedInvitationIds((prev) => {\n const next = new Set(prev);\n if (checked) {\n next.add(invitationId);\n } else {\n next.delete(invitationId);\n }\n return next;\n });\n };\n\n const toggleAllInvitations = (checked: boolean) => {\n setSelectedInvitationIds((prev) => {\n const next = new Set(prev);\n invitations.forEach((invitation) => {\n if (checked) {\n next.add(invitation.id);\n } else {\n next.delete(invitation.id);\n }\n });\n return next;\n });\n };\n\n const handleBulkSuspendMembers = async () => {\n if (suspendableMembers.length === 0) return;\n setActionError(null);\n const results = await Promise.allSettled(\n suspendableMembers.map((member) =>\n suspendMember({ variables: { id: member.id, reason: undefined } }),\n ),\n );\n const succeeded = results.filter(\n (result) => result.status === \"fulfilled\",\n ).length;\n const failed = results.length - succeeded;\n setActionSuccess(\n `${tr(\"devportal.suspended\", \"Suspended\")} ${succeeded} ${succeeded === 1 ? tr(\"devportal.memberLower\", \"member\") : tr(\"devportal.membersLower\", \"members\")}${\n failed > 0 ? ` • ${failed} ${tr(\"devportal.failed\", \"failed\")}` : \"\"\n }`,\n );\n setSelectedMemberIds(new Set());\n };\n\n const handleBulkReactivateMembers = async () => {\n if (reactivatableMembers.length === 0) return;\n setActionError(null);\n const results = await Promise.allSettled(\n reactivatableMembers.map((member) =>\n reactivateMember({ variables: { id: member.id } }),\n ),\n );\n const succeeded = results.filter(\n (result) => result.status === \"fulfilled\",\n ).length;\n const failed = results.length - succeeded;\n setActionSuccess(\n `${tr(\"devportal.reactivated\", \"Reactivated\")} ${succeeded} ${succeeded === 1 ? tr(\"devportal.memberLower\", \"member\") : tr(\"devportal.membersLower\", \"members\")}${\n failed > 0 ? ` • ${failed} ${tr(\"devportal.failed\", \"failed\")}` : \"\"\n }`,\n );\n setSelectedMemberIds(new Set());\n };\n\n const handleBulkRemoveMembers = async () => {\n if (removableMembers.length === 0) return;\n setActionError(null);\n const results = await Promise.allSettled(\n removableMembers.map((member) =>\n removeMember({ variables: { id: member.id } }),\n ),\n );\n const succeeded = results.filter(\n (result) => result.status === \"fulfilled\",\n ).length;\n const failed = results.length - succeeded;\n setActionSuccess(\n `${tr(\"devportal.removed\", \"Removed\")} ${succeeded} ${succeeded === 1 ? tr(\"devportal.memberLower\", \"member\") : tr(\"devportal.membersLower\", \"members\")}${\n failed > 0 ? ` • ${failed} ${tr(\"devportal.failed\", \"failed\")}` : \"\"\n }`,\n );\n setShowBulkRemoveMembersDialog(false);\n setSelectedMemberIds(new Set());\n };\n\n const handleBulkResendInvitations = async () => {\n if (selectedInvitations.length === 0) return;\n setActionError(null);\n const results = await Promise.allSettled(\n selectedInvitations.map((invitation) =>\n resendInvitation({ variables: { id: invitation.id } }),\n ),\n );\n const succeeded = results.filter(\n (result) => result.status === \"fulfilled\",\n ).length;\n const failed = results.length - succeeded;\n setActionSuccess(\n `${tr(\"devportal.resent\", \"Resent\")} ${succeeded} ${succeeded === 1 ? tr(\"devportal.invitationLower\", \"invitation\") : tr(\"devportal.invitationsLower\", \"invitations\")}${\n failed > 0 ? ` • ${failed} ${tr(\"devportal.failed\", \"failed\")}` : \"\"\n }`,\n );\n setSelectedInvitationIds(new Set());\n };\n\n const handleBulkCancelInvitations = async () => {\n if (selectedInvitations.length === 0) return;\n setActionError(null);\n const results = await Promise.allSettled(\n selectedInvitations.map((invitation) =>\n cancelInvitation({ variables: { id: invitation.id } }),\n ),\n );\n const succeeded = results.filter(\n (result) => result.status === \"fulfilled\",\n ).length;\n const failed = results.length - succeeded;\n setActionSuccess(\n `${tr(\"devportal.cancelled\", \"Cancelled\")} ${succeeded} ${succeeded === 1 ? tr(\"devportal.invitationLower\", \"invitation\") : tr(\"devportal.invitationsLower\", \"invitations\")}${\n failed > 0 ? ` • ${failed} ${tr(\"devportal.failed\", \"failed\")}` : \"\"\n }`,\n );\n setShowBulkCancelInvitationsDialog(false);\n setSelectedInvitationIds(new Set());\n };\n\n const isLoading = membersLoading || invitationsLoading;\n const hasError = membersError || invitationsError;\n\n if (isLoading && !membersData && !invitationsData) {\n return (\n <PageLayout\n title={tr(\"devportal.teamMembers\", \"Team Members\")}\n description={tr(\n \"devportal.manageYourOrganizationsTeam\",\n \"Manage your organization's team\",\n )}\n backLink={`/organizations/${orgId}`}\n backLabel={tr(\"devportal.backToOrganization\", \"Back to Organization\")}\n >\n <div className=\"flex items-center justify-center py-16\">\n <Loader2 className=\"h-8 w-8 animate-spin text-text-secondary\" />\n </div>\n </PageLayout>\n );\n }\n\n if (hasError && !membersData && !invitationsData) {\n return (\n <PageLayout\n title={tr(\"devportal.teamMembers\", \"Team Members\")}\n description={tr(\n \"devportal.manageYourOrganizationsTeam\",\n \"Manage your organization's team\",\n )}\n backLink={`/organizations/${orgId}`}\n backLabel={tr(\"devportal.backToOrganization\", \"Back to Organization\")}\n >\n <EmptyState\n icon={<AlertCircle size={32} className=\"text-status-error-text\" />}\n title={tr(\"devportal.failedToLoadMembers\", \"Failed to load members\")}\n description={\n membersError?.message ||\n invitationsError?.message ||\n tr(\n \"devportal.anErrorOccurredWhileLoadingTeamData\",\n \"An error occurred while loading team data\",\n )\n }\n />\n </PageLayout>\n );\n }\n\n return (\n <PermissionGuard permission={DEVPORTAL_PERMISSIONS.ORG_MEMBER_READ}>\n <PageLayout\n title={tr(\"devportal.teamMembers\", \"Team Members\")}\n description={tr(\n \"devportal.manageYourOrganizationsTeam\",\n \"Manage your organization's team\",\n )}\n purpose={tr(\n \"devportal.teamMembersPurpose\",\n \"Control who can work on this organization's apps and what they can do. Invite teammates by email, assign roles to grant the right level of access, and suspend or remove anyone who should no longer have it.\",\n )}\n backLink={`/organizations/${orgId}`}\n backLabel={tr(\"devportal.backToOrganization\", \"Back to Organization\")}\n actions={\n canInvite ? (\n <button\n onClick={() => setShowInviteDialog(true)}\n disabled={!canInvite}\n aria-disabled={!canInvite}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n <Plus size={16} />\n {tr(\"devportal.inviteMember\", \"Invite Member\")}\n </button>\n ) : undefined\n }\n >\n {/* Action Error Banner */}\n {actionError && (\n <div className=\"rounded-lg border border-border-subtle bg-status-error-bg-subtle p-3 flex items-center gap-2 text-sm text-status-error-text\">\n <AlertCircle size={16} className=\"flex-shrink-0\" />\n {actionError}\n <button\n onClick={() => setActionError(null)}\n className=\"ml-auto text-status-error-text hover:text-status-error-text\"\n >\n {tr(\"devportal.dismiss\", \"Dismiss\")}\n </button>\n </div>\n )}\n\n {actionSuccess && (\n <div className=\"rounded-lg border border-border-subtle bg-status-success-bg-subtle p-3 flex items-center gap-2 text-sm text-status-success-text\">\n <AlertCircle size={16} className=\"flex-shrink-0\" />\n {actionSuccess}\n <button\n onClick={() => setActionSuccess(null)}\n className=\"ml-auto text-status-success-text hover:text-status-success-text\"\n >\n {tr(\"devportal.dismiss\", \"Dismiss\")}\n </button>\n </div>\n )}\n\n {(selectedMemberIds.size > 0 || selectedInvitationIds.size > 0) && (\n <div className=\"flex flex-wrap items-center gap-3 rounded-card border border-border-seam bg-bg-surface p-4\">\n <span className=\"rounded-full bg-bg-sunken px-3 py-1 text-sm font-medium text-text-secondary\">\n {selectedMemberIds.size + selectedInvitationIds.size}{\" \"}\n {tr(\"devportal.selected\", \"selected\")}\n </span>\n <button\n onClick={clearSelections}\n className=\"text-sm text-text-secondary hover:text-text-primary\"\n >\n {tr(\"devportal.clearAllSelections\", \"Clear all selections\")}\n </button>\n </div>\n )}\n\n {/* Current Members */}\n <PageSection\n title={`${tr(\"devportal.members\", \"Members\")} (${membersCount})`}\n actions={\n members.length > 0 && canManageOrg ? (\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n onClick={handleBulkSuspendMembers}\n disabled={\n suspendableMembers.length === 0 || !canDeactivateMembers\n }\n aria-disabled={!canDeactivateMembers}\n className=\"rounded-md border border-status-warning-bg px-3 py-2 text-sm font-medium text-status-warning-text hover:bg-status-warning-bg-subtle disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {tr(\"devportal.suspendSelected\", \"Suspend Selected\")}\n {suspendableMembers.length > 0\n ? ` (${suspendableMembers.length})`\n : \"\"}\n </button>\n <button\n onClick={handleBulkReactivateMembers}\n disabled={\n reactivatableMembers.length === 0 || !canActivateMembers\n }\n aria-disabled={!canActivateMembers}\n className=\"rounded-md border border-status-success-bg px-3 py-2 text-sm font-medium text-status-success-text hover:bg-status-success-bg-subtle disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {tr(\"devportal.reactivateSelected\", \"Reactivate Selected\")}\n {reactivatableMembers.length > 0\n ? ` (${reactivatableMembers.length})`\n : \"\"}\n </button>\n <button\n onClick={() => setShowBulkRemoveMembersDialog(true)}\n disabled={removableMembers.length === 0 || !canRemoveMembers}\n aria-disabled={!canRemoveMembers}\n className=\"rounded-md border border-status-error-bg px-3 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg-subtle disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {tr(\"devportal.removeSelected\", \"Remove Selected\")}\n {removableMembers.length > 0\n ? ` (${removableMembers.length})`\n : \"\"}\n </button>\n </div>\n ) : null\n }\n >\n {members.length > 0 ? (\n <DataTable\n data={members}\n keyField=\"id\"\n columns={[\n ...(canManageOrg\n ? [\n {\n key: \"select\",\n header: (\n <SelectionCheckbox\n checked={allMembersSelected}\n indeterminate={partiallySelectedMembers}\n onChange={toggleAllMembers}\n ariaLabel={tr(\n \"devportal.selectAllMembers\",\n \"Select all members\",\n )}\n />\n ),\n className: \"w-12\",\n render: (member: OrgMemberFieldsFragment) => (\n <div onClick={(event) => event.stopPropagation()}>\n <SelectionCheckbox\n checked={selectedMemberIds.has(member.id)}\n disabled={member.role === \"OWNER\"}\n onChange={(checked) =>\n toggleMemberSelection(member.id, checked)\n }\n ariaLabel={`Select member ${resolveMemberLabel(member)}`}\n />\n </div>\n ),\n },\n ]\n : []),\n {\n key: \"email\",\n header: tr(\"devportal.memberColumn\", \"Member\"),\n render: (member) => {\n const label = resolveMemberLabel(member);\n return (\n <div className=\"flex items-center gap-3\">\n <div className=\"flex h-8 w-8 items-center justify-center rounded-full bg-action-primary-bg/10 text-primary text-sm font-medium\">\n {label.charAt(0).toUpperCase()}\n </div>\n <div>\n <p className=\"font-medium\">{label}</p>\n <p className=\"text-xs text-text-secondary\">\n {tr(\"devportal.joined\", \"Joined\")}{\" \"}\n {format(new Date(member.joinedAt), \"MMM d, yyyy\")}\n </p>\n </div>\n </div>\n );\n },\n },\n {\n key: \"role\",\n header: tr(\"devportal.roleColumn\", \"Role\"),\n render: (member) => (\n <div className=\"flex items-center gap-2\">\n <Shield size={14} className=\"text-text-secondary\" />\n {member.role === \"OWNER\" || !canManageMembers ? (\n <span>\n {ORG_ROLE_LABELS[member.role as DeveloperOrgRole] ??\n member.role}\n </span>\n ) : (\n <select\n value={member.role}\n onChange={(event) =>\n void handleUpdateMemberRole(\n member.id,\n event.target.value as DeveloperOrgRole,\n )\n }\n className=\"rounded-md border border-border-subtle bg-bg-surface px-2 py-1 text-xs text-text-primary\"\n >\n {Object.entries(ORG_ROLE_LABELS)\n .filter(([value]) => value !== \"OWNER\")\n .map(([value, label]) => (\n <option key={value} value={value}>\n {label}\n </option>\n ))}\n </select>\n )}\n </div>\n ),\n },\n {\n key: \"status\",\n header: tr(\"devportal.statusColumn\", \"Status\"),\n render: (member) =>\n member.isSuspended ? (\n <StatusBadge status=\"SUSPENDED\" type=\"app\" />\n ) : member.isActive ? (\n <StatusBadge status=\"ACTIVE\" type=\"app\" />\n ) : (\n <StatusBadge status=\"INACTIVE\" type=\"app\" />\n ),\n },\n ...(canManageOrg\n ? [\n {\n key: \"actions\",\n header: \"\",\n className: \"w-32\",\n render: (member: OrgMemberFieldsFragment) =>\n member.role !== \"OWNER\" && (\n <div className=\"flex gap-1\">\n {member.isSuspended ? (\n <button\n className=\"text-xs text-status-success-text hover:underline disabled:opacity-50\"\n disabled={!canActivateMembers}\n aria-disabled={!canActivateMembers}\n title={tr(\n \"devportal.reactivateMember\",\n \"Reactivate member\",\n )}\n onClick={() =>\n handleReactivateMember(member.id)\n }\n >\n {tr(\"devportal.reactivate\", \"Reactivate\")}\n </button>\n ) : (\n <button\n className=\"text-xs text-status-warning-text hover:underline disabled:opacity-50\"\n disabled={!canDeactivateMembers}\n aria-disabled={!canDeactivateMembers}\n title={tr(\n \"devportal.suspendMember\",\n \"Suspend member\",\n )}\n onClick={() => handleSuspendMember(member.id)}\n >\n {tr(\"devportal.suspend\", \"Suspend\")}\n </button>\n )}\n <button\n className=\"text-xs text-status-error-text hover:underline disabled:opacity-50\"\n disabled={!canRemoveMembers}\n aria-disabled={!canRemoveMembers}\n title={tr(\n \"devportal.removeMember\",\n \"Remove member\",\n )}\n onClick={() => handleRemoveMember(member.id)}\n >\n {tr(\"devportal.remove\", \"Remove\")}\n </button>\n <button\n className=\"text-xs text-primary hover:underline disabled:opacity-50\"\n disabled={!canManageMembers}\n aria-disabled={!canManageMembers}\n title={tr(\n \"devportal.transferOwnership\",\n \"Transfer ownership\",\n )}\n onClick={() =>\n setTransferOwnershipMember(member)\n }\n >\n {tr(\"devportal.makeOwner\", \"Make Owner\")}\n </button>\n </div>\n ),\n },\n ]\n : []),\n ]}\n />\n ) : (\n <EmptyState\n illustration=\"empty-people\"\n title={tr(\"devportal.noMembers\", \"No members\")}\n description={tr(\n \"devportal.inviteTeamMembersToCollaborate\",\n \"Invite team members to collaborate\",\n )}\n />\n )}\n </PageSection>\n\n {/* Pending Invitations */}\n {canManageOrg && invitations.length > 0 && (\n <PageSection\n title={`${tr(\"devportal.pendingInvitations\", \"Pending Invitations\")} (${invitationsCount})`}\n actions={\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n onClick={handleBulkResendInvitations}\n disabled={selectedInvitations.length === 0}\n className=\"rounded-md border border-primary/30 px-3 py-2 text-sm font-medium text-primary hover:bg-action-primary-bg/5 disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {tr(\"devportal.resendSelected\", \"Resend Selected\")}\n {selectedInvitations.length > 0\n ? ` (${selectedInvitations.length})`\n : \"\"}\n </button>\n <button\n onClick={() => setShowBulkCancelInvitationsDialog(true)}\n disabled={selectedInvitations.length === 0}\n className=\"rounded-md border border-status-error-bg px-3 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg-subtle disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {tr(\"devportal.cancelSelected\", \"Cancel Selected\")}\n {selectedInvitations.length > 0\n ? ` (${selectedInvitations.length})`\n : \"\"}\n </button>\n </div>\n }\n >\n <DataTable\n data={invitations}\n keyField=\"id\"\n columns={[\n {\n key: \"select\",\n header: (\n <SelectionCheckbox\n checked={allInvitationsSelected}\n indeterminate={partiallySelectedInvitations}\n onChange={toggleAllInvitations}\n ariaLabel={tr(\n \"devportal.selectAllInvitations\",\n \"Select all invitations\",\n )}\n />\n ),\n className: \"w-12\",\n render: (invitation) => (\n <div onClick={(event) => event.stopPropagation()}>\n <SelectionCheckbox\n checked={selectedInvitationIds.has(invitation.id)}\n onChange={(checked) =>\n toggleInvitationSelection(invitation.id, checked)\n }\n ariaLabel={`Select invitation ${invitation.invitedEmail}`}\n />\n </div>\n ),\n },\n {\n key: \"invitedEmail\",\n header: tr(\"devportal.emailColumn\", \"Email\"),\n render: (inv) => (\n <div className=\"flex items-center gap-2\">\n <Mail size={16} className=\"text-text-secondary\" />\n {inv.invitedEmail}\n </div>\n ),\n },\n {\n key: \"role\",\n header: tr(\"devportal.invitedRoleColumn\", \"Invited Role\"),\n render: (inv) =>\n ORG_ROLE_LABELS[inv.role as DeveloperOrgRole] ?? inv.role,\n },\n {\n key: \"status\",\n header: tr(\"devportal.statusColumn\", \"Status\"),\n render: (inv) => (\n <StatusBadge status={inv.status} type=\"invitation\" />\n ),\n },\n {\n key: \"expiresAt\",\n header: tr(\"devportal.expiresColumn\", \"Expires\"),\n render: (inv) =>\n format(new Date(inv.expiresAt), \"MMM d, yyyy\"),\n },\n {\n key: \"actions\",\n header: \"\",\n className: \"w-24\",\n render: (inv) => (\n <div className=\"flex gap-1\">\n <button\n className=\"rounded p-1 hover:bg-bg-sunken\"\n title={tr(\"devportal.resend\", \"Resend\")}\n onClick={() => handleResendInvitation(inv.id)}\n >\n <RefreshCw size={14} />\n </button>\n <button\n className=\"rounded p-1 hover:bg-bg-sunken text-status-error-text\"\n title={tr(\"devportal.cancel\", \"Cancel\")}\n onClick={() => handleCancelInvitation(inv.id)}\n >\n <UserMinus size={14} />\n </button>\n </div>\n ),\n },\n ]}\n />\n </PageSection>\n )}\n\n {/* Invite Dialog */}\n {showInviteDialog && (\n <ConfirmDialog\n open={showInviteDialog}\n onOpenChange={setShowInviteDialog}\n onConfirm={handleInvite}\n title={tr(\"devportal.inviteTeamMember\", \"Invite Team Member\")}\n confirmLabel={\n inviting\n ? tr(\"devportal.sendingDots\", \"Sending...\")\n : tr(\"devportal.sendInvitation\", \"Send Invitation\")\n }\n >\n <div className=\"space-y-4\">\n <InputField\n label={tr(\"devportal.emailAddress\", \"Email Address\")}\n type=\"email\"\n value={inviteEmail}\n onChange={(e) => setInviteEmail(e.target.value)}\n placeholder=\"colleague@example.com\"\n hint={tr(\n \"devportal.mustBeExistingVerifiedUser\",\n \"Must be an existing verified user\",\n )}\n required\n />\n <SelectField\n label={tr(\"devportal.roleColumn\", \"Role\")}\n value={inviteRole}\n onChange={(e) =>\n setInviteRole(e.target.value as DeveloperOrgRole)\n }\n options={[\n {\n value: \"ADMIN\",\n label: tr(\n \"devportal.adminFullAccess\",\n \"Admin - Full access\",\n ),\n },\n {\n value: \"DEVELOPER\",\n label: tr(\n \"devportal.developerCreateAndManageApps\",\n \"Developer - Create and manage apps\",\n ),\n },\n {\n value: \"VIEWER\",\n label: tr(\n \"devportal.viewerReadOnlyAccess\",\n \"Viewer - Read-only access\",\n ),\n },\n ]}\n required\n />\n <TextareaField\n label={tr(\"devportal.personalMessage\", \"Personal Message\")}\n value={inviteMessage}\n onChange={(e) => setInviteMessage(e.target.value)}\n placeholder={tr(\n \"devportal.personalMessagePlaceholder\",\n \"Add a short note to include in the invitation email\",\n )}\n rows={3}\n />\n </div>\n </ConfirmDialog>\n )}\n\n <ConfirmDialog\n open={showBulkRemoveMembersDialog}\n onOpenChange={setShowBulkRemoveMembersDialog}\n onConfirm={handleBulkRemoveMembers}\n title={tr(\n \"devportal.removeSelectedMembers\",\n \"Remove selected members\",\n )}\n description={`${tr(\"devportal.removeNSelectedMembers\", \"Remove\")} ${removableMembers.length} ${tr(\"devportal.selectedMembersFromOrg\", \"selected member(s) from this organization? This action cannot be undone.\")}`}\n confirmLabel={tr(\"devportal.removeMembers\", \"Remove Members\")}\n cancelLabel={tr(\"devportal.cancel\", \"Cancel\")}\n variant=\"danger\"\n />\n\n <ConfirmDialog\n open={showBulkCancelInvitationsDialog}\n onOpenChange={setShowBulkCancelInvitationsDialog}\n onConfirm={handleBulkCancelInvitations}\n title={tr(\n \"devportal.cancelSelectedInvitations\",\n \"Cancel selected invitations\",\n )}\n description={`${tr(\"devportal.cancelNPendingInvitations\", \"Cancel\")} ${selectedInvitations.length} ${tr(\"devportal.pendingInvitationsQuestion\", \"pending invitation(s)?\")}`}\n confirmLabel={tr(\"devportal.cancelInvitations\", \"Cancel Invitations\")}\n cancelLabel={tr(\"devportal.keepInvitations\", \"Keep Invitations\")}\n variant=\"danger\"\n />\n\n <ConfirmDialog\n open={transferOwnershipMember !== null}\n onOpenChange={(open) => {\n if (!open) setTransferOwnershipMember(null);\n }}\n onConfirm={handleTransferOwnership}\n title={tr(\"devportal.transferOwnership\", \"Transfer ownership\")}\n description={\n transferOwnershipMember\n ? tr(\n \"devportal.transferOwnershipConfirmation\",\n `Transfer organization ownership to ${resolveMemberLabel(transferOwnershipMember)}?`,\n )\n : undefined\n }\n confirmLabel={tr(\"devportal.makeOwner\", \"Make Owner\")}\n cancelLabel={tr(\"devportal.cancel\", \"Cancel\")}\n variant=\"warning\"\n />\n </PageLayout>\n </PermissionGuard>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"OrganizationMembersPage.js","names":[],"sources":["../../../src/devportal/pages/OrganizationMembersPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useEffect, useMemo, useState } from \"react\";\nimport { useParams } from \"react-router-dom\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n Plus,\n Users,\n Mail,\n Shield,\n UserMinus,\n RefreshCw,\n Loader2,\n AlertCircle,\n} from \"lucide-react\";\nimport { PageLayout, PageSection } from \"../components/PageLayout\";\nimport { DataTable } from \"../components/DataTable\";\nimport { StatusBadge } from \"../components/StatusBadge\";\nimport { ConfirmDialog } from \"../components/ConfirmDialog\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport {\n InputField,\n SelectField,\n TextareaField,\n} from \"../components/FormField\";\nimport { SelectionCheckbox } from \"../components/SelectionCheckbox\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport { useDevportalPermission } from \"../hooks/usePermission\";\nimport type { DeveloperOrgRole } from \"../types\";\nimport { ORG_ROLE_LABELS } from \"../utils/constants\";\nimport { format } from \"date-fns\";\nimport {\n useGetOrganizationQuery,\n useGetOrganizationMembersQuery,\n useGetOrganizationInvitationsQuery,\n useInviteMemberMutation,\n useRemoveMemberMutation,\n useSuspendMemberMutation,\n useReactivateMemberMutation,\n useUpdateMemberRoleMutation,\n useTransferOwnershipMutation,\n useCancelInvitationMutation,\n useResendInvitationMutation,\n type OrgMemberFieldsFragment,\n type OrgInvitationFieldsFragment,\n} from \"../hooks\";\n\nconst ORG_ROLE_PRIORITY: Record<DeveloperOrgRole, number> = {\n VIEWER: 0,\n DEVELOPER: 1,\n ADMIN: 2,\n OWNER: 3,\n};\n\nexport const OrganizationMembersPage: FC = () => {\n const { orgId } = useParams<{ orgId: string }>();\n const { currentUser } = useDevportal();\n const { allowed: canInviteByPermission } = useDevportalPermission(\n DEVPORTAL_PERMISSIONS.ORG_INVITATION_CREATE,\n );\n const { allowed: canManageMembersByPermission } = useDevportalPermission(\n DEVPORTAL_PERMISSIONS.ORG_MEMBER_UPDATE,\n );\n const { allowed: canRemoveMembersByPermission } = useDevportalPermission(\n DEVPORTAL_PERMISSIONS.ORG_MEMBER_DELETE,\n );\n const { allowed: canActivateMembersByPermission } = useDevportalPermission(\n DEVPORTAL_PERMISSIONS.ORG_MEMBER_ACTIVATE,\n );\n const { allowed: canDeactivateMembersByPermission } = useDevportalPermission(\n DEVPORTAL_PERMISSIONS.ORG_MEMBER_DEACTIVATE,\n );\n const resolveMemberLabel = (member: {\n userId: string;\n email?: string | null;\n }): string => {\n if (member.email) return member.email;\n if (currentUser?.id === member.userId && currentUser.email)\n return currentUser.email;\n return member.userId;\n };\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n const [showInviteDialog, setShowInviteDialog] = useState(false);\n const [inviteEmail, setInviteEmail] = useState(\"\");\n const [inviteRole, setInviteRole] = useState<DeveloperOrgRole>(\"DEVELOPER\");\n const [inviteMessage, setInviteMessage] = useState(\"\");\n const [actionError, setActionError] = useState<string | null>(null);\n const [actionSuccess, setActionSuccess] = useState<string | null>(null);\n const [selectedMemberIds, setSelectedMemberIds] = useState<Set<string>>(\n new Set(),\n );\n const [selectedInvitationIds, setSelectedInvitationIds] = useState<\n Set<string>\n >(new Set());\n const [showBulkRemoveMembersDialog, setShowBulkRemoveMembersDialog] =\n useState(false);\n const [showBulkCancelInvitationsDialog, setShowBulkCancelInvitationsDialog] =\n useState(false);\n const [transferOwnershipMember, setTransferOwnershipMember] =\n useState<OrgMemberFieldsFragment | null>(null);\n\n const { data: orgData } = useGetOrganizationQuery({\n variables: { id: orgId ?? \"\" },\n skip: !orgId,\n fetchPolicy: \"cache-first\",\n });\n useEffect(() => {\n const name = orgData?.developerOrganization?.name;\n if (!orgId || !name) return;\n const reg = (\n window as unknown as {\n __breadcrumbRegistry?: {\n register: (id: string, name: string) => void;\n unregister: (id: string) => void;\n };\n }\n ).__breadcrumbRegistry;\n reg?.register(orgId, name);\n return () => reg?.unregister(orgId);\n }, [orgId, orgData?.developerOrganization?.name]);\n\n // Queries\n const {\n data: membersData,\n loading: membersLoading,\n error: membersError,\n } = useGetOrganizationMembersQuery({\n variables: { organizationId: orgId! },\n skip: !orgId,\n fetchPolicy: \"cache-and-network\",\n });\n\n // Mutations\n const [inviteMember, { loading: inviting }] = useInviteMemberMutation({\n refetchQueries: [\"GetOrganizationInvitations\"],\n });\n\n const [removeMember] = useRemoveMemberMutation({\n refetchQueries: [\"GetOrganizationMembers\"],\n });\n\n const [suspendMember] = useSuspendMemberMutation({\n refetchQueries: [\"GetOrganizationMembers\"],\n });\n\n const [reactivateMember] = useReactivateMemberMutation({\n refetchQueries: [\"GetOrganizationMembers\"],\n });\n\n const [updateMemberRole] = useUpdateMemberRoleMutation({\n refetchQueries: [\"GetOrganizationMembers\", \"GetOrganization\"],\n });\n\n const [transferOwnership] = useTransferOwnershipMutation({\n refetchQueries: [\"GetOrganizationMembers\", \"GetOrganization\"],\n });\n\n const [cancelInvitation] = useCancelInvitationMutation({\n refetchQueries: [\"GetOrganizationInvitations\"],\n });\n\n const [resendInvitation] = useResendInvitationMutation();\n\n // Derived data\n const members: OrgMemberFieldsFragment[] = useMemo(\n () =>\n membersData?.developerOrgMembers?.edges?.map(\n (e: { node: OrgMemberFieldsFragment }) => e.node,\n ) ?? [],\n [membersData?.developerOrgMembers?.edges],\n );\n\n const currentMembership = currentUser\n ? members.find((member) => member.userId === currentUser.id)\n : undefined;\n const currentRole = currentMembership?.role as DeveloperOrgRole | undefined;\n const canManageOrg =\n currentRole !== undefined &&\n ORG_ROLE_PRIORITY[currentRole] >= ORG_ROLE_PRIORITY.ADMIN;\n const canInvite = canManageOrg && canInviteByPermission;\n const canManageMembers = canManageOrg && canManageMembersByPermission;\n const canRemoveMembers = canManageOrg && canRemoveMembersByPermission;\n const canActivateMembers = canManageOrg && canActivateMembersByPermission;\n const canDeactivateMembers = canManageOrg && canDeactivateMembersByPermission;\n\n const {\n data: invitationsData,\n loading: invitationsLoading,\n error: invitationsError,\n } = useGetOrganizationInvitationsQuery({\n variables: { organizationId: orgId!, status: \"PENDING\" as never },\n skip: !orgId || !canManageOrg,\n fetchPolicy: \"cache-and-network\",\n });\n\n const invitations: OrgInvitationFieldsFragment[] = useMemo(\n () =>\n invitationsData?.developerOrgInvitations?.edges?.map(\n (e: { node: OrgInvitationFieldsFragment }) => e.node,\n ) ?? [],\n [invitationsData?.developerOrgInvitations?.edges],\n );\n\n const membersCount = membersData?.developerOrgMembers?.totalCount ?? 0;\n const invitationsCount =\n invitationsData?.developerOrgInvitations?.totalCount ?? 0;\n\n const selectedMembers = useMemo(\n () => members.filter((member) => selectedMemberIds.has(member.id)),\n [members, selectedMemberIds],\n );\n const selectedInvitations = useMemo(\n () =>\n invitations.filter((invitation) =>\n selectedInvitationIds.has(invitation.id),\n ),\n [invitations, selectedInvitationIds],\n );\n\n const selectableMembers = members.filter((member) => member.role !== \"OWNER\");\n const allMembersSelected =\n selectableMembers.length > 0 &&\n selectableMembers.every((member) => selectedMemberIds.has(member.id));\n const partiallySelectedMembers =\n selectableMembers.some((member) => selectedMemberIds.has(member.id)) &&\n !allMembersSelected;\n\n const allInvitationsSelected =\n invitations.length > 0 &&\n invitations.every((invitation) => selectedInvitationIds.has(invitation.id));\n const partiallySelectedInvitations =\n invitations.some((invitation) =>\n selectedInvitationIds.has(invitation.id),\n ) && !allInvitationsSelected;\n\n const suspendableMembers = selectedMembers.filter(\n (member) => member.role !== \"OWNER\" && !member.isSuspended,\n );\n const reactivatableMembers = selectedMembers.filter(\n (member) => member.isSuspended,\n );\n const removableMembers = selectedMembers.filter(\n (member) => member.role !== \"OWNER\",\n );\n\n const handleInvite = async () => {\n if (!orgId || !inviteEmail.trim()) return;\n setActionError(null);\n\n try {\n await inviteMember({\n variables: {\n organizationId: orgId,\n input: {\n email: inviteEmail,\n role: inviteRole as never,\n message: inviteMessage || undefined,\n },\n },\n });\n setShowInviteDialog(false);\n setInviteEmail(\"\");\n setInviteRole(\"DEVELOPER\");\n setInviteMessage(\"\");\n setActionSuccess(\n tr(\n \"devportal.invitationSent\",\n \"Invitation created and email sent to the team member.\",\n ),\n );\n } catch (err) {\n setActionError(\n err instanceof Error\n ? err.message\n : tr(\"devportal.failedToSendInvitation\", \"Failed to send invitation\"),\n );\n }\n };\n\n const handleRemoveMember = async (memberId: string) => {\n setActionError(null);\n try {\n await removeMember({ variables: { id: memberId } });\n } catch (err) {\n setActionError(\n err instanceof Error\n ? err.message\n : tr(\"devportal.failedToRemoveMember\", \"Failed to remove member\"),\n );\n }\n };\n\n const handleSuspendMember = async (memberId: string, reason?: string) => {\n setActionError(null);\n try {\n await suspendMember({\n variables: { id: memberId, reason: reason || undefined },\n });\n } catch (err) {\n setActionError(\n err instanceof Error\n ? err.message\n : tr(\"devportal.failedToSuspendMember\", \"Failed to suspend member\"),\n );\n }\n };\n\n const handleReactivateMember = async (memberId: string) => {\n setActionError(null);\n try {\n await reactivateMember({ variables: { id: memberId } });\n } catch (err) {\n setActionError(\n err instanceof Error\n ? err.message\n : tr(\n \"devportal.failedToReactivateMember\",\n \"Failed to reactivate member\",\n ),\n );\n }\n };\n\n const handleUpdateMemberRole = async (\n memberId: string,\n role: DeveloperOrgRole,\n ) => {\n setActionError(null);\n try {\n await updateMemberRole({\n variables: {\n id: memberId,\n role: role as never,\n permissions: undefined,\n },\n });\n setActionSuccess(\n tr(\"devportal.memberRoleUpdated\", \"Member role updated successfully\"),\n );\n } catch (err) {\n setActionError(\n err instanceof Error\n ? err.message\n : tr(\n \"devportal.failedToUpdateMemberRole\",\n \"Failed to update member role\",\n ),\n );\n }\n };\n\n const handleTransferOwnership = async () => {\n const member = transferOwnershipMember;\n if (!orgId || !member) return;\n setActionError(null);\n\n try {\n await transferOwnership({\n variables: { organizationId: orgId, newOwnerId: member.userId },\n });\n setTransferOwnershipMember(null);\n setActionSuccess(\n tr(\n \"devportal.organizationOwnershipTransferred\",\n \"Organization ownership transferred successfully\",\n ),\n );\n } catch (err) {\n setActionError(\n err instanceof Error\n ? err.message\n : tr(\n \"devportal.failedToTransferOwnership\",\n \"Failed to transfer organization ownership\",\n ),\n );\n }\n };\n\n const handleCancelInvitation = async (invitationId: string) => {\n setActionError(null);\n try {\n await cancelInvitation({ variables: { id: invitationId } });\n } catch (err) {\n setActionError(\n err instanceof Error\n ? err.message\n : tr(\n \"devportal.failedToCancelInvitation\",\n \"Failed to cancel invitation\",\n ),\n );\n }\n };\n\n const handleResendInvitation = async (invitationId: string) => {\n setActionError(null);\n try {\n await resendInvitation({ variables: { id: invitationId } });\n } catch (err) {\n setActionError(\n err instanceof Error\n ? err.message\n : tr(\n \"devportal.failedToResendInvitation\",\n \"Failed to resend invitation\",\n ),\n );\n }\n };\n\n const clearSelections = () => {\n setSelectedMemberIds(new Set());\n setSelectedInvitationIds(new Set());\n };\n\n const toggleMemberSelection = (memberId: string, checked: boolean) => {\n setSelectedMemberIds((prev) => {\n const next = new Set(prev);\n if (checked) {\n next.add(memberId);\n } else {\n next.delete(memberId);\n }\n return next;\n });\n };\n\n const toggleAllMembers = (checked: boolean) => {\n setSelectedMemberIds((prev) => {\n const next = new Set(prev);\n selectableMembers.forEach((member) => {\n if (checked) {\n next.add(member.id);\n } else {\n next.delete(member.id);\n }\n });\n return next;\n });\n };\n\n const toggleInvitationSelection = (\n invitationId: string,\n checked: boolean,\n ) => {\n setSelectedInvitationIds((prev) => {\n const next = new Set(prev);\n if (checked) {\n next.add(invitationId);\n } else {\n next.delete(invitationId);\n }\n return next;\n });\n };\n\n const toggleAllInvitations = (checked: boolean) => {\n setSelectedInvitationIds((prev) => {\n const next = new Set(prev);\n invitations.forEach((invitation) => {\n if (checked) {\n next.add(invitation.id);\n } else {\n next.delete(invitation.id);\n }\n });\n return next;\n });\n };\n\n const handleBulkSuspendMembers = async () => {\n if (suspendableMembers.length === 0) return;\n setActionError(null);\n const results = await Promise.allSettled(\n suspendableMembers.map((member) =>\n suspendMember({ variables: { id: member.id, reason: undefined } }),\n ),\n );\n const succeeded = results.filter(\n (result) => result.status === \"fulfilled\",\n ).length;\n const failed = results.length - succeeded;\n setActionSuccess(\n `${tr(\"devportal.suspended\", \"Suspended\")} ${succeeded} ${succeeded === 1 ? tr(\"devportal.memberLower\", \"member\") : tr(\"devportal.membersLower\", \"members\")}${\n failed > 0 ? ` • ${failed} ${tr(\"devportal.failed\", \"failed\")}` : \"\"\n }`,\n );\n setSelectedMemberIds(new Set());\n };\n\n const handleBulkReactivateMembers = async () => {\n if (reactivatableMembers.length === 0) return;\n setActionError(null);\n const results = await Promise.allSettled(\n reactivatableMembers.map((member) =>\n reactivateMember({ variables: { id: member.id } }),\n ),\n );\n const succeeded = results.filter(\n (result) => result.status === \"fulfilled\",\n ).length;\n const failed = results.length - succeeded;\n setActionSuccess(\n `${tr(\"devportal.reactivated\", \"Reactivated\")} ${succeeded} ${succeeded === 1 ? tr(\"devportal.memberLower\", \"member\") : tr(\"devportal.membersLower\", \"members\")}${\n failed > 0 ? ` • ${failed} ${tr(\"devportal.failed\", \"failed\")}` : \"\"\n }`,\n );\n setSelectedMemberIds(new Set());\n };\n\n const handleBulkRemoveMembers = async () => {\n if (removableMembers.length === 0) return;\n setActionError(null);\n const results = await Promise.allSettled(\n removableMembers.map((member) =>\n removeMember({ variables: { id: member.id } }),\n ),\n );\n const succeeded = results.filter(\n (result) => result.status === \"fulfilled\",\n ).length;\n const failed = results.length - succeeded;\n setActionSuccess(\n `${tr(\"devportal.removed\", \"Removed\")} ${succeeded} ${succeeded === 1 ? tr(\"devportal.memberLower\", \"member\") : tr(\"devportal.membersLower\", \"members\")}${\n failed > 0 ? ` • ${failed} ${tr(\"devportal.failed\", \"failed\")}` : \"\"\n }`,\n );\n setShowBulkRemoveMembersDialog(false);\n setSelectedMemberIds(new Set());\n };\n\n const handleBulkResendInvitations = async () => {\n if (selectedInvitations.length === 0) return;\n setActionError(null);\n const results = await Promise.allSettled(\n selectedInvitations.map((invitation) =>\n resendInvitation({ variables: { id: invitation.id } }),\n ),\n );\n const succeeded = results.filter(\n (result) => result.status === \"fulfilled\",\n ).length;\n const failed = results.length - succeeded;\n setActionSuccess(\n `${tr(\"devportal.resent\", \"Resent\")} ${succeeded} ${succeeded === 1 ? tr(\"devportal.invitationLower\", \"invitation\") : tr(\"devportal.invitationsLower\", \"invitations\")}${\n failed > 0 ? ` • ${failed} ${tr(\"devportal.failed\", \"failed\")}` : \"\"\n }`,\n );\n setSelectedInvitationIds(new Set());\n };\n\n const handleBulkCancelInvitations = async () => {\n if (selectedInvitations.length === 0) return;\n setActionError(null);\n const results = await Promise.allSettled(\n selectedInvitations.map((invitation) =>\n cancelInvitation({ variables: { id: invitation.id } }),\n ),\n );\n const succeeded = results.filter(\n (result) => result.status === \"fulfilled\",\n ).length;\n const failed = results.length - succeeded;\n setActionSuccess(\n `${tr(\"devportal.cancelled\", \"Cancelled\")} ${succeeded} ${succeeded === 1 ? tr(\"devportal.invitationLower\", \"invitation\") : tr(\"devportal.invitationsLower\", \"invitations\")}${\n failed > 0 ? ` • ${failed} ${tr(\"devportal.failed\", \"failed\")}` : \"\"\n }`,\n );\n setShowBulkCancelInvitationsDialog(false);\n setSelectedInvitationIds(new Set());\n };\n\n const isLoading = membersLoading || invitationsLoading;\n const hasError = membersError || invitationsError;\n\n if (isLoading && !membersData && !invitationsData) {\n return (\n <PageLayout\n title={tr(\"devportal.teamMembers\", \"Team Members\")}\n description={tr(\n \"devportal.manageYourOrganizationsTeam\",\n \"Manage your organization's team\",\n )}\n backLink={`/organizations/${orgId}`}\n backLabel={tr(\"devportal.backToOrganization\", \"Back to Organization\")}\n >\n <div className=\"flex items-center justify-center py-16\">\n <Loader2 className=\"h-8 w-8 animate-spin text-text-secondary\" />\n </div>\n </PageLayout>\n );\n }\n\n if (hasError && !membersData && !invitationsData) {\n return (\n <PageLayout\n title={tr(\"devportal.teamMembers\", \"Team Members\")}\n description={tr(\n \"devportal.manageYourOrganizationsTeam\",\n \"Manage your organization's team\",\n )}\n backLink={`/organizations/${orgId}`}\n backLabel={tr(\"devportal.backToOrganization\", \"Back to Organization\")}\n >\n <EmptyState\n icon={<AlertCircle size={32} className=\"text-status-error-text\" />}\n title={tr(\"devportal.failedToLoadMembers\", \"Failed to load members\")}\n description={\n membersError?.message ||\n invitationsError?.message ||\n tr(\n \"devportal.anErrorOccurredWhileLoadingTeamData\",\n \"An error occurred while loading team data\",\n )\n }\n />\n </PageLayout>\n );\n }\n\n return (\n <PermissionGuard authOnly>\n <PageLayout\n title={tr(\"devportal.teamMembers\", \"Team Members\")}\n description={tr(\n \"devportal.manageYourOrganizationsTeam\",\n \"Manage your organization's team\",\n )}\n purpose={tr(\n \"devportal.teamMembersPurpose\",\n \"Control who can work on this organization's apps and what they can do. Invite teammates by email, assign roles to grant the right level of access, and suspend or remove anyone who should no longer have it.\",\n )}\n backLink={`/organizations/${orgId}`}\n backLabel={tr(\"devportal.backToOrganization\", \"Back to Organization\")}\n actions={\n canInvite ? (\n <button\n onClick={() => setShowInviteDialog(true)}\n disabled={!canInvite}\n aria-disabled={!canInvite}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 disabled:opacity-50\"\n >\n <Plus size={16} />\n {tr(\"devportal.inviteMember\", \"Invite Member\")}\n </button>\n ) : undefined\n }\n >\n {/* Action Error Banner */}\n {actionError && (\n <div className=\"rounded-lg border border-border-subtle bg-status-error-bg-subtle p-3 flex items-center gap-2 text-sm text-status-error-text\">\n <AlertCircle size={16} className=\"flex-shrink-0\" />\n {actionError}\n <button\n onClick={() => setActionError(null)}\n className=\"ml-auto text-status-error-text hover:text-status-error-text\"\n >\n {tr(\"devportal.dismiss\", \"Dismiss\")}\n </button>\n </div>\n )}\n\n {actionSuccess && (\n <div className=\"rounded-lg border border-border-subtle bg-status-success-bg-subtle p-3 flex items-center gap-2 text-sm text-status-success-text\">\n <AlertCircle size={16} className=\"flex-shrink-0\" />\n {actionSuccess}\n <button\n onClick={() => setActionSuccess(null)}\n className=\"ml-auto text-status-success-text hover:text-status-success-text\"\n >\n {tr(\"devportal.dismiss\", \"Dismiss\")}\n </button>\n </div>\n )}\n\n {(selectedMemberIds.size > 0 || selectedInvitationIds.size > 0) && (\n <div className=\"flex flex-wrap items-center gap-3 rounded-card border border-border-seam bg-bg-surface p-4\">\n <span className=\"rounded-full bg-bg-sunken px-3 py-1 text-sm font-medium text-text-secondary\">\n {selectedMemberIds.size + selectedInvitationIds.size}{\" \"}\n {tr(\"devportal.selected\", \"selected\")}\n </span>\n <button\n onClick={clearSelections}\n className=\"text-sm text-text-secondary hover:text-text-primary\"\n >\n {tr(\"devportal.clearAllSelections\", \"Clear all selections\")}\n </button>\n </div>\n )}\n\n {/* Current Members */}\n <PageSection\n title={`${tr(\"devportal.members\", \"Members\")} (${membersCount})`}\n actions={\n members.length > 0 && canManageOrg ? (\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n onClick={handleBulkSuspendMembers}\n disabled={\n suspendableMembers.length === 0 || !canDeactivateMembers\n }\n aria-disabled={!canDeactivateMembers}\n className=\"rounded-md border border-status-warning-bg px-3 py-2 text-sm font-medium text-status-warning-text hover:bg-status-warning-bg-subtle disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {tr(\"devportal.suspendSelected\", \"Suspend Selected\")}\n {suspendableMembers.length > 0\n ? ` (${suspendableMembers.length})`\n : \"\"}\n </button>\n <button\n onClick={handleBulkReactivateMembers}\n disabled={\n reactivatableMembers.length === 0 || !canActivateMembers\n }\n aria-disabled={!canActivateMembers}\n className=\"rounded-md border border-status-success-bg px-3 py-2 text-sm font-medium text-status-success-text hover:bg-status-success-bg-subtle disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {tr(\"devportal.reactivateSelected\", \"Reactivate Selected\")}\n {reactivatableMembers.length > 0\n ? ` (${reactivatableMembers.length})`\n : \"\"}\n </button>\n <button\n onClick={() => setShowBulkRemoveMembersDialog(true)}\n disabled={removableMembers.length === 0 || !canRemoveMembers}\n aria-disabled={!canRemoveMembers}\n className=\"rounded-md border border-status-error-bg px-3 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg-subtle disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {tr(\"devportal.removeSelected\", \"Remove Selected\")}\n {removableMembers.length > 0\n ? ` (${removableMembers.length})`\n : \"\"}\n </button>\n </div>\n ) : null\n }\n >\n {members.length > 0 ? (\n <DataTable\n data={members}\n keyField=\"id\"\n columns={[\n ...(canManageOrg\n ? [\n {\n key: \"select\",\n header: (\n <SelectionCheckbox\n checked={allMembersSelected}\n indeterminate={partiallySelectedMembers}\n onChange={toggleAllMembers}\n ariaLabel={tr(\n \"devportal.selectAllMembers\",\n \"Select all members\",\n )}\n />\n ),\n className: \"w-12\",\n render: (member: OrgMemberFieldsFragment) => (\n <div onClick={(event) => event.stopPropagation()}>\n <SelectionCheckbox\n checked={selectedMemberIds.has(member.id)}\n disabled={member.role === \"OWNER\"}\n onChange={(checked) =>\n toggleMemberSelection(member.id, checked)\n }\n ariaLabel={`Select member ${resolveMemberLabel(member)}`}\n />\n </div>\n ),\n },\n ]\n : []),\n {\n key: \"email\",\n header: tr(\"devportal.memberColumn\", \"Member\"),\n render: (member) => {\n const label = resolveMemberLabel(member);\n return (\n <div className=\"flex items-center gap-3\">\n <div className=\"flex h-8 w-8 items-center justify-center rounded-full bg-action-primary-bg/10 text-primary text-sm font-medium\">\n {label.charAt(0).toUpperCase()}\n </div>\n <div>\n <p className=\"font-medium\">{label}</p>\n <p className=\"text-xs text-text-secondary\">\n {tr(\"devportal.joined\", \"Joined\")}{\" \"}\n {format(new Date(member.joinedAt), \"MMM d, yyyy\")}\n </p>\n </div>\n </div>\n );\n },\n },\n {\n key: \"role\",\n header: tr(\"devportal.roleColumn\", \"Role\"),\n render: (member) => (\n <div className=\"flex items-center gap-2\">\n <Shield size={14} className=\"text-text-secondary\" />\n {member.role === \"OWNER\" || !canManageMembers ? (\n <span>\n {ORG_ROLE_LABELS[member.role as DeveloperOrgRole] ??\n member.role}\n </span>\n ) : (\n <select\n value={member.role}\n onChange={(event) =>\n void handleUpdateMemberRole(\n member.id,\n event.target.value as DeveloperOrgRole,\n )\n }\n className=\"rounded-md border border-border-subtle bg-bg-surface px-2 py-1 text-xs text-text-primary\"\n >\n {Object.entries(ORG_ROLE_LABELS)\n .filter(([value]) => value !== \"OWNER\")\n .map(([value, label]) => (\n <option key={value} value={value}>\n {label}\n </option>\n ))}\n </select>\n )}\n </div>\n ),\n },\n {\n key: \"status\",\n header: tr(\"devportal.statusColumn\", \"Status\"),\n render: (member) =>\n member.isSuspended ? (\n <StatusBadge status=\"SUSPENDED\" type=\"app\" />\n ) : member.isActive ? (\n <StatusBadge status=\"ACTIVE\" type=\"app\" />\n ) : (\n <StatusBadge status=\"INACTIVE\" type=\"app\" />\n ),\n },\n ...(canManageOrg\n ? [\n {\n key: \"actions\",\n header: \"\",\n className: \"w-32\",\n render: (member: OrgMemberFieldsFragment) =>\n member.role !== \"OWNER\" && (\n <div className=\"flex gap-1\">\n {member.isSuspended ? (\n <button\n className=\"text-xs text-status-success-text hover:underline disabled:opacity-50\"\n disabled={!canActivateMembers}\n aria-disabled={!canActivateMembers}\n title={tr(\n \"devportal.reactivateMember\",\n \"Reactivate member\",\n )}\n onClick={() =>\n handleReactivateMember(member.id)\n }\n >\n {tr(\"devportal.reactivate\", \"Reactivate\")}\n </button>\n ) : (\n <button\n className=\"text-xs text-status-warning-text hover:underline disabled:opacity-50\"\n disabled={!canDeactivateMembers}\n aria-disabled={!canDeactivateMembers}\n title={tr(\n \"devportal.suspendMember\",\n \"Suspend member\",\n )}\n onClick={() => handleSuspendMember(member.id)}\n >\n {tr(\"devportal.suspend\", \"Suspend\")}\n </button>\n )}\n <button\n className=\"text-xs text-status-error-text hover:underline disabled:opacity-50\"\n disabled={!canRemoveMembers}\n aria-disabled={!canRemoveMembers}\n title={tr(\n \"devportal.removeMember\",\n \"Remove member\",\n )}\n onClick={() => handleRemoveMember(member.id)}\n >\n {tr(\"devportal.remove\", \"Remove\")}\n </button>\n <button\n className=\"text-xs text-primary hover:underline disabled:opacity-50\"\n disabled={!canManageMembers}\n aria-disabled={!canManageMembers}\n title={tr(\n \"devportal.transferOwnership\",\n \"Transfer ownership\",\n )}\n onClick={() =>\n setTransferOwnershipMember(member)\n }\n >\n {tr(\"devportal.makeOwner\", \"Make Owner\")}\n </button>\n </div>\n ),\n },\n ]\n : []),\n ]}\n />\n ) : (\n <EmptyState\n illustration=\"empty-people\"\n title={tr(\"devportal.noMembers\", \"No members\")}\n description={tr(\n \"devportal.inviteTeamMembersToCollaborate\",\n \"Invite team members to collaborate\",\n )}\n />\n )}\n </PageSection>\n\n {/* Pending Invitations */}\n {canManageOrg && invitations.length > 0 && (\n <PageSection\n title={`${tr(\"devportal.pendingInvitations\", \"Pending Invitations\")} (${invitationsCount})`}\n actions={\n <div className=\"flex flex-wrap items-center gap-2\">\n <button\n onClick={handleBulkResendInvitations}\n disabled={selectedInvitations.length === 0}\n className=\"rounded-md border border-primary/30 px-3 py-2 text-sm font-medium text-primary hover:bg-action-primary-bg/5 disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {tr(\"devportal.resendSelected\", \"Resend Selected\")}\n {selectedInvitations.length > 0\n ? ` (${selectedInvitations.length})`\n : \"\"}\n </button>\n <button\n onClick={() => setShowBulkCancelInvitationsDialog(true)}\n disabled={selectedInvitations.length === 0}\n className=\"rounded-md border border-status-error-bg px-3 py-2 text-sm font-medium text-status-error-text hover:bg-status-error-bg-subtle disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {tr(\"devportal.cancelSelected\", \"Cancel Selected\")}\n {selectedInvitations.length > 0\n ? ` (${selectedInvitations.length})`\n : \"\"}\n </button>\n </div>\n }\n >\n <DataTable\n data={invitations}\n keyField=\"id\"\n columns={[\n {\n key: \"select\",\n header: (\n <SelectionCheckbox\n checked={allInvitationsSelected}\n indeterminate={partiallySelectedInvitations}\n onChange={toggleAllInvitations}\n ariaLabel={tr(\n \"devportal.selectAllInvitations\",\n \"Select all invitations\",\n )}\n />\n ),\n className: \"w-12\",\n render: (invitation) => (\n <div onClick={(event) => event.stopPropagation()}>\n <SelectionCheckbox\n checked={selectedInvitationIds.has(invitation.id)}\n onChange={(checked) =>\n toggleInvitationSelection(invitation.id, checked)\n }\n ariaLabel={`Select invitation ${invitation.invitedEmail}`}\n />\n </div>\n ),\n },\n {\n key: \"invitedEmail\",\n header: tr(\"devportal.emailColumn\", \"Email\"),\n render: (inv) => (\n <div className=\"flex items-center gap-2\">\n <Mail size={16} className=\"text-text-secondary\" />\n {inv.invitedEmail}\n </div>\n ),\n },\n {\n key: \"role\",\n header: tr(\"devportal.invitedRoleColumn\", \"Invited Role\"),\n render: (inv) =>\n ORG_ROLE_LABELS[inv.role as DeveloperOrgRole] ?? inv.role,\n },\n {\n key: \"status\",\n header: tr(\"devportal.statusColumn\", \"Status\"),\n render: (inv) => (\n <StatusBadge status={inv.status} type=\"invitation\" />\n ),\n },\n {\n key: \"expiresAt\",\n header: tr(\"devportal.expiresColumn\", \"Expires\"),\n render: (inv) =>\n format(new Date(inv.expiresAt), \"MMM d, yyyy\"),\n },\n {\n key: \"actions\",\n header: \"\",\n className: \"w-24\",\n render: (inv) => (\n <div className=\"flex gap-1\">\n <button\n className=\"rounded p-1 hover:bg-bg-sunken\"\n title={tr(\"devportal.resend\", \"Resend\")}\n onClick={() => handleResendInvitation(inv.id)}\n >\n <RefreshCw size={14} />\n </button>\n <button\n className=\"rounded p-1 hover:bg-bg-sunken text-status-error-text\"\n title={tr(\"devportal.cancel\", \"Cancel\")}\n onClick={() => handleCancelInvitation(inv.id)}\n >\n <UserMinus size={14} />\n </button>\n </div>\n ),\n },\n ]}\n />\n </PageSection>\n )}\n\n {/* Invite Dialog */}\n {showInviteDialog && (\n <ConfirmDialog\n open={showInviteDialog}\n onOpenChange={setShowInviteDialog}\n onConfirm={handleInvite}\n title={tr(\"devportal.inviteTeamMember\", \"Invite Team Member\")}\n confirmLabel={\n inviting\n ? tr(\"devportal.sendingDots\", \"Sending...\")\n : tr(\"devportal.sendInvitation\", \"Send Invitation\")\n }\n >\n <div className=\"space-y-4\">\n <InputField\n label={tr(\"devportal.emailAddress\", \"Email Address\")}\n type=\"email\"\n value={inviteEmail}\n onChange={(e) => setInviteEmail(e.target.value)}\n placeholder=\"colleague@example.com\"\n hint={tr(\n \"devportal.mustBeExistingVerifiedUser\",\n \"Must be an existing verified user\",\n )}\n required\n />\n <SelectField\n label={tr(\"devportal.roleColumn\", \"Role\")}\n value={inviteRole}\n onChange={(e) =>\n setInviteRole(e.target.value as DeveloperOrgRole)\n }\n options={[\n {\n value: \"ADMIN\",\n label: tr(\n \"devportal.adminFullAccess\",\n \"Admin - Full access\",\n ),\n },\n {\n value: \"DEVELOPER\",\n label: tr(\n \"devportal.developerCreateAndManageApps\",\n \"Developer - Create and manage apps\",\n ),\n },\n {\n value: \"VIEWER\",\n label: tr(\n \"devportal.viewerReadOnlyAccess\",\n \"Viewer - Read-only access\",\n ),\n },\n ]}\n required\n />\n <TextareaField\n label={tr(\"devportal.personalMessage\", \"Personal Message\")}\n value={inviteMessage}\n onChange={(e) => setInviteMessage(e.target.value)}\n placeholder={tr(\n \"devportal.personalMessagePlaceholder\",\n \"Add a short note to include in the invitation email\",\n )}\n rows={3}\n />\n </div>\n </ConfirmDialog>\n )}\n\n <ConfirmDialog\n open={showBulkRemoveMembersDialog}\n onOpenChange={setShowBulkRemoveMembersDialog}\n onConfirm={handleBulkRemoveMembers}\n title={tr(\n \"devportal.removeSelectedMembers\",\n \"Remove selected members\",\n )}\n description={`${tr(\"devportal.removeNSelectedMembers\", \"Remove\")} ${removableMembers.length} ${tr(\"devportal.selectedMembersFromOrg\", \"selected member(s) from this organization? This action cannot be undone.\")}`}\n confirmLabel={tr(\"devportal.removeMembers\", \"Remove Members\")}\n cancelLabel={tr(\"devportal.cancel\", \"Cancel\")}\n variant=\"danger\"\n />\n\n <ConfirmDialog\n open={showBulkCancelInvitationsDialog}\n onOpenChange={setShowBulkCancelInvitationsDialog}\n onConfirm={handleBulkCancelInvitations}\n title={tr(\n \"devportal.cancelSelectedInvitations\",\n \"Cancel selected invitations\",\n )}\n description={`${tr(\"devportal.cancelNPendingInvitations\", \"Cancel\")} ${selectedInvitations.length} ${tr(\"devportal.pendingInvitationsQuestion\", \"pending invitation(s)?\")}`}\n confirmLabel={tr(\"devportal.cancelInvitations\", \"Cancel Invitations\")}\n cancelLabel={tr(\"devportal.keepInvitations\", \"Keep Invitations\")}\n variant=\"danger\"\n />\n\n <ConfirmDialog\n open={transferOwnershipMember !== null}\n onOpenChange={(open) => {\n if (!open) setTransferOwnershipMember(null);\n }}\n onConfirm={handleTransferOwnership}\n title={tr(\"devportal.transferOwnership\", \"Transfer ownership\")}\n description={\n transferOwnershipMember\n ? tr(\n \"devportal.transferOwnershipConfirmation\",\n `Transfer organization ownership to ${resolveMemberLabel(transferOwnershipMember)}?`,\n )\n : undefined\n }\n confirmLabel={tr(\"devportal.makeOwner\", \"Make Owner\")}\n cancelLabel={tr(\"devportal.cancel\", \"Cancel\")}\n variant=\"warning\"\n />\n </PageLayout>\n </PermissionGuard>\n );\n};\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OrganizationsPage.d.ts","sourceRoot":"","sources":["../../../src/devportal/pages/OrganizationsPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"OrganizationsPage.d.ts","sourceRoot":"","sources":["../../../src/devportal/pages/OrganizationsPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC;AAyBhC;;;;;GAKG;AACH,eAAO,MAAM,iBAAiB,EAAE,EA8K/B,CAAC"}
|
|
@@ -7,121 +7,120 @@ import { StatusBadge as a, VerifiedBadge as o } from "../components/StatusBadge.
|
|
|
7
7
|
import { PermissionGuard as s } from "../components/PermissionGuard.js";
|
|
8
8
|
import "../components/index.js";
|
|
9
9
|
import "../hooks/index.js";
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import { EmphasisPanel as b, GlassCard as x } from "@burdenoff/fe-libs/ui";
|
|
10
|
+
import { useI18n as c } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";
|
|
11
|
+
import { AlertCircle as l, AppWindow as u, Building2 as d, CheckCircle as f, DollarSign as p, Loader2 as m, Plus as h, Users as g } from "lucide-react";
|
|
12
|
+
import { jsx as _, jsxs as v } from "react/jsx-runtime";
|
|
13
|
+
import { EmphasisPanel as y, GlassCard as b } from "@burdenoff/fe-libs/ui";
|
|
15
14
|
//#region src/devportal/pages/OrganizationsPage.tsx
|
|
16
|
-
var
|
|
17
|
-
let { navigate: a, basePath: o } = e(), { t:
|
|
18
|
-
let n =
|
|
15
|
+
var x = () => {
|
|
16
|
+
let { navigate: a, basePath: o } = e(), { t: u } = c(), d = (e, t) => {
|
|
17
|
+
let n = u(e);
|
|
19
18
|
return n === e ? t : n;
|
|
20
|
-
}, { data:
|
|
19
|
+
}, { data: f, loading: p, error: g, refetch: x } = n({ fetchPolicy: "cache-and-network" }), C = f?.myDeveloperOrganizations ?? [], w = (e) => {
|
|
21
20
|
a?.(`${o}${e}`);
|
|
22
21
|
};
|
|
23
|
-
return
|
|
24
|
-
title:
|
|
25
|
-
description:
|
|
26
|
-
children: /* @__PURE__ */
|
|
22
|
+
return p && !f ? /* @__PURE__ */ _(i, {
|
|
23
|
+
title: d("devportal.developerOrganizations", "Developer Organizations"),
|
|
24
|
+
description: d("devportal.manageYourDevTeams", "Manage your development teams"),
|
|
25
|
+
children: /* @__PURE__ */ v("div", {
|
|
27
26
|
className: "flex items-center justify-center py-12",
|
|
28
|
-
children: [/* @__PURE__ */
|
|
27
|
+
children: [/* @__PURE__ */ _(m, { className: "h-8 w-8 animate-spin text-text-secondary" }), /* @__PURE__ */ _("span", {
|
|
29
28
|
className: "ml-2 text-text-secondary",
|
|
30
|
-
children:
|
|
29
|
+
children: d("devportal.loadingOrganizations", "Loading organizations...")
|
|
31
30
|
})]
|
|
32
31
|
})
|
|
33
|
-
}) :
|
|
34
|
-
title:
|
|
35
|
-
description:
|
|
36
|
-
children: /* @__PURE__ */
|
|
32
|
+
}) : g ? /* @__PURE__ */ _(i, {
|
|
33
|
+
title: d("devportal.developerOrganizations", "Developer Organizations"),
|
|
34
|
+
description: d("devportal.manageYourDevTeams", "Manage your development teams"),
|
|
35
|
+
children: /* @__PURE__ */ v("div", {
|
|
37
36
|
className: "rounded-lg border border-status-error-border/50 bg-status-error-bg/10 p-6 text-center",
|
|
38
37
|
children: [
|
|
39
|
-
/* @__PURE__ */
|
|
40
|
-
/* @__PURE__ */
|
|
38
|
+
/* @__PURE__ */ _(l, { className: "mx-auto h-8 w-8 text-status-error-text" }),
|
|
39
|
+
/* @__PURE__ */ _("h3", {
|
|
41
40
|
className: "mt-2 font-medium text-status-error-text",
|
|
42
|
-
children:
|
|
41
|
+
children: d("devportal.failedToLoadOrganizations", "Failed to load organizations")
|
|
43
42
|
}),
|
|
44
|
-
/* @__PURE__ */
|
|
43
|
+
/* @__PURE__ */ _("p", {
|
|
45
44
|
className: "mt-1 text-sm text-text-secondary",
|
|
46
|
-
children: r(
|
|
45
|
+
children: r(g)
|
|
47
46
|
}),
|
|
48
|
-
/* @__PURE__ */
|
|
49
|
-
onClick: () =>
|
|
47
|
+
/* @__PURE__ */ _("button", {
|
|
48
|
+
onClick: () => x(),
|
|
50
49
|
className: "mt-4 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90",
|
|
51
|
-
children:
|
|
50
|
+
children: d("devportal.tryAgain", "Try Again")
|
|
52
51
|
})
|
|
53
52
|
]
|
|
54
53
|
})
|
|
55
|
-
}) : /* @__PURE__ */
|
|
56
|
-
|
|
57
|
-
children: /* @__PURE__ */
|
|
58
|
-
title:
|
|
59
|
-
description:
|
|
60
|
-
purpose:
|
|
61
|
-
actions: /* @__PURE__ */
|
|
62
|
-
onClick: () =>
|
|
54
|
+
}) : /* @__PURE__ */ _(s, {
|
|
55
|
+
authOnly: !0,
|
|
56
|
+
children: /* @__PURE__ */ v(i, {
|
|
57
|
+
title: d("devportal.developerOrganizations", "Developer Organizations"),
|
|
58
|
+
description: d("devportal.manageYourDevTeams", "Manage your development teams"),
|
|
59
|
+
purpose: d("devportal.organizationsPurpose", "An organization is the team that owns your apps, members, and revenue. Create one so collaborators can share access to the same applications, billing, and store presence instead of working in isolation."),
|
|
60
|
+
actions: /* @__PURE__ */ v("button", {
|
|
61
|
+
onClick: () => w("/organizations/new"),
|
|
63
62
|
className: "inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 transition-colors",
|
|
64
|
-
children: [/* @__PURE__ */
|
|
63
|
+
children: [/* @__PURE__ */ _(h, { size: 16 }), d("devportal.createOrganization", "Create Organization")]
|
|
65
64
|
}),
|
|
66
|
-
children: [
|
|
65
|
+
children: [C.length > 0 && /* @__PURE__ */ _(y, {
|
|
67
66
|
className: "mb-6",
|
|
68
|
-
children: /* @__PURE__ */
|
|
67
|
+
children: /* @__PURE__ */ v("div", {
|
|
69
68
|
className: "grid grid-cols-2 gap-4 sm:grid-cols-3",
|
|
70
69
|
children: [
|
|
71
|
-
/* @__PURE__ */
|
|
70
|
+
/* @__PURE__ */ v(b, {
|
|
72
71
|
treatment: "metric",
|
|
73
72
|
className: "p-4",
|
|
74
|
-
children: [/* @__PURE__ */
|
|
73
|
+
children: [/* @__PURE__ */ _("p", {
|
|
75
74
|
className: "text-sm text-text-secondary",
|
|
76
|
-
children:
|
|
77
|
-
}), /* @__PURE__ */
|
|
75
|
+
children: d("devportal.organizations", "Organizations")
|
|
76
|
+
}), /* @__PURE__ */ _("p", {
|
|
78
77
|
className: "mt-1 text-2xl font-semibold tabular-nums",
|
|
79
|
-
children:
|
|
78
|
+
children: C.length
|
|
80
79
|
})]
|
|
81
80
|
}),
|
|
82
|
-
/* @__PURE__ */
|
|
81
|
+
/* @__PURE__ */ v(b, {
|
|
83
82
|
treatment: "metric",
|
|
84
83
|
className: "p-4",
|
|
85
|
-
children: [/* @__PURE__ */
|
|
84
|
+
children: [/* @__PURE__ */ _("p", {
|
|
86
85
|
className: "text-sm text-text-secondary",
|
|
87
|
-
children:
|
|
88
|
-
}), /* @__PURE__ */
|
|
86
|
+
children: d("devportal.totalApps", "Total Apps")
|
|
87
|
+
}), /* @__PURE__ */ _("p", {
|
|
89
88
|
className: "mt-1 text-2xl font-semibold tabular-nums",
|
|
90
|
-
children:
|
|
89
|
+
children: C.reduce((e, t) => e + (t.totalApps ?? 0), 0)
|
|
91
90
|
})]
|
|
92
91
|
}),
|
|
93
|
-
/* @__PURE__ */
|
|
92
|
+
/* @__PURE__ */ v(b, {
|
|
94
93
|
treatment: "metric",
|
|
95
94
|
className: "p-4",
|
|
96
|
-
children: [/* @__PURE__ */
|
|
95
|
+
children: [/* @__PURE__ */ _("p", {
|
|
97
96
|
className: "text-sm text-text-secondary",
|
|
98
|
-
children:
|
|
99
|
-
}), /* @__PURE__ */
|
|
97
|
+
children: d("devportal.activeInstalls", "Active Installs")
|
|
98
|
+
}), /* @__PURE__ */ _("p", {
|
|
100
99
|
className: "mt-1 text-2xl font-semibold tabular-nums",
|
|
101
|
-
children:
|
|
100
|
+
children: C.reduce((e, t) => e + (t.activeInstalls ?? 0), 0).toLocaleString()
|
|
102
101
|
})]
|
|
103
102
|
})
|
|
104
103
|
]
|
|
105
104
|
})
|
|
106
|
-
}),
|
|
105
|
+
}), C.length > 0 ? /* @__PURE__ */ _("div", {
|
|
107
106
|
className: "grid gap-6",
|
|
108
|
-
children:
|
|
107
|
+
children: C.map((e) => /* @__PURE__ */ _(S, {
|
|
109
108
|
org: e,
|
|
110
|
-
onClick: () =>
|
|
109
|
+
onClick: () => w(`/organizations/${e.id}`)
|
|
111
110
|
}, e.id))
|
|
112
|
-
}) : /* @__PURE__ */
|
|
111
|
+
}) : /* @__PURE__ */ _(t, {
|
|
113
112
|
illustration: "empty-people",
|
|
114
|
-
title:
|
|
115
|
-
description:
|
|
116
|
-
action: /* @__PURE__ */
|
|
117
|
-
onClick: () =>
|
|
113
|
+
title: d("devportal.noOrganizationsYet", "No organizations yet"),
|
|
114
|
+
description: d("devportal.createOrgToCollaborate", "Create an organization to collaborate with your team and manage apps together"),
|
|
115
|
+
action: /* @__PURE__ */ v("button", {
|
|
116
|
+
onClick: () => w("/organizations/new"),
|
|
118
117
|
className: "inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90",
|
|
119
|
-
children: [/* @__PURE__ */
|
|
118
|
+
children: [/* @__PURE__ */ _(h, { size: 16 }), d("devportal.createOrganization", "Create Organization")]
|
|
120
119
|
})
|
|
121
120
|
})]
|
|
122
121
|
})
|
|
123
122
|
});
|
|
124
|
-
},
|
|
123
|
+
}, S = ({ org: e, onClick: t }) => /* @__PURE__ */ v("div", {
|
|
125
124
|
role: "button",
|
|
126
125
|
tabIndex: 0,
|
|
127
126
|
onClick: t,
|
|
@@ -129,106 +128,106 @@ var S = () => {
|
|
|
129
128
|
(e.key === "Enter" || e.key === " ") && (e.preventDefault(), t());
|
|
130
129
|
},
|
|
131
130
|
className: "cursor-pointer rounded-card border border-border-seam bg-bg-surface p-6 shadow-[var(--shadow-pop)] transition-[box-shadow,border-color] hover:shadow-[var(--shadow-pop-hover)] hover:border-border-strong focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
|
|
132
|
-
children: [/* @__PURE__ */
|
|
131
|
+
children: [/* @__PURE__ */ v("div", {
|
|
133
132
|
className: "flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between",
|
|
134
|
-
children: [/* @__PURE__ */
|
|
133
|
+
children: [/* @__PURE__ */ v("div", {
|
|
135
134
|
className: "flex items-start gap-4",
|
|
136
|
-
children: [e.logoUrl ? /* @__PURE__ */
|
|
135
|
+
children: [e.logoUrl ? /* @__PURE__ */ _("img", {
|
|
137
136
|
src: e.logoUrl,
|
|
138
137
|
alt: e.name,
|
|
139
138
|
className: "h-16 w-16 rounded-lg object-cover"
|
|
140
|
-
}) : /* @__PURE__ */
|
|
139
|
+
}) : /* @__PURE__ */ _("div", {
|
|
141
140
|
className: "flex h-16 w-16 items-center justify-center rounded-lg bg-action-primary-bg/10 text-primary",
|
|
142
|
-
children: /* @__PURE__ */
|
|
143
|
-
}), /* @__PURE__ */
|
|
144
|
-
/* @__PURE__ */
|
|
141
|
+
children: /* @__PURE__ */ _(d, { size: 32 })
|
|
142
|
+
}), /* @__PURE__ */ v("div", { children: [
|
|
143
|
+
/* @__PURE__ */ v("div", {
|
|
145
144
|
className: "flex items-center gap-2 flex-wrap",
|
|
146
145
|
children: [
|
|
147
|
-
/* @__PURE__ */
|
|
146
|
+
/* @__PURE__ */ _("h3", {
|
|
148
147
|
className: "text-lg font-semibold",
|
|
149
148
|
children: e.name
|
|
150
149
|
}),
|
|
151
|
-
e.isVerified && /* @__PURE__ */
|
|
152
|
-
e.isCertified && /* @__PURE__ */
|
|
150
|
+
e.isVerified && /* @__PURE__ */ _(o, { verified: !0 }),
|
|
151
|
+
e.isCertified && /* @__PURE__ */ v("span", {
|
|
153
152
|
className: "inline-flex items-center gap-1 rounded-full bg-status-info-bg-subtle px-2 py-0.5 text-xs font-medium text-status-info-text",
|
|
154
|
-
children: [/* @__PURE__ */
|
|
153
|
+
children: [/* @__PURE__ */ _(f, { size: 12 }), " Certified"]
|
|
155
154
|
})
|
|
156
155
|
]
|
|
157
156
|
}),
|
|
158
|
-
e.description && /* @__PURE__ */
|
|
157
|
+
e.description && /* @__PURE__ */ _("p", {
|
|
159
158
|
className: "mt-1 text-sm text-text-secondary",
|
|
160
159
|
children: e.description
|
|
161
160
|
}),
|
|
162
|
-
/* @__PURE__ */
|
|
161
|
+
/* @__PURE__ */ v("div", {
|
|
163
162
|
className: "mt-2 flex flex-wrap items-center gap-2",
|
|
164
|
-
children: [/* @__PURE__ */
|
|
163
|
+
children: [/* @__PURE__ */ _(a, {
|
|
165
164
|
status: e.tier,
|
|
166
165
|
type: "tier"
|
|
167
|
-
}), e.partnerTier && /* @__PURE__ */
|
|
166
|
+
}), e.partnerTier && /* @__PURE__ */ _(a, {
|
|
168
167
|
status: e.partnerTier,
|
|
169
168
|
type: "partner"
|
|
170
169
|
})]
|
|
171
170
|
})
|
|
172
171
|
] })]
|
|
173
|
-
}), /* @__PURE__ */
|
|
172
|
+
}), /* @__PURE__ */ v("div", {
|
|
174
173
|
className: "flex flex-wrap gap-6 text-sm",
|
|
175
174
|
children: [
|
|
176
|
-
/* @__PURE__ */
|
|
175
|
+
/* @__PURE__ */ v("div", {
|
|
177
176
|
className: "flex items-center gap-2",
|
|
178
|
-
children: [/* @__PURE__ */
|
|
177
|
+
children: [/* @__PURE__ */ _(g, {
|
|
179
178
|
size: 16,
|
|
180
179
|
className: "text-text-secondary"
|
|
181
|
-
}), /* @__PURE__ */
|
|
180
|
+
}), /* @__PURE__ */ v("span", { children: [e.members?.length || 0, " members"] })]
|
|
182
181
|
}),
|
|
183
|
-
/* @__PURE__ */
|
|
182
|
+
/* @__PURE__ */ v("div", {
|
|
184
183
|
className: "flex items-center gap-2",
|
|
185
|
-
children: [/* @__PURE__ */
|
|
184
|
+
children: [/* @__PURE__ */ _(u, {
|
|
186
185
|
size: 16,
|
|
187
186
|
className: "text-text-secondary"
|
|
188
|
-
}), /* @__PURE__ */
|
|
187
|
+
}), /* @__PURE__ */ v("span", { children: [
|
|
189
188
|
e.publishedApps,
|
|
190
189
|
" / ",
|
|
191
190
|
e.totalApps,
|
|
192
191
|
" apps"
|
|
193
192
|
] })]
|
|
194
193
|
}),
|
|
195
|
-
/* @__PURE__ */
|
|
194
|
+
/* @__PURE__ */ v("div", {
|
|
196
195
|
className: "flex items-center gap-2",
|
|
197
|
-
children: [/* @__PURE__ */
|
|
196
|
+
children: [/* @__PURE__ */ _(p, {
|
|
198
197
|
size: 16,
|
|
199
198
|
className: "text-text-secondary"
|
|
200
|
-
}), /* @__PURE__ */
|
|
199
|
+
}), /* @__PURE__ */ v("span", { children: ["$", (e.totalRevenue || 0).toLocaleString()] })]
|
|
201
200
|
})
|
|
202
201
|
]
|
|
203
202
|
})]
|
|
204
|
-
}), /* @__PURE__ */
|
|
203
|
+
}), /* @__PURE__ */ v("div", {
|
|
205
204
|
className: "mt-4 grid grid-cols-2 gap-4 rounded-lg bg-bg-sunken/50 p-4 sm:grid-cols-4",
|
|
206
205
|
children: [
|
|
207
|
-
/* @__PURE__ */
|
|
206
|
+
/* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("p", {
|
|
208
207
|
className: "text-xs text-text-secondary",
|
|
209
208
|
children: "Total Installs"
|
|
210
|
-
}), /* @__PURE__ */
|
|
209
|
+
}), /* @__PURE__ */ _("p", {
|
|
211
210
|
className: "text-lg font-semibold",
|
|
212
211
|
children: (e.totalInstalls || 0).toLocaleString()
|
|
213
212
|
})] }),
|
|
214
|
-
/* @__PURE__ */
|
|
213
|
+
/* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("p", {
|
|
215
214
|
className: "text-xs text-text-secondary",
|
|
216
215
|
children: "Active Installs"
|
|
217
|
-
}), /* @__PURE__ */
|
|
216
|
+
}), /* @__PURE__ */ _("p", {
|
|
218
217
|
className: "text-lg font-semibold",
|
|
219
218
|
children: (e.activeInstalls || 0).toLocaleString()
|
|
220
219
|
})] }),
|
|
221
|
-
/* @__PURE__ */
|
|
220
|
+
/* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("p", {
|
|
222
221
|
className: "text-xs text-text-secondary",
|
|
223
222
|
children: "API Calls"
|
|
224
|
-
}), /* @__PURE__ */
|
|
223
|
+
}), /* @__PURE__ */ _("p", {
|
|
225
224
|
className: "text-lg font-semibold",
|
|
226
225
|
children: e.totalApiCalls ? `${(parseInt(e.totalApiCalls) / 1e6).toFixed(1)}M` : "0"
|
|
227
226
|
})] }),
|
|
228
|
-
/* @__PURE__ */
|
|
227
|
+
/* @__PURE__ */ v("div", { children: [/* @__PURE__ */ _("p", {
|
|
229
228
|
className: "text-xs text-text-secondary",
|
|
230
229
|
children: "Avg Rating"
|
|
231
|
-
}), /* @__PURE__ */
|
|
230
|
+
}), /* @__PURE__ */ _("p", {
|
|
232
231
|
className: "text-lg font-semibold",
|
|
233
232
|
children: e.averageRating ? `${e.averageRating.toFixed(1)} ★` : "N/A"
|
|
234
233
|
})] })
|
|
@@ -236,6 +235,6 @@ var S = () => {
|
|
|
236
235
|
})]
|
|
237
236
|
});
|
|
238
237
|
//#endregion
|
|
239
|
-
export {
|
|
238
|
+
export { x as OrganizationsPage };
|
|
240
239
|
|
|
241
240
|
//# sourceMappingURL=OrganizationsPage.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OrganizationsPage.js","names":[],"sources":["../../../src/devportal/pages/OrganizationsPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n Plus,\n Building2,\n Users,\n AppWindow,\n DollarSign,\n CheckCircle,\n AlertCircle,\n Loader2,\n} from \"lucide-react\";\nimport { EmphasisPanel, GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../components/PageLayout\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { StatusBadge, VerifiedBadge } from \"../components/StatusBadge\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\nimport { DEVPORTAL_PERMISSIONS } from \"../constants/permissions\";\nimport type { DeveloperOrganization } from \"../types\";\nimport {\n useGetMyOrganizationsQuery,\n getErrorMessage,\n type OrganizationFieldsFragment,\n} from \"../hooks\";\n\n/**\n * Organizations listing page\n *\n * Displays all developer organizations the current user is a member of.\n * Uses GraphQL to fetch real data from the DevPortal backend.\n */\nexport const OrganizationsPage: FC = () => {\n const { navigate, basePath } = useDevportal();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n\n // Fetch organizations from GraphQL API\n const { data, loading, error, refetch } = useGetMyOrganizationsQuery({\n fetchPolicy: \"cache-and-network\",\n });\n\n const organizations = data?.myDeveloperOrganizations ?? [];\n\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n // Loading state\n if (loading && !data) {\n return (\n <PageLayout\n title={tr(\n \"devportal.developerOrganizations\",\n \"Developer Organizations\",\n )}\n description={tr(\n \"devportal.manageYourDevTeams\",\n \"Manage your development teams\",\n )}\n >\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 className=\"h-8 w-8 animate-spin text-text-secondary\" />\n <span className=\"ml-2 text-text-secondary\">\n {tr(\"devportal.loadingOrganizations\", \"Loading organizations...\")}\n </span>\n </div>\n </PageLayout>\n );\n }\n\n // Error state\n if (error) {\n return (\n <PageLayout\n title={tr(\n \"devportal.developerOrganizations\",\n \"Developer Organizations\",\n )}\n description={tr(\n \"devportal.manageYourDevTeams\",\n \"Manage your development teams\",\n )}\n >\n <div className=\"rounded-lg border border-status-error-border/50 bg-status-error-bg/10 p-6 text-center\">\n <AlertCircle className=\"mx-auto h-8 w-8 text-status-error-text\" />\n <h3 className=\"mt-2 font-medium text-status-error-text\">\n {tr(\n \"devportal.failedToLoadOrganizations\",\n \"Failed to load organizations\",\n )}\n </h3>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {getErrorMessage(error)}\n </p>\n <button\n onClick={() => refetch()}\n className=\"mt-4 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n {tr(\"devportal.tryAgain\", \"Try Again\")}\n </button>\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PermissionGuard permission={DEVPORTAL_PERMISSIONS.ORG_READ}>\n <PageLayout\n title={tr(\n \"devportal.developerOrganizations\",\n \"Developer Organizations\",\n )}\n description={tr(\n \"devportal.manageYourDevTeams\",\n \"Manage your development teams\",\n )}\n purpose={tr(\n \"devportal.organizationsPurpose\",\n \"An organization is the team that owns your apps, members, and revenue. Create one so collaborators can share access to the same applications, billing, and store presence instead of working in isolation.\",\n )}\n actions={\n <button\n onClick={() => handleNavigate(\"/organizations/new\")}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 transition-colors\"\n >\n <Plus size={16} />\n {tr(\"devportal.createOrganization\", \"Create Organization\")}\n </button>\n }\n >\n {organizations.length > 0 && (\n <EmphasisPanel className=\"mb-6\">\n <div className=\"grid grid-cols-2 gap-4 sm:grid-cols-3\">\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\"devportal.organizations\", \"Organizations\")}\n </p>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {organizations.length}\n </p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\"devportal.totalApps\", \"Total Apps\")}\n </p>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {organizations.reduce(\n (acc: number, org: OrganizationFieldsFragment) =>\n acc + (org.totalApps ?? 0),\n 0,\n )}\n </p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\"devportal.activeInstalls\", \"Active Installs\")}\n </p>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {organizations\n .reduce(\n (acc: number, org: OrganizationFieldsFragment) =>\n acc + (org.activeInstalls ?? 0),\n 0,\n )\n .toLocaleString()}\n </p>\n </GlassCard>\n </div>\n </EmphasisPanel>\n )}\n {organizations.length > 0 ? (\n <div className=\"grid gap-6\">\n {organizations.map((org: OrganizationFieldsFragment) => (\n <OrganizationCard\n key={org.id}\n org={org as DeveloperOrganization}\n onClick={() => handleNavigate(`/organizations/${org.id}`)}\n />\n ))}\n </div>\n ) : (\n <EmptyState\n illustration=\"empty-people\"\n title={tr(\"devportal.noOrganizationsYet\", \"No organizations yet\")}\n description={tr(\n \"devportal.createOrgToCollaborate\",\n \"Create an organization to collaborate with your team and manage apps together\",\n )}\n action={\n <button\n onClick={() => handleNavigate(\"/organizations/new\")}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus size={16} />\n {tr(\"devportal.createOrganization\", \"Create Organization\")}\n </button>\n }\n />\n )}\n </PageLayout>\n </PermissionGuard>\n );\n};\n\n// Organization card component\nconst OrganizationCard: FC<{\n org: DeveloperOrganization;\n onClick: () => void;\n}> = ({ org, onClick }) => {\n return (\n <div\n role=\"button\"\n tabIndex={0}\n onClick={onClick}\n onKeyDown={(event) => {\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n onClick();\n }\n }}\n className=\"cursor-pointer rounded-card border border-border-seam bg-bg-surface p-6 shadow-[var(--shadow-pop)] transition-[box-shadow,border-color] hover:shadow-[var(--shadow-pop-hover)] hover:border-border-strong focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\"\n >\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between\">\n {/* Left side - Info */}\n <div className=\"flex items-start gap-4\">\n {org.logoUrl ? (\n <img\n src={org.logoUrl}\n alt={org.name}\n className=\"h-16 w-16 rounded-lg object-cover\"\n />\n ) : (\n <div className=\"flex h-16 w-16 items-center justify-center rounded-lg bg-action-primary-bg/10 text-primary\">\n <Building2 size={32} />\n </div>\n )}\n <div>\n <div className=\"flex items-center gap-2 flex-wrap\">\n <h3 className=\"text-lg font-semibold\">{org.name}</h3>\n {org.isVerified && <VerifiedBadge verified />}\n {org.isCertified && (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-status-info-bg-subtle px-2 py-0.5 text-xs font-medium text-status-info-text\">\n <CheckCircle size={12} /> Certified\n </span>\n )}\n </div>\n {org.description && (\n <p className=\"mt-1 text-sm text-text-secondary\">\n {org.description}\n </p>\n )}\n <div className=\"mt-2 flex flex-wrap items-center gap-2\">\n <StatusBadge status={org.tier} type=\"tier\" />\n {org.partnerTier && (\n <StatusBadge status={org.partnerTier} type=\"partner\" />\n )}\n </div>\n </div>\n </div>\n\n {/* Right side - Stats */}\n <div className=\"flex flex-wrap gap-6 text-sm\">\n <div className=\"flex items-center gap-2\">\n <Users size={16} className=\"text-text-secondary\" />\n <span>{org.members?.length || 0} members</span>\n </div>\n <div className=\"flex items-center gap-2\">\n <AppWindow size={16} className=\"text-text-secondary\" />\n <span>\n {org.publishedApps} / {org.totalApps} apps\n </span>\n </div>\n <div className=\"flex items-center gap-2\">\n <DollarSign size={16} className=\"text-text-secondary\" />\n <span>${(org.totalRevenue || 0).toLocaleString()}</span>\n </div>\n </div>\n </div>\n\n {/* Stats bar */}\n <div className=\"mt-4 grid grid-cols-2 gap-4 rounded-lg bg-bg-sunken/50 p-4 sm:grid-cols-4\">\n <div>\n <p className=\"text-xs text-text-secondary\">Total Installs</p>\n <p className=\"text-lg font-semibold\">\n {(org.totalInstalls || 0).toLocaleString()}\n </p>\n </div>\n <div>\n <p className=\"text-xs text-text-secondary\">Active Installs</p>\n <p className=\"text-lg font-semibold\">\n {(org.activeInstalls || 0).toLocaleString()}\n </p>\n </div>\n <div>\n <p className=\"text-xs text-text-secondary\">API Calls</p>\n <p className=\"text-lg font-semibold\">\n {org.totalApiCalls\n ? `${(parseInt(org.totalApiCalls) / 1000000).toFixed(1)}M`\n : \"0\"}\n </p>\n </div>\n <div>\n <p className=\"text-xs text-text-secondary\">Avg Rating</p>\n <p className=\"text-lg font-semibold\">\n {org.averageRating ? `${org.averageRating.toFixed(1)} ★` : \"N/A\"}\n </p>\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"OrganizationsPage.js","names":[],"sources":["../../../src/devportal/pages/OrganizationsPage.tsx"],"sourcesContent":["import type { FC } from \"react\";\nimport { useI18n } from \"@burdenoff/fe-libs/shared/providers/shell/I18nProvider\";\nimport {\n Plus,\n Building2,\n Users,\n AppWindow,\n DollarSign,\n CheckCircle,\n AlertCircle,\n Loader2,\n} from \"lucide-react\";\nimport { EmphasisPanel, GlassCard } from \"@burdenoff/fe-libs/ui\";\nimport { PageLayout } from \"../components/PageLayout\";\nimport { EmptyState } from \"../components/EmptyState\";\nimport { StatusBadge, VerifiedBadge } from \"../components/StatusBadge\";\nimport { useDevportal } from \"../providers/DevportalProvider\";\nimport { PermissionGuard } from \"../components\";\nimport type { DeveloperOrganization } from \"../types\";\nimport {\n useGetMyOrganizationsQuery,\n getErrorMessage,\n type OrganizationFieldsFragment,\n} from \"../hooks\";\n\n/**\n * Organizations listing page\n *\n * Displays all developer organizations the current user is a member of.\n * Uses GraphQL to fetch real data from the DevPortal backend.\n */\nexport const OrganizationsPage: FC = () => {\n const { navigate, basePath } = useDevportal();\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const value = t(key);\n return value === key ? fallback : value;\n };\n\n // Fetch organizations from GraphQL API\n const { data, loading, error, refetch } = useGetMyOrganizationsQuery({\n fetchPolicy: \"cache-and-network\",\n });\n\n const organizations = data?.myDeveloperOrganizations ?? [];\n\n const handleNavigate = (path: string) => {\n navigate?.(`${basePath}${path}`);\n };\n\n // Loading state\n if (loading && !data) {\n return (\n <PageLayout\n title={tr(\n \"devportal.developerOrganizations\",\n \"Developer Organizations\",\n )}\n description={tr(\n \"devportal.manageYourDevTeams\",\n \"Manage your development teams\",\n )}\n >\n <div className=\"flex items-center justify-center py-12\">\n <Loader2 className=\"h-8 w-8 animate-spin text-text-secondary\" />\n <span className=\"ml-2 text-text-secondary\">\n {tr(\"devportal.loadingOrganizations\", \"Loading organizations...\")}\n </span>\n </div>\n </PageLayout>\n );\n }\n\n // Error state\n if (error) {\n return (\n <PageLayout\n title={tr(\n \"devportal.developerOrganizations\",\n \"Developer Organizations\",\n )}\n description={tr(\n \"devportal.manageYourDevTeams\",\n \"Manage your development teams\",\n )}\n >\n <div className=\"rounded-lg border border-status-error-border/50 bg-status-error-bg/10 p-6 text-center\">\n <AlertCircle className=\"mx-auto h-8 w-8 text-status-error-text\" />\n <h3 className=\"mt-2 font-medium text-status-error-text\">\n {tr(\n \"devportal.failedToLoadOrganizations\",\n \"Failed to load organizations\",\n )}\n </h3>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {getErrorMessage(error)}\n </p>\n <button\n onClick={() => refetch()}\n className=\"mt-4 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n {tr(\"devportal.tryAgain\", \"Try Again\")}\n </button>\n </div>\n </PageLayout>\n );\n }\n\n return (\n <PermissionGuard authOnly>\n <PageLayout\n title={tr(\n \"devportal.developerOrganizations\",\n \"Developer Organizations\",\n )}\n description={tr(\n \"devportal.manageYourDevTeams\",\n \"Manage your development teams\",\n )}\n purpose={tr(\n \"devportal.organizationsPurpose\",\n \"An organization is the team that owns your apps, members, and revenue. Create one so collaborators can share access to the same applications, billing, and store presence instead of working in isolation.\",\n )}\n actions={\n <button\n onClick={() => handleNavigate(\"/organizations/new\")}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90 transition-colors\"\n >\n <Plus size={16} />\n {tr(\"devportal.createOrganization\", \"Create Organization\")}\n </button>\n }\n >\n {organizations.length > 0 && (\n <EmphasisPanel className=\"mb-6\">\n <div className=\"grid grid-cols-2 gap-4 sm:grid-cols-3\">\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\"devportal.organizations\", \"Organizations\")}\n </p>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {organizations.length}\n </p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\"devportal.totalApps\", \"Total Apps\")}\n </p>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {organizations.reduce(\n (acc: number, org: OrganizationFieldsFragment) =>\n acc + (org.totalApps ?? 0),\n 0,\n )}\n </p>\n </GlassCard>\n <GlassCard treatment=\"metric\" className=\"p-4\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\"devportal.activeInstalls\", \"Active Installs\")}\n </p>\n <p className=\"mt-1 text-2xl font-semibold tabular-nums\">\n {organizations\n .reduce(\n (acc: number, org: OrganizationFieldsFragment) =>\n acc + (org.activeInstalls ?? 0),\n 0,\n )\n .toLocaleString()}\n </p>\n </GlassCard>\n </div>\n </EmphasisPanel>\n )}\n {organizations.length > 0 ? (\n <div className=\"grid gap-6\">\n {organizations.map((org: OrganizationFieldsFragment) => (\n <OrganizationCard\n key={org.id}\n org={org as DeveloperOrganization}\n onClick={() => handleNavigate(`/organizations/${org.id}`)}\n />\n ))}\n </div>\n ) : (\n <EmptyState\n illustration=\"empty-people\"\n title={tr(\"devportal.noOrganizationsYet\", \"No organizations yet\")}\n description={tr(\n \"devportal.createOrgToCollaborate\",\n \"Create an organization to collaborate with your team and manage apps together\",\n )}\n action={\n <button\n onClick={() => handleNavigate(\"/organizations/new\")}\n className=\"inline-flex items-center gap-2 rounded-md bg-action-primary-bg px-4 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg/90\"\n >\n <Plus size={16} />\n {tr(\"devportal.createOrganization\", \"Create Organization\")}\n </button>\n }\n />\n )}\n </PageLayout>\n </PermissionGuard>\n );\n};\n\n// Organization card component\nconst OrganizationCard: FC<{\n org: DeveloperOrganization;\n onClick: () => void;\n}> = ({ org, onClick }) => {\n return (\n <div\n role=\"button\"\n tabIndex={0}\n onClick={onClick}\n onKeyDown={(event) => {\n if (event.key === \"Enter\" || event.key === \" \") {\n event.preventDefault();\n onClick();\n }\n }}\n className=\"cursor-pointer rounded-card border border-border-seam bg-bg-surface p-6 shadow-[var(--shadow-pop)] transition-[box-shadow,border-color] hover:shadow-[var(--shadow-pop-hover)] hover:border-border-strong focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2\"\n >\n <div className=\"flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between\">\n {/* Left side - Info */}\n <div className=\"flex items-start gap-4\">\n {org.logoUrl ? (\n <img\n src={org.logoUrl}\n alt={org.name}\n className=\"h-16 w-16 rounded-lg object-cover\"\n />\n ) : (\n <div className=\"flex h-16 w-16 items-center justify-center rounded-lg bg-action-primary-bg/10 text-primary\">\n <Building2 size={32} />\n </div>\n )}\n <div>\n <div className=\"flex items-center gap-2 flex-wrap\">\n <h3 className=\"text-lg font-semibold\">{org.name}</h3>\n {org.isVerified && <VerifiedBadge verified />}\n {org.isCertified && (\n <span className=\"inline-flex items-center gap-1 rounded-full bg-status-info-bg-subtle px-2 py-0.5 text-xs font-medium text-status-info-text\">\n <CheckCircle size={12} /> Certified\n </span>\n )}\n </div>\n {org.description && (\n <p className=\"mt-1 text-sm text-text-secondary\">\n {org.description}\n </p>\n )}\n <div className=\"mt-2 flex flex-wrap items-center gap-2\">\n <StatusBadge status={org.tier} type=\"tier\" />\n {org.partnerTier && (\n <StatusBadge status={org.partnerTier} type=\"partner\" />\n )}\n </div>\n </div>\n </div>\n\n {/* Right side - Stats */}\n <div className=\"flex flex-wrap gap-6 text-sm\">\n <div className=\"flex items-center gap-2\">\n <Users size={16} className=\"text-text-secondary\" />\n <span>{org.members?.length || 0} members</span>\n </div>\n <div className=\"flex items-center gap-2\">\n <AppWindow size={16} className=\"text-text-secondary\" />\n <span>\n {org.publishedApps} / {org.totalApps} apps\n </span>\n </div>\n <div className=\"flex items-center gap-2\">\n <DollarSign size={16} className=\"text-text-secondary\" />\n <span>${(org.totalRevenue || 0).toLocaleString()}</span>\n </div>\n </div>\n </div>\n\n {/* Stats bar */}\n <div className=\"mt-4 grid grid-cols-2 gap-4 rounded-lg bg-bg-sunken/50 p-4 sm:grid-cols-4\">\n <div>\n <p className=\"text-xs text-text-secondary\">Total Installs</p>\n <p className=\"text-lg font-semibold\">\n {(org.totalInstalls || 0).toLocaleString()}\n </p>\n </div>\n <div>\n <p className=\"text-xs text-text-secondary\">Active Installs</p>\n <p className=\"text-lg font-semibold\">\n {(org.activeInstalls || 0).toLocaleString()}\n </p>\n </div>\n <div>\n <p className=\"text-xs text-text-secondary\">API Calls</p>\n <p className=\"text-lg font-semibold\">\n {org.totalApiCalls\n ? `${(parseInt(org.totalApiCalls) / 1000000).toFixed(1)}M`\n : \"0\"}\n </p>\n </div>\n <div>\n <p className=\"text-xs text-text-secondary\">Avg Rating</p>\n <p className=\"text-lg font-semibold\">\n {org.averageRating ? `${org.averageRating.toFixed(1)} ★` : \"N/A\"}\n </p>\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;AA+BA,IAAa,UAA8B;CACzC,IAAM,EAAE,aAAU,gBAAa,GAAc,EACvC,EAAE,SAAM,GAAS,EACjB,KAAM,GAAa,MAA6B;EACpD,IAAM,IAAQ,EAAE,EAAI;AACpB,SAAO,MAAU,IAAM,IAAW;IAI9B,EAAE,SAAM,YAAS,UAAO,eAAY,EAA2B,EACnE,aAAa,qBACd,CAAC,EAEI,IAAgB,GAAM,4BAA4B,EAAE,EAEpD,KAAkB,MAAiB;AACvC,MAAW,GAAG,IAAW,IAAO;;AA6DlC,QAzDI,KAAW,CAAC,IAEZ,kBAAC,GAAD;EACE,OAAO,EACL,oCACA,0BACD;EACD,aAAa,EACX,gCACA,gCACD;YAED,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD,EAAS,WAAU,4CAA6C,CAAA,EAChE,kBAAC,QAAD;IAAM,WAAU;cACb,EAAG,kCAAkC,2BAA2B;IAC5D,CAAA,CACH;;EACK,CAAA,GAKb,IAEA,kBAAC,GAAD;EACE,OAAO,EACL,oCACA,0BACD;EACD,aAAa,EACX,gCACA,gCACD;YAED,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,GAAD,EAAa,WAAU,0CAA2C,CAAA;IAClE,kBAAC,MAAD;KAAI,WAAU;eACX,EACC,uCACA,+BACD;KACE,CAAA;IACL,kBAAC,KAAD;KAAG,WAAU;eACV,EAAgB,EAAM;KACrB,CAAA;IACJ,kBAAC,UAAD;KACE,eAAe,GAAS;KACxB,WAAU;eAET,EAAG,sBAAsB,YAAY;KAC/B,CAAA;IACL;;EACK,CAAA,GAKf,kBAAC,GAAD;EAAiB,UAAA;YACf,kBAAC,GAAD;GACE,OAAO,EACL,oCACA,0BACD;GACD,aAAa,EACX,gCACA,gCACD;GACD,SAAS,EACP,kCACA,6MACD;GACD,SACE,kBAAC,UAAD;IACE,eAAe,EAAe,qBAAqB;IACnD,WAAU;cAFZ,CAIE,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA,EACjB,EAAG,gCAAgC,sBAAsB,CACnD;;aApBb,CAuBG,EAAc,SAAS,KACtB,kBAAC,GAAD;IAAe,WAAU;cACvB,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,GAAD;OAAW,WAAU;OAAS,WAAU;iBAAxC,CACE,kBAAC,KAAD;QAAG,WAAU;kBACV,EAAG,2BAA2B,gBAAgB;QAC7C,CAAA,EACJ,kBAAC,KAAD;QAAG,WAAU;kBACV,EAAc;QACb,CAAA,CACM;;MACZ,kBAAC,GAAD;OAAW,WAAU;OAAS,WAAU;iBAAxC,CACE,kBAAC,KAAD;QAAG,WAAU;kBACV,EAAG,uBAAuB,aAAa;QACtC,CAAA,EACJ,kBAAC,KAAD;QAAG,WAAU;kBACV,EAAc,QACZ,GAAa,MACZ,KAAO,EAAI,aAAa,IAC1B,EACD;QACC,CAAA,CACM;;MACZ,kBAAC,GAAD;OAAW,WAAU;OAAS,WAAU;iBAAxC,CACE,kBAAC,KAAD;QAAG,WAAU;kBACV,EAAG,4BAA4B,kBAAkB;QAChD,CAAA,EACJ,kBAAC,KAAD;QAAG,WAAU;kBACV,EACE,QACE,GAAa,MACZ,KAAO,EAAI,kBAAkB,IAC/B,EACD,CACA,gBAAgB;QACjB,CAAA,CACM;;MACR;;IACQ,CAAA,EAEjB,EAAc,SAAS,IACtB,kBAAC,OAAD;IAAK,WAAU;cACZ,EAAc,KAAK,MAClB,kBAAC,GAAD;KAEO;KACL,eAAe,EAAe,kBAAkB,EAAI,KAAK;KACzD,EAHK,EAAI,GAGT,CACF;IACE,CAAA,GAEN,kBAAC,GAAD;IACE,cAAa;IACb,OAAO,EAAG,gCAAgC,uBAAuB;IACjE,aAAa,EACX,oCACA,gFACD;IACD,QACE,kBAAC,UAAD;KACE,eAAe,EAAe,qBAAqB;KACnD,WAAU;eAFZ,CAIE,kBAAC,GAAD,EAAM,MAAM,IAAM,CAAA,EACjB,EAAG,gCAAgC,sBAAsB,CACnD;;IAEX,CAAA,CAEO;;EACG,CAAA;GAKhB,KAGA,EAAE,QAAK,iBAET,kBAAC,OAAD;CACE,MAAK;CACL,UAAU;CACD;CACT,YAAY,MAAU;AACpB,GAAI,EAAM,QAAQ,WAAW,EAAM,QAAQ,SACzC,EAAM,gBAAgB,EACtB,GAAS;;CAGb,WAAU;WAVZ,CAYE,kBAAC,OAAD;EAAK,WAAU;YAAf,CAEE,kBAAC,OAAD;GAAK,WAAU;aAAf,CACG,EAAI,UACH,kBAAC,OAAD;IACE,KAAK,EAAI;IACT,KAAK,EAAI;IACT,WAAU;IACV,CAAA,GAEF,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD,EAAW,MAAM,IAAM,CAAA;IACnB,CAAA,EAER,kBAAC,OAAD,EAAA,UAAA;IACE,kBAAC,OAAD;KAAK,WAAU;eAAf;MACE,kBAAC,MAAD;OAAI,WAAU;iBAAyB,EAAI;OAAU,CAAA;MACpD,EAAI,cAAc,kBAAC,GAAD,EAAe,UAAA,IAAW,CAAA;MAC5C,EAAI,eACH,kBAAC,QAAD;OAAM,WAAU;iBAAhB,CACE,kBAAC,GAAD,EAAa,MAAM,IAAM,CAAA,EAAA,aACpB;;MAEL;;IACL,EAAI,eACH,kBAAC,KAAD;KAAG,WAAU;eACV,EAAI;KACH,CAAA;IAEN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD;MAAa,QAAQ,EAAI;MAAM,MAAK;MAAS,CAAA,EAC5C,EAAI,eACH,kBAAC,GAAD;MAAa,QAAQ,EAAI;MAAa,MAAK;MAAY,CAAA,CAErD;;IACF,EAAA,CAAA,CACF;MAGN,kBAAC,OAAD;GAAK,WAAU;aAAf;IACE,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD;MAAO,MAAM;MAAI,WAAU;MAAwB,CAAA,EACnD,kBAAC,QAAD,EAAA,UAAA,CAAO,EAAI,SAAS,UAAU,GAAE,WAAe,EAAA,CAAA,CAC3C;;IACN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD;MAAW,MAAM;MAAI,WAAU;MAAwB,CAAA,EACvD,kBAAC,QAAD,EAAA,UAAA;MACG,EAAI;MAAc;MAAI,EAAI;MAAU;MAChC,EAAA,CAAA,CACH;;IACN,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD;MAAY,MAAM;MAAI,WAAU;MAAwB,CAAA,EACxD,kBAAC,QAAD,EAAA,UAAA,CAAM,MAAG,EAAI,gBAAgB,GAAG,gBAAgB,CAAQ,EAAA,CAAA,CACpD;;IACF;KACF;KAGN,kBAAC,OAAD;EAAK,WAAU;YAAf;GACE,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;IAAG,WAAU;cAA8B;IAAkB,CAAA,EAC7D,kBAAC,KAAD;IAAG,WAAU;eACT,EAAI,iBAAiB,GAAG,gBAAgB;IACxC,CAAA,CACA,EAAA,CAAA;GACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;IAAG,WAAU;cAA8B;IAAmB,CAAA,EAC9D,kBAAC,KAAD;IAAG,WAAU;eACT,EAAI,kBAAkB,GAAG,gBAAgB;IACzC,CAAA,CACA,EAAA,CAAA;GACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;IAAG,WAAU;cAA8B;IAAa,CAAA,EACxD,kBAAC,KAAD;IAAG,WAAU;cACV,EAAI,gBACD,IAAI,SAAS,EAAI,cAAc,GAAG,KAAS,QAAQ,EAAE,CAAC,KACtD;IACF,CAAA,CACA,EAAA,CAAA;GACN,kBAAC,OAAD,EAAA,UAAA,CACE,kBAAC,KAAD;IAAG,WAAU;cAA8B;IAAc,CAAA,EACzD,kBAAC,KAAD;IAAG,WAAU;cACV,EAAI,gBAAgB,GAAG,EAAI,cAAc,QAAQ,EAAE,CAAC,MAAM;IACzD,CAAA,CACA,EAAA,CAAA;GACF;IACF"}
|