Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
8740806
chore: 미션 초기 세팅
SanginJeong Oct 24, 2025
2e5abc1
feat: 컬러,폰트, 이미지 설정
SanginJeong Oct 24, 2025
eb8d439
feat: 이미지 추가"
SanginJeong Oct 24, 2025
5cb8004
chore: 색상 변수 오타 수정
SanginJeong Oct 24, 2025
daacec4
feat: Button 컴포넌트 구현
SanginJeong Oct 24, 2025
7530b5b
feat: Button 컴포넌트 구현
SanginJeong Oct 24, 2025
7b362a1
feat: Badge 구현
SanginJeong Oct 24, 2025
40998da
feat: Badge 구현
SanginJeong Oct 24, 2025
6ec6662
feat: checkbox 구현
SanginJeong Oct 24, 2025
941126e
feat: todo 구현
SanginJeong Oct 25, 2025
80ec748
feat: TodoList 구현
SanginJeong Oct 25, 2025
1be6363
feat: Header 구현'
SanginJeong Oct 25, 2025
255c564
feat: Layout 구현
SanginJeong Oct 25, 2025
b558d6a
refactor: 버튼 props 전달
SanginJeong Oct 25, 2025
bc55513
feat: 검색창 UI 구현
SanginJeong Oct 25, 2025
7f712b0
chore: gitignore .env 추가
SanginJeong Oct 25, 2025
315e4f9
feat: axios instance 설정
SanginJeong Oct 25, 2025
1fbfd58
chore: TodoItem 타입 이동
SanginJeong Oct 25, 2025
d56106a
feat: getTodo api 구현
SanginJeong Oct 29, 2025
8d41f5e
refactor: TodoList 렌더링 시 key 추가
SanginJeong Oct 29, 2025
f11ad7a
feat: getTodo api 구현
SanginJeong Oct 29, 2025
560d7e2
refactor: TodoList 렌더링 시 key 추가
SanginJeong Oct 29, 2025
55879e0
feat: postTodo api 구현
SanginJeong Oct 29, 2025
ad8bc68
fix: 충돌 해결
SanginJeong Oct 29, 2025
78ee62b
feat: deleteTodo api 구현
SanginJeong Oct 29, 2025
d1c050c
feat: patchTodo api 구현'
SanginJeong Oct 29, 2025
b479700
Merge branch 'Next-정상인-sprint9' of https://github.com/SanginJeong/18-…
SanginJeong Oct 29, 2025
249a133
fix: onClick 수정
SanginJeong Oct 31, 2025
83aea8a
feat: todo 렌더링 및 스타일 수정
SanginJeong Oct 31, 2025
525a2bd
refactor: 버튼 disabled 스타일 적용
SanginJeong Oct 31, 2025
39367ec
refactor: Todo 커서 default 변경
SanginJeong Oct 31, 2025
1c39f9e
remove: 테스트 코드 삭제
SanginJeong Oct 31, 2025
2fc9fd8
feat: 빈 데이터일 때 이미지 보여주기
SanginJeong Oct 31, 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
1 change: 1 addition & 0 deletions my-app/.gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
.env
/node_modules
/.pnp
.pnp.*
Expand Down
15 changes: 15 additions & 0 deletions my-app/api/deleteTodo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { instance } from "./instance";

interface DeleteTodoRequest {
itemId: number;
}

export interface DeleteTodoResponse {
message: string;
}

export const deleteTodo = async ({
itemId,
}: DeleteTodoRequest): Promise<DeleteTodoResponse> => {
return instance.delete(`/items/${itemId}`);
};
16 changes: 16 additions & 0 deletions my-app/api/getTodos.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { instance } from "./instance";
import { TodoItem } from "@/types/global";

interface GetTodosRequest {
page?: number;
pageSize?: number;
}

export type GetTodosResponse = TodoItem[];

export const getTodos = async ({
page = 1,
pageSize = 10,
}: GetTodosRequest = {}): Promise<GetTodosResponse> => {
return instance.get("/items", { params: { page, pageSize } });
};
37 changes: 37 additions & 0 deletions my-app/api/instance.ts
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

크으 ~ Axios 세팅이 깔끔하고 좋네요 👍

Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import axios, { AxiosResponse } from "axios";

