Skip to content
Merged

Api #35

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
47 changes: 36 additions & 11 deletions src/api/Home.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
import axios from "axios";

// 개발 환경에서는 프록시 사용, 배포 환경에서는 실제 API URL 사용
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL || "";

export interface ProductResponse {
id: number;
title: string;
Expand Down Expand Up @@ -32,18 +29,46 @@ export interface ReviewApiResponse {
}

export const fetchProducts = async (): Promise<ApiResponse> => {
const response = await axios.get(`${API_BASE_URL}/api/products`);
console.log("Fetched products:", response.data);
return response.data;
try {
const response = await axios.get("api/products");
console.log("Fetched products:", response.data);
return response.data;
} catch (error) {
console.error("Products API 에러:", error);
return {
success: false,
response: [],
error: "상품 데이터를 불러올 수 없습니다.",
};
}
};

export const fetchProductsReview1 = async (): Promise<ReviewApiResponse> => {
const response = await axios.get(`${API_BASE_URL}/api/products/1/reviews`);
console.log("Fetched reviews for product 1:", response.data);
return response.data;
try {
const response = await axios.get("api/projects/1/reviews");
console.log("Fetched reviews for product 1:", response.data);
return response.data;
} catch (error) {
console.error("Product 1 리뷰 API 에러:", error);
return {
success: false,
response: [],
error: "리뷰 데이터를 불러올 수 없습니다.",
};
}
};

export const fetchProductsReview2 = async (): Promise<ReviewApiResponse> => {
const response = await axios.get(`${API_BASE_URL}/api/products/2/reviews`);
return response.data;
try {
const response = await axios.get("api/projects/2/reviews");
console.log("Fetched reviews for product 2:", response.data);
return response.data;
} catch (error) {
console.error("Product 2 리뷰 API 에러:", error);
return {
success: false,
response: [],
error: "리뷰 데이터를 불러올 수 없습니다.",
};
}
};
101 changes: 96 additions & 5 deletions src/components/home/ExtraCropSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,102 @@
participants: number;
}


interface ProductType {
id: number;
title?: string;
price: string | number;
imageUrl?: string;
}

// 더미데이터 - API 데이터로 덮어쓰지 않을 기본값들
const defaultExtraCropData = [
{ emoji: "🥕", name: "유기농 당근", price: "15,000", participants: 12 },
{ emoji: "🥬", name: "친환경 배추", price: "25,000", participants: 8 },
{ emoji: "🥒", name: "무농약 오이", price: "18,000", participants: 20 },
{ emoji: "🌽", name: "찰옥수수", price: "22,000", participants: 15 },
{ emoji: "🥔", name: "햇감자", price: "16,000", participants: 18 },
{ emoji: "🍆", name: "가지", price: "14,000", participants: 9 },
];

interface ExtraCropSectionProps {
product4?: ProductType;
product5?: ProductType;
product6?: ProductType;
product7?: ProductType;
product8?: ProductType;
product9?: ProductType;
}

