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