@@ -4,7 +4,7 @@ import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui/card";
44import { Button } from "@/components/ui/button" ;
55import { Input } from "@/components/ui/input" ;
66import { Progress } from "@/components/ui/progress" ;
7- import { Check , Circle , Loader2 , Monitor , Sun , Moon , AlertTriangle } from "lucide-react" ;
7+ import { Check , Circle , Loader2 , Monitor , Sun , Moon , AlertTriangle , LogOut } from "lucide-react" ;
88import { Theme , setTheme , getTheme } from "@/hooks/useSystemTheme" ;
99import { apiFetch } from "@/lib/api" ;
1010import { ModelSelector } from "@/components/ModelSelector" ;
@@ -67,6 +67,10 @@ export default function SettingsPage({ onNameChange }: SettingsPageProps) {
6767 const [ staleEmbeddingsCount , setStaleEmbeddingsCount ] = useState ( 0 ) ;
6868 const [ showReembedDialog , setShowReembedDialog ] = useState ( false ) ;
6969
70+ // Lock app state
71+ const [ showLockDialog , setShowLockDialog ] = useState ( false ) ;
72+ const [ locking , setLocking ] = useState ( false ) ;
73+
7074 // Use the reembed job hook for background re-embedding
7175 const reembedJob = useReembedJob ( {
7276 onComplete : ( processed , failed ) => {
@@ -309,6 +313,20 @@ export default function SettingsPage({ onNameChange }: SettingsPageProps) {
309313 setTheme ( newTheme ) ;
310314 } ;
311315
316+ const handleLock = async ( ) => {
317+ setLocking ( true ) ;
318+ try {
319+ const res = await apiFetch ( "/api/auth/logout" , { method : "POST" } ) ;
320+ if ( res . ok ) {
321+ window . location . reload ( ) ;
322+ }
323+ } catch ( err ) {
324+ console . error ( "Failed to lock:" , err ) ;
325+ } finally {
326+ setLocking ( false ) ;
327+ }
328+ } ;
329+
312330 // Progress from the job hook
313331 const progressPercent = reembedJob . progress ;
314332
@@ -567,6 +585,26 @@ export default function SettingsPage({ onNameChange }: SettingsPageProps) {
567585 </ Button >
568586 </ CardContent >
569587 </ Card >
588+
589+ { /* Security Section */ }
590+ < Card >
591+ < CardHeader >
592+ < CardTitle className = "text-base" > Security</ CardTitle >
593+ </ CardHeader >
594+ < CardContent >
595+ < Button
596+ variant = "outline"
597+ onClick = { ( ) => setShowLockDialog ( true ) }
598+ className = "w-full"
599+ >
600+ < LogOut className = "h-4 w-4 mr-2" />
601+ Lock App
602+ </ Button >
603+ < p className = "text-xs text-muted-foreground mt-2 text-center" >
604+ Lock the app and require password to access
605+ </ p >
606+ </ CardContent >
607+ </ Card >
570608 </ div >
571609
572610 { /* Settings change warning dialog - rendered via portal for full-screen overlay */ }
@@ -685,6 +723,39 @@ export default function SettingsPage({ onNameChange }: SettingsPageProps) {
685723 </ div > ,
686724 document . body
687725 ) }
726+
727+ { /* Lock app confirmation dialog */ }
728+ { showLockDialog && createPortal (
729+ < div className = "fixed inset-0 bg-black/50 flex items-center justify-center z-[100]" >
730+ < div className = "bg-background border rounded-lg shadow-xl max-w-md w-full mx-4 p-6" >
731+ < div className = "flex items-start gap-3 mb-4" >
732+ < LogOut className = "h-6 w-6 text-muted-foreground flex-shrink-0 mt-0.5" />
733+ < div >
734+ < h3 className = "font-semibold text-lg" > Lock App?</ h3 >
735+ < p className = "text-sm text-muted-foreground mt-1" >
736+ You'll need to enter your password to unlock.
737+ </ p >
738+ </ div >
739+ </ div >
740+
741+ < div className = "flex flex-col gap-2" >
742+ < Button onClick = { handleLock } disabled = { locking } className = "w-full" >
743+ { locking && < Loader2 className = "h-4 w-4 animate-spin mr-2" /> }
744+ Lock
745+ </ Button >
746+ < Button
747+ variant = "ghost"
748+ onClick = { ( ) => setShowLockDialog ( false ) }
749+ disabled = { locking }
750+ className = "w-full"
751+ >
752+ Cancel
753+ </ Button >
754+ </ div >
755+ </ div >
756+ </ div > ,
757+ document . body
758+ ) }
688759 </ >
689760 ) ;
690761}
0 commit comments