const NEXT_PUBLIC_BACKEND_URL = process.env.NEXT_PUBLIC_BACKEND_URL;
const NEXT_PUBLIC_TENANTID = process.env.NEXT_PUBLIC_TENANTID;

export const instance = axios.create({
baseURL: NEXT_PUBLIC_BACKEND_URL,
headers: {
"Content-Type": "application/json",
},
});

instance.interceptors.request.use((config) => {
config.baseURL = `${NEXT_PUBLIC_BACKEND_URL}/${NEXT_PUBLIC_TENANTID}`;

return config;
});

instance.interceptors.response.use(
(response: AxiosResponse) => {
return response.data;
},
(error) => {
if (axios.isAxiosError(error)) {
console.error(
"status: ",
error.response?.status,
"message: ",
error.message
);
} else if (error instanceof Error) {
console.error(error.message);
}

return Promise.reject(error);
}
);
22 changes: 22 additions & 0 deletions my-app/api/patchTodo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { TodoItem } from "@/types/global";
import { instance } from "./instance";

interface PatchTodoRequest {
itemId: number;
name?: string;
isCompleted?: boolean;
}

export interface PatchTodoResponse extends TodoItem {
tenantId: string;
memo: string;
imageUrl: string;
}

export const patchTodo = async ({
itemId,
name,
isCompleted,
}: PatchTodoRequest): Promise<PatchTodoResponse> => {
return instance.patch(`/items/${itemId}`, { name, isCompleted });
};
18 changes: 18 additions & 0 deletions my-app/api/postTodo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { TodoItem } from "@/types/global";
import { instance } from "./instance";

interface PostTodoRequest {
name: string;
}

export interface PostTodoResponse extends TodoItem {
tenantId: string;
memo: string;
imageUrl: string;
}

export const postTodo = async ({
name,
}: PostTodoRequest): Promise<PostTodoResponse> => {
return instance.post("/items", { name });
};
Comment on lines +4 to +18
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

굿굿 ! 응답과 요청 타입을 정의하셨군요 !

이제 타입스크립트에 완전 적응하신게 느껴집니다 👍👍👍👍

17 changes: 17 additions & 0 deletions my-app/components/Badge/Badge.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
.badge {
display: inline-block;
padding: 4px 27px;
border-radius: 23px;
font-size: 18px;
font-weight: 400;
}

.todo {
background-color: var(--lime-300);
color: var(--green-700);
}

.done {
background-color: var(--green-700);
color: var(--amber-300);
}
24 changes: 24 additions & 0 deletions my-app/components/Badge/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import styles from "./Badge.module.css";

const variantsToLabel = {
todo: "TO DO",
done: "DONE",
};

interface BadgeProps {
variants: keyof typeof variantsToLabel;
className?: string;
}

const Badge = ({ variants, className }: BadgeProps) => {
Comment on lines +3 to +13
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(의견/선택) Badge는 흔히 사용되는 유저 인터페이스예요 !

근데 지금 정의하신걸 보니까 해당 뱃지는 공통으로 사용되는 흔한 뱃지가 아니라 상태에 대한 뱃지를 표현하고 있는 것으로 보이는군요?
StatusBadge와 같이 네이밍을 해도 괜찮을 것 같습니다 !

return (
<span
className={`${styles.badge} ${styles[variants]} ${className ?? ""}`}
aria-label={variantsToLabel[variants]}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오호 aria-label도 신경쓰셨군요 ? 👐

>
{variantsToLabel[variants]}
</span>
);
};

export default Badge;
34 changes: 34 additions & 0 deletions my-app/components/Button/Button.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
.button {
border: 2px solid var(--state-900);
border-radius: 24px;
width: 164.35px;
height: 52px;
box-shadow: 4px 4px var(--black);
cursor: pointer;
}

.button:disabled {
background-color: var(--state-500);
cursor: default;
}

.append {
background-color: var(--violet-600);
color: var(--white);
}

.delete {
background-color: var(--rose-500);
color: var(--white);
}

.update {
background-color: var(--lime-300);
}

