fix: alert dialog when deleting a passkey

This commit is contained in:
Suyog Tandel
2026-01-14 20:00:24 +05:30
parent 578bf0032c
commit 626ea7fc69
4 changed files with 74 additions and 13 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "picoforge",
"version": "0.1.5",
"version": "0.2.0",
"description": "",
"type": "module",
"scripts": {
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "picoforge"
version = "0.1.5"
version = "0.2.0"
description = "An open source commissioning tool for Pico FIDO security keys. Developed with Rust, Tauri, and Svelte."
authors = ["Suyog Tandel"]
license = "AGPL-3.0"
+2 -2
View File
@@ -1,7 +1,7 @@
{
"$schema": "https://schema.tauri.app/config/2",
"productName": "picoforge",
"version": "0.1.5",
"version": "0.2.0",
"identifier": "in.suyogtandel.picoforge",
"build": {
"beforeDevCommand": "deno task dev",
@@ -13,7 +13,7 @@
"macOSPrivateApi": true,
"windows": [
{
"title": "pico-forge",
"title": "picoforge",
"width": 1280,
"height": 720,
"minWidth": 500,
+70 -9
View File
@@ -5,6 +5,7 @@
import * as Card from "$lib/components/ui/card";
import * as Alert from "$lib/components/ui/alert";
import * as Dialog from "$lib/components/ui/dialog";
import * as AlertDialog from "$lib/components/ui/alert-dialog";
import * as Drawer from "$lib/components/ui/drawer";
import { Badge } from "$lib/components/ui/badge";
import { Separator } from "$lib/components/ui/separator";
@@ -20,6 +21,10 @@
let detailsOpen = $state(false);
let selectedCredential: StoredCredential | null = $state(null);
let deleteDialogOpen = $state(false);
let credentialToDelete: StoredCredential | null = $state(null);
let deleteConfirmationInput = $state("");
$effect(() => {
if (device.connected && !device.unlocked) {
showPinDialog = true;
@@ -34,6 +39,7 @@
return;
}
loading = true;
const res = await device.getCredentials(pin);
if (!res.success) {
@@ -43,30 +49,42 @@
loading = false;
}
async function handleDelete(id: string, rpId: string) {
if (!confirm(`Are you sure you want to delete the passkey for "${rpId}"?`)) return;
loading = true;
function initiateDelete(cred: StoredCredential) {
credentialToDelete = cred;
deleteConfirmationInput = "";
deleteDialogOpen = true;
}
const res = await device.deleteCredential(pin, id);
async function executeDelete() {
if (!credentialToDelete) return;
loading = true;
const { credentialId } = credentialToDelete;
const res = await device.deleteCredential(pin, credentialId);
if (res.success) {
await device.getCredentials(pin);
if (selectedCredential?.credentialId === id) {
if (selectedCredential?.credentialId === credentialId) {
detailsOpen = false;
selectedCredential = null;
}
deleteDialogOpen = false;
credentialToDelete = null;
} else {
deleteDialogOpen = false;
error = typeof res.msg === "string" ? res.msg : "Failed to delete credential";
}
loading = false;
}
function handleLock() {
device.lock(); // Clears global state
device.lock();
pin = "";
error = "";
// The effect will see device.unlocked is false and open the dialog
}
function openDetails(cred: StoredCredential) {
@@ -75,6 +93,7 @@
}
</script>
<!-- TODO: Move the dialogs to their own files in src/lib/components/dialogs -->
<div class="space-y-6">
<div>
<h1 class="text-3xl font-bold tracking-tight">Passkeys</h1>
@@ -172,6 +191,7 @@
>
<KeyRound class="h-5 w-5 text-primary" />
</div>
<div class="flex-1 min-w-0">
<h4 class="font-semibold text-base truncate">
{cred.rpName || cred.rpId || "Unknown Service"}
@@ -186,11 +206,11 @@
class="h-8 w-8 text-muted-foreground hover:text-destructive hover:bg-destructive/10 shrink-0"
onclick={(e) => {
e.stopPropagation();
handleDelete(cred.credentialId, cred.rpId);
initiateDelete(cred);
}}
disabled={loading}
>
{#if loading}
{#if loading && credentialToDelete?.credentialId === cred.credentialId}
<Loader2 class="h-4 w-4 animate-spin" />
{:else}
<Trash2 class="h-4 w-4" />
@@ -255,6 +275,47 @@
</div>
</Drawer.Content>
</Drawer.Root>
<AlertDialog.Root bind:open={deleteDialogOpen}>
<AlertDialog.Content>
<AlertDialog.Header>
<AlertDialog.Title>Are you sure?</AlertDialog.Title>
<AlertDialog.Description>
This action cannot be undone. This will permanently delete the passkey for
<span class="font-semibold text-foreground">{credentialToDelete?.rpId}</span>.
</AlertDialog.Description>
</AlertDialog.Header>
<div class="py-4 space-y-3">
<Label for="confirm-delete">
Type <span class="font-mono text-xs bg-muted px-1 py-0.5 rounded">{credentialToDelete?.rpId}</span> to confirm
</Label>
<Input
id="confirm-delete"
bind:value={deleteConfirmationInput}
placeholder={credentialToDelete?.rpId}
autocomplete="off"
class="font-mono"
/>
</div>
<AlertDialog.Footer>
<AlertDialog.Cancel disabled={loading}>Cancel</AlertDialog.Cancel>
<Button
variant="destructive"
onclick={executeDelete}
disabled={deleteConfirmationInput !== credentialToDelete?.rpId || loading}
>
{#if loading}
<Loader2 class="mr-2 h-4 w-4 animate-spin" />
Deleting...
{:else}
Delete Passkey
{/if}
</Button>
</AlertDialog.Footer>
</AlertDialog.Content>
</AlertDialog.Root>
{/if}
{/if}
</div>