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
Expand Up @@ -74,15 +74,6 @@ export default function WorkConditionSection() {
const handleWageChange = (e: ChangeEvent<HTMLInputElement>) => {
const value = e.target.value.replace(/,/g, "");
const numericValue = Number(value);

// 최저시급 미만으로 설정 시도할 경우
if (numericValue < MINIMUM_WAGE) {
toast.error(`최저시급(${formatMoney(MINIMUM_WAGE.toString())}원) 이상을 입력해주세요.`);
setDisplayWage(formatMoney(MINIMUM_WAGE.toString()));
setValue("hourlyWage", MINIMUM_WAGE);
return;
}

setValue("hourlyWage", numericValue);
setDisplayWage(formatMoney(value));
};
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-2xl 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";

return (
<div className={cn(isForm ? FormStyle : DetailStyle)}>
Expand Down
17 changes: 9 additions & 8 deletions src/app/(pages)/albaList/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { IoAdd } from "react-icons/io5";
import { userRoles } from "@/constants/userRoles";
import FloatingBtn from "@/app/components/button/default/FloatingBtn";
import LoadingSpinner from "@/app/components/loading-spinner/LoadingSpinner";
import ContentSection from "@/app/components/layout/ContentSection";

const FORMS_PER_PAGE = 10;

Expand Down Expand Up @@ -102,19 +103,19 @@ export default function AlbaList() {
return (
<div className="flex min-h-screen flex-col items-center">
{/* 검색 섹션과 필터 드롭다운을 고정 위치로 설정 */}
<div className="fixed left-0 right-0 top-16 z-40 bg-white shadow-sm">
<div className="fixed left-0 right-0 top-16 z-30 bg-white shadow-sm">
{/* 검색 섹션 */}
<div className="w-full border-b border-grayscale-100">
<div className="mx-auto flex max-w-screen-2xl flex-col gap-4 px-4 py-4 md:px-6 lg:px-8">
<div className="w-full border-b border-line-100">
<div className="mx-auto flex max-w-screen-xl flex-col gap-4 px-4 py-4 md:px-6 lg:px-8">
<div className="flex items-center justify-between">
<SearchSection />
</div>
</div>
</div>

{/* 필터 드롭다운 섹션 */}
<div className="w-full border-b border-grayscale-100">
<div className="mx-auto flex max-w-screen-2xl items-center justify-between gap-2 px-4 py-4 md:px-6 lg:px-8">
<div className="w-full border-b border-line-100">
<div className="mx-auto flex max-w-screen-xl items-center justify-between gap-2 px-4 py-4 md:px-6 lg:px-8">
<FilterDropdown
options={filterRecruitingOptions.map((option) => option.label)}
initialValue={getInitialRecruitingValue(isRecruiting)}
Expand All @@ -131,7 +132,7 @@ export default function AlbaList() {
<div className="w-full pt-[132px]">
{/* 폼 만들기 버튼 - 고정 위치 */}
{isOwner && (
<Link href="/addform" className="fixed bottom-[50%] right-[5%] z-[9999] translate-y-1/2">
<Link href="/addform" className="fixed bottom-[50%] right-[5%] z-30 translate-y-1/2">
<FloatingBtn icon={<IoAdd className="size-6" />} variant="orange">
폼 만들기
</FloatingBtn>
Expand All @@ -144,7 +145,7 @@ export default function AlbaList() {
</div>
) : (
<div className="mx-auto mt-4 w-full max-w-screen-xl px-3">
<div className="flex flex-wrap justify-start gap-6">
<ContentSection>
{data?.pages.map((page) => (
<React.Fragment key={page.nextCursor}>
{page.data.map((form) => (
Expand All @@ -156,7 +157,7 @@ export default function AlbaList() {
))}
</React.Fragment>
))}
</div>
</ContentSection>

{/* 무한 스크롤 트리거 영역 */}
<div ref={ref} className="h-4 w-full">
Expand Down
251 changes: 251 additions & 0 deletions src/app/(pages)/albaTalk/[albatalkId]/edit/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,251 @@
"use client";

import React, { useState, useCallback, useEffect } from "react";
import { useForm, SubmitHandler, Controller } from "react-hook-form";
import { useRouter } from "next/navigation";
import Button from "@/app/components/button/default/Button";
import BaseInput from "@/app/components/input/text/BaseInput";
import ImageInputPlaceHolder from "@/app/components/input/file/ImageInput/ImageInputPlaceHolder";
import { useEditPost } from "@/hooks/queries/post/useEditPost";
import { usePostDetail } from "@/hooks/queries/post/usePostDetail";
import axios from "axios";
import DotLoadingSpinner from "@/app/components/loading-spinner/DotLoadingSpinner";
import { PostSchema } from "@/schemas/postSchema";
import toast from "react-hot-toast";
import LoadingSpinner from "@/app/components/loading-spinner/LoadingSpinner";

interface ImageInputType {
file: File | null;
url: string;
id: string;
}

export default function EditTalk({ params }: { params: { albatalkId: string } }) {
const [imageList, setImageList] = useState<ImageInputType[]>([]);
const router = useRouter();
const postId = params.albatalkId;

const { data: post, isLoading, error } = usePostDetail(postId);
const { mutate: editPost, isPending } = useEditPost(postId);

const {
control,
handleSubmit,
setValue,
formState: { errors },
reset,
} = useForm<PostSchema>({
defaultValues: {
title: "",
content: "",
imageUrl: "",
},
});

// 게시글 데이터로 폼 초기화
useEffect(() => {
if (post) {
reset({
title: post.title,
content: post.content,
imageUrl: post.imageUrl || "",
});

if (post.imageUrl) {
setImageList([
{
file: null,
url: post.imageUrl,
id: "initial-image",
},
]);
} else {
setImageList([]);
}
}
}, [post, reset]);

// 에러 처리
useEffect(() => {
if (error) {
toast.error("게시글을 불러오는데 실패했습니다.");
router.push("/albatalk");
}
}, [error, router]);

const uploadImage = useCallback(async (file: File): Promise<string> => {
const formData = new FormData();
formData.append("image", file);

try {
const response = await axios.post("/api/images/upload", formData, {
headers: {
"Content-Type": "multipart/form-data",
},
});

if (response.status === 201 && response.data?.url) {
return response.data.url;
}

throw new Error("이미지 업로드에 실패했습니다.");
} catch (error) {
toast.error("이미지 업로드에 실패했습니다.");
console.error("이미지 업로드 실패:", error);
throw error;
}
}, []);

const handleImagesChange = useCallback(
(newImages: ImageInputType[]) => {
setImageList(newImages);
const imageUrls = newImages.map((img) => img.url).join(",");
setValue("imageUrl", imageUrls);
},
[setValue]
);

const onSubmit: SubmitHandler<PostSchema> = async (data) => {
try {
if (!data.title) {
toast.error("제목을 입력하세요.");
return;
}

if (!data.content) {
toast.error("내용을 입력하세요.");
return;
}

const postData: PostSchema = {
title: data.title,
content: data.content,
imageUrl: imageList.map((img) => img.url).join(","),
};

editPost(postData, {
onSuccess: () => {
router.push(`/albatalk/${postId}`);
},
});
} catch (error) {
console.error("게시글 수정 실패:", error);
}
};

if (isLoading) {
return (
<div className="flex h-screen items-center justify-center">
<LoadingSpinner />
</div>
);
}

if (!post) return null;

return (
<div className="flex w-full flex-col bg-grayscale-50 px-6 font-nexon lg:px-10">
<div className="w-full max-w-[1480px] rounded-md bg-white">
<div className="mb-[40px] flex h-[58px] w-full items-center justify-between border-b border-line-100 md:h-[78px] lg:h-[126px]">
<div className="flex items-center text-[18px] font-semibold md:text-[20px] lg:text-[32px]">
게시글 수정하기
</div>
<div className="hidden space-x-1 font-semibold md:flex md:space-x-2 lg:space-x-4">
<Button
color="gray"
className="text-grayscale-50md:h-[46px] text-white md:w-[108px] md:text-[14px] lg:h-[58px] lg:w-[180px] lg:text-[18px]"
onClick={() => router.push("/albaTalk")}
>
취소
</Button>
<Button
className="text-grayscale-50 md:h-[46px] md:w-[108px] md:text-[14px] lg:h-[58px] lg:w-[180px] lg:text-[18px]"
onClick={handleSubmit(onSubmit)}
disabled={isPending}
>
{isPending ? <DotLoadingSpinner /> : "수정하기"}
</Button>
</div>
</div>
<form className="mx-auto max-w-[1432px] space-y-6 md:space-y-8" onSubmit={handleSubmit(onSubmit)}>
<div className="w-full">
<label
htmlFor="title"
className="mb-2 flex items-center text-[16px] font-medium text-black-300 md:text-[18px] lg:text-[20px]"
>
제목<span className="ml-1 text-primary-orange-300">*</span>
</label>
<Controller
name="title"
control={control}
rules={{ required: "제목을 입력하세요." }}
render={({ field }) => (
<BaseInput
{...field}
type="text"
variant="white"
name="title"
size="w-full h-[52px] md:h-[54px] lg:h-[64px]"
placeholder="제목을 입력하세요"
errormessage={errors.title?.message}
/>
)}
/>
</div>

<div className="w-full">
<label
htmlFor="content"
className="mb-2 flex items-center text-[16px] font-medium text-black-300 md:text-[18px] lg:text-[20px]"
>
내용<span className="ml-1 text-primary-orange-300">*</span>
</label>
<Controller
name="content"
control={control}
rules={{ required: "내용을 입력하세요." }}
render={({ field }) => (
<textarea
{...field}
className="h-[240px] w-full resize-none rounded-lg bg-background-200 p-[14px] text-[16px] placeholder:text-grayscale-400 focus:outline-none focus:ring-2 focus:ring-primary-orange-300 md:text-[18px] lg:text-[20px]"
placeholder="내용을 입력하세요"
/>
)}
/>
</div>

<div className="w-full">
<label
htmlFor="image"
className="mb-2 block text-[16px] font-medium text-black-300 md:text-[18px] lg:text-[20px]"
>
이미지
</label>
<ImageInputPlaceHolder
onImageUpload={uploadImage}
onImagesChange={handleImagesChange}
initialImages={imageList}
/>
</div>
</form>
</div>
{/* 모바일 버전 버튼 */}
<div className="flex w-full flex-col items-center justify-center space-y-2 rounded-t-lg bg-white py-10 font-semibold md:hidden">
<Button
color="gray"
className="mb-2 h-[58px] w-full rounded-[8px] bg-grayscale-100 text-white hover:bg-grayscale-200"
onClick={() => router.push("/albaTalk")}
>
취소
</Button>
<Button
className="h-[58px] w-full rounded-[8px] bg-primary-orange-300 text-white hover:bg-orange-400"
onClick={handleSubmit(onSubmit)}
disabled={isPending}
>
{isPending ? <DotLoadingSpinner /> : "수정하기"}
</Button>
</div>
</div>
);
}
2 changes: 1 addition & 1 deletion src/app/(pages)/albaTalk/[albatalkId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export default function PostDetailPage() {
const { albatalkId } = useParams();

return (
<div className="mx-auto flex w-full min-w-[375px] flex-col items-center px-4 lg:w-[1024px] xl:w-[1480px]">
<div className="mx-auto flex w-full min-w-[375px] max-w-screen-xl flex-col items-center px-4">
<PostDetailSection postId={albatalkId.toString()} />
<CommentsSection postId={albatalkId.toString()} />
</div>
Expand Down
Loading
Loading