Files
app/components/UserMenu.tsx
Albert 57d5405c41 feat: Move theme toggle to profile dropdown with icon-only SegmentedControl
Changes:
- Moved theme toggle from separate DesktopSidebar component into UserMenu dropdown
- Replaced simple light/dark toggle with SegmentedControl offering three options:
  - Light (sun icon)
  - Dark (moon icon)
  - System/Auto (desktop icon)
- Uses icon-only labels for compact display in dropdown menu
- Defaults to 'auto' mode (respects system preference) as configured in layout.tsx
- Removed standalone ThemeToggle component from DesktopSidebar

Benefits:
- Cleaner navigation UI with one less separate control
- Better UX with system preference option
- More compact dropdown menu layout

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-10 01:26:33 +00:00

184 lines
4.8 KiB
TypeScript

'use client';
import { useState, useEffect } from 'react';
import { Menu, Avatar, NavLink, ActionIcon, SegmentedControl, Text } from '@mantine/core';
import { useMantineColorScheme } from '@mantine/core';
import { IconSun, IconMoon, IconDeviceDesktop } from '@tabler/icons-react';
import { useRouter } from 'next/navigation';
interface UserProfile {
did: string;
handle: string;
displayName: string | null;
avatar: string | null;
}
export function UserMenu({ showLabel = false }: { showLabel?: boolean } = {}) {
const router = useRouter();
const { colorScheme, setColorScheme } = useMantineColorScheme();
const [profile, setProfile] = useState<UserProfile | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
// Fetch user profile on mount
fetch('/api/user/profile')
.then((res) => res.json())
.then((data) => {
if (!data.error) {
setProfile(data);
}
})
.catch((error) => {
console.error('Failed to fetch profile:', error);
})
.finally(() => {
setLoading(false);
});
}, []);
const handleLogout = async () => {
try {
await fetch('/api/auth/logout', { method: 'POST' });
router.push('/login');
} catch (error) {
console.error('Logout failed:', error);
}
};
if (loading || !profile) {
return showLabel ? (
<NavLink
label="Profile"
leftSection={
<Avatar radius="xl" size={20} color="gray">
?
</Avatar>
}
variant="filled"
color="blue"
styles={{
root: {
borderRadius: '8px',
fontWeight: 400,
},
}}
disabled
/>
) : (
<ActionIcon variant="subtle" color="gray" size={40} radius="md">
<Avatar radius="xl" size={24} color="gray">
?
</Avatar>
</ActionIcon>
);
}
// Get display name or handle
const displayText = profile.displayName || profile.handle;
// Get initials for fallback
const initials = profile.displayName
? profile.displayName
.split(' ')
.map((n) => n[0])
.join('')
.toUpperCase()
.slice(0, 2)
: profile.handle.slice(0, 2).toUpperCase();
return (
<Menu shadow="md" width={200} position="bottom-end">
<Menu.Target>
{showLabel ? (
<NavLink
label="Profile"
leftSection={
<Avatar
src={profile.avatar}
alt={displayText}
radius="xl"
size={20}
>
{initials}
</Avatar>
}
variant="filled"
color="blue"
styles={{
root: {
borderRadius: '8px',
fontWeight: 400,
},
}}
/>
) : (
<ActionIcon variant="subtle" color="gray" size={40} radius="md">
<Avatar
src={profile.avatar}
alt={displayText}
radius="xl"
size={24}
>
{initials}
</Avatar>
</ActionIcon>
)}
</Menu.Target>
<Menu.Dropdown>
<Menu.Label>
{displayText}
<br />
<span style={{ color: 'var(--mantine-color-dimmed)', fontSize: '0.75rem' }}>
@{profile.handle}
</span>
</Menu.Label>
<Menu.Divider />
{/* Theme Selection */}
<div style={{ padding: '8px 12px' }}>
<Text size="xs" fw={500} c="dimmed" mb={8}>
Theme
</Text>
<SegmentedControl
value={colorScheme}
onChange={(value) => setColorScheme(value as 'light' | 'dark' | 'auto')}
data={[
{
value: 'light',
label: (
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<IconSun size={16} />
</div>
),
},
{
value: 'dark',
label: (
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<IconMoon size={16} />
</div>
),
},
{
value: 'auto',
label: (
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<IconDeviceDesktop size={16} />
</div>
),
},
]}
fullWidth
size="xs"
/>
</div>
<Menu.Divider />
<Menu.Item onClick={handleLogout} c="red">
Log out
</Menu.Item>
</Menu.Dropdown>
</Menu>
);
}