@@ -12,7 +12,15 @@ import { cn } from "@/lib/utils";
1212import { decrypt , generateAndStoreKey , retrieveKey } from "@/utils/encryption" ;
1313import { useUser } from "@clerk/nextjs" ;
1414import 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" ;
1624import Image from "next/image" ;
1725import { useRouter } from "next/navigation" ;
1826import { useEffect , useState } from "react" ;
@@ -27,6 +35,7 @@ import {
2735import { EmptyState } from "./empty-state" ;
2836import { PasswordDetails } from "./password-details" ;
2937import { Sidebar } from "./sidebar" ;
38+ import { Tooltip , TooltipContent , TooltipTrigger } from "../ui/tooltip" ;
3039
3140interface PasswordEntry {
3241 id : string ;
@@ -65,9 +74,11 @@ export const VaultPage: React.FC<VaultPageProps> = ({ user }) => {
6574 const [ isCreateDialogOpen , setIsCreateDialogOpen ] = useState ( false ) ;
6675 const [ isEditDialogOpen , setIsEditDialogOpen ] = useState ( false ) ;
6776 const [ searchQuery , setSearchQuery ] = useState ( "" ) ;
68- const [ filteredEntries , setFilteredEntries ] = useState < PasswordEntry [ ] > ( [ ] ) ;
6977 const [ passwords , setPasswords ] = useState < PasswordEntry [ ] > ( [ ] ) ;
7078 const [ passwordItems , setPasswordItems ] = useState ( user ?. passwordItems ) ;
79+ const [ sortBy , setSortBy ] = useState < "name" | "created" | "updated" > (
80+ "created"
81+ ) ;
7182
7283 useEffect ( ( ) => {
7384 const ensureEncryptionKey = async ( ) => {
@@ -139,16 +150,59 @@ export const VaultPage: React.FC<VaultPageProps> = ({ user }) => {
139150 decryptPasswords ( ) ;
140151 } , [ user ?. passwordItems , clerkUser , passwordItems ] ) ;
141152
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+
142174 const handleSearchChange = ( e : React . ChangeEvent < HTMLInputElement > ) => {
143175 setSearchQuery ( e . target . value ) ;
144- // Filter entries based on search query
145- // ... (filter entries logic)
146176 } ;
147177
148178 const handleEditEntry = ( ) => {
149179 setIsEditDialogOpen ( true ) ;
150180 } ;
151181
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+
152206 return (
153207 < div className = "flex h-screen overflow-hidden bg-white dark:bg-gray-900" >
154208 < 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 }) => {
182236 onChange = { handleSearchChange }
183237 className = "w-48 focus:ring-0 ring-0 focus-visible:ring-0 dark:bg-gray-800 dark:text-white"
184238 />
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 >
185296 < Button
186297 size = "icon"
187298 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 }) => {
205316 < ScrollArea className = "h-full" >
206317 < div className = "space-y-2 p-4" >
207318 { activeTab === "passwords" &&
208- passwords . map ( ( password ) => (
319+ filteredAndSortedPasswords . map ( ( password ) => (
209320 < ContextMenu key = { password . id } >
210321 < ContextMenuTrigger >
211322 < Button
@@ -363,6 +474,7 @@ export const VaultPage: React.FC<VaultPageProps> = ({ user }) => {
363474 const updatedItems = await getPasswords ( user ?. id as string ) ;
364475 setPasswordItems ( updatedItems ?. passwordItems ) ;
365476 } }
477+ setSelectedEntry = { setSelectedEntry }
366478 />
367479 </ div >
368480 ) ;
0 commit comments