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
2 changes: 1 addition & 1 deletion src/app/(pages)/albaList/components/SearchSection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ export default function SearchSection() {
return (
<form onSubmit={handleSubmit} className="w-full">
<div className="mx-auto flex items-center justify-between gap-4">
<div className="w-[270px] md:w-[500px] lg:w-[700px] xl:w-[900px] 2xl:w-[1100px]">
<div className="w-[270px] md:w-[500px] lg:w-[700px] xl:w-[900px]">
<SearchInput
value={keyword}
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setKeyword(e.target.value)}
Expand Down
2 changes: 1 addition & 1 deletion src/app/(pages)/albaList/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { Suspense } from "react";

export default function AlbaListLayout({ children }: { children: React.ReactNode }) {
return (
<div className="mx-auto max-w-screen-2xl px-4 py-8">
<div className="mx-auto max-w-screen-xl px-4 py-8">
<Suspense
fallback={
<div className="flex h-[calc(100vh-200px)] items-center justify-center">
Expand Down
11 changes: 6 additions & 5 deletions src/app/(pages)/albaList/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import SearchSection from "./components/SearchSection";
import { useUser } from "@/hooks/queries/user/me/useUser";
import Link from "next/link";
import { IoAdd } from "react-icons/io5";
import { userRoles } from "@/constants/userRoles";

const FORMS_PER_PAGE = 10;

Expand All @@ -20,7 +21,7 @@ export default function AlbaList() {
const pathname = usePathname();
const searchParams = useSearchParams();
const { user } = useUser();
const isOwner = user?.role === "owner";
const isOwner = user?.role === userRoles.OWNER;

// URL 쿼리 파라미터에서 필터 상태와 키워드 가져오기
const isRecruiting = searchParams.get("isRecruiting");
Expand Down Expand Up @@ -124,10 +125,10 @@ export default function AlbaList() {
</div>

{/* 메인 콘텐츠 영역 */}
<div className="w-full pt-[224px]">
<div className="w-full pt-[132px]">
{/* 폼 만들기 버튼 - 고정 위치 */}
{isOwner && (
<div className="fixed bottom-[28%] right-8 z-[9999] translate-y-1/2 md:right-12 lg:right-16 xl:right-20">
<div className="fixed bottom-[50%] right-4 z-[9999] translate-y-1/2">
<Link
href="/addForm"
className="flex items-center gap-2 rounded-lg bg-[#FFB800] px-4 py-3 text-base font-semibold text-white shadow-lg transition-all hover:bg-[#FFA800] md:px-6 md:text-lg"
Expand All @@ -143,8 +144,8 @@ export default function AlbaList() {
<p className="text-grayscale-500">등록된 알바 공고가 없습니다.</p>
</div>
) : (
<div className="mx-auto mt-4 w-full max-w-screen-2xl px-4 md:px-6 lg:px-8">
<div className="flex flex-wrap items-center justify-center gap-6">
<div className="mx-auto mt-4 w-full max-w-screen-xl px-3">
<div className="flex flex-wrap justify-start gap-6">
{data?.pages.map((page) => (
<React.Fragment key={page.nextCursor}>
{page.data.map((form) => (
Expand Down
50 changes: 34 additions & 16 deletions src/app/components/card/cardList/AlbaListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import useModalStore from "@/store/modalStore";
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";

/**
* 알바폼 리스트 아이템 컴포넌트
Expand All @@ -31,6 +33,7 @@ const AlbaListItem = ({
const [showDropdown, setShowDropdown] = useState(false); // 드롭다운 메뉴 표시 상태
const [currentImageIndex, setCurrentImageIndex] = useState(0); // 현재 이미지 인덱스
const dropdownRef = useRef<HTMLDivElement>(null); // 드롭다운 메뉴 참조
const [imageError, setImageError] = useState(false);

// 모집 상태 및 D-day 계산
const recruitmentStatus = getRecruitmentStatus(recruitmentEndDate);
Expand Down Expand Up @@ -81,22 +84,37 @@ const AlbaListItem = ({
});
};

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

return (
<div className="relative h-[360px] w-[327px] overflow-hidden rounded-xl border border-grayscale-200 bg-white shadow-md transition-transform duration-300 hover:scale-[1.02] lg:h-[536px] lg:w-[477px]">
<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]">
{/* 이미지 슬라이더 영역 */}
<div className="relative h-[200px] overflow-hidden rounded-t-xl lg:h-[310px]">
{/* 현재 이미지 */}
{imageUrls[currentImageIndex] && (
<Image
src={imageUrls[currentImageIndex]}
alt={`Recruit Image ${currentImageIndex + 1}`}
fill
className="object-cover transition-opacity duration-300"
/>
<div className="relative h-[200px] overflow-hidden rounded-t-xl lg:h-[240px]">
{imageUrls[currentImageIndex] && !imageError ? (
isValidS3Url(imageUrls[currentImageIndex]) ? (
<Image
src={imageUrls[currentImageIndex]}
alt={`Recruit Image ${currentImageIndex + 1}`}
fill
className="object-cover transition-opacity duration-300"
onError={() => setImageError(true)}
/>
) : (
<div className="flex h-full w-full items-center justify-center bg-grayscale-100">
<MdOutlineImage className="size-20 text-grayscale-400" />
</div>
)
) : (
<div className="flex h-full w-full items-center justify-center bg-grayscale-100">
<MdOutlineImage className="size-20 text-grayscale-400" />
</div>
)}

{/* 이미지 인디케이터 */}
{imageUrls.length > 1 && (
{/* 이미지 인디케이터 - 유효한 이미지가 2개 이상이고 에러가 없을 때만 표시 */}
{imageUrls.filter((url) => isValidS3Url(url)).length > 1 && !imageError && (
Comment on lines +116 to +117
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

<div className="absolute bottom-4 left-1/2 -translate-x-1/2">
<Indicator
imageCount={imageUrls.length}
Expand All @@ -108,7 +126,7 @@ const AlbaListItem = ({
</div>

{/* 콘텐츠 영역 */}
<div className="relative flex h-[140px] flex-col justify-between p-4 lg:h-[226px] lg:p-6">
<div className="relative flex h-[140px] flex-col justify-between p-2 lg:h-[160px]">
{/* 상단 영역 */}
<div className="flex flex-col gap-4">
{/* 상태 표시 영역 (공개여부, 모집상태, 날짜) */}
Expand All @@ -117,7 +135,7 @@ const AlbaListItem = ({
<div className="flex items-center justify-between">
<Chip label={isPublic ? "공개" : "비공개"} variant={isPublic ? "positive" : "negative"} />
<Chip label={recruitmentStatus} variant={recruitmentStatus === "모집 중" ? "positive" : "negative"} />
<span className="text-xs font-medium text-grayscale-500 md:inline">
<span className="text-xs font-medium tracking-tighter text-grayscale-500 md:inline lg:text-sm">
{formatRecruitDate(recruitmentStartDate, true)} ~ {formatRecruitDate(recruitmentEndDate, true)}
</span>
</div>
Expand Down Expand Up @@ -152,11 +170,11 @@ const AlbaListItem = ({
</div>

{/* 제목 */}
<div className="text-grayscale-900 truncate text-base font-bold lg:text-lg">{title}</div>
<div className="text-grayscale-900 truncate pl-2 text-base font-bold lg:text-lg">{title}</div>
</div>

{/* 통계 정보 영역 - mt-auto 제거하고 부모 컨테이너에 justify-between 추가 */}
<div className="text-grayscale-700 mt-4 flex h-[50px] items-center justify-between rounded-2xl border border-grayscale-100 p-2 text-sm lg:text-base">
<div className="text-grayscale-700 mt-4 flex h-[50px] items-center justify-between rounded-2xl border border-grayscale-100 text-sm lg:text-base">
<div className="flex flex-1 items-center justify-center">
<span className="font-medium">지원자 {applyCount}명</span>
</div>
Expand Down
54 changes: 36 additions & 18 deletions src/app/components/card/cardList/ScrapListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import Indicator from "../../pagination/Indicator";
import { FormListType } from "@/types/response/form";
import { useFormScrap } from "@/hooks/queries/form/useFormScap";
import { useRouter } from "next/navigation";
import { MdOutlineImage } from "react-icons/md";
import { S3_URL } from "@/constants/config";

/**
* 알바폼 스크랩 리스트 아이템 컴포넌트
Expand All @@ -30,6 +32,7 @@ const ScrapListItem = ({
const [showDropdown, setShowDropdown] = useState(false); // 드롭다운 메뉴 표시 상태
const [currentImageIndex, setCurrentImageIndex] = useState(0); // 현재 표시 중인 이미지 인덱스
const dropdownRef = useRef<HTMLDivElement>(null); // 드롭다운 메뉴 참조
const [imageError, setImageError] = useState(false);

// 모집 상태 및 D-day 계산
const recruitmentStatus = getRecruitmentStatus(recruitmentEndDate);
Expand Down Expand Up @@ -80,22 +83,37 @@ const ScrapListItem = ({
});
};

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

return (
<div className="relative h-[360px] w-[327px] overflow-hidden rounded-xl border border-grayscale-200 bg-white shadow-md transition-transform duration-300 hover:scale-[1.02] lg:h-[536px] lg:w-[477px]">
<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]">
{/* 이미지 슬라이더 영역 */}
<div className="relative h-[200px] overflow-hidden rounded-t-xl lg:h-[310px]">
{/* 현재 이미지 */}
{imageUrls[currentImageIndex] && (
<Image
src={imageUrls[currentImageIndex]}
alt={`Recruit Image ${currentImageIndex + 1}`}
fill
className="object-cover transition-opacity duration-300"
/>
<div className="relative h-[200px] overflow-hidden rounded-t-xl lg:h-[240px]">
{imageUrls[currentImageIndex] && !imageError ? (
isValidS3Url(imageUrls[currentImageIndex]) ? (
<Image
src={imageUrls[currentImageIndex]}
alt={`Recruit Image ${currentImageIndex + 1}`}
fill
className="object-cover transition-opacity duration-300"
onError={() => setImageError(true)}
/>
) : (
<div className="flex h-full w-full items-center justify-center bg-grayscale-100">
<MdOutlineImage className="size-20 text-grayscale-400" />
</div>
)
) : (
<div className="flex h-full w-full items-center justify-center bg-grayscale-100">
<MdOutlineImage className="size-20 text-grayscale-400" />
</div>
)}

{/* 이미지 인디케이터 */}
{imageUrls.length > 1 && (
{/* 이미지 인디케이터 - 유효한 이미지가 2개 이상이고 에러가 없을 때만 표시 */}
{imageUrls.filter((url) => isValidS3Url(url)).length > 1 && !imageError && (
<div className="absolute bottom-4 left-1/2 -translate-x-1/2">
<Indicator
imageCount={imageUrls.length}
Expand All @@ -107,7 +125,7 @@ const ScrapListItem = ({
</div>

{/* 콘텐츠 영역 */}
<div className="relative flex h-[140px] flex-col justify-between p-4 lg:h-[226px] lg:p-6">
<div className="relative flex h-[140px] flex-col justify-between p-2 lg:h-[160px]">
{/* 상단 영역 */}
<div className="flex flex-col gap-4">
{/* 상태 표시 영역 (공개여부, 모집상태, 날짜) */}
Expand All @@ -116,7 +134,7 @@ const ScrapListItem = ({
<div className="flex items-center justify-between">
<Chip label={isPublic ? "공개" : "비공개"} variant={isPublic ? "positive" : "negative"} />
<Chip label={recruitmentStatus} variant={recruitmentStatus === "모집 중" ? "positive" : "negative"} />
<span className="text-xs font-medium text-grayscale-500 md:inline">
<span className="text-xs font-medium tracking-tighter text-grayscale-500 md:inline lg:text-sm">
{formatRecruitDate(recruitmentStartDate, true)} ~ {formatRecruitDate(recruitmentEndDate, true)}
</span>
</div>
Expand All @@ -133,7 +151,7 @@ const ScrapListItem = ({
{showDropdown && (
<div className="absolute right-0 top-8 z-10 w-32 rounded-lg border border-grayscale-200 bg-white py-2 shadow-lg">
<button
className="w-full px-4 py-2 text-left text-sm hover:bg-primary-orange-100 disabled:opacity-50"
className="w-full px-4 py-2 text-left text-sm hover:bg-primary-orange-100"
onClick={handleFormApplication}
>
지원하기
Expand All @@ -151,11 +169,11 @@ const ScrapListItem = ({
</div>

{/* 제목 */}
<div className="text-grayscale-900 truncate text-base font-bold lg:text-lg">{title}</div>
<div className="text-grayscale-900 truncate pl-2 text-base font-bold lg:text-lg">{title}</div>
</div>

{/* 통계 정보 영역 - mt-auto 제거하고 부모 컨테이너에 justify-between 추가 */}
<div className="text-grayscale-700 mt-4 flex h-[50px] items-center justify-between rounded-2xl border border-grayscale-100 p-2 text-sm lg:text-base">
{/* 통계 정보 영역 */}
<div className="text-grayscale-700 mt-4 flex h-[50px] items-center justify-between rounded-2xl border border-grayscale-100 text-sm lg:text-base">
<div className="flex flex-1 items-center justify-center">
<span className="font-medium">지원자 {applyCount}명</span>
</div>
Expand Down
6 changes: 3 additions & 3 deletions src/app/components/chip/Chip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ interface ChipProps {
const Chip: React.FC<ChipProps> = ({ label = "Label", variant, border, icon, textStyle = "" }: ChipProps) => {
const wrapperStyle = "rounded flex items-center justify-center min-w-[60px] m-1";
const paddingStyle = icon
? "px-[10px] py-1 md:px-[14.5px] md:py-1 lg:px-[10px] lg:py-[6px]"
: "px-2 py-1 md:px-[10px] lg:py-[6px] lg:px-3";
? "px-[8px] py-1 md:px-[12px] md:py-1 lg:px-[8px] lg:py-[6px]"
: "px-2 py-1 md:px-2 lg:py-[6px] lg:px-2";
const variantStyle =
variant === "positive" ? "bg-primary-orange-50 text-primary-orange-300" : "bg-line-100 text-grayscale-200";
const baseTextStyle =
"text-xs leading-[20px] md:leading-[24px] lg:text-base lg:leading-[26px] font-medium tracking-tight";
"text-xs leading-[18px] md:leading-[20px] lg:text-sm lg:leading-[22px] font-medium tracking-tighter whitespace-nowrap";
const borderStyle = border ? "border border-primary-orange-100" : "";
const iconStyle = "flex items-center justify-center";

Expand Down
8 changes: 4 additions & 4 deletions src/app/stories/design-system/pages/albaList/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,9 +100,9 @@ const AlbaList: React.FC<AlbaListProps> = () => {
</div>

{/* 메인 콘텐츠 영역 */}
<div className="w-full pt-[224px]">
<div className="w-full pt-[132px]">
{/* 폼 만들기 버튼 - 고정 위치 */}
<div className="fixed bottom-[28%] right-8 z-[9999] translate-y-1/2 md:right-12 lg:right-16 xl:right-20">
<div className="fixed bottom-[50%] right-4 z-[9999] translate-y-1/2">
<Link
href="/addForm"
className="flex items-center gap-2 rounded-lg bg-[#FFB800] px-4 py-3 text-base font-semibold text-white shadow-lg transition-all hover:bg-[#FFA800] md:px-6 md:text-lg"
Expand All @@ -117,8 +117,8 @@ const AlbaList: React.FC<AlbaListProps> = () => {
<p className="text-grayscale-500">등록된 알바 공고가 없습니다.</p>
</div>
) : (
<div className="mx-auto mt-4 w-full max-w-screen-2xl px-4 md:px-6 lg:px-8">
<div className="flex flex-wrap items-center justify-center gap-6">
<div className="mx-auto mt-4 w-full max-w-screen-xl px-3">
<div className="flex flex-wrap justify-start gap-6">
{items.map((form) => (
<div key={form.id}>
<AlbaListItem {...form} />
Expand Down
2 changes: 2 additions & 0 deletions src/constants/config.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export const API_URL = process.env.NEXT_PUBLIC_API_URL;

export const TEAM_NAME = process.env.NEXT_PUBLIC_TEAM_ID;

export const S3_URL = "https://sprint-fe-project.s3.ap-northeast-2.amazonaws.com";
5 changes: 5 additions & 0 deletions src/utils/workDayFormatter.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { workDayOptions } from "@/constants/workDayOptions";

export const formatRecruitDate = (date: Date, isMd: boolean = false) => {
// 유효한 Date 객체인지 확인
if (!(date instanceof Date) || isNaN(date.getTime())) {
return new Date().toLocaleDateString("ko-KR", { year: "numeric", month: "2-digit", day: "2-digit" });
}

Comment on lines +4 to +8
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

const year = isMd ? date.getFullYear().toString() : date.getFullYear().toString().slice(2);
const month = String(date.getMonth() + 1).padStart(2, "0");
const day = String(date.getDate()).padStart(2, "0");
Expand Down
Loading