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
8 changes: 6 additions & 2 deletions src/app/(pages)/(albaform)/addform/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import WorkConditionSection from "./section/WorkConditionSection";
import useEditing from "@/hooks/useEditing";
import { SubmitFormDataType } from "@/types/addform";
import CustomFormModal from "@/app/components/modal/modals/confirm/CustomFormModal";
import DotLoadingSpinner from "@/app/components/loading-spinner/DotLodingSpinner";

export default function AddFormPage() {
const router = useRouter();
Expand Down Expand Up @@ -61,6 +62,7 @@ export default function AddFormPage() {
// 폼 제출 리액트쿼리
const mutation = useMutation({
mutationFn: async () => {
setLoading(true);
// 이미지 필수 체크
if (!imageFiles || imageFiles.length === 0) {
toast.error("이미지를 첨부해주세요.");
Expand Down Expand Up @@ -110,11 +112,12 @@ export default function AddFormPage() {
if (typeof window !== "undefined") {
window.localStorage.removeItem("tempAddFormData");
}
setLoading(false);
toast.success("알바폼을 등록했습니다.");
// if (formId) router.push(`/alba/${formId}`);
},
onError: (error) => {
console.error("에러가 발생했습니다.", error);
setLoading(false);
toast.error("에러가 발생했습니다.");
onTempSave();
},
Expand All @@ -131,6 +134,7 @@ export default function AddFormPage() {
const currentParam = searchParams.get("tab");
const [prevOption, setPrevOption] = useState<string | null>(null);
const initialLoad = currentParam === null; // 초기 로딩 여부 확인
const [loading, setLoading] = useState(false);

const handleOptionChange = async (option: string) => {
setSelectedOption(option);
Expand Down Expand Up @@ -313,7 +317,7 @@ export default function AddFormPage() {
disabled={!isValid}
onClick={handleSubmit(() => mutation.mutate())}
>
작성 완료
{loading ? <DotLoadingSpinner /> : "작성 완료"}
</Button>
</div>
</aside>
Expand Down
75 changes: 53 additions & 22 deletions src/app/(pages)/(albaform)/alba/[formId]/components/FormActions.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
import React from "react";
import Button from "@/app/components/button/default/Button";
import { FcEmptyTrash, FcEditImage, FcFile, FcSearch } from "react-icons/fc";
"use client";
import React, { useState } from "react"; // useState 추가
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 { VscGitStashApply } from "react-icons/vsc";
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/DotLodingSpinner";

interface FormActionsProps {
formId: string | number;
Expand All @@ -12,38 +20,61 @@ interface FormActionsProps {

export default function FormActions({ formId, albaFormDetailData }: FormActionsProps) {
const { user } = useUser();
const router = useRouter();
const isMyAlbaForm = user?.id === albaFormDetailData.ownerId;
const isOwnerRole = user?.role === "OWNER";

const buttonStyle = "h-10 lg:h-16";
const buttonStyle = "h-10 lg:h-16 w-full rounded-lg font-bold ";
const [loading, setLoading] = useState(false);

if (!user) return null;
// 사장님이 아니면 지원하기/내 지원내역 보기 버튼
if (!isOwnerRole) {
return (
<div className="space-y-4 text-2xl">
<Button className={buttonStyle} width="lg" icon={<FcFile />}>
지원하기
</Button>
<Button className={buttonStyle} width="lg" icon={<FcSearch />} variant="outlined">
내 지원내역 보기
</Button>
</div>
);
}

const handleDelete = async () => {
setLoading(true);
try {
await axios.delete(`/api/forms/${formId}`);
toast.success("성공적으로 삭제되었습니다.");
router.push(`/albalist`);
} catch (error) {
toast.error("삭제 중 오류가 발생했습니다.");
} finally {
setLoading(false);
}
};

// 사장님이면 수정하기/삭제하기 버튼
if (isOwnerRole) {
if (!isMyAlbaForm) return null;
return (
<div className="space-y-4 text-2xl">
<Link href={`/alba/${formId}/edit`}>
<Button className={buttonStyle} width="lg" icon={<FcEditImage />}>
<FloatingBtn className={`${buttonStyle}`} icon={<FaEdit />} disabled={loading}>
수정하기
</Button>
</FloatingBtn>
</Link>
<Button className={buttonStyle} width="lg" icon={<FcEmptyTrash />} variant="outlined">
삭제하기
</Button>
<FloatingBtn
variant="white"
className={buttonStyle}
icon={<MdDeleteForever />}
onClick={handleDelete}
disabled={loading}
>
{loading ? <DotLoadingSpinner /> : "삭제하기"}
</FloatingBtn>
</div>
);
}

// 사장님이 아니면 지원하기/내 지원내역 보기 버튼
if (!isOwnerRole) {
return (
<div className="space-y-4 text-2xl">
<FloatingBtn className={buttonStyle} icon={<VscGitStashApply />}>
지원하기
</FloatingBtn>
<FloatingBtn variant="white" className={buttonStyle} icon={<CiMemoPad />}>
내 지원내역 보기
</FloatingBtn>
</div>
);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Image from "next/image";
import Indicator from "@/app/components/pagination/Indicator";
import { isValidS3Url } from "@/utils/checkS3Url";

interface FormImageProps {
imageUrls: string[];
Expand Down Expand Up @@ -30,9 +31,11 @@ export default function FormImage({ imageUrls, currentPage, onPageChange }: Form
))}

{/* 인디케이터 */}
<div className="absolute bottom-4 left-1/2 -translate-x-1/2">
<Indicator imageCount={imageUrls?.length ?? 0} currentPage={currentPage} onPageChange={onPageChange} />
</div>
{imageUrls.filter((url) => isValidS3Url(url)).length > 1 && (
<div className="absolute bottom-4 left-1/2 -translate-x-1/2">
<Indicator imageCount={imageUrls?.length ?? 0} currentPage={currentPage} onPageChange={onPageChange} />
</div>
)}
</div>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,14 @@ export default function RecruitInformation({ albaFormDetailData, formId }: Recru
<>
<RecruitIcon {...recruitmentDetails} />
<RecruitDetail recruitData={albaFormDetailData} />
<FormActions formId={formId} albaFormDetailData={albaFormDetailData} />
<p className="text-3xl font-bold">모집 조건</p>
<RecruitCondition recruitData={albaFormDetailData} />
<div className="hidden lg:block">
<FormActions formId={formId} albaFormDetailData={albaFormDetailData} />
</div>
<div className="block lg:hidden">
<FormActions formId={formId} albaFormDetailData={albaFormDetailData} />
</div>
</>
);
}
7 changes: 1 addition & 6 deletions src/app/components/card/cardList/AlbaListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ import Indicator from "../../pagination/Indicator";
import { FormListType } from "@/types/response/form";
import { useFormScrap } from "@/hooks/queries/form/useFormScap";
import { MdOutlineImage } from "react-icons/md";
import { S3_URL } from "@/constants/config";
import DotLoadingSpinner from "../../loading-spinner/DotLodingSpinner";
import { isValidS3Url } from "@/utils/checkS3Url";

/**
* 알바폼 리스트 아이템 컴포넌트
Expand Down Expand Up @@ -125,11 +125,6 @@ const AlbaListItem = ({
});
};

// S3 URL 체크 함수
const isValidS3Url = (url: string) => {
return url.startsWith(S3_URL);
};

return (
<div className="relative h-auto w-[327px] overflow-hidden rounded-xl border border-grayscale-200 bg-white shadow-md transition-transform duration-300 hover:scale-[1.02] lg:w-[372px]">
{/* 이미지 슬라이더 영역 */}
Expand Down
4 changes: 2 additions & 2 deletions src/app/components/card/cardList/RecruitCondition.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@ interface RecruitConditionProps {

// 모집조건 카드 컴포넌트
const RecruitCondition = ({ recruitData }: RecruitConditionProps) => {
const titleStyle = "text-black-200 min-w-[64px] lg:min-w-[120px]";
const titleStyle = "text-black-200 min-w-28 lg:min-w-[120px]";
return (
<div className="flex w-[327px] flex-col gap-5 rounded-lg border border-line-100 bg-white p-4 text-xs lg:w-[640px] lg:p-6 lg:text-lg">
<div className="flex w-full flex-col gap-5 rounded-lg border border-line-100 bg-white p-4 text-sm lg:w-[640px] lg:p-6 lg:text-lg">
<div className="flex">
<span className={cn(titleStyle)}>모집 인원</span>
<div>
Expand Down
4 changes: 2 additions & 2 deletions src/app/components/card/cardList/RecruitIconItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ const RecruitIconItem = ({ icon, label, value }: RecruitIconItemProps) => {
</div>
</div>
<div className="flex-1">
<div className="text-grayscale-600 text-[10px] font-medium sm:text-xs lg:text-xl">{label}</div>
<div className="text-xs font-semibold text-primary-orange-300 sm:text-sm lg:h-[68px] lg:text-2xl">{value}</div>
<div className="text-grayscale-600 text-sm font-medium lg:text-xl">{label}</div>
<div className="text-sm font-semibold text-primary-orange-300 lg:h-[68px] lg:text-2xl">{value}</div>
</div>
</div>
);
Expand Down
17 changes: 5 additions & 12 deletions src/app/components/loading-spinner/DotLodingSpinner.tsx
Original file line number Diff line number Diff line change
@@ -1,23 +1,16 @@
"use client";

import { cn } from "@/lib/tailwindUtil";
import React from "react";

export default function DotLoadingSpinner() {
const dotStyle = "h-2 w-2 animate-bounce rounded-full ";
return (
<div className="flex justify-center py-2">
<div className="flex items-center justify-center">
<div
className="h-2 w-2 animate-bounce rounded-full bg-primary-orange-300"
style={{ animationDelay: "0s" }}
></div>
<div
className="ml-2 h-2 w-2 animate-bounce rounded-full bg-primary-orange-300"
style={{ animationDelay: "0.2s" }}
></div>
<div
className="ml-2 h-2 w-2 animate-bounce rounded-full bg-primary-orange-300"
style={{ animationDelay: "0.4s" }}
></div>
<div className={cn(dotStyle, "bg-orange-200")} style={{ animationDelay: "0s" }}></div> {/* 연한 오렌지 */}
<div className={cn(dotStyle, "ml-2 bg-green-200")} style={{ animationDelay: "0.2s" }}></div> {/* 연한 그린 */}
<div className={cn(dotStyle, "ml-2 bg-blue-200")} style={{ animationDelay: "0.4s" }}></div> {/* 연한 블루 */}
</div>
</div>
);
Expand Down
9 changes: 6 additions & 3 deletions src/app/components/pagination/Indicator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ interface IndicatorProps {
}

const Indicator = ({ imageCount, currentPage, onPageChange }: IndicatorProps) => {
const activeStyle = "size-4 text-grayscale-300 opacity-60";
const defaultStyle = "size-3 text-grayscale-50 opacity-60";
const activeStyle = "lg:size-5 text-primary-orange-300 opacity-60 size:3";
const defaultStyle = "lg:size-3 text-primary-orange-100 opacity-60 size:1";

// 이미지 개수 만큼 동적으로 생성
const indicators = Array(imageCount)
Expand All @@ -24,7 +24,10 @@ const Indicator = ({ imageCount, currentPage, onPageChange }: IndicatorProps) =>
));

return (
<button type="button" className="flex items-center justify-between gap-2">
<button
type="button"
className="flex items-center justify-between gap-1 rounded-full bg-primary-orange-50 bg-opacity-70 p-1 lg:gap-2 lg:p-2"
>
{indicators}
</button>
);
Expand Down
6 changes: 6 additions & 0 deletions src/utils/checkS3Url.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { S3_URL } from "@/constants/config";

// S3 URL 체크 함수
export const isValidS3Url = (url: string) => {
return url.startsWith(S3_URL);
};
Loading