@cosmicdrift/kumiko-samples 0.341.0 → 0.343.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/package.json +1 -1
- package/packages/bundled-features/package.json +1 -1
- package/packages/bundled-features/src/audit/changes.json +6 -0
- package/packages/bundled-features/src/audit/constants.ts +9 -0
- package/packages/bundled-features/src/audit/escape-hatch-audit-sink.ts +1 -0
- package/packages/bundled-features/src/audit/feature.ts +47 -6
- package/packages/bundled-features/src/audit/run-escape-hatch-retention.ts +144 -0
- package/packages/bundled-features/src/auth-email-password/bootstrap.ts +18 -2
- package/packages/bundled-features/src/auth-email-password/changes.json +19 -0
- package/packages/bundled-features/src/auth-email-password/email-templates.ts +76 -16
- package/packages/bundled-features/src/auth-email-password/handlers/invite-create.write.ts +4 -1
- package/packages/bundled-features/src/auth-email-password/handlers/signup-request.write.ts +4 -1
- package/packages/bundled-features/src/auth-email-password/handlers/token-request-handler.ts +3 -0
- package/packages/bundled-features/src/auth-email-password/magic-link-mail.ts +4 -0
- package/packages/bundled-features/src/auth-email-password/web/auth-form-primitives.tsx +24 -6
- package/packages/bundled-features/src/auth-email-password/web/confirm-account-unlock-screen.tsx +12 -18
- package/packages/bundled-features/src/auth-email-password/web/forgot-password-screen.tsx +4 -4
- package/packages/bundled-features/src/auth-email-password/web/invite-accept-screen.tsx +65 -69
- package/packages/bundled-features/src/auth-email-password/web/login-screen.tsx +93 -95
- package/packages/bundled-features/src/auth-email-password/web/request-account-unlock-screen.tsx +4 -4
- package/packages/bundled-features/src/auth-email-password/web/reset-password-screen.tsx +8 -10
- package/packages/bundled-features/src/auth-email-password/web/session-bootstrap-error.tsx +13 -15
- package/packages/bundled-features/src/auth-email-password/web/signup-complete-screen.tsx +43 -49
- package/packages/bundled-features/src/auth-email-password/web/signup-screen.tsx +4 -4
- package/packages/bundled-features/src/auth-email-password/web/verify-email-screen.tsx +12 -18
- package/packages/bundled-features/src/auth-mfa/web/mfa-setup-preauth-screen.tsx +83 -85
- package/packages/bundled-features/src/auth-mfa/web/mfa-verify-screen.tsx +24 -26
- package/packages/bundled-features/src/channel-discord/feature.ts +1 -0
- package/packages/bundled-features/src/channel-email/changes.json +6 -0
- package/packages/bundled-features/src/channel-email/email-channel.ts +8 -1
- package/packages/bundled-features/src/channel-email/feature.ts +1 -0
- package/packages/bundled-features/src/channel-email/pii-guard.ts +6 -5
- package/packages/bundled-features/src/channel-email/smtp-transport.ts +27 -1
- package/packages/bundled-features/src/channel-email/types.ts +4 -0
- package/packages/bundled-features/src/channel-in-app/feature.ts +1 -0
- package/packages/bundled-features/src/channel-push/feature.ts +1 -0
- package/packages/bundled-features/src/channel-slack/feature.ts +1 -0
- package/packages/bundled-features/src/channel-teams/changes.json +8 -0
- package/packages/bundled-features/src/channel-teams/feature.ts +1 -0
- package/packages/bundled-features/src/channel-teams/teams-channel.ts +16 -0
- package/packages/bundled-features/src/channel-telegram/feature.ts +1 -0
- package/packages/bundled-features/src/delivery/changes.json +32 -0
- package/packages/bundled-features/src/delivery/chat-webhook-channel.ts +7 -2
- package/packages/bundled-features/src/delivery/chat-webhook-sender.ts +60 -14
- package/packages/bundled-features/src/delivery/constants.ts +4 -0
- package/packages/bundled-features/src/delivery/delivery-service.ts +70 -5
- package/packages/bundled-features/src/delivery/events.ts +15 -1
- package/packages/bundled-features/src/delivery/feature.ts +32 -4
- package/packages/bundled-features/src/delivery/handlers/log.query.ts +65 -20
- package/packages/bundled-features/src/delivery/i18n.ts +55 -0
- package/packages/bundled-features/src/delivery/index.ts +2 -0
- package/packages/bundled-features/src/delivery/jobs.ts +43 -20
- package/packages/bundled-features/src/delivery/notify-factory.ts +19 -0
- package/packages/bundled-features/src/delivery/public-names.ts +4 -0
- package/packages/bundled-features/src/delivery/tables.ts +3 -0
- package/packages/bundled-features/src/delivery/testing.ts +5 -9
- package/packages/bundled-features/src/delivery/types.ts +6 -2
- package/packages/bundled-features/src/delivery/web/client-plugin.tsx +16 -1
- package/packages/bundled-features/src/delivery/web/delivery-channel-cell.tsx +13 -0
- package/packages/bundled-features/src/delivery/web/delivery-error-cell.tsx +17 -0
- package/packages/bundled-features/src/delivery/web/delivery-status-cell.tsx +10 -3
- package/packages/bundled-features/src/delivery/web/delivery-time-cell.tsx +8 -0
- package/packages/bundled-features/src/delivery/web/delivery-type-cell.tsx +20 -0
- package/packages/bundled-features/src/jobs/changes.json +6 -0
- package/packages/bundled-features/src/secrets/changes.json +6 -0
- package/packages/bundled-features/src/secrets/constants.ts +10 -0
- package/packages/bundled-features/src/secrets/feature.ts +4 -0
- package/packages/bundled-features/src/secrets/i18n.ts +5 -3
- package/packages/bundled-features/src/secrets/index.ts +8 -1
- package/packages/bundled-features/src/secrets/write-gate.ts +17 -5
- package/packages/bundled-features/src/shared/index.ts +6 -0
- package/packages/bundled-features/src/shared/redact.ts +27 -0
- package/packages/bundled-features/src/step-dispatcher/feature.ts +2 -6
- package/packages/bundled-features/src/step-dispatcher/mail-runner.ts +0 -6
- package/packages/bundled-features/src/tenant/web/member-roles-cell.tsx +1 -1
- package/packages/bundled-features/src/tenant/web/member-status-cell.tsx +1 -1
- package/packages/bundled-features/src/tenant-lifecycle/feature.ts +12 -2
- package/packages/bundled-features/src/tenant-lifecycle/run-tenant-destroy.ts +23 -8
- package/packages/bundled-features/src/user/index.ts +1 -0
- package/packages/bundled-features/src/user/resolve-display-names.ts +35 -0
- package/packages/bundled-features/src/waitlist/changes.json +6 -0
- package/packages/bundled-features/src/waitlist/handlers/submit.write.ts +1 -1
- package/packages/bundled-features/src/waitlist/options.ts +3 -2
- package/samples/apps/use-all-bundled/feature-manifest.json +27 -2
- /package/packages/bundled-features/src/{tenant → shared}/web/translate-or-raw.ts +0 -0
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// die früheren authButtonClass/authMutedLinkClass sind dorthin gewandert.
|
|
16
16
|
|
|
17
17
|
import { usePrimitives } from "@cosmicdrift/kumiko-renderer";
|
|
18
|
-
import { BareFormProvider } from "@cosmicdrift/kumiko-renderer-web";
|
|
18
|
+
import { BareFormProvider, cn } from "@cosmicdrift/kumiko-renderer-web";
|
|
19
19
|
import { createContext, type ReactNode, useContext, useEffect, useState } from "react";
|
|
20
20
|
|
|
21
21
|
// Wrappt die zentrierte Auth-Card in ihre Umgebung. Default = Fullscreen-
|
|
@@ -47,25 +47,41 @@ export function useAuthShell(): AuthShellRenderer | null {
|
|
|
47
47
|
export type AuthCardProps = {
|
|
48
48
|
readonly title?: string;
|
|
49
49
|
readonly subtitle?: ReactNode;
|
|
50
|
+
/** Merged onto the Card (default `w-full max-w-sm`). */
|
|
51
|
+
readonly className?: string;
|
|
52
|
+
readonly headerClassName?: string;
|
|
53
|
+
readonly titleClassName?: string;
|
|
54
|
+
/** Merged onto the body wrapper (default `p-6 pt-0 flex flex-col gap-4`). */
|
|
55
|
+
readonly bodyClassName?: string;
|
|
50
56
|
readonly children: ReactNode;
|
|
51
57
|
};
|
|
52
58
|
|
|
53
59
|
// kumiko-lint-ignore no-custom-primitives Wraps usePrimitives().Card, pure domain name for the auth-screen chrome, not a primitive reimplementation
|
|
54
|
-
export function AuthCard({
|
|
60
|
+
export function AuthCard({
|
|
61
|
+
title,
|
|
62
|
+
subtitle,
|
|
63
|
+
className,
|
|
64
|
+
headerClassName,
|
|
65
|
+
titleClassName,
|
|
66
|
+
bodyClassName,
|
|
67
|
+
children,
|
|
68
|
+
}: AuthCardProps): ReactNode {
|
|
55
69
|
const { Card } = usePrimitives();
|
|
56
70
|
const shell = useAuthShell() ?? defaultAuthShell;
|
|
57
71
|
// h1 (Seiten-Hauptüberschrift) via Header-Slot erhalten — die Card-Default-
|
|
58
72
|
// Header wäre h3. padded:false = Form sitzt randlos wie bisher (bare form).
|
|
59
73
|
const card = (
|
|
60
74
|
<Card
|
|
61
|
-
className="w-full max-w-sm"
|
|
75
|
+
className={cn("w-full max-w-sm", className)}
|
|
62
76
|
options={{ padded: false }}
|
|
63
77
|
slots={{
|
|
64
78
|
header:
|
|
65
79
|
title !== undefined || subtitle !== undefined ? (
|
|
66
|
-
<div className="flex flex-col space-y-1.5 p-6 pb-4">
|
|
80
|
+
<div className={cn("flex flex-col space-y-1.5 p-6 pb-4", headerClassName)}>
|
|
67
81
|
{title !== undefined && (
|
|
68
|
-
<h1 className="text-xl font-semibold tracking-tight"
|
|
82
|
+
<h1 className={cn("text-xl font-semibold tracking-tight", titleClassName)}>
|
|
83
|
+
{title}
|
|
84
|
+
</h1>
|
|
69
85
|
)}
|
|
70
86
|
{subtitle !== undefined && (
|
|
71
87
|
<p className="text-sm text-muted-foreground">{subtitle}</p>
|
|
@@ -74,7 +90,9 @@ export function AuthCard({ title, subtitle, children }: AuthCardProps): ReactNod
|
|
|
74
90
|
) : undefined,
|
|
75
91
|
}}
|
|
76
92
|
>
|
|
77
|
-
<BareFormProvider>
|
|
93
|
+
<BareFormProvider>
|
|
94
|
+
<div className={cn("p-6 pt-0 flex flex-col gap-4", bodyClassName)}>{children}</div>
|
|
95
|
+
</BareFormProvider>
|
|
78
96
|
</Card>
|
|
79
97
|
);
|
|
80
98
|
return shell(card);
|
package/packages/bundled-features/src/auth-email-password/web/confirm-account-unlock-screen.tsx
CHANGED
|
@@ -47,12 +47,10 @@ export function ConfirmAccountUnlockScreen({
|
|
|
47
47
|
if (status === "missing-token") {
|
|
48
48
|
return (
|
|
49
49
|
<AuthCard title={title ?? t("auth.unlockAccount.errorTitle")}>
|
|
50
|
-
<
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
</Link>
|
|
55
|
-
</div>
|
|
50
|
+
<p className="text-sm text-muted-foreground">{t("auth.unlockAccount.missingToken")}</p>
|
|
51
|
+
<Link href={loginHref} variant="muted">
|
|
52
|
+
{t("auth.unlockAccount.goToLogin")}
|
|
53
|
+
</Link>
|
|
56
54
|
</AuthCard>
|
|
57
55
|
);
|
|
58
56
|
}
|
|
@@ -72,12 +70,10 @@ export function ConfirmAccountUnlockScreen({
|
|
|
72
70
|
if (status === "success") {
|
|
73
71
|
return (
|
|
74
72
|
<AuthCard title={title ?? t("auth.unlockAccount.successTitle")}>
|
|
75
|
-
<
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
</Link>
|
|
80
|
-
</div>
|
|
73
|
+
<p className="text-sm text-muted-foreground">{t("auth.unlockAccount.successBody")}</p>
|
|
74
|
+
<Link href={loginHref} variant="button">
|
|
75
|
+
{t("auth.unlockAccount.goToLogin")}
|
|
76
|
+
</Link>
|
|
81
77
|
</AuthCard>
|
|
82
78
|
);
|
|
83
79
|
}
|
|
@@ -85,12 +81,10 @@ export function ConfirmAccountUnlockScreen({
|
|
|
85
81
|
// status === "error"
|
|
86
82
|
return (
|
|
87
83
|
<AuthCard title={title ?? t("auth.unlockAccount.errorTitle")}>
|
|
88
|
-
<
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
</Link>
|
|
93
|
-
</div>
|
|
84
|
+
<p className="text-sm text-muted-foreground">{t("auth.unlockAccount.errorBody")}</p>
|
|
85
|
+
<Link href={loginHref} variant="muted">
|
|
86
|
+
{t("auth.unlockAccount.goToLogin")}
|
|
87
|
+
</Link>
|
|
94
88
|
</AuthCard>
|
|
95
89
|
);
|
|
96
90
|
}
|
|
@@ -72,7 +72,7 @@ export function ForgotPasswordScreen({
|
|
|
72
72
|
return (
|
|
73
73
|
<AuthCard title={effectiveTitle} subtitle={subtitle}>
|
|
74
74
|
{done ? (
|
|
75
|
-
|
|
75
|
+
<>
|
|
76
76
|
<Banner variant="info">
|
|
77
77
|
<p className="font-medium text-foreground">{t("auth.forgotPassword.successTitle")}</p>
|
|
78
78
|
<p className="mt-1">{t("auth.forgotPassword.successBody")}</p>
|
|
@@ -80,9 +80,9 @@ export function ForgotPasswordScreen({
|
|
|
80
80
|
<Link href={loginHref} variant="muted">
|
|
81
81
|
{t("auth.forgotPassword.backToLogin")}
|
|
82
82
|
</Link>
|
|
83
|
-
|
|
83
|
+
</>
|
|
84
84
|
) : (
|
|
85
|
-
|
|
85
|
+
<>
|
|
86
86
|
<p className="text-sm text-muted-foreground">{t("auth.forgotPassword.intro")}</p>
|
|
87
87
|
<Form onSubmit={onSubmit}>
|
|
88
88
|
<Field id="forgot-email" label={t("auth.forgotPassword.email")} required>
|
|
@@ -104,7 +104,7 @@ export function ForgotPasswordScreen({
|
|
|
104
104
|
<Link href={loginHref} variant="muted" className="self-center">
|
|
105
105
|
{t("auth.forgotPassword.backToLogin")}
|
|
106
106
|
</Link>
|
|
107
|
-
|
|
107
|
+
</>
|
|
108
108
|
)}
|
|
109
109
|
</AuthCard>
|
|
110
110
|
);
|
|
@@ -186,87 +186,83 @@ export function InviteAcceptScreen({
|
|
|
186
186
|
if (token === "") {
|
|
187
187
|
return (
|
|
188
188
|
<AuthCard title={effectiveTitle}>
|
|
189
|
-
<
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
</Link>
|
|
194
|
-
</div>
|
|
189
|
+
<p className="text-sm text-muted-foreground">{t("auth.inviteAccept.missingToken")}</p>
|
|
190
|
+
<Link href={loginHref} variant="muted">
|
|
191
|
+
{t("auth.inviteAccept.goToLogin")}
|
|
192
|
+
</Link>
|
|
195
193
|
</AuthCard>
|
|
196
194
|
);
|
|
197
195
|
}
|
|
198
196
|
|
|
199
197
|
return (
|
|
200
198
|
<AuthCard title={effectiveTitle}>
|
|
201
|
-
<
|
|
202
|
-
<p className="text-sm text-muted-foreground">{t("auth.inviteAccept.intro")}</p>
|
|
199
|
+
<p className="text-sm text-muted-foreground">{t("auth.inviteAccept.intro")}</p>
|
|
203
200
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
<Input
|
|
228
|
-
kind="email"
|
|
229
|
-
id="invite-email"
|
|
230
|
-
name="invite-email"
|
|
231
|
-
value={email}
|
|
232
|
-
onChange={setEmail}
|
|
233
|
-
disabled={submitting}
|
|
234
|
-
required
|
|
235
|
-
autoComplete="email"
|
|
236
|
-
/>
|
|
237
|
-
</Field>
|
|
238
|
-
)}
|
|
239
|
-
<Field id="invite-password" label={t("auth.inviteAccept.password")} required>
|
|
201
|
+
{mode === "loggedin" ? (
|
|
202
|
+
<Form onSubmit={onSubmit}>
|
|
203
|
+
<p className="text-sm">
|
|
204
|
+
{t("auth.inviteAccept.loggedInAs", { email: session.user?.email ?? "" })}
|
|
205
|
+
</p>
|
|
206
|
+
{error !== null && <Banner variant="error">{error}</Banner>}
|
|
207
|
+
<Button type="submit" loading={submitting} disabled={submitting}>
|
|
208
|
+
{submitting ? t("auth.inviteAccept.submitting") : t("auth.inviteAccept.acceptButton")}
|
|
209
|
+
</Button>
|
|
210
|
+
<Button
|
|
211
|
+
type="button"
|
|
212
|
+
variant="secondary"
|
|
213
|
+
onClick={() => {
|
|
214
|
+
setMode("anon-existing");
|
|
215
|
+
}}
|
|
216
|
+
>
|
|
217
|
+
{t("auth.inviteAccept.useOtherAccount")}
|
|
218
|
+
</Button>
|
|
219
|
+
</Form>
|
|
220
|
+
) : (
|
|
221
|
+
<Form onSubmit={onSubmit}>
|
|
222
|
+
{mode === "anon-existing" && (
|
|
223
|
+
<Field id="invite-email" label={t("auth.inviteAccept.email")} required>
|
|
240
224
|
<Input
|
|
241
|
-
kind="
|
|
242
|
-
id="invite-
|
|
243
|
-
name="invite-
|
|
244
|
-
value={
|
|
245
|
-
onChange={
|
|
225
|
+
kind="email"
|
|
226
|
+
id="invite-email"
|
|
227
|
+
name="invite-email"
|
|
228
|
+
value={email}
|
|
229
|
+
onChange={setEmail}
|
|
246
230
|
disabled={submitting}
|
|
247
231
|
required
|
|
248
|
-
autoComplete=
|
|
232
|
+
autoComplete="email"
|
|
249
233
|
/>
|
|
250
234
|
</Field>
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
</
|
|
268
|
-
|
|
269
|
-
|
|
235
|
+
)}
|
|
236
|
+
<Field id="invite-password" label={t("auth.inviteAccept.password")} required>
|
|
237
|
+
<Input
|
|
238
|
+
kind="password"
|
|
239
|
+
id="invite-password"
|
|
240
|
+
name="invite-password"
|
|
241
|
+
value={password}
|
|
242
|
+
onChange={setPassword}
|
|
243
|
+
disabled={submitting}
|
|
244
|
+
required
|
|
245
|
+
autoComplete={mode === "anon-existing" ? "current-password" : "new-password"}
|
|
246
|
+
/>
|
|
247
|
+
</Field>
|
|
248
|
+
{error !== null && <Banner variant="error">{error}</Banner>}
|
|
249
|
+
<Button type="submit" loading={submitting} disabled={submitting}>
|
|
250
|
+
{submitting ? t("auth.inviteAccept.submitting") : t("auth.inviteAccept.submit")}
|
|
251
|
+
</Button>
|
|
252
|
+
<Button
|
|
253
|
+
type="button"
|
|
254
|
+
variant="secondary"
|
|
255
|
+
onClick={() => {
|
|
256
|
+
setMode(mode === "anon-existing" ? "anon-new" : "anon-existing");
|
|
257
|
+
setError(null);
|
|
258
|
+
}}
|
|
259
|
+
>
|
|
260
|
+
{mode === "anon-existing"
|
|
261
|
+
? t("auth.inviteAccept.toggleNew")
|
|
262
|
+
: t("auth.inviteAccept.toggleExisting")}
|
|
263
|
+
</Button>
|
|
264
|
+
</Form>
|
|
265
|
+
)}
|
|
270
266
|
</AuthCard>
|
|
271
267
|
);
|
|
272
268
|
}
|
|
@@ -195,104 +195,102 @@ export function LoginScreen({
|
|
|
195
195
|
|
|
196
196
|
return (
|
|
197
197
|
<AuthCard title={effectiveTitle} subtitle={subtitle}>
|
|
198
|
-
<
|
|
199
|
-
<
|
|
200
|
-
<
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
<
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
<
|
|
231
|
-
<
|
|
232
|
-
|
|
233
|
-
{(
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
email === failedLoginEmail && (
|
|
241
|
-
<span className="self-start">
|
|
242
|
-
<Button
|
|
243
|
-
variant="link"
|
|
244
|
-
onClick={() => void onResend()}
|
|
245
|
-
disabled={resendStatus.kind === "sending"}
|
|
246
|
-
>
|
|
247
|
-
{resendStatus.kind === "sending"
|
|
248
|
-
? t("auth.login.submitting")
|
|
249
|
-
: t("auth.login.resendVerification")}
|
|
250
|
-
</Button>
|
|
251
|
-
</span>
|
|
252
|
-
)}
|
|
253
|
-
{error.reason === "account_locked" && unlockAccountHref !== undefined && (
|
|
198
|
+
<Form onSubmit={onSubmit}>
|
|
199
|
+
<Field id="login-email" label={t("auth.login.email")} required>
|
|
200
|
+
<Input
|
|
201
|
+
kind="email"
|
|
202
|
+
id="login-email"
|
|
203
|
+
name="login-email"
|
|
204
|
+
value={email}
|
|
205
|
+
onChange={setEmail}
|
|
206
|
+
disabled={submitting}
|
|
207
|
+
required
|
|
208
|
+
autoComplete="email"
|
|
209
|
+
/>
|
|
210
|
+
</Field>
|
|
211
|
+
<Field id="login-password" label={t("auth.login.password")} required>
|
|
212
|
+
<Input
|
|
213
|
+
kind="password"
|
|
214
|
+
id="login-password"
|
|
215
|
+
name="login-password"
|
|
216
|
+
value={password}
|
|
217
|
+
onChange={setPassword}
|
|
218
|
+
disabled={submitting}
|
|
219
|
+
required
|
|
220
|
+
autoComplete="current-password"
|
|
221
|
+
/>
|
|
222
|
+
</Field>
|
|
223
|
+
{session.signedOutReason === "session-ended" && (
|
|
224
|
+
<Banner variant="info">{t("auth.login.sessionEnded")}</Banner>
|
|
225
|
+
)}
|
|
226
|
+
{resendStatus.kind === "success" ? (
|
|
227
|
+
<Banner variant="info">{t("auth.login.resendSuccess")}</Banner>
|
|
228
|
+
) : error !== null ? (
|
|
229
|
+
<Banner variant="error">
|
|
230
|
+
<div className="flex flex-col gap-1">
|
|
231
|
+
<span>
|
|
232
|
+
{(() => {
|
|
233
|
+
const { key, params } = reasonToKey(error);
|
|
234
|
+
return t(key, params);
|
|
235
|
+
})()}
|
|
236
|
+
</span>
|
|
237
|
+
{error.reason === "email_not_verified" &&
|
|
238
|
+
email.trim().length > 0 &&
|
|
239
|
+
email === failedLoginEmail && (
|
|
254
240
|
<span className="self-start">
|
|
255
|
-
<
|
|
256
|
-
|
|
257
|
-
|
|
241
|
+
<Button
|
|
242
|
+
variant="link"
|
|
243
|
+
onClick={() => void onResend()}
|
|
244
|
+
disabled={resendStatus.kind === "sending"}
|
|
245
|
+
>
|
|
246
|
+
{resendStatus.kind === "sending"
|
|
247
|
+
? t("auth.login.submitting")
|
|
248
|
+
: t("auth.login.resendVerification")}
|
|
249
|
+
</Button>
|
|
258
250
|
</span>
|
|
259
251
|
)}
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
252
|
+
{error.reason === "account_locked" && unlockAccountHref !== undefined && (
|
|
253
|
+
<span className="self-start">
|
|
254
|
+
<Link href={unlockAccountHref} variant="default">
|
|
255
|
+
{t("auth.login.unlockAccount")}
|
|
256
|
+
</Link>
|
|
257
|
+
</span>
|
|
258
|
+
)}
|
|
259
|
+
{resendStatus.kind === "rateLimited" && (
|
|
260
|
+
<span className="text-xs">{t("auth.login.resendRateLimited")}</span>
|
|
261
|
+
)}
|
|
262
|
+
{resendStatus.kind === "error" && (
|
|
263
|
+
<span className="text-xs">{t("auth.login.resendError")}</span>
|
|
264
|
+
)}
|
|
265
|
+
</div>
|
|
266
|
+
</Banner>
|
|
267
|
+
) : null}
|
|
268
|
+
<Button type="submit" loading={submitting} disabled={submitting}>
|
|
269
|
+
{submitting ? t("auth.login.submitting") : effectiveSubmit}
|
|
270
|
+
</Button>
|
|
271
|
+
</Form>
|
|
272
|
+
{forgotPasswordHref !== undefined && (
|
|
273
|
+
<Link href={forgotPasswordHref} variant="muted" className="self-center">
|
|
274
|
+
{t("auth.login.forgotPassword")}
|
|
275
|
+
</Link>
|
|
276
|
+
)}
|
|
277
|
+
{signupHref !== undefined && (
|
|
278
|
+
<Link href={signupHref} variant="muted" className="self-center">
|
|
279
|
+
{t("auth.signup.title")}
|
|
280
|
+
</Link>
|
|
281
|
+
)}
|
|
282
|
+
{legalLinks !== undefined && legalLinks.length > 0 && (
|
|
283
|
+
<nav
|
|
284
|
+
data-testid="login-legal-links"
|
|
285
|
+
className="flex items-center justify-center gap-3 pt-2 border-t border-border/50"
|
|
286
|
+
>
|
|
287
|
+
{legalLinks.map((link) => (
|
|
288
|
+
<Link key={link.href} href={link.href} variant="muted" className="text-xs">
|
|
289
|
+
{link.label}
|
|
290
|
+
</Link>
|
|
291
|
+
))}
|
|
292
|
+
</nav>
|
|
293
|
+
)}
|
|
296
294
|
</AuthCard>
|
|
297
295
|
);
|
|
298
296
|
}
|
package/packages/bundled-features/src/auth-email-password/web/request-account-unlock-screen.tsx
CHANGED
|
@@ -73,7 +73,7 @@ export function RequestAccountUnlockScreen({
|
|
|
73
73
|
return (
|
|
74
74
|
<AuthCard title={effectiveTitle} subtitle={subtitle}>
|
|
75
75
|
{done ? (
|
|
76
|
-
|
|
76
|
+
<>
|
|
77
77
|
<Banner variant="info">
|
|
78
78
|
<p className="font-medium text-foreground">{t("auth.requestUnlock.successTitle")}</p>
|
|
79
79
|
<p className="mt-1">{t("auth.requestUnlock.successBody")}</p>
|
|
@@ -81,9 +81,9 @@ export function RequestAccountUnlockScreen({
|
|
|
81
81
|
<Link href={loginHref} variant="muted">
|
|
82
82
|
{t("auth.requestUnlock.backToLogin")}
|
|
83
83
|
</Link>
|
|
84
|
-
|
|
84
|
+
</>
|
|
85
85
|
) : (
|
|
86
|
-
|
|
86
|
+
<>
|
|
87
87
|
<p className="text-sm text-muted-foreground">{t("auth.requestUnlock.intro")}</p>
|
|
88
88
|
<Form onSubmit={onSubmit}>
|
|
89
89
|
<Field id="unlock-email" label={t("auth.requestUnlock.email")} required>
|
|
@@ -105,7 +105,7 @@ export function RequestAccountUnlockScreen({
|
|
|
105
105
|
<Link href={loginHref} variant="muted" className="self-center">
|
|
106
106
|
{t("auth.requestUnlock.backToLogin")}
|
|
107
107
|
</Link>
|
|
108
|
-
|
|
108
|
+
</>
|
|
109
109
|
)}
|
|
110
110
|
</AuthCard>
|
|
111
111
|
);
|
|
@@ -83,12 +83,10 @@ export function ResetPasswordScreen({
|
|
|
83
83
|
if (token === "") {
|
|
84
84
|
return (
|
|
85
85
|
<AuthCard title={effectiveTitle}>
|
|
86
|
-
<
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
</Link>
|
|
91
|
-
</div>
|
|
86
|
+
<p className="text-sm text-muted-foreground">{t("auth.resetPassword.missingToken")}</p>
|
|
87
|
+
<Link href={loginHref} variant="muted">
|
|
88
|
+
{t("auth.resetPassword.goToLogin")}
|
|
89
|
+
</Link>
|
|
92
90
|
</AuthCard>
|
|
93
91
|
);
|
|
94
92
|
}
|
|
@@ -96,7 +94,7 @@ export function ResetPasswordScreen({
|
|
|
96
94
|
return (
|
|
97
95
|
<AuthCard title={effectiveTitle}>
|
|
98
96
|
{done ? (
|
|
99
|
-
|
|
97
|
+
<>
|
|
100
98
|
<Banner variant="info">
|
|
101
99
|
<p className="font-medium text-foreground">{t("auth.resetPassword.successTitle")}</p>
|
|
102
100
|
<p className="mt-1">{t("auth.resetPassword.successBody")}</p>
|
|
@@ -104,9 +102,9 @@ export function ResetPasswordScreen({
|
|
|
104
102
|
<Link href={loginHref} variant="button">
|
|
105
103
|
{t("auth.resetPassword.goToLogin")}
|
|
106
104
|
</Link>
|
|
107
|
-
|
|
105
|
+
</>
|
|
108
106
|
) : (
|
|
109
|
-
|
|
107
|
+
<>
|
|
110
108
|
<p className="text-sm text-muted-foreground">{t("auth.resetPassword.intro")}</p>
|
|
111
109
|
<Form onSubmit={onSubmit}>
|
|
112
110
|
<Field id="reset-new-password" label={t("auth.resetPassword.newPassword")} required>
|
|
@@ -142,7 +140,7 @@ export function ResetPasswordScreen({
|
|
|
142
140
|
{submitting ? t("auth.resetPassword.submitting") : t("auth.resetPassword.submit")}
|
|
143
141
|
</Button>
|
|
144
142
|
</Form>
|
|
145
|
-
|
|
143
|
+
</>
|
|
146
144
|
)}
|
|
147
145
|
</AuthCard>
|
|
148
146
|
);
|
|
@@ -44,22 +44,20 @@ export function SessionBootstrapErrorScreen({
|
|
|
44
44
|
|
|
45
45
|
return (
|
|
46
46
|
<div data-testid="session-bootstrap-error" data-http-status={failure.httpStatus ?? "network"}>
|
|
47
|
-
<AuthCard title={t("auth.sessionBootstrap.errorTitle")}>
|
|
48
|
-
<
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
47
|
+
<AuthCard title={t("auth.sessionBootstrap.errorTitle")} bodyClassName="pb-4">
|
|
48
|
+
<Banner variant="error">
|
|
49
|
+
{failure.httpStatus === 429
|
|
50
|
+
? t("auth.sessionBootstrap.rateLimitedBody")
|
|
51
|
+
: t("auth.sessionBootstrap.errorBody")}
|
|
52
|
+
</Banner>
|
|
53
|
+
<Button variant="primary" onClick={handleRetry} disabled={retrying}>
|
|
54
|
+
{retrying ? t("auth.sessionBootstrap.retrying") : t("auth.sessionBootstrap.retry")}
|
|
55
|
+
</Button>
|
|
56
|
+
{onSignOut !== undefined && (
|
|
57
|
+
<Button variant="secondary" onClick={() => void onSignOut()} disabled={retrying}>
|
|
58
|
+
{t("auth.sessionBootstrap.signOut")}
|
|
56
59
|
</Button>
|
|
57
|
-
|
|
58
|
-
<Button variant="secondary" onClick={() => void onSignOut()} disabled={retrying}>
|
|
59
|
-
{t("auth.sessionBootstrap.signOut")}
|
|
60
|
-
</Button>
|
|
61
|
-
)}
|
|
62
|
-
</div>
|
|
60
|
+
)}
|
|
63
61
|
</AuthCard>
|
|
64
62
|
</div>
|
|
65
63
|
);
|