Vendor backend and frontend into the monorepo

The submodule collapse was only half applied: .gitmodules was deleted but
backend/ and frontend/ were still recorded as gitlinks, so none of their
files were tracked. Replace the gitlinks with the real trees.

Also untrack .env (it carried placeholder secrets) in favour of a tracked
.env.example, drop the committed __pycache__, and narrow the blanket *.png
ignore that would have swallowed design assets.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Melvin Strobl
2026-08-09 15:14:54 +02:00
co-authored by Claude Opus 5
parent 3cb16958ba
commit 1916f7f778
216 changed files with 20353 additions and 54 deletions
@@ -0,0 +1,43 @@
import { Briefcase, Home, Users } from "lucide-react"
import { SidebarAppearance } from "@/components/Common/Appearance"
import { Logo } from "@/components/Common/Logo"
import {
Sidebar,
SidebarContent,
SidebarFooter,
SidebarHeader,
} from "@/components/ui/sidebar"
import useAuth from "@/hooks/useAuth"
import { type Item, Main } from "./Main"
import { User } from "./User"
const baseItems: Item[] = [
{ icon: Home, title: "Dashboard", path: "/" },
{ icon: Briefcase, title: "Items", path: "/items" },
]
export function AppSidebar() {
const { user: currentUser } = useAuth()
const items = currentUser?.is_superuser
? [...baseItems, { icon: Users, title: "Admin", path: "/admin" }]
: baseItems
return (
<Sidebar collapsible="icon">
<SidebarHeader className="px-4 py-6 group-data-[collapsible=icon]:px-0 group-data-[collapsible=icon]:items-center">
<Logo variant="responsive" />
</SidebarHeader>
<SidebarContent>
<Main items={items} />
</SidebarContent>
<SidebarFooter>
<SidebarAppearance />
<User user={currentUser} />
</SidebarFooter>
</Sidebar>
)
}
export default AppSidebar
+60
View File
@@ -0,0 +1,60 @@
import { Link as RouterLink, useRouterState } from "@tanstack/react-router"
import type { LucideIcon } from "lucide-react"
import {
SidebarGroup,
SidebarGroupContent,
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
useSidebar,
} from "@/components/ui/sidebar"
export type Item = {
icon: LucideIcon
title: string
path: string
}
interface MainProps {
items: Item[]
}
export function Main({ items }: MainProps) {
const { isMobile, setOpenMobile } = useSidebar()
const router = useRouterState()
const currentPath = router.location.pathname
const handleMenuClick = () => {
if (isMobile) {
setOpenMobile(false)
}
}
return (
<SidebarGroup>
<SidebarGroupContent>
<SidebarMenu>
{items.map((item) => {
const isActive = currentPath === item.path
return (
<SidebarMenuItem key={item.title}>
<SidebarMenuButton
tooltip={item.title}
isActive={isActive}
asChild
>
<RouterLink to={item.path} onClick={handleMenuClick}>
<item.icon />
<span>{item.title}</span>
</RouterLink>
</SidebarMenuButton>
</SidebarMenuItem>
)
})}
</SidebarMenu>
</SidebarGroupContent>
</SidebarGroup>
)
}
+97
View File
@@ -0,0 +1,97 @@
import { Link as RouterLink } from "@tanstack/react-router"
import { ChevronsUpDown, LogOut, Settings } from "lucide-react"
import { Avatar, AvatarFallback } from "@/components/ui/avatar"
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu"
import {
SidebarMenu,
SidebarMenuButton,
SidebarMenuItem,
useSidebar,
} from "@/components/ui/sidebar"
import useAuth from "@/hooks/useAuth"
import { getInitials } from "@/utils"
interface UserInfoProps {
fullName?: string
email?: string
}
function UserInfo({ fullName, email }: UserInfoProps) {
return (
<div className="flex items-center gap-2.5 w-full min-w-0">
<Avatar className="size-8">
<AvatarFallback className="bg-zinc-600 text-white">
{getInitials(fullName || "User")}
</AvatarFallback>
</Avatar>
<div className="flex flex-col items-start min-w-0">
<p className="text-sm font-medium truncate w-full">{fullName}</p>
<p className="text-xs text-muted-foreground truncate w-full">{email}</p>
</div>
</div>
)
}
export function User({ user }: { user: any }) {
const { logout } = useAuth()
const { isMobile, setOpenMobile } = useSidebar()
if (!user) return null
const handleMenuClick = () => {
if (isMobile) {
setOpenMobile(false)
}
}
const handleLogout = async () => {
logout()
}
return (
<SidebarMenu>
<SidebarMenuItem>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<SidebarMenuButton
size="lg"
className="data-[state=open]:bg-sidebar-accent data-[state=open]:text-sidebar-accent-foreground"
data-testid="user-menu"
>
<UserInfo fullName={user?.full_name} email={user?.email} />
<ChevronsUpDown className="ml-auto size-4 text-muted-foreground" />
</SidebarMenuButton>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-(--radix-dropdown-menu-trigger-width) min-w-56 rounded-lg"
side={isMobile ? "bottom" : "right"}
align="end"
sideOffset={4}
>
<DropdownMenuLabel className="p-0 font-normal">
<UserInfo fullName={user?.full_name} email={user?.email} />
</DropdownMenuLabel>
<DropdownMenuSeparator />
<RouterLink to="/settings" onClick={handleMenuClick}>
<DropdownMenuItem>
<Settings />
User Settings
</DropdownMenuItem>
</RouterLink>
<DropdownMenuItem onClick={handleLogout}>
<LogOut />
Log Out
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</SidebarMenuItem>
</SidebarMenu>
)
}