Skip to content

Commit aab62d5

Browse files
committed
fix(settings): improve UX and remove broken features
- Remove delete account button (was non-functional placeholder) - Add typing confirmation for delete repos ('delete all' required) - Remove Settings and Global Search from sidebar (Settings in avatar) - Replace custom dropdown with shadcn DropdownMenu (fixes click-outside) - Cleaner sidebar with just Repositories and Documentation
1 parent 4cc24ec commit aab62d5

3 files changed

Lines changed: 85 additions & 137 deletions

File tree

frontend/src/components/dashboard/Sidebar.tsx

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import { Link, useLocation } from 'react-router-dom'
22
import {
33
FolderGit2,
4-
Search,
5-
Settings,
64
BookOpen,
75
ChevronLeft,
86
ChevronRight,
@@ -26,12 +24,10 @@ interface NavItem {
2624

2725
const mainNavItems: NavItem[] = [
2826
{ name: 'Repositories', href: '/dashboard', icon: <FolderGit2 className="w-5 h-5" /> },
29-
{ name: 'Global Search', href: '/dashboard/search', icon: <Search className="w-5 h-5" /> },
3027
]
3128

3229
const bottomNavItems: NavItem[] = [
3330
{ name: 'Documentation', href: '/docs', icon: <BookOpen className="w-5 h-5" />, external: true },
34-
{ name: 'Settings', href: '/dashboard/settings', icon: <Settings className="w-5 h-5" /> },
3531
]
3632

3733
export function Sidebar({ collapsed, onToggle, mobileOpen, onMobileClose }: SidebarProps) {

frontend/src/components/dashboard/TopNav.tsx

Lines changed: 50 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,16 @@
11
import { Link } from 'react-router-dom'
22
import { useAuth } from '../../contexts/AuthContext'
3-
import { useState } from 'react'
4-
import {
5-
Menu,
6-
Search,
7-
Github,
8-
Sun,
9-
Moon,
10-
LogOut,
11-
Settings,
12-
BookOpen,
13-
ExternalLink
14-
} from 'lucide-react'
3+
import { Menu, Search, Github, Sun, Moon, LogOut, Settings, BookOpen, ExternalLink } from 'lucide-react'
154
import { useTheme } from 'next-themes'
165
import { Button } from '@/components/ui/button'
6+
import {
7+
DropdownMenu,
8+
DropdownMenuContent,
9+
DropdownMenuItem,
10+
DropdownMenuLabel,
11+
DropdownMenuSeparator,
12+
DropdownMenuTrigger,
13+
} from '@/components/ui/dropdown-menu'
1714

1815
interface TopNavProps {
1916
onToggleSidebar: () => void
@@ -24,7 +21,6 @@ interface TopNavProps {
2421
export function TopNav({ onToggleSidebar, sidebarCollapsed, onOpenCommandPalette }: TopNavProps) {
2522
const { session, signOut } = useAuth()
2623
const { theme, setTheme } = useTheme()
27-
const [showUserMenu, setShowUserMenu] = useState(false)
2824

2925
const userEmail = session?.user?.email || 'User'
3026
const userInitial = userEmail.charAt(0).toUpperCase()
@@ -55,7 +51,7 @@ export function TopNav({ onToggleSidebar, sidebarCollapsed, onOpenCommandPalette
5551
</div>
5652

5753
{/* Center - Command Palette Trigger */}
58-
<button
54+
<button
5955
className="hidden sm:flex items-center gap-2 px-3 py-1.5 bg-muted border border-border rounded-lg text-muted-foreground hover:text-foreground hover:bg-muted/80 transition-all max-w-xs"
6056
onClick={onOpenCommandPalette}
6157
>
@@ -84,72 +80,49 @@ export function TopNav({ onToggleSidebar, sidebarCollapsed, onOpenCommandPalette
8480
className="text-muted-foreground hover:text-foreground"
8581
aria-label={theme === 'dark' ? 'Switch to light mode' : 'Switch to dark mode'}
8682
>
87-
{theme === 'dark' ? (
88-
<Sun className="w-5 h-5" />
89-
) : (
90-
<Moon className="w-5 h-5" />
91-
)}
83+
{theme === 'dark' ? <Sun className="w-5 h-5" /> : <Moon className="w-5 h-5" />}
9284
</Button>
9385

94-
{/* User Menu */}
95-
<div className="relative">
96-
<button
97-
onClick={() => setShowUserMenu(!showUserMenu)}
98-
className="flex items-center gap-2 p-1 rounded-lg hover:bg-muted transition-colors"
99-
>
100-
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-primary to-purple-600 flex items-center justify-center">
101-
<span className="text-primary-foreground text-sm font-medium">{userInitial}</span>
102-
</div>
103-
</button>
104-
105-
{showUserMenu && (
106-
<>
107-
<div
108-
className="fixed inset-0 z-40"
109-
onClick={() => setShowUserMenu(false)}
110-
/>
111-
<div className="absolute right-0 top-full mt-2 w-56 bg-card border border-border rounded-xl shadow-lg z-50 py-2">
112-
<div className="px-4 py-2 border-b border-border">
113-
<p className="text-sm text-foreground font-medium truncate">{userEmail}</p>
114-
<p className="text-xs text-muted-foreground">Free Plan</p>
115-
</div>
116-
<div className="py-1">
117-
<Link
118-
to="/dashboard/settings"
119-
className="flex items-center gap-2 px-4 py-2 text-sm text-muted-foreground hover:text-foreground hover:bg-muted transition-colors"
120-
onClick={() => setShowUserMenu(false)}
121-
>
122-
<Settings className="w-4 h-4" />
123-
Settings
124-
</Link>
125-
<a
126-
href="/docs"
127-
target="_blank"
128-
rel="noopener noreferrer"
129-
className="flex items-center gap-2 px-4 py-2 text-sm text-muted-foreground hover:text-foreground hover:bg-muted transition-colors"
130-
onClick={() => setShowUserMenu(false)}
131-
>
132-
<BookOpen className="w-4 h-4" />
133-
Documentation
134-
<ExternalLink className="w-3 h-3 ml-auto opacity-50" />
135-
</a>
136-
</div>
137-
<div className="border-t border-border py-1">
138-
<button
139-
onClick={() => {
140-
signOut()
141-
setShowUserMenu(false)
142-
}}
143-
className="flex items-center gap-2 w-full text-left px-4 py-2 text-sm text-destructive hover:bg-muted transition-colors"
144-
>
145-
<LogOut className="w-4 h-4" />
146-
Sign out
147-
</button>
148-
</div>
86+
{/* User Menu - using shadcn DropdownMenu for proper click-outside handling */}
87+
<DropdownMenu>
88+
<DropdownMenuTrigger asChild>
89+
<button className="flex items-center gap-2 p-1 rounded-lg hover:bg-muted transition-colors focus:outline-none">
90+
<div className="w-8 h-8 rounded-full bg-gradient-to-br from-primary to-purple-600 flex items-center justify-center">
91+
<span className="text-primary-foreground text-sm font-medium">{userInitial}</span>
92+
</div>
93+
</button>
94+
</DropdownMenuTrigger>
95+
<DropdownMenuContent align="end" className="w-56">
96+
<DropdownMenuLabel className="font-normal">
97+
<div className="flex flex-col space-y-1">
98+
<p className="text-sm font-medium truncate">{userEmail}</p>
99+
<p className="text-xs text-muted-foreground">Free Plan</p>
149100
</div>
150-
</>
151-
)}
152-
</div>
101+
</DropdownMenuLabel>
102+
<DropdownMenuSeparator />
103+
<DropdownMenuItem asChild>
104+
<Link to="/dashboard/settings" className="cursor-pointer">
105+
<Settings className="mr-2 h-4 w-4" />
106+
Settings
107+
</Link>
108+
</DropdownMenuItem>
109+
<DropdownMenuItem asChild>
110+
<a href="/docs" target="_blank" rel="noopener noreferrer" className="cursor-pointer">
111+
<BookOpen className="mr-2 h-4 w-4" />
112+
Documentation
113+
<ExternalLink className="ml-auto h-3 w-3 opacity-50" />
114+
</a>
115+
</DropdownMenuItem>
116+
<DropdownMenuSeparator />
117+
<DropdownMenuItem
118+
onClick={() => signOut()}
119+
className="text-destructive focus:text-destructive cursor-pointer"
120+
>
121+
<LogOut className="mr-2 h-4 w-4" />
122+
Sign out
123+
</DropdownMenuItem>
124+
</DropdownMenuContent>
125+
</DropdownMenu>
153126
</div>
154127
</div>
155128
</nav>

frontend/src/pages/SettingsPage.tsx

Lines changed: 35 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/com
77
import { Badge } from '@/components/ui/badge'
88
import { Separator } from '@/components/ui/separator'
99
import { Skeleton } from '@/components/ui/Skeleton'
10+
import { Input } from '@/components/ui/input'
11+
import { Label } from '@/components/ui/label'
1012
import {
1113
Dialog,
1214
DialogContent,
@@ -25,18 +27,18 @@ interface Repository {
2527
}
2628

2729
const MAX_REPOS = 3
30+
const DELETE_CONFIRMATION_TEXT = 'delete all'
2831

2932
export function SettingsPage() {
30-
const { user, session, signOut } = useAuth()
33+
const { user, session } = useAuth()
3134
const { status, checkStatus, disconnect, loading: githubLoading } = useGitHubRepos()
3235

3336
const [repos, setRepos] = useState<Repository[]>([])
3437
const [reposLoading, setReposLoading] = useState(true)
3538
const [disconnectLoading, setDisconnectLoading] = useState(false)
3639
const [deleteReposDialog, setDeleteReposDialog] = useState(false)
37-
const [deleteAccountDialog, setDeleteAccountDialog] = useState(false)
3840
const [deleteReposLoading, setDeleteReposLoading] = useState(false)
39-
const [deleteAccountLoading, setDeleteAccountLoading] = useState(false)
41+
const [deleteConfirmation, setDeleteConfirmation] = useState('')
4042

4143
useEffect(() => {
4244
checkStatus()
@@ -70,6 +72,8 @@ export function SettingsPage() {
7072
}
7173

7274
const handleDeleteAllRepos = async () => {
75+
if (deleteConfirmation !== DELETE_CONFIRMATION_TEXT) return
76+
7377
setDeleteReposLoading(true)
7478
try {
7579
for (const repo of repos) {
@@ -80,6 +84,7 @@ export function SettingsPage() {
8084
}
8185
setRepos([])
8286
setDeleteReposDialog(false)
87+
setDeleteConfirmation('')
8388
toast.success('All repositories deleted')
8489
} catch (error) {
8590
toast.error('Failed to delete repositories')
@@ -88,17 +93,9 @@ export function SettingsPage() {
8893
}
8994
}
9095

91-
const handleDeleteAccount = async () => {
92-
setDeleteAccountLoading(true)
93-
try {
94-
toast.info('Account deletion coming soon. Signing you out for now.')
95-
await signOut()
96-
} catch (error) {
97-
toast.error('Failed to delete account')
98-
} finally {
99-
setDeleteAccountLoading(false)
100-
setDeleteAccountDialog(false)
101-
}
96+
const handleCloseDeleteDialog = () => {
97+
setDeleteReposDialog(false)
98+
setDeleteConfirmation('')
10299
}
103100

104101
const formatDate = (dateString: string | undefined) => {
@@ -110,6 +107,8 @@ export function SettingsPage() {
110107
})
111108
}
112109

110+
const isDeleteEnabled = deleteConfirmation === DELETE_CONFIRMATION_TEXT
111+
113112
return (
114113
<div className="max-w-2xl space-y-6">
115114
<div>
@@ -237,7 +236,7 @@ export function SettingsPage() {
237236
<Alert variant="destructive" className="border-destructive/30 bg-destructive/5">
238237
<AlertTriangle className="h-5 w-5" />
239238
<AlertTitle className="text-lg font-semibold">Danger Zone</AlertTitle>
240-
<AlertDescription className="mt-4 space-y-4">
239+
<AlertDescription className="mt-4">
241240
<div className="flex items-center justify-between">
242241
<div>
243242
<p className="font-medium text-foreground">Delete all repositories</p>
@@ -253,26 +252,11 @@ export function SettingsPage() {
253252
Delete All
254253
</Button>
255254
</div>
256-
<Separator className="bg-destructive/20" />
257-
<div className="flex items-center justify-between">
258-
<div>
259-
<p className="font-medium text-foreground">Delete account</p>
260-
<p className="text-sm text-muted-foreground">Permanently delete your account and all data</p>
261-
</div>
262-
<Button
263-
variant="outline"
264-
size="sm"
265-
className="border-destructive/50 text-destructive hover:bg-destructive hover:text-destructive-foreground"
266-
onClick={() => setDeleteAccountDialog(true)}
267-
>
268-
Delete Account
269-
</Button>
270-
</div>
271255
</AlertDescription>
272256
</Alert>
273257

274-
{/* Delete Repos Dialog */}
275-
<Dialog open={deleteReposDialog} onOpenChange={setDeleteReposDialog}>
258+
{/* Delete Repos Dialog with typing confirmation */}
259+
<Dialog open={deleteReposDialog} onOpenChange={handleCloseDeleteDialog}>
276260
<DialogContent>
277261
<DialogHeader>
278262
<DialogTitle>Delete all repositories?</DialogTitle>
@@ -281,39 +265,34 @@ export function SettingsPage() {
281265
all indexed data. This action cannot be undone.
282266
</DialogDescription>
283267
</DialogHeader>
268+
<div className="space-y-2 py-4">
269+
<Label htmlFor="delete-confirmation">
270+
To confirm, type <span className="font-mono font-semibold text-destructive">{DELETE_CONFIRMATION_TEXT}</span> below:
271+
</Label>
272+
<Input
273+
id="delete-confirmation"
274+
value={deleteConfirmation}
275+
onChange={(e) => setDeleteConfirmation(e.target.value)}
276+
placeholder={DELETE_CONFIRMATION_TEXT}
277+
className="font-mono"
278+
autoComplete="off"
279+
/>
280+
</div>
284281
<DialogFooter>
285-
<Button variant="outline" onClick={() => setDeleteReposDialog(false)}>
282+
<Button variant="outline" onClick={handleCloseDeleteDialog}>
286283
Cancel
287284
</Button>
288-
<Button variant="destructive" onClick={handleDeleteAllRepos} disabled={deleteReposLoading}>
285+
<Button
286+
variant="destructive"
287+
onClick={handleDeleteAllRepos}
288+
disabled={!isDeleteEnabled || deleteReposLoading}
289+
>
289290
{deleteReposLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
290291
Delete All Repositories
291292
</Button>
292293
</DialogFooter>
293294
</DialogContent>
294295
</Dialog>
295-
296-
{/* Delete Account Dialog */}
297-
<Dialog open={deleteAccountDialog} onOpenChange={setDeleteAccountDialog}>
298-
<DialogContent>
299-
<DialogHeader>
300-
<DialogTitle>Delete your account?</DialogTitle>
301-
<DialogDescription>
302-
This will permanently delete your account, all repositories, and all associated data. This
303-
action cannot be undone.
304-
</DialogDescription>
305-
</DialogHeader>
306-
<DialogFooter>
307-
<Button variant="outline" onClick={() => setDeleteAccountDialog(false)}>
308-
Cancel
309-
</Button>
310-
<Button variant="destructive" onClick={handleDeleteAccount} disabled={deleteAccountLoading}>
311-
{deleteAccountLoading && <Loader2 className="mr-2 h-4 w-4 animate-spin" />}
312-
Delete Account
313-
</Button>
314-
</DialogFooter>
315-
</DialogContent>
316-
</Dialog>
317296
</div>
318297
)
319298
}

0 commit comments

Comments
 (0)