Skip to content
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

Merged
merged 11 commits into from
Jan 28, 2025
9 changes: 7 additions & 2 deletions src/app/api/v1/tarot/question/recommends/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,27 @@ import { NextResponse } from 'next/server';

export async function GET() {
const mockData: TarotQuestionRecommendListResponse = {
question: [
questions: [
{
recommendQuestionId: 1,
question: '썸남 썸녀랑 잘 될까?',
referenceCount: 1,
},
{
recommendQuestionId: 2,
question: '상반기에 취업할 수 있을까?',
question: '상반기에 취업할 수 있을까? 상반기에 취업할 수 있을까? 상반기에 취업할 수 있을까?',
referenceCount: 222,
},
{
recommendQuestionId: 3,
question: '그 사람은 내 생각하고 있을까?',
referenceCount: 3333,
},
{
recommendQuestionId: 4,
question: '그 사람은 내 생각하고 있을까?',
referenceCount: 3334,
},
],
};

Expand Down
21 changes: 14 additions & 7 deletions src/app/page.tsx
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)}>
Copy link
Member Author

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

<ChatOverview />
</HydrationBoundary>
);
}
18 changes: 10 additions & 8 deletions src/chat/apis/createChatRoom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { MessageSenderTypeSchema } from '../models/messageSender';

export type CreateChatRoomResponse = {
roomId: number;
message: {
message?: {
messageId: number;
type: string;
sender: string;
Expand All @@ -15,12 +15,14 @@ export type CreateChatRoomResponse = {

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()),
}),
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()),
})
.optional(),
});

type CreateChatRoomData = z.infer<typeof schema>;
Expand All @@ -36,6 +38,6 @@ export const createChatRoom = () => {
.then((res) => validate(res.data))
.catch((error) => {
console.error(error);
return undefined;
throw error;
});
};
29 changes: 29 additions & 0 deletions src/chat/components/ChatOverview/index.tsx
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>
);
}
70 changes: 70 additions & 0 deletions src/chat/components/QuickQuestionPicker/index.tsx
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
Copy link
Contributor

Choose a reason for hiding this comment

The 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>
);
}
77 changes: 77 additions & 0 deletions src/chat/components/QuickQuestionPickerBox/index.tsx
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(
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재 에러 처리가 되어있지 않습니다.
기획적으로 정리하고 #25 에서 작업 예정

{
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>
);
}
33 changes: 33 additions & 0 deletions src/chat/components/RefreshQuickQuestionButton/index.tsx
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: 아이콘 추가 */}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

#15 에서 아이콘 컴포넌트 작업이 마무리 되면 추가하겠습니다.

</button>
);
}
25 changes: 23 additions & 2 deletions src/shared/lib/reactQuery/ReactQueryClientProvider.tsx
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>;
}
6 changes: 3 additions & 3 deletions src/tarot/apis/getTarotQuestionRecommends.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ import apiClient from '@/shared/lib/axios/apiClient';
import { z } from 'zod';

export type TarotQuestionRecommendListResponse = {
question: {
questions: {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

필드명을 잘못 적었는데 zod가 잡아줬습니다! ㅎㅎ

Copy link
Contributor

Choose a reason for hiding this comment

The 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(),
Expand All @@ -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);
Expand Down