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
15 changes: 11 additions & 4 deletions app/(dashboard)/_ui/list-header/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,19 @@ import styles from './styles.module.scss'

const cx = classNames.bind(styles)

const LIST_HEADER = ['전략', '분석', 'MDD', 'SM SCORE', '수익률', '구독']
const LIST_HEADER = {
default: ['전략', '분석', 'MDD', 'SM SCORE', '수익률', '구독'],
my: ['전략', '분석', 'MDD', 'SM SCORE', '수익률', '공개', '관리'],
}

interface Props {
type?: 'default' | 'my'
}

const ListHeader = () => {
const ListHeader = ({ type = 'default' }: Props) => {
return (
<div className={cx('container')}>
{LIST_HEADER.map((category) => (
<div className={cx('container', type)}>
{LIST_HEADER[type].map((category) => (
<div key={category} className={cx('category')}>
{category}
</div>
Expand Down
4 changes: 4 additions & 0 deletions app/(dashboard)/_ui/list-header/styles.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@
height: 42px;
margin: 20px 0 10px;

&.my {
grid-template-columns: 3fr 1.5fr 1.7fr 1.3fr 1.5fr 1.2fr 1.2fr;
}

.category {
display: flex;
align-items: center;
Expand Down
19 changes: 19 additions & 0 deletions app/(dashboard)/my/_api/get-my-strategy-list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import axiosInstance from '@/shared/api/axios'
import { StrategiesModel } from '@/shared/types/strategy-details-data'

// 실제 api 나오면 수정 필요함
// totalElements 사용해서 hasmore값 계산해야될 것 같음

interface StrategiesResponseModel {
result: {
strategies: StrategiesModel[]
hasMore: boolean
}
}

export const getMyStrategyList = async ({ page = 1, size = 4 }: { page: number; size: number }) => {
const response = await axiosInstance.get<StrategiesResponseModel>(
`/api/my-strategies/page=${page}&size=${size}`
)
return response.data.result
}
24 changes: 24 additions & 0 deletions app/(dashboard)/my/_hooks/query/use-get-my-strategy-list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { getMyStrategyList } from '@/app/(dashboard)/my/_api/get-my-strategy-list'
import { useInfiniteQuery } from '@tanstack/react-query'

import { StrategiesModel } from '@/shared/types/strategy-details-data'

interface StrategiesPageModel {
strategies: StrategiesModel[]
hasMore: boolean
}

export const useGetMyStrategyList = () => {
return useInfiniteQuery<StrategiesPageModel, Error>({
queryKey: ['myStrategies'],
queryFn: async ({ pageParam = 1 }) => {
const page = typeof pageParam === 'number' ? pageParam : 1
return getMyStrategyList({ page, size: 4 })
},
getNextPageParam: (lastPage, pages) => {
if (!lastPage.hasMore) return undefined
return pages.length + 1
},
initialPageParam: 1,
})
}
42 changes: 42 additions & 0 deletions app/(dashboard)/my/strategies/_ui/my-strategy-list/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
'use client'

import { useCallback, useRef } from 'react'

import StrategiesItem from '@/app/(dashboard)/_ui/strategies-item'
import { useGetMyStrategyList } from '@/app/(dashboard)/my/_hooks/query/use-get-my-strategy-list'

import { useIntersectionObserver } from '@/shared/hooks/custom/use-intersection-observer'

const MyStrategyList = () => {
const { data, fetchNextPage, hasNextPage, isFetchingNextPage } = useGetMyStrategyList()

const loadMoreRef = useRef<HTMLDivElement>(null)

const onIntersect = useCallback(
(entry: IntersectionObserverEntry) => {
if (entry.isIntersecting && hasNextPage && !isFetchingNextPage) {
fetchNextPage()
}
},
[fetchNextPage, hasNextPage, isFetchingNextPage]
)

useIntersectionObserver({
ref: loadMoreRef,
onIntersect,
})

const strategies = data?.pages.flatMap((page) => page.strategies) || []

return (
<>
{strategies.map((strategy) => (
<StrategiesItem key={strategy.strategyId} strategiesData={strategy} />
))}
<div ref={loadMoreRef} />
{isFetchingNextPage && <div>로딩 중...</div>}
</>
)
}

export default MyStrategyList
22 changes: 21 additions & 1 deletion app/(dashboard)/my/strategies/page.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
import { Suspense } from 'react'

import classNames from 'classnames/bind'

import Title from '@/shared/ui/title'

import ListHeader from '../../_ui/list-header'
import MyStrategyList from './_ui/my-strategy-list'
import styles from './styles.module.scss'

const cx = classNames.bind(styles)

const MyStrategiesPage = () => {
return <></>
return (
<div className={cx('container')}>
<Title label={'나의 전략'} />
<ListHeader type="my" />
<Suspense fallback={<div>Loading...</div>}>
<MyStrategyList />
</Suspense>
</div>
)
}

export default MyStrategiesPage
3 changes: 3 additions & 0 deletions app/(dashboard)/my/strategies/styles.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.container {
margin-top: 80px;
}
37 changes: 37 additions & 0 deletions shared/hooks/custom/use-intersection-observer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { RefObject, useEffect } from 'react'

interface UseIntersectionObserverProps {
ref: RefObject<HTMLElement>
onIntersect: (entry: IntersectionObserverEntry) => void
threshold?: number
rootMargin?: string
}

export const useIntersectionObserver = ({
ref,
onIntersect,
threshold = 0.1,
rootMargin = '0px',
}: UseIntersectionObserverProps) => {
useEffect(() => {
if (!ref.current) return

const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => onIntersect(entry))
},
{
threshold,
rootMargin,
}
)

observer.observe(ref.current)

return () => {
if (ref.current) {
observer.unobserve(ref.current)
}
}
}, [ref, threshold, rootMargin, onIntersect])
}
4 changes: 2 additions & 2 deletions shared/types/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,10 @@ export interface TokenStatusModel {

export const isAdmin = (user: UserModel | null): boolean => {
if (!user) return false
return user.role.includes('admin')
return user.role.includes('ADMIN')
}

export const isTrader = (user: UserModel | null): boolean => {
if (!user) return false
return user.role.includes('trader')
return user.role.includes('TRADER')
}