Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 29 additions & 22 deletions src/frontend/src/lib/components/views/AccessMethods.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
import identityInfo from "$lib/stores/identity-info.state.svelte";
import AccessMethod from "$lib/components/ui/AccessMethod.svelte";
import PasskeyIcon from "$lib/components/icons/PasskeyIcon.svelte";
import RemoveOpenIdCredential from "$lib/components/views/RemoveOpenIdCredential.svelte";
import UnlinkOpenIdCredential from "$lib/components/views/UnlinkOpenIdCredential.svelte";
import { invalidateAll } from "$app/navigation";
import type {
AuthnMethodData,
OpenIdCredential,
} from "$lib/generated/internet_identity_types";
import RemovePasskeyDialog from "$lib/components/views/RemovePasskeyDialog.svelte";
import RenamePasskeyDialog from "$lib/components/views/RenamePasskeyDialog.svelte";
import RemovePasskey from "$lib/components/views/RemovePasskey.svelte";
import RenamePasskey from "$lib/components/views/RenamePasskey.svelte";
import { nonNullish } from "@dfinity/utils";
import { handleError } from "$lib/components/utils/error";
import {
Expand All @@ -27,6 +27,7 @@
import { openIdLogo, openIdName } from "$lib/utils/openID";
import Tooltip from "../ui/Tooltip.svelte";
import { accessMethods } from "$lib/derived/accessMethods.derived.svelte";
import Dialog from "$lib/components/ui/Dialog.svelte";

let isAddAccessMethodWizardOpen = $state(false);
let removableAuthnMethod = $state<AuthnMethodData | null>(null);
Expand Down Expand Up @@ -239,31 +240,37 @@
</div>

{#if removableOpenIdCredential}
<RemoveOpenIdCredential
onRemove={handleRemoveOpenIdCredential}
onClose={() => (removableOpenIdCredential = null)}
openIDName={openIdName(
removableOpenIdCredential.iss,
removableOpenIdCredential.metadata,
) ?? "Google"}
isCurrentAccessMethod={isRemovableOpenIdCredentialCurrentAccessMethod}
/>
<Dialog onClose={() => (removableOpenIdCredential = null)}>
<UnlinkOpenIdCredential
onUnlink={handleRemoveOpenIdCredential}
onCancel={() => (removableOpenIdCredential = null)}
providerName={openIdName(
removableOpenIdCredential.iss,
removableOpenIdCredential.metadata,
) ?? "Google"}
isCurrentAccessMethod={isRemovableOpenIdCredentialCurrentAccessMethod}
/>
</Dialog>
{/if}

{#if removableAuthnMethod}
<RemovePasskeyDialog
onRemove={handleRemovePasskey}
onClose={() => (removableAuthnMethod = null)}
isCurrentAccessMethod={isRemovableAuthnMethodCurrentAccessMethod}
/>
<Dialog onClose={() => (removableAuthnMethod = null)}>
<RemovePasskey
onRemove={handleRemovePasskey}
onCancel={() => (removableAuthnMethod = null)}
isCurrentAccessMethod={isRemovableAuthnMethodCurrentAccessMethod}
/>
</Dialog>
{/if}

{#if renamableAuthnMethod}
<RenamePasskeyDialog
currentName={getAuthnMethodAlias(renamableAuthnMethod)}
onRename={handleRenamePasskey}
onClose={() => (renamableAuthnMethod = null)}
/>
<Dialog onClose={() => (renamableAuthnMethod = null)}>
<RenamePasskey
name={getAuthnMethodAlias(renamableAuthnMethod)}
onRename={handleRenamePasskey}
onCancel={() => (renamableAuthnMethod = null)}
/>
</Dialog>
{/if}

{#if isAddAccessMethodWizardOpen}
Expand Down
2 changes: 1 addition & 1 deletion src/frontend/src/lib/components/views/EditAccount.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@
variant="primary"
size="lg"
type="submit"
disabled={name.length === 0 ||
disabled={name.trim().length === 0 ||
name.length > 32 ||
!hasChanges ||
nameExists ||
Expand Down

This file was deleted.

80 changes: 80 additions & 0 deletions src/frontend/src/lib/components/views/RemovePasskey.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<script lang="ts">
import FeaturedIcon from "$lib/components/ui/FeaturedIcon.svelte";
import Button from "$lib/components/ui/Button.svelte";
import { TriangleAlertIcon } from "@lucide/svelte";
import ProgressRing from "$lib/components/ui/ProgressRing.svelte";
import { t } from "$lib/stores/locale.store";
import { Trans } from "$lib/components/locale";

interface Props {
onRemove: () => Promise<void>;
onCancel: () => void;
isCurrentAccessMethod?: boolean;
}

const { onRemove, onCancel, isCurrentAccessMethod = false }: Props = $props();

let isRemoving = $state(false);

const handleRemove = async () => {
try {
isRemoving = true;
await onRemove();
} finally {
isRemoving = false;
}
};
</script>

<div class="flex flex-1 flex-col">
<div class="mb-8 flex flex-col">
<FeaturedIcon variant="warning" size="lg" class="mb-3">
<TriangleAlertIcon class="size-6" />
</FeaturedIcon>
<h1 class="text-text-primary mb-3 text-2xl font-medium">
{$t`Are you sure?`}
</h1>
<div
class={[
"flex flex-col gap-4",
"[&_p]:text-text-tertiary [&_p]:text-base [&_p]:font-medium",
]}
>
<p>
<Trans>
Removing this passkey means you won't be able to use it to sign in
anymore. You can always add a new one later.
</Trans>
</p>
<p>
<Trans>It won't be removed from your device or password manager.</Trans>
</p>
{#if isCurrentAccessMethod}
<p>
<Trans>
As you are currently signed in with this passkey, you will be signed
out.
</Trans>
</p>
{/if}
</div>
</div>
<div class="mt-auto flex flex-col items-stretch gap-3">
<Button onclick={handleRemove} size="lg" danger disabled={isRemoving}>
{#if isRemoving}
<ProgressRing />
<span>{$t`Removing passkey...`}</span>
{:else}
<span>{$t`Remove passkey`}</span>
{/if}
</Button>
<Button
onclick={onCancel}
variant="tertiary"
size="lg"
disabled={isRemoving}
>
{$t`Cancel`}
</Button>
</div>
</div>

This file was deleted.

90 changes: 90 additions & 0 deletions src/frontend/src/lib/components/views/RenamePasskey.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
<script lang="ts">
import FeaturedIcon from "$lib/components/ui/FeaturedIcon.svelte";
import Button from "$lib/components/ui/Button.svelte";
import Input from "$lib/components/ui/Input.svelte";
import { PencilIcon } from "@lucide/svelte";
import { t } from "$lib/stores/locale.store";
import ProgressRing from "$lib/components/ui/ProgressRing.svelte";

interface Props {
name: string;
onRename: (name: string) => Promise<void>;
onCancel: () => void;
}

const props: Props = $props();

let name = $state(props.name);
let isSubmitting = $state(false);
let inputRef = $state<HTMLInputElement>();

const hasChanges = $derived(props.name !== name.trim());

const handleSubmit = async () => {
try {
isSubmitting = true;
await props.onRename(name.trim());
} finally {
isSubmitting = false;
}
};

$effect(() => {
inputRef?.focus();
});
</script>

<form class="flex flex-1 flex-col">
<div class="mb-8 flex flex-col">
<FeaturedIcon size="lg" class="mb-4 self-start">
<PencilIcon class="size-6" />
</FeaturedIcon>
<h1 class="text-text-primary mb-3 text-2xl font-medium">
{$t`Rename passkey`}
</h1>
<p class="text-text-tertiary mb-6 text-base font-medium">
{$t`Give your passkey a memorable name to help you identify it.`}
</p>
<Input
bind:element={inputRef}
bind:value={name}
inputmode="text"
placeholder={$t`Passkey name`}
type="text"
autocomplete="off"
autocorrect="off"
spellcheck="false"
error={name.length > 32
? $t`Maximum length is 32 characters.`
: undefined}
disabled={isSubmitting}
aria-label={$t`Passkey name`}
/>
</div>
<div class="mt-auto flex flex-col items-stretch gap-3">
<Button
type="submit"
size="lg"
onclick={handleSubmit}
disabled={name.trim().length === 0 ||
name.length > 32 ||
!hasChanges ||
isSubmitting}
>
{#if isSubmitting}
<ProgressRing />
<span>{$t`Saving changes...`}</span>
{:else}
<span>{$t`Save changes`}</span>
{/if}
</Button>
<Button
onclick={props.onCancel}
variant="tertiary"
size="lg"
disabled={isSubmitting}
>
{$t`Cancel`}
</Button>
</div>
</form>
Loading
Loading