From b3e7b7368d5ea09bc47dad35c184ede215cd1876 Mon Sep 17 00:00:00 2001 From: Taegwan Youn Date: Tue, 1 Jul 2025 21:04:02 +0900 Subject: [PATCH 1/3] =?UTF-8?q?chore:=20dotenv=20=EC=84=A4=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 13 +++++++++++++ package.json | 6 +++++- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 5bab43f..e6be0f4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "dependencies": { "@prisma/client": "^6.10.1", "@prisma/extension-accelerate": "^2.0.1", + "dotenv": "^17.0.0", "next": "15.3.4", "react": "^19.0.0", "react-dom": "^19.0.0" @@ -3062,6 +3063,18 @@ "node": ">=0.10.0" } }, + "node_modules/dotenv": { + "version": "17.0.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.0.0.tgz", + "integrity": "sha512-A0BJ5lrpJVSfnMMXjmeO0xUnoxqsBHWCoqqTnGwGYVdnctqXXUEhJOO7LxmgxJon9tEZFGpe0xPRX0h2v3AANQ==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", diff --git a/package.json b/package.json index 39bc04a..0dcab54 100644 --- a/package.json +++ b/package.json @@ -6,11 +6,15 @@ "dev": "next dev --turbopack", "build": "next build", "start": "next start", - "lint": "next lint" + "lint": "next lint", + "db:pull": "dotenv -e .env prisma db pull", + "db:push": "dotenv -e .env prisma db push", + "db:studio": "dotenv -e .env prisma studio" }, "dependencies": { "@prisma/client": "^6.10.1", "@prisma/extension-accelerate": "^2.0.1", + "dotenv": "^17.0.0", "next": "15.3.4", "react": "^19.0.0", "react-dom": "^19.0.0" From 7dedc8493986276ee3b9bd08dfd0ed68e33feea6 Mon Sep 17 00:00:00 2001 From: Taegwan Youn Date: Tue, 1 Jul 2025 21:18:41 +0900 Subject: [PATCH 2/3] =?UTF-8?q?chore:=20motion=20=EC=84=A4=EC=B9=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .vscode/settings.json | 3 + app/create/page.tsx | 13 +--- app/layout.tsx | 6 +- app/page.tsx | 32 +-------- app/template.tsx | 19 +++++ package-lock.json | 109 +++++++++++++++++++++++++++++ package.json | 9 ++- prisma/schema.prisma | 95 +++++++++---------------- src/{app => core}/db/db.ts | 0 src/shared/libs/getMoodEmoji.ts | 11 +++ src/shared/libs/getWeatherEmoji.ts | 9 +++ 11 files changed, 196 insertions(+), 110 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 app/template.tsx rename src/{app => core}/db/db.ts (100%) create mode 100644 src/shared/libs/getMoodEmoji.ts create mode 100644 src/shared/libs/getWeatherEmoji.ts diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..9903a41 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "cSpell.words": ["autoincrement"] +} diff --git a/app/create/page.tsx b/app/create/page.tsx index 8fc0afd..8166565 100644 --- a/app/create/page.tsx +++ b/app/create/page.tsx @@ -40,7 +40,6 @@ export default function CreateDiary() { const handleSubmit = (e: React.FormEvent) => { e.preventDefault(); - // 실제 저장 로직은 나중에 구현 console.log("일기 저장:", formData); }; @@ -67,7 +66,6 @@ export default function CreateDiary() { - {/* Main Content */}

새로운 일기 작성

@@ -76,11 +74,8 @@ export default function CreateDiary() {
- {/* Title */}
- +
- {/* Mood & Weather */}
@@ -136,7 +130,6 @@ export default function CreateDiary() {
- {/* Content */}
- {/* Category */}
@@ -173,7 +165,6 @@ export default function CreateDiary() {
- {/* Tags */}
- {/* Public/Private */}
- {/* Action Buttons */}
) { return ( - - {children} - + {children} ); } diff --git a/app/page.tsx b/app/page.tsx index 328a4b9..1307d88 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,3 +1,5 @@ +import { getMoodEmoji } from "@/src/shared/libs/getMoodEmoji"; +import { getWeatherEmoji } from "@/src/shared/libs/getWeatherEmoji"; import Link from "next/link"; export default function Home() { @@ -40,31 +42,8 @@ export default function Home() { }, ]; - const getMoodEmoji = (mood: string) => { - const moodMap: { [key: string]: string } = { - happy: "😊", - sad: "😢", - angry: "😠", - excited: "🤩", - calm: "😌", - nervous: "😰", - }; - return moodMap[mood] || "😐"; - }; - - const getWeatherEmoji = (weather: string) => { - const weatherMap: { [key: string]: string } = { - sunny: "☀️", - rainy: "🌧️", - cloudy: "☁️", - snowy: "❄️", - }; - return weatherMap[weather] || "🌤️"; - }; - return (
- {/* Navigation */} - {/* Main Content */}
- {/* Header */}

나의 일기장

소중한 순간들을 기록해보세요

- {/* Diary List */}
{dummyDiaries.map((diary) => (
- {/* Header */}
@@ -123,10 +98,8 @@ export default function Home() {
- {/* Content Preview */}

{diary.content}

- {/* Tags */}
{diary.tags.map((tag, index) => ( @@ -144,7 +117,6 @@ export default function Home() { ))}
- {/* Empty State (일기가 없을 때) */} {dummyDiaries.length === 0 && (
📝
diff --git a/app/template.tsx b/app/template.tsx new file mode 100644 index 0000000..535eb67 --- /dev/null +++ b/app/template.tsx @@ -0,0 +1,19 @@ +"use client"; + +import { useEffect, useState } from "react"; + +export default function Template({ children }: { children: React.ReactNode }) { + const [isVisible, setIsVisible] = useState(false); + + useEffect(() => { + const timer = setTimeout(() => { + setIsVisible(true); + }, 50); + + console.log("새 페이지 진입:", new Date().toISOString()); + + return () => clearTimeout(timer); + }, []); + + return
{children}
; +} diff --git a/package-lock.json b/package-lock.json index e6be0f4..1045f35 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@prisma/client": "^6.10.1", "@prisma/extension-accelerate": "^2.0.1", "dotenv": "^17.0.0", + "motion": "^12.20.4", "next": "15.3.4", "react": "^19.0.0", "react-dom": "^19.0.0" @@ -21,6 +22,7 @@ "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", + "dotenv-cli": "^8.0.0", "eslint": "^9", "eslint-config-next": "15.3.4", "prisma": "^6.10.1", @@ -3075,6 +3077,45 @@ "url": "https://dotenvx.com" } }, + "node_modules/dotenv-cli": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/dotenv-cli/-/dotenv-cli-8.0.0.tgz", + "integrity": "sha512-aLqYbK7xKOiTMIRf1lDPbI+Y+Ip/wo5k3eyp6ePysVaSqbyxjyK3dK35BTxG+rmd7djf5q2UPs4noPNH+cj0Qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.6", + "dotenv": "^16.3.0", + "dotenv-expand": "^10.0.0", + "minimist": "^1.2.6" + }, + "bin": { + "dotenv": "cli.js" + } + }, + "node_modules/dotenv-cli/node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dotenv-expand": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-10.0.0.tgz", + "integrity": "sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", @@ -3909,6 +3950,33 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/framer-motion": { + "version": "12.20.4", + "resolved": "https://registry.npmjs.org/framer-motion/-/framer-motion-12.20.4.tgz", + "integrity": "sha512-wV3I/BbHVVrTKmvB3hxfkoOocAD79IGsFCqsJzv8wvmKOiU2fxhpOo29xdRSJ5SVcZ+6yFkgRZl+9qbUGX5k4g==", + "license": "MIT", + "dependencies": { + "motion-dom": "^12.20.3", + "motion-utils": "^12.19.0", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -5196,6 +5264,47 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/motion": { + "version": "12.20.4", + "resolved": "https://registry.npmjs.org/motion/-/motion-12.20.4.tgz", + "integrity": "sha512-NOFQGGhFdq+3uK2n8g8lUe5Iz3ZQYzztIvLaO27esJ21AZeSXyUFKWKCaqvrTB1U3XsDnd4GT6HpSqWzt0S25Q==", + "license": "MIT", + "dependencies": { + "framer-motion": "^12.20.4", + "tslib": "^2.4.0" + }, + "peerDependencies": { + "@emotion/is-prop-valid": "*", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/is-prop-valid": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + } + } + }, + "node_modules/motion-dom": { + "version": "12.20.3", + "resolved": "https://registry.npmjs.org/motion-dom/-/motion-dom-12.20.3.tgz", + "integrity": "sha512-lcwmMrkNtPVq0KrOPln5+ZNlICy6x/VsxckQ+sSvmak6rTAi50RaOv7lKyc6lepMO6PIVRMSaU9TENGnOmvmAw==", + "license": "MIT", + "dependencies": { + "motion-utils": "^12.19.0" + } + }, + "node_modules/motion-utils": { + "version": "12.19.0", + "resolved": "https://registry.npmjs.org/motion-utils/-/motion-utils-12.19.0.tgz", + "integrity": "sha512-BuFTHINYmV07pdWs6lj6aI63vr2N4dg0vR+td0rtrdpWOhBzIkEklZyLcvKBoEtwSqx8Jg06vUB5RS0xDiUybw==", + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", diff --git a/package.json b/package.json index 0dcab54..e54257f 100644 --- a/package.json +++ b/package.json @@ -9,12 +9,18 @@ "lint": "next lint", "db:pull": "dotenv -e .env prisma db pull", "db:push": "dotenv -e .env prisma db push", - "db:studio": "dotenv -e .env prisma studio" + "db:studio": "dotenv -e .env prisma studio", + "db:generate": "dotenv -e .env prisma generate", + "db:migrate": "dotenv -e .env prisma migrate dev", + "db:migrate:reset": "dotenv -e .env prisma migrate reset", + "db:seed": "dotenv -e .env prisma db seed", + "db:reset": "dotenv -e .env prisma migrate reset && dotenv -e .env prisma db seed" }, "dependencies": { "@prisma/client": "^6.10.1", "@prisma/extension-accelerate": "^2.0.1", "dotenv": "^17.0.0", + "motion": "^12.20.4", "next": "15.3.4", "react": "^19.0.0", "react-dom": "^19.0.0" @@ -25,6 +31,7 @@ "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", + "dotenv-cli": "^8.0.0", "eslint": "^9", "eslint-config-next": "15.3.4", "prisma": "^6.10.1", diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 20742ce..651c640 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -1,6 +1,3 @@ -// This is your Prisma schema file, -// learn more about it in the docs: https://pris.ly/d/prisma-schema - generator client { provider = "prisma-client-js" } @@ -10,101 +7,75 @@ datasource db { url = env("DATABASE_URL") } -// 사용자 모델 model User { - id Int @id @default(autoincrement()) - email String @unique - name String? - password String // 실제 프로덕션에서는 해시된 패스워드 저장 - avatar String? // 프로필 이미지 URL - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // 관계 - diaries Diary[] + id Int @id @default(autoincrement()) + email String @unique + name String? + password String + avatar String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt categories Category[] + diaries Diary[] tags Tag[] @@map("users") } -// 다이어리 엔트리 모델 model Diary { - id Int @id @default(autoincrement()) - title String - content String // 일기 내용 - mood String? // 기분 (happy, sad, angry, excited, calm, etc.) - weather String? // 날씨 (sunny, rainy, cloudy, snowy, etc.) - isPublic Boolean @default(false) // 공개/비공개 설정 - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // 외래키 + id Int @id @default(autoincrement()) + title String + content String + mood String? + weather String? + isPublic Boolean @default(false) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt userId Int categoryId Int? - - // 관계 - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - category Category? @relation(fields: [categoryId], references: [id], onDelete: SetNull) - tags DiaryTag[] + category Category? @relation(fields: [categoryId], references: [id]) + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + tags DiaryTag[] @@map("diaries") } -// 카테고리 모델 model Category { id Int @id @default(autoincrement()) name String description String? - color String @default("#3B82F6") // 헥스 컬러 코드 + color String @default("#3B82F6") createdAt DateTime @default(now()) updatedAt DateTime @updatedAt + userId Int + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + diaries Diary[] - // 외래키 - userId Int - - // 관계 - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - diaries Diary[] - - // 사용자별로 카테고리명 중복 방지 @@unique([userId, name]) @@map("categories") } -// 태그 모델 model Tag { - id Int @id @default(autoincrement()) + id Int @id @default(autoincrement()) name String - color String @default("#10B981") // 헥스 컬러 코드 - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - // 외래키 - userId Int + color String @default("#10B981") + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + userId Int + diaries DiaryTag[] + user User @relation(fields: [userId], references: [id], onDelete: Cascade) - // 관계 - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - diaries DiaryTag[] - - // 사용자별로 태그명 중복 방지 @@unique([userId, name]) @@map("tags") } -// 다이어리-태그 중간 테이블 (다대다 관계) model DiaryTag { - id Int @id @default(autoincrement()) - - // 외래키 + id Int @id @default(autoincrement()) diaryId Int tagId Int + diary Diary @relation(fields: [diaryId], references: [id], onDelete: Cascade) + tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade) - // 관계 - diary Diary @relation(fields: [diaryId], references: [id], onDelete: Cascade) - tag Tag @relation(fields: [tagId], references: [id], onDelete: Cascade) - - // 같은 다이어리에 같은 태그 중복 방지 @@unique([diaryId, tagId]) @@map("diary_tags") } diff --git a/src/app/db/db.ts b/src/core/db/db.ts similarity index 100% rename from src/app/db/db.ts rename to src/core/db/db.ts diff --git a/src/shared/libs/getMoodEmoji.ts b/src/shared/libs/getMoodEmoji.ts new file mode 100644 index 0000000..59c6533 --- /dev/null +++ b/src/shared/libs/getMoodEmoji.ts @@ -0,0 +1,11 @@ +export const getMoodEmoji = (mood: string) => { + const moodMap: { [key: string]: string } = { + happy: "😊", + sad: "😢", + angry: "😠", + excited: "🤩", + calm: "😌", + nervous: "😰", + }; + return moodMap[mood] || "😐"; +}; diff --git a/src/shared/libs/getWeatherEmoji.ts b/src/shared/libs/getWeatherEmoji.ts new file mode 100644 index 0000000..a66c347 --- /dev/null +++ b/src/shared/libs/getWeatherEmoji.ts @@ -0,0 +1,9 @@ +export const getWeatherEmoji = (weather: string) => { + const weatherMap: { [key: string]: string } = { + sunny: "☀️", + rainy: "🌧️", + cloudy: "☁️", + snowy: "❄️", + }; + return weatherMap[weather] || "🌤️"; +}; From 69eb844b630a0fe96df37d1d2fa1e61cf66aad6b Mon Sep 17 00:00:00 2001 From: Taegwan Youn Date: Tue, 1 Jul 2025 22:21:40 +0900 Subject: [PATCH 3/3] =?UTF-8?q?=EC=A4=91=EA=B0=84=20=EC=A0=90=EA=B2=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/create/page.tsx | 93 +++++++------------- app/diary/[id]/page.tsx | 14 +-- app/template.tsx | 59 +++++++++++-- package.json | 1 + src/features/diary/actions/createDiary.ts | 56 ++++++++++++ src/features/diary/actions/deleteDiary.ts | 4 + src/features/diary/hooks/useSelectedItem.tsx | 17 ++++ src/features/diary/ui/moods.tsx | 57 ++++++++++++ src/{core => shared}/db/db.ts | 0 src/shared/types/db.ts | 9 ++ 10 files changed, 231 insertions(+), 79 deletions(-) create mode 100644 src/features/diary/actions/createDiary.ts create mode 100644 src/features/diary/actions/deleteDiary.ts create mode 100644 src/features/diary/hooks/useSelectedItem.tsx create mode 100644 src/features/diary/ui/moods.tsx rename src/{core => shared}/db/db.ts (100%) create mode 100644 src/shared/types/db.ts diff --git a/app/create/page.tsx b/app/create/page.tsx index 8166565..9eec296 100644 --- a/app/create/page.tsx +++ b/app/create/page.tsx @@ -1,27 +1,17 @@ "use client"; +import { createDiary } from "@/src/features/diary/actions/createDiary"; +import Moods from "@/src/features/diary/ui/moods"; import Link from "next/link"; -import { useState } from "react"; - -export default function CreateDiary() { - const [formData, setFormData] = useState({ - title: "", - content: "", - mood: "", - weather: "", - categoryId: "", - tags: "", - isPublic: false, - }); +import { useActionState, useEffect } from "react"; - const moods = [ - { value: "happy", label: "😊 행복", emoji: "😊" }, - { value: "sad", label: "😢 슬픔", emoji: "😢" }, - { value: "angry", label: "😠 화남", emoji: "😠" }, - { value: "excited", label: "🤩 신남", emoji: "🤩" }, - { value: "calm", label: "😌 평온", emoji: "😌" }, - { value: "nervous", label: "😰 긴장", emoji: "😰" }, - ]; +const initialState = { + success: false, + message: "", +}; + +export default function CreateDiaryPage() { + const [state, formAction] = useActionState(createDiary, initialState); const weathers = [ { value: "sunny", label: "☀️ 맑음", emoji: "☀️" }, @@ -38,14 +28,12 @@ export default function CreateDiary() { { id: "5", name: "꿈", color: "#F59E0B" }, ]; - const handleSubmit = (e: React.FormEvent) => { - e.preventDefault(); - console.log("일기 저장:", formData); - }; + useEffect(() => { + console.log(state); + }); return (
- {/* Navigation */}
@@ -72,7 +57,10 @@ export default function CreateDiary() {

오늘의 소중한 순간을 기록해보세요

- + + {/* 숨겨진 필수 필드들 */} + +
@@ -80,8 +68,6 @@ export default function CreateDiary() { type="text" id="title" name="title" - value={formData.title} - onChange={(e) => setFormData((prev) => ({ ...prev, title: e.target.value }))} placeholder="오늘의 일기 제목을 입력하세요" className="w-full px-4 py-3 border border-gray-300 dark:border-gray-600 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 dark:bg-gray-700 dark:text-white" required @@ -92,20 +78,7 @@ export default function CreateDiary() {
- {moods.map((mood) => ( - - ))} +
@@ -116,9 +89,8 @@ export default function CreateDiary() {
+ {/* 서버 응답 상태 표시 */} + {state.message && ( +
+

{state.message}

+
+ )} +
{ - "use server"; - // 삭제 확인 및 삭제 로직 - if (confirm("정말로 이 일기를 삭제하시겠습니까?")) { - console.log("일기 삭제"); - } - }; - return (
{/* Navigation */} @@ -83,9 +76,6 @@ export default function DiaryDetail({ params }: { params: { id: string } }) { 홈 - - 소개 - ✏️ 일기 쓰기 @@ -150,7 +140,7 @@ export default function DiaryDetail({ params }: { params: { id: string } }) { ✏️ 수정 + ))} +
+ + ); +} diff --git a/src/core/db/db.ts b/src/shared/db/db.ts similarity index 100% rename from src/core/db/db.ts rename to src/shared/db/db.ts diff --git a/src/shared/types/db.ts b/src/shared/types/db.ts new file mode 100644 index 0000000..ff234e6 --- /dev/null +++ b/src/shared/types/db.ts @@ -0,0 +1,9 @@ +import { Prisma } from "@prisma/client"; + +export type Diary = Prisma.DiaryGetPayload<{ + include: { + category: true; + tags: true; + user: true; + }; +}>;