Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
1fec91f
chore(Sprint9): 사용하지 않는 초기 파일 및 코드 삭제
cskime Sep 8, 2025
c50f6f7
feat(Sprint9): Color component 구현
cskime Sep 8, 2025
fba3252
feat(Sprint9): SearchInput component 구현
cskime Sep 8, 2025
5b8c8e4
feat(Sprint9): Button component 구현
cskime Sep 8, 2025
fc5eb4e
feat(Sprint9): SearchInput style 수정
cskime Sep 8, 2025
33b547d
feat(Sprint9): GlobalNavBar component 구현
cskime Sep 8, 2025
67eac7b
feat(Sprint9): Home page 구현 - nav bar, search form
cskime Sep 8, 2025
80f89ae
feat(Sprint9): Todo component 구현
cskime Sep 8, 2025
fba7503
refactor(Sprint9): Home content에 반응형 적용, GlobalNavBar 불필요한 style 제거
cskime Sep 8, 2025
ad556b0
feat(Sprint9): Todo list 구현
cskime Sep 8, 2025
96d583d
feat(Sprint9): Font 설정
cskime Sep 8, 2025
74ba397
fix(Sprint9): Nav bar 세로 정렬 수정
cskime Sep 8, 2025
3438aeb
feat(Sprint9): 로고 클릭 시 home 이동, 잘못 사용하고 있는 <picture> 수정
cskime Sep 8, 2025
e56342f
feat(Sprint9): TODO/DONE 목록 비었을 때 empty message 표시
cskime Sep 8, 2025
7198c53
feat(Sprint9): Todo 추가, TODO/DONE 상태 변경
cskime Sep 8, 2025
55ca1a7
refactor(Sprint9): stylesheet link를 document로 이동
cskime Sep 8, 2025
4e9e5cc
feat(Sprint9): Head 정보를 document로 이동
cskime Sep 8, 2025
6416546
feat(Sprint9): API 연동
cskime Sep 11, 2025
59e35c3
feat(Sprint9): 로딩 중 dimmed view 추가
cskime Sep 11, 2025
644b899
refactor(Sprint9): SSR 방식으로 초기 화면 로딩
cskime Sep 11, 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
7 changes: 7 additions & 0 deletions my-app/components/button/button-type.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const BUTTON_TYPE = Object.freeze({
add: "add",
edit: "edit",
delete: "delete",
});
Comment on lines +1 to +5
Copy link
Collaborator

Choose a reason for hiding this comment

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

오우... Object.freeze라니. ㄷㄷ 꼼꼼하시군요? 👍

어라 근데 왜 타입스크립트는 안사용하셨나요? 🤔

Copy link
Collaborator Author

@cskime cskime Sep 12, 2025

Choose a reason for hiding this comment

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

강의가 JavaScript로 진행되어서 별 생각 없이 이어지는 미션도 JavaScript를 사용했어요 ㅎㅎ.. 미션 10은 TypeScript로 바꾼 다음 진행할 예정입니다!


export { BUTTON_TYPE };
27 changes: 27 additions & 0 deletions my-app/components/button/button.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { BUTTON_TYPE } from "./button-type";
import styles from "./button.module.css";

const className = {
[BUTTON_TYPE.add]: `${styles.button} ${styles.add}`,
[BUTTON_TYPE.edit]: `${styles.button} ${styles.edit}`,
[BUTTON_TYPE.delete]: `${styles.button} ${styles.delete}`,
};

const leadingIcon = {
[BUTTON_TYPE.add]: "/icons/ic-plus.svg",
[BUTTON_TYPE.edit]: "/icons/ic-check.svg",
[BUTTON_TYPE.delete]: "/icons/ic-xmark-white.svg",
};

function Button({ children, type = BUTTON_TYPE.add, ...props }) {
return (
<button className={className[type]} {...props}>
<div className={styles.trailingIcon}>
<img src={leadingIcon[type]} alt={type} />
</div>
{children}
</button>
);
}
Comment on lines +4 to +25
Copy link
Collaborator

Choose a reason for hiding this comment

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

크으 공통 컴포넌트가 참 깔끔하네요 👍

다만, propstype이라는 이름 보다는 color(primary, secondary, warning 등)이라고 지을 수도 있었겠네요.
...
라고 작성하려 하였으나 방금 디자인 문서 보고 오니까 디자이너가 정의해준 대로 하셨던거군요 ! 😅😅


