diff --git a/Mission-FE-Zero100 b/Mission-FE-Zero100
new file mode 160000
index 0000000..839460c
--- /dev/null
+++ b/Mission-FE-Zero100
@@ -0,0 +1 @@
+Subproject commit 839460c80639bc697cb6f3621879e9e8900adf57
diff --git a/package-lock.json b/package-lock.json
index 99bd306..9646210 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,7 +10,7 @@
"dependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0",
- "react-router-dom": "^7.4.1",
+ "react-router-dom": "^7.6.0",
"styled-components": "^6.1.16"
},
"devDependencies": {
@@ -1377,12 +1377,6 @@
"@babel/types": "^7.20.7"
}
},
- "node_modules/@types/cookie": {
- "version": "0.6.0",
- "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
- "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==",
- "license": "MIT"
- },
"node_modules/@types/estree": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.7.tgz",
@@ -2605,15 +2599,13 @@
}
},
"node_modules/react-router": {
- "version": "7.4.1",
- "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.4.1.tgz",
- "integrity": "sha512-Vmizn9ZNzxfh3cumddqv3kLOKvc7AskUT0dC1prTabhiEi0U4A33LmkDOJ79tXaeSqCqMBXBU/ySX88W85+EUg==",
+ "version": "7.6.0",
+ "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.6.0.tgz",
+ "integrity": "sha512-GGufuHIVCJDbnIAXP3P9Sxzq3UUsddG3rrI3ut1q6m0FI6vxVBF3JoPQ38+W/blslLH4a5Yutp8drkEpXoddGQ==",
"license": "MIT",
"dependencies": {
- "@types/cookie": "^0.6.0",
"cookie": "^1.0.1",
- "set-cookie-parser": "^2.6.0",
- "turbo-stream": "2.4.0"
+ "set-cookie-parser": "^2.6.0"
},
"engines": {
"node": ">=20.0.0"
@@ -2629,12 +2621,12 @@
}
},
"node_modules/react-router-dom": {
- "version": "7.4.1",
- "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.4.1.tgz",
- "integrity": "sha512-L3/4tig0Lvs6m6THK0HRV4eHUdpx0dlJasgCxXKnavwhh4tKYgpuZk75HRYNoRKDyDWi9QgzGXsQ1oQSBlWpAA==",
+ "version": "7.6.0",
+ "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.6.0.tgz",
+ "integrity": "sha512-DYgm6RDEuKdopSyGOWZGtDfSm7Aofb8CCzgkliTjtu/eDuB0gcsv6qdFhhi8HdtmA+KHkt5MfZ5K2PdzjugYsA==",
"license": "MIT",
"dependencies": {
- "react-router": "7.4.1"
+ "react-router": "7.6.0"
},
"engines": {
"node": ">=20.0.0"
@@ -2879,12 +2871,6 @@
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
"license": "0BSD"
},
- "node_modules/turbo-stream": {
- "version": "2.4.0",
- "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz",
- "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==",
- "license": "ISC"
- },
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
diff --git a/package.json b/package.json
index 4003de3..d0aa047 100644
--- a/package.json
+++ b/package.json
@@ -13,7 +13,7 @@
"dependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0",
- "react-router-dom": "^7.4.1",
+ "react-router-dom": "^7.6.0",
"styled-components": "^6.1.16"
},
"devDependencies": {
diff --git a/src/App.css b/src/App.css
index 66f8f1f..29255d1 100644
--- a/src/App.css
+++ b/src/App.css
@@ -3,18 +3,23 @@ body {
font-family: Arial, sans-serif; /* 폰트 변경 */
display: flex;
justify-content: center;
- align-items: center;
+ align-items: flex-start; /* 상단 정렬 */
min-height: 100vh;
margin: 0;
background-color: #fff;
+ padding-top: 50px; /* 상단 여백 추가 */
}
.app-container {
max-width: 600px;
width: 100%;
padding: 20px;
+ /* 그림자에 대한 언급은 없으므로 제거하거나 유지 */
+ /* box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); */
+ background-color: #fff; /* 배경색 확실히 지정 */
}
+/* 기존 요소들 스타일 */
h1 {
font-size: 2.5rem;
font-weight: bold;
@@ -29,7 +34,7 @@ h2 {
text-align: left;
}
-/* 입력창 */
+/* 입력창 (AddTodo) */
.input-container {
margin-bottom: 10px;
}
@@ -40,7 +45,8 @@ h2 {
font-size: 1rem;
border: 2px solid #333; /* 입력창만 굵은 테두리 */
border-radius: 0;
- box-sizing: border-box;
+ box-sizing: border-box; /* 패딩과 테두리가 width에 포함되도록 */
+ outline: none; /* 포커스 시 기본 외곽선 제거 */
}
/* Add 버튼 */
@@ -54,9 +60,11 @@ h2 {
font-size: 1rem;
background-color: #000;
color: #fff;
- border: none;
+ border: none; /* Add 버튼은 테두리 없음 */
border-radius: 0;
cursor: pointer;
+ outline: none;
+ transition: background-color 0.2s ease; /* 호버 효과 부드럽게 */
}
.add-button-container button:hover {
@@ -80,9 +88,18 @@ h2 {
border: 1px solid #333; /* 얇은 테두리 */
border-radius: 0;
cursor: pointer;
+ outline: none;
+ transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease;
}
-.filter-buttons button:hover {
+/* 활성 필터 버튼 스타일 */
+.filter-buttons button.active {
+ background-color: #000; /* 활성 버튼은 배경 검정 */
+ color: #fff; /* 글자 흰색 */
+ border-color: #000;
+}
+
+.filter-buttons button:not(.active):hover { /* 활성 상태가 아닐 때만 호버 스타일 적용 */
background-color: #f0f0f0;
}
@@ -94,38 +111,53 @@ h2 {
text-align: left;
}
-/* 작업 목록 */
+/* 작업 목록 컨테이너 */
.task-list {
- margin-top: 10px;
+ /* margin-top: 10px; */
}
+/* 각 할 일 항목 (기본 & 편집 공통 스타일) */
.task-item {
- margin-bottom: 20px;
+ margin-bottom: 20px; /* 항목 하단 간격 */
+ border-bottom: 1px solid #eee; /* 항목 구분선 */
+ padding-bottom: 20px; /* 구분선과 내용 간 간격 */
}
-.task-row {
+.task-item:last-child {
+ border-bottom: none; /* 마지막 항목은 구분선 없음 */
+ padding-bottom: 0;
+}
+
+
+/* 기본 모드 (.task-item 안에 있을 때) */
+.task-item .task-row {
display: flex;
align-items: center;
- margin-bottom: 5px;
+ margin-bottom: 5px; /* 체크박스/이름과 버튼 간 간격 */
}
-.task-row input[type="checkbox"] {
+/* 체크박스 기본 스타일 */
+.task-item .task-row input[type="checkbox"] {
width: 20px;
height: 20px;
margin-right: 10px;
border: 2px solid #333;
border-radius: 0;
- appearance: none;
+ appearance: none; /* 기본 브라우저 스타일 제거 */
cursor: pointer;
+ flex-shrink: 0; /* 체크박스가 줄어들지 않도록 */
+ position: relative; /* 체크마크 위치 기준 */
+ outline: none;
}
-.task-row input[type="checkbox"]:checked {
+/* 체크박스 체크된 상태 스타일 */
+.task-item .task-row input[type="checkbox"]:checked {
background-color: #000;
border-color: #000;
- position: relative;
}
-.task-row input[type="checkbox"]:checked::after {
+/* 체크박스 체크마크 */
+.task-item .task-row input[type="checkbox"]:checked::after {
content: "✔";
color: #fff;
font-size: 14px;
@@ -135,36 +167,258 @@ h2 {
transform: translate(-50%, -50%);
}
-.task-row .task-name {
+/* 할 일 이름 */
+.task-item .task-row .task-name {
font-size: 1rem;
+ flex-grow: 1; /* 남은 공간 차지 */
+ word-break: break-word; /* 긴 단어 줄바꿈 */
}
-.task-actions {
+/* 완료된 할 일 이름 스타일 */
+.task-item .task-row .task-name.completed {
+ text-decoration: line-through;
+ color: #888; /* 회색으로 변경 */
+}
+
+
+/* Edit/Delete 버튼 컨테이너 (기본 모드 & 편집 모드 공통) */
+.task-item .task-actions {
display: flex;
- gap: 10px;
+ gap: 10px; /* 버튼 간 간격 */
}
-.task-actions button {
- flex: 1;
+/* Edit/Delete 버튼 기본 스타일 */
+.task-item .task-actions button {
+ flex: 1; /* 필터 버튼과 동일한 폭 */
padding: 5px 10px;
font-size: 0.9rem;
background-color: #fff;
color: #000;
- border: 1px solid #333;
+ border: 1px solid #333; /* 얇은 테두리 */
border-radius: 0;
cursor: pointer;
+ outline: none;
+ transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease;
}
-.task-actions button:hover {
+.task-item .task-actions button:hover {
background-color: #f0f0f0;
}
-.task-actions .button-delete {
- background-color: #ff0000;
- color: #fff;
-
+/* Delete 버튼 스타일 */
+.task-item .task-actions button:last-child {
+ background-color: #ff0000; /* 빨간색 배경 */
+ color: #fff; /* 흰색 글씨 */
+ border-color: #ff0000; /* 배경과 동일한 색 테두리 */
+}
+
+.task-item .task-actions button:last-child:hover {
+ background-color: #e60000; /* 호버 시 약간 어두운 빨간색 */
+ border-color: #e60000;
+}
+
+
+/* --- 👇 편집 모드 (Editing Mode) 스타일 --- */
+
+/* 편집 모드일 때 할 일 항목 컨테이너 레이아웃 */
+.task-item.editing {
+ display: flex; /* Flexbox 사용 */
+ flex-direction: column; /* 자식 요소를 세로로 쌓음 */
+ align-items: flex-start; /* 왼쪽 정렬 */
+}
+
+/* 편집 모드일 때 기본 .task-row (체크박스 + 이름) 숨기기 */
+.task-item.editing .task-row {
+ display: none;
+}
+
+/* 편집 모드 라벨 스타일 ("New name for...") */
+.task-item.editing .edit-label {
+ font-size: 1rem; /* task-name과 동일한 크기 */
+ margin-bottom: 5px; /* 라벨과 입력창 간 간격 */
+ font-weight: normal; /* 볼드 해제 (task-name은 볼드 아니었으므로) */
+}
+
+/* 편집 모드 입력창 스타일 */
+.task-item.editing input.edit-input {
+ width: 100%; /* 부모 컨테이너 폭에 맞춤 */
+ padding: 8px;
+ font-size: 1rem;
+ border: 2px solid #333;
+ border-radius: 0;
+ box-sizing: border-box;
+ margin-bottom: 10px; /* 입력창과 버튼 간 간격 */
+ outline: none; /* 포커스 시 기본 외곽선 제거 */
+}
+
+/* 편집 모드 버튼 컨테이너 스타일 (Cancel/Save) */
+.task-item.editing .task-actions {
+ display: flex; /* 버튼들은 Flex로 */
+ gap: 10px; /* 버튼 간 간격 */
+ width: 100%; /* 부모(task-item.editing) 너비에 맞춤 */
+}
+
+/* Cancel 버튼 스타일 */
+.task-item.editing .task-actions .cancel-button { /* 클래스 선택자 사용 */
+ flex: 1; /* 동일 폭 */
+ padding: 5px 10px;
+ font-size: 0.9rem;
+ background-color: #fff; /* 흰색 배경 */
+ color: #000; /* 검은색 글씨 */
+ border: 1px solid #333; /* 얇은 테두리 */
+ border-radius: 0;
+}
+.task-item.editing .task-actions .cancel-button:hover {
+ background-color: #f0f0f0;
+}
+
+/* Save 버튼 스타일 */
+.task-item.editing .task-actions .save-button { /* 클래스 선택자 사용 */
+ flex: 1; /* 동일 폭 */
+ padding: 5px 10px;
+ font-size: 0.9rem;
+ /* 👇 여기! Save 버튼 배경을 검정색으로 변경 */
+ background-color: #000; /* 검은색 배경 */
+ color: #fff; /* 글자색 흰색 유지 */
+ border: none; /* 테두리 없음 유지 */
+ /* 👆 여기까지 수정 */
+ border-radius: 0;
+}
+/* Save 버튼 호버 스타일 */
+.task-item.editing .task-actions .save-button:hover {
+ background-color: #333; /* 호버 시 약간 밝은 검정색 */
+}
+
+body {
+ font-family: sans-serif; /* 폰트는 적절히 수정하세요 */
+ margin: 0;
+ background-color: #ffffff; /* 이미지 배경이 흰색이므로 흰색으로 설정 */
+ display: flex;
+ justify-content: center;
+ padding-top: 20px;
+}
+
+.app-container {
+ text-align: center;
+ width: 100%;
+ max-width: 450px; /* 이미지의 UI 너비에 맞게 조절 */
+}
+
+.app-title {
+ font-size: 2.5em; /* 이미지와 유사하게 크기 조절 */
+ font-weight: bold;
+ margin-bottom: 40px; /* 버튼들과의 간격 */
+ color: #000000; /* 검은색 글씨 */
+}
+
+.nav-buttons button {
+ background-color: #e7e7e7; /* 이미지의 밝은 회색 버튼 */
+ color: #333;
+ border: none;
+ padding: 15px 30px; /* 버튼 크기 조절 */
+ margin: 10px;
+ border-radius: 30px; /* 둥근 모서리 */
+ font-size: 1.1em;
+ font-weight: bold; /* 글씨 굵게 */
+ cursor: pointer;
+ transition: background-color 0.3s ease;
+ width: 80%; /* 버튼 너비 조절 */
+ max-width: 300px; /* 최대 너비 */
+}
+
+.nav-buttons button:hover {
+ background-color: #dcdcdc;
+}
+
+.form-container {
+ background-color: #f7f7f7; /* 이미지의 폼 배경색 */
+ padding: 30px 40px; /* 패딩 조절 */
+ border-radius: 8px; /* 약간의 둥근 모서리 */
+ /* box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1); 이미지에는 그림자가 없어보이지만, 필요시 추가 */
+ margin-top: 30px;
+ text-align: left;
+}
+
+.form-container h2 {
+ font-size: 2.2em; /* 제목 크기 조절 */
+ font-weight: bold;
+ color: #000000;
+ text-align: center;
+ margin-bottom: 30px;
+}
+
+.form-group {
+ margin-bottom: 20px;
+ display: flex;
+ align-items: center;
+}
+
+.form-group label {
+ font-weight: bold;
+ color: #333; /* 글자색 */
+ margin-right: 15px;
+ width: 90px; /* 레이블 너비 고정 (비밀번호 글자 수에 맞춤) */
+ text-align: left; /* 레이블 왼쪽 정렬 */
+ font-size: 1em;
+}
+
+.form-group input[type="text"],
+.form-group input[type="password"] {
+ flex-grow: 1;
+ padding: 12px 15px;
+ border: 1px solid #cccccc;
+ border-radius: 5px; /* 인풋 둥근 모서리 */
+ font-size: 1em;
+}
+
+/* 로그인 페이지의 로그인 버튼 */
+.form-container button[type="submit"] {
+ background-color: #5a5a5a; /* 이미지의 어두운 회색 버튼 */
+ color: white;
+ display: block;
+ width: 100%;
+ padding: 14px;
+ font-size: 1.1em;
+ font-weight: bold;
+ border: none;
+ border-radius: 5px; /* 버튼 모서리 */
+ cursor: pointer;
+ transition: background-color 0.3s ease;
+ margin-top: 10px; /* 에러 메시지 위 공간 */
+}
+.form-container button[type="submit"]:hover {
+ background-color: #4a4a4a;
+}
+
+/* 로그인 폼 내의 회원가입 이동 텍스트 (버튼처럼 보이지 않게) */
+.login-form-signup-button {
+ background-color: transparent;
+ color: #555; /* 글자색 */
+ border: none;
+ padding: 0; /* 패딩 제거 */
+ font-size: 0.95em; /* 글자 크기 */
+ cursor: pointer;
+ text-decoration: none; /* 밑줄 제거, 필요시 추가 */
+ display: block;
+ text-align: center; /* 중앙 정렬 */
+ margin-top: 25px; /* 로그인 버튼과의 간격 */
+}
+
+.login-form-signup-button:hover {
+ color: #000; /* 호버 시 색상 변경 */
+ text-decoration: underline; /* 호버 시 밑줄 */
+}
+
+
+.error-message {
+ color: red;
+ font-size: 1.2em; /* 점 크기를 위해 폰트 사이즈 조절 */
+ text-align: right;
+ /* height: 20px; 레이아웃을 위해 필요하다면 설정 */
+ /* padding-right: 0; 로그인 폼 오른쪽 끝에 빨간 점을 위한 위치 조정 */
+ /* 이미지는 입력 필드 오른쪽에 점이 있으므로, 이 스타일은 form-group 내에 위치하는게 좋을 수 있음 */
+ /* 지금은 로그인 버튼 위에 표시되도록 설정되어 있음 */
+ margin-top: 5px;
+ margin-bottom: 10px; /* 로그인 버튼과의 간격 */
}
-.task-actions .button-delete:hover {
- background-color: #e60000;
-}
\ No newline at end of file
diff --git a/src/App.jsx b/src/App.jsx
index 755756d..73fe676 100644
--- a/src/App.jsx
+++ b/src/App.jsx
@@ -1,67 +1,29 @@
+import React from 'react';
+import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
-import Header from "@/component/Header";
-import AddTodo from "@/component/AddTodo";
-import Category from "@/component/Category";
-import TodoList from "@/component/TodoList";
+// 페이지 컴포넌트 import
+import HomePage from './pages/HomePage'; // 이전 단계에서 생성
+import LoginPage from './pages/LoginPage'; // 이전 단계에서 생성
+import SignupPage from './pages/SignupPage'; // 이전 단계에서 생성
+import TodoPage from './pages/TodoPage'; // 방금 수정한 TodoPage
-import "./App.css";
+import './App.css'; // 전역 스타일 및 로그인/회원가입 UI 스타일 포함
function App() {
- const [tasks, setTasks] = useState([
- { name: "Eat", completed: true },
- { name: "Sleep", completed: false },
- { name: "Repeat", completed: false },
- ]);
- const [inputValue, setInputValue] = useState("");
- const [filter, setFilter] = useState("all");
-
- const addTask = () => {
- if (inputValue) {
- setTasks([...tasks, { name: inputValue, completed: false }]);
- setInputValue("");
- }
- };
-
- const toggleTask = (index) => {
- const updatedTasks = [...tasks];
- updatedTasks[index].completed = !updatedTasks[index].completed;
- setTasks(updatedTasks);
- };
-
- const deleteTask = (index) => {
- const updatedTasks = tasks.filter((_, i) => i !== index);
- setTasks(updatedTasks);
- };
-
- const editTask = (index, newName) => {
- const updatedTasks = [...tasks];
- updatedTasks[index].name = newName;
- setTasks(updatedTasks);
- };
-
- const filteredTasks = tasks.filter((task) => {
- if (filter === "all") return true;
- if (filter === "active") return !task.completed;
- if (filter === "completed") return task.completed;
- return true;
- });
-
return (
-
+
+ {/* 전체 앱을 감싸는 컨테이너 */}
+
OO's TODO
{/* 모든 페이지 상단에 표시될 제목 */}
+
+ } />
+ } />
+ } />
+ } /> {/* Todo 페이지 라우트 */}
+ {/* 일치하는 라우트가 없을 경우 루트 페이지로 리다이렉트 (선택 사항) */}
+ } />
+
+
+
);
}
diff --git a/src/component/AddTodo.jsx b/src/component/AddTodo.jsx
index 4207b6b..cfd0715 100644
--- a/src/component/AddTodo.jsx
+++ b/src/component/AddTodo.jsx
@@ -10,7 +10,9 @@ function AddTodo({ inputValue, setInputValue, addTask }) {
onChange={(e) => setInputValue(e.target.value)}
/>
-
+
+
+
);
}
diff --git a/src/component/ButtonComponent.jsx b/src/component/ButtonComponent.jsx
index 701db59..bcb646d 100644
--- a/src/component/ButtonComponent.jsx
+++ b/src/component/ButtonComponent.jsx
@@ -1,10 +1,23 @@
-function ButtonComponent({ label, onClick, type }) {
-
- const buttonClass = type === "add" ? "add-button-container" : "";
+// src/component/ButtonComponent.jsx
+import React from "react";
+function ButtonComponent({
+ children, // 버튼 내부에 표시될 내용 (텍스트, 아이콘 등)
+ onClick, // 클릭 시 실행될 함수
+ className = "", // CSS 클래스 (기본값 빈 문자열)
+ type = "button", // 버튼 타입 (기본값 "button")
+ disabled = false, // 비활성화 여부 (기본값 false)
+ ...restProps // 그 외 HTML button 속성들 (예: aria-label)
+}) {
return (
-