Skip to content

Commit 2bc39b9

Browse files
authored
Merge pull request #214 from DevKor-github/feat/#155/notification-page
feat: ์•Œ๋ฆผ ๋‚ด์—ญ
2 parents 2a1ec4c + b2dc333 commit 2bc39b9

File tree

18 files changed

+456
-31
lines changed

18 files changed

+456
-31
lines changed

โ€Žsrc/common/components/MainTopBar/index.tsxโ€Ž

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ const MainTopBar = () => {
1515
</Link>
1616
<div className={s.Menu}>
1717
<Link className="mgc_search_2_fill" to={'/search'} />
18-
<button className="mgc_notification_fill" />
18+
<Link className="mgc_notification_fill" to={'/notification'} />
1919
</div>
2020
</div>
2121
);

โ€Žsrc/common/utils/parseDate.tsโ€Ž

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,8 @@ export const parseTime = (raw: string) => {
1515
const date = new Date(raw);
1616
return format(date, 'a h:mm', { locale: ko });
1717
};
18+
19+
export const parsePickTime = (raw: string) => {
20+
const date = new Date(raw);
21+
return format(date, 'H:mm', { locale: ko });
22+
};

โ€Žsrc/features/likedList/components/itemList/index.tsxโ€Ž

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,13 @@ const LikedItemList = ({ likes }: Props) => {
1818
return (
1919
<>
2020
{likes.map(item => (
21-
<LikedItemRow key={item.itemId} item={item} />
21+
<LikedItemCard key={item.itemId} item={item} />
2222
))}
2323
</>
2424
);
2525
};
2626

