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
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
"use client";
import React, { useState } from "react"; // useState 추가
import React, { useState } from "react";
import Link from "next/link";
import { useUser } from "@/hooks/queries/user/me/useUser";
import { FormDetailResponse } from "@/types/response/form";
import FloatingBtn from "@/app/components/button/default/FloatingBtn";
import { FaEdit } from "react-icons/fa";
import { MdDeleteForever } from "react-icons/md";
import { CiMemoPad } from "react-icons/ci";
import axios from "axios";
import toast from "react-hot-toast";
import { useRouter } from "next/navigation";
import DotLoadingSpinner from "@/app/components/loading-spinner/DotLoadingSpinner";
import { HiMail } from "react-icons/hi";
import { HiDocumentText } from "react-icons/hi";
import { useMyApplications } from "@/hooks/queries/user/me/useMyApplications";
interface FormActionsProps {
formId: string | number;
albaFormDetailData: FormDetailResponse;
}

export default function FormActions({ formId, albaFormDetailData }: FormActionsProps) {
const { user } = useUser();
const { data: applicantData } = useMyApplications();
const router = useRouter();
const isMyAlbaForm = user?.id === albaFormDetailData.ownerId;
const isOwnerRole = user?.role === "OWNER";
Expand Down Expand Up @@ -66,15 +67,26 @@ export default function FormActions({ formId, albaFormDetailData }: FormActionsP
);
}

// 이미 지원한 공고인지 확인
const hasApplied = applicantData?.pages
?.flatMap((page) => page.data) // 1차원 배열로 변환
.some((applicant) => applicant.form?.id === formId); // 일치하는 항목을 찾으면 즉시 true를 반환

