Skip to content

Commit 2d51dd4

Browse files
committed
Confirmation Dialog for deletion + UX fixes / Improvements
* Confirmation Dialog on all deletion actions * Added X button in scrollarea items * Added version span on the bottom of the sidebar * Fixed Cards and Pins tab not having a "No {name} available" message * Password creation dialog now automatically adds 'https://' to the start of urls which was signified by the https placeholder. * Bumped version to 0.1.1b * Cleaned up imports
1 parent 1874984 commit 2d51dd4

File tree

6 files changed

+153
-20
lines changed

6 files changed

+153
-20
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "lockscript-vault",
3-
"version": "0.1.0",
3+
"version": "0.1.1",
44
"private": true,
55
"scripts": {
66
"dev": "next dev",
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
"use client";
2+
3+
import { Button } from "@/components/ui/button";
4+
import {
5+
Dialog,
6+
DialogContent,
7+
DialogFooter,
8+
DialogHeader,
9+
DialogTitle,
10+
} from "@/components/ui/dialog";
11+
import { Loader2 } from "lucide-react";
12+
13+
export const ConfirmationDialog = ({
14+
open,
15+
onClose,
16+
title,
17+
message,
18+
onConfirm,
19+
loading = false,
20+
}: {
21+
open: boolean;
22+
onClose: () => void;
23+
title: string;
24+
message: string;
25+
onConfirm: () => void;
26+
loading?: boolean;
27+
}) => {
28+
return (
29+
<Dialog open={open} onOpenChange={onClose}>
30+
<DialogContent>
31+
<DialogHeader>
32+
<DialogTitle>{title}</DialogTitle>
33+
</DialogHeader>
34+
<div className="mt-2 text-sm text-muted-foreground">{message}</div>
35+
<DialogFooter>
36+
<Button variant="outline" onClick={onClose}>
37+
Cancel
38+
</Button>
39+
<Button onClick={onConfirm}>
40+
{loading ? <Loader2 className="w-4 h-4 animate-spin" /> : "Confirm"}
41+
</Button>
42+
</DialogFooter>
43+
</DialogContent>
44+
</Dialog>
45+
);
46+
};

src/components/vault/dialogs/create-password-dialog.tsx

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,14 @@ export const CreatePasswordDialog = ({
7474
const handleSave = async () => {
7575
setLoading(true);
7676

77-
const validationResult = passwordSchema.safeParse(passwordItem);
77+
const updatedPasswordItem = {
78+
...passwordItem,
79+
website: passwordItem.website.startsWith("https://")
80+
? passwordItem.website
81+
: `https://${passwordItem.website}`,
82+
};
83+
84+
const validationResult = passwordSchema.safeParse(updatedPasswordItem);
7885

7986
if (!validationResult.success) {
8087
const errorMessage =
@@ -92,15 +99,15 @@ export const CreatePasswordDialog = ({
9299

93100
try {
94101
const encryptedUsername = await encrypt(
95-
passwordItem.username,
102+
updatedPasswordItem.username,
96103
clerkuser.id
97104
);
98105
const encryptedWebsite = await encrypt(
99-
passwordItem.website,
106+
updatedPasswordItem.website,
100107
clerkuser.id
101108
);
102109
const encryptedPassword = await encrypt(
103-
passwordItem.password,
110+
updatedPasswordItem.password,
104111
clerkuser.id
105112
);
106113

@@ -115,16 +122,16 @@ export const CreatePasswordDialog = ({
115122

116123
const passwordEntry: PasswordEntry = {
117124
id: item.id,
118-
name: passwordItem.name,
119-
username: passwordItem.username,
120-
website: passwordItem.website,
121-
password: passwordItem.password,
125+
name: updatedPasswordItem.name,
126+
username: updatedPasswordItem.username,
127+
website: updatedPasswordItem.website,
128+
password: updatedPasswordItem.password,
122129
usernameIV: item.usernameIV,
123130
websiteIV: item.websiteIV,
124131
passwordIV: item.passwordIV,
125132
updatedAt: item.updatedAt.toISOString(),
126133
lastAccess: item.updatedAt.toISOString(),
127-
created: item.createdAt.toISOString()
134+
created: item.createdAt.toISOString(),
128135
};
129136

130137
toast.success("Password created");

src/components/vault/password-details.tsx

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
import { useState } from "react";
2323
import toast from "react-hot-toast";
2424
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
25+
import { ConfirmationDialog } from "./dialogs/confirm-dialog";
2526

2627
interface PasswordEntry {
2728
id: string;
@@ -45,7 +46,16 @@ export const PasswordDetails: React.FC<PasswordDetailsProps> = ({
4546
onEdit,
4647
onDelete,
4748
}) => {
49+
const [isDialogOpen, setIsDialogOpen] = useState(false);
4850
const [isPasswordVisible, setIsPasswordVisible] = useState(false);
51+
const [isLoading, setIsLoading] = useState(false);
52+
53+
const handleDelete = () => {
54+
setIsLoading(true);
55+
onDelete();
56+
setIsDialogOpen(false);
57+
setIsLoading(false);
58+
};
4959

5060
return (
5161
<div className="space-y-6 mx-auto">
@@ -68,7 +78,7 @@ export const PasswordDetails: React.FC<PasswordDetailsProps> = ({
6878
<Settings className="mr-2 h-4 w-4" />
6979
Edit
7080
</DropdownMenuItem>
71-
<DropdownMenuItem onClick={onDelete}>
81+
<DropdownMenuItem onClick={() => setIsDialogOpen(true)}>
7282
<Trash className="mr-2 h-4 w-4" />
7383
Delete
7484
</DropdownMenuItem>
@@ -155,6 +165,15 @@ export const PasswordDetails: React.FC<PasswordDetailsProps> = ({
155165
))}
156166
</div>
157167
</div>
168+
169+
<ConfirmationDialog
170+
open={isDialogOpen}
171+
onClose={() => setIsDialogOpen(false)}
172+
title="Confirm Deletion"
173+
message="Are you sure you want to delete this password entry? This action cannot be undone."
174+
onConfirm={handleDelete}
175+
loading={isLoading}
176+
/>
158177
</div>
159178
);
160179
};

src/components/vault/sidebar.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,9 @@ export const Sidebar: React.FC<SidebarProps> = ({
7070
<Settings className="h-5 w-5" />
7171
Settings
7272
</Button>
73+
<span className="text-xs text-gray-500 block text-left mt-4">
74+
LockScript Vault v0.1.1b
75+
</span>
7376
</div>
7477
</div>
7578
);

src/components/vault/vault-page.tsx

Lines changed: 67 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,14 @@ import { decrypt, generateAndStoreKey, retrieveKey } from "@/utils/encryption";
1313
import { useUser } from "@clerk/nextjs";
1414
import type { Prisma } from "@prisma/client";
1515
import {
16+
ArrowDownAZ,
17+
ArrowDownWideNarrow,
18+
Clock,
1619
Plus,
1720
SquareArrowOutUpRight,
1821
Trash,
1922
User,
20-
ArrowDownAZ,
21-
ArrowDownWideNarrow,
22-
Clock,
23+
X,
2324
} from "lucide-react";
2425
import Image from "next/image";
2526
import { useRouter } from "next/navigation";
@@ -32,10 +33,11 @@ import {
3233
ContextMenuLabel,
3334
ContextMenuTrigger,
3435
} from "../ui/context-menu";
36+
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
37+
import { ConfirmationDialog } from "./dialogs/confirm-dialog";
3538
import { EmptyState } from "./empty-state";
3639
import { PasswordDetails } from "./password-details";
3740
import { Sidebar } from "./sidebar";
38-
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
3941

4042
interface PasswordEntry {
4143
id: string;
@@ -79,6 +81,11 @@ export const VaultPage: React.FC<VaultPageProps> = ({ user }) => {
7981
const [sortBy, setSortBy] = useState<"name" | "created" | "updated">(
8082
"created"
8183
);
84+
const [isConfirmationDialogOpen, setIsConfirmationDialogOpen] =
85+
useState(false);
86+
const [passwordToDelete, setPasswordToDelete] =
87+
useState<PasswordEntry | null>(null);
88+
const [isDeleting, setIsDeleting] = useState(false);
8289

8390
useEffect(() => {
8491
const ensureEncryptionKey = async () => {
@@ -319,10 +326,9 @@ export const VaultPage: React.FC<VaultPageProps> = ({ user }) => {
319326
filteredAndSortedPasswords.map((password) => (
320327
<ContextMenu key={password.id}>
321328
<ContextMenuTrigger>
322-
<Button
323-
variant="ghost"
329+
<div
324330
className={cn(
325-
"w-full justify-start rounded-xl p-4 text-left transition-all hover:bg-rose-50/50 dark:hover:bg-rose-900/50",
331+
"flex w-full justify-between rounded-xl p-2 text-left transition-all hover:bg-rose-50/50 dark:hover:bg-rose-900/50 hover:cursor-pointer",
326332
selectedEntry?.id === password.id &&
327333
"bg-rose-50 dark:bg-rose-900"
328334
)}
@@ -347,7 +353,20 @@ export const VaultPage: React.FC<VaultPageProps> = ({ user }) => {
347353
</div>
348354
</div>
349355
</div>
350-
</Button>
356+
357+
<Button
358+
variant="ghost"
359+
size="icon"
360+
className="text-muted-foreground hover:text-foreground"
361+
onClick={(e) => {
362+
e.stopPropagation();
363+
setPasswordToDelete(password);
364+
setIsConfirmationDialogOpen(true);
365+
}}
366+
>
367+
<X className="h-4 w-4" />
368+
</Button>
369+
</div>
351370
</ContextMenuTrigger>
352371
<ContextMenuContent className="rounded-xl">
353372
<ContextMenuLabel>
@@ -413,7 +432,16 @@ export const VaultPage: React.FC<VaultPageProps> = ({ user }) => {
413432
</ContextMenuContent>
414433
</ContextMenu>
415434
))}
416-
435+
{activeTab === "notes" && (
436+
<div className="flex h-full items-center justify-center text-gray-500 dark:text-gray-400">
437+
No {activeTab} available
438+
</div>
439+
)}
440+
{activeTab === "pins" && (
441+
<div className="flex h-full items-center justify-center text-gray-500 dark:text-gray-400">
442+
No {activeTab} available
443+
</div>
444+
)}
417445
{activeTab === "cards" && (
418446
<div className="flex h-full items-center justify-center text-gray-500 dark:text-gray-400">
419447
No {activeTab} available
@@ -452,6 +480,7 @@ export const VaultPage: React.FC<VaultPageProps> = ({ user }) => {
452480
)}
453481
</div>
454482
</div>
483+
455484
{isEditDialogOpen && (
456485
<EditPasswordDialog
457486
isOpen={isEditDialogOpen}
@@ -466,6 +495,7 @@ export const VaultPage: React.FC<VaultPageProps> = ({ user }) => {
466495
entry={selectedEntry}
467496
/>
468497
)}
498+
469499
<CreatePasswordDialog
470500
open={isCreateDialogOpen}
471501
onClose={async () => {
@@ -476,6 +506,34 @@ export const VaultPage: React.FC<VaultPageProps> = ({ user }) => {
476506
}}
477507
setSelectedEntry={setSelectedEntry}
478508
/>
509+
510+
<ConfirmationDialog
511+
open={isConfirmationDialogOpen}
512+
onClose={() => setIsConfirmationDialogOpen(false)}
513+
title="Delete Password"
514+
message="Are you sure you want to delete this password? This action cannot be undone."
515+
onConfirm={async () => {
516+
if (passwordToDelete) {
517+
setIsDeleting(true);
518+
try {
519+
await deletePasswordItem(passwordToDelete.id);
520+
const updatedItems = await getPasswords(user?.id as string);
521+
setPasswordItems(updatedItems?.passwordItems);
522+
if (selectedEntry?.id === passwordToDelete.id) {
523+
setSelectedEntry(null);
524+
}
525+
toast.success("Password deleted successfully");
526+
} catch {
527+
toast.error("Failed to delete password");
528+
} finally {
529+
setIsDeleting(false);
530+
setIsConfirmationDialogOpen(false);
531+
setPasswordToDelete(null);
532+
}
533+
}
534+
}}
535+
loading={isDeleting}
536+
/>
479537
</div>
480538
);
481539
};

0 commit comments

Comments
 (0)