Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
9fbbcad
✨ feat(mentor): 이미지 파일 import 위한 global.d.ts 파일 추가
Moon-ju-young Jul 1, 2025
ed41393
✨ feat(mentor): Btn default type="button"으로 설정
Moon-ju-young Jul 1, 2025
440f0dd
♻️ refactor(mentor): SimpleItem type Pick으로 변경
Moon-ju-young Jul 1, 2025
29cc4e3
♻️ refactor(mentor): index page ref 대신 FormData 사용
Moon-ju-young Jul 1, 2025
5c78dfe
✨ feat: BtnImage component 파일 생성
Moon-ju-young Jul 1, 2025
9fd1d95
✨ feat: BtnImage component props type 설정 및 추가
Moon-ju-young Jul 1, 2025
c2e7727
✨ feat: BtnImage component icon image 추가
Moon-ju-young Jul 1, 2025
908c6b8
✨ feat: BtnImage component 구조 형성
Moon-ju-young Jul 1, 2025
0e58559
✨ feat: BtnImage component mode className 추가
Moon-ju-young Jul 1, 2025
ccdf389
✨ feat: BtnImage component type prop 기본값 설정
Moon-ju-young Jul 1, 2025
9f2ee47
💄 style: BtnImage component style 적용
Moon-ju-young Jul 1, 2025
73f8f8e
✨ feat: CheckListDetail component 파일 생성
Moon-ju-young Jul 1, 2025
d8fc946
✨ feat: CheckListDetail component props type 설정 및 추가
Moon-ju-young Jul 1, 2025
449b4e1
✨ feat: CheckListDetail component 구조 형성
Moon-ju-young Jul 2, 2025
ab72738
✨ feat: --violet200 color 변수 추가
Moon-ju-young Jul 2, 2025
100500b
✨ feat: CheckListDetail component 값 수정 로직 변경
Moon-ju-young Jul 3, 2025
6bbc5fd
✨ feat: CheckListDetail component button 일부 속성 및 input name 추가
Moon-ju-young Jul 3, 2025
2bd8cee
✨ feat: CheckListDetail component input width 조절 로직 추가 및 구조 개선
Moon-ju-young Jul 5, 2025
f2e6405
💄 style: CheckListDetail component style 적용
Moon-ju-young Jul 5, 2025
94d6b09
💄 style: reset.css a tag text-decoration 초기화
Moon-ju-young Jul 7, 2025
169f92e
✨ feat: CheckList component Link로 변경
Moon-ju-young Jul 7, 2025
8cd39fc
✨ feat: item page 파일 생성
Moon-ju-young Jul 7, 2025
a5d57ae
✨ feat: item page Gnb 추가
Moon-ju-young Jul 7, 2025
7fe74a0
♻️ refactor: CheckListDetail component isChecked 내부 state로 변경
Moon-ju-young Jul 7, 2025
8be3feb
✨ feat: CheckListDetail component isCompleted input 추가
Moon-ju-young Jul 7, 2025
a95c2fb
✨ feat: item page getServerSideProps 추가
Moon-ju-young Jul 7, 2025
1b088b8
✨ feat: item page CheckListDetail 추가
Moon-ju-young Jul 7, 2025
45baaad
✨ feat: postImage api 함수 추가
Moon-ju-young Jul 7, 2025
32a588e
✨ feat: item page file input 추가
Moon-ju-young Jul 7, 2025
edb28ac
✨ feat: item page memo textarea 추가
Moon-ju-young Jul 7, 2025
5045e60
✨ feat: item page Btn 추가
Moon-ju-young Jul 7, 2025
7b835bb
♻️ refactor: index.module.css 파일명 변경
Moon-ju-young Jul 7, 2025
24b6a50
✨ feat: item page formRef 추가
Moon-ju-young Jul 7, 2025
16360a2
✨ feat: item page itemId prop 추가
Moon-ju-young Jul 7, 2025
f5ecffe
✨ feat: item page handleEditClick 함수 및 로직 추가
Moon-ju-young Jul 7, 2025
1290138
✨ feat: item page handleDeleteClick 함수 및 로직 추가
Moon-ju-young Jul 7, 2025
11e8f53
✨ feat: item page textarea defaultValue 추가
Moon-ju-young Jul 7, 2025
adcc711
✨ feat: item.module.css 파일 추가 및 연결
Moon-ju-young Jul 7, 2025
024de26
✨ feat: item page 요소 className 추가
Moon-ju-young Jul 7, 2025
ae9abe8
✨ feat: --gray50 color 변수 추가
Moon-ju-young Jul 7, 2025
2094d80
💄 style: item page form 스타일링
Moon-ju-young Jul 7, 2025
f88f08a
🐛 fix: item page image 렌더링 오류 수정
Moon-ju-young Jul 7, 2025
b6630c5
💄 style: item page section 스타일링
Moon-ju-young Jul 7, 2025
b9c3936
✨ feat: item page image width 추가
Moon-ju-young Jul 7, 2025
2eaeb0d
♻️ refator: BtnImage component 최상위 태그 label로 변경
Moon-ju-young Jul 7, 2025
8e60fb1
✨ feat: --slate50 color 변수 추가
Moon-ju-young Jul 7, 2025
e48f132
💄 style: item page image-file 부분 스타일링
Moon-ju-young Jul 7, 2025
17ab33b
♻️ refactor: item page memo 부분 구조 리팩토링
Moon-ju-young Jul 8, 2025
b98b568
💄 style: reset.css input textarea 스타일 초기화
Moon-ju-young Jul 8, 2025
dadc8a9
✨ feat: item page textarea 높이 맞춤 로직 추가
Moon-ju-young Jul 8, 2025
b000c53
✨ feat: --ember200 color 변수 추가
Moon-ju-young Jul 8, 2025
1734449
💄 style: item page memo 스타일링
Moon-ju-young Jul 8, 2025
e133fef
✨ feat: item page disabled state 및 속성 추가
Moon-ju-young Jul 8, 2025
144bed8
🐛 fix: item page handleEditClick imageUrl 정상적으로 보내지게 수정
Moon-ju-young Jul 8, 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: 6 additions & 1 deletion components/Btn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const CONTENT: Record<Props["mode"], string> = {

export default function Btn({
className = "",
type = "button",
size = "large",
mode,
...props
Expand All @@ -35,7 +36,11 @@ export default function Btn({
}, [mode]);

return (
<button className={`${styles.btn} ${styles[size]} ${className}`} {...props}>
<button
className={`${styles.btn} ${styles[size]} ${className}`}
type={type}
{...props}
>
<div className={styles.shadow}></div>
<div className={`${styles.content} ${styles[mode]}`}>
<Image height={16} width={16} id={styles.dark} {...img} />
Expand Down
20 changes: 20 additions & 0 deletions components/BtnImage.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
.btn {
justify-items: center;
align-content: center;
width: 64px;
height: 64px;
border-radius: 9999px;
}

.btn.plus {
background-color: var(--slate200);
}

.btn.edit {
border: 2px solid var(--slate900);
background-color: rgb(from var(--slate900) r g b / 0.5);
}

.btn img {
display: block;
}
17 changes: 17 additions & 0 deletions components/BtnImage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { LabelHTMLAttributes } from "react";
import Image from "next/image";
import ic_plus from "@/assets/icons/plus_big.svg";
import ic_edit from "@/assets/icons/edit.svg";
import styles from "./BtnImage.module.css";

interface Props extends LabelHTMLAttributes<HTMLLabelElement> {
mode: "plus" | "edit";
}

export default function BtnImage({ className = "", mode, ...props }: Props) {
return (
<label className={`${styles.btn} ${styles[mode]} ${className}`} {...props}>
<Image alt={mode} src={mode === "plus" ? ic_plus : ic_edit} />
</label>
);
}
12 changes: 8 additions & 4 deletions components/CheckList.tsx
Copy link
Collaborator

Choose a reason for hiding this comment

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

💊 제안
CheckList라는 컴포넌트 명은 "List" 때문에 부적절한 것 같아요. CheckItem 같은 컴포넌트명을 추천드립니다.

Copy link
Collaborator

Choose a reason for hiding this comment

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

💊 제안
요구사항으로 인해 어쩔 수 없지만 a 태그 안에 button 태그가 들어가 있어 구조가 명확하지 않습니다.
이런 경우 CheckList 컴포넌트를 아래처럼 작성하셔서 조금 더 구조를 명확하게 작성하시는 것을 추천드립니다.

export default function CheckItem({
  className = "",
  children,
  isChecked,
  ...props
}: Props) {
  return (
    <Link
      className={`${styles["check-list"]} ${
        styles[String(isChecked)]
      } ${className}`}
      {...props}
    >
      {children}
    </Link>
  );
}
<CheckItem>
    <button type="button" onClick={onClick}>
      <Image alt="checkbox" src={isChecked ? ic_checked : ic_empty} />
    </button>
    {item.name}
</CheckItem>

또한 button 클릭 시 링크 이동이 되지 않도록 Event.preventDefault 함수를 호출하세요~

Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { HTMLAttributes, MouseEventHandler } from "react";
import { AnchorHTMLAttributes, MouseEventHandler } from "react";
import Image from "next/image";
import Link, { LinkProps } from "next/link";
import ic_checked from "@/assets/icons/checkbox_checked.svg";
import ic_empty from "@/assets/icons//checkbox_empty.svg";
import styles from "./CheckList.module.css";

interface Props extends HTMLAttributes<HTMLDivElement> {
type LinkAttributes = AnchorHTMLAttributes<HTMLAnchorElement> & LinkProps;

interface Props extends LinkAttributes {
href: string;
Copy link
Collaborator

Choose a reason for hiding this comment

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

💊 제안
href 속성이 LinkProps에서 필수값으로 선언되어 있으므로 href 부분이 중복됩니다. 지우셔도 될 것 같아요.

Suggested change
href: string;

isChecked: boolean;
onButtonClick: MouseEventHandler<HTMLButtonElement>;
}
Expand All @@ -17,7 +21,7 @@ export default function CheckList({
...props
}: Props) {
return (
<div
<Link
className={`${styles["check-list"]} ${
styles[String(isChecked)]
} ${className}`}
Expand All @@ -27,6 +31,6 @@ export default function CheckList({
<Image alt="checkbox" src={isChecked ? ic_checked : ic_empty} />
</button>
{children}
</div>
</Link>
);
}
43 changes: 43 additions & 0 deletions components/CheckListDetail.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
.check-list {
display: flex;
justify-content: center;
align-items: center;
gap: 16px;
height: 64px;
border: 2px solid var(--slate900);
border-radius: 24px;
}

.check-list.true {
background-color: var(--violet200);
}

.check-list.false {
background-color: white;
}

.check-list * {
color: var(--slate900);
line-height: 23px;
font-size: 20px;
font-weight: 700;
}

.check-list button {
line-height: 0;
}

.check-list span {
z-index: -100;
position: fixed;
visibility: hidden;
white-space: pre;
}

.check-list input {
padding: 0;
border: 0;
outline: none;
background-color: transparent;
text-decoration: underline;
}
48 changes: 48 additions & 0 deletions components/CheckListDetail.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { HTMLAttributes, useEffect, useRef, useState } from "react";
import Image from "next/image";
import ic_checked from "@/assets/icons/checkbox_checked.svg";
import ic_empty from "@/assets/icons//checkbox_empty.svg";
import styles from "./CheckListDetail.module.css";

interface Props extends HTMLAttributes<HTMLDivElement> {
defaultIsChecked: boolean;
defaultValue: string;
}

export default function CheckListDetail({
className = "",
defaultIsChecked = false,
defaultValue = "",
...props
}: Props) {
const [value, setValue] = useState(defaultValue ?? "");
const [isChecked, setIsChecked] = useState(defaultIsChecked);
Comment on lines +7 to +19
Copy link
Collaborator

Choose a reason for hiding this comment

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

💊 제안
defaultValue, defaultIsChecked와 같이 변수명을 다르게 지정하신 이유가 props와 내부 상태 변수명이 충돌하거나 혼동될 수 있어서라면, 아래와 같이 구조 분해 할당 시 별칭을 주는 방식으로 작성하셔도 됩니다!

Suggested change
interface Props extends HTMLAttributes<HTMLDivElement> {
defaultIsChecked: boolean;
defaultValue: string;
}
export default function CheckListDetail({
className = "",
defaultIsChecked = false,
defaultValue = "",
...props
}: Props) {
const [value, setValue] = useState(defaultValue ?? "");
const [isChecked, setIsChecked] = useState(defaultIsChecked);
interface Props extends HTMLAttributes<HTMLDivElement> {
isChecked: boolean;
value: string;
}
export default function CheckListDetail({
className = "",
isChecked:_isChecked= false,
value:_value = "",
...props
}: Props) {
const [value, setValue] = useState(_value ?? "");
const [isChecked, setIsChecked] = useState(_isChecked);

const spanRef = useRef<HTMLSpanElement>(null);
const inputRef = useRef<HTMLInputElement>(null);

useEffect(() => {
const width = (spanRef.current?.offsetWidth ?? 0) + 1;
if (inputRef.current) inputRef.current.style.width = width + "px";
}, [value]);

Comment on lines +23 to +27
Copy link
Collaborator

Choose a reason for hiding this comment

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

❗️ 수정요청
디자인을 봤을 때 CSS로 width를 조작하셔도 될 것 같아요. js로 width를 조작해야할 이유가 없다면, css로 처리를 해주세요.
지금의 경우 span 요소의 offsetWidth 에 따라 input 의 width가 결정되도록 되어 있어 작성한 할 일이 제대로 보이지 않습니다.
확인해보시고 수정하시면 좋겠습니다. 또한 할 일의 글자수가 길 경우도 고려해서 수정해보시면 더 좋겠습니다.

return (
<div
className={`${styles["check-list"]} ${
styles[String(isChecked)]
} ${className}`}
{...props}
>
<button type="button" onClick={() => setIsChecked((prev) => !prev)}>
<Image alt="checkbox" src={isChecked ? ic_checked : ic_empty} />
<input type="hidden" name="isCompleted" value={String(isChecked)} />
</button>
Comment on lines +35 to +38
Copy link
Collaborator

Choose a reason for hiding this comment

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

💊 제안
버튼 태그안에 해당 input이 있어야 하는 이유를 모르겠습니다~ 상호작용되거나 form으로 해당 input의 값이 읽혀 제출될 필요가 없다면 지우시는 것을 추천드려요.

<span ref={spanRef}>{value}</span>
<input
ref={inputRef}
name="name"
value={value}
onChange={(e) => setValue(e.currentTarget.value)}
/>
</div>
);
}
6 changes: 6 additions & 0 deletions global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
declare module "*.jpg";
declare module "*.png";
declare module "*.jpeg";
declare module "*.gif";
declare module "*.svg";
declare module "*.css";
19 changes: 11 additions & 8 deletions lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,6 @@ const api = axios.create({
baseURL: process.env.NEXT_PUBLIC_BASE_URL,
});

export type SimpleItem = {
isCompleted: boolean;
name: string;
id: number;
};

export type ResponseSimpleItems = SimpleItem[];

export type ResponseItem = {
isCompleted: boolean;
imageUrl: string | null;
Expand All @@ -21,6 +13,10 @@ export type ResponseItem = {
id: number;
};

export type SimpleItem = Pick<ResponseItem, "id" | "name" | "isCompleted">;

export type ResponseSimpleItems = SimpleItem[];

export type ResponseDelete = {
message: string;
};
Expand Down Expand Up @@ -60,3 +56,10 @@ export async function deleteItem(itemId: number) {
const response = await api.delete<ResponseDelete>("/items/" + itemId);
return response.data;
}

export async function postImage(file: File) {
const data = new FormData();
data.append("image", file);
const response = await api.post<{ url: string }>("/images/upload", data);
return response.data;
}
22 changes: 11 additions & 11 deletions pages/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import todo from "@/assets/images/todo.png";
import done from "@/assets/images/done.png";
import empty_todo from "@/assets/images/empty_todo.png";
import empty_done from "@/assets/images/empty_done.png";
import styles from "@/styles/index.module.css";
import styles from "@/styles/home.module.css";

export async function getServerSideProps() {
const items = await getItems();
Expand All @@ -30,16 +30,18 @@ export default function Home({
}) {
const [items, setItems] = useState(initialItems);
const [disabled, setDisabled] = useState(false);
const searchRef = useRef<HTMLInputElement>(null);

const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
if (searchRef.current?.value) {
const form = e.currentTarget;
const name = new FormData(form).get("name")?.toString().trim();

if (name) {
try {
setDisabled(true);
const item = await postItem({ name: searchRef.current.value });
const item = await postItem({ name });
setItems((prev) => [...prev, item]);
searchRef.current.value = "";
form.reset();
} finally {
setDisabled(false);
}
Expand All @@ -61,12 +63,8 @@ export default function Home({
<Gnb />
<main>
<form onSubmit={handleSubmit}>
<Search
className={styles.search}
disabled={disabled}
ref={searchRef}
/>
<Btn mode="add" disabled={disabled} />
<Search name="name" className={styles.search} disabled={disabled} />
<Btn mode="add" type="submit" disabled={disabled} />
</form>
<div>
<section>
Expand All @@ -77,6 +75,7 @@ export default function Home({
.map((item) => (
<CheckList
key={item.id}
href={`/items/${item.id}`}
isChecked={false}
onButtonClick={async (e) => {
e.currentTarget.disabled = true;
Expand Down Expand Up @@ -107,6 +106,7 @@ export default function Home({
.map((item) => (
<CheckList
key={item.id}
href={`/items/${item.id}`}
isChecked={true}
onButtonClick={async (e) => {
e.currentTarget.disabled = true;
Expand Down
Loading
Loading