// 사장님이 아니면 지원하기/내 지원내역 보기 버튼
if (!isOwnerRole) {
return (
<div className="space-y-4 text-2xl">
<Link href={`/apply/${formId}`}>
<FloatingBtn className={`${buttonStyle} mb-4`} icon={<HiMail />}>
{isLoading ? <DotLoadingSpinner /> : "지원하기"}
{hasApplied ? (
<FloatingBtn className={`${buttonStyle} mb-4`} icon={<HiMail />} disabled>
{isLoading ? <DotLoadingSpinner /> : "이미 지원한 공고"}
</FloatingBtn>
</Link>
) : (
<Link href={`/apply/${formId}`}>
<FloatingBtn className={`${buttonStyle} mb-4`} icon={<HiMail />}>
{isLoading ? <DotLoadingSpinner /> : "지원하기"}
</FloatingBtn>
</Link>
)}
<Link href={`/my-apply/${formId}`}>
<FloatingBtn variant="white" className={buttonStyle} icon={<HiDocumentText />}>
{isLoading ? <DotLoadingSpinner /> : "내 지원내역 보기"}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ export default function RecruitInformation({ albaFormDetailData, formId }: Recru
};

return (
<div className="flex flex-col gap-10 lg:pl-6">
<div className="mt-10 flex flex-col gap-10 lg:mt-0 lg:pl-6">
<RecruitIcon {...recruitmentDetails} />
<RecruitDetail recruitData={albaFormDetailData} />
<div className="flex flex-col gap-6">
Expand Down
30 changes: 14 additions & 16 deletions src/app/(pages)/(albaform)/alba/[formId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,6 @@ export default function AlbaFormDetailPage() {
libraries: ["services"],
});

// 카카오 SDK 초기화
useEffect(() => {
if (window.Kakao && !window.Kakao.isInitialized()) {
window.Kakao.init(process.env.NEXT_PUBLIC_KAKAO_APP_KEY);
}
}, []);

useEffect(() => {
if (formId) {
setFormIdState(Number(formId));
Expand All @@ -73,11 +66,6 @@ export default function AlbaFormDetailPage() {
});
}, [albaFormDetailData?.location]);

// 사용자 데이터가 로딩 중인지 확인
if (!user) {
return <LoadingSpinner />;
}

if (isLoading)
return (
<div className="flex h-[calc(100vh-200px)] items-center justify-center">
Expand All @@ -88,7 +76,17 @@ export default function AlbaFormDetailPage() {
return (
<div className="container flex min-h-screen flex-col px-4 lg:px-0">
{/* 카카오 공유 스트립트 */}
<Script src="https://developers.kakao.com/sdk/js/kakao.min.js" strategy="beforeInteractive" />
<Script
src={`https://developers.kakao.com/sdk/js/kakao.min.js?v=${new Date().getTime()}`}
strategy="afterInteractive"
onLoad={() => {
const kakaoKey = process.env.NEXT_PUBLIC_KAKAO_APP_KEY;
if (window.Kakao && !window.Kakao.isInitialized()) {
window.Kakao.init(kakaoKey);
}
}}
/>

{/* 사진영역 */}
{albaFormDetailData && (
<FormImage
Expand All @@ -113,7 +111,7 @@ export default function AlbaFormDetailPage() {
<div className="h-[280px] lg:h-[320px]">
{error && <div className="text-red-500">Map load error: {String(error)}</div>}
{!loading && !error && albaFormDetailData && (
<Map center={coords} style={{ width: "100%", height: "100%" }} level={3}>
<Map center={coords} style={{ width: "100%", height: "100%", zIndex: 0 }} level={3}>
<MapMarker position={coords}>
<div className="whitespace-nowrap p-2 text-center">
<p className="">{albaFormDetailData.storeName}</p>
Expand All @@ -132,8 +130,8 @@ export default function AlbaFormDetailPage() {
{/* 지원 현황 */}
{isOwner && isAuthor && <ApplyStatus formId={formIdState} />}

<div className="fixed right-10 top-1/2 flex flex-col items-end gap-5">
<ScrapBtn formId={formIdState} />
<div className="fixed right-10 top-1/2 flex w-12 flex-col items-end gap-5">
{user && !isOwner && <ScrapBtn formId={formIdState} />}
<ExpandedFloatingBtn icon={<IoShareSocialSharp />} variant="orange" />
</div>
</div>
Expand Down
2 changes: 1 addition & 1 deletion src/app/(pages)/(albaform)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export default function Layout({ children }: { children: ReactNode }) {
);

// 상세 페이지 레이아웃
const DetailStyle = "mx-auto max-w-screen-xl px-4 py-4 sm:px-6 md:py-8";
const DetailStyle = "mx-auto max-w-screen-xl px-4 py-4 sm:px-6 md:py-8 justify-center flex";

return (
<div className={cn(isForm ? FormStyle : DetailStyle)}>
Expand Down
21 changes: 12 additions & 9 deletions src/app/(pages)/my-albaform/(role)/applicant/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { useMyApplications } from "@/hooks/queries/user/me/useMyApplications";
import ApplicantSortSection from "./components/ApplicantSortSection";
import LoadingSpinner from "@/app/components/loading-spinner/LoadingSpinner";
import ContentSection from "@/app/components/layout/ContentSection";
import Link from "next/link";

const APPLICATIONS_PER_PAGE = 10;

Expand Down Expand Up @@ -112,15 +113,17 @@ export default function ApplicantPage() {
<React.Fragment key={page.nextCursor}>
{page.data.map((application) => (
<div key={application.id}>
<MyApplicationListItem
id={application.id}
createdAt={application.createdAt}
updatedAt={application.updatedAt}
status={application.status}
resumeId={application.resumeId}
resumeName={application.resumeName}
form={application.form}
/>
<Link href={`/alba/${application.form.id}`}>
<MyApplicationListItem
id={application.id}
createdAt={application.createdAt}
updatedAt={application.updatedAt}
status={application.status}
resumeId={application.resumeId}
resumeName={application.resumeName}
form={application.form}
/>
</Link>
</div>
))}
</React.Fragment>
Expand Down
2 changes: 1 addition & 1 deletion src/app/(pages)/my-apply/[formId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import CardChipIcon from "@/app/components/card/cardList/CardChipIcon";
import Chip from "@/app/components/chip/Chip";
import React from "react";

// 알바폼 지원내역 상세 페이지
// 알바폼 지원내역 상세 페이지 (수정해야하는 페이지)
export default function MyApplyPage({ formId }: { formId: number }) {
return (
<div className="container flex min-h-screen flex-col">
Expand Down
19 changes: 17 additions & 2 deletions src/app/components/button/default/ExpandedFloatingBtn.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
"use client";
import React, { useState, ButtonHTMLAttributes, ReactNode } from "react";
import React, { useState, ButtonHTMLAttributes, ReactNode, useEffect, useRef } from "react";
import { cn } from "@/lib/tailwindUtil";
import { IoShareSocialSharp } from "react-icons/io5"; // 기본 공유 아이콘
import { FaLink } from "react-icons/fa"; // 링크 및 카카오톡 아이콘
import { FcShare } from "react-icons/fc";
import shareToKakao from "@/utils/shareToKakao";
import toast from "react-hot-toast";
Expand All @@ -15,6 +14,7 @@ interface ExpandedFloatingBtnProps extends ButtonHTMLAttributes<HTMLButtonElemen

const ExpandedFloatingBtn = ({ variant = "orange", className, ...props }: ExpandedFloatingBtnProps) => {
const [isExpanded, setIsExpanded] = useState(false);
const buttonRef = useRef<HTMLButtonElement | null>(null);

const baseStyles = "inline-flex items-center justify-center transition-colors font-medium rounded-full";
const expandedStyles = "h-32 px-1 bg-primary-orange-200 hover:bg-primary-orange-100";
Expand All @@ -34,8 +34,23 @@ const ExpandedFloatingBtn = ({ variant = "orange", className, ...props }: Expand
toast.success("링크가 복사되었습니다.");
};

// 외부 클릭 감지 로직
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (buttonRef.current && !buttonRef.current.contains(event.target as Node)) {
setIsExpanded(false);
}
};

document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, []);

return (
<button
ref={buttonRef} // 버튼 참조
className={cn(baseStyles, variants[variant], isExpanded ? expandedStyles : collapsedStyles, className)}
onClick={handleToggle}
{...props}
Expand Down
29 changes: 24 additions & 5 deletions src/app/components/button/default/FloatingBtn.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,41 @@ import { cn } from "@/lib/tailwindUtil";
interface FloatingBtnProps extends ButtonHTMLAttributes<HTMLButtonElement> {
variant?: "orange" | "white";
icon?: ReactNode;
disabled?: boolean;
}

const FloatingBtn = ({ variant = "orange", icon, children, className, ...props }: FloatingBtnProps) => {
const FloatingBtn = ({ variant = "orange", icon, children, className, disabled, ...props }: FloatingBtnProps) => {
const baseStyles = "inline-flex items-center justify-center transition-colors font-medium rounded-full h-12";

const variants = {
orange: "bg-primary-orange-300 text-white hover:bg-primary-orange-200",
white: "bg-white text-primary-orange-300 border border-primary-orange-300 hover:bg-primary-grayscale-100",
orange: "bg-primary-orange-300 text-white",
white: "bg-white text-primary-orange-300 border border-primary-orange-300",
};

const hoverStyles = {
orange: "hover:bg-primary-orange-200",
white: "hover:text-primary-orange-200",
};

const disabledStyles = "bg-grayscale-100 text-white cursor-not-allowed";

const widthStyles = children ? "w-auto px-3" : "w-12";

return (
<button type="button" className={cn(baseStyles, variants[variant], widthStyles, className)} {...props}>
<button
type="button"
className={cn(
baseStyles,
variants[variant],
widthStyles,
disabled ? disabledStyles : hoverStyles[variant],
className
)}
disabled={disabled}
{...props}
>
{icon && <span className="text-2xl">{icon}</span>}
<span className={cn(children ? "ml-2" : "", "hidden sm:inline")}>{children}</span>
<span className={cn(children ? "ml-2" : "", "hidden text-lg sm:inline lg:text-2xl")}>{children}</span>
</button>
);
};
Expand Down
54 changes: 30 additions & 24 deletions src/app/components/button/default/ScrapBtn.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
"use client";
import React, { useState } from "react";
import React, { useEffect, useState } from "react";
import { FaBookmark, FaRegBookmark } from "react-icons/fa";
import { cn } from "@/lib/tailwindUtil";
import axios from "axios";
import toast from "react-hot-toast";
import { useQueryClient } from "@tanstack/react-query";
import useFormDetail from "@/hooks/queries/form/detail/useFormDetail";
import { useUser } from "@/hooks/queries/user/me/useUser";

interface ScrapBtnProps {
className?: string;
Expand All @@ -12,47 +15,50 @@ interface ScrapBtnProps {

const ScrapBtn = ({ className = "", formId }: ScrapBtnProps) => {
const [isScraped, setIsScraped] = useState(false);
const queryClient = useQueryClient(); // React Query의 QueryClient 인스턴스 사용
const { albaFormDetailData } = useFormDetail({ formId });

const toggleScrap = () => {
const toggleScrap = async () => {
setIsScraped((prev) => !prev);
};

const handleScrap = () => {
toggleScrap();
if (!isScraped) {
// 스크랩
try {
axios.post(`/api/forms/${formId}/scrap`, { formId });
try {
if (!isScraped) {
// 스크랩
await axios.post(`/api/forms/${formId}/scrap`, { formId });
toast.success("스크랩 되었습니다.");
} catch (error) {
toggleScrap();
}
}
if (isScraped) {
// 스크랩 취소
try {
axios.delete(`/api/forms/${formId}/scrap`);
} else {
// 스크랩 취소
await axios.delete(`/api/forms/${formId}/scrap`);
toast.success("스크랩이 취소되었습니다.");
} catch (error) {
toggleScrap();
}
return;

// 특정 쿼리 무효화 (키를 기준으로 쿼리를 다시 가져오게 함)
queryClient.invalidateQueries({ queryKey: ["formDetail", formId] });
} catch (error) {
// 오류 발생 시 상태 롤백
setIsScraped((prev) => !prev);
toast.error("스크랩 요청에 실패했습니다.");
}
};
console.log(isScraped);

useEffect(() => {
if (albaFormDetailData?.isScrapped !== undefined) {
setIsScraped(albaFormDetailData.isScrapped);
}
}, [albaFormDetailData?.isScrapped]);

return (
<button
className={cn(
"inline-flex h-12 w-12 items-center justify-center rounded-full bg-primary-orange-50 p-2 transition-colors",
className
)}
onClick={handleScrap}
onClick={toggleScrap}
>
{isScraped ? (
<FaBookmark className={cn("text-xl text-primary-orange-300")} />
<FaBookmark className="text-xl text-primary-orange-300" />
) : (
<FaRegBookmark className={cn("text-xl text-primary-orange-300")} />
<FaRegBookmark className="text-xl text-primary-orange-300" />
)}
</button>
);
Expand Down
Loading
Loading