-
Notifications
You must be signed in to change notification settings - Fork 4
✨ feat: Notice 컴포넌트 제작 #38 #50
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 18 commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
e19e3af
♻️ refactor: post 매번 계산하는 로직이 아니므로 memo 제거 및 href 를 id 값을 받아서 사용하도록 변…
sohyun0 dde62bd
♻️ refactor: href 를 id 값을 받아서 사용하도록 변경 #38
sohyun0 99b3425
Merge branch 'develop' into feat/#38-post
sohyun0 d5b31c2
♻️ refactor: 중복된 navVariant 삭제
sohyun0 1858d5d
Merge remote-tracking branch 'upstream/develop' into feat/#38-post
sohyun0 2cd453b
🔧 chore: 파일위치 변경 및 예제 파일 삭제
sohyun0 cc60a7e
💄 design: post card 컴포넌트 스타일 작성
sohyun0 fba8a8f
✨ feat: notice card 컴포넌트에 필요한 타입 작성
sohyun0 c625bd5
✨ feat: notice card 컴포넌트 작성 및 공고 상태 유틸함수로 분리
sohyun0 ae01834
✨ feat: notice card mockData 및 스토리북 작성
sohyun0 999a4b9
✨ feat: card 컴포넌트로 분리한 스타일링 적용
sohyun0 7ae5a79
✨ feat: Card 컴포넌트 테스트용 뷰 작성
sohyun0 da5174f
Merge branch 'develop' into feat/#38-post
sohyun0 64fb3c0
🔧 chore: 유틸 파일 명 변경 #38
sohyun0 16537cf
✨ feat: button 컴포넌트에 cn 함수 추가
sohyun0 bc901bf
🔧 chore: 폴더 위치 변경
sohyun0 415d119
🐛 fix: cssConflict 오류 해결
sohyun0 62f85ec
✨ feat: dropdown 스토리북 수정
sohyun0 580925b
♻️ refactor: notice 타입별 컴포넌트 파일로 분리 #38
sohyun0 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,61 @@ | ||
| import { cva, type VariantProps } from 'class-variance-authority'; | ||
|
|
||
| export const cardFrame = cva('rounded-xl border border-gray-200 bg-white'); | ||
|
|
||
| export const cardImageWrapper = cva('relative rounded-xl overflow-hidden'); | ||
|
|
||
| export const cardHeading = cva('font-bold', { | ||
| variants: { | ||
| size: { | ||
| sm: 'text-heading-s', | ||
| md: 'text-heading-m', | ||
| lg: 'text-heading-l', | ||
| }, | ||
| status: { | ||
| open: 'text-black', | ||
| inactive: 'text-gray-300', | ||
| }, | ||
| }, | ||
| defaultVariants: { size: 'md', status: 'open' }, | ||
| }); | ||
|
|
||
| export const cardInfoLayout = cva('flex flex-nowrap items-start tablet:items-center gap-1.5'); | ||
|
|
||
| export const cardInfoText = cva('text-caption tablet:text-body-s', { | ||
| variants: { | ||
| status: { | ||
| open: 'text-gray-500', | ||
| inactive: 'text-gray-300', | ||
| }, | ||
| }, | ||
| defaultVariants: { status: 'open' }, | ||
| }); | ||
|
|
||
| export const cardInfoIcon = cva('', { | ||
| variants: { | ||
| status: { | ||
| open: 'bg-red-300', | ||
| inactive: 'bg-gray-300', | ||
| }, | ||
| }, | ||
| defaultVariants: { status: 'open' }, | ||
| }); | ||
|
|
||
| export const cardPayLayout = cva('flex items-center gap-x-3'); | ||
|
|
||
| export const cardBadge = cva('flex items-center gap-x-0.5 rounded-full'); | ||
|
|
||
| export const cardBadgeText = cva('whitespace-nowrap text-caption tablet:text-body-s'); | ||
|
|
||
| export const cardLayout = { | ||
| frame: cardFrame, | ||
| imageWrapper: cardImageWrapper, | ||
| heading: cardHeading, | ||
| infoLayout: cardInfoLayout, | ||
| info: cardInfoText, | ||
| infoIcon: cardInfoIcon, | ||
| payLayout: cardPayLayout, | ||
| badge: cardBadge, | ||
| badgeText: cardBadgeText, | ||
| }; | ||
| export type CardStatusVariant = VariantProps<typeof cardHeading>['status']; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| export { default as Notice } from '@/components/ui/card/notice/notice'; | ||
| export { default as Post } from '@/components/ui/card/post/post'; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| import { cardLayout } from '@/components/ui/card/card.styles'; | ||
| import { noticeLabel } from '@/components/ui/card/notice/notice.styles'; | ||
|
|
||
| interface NoticeHeaderProps { | ||
| name?: string; | ||
| category?: string; | ||
| className?: string; | ||
| } | ||
| const NoticeHeader = ({ name, category, className }: NoticeHeaderProps) => ( | ||
| <div className={className}> | ||
| <p className={noticeLabel()}>{category}</p> | ||
| <h2 className={cardLayout.heading({ size: 'lg' })}>{name}</h2> | ||
| </div> | ||
| ); | ||
| export default NoticeHeader; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| import { noticeImageWrapper } from '@/components/ui/card/notice/notice.styles'; | ||
| import Image from 'next/image'; | ||
|
|
||
| interface NoticeImageProps { | ||
| name?: string; | ||
| imageUrl?: string; | ||
| } | ||
| const NoticeImage = ({ name, imageUrl }: NoticeImageProps) => ( | ||
| <div className={noticeImageWrapper()}> | ||
| <Image | ||
| src={imageUrl ?? ''} | ||
| alt={`${name} 가게 이미지`} | ||
| fill | ||
| sizes='(max-width: 744px) 630px, 540px' | ||
| className='object-cover' | ||
| priority | ||
| /> | ||
| </div> | ||
| ); | ||
| export default NoticeImage; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,91 @@ | ||
| import { cardLayout } from '@/components/ui/card/card.styles'; | ||
| import { NoticeVariant } from '@/components/ui/card/notice/notice'; | ||
| 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 { ReactNode } from 'react'; | ||
| import NoticeHeader from './noticeHeader'; | ||
|
|
||
| interface NoticeInfoProps<T extends Partial<NoticeCard>> { | ||
| value: T; | ||
| variant: NoticeVariant; | ||
| buttonComponent: ReactNode; | ||
| } | ||
|
|
||
| const NoticeInfo = <T extends Partial<NoticeCard>>({ | ||
| value, | ||
| variant, | ||
| buttonComponent, | ||
| }: NoticeInfoProps<T>) => { | ||
| const { | ||
| name, | ||
| category, | ||
| hourlyPay, | ||
| originalHourlyPay, | ||
| startsAt, | ||
| workhour, | ||
| 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'> | ||
| {variant === 'notice' && ( | ||
| <> | ||
| <li> | ||
| <p className={noticeLabel()}>시급</p> | ||
| <div className={cardLayout.payLayout()}> | ||
| <span className='text-heading-s font-bold tracking-wide'> | ||
| {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> | ||
| )} | ||
| </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> | ||
| </> | ||
| )} | ||
| {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> | ||
| <li className='text-body-l'>{shopDescription}</li> | ||
| </ul> | ||
| <div className={noticeButton()}>{buttonComponent}</div> | ||
| </div> | ||
| ); | ||
| }; | ||
| export default NoticeInfo; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| { | ||
| "item": { | ||
| "id": "notice-001", | ||
| "hourlyPay": 20000, | ||
| "startsAt": "2025-10-11T11:00:00Z", | ||
| "workhour": 4, | ||
| "description": "주말 점심 시간대 근무자를 모집합니다.", | ||
| "closed": false, | ||
| "shop": { | ||
| "item": { | ||
| "id": "shop-bridge", | ||
| "name": "여의도 베이커리 카페", | ||
| "category": "카페", | ||
| "address1": "서울시 영등포구", | ||
| "address2": "여의도동 2가 123-45", | ||
| "description": "여의도 한강 뷰를 즐길 수 있는 베이커리 카페! 직장인이 많은 곳이라 평일 점심에만 많이바쁘고 그 외는 한가한 편입니다.", | ||
| "imageUrl": "https://picsum.photos/id/16/640/360", | ||
| "originalHourlyPay": 18000 | ||
| }, | ||
| "href": "/shops/shop-bridge" | ||
| }, | ||
| "currentUserApplication": null | ||
| }, | ||
| "links": [ | ||
| { | ||
| "rel": "self", | ||
| "description": "공고 정보", | ||
| "method": "GET", | ||
| "href": "/shops/shop-bridge/notices/notice-001" | ||
| }, | ||
| { | ||
| "rel": "update", | ||
| "description": "공고 수정", | ||
| "method": "PUT", | ||
| "href": "/shops/shop-bridge/notices/notice-001", | ||
| "body": { | ||
| "hourlyPay": "number", | ||
| "startsAt": "string", | ||
| "workhour": "string", | ||
| "description": "string" | ||
| } | ||
| }, | ||
| { | ||
| "rel": "applications", | ||
| "description": "지원 목록", | ||
| "method": "GET", | ||
| "href": "/shops/shop-bridge/notices/notice-001/applications", | ||
| "query": { | ||
| "offset": "undefined | number", | ||
| "limit": "undefined | number" | ||
| } | ||
| }, | ||
| { | ||
| "rel": "create", | ||
| "description": "지원하기", | ||
| "method": "POST", | ||
| "href": "/shops/shop-bridge/notices/notice-001/applications" | ||
| }, | ||
| { | ||
| "rel": "shop", | ||
| "description": "가게 정보", | ||
| "method": "GET", | ||
| "href": "/shops/shop-bridge" | ||
| }, | ||
| { | ||
| "rel": "list", | ||
| "description": "공고 목록", | ||
| "method": "GET", | ||
| "href": "/shops/shop-bridge/notices", | ||
| "query": { | ||
| "offset": "undefined | number", | ||
| "limit": "undefined | number" | ||
| } | ||
| } | ||
| ] | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| 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 { NoticeShopCard } from '@/types/shop'; | ||
| import Link from 'next/link'; | ||
| import mockResponse from './mockData.json'; | ||
| import shopMockResponse from './shopMockData.json'; | ||
|
|
||
| // mockData | ||
| type RawNotice = typeof mockResponse; | ||
| type RawShopNotice = typeof shopMockResponse; | ||
|
|
||
| const toNoticeCard = ({ item }: RawNotice): NoticeCard => { | ||
| const shop = item.shop.item; | ||
|
|
||
| return { | ||
| id: item.id, | ||
| hourlyPay: item.hourlyPay, | ||
| startsAt: item.startsAt, | ||
| workhour: item.workhour, | ||
| description: item.description, | ||
| closed: item.closed, | ||
| shopId: shop.id, | ||
| name: shop.name, | ||
| category: shop.category, | ||
| address1: shop.address1, | ||
| shopDescription: shop.description, | ||
| imageUrl: shop.imageUrl, | ||
| originalHourlyPay: shop.originalHourlyPay, | ||
| }; | ||
| }; | ||
| const toShopCard = ({ item }: RawShopNotice): NoticeShopCard => { | ||
| const shop = item; | ||
|
|
||
| return { | ||
| shopId: shop.id, | ||
| name: shop.name, | ||
| category: shop.category, | ||
| address1: shop.address1, | ||
| shopDescription: shop.description, | ||
| imageUrl: shop.imageUrl, | ||
| }; | ||
| }; | ||
|
|
||
| const NoticeWrapper = () => { | ||
| // notice | ||
| const notice: NoticeCard = toNoticeCard(mockResponse); | ||
| const status = getNoticeStatus(notice.closed, notice.startsAt); | ||
| const href = `/shops/${notice.shopId}/notices/${notice.id}`; | ||
| // shop | ||
| const shopItem: NoticeShopCard = toShopCard(shopMockResponse); | ||
| return ( | ||
| <> | ||
| <Notice notice={notice}> | ||
| <Button | ||
| as={Link} | ||
| href={href} | ||
| size='xs38' | ||
| full | ||
| className='font-bold' | ||
| variant={status !== 'open' ? 'disabled' : 'primary'} | ||
| > | ||
| {status !== 'open' ? '신청 불가' : '신청하기'} | ||
| </Button> | ||
| </Notice> | ||
| <Notice notice={shopItem} variant='shop'> | ||
| <Button as={Link} href={href} size='xs38' full className='font-bold'> | ||
| 가게 편집하기 | ||
| </Button> | ||
| </Notice> | ||
| </> | ||
| ); | ||
| }; | ||
|
|
||
| export default NoticeWrapper; | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
조건문을 변수로 사용하는 건 어떨까요? (제안)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
아 이부분은 실제로 사용하는건 아니고 사용법을 보여드리기위한 목업컴포넌트라 추후 삭제할 예정입니다 :D