.content {
display: flex;
justify-content: center;
align-items: center;
gap: 4px;
}
39 changes: 39 additions & 0 deletions my-app/components/Button/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { ButtonHTMLAttributes } from "react";
import styles from "./Button.module.css";
import Image from "next/image";
import IcPlus from "@/public/images/ic_plus.svg";
import IcDelete from "@/public/images/ic_x.svg";
import IcUpdate from "@/public/images/ic_check.svg";

const variantsToLabel = {
append: "추가하기",
delete: "삭제하기",
update: "수정완료",
};

const variantsToIcon = {
append: <Image src={IcPlus} alt="추가하기" aria-label="항목 추가하기" />,
delete: <Image src={IcDelete} alt="삭제하기" aria-label="항목 삭제하기" />,
update: <Image src={IcUpdate} alt="수정완료" aria-label="항목 수정완료" />,
};

interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variants: keyof typeof variantsToLabel;
}

const Button = ({ variants, ...props }: ButtonProps) => {
return (
<button
className={`${styles.button} ${styles[variants]} font-bold-16`}
aria-label={variantsToLabel[variants]}
{...props}
>
<span className={styles.content}>
{variantsToIcon[variants]}
<span>{variantsToLabel[variants]}</span>
</span>
</button>
);
};

export default Button;
9 changes: 9 additions & 0 deletions my-app/components/CheckBox/CheckBox.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
.button {
background: none;
border: none;
cursor: pointer;
padding: 0;
width: 32px;
height: 32px;
position: relative;
}
35 changes: 35 additions & 0 deletions my-app/components/CheckBox/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { ButtonHTMLAttributes } from "react";
import IcCheckTrue from "@/public/images/ic_check-true.svg";
import IcCheckFalse from "@/public/images/ic_check-false.svg";
import Image from "next/image";
import styles from "./CheckBox.module.css";

interface CheckBoxProps extends ButtonHTMLAttributes<HTMLButtonElement> {
isCompleted: boolean;
}

const CheckBox = ({ isCompleted, ...props }: CheckBoxProps) => {
return (
<button className={styles.button} onClick={props.onClick}>
{isCompleted ? (
<Image
src={IcCheckTrue}
alt="완료"
aria-label="done"
fill
quality={100}
/>
) : (
<Image
src={IcCheckFalse}
alt="미완료"
aria-label="not-done"
fill
quality={100}
/>
)}
</button>
);
};

export default CheckBox;
25 changes: 25 additions & 0 deletions my-app/components/Header/Header.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
.header {
border-bottom: 1px solid var(--state-200);
height: 60px;
}

.nav {
max-width: 1200px;
margin: 0 auto;
height: 100%;
display: flex;
align-items: center;
}

.logo-small {
display: none;
}

@media screen and (max-width: 720px) {
.logo-large {
display: none;
}
.logo-small {
display: block;
}
}
30 changes: 30 additions & 0 deletions my-app/components/Header/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import ImgLogoLarge from "@/public/images/img_logo-large.svg";
import ImgLogoSmall from "@/public/images/img_logo-small.svg";
import Image from "next/image";
import Link from "next/link";
import styles from "./Header.module.css";

const Header = () => {
return (
<header className={styles.header}>
<nav className={styles.nav}>
<Link href="/">
<Image
src={ImgLogoLarge}
alt="헤더 로고"
quality={100}
className={styles["logo-large"]}
/>
<Image
src={ImgLogoSmall}
alt="헤더 로고"
quality={100}
className={styles["logo-small"]}
/>
</Link>
</nav>
</header>
);
};

export default Header;
5 changes: 5 additions & 0 deletions my-app/components/Layout/Layout.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
.layout {
max-width: 1200px;
margin: 0 auto;
padding-top: 24px;
}
18 changes: 18 additions & 0 deletions my-app/components/Layout/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ReactNode } from "react";
import Header from "../Header";
import styles from "./Layout.module.css";

interface LayoutProps {
children: ReactNode;
}

const Layout = ({ children }: LayoutProps) => {
return (
<>
<Header />
<div className={styles.layout}>{children}</div>
</>
);
};

export default Layout;
13 changes: 13 additions & 0 deletions my-app/components/Todo/Todo.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
.todo {
display: flex;
align-items: center;
gap: 16px;
border: 2px solid var(--state-900);
border-radius: 27px;
padding: 9px;
}

.done {
background-color: var(--violet-100);
text-decoration-line: line-through;
}
Loading
Loading