Skip to content
Snippets Groups Projects
Commit 9b250a41 authored by 한동현's avatar 한동현
Browse files

feat: 프로젝트 목록 API 연동

parent b3096d94
Branches
No related tags found
No related merge requests found
...@@ -17,7 +17,6 @@ import { ...@@ -17,7 +17,6 @@ import {
import { ProjectSwitcher } from '@/components/project-switcher'; import { ProjectSwitcher } from '@/components/project-switcher';
const data = { const data = {
projects: ['aolda_edu', 'proxy_manager', 'blog'],
menus: [ menus: [
{ {
title: '웹 프록시 서버', title: '웹 프록시 서버',
...@@ -58,15 +57,15 @@ const data = { ...@@ -58,15 +57,15 @@ const data = {
}; };
export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) { export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
const { token } = useAuthStore(); const { token, selectedProject } = useAuthStore();
const location = useLocation(); const location = useLocation();
const selected = location.pathname.split('/')[1] || ''; const selected = location.pathname.split('/')[1] || '';
return ( return (
<Sidebar className="top-(--header-height) h-[calc(100svh-var(--header-height))]!" {...props}> <Sidebar className="top-(--header-height) h-[calc(100svh-var(--header-height))]!" {...props}>
{token ? ( {token && selectedProject ? (
<SidebarHeader> <SidebarHeader>
<ProjectSwitcher projects={data.projects} defaultProject={data.projects[0]} /> <ProjectSwitcher />
</SidebarHeader> </SidebarHeader>
) : null} ) : null}
<SidebarContent> <SidebarContent>
...@@ -78,7 +77,7 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) { ...@@ -78,7 +77,7 @@ export function AppSidebar({ ...props }: React.ComponentProps<typeof Sidebar>) {
{item.items.map((item) => ( {item.items.map((item) => (
<SidebarMenuItem key={item.title}> <SidebarMenuItem key={item.title}>
<SidebarMenuButton asChild isActive={selected === item.url}> <SidebarMenuButton asChild isActive={selected === item.url}>
<Link to={item.url} aria-disabled={token === null}> <Link to={item.url} aria-disabled={!token || !selectedProject}>
<item.icon /> <item.icon />
{item.title} {item.title}
</Link> </Link>
......
import * as React from 'react'; import { useAuthStore } from '@/stores/authStore';
import { Check, ChevronsUpDown, GalleryVerticalEnd } from 'lucide-react'; import { Check, ChevronsUpDown, GalleryVerticalEnd } from 'lucide-react';
import { import {
DropdownMenu, DropdownMenu,
...@@ -8,8 +8,8 @@ import { ...@@ -8,8 +8,8 @@ import {
} from '@/components/ui/dropdown-menu'; } from '@/components/ui/dropdown-menu';
import { SidebarMenu, SidebarMenuButton, SidebarMenuItem } from '@/components/ui/sidebar'; import { SidebarMenu, SidebarMenuButton, SidebarMenuItem } from '@/components/ui/sidebar';
export function ProjectSwitcher({ projects, defaultProject }: { projects: string[]; defaultProject: string }) { export function ProjectSwitcher() {
const [selectedProject, setSelectedProject] = React.useState(defaultProject); const { projects, selectedProject, setSelectedProject } = useAuthStore();
return ( return (
<SidebarMenu> <SidebarMenu>
...@@ -24,16 +24,22 @@ export function ProjectSwitcher({ projects, defaultProject }: { projects: string ...@@ -24,16 +24,22 @@ export function ProjectSwitcher({ projects, defaultProject }: { projects: string
<GalleryVerticalEnd className="size-4" /> <GalleryVerticalEnd className="size-4" />
</div> </div>
<div className="flex flex-col gap-0.5 leading-none"> <div className="flex flex-col gap-0.5 leading-none">
{selectedProject ? (
<>
<span className="font-semibold">Project</span> <span className="font-semibold">Project</span>
<span className="">{selectedProject}</span> <span>{selectedProject.name}</span>
</>
) : (
<span className="text-muted-foreground">프로젝트를 선택해주세요</span>
)}
</div> </div>
<ChevronsUpDown className="ml-auto" /> <ChevronsUpDown className="ml-auto" />
</SidebarMenuButton> </SidebarMenuButton>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent className="w-[--radix-dropdown-menu-trigger-width]" align="start"> <DropdownMenuContent className="w-[--radix-dropdown-menu-trigger-width]" align="start">
{projects.map((project) => ( {projects.map((project) => (
<DropdownMenuItem key={project} onSelect={() => setSelectedProject(project)}> <DropdownMenuItem key={project.id} onSelect={() => setSelectedProject(project)}>
{project} {project === selectedProject && <Check className="ml-auto" />} {project.name} {project.id === selectedProject?.id && <Check className="ml-auto" />}
</DropdownMenuItem> </DropdownMenuItem>
))} ))}
</DropdownMenuContent> </DropdownMenuContent>
......
...@@ -16,7 +16,7 @@ import { ...@@ -16,7 +16,7 @@ import {
export function SiteHeader() { export function SiteHeader() {
const navigate = useNavigate(); const navigate = useNavigate();
const { toggleSidebar } = useSidebar(); const { toggleSidebar } = useSidebar();
const { token, logout } = useAuthStore(); const { token, username, logout } = useAuthStore();
const handleLogout = () => { const handleLogout = () => {
logout(); logout();
...@@ -36,11 +36,11 @@ export function SiteHeader() { ...@@ -36,11 +36,11 @@ export function SiteHeader() {
<DropdownMenu> <DropdownMenu>
<DropdownMenuTrigger asChild className="ml-auto cursor-pointer"> <DropdownMenuTrigger asChild className="ml-auto cursor-pointer">
<Avatar className="w-10 h-10 border"> <Avatar className="w-10 h-10 border">
<AvatarFallback>{'admin'.charAt(0)}</AvatarFallback> <AvatarFallback>{username.charAt(0)}</AvatarFallback>
</Avatar> </Avatar>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">
<DropdownMenuLabel>닉네임</DropdownMenuLabel> <DropdownMenuLabel>{username}</DropdownMenuLabel>
<DropdownMenuSeparator /> <DropdownMenuSeparator />
<DropdownMenuItem onClick={handleLogout} className="cursor-pointer"> <DropdownMenuItem onClick={handleLogout} className="cursor-pointer">
<LogOut /> <LogOut />
......
import { create } from 'zustand'; import { create } from 'zustand';
import { persist } from 'zustand/middleware'; import { persist } from 'zustand/middleware';
import { Project } from '@/types/project';
export interface AuthStore { export interface AuthStore {
token: string | null; token: string | null;
username: string;
isAdmin: boolean;
projects: Project[];
selectedProject: Project | null;
setSelectedProject: (project: Project) => void;
login: (username: string, password: string) => void; login: (username: string, password: string) => void;
logout: () => void; logout: () => void;
} }
...@@ -11,6 +17,11 @@ export const useAuthStore = create<AuthStore>()( ...@@ -11,6 +17,11 @@ export const useAuthStore = create<AuthStore>()(
persist( persist(
(set) => ({ (set) => ({
token: null, token: null,
username: '',
isAdmin: false,
projects: [],
selectedProject: null,
setSelectedProject: (project) => set({ selectedProject: project }),
login: async (username, password) => { login: async (username, password) => {
const response = await fetch('/api/auth/login', { const response = await fetch('/api/auth/login', {
method: 'POST', method: 'POST',
...@@ -23,11 +34,11 @@ export const useAuthStore = create<AuthStore>()( ...@@ -23,11 +34,11 @@ export const useAuthStore = create<AuthStore>()(
} }
const token = response.headers.get('X-Subject-Token')!; const token = response.headers.get('X-Subject-Token')!;
console.log(token); const { isAdmin, projects } = await response.json();
set({ token }); set({ token, username, isAdmin, projects, selectedProject: projects[0] });
}, },
logout: () => set({ token: null }), logout: () => set({ token: null, username: '', isAdmin: false, projects: [], selectedProject: null }),
}), }),
{ name: 'authStorage' } { name: 'authStorage' }
) )
......
export interface Project {
id: string;
name: string;
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment