-
Notifications
You must be signed in to change notification settings - Fork 0
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
[Feat/#22] 인덱스 페이지 구현 #26
Changes from 9 commits
3d91be7
a67b4c7
3c40c52
21638da
af29994
a000441
7e98822
93e9190
133288f
6aaf17a
09220ba
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,18 @@ | ||
import HeaderContent from '@/shared/components/HeaderContent'; | ||
import MainContent from '@/shared/components/MainContent'; | ||
import ChatOverview from '@/chat/components/ChatOverview'; | ||
import { getTarotQuestionRecommends } from '@/tarot/apis/getTarotQuestionRecommends'; | ||
import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query'; | ||
|
||
export default async function HomePage() { | ||
const queryClient = new QueryClient(); | ||
|
||
await queryClient.prefetchQuery({ | ||
queryKey: ['tarotQuestionRecommends'], | ||
queryFn: getTarotQuestionRecommends, | ||
}); | ||
|
||
export default function HomePage() { | ||
return ( | ||
<> | ||
<HeaderContent>{null}</HeaderContent> | ||
<MainContent>{null}</MainContent> | ||
</> | ||
<HydrationBoundary state={dehydrate(queryClient)}> | ||
<ChatOverview /> | ||
</HydrationBoundary> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
'use client'; | ||
|
||
import QuickQuestionPickerBox from '@/chat/components/QuickQuestionPickerBox'; | ||
import MainContent from '@/shared/components/MainContent'; | ||
import { css } from 'styled-components'; | ||
|
||
export default function ChatOverview() { | ||
return ( | ||
<MainContent> | ||
<h1 | ||
css={css` | ||
margin-top: 170px; | ||
text-align: center; | ||
${(props) => props.theme.fonts.headline2} | ||
`} | ||
> | ||
AI 타로 술사, 타로냥에게 | ||
<br /> 무엇이든 물어봐라냥 | ||
</h1> | ||
<div | ||
css={css` | ||
margin-top: 32px; | ||
`} | ||
> | ||
<QuickQuestionPickerBox /> | ||
</div> | ||
</MainContent> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import { ColorsTypes } from '@/shared/lib/styledComponents/theme'; | ||
import { css, useTheme } from 'styled-components'; | ||
type Props = { | ||
question: string; | ||
onClick: () => void; | ||
selectedCount: number; | ||
color: keyof Pick<ColorsTypes, 'primary03' | 'primary01' | 'grey10' | 'grey60'>; | ||
}; | ||
Comment on lines
+7
to
+8
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 타입의 마술사 이 준 근 |
||
|
||
export default function QuickQuestionPicker({ question, onClick, selectedCount, color }: Props) { | ||
const theme = useTheme(); | ||
const formattedSelectedCount = selectedCount.toLocaleString(); | ||
|
||
const componentTheme = (() => { | ||
switch (color) { | ||
case 'primary03': | ||
case 'grey60': | ||
return { | ||
backgroundColor: theme.colors[color], | ||
titleColor: theme.colors.white, | ||
captionColor: theme.colors.grey10, | ||
}; | ||
case 'primary01': | ||
case 'grey10': | ||
return { | ||
backgroundColor: theme.colors[color], | ||
titleColor: theme.colors.grey70, | ||
captionColor: theme.colors.grey60, | ||
}; | ||
|
||
default: | ||
const _exhausted: never = color; | ||
throw new Error(`처리되지 않은 색상 타입입니다. ${_exhausted}`); | ||
} | ||
})(); | ||
|
||
return ( | ||
<button | ||
type="button" | ||
onClick={onClick} | ||
css={css` | ||
min-height: 94px; | ||
padding: 8px 12px; | ||
border: none; | ||
border-radius: 8px; | ||
background-color: ${componentTheme.backgroundColor}; | ||
display: flex; | ||
flex-direction: column; | ||
justify-content: space-between; | ||
text-align: left; | ||
cursor: pointer; | ||
`} | ||
> | ||
<p | ||
css={css` | ||
${theme.fonts.subHead3} | ||
color: ${componentTheme.titleColor}; | ||
`} | ||
> | ||
{question} | ||
</p> | ||
<p | ||
css={css` | ||
${theme.fonts.caption} | ||
color: ${componentTheme.captionColor}; | ||
`} | ||
>{`${formattedSelectedCount}명이 질문 중`}</p> | ||
</button> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
import { createUserKeyCookie } from '@/auth/utils/createUserKeyCookie'; | ||
import { useCreateChatRoom } from '@/chat/hooks/useCreateChatRoom'; | ||
import { useSendChatMessage } from '@/chat/hooks/useSendChatMesasge'; | ||
import { TarotQuestionRecommendListData } from '@/tarot/apis/getTarotQuestionRecommends'; | ||
import { useTarotQuestionRecommends } from '@/tarot/hooks/useTarotQuestionRecommends'; | ||
import { useRouter } from 'next/navigation'; | ||
import { css } from 'styled-components'; | ||
import QuickQuestionPicker from '../QuickQuestionPicker'; | ||
import RefreshQuickQuestionButton from '../RefreshQuickQuestionButton'; | ||
|
||
export default function QuickQuestionPickerBox() { | ||
const { data } = useTarotQuestionRecommends(); | ||
const { mutate: createChatRoom } = useCreateChatRoom(); | ||
const { mutateAsync: sendChatMessage } = useSendChatMessage(); | ||
const router = useRouter(); | ||
|
||
if (!data) return null; | ||
|
||
const adaptQuestionRecommends = (data: TarotQuestionRecommendListData) => { | ||
const colors = ['primary03', 'grey10', 'primary01', 'grey60'] as const; | ||
return data.questions.map((question, i) => ({ | ||
...question, | ||
color: colors[i], | ||
onClick: async () => { | ||
await createUserKeyCookie(); | ||
createChatRoom(undefined, { | ||
onSuccess: (data) => { | ||
sendChatMessage( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 현재 에러 처리가 되어있지 않습니다. |
||
{ | ||
roomId: data.roomId, | ||
message: question.question, | ||
intent: 'RECOMMEND_QUESTION', | ||
referenceQuestionId: question.recommendQuestionId, | ||
}, | ||
{ | ||
onSuccess: () => { | ||
router.push(`/chats/${data.roomId}`); | ||
}, | ||
} | ||
); | ||
}, | ||
}); | ||
}, | ||
})); | ||
}; | ||
|
||
return ( | ||
<div> | ||
<div | ||
css={css` | ||
display: grid; | ||
grid-template-columns: repeat(2, 1fr); | ||
grid-template-rows: repeat(2, 1fr); | ||
gap: 8px; | ||
`} | ||
> | ||
{adaptQuestionRecommends(data).map((question) => ( | ||
<QuickQuestionPicker | ||
key={question.recommendQuestionId} | ||
question={question.question} | ||
onClick={question.onClick} | ||
selectedCount={question.referenceCount} | ||
color={question.color} | ||
/> | ||
))} | ||
</div> | ||
<div | ||
css={css` | ||
width: fit-content; | ||
margin: 12px auto 0; | ||
`} | ||
> | ||
<RefreshQuickQuestionButton /> | ||
</div> | ||
</div> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import { useQueryClient } from '@tanstack/react-query'; | ||
import { css } from 'styled-components'; | ||
|
||
export default function RefreshQuickQuestionButton() { | ||
const queryClient = useQueryClient(); | ||
|
||
const handleClick = () => { | ||
queryClient.invalidateQueries({ queryKey: ['tarotQuestionRecommends'] }); | ||
}; | ||
|
||
return ( | ||
<button | ||
type="button" | ||
onClick={handleClick} | ||
css={css` | ||
padding: 5px 8px; | ||
border: none; | ||
background-color: transparent; | ||
color: ${(props) => props.theme.colors.grey60}; | ||
cursor: pointer; | ||
`} | ||
> | ||
<span | ||
css={css` | ||
${(props) => props.theme.fonts.body1}; | ||
`} | ||
> | ||
추천 질문 변경 | ||
</span> | ||
{/* TODO: 아이콘 추가 */} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. #15 에서 아이콘 컴포넌트 작업이 마무리 되면 추가하겠습니다. |
||
</button> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,31 @@ | ||
'use client'; | ||
|
||
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; | ||
import { isServer, QueryClient, QueryClientProvider } from '@tanstack/react-query'; | ||
import { ReactNode } from 'react'; | ||
|
||
const queryClient = new QueryClient(); | ||
function makeQueryClient() { | ||
return new QueryClient({ | ||
defaultOptions: { | ||
queries: { | ||
staleTime: 60 * 1000, | ||
}, | ||
}, | ||
}); | ||
} | ||
|
||
let browserQueryClient: QueryClient | undefined = undefined; | ||
|
||
function getQueryClient() { | ||
if (isServer) { | ||
return makeQueryClient(); | ||
} else { | ||
if (!browserQueryClient) browserQueryClient = makeQueryClient(); | ||
return browserQueryClient; | ||
} | ||
} | ||
|
||
export default function ReactQueryClientProvider({ children }: { children: ReactNode }) { | ||
const queryClient = getQueryClient(); | ||
|
||
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,15 +2,15 @@ import apiClient from '@/shared/lib/axios/apiClient'; | |
import { z } from 'zod'; | ||
|
||
export type TarotQuestionRecommendListResponse = { | ||
question: { | ||
questions: { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 필드명을 잘못 적었는데 zod가 잡아줬습니다! ㅎㅎ There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ㅋㅋㅋㅋ 좋습니다~! |
||
recommendQuestionId: number; | ||
question: string; | ||
referenceCount: number; | ||
}[]; | ||
}; | ||
|
||
const schema = z.object({ | ||
question: z.array( | ||
questions: z.array( | ||
z.object({ | ||
recommendQuestionId: z.number(), | ||
question: z.string(), | ||
|
@@ -19,7 +19,7 @@ const schema = z.object({ | |
), | ||
}); | ||
|
||
type TarotQuestionRecommendListData = z.infer<typeof schema>; | ||
export type TarotQuestionRecommendListData = z.infer<typeof schema>; | ||
|
||
const validate = (data: TarotQuestionRecommendListResponse): TarotQuestionRecommendListData => { | ||
const validatedData = schema.parse(data); | ||
|
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.
공식문서 참고하여 프리페치 SSR 기능을 추가했습니다.
https://tanstack.com/query/v5/docs/framework/react/guides/advanced-ssr