Files
tabungin/apps/web/src/components/layout/AppSidebar.tsx
dwindown 371b5e0a66 feat: Implement multi-language system (ID/EN) for member dashboard
- Create translation files (locales/id.ts, locales/en.ts)
- Add LanguageContext with useLanguage hook
- Add LanguageToggle component in sidebar
- Default language: Indonesian (ID)
- Translate WalletDialog and TransactionDialog
- Language preference persisted in localStorage
- Type-safe translations with autocomplete

Next: Translate remaining pages (Overview, Wallets, Transactions, Profile)
2025-10-12 08:51:48 +07:00

129 lines
3.9 KiB
TypeScript

import { Home, Wallet, Receipt, User, LogOut } from "lucide-react"
import { Logo } from "../Logo"
import { LanguageToggle } from "../LanguageToggle"
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarGroup,
SidebarGroupContent,
SidebarHeader,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
useSidebar,
} from "@/components/ui/sidebar"
import { useAuth } from "@/contexts/AuthContext"
import { useLanguage } from "@/contexts/LanguageContext"
import { getAvatarUrl } from "@/lib/utils"
interface AppSidebarProps {
currentPage: string
onNavigate: (page: string) => void
}
export function AppSidebar({ currentPage, onNavigate }: AppSidebarProps) {
const { user, logout } = useAuth()
const { isMobile, setOpenMobile } = useSidebar()
const { t } = useLanguage()
const items = [
{
title: t.nav.overview,
url: "/",
icon: Home,
},
{
title: t.nav.wallets,
url: "/wallets",
icon: Wallet,
},
{
title: t.nav.transactions,
url: "/transactions",
icon: Receipt,
},
{
title: t.nav.profile,
url: "/profile",
icon: User,
},
]
return (
<Sidebar>
<SidebarHeader className="p-4">
<div className="mx-auto">
<Logo variant="large"/>
</div>
</SidebarHeader>
<SidebarContent className="p-4">
<SidebarGroup>
<SidebarGroupContent>
<SidebarMenu>
{items.map((item) => {
const isActive = currentPage === item.url
return (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton
asChild
isActive={isActive}
onClick={() => {
onNavigate(item.url)
if (isMobile) setOpenMobile(false)
}}
className={`${
isActive
? 'bg-primary/10 text-primary hover:bg-primary/10 hover:text-primary'
: ''
}`}
>
<button className="cursor-pointer w-full py-6 px-4">
<item.icon />
<span>{item.title}</span>
</button>
</SidebarMenuButton>
</SidebarMenuItem>
)
})}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
</SidebarContent>
<SidebarFooter className="p-4">
<div className="flex items-center justify-between gap-3">
<div className="flex items-center gap-3 flex-1 min-w-0">
{getAvatarUrl(user?.avatarUrl) ? (
<img
src={getAvatarUrl(user?.avatarUrl)!}
alt={user?.name || user?.email || 'User'}
className="h-10 w-10 rounded-full"
/>
) : (
<div className="h-10 w-10 rounded-full bg-primary flex items-center justify-center text-primary-foreground font-semibold">
{user?.name?.[0] || user?.email[0].toUpperCase()}
</div>
)}
<div className="flex flex-col min-w-0">
{user?.name && (
<span className="text-sm font-medium truncate">{user.name}</span>
)}
<span className="text-xs text-muted-foreground truncate">{user?.email}</span>
</div>
</div>
</div>
<div className="flex gap-2 mt-3">
<LanguageToggle />
<button
onClick={logout}
className="flex-1 flex items-center justify-center px-4 py-2 text-sm font-medium text-destructive bg-destructive/10 rounded-lg hover:bg-destructive/20 transition-colors cursor-pointer"
>
<LogOut className="h-4 w-4 mr-2" />
{t.nav.logout}
</button>
</div>
</SidebarFooter>
</Sidebar>
)
}