feat: add passkey offer step to onboarding wizard
After tenant creation, checks vendor auth policy and conditionally shows a passkey enrollment offer screen before redirecting. User can skip and set up later. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -20,6 +20,7 @@ export function OnboardingPage() {
|
|||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const [slugAvailable, setSlugAvailable] = useState<boolean | null>(null);
|
const [slugAvailable, setSlugAvailable] = useState<boolean | null>(null);
|
||||||
const [checkingSlug, setCheckingSlug] = useState(false);
|
const [checkingSlug, setCheckingSlug] = useState(false);
|
||||||
|
const [showPasskeyOffer, setShowPasskeyOffer] = useState(false);
|
||||||
const debounceRef = useRef<ReturnType<typeof setTimeout>>(undefined);
|
const debounceRef = useRef<ReturnType<typeof setTimeout>>(undefined);
|
||||||
|
|
||||||
const slug = toSlug(name);
|
const slug = toSlug(name);
|
||||||
@@ -50,6 +51,17 @@ export function OnboardingPage() {
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
try {
|
try {
|
||||||
await api.post<TenantResponse>('/onboarding/tenant', { name, slug });
|
await api.post<TenantResponse>('/onboarding/tenant', { name, slug });
|
||||||
|
// Check if passkeys are enabled in vendor policy
|
||||||
|
try {
|
||||||
|
const config = await fetch('/platform/api/config').then(r => r.json());
|
||||||
|
if (config.vendorAuthPolicy?.passkeyEnabled) {
|
||||||
|
setShowPasskeyOffer(true);
|
||||||
|
setLoading(false);
|
||||||
|
return; // Don't redirect yet
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
// Ignore — proceed without passkey offer
|
||||||
|
}
|
||||||
// Tenant created — force a fresh OIDC sign-in so the Logto SDK gets
|
// Tenant created — force a fresh OIDC sign-in so the Logto SDK gets
|
||||||
// new tokens that include the org membership just created. The existing
|
// new tokens that include the org membership just created. The existing
|
||||||
// Logto session cookie means the user won't see a login form — Logto
|
// Logto session cookie means the user won't see a login form — Logto
|
||||||
@@ -66,6 +78,34 @@ export function OnboardingPage() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleSkipPasskey() {
|
||||||
|
await signIn(`${window.location.origin}/platform/callback`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (showPasskeyOffer) {
|
||||||
|
return (
|
||||||
|
<div className={styles.page}>
|
||||||
|
<div className={styles.wrapper}>
|
||||||
|
<Card className={styles.card}>
|
||||||
|
<div className={styles.inner}>
|
||||||
|
<div style={{ textAlign: 'center' }}>
|
||||||
|
<h2 style={{ margin: '16px 0 8px' }}>Secure your account</h2>
|
||||||
|
<p style={{ color: 'var(--text-muted)', marginBottom: 24 }}>
|
||||||
|
Add a passkey to sign in faster with your fingerprint, face, or security key.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div style={{ display: 'flex', flexDirection: 'column', gap: 12 }}>
|
||||||
|
<Button variant="secondary" onClick={handleSkipPasskey}>
|
||||||
|
Set up later
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.page}>
|
<div className={styles.page}>
|
||||||
<div className={styles.wrapper}>
|
<div className={styles.wrapper}>
|
||||||
|
|||||||
Reference in New Issue
Block a user