Skip to content

Commit

Permalink
Merge pull request #174 from boostcampwm-2024/feature/hub-pages
Browse files Browse the repository at this point in the history
허브 페이지 기능까지 완성(수정 제외)
  • Loading branch information
SeoGeonhyuk authored Dec 3, 2024
2 parents 4d2df23 + 836cc71 commit f823e3e
Show file tree
Hide file tree
Showing 19 changed files with 350 additions and 72 deletions.
47 changes: 38 additions & 9 deletions apps/hub/src/app/architectures/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
'use client';
import { DeleteIcon } from '@/ui/DeleteIcon';
import { EditIcon } from '@/ui/EditIcon';
import { ErrorMessage } from '@/ui/ErrorMessage';
import { ImportIcon } from '@/ui/ImportIcon';
import { LoadingSpinner } from '@/ui/LoadingSpinner';
Expand All @@ -17,6 +19,7 @@ interface PublicArchitecture {
cost: number;
tags: { tag: { name: string } }[];
stars: any[];
isAuthor: boolean;
_count: {
stars: number;
imports: number;
Expand All @@ -40,12 +43,27 @@ export default function ArchitectureDetailPage() {
cost,
tags,
stars: starData,
isAuthor,
_count: { stars, imports },
} = data!;

const isStarred = starData?.length > 0;
const isLoggedIn = localStorage.getItem('isLoggedIn') !== null;

const handleDelete = async () => {
const shouldDelete = confirm('삭제하시겠습니까?');
if (!shouldDelete) return;
await fetch(
`${process.env.BACK_URL}/public-architectures/${params.id}`,
{
method: 'DELETE',
credentials: 'include',
},
);
alert('삭제되었습니다.');
location.href = '/';
};

const toggleStar = async () => {
await fetch(
`${process.env.BACK_URL}/public-architectures/${params.id}/stars`,
Expand Down Expand Up @@ -81,25 +99,36 @@ export default function ArchitectureDetailPage() {
<Tag key={name} tag={name} />
))}
</div>
<h2 className="text-4xl font-extrabold">{title}</h2>
</div>
<div className="flex gap-6 text-gray-500 text-sm">
<div className="flex gap-1">
<span>by</span>
<span className="text-black">{author}</span>
<div className="flex gap-2">
<h2 className="text-4xl font-extrabold flex-1">
{title}
</h2>
{isAuthor && (
<>
<button>
<EditIcon />
</button>
<button onClick={handleDelete}>
<DeleteIcon />
</button>
</>
)}
</div>
</div>
<div className="flex gap-4 text-gray-500 text-sm">
<div>{new Date(createdAt).toLocaleString()}</div>
<div>{author}</div>
<div className="flex gap-1">
<span className="text-black">{imports}</span>
<span>{imports}</span>
<span>imported</span>
</div>
</div>
<div className="flex gap-4 justify-end items-center">
<div className="mr-2">
<span className="font-black text-xl text-emerald-600">
${cost}
{cost}
</span>
<span className="text-xs"> / month</span>
<span> / month</span>
</div>
<button
className={`flex items-center gap-1 ${isLoggedIn && isStarred ? 'text-yellow-400' : 'text-gray-300'}`}
Expand Down
Binary file added apps/hub/src/app/favicon.ico
Binary file not shown.
6 changes: 6 additions & 0 deletions apps/hub/src/app/fonts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import localFont from 'next/font/local';

export const gamjaFlower = localFont({
src: '../fonts/GamjaFlower-Regular.ttf',
variable: '--font-gamja-flower',
});
7 changes: 4 additions & 3 deletions apps/hub/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import type { Metadata } from 'next';
import './globals.css';
import { GlobalHeader } from '@/components/GlobalHeader';
import { gamjaFlower } from './fonts';

export const metadata: Metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
title: 'Cloud Canvas',
description: 'Draw your cloud architecture with ease',
};

export default function RootLayout({
children,
}: Readonly<{ children: React.ReactNode }>) {
return (
<html lang="ko">
<body className={`antialiased`}>
<body className={`antialiased ${gamjaFlower.variable}`}>
<GlobalHeader />
<main className="max-w-7xl mx-auto mt-10">{children}</main>
</body>
Expand Down
14 changes: 6 additions & 8 deletions apps/hub/src/components/ArchitectureBoard/ArchitectureItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,26 +23,24 @@ export const ArchitectureItem = ({
}) => {
const { imports, stars } = _count;
return (
<li className="hover:bg-gray-50 border-b flex px-3 py-2 pl-4">
<li className="hover:bg-gray-50 border-b flex px-3 py-2 pl-4 items-center">
<div className="flex flex-col w-full">
<div>
<Link href={`/architectures/${id}`}>{title}</Link>
</div>
<div className="text-xs text-gray-400 flex">
<div className="text-xs text-gray-400 flex gap-3">
<div>{new Date(createdAt).toLocaleString()}</div>
<div className="ml-2">{author.name}</div>
<div>{author.name}</div>
</div>
<div className="flex gap-1 mt-1">
{tags?.map(({ tag: { name } }) => (
<Tag key={name} tag={name} />
))}
</div>
</div>
<div className="flex items-center text-sm">
<div className="w-28">{cost}</div>
<div className="w-28">{imports}</div>
<div className="w-28">{stars}</div>
</div>
<div className="w-52">{cost}</div>
<div className="w-40">{stars}</div>
<div className="w-40">{imports}</div>
</li>
);
};
6 changes: 3 additions & 3 deletions apps/hub/src/components/ArchitectureBoard/BoardHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,9 @@ export const BoardHeader = ({
}) => {
const columns = [
{ key: 'name', title: 'Architecture', width: 'w-full' },
{ key: 'cost', title: 'Costs', width: 'w-40' },
{ key: 'imports', title: 'Imports', width: 'w-40' },
{ key: 'cost', title: 'Costs', width: 'w-52' },
{ key: 'stars', title: 'Stars', width: 'w-40' },
{ key: 'imports', title: 'Imports', width: 'w-40' },
];

const getSortIcon = (columnKey: string) => {
Expand All @@ -27,7 +27,7 @@ export const BoardHeader = ({
};

return (
<div className="bg-gray-50 flex border-b p-4 font-semibold">
<div className="bg-slate-50 flex border-b p-4 font-semibold">
{columns.map((column) => (
<div
key={column.key}
Expand Down
10 changes: 7 additions & 3 deletions apps/hub/src/components/GlobalHeader/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import Link from 'next/link';
import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { LinkButton } from '@/ui/LinkButton';
import { CloudCanvasIcon } from '@/ui/CloudCanvasIcon';

export const GlobalHeader = () => {
const router = useRouter();
Expand Down Expand Up @@ -40,10 +41,13 @@ export const GlobalHeader = () => {
};

return (
<header className="sticky top-0 w-full bg-slate-100">
<header className="sticky top-0 w-full bg-gray-50 shadow">
<div className="mx-auto flex h-16 max-w-7xl items-center justify-between px-4">
<Link href="/">
<h1 className="text-xl font-bold">Cloud Canvas</h1>
<Link href="/" className="flex items-center gap-2">
<CloudCanvasIcon />
<h1 className="text-3xl font-bold font-gamjaFlower">
Cloud Canvas
</h1>
</Link>
{/* TODO: 검색창 추가(새 컴포넌트로) */}
<nav className="flex">
Expand Down
80 changes: 40 additions & 40 deletions apps/hub/src/components/PrivateArchitectureBoard/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { ErrorMessage } from '@/ui/ErrorMessage';
import { LoadingSpinner } from '@/ui/LoadingSpinner';
import { calculateTotalPages } from '@/utils/pagination';
import Link from 'next/link';
import { Suspense } from 'react';

export const PrivateArchitectureBoard = ({ apiUrl }: { apiUrl: string }) => {
const { params, setParams } = useQueryParams();
Expand All @@ -18,47 +17,48 @@ export const PrivateArchitectureBoard = ({ apiUrl }: { apiUrl: string }) => {

const handlePageChange = (page: number) => setParams({ page });

if (error)
return (
<Suspense>
<ErrorMessage message={(error as Error).message} />
</Suspense>
);
if (error) return <ErrorMessage message={(error as Error).message} />;

return (
<Suspense>
<div className="max-w-5xl mx-auto px-4">
<SearchBar onSearch={handleSearch} />
{/* 헤더 추가하고 아이템 너비 맞추기 */}
{isLoading ? (
<LoadingSpinner />
) : (
<>
<div className="mb-10">
{data.map((item: any) => (
<li
key={item.id}
className="hover:bg-gray-50 border-b flex px-3 py-2 pl-4"
>
<Link href={`/canvas/${item.id}`}>
<h3 className="text-lg">
{item.title}
</h3>
</Link>
<div>{item.cost}</div>
<div>{item.createdAt}</div>
<div>{item.updatedAt}</div>
</li>
))}
</div>
<Pagination
currentPage={params.page}
totalPages={calculateTotalPages(total ?? 0)}
onPageChange={handlePageChange}
/>
</>
)}
<div className="max-w-5xl mx-auto px-4">
<SearchBar onSearch={handleSearch} />
<div className="bg-gray-50 flex border-b p-4 font-semibold">
Architecture
</div>
</Suspense>
{isLoading ? (
<LoadingSpinner />
) : (
<>
<div className="mb-10">
{data.map((item: any) => (
<li
key={item.id}
className="hover:bg-slate-50 border-b flex px-3 py-2 pl-4"
>
<Link
href={`/canvas/${item.id}`}
className="flex flex-col"
>
<h3 className="text-lg">{item.title}</h3>
<div className="text-xs flex gap-2 text-gray-400">
<span>마지막 수정</span>
<span>
{new Date(
item.updatedAt,
).toLocaleString()}
</span>
</div>
</Link>
</li>
))}
</div>
<Pagination
currentPage={params.page}
totalPages={calculateTotalPages(total ?? 0)}
onPageChange={handlePageChange}
/>
</>
)}
</div>
);
};
2 changes: 1 addition & 1 deletion apps/hub/src/components/SearchBar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export const SearchBar = ({
/>
<button
type="submit"
className="bg-blue-700 text-white px-4 rounded-r-xl"
className="bg-blue-600 text-white px-4 rounded-r-xl hover:bg-blue-700"
>
<SearchIcon />
</button>
Expand Down
Binary file added apps/hub/src/fonts/GamjaFlower-Regular.ttf
Binary file not shown.
2 changes: 1 addition & 1 deletion apps/hub/src/ui/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export const Button = ({
}) => {
return (
<button
className="bg-blue-700 text-white font-semibold py-1.5 px-4 rounded-lg"
className="bg-blue-600 text-white font-semibold py-1.5 px-4 rounded-lg hover:bg-blue-800 transition"
{...props}
>
{children}
Expand Down
Loading

0 comments on commit f823e3e

Please sign in to comment.