diff --git a/components/board/AllIArticleSection.jsx b/components/board/AllIArticleSection.jsx
new file mode 100644
index 000000000..ce460fa84
--- /dev/null
+++ b/components/board/AllIArticleSection.jsx
@@ -0,0 +1,137 @@
+import { useEffect, useState, useRef, useCallback } from "react";
+import axios from "@/lib/axios";
+import Link from "next/link";
+import styles from "@/styles/Boards.module.css";
+import SearchForm from "@/components/ui/SearchForm";
+import Dropdown from "@/components/button/DropdownButton";
+import ArticleList from "@/components/board/ArticleList";
+
+export default function AllArticleSection() {
+ const [articles, setArticles] = useState([]); //게시글 데이터
+ const [page, setPage] = useState(1); //쿼리스트링
+ const [pageSize] = useState(30); // 한 페이지당 게시글 수(무한스크롤 일단 임시코드)
+ const [orderBy, setOrderBy] = useState("recent"); // 기본 정렬: 최신순
+ const [keyword, setKeyword] = useState(""); // 검색 키워드
+
+ const [isLoading, setIsLoading] = useState(false); //로딩 상태
+ const [hasMore, setHasMore] = useState(true); //다음 페이지(데이터)가 있는지
+
+ const observer = useRef();
+
+ //게시글 불러오기
+ const getArticles = async (currentPage, currentOrderBy, currentKeyword) => {
+ setIsLoading(true);
+ try {
+ const response = await axios.get(`/articles`, {
+ params: {
+ page: currentPage,
+ pageSize,
+ orderBy: currentOrderBy,
+ keyword: currentKeyword,
+ },
+ });
+
+ const nextArticles = response.data.list;
+
+ // 첫 페이지(쿼리스트링)일 경우 새로운 데이터 업데이트, 아닐 경우 기존 데이터 배열 뒤에 추가
+ setArticles((prev) => {
+ if (currentPage === 1) return nextArticles;
+ return [...prev, ...nextArticles];
+ });
+
+ // 다음 페이지(데이터)가 있는지 확인
+ setHasMore(nextArticles.length > 0);
+ } catch (error) {
+ console.error("Error fetching articles:", error);
+ }
+ setIsLoading(false);
+ };
+
+ //임시 무한스크롤 코드
+ const lastArticleElementRef = useCallback(
+ (node) => {
+ if (isLoading) return;
+ if (observer.current) observer.current.disconnect();
+
+ observer.current = new IntersectionObserver((entries) => {
+ if (entries[0].isIntersecting && hasMore) {
+ setPage((prevPage) => prevPage + 1);
+ }
+ });
+
+ if (node) observer.current.observe(node);
+ },
+ [isLoading, hasMore]
+ );
+
+ //드롭다운 데이터
+ const handleOrderChange = (value) => {
+ const order = value === "최신순" ? "recent" : "like";
+ setOrderBy(order);
+ setPage(1);
+ getArticles(1, order, keyword);
+ };
+
+ //검색
+ const handleSearch = (searchKeyword) => {
+ setKeyword(searchKeyword);
+ setPage(1);
+ getArticles(1, orderBy, searchKeyword);
+
+ if (searchKeyword === "") {
+ getArticles(1, orderBy, ""); // 기본 게시글 목록 호출
+ } else {
+ getArticles(1, orderBy, searchKeyword); // 검색된 게시글 목록 호출
+ }
+ };
+
+ //최초 게시글 불러오기
+ useEffect(() => {
+ getArticles(page, orderBy, keyword);
+ }, [page]);
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+ {articles.map((article, index) => {
+ if (index === articles.length - 1) {
+ return (
+
+
{article.title}
+
{article.content}
+
+ );
+ } else {
+ return (
+
+
{article.title}
+
{article.content}
+
+ );
+ }
+ })}
+
+
+ {isLoading &&
로딩 중...
}
+ {!hasMore && (
+
더 이상 게시글이 없습니다.
+ )}
+
+ >
+ );
+}
diff --git a/components/board/ArticleList.jsx b/components/board/ArticleList.jsx
new file mode 100644
index 000000000..70b5560ef
--- /dev/null
+++ b/components/board/ArticleList.jsx
@@ -0,0 +1,58 @@
+import Link from "next/link";
+import Image from "next/image";
+import like from "@/public/icons/ic_heart.svg";
+import profile from "@/public/icons/ic_profile.png";
+import styles from "@/styles/Boards.module.css";
+import defaultImg from "@/public/icons/img_default.png";
+
+export default function ArticleList({ articles = [] }) {
+ return (
+
+ {articles.map((articles) => (
+
+
+
+
{articles.title}
+
+ {articles.image ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
{articles.writer.nickname}
+
+ {new Date(articles.createdAt).toLocaleDateString()}
+
+
+
+
+
{articles.likeCount}
+
+
+
+ ))}
+
+ );
+}
diff --git a/components/board/BestArticle.jsx b/components/board/BestArticle.jsx
new file mode 100644
index 000000000..849aa550f
--- /dev/null
+++ b/components/board/BestArticle.jsx
@@ -0,0 +1,65 @@
+import Link from "next/link";
+import Image from "next/image";
+import like from "@/public/icons/ic_heart.svg";
+import profile from "@/public/icons/ic_profile.png";
+import styles from "@/styles/Boards.module.css";
+import defaultImg from "@/public/icons/img_default.png";
+import badge from "@/public/icons/img_badge.png";
+
+export default function BestArticle({ articles = [] }) {
+ return (
+
+ {articles.map((articles) => (
+
+
+
+
+
{articles.title}
+
+ {articles.image ? (
+
+ ) : (
+
+ )}
+
+
+
+
+
{articles.writer.nickname}
+
+ {new Date(articles.createdAt).toLocaleDateString()}
+
+
+
+
+
{articles.likeCount}
+
+
+
+ ))}
+
+ );
+}
diff --git a/components/board/BestArticleSection.jsx b/components/board/BestArticleSection.jsx
new file mode 100644
index 000000000..a8efda82f
--- /dev/null
+++ b/components/board/BestArticleSection.jsx
@@ -0,0 +1,38 @@
+import { useEffect, useState } from "react";
+import axios from "@/lib/axios";
+import styles from "@/styles/Boards.module.css";
+import BestArticle from "./BestArticle";
+
+export default function BestArticleSection() {
+ const [bestArticles, setBestArticles] = useState([]);
+
+ // 베스트 게시글 불러오기
+ async function getBestArticles() {
+ try {
+ const res = await axios.get("/articles");
+ const nextBestArticles = res.data.list;
+
+ // likeCount 기준으로 정렬 후 상위 3개 추출
+ const sortedArticles = nextBestArticles
+ .sort((a, b) => b.likeCount - a.likeCount)
+ .slice(0, 3);
+
+ setBestArticles(sortedArticles);
+ } catch (error) {
+ console.error("Error fetching articles:", error);
+ }
+ }
+
+ useEffect(() => {
+ getBestArticles();
+ }, []);
+
+ return (
+ <>
+
+ >
+ );
+}
diff --git a/components/button/DropdownButton.jsx b/components/button/DropdownButton.jsx
new file mode 100644
index 000000000..12a1f395d
--- /dev/null
+++ b/components/button/DropdownButton.jsx
@@ -0,0 +1,88 @@
+import React, { useState } from "react";
+import styled from "styled-components";
+
+const DropdownContainer = styled.div`
+ position: relative;
+`;
+
+const DropdownButton = styled.button`
+ width: 100%;
+ padding: 12px 20px;
+ margin: auto;
+ font-size: 16px;
+ font-weight: 400;
+ text-align: center;
+ background-color: #ffffff;
+ border: 1px solid #ccc;
+ border-radius: 12px;
+ cursor: pointer;
+ transition: all 0.2s ease;
+
+ &:hover {
+ background-color: #f9f9f9;
+ }
+`;
+
+const DropdownList = styled.ul`
+ position: absolute;
+ top: 100%;
+ left: 0;
+ width: 100%;
+ margin: 0;
+ padding: 0;
+ list-style: none;
+ background-color: #ffffff;
+ border: 1px solid #ccc;
+ border-radius: 12px;
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
+ z-index: 1000;
+`;
+
+const DropdownItem = styled.li`
+ display: block;
+ justify-content: center;
+ align-items: center;
+ padding: 12px 20px;
+ font-size: 16px;
+ line-height: 26px;
+ font-weight: 400;
+ cursor: pointer;
+ transition: all 0.2s ease;
+
+ &:hover {
+ background-color: #f0f0f0;
+ }
+`;
+
+// DropdownButton.jsx
+const Dropdown = ({ onSelect }) => {
+ const [isOpen, setIsOpen] = useState(false);
+ const [selected, setSelected] = useState("최신순");
+
+ const toggleDropdown = () => setIsOpen(!isOpen);
+
+ // 선택된 값에 따라 상태 업데이트
+ const handleSelect = (value) => {
+ setSelected(value);
+ setIsOpen(false); // 드롭다운 닫기
+ onSelect(value); // 선택된 값 부모로 전달
+ };
+
+ return (
+
+ {selected} ㅤㅤ▼
+ {isOpen && (
+
+ handleSelect("최신순")}>
+ 최신순
+
+ handleSelect("인기순")}>
+ 인기순
+
+
+ )}
+
+ );
+};
+
+export default Dropdown;
diff --git a/components/layout/Header.jsx b/components/layout/Header.jsx
new file mode 100644
index 000000000..6c50f7c5c
--- /dev/null
+++ b/components/layout/Header.jsx
@@ -0,0 +1,52 @@
+import React from "react";
+import Link from "next/link";
+import Image from "next/image";
+import { useRouter } from "next/router";
+import styles from "@/styles/Header.module.css";
+import Logo from "@/public/logo/logo.svg";
+import profile from "@/public/icons/ic_profile.png";
+
+function getLinkStyle(path, currentPath) {
+ return path === currentPath ? { color: "var(--blue)" } : {};
+}
+
+function Header() {
+ const router = useRouter();
+
+ return (
+
+
+
+
+
+
+
+
+
+ );
+}
+
+export default Header;
diff --git a/components/ui/Input.jsx b/components/ui/Input.jsx
new file mode 100644
index 000000000..53befed9a
--- /dev/null
+++ b/components/ui/Input.jsx
@@ -0,0 +1,27 @@
+import styled from "styled-components";
+
+const Icon = styled.img`
+ width: 24px;
+ height: 24px;
+`;
+
+const Input = styled.input`
+ flex-grow: 1; /* 남은 공간을 모두 차지 */
+ max-width: 1050px;
+ height: 42px;
+ border: none;
+ border-radius: 12px;
+ color: var(--gray-400);
+ background-color: var(--gray-100);
+ padding: 12px 16px;
+ margin: 24px auto;
+
+ &::placeholder {
+ color: var(--gray-500);
+ font-size: 16px;
+ line-height: 26px;
+ font-weight: 400;
+ }
+`;
+
+export default Input;
diff --git a/components/ui/SearchForm.jsx b/components/ui/SearchForm.jsx
new file mode 100644
index 000000000..19d2c098d
--- /dev/null
+++ b/components/ui/SearchForm.jsx
@@ -0,0 +1,35 @@
+import { useRouter } from "next/router";
+import { useState } from "react";
+import search from "@/public/icons/ic_search.svg";
+import styles from "@/styles/Boards.module.css";
+import Input from "@/components/ui/input";
+
+export default function SearchForm({ onSearch, initialValue = "" }) {
+ const [value, setValue] = useState(initialValue);
+
+ function handleChange(e) {
+ const newValue = e.target.value;
+ setValue(newValue);
+ if (newValue === "") {
+ onSearch(""); // 검색어가 비어있으면 검색어 초기화
+ }
+ }
+
+ function handleSubmit(e) {
+ e.preventDefault();
+ onSearch(value); // 부모 컴포넌트로 검색어 전달
+ }
+
+ return (
+
+ );
+}
diff --git a/components/ui/likeCount.jsx b/components/ui/likeCount.jsx
new file mode 100644
index 000000000..f149c2f0a
--- /dev/null
+++ b/components/ui/likeCount.jsx
@@ -0,0 +1,30 @@
+import React, { useEffect, useState } from "react";
+import Image from "next/image";
+import like from "@/public/icons/ic_heart.svg";
+import styles from "@/styles/Boards.module.css";
+
+export default function LikeCount({ postId }) {
+ const [likeCount, setLikeCount] = useState(null);
+
+ useEffect(() => {
+ // API 호출
+ const fetchLikeCount = async () => {
+ try {
+ const response = await fetch(`/articles/likeCount?postId=${postId}`);
+ const data = await response.json();
+ setLikeCount(data.likeCount);
+ } catch (error) {
+ console.error("Failed to fetch like count:", error);
+ }
+ };
+
+ fetchLikeCount();
+ }, [postId]);
+
+ return (
+
+
+ {likeCount}
+
+ );
+}
diff --git a/lib/axios.js b/lib/axios.js
new file mode 100644
index 000000000..739fca65d
--- /dev/null
+++ b/lib/axios.js
@@ -0,0 +1,7 @@
+import axios from "axios";
+
+const instance = axios.create({
+ baseURL: "https://panda-market-api.vercel.app",
+});
+
+export default instance;
diff --git a/next.config.js b/next.config.js
index a843cbee0..956f1797d 100644
--- a/next.config.js
+++ b/next.config.js
@@ -1,6 +1,26 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
reactStrictMode: true,
-}
+ compiler: {
+ styledComponents: {
+ ssr: true,
+ displayName: true,
+ },
+ },
+ images: {
+ remotePatterns: [
+ {
+ protocol: "https",
+ hostname: "sprint-fe-project.s3.ap-northeast-2.amazonaws.com",
+ pathname: "/**",
+ },
+ {
+ protocol: "https",
+ hostname: "example.com",
+ pathname: "/**",
+ },
+ ],
+ },
+};
-module.exports = nextConfig
+module.exports = nextConfig;
diff --git a/package-lock.json b/package-lock.json
index baa2b6655..749667ca4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,9 +8,11 @@
"name": "fe-weekly-mission",
"version": "0.1.0",
"dependencies": {
+ "axios": "^1.7.9",
"next": "13.5.6",
"react": "^18",
- "react-dom": "^18"
+ "react-dom": "^18",
+ "styled-components": "^6.1.14"
},
"devDependencies": {
"@types/node": "^20",
@@ -42,6 +44,27 @@
"node": ">=6.9.0"
}
},
+ "node_modules/@emotion/is-prop-valid": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.2.2.tgz",
+ "integrity": "sha512-uNsoYd37AFmaCdXlg6EYD1KaPOaRWRByMCYzbKUX4+hhMfrxdVSelShywL4JVaAeM/eHUOSprYBQls+/neX3pw==",
+ "license": "MIT",
+ "dependencies": {
+ "@emotion/memoize": "^0.8.1"
+ }
+ },
+ "node_modules/@emotion/memoize": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.8.1.tgz",
+ "integrity": "sha512-W2P2c/VRW1/1tLox0mVUalvnWXxavmv/Oum2aPsRcoDJuob75FC3Y8FbpfLwUegRcxINtGUMPq0tFCvYNTBXNA==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/unitless": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.8.1.tgz",
+ "integrity": "sha512-KOEGMu6dmJZtpadb476IsZBclKvILjopjUii3V+7MnXIQCYh8W3NgNcgwo21n9LXZX6EDIKvqfjYxXebDwxKmQ==",
+ "license": "MIT"
+ },
"node_modules/@eslint-community/eslint-utils": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@@ -376,6 +399,12 @@
"integrity": "sha512-WZLiwShhwLRmeV6zH+GkbOFT6Z6VklCItrDioxUnv+u4Ll+8vKeFySoFyK/0ctcRpOmwAicELfmys1sDc/Rw+A==",
"dev": true
},
+ "node_modules/@types/stylis": {
+ "version": "4.2.5",
+ "resolved": "https://registry.npmjs.org/@types/stylis/-/stylis-4.2.5.tgz",
+ "integrity": "sha512-1Xve+NMN7FWjY14vLoY5tL3BVEQ/n42YLwaqJIPYhotZ9uBHt87VceMwWQpzmdEt2TNXIorIFG+YeCUUW7RInw==",
+ "license": "MIT"
+ },
"node_modules/@typescript-eslint/parser": {
"version": "6.12.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.12.0.tgz",
@@ -705,6 +734,12 @@
"has-symbols": "^1.0.3"
}
},
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "license": "MIT"
+ },
"node_modules/available-typed-arrays": {
"version": "1.0.5",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz",
@@ -726,6 +761,17 @@
"node": ">=4"
}
},
+ "node_modules/axios": {
+ "version": "1.7.9",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz",
+ "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
+ "license": "MIT",
+ "dependencies": {
+ "follow-redirects": "^1.15.6",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
"node_modules/axobject-query": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-3.2.1.tgz",
@@ -797,6 +843,15 @@
"node": ">=6"
}
},
+ "node_modules/camelize": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.1.tgz",
+ "integrity": "sha512-dU+Tx2fsypxTgtLoE36npi3UqcjSSMNYfkqgmoEhtZrraP5VWq0K7FkWVTYa8eMPtnU/G2txVsfdCJTn9uzpuQ==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
"node_modules/caniuse-lite": {
"version": "1.0.30001564",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001564.tgz",
@@ -855,6 +910,18 @@
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -875,11 +942,31 @@
"node": ">= 8"
}
},
+ "node_modules/css-color-keywords": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz",
+ "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/css-to-react-native": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/css-to-react-native/-/css-to-react-native-3.2.0.tgz",
+ "integrity": "sha512-e8RKaLXMOFii+02mOlqwjbD00KSEKqblnpO9e++1aXS1fPQOpS1YoqdVHBqPjHNoxeF2mimzVqawm2KCbEdtHQ==",
+ "license": "MIT",
+ "dependencies": {
+ "camelize": "^1.0.0",
+ "css-color-keywords": "^1.0.0",
+ "postcss-value-parser": "^4.0.2"
+ }
+ },
"node_modules/csstype": {
- "version": "3.1.2",
- "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz",
- "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==",
- "dev": true
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==",
+ "license": "MIT"
},
"node_modules/damerau-levenshtein": {
"version": "1.0.8",
@@ -941,6 +1028,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
"node_modules/dequal": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz",
@@ -1645,6 +1741,26 @@
"integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==",
"dev": true
},
+ "node_modules/follow-redirects": {
+ "version": "1.15.9",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
+ "integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
"node_modules/for-each": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
@@ -1654,6 +1770,20 @@
"is-callable": "^1.1.3"
}
},
+ "node_modules/form-data": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
+ "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
@@ -2501,6 +2631,27 @@
"node": ">=8.6"
}
},
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -2868,6 +3019,12 @@
"node": "^10 || ^12 || >=14"
}
},
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+ "license": "MIT"
+ },
"node_modules/prelude-ls": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -2888,6 +3045,12 @@
"react-is": "^16.13.1"
}
},
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+ "license": "MIT"
+ },
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -3156,6 +3319,12 @@
"node": ">= 0.4"
}
},
+ "node_modules/shallowequal": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/shallowequal/-/shallowequal-1.1.0.tgz",
+ "integrity": "sha512-y0m1JoUZSlPAjXVtPPW70aZWfIL/dSP7AFkRnniLCrK/8MDKog3TySTBmckD+RObVxH0v4Tox67+F14PdED2oQ==",
+ "license": "MIT"
+ },
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
@@ -3201,9 +3370,10 @@
}
},
"node_modules/source-map-js": {
- "version": "1.0.2",
- "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
- "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "license": "BSD-3-Clause",
"engines": {
"node": ">=0.10.0"
}
@@ -3314,6 +3484,62 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/styled-components": {
+ "version": "6.1.14",
+ "resolved": "https://registry.npmjs.org/styled-components/-/styled-components-6.1.14.tgz",
+ "integrity": "sha512-KtfwhU5jw7UoxdM0g6XU9VZQFV4do+KrM8idiVCH5h4v49W+3p3yMe0icYwJgZQZepa5DbH04Qv8P0/RdcLcgg==",
+ "license": "MIT",
+ "dependencies": {
+ "@emotion/is-prop-valid": "1.2.2",
+ "@emotion/unitless": "0.8.1",
+ "@types/stylis": "4.2.5",
+ "css-to-react-native": "3.2.0",
+ "csstype": "3.1.3",
+ "postcss": "8.4.38",
+ "shallowequal": "1.1.0",
+ "stylis": "4.3.2",
+ "tslib": "2.6.2"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/styled-components"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8.0",
+ "react-dom": ">= 16.8.0"
+ }
+ },
+ "node_modules/styled-components/node_modules/postcss": {
+ "version": "8.4.38",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz",
+ "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.7",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.2.0"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
"node_modules/styled-jsx": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.1.tgz",
@@ -3336,6 +3562,12 @@
}
}
},
+ "node_modules/stylis": {
+ "version": "4.3.2",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.3.2.tgz",
+ "integrity": "sha512-bhtUjWd/z6ltJiQwg0dUfxEJ+W+jdqQd8TbWLWyeIJHlnsqmGLRFFd8e5mA0AZi/zx90smXRlN66YMTcaSFifg==",
+ "license": "MIT"
+ },
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
diff --git a/package.json b/package.json
index 1ce24924f..42dbc0f32 100644
--- a/package.json
+++ b/package.json
@@ -9,16 +9,18 @@
"lint": "next lint"
},
"dependencies": {
+ "axios": "^1.7.9",
+ "next": "13.5.6",
"react": "^18",
"react-dom": "^18",
- "next": "13.5.6"
+ "styled-components": "^6.1.14"
},
"devDependencies": {
- "typescript": "^5",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"eslint": "^8",
- "eslint-config-next": "13.5.6"
+ "eslint-config-next": "13.5.6",
+ "typescript": "^5"
}
}
diff --git a/pages/_app.tsx b/pages/_app.tsx
index 021681f4d..fdd03c652 100644
--- a/pages/_app.tsx
+++ b/pages/_app.tsx
@@ -1,6 +1,15 @@
-import '@/styles/globals.css'
-import type { AppProps } from 'next/app'
+import "@/styles/globals.css";
+import type { AppProps } from "next/app";
+import Header from "@/components/layout/Header";
+import React from "react";
export default function App({ Component, pageProps }: AppProps) {
- return
+ return (
+ <>
+
+ Panda Market
+
+
+ >
+ );
}
diff --git a/pages/api/hello.ts b/pages/api/hello.ts
deleted file mode 100644
index f8bcc7e5c..000000000
--- a/pages/api/hello.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
-import type { NextApiRequest, NextApiResponse } from 'next'
-
-type Data = {
- name: string
-}
-
-export default function handler(
- req: NextApiRequest,
- res: NextApiResponse
-) {
- res.status(200).json({ name: 'John Doe' })
-}
diff --git a/pages/boards/[id].js b/pages/boards/[id].js
new file mode 100644
index 000000000..9b5381dc1
--- /dev/null
+++ b/pages/boards/[id].js
@@ -0,0 +1,33 @@
+import { useRouter } from "next/router";
+import { useEffect, useState } from "react";
+import axios from "@/lib/axios";
+
+export default function Article() {
+ const [article, setArticle] = useState();
+ const router = useRouter();
+ const { id } = router.query;
+
+ async function getArticle(targetId) {
+ const res = await axios.get(`/articles/${targetId}`);
+ const nextArticle = res.data;
+ setArticle(nextArticle);
+ }
+
+ useEffect(() => {
+ if (!id) return;
+
+ getArticle(id);
+ }, [id]);
+
+ if (!article) return null;
+
+ return (
+
+
{article.title}
+
{article.content}
+

+
{article.writer.nickname}
+
{new Date(article.createdAt).toLocaleDateString()}
+
+ );
+}
diff --git a/pages/boards/index.jsx b/pages/boards/index.jsx
new file mode 100644
index 000000000..01ad10a3c
--- /dev/null
+++ b/pages/boards/index.jsx
@@ -0,0 +1,12 @@
+import AllArticlesSection from "@/components/board/AllIArticleSection.jsx";
+import BestArticlesSection from "@/components/board/BestArticleSection.jsx";
+import "@/styles/Boards.module.css";
+
+export default function Boards() {
+ return (
+
+ );
+}
diff --git a/pages/index.tsx b/pages/index.tsx
index 02c4dee04..cff5b6a9c 100644
--- a/pages/index.tsx
+++ b/pages/index.tsx
@@ -1,114 +1,7 @@
-import Head from 'next/head'
-import Image from 'next/image'
-import { Inter } from 'next/font/google'
-import styles from '@/styles/Home.module.css'
-
-const inter = Inter({ subsets: ['latin'] })
-
export default function Home() {
return (
- <>
-
- Create Next App
-
-
-
-
-
-
-
- Get started by editing
- pages/index.tsx
-
-
-
-
-
-
-
-
-
-
- >
- )
+
+
Panda Market Root Page
+
+ );
}
diff --git a/pages/search.js b/pages/search.js
new file mode 100644
index 000000000..320818159
--- /dev/null
+++ b/pages/search.js
@@ -0,0 +1,33 @@
+import { useRouter } from "next/router";
+import { useEffect, useState } from "react";
+
+export default function Search() {
+ const router = useRouter();
+ const { q } = router.query;
+ const [results, setResults] = useState([]);
+
+ useEffect(() => {
+ if (q) {
+ // 검색어에 따라 API 호출
+ fetch(`/api/search?q=${q}`)
+ .then((response) => response.json())
+ .then((data) => {
+ setResults(data);
+ });
+ }
+ }, [q]);
+
+ return (
+
+
Search Page
+
Search Result: {q}
+
+ {results.length > 0 ? (
+ results.map((result, index) => - {result.title}
)
+ ) : (
+ No results found
+ )}
+
+
+ );
+}
diff --git a/public/favicon.ico b/public/favicon.ico
deleted file mode 100644
index 718d6fea4..000000000
Binary files a/public/favicon.ico and /dev/null differ
diff --git a/public/icons/arrow_left.svg b/public/icons/arrow_left.svg
new file mode 100644
index 000000000..2a9de23a6
--- /dev/null
+++ b/public/icons/arrow_left.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/icons/arrow_right.svg b/public/icons/arrow_right.svg
new file mode 100644
index 000000000..daa483c3e
--- /dev/null
+++ b/public/icons/arrow_right.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/icons/ic_back.svg b/public/icons/ic_back.svg
new file mode 100644
index 000000000..f8d47f89d
--- /dev/null
+++ b/public/icons/ic_back.svg
@@ -0,0 +1,4 @@
+
diff --git a/public/icons/ic_heart.svg b/public/icons/ic_heart.svg
new file mode 100644
index 000000000..cad016c13
--- /dev/null
+++ b/public/icons/ic_heart.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/icons/ic_kebab.svg b/public/icons/ic_kebab.svg
new file mode 100644
index 000000000..dd7ed7f5e
--- /dev/null
+++ b/public/icons/ic_kebab.svg
@@ -0,0 +1,5 @@
+
diff --git a/public/icons/ic_plus.svg b/public/icons/ic_plus.svg
new file mode 100644
index 000000000..5bb9abf55
--- /dev/null
+++ b/public/icons/ic_plus.svg
@@ -0,0 +1,4 @@
+
diff --git a/public/icons/ic_profile.png b/public/icons/ic_profile.png
new file mode 100644
index 000000000..207a7d23d
Binary files /dev/null and b/public/icons/ic_profile.png differ
diff --git a/public/icons/ic_search.svg b/public/icons/ic_search.svg
new file mode 100644
index 000000000..52241e6d8
--- /dev/null
+++ b/public/icons/ic_search.svg
@@ -0,0 +1,3 @@
+
diff --git a/public/icons/ic_sort.svg b/public/icons/ic_sort.svg
new file mode 100644
index 000000000..657b44f93
--- /dev/null
+++ b/public/icons/ic_sort.svg
@@ -0,0 +1,6 @@
+
diff --git a/public/icons/ic_x.svg b/public/icons/ic_x.svg
new file mode 100644
index 000000000..8ff53504e
--- /dev/null
+++ b/public/icons/ic_x.svg
@@ -0,0 +1,4 @@
+
diff --git a/public/icons/img_badge.png b/public/icons/img_badge.png
new file mode 100644
index 000000000..940d168e2
Binary files /dev/null and b/public/icons/img_badge.png differ
diff --git a/public/icons/img_default.png b/public/icons/img_default.png
new file mode 100644
index 000000000..239d2a10a
Binary files /dev/null and b/public/icons/img_default.png differ
diff --git a/public/logo/favicon.ico b/public/logo/favicon.ico
new file mode 100644
index 000000000..8d4255ddc
Binary files /dev/null and b/public/logo/favicon.ico differ
diff --git a/public/logo/logo.svg b/public/logo/logo.svg
new file mode 100644
index 000000000..d497acbfe
--- /dev/null
+++ b/public/logo/logo.svg
@@ -0,0 +1,15 @@
+
diff --git a/public/next.svg b/public/next.svg
deleted file mode 100644
index 5174b28c5..000000000
--- a/public/next.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/public/ui/empty-comments.svg b/public/ui/empty-comments.svg
new file mode 100644
index 000000000..d9d2d590e
--- /dev/null
+++ b/public/ui/empty-comments.svg
@@ -0,0 +1,17 @@
+
diff --git a/public/ui/ic_profile.svg b/public/ui/ic_profile.svg
new file mode 100644
index 000000000..7445a45cf
--- /dev/null
+++ b/public/ui/ic_profile.svg
@@ -0,0 +1,24 @@
+
diff --git a/public/vercel.svg b/public/vercel.svg
deleted file mode 100644
index d2f842227..000000000
--- a/public/vercel.svg
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/styles/Boards.module.css b/styles/Boards.module.css
new file mode 100644
index 000000000..a90c9a0ae
--- /dev/null
+++ b/styles/Boards.module.css
@@ -0,0 +1,141 @@
+.container {
+ display: flex;
+ flex-direction: column;
+ max-width: 1200px;
+ margin: 94px auto;
+}
+
+.bestArticle {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ margin-top: 24px;
+}
+
+.bestArticleContainer {
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
+ background-color: var(--gray-50);
+ height: 169px;
+ width: 384px;
+ padding: 0 24px 16px;
+ margin: 0 auto;
+ border-radius: 8px;
+}
+
+.button {
+ font-size: 16px;
+ font-weight: 600;
+ border-radius: 8px;
+ padding: 11.5px 23px;
+ background-color: var(--primary-100);
+ color: #ffffff;
+}
+
+.button:hover {
+ background-color: var(--primary-200);
+}
+
+.button:focus {
+ background-color: var(--primary-300);
+}
+
+.button:disabled {
+ background-color: #9ca3af;
+ cursor: default;
+ pointer-events: none;
+}
+
+.bestArticle {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.search {
+ display: flex;
+ background-color: var(--gray-100);
+}
+
+.searchContainer {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ gap: 16px;
+}
+
+.allArticle {
+ display: flex;
+ justify-content: space-between;
+}
+
+.containerTitle {
+ font-size: 20px;
+ line-height: 32px;
+ font-weight: 700;
+}
+
+.articleContainer {
+ background-color: #fcfcfc;
+ font-size: 20px;
+ font-weight: 700;
+ line-height: 32px;
+ text-align: left;
+ margin: 24px auto;
+ padding: 5px 5px 24px;
+ border-bottom: 1px solid var(--gray-200);
+}
+
+.articleTitle {
+ font-size: 20px;
+ font-weight: 600;
+ line-height: 32px;
+ text-align: left;
+}
+
+.articleInfo {
+ display: flex;
+ justify-content: space-between;
+ align-items: top;
+}
+
+.writerInfo {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+}
+
+.like {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.writer {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ gap: 8px;
+}
+
+.nickname {
+ font-size: 14px;
+ line-height: 24px;
+ font-weight: 400;
+ color: var(--gray-600);
+}
+
+.date {
+ font-size: 14px;
+ line-height: 24px;
+ font-weight: 400;
+ color: var(--gray-400);
+}
+
+.likeCount {
+ font-size: 14px;
+ line-height: 24px;
+ font-weight: 400;
+ color: var(--gray-500);
+}
diff --git a/styles/Header.module.css b/styles/Header.module.css
new file mode 100644
index 000000000..1bc2b02c5
--- /dev/null
+++ b/styles/Header.module.css
@@ -0,0 +1,45 @@
+.headerLeft {
+ display: flex;
+ align-items: center;
+}
+
+.headerLogo {
+ margin-right: 16px;
+}
+
+.globalHeader nav ul {
+ display: flex;
+ list-style: none;
+ gap: 8px;
+ font-weight: bold;
+ font-size: 16px;
+ color: #4b5563;
+}
+
+.globalHeader nav ul li a:hover {
+ color: var(--blue);
+}
+
+.loginLink {
+ font-size: 16px;
+ font-weight: 600;
+ border-radius: 8px;
+ padding: 11.5px 23px;
+}
+
+@media (min-width: 768px) {
+ .globalHeader nav ul {
+ gap: 36px;
+ font-size: 18px;
+ }
+
+ .headerLogo {
+ margin-right: 35px;
+ }
+}
+
+@media (min-width: 1280px) {
+ .headerLogo {
+ margin-right: 47px;
+ }
+}
diff --git a/styles/globals.css b/styles/globals.css
index d4f491e15..69ad2b1ae 100644
--- a/styles/globals.css
+++ b/styles/globals.css
@@ -1,107 +1,211 @@
+/* Mobile styles */
+
:root {
- --max-width: 1100px;
- --border-radius: 12px;
- --font-mono: ui-monospace, Menlo, Monaco, 'Cascadia Mono', 'Segoe UI Mono',
- 'Roboto Mono', 'Oxygen Mono', 'Ubuntu Monospace', 'Source Code Pro',
- 'Fira Mono', 'Droid Sans Mono', 'Courier New', monospace;
-
- --foreground-rgb: 0, 0, 0;
- --background-start-rgb: 214, 219, 220;
- --background-end-rgb: 255, 255, 255;
-
- --primary-glow: conic-gradient(
- from 180deg at 50% 50%,
- #16abff33 0deg,
- #0885ff33 55deg,
- #54d6ff33 120deg,
- #0071ff33 160deg,
- transparent 360deg
- );
- --secondary-glow: radial-gradient(
- rgba(255, 255, 255, 1),
- rgba(255, 255, 255, 0)
- );
-
- --tile-start-rgb: 239, 245, 249;
- --tile-end-rgb: 228, 232, 233;
- --tile-border: conic-gradient(
- #00000080,
- #00000040,
- #00000030,
- #00000020,
- #00000010,
- #00000010,
- #00000080
- );
-
- --callout-rgb: 238, 240, 241;
- --callout-border-rgb: 172, 175, 176;
- --card-rgb: 180, 185, 188;
- --card-border-rgb: 131, 134, 135;
-}
-
-@media (prefers-color-scheme: dark) {
- :root {
- --foreground-rgb: 255, 255, 255;
- --background-start-rgb: 0, 0, 0;
- --background-end-rgb: 0, 0, 0;
-
- --primary-glow: radial-gradient(rgba(1, 65, 255, 0.4), rgba(1, 65, 255, 0));
- --secondary-glow: linear-gradient(
- to bottom right,
- rgba(1, 65, 255, 0),
- rgba(1, 65, 255, 0),
- rgba(1, 65, 255, 0.3)
- );
-
- --tile-start-rgb: 2, 13, 46;
- --tile-end-rgb: 2, 5, 19;
- --tile-border: conic-gradient(
- #ffffff80,
- #ffffff40,
- #ffffff30,
- #ffffff20,
- #ffffff10,
- #ffffff10,
- #ffffff80
- );
-
- --callout-rgb: 20, 20, 20;
- --callout-border-rgb: 108, 108, 108;
- --card-rgb: 100, 100, 100;
- --card-border-rgb: 200, 200, 200;
- }
+ /* Gray scale */
+ --gray-900: #111827;
+ --gray-800: #1f2937;
+ --gray-700: #374151;
+ --gray-600: #4b5563;
+ --gray-500: #6b7280;
+ --gray-400: #9ca3af;
+ --gray-200: #e5e7eb;
+ --gray-100: #f3f4f6;
+ --gray-50: #f9fafb;
+
+ /* Primary color */
+ --primary-100: #3692ff;
+ --primary-200: #1967d6;
+ --primary-300: #1251aa;
+
+ --error: #f74747;
+
+ /* Layout dimensions */
+ --header-height: 70px;
}
* {
- box-sizing: border-box;
- padding: 0;
margin: 0;
+ padding: 0;
+ box-sizing: border-box;
}
-html,
-body {
- max-width: 100vw;
- overflow-x: hidden;
+a {
+ text-decoration: none;
+ color: inherit;
+}
+
+button,
+input,
+textarea,
+select {
+ font-family: inherit;
+ font-size: inherit;
+ line-height: inherit;
+ color: inherit;
+ -webkit-appearance: none;
+ -moz-appearance: none;
+ appearance: none;
+}
+
+button {
+ background: none;
+ border: none;
+ outline: none;
+ box-shadow: none;
+ cursor: pointer;
+}
+
+img,
+svg {
+ vertical-align: bottom;
}
body {
- color: rgb(var(--foreground-rgb));
- background: linear-gradient(
- to bottom,
- transparent,
- rgb(var(--background-end-rgb))
- )
- rgb(var(--background-start-rgb));
+ color: #374151;
+ word-break: keep-all;
+ font-family: "Pretendard", sans-serif;
}
-a {
- color: inherit;
- text-decoration: none;
+header {
+ position: fixed;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: var(--header-height);
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ padding: 0 16px;
+ background-color: #ffffff;
+ border-bottom: 1px solid #dfdfdf;
+ z-index: 999;
+}
+
+.withHeader {
+ margin-top: var(--header-height);
+}
+
+footer {
+ background-color: #111827;
+ color: #9ca3af;
+ font-size: 16px;
+ padding: 32px;
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 60px;
+}
+
+#copyright {
+ order: 3;
+ flex-basis: 100%;
+}
+
+#footerMenu {
+ display: flex;
+ gap: 30px;
+ color: var(--gray-200);
+}
+
+#socialMedia {
+ display: flex;
+ gap: 12px;
+}
+
+.wrapper {
+ width: 100%;
+ padding: 0 16px;
}
-@media (prefers-color-scheme: dark) {
- html {
- color-scheme: dark;
+h1 {
+ font-size: 40px;
+ font-weight: 700;
+ line-height: 56px;
+ letter-spacing: 0.02em;
+}
+
+.button {
+ background-color: var(--primary-100);
+ color: #ffffff;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+}
+
+.button:hover {
+ background-color: #1967d6;
+}
+
+.button:focus {
+ background-color: #1251aa;
+}
+
+.button:disabled {
+ background-color: #9ca3af;
+ cursor: default;
+ pointer-events: none;
+}
+
+.pill-button {
+ font-size: 16px;
+ font-weight: 600;
+ border-radius: 999px;
+ padding: 14.5px 33.5px;
+}
+
+.full-width {
+ width: 100%;
+}
+
+.break-on-desktop {
+ display: none;
+}
+
+/* Tablet styles */
+
+@media (min-width: 768px) {
+ header {
+ padding: 0 24px;
+ }
+
+ .wrapper {
+ padding: 0 24px;
+ }
+
+ .pill-button {
+ font-size: 20px;
+ font-weight: 700;
+ padding: 16px 126px;
+ }
+
+ footer {
+ padding: 32px 104px 108px 104px;
+ }
+
+ #copyright {
+ flex-basis: auto;
+ order: 0;
+ }
+}
+
+/* Desktop styles */
+
+@media (min-width: 1280px) {
+ header {
+ padding: 0 100px;
+ }
+
+ .wrapper {
+ max-width: 1200px;
+ margin: 0 auto;
+ }
+
+ .break-on-desktop {
+ display: inline;
+ }
+
+ footer {
+ padding: 32px 200px 108px 200px;
}
}
diff --git a/tsconfig.json b/tsconfig.json
index 670224f3e..381411dba 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -17,6 +17,12 @@
"@/*": ["./*"]
}
},
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"],
+ "include": [
+ "next-env.d.ts",
+ "**/*.ts",
+ "**/*.tsx",
+ "pages/boards/[id].jx",
+ "pages/boards/index.tsx"
+, "pages/_app.jsx" ],
"exclude": ["node_modules"]
}