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" ;
5
5
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" ;
10
11
11
12
const websiteSchema = z . string ( ) . url ( ) ;
12
13
@@ -21,23 +22,59 @@ export const CreatePasswordDialog = ({
21
22
const [ username , setUsername ] = useState ( "" ) ;
22
23
const [ website , setWebsite ] = useState ( "" ) ;
23
24
const [ password , setPassword ] = useState ( "" ) ;
25
+ const [ loading , setLoading ] = useState ( false ) ;
26
+
24
27
const { user : clerkuser } = useUser ( ) ;
25
28
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
+
26
48
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 ) ;
31
62
return ;
32
63
}
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
+ }
41
78
} ;
42
79
43
80
return (
@@ -47,33 +84,57 @@ export const CreatePasswordDialog = ({
47
84
< DialogTitle > Create Password</ DialogTitle >
48
85
</ DialogHeader >
49
86
< 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 >
71
130
</ div >
72
131
< div className = "mt-4 flex justify-end gap-2" >
73
132
< Button variant = "outline" onClick = { onClose } >
74
133
Cancel
75
134
</ Button >
76
- < Button onClick = { handleSave } > Save</ Button >
135
+ < Button onClick = { handleSave } >
136
+ { loading ? < Loader2 className = "w-4 h-4 animate-spin" /> : "Save" }
137
+ </ Button >
77
138
</ div >
78
139
</ DialogContent >
79
140
</ Dialog >
0 commit comments