@petrarca/sonnet-playground 0.6.0 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -101
- package/dist/PlayAuth-SOEYPOOL.js +8 -0
- package/dist/PlayForms-L532W3JL.js +7 -0
- package/dist/PlayGraph-M4KQSFI7.js +7 -0
- package/dist/PlaySchemaDesigner-S4DBB53G.js +7 -0
- package/dist/PlaySchemaDesigner-S4DBB53G.js.map +1 -0
- package/dist/{chunk-6VQJFBTM.js → chunk-2XMWOZ5O.js} +1 -1
- package/dist/chunk-2XMWOZ5O.js.map +1 -0
- package/dist/{chunk-C2P3UQMZ.js → chunk-E5X7GVLU.js} +2 -2
- package/dist/chunk-E5X7GVLU.js.map +1 -0
- package/dist/{chunk-ZSUCFEB6.js → chunk-I2FLG6ZD.js} +2 -1
- package/dist/{chunk-ZSUCFEB6.js.map → chunk-I2FLG6ZD.js.map} +1 -1
- package/dist/chunk-RYCRENYO.js +401 -0
- package/dist/chunk-RYCRENYO.js.map +1 -0
- package/dist/index.d.ts +20 -10
- package/dist/index.js +50 -5
- package/dist/index.js.map +1 -1
- package/dist/pages/index.d.ts +3 -1
- package/dist/pages/index.js +7 -3
- package/package.json +7 -7
- package/dist/PlayForms-2G7PROCH.js +0 -7
- package/dist/PlayGraph-M7UE5MZC.js +0 -7
- package/dist/PlaySchemaDesigner-ZM4F7KBU.js +0 -7
- package/dist/chunk-6VQJFBTM.js.map +0 -1
- package/dist/chunk-C2P3UQMZ.js.map +0 -1
- /package/dist/{PlayForms-2G7PROCH.js.map → PlayAuth-SOEYPOOL.js.map} +0 -0
- /package/dist/{PlayGraph-M7UE5MZC.js.map → PlayForms-L532W3JL.js.map} +0 -0
- /package/dist/{PlaySchemaDesigner-ZM4F7KBU.js.map → PlayGraph-M4KQSFI7.js.map} +0 -0
|
@@ -0,0 +1,401 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PlaygroundSection
|
|
3
|
+
} from "./chunk-C2EZ2ECG.js";
|
|
4
|
+
|
|
5
|
+
// src/pages/PlayAuth.tsx
|
|
6
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
7
|
+
import { createPortal } from "react-dom";
|
|
8
|
+
import { useLocation, useNavigate } from "react-router-dom";
|
|
9
|
+
import {
|
|
10
|
+
Button,
|
|
11
|
+
Checkbox,
|
|
12
|
+
Input,
|
|
13
|
+
Label,
|
|
14
|
+
Select,
|
|
15
|
+
SelectContent,
|
|
16
|
+
SelectItem,
|
|
17
|
+
SelectTrigger,
|
|
18
|
+
SelectValue
|
|
19
|
+
} from "@petrarca/sonnet-ui";
|
|
20
|
+
import {
|
|
21
|
+
Login
|
|
22
|
+
} from "@petrarca/sonnet-shell/auth";
|
|
23
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
24
|
+
var SESSION_LABELS = {
|
|
25
|
+
anonymous: "Not authenticated -- login form",
|
|
26
|
+
"orgs-null": "Authenticated, orgs = null (not loaded)",
|
|
27
|
+
"orgs-empty": "Authenticated, orgs = [] (no memberships)",
|
|
28
|
+
"orgs-one": "Authenticated, one org (auto-selects)",
|
|
29
|
+
"orgs-many": "Authenticated, several orgs (picker)"
|
|
30
|
+
};
|
|
31
|
+
var ENTRY_LABELS = {
|
|
32
|
+
direct: "Direct visit -- no location state",
|
|
33
|
+
"tenant-selection": 'Redirected with step="tenant-selection"',
|
|
34
|
+
"org-selection": 'Redirected with step="org-selection"'
|
|
35
|
+
};
|
|
36
|
+
var MANY_ORGS = [
|
|
37
|
+
{ id: "org-north", name: "Northwind Trading" },
|
|
38
|
+
{ id: "org-acme", name: "Acme Industrial" },
|
|
39
|
+
{ id: "org-globex", name: "Globex Research" }
|
|
40
|
+
];
|
|
41
|
+
var ONE_ORG = [{ id: "org-north", name: "Northwind Trading" }];
|
|
42
|
+
function PlayAuth() {
|
|
43
|
+
const [flow, setFlow] = useState("identifier-first");
|
|
44
|
+
const [providers, setProviders] = useState("both");
|
|
45
|
+
const [session, setSession] = useState("anonymous");
|
|
46
|
+
const [error, setError] = useState("");
|
|
47
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
48
|
+
const [isDiscovering, setIsDiscovering] = useState(false);
|
|
49
|
+
const [appName, setAppName] = useState("Example");
|
|
50
|
+
const [entry, setEntry] = useState("direct");
|
|
51
|
+
const [requiresOrg, setRequiresOrg] = useState(true);
|
|
52
|
+
const [pendingLaunch, setPendingLaunch] = useState(false);
|
|
53
|
+
const [open, setOpen] = useState(false);
|
|
54
|
+
const [events, setEvents] = useState([]);
|
|
55
|
+
const nextId = useRef(0);
|
|
56
|
+
const log = useCallback((message) => {
|
|
57
|
+
setEvents(
|
|
58
|
+
(prev) => [
|
|
59
|
+
{
|
|
60
|
+
id: nextId.current++,
|
|
61
|
+
time: (/* @__PURE__ */ new Date()).toLocaleTimeString(),
|
|
62
|
+
message
|
|
63
|
+
},
|
|
64
|
+
...prev
|
|
65
|
+
].slice(0, 40)
|
|
66
|
+
);
|
|
67
|
+
}, []);
|
|
68
|
+
useEffect(() => {
|
|
69
|
+
if (!open) return;
|
|
70
|
+
const onKey = (e) => {
|
|
71
|
+
if (e.key === "Escape") setOpen(false);
|
|
72
|
+
};
|
|
73
|
+
window.addEventListener("keydown", onKey);
|
|
74
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
75
|
+
}, [open]);
|
|
76
|
+
const discovered = useMemo(() => {
|
|
77
|
+
const local = {
|
|
78
|
+
type: "local",
|
|
79
|
+
name: "local",
|
|
80
|
+
label: "Password"
|
|
81
|
+
};
|
|
82
|
+
const external = {
|
|
83
|
+
type: "external",
|
|
84
|
+
name: "acme-sso",
|
|
85
|
+
label: "Continue with Acme SSO",
|
|
86
|
+
// The app attaches whatever its onExternalLogin needs; the shell only
|
|
87
|
+
// carries it through. No `as any` on the provider.
|
|
88
|
+
metadata: {
|
|
89
|
+
loginUrl: "https://example.invalid/authorize",
|
|
90
|
+
targetId: "acme"
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
if (providers === "local") return [local];
|
|
94
|
+
if (providers === "external") return [external];
|
|
95
|
+
if (providers === "none") return [];
|
|
96
|
+
return [local, external];
|
|
97
|
+
}, [providers]);
|
|
98
|
+
const config = useMemo(() => {
|
|
99
|
+
const oauthOn = providers === "external" || providers === "both";
|
|
100
|
+
return {
|
|
101
|
+
enabled: true,
|
|
102
|
+
features: {
|
|
103
|
+
local_auth: providers === "local" || providers === "both",
|
|
104
|
+
oauth: oauthOn
|
|
105
|
+
},
|
|
106
|
+
oauth: oauthOn ? {
|
|
107
|
+
providers: {
|
|
108
|
+
"acme-sso": {
|
|
109
|
+
enabled: true,
|
|
110
|
+
name: "Acme SSO",
|
|
111
|
+
login_url: "https://example.invalid/authorize"
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
} : null,
|
|
115
|
+
// Never exposed as a control: Login acts on this with
|
|
116
|
+
// window.location.href, which would leave the playground.
|
|
117
|
+
ui: { direct_redirect: null }
|
|
118
|
+
};
|
|
119
|
+
}, [providers]);
|
|
120
|
+
const orgs = useMemo(() => {
|
|
121
|
+
if (session === "orgs-empty") return [];
|
|
122
|
+
if (session === "orgs-one") return ONE_ORG;
|
|
123
|
+
if (session === "orgs-many") return MANY_ORGS;
|
|
124
|
+
return null;
|
|
125
|
+
}, [session]);
|
|
126
|
+
const isAuthenticated = session !== "anonymous";
|
|
127
|
+
const onLogin = useCallback(
|
|
128
|
+
async (credentials) => {
|
|
129
|
+
log(
|
|
130
|
+
`onLogin({ identifier: "${credentials.identifier}", password: "${"*".repeat(credentials.password.length)}" })`
|
|
131
|
+
);
|
|
132
|
+
},
|
|
133
|
+
[log]
|
|
134
|
+
);
|
|
135
|
+
const onFetchCurrentUser = useCallback(async () => {
|
|
136
|
+
log("onFetchCurrentUser()");
|
|
137
|
+
}, [log]);
|
|
138
|
+
const onSelectOrg = useCallback(
|
|
139
|
+
async (id) => {
|
|
140
|
+
log(`onSelectOrg("${id}")`);
|
|
141
|
+
},
|
|
142
|
+
[log]
|
|
143
|
+
);
|
|
144
|
+
const onClearError = useCallback(() => {
|
|
145
|
+
log("onClearError()");
|
|
146
|
+
}, [log]);
|
|
147
|
+
const onDiscover = useCallback(
|
|
148
|
+
async (identifier) => {
|
|
149
|
+
log(`onDiscover("${identifier}") -> ${discovered.length} provider(s)`);
|
|
150
|
+
return discovered;
|
|
151
|
+
},
|
|
152
|
+
[log, discovered]
|
|
153
|
+
);
|
|
154
|
+
const onExternalLogin = useCallback(
|
|
155
|
+
(provider) => {
|
|
156
|
+
const loginUrl = provider.metadata?.loginUrl;
|
|
157
|
+
log(
|
|
158
|
+
`onExternalLogin("${provider.name}", loginUrl=${loginUrl ?? "none"}) -- a real app redirects to the IdP here, so the screen stays on "Redirecting..."`
|
|
159
|
+
);
|
|
160
|
+
},
|
|
161
|
+
[log]
|
|
162
|
+
);
|
|
163
|
+
const orgEmptyAction = useMemo(
|
|
164
|
+
() => /* @__PURE__ */ jsx(
|
|
165
|
+
Button,
|
|
166
|
+
{
|
|
167
|
+
variant: "outline",
|
|
168
|
+
className: "w-full",
|
|
169
|
+
onClick: () => log("orgEmptyAction: Sign out clicked"),
|
|
170
|
+
children: "Sign out"
|
|
171
|
+
}
|
|
172
|
+
),
|
|
173
|
+
[log]
|
|
174
|
+
);
|
|
175
|
+
const navigate = useNavigate();
|
|
176
|
+
const location = useLocation();
|
|
177
|
+
const launch = useCallback(() => {
|
|
178
|
+
log(
|
|
179
|
+
`launched: flow=${flow}, providers=${providers}, session=${session}, entry=${entry}, requiresOrgSelection=${requiresOrg}`
|
|
180
|
+
);
|
|
181
|
+
navigate(location.pathname, {
|
|
182
|
+
replace: true,
|
|
183
|
+
state: entry === "direct" ? null : { step: entry, from: { pathname: "/dashboard" } }
|
|
184
|
+
});
|
|
185
|
+
setPendingLaunch(true);
|
|
186
|
+
}, [
|
|
187
|
+
log,
|
|
188
|
+
flow,
|
|
189
|
+
providers,
|
|
190
|
+
session,
|
|
191
|
+
entry,
|
|
192
|
+
requiresOrg,
|
|
193
|
+
navigate,
|
|
194
|
+
location.pathname
|
|
195
|
+
]);
|
|
196
|
+
useEffect(() => {
|
|
197
|
+
if (!pendingLaunch) return;
|
|
198
|
+
const state = location.state;
|
|
199
|
+
const wanted = entry === "direct" ? void 0 : entry;
|
|
200
|
+
if ((state?.step ?? void 0) !== wanted) return;
|
|
201
|
+
setPendingLaunch(false);
|
|
202
|
+
setOpen(true);
|
|
203
|
+
}, [pendingLaunch, location.state, entry]);
|
|
204
|
+
return /* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
|
|
205
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
206
|
+
/* @__PURE__ */ jsx("h1", { children: "Auth Screens" }),
|
|
207
|
+
/* @__PURE__ */ jsxs("p", { className: "text-sm text-muted-foreground mt-1", children: [
|
|
208
|
+
"Login and organization selection from",
|
|
209
|
+
" ",
|
|
210
|
+
/* @__PURE__ */ jsx("code", { children: "@petrarca/sonnet-shell/auth" }),
|
|
211
|
+
". Configure a scenario, launch it full-screen, and read what the component called back."
|
|
212
|
+
] })
|
|
213
|
+
] }),
|
|
214
|
+
/* @__PURE__ */ jsxs(
|
|
215
|
+
PlaygroundSection,
|
|
216
|
+
{
|
|
217
|
+
title: "Scenario",
|
|
218
|
+
description: "Login renders four different screens depending on these props. Most are unreachable from the default state.",
|
|
219
|
+
variant: "card",
|
|
220
|
+
children: [
|
|
221
|
+
/* @__PURE__ */ jsxs("div", { className: "grid gap-4 sm:grid-cols-2", children: [
|
|
222
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
223
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "auth-flow", children: "Flow" }),
|
|
224
|
+
/* @__PURE__ */ jsxs(Select, { value: flow, onValueChange: (v) => setFlow(v), children: [
|
|
225
|
+
/* @__PURE__ */ jsx(SelectTrigger, { id: "auth-flow", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
|
|
226
|
+
/* @__PURE__ */ jsxs(SelectContent, { children: [
|
|
227
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "identifier-first", children: "Identifier-first (onDiscover wired)" }),
|
|
228
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "single-screen", children: "Single screen (no onDiscover)" })
|
|
229
|
+
] })
|
|
230
|
+
] })
|
|
231
|
+
] }),
|
|
232
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
233
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "auth-providers", children: "Providers" }),
|
|
234
|
+
/* @__PURE__ */ jsxs(
|
|
235
|
+
Select,
|
|
236
|
+
{
|
|
237
|
+
value: providers,
|
|
238
|
+
onValueChange: (v) => setProviders(v),
|
|
239
|
+
children: [
|
|
240
|
+
/* @__PURE__ */ jsx(SelectTrigger, { id: "auth-providers", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
|
|
241
|
+
/* @__PURE__ */ jsxs(SelectContent, { children: [
|
|
242
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "both", children: "Local and external" }),
|
|
243
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "local", children: "Local only" }),
|
|
244
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "external", children: "External only" }),
|
|
245
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "none", children: "None available" })
|
|
246
|
+
] })
|
|
247
|
+
]
|
|
248
|
+
}
|
|
249
|
+
)
|
|
250
|
+
] }),
|
|
251
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
252
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "auth-entry", children: "Entry state" }),
|
|
253
|
+
/* @__PURE__ */ jsxs(Select, { value: entry, onValueChange: (v) => setEntry(v), children: [
|
|
254
|
+
/* @__PURE__ */ jsx(SelectTrigger, { id: "auth-entry", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
|
|
255
|
+
/* @__PURE__ */ jsx(SelectContent, { children: Object.keys(ENTRY_LABELS).map((key) => /* @__PURE__ */ jsx(SelectItem, { value: key, children: ENTRY_LABELS[key] }, key)) })
|
|
256
|
+
] })
|
|
257
|
+
] }),
|
|
258
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
259
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "auth-session", children: "Session" }),
|
|
260
|
+
/* @__PURE__ */ jsxs(
|
|
261
|
+
Select,
|
|
262
|
+
{
|
|
263
|
+
value: session,
|
|
264
|
+
onValueChange: (v) => setSession(v),
|
|
265
|
+
children: [
|
|
266
|
+
/* @__PURE__ */ jsx(SelectTrigger, { id: "auth-session", children: /* @__PURE__ */ jsx(SelectValue, {}) }),
|
|
267
|
+
/* @__PURE__ */ jsx(SelectContent, { children: Object.keys(SESSION_LABELS).map((key) => /* @__PURE__ */ jsx(SelectItem, { value: key, children: SESSION_LABELS[key] }, key)) })
|
|
268
|
+
]
|
|
269
|
+
}
|
|
270
|
+
)
|
|
271
|
+
] }),
|
|
272
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
273
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "auth-error", children: "Error message" }),
|
|
274
|
+
/* @__PURE__ */ jsx(
|
|
275
|
+
Input,
|
|
276
|
+
{
|
|
277
|
+
id: "auth-error",
|
|
278
|
+
value: error,
|
|
279
|
+
placeholder: "Leave empty for no error",
|
|
280
|
+
onChange: (e) => setError(e.target.value)
|
|
281
|
+
}
|
|
282
|
+
)
|
|
283
|
+
] }),
|
|
284
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
|
|
285
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "auth-appname", children: "App name" }),
|
|
286
|
+
/* @__PURE__ */ jsx(
|
|
287
|
+
Input,
|
|
288
|
+
{
|
|
289
|
+
id: "auth-appname",
|
|
290
|
+
value: appName,
|
|
291
|
+
placeholder: "Shown above the form",
|
|
292
|
+
onChange: (e) => setAppName(e.target.value)
|
|
293
|
+
}
|
|
294
|
+
)
|
|
295
|
+
] }),
|
|
296
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-6 sm:col-span-2", children: [
|
|
297
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
298
|
+
/* @__PURE__ */ jsx(
|
|
299
|
+
Checkbox,
|
|
300
|
+
{
|
|
301
|
+
id: "auth-loading",
|
|
302
|
+
checked: isLoading,
|
|
303
|
+
onCheckedChange: (v) => setIsLoading(v === true)
|
|
304
|
+
}
|
|
305
|
+
),
|
|
306
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "auth-loading", children: "isLoading" })
|
|
307
|
+
] }),
|
|
308
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
309
|
+
/* @__PURE__ */ jsx(
|
|
310
|
+
Checkbox,
|
|
311
|
+
{
|
|
312
|
+
id: "auth-discovering",
|
|
313
|
+
checked: isDiscovering,
|
|
314
|
+
onCheckedChange: (v) => setIsDiscovering(v === true)
|
|
315
|
+
}
|
|
316
|
+
),
|
|
317
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "auth-discovering", children: "isDiscovering" })
|
|
318
|
+
] }),
|
|
319
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
320
|
+
/* @__PURE__ */ jsx(
|
|
321
|
+
Checkbox,
|
|
322
|
+
{
|
|
323
|
+
id: "auth-requires-org",
|
|
324
|
+
checked: requiresOrg,
|
|
325
|
+
onCheckedChange: (v) => setRequiresOrg(v === true)
|
|
326
|
+
}
|
|
327
|
+
),
|
|
328
|
+
/* @__PURE__ */ jsx(Label, { htmlFor: "auth-requires-org", children: "requiresOrgSelection" })
|
|
329
|
+
] })
|
|
330
|
+
] })
|
|
331
|
+
] }),
|
|
332
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 pt-2", children: [
|
|
333
|
+
/* @__PURE__ */ jsx(Button, { onClick: launch, children: "Launch login screen" }),
|
|
334
|
+
/* @__PURE__ */ jsx(
|
|
335
|
+
Button,
|
|
336
|
+
{
|
|
337
|
+
variant: "outline",
|
|
338
|
+
onClick: () => setEvents([]),
|
|
339
|
+
disabled: events.length === 0,
|
|
340
|
+
children: "Clear log"
|
|
341
|
+
}
|
|
342
|
+
)
|
|
343
|
+
] })
|
|
344
|
+
]
|
|
345
|
+
}
|
|
346
|
+
),
|
|
347
|
+
/* @__PURE__ */ jsx(
|
|
348
|
+
PlaygroundSection,
|
|
349
|
+
{
|
|
350
|
+
title: "Event log",
|
|
351
|
+
description: "Every prop callback the component invoked, most recent first.",
|
|
352
|
+
variant: "card",
|
|
353
|
+
children: events.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "Nothing yet. Launch the screen and interact with it." }) : /* @__PURE__ */ jsx("ul", { className: "space-y-1", children: events.map((entry2) => /* @__PURE__ */ jsxs("li", { className: "flex gap-3 text-sm", children: [
|
|
354
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-xs text-tertiary-foreground shrink-0 pt-0.5", children: entry2.time }),
|
|
355
|
+
/* @__PURE__ */ jsx("span", { className: "font-mono text-xs break-all", children: entry2.message })
|
|
356
|
+
] }, entry2.id)) })
|
|
357
|
+
}
|
|
358
|
+
),
|
|
359
|
+
open && createPortal(
|
|
360
|
+
/* @__PURE__ */ jsxs("div", { className: "fixed inset-0 z-50 overflow-auto bg-background", children: [
|
|
361
|
+
/* @__PURE__ */ jsx(
|
|
362
|
+
Login,
|
|
363
|
+
{
|
|
364
|
+
config,
|
|
365
|
+
isAuthenticated,
|
|
366
|
+
activeTenantId: null,
|
|
367
|
+
isLoading,
|
|
368
|
+
error: error || null,
|
|
369
|
+
orgs,
|
|
370
|
+
appName: appName || void 0,
|
|
371
|
+
onLogin,
|
|
372
|
+
onFetchCurrentUser,
|
|
373
|
+
onSelectOrg,
|
|
374
|
+
onClearError,
|
|
375
|
+
onDiscover: flow === "identifier-first" ? onDiscover : void 0,
|
|
376
|
+
onExternalLogin,
|
|
377
|
+
isDiscovering,
|
|
378
|
+
requiresOrgSelection: requiresOrg,
|
|
379
|
+
orgEmptyAction
|
|
380
|
+
}
|
|
381
|
+
),
|
|
382
|
+
/* @__PURE__ */ jsx(
|
|
383
|
+
Button,
|
|
384
|
+
{
|
|
385
|
+
variant: "outline",
|
|
386
|
+
size: "sm",
|
|
387
|
+
className: "fixed top-4 right-4 z-10",
|
|
388
|
+
onClick: () => setOpen(false),
|
|
389
|
+
children: "Exit preview (Esc)"
|
|
390
|
+
}
|
|
391
|
+
)
|
|
392
|
+
] }),
|
|
393
|
+
document.body
|
|
394
|
+
)
|
|
395
|
+
] });
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
export {
|
|
399
|
+
PlayAuth
|
|
400
|
+
};
|
|
401
|
+
//# sourceMappingURL=chunk-RYCRENYO.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/pages/PlayAuth.tsx"],"sourcesContent":["import { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport { useLocation, useNavigate } from \"react-router-dom\";\nimport {\n Button,\n Checkbox,\n Input,\n Label,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@petrarca/sonnet-ui\";\nimport {\n Login,\n type AuthConfig,\n type LoginProvider,\n type OrgOption,\n} from \"@petrarca/sonnet-shell/auth\";\nimport { PlaygroundSection } from \"../PlaygroundSection\";\n\n/**\n * PlayAuth -- exercises the pre-application auth screens from\n * `@petrarca/sonnet-shell/auth`.\n *\n * Login is a full-page state machine (identifier -> auth -> credentials ->\n * org-selection) rather than a dialog, so the preview takes over the viewport\n * instead of rendering inline. Every callback is a logger: no backend, no\n * mocking, and the log is the only feedback channel.\n *\n * Login calls useNavigate and redirects once a session resolves. It cannot be\n * sandboxed in a nested MemoryRouter -- react-router forbids a Router inside a\n * Router -- so the scenarios stop short of that branch instead: the redirect\n * fires only when a tenant is already active, and no scenario here sets one.\n * activeTenantId is therefore pinned to null and Login never navigates.\n *\n * The preview is portalled to document.body. Rendered in place it would sit\n * inside the page's space-y-6 wrapper, whose margin-top applies to a fixed\n * child too and pushes the overlay off the viewport by 24px.\n *\n * `config.ui.direct_redirect` is deliberately fixed to null and not exposed --\n * Login acts on it with `window.location.href`, which would take the browser\n * off the playground entirely.\n */\n\ntype Flow = \"identifier-first\" | \"single-screen\";\n/** What ProtectedRoute puts in location.state when it bounces a user here. */\ntype Entry = \"direct\" | \"tenant-selection\" | \"org-selection\";\ntype Providers = \"both\" | \"local\" | \"external\" | \"none\";\ntype Session =\n | \"anonymous\"\n | \"orgs-null\"\n | \"orgs-empty\"\n | \"orgs-one\"\n | \"orgs-many\";\n\ninterface LogEntry {\n id: number;\n time: string;\n message: string;\n}\n\nconst SESSION_LABELS: Record<Session, string> = {\n anonymous: \"Not authenticated -- login form\",\n \"orgs-null\": \"Authenticated, orgs = null (not loaded)\",\n \"orgs-empty\": \"Authenticated, orgs = [] (no memberships)\",\n \"orgs-one\": \"Authenticated, one org (auto-selects)\",\n \"orgs-many\": \"Authenticated, several orgs (picker)\",\n};\n\nconst ENTRY_LABELS: Record<Entry, string> = {\n direct: \"Direct visit -- no location state\",\n \"tenant-selection\": 'Redirected with step=\"tenant-selection\"',\n \"org-selection\": 'Redirected with step=\"org-selection\"',\n};\n\nconst MANY_ORGS: OrgOption[] = [\n { id: \"org-north\", name: \"Northwind Trading\" },\n { id: \"org-acme\", name: \"Acme Industrial\" },\n { id: \"org-globex\", name: \"Globex Research\" },\n];\n\nconst ONE_ORG: OrgOption[] = [{ id: \"org-north\", name: \"Northwind Trading\" }];\n\nexport default function PlayAuth() {\n const [flow, setFlow] = useState<Flow>(\"identifier-first\");\n const [providers, setProviders] = useState<Providers>(\"both\");\n const [session, setSession] = useState<Session>(\"anonymous\");\n const [error, setError] = useState(\"\");\n const [isLoading, setIsLoading] = useState(false);\n const [isDiscovering, setIsDiscovering] = useState(false);\n const [appName, setAppName] = useState(\"Example\");\n const [entry, setEntry] = useState<Entry>(\"direct\");\n const [requiresOrg, setRequiresOrg] = useState(true);\n const [pendingLaunch, setPendingLaunch] = useState(false);\n const [open, setOpen] = useState(false);\n const [events, setEvents] = useState<LogEntry[]>([]);\n const nextId = useRef(0);\n\n const log = useCallback((message: string) => {\n setEvents((prev) =>\n [\n {\n id: nextId.current++,\n time: new Date().toLocaleTimeString(),\n message,\n },\n ...prev,\n ].slice(0, 40),\n );\n }, []);\n\n // Escape leaves the preview.\n useEffect(() => {\n if (!open) return;\n const onKey = (e: KeyboardEvent) => {\n if (e.key === \"Escape\") setOpen(false);\n };\n window.addEventListener(\"keydown\", onKey);\n return () => window.removeEventListener(\"keydown\", onKey);\n }, [open]);\n\n const discovered = useMemo<LoginProvider[]>(() => {\n const local: LoginProvider = {\n type: \"local\",\n name: \"local\",\n label: \"Password\",\n };\n const external: LoginProvider = {\n type: \"external\",\n name: \"acme-sso\",\n label: \"Continue with Acme SSO\",\n // The app attaches whatever its onExternalLogin needs; the shell only\n // carries it through. No `as any` on the provider.\n metadata: {\n loginUrl: \"https://example.invalid/authorize\",\n targetId: \"acme\",\n },\n };\n if (providers === \"local\") return [local];\n if (providers === \"external\") return [external];\n if (providers === \"none\") return [];\n return [local, external];\n }, [providers]);\n\n const config = useMemo<AuthConfig>(() => {\n const oauthOn = providers === \"external\" || providers === \"both\";\n return {\n enabled: true,\n features: {\n local_auth: providers === \"local\" || providers === \"both\",\n oauth: oauthOn,\n },\n oauth: oauthOn\n ? {\n providers: {\n \"acme-sso\": {\n enabled: true,\n name: \"Acme SSO\",\n login_url: \"https://example.invalid/authorize\",\n },\n },\n }\n : null,\n // Never exposed as a control: Login acts on this with\n // window.location.href, which would leave the playground.\n ui: { direct_redirect: null },\n };\n }, [providers]);\n\n // Stable identity matters: Login runs an effect over `orgs` that calls\n // onSelectOrg for a single-org session. A fresh array each render would\n // re-fire it on every render.\n const orgs = useMemo<OrgOption[] | null>(() => {\n if (session === \"orgs-empty\") return [];\n if (session === \"orgs-one\") return ONE_ORG;\n if (session === \"orgs-many\") return MANY_ORGS;\n return null;\n }, [session]);\n\n const isAuthenticated = session !== \"anonymous\";\n\n const onLogin = useCallback(\n async (credentials: { identifier: string; password: string }) => {\n log(\n `onLogin({ identifier: \"${credentials.identifier}\", password: \"${\"*\".repeat(credentials.password.length)}\" })`,\n );\n },\n [log],\n );\n\n const onFetchCurrentUser = useCallback(async () => {\n log(\"onFetchCurrentUser()\");\n }, [log]);\n\n const onSelectOrg = useCallback(\n async (id: string) => {\n log(`onSelectOrg(\"${id}\")`);\n },\n [log],\n );\n\n const onClearError = useCallback(() => {\n log(\"onClearError()\");\n }, [log]);\n\n const onDiscover = useCallback(\n async (identifier: string) => {\n log(`onDiscover(\"${identifier}\") -> ${discovered.length} provider(s)`);\n return discovered;\n },\n [log, discovered],\n );\n\n const onExternalLogin = useCallback(\n (provider: LoginProvider) => {\n // Read back the per-provider metadata the discover step attached, with\n // one narrowing cast on the field -- not on the whole provider.\n const loginUrl = provider.metadata?.loginUrl as string | undefined;\n log(\n `onExternalLogin(\"${provider.name}\", loginUrl=${loginUrl ?? \"none\"}) -- a real app redirects to the IdP here, so the screen stays on \"Redirecting...\"`,\n );\n },\n [log],\n );\n\n // The empty-org escape hatch the consumer supplies. The library states the\n // fact; the app decides the way out.\n const orgEmptyAction = useMemo(\n () => (\n <Button\n variant=\"outline\"\n className=\"w-full\"\n onClick={() => log(\"orgEmptyAction: Sign out clicked\")}\n >\n Sign out\n </Button>\n ),\n [log],\n );\n\n const navigate = useNavigate();\n const location = useLocation();\n\n // Login seeds its step from location.state, which only ProtectedRoute sets.\n // Writing it onto the playground's own location is the only way to reach the\n // screens that entry state selects.\n //\n // The open flag cannot be set in the same handler: react-router resolves a\n // navigation asynchronously, so Login would mount and read location.state\n // before the new state landed. Wait for the location to agree first.\n const launch = useCallback(() => {\n log(\n `launched: flow=${flow}, providers=${providers}, session=${session}, entry=${entry}, requiresOrgSelection=${requiresOrg}`,\n );\n navigate(location.pathname, {\n replace: true,\n state:\n entry === \"direct\"\n ? null\n : { step: entry, from: { pathname: \"/dashboard\" } },\n });\n setPendingLaunch(true);\n }, [\n log,\n flow,\n providers,\n session,\n entry,\n requiresOrg,\n navigate,\n location.pathname,\n ]);\n\n useEffect(() => {\n if (!pendingLaunch) return;\n const state = location.state as { step?: string } | null;\n const wanted = entry === \"direct\" ? undefined : entry;\n if ((state?.step ?? undefined) !== wanted) return;\n setPendingLaunch(false);\n setOpen(true);\n }, [pendingLaunch, location.state, entry]);\n\n return (\n <div className=\"space-y-6\">\n <div>\n <h1>Auth Screens</h1>\n <p className=\"text-sm text-muted-foreground mt-1\">\n Login and organization selection from{\" \"}\n <code>@petrarca/sonnet-shell/auth</code>. Configure a scenario, launch\n it full-screen, and read what the component called back.\n </p>\n </div>\n\n <PlaygroundSection\n title=\"Scenario\"\n description=\"Login renders four different screens depending on these props. Most are unreachable from the default state.\"\n variant=\"card\"\n >\n <div className=\"grid gap-4 sm:grid-cols-2\">\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"auth-flow\">Flow</Label>\n <Select value={flow} onValueChange={(v) => setFlow(v as Flow)}>\n <SelectTrigger id=\"auth-flow\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"identifier-first\">\n Identifier-first (onDiscover wired)\n </SelectItem>\n <SelectItem value=\"single-screen\">\n Single screen (no onDiscover)\n </SelectItem>\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"auth-providers\">Providers</Label>\n <Select\n value={providers}\n onValueChange={(v) => setProviders(v as Providers)}\n >\n <SelectTrigger id=\"auth-providers\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"both\">Local and external</SelectItem>\n <SelectItem value=\"local\">Local only</SelectItem>\n <SelectItem value=\"external\">External only</SelectItem>\n <SelectItem value=\"none\">None available</SelectItem>\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"auth-entry\">Entry state</Label>\n <Select value={entry} onValueChange={(v) => setEntry(v as Entry)}>\n <SelectTrigger id=\"auth-entry\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {(Object.keys(ENTRY_LABELS) as Entry[]).map((key) => (\n <SelectItem key={key} value={key}>\n {ENTRY_LABELS[key]}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"auth-session\">Session</Label>\n <Select\n value={session}\n onValueChange={(v) => setSession(v as Session)}\n >\n <SelectTrigger id=\"auth-session\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {(Object.keys(SESSION_LABELS) as Session[]).map((key) => (\n <SelectItem key={key} value={key}>\n {SESSION_LABELS[key]}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"auth-error\">Error message</Label>\n <Input\n id=\"auth-error\"\n value={error}\n placeholder=\"Leave empty for no error\"\n onChange={(e) => setError(e.target.value)}\n />\n </div>\n\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"auth-appname\">App name</Label>\n <Input\n id=\"auth-appname\"\n value={appName}\n placeholder=\"Shown above the form\"\n onChange={(e) => setAppName(e.target.value)}\n />\n </div>\n\n <div className=\"flex items-center gap-6 sm:col-span-2\">\n <div className=\"flex items-center gap-2\">\n <Checkbox\n id=\"auth-loading\"\n checked={isLoading}\n onCheckedChange={(v) => setIsLoading(v === true)}\n />\n <Label htmlFor=\"auth-loading\">isLoading</Label>\n </div>\n <div className=\"flex items-center gap-2\">\n <Checkbox\n id=\"auth-discovering\"\n checked={isDiscovering}\n onCheckedChange={(v) => setIsDiscovering(v === true)}\n />\n <Label htmlFor=\"auth-discovering\">isDiscovering</Label>\n </div>\n <div className=\"flex items-center gap-2\">\n <Checkbox\n id=\"auth-requires-org\"\n checked={requiresOrg}\n onCheckedChange={(v) => setRequiresOrg(v === true)}\n />\n <Label htmlFor=\"auth-requires-org\">requiresOrgSelection</Label>\n </div>\n </div>\n </div>\n\n <div className=\"flex items-center gap-3 pt-2\">\n <Button onClick={launch}>Launch login screen</Button>\n <Button\n variant=\"outline\"\n onClick={() => setEvents([])}\n disabled={events.length === 0}\n >\n Clear log\n </Button>\n </div>\n </PlaygroundSection>\n\n <PlaygroundSection\n title=\"Event log\"\n description=\"Every prop callback the component invoked, most recent first.\"\n variant=\"card\"\n >\n {events.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n Nothing yet. Launch the screen and interact with it.\n </p>\n ) : (\n <ul className=\"space-y-1\">\n {events.map((entry) => (\n <li key={entry.id} className=\"flex gap-3 text-sm\">\n <span className=\"font-mono text-xs text-tertiary-foreground shrink-0 pt-0.5\">\n {entry.time}\n </span>\n <span className=\"font-mono text-xs break-all\">\n {entry.message}\n </span>\n </li>\n ))}\n </ul>\n )}\n </PlaygroundSection>\n\n {open &&\n createPortal(\n <div className=\"fixed inset-0 z-50 overflow-auto bg-background\">\n <Login\n config={config}\n isAuthenticated={isAuthenticated}\n activeTenantId={null}\n isLoading={isLoading}\n error={error || null}\n orgs={orgs}\n appName={appName || undefined}\n onLogin={onLogin}\n onFetchCurrentUser={onFetchCurrentUser}\n onSelectOrg={onSelectOrg}\n onClearError={onClearError}\n onDiscover={flow === \"identifier-first\" ? onDiscover : undefined}\n onExternalLogin={onExternalLogin}\n isDiscovering={isDiscovering}\n requiresOrgSelection={requiresOrg}\n orgEmptyAction={orgEmptyAction}\n />\n <Button\n variant=\"outline\"\n size=\"sm\"\n className=\"fixed top-4 right-4 z-10\"\n onClick={() => setOpen(false)}\n >\n Exit preview (Esc)\n </Button>\n </div>,\n document.body,\n )}\n </div>\n );\n}\n"],"mappings":"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","names":["entry"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -55,17 +55,21 @@ interface PlaygroundModuleOptions {
|
|
|
55
55
|
grouped?: boolean;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
/**
|
|
59
|
-
* PlaygroundRegistry -- mutable collection of playground entries.
|
|
60
|
-
*
|
|
61
|
-
* The library ships a default instance pre-populated with the built-in
|
|
62
|
-
* sonnet component demos. Consumers register additional entries for
|
|
63
|
-
* domain-specific playgrounds before passing the registry to
|
|
64
|
-
* createPlaygroundModule().
|
|
65
|
-
*/
|
|
66
|
-
|
|
67
58
|
declare class PlaygroundRegistry {
|
|
68
59
|
private entries;
|
|
60
|
+
/**
|
|
61
|
+
* Report registrations that cannot work, rather than silently misbehaving.
|
|
62
|
+
*
|
|
63
|
+
* Entries are keyed by `id`, so two entries may legally share a `path` --
|
|
64
|
+
* and then both produce a route on the same URL and one is unreachable.
|
|
65
|
+
* Groups are bucketed by their exact label, so two labels differing only by
|
|
66
|
+
* case become two separate groups.
|
|
67
|
+
*
|
|
68
|
+
* Neither is corrected here: dropping an entry, or merging groups the caller
|
|
69
|
+
* spelled differently, would be its own surprise. The registration stands and
|
|
70
|
+
* the problem is made diagnosable.
|
|
71
|
+
*/
|
|
72
|
+
private warnOnConflicts;
|
|
69
73
|
/** Register a playground entry. Overwrites any existing entry with the same id. */
|
|
70
74
|
register(entry: PlaygroundEntry): void;
|
|
71
75
|
/** Register multiple entries at once. */
|
|
@@ -130,6 +134,12 @@ interface PlaygroundSectionProps {
|
|
|
130
134
|
children: React$1.ReactNode;
|
|
131
135
|
variant?: "default" | "card";
|
|
132
136
|
}
|
|
137
|
+
interface ExampleBlockProps {
|
|
138
|
+
label: string;
|
|
139
|
+
children: React$1.ReactNode;
|
|
140
|
+
}
|
|
141
|
+
/** Labeled divider wrapper for individual component examples within a section. */
|
|
142
|
+
declare function ExampleBlock({ label, children, }: ExampleBlockProps): React$1.ReactElement;
|
|
133
143
|
/** Titled section wrapper used across playground pages. */
|
|
134
144
|
declare function PlaygroundSection({ title, description, children, variant, }: PlaygroundSectionProps): React$1.ReactElement;
|
|
135
145
|
|
|
@@ -200,4 +210,4 @@ declare const builtinEntries: PlaygroundEntry[];
|
|
|
200
210
|
*/
|
|
201
211
|
declare function createPlaygroundRegistry(): PlaygroundRegistry;
|
|
202
212
|
|
|
203
|
-
export { type PlaygroundColor, type PlaygroundEntry, type PlaygroundModuleOptions, PlaygroundOverview, PlaygroundRegistry, PlaygroundSection, type PlaygroundSettings, PlaygroundSettingsPanel, PlaygroundSettingsProvider, builtinEntries, createPlaygroundModule, createPlaygroundRegistry, usePlaygroundSettings };
|
|
213
|
+
export { ExampleBlock, type PlaygroundColor, type PlaygroundEntry, type PlaygroundModuleOptions, PlaygroundOverview, PlaygroundRegistry, PlaygroundSection, type PlaygroundSettings, PlaygroundSettingsPanel, PlaygroundSettingsProvider, builtinEntries, createPlaygroundModule, createPlaygroundRegistry, usePlaygroundSettings };
|
package/dist/index.js
CHANGED
|
@@ -1,18 +1,50 @@
|
|
|
1
1
|
import {
|
|
2
|
+
ExampleBlock,
|
|
2
3
|
PlaygroundSection
|
|
3
4
|
} from "./chunk-C2EZ2ECG.js";
|
|
4
5
|
|
|
5
6
|
// src/PlaygroundRegistry.ts
|
|
7
|
+
import { warnLog } from "@petrarca/sonnet-core";
|
|
6
8
|
var PlaygroundRegistry = class _PlaygroundRegistry {
|
|
7
9
|
entries = /* @__PURE__ */ new Map();
|
|
10
|
+
/**
|
|
11
|
+
* Report registrations that cannot work, rather than silently misbehaving.
|
|
12
|
+
*
|
|
13
|
+
* Entries are keyed by `id`, so two entries may legally share a `path` --
|
|
14
|
+
* and then both produce a route on the same URL and one is unreachable.
|
|
15
|
+
* Groups are bucketed by their exact label, so two labels differing only by
|
|
16
|
+
* case become two separate groups.
|
|
17
|
+
*
|
|
18
|
+
* Neither is corrected here: dropping an entry, or merging groups the caller
|
|
19
|
+
* spelled differently, would be its own surprise. The registration stands and
|
|
20
|
+
* the problem is made diagnosable.
|
|
21
|
+
*/
|
|
22
|
+
warnOnConflicts(entry) {
|
|
23
|
+
for (const existing of this.entries.values()) {
|
|
24
|
+
if (existing.id === entry.id) continue;
|
|
25
|
+
if (existing.path === entry.path) {
|
|
26
|
+
warnLog(
|
|
27
|
+
`[PlaygroundRegistry] entries "${existing.id}" and "${entry.id}" share the path "${entry.path}". Both will route to the same URL and one will be unreachable.`
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
const group = entry.group ?? _PlaygroundRegistry.DEFAULT_GROUP;
|
|
31
|
+
const existingGroup = existing.group ?? _PlaygroundRegistry.DEFAULT_GROUP;
|
|
32
|
+
if (group !== existingGroup && group.toLowerCase() === existingGroup.toLowerCase()) {
|
|
33
|
+
warnLog(
|
|
34
|
+
`[PlaygroundRegistry] group "${group}" differs from "${existingGroup}" only by case; they will render as two separate groups.`
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
8
39
|
/** Register a playground entry. Overwrites any existing entry with the same id. */
|
|
9
40
|
register(entry) {
|
|
41
|
+
this.warnOnConflicts(entry);
|
|
10
42
|
this.entries.set(entry.id, entry);
|
|
11
43
|
}
|
|
12
44
|
/** Register multiple entries at once. */
|
|
13
45
|
registerAll(entries) {
|
|
14
46
|
for (const entry of entries) {
|
|
15
|
-
this.
|
|
47
|
+
this.register(entry);
|
|
16
48
|
}
|
|
17
49
|
}
|
|
18
50
|
/** Remove an entry by id. Returns true if it existed. */
|
|
@@ -87,7 +119,8 @@ import {
|
|
|
87
119
|
Table2,
|
|
88
120
|
Network,
|
|
89
121
|
SquareTerminal,
|
|
90
|
-
PencilRuler
|
|
122
|
+
PencilRuler,
|
|
123
|
+
ShieldCheck
|
|
91
124
|
} from "lucide-react";
|
|
92
125
|
var builtinEntries = [
|
|
93
126
|
{
|
|
@@ -163,7 +196,7 @@ var builtinEntries = [
|
|
|
163
196
|
icon: Edit,
|
|
164
197
|
color: "purple",
|
|
165
198
|
path: "forms",
|
|
166
|
-
component: lazy(() => import("./PlayForms-
|
|
199
|
+
component: lazy(() => import("./PlayForms-L532W3JL.js")),
|
|
167
200
|
group: "Forms",
|
|
168
201
|
order: 1
|
|
169
202
|
},
|
|
@@ -185,7 +218,7 @@ var builtinEntries = [
|
|
|
185
218
|
icon: PencilRuler,
|
|
186
219
|
color: "teal",
|
|
187
220
|
path: "schema-designer",
|
|
188
|
-
component: lazy(() => import("./PlaySchemaDesigner-
|
|
221
|
+
component: lazy(() => import("./PlaySchemaDesigner-S4DBB53G.js")),
|
|
189
222
|
group: "Schemas",
|
|
190
223
|
order: 0
|
|
191
224
|
},
|
|
@@ -207,7 +240,7 @@ var builtinEntries = [
|
|
|
207
240
|
icon: Network,
|
|
208
241
|
color: "cyan",
|
|
209
242
|
path: "graph",
|
|
210
|
-
component: lazy(() => import("./PlayGraph-
|
|
243
|
+
component: lazy(() => import("./PlayGraph-M4KQSFI7.js")),
|
|
211
244
|
group: "Graph",
|
|
212
245
|
order: 0
|
|
213
246
|
},
|
|
@@ -221,6 +254,17 @@ var builtinEntries = [
|
|
|
221
254
|
component: lazy(() => import("./PlayShellApi-5O6VYR2X.js")),
|
|
222
255
|
group: "Shell",
|
|
223
256
|
order: 0
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
id: "sonnet.auth",
|
|
260
|
+
title: "Auth Screens",
|
|
261
|
+
description: "The pre-application login and organization selection screens: identifier-first and legacy flows, provider combinations, error and loading states, and every org-selection outcome.",
|
|
262
|
+
icon: ShieldCheck,
|
|
263
|
+
color: "teal",
|
|
264
|
+
path: "auth",
|
|
265
|
+
component: lazy(() => import("./PlayAuth-SOEYPOOL.js")),
|
|
266
|
+
group: "Shell",
|
|
267
|
+
order: 1
|
|
224
268
|
}
|
|
225
269
|
];
|
|
226
270
|
|
|
@@ -536,6 +580,7 @@ function createPlaygroundRegistry() {
|
|
|
536
580
|
return registry;
|
|
537
581
|
}
|
|
538
582
|
export {
|
|
583
|
+
ExampleBlock,
|
|
539
584
|
PlaygroundOverview,
|
|
540
585
|
PlaygroundRegistry,
|
|
541
586
|
PlaygroundSection,
|