export default Button;
42 changes: 42 additions & 0 deletions my-app/components/button/button.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
.button {
color: var(--color-state-900);
background-color: var(--color-state-200);
padding: 18px 40px;
border: 2px solid var(--color-state-900);
border-radius: 24px;
box-shadow: 4px 4px 0px var(--color-state-900);
font-size: 16px;
font-weight: 700;
line-height: 100%;
cursor: pointer;
display: flex;
align-items: center;
gap: 4px;
white-space: nowrap;
}
.button .trailingIcon {
width: 16px;
height: 16px;
}
.button .trailingIcon img {
width: 100%;
height: 100%;
}

.button.add:active {
color: white;
background-color: var(--color-violet-600);
}
.button.add:active img {
filter: invert(100%) sepia(14%) saturate(1801%) hue-rotate(190deg)
brightness(116%) contrast(101%);
}

.button.edit:active {
background-color: var(--color-lime-300);
}

.button.delete {
color: white;
background-color: var(--color-rose-500);
}
30 changes: 30 additions & 0 deletions my-app/components/color/color.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
function colorVariable(name, shade) {
return `var(--color-${name}-${Math.max(100, Math.min(900, shade))})`;
}
Comment on lines +1 to +3
Copy link
Collaborator

Choose a reason for hiding this comment

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

크으.. 여기서도 꼼꼼함이 돋보이는군요.

string은 참 다루기 어려워요. 오타 하나라도 나면 에러가 발생하는 것도 아니고..
이렇게 함수로 만들어두면 휴먼에러 발생률이 현저히 줄겠네요 👍👍
좋은 아이디어입니다 !


const Colors = {
state: {
100: colorVariable("state", 100),
200: colorVariable("state", 200),
300: colorVariable("state", 300),
400: colorVariable("state", 400),
500: colorVariable("state", 500),
800: colorVariable("state", 800),
900: colorVariable("state", 900),
},
violet: {
100: colorVariable("violet", 100),
600: colorVariable("violet", 600),
},
rose: {
500: colorVariable("rose", 500),
},
lime: {
300: colorVariable("lime", 300),
},
amber: {
800: colorVariable("amber", 800),
},
};

export default Colors;
11 changes: 11 additions & 0 deletions my-app/components/input/search-input.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import styles from "./search-input.module.css";

function SearchInput({ ...props }) {
return (
<div className={styles.searchInput}>
<input type="text" {...props} />
</div>
);
}

export default SearchInput;
24 changes: 24 additions & 0 deletions my-app/components/input/search-input.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
.searchInput {
width: 100%;
background-color: var(--color-state-100);
padding: 16px 24px;
border: 2px solid var(--color-state-900);
border-radius: 24px;
box-shadow: 4px 4px 0px var(--color-state-900);
}
.searchInput input {
background: none;
border: none;
outline: none;
width: 100%;
font-size: 16px;
font-weight: 400;
line-height: 100%;
color: var(--color-state-900);
}
.searchInput input::placeholder {
font-size: 16px;
font-weight: 400;
line-height: 100%;
color: var(--color-state-500);
}
22 changes: 22 additions & 0 deletions my-app/components/nav-bar/global-nav-bar.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import Link from "next/link";
import styles from "./global-nav-bar.module.css";

function GlobalNavBar() {
return (
<nav className={styles.globalNavBar}>
<div className={styles.content}>
<Link href="/">
<picture>
<source
srcSet="/images/logo-small.svg"
media="(max-width: 743px)"
/>
<img src="/images/logo-large.svg" alt="logo" />
</picture>
</Link>
</div>
</nav>
);
}

export default GlobalNavBar;
31 changes: 31 additions & 0 deletions my-app/components/nav-bar/global-nav-bar.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
.globalNavBar {
height: 60px;
background-color: white;
padding: 0 200px;
border-bottom: 1px solid var(--color-state-200);
}

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

@media (max-width: 1199px) {
.globalNavBar {
padding: 0 24px;
}

.globalNavBar .content {
max-width: none;
margin: 0;
}
}

@media (max-width: 743px) {
.globalNavBar {
padding: 0 16px;
}
}
15 changes: 15 additions & 0 deletions my-app/components/portal/portal.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { useEffect, useState } from "react";
import { createPortal } from "react-dom";

function Portal({ children }) {
const [container, setContainer] = useState(null);

useEffect(() => {
// setContainer(document.getElementById("portal"));
setContainer(document.body);
}, []);

return container && createPortal(children, container);
}

export default Portal;
12 changes: 12 additions & 0 deletions my-app/components/todo/todo-label.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import styles from "./todo-label.module.css";

function TodoLabel({ done }) {
let className = styles.todoLabel;
if (done) {
className += ` ${styles.done}`;
}

return <span className={className}>{done ? "DONE" : "TO DO"}</span>;
}
Comment on lines +3 to +10
Copy link
Collaborator

