diff --git a/src/apis/alert/alert.ts b/src/apis/alert/alert.ts new file mode 100644 index 0000000..eb3c9c0 --- /dev/null +++ b/src/apis/alert/alert.ts @@ -0,0 +1,26 @@ +import apiInstance from '../axios/instance'; +import type { AlertResponse } from '../../types/alert.type'; +import type { FilterValue } from '../../types/filter.type'; + +interface FetchAlertsParams { + type?: FilterValue; + page?: number; +} + +export const alertFetchData = async ({ + type = 'ALL', + page = 0, +}: FetchAlertsParams): Promise => { + try { + const res = await apiInstance.get('api/alerts', { + params: { + type, + page, + }, + }); + + return res.data; + } catch (err) { + throw err; + } +}; diff --git a/src/assets/icons/news.svg b/src/assets/icons/news.svg deleted file mode 100644 index fec5f5f..0000000 --- a/src/assets/icons/news.svg +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/assets/icons/thesis.svg b/src/assets/icons/thesis.svg deleted file mode 100644 index b488d0b..0000000 --- a/src/assets/icons/thesis.svg +++ /dev/null @@ -1,23 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/src/config/linkIcons.ts b/src/config/linkIcons.ts deleted file mode 100644 index 0b883ee..0000000 --- a/src/config/linkIcons.ts +++ /dev/null @@ -1,8 +0,0 @@ -import ThesisIcon from '@/assets/icons/thesis.svg'; -import NewsIcon from '@/assets/icons/news.svg'; -import { LINK_TYPE } from '../constants/linkType'; - -export const LINK_ICONS = { - [LINK_TYPE.THESIS]: ThesisIcon, - [LINK_TYPE.NEWS]: NewsIcon, -}; diff --git a/src/constants/linkType.ts b/src/constants/linkType.ts deleted file mode 100644 index b4cc54a..0000000 --- a/src/constants/linkType.ts +++ /dev/null @@ -1,6 +0,0 @@ -export const LINK_TYPE = { - THESIS: 'THESIS', - NEWS: 'NEWS', -}; - -export type LinkType = (typeof LINK_TYPE)[keyof typeof LINK_TYPE]; diff --git a/src/hooks/useQuery/useGetAlertData.ts b/src/hooks/useQuery/useGetAlertData.ts new file mode 100644 index 0000000..0ecf008 --- /dev/null +++ b/src/hooks/useQuery/useGetAlertData.ts @@ -0,0 +1,17 @@ +import { useQuery } from '@tanstack/react-query'; +import { alertFetchData } from '../../apis/alert/alert'; +import type { AlertResponse } from '../../types/alert.type'; +import type { FilterValue } from '../../types/filter.type'; + +interface UseGetAlertDataParams { + type: FilterValue; + page: number; +} + +export const useGetAlertData = ({ type, page }: UseGetAlertDataParams) => { + return useQuery({ + queryKey: ['alerts', type, page], + queryFn: () => alertFetchData({ type, page }), + staleTime: 0, + }); +}; diff --git a/src/mocks/alertData.ts b/src/mocks/alertData.ts deleted file mode 100644 index 0b6a350..0000000 --- a/src/mocks/alertData.ts +++ /dev/null @@ -1,48 +0,0 @@ -import type { AlertItem } from '../types/alert.type'; - -export const alertData: AlertItem[] = [ - { - id: 1, - summary: 'Subject is expected to decline by more than 20%.', - createdAt: '2025-12-20T18:30:00Z', - links: [ - { - id: 1, - type: 'NEWS', - url: 'http://dddddddddddddd', - }, - { - id: 2, - type: 'NEWS', - url: 'http://dddddddddddddd', - }, - { - id: 3, - type: 'NEWS', - url: 'http://dddddddddddddd', - }, - ], - }, - { - id: 2, - summary: 'Subject is expected to decline by more than 60%.', - createdAt: '2025-12-20T18:30:00Z', - links: [ - { - id: 1, - type: 'THESIS', - url: 'http://dddddddddddddd', - }, - { - id: 2, - type: 'THESIS', - url: 'http://dddddddddddddd', - }, - { - id: 3, - type: 'THESIS', - url: 'http://dddddddddddddd', - }, - ], - }, -]; diff --git a/src/mocks/filterData.ts b/src/mocks/filterData.ts index 0102e5e..b9d0f2a 100644 --- a/src/mocks/filterData.ts +++ b/src/mocks/filterData.ts @@ -1,4 +1,6 @@ -export const filterData = [ +import type { FilterItem } from '../types/filter.type'; + +export const filterData: FilterItem[] = [ { id: 1, label: 'ALL', diff --git a/src/mocks/siteData.ts b/src/mocks/siteData.ts index fcf125c..56cc66a 100644 --- a/src/mocks/siteData.ts +++ b/src/mocks/siteData.ts @@ -4,16 +4,16 @@ export const siteData: SiteItem[] = [ { id: 1, type: 'BLOCKCHAIN', - url: 'https://www.naver.com', + url: 'https://www.blockchain.com/', }, { id: 2, type: 'COINMARKETCAP', - url: 'https://www.google.com', + url: 'https://coinmarketcap.com/', }, { id: 3, type: 'BINANCE', - url: 'https://www.youtube.com', + url: 'https://www.binance.com/en', }, ]; diff --git a/src/pages/home/components/Alert/Alert.tsx b/src/pages/home/components/Alert/Alert.tsx index 812c9d8..6f59f0b 100644 --- a/src/pages/home/components/Alert/Alert.tsx +++ b/src/pages/home/components/Alert/Alert.tsx @@ -1,16 +1,29 @@ +import { useState, useEffect } from 'react'; import Title from '../../../../components/Title/Title'; import ListAlert from './List/ListAlert'; import MoreButton from './Button/MoreButton'; -import { alertData } from '../../../../mocks/alertData'; +import { useFilterStore } from '../../../../stores/filterStore'; const Alert = () => { + const [page, setPage] = useState(0); + const { selectedFilter } = useFilterStore(); + const [hasNext, setHasNext] = useState(false); + + const handleLoadMore = () => { + setPage((prev) => prev + 1); + }; + + useEffect(() => { + setPage(0); + }, [selectedFilter]); + return ( <> - <ListAlert data={alertData} /> + <ListAlert page={page} onHasNext={setHasNext} /> - <MoreButton /> + {hasNext && <MoreButton onClick={handleLoadMore} />} </> ); }; diff --git a/src/pages/home/components/Alert/Button/MoreButton.tsx b/src/pages/home/components/Alert/Button/MoreButton.tsx index 7d1b0f2..9099645 100644 --- a/src/pages/home/components/Alert/Button/MoreButton.tsx +++ b/src/pages/home/components/Alert/Button/MoreButton.tsx @@ -1,7 +1,14 @@ -const MoreButton = () => { +interface MoreButtonProps { + onClick: () => void; +} + +const MoreButton = ({ onClick }: MoreButtonProps) => { return ( - <div className="w-full flex justify-center"> - <button className="px-[1.6rem] py-[0.6rem] text-[1.4rem] text-gray-400 cursor-pointer border border-gray-400 rounded-[11.7rem] border-solid-[0.1rem]"> + <div className="w-full flex justify-center pb-[3.7rem]"> + <button + onClick={onClick} + className="px-[1.6rem] py-[0.6rem] text-[1.4rem] text-gray-400 cursor-pointer border border-gray-400 rounded-[11.7rem]" + > More </button> </div> diff --git a/src/pages/home/components/Alert/Item/ItemAlert.tsx b/src/pages/home/components/Alert/Item/ItemAlert.tsx index 1104f87..608a5cd 100644 --- a/src/pages/home/components/Alert/Item/ItemAlert.tsx +++ b/src/pages/home/components/Alert/Item/ItemAlert.tsx @@ -7,19 +7,19 @@ interface ItemAlertProps { } const ItemAlert = ({ alert }: ItemAlertProps) => { - const { summary, createdAt, links } = alert; - return ( <details className="w-full group"> <summary className="bg-white list-none cursor-pointer p-[1.1rem] flex items-start justify-between transition-shadow group-open:shadow-card rounded-[0.8rem]"> <div className="flex items-start gap-[0.6rem]"> <span aria-hidden="true" - className="w-[0.8rem] h-[0.8rem] mt-[0.6rem] bg-red rounded-full" + className="min-w-[0.8rem] min-h-[0.8rem] mt-[0.6rem] bg-red rounded-full" /> <div> - <p className="text-[1.4rem] font-medium">{summary}</p> - <time className="text-[1.2rem] text-gray-200">{createdAt}</time> + <p className="text-[1.4rem] font-medium">{alert.message}</p> + <time className="text-[1.2rem] text-gray-200"> + {alert.createdAt} + </time> </div> </div> <img @@ -33,7 +33,7 @@ const ItemAlert = ({ alert }: ItemAlertProps) => { <p className="text-[1.4rem]"> For these reasons, a decline was expected. </p> - <ListLink data={links} /> + <ListLink data={alert.links} /> </div> </details> ); diff --git a/src/pages/home/components/Alert/Item/ItemLink.tsx b/src/pages/home/components/Alert/Item/ItemLink.tsx index f86d8dd..cacc674 100644 --- a/src/pages/home/components/Alert/Item/ItemLink.tsx +++ b/src/pages/home/components/Alert/Item/ItemLink.tsx @@ -1,25 +1,21 @@ -import { LINK_ICONS } from '../../../../../config/linkIcons'; -import type { LinkItem } from '../../../../../types/link.type'; import useNavigation from '../../../../../hooks/useNavigation'; +import type { AlertLink } from '../../../../../types/alert.type'; interface ItemLinkProps { - item: LinkItem; + item: AlertLink; } const ItemLink = ({ item }: ItemLinkProps) => { - const { type, url } = item; - const Icon = LINK_ICONS[type]; const { goTo } = useNavigation(); return ( <li className="w-full"> <button type="button" - onClick={() => goTo(url)} + onClick={() => goTo(item.url)} className="w-full flex items-center gap-[0.4rem] text-left" > - <img src={Icon} alt={type.toLowerCase()} className="w-8" /> - <p className="font-[1.1rem] text-gray-600">{url}</p> + <p className="font-[1.1rem] text-gray-600">{item.url}</p> </button> </li> ); diff --git a/src/pages/home/components/Alert/List/ListAlert.tsx b/src/pages/home/components/Alert/List/ListAlert.tsx index 456c714..f7c4cb1 100644 --- a/src/pages/home/components/Alert/List/ListAlert.tsx +++ b/src/pages/home/components/Alert/List/ListAlert.tsx @@ -1,14 +1,44 @@ +import { useEffect, useState } from 'react'; import ItemAlert from '../Item/ItemAlert'; +import { useGetAlertData } from '../../../../../hooks/useQuery/useGetAlertData'; +import { useFilterStore } from '../../../../../stores/filterStore'; import type { AlertItem } from '../../../../../types/alert.type'; interface ListAlertProps { - data: AlertItem[]; + page: number; + onHasNext: (hasNext: boolean) => void; } -const ListAlert = ({ data }: ListAlertProps) => { +const ListAlert = ({ page, onHasNext }: ListAlertProps) => { + const { selectedFilter } = useFilterStore(); + const [alerts, setAlerts] = useState<AlertItem[]>([]); + + const { data } = useGetAlertData({ + type: selectedFilter, + page, + }); + + useEffect(() => { + if (data?.alerts) { + setAlerts((prev) => + page === 0 ? data.alerts : [...prev, ...data.alerts], + ); + + onHasNext(data.hasNext); + } + }, [data, page, onHasNext]); + + if (!alerts.length) { + return ( + <p className="text-center text-[1.4rem] text-font mt-[10.3rem] pb-[18.7rem]"> + There's no data to display yet. + </p> + ); + } + return ( - <div className="w-full flex flex-col gap-[0.8rem] mb-[1.6rem]"> - {data.map((alert) => ( + <div className="w-full flex flex-col gap-[0.8rem] pb-[1.6rem]"> + {alerts.map((alert) => ( <ItemAlert key={alert.id} alert={alert} /> ))} </div> diff --git a/src/pages/home/components/Alert/List/ListLink.tsx b/src/pages/home/components/Alert/List/ListLink.tsx index c8c91fe..45e0550 100644 --- a/src/pages/home/components/Alert/List/ListLink.tsx +++ b/src/pages/home/components/Alert/List/ListLink.tsx @@ -1,8 +1,8 @@ import ItemLink from '../Item/ItemLink'; -import type { LinkItem } from '../../../../../types/link.type'; +import type { AlertLink } from '../../../../../types/alert.type'; interface ListLinkProps { - data: LinkItem[]; + data: AlertLink[]; } const ListLink = ({ data }: ListLinkProps) => { diff --git a/src/stores/filterStore.ts b/src/stores/filterStore.ts index 0b71a9e..ff78575 100644 --- a/src/stores/filterStore.ts +++ b/src/stores/filterStore.ts @@ -1,6 +1,5 @@ import { create } from 'zustand'; - -export type FilterValue = string; +import type { FilterValue } from '../types/filter.type'; interface FilterState { selectedFilter: FilterValue; diff --git a/src/types/alert.type.ts b/src/types/alert.type.ts index cd2cc66..9cc475d 100644 --- a/src/types/alert.type.ts +++ b/src/types/alert.type.ts @@ -1,8 +1,16 @@ -import type { LinkItem } from './link.type'; +export interface AlertLink { + id: number; + url: string; +} export interface AlertItem { id: number; - summary: string; + message: string; createdAt: string; - links: LinkItem[]; + links: AlertLink[]; +} + +export interface AlertResponse { + alerts: AlertItem[]; + hasNext: boolean; } diff --git a/src/types/filter.type.ts b/src/types/filter.type.ts index 0baa9ba..47c5b72 100644 --- a/src/types/filter.type.ts +++ b/src/types/filter.type.ts @@ -1,5 +1,7 @@ export interface FilterItem { id: number; label: string; - value: string; + value: FilterValue; } + +export type FilterValue = 'ALL' | 'Bitcoin' | 'Ethereum' | 'Solana'; diff --git a/src/types/link.type.ts b/src/types/link.type.ts deleted file mode 100644 index 3be1a91..0000000 --- a/src/types/link.type.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { LinkType } from '../constants/linkType'; - -export interface LinkItem { - id: number; - type: LinkType; - url: string; -}