-
-
-
-
- 댓글
-
-
-
아직 댓글이 없습니다
-
댓글을 남겨보세요!
-
+
{
+ if (!isPage) router.back();
+ }}
+ >
+
e.stopPropagation()}
+ >
+
+
+
+
+ 댓글
+
+
+
+
diff --git a/src/constants/missionCard.ts b/src/constants/missionCard.ts
new file mode 100644
index 0000000..408c256
--- /dev/null
+++ b/src/constants/missionCard.ts
@@ -0,0 +1,28 @@
+export const MISSION_STATUS = {
+ NOT_STARTED: {
+ title: "하루한컷 미션 도착!",
+ subtitle: (localday: string) => localday,
+ buttonText: "시작하기",
+ },
+ IMAGE_UPLOADED: {
+ title: "하루한컷 미션을 공유할까요?",
+ subtitle: "확인을 누르면 미션이 완료돼요",
+ buttonText: "공유하기",
+ },
+ IMAGE_CONFIRMED: {
+ title: "미션 업로드 성공",
+ subtitle: "소중한 오늘의 한컷을 기록했어요",
+ buttonText: "보러가기",
+ },
+} as const;
+
+export const MISSION_SORT = [
+ { sort: "food", icon: "/images/missionIcon/food.svg" },
+ {
+ sort: "plant",
+ icon: "/images/missionIcon/plant.svg",
+ },
+ { sort: "color", icon: "/images/missionIcon/color.svg" },
+ { sort: "time", icon: "/images/missionIcon/time.svg" },
+ { sort: "tv", icon: "/images/missionIcon/tv.svg" },
+];
diff --git a/src/constants/navBarItems.ts b/src/constants/navBarItems.ts
index fe8bc36..aa8a099 100644
--- a/src/constants/navBarItems.ts
+++ b/src/constants/navBarItems.ts
@@ -22,7 +22,7 @@ export const NAV_ITEMS = [
{
key: "record",
label: "하루기록",
- href: "/record",
+ href: "/day-log",
icons: {
blank: RecordBlankIcon,
fill: RecordFillIcon,
diff --git a/src/constants/weekDays.ts b/src/constants/weekDays.ts
new file mode 100644
index 0000000..8fb05b7
--- /dev/null
+++ b/src/constants/weekDays.ts
@@ -0,0 +1,19 @@
+export const WEEK_DAYS_KOR = [
+ "월",
+ "화",
+ "수",
+ "목",
+ "금",
+ "토",
+ "일",
+] as const;
+
+export const WEEK_DAY_KEYS = [
+ "monday",
+ "tuesday",
+ "wednesday",
+ "thursday",
+ "friday",
+ "saturday",
+ "sunday",
+] as const;
diff --git a/src/hooks/useImageUpload.ts b/src/hooks/useImageUpload.ts
new file mode 100644
index 0000000..54d56d1
--- /dev/null
+++ b/src/hooks/useImageUpload.ts
@@ -0,0 +1,42 @@
+import { useRef } from "react";
+
+import { UploadImageResult, uploadImage } from "@/utils/uploadImage.util";
+
+interface UseImageUploadProps {
+ onSelect: (result: UploadImageResult) => void;
+ maxSizeMB?: number;
+}
+
+export const useImageUpload = ({
+ onSelect,
+ maxSizeMB,
+}: UseImageUploadProps) => {
+ const inputRef = useRef
(null);
+
+ const openFilePicker = () => {
+ inputRef.current?.click();
+ };
+
+ const onChangeFile = (e: React.ChangeEvent) => {
+ const file = e.target.files?.[0];
+ if (!file) return;
+
+ try {
+ const result = uploadImage(file, { maxSizeMB });
+ onSelect(result);
+ } catch (error) {
+ if (error instanceof Error) {
+ alert(error.message);
+ }
+ }
+
+ // 같은 파일 재선택 가능하게
+ e.target.value = "";
+ };
+
+ return {
+ inputRef,
+ openFilePicker,
+ onChangeFile,
+ };
+};
diff --git a/src/mock/dailyRecordProgress.json b/src/mock/dailyRecordProgress.json
new file mode 100644
index 0000000..26b76ba
--- /dev/null
+++ b/src/mock/dailyRecordProgress.json
@@ -0,0 +1,9 @@
+{
+ "monday": true,
+ "tuesday": false,
+ "wednesday": true,
+ "thursday": false,
+ "friday": false,
+ "saturday": true,
+ "sunday": false
+}
diff --git a/src/mock/diaryComment.json b/src/mock/diaryComment.json
new file mode 100644
index 0000000..aa88400
--- /dev/null
+++ b/src/mock/diaryComment.json
@@ -0,0 +1,23 @@
+[
+ {
+ "id": 1,
+ "userName": "박혜미",
+ "profileImg": "/images/1.jpg",
+ "comment": "코코넛 커피도 마셔주세요",
+ "createdAt": "2025-12-30T09:12:00"
+ },
+ {
+ "id": 2,
+ "userName": "신수진",
+ "profileImg": "/images/2.jpg",
+ "comment": "코코넛 커피도 마셔주세요2",
+ "createdAt": "2026-01-01T09:12:00"
+ },
+ {
+ "id": 3,
+ "userName": "박영신",
+ "profileImg": "/images/3.jpg",
+ "comment": "코코넛 커피도 마셔주세요3",
+ "createdAt": "2026-01-03T09:12:00"
+ }
+]
diff --git a/src/mock/randomMission.json b/src/mock/randomMission.json
new file mode 100644
index 0000000..b5fa404
--- /dev/null
+++ b/src/mock/randomMission.json
@@ -0,0 +1,13 @@
+[
+ { "kind": "food", "mission": "오늘의 점심식사를 공유해주세요" },
+ { "kind": "plant", "mission": "오늘의 꽃을 공유해주세요" },
+ {
+ "kind": "color",
+ "mission": "오늘의 빨간색을 공유해주세요"
+ },
+ {
+ "kind": "time",
+ "mission": "오늘 행복한 순간을 공유해주세요"
+ },
+ { "kind": "tv", "mission": "오늘의 드라마를 공유해주세요" }
+]
diff --git a/src/styles/globals.css b/src/styles/globals.css
index c9b4367..962eed8 100644
--- a/src/styles/globals.css
+++ b/src/styles/globals.css
@@ -41,7 +41,7 @@
--color-neutral-11: #f9f9fb;
--color-white: #fefefe;
- --color-dim-02: #242628D4;
+ --color-dim-02: #242628d4;
}
@layer utilities {
@@ -68,50 +68,27 @@
.bg-dim02-gradient {
background: linear-gradient(
- 0deg,
- rgba(36, 38, 40, 0.83) 0%,
- rgba(36, 38, 40, 0.83) 100%
- )
+ 0deg,
+ rgba(36, 38, 40, 0.83) 0%,
+ rgba(36, 38, 40, 0.83) 100%
+ );
}
- .bg-onboarding-circle-mint {
- border-radius: 146px;
+ .bg-onboarding-circle-mint {
background: linear-gradient(
180deg,
- rgba(79, 255, 144, 0.10) 0%,
- rgba(255, 255, 255, 0.10) 100%
+ rgba(79, 255, 144, 0.1) 0%,
+ rgba(255, 255, 255, 0.1) 100%
);
}
.bg-onboarding-circle-gray {
- border-radius: 146px;
background: linear-gradient(
160deg,
- rgba(255, 255, 255, 0.10) 42.99%,
- rgba(103, 103, 103, 0.10) 80.56%
+ rgba(255, 255, 255, 0.1) 42.99%,
+ rgba(103, 103, 103, 0.1) 80.56%
);
}
-}
-
-@layer utilities {
- .scrollbar-hide {
- -ms-overflow-style: none; /* IE and Edge */
- scrollbar-width: none; /* Firefox */
- }
- .scrollbar-hide::-webkit-scrollbar {
- display: none; /* Chrome, Safari, Opera*/
- }
- .bg-radial-yellowgreen-mintgreen {
- background:
- radial-gradient(
- 39.1% 31.12% at 80.25% 2.51%,
- rgba(246, 248, 173, 0.42) 57.26%,
- rgba(255, 255, 255, 0.42) 100%
- ),
- radial-gradient(
- 39.99% 49.21% at 23.64% 2.72%,
- rgba(66, 240, 158, 0.66) 21.15%,
- rgba(255, 255, 255, 0.87) 93.53%
- );
+ .bg-gradient-orange {
+ background: linear-gradient(180deg, #f9cb4c 0%, #fdbb05 100%);
}
}
-
diff --git a/src/types/diaryComment.type.ts b/src/types/diaryComment.type.ts
new file mode 100644
index 0000000..e877568
--- /dev/null
+++ b/src/types/diaryComment.type.ts
@@ -0,0 +1,7 @@
+export type DiaryComment = {
+ id: number;
+ userName: string;
+ profileImg: string;
+ comment: string;
+ createdAt: string;
+};
diff --git a/src/utils/formatDate.ts b/src/utils/formatDate.ts
new file mode 100644
index 0000000..56c71dc
--- /dev/null
+++ b/src/utils/formatDate.ts
@@ -0,0 +1,13 @@
+export const formatDate = (createdAt: string): string => {
+ const date = new Date(createdAt);
+
+ if (isNaN(date.getTime())) {
+ throw new Error("Invalid date string");
+ }
+
+ const year = date.getFullYear().toString().slice(2);
+ const month = String(date.getMonth() + 1).padStart(2, "0");
+ const day = String(date.getDate()).padStart(2, "0");
+
+ return `${year}년 ${month}월${day}일`;
+};
diff --git a/src/utils/getCurrentDay.ts b/src/utils/getCurrentDay.ts
new file mode 100644
index 0000000..f03fe2d
--- /dev/null
+++ b/src/utils/getCurrentDay.ts
@@ -0,0 +1,18 @@
+// 월:0 ~ 일:6
+export const getMondayStartIndex = (day: number) => (day === 0 ? 6 : day - 1);
+
+// 오늘 요일 인덱스
+export const getTodayIndex = () => {
+ const today = new Date().getDay();
+ return getMondayStartIndex(today);
+};
+
+type DayStatus = "past" | "today" | "future";
+
+export const getWeekDayStatus = (targetIndex: number): DayStatus => {
+ const todayIndex = getTodayIndex();
+
+ if (targetIndex < todayIndex) return "past";
+ if (targetIndex > todayIndex) return "future";
+ return "today";
+};
diff --git a/src/utils/getMissionSubtitle.ts b/src/utils/getMissionSubtitle.ts
new file mode 100644
index 0000000..011c84e
--- /dev/null
+++ b/src/utils/getMissionSubtitle.ts
@@ -0,0 +1,15 @@
+import { MISSION_STATUS } from "@/constants/missionCard";
+import { WEEK_DAYS_KOR } from "@/constants/weekDays";
+
+export const getMissionSubtitle = (
+ status: keyof typeof MISSION_STATUS,
+ todayIndex: number,
+) => {
+ const currentDay = WEEK_DAYS_KOR[todayIndex];
+ const rawSubtitle = MISSION_STATUS[status].subtitle;
+ const text =
+ typeof rawSubtitle === "function"
+ ? `${rawSubtitle(currentDay)}요일`
+ : rawSubtitle;
+ return text;
+};
diff --git a/src/utils/uploadImage.util.ts b/src/utils/uploadImage.util.ts
new file mode 100644
index 0000000..705b486
--- /dev/null
+++ b/src/utils/uploadImage.util.ts
@@ -0,0 +1,35 @@
+export interface UploadImageOptions {
+ maxSizeMB?: number;
+ allowedTypes?: string[];
+}
+
+export interface UploadImageResult {
+ file: File;
+ previewUrl: string;
+}
+
+export const uploadImage = (
+ file: File,
+ options: UploadImageOptions = {},
+): UploadImageResult => {
+ const {
+ maxSizeMB = 5,
+ allowedTypes = ["image/jpeg", "image/png", "image/webp"],
+ } = options;
+
+ // 타입 검사
+ if (!allowedTypes.includes(file.type)) {
+ throw new Error("지원하지 않는 이미지 형식입니다.");
+ }
+
+ // 용량 검사
+ const maxSize = maxSizeMB * 1024 * 1024;
+ if (file.size > maxSize) {
+ throw new Error(`이미지 용량은 ${maxSizeMB}MB 이하만 가능합니다.`);
+ }
+
+ return {
+ file,
+ previewUrl: URL.createObjectURL(file),
+ };
+};
diff --git a/yarn.lock b/yarn.lock
index 4a6fba9..552b8aa 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -847,7 +847,7 @@
"@babel/plugin-transform-modules-commonjs" "^7.27.1"
"@babel/plugin-transform-typescript" "^7.28.5"
-"@babel/runtime@^7.14.8", "@babel/runtime@^7.19.0", "@babel/runtime@^7.21.0", "@babel/runtime@^7.23.8", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.2":
+"@babel/runtime@^7.23.8", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.2", "@babel/runtime@^7.7.2":
version "7.28.4"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.28.4.tgz#a70226016fabe25c5783b2f22d3e1c9bc5ca3326"
integrity sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==
@@ -1507,63 +1507,6 @@
postcss "^8.4.41"
tailwindcss "4.1.18"
-"@toss/assert@^1.2.2":
- version "1.2.2"
- resolved "https://registry.yarnpkg.com/@toss/assert/-/assert-1.2.2.tgz#78a96586e134edd38947553b20a18bc24a692c52"
- integrity sha512-qK2G1LzI2ghY0aUOsz9mFiy2v/eNlMHG5qXdSogfGFLxHqFZ6KQWJQpnb9eN+dyHYIudBVWPZhbkljqnT8R3/g==
- dependencies:
- "@toss/utils" "^1.6.1"
-
-"@toss/react@^1.8.1":
- version "1.8.1"
- resolved "https://registry.yarnpkg.com/@toss/react/-/react-1.8.1.tgz#d69d9e5dfed21c8f9fa791473293239e31ac5dda"
- integrity sha512-jH3oo/7yctexuutj/YgQrddaK1bU2s5659dkJIXOe23bEjkY+lbhvEz2FLEhRjSo6k6ktPagpxO4AcdhCi5k5A==
- dependencies:
- "@babel/runtime" "^7.14.8"
- "@toss/storage" "^1.4.1"
- "@toss/utils" "^1.6.1"
- classnames "^2.3.1"
- lodash.debounce "^4.0.8"
- lodash.throttle "^4.1.1"
-
-"@toss/storage@^1.4.1":
- version "1.4.1"
- resolved "https://registry.yarnpkg.com/@toss/storage/-/storage-1.4.1.tgz#c7f03946244a35631dbd4f40a7c513c314750f86"
- integrity sha512-jvnBXAQ/Fqqdt+gqYKeHYk7SzR2LX/FC50JIoVI0RldG1mDh1tebOqD7XkrZ89q77t/RwTuh60+8fjZDgend/g==
- dependencies:
- "@babel/runtime" "^7.19.0"
-
-"@toss/use-funnel@^1.4.2":
- version "1.4.2"
- resolved "https://registry.yarnpkg.com/@toss/use-funnel/-/use-funnel-1.4.2.tgz#08e7d681e4f200fb83159100c037fb195dd43e8e"
- integrity sha512-qgfYhdoJh07D4+kyRgRE3Du5wEdecFOR9Ht3MjgFMYAJyGplNbD++hpD/FcXrGT5oa14KG0bB5bpp60KLmPkFw==
- dependencies:
- "@toss/assert" "^1.2.2"
- "@toss/react" "^1.8.1"
- "@toss/storage" "^1.4.1"
- "@toss/use-query-param" "^1.3.1"
- "@toss/utils" "^1.6.1"
- fast-deep-equal "^3.1.3"
-
-"@toss/use-query-param@^1.3.1":
- version "1.3.1"
- resolved "https://registry.yarnpkg.com/@toss/use-query-param/-/use-query-param-1.3.1.tgz#a9bc67692972f300b8d011e00f0af17f6fa3d7d0"
- integrity sha512-GRA+6st46/88KgmP9PGx8mV9sxxkewwLMzBl25TpXPjnqHz+tiZdybVZMQ5UHj0Kzf7bZnHGF3kd5oko492vxA==
-
-"@toss/utility-types@^1.2.1":
- version "1.2.1"
- resolved "https://registry.yarnpkg.com/@toss/utility-types/-/utility-types-1.2.1.tgz#3c67bea23b7aaffd4b4e7c2a518773ae22a496f6"
- integrity sha512-1y8s1bvmuhuMX/d6qR9mmvcgFZIKYIQqJbAIshlGArXkjk/ec67gXc5uByEV1Y7in9ZhrGNRmjD8DTH0988vpQ==
-
-"@toss/utils@^1.6.1":
- version "1.6.1"
- resolved "https://registry.yarnpkg.com/@toss/utils/-/utils-1.6.1.tgz#e1226b1274b3d7e04b5b648ef11accdc013568c3"
- integrity sha512-x6m8jLKWtAmCbxTLXbgTzJ5wZyRSUQPLpR/oLJP1ZK9ytXcRf03oA46W/+78kErUkEw/yQz2L+t2xFDHSeZ6IQ==
- dependencies:
- "@babel/runtime" "^7.14.8"
- "@toss/utility-types" "^1.2.1"
- date-fns "^2.25.0"
-
"@trivago/prettier-plugin-sort-imports@^6.0.0":
version "6.0.1"
resolved "https://registry.yarnpkg.com/@trivago/prettier-plugin-sort-imports/-/prettier-plugin-sort-imports-6.0.1.tgz#b71c020c4069c0b7a75953227ad6c803cea3f568"
@@ -2345,11 +2288,6 @@ chrome-trace-event@^1.0.2:
resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz#05bffd7ff928465093314708c93bdfa9bd1f0f5b"
integrity sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==
-classnames@^2.3.1:
- version "2.5.1"
- resolved "https://registry.yarnpkg.com/classnames/-/classnames-2.5.1.tgz#ba774c614be0f016da105c858e7159eae8e7687b"
- integrity sha512-saHYOzhIQs6wy2sVxTM6bUDsQO4F50V9RQ22qBpEdCW+I+/Wmke2HOl6lS6dTpdxVhb88/I6+Hs+438c3lfUow==
-
cli-cursor@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-5.0.0.tgz#24a4831ecf5a6b01ddeb32fb71a4b2088b0dce38"
@@ -2521,13 +2459,6 @@ data-view-byte-offset@^1.0.1:
es-errors "^1.3.0"
is-data-view "^1.0.1"
-date-fns@^2.25.0:
- version "2.30.0"
- resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.30.0.tgz#f367e644839ff57894ec6ac480de40cae4b0f4d0"
- integrity sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==
- dependencies:
- "@babel/runtime" "^7.21.0"
-
debug@^3.2.7:
version "3.2.7"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.7.tgz#72580b7e9145fb39b6676f9c5e5fb100b934179a"
@@ -3875,11 +3806,6 @@ lodash.merge@^4.6.2:
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
-lodash.throttle@^4.1.1:
- version "4.1.1"
- resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4"
- integrity sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==
-
log-update@^6.1.0:
version "6.1.0"
resolved "https://registry.yarnpkg.com/log-update/-/log-update-6.1.0.tgz#1a04ff38166f94647ae1af562f4bd6a15b1b7cd4"