Skip to content
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
93a0e93
chore: 머지 후 브랜치 삭제 github action 추가
withyj-codeit Sep 3, 2023
b2e37bd
reset
hanseulhee Oct 10, 2023
6f8bbb0
Merge branch 'codeit-bootcamp-frontend:main' into main
hanseulhee Oct 10, 2023
e11e25f
fix: 머지 후 브랜치 삭제 github action 수정
hanseulhee Oct 10, 2023
212e864
env: workflows 폴더로 이동
hanseulhee Oct 10, 2023
4dc5dd0
Merge pull request #237 from hanseulhee/fix-github-actions
withyj-codeit Nov 6, 2023
5005ee1
initial commit
asksa1256 Jul 13, 2025
3c752a9
chore: 컬러, 폰트 크기 테일윈드 설정 + 필요없는 파일 삭제
asksa1256 Jul 13, 2025
fc1d1b7
feat: Header 추가
asksa1256 Jul 13, 2025
c311870
feat: Button 컴포넌트 추가
asksa1256 Jul 13, 2025
20327f0
chore: clsx 제거
asksa1256 Jul 13, 2025
e192a37
refactor: Button 컴포넌트 - variant별 버튼 분리
asksa1256 Jul 14, 2025
9bbb495
feat: 나눔스퀘어 폰트 추가
asksa1256 Jul 14, 2025
a5e60e6
style: Button border 색상 커스텀 컬러로 변경
asksa1256 Jul 14, 2025
aab5311
feat: Input, FormControl, AddTodoForm 컴포넌트 추가
asksa1256 Jul 14, 2025
d822549
feat: todo, done 리스트 빈 화면 추가
asksa1256 Jul 14, 2025
21cba9a
feat: todo 항목 클릭 시 해당 todo.isComplete = true 처리
asksa1256 Jul 14, 2025
bb8abbf
feat: todo 추가 기능 구현
asksa1256 Jul 14, 2025
cf9e8dd
style: Done 항목 스타일 추가
asksa1256 Jul 14, 2025
6bae3af
fix: 할일 모두 완료했을 때 empty 화면 안 보이는 이슈 수정
asksa1256 Jul 14, 2025
763d4b2
feat: done 항목 클릭 시 todo 항목으로 되돌리기
asksa1256 Jul 14, 2025
ca236a0
refactor: 공통 타입 선언
asksa1256 Jul 14, 2025
5426ae5
refactor: api url 환경변수화, tenantId 상수화
asksa1256 Jul 14, 2025
d48d5c2
refactor: api handler axios 방식으로 변경
asksa1256 Jul 14, 2025
ad6084c
refactor: todo/done 항목 전환 시 요청 횟수 줄이고 속도 개선
asksa1256 Jul 14, 2025
75d13b0
refactor: TodoList, DoneList → ItemList 컴포넌트로 통합 후 조합 구조로 변경
asksa1256 Jul 14, 2025
caced60
refactor: ListItem 조합 구조 변경 (ItemList와 구조 통일)
asksa1256 Jul 14, 2025
1f59ebe
refactor: EmptyList 컴포넌트 분리
asksa1256 Jul 14, 2025
f64e0ed
refactor: ItemListBaseProps 타입 선언 - todo types로 이동
asksa1256 Jul 14, 2025
4eb43ce
Merge branch 'Next-이상달' into Next-이상달-sprint9-1
asksa1256 Jul 14, 2025
5d0aca7
fix: pages router index.tsx 삭제 (앱 라우터 루트 페이지와 충돌)
asksa1256 Jul 16, 2025
5a2632d
feat: global error 추가
asksa1256 Aug 5, 2025
9925596
Merge branch 'Next-이상달-sprint9-1' of https://github.com/asksa1256/16-…
asksa1256 Aug 5, 2025
8c92cdf
refactor: 테일윈드 버튼 커스텀 클래스 생성 및 clsx 적용
asksa1256 Aug 5, 2025
e68fc1e
refactor: 서버 컴포넌트, 클라이언트 컴포넌트 구조로 분리
asksa1256 Aug 5, 2025
45c3038
refactor: ListItem 스타일 테일윈드 커스텀 클래스로 변경
asksa1256 Aug 5, 2025
c52ea94
refactor: Badge 스타일 테일윈드 커스텀 클래스로 변경
asksa1256 Aug 5, 2025
3ac1598
feat: 투두리스트 Suspense 추가, fallback skeleton ui 생성
asksa1256 Aug 5, 2025
7265528
refactor: tanstack query로 요청 구조 변경
asksa1256 Aug 5, 2025
75fb1ac
feat: ItemList patch 요청 실패 시 재요청 1회 추가
asksa1256 Aug 5, 2025
74efa8c
refactor: 투두 추가 요청 react query 구조로 변경
asksa1256 Aug 5, 2025
ecb64b5
refactor: 'lib/api.ts'로 todo api 함수들 이동
asksa1256 Aug 5, 2025
61ed467
fix: 빌드 에러 수정 (안 쓰는 파라미터, no-store 옵션 제거)
asksa1256 Aug 6, 2025
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
27 changes: 0 additions & 27 deletions .github/pull_request_template.md

