@@ -12,7 +12,15 @@ import { cn } from "@/lib/utils";
12
12
import { decrypt , generateAndStoreKey , retrieveKey } from "@/utils/encryption" ;
13
13
import { useUser } from "@clerk/nextjs" ;
14
14
import type { Prisma } from "@prisma/client" ;
15
- import { Plus , SquareArrowOutUpRight , Trash , User } from "lucide-react" ;
15
+ import {
16
+ Plus ,
17
+ SquareArrowOutUpRight ,
18
+ Trash ,
19
+ User ,
20
+ ArrowDownAZ ,
21
+ ArrowDownWideNarrow ,
22
+ Clock ,
23
+ } from "lucide-react" ;
16
24
import Image from "next/image" ;
17
25
import { useRouter } from "next/navigation" ;
18
26
import { useEffect , useState } from "react" ;
@@ -27,6 +35,7 @@ import {
27
35
import { EmptyState } from "./empty-state" ;
28
36
import { PasswordDetails } from "./password-details" ;
29
37
import { Sidebar } from "./sidebar" ;
38
+ import { Tooltip , TooltipContent , TooltipTrigger } from "../ui/tooltip" ;
30
39
31
40
interface PasswordEntry {
32
41
id : string ;
@@ -65,9 +74,11 @@ export const VaultPage: React.FC<VaultPageProps> = ({ user }) => {
65
74
const [ isCreateDialogOpen , setIsCreateDialogOpen ] = useState ( false ) ;
66
75
const [ isEditDialogOpen , setIsEditDialogOpen ] = useState ( false ) ;
67
76
const [ searchQuery , setSearchQuery ] = useState ( "" ) ;
68
- const [ filteredEntries , setFilteredEntries ] = useState < PasswordEntry [ ] > ( [ ] ) ;
69
77
const [ passwords , setPasswords ] = useState < PasswordEntry [ ] > ( [ ] ) ;
70
78
const [ passwordItems , setPasswordItems ] = useState ( user ?. passwordItems ) ;
79
+ const [ sortBy , setSortBy ] = useState < "name" | "created" | "updated" > (
80
+ "created"
81
+ ) ;
71
82
72
83
useEffect ( ( ) => {
73
84
const ensureEncryptionKey = async ( ) => {
@@ -139,16 +150,59 @@ export const VaultPage: React.FC<VaultPageProps> = ({ user }) => {
139
150
decryptPasswords ( ) ;
140
151
} , [ user ?. passwordItems , clerkUser , passwordItems ] ) ;
141
152
153
+ useEffect ( ( ) => {
154
+ const sortPasswords = ( ) => {
155
+ const sorted = [ ...passwords ] . sort ( ( a , b ) => {
156
+ switch ( sortBy ) {
157
+ case "name" :
158
+ return a . name . localeCompare ( b . name ) ;
159
+ case "updated" :
160
+ return (
161
+ new Date ( b . updatedAt ) . getTime ( ) - new Date ( a . updatedAt ) . getTime ( )
162
+ ) ;
163
+ default : // 'created'
164
+ return (
165
+ new Date ( b . created ) . getTime ( ) - new Date ( a . created ) . getTime ( )
166
+ ) ;
167
+ }
168
+ } ) ;
169
+ setPasswords ( sorted ) ;
170
+ } ;
171
+ sortPasswords ( ) ;
172
+ } , [ sortBy ] ) ;
173
+
142
174
const handleSearchChange = ( e : React . ChangeEvent < HTMLInputElement > ) => {
143
175
setSearchQuery ( e . target . value ) ;
144
- // Filter entries based on search query
145
- // ... (filter entries logic)
146
176
} ;
147
177
148
178
const handleEditEntry = ( ) => {
149
179
setIsEditDialogOpen ( true ) ;
150
180
} ;
151
181
182
+ const filteredAndSortedPasswords = passwords
183
+ . filter ( ( password ) => {
184
+ if ( ! searchQuery ) return true ;
185
+
186
+ const search = searchQuery . toLowerCase ( ) ;
187
+ return (
188
+ password . name . toLowerCase ( ) . includes ( search ) ||
189
+ password . username . toLowerCase ( ) . includes ( search ) ||
190
+ password . website . toLowerCase ( ) . includes ( search )
191
+ ) ;
192
+ } )
193
+ . sort ( ( a , b ) => {
194
+ switch ( sortBy ) {
195
+ case "name" :
196
+ return a . name . localeCompare ( b . name ) ;
197
+ case "updated" :
198
+ return (
199
+ new Date ( b . updatedAt ) . getTime ( ) - new Date ( a . updatedAt ) . getTime ( )
200
+ ) ;
201
+ default : // 'created'
202
+ return new Date ( b . created ) . getTime ( ) - new Date ( a . created ) . getTime ( ) ;
203
+ }
204
+ } ) ;
205
+
152
206
return (
153
207
< div className = "flex h-screen overflow-hidden bg-white dark:bg-gray-900" >
154
208
< div className = "hidden lg:block lg:w-64 border-r border-gray-100 dark:border-gray-800" >
@@ -182,6 +236,63 @@ export const VaultPage: React.FC<VaultPageProps> = ({ user }) => {
182
236
onChange = { handleSearchChange }
183
237
className = "w-48 focus:ring-0 ring-0 focus-visible:ring-0 dark:bg-gray-800 dark:text-white"
184
238
/>
239
+ < div className = "w-1/3 overflow-y-auto" >
240
+ < div className = "flex items-center gap-2 p-4" >
241
+ < Tooltip >
242
+ < TooltipTrigger asChild >
243
+ < Button
244
+ variant = "ghost"
245
+ size = "sm"
246
+ className = { cn (
247
+ "h-8 w-8 p-0" ,
248
+ sortBy === "name" &&
249
+ "bg-rose-50 dark:bg-rose-900 text-rose-900 dark:text-rose-50"
250
+ ) }
251
+ onClick = { ( ) => setSortBy ( "name" ) }
252
+ >
253
+ < ArrowDownAZ className = "h-4 w-4" />
254
+ </ Button >
255
+ </ TooltipTrigger >
256
+ < TooltipContent > Sort alphabetically</ TooltipContent >
257
+ </ Tooltip >
258
+
259
+ < Tooltip >
260
+ < TooltipTrigger asChild >
261
+ < Button
262
+ variant = "ghost"
263
+ size = "sm"
264
+ className = { cn (
265
+ "h-8 w-8 p-0" ,
266
+ sortBy === "created" &&
267
+ "bg-rose-50 dark:bg-rose-900 text-rose-900 dark:text-rose-50"
268
+ ) }
269
+ onClick = { ( ) => setSortBy ( "created" ) }
270
+ >
271
+ < ArrowDownWideNarrow className = "h-4 w-4" />
272
+ </ Button >
273
+ </ TooltipTrigger >
274
+ < TooltipContent > Sort by created</ TooltipContent >
275
+ </ Tooltip >
276
+
277
+ < Tooltip >
278
+ < TooltipTrigger asChild >
279
+ < Button
280
+ variant = "ghost"
281
+ size = "sm"
282
+ className = { cn (
283
+ "h-8 w-8 p-0" ,
284
+ sortBy === "updated" &&
285
+ "bg-rose-50 dark:bg-rose-900 text-rose-900 dark:text-rose-50"
286
+ ) }
287
+ onClick = { ( ) => setSortBy ( "updated" ) }
288
+ >
289
+ < Clock className = "h-4 w-4" />
290
+ </ Button >
291
+ </ TooltipTrigger >
292
+ < TooltipContent > Sort by updated</ TooltipContent >
293
+ </ Tooltip >
294
+ </ div >
295
+ </ div >
185
296
< Button
186
297
size = "icon"
187
298
className = "bg-rose-50 hover:hover:bg-rose-100 dark:bg-rose-900 dark:hover:bg-rose-800"
@@ -205,7 +316,7 @@ export const VaultPage: React.FC<VaultPageProps> = ({ user }) => {
205
316
< ScrollArea className = "h-full" >
206
317
< div className = "space-y-2 p-4" >
207
318
{ activeTab === "passwords" &&
208
- passwords . map ( ( password ) => (
319
+ filteredAndSortedPasswords . map ( ( password ) => (
209
320
< ContextMenu key = { password . id } >
210
321
< ContextMenuTrigger >
211
322
< Button
@@ -363,6 +474,7 @@ export const VaultPage: React.FC<VaultPageProps> = ({ user }) => {
363
474
const updatedItems = await getPasswords ( user ?. id as string ) ;
364
475
setPasswordItems ( updatedItems ?. passwordItems ) ;
365
476
} }
477
+ setSelectedEntry = { setSelectedEntry }
366
478
/>
367
479
</ div >
368
480
) ;
0 commit comments