Skip to content
Open
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
2 changes: 1 addition & 1 deletion src/frontend/src/lib/components/ui/Checkbox.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
"border-border-primary text-fg-primary-inversed bg-bg-primary",
"hover:bg-bg-primary_hover",
"has-checked:bg-bg-brand-solid has-checked:hover:bg-bg-brand-solid_hover has-checked:border-none",
"has-disabled:border-border-disabled has-disabled:bg-bg-disabled_subtle has-disabled:text-fg-disabled_subtle",
"has-disabled:border-border-disabled has-disabled:bg-bg-disabled_subtle has-disabled:hover:bg-bg-disabled_subtle has-disabled:text-fg-disabled_subtle has-disabled:cursor-not-allowed",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The checkbox still presented itself as clickable even though it was disabled...

"has-focus-visible:ring-focus-ring has-focus-visible:ring-offset-bg-primary outline-none has-focus-visible:ring-2 has-focus-visible:ring-offset-2",
{
sm: "size-4",
Expand Down
35 changes: 35 additions & 0 deletions src/frontend/src/lib/components/ui/Toggle.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<script lang="ts">
type ToggleProps = {
toggled: boolean;
onClick: (value: boolean) => void;
label?: string;
disabled?: boolean;
};
let { toggled, onClick, label, disabled = false }: ToggleProps = $props();
</script>

<label class="flex flex-row items-center gap-2">
<button
type="button"
aria-labelledby={label ? "toggle" : undefined}
Copy link

Copilot AI Sep 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The aria-labelledby references 'toggle' but the actual id is 'toggle-label' (line 22). This will break accessibility as the label association won't work correctly.

Copilot uses AI. Check for mistakes.

class={`bg-bg-tertiary relative flex h-5 w-9 flex-row items-center rounded-full p-0.5 ${toggled ? "justify-end" : "justify-start"}`}
onclick={() => onClick(!toggled)}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Svelte, the native DOM onclick attribute should be replaced with Svelte's event directive syntax on:click. The Svelte syntax properly binds component methods and ensures proper event handling within the component lifecycle.

on:click={() => onClick(!toggled)}

This change applies to both button elements in this component that currently use onclick.

Suggested change
onclick={() => onClick(!toggled)}
on:click={() => onClick(!toggled)}

Spotted by Diamond

Fix in Graphite


Is this helpful? React 👍 or 👎 to let us know.

>
<div class="size-4 rounded-full bg-white"></div>
</button>
{#if label}
<span id="toggle-label" class="text-text-secondary text-sm font-medium"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the id needed?

>{label}</span
>
{/if}
<input
type="checkbox"
role="switch"
aria-labelledby={label ? "toggle" : undefined}
Copy link

Copilot AI Sep 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The aria-labelledby references 'toggle' but the actual id is 'toggle-label' (line 22). This will break accessibility as the label association won't work correctly.

Copilot uses AI. Check for mistakes.

checked={toggled}
{disabled}
onchange={() => onClick(!toggled)}
Copy link

Copilot AI Sep 30, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hidden checkbox's onchange handler calls onClick(!toggled), but the button's onclick already does the same. This could cause the toggle to be called twice when using keyboard navigation, resulting in no state change.

Suggested change
onchange={() => onClick(!toggled)}

Copilot uses AI. Check for mistakes.

hidden
/>
</label>
44 changes: 39 additions & 5 deletions src/frontend/src/lib/components/views/CreateAccount.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,27 @@
import Button from "$lib/components/ui/Button.svelte";
import Input from "$lib/components/ui/Input.svelte";
import ProgressRing from "$lib/components/ui/ProgressRing.svelte";
import { AUTH_FLOW_UPDATES } from "$lib/state/featureFlags";
import Checkbox from "$lib/components/ui/Checkbox.svelte";

interface Props {
create: (name: string) => void;
create: (name: string, isDefault?: boolean) => void;
}

const { create }: Props = $props();

let inputRef = $state<HTMLInputElement>();
let name = $state("");
let loading = $state(false);
let isDefault = $state<boolean>(false);

const handleSubmit = () => {
loading = true;
create(name);
if ($AUTH_FLOW_UPDATES) {
create(name, isDefault);
} else {
create(name);
}
};

onMount(() => {
Expand All @@ -32,17 +39,28 @@
<UserPlusIcon size="1.5rem" />
</FeaturedIcon>
<h1 class="text-text-primary mb-3 text-2xl font-medium">
Name additional account
{#if $AUTH_FLOW_UPDATES}
Name account
{:else}
Name additional account
{/if}
</h1>
<p class="text-md text-text-tertiary mb-6 font-medium">
You can rename this account later if needed.
{#if $AUTH_FLOW_UPDATES}
You can edit this account later. Label it by use (e.g. 'Work' or
'Demo').
{:else}
You can rename this account later if needed.
{/if}
</p>
<Input
bind:element={inputRef}
bind:value={name}
inputmode="text"
placeholder="Account name"
hint={'Label it by how you\'ll use it — e.g., "Work", "Personal".'}
hint={$AUTH_FLOW_UPDATES
? ""
: 'Label it by how you\'ll use it — e.g., "Work", "Personal".'}
type="text"
size="md"
autocomplete="off"
Expand All @@ -52,6 +70,22 @@
error={name.length > 32 ? "Maximum length is 32 characters." : undefined}
aria-label="Account name"
/>
{#if $AUTH_FLOW_UPDATES}
<div class="mt-4.5 flex flex-col gap-6">
<div class="border-border-tertiary border-t"></div>
<div class="flex flex-row items-center gap-4">
<label class="flex cursor-pointer items-center gap-2">
<Checkbox
checked={isDefault}
onchange={(e) => (isDefault = e.currentTarget.checked)}
/>
<span class="text-text-secondary text-sm font-medium">
Set as default sign-in
</span>
</label>
</div>
</div>
{/if}
</div>
<div class="mt-auto flex flex-col items-stretch gap-3">
<Button
Expand Down
114 changes: 114 additions & 0 deletions src/frontend/src/lib/components/views/EditAccount.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
<script lang="ts">
import { onMount } from "svelte";
import FeaturedIcon from "$lib/components/ui/FeaturedIcon.svelte";
import { UserPlusIcon } from "@lucide/svelte";
import Button from "$lib/components/ui/Button.svelte";
import Input from "$lib/components/ui/Input.svelte";
import ProgressRing from "$lib/components/ui/ProgressRing.svelte";
import { AUTH_FLOW_UPDATES } from "$lib/state/featureFlags";
import Checkbox from "../ui/Checkbox.svelte";
import { type AccountInfo } from "$lib/generated/internet_identity_types";

interface Props {
handleEdit: (
account: AccountInfo,
name: string,
defaultAccount?: boolean,
) => void;
account: AccountInfo;
defaultAccount: AccountInfo;
}

const { handleEdit, account, defaultAccount }: Props = $props();

let inputRef = $state<HTMLInputElement>();
let name = $state<string>(account.name[0] ?? "");
let loading = $state(false);
let isDefault = $state<boolean>(
defaultAccount.account_number[0] === account.account_number[0],
);
let defaultIsDisabled =
defaultAccount.account_number[0] === account.account_number[0];

const handleSubmit = () => {
loading = true;
handleEdit(account, name, defaultIsDisabled ? undefined : isDefault);
};

onMount(() => {
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">
<UserPlusIcon size="1.5rem" />
</FeaturedIcon>
<h1 class="text-text-primary mb-3 text-2xl font-medium">
{#if $AUTH_FLOW_UPDATES}
Name account
{:else}
Name additional account
{/if}
</h1>
<p class="text-md text-text-tertiary mb-6 font-medium">
{#if $AUTH_FLOW_UPDATES}
You can edit this account later. Label it by use (e.g. 'Work' or
'Demo').
{:else}
You can rename this account later if needed.
{/if}
</p>
<Input
bind:element={inputRef}
bind:value={name}
inputmode="text"
placeholder="Account name"
hint={$AUTH_FLOW_UPDATES
? ""
: 'Label it by how you\'ll use it — e.g., "Work", "Personal".'}
type="text"
size="md"
autocomplete="off"
autocorrect="off"
spellcheck="false"
disabled={loading}
error={name.length > 32 ? "Maximum length is 32 characters." : undefined}
aria-label="Account name"
/>
{#if $AUTH_FLOW_UPDATES}
<div class="mt-4.5 flex flex-col gap-6">
<div class="border-border-tertiary border-t"></div>
<div class="flex flex-row items-center gap-4">
<label
class={`${defaultIsDisabled ? "cursor-not-allowed" : "cursor-pointer"} flex items-center gap-2`}
>
<Checkbox
checked={isDefault}
disabled={defaultIsDisabled}
onchange={(e) => (isDefault = e.currentTarget.checked)}
/>
<span class="text-text-secondary text-sm font-medium">
Set as default sign-in
</span>
</label>
</div>
</div>
{/if}
</div>
<div class="mt-auto flex flex-col items-stretch gap-3">
<Button
onclick={handleSubmit}
variant="primary"
size="lg"
type="submit"
disabled={name.length === 0 || name.length > 32 || loading}
>
{#if loading}
<ProgressRing />
{/if}
<span>Save changes</span>
</Button>
</div>
</form>
Loading
Loading