Choose a reason for hiding this comment

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

(제안) 좀 더 확장성있게 만들어볼 수 있겠군요 !

const colorMap = {
  success: styles.success,
  warning: styles.warning,
  error: styles.error,
  info: styles.info,
};

function Label({ status = "info", children }) {
  const baseClass = styles.label;
  const statusClass = colorMap[status] || "";
  const className = `${baseClass} ${statusClass}`.trim();

  return <span className={className}>{children}</span>;
}

위 제안드린 코드는 디자인에 명시된 프로퍼티는 아니지만 핵심은 "컬러를 결정짓는 props"와 "컨텐츠"를 결정짓는 props를 구분하면 어떨까 ! 라는 제안입니다 😉😉


export default TodoLabel;
15 changes: 15 additions & 0 deletions my-app/components/todo/todo-label.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
.todoLabel {
background-color: var(--color-lime-300);
color: var(--color-green-700);
font-family: "HsSantoki20";
font-size: 18px;
font-weight: 400;
line-height: 100%;
padding: 6px 27px;
border-radius: 24px;
}

.todoLabel.done {
background-color: var(--color-green-700);
color: var(--color-amber-300);
}
38 changes: 38 additions & 0 deletions my-app/components/todo/todo-list.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import TodoLabel from "./todo-label";
import styles from "./todo-list.module.css";

function EmptyMessage({ children }) {
const chunks = children.split("\n");
let messageChunks = [];
for (const chunk of chunks) {
messageChunks.push(<span key={chunk}>{chunk}</span>);
messageChunks.push(<br key={`${chunk}-br`} />);
}
messageChunks.pop();
return <p>{messageChunks}</p>;
}

function TodoList({ done = false, children }) {
const emptyMessage = done
? "아직 다 한 일이 없어요.\n해야 할 일을 체크해보세요!"
: "할 일이 없어요.\nTODO를 새롭게 추가해주세요!";

return (
<div className={styles.todoList}>
<TodoLabel done={done} />
{children.length > 0 ? (
<div className={styles.todoListContent}>{children}</div>
) : (
<div className={styles.todoListEmptyContainer}>
<img
src={`/images/${done ? "done" : "todo"}-list-empty.svg`}
alt="empty"
/>
<EmptyMessage>{emptyMessage}</EmptyMessage>
</div>
)}
</div>
);
}

export default TodoList;
32 changes: 32 additions & 0 deletions my-app/components/todo/todo-list.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
.todoList {
width: 100%;
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 16px;
}

.todoListContent {
width: 100%;
display: flex;
flex-direction: column;
gap: 16px;
}

.todoListEmptyContainer {
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
}
.todoListEmptyContainer img {
aspect-ratio: 1 / 1;
}
.todoListEmptyContainer p {
margin: 0;
font-size: 16px;
font-weight: 700;
line-height: 100%;
color: var(--color-state-400);
text-align: center;
}
23 changes: 23 additions & 0 deletions my-app/components/todo/todo.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import styles from "./todo.module.css";

function Todo({ children, checked = false, ...props }) {
let className = styles.todo;
if (checked) {
className += ` ${styles.checked}`;
}

const checkImage = checked
? "/images/checkbox-checked.svg"
: "/images/checkbox.svg";

return (
<div className={className} {...props}>
<div className={styles.checkImage}>
<img src={checkImage} alt="check" />
</div>
<p className={styles.title}>{children}</p>
</div>
);
}

export default Todo;
33 changes: 33 additions & 0 deletions my-app/components/todo/todo.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
.todo {
background-color: white;
border: 2px solid var(--color-state-900);
border-radius: 27px;
font-size: 16px;
font-weight: 400;
line-height: 100%;
color: var(--color-state-800);
display: flex;
align-items: center;
gap: 16px;
height: 50px;
padding: 9px 12px;
cursor: pointer;
}
.todo.checked {
background-color: var(--color-violet-100);
text-decoration: line-through;
}

.todo .checkImage {
width: 32px;
height: 32px;
}

.todo .checkImage img {
width: 100%;
height: 100%;
}

.todo .title {
flex-grow: 1;
}
17 changes: 17 additions & 0 deletions my-app/hooks/use-async-call.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
const { useState } = require("react");

export function useAsyncCall() {
const [isLoading, setLoading] = useState(false);

const execute = async (callback) => {
setLoading(true);
try {
const result = await callback();
return result;
} finally {
setLoading(false);
}
};

return [isLoading, execute];
}
Loading
Loading