Skip to content

Commit 7525b35

Browse files
committed
Update add access method wizard.
1 parent a62f661 commit 7525b35

File tree

4 files changed

+140
-103
lines changed

4 files changed

+140
-103
lines changed

src/frontend/src/lib/components/views/AccessMethods.svelte

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,14 +57,17 @@
5757
);
5858
5959
const handleOpenIDLinked = (credential: OpenIdCredential) => {
60+
isAddAccessMethodWizardOpen = false;
6061
openIdCredentials.push(credential);
6162
invalidateAll();
6263
};
6364
const handlePasskeyRegistered = (authnMethod: AuthnMethodData) => {
65+
isAddAccessMethodWizardOpen = false;
6466
authnMethods.push(authnMethod);
6567
invalidateAll();
6668
};
6769
const handleOtherDeviceRegistered = () => {
70+
isAddAccessMethodWizardOpen = false;
6871
toaster.success({
6972
title: "Passkey has been registered from another device.",
7073
});
@@ -268,11 +271,14 @@
268271

269272
{#if isAddAccessMethodWizardOpen}
270273
<AddAccessMethodWizard
271-
onOpenIDLinked={handleOpenIDLinked}
274+
onOpenIdLinked={handleOpenIDLinked}
272275
onPasskeyRegistered={handlePasskeyRegistered}
273276
onOtherDeviceRegistered={handleOtherDeviceRegistered}
274-
onClose={() => (isAddAccessMethodWizardOpen = false)}
275277
maxPasskeysReached={accessMethods.isMaxPasskeysReached}
278+
onError={(error) => {
279+
isAddAccessMethodWizardOpen = false;
280+
handleError(error);
281+
}}
276282
{openIdCredentials}
277283
{isUsingPasskeys}
278284
/>
Lines changed: 36 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
<script lang="ts">
2-
import { handleError } from "$lib/components/utils/error";
3-
import Dialog from "$lib/components/ui/Dialog.svelte";
42
import SystemOverlayBackdrop from "$lib/components/utils/SystemOverlayBackdrop.svelte";
53
import { AddAccessMethodFlow } from "$lib/flows/addAccessMethodFlow.svelte.js";
64
import type {
@@ -11,27 +9,24 @@
119
import AddAccessMethod from "$lib/components/wizards/addAccessMethod/views/AddAccessMethod.svelte";
1210
import AddPasskey from "$lib/components/wizards/addAccessMethod/views/AddPasskey.svelte";
1311
import { ConfirmAccessMethodWizard } from "$lib/components/wizards/confirmAccessMethod";
12+
import { isOpenIdCancelError } from "$lib/utils/openID";
13+
import { isWebAuthnCancelError } from "$lib/utils/webAuthnErrorUtils";
1414
1515
interface Props {
16-
onOpenIDLinked: (credential: OpenIdCredential) => void;
16+
onOpenIdLinked: (credential: OpenIdCredential) => void;
1717
onPasskeyRegistered: (credential: AuthnMethodData) => void;
1818
onOtherDeviceRegistered: () => void;
19-
onClose: () => void;
20-
onError?: (error: unknown) => void;
19+
onError: (error: unknown) => void;
2120
isUsingPasskeys?: boolean;
2221
openIdCredentials?: OpenIdCredential[];
2322
maxPasskeysReached?: boolean;
2423
}
2524
2625
const {
27-
onOpenIDLinked,
26+
onOpenIdLinked,
2827
onPasskeyRegistered,
2928
onOtherDeviceRegistered,
30-
onClose,
31-
onError = (error) => {
32-
onClose();
33-
handleError(error);
34-
},
29+
onError,
3530
isUsingPasskeys,
3631
openIdCredentials,
3732
maxPasskeysReached,
@@ -43,49 +38,49 @@
4338
4439
const handleContinueWithOpenId = async (config: OpenIdConfig) => {
4540
try {
46-
onOpenIDLinked(await addAccessMethodFlow.linkOpenIdAccount(config));
47-
onClose();
41+
onOpenIdLinked(await addAccessMethodFlow.linkOpenIdAccount(config));
4842
} catch (error) {
49-
onError(error);
43+
if (isOpenIdCancelError(error)) {
44+
return "cancelled";
45+
}
46+
onError(error); // Propagate unhandled errors to parent component
5047
}
5148
};
5249
const handleCreatePasskey = async () => {
5350
try {
5451
onPasskeyRegistered(await addAccessMethodFlow.createPasskey());
55-
onClose();
5652
} catch (error) {
53+
if (isWebAuthnCancelError(error)) {
54+
return "cancelled";
55+
}
5756
onError(error);
5857
}
5958
};
6059
const handleOtherDeviceRegistered = async () => {
6160
onOtherDeviceRegistered();
62-
onClose();
6361
};
6462
</script>
6563

66-
<Dialog {onClose}>
67-
{#if isContinueOnAnotherDeviceVisible}
68-
<ConfirmAccessMethodWizard
69-
onConfirm={handleOtherDeviceRegistered}
70-
{onError}
71-
/>
72-
{:else if addAccessMethodFlow.view === "chooseMethod"}
73-
<AddAccessMethod
74-
continueWithPasskey={addAccessMethodFlow.continueWithPasskey}
75-
linkOpenIdAccount={handleContinueWithOpenId}
76-
{maxPasskeysReached}
77-
{openIdCredentials}
78-
/>
79-
{:else if addAccessMethodFlow.view === "addPasskey"}
80-
<AddPasskey
81-
createPasskey={handleCreatePasskey}
82-
continueOnAnotherDevice={() => (isContinueOnAnotherDeviceVisible = true)}
83-
{isUsingPasskeys}
84-
/>
85-
{/if}
64+
{#if isContinueOnAnotherDeviceVisible}
65+
<ConfirmAccessMethodWizard
66+
onConfirm={handleOtherDeviceRegistered}
67+
{onError}
68+
/>
69+
{:else if addAccessMethodFlow.view === "chooseMethod"}
70+
<AddAccessMethod
71+
continueWithPasskey={addAccessMethodFlow.continueWithPasskey}
72+
linkOpenIdAccount={handleContinueWithOpenId}
73+
{maxPasskeysReached}
74+
{openIdCredentials}
75+
/>
76+
{:else if addAccessMethodFlow.view === "addPasskey"}
77+
<AddPasskey
78+
createPasskey={handleCreatePasskey}
79+
continueOnAnotherDevice={() => (isContinueOnAnotherDeviceVisible = true)}
80+
{isUsingPasskeys}
81+
/>
82+
{/if}
8683

87-
<!-- Rendered within dialog to be on top of it -->
88-
{#if addAccessMethodFlow.isSystemOverlayVisible}
89-
<SystemOverlayBackdrop />
90-
{/if}
91-
</Dialog>
84+
{#if addAccessMethodFlow.isSystemOverlayVisible}
85+
<SystemOverlayBackdrop />
86+
{/if}

src/frontend/src/lib/components/wizards/addAccessMethod/views/AddAccessMethod.svelte

Lines changed: 58 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,12 @@
1212
OpenIdConfig,
1313
OpenIdCredential,
1414
} from "$lib/generated/internet_identity_types";
15+
import { waitFor } from "$lib/utils/utils";
16+
import { t } from "$lib/stores/locale.store";
17+
import { Trans } from "$lib/components/locale";
1518
1619
interface Props {
17-
linkOpenIdAccount: (config: OpenIdConfig) => Promise<void>;
20+
linkOpenIdAccount: (config: OpenIdConfig) => Promise<"cancelled" | void>;
1821
continueWithPasskey: () => void;
1922
openIdCredentials?: OpenIdCredential[];
2023
maxPasskeysReached?: boolean;
@@ -27,20 +30,20 @@
2730
maxPasskeysReached,
2831
}: Props = $props();
2932
30-
let authenticatingGoogle = $state(false);
3133
let authenticatingProviderId = $state<string | null>();
32-
let authenticating = $derived(
33-
nonNullish(authenticatingProviderId) || authenticatingGoogle,
34-
);
34+
let cancelledProviderId = $state<string | null>(null);
3535
3636
const isPasskeySupported = nonNullish(window.PublicKeyCredential);
3737
3838
const handleContinueWithOpenId = async (config: OpenIdConfig) => {
3939
authenticatingProviderId = config.client_id;
40-
try {
41-
await linkOpenIdAccount(config);
42-
} finally {
43-
authenticatingProviderId = null;
40+
const result = await linkOpenIdAccount(config);
41+
authenticatingProviderId = null;
42+
43+
if (result === "cancelled") {
44+
cancelledProviderId = config.client_id;
45+
await waitFor(4000);
46+
cancelledProviderId = null;
4447
}
4548
};
4649
@@ -57,62 +60,75 @@
5760
<ShieldIllustration class="text-text-primary mb-8 h-24" />
5861
</div>
5962
<h1 class="text-text-primary mb-3 text-2xl font-medium sm:text-center">
60-
Add access method
63+
{$t`Add access method`}
6164
</h1>
6265
<p
6366
class="text-text-tertiary text-base font-medium text-balance sm:text-center"
6467
>
65-
Add another way to sign in with a passkey or third-party account for secure
66-
access.
68+
<Trans>
69+
Add another way to sign in with a passkey or third-party account for
70+
secure access.
71+
</Trans>
6772
</p>
6873
</div>
6974
<div class="flex flex-col items-stretch gap-6">
7075
{#if !isPasskeySupported}
7176
<Alert
72-
title="Passkeys not available here"
73-
description="Passkeys are unavailable on this device or browser. Please choose
74-
another access method to continue."
77+
title={$t`Passkeys not available here`}
78+
description={$t`Passkeys are unavailable on this device or browser. Please choose another access method to continue.`}
7579
direction="horizontal"
7680
/>
7781
{/if}
7882
<div class="flex flex-col items-stretch gap-3">
83+
<div class="flex flex-row flex-nowrap justify-stretch gap-3">
84+
{#each openIdProviders as provider}
85+
{@const name = provider.name}
86+
<Tooltip
87+
label={$t`Interaction canceled. Please try again.`}
88+
hidden={cancelledProviderId !== provider.client_id}
89+
manual
90+
>
91+
<Tooltip
92+
label={$t`You already have a ${name} account linked`}
93+
hidden={!hasCredential(provider.issuer)}
94+
>
95+
<Button
96+
onclick={() => handleContinueWithOpenId(provider)}
97+
variant="secondary"
98+
disabled={nonNullish(authenticatingProviderId) ||
99+
hasCredential(provider.issuer)}
100+
size="xl"
101+
class="flex-1"
102+
aria-label={$t`Continue with ${name}`}
103+
>
104+
{#if authenticatingProviderId === provider.client_id}
105+
<ProgressRing />
106+
{:else if provider.logo}
107+
<div class="size-6">
108+
{@html provider.logo}
109+
</div>
110+
{/if}
111+
</Button>
112+
</Tooltip>
113+
</Tooltip>
114+
{/each}
115+
</div>
79116
<Tooltip
80-
label="You have reached the maximum number of passkeys."
117+
label={$t`You have reached the maximum number of passkeys`}
81118
hidden={!maxPasskeysReached}
82119
>
83120
<Button
84121
onclick={continueWithPasskey}
85-
disabled={!isPasskeySupported || authenticating || maxPasskeysReached}
122+
variant="secondary"
123+
disabled={!isPasskeySupported ||
124+
nonNullish(authenticatingProviderId) ||
125+
maxPasskeysReached}
86126
size="xl"
87127
>
88128
<PasskeyIcon />
89-
Continue with passkey
129+
<span>{$t`Continue with passkey`}</span>
90130
</Button>
91131
</Tooltip>
92-
<div class="flex flex-row flex-nowrap justify-stretch gap-3">
93-
{#each openIdProviders as provider}
94-
<Tooltip
95-
label={`You already have a ${provider.name} account linked`}
96-
hidden={!hasCredential(provider.issuer)}
97-
>
98-
<Button
99-
onclick={() => handleContinueWithOpenId(provider)}
100-
variant="secondary"
101-
disabled={authenticating || hasCredential(provider.issuer)}
102-
size="xl"
103-
class="flex-1"
104-
>
105-
{#if authenticatingProviderId === provider.client_id}
106-
<ProgressRing />
107-
{:else if provider.logo}
108-
<div class="size-6">
109-
{@html provider.logo}
110-
</div>
111-
{/if}
112-
</Button>
113-
</Tooltip>
114-
{/each}
115-
</div>
116132
</div>
117133
</div>
118134

0 commit comments

Comments
 (0)