Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.todoDetailTitle {
.todoDetailHeader {
background-color: white;
border: 2px solid var(--color-state-900);
border-radius: 24px;
Expand All @@ -8,21 +8,31 @@
align-items: center;
gap: 16px;
height: 64px;
text-decoration: underline;
}
.todoDetailTitle.checked {
.todoDetailHeader.checked {
background-color: var(--color-violet-200);
}

.todoDetailTitle input {
field-sizing: content;
.todoTitle,
.todoTitleMirror {
font-size: 20px;
font-weight: 800;
line-height: 100%;
text-decoration: underline;
}

.todoTitle {
padding: 0;
background: none;
border: none;
outline: none;
font-size: 20px;
font-weight: 700;
line-height: 100%;
}

.todoTitleMirror {
position: absolute;
visibility: hidden;
white-space: pre;
pointer-events: none;
}

.checkImage {
Expand Down
60 changes: 60 additions & 0 deletions my-app/components/todo/todo-detail-header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { ChangeEvent, useEffect, useRef } from "react";
import styles from "./todo-detail-header.module.css";

export default function TodoDetailHeader({
name,
isCompleted,
onNameChange,
onCompletedChange,
}: {
name: string;
isCompleted: boolean;
onNameChange: (newName: string) => void;
onCompletedChange: (newCompleted: boolean) => void;
}) {
const spanRef = useRef<HTMLSpanElement>(null);
const inputRef = useRef<HTMLInputElement>(null);

useEffect(() => {
if (spanRef.current && inputRef.current) {
const newWidth = spanRef.current.offsetWidth + 4;
inputRef.current.style.width = `${newWidth}px`;
}
}, [inputRef.current?.value, spanRef.current]);

let className = styles.todoDetailHeader;
if (isCompleted) {
className += ` ${styles.checked}`;
}

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

const handleClick = () => {
onCompletedChange(!isCompleted);
};

const handleChange = (event: ChangeEvent<HTMLInputElement>) => {
onNameChange(event.target.value);
};

return (
<div className={className}>
<div className={styles.checkImage} onClick={handleClick}>
<img src={checkImage} alt="check" />
</div>
<div>
<input
className={styles.todoTitle}
value={name}
onChange={handleChange}
ref={inputRef}
/>
<span className={styles.todoTitleMirror} ref={spanRef}>
{name}
</span>
</div>
</div>
);
}
5 changes: 3 additions & 2 deletions my-app/components/todo/todo-detail-image-preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { useEffect, useMemo, useState } from "react";
import TodoDetailImageButton from "./todo-detail-image-button";
import styles from "./todo-detail-image-preview.module.css";

const MAX_FILE_SIZE = 5 * 1024 * 1024; // 5MB

export default function TodoDetailImagePreview({
imageUrl,
onChange,
Expand All @@ -20,8 +22,7 @@ export default function TodoDetailImagePreview({
const handlePreviewChanged = (file: File | null, reset: () => void) => {
if (!file) return;

const maxSize = 5 * 1024 * 1024;
if (file.size > maxSize) {
if (file.size > MAX_FILE_SIZE) {
alert("파일 크기는 5MB 이하여야 합니다.");
reset();
return;
Expand Down
42 changes: 0 additions & 42 deletions my-app/components/todo/todo-detail-title.tsx

This file was deleted.

9 changes: 9 additions & 0 deletions my-app/libs/apis/revalidate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export async function revalidate(paths: string[]) {
await fetch("/api/revalidate", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ paths }),
});
}
24 changes: 24 additions & 0 deletions my-app/pages/api/revalidate/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { NextApiRequest, NextApiResponse } from "next";

export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== "POST") {
return res.status(405).json({ message: "Method not allowed" });
}

try {
const { paths } = req.body;

if (!paths || !Array.isArray(paths) || paths.length === 0) {
return res.status(400).json({ message: "Paths are required" });
}

await Promise.all(paths.map((path) => res.revalidate(path)));

return res.json({ revalidated: true });
} catch (err) {
return res.status(500).json({ message: "Error revalidating" });
}
}
13 changes: 10 additions & 3 deletions my-app/pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import TodoList from "@/components/todo/todo-list";
import TodoListItem from "@/components/todo/todo-list-item";
import { TodoStatus } from "@/components/todo/todo-status";
import { useAsyncCall } from "@/hooks/use-async-call";
import { revalidate } from "@/libs/apis/revalidate";
import { addTodo, getTodos, toggleTodo } from "@/libs/apis/todo";
import styles from "@/styles/home.module.css";
import type { Todo } from "@/types";
Expand All @@ -17,7 +18,7 @@ import {
useState,
} from "react";

export async function getServerSideProps() {
export async function getStaticProps() {
const todos = await getTodos();
return { props: { todos } };
}
Expand All @@ -43,18 +44,24 @@ export default function Home({ todos: initialTodos }: { todos: Todo[] }) {

const handleAddClick: MouseEventHandler = async (event) => {
event.preventDefault();
execute(async () => {
await execute(async () => {
const newTodo = await addTodo(inputValue);
if (!newTodo) return;

await revalidate(["/", `/items/${newTodo.id}`]);

setTodos((prevTodos) => [...prevTodos, newTodo]);
setInputValue("");
});
};

const handleTodoChange = async (todo: Todo) => {
execute(async () => {
await execute(async () => {
const updatedTodo = await toggleTodo(todo);
if (!updatedTodo) return;

await revalidate(["/", `/items/${todo.id}`]);

setTodos((prevTodos) => {
const index = prevTodos.findIndex(
(prevTodo) => prevTodo.id === todo.id
Expand Down
26 changes: 16 additions & 10 deletions my-app/pages/items/[id]/index.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
import Button from "@/components/button/button";
import { ButtonType } from "@/components/button/button-type";
import Portal from "@/components/portal/portal";
import TodoDetailHeader from "@/components/todo/todo-detail-header";
import TodoDetailImagePreview from "@/components/todo/todo-detail-image-preview";
import TodoDetailTitle from "@/components/todo/todo-detail-title";
import { useAsyncCall } from "@/hooks/use-async-call";
import { uploadImage } from "@/libs/apis/image";
import { deleteTodo, editTodo, getTodo } from "@/libs/apis/todo";
import { revalidate } from "@/libs/apis/revalidate";
import { deleteTodo, editTodo, getTodo, getTodos } from "@/libs/apis/todo";
import styles from "@/styles/item.module.css";
import type { Todo } from "@/types";
import { GetServerSidePropsContext } from "next";
import { GetStaticPropsContext } from "next";
import { useRouter } from "next/router";
import { ChangeEvent, useMemo, useState } from "react";

export async function getServerSideProps(context: GetServerSidePropsContext) {
export async function getStaticPaths() {
const todos = await getTodos();
const paths = todos.map((todo) => ({
params: { id: todo.id.toString() },
}));
return { paths, fallback: false };
}

export async function getStaticProps(context: GetStaticPropsContext) {
const { id } = context.params!;
const todo = await getTodo(Number(id));

Expand All @@ -34,10 +43,6 @@ type TodoValuesState = Pick<Todo, "name" | "memo" | "isCompleted">;
export default function Page({ todo }: { todo: Todo }) {
const router = useRouter();

if (!todo) {
return <div>Todo not found</div>;
}

const [todoValues, setTodoValues] = useState<TodoValuesState>({
name: todo.name,
memo: todo.memo,
Expand Down Expand Up @@ -96,7 +101,8 @@ export default function Page({ todo }: { todo: Todo }) {

const result = await editTodo(todo.id, updateValues);
if (result) {
router.replace("/");
await revalidate(["/", `/items/${todo.id}`]);
router.push("/");
}
});
};
Expand All @@ -121,7 +127,7 @@ export default function Page({ todo }: { todo: Todo }) {
<>
<div className={styles.container}>
<div className={styles.content}>
<TodoDetailTitle
<TodoDetailHeader
name={todoValues.name}
isCompleted={todoValues.isCompleted}
onNameChange={handleNameChange}
Expand Down