diff --git a/next.config.mjs b/next.config.mjs
index 21b3b2ec..5b449033 100644
--- a/next.config.mjs
+++ b/next.config.mjs
@@ -11,6 +11,12 @@ const nextConfig = {
port: "",
pathname: "/Albaform/**",
},
+ {
+ protocol: "https",
+ hostname: "lh3.googleusercontent.com",
+ port: "",
+ pathname: "/a/**",
+ },
],
},
webpack(config) {
diff --git a/src/app/(auth)/login/page.tsx b/src/app/(auth)/login/page.tsx
index 13c4d836..c215565b 100644
--- a/src/app/(auth)/login/page.tsx
+++ b/src/app/(auth)/login/page.tsx
@@ -63,6 +63,11 @@ export default function LoginPage() {
{isPending ? "로그인 중..." : "로그인"}
+
+
+ SNS 계정으로 로그인하기
+
+
+
+
+ SNS 계정으로 회원가입하기
+
+
+
+
+ SNS 계정으로 회원가입하기
+
+
());
const observer = useRef
(null);
+ const { user } = useUser();
useEffect(() => {
// 클라이언트 환경에서만 IntersectionObserver 실행
@@ -57,11 +59,19 @@ export default function Home() {
한 곳에서 관리하는 알바 구인 플랫폼
-
-
- 알바를 시작하기
-
-
+ {user ? (
+
+
+ 알바 둘러보기
+
+
+ ) : (
+
+
+ 알바를 시작하기
+
+
+ )}
{/* 배경 이미지 */}
diff --git a/src/app/(pages)/(albaform)/addform/RecruitCondition.tsx b/src/app/(pages)/(albaform)/addform/RecruitConditionSection.tsx
similarity index 97%
rename from src/app/(pages)/(albaform)/addform/RecruitCondition.tsx
rename to src/app/(pages)/(albaform)/addform/RecruitConditionSection.tsx
index 4678d3a2..29f5dd5b 100644
--- a/src/app/(pages)/(albaform)/addform/RecruitCondition.tsx
+++ b/src/app/(pages)/(albaform)/addform/RecruitConditionSection.tsx
@@ -4,7 +4,7 @@ import Label from "../Label";
import InputDropdown from "@/app/components/button/dropdown/InputDropdown";
// 알바폼 만들기 - 사장님- 2-모집조건
-export default function RecruitCondition() {
+export default function RecruitConditionSection() {
const {
register,
formState: { errors },
diff --git a/src/app/(pages)/(albaform)/addform/RecruitContent.tsx b/src/app/(pages)/(albaform)/addform/RecruitContentSection.tsx
similarity index 92%
rename from src/app/(pages)/(albaform)/addform/RecruitContent.tsx
rename to src/app/(pages)/(albaform)/addform/RecruitContentSection.tsx
index 9f02d4e0..f1f7ee33 100644
--- a/src/app/(pages)/(albaform)/addform/RecruitContent.tsx
+++ b/src/app/(pages)/(albaform)/addform/RecruitContentSection.tsx
@@ -7,11 +7,10 @@ import DatePickerInput from "@/app/components/input/dateTimeDaypicker/DatePicker
import { cn } from "@/lib/tailwindUtil";
import { useFormContext } from "react-hook-form";
import { useEffect, useState } from "react";
-import { useSearchParams } from "next/navigation";
// 알바폼 만들기 - 사장님 - 1-모집내용
-export default function RecruitContent() {
+export default function RecruitContentSection() {
// 이미지 파일을 로컬 상태에 저장
const [initialImageList, setInitialImageList] = useState<{ file: File; url: string; id: string }[]>([]);
@@ -41,12 +40,9 @@ export default function RecruitContent() {
]);
};
- const searchParams = useSearchParams();
- const currentParam = searchParams.get("tab");
- const initialLoad = currentParam === null; // 초기 로딩 여부 확인
// 컴포넌트가 마운트될 때 이미지 초기값 설정 (초기로딩 제외)
useEffect(() => {
- if (!initialLoad && currentValue.imageFiles?.length > 0) {
+ if (currentValue.imageFiles?.length > 0) {
handleChangeImages(currentValue.imageFiles);
}
}, []);
diff --git a/src/app/(pages)/(albaform)/addform/WorkCondition.tsx b/src/app/(pages)/(albaform)/addform/WorkConditionSection.tsx
similarity index 99%
rename from src/app/(pages)/(albaform)/addform/WorkCondition.tsx
rename to src/app/(pages)/(albaform)/addform/WorkConditionSection.tsx
index ce3e957e..669e8720 100644
--- a/src/app/(pages)/(albaform)/addform/WorkCondition.tsx
+++ b/src/app/(pages)/(albaform)/addform/WorkConditionSection.tsx
@@ -12,7 +12,7 @@ import BaseInput from "@/app/components/input/text/BaseInput";
import CheckBtn from "@/app/components/button/default/CheckBtn";
// 알바폼 만들기 - 사장님 - 3-근무조건
-export default function WorkCondition() {
+export default function WorkConditionSection() {
const {
register,
setValue,
diff --git a/src/app/(pages)/(albaform)/addform/page.tsx b/src/app/(pages)/(albaform)/addform/page.tsx
index 24254bac..93b1a3bb 100644
--- a/src/app/(pages)/(albaform)/addform/page.tsx
+++ b/src/app/(pages)/(albaform)/addform/page.tsx
@@ -4,13 +4,13 @@ import { useRouter, useSearchParams } from "next/navigation";
import { FormProvider, useForm } from "react-hook-form";
import axios from "axios";
import TabMenuDropdown from "@/app/components/button/dropdown/TabMenuDropdown";
-import RecruitCondition from "./RecruitCondition";
import Button from "@/app/components/button/default/Button";
import { toast } from "react-hot-toast";
import { useMutation } from "@tanstack/react-query";
import { useUpdateProfile } from "@/hooks/queries/user/me/useUpdateProfile";
-import RecruitContent from "./RecruitContent";
-import WorkCondition from "./WorkCondition";
+import RecruitContentSection from "./RecruitContentSection";
+import RecruitConditionSection from "./RecruitConditionSection";
+import WorkConditionSection from "./WorkConditionSection";
interface SubmitFormDataType {
isPublic: boolean;
@@ -66,13 +66,12 @@ export default function AddFormPage() {
const {
setValue,
- getValues,
handleSubmit,
formState: { isDirty, isValid },
} = methods;
// 훅폼에서 관리하는 전체 데이터를 가져오는 함수
- const currentValues: SubmitFormDataType = getValues();
+ const currentValues: SubmitFormDataType = methods.watch();
// 이미지 업로드 api 처리를 위해 별도 변수에 할당
const imageFiles = currentValues.imageFiles;
@@ -135,17 +134,17 @@ export default function AddFormPage() {
"모집 조건": "recruit-condition",
"근무 조건": "work-condition",
}[option];
- router.replace(`/addform?tab=${params}`);
+ router.push(`/addform?tab=${params}`);
};
const renderChildren = () => {
switch (selectedOption) {
case "모집 내용":
- return ;
+ return ;
case "모집 조건":
- return ;
+ return ;
case "근무 조건":
- return ;
+ return ;
default:
return <>>;
}
@@ -204,33 +203,27 @@ export default function AddFormPage() {
window.localStorage.setItem("tempAddFormData", JSON.stringify(currentValues));
}
toast.success("임시 저장되었습니다.");
- // console.log("임시저장 데이터", currentValues);
+ console.log("임시저장 데이터", currentValues);
};
// 각각의 탭 작성중 여부
const isEditingRecruitContent =
- currentValues.title !== "" ||
- currentValues.description !== "" ||
- currentValues.recruitmentStartDate !== "" ||
- currentValues.imageUrls
+ currentValues.title !== "" || currentValues.description !== "" || currentValues.recruitmentStartDate !== undefined
? true
: false;
const isEditingRecruitCondition =
- currentValues.gender ||
- currentValues.numberOfPositions ||
- currentValues.education ||
- currentValues.age ||
- currentValues.preferred
+ currentValues.gender !== "" ||
+ currentValues.numberOfPositions !== 0 ||
+ currentValues.education !== "" ||
+ currentValues.age !== "" ||
+ currentValues.preferred !== ""
? true
: false;
const isEditingWorkCondition =
- currentValues.location ||
- currentValues.workDays ||
- currentValues.workStartTime ||
- currentValues.workStartDate ||
- currentValues.hourlyWage ||
- currentValues.isNegotiableWorkDays ||
- currentValues.isPublic
+ currentValues.location !== "" ||
+ currentValues.workStartTime !== "" ||
+ currentValues.workStartDate !== "" ||
+ currentValues.hourlyWage > 0
? true
: false;
@@ -242,7 +235,7 @@ export default function AddFormPage() {
options={[
{
label: "모집 내용",
- isEditing: isEditingRecruitContent || initialLoad || currentParam === "recruit-condition",
+ isEditing: isEditingRecruitContent || initialLoad || currentParam === "recruit-content",
},
{ label: "모집 조건", isEditing: isEditingRecruitCondition || currentParam === "recruit-condition" },
{ label: "근무 조건", isEditing: isEditingWorkCondition || currentParam === "work-condition" },
diff --git a/src/app/(pages)/albaFormDetail/applicant/[formId]/page.tsx b/src/app/(pages)/albaFormDetail/applicant/[formId]/page.tsx
new file mode 100644
index 00000000..29a68e2a
--- /dev/null
+++ b/src/app/(pages)/albaFormDetail/applicant/[formId]/page.tsx
@@ -0,0 +1,74 @@
+"use client";
+
+import { useParams } from "next/navigation";
+import { useUserFormDetail } from "@/hooks/queries/form/userFormDetail";
+import React, { useEffect, useState } from "react";
+import CardChipIcon from "@/app/components/card/cardList/CardChipIcon";
+import Chip from "@/app/components/chip/Chip";
+
+export default function AlbaFormDetailPage() {
+ const { formId } = useParams(); // useParams로 formId 추출
+ const [formIdState, setFormIdState] = useState(0);
+
+ useEffect(() => {
+ // formId가 문자열로 전달되므로 숫자로 변환하여 상태에 저장
+ if (formId) {
+ setFormIdState(Number(formId)); // formId를 숫자로 변환하여 상태에 저장
+ }
+ }, [formId]);
+
+ // formId가 설정되면 useUserFormDetail 호출
+ const { data, isLoading, error } = useUserFormDetail({ formId: formIdState });
+
+ if (isLoading) {
+ return Loading...
;
+ }
+
+ if (error) {
+ return Error: 데이터를 불러오는데 문제가 발생했습니다.
;
+ }
+
+ if (!data) {
+ return 데이터가 없습니다.
;
+ }
+
+ // 모집 상태 계산
+ const recruitmentStatus = new Date(data.recruitmentEndDate) > new Date() ? "모집중" : "모집완료";
+
+ return (
+
+
사진영역
+
+
+
+
+
+
{new Date(data.createdAt).toLocaleString()} 등록
+
+
+ {data.storeName || "가게명"}
+
+ {data.location || "위치"} ・ {"경력 정보 없음"}
+
+
+
{data.title}
+
+
{data.description}
+
+
+
+ );
+}
diff --git a/src/app/(pages)/albaFormDetail/layout.tsx b/src/app/(pages)/albaFormDetail/layout.tsx
new file mode 100644
index 00000000..af80a7bd
--- /dev/null
+++ b/src/app/(pages)/albaFormDetail/layout.tsx
@@ -0,0 +1,17 @@
+import React, { Suspense } from "react";
+
+export default function AlbaFormDetailLayout({ children }: { children: React.ReactNode }) {
+ return (
+
+ }
+ >
+ {children}
+
+
+ );
+}
diff --git a/src/app/(pages)/albaFormDetail/owner/[id]/page.tsx b/src/app/(pages)/albaFormDetail/owner/[id]/page.tsx
new file mode 100644
index 00000000..8754305e
--- /dev/null
+++ b/src/app/(pages)/albaFormDetail/owner/[id]/page.tsx
@@ -0,0 +1,49 @@
+"use client";
+import CardChipIcon from "@/app/components/card/cardList/CardChipIcon";
+import Chip from "@/app/components/chip/Chip";
+import ChipWithIcon from "@/app/components/chip/ChipWithIcon";
+import React from "react";
+
+// 알바폼 상세 사장님 페이지
+export default function AlbaFormDetailPage({ formId }: { formId: number }) {
+ return (
+
+
사진영역
+
+
+
+
+
+
2024. 05. 04 12:30:54 등록
+
+
+ 코드잇
+ 서울 종로구 ・ 경력 무관
+
+
코드잇 스터디카페 관리 (주말 오전) 모집합니다 서울 종로구 용산구 서대문
+
+
+ 코드잇 스터디 카페입니다. 주말 토, 일 오픈업무 하실 분 구합니다. 성실하게 일하실 분들만 지원 바랍니다.
+ 작성한 이력서(사진 부착)를 알바폼에 첨부해주시고, 아래와 같이 문자 보내주세요. 근무 중 전화통화 불가합니다.
+ 예) OOO입니다. __에 거주합니다. 알바폼 지원. 이력서 검토 후 면접진행자에 한해 면접일정 개별
+ 연락드리겠습니다. 많은 지원 바랍니다.
+
+
+
{/* 오른쪽 콘텐츠 */}
+
+
+ );
+}
diff --git a/src/app/(pages)/albaFormSupportDetail/layout.tsx b/src/app/(pages)/albaFormSupportDetail/layout.tsx
new file mode 100644
index 00000000..af80a7bd
--- /dev/null
+++ b/src/app/(pages)/albaFormSupportDetail/layout.tsx
@@ -0,0 +1,17 @@
+import React, { Suspense } from "react";
+
+export default function AlbaFormDetailLayout({ children }: { children: React.ReactNode }) {
+ return (
+
+ }
+ >
+ {children}
+
+
+ );
+}
diff --git a/src/app/(pages)/albaFormSupportDetail/page.tsx b/src/app/(pages)/albaFormSupportDetail/page.tsx
new file mode 100644
index 00000000..c5df0666
--- /dev/null
+++ b/src/app/(pages)/albaFormSupportDetail/page.tsx
@@ -0,0 +1,44 @@
+import CardChipIcon from "@/app/components/card/cardList/CardChipIcon";
+import Chip from "@/app/components/chip/Chip";
+import React from "react";
+
+// 알바폼 지원내역 상세 페이지
+export default function AlbaFormSupportDetailPage({ formId }: { formId: number }) {
+ return (
+
+
지도영역
+
+
+
+
+
2024. 05. 04 12:30:54 등록
+
+
+ 코드잇
+ 서울 종로구 ・ 경력 무관
+
+
코드잇 스터디카페 관리 (주말 오전) 모집합니다 서울 종로구 용산구 서대문
+
+
+ 코드잇 스터디 카페입니다. 주말 토, 일 오픈업무 하실 분 구합니다. 성실하게 일하실 분들만 지원 바랍니다. 작성한
+ 이력서(사진 부착)를 알바폼에 첨부해주시고, 아래와 같이 문자 보내주세요. 근무 중 전화통화 불가합니다. 예)
+ OOO입니다. __에 거주합니다. 알바폼 지원. 이력서 검토 후 면접진행자에 한해 면접일정 개별 연락드리겠습니다. 많은
+ 지원 바랍니다.
+
+
+
+ );
+}
diff --git a/src/app/(pages)/albaList/page.tsx b/src/app/(pages)/albaList/page.tsx
index f7c74286..db73d122 100644
--- a/src/app/(pages)/albaList/page.tsx
+++ b/src/app/(pages)/albaList/page.tsx
@@ -150,7 +150,11 @@ export default function AlbaList() {
{page.data.map((form) => (
))}
diff --git a/src/app/api/forms/[formId]/applications/route.ts b/src/app/api/forms/[formId]/applications/route.ts
index 53c274aa..733d5fd5 100644
--- a/src/app/api/forms/[formId]/applications/route.ts
+++ b/src/app/api/forms/[formId]/applications/route.ts
@@ -20,9 +20,10 @@ export async function POST(req: NextRequest, { params }: { params: { formId: str
"Content-Type": "application/json",
},
});
-
+ console.log(response.data);
return NextResponse.json(response.data);
} catch (error: unknown) {
+ console.error(error);
if (error instanceof AxiosError) {
console.error(`POST /api/forms/${params.formId}/applications error:`, error);
if (error.response) {
diff --git a/src/app/api/oauth/callback/google/route.ts b/src/app/api/oauth/callback/google/route.ts
index 64d21043..809dd5e9 100644
--- a/src/app/api/oauth/callback/google/route.ts
+++ b/src/app/api/oauth/callback/google/route.ts
@@ -4,6 +4,7 @@ import { decodeJwt } from "@/middleware";
import apiClient from "@/lib/apiClient";
import { OauthLoginUser, OauthResponse, OauthSignupUser } from "@/types/oauth/oauth";
import { cookies } from "next/headers";
+import { RedirectError } from "@/utils/oauthLoginError";
export const GET = async (request: NextRequest) => {
const searchParams = request.nextUrl.searchParams;
@@ -82,15 +83,25 @@ export const GET = async (request: NextRequest) => {
};
const loginUser = async () => {
- const { data: loginResponse } = await axios.post(
- `${process.env.NEXT_PUBLIC_DOMAIN_URL}/api/oauth/login/${provider}`,
- googleUser.login
- );
- console.log("구글 로그인 성공:", loginResponse);
+ try {
+ const { data: loginResponse } = await axios.post(
+ `${process.env.NEXT_PUBLIC_DOMAIN_URL}/api/oauth/login/${provider}`,
+ googleUser.login
+ );
+ console.log("구글 로그인 성공:", loginResponse);
- // 쿠키 저장
- const { accessToken, refreshToken } = loginResponse;
- setCookies(accessToken, refreshToken);
+ // 쿠키 저장
+ const { accessToken, refreshToken } = loginResponse;
+ setCookies(accessToken, refreshToken);
+ } catch (error: any) {
+ if (error.response?.status === 403) {
+ console.log("회원가입이 필요합니다. 회원가입 페이지로 이동합니다...");
+ throw new RedirectError("/signup");
+ } else {
+ console.error("구글 로그인 중 오류:", error.message || error);
+ throw new Error("로그인 중 서버 오류");
+ }
+ }
};
const setCookies = (accessToken: string, refreshToken: string) => {
@@ -108,11 +119,18 @@ export const GET = async (request: NextRequest) => {
});
};
- await processUser();
+ try {
+ await processUser();
+ } catch (error) {
+ if (error instanceof RedirectError) {
+ return NextResponse.redirect(new URL(error.redirectPath, request.url));
+ }
+ throw error;
+ }
} catch (error: any) {
console.error("OAuth 처리 중 오류:", error.message || error);
return NextResponse.json({ message: error.message || "서버 오류" }, { status: 500 });
}
- return NextResponse.redirect(new URL("/", request.url));
+ return NextResponse.redirect(new URL("/mypage", request.url));
};
diff --git a/src/app/api/oauth/callback/kakao/route.ts b/src/app/api/oauth/callback/kakao/route.ts
index bc7a322a..50a4ba0d 100644
--- a/src/app/api/oauth/callback/kakao/route.ts
+++ b/src/app/api/oauth/callback/kakao/route.ts
@@ -3,6 +3,7 @@ import axios from "axios";
import apiClient from "@/lib/apiClient";
import { OauthLoginUser, OauthResponse, OauthSignupUser } from "@/types/oauth/oauth";
import { cookies } from "next/headers";
+import { RedirectError } from "@/utils/oauthLoginError";
export const GET = async (request: NextRequest) => {
const searchParams = request.nextUrl.searchParams;
@@ -71,8 +72,13 @@ export const GET = async (request: NextRequest) => {
const { accessToken, refreshToken } = loginResponse;
setCookies(accessToken, refreshToken);
} catch (error: any) {
- console.error("카카오 로그인 중 오류:", error.message || error);
- throw new Error("로그인 중 서버 오류");
+ if (error.response?.status === 403) {
+ console.log("회원가입이 필요합니다. 회원가입 시도 중...");
+ //회원가입 페이지로 리다이렉트
+ throw new RedirectError("/signup");
+ } else {
+ throw new Error("회원가입 중 서버 오류");
+ }
}
};
@@ -98,5 +104,5 @@ export const GET = async (request: NextRequest) => {
return NextResponse.json({ message: error.message || "서버 오류" }, { status: 500 });
}
- return NextResponse.redirect(new URL("/", request.url));
+ return NextResponse.redirect(new URL("/mypage", request.url));
};
diff --git a/src/app/components/input/file/ImageInput/ImageInput.tsx b/src/app/components/input/file/ImageInput/ImageInput.tsx
index c80f7f60..f73f737e 100644
--- a/src/app/components/input/file/ImageInput/ImageInput.tsx
+++ b/src/app/components/input/file/ImageInput/ImageInput.tsx
@@ -17,7 +17,7 @@ interface ImageInputProps {
}
const ImageInput = forwardRef((props, ref) => {
- const [imageList, setImageList] = useState(props.initialImageList || []); // 단순히 이미지 프리뷰를 위한 상태 관리
+ const [imageList, setImageList] = useState([]); // 단순히 이미지 프리뷰를 위한 상태 관리
const handleFileChange = (selectedFile: File | null) => {
if (selectedFile) {
@@ -42,6 +42,7 @@ const ImageInput = forwardRef((props, ref) =>
props.onChange?.(newImageList.map((img) => img.file).filter((file) => file !== null));
}
};
+
const handleOpenFileSelecter = () => {
if (typeof ref === "function") {
// input 요소를 찾아서 클릭
@@ -55,6 +56,10 @@ const ImageInput = forwardRef((props, ref) =>
};
const handleDeleteImage = (targetId: string) => {
+ const targetImage = imageList.find((image) => image.id === targetId);
+ if (targetImage) {
+ URL.revokeObjectURL(targetImage.url); // URL 객체 해제
+ }
const newImageList = imageList.filter((image) => image.id !== targetId);
setImageList(newImageList);
props.onChange?.(newImageList.map((img) => img.file).filter((file) => file !== null));
diff --git a/src/app/components/layout/addFormLayout/ApplyHeader.tsx b/src/app/components/layout/addFormLayout/ApplyHeader.tsx
index 11ecc82e..460a9107 100644
--- a/src/app/components/layout/addFormLayout/ApplyHeader.tsx
+++ b/src/app/components/layout/addFormLayout/ApplyHeader.tsx
@@ -2,7 +2,7 @@ import Button from "@/app/components/button/default/Button";
const ApplyHeader = ({ title, onCancel }: { title: string; onCancel: () => void }) => {
return (
-
+
{title}
작성 취소
diff --git a/src/hooks/queries/form/userFormDetail.ts b/src/hooks/queries/form/userFormDetail.ts
new file mode 100644
index 00000000..a1029129
--- /dev/null
+++ b/src/hooks/queries/form/userFormDetail.ts
@@ -0,0 +1,27 @@
+import { FormDetailResponse } from "@/types/response/form";
+import { useQuery } from "@tanstack/react-query";
+import axios from "axios";
+
+interface UseUserFormDetailParams {
+ formId?: number;
+}
+
+export const useUserFormDetail = ({ formId }: UseUserFormDetailParams) => {
+ const query = useQuery({
+ queryKey: ["formDetail", formId],
+ queryFn: async () => {
+ if (!formId) {
+ throw new Error("formId가 없습니다.");
+ }
+ const response = await axios.get(`/api/forms/${formId}`);
+ return response.data;
+ },
+ enabled: !!formId, // formId가 유효한 경우에만 쿼리를 실행
+ });
+
+ return {
+ ...query,
+ isPending: query.isPending,
+ error: query.error,
+ };
+};
diff --git a/src/utils/oauthLoginError.ts b/src/utils/oauthLoginError.ts
new file mode 100644
index 00000000..ac54f41d
--- /dev/null
+++ b/src/utils/oauthLoginError.ts
@@ -0,0 +1,10 @@
+export class RedirectError extends Error {
+ redirectPath: string;
+
+ constructor(redirectPath: string) {
+ super("Redirect required");
+ this.redirectPath = redirectPath;
+
+ Object.setPrototypeOf(this, RedirectError.prototype);
+ }
+}