본문 바로가기
Clone Coding

Jira-clone - 독립 실행형 레이아웃과 워크스페이스 생성 페이지 구현

by zzuny-code 2025. 10. 10.
반응형

https://www.youtube.com/watch?v=Av9C7xlV0fA

06:12:10

 

개별 독립 실행형(누락된 공간에 대한 경로 생성하기)

src/app/
  ├── (standalone)/
  │   ├── layout.tsx          # 독립 실행형 레이아웃
  │   └── workspaces/
  │       └── create/
  │           └── page.tsx    # 워크스페이스 생성 페이지
  └── (dashboard)/            # 대시보드용 레이아웃 그룹

 

조건부 렌더링으로 컨텍스트에 맞는 UI 제공

- workspaceId가 없는경우 생성부터 시작하기!

src/app/(standalone)/workspaces/create/page.tsx

import { CreateWorkspaceForm } from "@/features/workspaces/component/create-workspace-form"


const WorkspaceCreatePage = () => {
    return(
        <div className="w-full lg:max-w-xl">
            <CreateWorkspaceForm/>
        </div>
    )
}

export default WorkspaceCreatePage

 

 

src/app/(standalone)/layout.tsx

import { UserButton } from "@/features/auth/components/user-button"
import Image from "next/image"
import Link from "next/link"

interface StandloneLayoutProps {
    children: React.ReactNode
}

const StandloneLayout = ({children}:StandloneLayoutProps) => {
    return(
        <main className="bg-neutral-100 min-h-screen">
            <div className="mx-auto max-w-screen-2xl p-4">
                <nav className="flex justify-between items-center h-[73px">
                    <Link href="/">
                        <Image src='/logo.svg' alt="logo" height={56} width={152}/>
                    </Link>  
                    <UserButton/>              
                </nav>
                <div className="flex flex-col items-center justify-center py-4">
                    {children}
                </div>
            </div>
        </main>
    )
}

export default StandloneLayout

 

http://localhost:3000/workspaces/create

 

 

Cancel 버튼 조건부 스타일링

 

src/features/workspaces/components/create-workspace-form.tsx

 

  • 독립 페이지 (/workspaces/create): Cancel 기능이 불필요 → 버튼 숨김
  • 모달 내부 (/workspaces/[id]?create-workspace=true): Cancel로 모달 닫기 → 버튼 표시

 

import { cn } from "@/lib/utils";

<Button
    type="button"
    size="lg"
    variant='secondary'
    onClick={onCancel}
    disabled={isPending}
    className={cn(!onCancel && "invisible")}
    // onCancel prop이 전달되지 않으면 버튼을 invisible 처리하여 레이아웃은 유지하되 시각적으로 숨김
>
    Cancel
</Button>

 

 

워크스페이스 ID 페이지 보안

- 워크스페이스 ID 페이지에서 로그아웃 시 로그인 페이지로 리다이렉트되지 않는 문제

- 서버 사이드 인증 체크로 보안 강화

 

src/app/(dashboard)/workspaces/[workspaceId]/page.tsx

import { getCurrent } from "@/features/auth/actions";
import { redirect } from "next/navigation";

const WorkspaceIdPage = async() => {

    const user = await getCurrent();
    if(!user) redirect("/sign-in")
        
    return(
        <div>
            WorkspaceIdPage
        </div>
    )
}

export default WorkspaceIdPage;

 

src/app/(standalone)/workspaces/create/page.tsx

import { getCurrent } from "@/features/auth/actions";
import { CreateWorkspaceForm } from "@/features/workspaces/component/create-workspace-form"
import { redirect } from "next/navigation";


const WorkspaceCreatePage = async() => {

    const user = await getCurrent();
    if(!user) redirect("/sign-in")

    return(
        <div className="w-full lg:max-w-xl">
            <CreateWorkspaceForm/>
        </div>
    )
}

export default WorkspaceCreatePage

 

반응형