const ExtraCropSection = ({
product4,
product5,
product6,
product7,
product8,
product9,
}: ExtraCropSectionProps) => {
// API 데이터와 더미 데이터를 병합하는 함수
const mergeWithDefaults = (
apiProduct: ProductType | null | undefined,
defaultData: any
) => {
if (apiProduct) {
return {
...defaultData,
name: apiProduct.title || defaultData.name,
price:
typeof apiProduct.price === "number"
? apiProduct.price.toLocaleString()
: apiProduct.price,
};
}
return defaultData;
};

// API 데이터와 더미 데이터를 병합
const extraCrops = [
mergeWithDefaults(product4, defaultExtraCropData[0]),
mergeWithDefaults(product5, defaultExtraCropData[1]),
mergeWithDefaults(product6, defaultExtraCropData[2]),
mergeWithDefaults(product7, defaultExtraCropData[3]),
mergeWithDefaults(product8, defaultExtraCropData[4]),
mergeWithDefaults(product9, defaultExtraCropData[5]),
];

return (
<section className="w-full max-w-[1200px] px-6 py-12 flex flex-col gap-8">

Check failure on line 72 in src/components/home/ExtraCropSection.tsx

View workflow job for this annotation

GitHub Actions / build_and_preview

JSX element 'section' has no corresponding closing tag.
<h2 className="text-gray-900 text-2xl md:text-3xl font-bold text-center mb-4">
다른 농작물 둘러보기
</h2>
<div className="flex flex-wrap justify-center gap-6">

Check failure on line 76 in src/components/home/ExtraCropSection.tsx

View workflow job for this annotation

GitHub Actions / build_and_preview

JSX element 'div' has no corresponding closing tag.
{extraCrops.map((crop, idx) => (
<div

Check failure on line 78 in src/components/home/ExtraCropSection.tsx

View workflow job for this annotation

GitHub Actions / build_and_preview

JSX element 'div' has no corresponding closing tag.
key={idx}
className="w-full max-w-[320px] flex flex-col gap-4 bg-common-000 rounded-3xl shadow p-6 border border-opacity-200/20"
>
<div className="flex gap-1 items-center text-red-400 text-base font-semibold">
<span>🚩</span>
<span>{crop.participants}명 참여중!</span>
</div>
<div className="w-full h-40 flex items-center justify-center text-4xl bg-opacity-000/5 rounded-2xl">
{crop.emoji}
</div>
<div className="flex flex-col gap-2">

Check failure on line 89 in src/components/home/ExtraCropSection.tsx

View workflow job for this annotation

GitHub Actions / build_and_preview

JSX element 'div' has no corresponding closing tag.
<div className="text-gray-900 text-xl font-bold">{crop.name}</div>
<div className="flex gap-2 items-center text-mint-700 text-lg font-bold">
<span>{crop.price}원</span>
<span className="text-gray-600 text-sm font-normal">
박스당 1박스
</span>
</div>

const ExtraCropSection = ({ extraCrops }: { extraCrops: ExtraCrop[] }) => (

Check failure on line 98 in src/components/home/ExtraCropSection.tsx

View workflow job for this annotation

GitHub Actions / build_and_preview

Unexpected token. Did you mean `***'>'***` or `&gt;`?

Check failure on line 98 in src/components/home/ExtraCropSection.tsx

View workflow job for this annotation

GitHub Actions / build_and_preview

Unexpected token. Did you mean `***'***'***` or `&rbrace;`?

Check failure on line 98 in src/components/home/ExtraCropSection.tsx

View workflow job for this annotation

GitHub Actions / build_and_preview

'***' expected.
<section className="w-full max-w-[1200px] px-6 py-12 flex flex-col gap-8">
<h2 className="text-gray-900 text-2xl md:text-3xl font-bold text-center mb-4">
다른 농작물 둘러보기
</h2>
<div className="flex flex-wrap justify-center gap-6">

Check failure on line 103 in src/components/home/ExtraCropSection.tsx

View workflow job for this annotation

GitHub Actions / build_and_preview

JSX element 'div' has no corresponding closing tag.
{extraCrops.map((crop, idx) => (
<div
key={idx}
Expand All @@ -30,12 +120,13 @@
<span className="text-gray-600 text-sm font-normal">
박스당 1박스
</span>

</div>
</div>
</div>
))}
</div>
</section>
);
))}

Check failure on line 126 in src/components/home/ExtraCropSection.tsx

View workflow job for this annotation

GitHub Actions / build_and_preview

Unexpected token. Did you mean `***'***'***` or `&rbrace;`?
</div>
</section>

Check failure on line 128 in src/components/home/ExtraCropSection.tsx

View workflow job for this annotation

GitHub Actions / build_and_preview

')' expected.
);
};

export default ExtraCropSection;
31 changes: 31 additions & 0 deletions src/components/home/HeroSection.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,33 @@

import { useNavigate } from "react-router-dom";

const HeroSection = () => {
const navigate = useNavigate();

const handleStartClick = () => {
navigate("/login");
};

return (
<section className="w-full max-w-[1200px] px-6 pt-24 pb-16 flex flex-col items-center gap-8 bg-green-000 ">
<h1 className="text-mint-900 text-[40px] md:text-[48px] font-bold leading-tight text-center">
농부와 함께 키우는
<br />
믿음직한 농작물
</h1>
<p className="text-green-600 text-lg md:text-xl text-center">
농부의 신선한 농작물을 펀딩하고, 성장 과정을 지켜보세요 🌱
</p>
<button
onClick={handleStartClick}
className="mt-4 px-10 py-4 bg-mint-600 rounded-full shadow-md hover:bg-mint-700 transition text-common-000 text-lg font-semibold"
>
시작하기
</button>
</section>
);
};