27-
const LikedItemRow = ({ item }: { item: LikeInterface }) => {
27+
const LikedItemCard = ({ item }: { item: LikeInterface }) => {
2828
const { mutate: likeItem } = usePostLike();
2929
const queryClient = useQueryClient();
3030

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import client from '@/common/utils/client';
2+
import { QUERY_KEYS } from '@/libs/queryKeys';
3+
import { useQuery } from '@tanstack/react-query';
4+
import type { NotificationInterface } from '../types';
5+
6+
export interface NotificationResponse {
7+
message: string;
8+
data: NotificationInterface[];
9+
}
10+
11+
const getNotification = async () => {
12+
const res = await client.get<NotificationResponse>('api/v1/notification');
13+
return res.data.data;
14+
};
15+
16+
export const useGetNotification = () => {
17+
return useQuery({
18+
queryKey: [QUERY_KEYS.NOTIFICATION_LIST],
19+
queryFn: getNotification,
20+
staleTime: 0,
21+
});
22+
};
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
import ItemTokenList from '@/common/components/ItemTokenList';
2+
import PriceToken from '@/features/home/components/ItemCard/PriceToken';
3+
import { Link } from 'react-router';
4+
import * as s from './style.css';
5+
import getImageUrl from '@/common/utils/getImageUrl';
6+
import { cx } from '@styled-system/css';
7+
import type { NotificationInterface } from '../../types';
8+
import { parsePickDate, parsePickTime } from '@/common/utils/parseDate';
9+
10+
interface Props {
11+
data: NotificationInterface;
12+
}
13+
14+
const NotificationItemCard = ({ data }: Props) => {
15+
const isRental = data.item.transactionTypes.includes('RENTAL');
16+
const isSale = data.item.transactionTypes.includes('SALE');
17+
const type = data.type;
18+
const isRemind = type === 'APPOINTMENT_REMIND';
19+
20+
const label = (() => {
21+
if (type === 'APPOINTMENT_CANCEL') return '๋‚˜์˜ Pick์ด ์ทจ์†Œ๋์–ด์š”.';
22+
if (type === 'APPOINTMENT_EXPIRE') return '๋‚˜์˜ Pick ์š”์ฒญ์ด ๋งŒ๋ฃŒ๋์–ด์š”.';
23+
if (type === 'APPOINTMENT_PROPOSAL') return 'Pick ์š”์ฒญ์„ ๋ฐ›์•˜์–ด์š”.';
24+
if (type === 'APPOINTMENT_REJECT') return '๋‚˜์˜ Pick ์š”์ฒญ์ด ๊ฑฐ์ ˆ๋์–ด์š”.';
25+
if (type === 'APPOINTMENT_REMIND') return '์˜ค๋Š˜์€ ์•„๋ž˜ ์ƒํ’ˆ์˜ ๊ฑฐ๋ž˜ ๋‚ ์ด์—์š”!';
26+
if (type === 'APPOINTMENT_CONFIRM') return '๋‚˜์˜ Pick์ด ํ™•์ •๋์–ด์š”.';
27+
})();
28+
29+
const icon = (() => {
30+
if (type === 'APPOINTMENT_CANCEL') return 'mgc_sob_fill';
31+
if (type === 'APPOINTMENT_EXPIRE') return 'mgc_sob_fill';
32+
if (type === 'APPOINTMENT_PROPOSAL') return 'mgc_emoji_fill';
33+
if (type === 'APPOINTMENT_REJECT') return 'mgc_sob_fill';
34+
if (type === 'APPOINTMENT_REMIND') return 'mgc_t_shirt_fill';
35+
if (type === 'APPOINTMENT_CONFIRM') return 'mgc_emoji_fill';
36+
})();
37+
38+
return (
39+
<div className={s.Wrapper({ isRemind })}>
40+
<div className={s.RemindTime}>
41+
<div className={s.Type}>
42+
<div className={cx(`${icon}`, s.Icon({ icon }))} />
43+
<div className={s.Label}>{label}</div>
44+
</div>
45+
{/* {isRemind && */}
46+
<div className={s.Date}>
47+
<h1>{parsePickDate(data.createdAt)}</h1>
48+
<h1>{parsePickTime(data.createdAt)}</h1>
49+
</div>
50+
{/*} */}
51+
</div>
52+
<Link className={s.Container} to={`/pick-detail/${data.appointmentId}`}>
53+
<img className={s.Image} src={getImageUrl(data.item.thumbnail)} aria-hidden />
54+
<div className={s.Info}>
55+
<div className={s.Header}>
56+
<h2 className={s.Title}>{data.item.title}</h2>
57+
<div className={s.Price}>
58+
{isRental && <PriceToken price={data.item.rentalFee} deposit={data.item.deposit} />}
59+
{isSale && <PriceToken price={data.item.salePrice} />}
60+
</div>
61+
</div>
62+
<div className={s.Footer}>
63+
<div className={s.Tokens}>
64+
<ItemTokenList
65+
showCount={3}
66+
itemInfo={{
67+
productTypes: data.item.productTypes,
68+
quality: data.item.quality,
69+
size: data.item.size,
70+
color: data.item.color,
71+
tradeMethods: data.item.tradeMethods,
72+
}}
73+
/>
74+
</div>
75+
<div className={s.Interactions}>
76+
<div className={s.InteractionItem}>
77+
<span className="mgc_heart_fill" />
78+
<p>{data.item.likeCount}</p>
79+
</div>
80+
<div className={s.InteractionItem}>
81+
<span className="mgc_chat_2_fill" />
82+
<p>{data.item.chatRoomCount}</p>
83+
</div>
84+
</div>
85+
</div>
86+
</div>
87+
</Link>
88+
</div>
89+
);
90+
};
91+
92+
export default NotificationItemCard;
Lines changed: 157 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,157 @@
1+
import { css, cva } from '@styled-system/css';
2+
3+
export const Wrapper = cva({
4+
base: {
5+
display: 'flex',
6+
flexDir: 'column',
7+
gap: '0.625rem',
8+
padding: '0.875rem 1.25rem 1.125rem 1.25rem',
9+
},
10+
variants: {
11+
isRemind: {
12+
true: {
13+
bg: 'systemGray5',
14+
},
15+
},
16+
},
17+
});
18+
19+
export const Container = css({
20+
w: 'full',
21+
h: '6.5rem',
22+
display: 'flex',
23+
alignItems: 'stretch',
24+
gap: '1rem',
25+
});
26+
27+
export const RemindTime = css({
28+
display: 'flex',
29+
flex: 1,
30+
justifyContent: 'space-between',
31+
});
32+
33+
export const Type = css({
34+
display: 'flex',
35+
gap: '0.25rem',
36+
alignItems: 'center',
37+
});
38+
39+
export const Icon = cva({
40+
base: {
41+
fontSize: '1.125rem',
42+
backgroundClip: 'text',
43+
color: 'transparent',
44+
},
45+
variants: {
46+
icon: {
47+
mgc_sob_fill: {
48+
background: 'linear-gradient(128deg, #0F71FF 16.45%, #FF0004 102.04%)',
49+
},
50+
mgc_emoji_fill: {
51+
background: 'linear-gradient(180deg, #FF9F05 0%, #FFC505 81.73%)',
52+
},
53+
mgc_t_shirt_fill: {
54+
background: 'linear-gradient(180deg, #3F2EFF 0%, #FF9F18 100%);',
55+
},
56+
},
57+
},
58+
});
59+
60+
export const Label = css({
61+
color: '80',
62+
fontFamily: 'Pretendard',
63+
fontSize: '0.875rem',
64+
fontStyle: 'normal',
65+
fontWeight: 500,
66+
lineHeight: 'normal',
67+
letterSpacing: '-0.035rem',
68+
});
69+
70+
export const Date = css({
71+
display: 'flex',
72+
gap: '0.38rem',
73+
74+
color: '54',
75+
fontFamily: 'Pretendard',
76+
fontSize: '0.75rem',
77+
fontStyle: 'normal',
78+
fontWeight: 400,
79+
lineHeight: 'normal',
80+
letterSpacing: '-0.03rem',
81+
});
82+
83+
export const Image = css({
84+
w: '6.5rem',
85+
h: '6.5rem',
86+
borderRadius: '0.78125rem',
87+
objectFit: 'cover',
88+
flexShrink: 0,
89+
});
90+
91+
export const Info = css({
92+
flex: '1 0 0',
93+
display: 'flex',
94+
flexDir: 'column',
95+
justifyContent: 'space-between',
96+
padding: '0.125rem 0',
97+
overflow: 'hidden',
98+
});
99+
100+
export const Header = css({
101+
display: 'flex',
102+
flexDir: 'column',
103+
gap: '0.25rem',
104+
alignItems: 'stretch',
105+
});
106+
107+
export const Title = css({
108+
color: '100',
109+
fontSize: '1rem',
110+
fontWeight: 500,
111+
lineHeight: 'normal',
112+
letterSpacing: '-0.04rem',
113+
lineClamp: 1,
114+
});
115+
116+
export const Price = css({
117+
display: 'flex',
118+
flexDir: 'column',
119+
});
120+
121+
export const Footer = css({
122+
display: 'flex',
123+
alignItems: 'center',
124+
justifyContent: 'space-between',
125+
width: 'full',
126+
gap: '0.25rem',
127+
});
128+
129+
export const Tokens = css({
130+
display: 'flex',
131+
alignItems: 'center',
132+
gap: '0.25rem',
133+
overflowX: 'auto',
134+
});
135+
136+
export const Interactions = css({
137+
display: 'flex',
138+
alignItems: 'center',
139+
gap: '0.625rem',
140+
flexShrink: 0,
141+
});
142+
143+
export const InteractionItem = css({
144+
display: 'flex',
145+
alignItems: 'flex-end',
146+
gap: '0.1875rem',
147+
color: '54',
148+
'& span': {
149+
fontSize: '1rem',
150+
},
151+
'& p': {
152+
fontSize: '0.75rem',
153+
fontWeight: 400,
154+
letterSpacing: '-0.03rem',
155+
lineHeight: 1.2,
156+
},
157+
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import type { NotificationInterface } from '../../types';
2+
import NotificationItemCard from '../NotificationItemCard';
3+
4+
interface Props {
5+
notifications: NotificationInterface[];
6+
}
7+
8+
const NotificationList = ({ notifications }: Props) => {
9+
return (
10+
<>
11+
{notifications.map(item => (
12+
<NotificationItemCard data={item} />
13+
))}
14+
</>
15+
);
16+
};
17+
18+
export default NotificationList;

โ€Žsrc/features/notification/components/NotificationList/style.css.tsโ€Ž

Whitespace-only changes.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import type { Color, ProductType, Quality, Size, TradeMethods, TransactionType } from '@/libs/types/item';
2+
3+
export const NOTIFICATION_TYPE_ARRAY = [
4+
'APPOINTMENT_PROPOSAL',
5+
'APPOINTMENT_CANCEL',
6+
'APPOINTMENT_REJECT',
7+
'APPOINTMENT_EXPIRE',
8+
'APPOINTMENT_REMIND',
9+
'APPOINTMENT_CONFIRM',
10+
] as const;
11+
export type NotificationType = (typeof NOTIFICATION_TYPE_ARRAY)[number];
12+
13+
export interface ItemNotificationInterface {
14+
itemId: number;
15+
productTypes: ProductType[];
16+
transactionTypes: TransactionType[];
17+
thumbnail: string;
18+
title: string;
19+
rentalFee: number;
20+
salePrice: number;
21+
deposit: number;
22+
size: Size;
23+
color: Color;
24+
quality: Quality;
25+
tradeMethods: TradeMethods[];
26+
likeCount: number;
27+
chatRoomCount: number;
28+
repostDate: string;
29+
}
30+
31+
export interface NotificationInterface {
32+
notificationId: number;
33+
item: ItemNotificationInterface;
34+
appointmentId: number;
35+
type: NotificationType;
36+
createdAt: string;
37+
}

โ€Žsrc/features/pick/components/DetailBottom/index.tsxโ€Ž

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ const DetailBottom = ({ id, itemId, isCreator, pickState, chatRoomId }: Props) =
2828
const { openToast } = useToast();
2929
const [showAlert, setShowAlert] = useState(false);
3030

31-
const cancelType = pickState === 'PENDING' ? '๊ฑฐ์ ˆ' : '์ทจ์†Œ';
31+
const cancelType = isCreator ? '์ทจ์†Œ' : pickState === 'PENDING' ? '๊ฑฐ์ ˆ' : '์ทจ์†Œ';
3232

3333
const onNo = () => {
3434
setShowAlert(false);
@@ -85,9 +85,9 @@ const DetailBottom = ({ id, itemId, isCreator, pickState, chatRoomId }: Props) =
8585
{showAlert && (
8686
<CustomAlert
8787
onYes={cancelPick}
88-
subTitle="์ •๋ง ์ทจ์†Œํ•˜์‹ค ๊ฑด๊ฐ€์š”?"
89-
title={`PICK์„ ${cancelType}ํ•˜๋ฉด\n๋” ์ด์ƒ ๊ฑฐ๋ž˜๋ฅผ ํ•  ์ˆ˜ ์—†์–ด์š”.`}
90-
yesBtn="๋„ค, ์ทจ์†Œํ• ๋ž˜์š”"
88+
subTitle={`์ •๋ง ${cancelType}ํ•˜์‹ค ๊ฑด๊ฐ€์š”?`}
89+
title={`PICK์„ ${cancelType}ํ•˜๋ฉด\n๊ธฐ์กด ๋‚ด์šฉ์€ ์‚ฌ๋ผ์ ธ์š”.`}
90+
yesBtn={`๋„ค, ${cancelType}ํ• ๋ž˜์š”`}
9191
onNo={onNo}
9292
/>
9393
)}

0 commit comments

Comments
ย (0)