Skip to content

Commit 98968be

Browse files
committed
Password Creation Dialog Validation
1 parent b7f14c4 commit 98968be

File tree

1 file changed

+103
-42
lines changed

1 file changed

+103
-42
lines changed

src/components/ui/create-password-dialog.tsx

Lines changed: 103 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
1-
import {createPasswordItem} from "@/app/actions";
2-
import {encrypt} from "@/utils/encryption";
3-
import {useUser} from "@clerk/nextjs";
4-
import {useState} from "react";
1+
import { createPasswordItem } from "@/app/actions";
2+
import { encrypt } from "@/utils/encryption";
3+
import { useUser } from "@clerk/nextjs";
4+
import { useState } from "react";
55
import toast from "react-hot-toast";
6-
import {z} from "zod";
7-
import {Button} from "./button";
8-
import {Dialog,DialogContent,DialogHeader,DialogTitle} from "./dialog";
9-
import {Input} from "./input";
6+
import { z } from "zod";
7+
import { Button } from "./button";
8+
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "./dialog";
9+
import { Input } from "./input";
10+
import { Loader2 } from "lucide-react";
1011

1112
const websiteSchema = z.string().url();
1213

@@ -21,23 +22,59 @@ export const CreatePasswordDialog = ({
2122
const [username, setUsername] = useState("");
2223
const [website, setWebsite] = useState("");
2324
const [password, setPassword] = useState("");
25+
const [loading, setLoading] = useState(false);
26+
2427
const { user: clerkuser } = useUser();
2528

29+
const passwordSchema = z.object({
30+
name: z
31+
.string()
32+
.min(1, "Name is required")
33+
.max(50, "Name must be at most 50 characters"),
34+
username: z
35+
.string()
36+
.min(1, "Username is required")
37+
.max(30, "Username must be at most 30 characters"),
38+
website: z
39+
.string()
40+
.url("Invalid website URL")
41+
.max(2048, "Website URL is too long"),
42+
password: z
43+
.string()
44+
.min(6, "Password must be at least 6 characters long")
45+
.max(128, "Password must be at most 128 characters"),
46+
});
47+
2648
const handleSave = async () => {
27-
const websiteValidation = websiteSchema.safeParse(website);
28-
29-
if (!websiteValidation.success) {
30-
toast.error("Invalid website URL");
49+
setLoading(true);
50+
const validationResult = passwordSchema.safeParse({
51+
name,
52+
username,
53+
website,
54+
password,
55+
});
56+
57+
if (!validationResult.success) {
58+
const errorMessage =
59+
validationResult.error.errors[0]?.message || "Validation failed";
60+
toast.error(errorMessage);
61+
setLoading(false);
3162
return;
3263
}
33-
34-
await createPasswordItem(
35-
encrypt(username, clerkuser),
36-
encrypt(website, clerkuser),
37-
encrypt(password, clerkuser)
38-
);
39-
toast.success("Password created");
40-
onClose();
64+
65+
try {
66+
await createPasswordItem(
67+
encrypt(username, clerkuser),
68+
encrypt(website, clerkuser),
69+
encrypt(password, clerkuser)
70+
);
71+
toast.success("Password created");
72+
onClose();
73+
} catch (error) {
74+
toast.error("Failed to create password");
75+
} finally {
76+
setLoading(false);
77+
}
4178
};
4279

4380
return (
@@ -47,33 +84,57 @@ export const CreatePasswordDialog = ({
4784
<DialogTitle>Create Password</DialogTitle>
4885
</DialogHeader>
4986
<div className="space-y-4">
50-
<Input
51-
placeholder="Name"
52-
value={name}
53-
onChange={(e) => setName(e.target.value)}
54-
/>
55-
<Input
56-
placeholder="Username"
57-
value={username}
58-
onChange={(e) => setUsername(e.target.value)}
59-
/>
60-
<Input
61-
placeholder="Website"
62-
value={website}
63-
onChange={(e) => setWebsite(e.target.value)}
64-
/>
65-
<Input
66-
placeholder="Password"
67-
value={password}
68-
onChange={(e) => setPassword(e.target.value)}
69-
type="password"
70-
/>
87+
<div className="relative">
88+
<Input
89+
placeholder="Name"
90+
value={name}
91+
onChange={(e) => setName(e.target.value)}
92+
maxLength={50}
93+
/>
94+
<div className="mt-1 text-sm text-gray-500">{name.length} / 50</div>
95+
</div>
96+
<div className="relative">
97+
<Input
98+
placeholder="Username"
99+
value={username}
100+
onChange={(e) => setUsername(e.target.value)}
101+
maxLength={30}
102+
/>
103+
<div className="mt-1 text-sm text-gray-500">
104+
{username.length} / 30
105+
</div>
106+
</div>
107+
<div className="relative">
108+
<Input
109+
placeholder="Website"
110+
value={website}
111+
onChange={(e) => setWebsite(e.target.value)}
112+
maxLength={1024}
113+
/>
114+
<div className="mt-1 text-sm text-gray-500">
115+
{website.length} / 1024
116+
</div>
117+
</div>
118+
<div className="relative">
119+
<Input
120+
placeholder="Password"
121+
value={password}
122+
onChange={(e) => setPassword(e.target.value)}
123+
type="password"
124+
maxLength={128}
125+
/>
126+
<div className=" mt-1 text-sm text-gray-500">
127+
{password.length} / 128
128+
</div>
129+
</div>
71130
</div>
72131
<div className="mt-4 flex justify-end gap-2">
73132
<Button variant="outline" onClick={onClose}>
74133
Cancel
75134
</Button>
76-
<Button onClick={handleSave}>Save</Button>
135+
<Button onClick={handleSave}>
136+
{loading ? <Loader2 className="w-4 h-4 animate-spin" /> : "Save"}
137+
</Button>
77138
</div>
78139
</DialogContent>
79140
</Dialog>

0 commit comments

Comments
 (0)