diff --git a/src/app/api/v1/chat/room/route.ts b/src/app/api/v1/chat/room/route.ts index cb6f246..10b4621 100644 --- a/src/app/api/v1/chat/room/route.ts +++ b/src/app/api/v1/chat/room/route.ts @@ -4,12 +4,6 @@ import { NextResponse } from 'next/server'; export async function POST() { const mockData: CreateChatRoomResponse = { roomId: 1, - message: { - messageId: 1, - type: 'SYSTEM_NORMAL', - sender: 'SYSTEM', - answer: ['안녕이다냥?'], - }, }; return NextResponse.json({ data: mockData }); diff --git a/src/app/api/v1/tarot/question/recommends/route.ts b/src/app/api/v1/tarot/question/recommends/route.ts index 0b02c62..0c761b4 100644 --- a/src/app/api/v1/tarot/question/recommends/route.ts +++ b/src/app/api/v1/tarot/question/recommends/route.ts @@ -3,7 +3,7 @@ import { NextResponse } from 'next/server'; export async function GET() { const mockData: TarotQuestionRecommendListResponse = { - question: [ + questions: [ { recommendQuestionId: 1, question: '썸남 썸녀랑 잘 될까?', @@ -11,7 +11,7 @@ export async function GET() { }, { recommendQuestionId: 2, - question: '상반기에 취업할 수 있을까?', + question: '상반기에 취업할 수 있을까? 상반기에 취업할 수 있을까? 상반기에 취업할 수 있을까?', referenceCount: 222, }, { @@ -19,6 +19,11 @@ export async function GET() { question: '그 사람은 내 생각하고 있을까?', referenceCount: 3333, }, + { + recommendQuestionId: 4, + question: '그 사람은 내 생각하고 있을까?', + referenceCount: 3334, + }, ], }; diff --git a/src/app/page.tsx b/src/app/page.tsx index f9e4e5b..fb1e950 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -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 ( - <> - {null} - {null} - + + + ); } diff --git a/src/chat/apis/createChatRoom.ts b/src/chat/apis/createChatRoom.ts index f07a3ad..378c6ff 100644 --- a/src/chat/apis/createChatRoom.ts +++ b/src/chat/apis/createChatRoom.ts @@ -1,26 +1,12 @@ import apiClient from '@/shared/lib/axios/apiClient'; import { z } from 'zod'; -import { MessageCategorySchema } from '../models/messageCategory'; -import { MessageSenderTypeSchema } from '../models/messageSender'; export type CreateChatRoomResponse = { roomId: number; - message: { - messageId: number; - type: string; - sender: string; - answer: string[]; - }; }; const schema = z.object({ roomId: z.number(), - message: z.object({ - messageId: z.number(), - type: z.literal(MessageCategorySchema.Enum.SYSTEM_NORMAL_REPLY), - sender: z.literal(MessageSenderTypeSchema.Enum.SYSTEM), - answer: z.array(z.string()), - }), }); type CreateChatRoomData = z.infer; @@ -36,6 +22,6 @@ export const createChatRoom = () => { .then((res) => validate(res.data)) .catch((error) => { console.error(error); - return undefined; + throw error; }); }; diff --git a/src/chat/components/ChatOverview/index.tsx b/src/chat/components/ChatOverview/index.tsx new file mode 100644 index 0000000..e82cfc3 --- /dev/null +++ b/src/chat/components/ChatOverview/index.tsx @@ -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 ( + +

props.theme.fonts.headline2} + `} + > + AI 타로 술사, 타로냥에게 +
무엇이든 물어봐라냥 +

+
+ +
+
+ ); +} diff --git a/src/chat/components/QuickQuestionPicker/index.tsx b/src/chat/components/QuickQuestionPicker/index.tsx new file mode 100644 index 0000000..15160d5 --- /dev/null +++ b/src/chat/components/QuickQuestionPicker/index.tsx @@ -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; +}; + +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 ( + + ); +} diff --git a/src/chat/components/QuickQuestionPickerBox/index.tsx b/src/chat/components/QuickQuestionPickerBox/index.tsx new file mode 100644 index 0000000..0cb2cbc --- /dev/null +++ b/src/chat/components/QuickQuestionPickerBox/index.tsx @@ -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( + { + roomId: data.roomId, + message: question.question, + intent: 'RECOMMEND_QUESTION', + referenceQuestionId: question.recommendQuestionId, + }, + { + onSuccess: () => { + router.push(`/chats/${data.roomId}`); + }, + } + ); + }, + }); + }, + })); + }; + + return ( +
+
+ {adaptQuestionRecommends(data).map((question) => ( + + ))} +
+
+ +
+
+ ); +} diff --git a/src/chat/components/RefreshQuickQuestionButton/index.tsx b/src/chat/components/RefreshQuickQuestionButton/index.tsx new file mode 100644 index 0000000..1a95442 --- /dev/null +++ b/src/chat/components/RefreshQuickQuestionButton/index.tsx @@ -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 ( + + ); +} diff --git a/src/shared/lib/reactQuery/ReactQueryClientProvider.tsx b/src/shared/lib/reactQuery/ReactQueryClientProvider.tsx index 0111787..f47009c 100644 --- a/src/shared/lib/reactQuery/ReactQueryClientProvider.tsx +++ b/src/shared/lib/reactQuery/ReactQueryClientProvider.tsx @@ -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 {children}; } diff --git a/src/tarot/apis/getTarotQuestionRecommends.ts b/src/tarot/apis/getTarotQuestionRecommends.ts index 485db64..fe2a149 100644 --- a/src/tarot/apis/getTarotQuestionRecommends.ts +++ b/src/tarot/apis/getTarotQuestionRecommends.ts @@ -2,7 +2,7 @@ import apiClient from '@/shared/lib/axios/apiClient'; import { z } from 'zod'; export type TarotQuestionRecommendListResponse = { - question: { + questions: { recommendQuestionId: number; question: string; referenceCount: number; @@ -10,7 +10,7 @@ export type TarotQuestionRecommendListResponse = { }; 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; +export type TarotQuestionRecommendListData = z.infer; const validate = (data: TarotQuestionRecommendListResponse): TarotQuestionRecommendListData => { const validatedData = schema.parse(data);