Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
067ef67
:lipstick: support 페이지 banner UI 제작
dolmeengii May 29, 2025
e11f175
:lipstick: support 페이지 input UI 제작
dolmeengii May 29, 2025
fe1c219
:lipstick: support 페이지 checkbox-agreement UI 제작
dolmeengii May 29, 2025
c7c6dd1
:lipstick: support 페이지 request-button UI 제작
dolmeengii May 29, 2025
6fdf2df
:lipstick: support 페이지 배경이미지 추가
dolmeengii May 29, 2025
944251f
:sparkles: support 페이지 제작 및 기능 추가 #122
dolmeengii May 29, 2025
4a1ec42
:sparkles: support page 연결
dolmeengii May 29, 2025
8b3e7cb
:recycle: send message 함수를 props를 통해 받도록 변경
nijesmik May 19, 2025
9c9fbbc
:wrench: UI 라이브러리 의존성 변경에 따른 tailwind config 수정
nijesmik May 19, 2025
9bcf63c
:sparkles: add `isFixed` prop to Header component for fixed positioning
nijesmik May 19, 2025
2779a42
:sparkles: add Mockup component and update tailwind config for mockup…
nijesmik May 19, 2025
00ab273
:bug: Message 타입 수정
nijesmik May 26, 2025
e4b2669
:lipstick: Mockup 컴포넌트 색상 변경
nijesmik May 26, 2025
20b279a
:recycle: Message 컴포넌트 경로 수정 및 Messages 컴포넌트 추가
nijesmik May 26, 2025
8eac8d9
:construction: 랜딩 페이지에 채팅 필터링 튜토리얼 컴포넌트 추가 #123
nijesmik May 26, 2025
d90e00b
:recycle: chatting room store 리팩토링 및 경로 수정
nijesmik May 26, 2025
7b9f178
:bug: enter key 입력 시 두번 입력되는 현상 해결
nijesmik May 26, 2025
0a8c154
:bug: onSendMessage 콜백 함수를 setValue 함수 밖에서 처리하도록 변경
nijesmik May 26, 2025
debfeb7
:sparkles: 튜토리얼 채팅 기능 개선 #123
nijesmik May 26, 2025
71eda5d
:wrench: production 환경에서 console 로그 제거 기능 추가
nijesmik May 30, 2025
d10a653
:sparkles: 필터링 api 추가
nijesmik May 30, 2025
7a78e7c
:lipstick: Hero 컴포넌트 스타일 개선 및 props 변경
nijesmik May 30, 2025
c9a9084
:truck: Hero 컴포넌트 파일 경로 변경
nijesmik May 30, 2025
5fe3fe5
:lipstick: style 수정
dolmeengii Jun 4, 2025
430d3a1
:lipstick: style 수정
dolmeengii Jun 5, 2025
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
2 changes: 1 addition & 1 deletion app/(home)/page.tsx
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { default } from '@/pages/coming-soon';
export { default } from '@/pages/landing';
2 changes: 1 addition & 1 deletion app/support/assist/page.tsx
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { default } from '@/pages/coming-soon';
export { default } from '@/pages/support';
2 changes: 1 addition & 1 deletion app/support/request/page.tsx
Original file line number Diff line number Diff line change
@@ -1 +1 @@
export { default } from '@/pages/coming-soon';
export { default } from '@/pages/support';
7 changes: 7 additions & 0 deletions next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,16 @@ const nextConfig = {
source: '/client/:path*',
destination: `${process.env.NEXT_PUBLIC_API_BASE_URL}/:path*`,
},
{
source: '/filtering/:path*',
destination: `${process.env.NEXT_PUBLIC_FILTERING_API_BASE_URL}/:path*`,
},
];
},
}),
compiler: {
removeConsole: process.env.NODE_ENV === 'production',
},
images: {
remotePatterns: [
{
Expand Down
9 changes: 9 additions & 0 deletions public/images/background-support.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/app/layouts/default.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ interface Props {
const Layout: FC<Props> = ({ children }) => {
return (
<AuthProvider>
<Header />
<Header isFixed />
<main>{children}</main>
</AuthProvider>
);
Expand Down
19 changes: 10 additions & 9 deletions src/app/styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,18 @@
@tailwind utilities;

@layer base {
html {
font-family: "Pretendard Variable", Pretendard, -apple-system, BlinkMacSystemFont, system-ui, Roboto, "Helvetica Neue", "Segoe UI", "Apple SD Gothic Neo", "Noto Sans KR", "Malgun Gothic", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", sans-serif;
}
html {
font-family: "Pretendard Variable", Pretendard, -apple-system, BlinkMacSystemFont, system-ui, Roboto, "Helvetica Neue", "Segoe UI", "Apple SD Gothic Neo", "Noto Sans KR", "Malgun Gothic", "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", sans-serif;
scrollbar-width: thin;
}

body {
@apply tracking-tight overflow-x-hidden;
}
body {
@apply tracking-tight overflow-x-hidden;
}
}

@layer components {
.wrapper {
@apply max-w-screen-xl mx-auto px-5 md:max-xl:px-7;
}
.wrapper {
@apply max-w-screen-xl mx-auto px-5 md:px-7 lg:px-10;
}
}
1 change: 1 addition & 0 deletions src/entities/chatting-room/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { ChattingRoomProvider, useChattingRoomStore } from './store';
36 changes: 36 additions & 0 deletions src/entities/chatting-room/lib.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { addParsedTime } from '@/entities/message';

const getCounterpart = (
chattingRoom: ChattingRoom.RawData,
currentMemberId?: MemberId,
) => {
if (chattingRoom.channelType === 'group' || !currentMemberId) {
return undefined;
}
return chattingRoom.userList.find((id) => id !== currentMemberId);
};

export const getChattingRoomMap = ({
chattingRooms,
memberId,
}: {
chattingRooms?: ChattingRoom.RawData[];
memberId?: MemberId;
}) => {
if (!chattingRooms) {
return {};
}

return Object.fromEntries(
chattingRooms.map((room) => {
return [
room.roomId,
{
...room,
lastMessage: addParsedTime(room.lastMessage),
counterpart: getCounterpart(room, memberId),
},
];
}),
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,10 @@ import { useStore } from 'zustand';
import { createStore } from 'zustand/vanilla';

import { useMemberStore } from '@/entities/member';
import { addParsedTime } from '@/entities/message';

import { getCounterpart } from '../lib';
import { getChattingRoomMap } from './lib';

type RoomId = ChattingRoom['roomId'];
type MemberId = Member['uuid'];

interface State {
chattingRoomMap: Record<RoomId, ChattingRoom>;
Expand Down Expand Up @@ -55,25 +53,15 @@ const ChattingRoomContext = createContext<ChattingRoomStoreApi | null>(null);

export const ChattingRoomProvider: FC<{
children: ReactNode;
chattingRooms: ChattingRoom.RawData[];
chattingRooms?: ChattingRoom.RawData[];
chattingMemberMap: Record<MemberId, Member>;
}> = ({ children, chattingRooms, chattingMemberMap }) => {
const storeRef = useRef<ChattingRoomStoreApi | null>(null);
const memberId = useMemberStore((state) => state.member?.uuid);

if (storeRef.current === null) {
const chattingRoomMap = Object.fromEntries(
chattingRooms.map((room) => {
return [
room.roomId,
{
...room,
lastMessage: addParsedTime(room.lastMessage),
counterpart: getCounterpart(room, memberId),
},
];
}),
);
const chattingRoomMap = getChattingRoomMap({ chattingRooms, memberId });

storeRef.current = createChattingRoomStore({
chattingRoomMap,
chattingMemberMap,
Expand Down
File renamed without changes.
1 change: 1 addition & 0 deletions src/features/messages/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as Messages } from './ui/messages';
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { usePathname } from 'next/navigation';
import { memo } from 'react';

import { useChattingRoomStore } from '@/entities/chatting-room';
import { useMemberStore } from '@/entities/member';
import {
MessageNotice,
MessageReceived,
MessageSent,
} from '@/entities/message';
import { TUTORIAL_CHAT_MEMBER_ID } from '@/shared/config';

const evaluateIsFirst = (message: Message.Common, prevMessage?: Message) => {
if (!prevMessage || prevMessage.type === 'NoticeMessage') {
Expand All @@ -24,12 +27,26 @@ const evaluateIsLast = (message: Message.Common, nextMessage?: Message) => {
);
};

const useMemberId = () => {
const memberId = useMemberStore((state) => state.member?.uuid);

const pathname = usePathname();
if (pathname === '/') {
return TUTORIAL_CHAT_MEMBER_ID.user;
}

return memberId;
};

const Message = memo<{
message: Message;
prevMessage?: Message;
nextMessage?: Message;
prevMessage: Message | undefined;
nextMessage: Message | undefined;
}>(({ message, prevMessage, nextMessage }) => {
const currentMemberId = useMemberStore((state) => state.member?.uuid);
const currentMemberId = useMemberId();
const chattingMemberMap = useChattingRoomStore(
(state) => state.chattingMemberMap,
);

if (message.type === 'NoticeMessage') {
const text = message.content; // TODO: 테스트 필요
Expand All @@ -50,8 +67,7 @@ const Message = memo<{
);
}

// TODO: senderId를 통해서 유저 정보를 가져와야 함
const sender = { nickname: '테스트', profileLink: null };
const sender = chattingMemberMap[message.senderId];

return (
<MessageReceived
Expand Down
18 changes: 18 additions & 0 deletions src/features/messages/ui/messages.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import Message from './message';

interface Props {
messages: Message[];
}

const Messages = ({ messages }: Props) => {
return messages.map((message, index) => (
<Message
key={message.messageId}
message={message}
prevMessage={messages[index - 1]}
nextMessage={messages[index + 1]}
/>
));
};

export default Messages;
9 changes: 0 additions & 9 deletions src/pages/chat/lib.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/pages/chat/ui/chatting/list-item-nickname.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Skeleton } from '@heroui/react';

import { useChattingRoomStore } from '../../store/chatting-room';
import { useChattingRoomStore } from '@/entities/chatting-room';

const Nickname: FC<{
chattingRoom: ChattingRoom;
Expand Down
2 changes: 1 addition & 1 deletion src/pages/chat/ui/chatting/list-item-preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import { Chip } from '@heroui/react';

import { useChattingRoomStore } from '../../store/chatting-room';
import { useChattingRoomStore } from '@/entities/chatting-room';

const Preview: FC<{
chattingRoomId: ChattingRoom['roomId'];
Expand Down
3 changes: 1 addition & 2 deletions src/pages/chat/ui/chatting/list.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
'use client';

import { useChattingRoomStore } from '@/entities/chatting-room';
import ChattingListItem from '@/pages/chat/ui/chatting/list-item';

import { useChattingRoomStore } from '../../store/chatting-room';

const compareDate = (a: ChattingRoom, b: ChattingRoom) => {
if (a.lastMessage.createdDate > b.lastMessage.createdDate) {
return -1;
Expand Down
2 changes: 1 addition & 1 deletion src/pages/chat/ui/page.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { ChattingRoomProvider } from '@/entities/chatting-room';
import { WebsocketProvider as WebsocketConnector } from '@/features/websocket';
import { requestChattingMemberMap } from '@/pages/chat/api/member';
import { Chatting } from '@/widgets/chatting';
import ServiceContent from '@/widgets/service-content';

import { requestChattingList } from '../api/chatting';
import { CHATTING_ACTION_KEY } from '../config';
import { ChattingRoomProvider } from '../store/chatting-room';
import ChattingList from './chatting/list';

const ChattingPage = async () => {
Expand Down
25 changes: 25 additions & 0 deletions src/pages/landing/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
'use client';

import axios from 'axios';

import { FILTERING_API_BASE_URL } from '@/shared/config';

const baseURL =
process.env.NODE_ENV === 'development'
? '/filtering'
: FILTERING_API_BASE_URL;

const client = axios.create({
baseURL,
headers: {
'Content-Type': 'application/json',
},
});

export const fetchFilteringPrediction = async (text: string) => {
return client.post('/predict', { text }).then((res) => {
const { prediction } = res.data;
console.debug('prediction:', prediction);
return prediction === 2;
});
};
1 change: 1 addition & 0 deletions src/pages/landing/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default } from './ui/page';
26 changes: 26 additions & 0 deletions src/pages/landing/lib/tutorial-chatting.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
'use client';

let id = 0;

export const createMessage = (
content: string,
senderId: string,
): Message.Common => {
const messageId = id++;
const date = new Date();
const createdDate = date.toISOString();
const time = date.toLocaleTimeString('ko-KR', {
hour: '2-digit',
minute: '2-digit',
});

return {
type: 'Common',
content,
messageId,
createdDate,
time,
senderId,
filteredLevel: 0,
};
};
15 changes: 15 additions & 0 deletions src/pages/landing/ui/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Hero } from '@/shared/ui';

import TutorialChatting from './tutorial-chatting/chatting';
import TutorialChattingDescription from './tutorial-chatting/description';

const LandingPage = () => {
return (
<Hero className='bg-sky-50/40' isFirst>
<TutorialChattingDescription />
<TutorialChatting />
</Hero>
);
};

export default LandingPage;
50 changes: 50 additions & 0 deletions src/pages/landing/ui/tutorial-chatting/chatting-body.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
'use client';

import { CardBody, CardFooter } from '@heroui/react';
import { useEffect, useRef, useState } from 'react';

import { Messages } from '@/features/messages';
import { TUTORIAL_CHAT_MEMBER_ID } from '@/shared/config';
import { ChattingInput } from '@/widgets/chatting';

import { fetchFilteringPrediction } from '../../api';
import { createMessage } from '../../lib/tutorial-chatting';

const TutorialChattingBody = () => {
const ref = useRef<HTMLDivElement>(null);
const [messages, setMessages] = useState<Message[]>([
createMessage('안녕하세요', TUTORIAL_CHAT_MEMBER_ID.ai),
createMessage('무엇을 도와드릴까요?', TUTORIAL_CHAT_MEMBER_ID.ai),
]);

const handleSendMessage = (value: string) => {
fetchFilteringPrediction(value).then((filtered) => {
if (filtered) {
// TODO: 필터링된 메시지 추가
}
const message = createMessage(value, TUTORIAL_CHAT_MEMBER_ID.user);
setMessages((prev) => [...prev, message]);
});
};

useEffect(() => {
if (ref.current) {
ref.current.scrollTop = ref.current.scrollHeight;
}
}, [messages]);

return (
<>
<CardBody className='overflow-y-hidden p-0'>
<div ref={ref} className='overflow-y-auto px-1'>
<Messages messages={messages} />
</div>
</CardBody>
<CardFooter className='px-1 pb-2 pt-3'>
<ChattingInput onSendMessage={handleSendMessage} />
</CardFooter>
</>
);
};

export default TutorialChattingBody;
Loading