Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/assets/icon/common/uploadImg.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 4 additions & 4 deletions src/components/common/button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,13 @@ export default function Button({

const variantClasses = {
primary:
"bg-chart-3 text-white hover:opacity-80 disabled:bg-bg-disabled disabled:text-text-disabled disabled:hover:opacity-50",
"bg-chart-3 text-white hover:bg-[#0e6add] disabled:bg-bg-disabled disabled:text-text-disabled disabled:hover:bg-bg-disabled",
secondary:
"bg-gray-100 text-brand-900 hover:bg-gray-200 disabled:bg-bg-disabled disabled:text-text-disabled disabled:hover:opacity-50 disabled:hover:opacity-50",
"bg-gray-100 text-brand-900 hover:bg-gray-200 disabled:bg-bg-disabled disabled:text-text-disabled disabled:hover:bg-bg-disabled",
outline:
"bg-transparent border border-chart-3 text-chart-3 hover:bg-brand-300 disabled:border-gray-300 disabled:text-text-disabled disabled:hover:bg-transparent",
"bg-transparent border border-chart-3 text-chart-3 hover:bg-chart-3/5 disabled:border-gray-300 disabled:text-text-disabled disabled:hover:bg-transparent",
danger:
"bg-status-red text-white hover:opacity-80 disabled:bg-bg-disabled disabled:text-text-disabled disabled:hover:opacity-50",
"bg-status-red text-white hover:bg-[#d91632] disabled:bg-bg-disabled disabled:text-text-disabled disabled:hover:bg-bg-disabled",
dangerSoft:
"bg-status-red/10 text-status-red border border-status-red hover:bg-status-red/20 disabled:bg-bg-disabled disabled:text-text-disabled disabled:border-gray-300 disabled:hover:bg-bg-disabled",
gradient:
Expand Down
42 changes: 22 additions & 20 deletions src/components/common/dropdownmenu/DropdownMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export function DropdownMenu({
<div
id={menuId}
role="menu"
className="absolute right-0 top-full mt-2 w-56 max-w-[calc(100vw-40px)] rounded-component-md bg-brand-200 py-3 px-1 shadow-Medium z-50"
className="absolute right-0 top-full mt-2 w-56 max-w-[calc(100vw-40px)] rounded-component-md bg-brand-200 border border-gray-100 py-3 px-1 shadow-Medium z-50 animate-modal-content origin-top-right"
>
<div className="space-y-1">
{items.map((it, idx) => (
Expand All @@ -70,33 +70,35 @@ export function DropdownMenu({
setOpen(false);
}}
className={twMerge(
"group flex w-full items-center gap-3 rounded-component-md px-5 py-4 text-left font-body2 transition-fast",
"group flex w-full justify-between items-center rounded-component-md px-5 py-4 text-left font-body2 transition-fast",
it.active
? "bg-brand-300 text-status-blue"
: "text-text-main hover:bg-brand-300 hover:text-status-blue",
? "bg-status-blue/10 text-status-blue"
: "text-text-main hover:bg-status-blue/5 hover:text-status-blue",
)}
>
{it.icon ? (
<div className="flex items-center gap-3">
{it.icon ? (
<span
className={twMerge(
"inline-flex h-5 w-5 items-center justify-center text-text-main",
it.active
? "text-status-blue"
: "group-hover:text-status-blue",
)}
aria-hidden="true"
>
{it.icon}
</span>
) : null}
<span
className={twMerge(
"inline-flex h-5 w-5 items-center justify-center text-text-main",
it.active
? "text-status-blue"
: "group-hover:text-status-blue",
"whitespace-nowrap",
it.active ? "font-semibold" : "font-medium",
)}
aria-hidden="true"
>
{it.icon}
{it.label}
</span>
) : null}
<span
className={twMerge(
"whitespace-nowrap",
it.active ? "font-semibold" : "font-medium",
)}
>
{it.label}
</span>
</div>
</button>
</div>
))}
Expand Down
2 changes: 1 addition & 1 deletion src/components/workspace/MemberRoleSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export default function MemberRoleSelect({
>
<span>{ROLE_LABEL_MAP[role]}</span>
<ChevonIcon
className={`h-4 w-4 shrink-0 transition-transform ${open ? "rotate-0" : "-rotate-180"}`}
className={`h-4 w-4 shrink-0 ${open ? "rotate-0" : "-rotate-180"}`}
/>
</div>
)}
Expand Down
121 changes: 51 additions & 70 deletions src/components/workspace/WorkspaceCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,28 +4,16 @@ import { twMerge } from "tailwind-merge";
import type { TWorkspace } from "@/types/workspace/workspace";
import { ROLE_LABEL_MAP } from "@/constants/workspaceRole";

import {
DropdownMenu,
type TMenuItem,
} from "@/components/common/dropdownmenu/DropdownMenu";

import BuildingIcon from "@/assets/icon/common/building.svg?react";
import VectorIcon from "@/assets/icon/common/more.svg?react";
import { getImageUrl } from "@/lib/getImageUrl";

type TProps = {
workspace: TWorkspace;
menuItems: TMenuItem[];
isSelected?: boolean;
onClick?: () => void;
};

function WorkspaceCard({
workspace: w,
menuItems,
isSelected = false,
onClick,
}: TProps) {
function WorkspaceCard({ workspace: w, isSelected = false, onClick }: TProps) {
const [imageError, setImageError] = useState(false);

useEffect(() => {
Expand All @@ -36,68 +24,61 @@ function WorkspaceCard({
const showPlaceholder = !imageSrc || imageError;

return (
<li
className={twMerge(
"flex items-center justify-between rounded-component-md bg-white px-6 py-5 shadow-Soft border tablet:px-4 tablet:py-4",
onClick &&
"cursor-pointer hover:bg-gray-50 active:scale-93 duration-50 transition-transform",
isSelected ? "border-chart-3/80 bg-chart-3/2" : "border-gray-100",
)}
onClick={onClick}
>
<div className="flex items-center gap-5 min-w-0 tablet:gap-3">
<div className="w-24 h-24 bg-gray-100 shrink-0 rounded-component-sm tablet:h-16 tablet:w-16">
{showPlaceholder ? (
<div className="w-full h-full flex items-center justify-center">
<BuildingIcon className="w-8 h-8 text-text-placeholder" />
</div>
) : (
<img
src={imageSrc}
alt={`${w.name} 로고`}
className="w-full h-full object-cover rounded-component-sm"
onError={() => {
setImageError(true);
}}
/>
)}
</div>

<div className="min-w-0">
<div className="flex items-center gap-2">
<div className="font-heading4 font-semibold! text-text-main truncate">
{w.name}
</div>
{isSelected && (
<span
role="status"
className="shrink-0 rounded-full bg-chart-3/12 px-2 py-1 font-caption text-chart-3"
>
현재 대시보드 기준
</span>
<li className="block w-full">
<button
type="button"
className={twMerge(
"w-full text-left flex items-center justify-between rounded-component-md bg-white px-6 py-5 shadow-Soft border tablet:px-4 tablet:py-4 focus-visible:ring-2 focus-visible:ring-chart-3 focus-visible:outline-none",
onClick &&
"cursor-pointer hover:bg-chart-3/5 hover:-translate-y-1 hover:shadow-Medium hover:border-chart-3/30 active:scale-[0.98] transition-all duration-300 ease-out",
isSelected
? "border-chart-3/80 bg-chart-3/5"
: "border-gray-100 focus-within:border-chart-3/50",
)}
onClick={onClick}
>
<div className="flex items-center gap-5 min-w-0 tablet:gap-3">
<div className="w-24 h-24 bg-gray-100 shrink-0 rounded-component-sm tablet:h-16 tablet:w-16">
{showPlaceholder ? (
<div className="w-full h-full flex items-center justify-center">
<BuildingIcon className="w-8 h-8 text-text-placeholder" />
</div>
) : (
<img
src={imageSrc}
alt={`${w.name} 로고`}
className="w-full h-full object-cover rounded-component-sm pointer-events-none"
onError={() => {
setImageError(true);
}}
/>
)}
</div>

<div className="font-body2 text-text-main mt-1 truncate">
{w.description ?? ""}
</div>
<div className="font-body1 text-text-sub mt-2">
{ROLE_LABEL_MAP[w.myRole]}
<div className="min-w-0">
<div className="flex items-center gap-2">
<div className="font-heading4 font-semibold! text-text-main truncate">
{w.name}
</div>
{isSelected && (
<span
role="status"
className="shrink-0 rounded-full bg-chart-3/12 px-2 py-1 font-caption text-chart-3"
>
현재 대시보드 기준
</span>
)}
</div>

<div className="font-body2 text-text-main mt-1 truncate">
{w.description ?? ""}
</div>
<div className="font-body1 text-text-sub mt-2">
{ROLE_LABEL_MAP[w.myRole]}
</div>
</div>
</div>
</div>

<div
className="flex items-center gap-4 shrink-0"
onClick={(e) => e.stopPropagation()}
>
<DropdownMenu
trigger={<VectorIcon aria-hidden="true" />}
aria-label={`${w.name} 워크스페이스 메뉴`}
className="h-10 w-10 cursor-pointer rounded-component-sm hover:bg-gray-100 transition-colors flex items-center justify-center"
items={menuItems}
/>
</div>
</button>
</li>
);
}
Expand Down
34 changes: 27 additions & 7 deletions src/components/workspace/WorkspaceListLoading.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,33 @@
import { Skeleton } from "@/components/common/skeleton/Skeleton";

function WorkspaceCardSkeleton() {
return (
<>
<li className="flex items-center justify-between rounded-component-md bg-white px-6 py-5 shadow-Soft border border-gray-100 tablet:px-4 tablet:py-4">
<div className="flex items-center gap-5 min-w-0 tablet:gap-3">
<Skeleton className="w-24 h-24 shrink-0 rounded-component-sm tablet:h-16 tablet:w-16" />
<div className="flex flex-col gap-2 min-w-0">
<Skeleton className="h-5 w-40" />
<Skeleton className="h-4 w-64" />
<Skeleton className="h-4 w-16" />
</div>
</div>
<Skeleton className="h-10 w-10 shrink-0 rounded-component-sm" />
</li>
</>
);
}

export default function WorkspaceListLoading() {
return (
<div
<ul
role="status"
aria-live="polite"
className="bg-white p-10 text-center border border-gray-100 rounded-component-lg"
aria-label="워크스페이스 목록 불러오는 중"
className="space-y-5"
>
<p className="font-body2 text-text-sub">
워크스페이스 목록을 불러오는 중입니다.
</p>
</div>
{Array.from({ length: 3 }).map((_, i) => (
<WorkspaceCardSkeleton key={i} />
))}
</ul>
);
}
33 changes: 33 additions & 0 deletions src/layout/workspace/WorkspaceManageLayout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { NavLink, Outlet, useParams } from "react-router-dom";

const tabLinkClass = ({ isActive }: { isActive: boolean }) =>
`px-4 py-2 font-body1 border-b-2 -mb-px transition-colors ${
isActive
? "border-chart-3 text-chart-3"
: "border-transparent text-text-sub hover:text-text-main"
}`;

export default function WorkspaceManageLayout() {
const { workspaceId } = useParams();

return (
<section className="w-full flex flex-col gap-8">
<nav className="flex border-b border-gray-100">
<NavLink
to={`/workspace/${workspaceId}/settings`}
className={tabLinkClass}
>
기본 정보
</NavLink>
<NavLink
to={`/workspace/${workspaceId}/members`}
className={tabLinkClass}
>
멤버 관리
</NavLink>
</nav>

<Outlet />
</section>
);
}
21 changes: 1 addition & 20 deletions src/pages/workspace/MemberManagement.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { useEffect, useMemo, useRef, useState } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { useParams } from "react-router-dom";
import {
useInfiniteQuery,
useMutation,
Expand All @@ -15,7 +15,6 @@ import {
type TWorkspaceMember,
} from "@/types/workspace/workspace";

import PageHeader from "@/components/common/PageHeader";
import DeleteMemberModal from "@/components/workspace/DeleteMemberModal";
import MemberList from "@/components/workspace/MemberList";
import PermissionTable from "@/components/workspace/PermissionTable";
Expand All @@ -30,7 +29,6 @@ import {
const PAGE_SIZE = 20;

export default function MemberManagement() {
const navigate = useNavigate();
const { workspaceId } = useParams<{ workspaceId: string }>();
const orgId = Number(workspaceId);
const queryClient = useQueryClient();
Expand Down Expand Up @@ -248,23 +246,6 @@ export default function MemberManagement() {

return (
<section className="w-full min-w-0 flex flex-col gap-8">
<div className="flex flex-col gap-3">
<button
type="button"
onClick={() => {
void navigate(-1);
}}
className="inline-flex w-fit items-center gap-1 text-text-sub transition-colors hover:text-text-main"
>
<span aria-hidden="true">←</span>
<span className="font-body2">뒤로 이동</span>
</button>
<PageHeader
title="멤버 관리"
description="팀 멤버를 효율적으로 관리하세요"
/>
</div>

<div className="flex w-full min-w-0 flex-col gap-10">
<MemberList
orgId={orgId}
Expand Down
Loading
Loading