Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • main
1 result

Target

Select target project
  • aolda/proxy-manager-frontend
1 result
Select Git revision
  • main
1 result
Show changes
Commits on Source (2)
...@@ -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 { toast } from 'sonner';
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
...@@ -7,9 +8,19 @@ import { ...@@ -7,9 +8,19 @@ import {
DropdownMenuTrigger, DropdownMenuTrigger,
} 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';
import { Project } from '@/types/project';
export function ProjectSwitcher({ projects, defaultProject }: { projects: string[]; defaultProject: string }) { export function ProjectSwitcher() {
const [selectedProject, setSelectedProject] = React.useState(defaultProject); const { projects, selectedProject, setSelectedProject } = useAuthStore();
const handleSelectProject = async (project: Project) => {
try {
await setSelectedProject(project);
} catch (error) {
console.error(error);
toast.error('프로젝트 권한을 조회할 수 없습니다.');
}
};
return ( return (
<SidebarMenu> <SidebarMenu>
...@@ -24,16 +35,22 @@ export function ProjectSwitcher({ projects, defaultProject }: { projects: string ...@@ -24,16 +35,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={() => handleSelectProject(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 & { role?: string }) | null;
setSelectedProject: (project: Project) => Promise<void>;
login: (username: string, password: string) => void; login: (username: string, password: string) => void;
logout: () => void; logout: () => void;
} }
export const useAuthStore = create<AuthStore>()( export const useAuthStore = create<AuthStore>()(
persist( persist(
(set) => ({ (set, get) => ({
token: null, token: null,
username: '',
isAdmin: false,
projects: [],
selectedProject: null,
setSelectedProject: async (project) => {
set({ selectedProject: project });
console.log(project);
const response = await fetch(`/api/auth/role?projectId=${project.id}`, {
headers: { 'X-Subject-Token': get().token! },
});
if (!response.ok) {
throw new Error('프로젝트 정보를 가져오지 못했습니다.');
}
const { role } = await response.json();
set({ selectedProject: { ...project, role } });
console.log(role);
},
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 +50,12 @@ export const useAuthStore = create<AuthStore>()( ...@@ -23,11 +50,12 @@ 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 });
get().setSelectedProject(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;
}