Skip to content
Merged
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
1,035 changes: 650 additions & 385 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
106 changes: 69 additions & 37 deletions services/authService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ interface SignupData {
email: string;
password: string;
name: string;
phone: string;
userType: 'BUYER' | 'FARMER';
phoneNumber: string;
userType: "BUYER" | "FARMER";
}

interface LoginCredentials {
Expand All @@ -15,6 +15,7 @@ interface User {
id: string;
email: string;
name: string;
phoneNumber: string;
}

interface LoginResponse {
Expand All @@ -34,103 +35,134 @@ interface ApiResponse<T> {
// 응답이 유효한 JSON인지 확인하는 헬퍼 함수
const parseJsonResponse = async (response: Response): Promise<any> => {
const responseText = await response.text();

// HTML 응답인지 확인
if (responseText.trim().startsWith('<!')) {
throw new Error('서버에서 HTML 응답을 받았습니다. API 엔드포인트를 확인해주세요.');
if (responseText.trim().startsWith("<!")) {
throw new Error(
"서버에서 HTML 응답을 받았습니다. API 엔드포인트를 확인해주세요."
);
}

// 빈 응답인지 확인
if (!responseText.trim()) {
throw new Error('서버에서 빈 응답을 받았습니다.');
throw new Error("서버에서 빈 응답을 받았습니다.");
}

try {
return JSON.parse(responseText);
} catch (error) {
console.error('JSON 파싱 실패:', responseText);
throw new Error('서버 응답을 파싱할 수 없습니다.');
console.error("JSON 파싱 실패:", responseText);
throw new Error("서버 응답을 파싱할 수 없습니다.");
}
};

// 개발 환경에서는 프록시 사용, 배포 환경에서는 실제 API URL 사용
const API_BASE_URL = import.meta.env.VITE_API_BASE_URL ||
const API_BASE_URL =
import.meta.env.VITE_API_BASE_URL ||
(import.meta.env.DEV ? "" : "https://zerojae175-dev.store");

export const authService = {
signup: async (userData: SignupData): Promise<SignupResponse> => {
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<LoginResponse> => {
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),
});

if (!response.ok) {
const errorText = await response.text();
console.error('로그인 실패 응답:', errorText);
console.error("로그인 실패 응답:", errorText);
throw new Error(`로그인 실패: ${response.status}`);
}

const data: ApiResponse<LoginResponse> = await parseJsonResponse(response);
return data.response; // { accessToken, user }
const data: ApiResponse<LoginResponse> =
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("로그인 처리 중 알 수 없는 오류가 발생했습니다.");
}
},
};
3 changes: 3 additions & 0 deletions src/App.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
html {
overflow-y: scroll;
}
22 changes: 22 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ 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";
import Reserve from "./pages/Reserve";

function App() {
const { isLoggedIn, _hasHydrated } = useAuthStore();
Expand All @@ -33,6 +35,24 @@ function App() {

return (
<div className="w-full min-h-screen m-auto bg-white">
<Toaster
position="top-center"
reverseOrder={false}
toastOptions={{
style: {
background: "#fff",
color: "#374151",
border: "1px solid #e5e7eb",
borderRadius: "12px",
fontSize: "14px",
fontWeight: "500",
padding: "12px 16px",
boxShadow:
"0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)",
marginTop: "32px",
},
}}
/>
<div className="w-full max-w-[1280px] m-auto bg-white pt-20">
<Nav />
<ScrollToTop />
Expand All @@ -46,6 +66,8 @@ function App() {
<Route path="/cart" element={<Cart />} />
<Route path="/payment" element={<PaymentComplete />} />
<Route path="/list" element={<ProjectList />} />
<Route path="/reserve" element={<Reserve />} />

<Route path="*" element={<NotFound />} /> {/* 404 페이지 처리 */}
<Route
path="/mypage"
Expand Down
6 changes: 6 additions & 0 deletions src/api/Home.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import axios from "axios";

export const fetchProducts = async () => {
const response = await axios.get("api/products");
return response.data;
};
46 changes: 0 additions & 46 deletions src/components/common/Alert.tsx

This file was deleted.

63 changes: 63 additions & 0 deletions src/components/common/EmailAlert.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
interface EmailAlertProps {
email: string;
onConfirm: () => void;
onCancel: () => void;
}

export default function EmailAlert({
email,
onConfirm,
onCancel,
}: EmailAlertProps) {
return (
<div
data-showbuttongroup="true"
data-showbuttonmore="true"
className="w-150 px-8 pt-6 pb-8 relative bg-white rounded-3xl flex flex-col justify-start items-start gap-8 opacity-100"
>
<div className="self-stretch flex flex-col justify-start items-start gap-6">
<div
data-size="medium"
className="self-stretch pt-2 inline-flex justify-start items-center"
>
<div className="flex-1 justify-start text-mint-700 text-2xl font-bold font-pretendard leading-9">
이메일 알림
</div>
</div>
<div className="self-stretch justify-start text-black text-base font-normal font-pretendard leading-normal">
농장 소식을 구독하시겠습니까?
<br />
<span className="text-mint-700 font-bold">
소식 받을 이메일 : {email}
</span>
<br />
새로운 농작물과 특별 혜택 소식을 가장 먼저 받아보세요!
</div>
</div>
<div className="self-stretch inline-flex justify-end items-center gap-4">
<button
onClick={onCancel}
className="px-6 py-2 bg-white rounded border border-gray-300 flex justify-center items-center"
>
<div className="text-center justify-start text-gray-800 text-base font-semibold font-pretendard leading-normal">
취소
</div>
</button>
<button
onClick={onConfirm}
className="px-6 py-2 bg-mint-600 rounded border border-mint-600 flex justify-center items-center"
>
<div className="text-center justify-start text-white text-base font-semibold font-pretendard leading-normal">
확인
</div>
</button>
</div>
<button
onClick={onCancel}
className="w-6 h-6 right-6 top-6 absolute inline-flex justify-center items-center text-gray-400 hover:text-gray-600"
>
</button>
</div>
);
}
Loading