From 620f696592348891e2f87ccad1c1a3b8578c724c Mon Sep 17 00:00:00 2001 From: goorm Date: Sun, 3 Aug 2025 00:22:39 +0900 Subject: [PATCH 1/6] =?UTF-8?q?feat:=20=EC=9D=B4=EB=A9=94=EC=9D=BC=20?= =?UTF-8?q?=EA=B5=AC=EB=8F=85=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/Alert.tsx | 46 ---------------------- src/components/common/EmailAlert.tsx | 59 ++++++++++++++++++++++++++++ src/components/common/Nav.tsx | 38 ++++++++---------- src/pages/Home.tsx | 50 ++++++++++++++++++++++- 4 files changed, 124 insertions(+), 69 deletions(-) delete mode 100644 src/components/common/Alert.tsx create mode 100644 src/components/common/EmailAlert.tsx diff --git a/src/components/common/Alert.tsx b/src/components/common/Alert.tsx deleted file mode 100644 index 0f71fc6..0000000 --- a/src/components/common/Alert.tsx +++ /dev/null @@ -1,46 +0,0 @@ -export default function Alert() { - return ( -
-
-
-
- Label -
-
-
- 본문 텍스트 본문 텍스트 -
- 본문 텍스트 -
-
-
-
-
- btn1 -
-
-
-
- btn2 -
-
-
-
-
-
-
- ); -} diff --git a/src/components/common/EmailAlert.tsx b/src/components/common/EmailAlert.tsx new file mode 100644 index 0000000..2713af7 --- /dev/null +++ b/src/components/common/EmailAlert.tsx @@ -0,0 +1,59 @@ +interface EmailAlertProps { + email: string; + onConfirm: () => void; + onCancel: () => void; +} + +export default function EmailAlert({ + email, + onConfirm, + onCancel, +}: EmailAlertProps) { + return ( +
+
+
+
+ 이메일 알림 +
+
+
+ 농장 소식을 구독하시겠습니까? +
+ 새로운 농작물과 특별 혜택 소식을 가장 먼저 받아보세요! +
+
+
+ + +
+ +
+ ); +} diff --git a/src/components/common/Nav.tsx b/src/components/common/Nav.tsx index aef5039..c507f68 100644 --- a/src/components/common/Nav.tsx +++ b/src/components/common/Nav.tsx @@ -42,27 +42,6 @@ export default function Nav() { return ( <> -
{/* 좌측 여백/로고 */} @@ -112,6 +91,23 @@ export default function Nav() {
+ ); } diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 35915d7..2a4186c 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -1,4 +1,6 @@ import { useState } from "react"; +import toast, { Toaster } from "react-hot-toast"; +import EmailAlert from "../components/common/EmailAlert"; const cropCards = [ { @@ -111,17 +113,43 @@ const steps = [ export default function Home() { const [email, setEmail] = useState(""); + const [showEmailAlert, setShowEmailAlert] = useState(false); + const handleEmailSubmit = (e: React.FormEvent) => { e.preventDefault(); + if (email.trim()) { + setShowEmailAlert(true); + } + }; + + const handleConfirmSubscription = () => { + setShowEmailAlert(false); + toast.success("구독완료!", { + position: "bottom-center", + style: { + background: "#10b981", + color: "#fff", + fontSize: "16px", + fontWeight: "600", + padding: "12px 20px", + borderRadius: "9999px", + marginBottom: "100px", + }, + duration: 3000, + }); setEmail(""); console.log("이메일 제출:", email); }; + const handleCloseAlert = () => { + setShowEmailAlert(false); + }; + return ( -
+
{/* 히어로 섹션 */} -
+

농부와 함께 키우는
@@ -350,6 +378,7 @@ export default function Home() {

+ + {/* Email Alert 모달 */} + {showEmailAlert && ( +
+ +
+ )} + + {/* Toast 컨테이너 */} +
); } From 932c73c6f44e54ec73be1e52ab4f1f0433486ac1 Mon Sep 17 00:00:00 2001 From: goorm Date: Sun, 3 Aug 2025 00:25:17 +0900 Subject: [PATCH 2/6] =?UTF-8?q?fix:=20=EB=AF=B8=EC=82=AC=EC=9A=A9=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/EmailAlert.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/common/EmailAlert.tsx b/src/components/common/EmailAlert.tsx index 2713af7..110c3fa 100644 --- a/src/components/common/EmailAlert.tsx +++ b/src/components/common/EmailAlert.tsx @@ -27,6 +27,10 @@ export default function EmailAlert({
농장 소식을 구독하시겠습니까?
+ + 소식 받을 이메일 : {email} + +
새로운 농작물과 특별 혜택 소식을 가장 먼저 받아보세요!
From cd346afafbeef805aec5cad8cfc895e4aa0034e5 Mon Sep 17 00:00:00 2001 From: 6108 <> Date: Sun, 3 Aug 2025 00:40:22 +0900 Subject: [PATCH 3/6] =?UTF-8?q?style:=20=EC=98=88=EC=95=BD=20=ED=8E=98?= =?UTF-8?q?=EC=9D=B4=EC=A7=80=20=EC=8A=A4=ED=83=80=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.tsx | 3 + src/components/reserve/ReserveCard.tsx | 112 +++++++++++++++++++++++++ src/pages/Reserve.tsx | 20 +++++ 3 files changed, 135 insertions(+) create mode 100644 src/components/reserve/ReserveCard.tsx create mode 100644 src/pages/Reserve.tsx diff --git a/src/App.tsx b/src/App.tsx index c3a0983..21b4a35 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -19,6 +19,7 @@ import ScrollToTop from "./components/common/ScrollToTop"; import NotFound from "./pages/NotFound"; import ProjectList from "./pages/ProjectList"; +import Reserve from "./pages/Reserve"; function App() { const { isLoggedIn, _hasHydrated } = useAuthStore(); @@ -46,6 +47,8 @@ function App() { } /> } /> } /> + } /> + } /> {/* 404 페이지 처리 */} (null); + const [height, setHeight] = useState(0); + + useEffect(() => { + if (isOpen && contentRef.current) { + setHeight(contentRef.current.scrollHeight); + } else { + setHeight(0); + } + }, [isOpen, count]); // count도 포함하여 높이 재계산 + + const handleReserveClick = () => { + if (!isOpen) { + setIsOpen(true); // 펼침 + } else { + // 예약 실행 로직 (예시) + alert(`예약이 완료되었습니다. 예약 인원: ${count}명`); + } + }; + + const increment = () => { + setCount((prev) => prev + 1); + }; + + const decrement = () => { + setCount((prev) => (prev > 0 ? prev - 1 : 0)); + }; + + return ( +
+ {/* 상단 정보 */} +
+

재배 체험

+

00월 00일 0요일 00시 00분

+

주소지

+

00명 예약 가능

+
+ +
+
+

체험 일정 1

+

체험 일정 2

+

체험 일정 3

+
+ + {/* 애니메이션 영역 */} +
+
+
+ {/* 방문 예정 인원 */} +
+ 방문 예정 인원 + + {count}명 + +
+ + {/* 방문 주의 사항 */} +
+

방문 주의 사항

+

농장 방문 시 주의사항을 안내하는 문구 작성되는 영역

+ +
+
+
+ + {/* 예약하기 버튼 (항상 하나) */} +
+ {/* 닫기 버튼 */} + {isOpen && ( + + )} + +
+
+ ); +} diff --git a/src/pages/Reserve.tsx b/src/pages/Reserve.tsx new file mode 100644 index 0000000..5e71d84 --- /dev/null +++ b/src/pages/Reserve.tsx @@ -0,0 +1,20 @@ +import ReserveCard from "../components/reserve/ReserveCard"; + +export default function Reserve() { + return ( + <> +
+
홈 농작물목록 text
+
+

농장이름 농장 방문 예약

+

농장에서 안내하는 주의사항 문구 작성되는 영역 농장에서 안내하는 주의사항 문구 작성되는 영역

+
+
+ + + +
+
+ + ) +} From b290162da59bfd49363f8cc34062774ae22fc033 Mon Sep 17 00:00:00 2001 From: goorm Date: Sun, 3 Aug 2025 01:30:50 +0900 Subject: [PATCH 4/6] =?UTF-8?q?feat:=20api=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 259 ++++++++++++++++++++ package.json | 1 + services/authService.ts | 106 ++++++--- src/App.css | 3 + src/App.tsx | 19 ++ src/api/Home.ts | 6 + src/components/common/Nav.tsx | 20 +- src/components/home/CropSection.tsx | 88 +++++++ src/components/home/ExtraCropSection.tsx | 41 ++++ src/components/home/HeroSection.tsx | 17 ++ src/components/home/ReviewSection.tsx | 47 ++++ src/components/home/StatsSection.tsx | 28 +++ src/components/home/StepsSection.tsx | 35 +++ src/components/home/SubscribeSection.tsx | 42 ++++ src/pages/Home.tsx | 286 +++-------------------- src/pages/Login.tsx | 10 +- src/pages/Mypage.tsx | 2 - src/pages/Signup.tsx | 20 +- src/utils/httpClient.ts | 4 +- vite.config.ts | 2 +- 20 files changed, 708 insertions(+), 328 deletions(-) create mode 100644 src/api/Home.ts create mode 100644 src/components/home/CropSection.tsx create mode 100644 src/components/home/ExtraCropSection.tsx create mode 100644 src/components/home/HeroSection.tsx create mode 100644 src/components/home/ReviewSection.tsx create mode 100644 src/components/home/StatsSection.tsx create mode 100644 src/components/home/StepsSection.tsx create mode 100644 src/components/home/SubscribeSection.tsx diff --git a/package-lock.json b/package-lock.json index e62fc45..b68c5b0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "reactd", "version": "0.0.0", "dependencies": { + "axios": "^1.11.0", "react": "^19.1.0", "react-daum-postcode": "^3.2.0", "react-dom": "^19.1.0", @@ -2247,6 +2248,23 @@ "license": "Python-2.0", "peer": true }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.11.0.tgz", + "integrity": "sha512-1Lx3WLFQWm3ooKDYZD1eXmoGO9fxYQjrycfHFC8P0sCfQVXyROp0p9PFWBehewBOdCwHc+f/b8I0fMto5eSfwA==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/babel-plugin-macros": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz", @@ -2327,6 +2345,19 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2407,6 +2438,18 @@ "license": "MIT", "peer": true }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2503,6 +2546,15 @@ "license": "MIT", "peer": true }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/detect-libc": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", @@ -2523,6 +2575,20 @@ "csstype": "^3.0.2" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.178", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.178.tgz", @@ -2553,6 +2619,51 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { "version": "0.25.5", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.5.tgz", @@ -2939,6 +3050,42 @@ "license": "ISC", "peer": true }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fsevents": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", @@ -2973,6 +3120,43 @@ "node": ">=6.9.0" } }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -3009,6 +3193,18 @@ "csstype": "^3.0.10" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -3034,6 +3230,33 @@ "node": ">=8" } }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", @@ -3568,6 +3791,15 @@ "@jridgewell/sourcemap-codec": "^1.5.0" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/memoize-one": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", @@ -3598,6 +3830,27 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -3905,6 +4158,12 @@ "react-is": "^16.13.1" } }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", diff --git a/package.json b/package.json index 6dbe0e4..ad1eb15 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "preview": "vite preview" }, "dependencies": { + "axios": "^1.11.0", "react": "^19.1.0", "react-daum-postcode": "^3.2.0", "react-dom": "^19.1.0", diff --git a/services/authService.ts b/services/authService.ts index 9d04cf8..819bd56 100644 --- a/services/authService.ts +++ b/services/authService.ts @@ -2,8 +2,8 @@ interface SignupData { email: string; password: string; name: string; - phone: string; - userType: 'BUYER' | 'FARMER'; + phoneNumber: string; + userType: "BUYER" | "FARMER"; } interface LoginCredentials { @@ -15,6 +15,7 @@ interface User { id: string; email: string; name: string; + phoneNumber: string; } interface LoginResponse { @@ -34,78 +35,94 @@ interface ApiResponse { // 응답이 유효한 JSON인지 확인하는 헬퍼 함수 const parseJsonResponse = async (response: Response): Promise => { const responseText = await response.text(); - + // HTML 응답인지 확인 - if (responseText.trim().startsWith(' => { - const url = `${API_BASE_URL}/api/v1/users`; - console.log('회원가입 요청 URL:', url); // 디버깅용 - + const url = `${API_BASE_URL}/api/users`; + console.log("회원가입 요청 URL:", url); // 디버깅용 + try { const response = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json", - "Accept": "application/json", + Accept: "application/json", }, - credentials: 'include', // CORS 요청에 credentials 포함 + credentials: "include", // CORS 요청에 credentials 포함 body: JSON.stringify(userData), }); if (!response.ok) { const errorText = await response.text(); - console.error('회원가입 실패 응답:', errorText); + console.error("회원가입 실패 응답:", errorText); throw new Error(`회원가입 실패: ${response.status}`); } - return parseJsonResponse(response); + const result = await parseJsonResponse(response); + // phoneNumber 정보가 누락된 경우, userData에서 보완 + if ( + result && + result.user && + !result.user.phoneNumber && + userData.phoneNumber + ) { + result.user.phoneNumber = userData.phoneNumber; + } + return result; } catch (error) { - console.error('회원가입 처리 중 오류:', error); - + console.error("회원가입 처리 중 오류:", error); + // CORS 에러 특별 처리 - if (error instanceof TypeError && error.message.includes('Failed to fetch')) { - throw new Error('서버 연결에 실패했습니다. CORS 설정을 확인해주세요.'); + if ( + error instanceof TypeError && + error.message.includes("Failed to fetch") + ) { + throw new Error("서버 연결에 실패했습니다. CORS 설정을 확인해주세요."); } - + if (error instanceof Error) { throw error; } - throw new Error('회원가입 처리 중 알 수 없는 오류가 발생했습니다.'); + throw new Error("회원가입 처리 중 알 수 없는 오류가 발생했습니다."); } }, /* 로그인 */ login: async (credentials: LoginCredentials): Promise => { - const url = `${API_BASE_URL}/api/v1/auth/login`; - console.log('로그인 요청 URL:', url); // 디버깅용 - + const url = `${API_BASE_URL}/api/auth/login`; + console.log("로그인 요청 URL:", url); // 디버깅용 + try { const response = await fetch(url, { method: "POST", headers: { "Content-Type": "application/json", - "Accept": "application/json", + Accept: "application/json", }, credentials: "include", body: JSON.stringify(credentials), @@ -113,24 +130,39 @@ export const authService = { if (!response.ok) { const errorText = await response.text(); - console.error('로그인 실패 응답:', errorText); + console.error("로그인 실패 응답:", errorText); throw new Error(`로그인 실패: ${response.status}`); } - const data: ApiResponse = await parseJsonResponse(response); - return data.response; // { accessToken, user } + const data: ApiResponse = + await parseJsonResponse(response); + // phoneNumber 정보가 누락된 경우, credentials에서 보완 + if ( + data && + data.response && + data.response.user && + !data.response.user.phoneNumber && + credentials.email + ) { + // phoneNumber는 로그인 시 서버에서 반환하지 않을 수 있으므로, 필요시 추가 로직 구현 + // 여기서는 email만 보완, 실제 phoneNumber는 서버에서 반환해야 함 + } + return data.response; } catch (error) { - console.error('로그인 처리 중 오류:', error); - + console.error("로그인 처리 중 오류:", error); + // CORS 에러 특별 처리 - if (error instanceof TypeError && error.message.includes('Failed to fetch')) { - throw new Error('서버 연결에 실패했습니다. CORS 설정을 확인해주세요.'); + if ( + error instanceof TypeError && + error.message.includes("Failed to fetch") + ) { + throw new Error("서버 연결에 실패했습니다. CORS 설정을 확인해주세요."); } - + if (error instanceof Error) { throw error; } - throw new Error('로그인 처리 중 알 수 없는 오류가 발생했습니다.'); + throw new Error("로그인 처리 중 알 수 없는 오류가 발생했습니다."); } }, }; diff --git a/src/App.css b/src/App.css index e69de29..ad77f09 100644 --- a/src/App.css +++ b/src/App.css @@ -0,0 +1,3 @@ +html { + overflow-y: scroll; +} diff --git a/src/App.tsx b/src/App.tsx index c3a0983..a5dc085 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -18,6 +18,7 @@ import PaymentComplete from "./pages/PaymentComplete"; import ScrollToTop from "./components/common/ScrollToTop"; import NotFound from "./pages/NotFound"; +import { Toaster } from "react-hot-toast"; import ProjectList from "./pages/ProjectList"; function App() { @@ -33,6 +34,24 @@ function App() { return (
+
- + {/* Toaster는 App.tsx에서 전역으로 렌더링 */} ); } diff --git a/src/components/home/CropSection.tsx b/src/components/home/CropSection.tsx new file mode 100644 index 0000000..9bccfc4 --- /dev/null +++ b/src/components/home/CropSection.tsx @@ -0,0 +1,88 @@ +interface CropCard { + id: number; + emoji: string; + name: string; + farmer: string; + location: string; + experience: string; + rating: string; + reviews: number; + price: string; + weight: string; + participants: number; + totalBoxes: number; + completedBoxes: number; + percentage: number; + status: string; + deadline: string; + bgColor: string; +} + +const CropSection = ({ cropCards }: { cropCards: CropCard[] }) => ( +
+
+ + 이번 주 인기 농작물 + +

+ 지금 참여 가능한 위탁 농작물 +

+
+
+ {cropCards.map((crop) => ( +
+
+ ⏰ 마감까지 D-{crop.deadline?.replace(/[^0-9]/g, "") || "00"} +
+
+ {crop.emoji} +
+
+
{crop.name}
+
+ 📍 {crop.farmer} + {crop.location} + {crop.experience} +
+
+ ⭐️ {crop.rating} / 5 + ({crop.reviews}개 후기) +
+
+ {crop.price}원 + + 박스당 {crop.weight} + +
+
+
+
+ 달성률 + {crop.percentage}% +
+
+
+
+
+
+ + 총 {crop.totalBoxes}박스 중 {crop.completedBoxes}박스 위탁 완료! + +
+
+ +
+ ))} +
+
+); + +export default CropSection; diff --git a/src/components/home/ExtraCropSection.tsx b/src/components/home/ExtraCropSection.tsx new file mode 100644 index 0000000..0a83bde --- /dev/null +++ b/src/components/home/ExtraCropSection.tsx @@ -0,0 +1,41 @@ +interface ExtraCrop { + emoji: string; + name: string; + price: string; + participants: number; +} + +const ExtraCropSection = ({ extraCrops }: { extraCrops: ExtraCrop[] }) => ( +
+

+ 다른 농작물 둘러보기 +

+
+ {extraCrops.map((crop, idx) => ( +
+
+ 🚩 + {crop.participants}명 참여중! +
+
+ {crop.emoji} +
+
+
{crop.name}
+
+ {crop.price}원 + + 박스당 1박스 + +
+
+
+ ))} +
+
+); + +export default ExtraCropSection; diff --git a/src/components/home/HeroSection.tsx b/src/components/home/HeroSection.tsx new file mode 100644 index 0000000..05094f0 --- /dev/null +++ b/src/components/home/HeroSection.tsx @@ -0,0 +1,17 @@ +const HeroSection = () => ( +
+

+ 농부와 함께 키우는 +
+ 믿음직한 농작물 +

+

+ 농부의 신선한 농작물을 펀딩하고, 성장 과정을 지켜보세요 🌱 +

+ +
+); + +export default HeroSection; diff --git a/src/components/home/ReviewSection.tsx b/src/components/home/ReviewSection.tsx new file mode 100644 index 0000000..fe49d94 --- /dev/null +++ b/src/components/home/ReviewSection.tsx @@ -0,0 +1,47 @@ +interface Review { + rating: number; + title: string; + content: string; + name: string; + location: string; + date: string; +} + +const ReviewSection = ({ reviews }: { reviews: Review[] }) => ( +
+

+ 생생한 고객 후기 +

+
+ {reviews.map((review, idx) => ( +
+
+ {"⭐️".repeat(review.rating)} +
+
+ " + {review.title} + " +
+
{review.content}
+
+
+
+ + {review.name} + + + {review.date.replace(/\./g, "년 ").replace(/\.$/, "월")} 참가자 + +
+
+
+ ))} +
+
+); + +export default ReviewSection; diff --git a/src/components/home/StatsSection.tsx b/src/components/home/StatsSection.tsx new file mode 100644 index 0000000..ece4cde --- /dev/null +++ b/src/components/home/StatsSection.tsx @@ -0,0 +1,28 @@ +const StatsSection = () => ( +
+
+ 참여 농부 + + 1,250+ + +
+
+ 위탁 완료 + + 5,680+ + +
+
+ 만족도 + 98% +
+
+ 평균 경력 + + 24개월 + +
+
+); + +export default StatsSection; diff --git a/src/components/home/StepsSection.tsx b/src/components/home/StepsSection.tsx new file mode 100644 index 0000000..8a6240b --- /dev/null +++ b/src/components/home/StepsSection.tsx @@ -0,0 +1,35 @@ +interface Step { + number: number; + title: string; + description: string[]; +} + +const StepsSection = ({ steps }: { steps: Step[] }) => ( +
+

+ 가상농장 이용 방법 +

+
+ {steps.map((step) => ( +
+
+ {step.number} +
+
+ {step.title} +
+
+ {step.description.map((line, idx) => ( + {line} + ))} +
+
+ ))} +
+
+); + +export default StepsSection; diff --git a/src/components/home/SubscribeSection.tsx b/src/components/home/SubscribeSection.tsx new file mode 100644 index 0000000..30f8ae2 --- /dev/null +++ b/src/components/home/SubscribeSection.tsx @@ -0,0 +1,42 @@ +interface SubscribeSectionProps { + email: string; + setEmail: (email: string) => void; + handleEmailSubmit: (e: React.FormEvent) => void; +} + +const SubscribeSection = ({ + email, + setEmail, + handleEmailSubmit, +}: SubscribeSectionProps) => ( +
+

+ 농장 소식 미리받기 +

+

+ 새로운 농작물과 특별 혜택 소식을 가장 먼저 받아보세요! +

+ + setEmail(e.target.value)} + placeholder="이메일 주소를 입력하세요" + className="flex-1 px-6 py-3 bg-common-000 rounded-full outline-1 outline-offset-[-1px] outline-green-400 text-base" + required + /> + + +
+); + +export default SubscribeSection; diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index 2a4186c..b8c6e47 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -1,6 +1,14 @@ -import { useState } from "react"; -import toast, { Toaster } from "react-hot-toast"; +import toast from "react-hot-toast"; +import { useState, useEffect } from "react"; +import { fetchProducts } from "../api/Home"; import EmailAlert from "../components/common/EmailAlert"; +import HeroSection from "../components/home/HeroSection"; +import StatsSection from "../components/home/StatsSection"; +import CropSection from "../components/home/CropSection"; +import StepsSection from "../components/home/StepsSection"; +import ReviewSection from "../components/home/ReviewSection"; +import ExtraCropSection from "../components/home/ExtraCropSection"; +import SubscribeSection from "../components/home/SubscribeSection"; const cropCards = [ { @@ -115,6 +123,18 @@ export default function Home() { const [email, setEmail] = useState(""); const [showEmailAlert, setShowEmailAlert] = useState(false); + useEffect(() => { + const getProducts = async () => { + try { + const data = await fetchProducts(); + console.log("products:", data); + } catch (error) { + console.error("Failed to fetch products:", error); + } + }; + getProducts(); + }, []); + const handleEmailSubmit = (e: React.FormEvent) => { e.preventDefault(); if (email.trim()) { @@ -148,254 +168,17 @@ export default function Home() { return (
- {/* 히어로 섹션 */} -
-

- 농부와 함께 키우는 -
- 믿음직한 농작물 -

-

- 농부의 신선한 농작물을 펀딩하고, 성장 과정을 지켜보세요 🌱 -

- -
- - {/* 통계 섹션 */} -
-
- - 참여 농부 - - - 1,250+ - -
-
- - 위탁 완료 - - - 5,680+ - -
-
- 만족도 - - 98% - -
-
- - 평균 경력 - - - 24개월 - -
-
- - {/* 인기 농작물 섹션 */} -
-
- - 이번 주 인기 농작물 - -

- 지금 참여 가능한 위탁 농작물 -

-
-
- {cropCards.map((crop) => ( -
-
- ⏰ 마감까지 D- - {crop.deadline?.replace(/[^0-9]/g, "") || "00"} -
-
- {crop.emoji} -
-
-
- {crop.name} -
-
- 📍 {crop.farmer} - {crop.location} - {crop.experience} -
-
- ⭐️ {crop.rating} / 5 - ({crop.reviews}개 후기) -
-
- {crop.price}원 - - 박스당 {crop.weight} - -
-
-
-
- 달성률 - {crop.percentage}% -
-
-
-
-
-
- - 총 {crop.totalBoxes}박스 중 {crop.completedBoxes}박스 위탁 - 완료! - -
-
- -
- ))} -
-
- - {/* 이용 방법 섹션 */} -
-

- 가상농장 이용 방법 -

-
- {steps.map((step) => ( -
-
- {step.number} -
-
- {step.title} -
-
- {step.description.map((line, idx) => ( - {line} - ))} -
-
- ))} -
-
- - {/* 후기 섹션 */} -
-

- 생생한 고객 후기 -

-
- {reviews.map((review, idx) => ( -
-
- {"⭐️".repeat(review.rating)} -
-
- " - {review.title} - " -
-
- {review.content} -
-
-
-
- - {review.name} - - - {review.date.replace(/\./g, "년 ").replace(/\.$/, "월")}{" "} - 참가자 - -
-
-
- ))} -
-
- - {/* 다른 농작물 섹션 */} -
-

- 다른 농작물 둘러보기 -

-
- {extraCrops.map((crop, idx) => ( -
-
- 🚩 - {crop.participants}명 참여중! -
-
- {crop.emoji} -
-
-
- {crop.name} -
-
- {crop.price}원 - - 박스당 1박스 - -
-
-
- ))} -
-
- - {/* 구독 섹션 */} -
-

- 농장 소식 미리받기 -

-

- 새로운 농작물과 특별 혜택 소식을 가장 먼저 받아보세요! -

-
- setEmail(e.target.value)} - placeholder="이메일 주소를 입력하세요" - className="flex-1 px-6 py-3 bg-common-000 rounded-full outline-1 outline-offset-[-1px] outline-green-400 text-base" - required - /> - -
-
+ + + + + + +
{/* Email Alert 모달 */} @@ -412,8 +195,7 @@ export default function Home() {
)} - {/* Toast 컨테이너 */} - + {/* Toaster는 App.tsx에서 전역으로 렌더링 */} ); } diff --git a/src/pages/Login.tsx b/src/pages/Login.tsx index cb77af8..9c29a5a 100644 --- a/src/pages/Login.tsx +++ b/src/pages/Login.tsx @@ -139,7 +139,7 @@ export default function Login() { onSubmit={handleSubmit} > {/* Email Field */} -
+
이메일 @@ -154,13 +154,13 @@ export default function Login() { value={email} onChange={(e) => setEmail(e.target.value)} /> - {emailMessage && ( -
+
+ {emailMessage && (
{emailMessage}
-
- )} + )} +
{/* Password Field */} diff --git a/src/pages/Mypage.tsx b/src/pages/Mypage.tsx index 4fe6e1e..6cb0cfb 100644 --- a/src/pages/Mypage.tsx +++ b/src/pages/Mypage.tsx @@ -1,6 +1,5 @@ import { useNavigate } from "react-router-dom"; import { useAuthStore } from "../store/authStore"; -import toast from "react-hot-toast"; export default function Mypage() { const { removeToken } = useAuthStore(); @@ -8,7 +7,6 @@ export default function Mypage() { const handleLogout = () => { removeToken(); - toast.success("로그아웃되었습니다."); navigate("/"); }; diff --git a/src/pages/Signup.tsx b/src/pages/Signup.tsx index e990e3e..337e5b6 100644 --- a/src/pages/Signup.tsx +++ b/src/pages/Signup.tsx @@ -59,21 +59,19 @@ export default function Signup() { setSignupError(""); try { - console.log("회원가입 시도:", { + const signupPayload = { email, password, name, - phone: fullPhone, - userType: selectedKey.toUpperCase(), - }); - const signupResponse = await authService.signup({ - email, - password, - name, - phone: fullPhone, + phoneNumber: fullPhone, userType: selectedKey.toUpperCase() as "BUYER" | "FARMER", - }); - console.log("회원가입 성공:", signupResponse); + }; + console.log("회원가입 시도 payload:", signupPayload); + const signupResponse = await authService.signup(signupPayload); + console.log("회원가입 성공 response:", signupResponse); + if (signupResponse && signupResponse.user) { + console.log("가입된 유저 정보:", signupResponse.user); + } navigate("/login"); } catch (error) { console.error("회원가입 실패:", error); diff --git a/src/utils/httpClient.ts b/src/utils/httpClient.ts index d968ba5..85fa958 100644 --- a/src/utils/httpClient.ts +++ b/src/utils/httpClient.ts @@ -1,5 +1,5 @@ async function refreshAccessToken(): Promise { - const response = await fetch("/api/v1/auth/refresh", { + const response = await fetch("/api/auth/refresh", { method: "POST", credentials: "include", }); @@ -44,4 +44,4 @@ export function createAuthenticatedFetch( return response; }; -} \ No newline at end of file +} diff --git a/vite.config.ts b/vite.config.ts index 519807a..79b7a48 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -7,7 +7,7 @@ export default defineConfig({ plugins: [react(), tailwindcss()], server: { proxy: { - "/api/v1": { + "/api": { target: "https://zerojae175-dev.store", changeOrigin: true, secure: true, From ce9d379fc264dd632d6608e89774c4fc645d8618 Mon Sep 17 00:00:00 2001 From: goorm Date: Sun, 3 Aug 2025 01:34:44 +0900 Subject: [PATCH 5/6] =?UTF-8?q?fix:=20=EB=B2=84=EA=B7=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/pages/Home.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/pages/Home.tsx b/src/pages/Home.tsx index b8c6e47..618ed8a 100644 --- a/src/pages/Home.tsx +++ b/src/pages/Home.tsx @@ -46,6 +46,7 @@ const cropCards = [ completedBoxes: 90, percentage: 45, status: "유기농 인증", + deadline: "3일 남음", bgColor: "from-amber-400 to-yellow-300", }, { @@ -64,6 +65,7 @@ const cropCards = [ completedBoxes: 120, percentage: 80, status: "베스트셀러", + deadline: "1일 남음", bgColor: "from-green-300 to-green-300", }, ]; From 5d7e580ba95b0479209e733e5e31d17430292e72 Mon Sep 17 00:00:00 2001 From: goorm Date: Sun, 3 Aug 2025 01:44:28 +0900 Subject: [PATCH 6/6] trigger deployment