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
Binary file added public/fallback.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions src/assets/icon/ic-calendar-clock.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 8 additions & 0 deletions src/assets/icon/ic-coins.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/assets/icon/ic-map-pin.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion src/components/layout/container/container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,22 @@ import { ElementType, ReactNode } from 'react';
interface Props {
as?: ElementType;
className?: string;
isPage?: boolean;
children: ReactNode;
}

export const Wrapper = ({ children }: { children: ReactNode }) => {
return <div className='flex min-h-screen flex-col'>{children}</div>;
};

const Container = ({ as: Component = 'div', className, children }: Props) => {
const Container = ({ as: Component = 'div', isPage = false, className, children }: Props) => {
return (
<Component
className={cn(
'relative z-[1]',
'mx-auto w-full max-w-[1028px] px-3',
'tablet:px-8',
isPage && "py-10 tablet:py-16" ,
className
)}
>
Expand Down
32 changes: 28 additions & 4 deletions src/components/ui/card/card.styles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ const cardHeading = cva('font-bold', {
defaultVariants: { size: 'md', status: 'open' },
});

const cardInfoLayout = cva('flex flex-nowrap items-start tablet:items-center gap-1.5');
const cardInfoLayout = cva('flex flex-nowrap items-center tablet:items-center gap-1.5');

const cardInfoText = cva('text-caption tablet:text-body-s', {
variants: {
Expand All @@ -34,7 +34,7 @@ const cardInfoText = cva('text-caption tablet:text-body-s', {
const cardInfoIcon = cva('', {
variants: {
status: {
open: 'bg-red-300',
open: 'bg-gray-700',
inactive: 'bg-gray-300',
},
},
Expand All @@ -43,9 +43,31 @@ const cardInfoIcon = cva('', {

const cardPayLayout = cva('flex items-center gap-x-3');

const cardBadge = cva('flex items-center gap-x-0.5 rounded-full');
const cardPayText = cva('font-bold text-modal tracking-wide', {
variants: {
status: {
open: 'text-black',
inactive: 'text-gray-300',
},
},
defaultVariants: { status: 'open' },
});
const cardBadge = cva(
'flex items-center gap-x-0.5 rounded-full px-2 py-1 tablet:py-2 tablet:px-3',
{
variants: {
status: {
post: 'absolute bottom-3 right-3 z-[1] border border-white',
notice: '',
},
},
defaultVariants: { status: 'notice' },
}
);

const cardBadgeText = cva('whitespace-nowrap text-caption text-white font-bold tablet:text-body-s');

const cardBadgeText = cva('whitespace-nowrap text-caption tablet:text-body-s');
const cardBadgeIcon = cva('self-start bg-white');

export const cardLayout = {
frame: cardFrame,
Expand All @@ -55,7 +77,9 @@ export const cardLayout = {
info: cardInfoText,
infoIcon: cardInfoIcon,
payLayout: cardPayLayout,
payText: cardPayText,
badge: cardBadge,
badgeText: cardBadgeText,
badgeIcon: cardBadgeIcon,
};
export type CardStatusVariant = VariantProps<typeof cardHeading>['status'];
44 changes: 44 additions & 0 deletions src/components/ui/card/cardBadge.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Icon } from '@/components/ui/icon';
import { calcPayIncreasePercent } from '@/lib/utils/calcPayIncrease';
import { cn } from '@/lib/utils/cn';
import { type CardVariant } from '@/types/notice';
import { cardLayout } from './card.styles';
interface CardBadgeProps {
variant: CardVariant;
hourlyPay?: number;
originalHourlyPay?: number;
}
const CardBadge = ({ variant, hourlyPay, originalHourlyPay }: CardBadgeProps) => {
if (!hourlyPay || !originalHourlyPay) return;

const payIncreasePercent = calcPayIncreasePercent(hourlyPay, originalHourlyPay);
const payIncreaseLabel =
payIncreasePercent && (payIncreasePercent > 100 ? '100% ์ด์ƒ' : `${payIncreasePercent}%`);
const badgeColorClass =
payIncreasePercent == null
? ''
: payIncreasePercent >= 50
? 'bg-red-500'
: payIncreasePercent >= 30
? 'bg-red-300'
: 'bg-red-200';

return (
<>
{payIncreasePercent !== null && (
<div className={cn(cardLayout.badge({ status: variant }), badgeColorClass)}>
<span className={cardLayout.badgeText()}>๊ธฐ์กด ์‹œ๊ธ‰ {payIncreaseLabel}</span>
<Icon
iconName='arrowUp'
iconSize='sm'
bigScreenSize='rg'
decorative
className={cardLayout.badgeIcon()}
/>
</div>
)}
</>
);
};

export default CardBadge;
53 changes: 53 additions & 0 deletions src/components/ui/card/cardImage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { cn } from '@/lib/utils/cn';
import { type CardVariant } from '@/types/notice';
import Image from 'next/image';
import { ReactNode, useState } from 'react';
import { cardLayout } from './card.styles';

interface CardImageProps {
variant: CardVariant;
src?: string;
alt?: string;
className?: string;
children?: ReactNode;
}
const FALLBACK_SRC = '/fallback.png';
const CardImage = ({ variant, src, alt, className, children }: CardImageProps) => {
const [imgSrc, setImgSrc] = useState(src ?? FALLBACK_SRC);

const handleError = () => {
setImgSrc(FALLBACK_SRC);
};

const isValidSrc =
typeof imgSrc === 'string' &&
(imgSrc.startsWith('https://bootcamp-project-api.s3.ap-northeast-2.amazonaws.com') ||
imgSrc.startsWith('https://picsum.photos'));
return (
<div
className={cn(
cardLayout.imageWrapper(),
variant === 'notice'
? 'h-[180px] w-full tablet:h-[360px] desktop:h-auto'
: 'h-[120px] tablet:h-[160px]',
className
)}
>
<Image
src={isValidSrc ? imgSrc : FALLBACK_SRC}
alt={`${alt} ๊ฐ€๊ฒŒ ์ด๋ฏธ์ง€`}
fill
sizes={
variant === 'notice'
? '(max-width: 744px) 630px, 540px'
: '(max-width: 744px) 120px, 160px'
}
className='object-cover'
onError={handleError}
priority
/>
{children}
</div>
);
};
export default CardImage;
27 changes: 27 additions & 0 deletions src/components/ui/card/cardInfo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Icon } from '@/components/ui/icon';
import { IconName } from '@/constants/icon';
import { ReactNode } from 'react';
import { cardLayout, CardStatusVariant } from './card.styles';

interface CardInfo {
iconName: IconName;
status?: CardStatusVariant;
ariaLabel: string;
children?: ReactNode;
}

const CardInfo = ({ iconName, ariaLabel, status, children }: CardInfo) => {
return (
<li className={cardLayout.infoLayout()}>
<Icon
iconName={iconName}
iconSize='rg'
ariaLabel={ariaLabel}
className={cardLayout.infoIcon({ status: status })}
/>
<p className={cardLayout.info({ status: status })}>{children}</p>
</li>
);
};

export default CardInfo;
20 changes: 0 additions & 20 deletions src/components/ui/card/notice/components/noticeImage.tsx

This file was deleted.

49 changes: 17 additions & 32 deletions src/components/ui/card/notice/components/noticeInfo.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,14 @@
import { cardLayout } from '@/components/ui/card/card.styles';
import { NoticeVariant } from '@/components/ui/card/notice/notice';
import CardBadge from '@/components/ui/card/cardBadge';
import CardInfo from '@/components/ui/card/cardInfo';
import {
badgeText,
noticeButton,
noticeInfoWrapper,
noticeLabel,
payBadge,
} from '@/components/ui/card/notice/notice.styles';
import { Icon } from '@/components/ui/icon';
import { calcPayIncreasePercent } from '@/lib/utils/calcPayIncrease';
import { getTime } from '@/lib/utils/dateFormatter';
import { formatNumber } from '@/lib/utils/formatNumber';
import { NoticeCard } from '@/types/notice';
import { type NoticeCard, type NoticeVariant } from '@/types/notice';
import { ReactNode } from 'react';
import NoticeHeader from './noticeHeader';

Expand All @@ -36,10 +33,9 @@ const NoticeInfo = <T extends Partial<NoticeCard>>({
address1,
shopDescription,
} = value;
const payIncreasePercent = calcPayIncreasePercent(hourlyPay ?? 0, originalHourlyPay ?? 0);

const { date, startTime, endTime } = getTime(startsAt ?? '', workhour ?? 0);
const payIncreaseLabel =
payIncreasePercent && (payIncreasePercent > 100 ? '100% ์ด์ƒ' : `${payIncreasePercent}%`);

return (
<div className={noticeInfoWrapper()}>
<ul className='flex flex-col gap-3'>
Expand All @@ -48,40 +44,29 @@ const NoticeInfo = <T extends Partial<NoticeCard>>({
<li>
<p className={noticeLabel()}>์‹œ๊ธ‰</p>
<div className={cardLayout.payLayout()}>
<span className='text-heading-s font-bold tracking-wide'>
<span className={cardLayout.payText({ className: 'text-heading-s' })}>
{formatNumber(hourlyPay ?? 0)}์›
</span>
{payIncreasePercent !== null && (
<div className={payBadge()}>
<span className={badgeText()}>๊ธฐ์กด ์‹œ๊ธ‰ {payIncreaseLabel}</span>
<Icon
iconName='arrowUp'
iconSize='sm'
bigScreenSize='rg'
decorative
className='self-start bg-white'
/>
</div>
)}
<CardBadge
variant='notice'
hourlyPay={hourlyPay}
originalHourlyPay={originalHourlyPay}
/>
</div>
</li>
<li className={cardLayout.infoLayout()}>
<Icon iconName='clock' iconSize='sm' ariaLabel='๊ทผ๋ฌด์‹œ๊ฐ„' className='bg-red-300' />
<p className={cardLayout.info()}>
{date} {startTime} ~ {endTime} ({workhour}์‹œ๊ฐ„)
</p>
</li>
<CardInfo iconName='calendarClock' ariaLabel='๊ทผ๋ฌด์‹œ๊ฐ„'>
{date} {startTime} ~ {endTime} ({workhour}์‹œ๊ฐ„)
</CardInfo>
</>
)}
{variant === 'shop' && (
<li>
<NoticeHeader name={name} category={category} className='mt-4' />
</li>
)}
<li className={cardLayout.infoLayout()}>
<Icon iconName='map' iconSize='sm' ariaLabel='๊ทผ๋ฌด์œ„์น˜' className='bg-red-300' />
<p className={cardLayout.info()}>{address1}</p>
</li>
<CardInfo iconName='mapPin' ariaLabel='๊ทผ๋ฌด์œ„์น˜'>
{address1}
</CardInfo>
<li className='text-body-l'>{shopDescription}</li>
</ul>
<div className={noticeButton()}>{buttonComponent}</div>
Expand Down
10 changes: 5 additions & 5 deletions src/components/ui/card/notice/components/renderNotice.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { noticeFrame } from '@/components/ui/card/notice/notice.styles';
import { NoticeCard } from '@/types/notice';
import CardImage from '@/components/ui/card/cardImage';
import { descriptionWrapper, noticeFrame } from '@/components/ui/card/notice/notice.styles';
import { type NoticeCard } from '@/types/notice';
import { ReactNode } from 'react';
import NoticeHeader from './noticeHeader';
import NoticeImage from './noticeImage';
import NoticeInfo from './noticeInfo';

interface RenderNoticeProps<T extends Partial<NoticeCard>> {
Expand All @@ -26,10 +26,10 @@ const RenderNotice = <T extends Partial<NoticeCard>>({
<>
<NoticeHeader name={name} category={category} />
<section className={noticeFrame()}>
<NoticeImage name={name} imageUrl={imageUrl} />
<CardImage variant='notice' src={imageUrl} alt={name} />
<NoticeInfo variant={variant} value={value} buttonComponent={buttonComponent} />
</section>
<section className='flex flex-col gap-3 rounded-xl bg-gray-100 p-8'>
<section className={descriptionWrapper()}>
<h3 className='text-body-l font-bold'>๊ณต๊ณ  ์„ค๋ช…</h3>
<p className='text-body-l'>{description}</p>
</section>
Expand Down
4 changes: 2 additions & 2 deletions src/components/ui/card/notice/components/renderShop.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { cardLayout } from '@/components/ui/card/card.styles';
import CardImage from '@/components/ui/card/cardImage';
import { noticeFrame } from '@/components/ui/card/notice/notice.styles';
import { cn } from '@/lib/utils/cn';
import { NoticeShopCard } from '@/types/shop';
import { ReactNode } from 'react';
import NoticeImage from './noticeImage';
import NoticeInfo from './noticeInfo';

type ShopCard = Omit<NoticeShopCard, 'shopId'>;
Expand All @@ -26,7 +26,7 @@ const RenderShop = ({ items, buttonComponent }: RenderShopProps) => {
<>
<h2 className={cardLayout.heading({ size: 'lg' })}>๋‚ด ๊ฐ€๊ฒŒ</h2>
<section className={cn(noticeFrame(), 'bg-red-100')}>
<NoticeImage name={name} imageUrl={imageUrl} />
<CardImage variant='notice' src={imageUrl} alt={name} />
<NoticeInfo variant={variant} value={value} buttonComponent={buttonComponent} />
</section>
</>
Expand Down
2 changes: 1 addition & 1 deletion src/components/ui/card/notice/mockData/noticeWrapper.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Button } from '@/components/ui/button';
import Notice from '@/components/ui/card/notice/notice';
import { getNoticeStatus } from '@/lib/utils/getNoticeStatus';
import type { NoticeCard } from '@/types/notice';
import { type NoticeCard } from '@/types/notice';
import { NoticeShopCard } from '@/types/shop';
import Link from 'next/link';
import mockResponse from './mockData.json';
Expand Down
Loading