Skip to content

Commit 7a0069a

Browse files
authored
Merge pull request #26 from Nexters/feat/#22
[Feat/#22] 인덱스 페이지 구현
2 parents a806438 + 09220ba commit 7a0069a

File tree

10 files changed

+256
-34
lines changed

10 files changed

+256
-34
lines changed

src/app/api/v1/chat/room/route.ts

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,6 @@ import { NextResponse } from 'next/server';
44
export async function POST() {
55
const mockData: CreateChatRoomResponse = {
66
roomId: 1,
7-
message: {
8-
messageId: 1,
9-
type: 'SYSTEM_NORMAL',
10-
sender: 'SYSTEM',
11-
answer: ['안녕이다냥?'],
12-
},
137
};
148

159
return NextResponse.json({ data: mockData });

src/app/api/v1/tarot/question/recommends/route.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,27 @@ import { NextResponse } from 'next/server';
33

44
export async function GET() {
55
const mockData: TarotQuestionRecommendListResponse = {
6-
question: [
6+
questions: [
77
{
88
recommendQuestionId: 1,
99
question: '썸남 썸녀랑 잘 될까?',
1010
referenceCount: 1,
1111
},
1212
{
1313
recommendQuestionId: 2,
14-
question: '상반기에 취업할 수 있을까?',
14+
question: '상반기에 취업할 수 있을까? 상반기에 취업할 수 있을까? 상반기에 취업할 수 있을까?',
1515
referenceCount: 222,
1616
},
1717
{
1818
recommendQuestionId: 3,
1919
question: '그 사람은 내 생각하고 있을까?',
2020
referenceCount: 3333,
2121
},
22+
{
23+
recommendQuestionId: 4,
24+
question: '그 사람은 내 생각하고 있을까?',
25+
referenceCount: 3334,
26+
},
2227
],
2328
};
2429

src/app/page.tsx

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,18 @@
1-
import HeaderContent from '@/shared/components/HeaderContent';
2-
import MainContent from '@/shared/components/MainContent';
1+
import ChatOverview from '@/chat/components/ChatOverview';
2+
import { getTarotQuestionRecommends } from '@/tarot/apis/getTarotQuestionRecommends';
3+
import { dehydrate, HydrationBoundary, QueryClient } from '@tanstack/react-query';
4+
5+
export default async function HomePage() {
6+
const queryClient = new QueryClient();
7+
8+
await queryClient.prefetchQuery({
9+
queryKey: ['tarotQuestionRecommends'],
10+
queryFn: getTarotQuestionRecommends,
11+
});
312

4-
export default function HomePage() {
513
return (
6-
<>
7-
<HeaderContent>{null}</HeaderContent>
8-
<MainContent>{null}</MainContent>
9-
</>
14+
<HydrationBoundary state={dehydrate(queryClient)}>
15+
<ChatOverview />
16+
</HydrationBoundary>
1017
);
1118
}

src/chat/apis/createChatRoom.ts

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,12 @@
11
import apiClient from '@/shared/lib/axios/apiClient';
22
import { z } from 'zod';
3-
import { MessageCategorySchema } from '../models/messageCategory';
4-
import { MessageSenderTypeSchema } from '../models/messageSender';
53

64
export type CreateChatRoomResponse = {
75
roomId: number;
8-
message: {
9-
messageId: number;
10-
type: string;
11-
sender: string;
12-
answer: string[];
13-
};
146
};
157

168
const schema = z.object({
179
roomId: z.number(),
18-
message: z.object({
19-
messageId: z.number(),
20-
type: z.literal(MessageCategorySchema.Enum.SYSTEM_NORMAL_REPLY),
21-
sender: z.literal(MessageSenderTypeSchema.Enum.SYSTEM),
22-
answer: z.array(z.string()),
23-
}),
2410
});
2511

2612
type CreateChatRoomData = z.infer<typeof schema>;
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
'use client';
2+
3+
import QuickQuestionPickerBox from '@/chat/components/QuickQuestionPickerBox';
4+
import MainContent from '@/shared/components/MainContent';
5+
import { css } from 'styled-components';
6+
7+
export default function ChatOverview() {
8+
return (
9+
<MainContent>
10+
<h1
11+
css={css`
12+
margin-top: 170px;
13+
text-align: center;
14+
${(props) => props.theme.fonts.headline2}
15+
`}
16+
>
17+
AI 타로 술사, 타로냥에게
18+
<br /> 무엇이든 물어봐라냥
19+
</h1>
20+
<div
21+
css={css`
22+
margin-top: 32px;
23+
`}
24+
>
25+
<QuickQuestionPickerBox />
26+
</div>
27+
</MainContent>
28+
);
29+
}
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import { ColorsTypes } from '@/shared/lib/styledComponents/theme';
2+
import { css, useTheme } from 'styled-components';
3+
type Props = {
4+
question: string;
5+
onClick: () => void;
6+
selectedCount: number;
7+
color: keyof Pick<ColorsTypes, 'primary03' | 'primary01' | 'grey10' | 'grey60'>;
8+
};
9+
10+
export default function QuickQuestionPicker({ question, onClick, selectedCount, color }: Props) {
11+
const theme = useTheme();
12+
const formattedSelectedCount = selectedCount.toLocaleString();
13+
14+
const componentTheme = (() => {
15+
switch (color) {
16+
case 'primary03':
17+
case 'grey60':
18+
return {
19+
backgroundColor: theme.colors[color],
20+
titleColor: theme.colors.white,
21+
captionColor: theme.colors.grey10,
22+
};
23+
case 'primary01':
24+
case 'grey10':
25+
return {
26+
backgroundColor: theme.colors[color],
27+
titleColor: theme.colors.grey70,
28+
captionColor: theme.colors.grey60,
29+
};
30+
31+
default:
32+
const _exhausted: never = color;
33+
throw new Error(`처리되지 않은 색상 타입입니다. ${_exhausted}`);
34+
}
35+
})();
36+
37+
return (
38+
<button
39+
type="button"
40+
onClick={onClick}
41+
css={css`
42+
min-height: 94px;
43+
padding: 8px 12px;
44+
border: none;
45+
border-radius: 8px;
46+
background-color: ${componentTheme.backgroundColor};
47+
display: flex;
48+
flex-direction: column;
49+
justify-content: space-between;
50+
text-align: left;
51+
cursor: pointer;
52+
`}
53+
>
54+
<p
55+
css={css`
56+
${theme.fonts.subHead3}
57+
color: ${componentTheme.titleColor};
58+
`}
59+
>
60+
{question}
61+
</p>
62+
<p
63+
css={css`
64+
${theme.fonts.caption}
65+
color: ${componentTheme.captionColor};
66+
`}
67+
>{`${formattedSelectedCount}명이 질문 중`}</p>
68+
</button>
69+
);
70+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
import { createUserKeyCookie } from '@/auth/utils/createUserKeyCookie';
2+
import { useCreateChatRoom } from '@/chat/hooks/useCreateChatRoom';
3+
import { useSendChatMessage } from '@/chat/hooks/useSendChatMesasge';
4+
import { TarotQuestionRecommendListData } from '@/tarot/apis/getTarotQuestionRecommends';
5+
import { useTarotQuestionRecommends } from '@/tarot/hooks/useTarotQuestionRecommends';
6+
import { useRouter } from 'next/navigation';
7+
import { css } from 'styled-components';
8+
import QuickQuestionPicker from '../QuickQuestionPicker';
9+
import RefreshQuickQuestionButton from '../RefreshQuickQuestionButton';
10+
11+
export default function QuickQuestionPickerBox() {
12+
const { data } = useTarotQuestionRecommends();
13+
const { mutate: createChatRoom } = useCreateChatRoom();
14+
const { mutateAsync: sendChatMessage } = useSendChatMessage();
15+
const router = useRouter();
16+
17+
if (!data) return null;
18+
19+
const adaptQuestionRecommends = (data: TarotQuestionRecommendListData) => {
20+
const colors = ['primary03', 'grey10', 'primary01', 'grey60'] as const;
21+
return data.questions.map((question, i) => ({
22+
...question,
23+
color: colors[i],
24+
onClick: async () => {
25+
await createUserKeyCookie();
26+
createChatRoom(undefined, {
27+
onSuccess: (data) => {
28+
sendChatMessage(
29+
{
30+
roomId: data.roomId,
31+
message: question.question,
32+
intent: 'RECOMMEND_QUESTION',
33+
referenceQuestionId: question.recommendQuestionId,
34+
},
35+
{
36+
onSuccess: () => {
37+
router.push(`/chats/${data.roomId}`);
38+
},
39+
}
40+
);
41+
},
42+
});
43+
},
44+
}));
45+
};
46+
47+
return (
48+
<div>
49+
<div
50+
css={css`
51+
display: grid;
52+
grid-template-columns: repeat(2, 1fr);
53+
grid-template-rows: repeat(2, 1fr);
54+
gap: 8px;
55+
`}
56+
>
57+
{adaptQuestionRecommends(data).map((question) => (
58+
<QuickQuestionPicker
59+
key={question.recommendQuestionId}
60+
question={question.question}
61+
onClick={question.onClick}
62+
selectedCount={question.referenceCount}
63+
color={question.color}
64+
/>
65+
))}
66+
</div>
67+
<div
68+
css={css`
69+
width: fit-content;
70+
margin: 12px auto 0;
71+
`}
72+
>
73+
<RefreshQuickQuestionButton />
74+
</div>
75+
</div>
76+
);
77+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { useQueryClient } from '@tanstack/react-query';
2+
import { css } from 'styled-components';
3+
4+
export default function RefreshQuickQuestionButton() {
5+
const queryClient = useQueryClient();
6+
7+
const handleClick = () => {
8+
queryClient.invalidateQueries({ queryKey: ['tarotQuestionRecommends'] });
9+
};
10+
11+
return (
12+
<button
13+
type="button"
14+
onClick={handleClick}
15+
css={css`
16+
padding: 5px 8px;
17+
border: none;
18+
background-color: transparent;
19+
color: ${(props) => props.theme.colors.grey60};
20+
cursor: pointer;
21+
`}
22+
>
23+
<span
24+
css={css`
25+
${(props) => props.theme.fonts.body1};
26+
`}
27+
>
28+
추천 질문 변경
29+
</span>
30+
{/* TODO: 아이콘 추가 */}
31+
</button>
32+
);
33+
}
Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,31 @@
11
'use client';
22

3-
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
3+
import { isServer, QueryClient, QueryClientProvider } from '@tanstack/react-query';
44
import { ReactNode } from 'react';
55

6-
const queryClient = new QueryClient();
6+
function makeQueryClient() {
7+
return new QueryClient({
8+
defaultOptions: {
9+
queries: {
10+
staleTime: 60 * 1000,
11+
},
12+
},
13+
});
14+
}
15+
16+
let browserQueryClient: QueryClient | undefined = undefined;
17+
18+
function getQueryClient() {
19+
if (isServer) {
20+
return makeQueryClient();
21+
} else {
22+
if (!browserQueryClient) browserQueryClient = makeQueryClient();
23+
return browserQueryClient;
24+
}
25+
}
726

827
export default function ReactQueryClientProvider({ children }: { children: ReactNode }) {
28+
const queryClient = getQueryClient();
29+
930
return <QueryClientProvider client={queryClient}>{children}</QueryClientProvider>;
1031
}

src/tarot/apis/getTarotQuestionRecommends.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,15 @@ import apiClient from '@/shared/lib/axios/apiClient';
22
import { z } from 'zod';
33

44
export type TarotQuestionRecommendListResponse = {
5-
question: {
5+
questions: {
66
recommendQuestionId: number;
77
question: string;
88
referenceCount: number;
99
}[];
1010
};
1111

1212
const schema = z.object({
13-
question: z.array(
13+
questions: z.array(
1414
z.object({
1515
recommendQuestionId: z.number(),
1616
question: z.string(),
@@ -19,7 +19,7 @@ const schema = z.object({
1919
),
2020
});
2121

22-
type TarotQuestionRecommendListData = z.infer<typeof schema>;
22+
export type TarotQuestionRecommendListData = z.infer<typeof schema>;
2323

2424
const validate = (data: TarotQuestionRecommendListResponse): TarotQuestionRecommendListData => {
2525
const validatedData = schema.parse(data);

0 commit comments

Comments
 (0)