- 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)
129 lines
3.9 KiB
TypeScript
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>
|
|
)
|
|
}
|