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" ;
55import 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
1112const 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