const HeroSection = () => (
<section className="w-full max-w-[1200px] px-6 pt-24 pb-16 flex flex-col items-center gap-8 bg-green-000 ">
<h1 className="text-mint-900 text-[40px] md:text-[48px] font-bold leading-tight text-center">
Expand All @@ -14,4 +44,5 @@ const HeroSection = () => (
</section>
);


export default HeroSection;
2 changes: 1 addition & 1 deletion src/components/home/ReviewSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ const ReviewSection = ({ reviews: apiReviews }: ReviewSectionProps) => {
{reviews.map((review, idx) => (
<div
key={idx}
className="w-full max-w-[340px] flex flex-col gap-4 bg-common-000 rounded-2xl shadow p-6 border border-opacity-100/10"
className="w-full max-w-[380px] flex flex-col gap-4 bg-common-000 rounded-2xl shadow p-6 border border-opacity-100/10"
>
<div className="flex gap-2 items-center text-cool-gray-800 text-base">
{"⭐️".repeat(review.rating)}
Expand Down
53 changes: 31 additions & 22 deletions src/pages/Home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,6 @@ import {
import type { ProductResponse, ReviewResponse } from "../api/Home";


const dummyExtraCrops = [
{ emoji: "🥕", name: "유기농 당근", price: "15,000", participants: 12 },
{ emoji: "🥬", name: "친환경 배추", price: "25,000", participants: 8 },
{ emoji: "🥒", name: "무농약 오이", price: "18,000", participants: 20 },
];


const steps = [

{
Expand Down Expand Up @@ -78,6 +71,10 @@ export default function Home() {
>(null);


const [email, setEmail] = useState("");
const [showEmailAlert, setShowEmailAlert] = useState(false);


useEffect(() => {
const loadProducts = async () => {
try {
Expand Down Expand Up @@ -109,20 +106,27 @@ export default function Home() {

const loadReviews = async () => {
try {
// 여러 상품의 리뷰를 가져와서 병합
const [review1Response, review2Response] = await Promise.all([
fetchProductsReview1(),
fetchProductsReview2(),
]);

// 여러 상품의 리뷰를 가져와서 병합 - 각각 개별적으로 처리
const allReviews: ReviewResponse[] = [];

if (review1Response.success && review1Response.response) {
allReviews.push(...review1Response.response);
// 첫 번째 리뷰 API 호출
try {
const review1Response = await fetchProductsReview1();
if (review1Response.success && review1Response.response) {
allReviews.push(...review1Response.response);
}
} catch (error) {
console.warn("Product 1 리뷰 로딩 실패:", error);
}

if (review2Response.success && review2Response.response) {
allReviews.push(...review2Response.response);
// 두 번째 리뷰 API 호출
try {
const review2Response = await fetchProductsReview2();
if (review2Response.success && review2Response.response) {
allReviews.push(...review2Response.response);
}
} catch (error) {
console.warn("Product 2 리뷰 로딩 실패:", error);
}

// API 데이터를 ReviewSection 형태로 변환
Expand Down Expand Up @@ -151,10 +155,6 @@ export default function Home() {
loadProducts();
loadReviews();
}, []);
const [email, setEmail] = useState("");
const [showEmailAlert, setShowEmailAlert] = useState(false);

// 더미데이터만 사용, API 호출 및 product1~product9 상태 제거

const handleEmailSubmit = (e: React.FormEvent) => {
e.preventDefault();
Expand Down Expand Up @@ -198,7 +198,16 @@ export default function Home() {
/>
<StepsSection steps={steps} />
<ReviewSection reviews={reviews || undefined} />
<ExtraCropSection extraCrops={dummyExtraCrops} />

<ExtraCropSection
product4={product4}
product5={product5}
product6={product6}
product7={product7}
product8={product8}
product9={product9}
/>

<SubscribeSection
email={email}
setEmail={setEmail}
Expand Down