This file was deleted.

13 changes: 10 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,13 @@
# dependencies
/node_modules
/.pnp
.pnp.js
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions


# testing
/coverage
Expand All @@ -23,9 +29,10 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*

# local env files
.env*.local
# env files (can opt-in for committing if needed)
.env*

# vercel
.vercel
Expand Down
17 changes: 10 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).

This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app).

## Getting Started

Expand All @@ -16,13 +17,11 @@ bun dev

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `pages/index.tsx`. The page auto-updates as you edit the file.

[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`.
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.

The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
This project uses [`next/font`](https://nextjs.org/docs/app/building-your-application/optimizing/fonts) to automatically optimize and load [Geist](https://vercel.com/font), a new font family for Vercel.

This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.

## Learn More

Expand All @@ -31,10 +30,14 @@ To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js) - your feedback and contributions are welcome!


## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/app/building-your-application/deploying) for more details.

27 changes: 27 additions & 0 deletions app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { Metadata } from "next";
import "@/styles/reset.css";
import "@/styles/globals.css";
import Header from "@/components/Header";
import nanumSquare from "@/assets/fonts/NanumSquare/nanumSquare";

export const metadata: Metadata = {
title: "간편한 투두리스트 - Do it",
description: "Generated by create next app",
};

export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="ko">
<body className={`${nanumSquare.className} bg-gray-50`}>
<Header />
<div className="my-6 md:w-full lg:w-[1200px] lg:mx-auto">
{children}
</div>
</body>
</html>
);
}
42 changes: 42 additions & 0 deletions app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"use client";

import { useEffect, useState } from "react";
import AddTodoForm from "@/components/AddTodoForm";
import ItemList from "@/components/ItemList";
import { Item } from "@/types/todo";
import { BASE_URL, TENANT_ID } from "@/constants/constants";
import axios from "@/lib/axios";

export default function Home() {
const [items, setItems] = useState<Item[]>([]);

const handleChange = (updatedItem: Item) => {
setItems((prev) =>
prev.map((item) => (item.id === updatedItem.id ? updatedItem : item))
);
};

const fetchTodos = async () => {
const res = await axios.get(`${BASE_URL}/${TENANT_ID}/items`);
const results = res.data;

setItems(results);
};

const todos = items.filter((i) => !i.isCompleted);
const dones = items.filter((i) => i.isCompleted);

useEffect(() => {
fetchTodos();
}, []);

return (
<>
<AddTodoForm onAddTodo={fetchTodos} />
<div className="flex gap-6 mt-10">
<ItemList.Todo items={todos} onClick={handleChange} />
<ItemList.Done items={dones} onClick={handleChange} />
</div>
</>
);
}
Binary file added assets/fonts/HSSantokki/HSSantokki-Regular.ttf
Binary file not shown.
15 changes: 15 additions & 0 deletions assets/fonts/HSSantokki/santokki.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import localFont from "next/font/local";

const santokki = localFont({
src: [
{
path: "./HSSantokki-Regular.ttf",
weight: "400",
style: "normal",
},
],
variable: "--font-santokki",
display: "swap",
});

export default santokki;
Binary file added assets/fonts/NanumSquare/NanumSquareB.woff
Binary file not shown.
Binary file added assets/fonts/NanumSquare/NanumSquareR.woff
Binary file not shown.
20 changes: 20 additions & 0 deletions assets/fonts/NanumSquare/nanumSquare.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import localFont from "next/font/local";

const nanumSquare = localFont({
src: [
{
path: "./NanumSquareR.woff",
weight: "400",
style: "normal",
},
{
path: "./NanumSquareB.woff",
weight: "700",
style: "normal",
},
],
variable: "--font-nanumSquare",
display: "swap",
});

export default nanumSquare;
51 changes: 51 additions & 0 deletions components/AddTodoForm/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { FormEvent, useState } from "react";
import Image from "next/image";
import Button from "@/components/Button";
import Input from "@/components/Input";
import FormControl from "@/components/FormControl";
import { TENANT_ID } from "@/constants/constants";
import axios from "@/lib/axios";

interface AddTodoFormProps {
onAddTodo: () => void;
}

const AddTodoForm = ({ onAddTodo }: AddTodoFormProps) => {
const [value, setValue] = useState("");

const handleSubmit = async (e: FormEvent) => {
e.preventDefault();

const newTodo = { name: value };

await axios.post(`/${TENANT_ID}/items`, newTodo);

onAddTodo();
setValue("");
};

return (
<form onSubmit={handleSubmit}>
<FormControl>
<Input
id="todoInput"
value={value}
placeholder="할 일을 입력해주세요"
onChange={(e) => setValue(e.target.value)}
/>
<Button variant="primary" disabled={!value}>
<Image
src={`images/ico-plus-${value ? "wt" : "bk"}.svg`}
alt=""
width="16"
height="16"
className="mr-1"
/>
추가하기
</Button>
</FormControl>
</form>
);
};

export default AddTodoForm;
27 changes: 27 additions & 0 deletions components/Badge/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import santokki from "@/assets/fonts/HSSantokki/santokki";

interface BadgeProps {
text: string;
variant?: "todo" | "done";
}

const baseBadgeStyle = `${santokki.className} text-lg px-[28px] py-2 rounded-3xl`;

const Badge = ({ text, variant }: BadgeProps) => {
const getVariantStyle = () => {
switch (variant) {
case "todo":
return "bg-lime text-green";
case "done":
return "bg-green text-amber-300";
default:
return "bg-gray-200 text-gray-900";
}
};

return (
<span className={`${baseBadgeStyle} ${getVariantStyle()}`}>{text}</span>
);
};

export default Badge;
40 changes: 40 additions & 0 deletions components/Button/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { ReactNode } from "react";

interface ButtonProps {
type?: "submit" | "reset" | "button" | undefined;
variant: "primary" | "success" | "danger";
children: ReactNode;
disabled?: boolean;
}

const baseButtonStyle =
"w-[164px] h-[52px] flex items-center justify-center text-base font-bold rounded-3xl border-2 border-gray-900 shadow-button duration-150";

const Button = ({ type, variant, children, disabled }: ButtonProps) => {
const getVariantStyle = () => {
if (disabled) {
return "bg-gray-200 text-gray-900";
}

switch (variant) {
case "primary":
return "bg-primary text-white hover:bg-primary-dark";
case "danger":
return "bg-danger text-white hover:bg-danger-dark";
default:
return "bg-gray-200 text-gray-900";
}
};

return (
<button
type={type}
disabled={disabled}
className={`${baseButtonStyle} ${getVariantStyle()}`}
>
{children}
</button>
);
};

export default Button;
7 changes: 7 additions & 0 deletions components/FormControl/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { ReactNode } from "react";

const FormControl = ({ children }: { children: ReactNode }) => {
return <div className="flex items-center gap-4">{children}</div>;
};

export default FormControl;
28 changes: 28 additions & 0 deletions components/Header/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import Image from "next/image";
import Link from "next/link";

const Header = () => {
return (
<header className="h-[60px] py-[10px] bg-white border-b border-b-gray-200">
<div className="flex items-center md:w-full lg:w-[1200px] lg:mx-auto">
<Link href="/" className="flex items-center">
<Image
src="/images/logo-img.svg"
alt="두잇 로고 이미지"
width="70"
height="40"
className="mr-2 h-[40px]"
/>
<Image
src="/images/logo-txt.svg"
alt="do it;"
width="74"
height="40"
/>
</Link>
</div>
</header>
);
};

export default Header;
26 changes: 26 additions & 0 deletions components/Input/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
"use client";

import { ChangeEvent } from "react";

interface InputProps {
id: string;
type?: string;
value: string;
placeholder?: string;
onChange: (e: ChangeEvent<HTMLInputElement>) => void;
}

const Input = ({ id, type, value, placeholder, onChange }: InputProps) => {
return (
<input
id={id}
type={type}
value={value}
placeholder={placeholder}
className="w-full h-[52px] px-6 text-base text-gray-900 bg-gray-100 rounded-3xl border-2 border-gray-900 shadow-button"
onChange={onChange}
/>
);
};

export default Input;
Loading