@petrarca/sonnet-shell 0.5.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 +56 -211
- package/dist/auth/index.d.ts +86 -32
- package/dist/auth/index.js +457 -141
- package/dist/auth/index.js.map +1 -1
- package/dist/index.d.ts +27 -5
- package/dist/index.js +118 -43
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/auth/index.js
CHANGED
|
@@ -1,54 +1,66 @@
|
|
|
1
1
|
// src/auth/Login.tsx
|
|
2
2
|
import { useState as useState2, useEffect, useCallback as useCallback2 } from "react";
|
|
3
3
|
import { useNavigate, useLocation } from "react-router-dom";
|
|
4
|
+
import { Alert as Alert2 } from "@petrarca/sonnet-ui";
|
|
4
5
|
import { Button as Button2 } from "@petrarca/sonnet-ui";
|
|
5
6
|
import { Input } from "@petrarca/sonnet-ui";
|
|
6
7
|
import { Label } from "@petrarca/sonnet-ui";
|
|
7
|
-
import {
|
|
8
|
+
import { Spinner } from "@petrarca/sonnet-ui";
|
|
8
9
|
|
|
9
|
-
// src/auth/
|
|
10
|
+
// src/auth/OrgSelection.tsx
|
|
10
11
|
import { useState, useCallback } from "react";
|
|
11
12
|
import { Check } from "lucide-react";
|
|
12
13
|
import { Button } from "@petrarca/sonnet-ui";
|
|
13
14
|
import { Alert } from "@petrarca/sonnet-ui";
|
|
14
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
15
|
-
function
|
|
16
|
-
|
|
15
|
+
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
16
|
+
function OrgSelection({
|
|
17
|
+
orgs,
|
|
17
18
|
isLoading,
|
|
18
19
|
error,
|
|
19
|
-
onSelect
|
|
20
|
+
onSelect,
|
|
21
|
+
emptyAction
|
|
20
22
|
}) {
|
|
21
23
|
const [selectedId, setSelectedId] = useState(null);
|
|
24
|
+
const isEmpty = orgs.length === 0;
|
|
22
25
|
const handleContinue = useCallback(() => {
|
|
23
|
-
if (selectedId)
|
|
24
|
-
onSelect(selectedId);
|
|
25
|
-
}
|
|
26
|
+
if (selectedId) onSelect(selectedId);
|
|
26
27
|
}, [selectedId, onSelect]);
|
|
27
|
-
return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center min-h-screen bg-
|
|
28
|
-
/* @__PURE__ */
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
onClick: () => setSelectedId(tenant.id),
|
|
35
|
-
className: `w-full flex items-center justify-between px-4 py-3 rounded-md border text-left text-sm transition-colors ${selectedId === tenant.id ? "border-blue-500 bg-blue-50 text-blue-900" : "border-slate-200 hover:bg-slate-50"}`,
|
|
36
|
-
children: [
|
|
37
|
-
/* @__PURE__ */ jsx("span", { children: tenant.name }),
|
|
38
|
-
selectedId === tenant.id && /* @__PURE__ */ jsx(Check, { className: "h-4 w-4 text-blue-500" })
|
|
39
|
-
]
|
|
40
|
-
},
|
|
41
|
-
tenant.id
|
|
42
|
-
)) }),
|
|
43
|
-
/* @__PURE__ */ jsx(
|
|
44
|
-
Button,
|
|
28
|
+
return /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center min-h-screen bg-muted px-4", children: /* @__PURE__ */ jsxs("div", { className: "w-full max-w-sm bg-card rounded-lg shadow-sm border border-border p-8", children: [
|
|
29
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-6", children: [
|
|
30
|
+
/* @__PURE__ */ jsx("h1", { className: "text-2xl font-semibold text-foreground text-center", children: "Select organization" }),
|
|
31
|
+
/* @__PURE__ */ jsx("p", { className: "text-center text-sm text-muted-foreground mt-1", children: isEmpty ? "No organizations assigned to your account." : "Choose the organization you want to work in." })
|
|
32
|
+
] }),
|
|
33
|
+
error ? /* @__PURE__ */ jsx(
|
|
34
|
+
Alert,
|
|
45
35
|
{
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
children: isLoading ? "Loading..." : "Continue"
|
|
36
|
+
variant: "destructive",
|
|
37
|
+
className: "mb-4 bg-destructive/10 text-sm py-2.5 px-3",
|
|
38
|
+
children: error
|
|
50
39
|
}
|
|
51
|
-
)
|
|
40
|
+
) : null,
|
|
41
|
+
isEmpty ? emptyAction ? /* @__PURE__ */ jsx("div", { className: "mt-2", children: emptyAction }) : null : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
42
|
+
/* @__PURE__ */ jsx("div", { className: "space-y-2 mb-6 max-h-64 overflow-y-auto", children: orgs.map((org) => /* @__PURE__ */ jsxs(
|
|
43
|
+
"button",
|
|
44
|
+
{
|
|
45
|
+
onClick: () => setSelectedId(org.id),
|
|
46
|
+
className: `w-full flex items-center justify-between px-4 py-3 rounded-md border text-left text-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 ${selectedId === org.id ? "border-primary bg-state-selected text-foreground" : "border-border hover:bg-state-hover text-foreground"}`,
|
|
47
|
+
children: [
|
|
48
|
+
/* @__PURE__ */ jsx("span", { className: "font-medium", children: org.name }),
|
|
49
|
+
selectedId === org.id ? /* @__PURE__ */ jsx(Check, { className: "h-4 w-4 text-primary shrink-0" }) : null
|
|
50
|
+
]
|
|
51
|
+
},
|
|
52
|
+
org.id
|
|
53
|
+
)) }),
|
|
54
|
+
/* @__PURE__ */ jsx(
|
|
55
|
+
Button,
|
|
56
|
+
{
|
|
57
|
+
onClick: handleContinue,
|
|
58
|
+
disabled: !selectedId || isLoading,
|
|
59
|
+
className: "w-full h-10",
|
|
60
|
+
children: isLoading ? "Loading..." : "Continue"
|
|
61
|
+
}
|
|
62
|
+
)
|
|
63
|
+
] })
|
|
52
64
|
] }) });
|
|
53
65
|
}
|
|
54
66
|
|
|
@@ -60,43 +72,80 @@ function useLoginFlow(props) {
|
|
|
60
72
|
config,
|
|
61
73
|
isAuthenticated,
|
|
62
74
|
activeTenantId,
|
|
63
|
-
|
|
75
|
+
orgs,
|
|
64
76
|
onLogin,
|
|
65
77
|
onFetchCurrentUser,
|
|
66
|
-
|
|
67
|
-
onClearError
|
|
78
|
+
onSelectOrg,
|
|
79
|
+
onClearError,
|
|
80
|
+
onDiscover,
|
|
81
|
+
onExternalLogin,
|
|
82
|
+
requiresOrgSelection = true
|
|
68
83
|
} = props;
|
|
69
84
|
const navigate = useNavigate();
|
|
70
85
|
const location = useLocation();
|
|
71
86
|
const locationState = location.state;
|
|
87
|
+
const identifierFirst = Boolean(onDiscover);
|
|
88
|
+
const requestedStep = requiresOrgSelection && locationState?.step ? locationState.step : void 0;
|
|
89
|
+
const initial = requestedStep ?? (identifierFirst ? "identifier" : "credentials");
|
|
72
90
|
const [redirectTo] = useState2(() => locationState?.from?.pathname ?? "/");
|
|
73
|
-
const [step, setStep] = useState2(
|
|
74
|
-
() => locationState?.step ?? "credentials"
|
|
75
|
-
);
|
|
91
|
+
const [step, setStep] = useState2(initial);
|
|
76
92
|
const [identifier, setIdentifier] = useState2("");
|
|
77
93
|
const [password, setPassword] = useState2("");
|
|
78
|
-
const [
|
|
94
|
+
const [providers, setProviders] = useState2([]);
|
|
95
|
+
const [discoverError, setDiscoverError] = useState2(null);
|
|
96
|
+
const [isRedirecting, setIsRedirecting] = useState2(false);
|
|
97
|
+
const [orgError, setOrgError] = useState2(null);
|
|
79
98
|
useEffect(() => {
|
|
80
|
-
if (isAuthenticated && activeTenantId)
|
|
99
|
+
if (isAuthenticated && (activeTenantId || !requiresOrgSelection))
|
|
81
100
|
navigate(redirectTo, { replace: true });
|
|
82
|
-
}, [
|
|
101
|
+
}, [
|
|
102
|
+
isAuthenticated,
|
|
103
|
+
activeTenantId,
|
|
104
|
+
requiresOrgSelection,
|
|
105
|
+
navigate,
|
|
106
|
+
redirectTo
|
|
107
|
+
]);
|
|
83
108
|
useEffect(() => {
|
|
84
|
-
if (!
|
|
85
|
-
if (
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
109
|
+
if (!requiresOrgSelection) return;
|
|
110
|
+
if (!isAuthenticated || !orgs || activeTenantId) return;
|
|
111
|
+
if (orgs.length === 1) {
|
|
112
|
+
onSelectOrg(orgs[0].id).catch((err) => {
|
|
113
|
+
setOrgError(
|
|
114
|
+
normalizeError(err).message || "Failed to select organization"
|
|
89
115
|
);
|
|
90
|
-
setStep("
|
|
116
|
+
setStep("org-selection");
|
|
91
117
|
});
|
|
92
|
-
} else if (
|
|
93
|
-
setStep("
|
|
118
|
+
} else if (orgs.length > 1) {
|
|
119
|
+
setStep("org-selection");
|
|
94
120
|
}
|
|
95
|
-
}, [
|
|
121
|
+
}, [
|
|
122
|
+
requiresOrgSelection,
|
|
123
|
+
isAuthenticated,
|
|
124
|
+
orgs,
|
|
125
|
+
activeTenantId,
|
|
126
|
+
onSelectOrg
|
|
127
|
+
]);
|
|
96
128
|
useEffect(() => {
|
|
97
129
|
if (config?.ui.direct_redirect)
|
|
98
130
|
window.location.href = config.ui.direct_redirect;
|
|
99
131
|
}, [config]);
|
|
132
|
+
const handleIdentifierSubmit = useCallback2(
|
|
133
|
+
async (e) => {
|
|
134
|
+
e.preventDefault();
|
|
135
|
+
if (!onDiscover || !identifier.trim()) return;
|
|
136
|
+
setDiscoverError(null);
|
|
137
|
+
try {
|
|
138
|
+
const resolved = await onDiscover(identifier.trim());
|
|
139
|
+
setProviders(resolved);
|
|
140
|
+
setStep("auth");
|
|
141
|
+
} catch (err) {
|
|
142
|
+
setDiscoverError(
|
|
143
|
+
normalizeError(err).message || "Failed to look up providers"
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
},
|
|
147
|
+
[identifier, onDiscover]
|
|
148
|
+
);
|
|
100
149
|
const handleCredentialSubmit = useCallback2(
|
|
101
150
|
async (e) => {
|
|
102
151
|
e.preventDefault();
|
|
@@ -109,94 +158,176 @@ function useLoginFlow(props) {
|
|
|
109
158
|
},
|
|
110
159
|
[identifier, password, onLogin, onFetchCurrentUser, onClearError]
|
|
111
160
|
);
|
|
112
|
-
const
|
|
161
|
+
const handleExternalLogin = useCallback2(
|
|
162
|
+
async (provider) => {
|
|
163
|
+
if (!onExternalLogin) return;
|
|
164
|
+
setIsRedirecting(true);
|
|
165
|
+
try {
|
|
166
|
+
await onExternalLogin(provider);
|
|
167
|
+
} catch {
|
|
168
|
+
setIsRedirecting(false);
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
[onExternalLogin]
|
|
172
|
+
);
|
|
173
|
+
const handleOrgSelect = useCallback2(
|
|
113
174
|
async (id) => {
|
|
114
|
-
|
|
175
|
+
setOrgError(null);
|
|
115
176
|
try {
|
|
116
|
-
await
|
|
177
|
+
await onSelectOrg(id);
|
|
117
178
|
} catch (err) {
|
|
118
|
-
|
|
119
|
-
normalizeError(err).message || "Failed to select
|
|
179
|
+
setOrgError(
|
|
180
|
+
normalizeError(err).message || "Failed to select organization"
|
|
120
181
|
);
|
|
121
182
|
}
|
|
122
183
|
},
|
|
123
|
-
[
|
|
184
|
+
[onSelectOrg]
|
|
124
185
|
);
|
|
186
|
+
const goBack = useCallback2(() => {
|
|
187
|
+
setPassword("");
|
|
188
|
+
setDiscoverError(null);
|
|
189
|
+
onClearError();
|
|
190
|
+
setStep(identifierFirst ? "identifier" : "credentials");
|
|
191
|
+
}, [identifierFirst, onClearError]);
|
|
125
192
|
return {
|
|
126
193
|
step,
|
|
127
194
|
identifier,
|
|
128
195
|
setIdentifier,
|
|
129
196
|
password,
|
|
130
197
|
setPassword,
|
|
131
|
-
|
|
198
|
+
providers,
|
|
199
|
+
discoverError,
|
|
200
|
+
isRedirecting,
|
|
201
|
+
orgError,
|
|
202
|
+
handleIdentifierSubmit,
|
|
132
203
|
handleCredentialSubmit,
|
|
133
|
-
|
|
204
|
+
handleExternalLogin,
|
|
205
|
+
handleOrgSelect,
|
|
206
|
+
goBack
|
|
134
207
|
};
|
|
135
208
|
}
|
|
209
|
+
function resolveScreen(args) {
|
|
210
|
+
if (args.directRedirect || args.isRedirecting) return "redirecting";
|
|
211
|
+
const canShowOrgPicker = args.requiresOrgSelection && args.step === "org-selection" && args.orgsLoaded;
|
|
212
|
+
if (args.isAuthenticated && !args.activeTenantId && !canShowOrgPicker)
|
|
213
|
+
return "redirecting";
|
|
214
|
+
return canShowOrgPicker ? "org-selection" : "form";
|
|
215
|
+
}
|
|
136
216
|
function Login(props) {
|
|
137
|
-
const {
|
|
217
|
+
const {
|
|
218
|
+
config,
|
|
219
|
+
isAuthenticated,
|
|
220
|
+
activeTenantId,
|
|
221
|
+
isLoading,
|
|
222
|
+
isDiscovering,
|
|
223
|
+
error,
|
|
224
|
+
orgs,
|
|
225
|
+
appName,
|
|
226
|
+
logoUrl,
|
|
227
|
+
orgEmptyAction,
|
|
228
|
+
requiresOrgSelection = true
|
|
229
|
+
} = props;
|
|
138
230
|
const flow = useLoginFlow(props);
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
231
|
+
const screen = resolveScreen({
|
|
232
|
+
isRedirecting: flow.isRedirecting,
|
|
233
|
+
directRedirect: Boolean(config?.ui.direct_redirect),
|
|
234
|
+
isAuthenticated,
|
|
235
|
+
activeTenantId,
|
|
236
|
+
requiresOrgSelection,
|
|
237
|
+
step: flow.step,
|
|
238
|
+
orgsLoaded: orgs !== null
|
|
239
|
+
});
|
|
240
|
+
if (screen === "redirecting") return /* @__PURE__ */ jsx2(RedirectingMessage, {});
|
|
241
|
+
if (screen === "org-selection") {
|
|
143
242
|
return /* @__PURE__ */ jsx2(
|
|
144
|
-
|
|
243
|
+
OrgSelection,
|
|
145
244
|
{
|
|
146
|
-
|
|
245
|
+
orgs: orgs ?? [],
|
|
147
246
|
isLoading,
|
|
148
|
-
error: flow.
|
|
149
|
-
onSelect: flow.
|
|
247
|
+
error: flow.orgError,
|
|
248
|
+
onSelect: flow.handleOrgSelect,
|
|
249
|
+
emptyAction: orgEmptyAction
|
|
150
250
|
}
|
|
151
251
|
);
|
|
152
252
|
}
|
|
153
|
-
return /* @__PURE__ */ jsx2(
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
config,
|
|
157
|
-
error,
|
|
158
|
-
isLoading,
|
|
159
|
-
flow
|
|
160
|
-
}
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
|
-
function RedirectingMessage() {
|
|
164
|
-
return /* @__PURE__ */ jsx2("div", { className: "flex items-center justify-center min-h-screen", children: /* @__PURE__ */ jsx2("p", { className: "text-muted-foreground", children: "Redirecting to authentication..." }) });
|
|
165
|
-
}
|
|
166
|
-
function CredentialPage({
|
|
167
|
-
config,
|
|
168
|
-
error,
|
|
169
|
-
isLoading,
|
|
170
|
-
flow
|
|
171
|
-
}) {
|
|
172
|
-
return /* @__PURE__ */ jsx2("div", { className: "flex items-center justify-center min-h-screen bg-slate-100", children: /* @__PURE__ */ jsxs2("div", { className: "w-full max-w-md bg-white rounded-lg shadow-lg p-8", children: [
|
|
173
|
-
/* @__PURE__ */ jsx2("h1", { className: "text-center mb-6", children: "Sign In" }),
|
|
174
|
-
error && /* @__PURE__ */ jsx2(Alert2, { className: "mb-4 border-red-200 bg-red-50 text-red-700", children: /* @__PURE__ */ jsx2("p", { className: "text-sm", children: error }) }),
|
|
175
|
-
config?.features.local_auth && /* @__PURE__ */ jsx2(
|
|
176
|
-
CredentialForm,
|
|
253
|
+
return /* @__PURE__ */ jsx2(PageShell, { children: /* @__PURE__ */ jsxs2(Card, { children: [
|
|
254
|
+
flow.step === "identifier" && /* @__PURE__ */ jsx2(
|
|
255
|
+
IdentifierStep,
|
|
177
256
|
{
|
|
257
|
+
appName,
|
|
258
|
+
logoUrl,
|
|
178
259
|
identifier: flow.identifier,
|
|
260
|
+
error: flow.discoverError,
|
|
261
|
+
isLoading: isDiscovering ?? false,
|
|
262
|
+
onIdentifierChange: flow.setIdentifier,
|
|
263
|
+
onSubmit: flow.handleIdentifierSubmit
|
|
264
|
+
}
|
|
265
|
+
),
|
|
266
|
+
flow.step === "auth" && /* @__PURE__ */ jsx2(
|
|
267
|
+
AuthStep,
|
|
268
|
+
{
|
|
269
|
+
appName,
|
|
270
|
+
logoUrl,
|
|
271
|
+
identifier: flow.identifier,
|
|
272
|
+
providers: flow.providers,
|
|
179
273
|
password: flow.password,
|
|
274
|
+
error,
|
|
180
275
|
isLoading,
|
|
181
|
-
onIdentifierChange: flow.setIdentifier,
|
|
182
276
|
onPasswordChange: flow.setPassword,
|
|
183
|
-
|
|
277
|
+
onCredentialSubmit: flow.handleCredentialSubmit,
|
|
278
|
+
onExternalLogin: flow.handleExternalLogin,
|
|
279
|
+
onBack: flow.goBack
|
|
184
280
|
}
|
|
185
281
|
),
|
|
186
|
-
|
|
282
|
+
flow.step === "credentials" && /* @__PURE__ */ jsx2(
|
|
283
|
+
CredentialsStep,
|
|
284
|
+
{
|
|
285
|
+
appName,
|
|
286
|
+
logoUrl,
|
|
287
|
+
config,
|
|
288
|
+
identifier: flow.identifier,
|
|
289
|
+
password: flow.password,
|
|
290
|
+
error,
|
|
291
|
+
isLoading,
|
|
292
|
+
onIdentifierChange: flow.setIdentifier,
|
|
293
|
+
onPasswordChange: flow.setPassword,
|
|
294
|
+
onSubmit: flow.handleCredentialSubmit,
|
|
295
|
+
onExternalLogin: flow.handleExternalLogin
|
|
296
|
+
}
|
|
297
|
+
)
|
|
187
298
|
] }) });
|
|
188
299
|
}
|
|
189
|
-
function
|
|
300
|
+
function PageShell({ children }) {
|
|
301
|
+
return /* @__PURE__ */ jsx2("div", { className: "flex flex-col items-center justify-center min-h-screen bg-muted px-4", children });
|
|
302
|
+
}
|
|
303
|
+
function Card({ children }) {
|
|
304
|
+
return /* @__PURE__ */ jsx2("div", { className: "w-full max-w-sm bg-card rounded-lg border border-border shadow-sm p-8", children });
|
|
305
|
+
}
|
|
306
|
+
function BrandHeader({
|
|
307
|
+
appName,
|
|
308
|
+
logoUrl
|
|
309
|
+
}) {
|
|
310
|
+
if (!appName && !logoUrl) return null;
|
|
311
|
+
return /* @__PURE__ */ jsx2("div", { className: "flex flex-col items-center mb-6", children: logoUrl ? /* @__PURE__ */ jsx2("img", { src: logoUrl, alt: appName ?? "Logo", className: "h-10 w-auto" }) : /* @__PURE__ */ jsx2("span", { className: "text-lg font-semibold text-muted-foreground tracking-wide", children: appName }) });
|
|
312
|
+
}
|
|
313
|
+
function IdentifierStep({
|
|
314
|
+
appName,
|
|
315
|
+
logoUrl,
|
|
190
316
|
identifier,
|
|
191
|
-
|
|
317
|
+
error,
|
|
192
318
|
isLoading,
|
|
193
319
|
onIdentifierChange,
|
|
194
|
-
onPasswordChange,
|
|
195
320
|
onSubmit
|
|
196
321
|
}) {
|
|
197
|
-
return /* @__PURE__ */ jsxs2("form", { onSubmit, className: "space-y-
|
|
198
|
-
/* @__PURE__ */
|
|
199
|
-
|
|
322
|
+
return /* @__PURE__ */ jsxs2("form", { onSubmit, className: "space-y-6", children: [
|
|
323
|
+
/* @__PURE__ */ jsx2(BrandHeader, { appName, logoUrl }),
|
|
324
|
+
/* @__PURE__ */ jsxs2("div", { className: "text-center", children: [
|
|
325
|
+
/* @__PURE__ */ jsx2("h1", { className: "text-2xl font-semibold text-foreground leading-tight", children: "Welcome" }),
|
|
326
|
+
/* @__PURE__ */ jsx2("p", { className: "mt-2 text-sm text-muted-foreground", children: "Enter your email or username to continue." })
|
|
327
|
+
] }),
|
|
328
|
+
error ? /* @__PURE__ */ jsx2(ErrorAlert, { message: error }) : null,
|
|
329
|
+
/* @__PURE__ */ jsxs2("div", { children: [
|
|
330
|
+
/* @__PURE__ */ jsx2(Label, { htmlFor: "identifier", className: "mb-1.5 block", children: "Email or username" }),
|
|
200
331
|
/* @__PURE__ */ jsx2(
|
|
201
332
|
Input,
|
|
202
333
|
{
|
|
@@ -205,55 +336,239 @@ function CredentialForm({
|
|
|
205
336
|
value: identifier,
|
|
206
337
|
onChange: (e) => onIdentifierChange(e.target.value),
|
|
207
338
|
required: true,
|
|
208
|
-
|
|
339
|
+
autoFocus: true,
|
|
340
|
+
autoComplete: "email",
|
|
341
|
+
placeholder: "you@company.com"
|
|
209
342
|
}
|
|
210
343
|
)
|
|
211
344
|
] }),
|
|
212
|
-
/* @__PURE__ */
|
|
213
|
-
|
|
345
|
+
/* @__PURE__ */ jsx2(
|
|
346
|
+
PrimaryButton,
|
|
347
|
+
{
|
|
348
|
+
type: "submit",
|
|
349
|
+
disabled: isLoading || !identifier.trim(),
|
|
350
|
+
loading: isLoading,
|
|
351
|
+
loadingText: "Looking up...",
|
|
352
|
+
children: "Continue"
|
|
353
|
+
}
|
|
354
|
+
)
|
|
355
|
+
] });
|
|
356
|
+
}
|
|
357
|
+
function AuthStep({
|
|
358
|
+
appName,
|
|
359
|
+
logoUrl,
|
|
360
|
+
identifier,
|
|
361
|
+
providers,
|
|
362
|
+
password,
|
|
363
|
+
error,
|
|
364
|
+
isLoading,
|
|
365
|
+
onPasswordChange,
|
|
366
|
+
onCredentialSubmit,
|
|
367
|
+
onExternalLogin,
|
|
368
|
+
onBack
|
|
369
|
+
}) {
|
|
370
|
+
const external = providers.filter((p) => p.type === "external");
|
|
371
|
+
const hasLocal = providers.some((p) => p.type === "local");
|
|
372
|
+
const hasBoth = external.length > 0 && hasLocal;
|
|
373
|
+
return /* @__PURE__ */ jsxs2("div", { className: "space-y-6", children: [
|
|
374
|
+
/* @__PURE__ */ jsx2(BrandHeader, { appName, logoUrl }),
|
|
375
|
+
/* @__PURE__ */ jsxs2("div", { children: [
|
|
376
|
+
/* @__PURE__ */ jsxs2(
|
|
377
|
+
"button",
|
|
378
|
+
{
|
|
379
|
+
type: "button",
|
|
380
|
+
onClick: onBack,
|
|
381
|
+
className: "flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground transition-colors mb-4",
|
|
382
|
+
children: [
|
|
383
|
+
/* @__PURE__ */ jsx2("span", { "aria-hidden": true, children: "\u2190" }),
|
|
384
|
+
" Back"
|
|
385
|
+
]
|
|
386
|
+
}
|
|
387
|
+
),
|
|
388
|
+
/* @__PURE__ */ jsx2("h1", { className: "text-2xl font-semibold text-foreground leading-tight", children: "Welcome back" }),
|
|
389
|
+
/* @__PURE__ */ jsx2("p", { className: "mt-1 text-sm text-muted-foreground", children: identifier })
|
|
390
|
+
] }),
|
|
391
|
+
error ? /* @__PURE__ */ jsx2(ErrorAlert, { message: error }) : null,
|
|
392
|
+
hasLocal ? /* @__PURE__ */ jsxs2("form", { onSubmit: onCredentialSubmit, className: "space-y-4", children: [
|
|
393
|
+
/* @__PURE__ */ jsxs2("div", { children: [
|
|
394
|
+
/* @__PURE__ */ jsx2(Label, { htmlFor: "password", className: "mb-1.5 block", children: "Password" }),
|
|
395
|
+
/* @__PURE__ */ jsx2(
|
|
396
|
+
Input,
|
|
397
|
+
{
|
|
398
|
+
id: "password",
|
|
399
|
+
type: "password",
|
|
400
|
+
value: password,
|
|
401
|
+
onChange: (e) => onPasswordChange(e.target.value),
|
|
402
|
+
required: true,
|
|
403
|
+
autoFocus: true,
|
|
404
|
+
autoComplete: "current-password",
|
|
405
|
+
placeholder: "Enter your password"
|
|
406
|
+
}
|
|
407
|
+
)
|
|
408
|
+
] }),
|
|
214
409
|
/* @__PURE__ */ jsx2(
|
|
215
|
-
|
|
410
|
+
PrimaryButton,
|
|
216
411
|
{
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
placeholder: "Enter your password"
|
|
412
|
+
type: "submit",
|
|
413
|
+
disabled: isLoading,
|
|
414
|
+
loading: isLoading,
|
|
415
|
+
loadingText: "Signing in...",
|
|
416
|
+
children: "Sign in"
|
|
223
417
|
}
|
|
224
418
|
)
|
|
225
|
-
] }),
|
|
226
|
-
/* @__PURE__ */ jsx2(
|
|
419
|
+
] }) : null,
|
|
420
|
+
hasBoth ? /* @__PURE__ */ jsx2(Separator, {}) : null,
|
|
421
|
+
external.length > 0 ? /* @__PURE__ */ jsx2("div", { className: "space-y-2", children: external.map((p) => /* @__PURE__ */ jsx2(
|
|
422
|
+
SsoButton,
|
|
423
|
+
{
|
|
424
|
+
label: p.label,
|
|
425
|
+
onClick: () => onExternalLogin(p)
|
|
426
|
+
},
|
|
427
|
+
p.name
|
|
428
|
+
)) }) : null,
|
|
429
|
+
providers.length === 0 ? /* @__PURE__ */ jsx2("p", { className: "text-sm text-muted-foreground text-center py-2", children: "No login methods available." }) : null
|
|
227
430
|
] });
|
|
228
431
|
}
|
|
229
|
-
function
|
|
230
|
-
return
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
432
|
+
function resolveOAuthProviders(config) {
|
|
433
|
+
return config?.features.oauth && config.oauth?.providers ? Object.entries(config.oauth.providers).filter(([, p]) => p.enabled) : [];
|
|
434
|
+
}
|
|
435
|
+
function CredentialsStep({
|
|
436
|
+
appName,
|
|
437
|
+
logoUrl,
|
|
438
|
+
config,
|
|
439
|
+
identifier,
|
|
440
|
+
password,
|
|
441
|
+
error,
|
|
442
|
+
isLoading,
|
|
443
|
+
onIdentifierChange,
|
|
444
|
+
onPasswordChange,
|
|
445
|
+
onSubmit,
|
|
446
|
+
onExternalLogin
|
|
447
|
+
}) {
|
|
448
|
+
const oauthProviders = resolveOAuthProviders(config);
|
|
449
|
+
const hasBoth = config?.features.local_auth && oauthProviders.length > 0;
|
|
450
|
+
return /* @__PURE__ */ jsxs2("div", { className: "space-y-6", children: [
|
|
451
|
+
/* @__PURE__ */ jsx2(BrandHeader, { appName, logoUrl }),
|
|
452
|
+
/* @__PURE__ */ jsx2("h1", { className: "text-2xl font-semibold text-foreground leading-tight text-center", children: "Welcome" }),
|
|
453
|
+
error ? /* @__PURE__ */ jsx2(ErrorAlert, { message: error }) : null,
|
|
454
|
+
config?.features.local_auth ? /* @__PURE__ */ jsxs2("form", { onSubmit, className: "space-y-4", children: [
|
|
455
|
+
/* @__PURE__ */ jsxs2("div", { children: [
|
|
456
|
+
/* @__PURE__ */ jsx2(Label, { htmlFor: "identifier", className: "mb-1.5 block", children: "Email or username" }),
|
|
457
|
+
/* @__PURE__ */ jsx2(
|
|
458
|
+
Input,
|
|
459
|
+
{
|
|
460
|
+
id: "identifier",
|
|
461
|
+
type: "text",
|
|
462
|
+
value: identifier,
|
|
463
|
+
onChange: (e) => onIdentifierChange(e.target.value),
|
|
464
|
+
required: true,
|
|
465
|
+
autoFocus: true,
|
|
466
|
+
autoComplete: "email",
|
|
467
|
+
placeholder: "you@company.com"
|
|
468
|
+
}
|
|
469
|
+
)
|
|
470
|
+
] }),
|
|
471
|
+
/* @__PURE__ */ jsxs2("div", { children: [
|
|
472
|
+
/* @__PURE__ */ jsx2(Label, { htmlFor: "password", className: "mb-1.5 block", children: "Password" }),
|
|
473
|
+
/* @__PURE__ */ jsx2(
|
|
474
|
+
Input,
|
|
475
|
+
{
|
|
476
|
+
id: "password",
|
|
477
|
+
type: "password",
|
|
478
|
+
value: password,
|
|
479
|
+
onChange: (e) => onPasswordChange(e.target.value),
|
|
480
|
+
required: true,
|
|
481
|
+
autoComplete: "current-password",
|
|
482
|
+
placeholder: "Enter your password"
|
|
483
|
+
}
|
|
484
|
+
)
|
|
485
|
+
] }),
|
|
486
|
+
/* @__PURE__ */ jsx2(
|
|
487
|
+
PrimaryButton,
|
|
238
488
|
{
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
)
|
|
489
|
+
type: "submit",
|
|
490
|
+
disabled: isLoading,
|
|
491
|
+
loading: isLoading,
|
|
492
|
+
loadingText: "Signing in...",
|
|
493
|
+
children: "Sign in"
|
|
494
|
+
}
|
|
495
|
+
)
|
|
496
|
+
] }) : null,
|
|
497
|
+
hasBoth ? /* @__PURE__ */ jsx2(Separator, {}) : null,
|
|
498
|
+
oauthProviders.length > 0 ? /* @__PURE__ */ jsx2("div", { className: "space-y-2", children: oauthProviders.map(([key, p]) => /* @__PURE__ */ jsx2(
|
|
499
|
+
SsoButton,
|
|
500
|
+
{
|
|
501
|
+
label: `Continue with ${p.name}`,
|
|
502
|
+
onClick: () => onExternalLogin({ type: "external", name: key, label: p.name })
|
|
503
|
+
},
|
|
504
|
+
key
|
|
505
|
+
)) }) : null
|
|
506
|
+
] });
|
|
507
|
+
}
|
|
508
|
+
function RedirectingMessage() {
|
|
509
|
+
return /* @__PURE__ */ jsxs2("div", { className: "flex flex-col items-center justify-center min-h-screen bg-muted gap-3 text-muted-foreground", children: [
|
|
510
|
+
/* @__PURE__ */ jsx2(Spinner, { size: "lg" }),
|
|
511
|
+
/* @__PURE__ */ jsx2("p", { className: "text-sm", children: "Redirecting..." })
|
|
512
|
+
] });
|
|
513
|
+
}
|
|
514
|
+
function ErrorAlert({ message }) {
|
|
515
|
+
return /* @__PURE__ */ jsx2(
|
|
516
|
+
Alert2,
|
|
517
|
+
{
|
|
518
|
+
variant: "destructive",
|
|
519
|
+
className: "bg-destructive/10 px-3 py-2.5 text-sm",
|
|
520
|
+
children: message
|
|
521
|
+
}
|
|
522
|
+
);
|
|
523
|
+
}
|
|
524
|
+
function Separator() {
|
|
525
|
+
return /* @__PURE__ */ jsxs2("div", { className: "relative", children: [
|
|
526
|
+
/* @__PURE__ */ jsx2("div", { className: "absolute inset-0 flex items-center", children: /* @__PURE__ */ jsx2("div", { className: "w-full border-t border-border" }) }),
|
|
527
|
+
/* @__PURE__ */ jsx2("div", { className: "relative flex justify-center text-xs", children: /* @__PURE__ */ jsx2("span", { className: "bg-card px-2 text-muted-foreground", children: "or continue with" }) })
|
|
249
528
|
] });
|
|
250
529
|
}
|
|
530
|
+
function PrimaryButton({
|
|
531
|
+
type = "button",
|
|
532
|
+
disabled,
|
|
533
|
+
loading,
|
|
534
|
+
loadingText,
|
|
535
|
+
children,
|
|
536
|
+
onClick
|
|
537
|
+
}) {
|
|
538
|
+
return /* @__PURE__ */ jsxs2(
|
|
539
|
+
Button2,
|
|
540
|
+
{
|
|
541
|
+
type,
|
|
542
|
+
size: "lg",
|
|
543
|
+
disabled,
|
|
544
|
+
onClick,
|
|
545
|
+
className: "w-full",
|
|
546
|
+
children: [
|
|
547
|
+
loading ? /* @__PURE__ */ jsx2(Spinner, { size: "sm" }) : null,
|
|
548
|
+
loading && loadingText ? loadingText : children
|
|
549
|
+
]
|
|
550
|
+
}
|
|
551
|
+
);
|
|
552
|
+
}
|
|
553
|
+
function SsoButton({ label, onClick }) {
|
|
554
|
+
return /* @__PURE__ */ jsx2(
|
|
555
|
+
Button2,
|
|
556
|
+
{
|
|
557
|
+
type: "button",
|
|
558
|
+
variant: "outline",
|
|
559
|
+
size: "lg",
|
|
560
|
+
onClick,
|
|
561
|
+
className: "w-full",
|
|
562
|
+
children: label
|
|
563
|
+
}
|
|
564
|
+
);
|
|
565
|
+
}
|
|
251
566
|
|
|
252
567
|
// src/auth/ProtectedRoute.tsx
|
|
253
568
|
import { Navigate, useLocation as useLocation2 } from "react-router-dom";
|
|
254
569
|
import { useEffect as useEffect2, useRef, useState as useState3 } from "react";
|
|
255
|
-
import { Spinner } from "@petrarca/sonnet-ui";
|
|
256
|
-
import { Fragment, jsx as jsx3 } from "react/jsx-runtime";
|
|
570
|
+
import { Spinner as Spinner2 } from "@petrarca/sonnet-ui";
|
|
571
|
+
import { Fragment as Fragment2, jsx as jsx3 } from "react/jsx-runtime";
|
|
257
572
|
function ProtectedRoute({
|
|
258
573
|
children,
|
|
259
574
|
authEnabled,
|
|
@@ -261,7 +576,8 @@ function ProtectedRoute({
|
|
|
261
576
|
isAuthenticated,
|
|
262
577
|
accessToken,
|
|
263
578
|
activeTenantId,
|
|
264
|
-
initializeAuth
|
|
579
|
+
initializeAuth,
|
|
580
|
+
requiresOrgSelection = true
|
|
265
581
|
}) {
|
|
266
582
|
const location = useLocation2();
|
|
267
583
|
const [initializing, setInitializing] = useState3(true);
|
|
@@ -270,10 +586,10 @@ function ProtectedRoute({
|
|
|
270
586
|
initRef.current().finally(() => setInitializing(false));
|
|
271
587
|
}, []);
|
|
272
588
|
if (authConfigLoading || initializing) {
|
|
273
|
-
return /* @__PURE__ */ jsx3("div", { className: "flex items-center justify-center min-h-screen", children: /* @__PURE__ */ jsx3(
|
|
589
|
+
return /* @__PURE__ */ jsx3("div", { className: "flex items-center justify-center min-h-screen", children: /* @__PURE__ */ jsx3(Spinner2, { size: "lg" }) });
|
|
274
590
|
}
|
|
275
591
|
if (!authEnabled) {
|
|
276
|
-
return /* @__PURE__ */ jsx3(
|
|
592
|
+
return /* @__PURE__ */ jsx3(Fragment2, { children });
|
|
277
593
|
}
|
|
278
594
|
if (!isAuthenticated || !accessToken) {
|
|
279
595
|
return /* @__PURE__ */ jsx3(
|
|
@@ -285,21 +601,21 @@ function ProtectedRoute({
|
|
|
285
601
|
}
|
|
286
602
|
);
|
|
287
603
|
}
|
|
288
|
-
if (!activeTenantId) {
|
|
604
|
+
if (requiresOrgSelection && !activeTenantId) {
|
|
289
605
|
return /* @__PURE__ */ jsx3(
|
|
290
606
|
Navigate,
|
|
291
607
|
{
|
|
292
608
|
to: "/login",
|
|
293
|
-
state: { from: location, step: "
|
|
609
|
+
state: { from: location, step: "org-selection" },
|
|
294
610
|
replace: true
|
|
295
611
|
}
|
|
296
612
|
);
|
|
297
613
|
}
|
|
298
|
-
return /* @__PURE__ */ jsx3(
|
|
614
|
+
return /* @__PURE__ */ jsx3(Fragment2, { children });
|
|
299
615
|
}
|
|
300
616
|
export {
|
|
301
617
|
Login,
|
|
302
|
-
|
|
303
|
-
|
|
618
|
+
OrgSelection,
|
|
619
|
+
ProtectedRoute
|
|
304
620
|
};
|
|
305
621
|
//# sourceMappingURL=index.js.map
|