Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ const StudentIdStep = lazy(() => import('./pages/Auth/SignUp/StudentIdStep'));
const TermStep = lazy(() => import('./pages/Auth/SignUp/TermStep'));
const UniversityStep = lazy(() => import('./pages/Auth/SignUp/UniversityStep'));
const ChatListPage = lazy(() => import('./pages/Chat'));
const ChatSearch = lazy(() => import('./pages/Chat/ChatSearch'));
const ChatAdd = lazy(() => import('./pages/Chat/AddChatRoom'));
const ChatRoom = lazy(() => import('./pages/Chat/ChatRoom'));
const ApplicationPage = lazy(() => import('./pages/Club/Application'));
const ApplyCompletePage = lazy(() => import('./pages/Club/Application/applyCompletePage'));
Expand Down Expand Up @@ -120,6 +122,8 @@ function App() {
<Route path="mypage/manager/:clubId/recruitment/write" element={<ManagedRecruitmentWrite />} />
<Route path="mypage/manager/:clubId/recruitment/account" element={<ManagedAccount />} />
<Route path="profile" element={<Profile />} />
<Route path="chats/search" element={<ChatSearch />} />
<Route path="chats/add" element={<ChatAdd />} />
<Route path="chats/:chatRoomId" element={<ChatRoom />} />
</Route>
</Route>
Expand Down
56 changes: 55 additions & 1 deletion src/apis/chat/entity.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { PaginationParams, PaginationResponse } from '../common/pagination';
import type { PaginationParams, PaginationResponse } from '@/apis/common/pagination';

export type ChatType = 'DIRECT' | 'CLUB_GROUP' | 'GROUP' | 'INQUIRY';
export type SortBy = 'CLUB' | 'NAME';

export interface Room {
roomId: number;
Expand Down Expand Up @@ -35,6 +36,7 @@ export interface SendChatMessageRequest {

export interface ChatMessageRequestParam extends PaginationParams {
chatRoomId: number;
messageId?: number;
}

export interface ChatMessagesResponse extends PaginationResponse {
Expand All @@ -45,3 +47,55 @@ export interface ChatMessagesResponse extends PaginationResponse {
export interface CreateChatRoomResponse {
chatRoomId: number;
}

export interface Messages {
roomId: number;
chatType: ChatType;
roomName: string;
roomImageUrl: string;
matchedMessage: string;
matchedMessageSentAt: string;
matchedMessageId: number;
}

export interface RoomMatched extends PaginationResponse {
rooms?: Room[];
}

export interface MessageMatched extends PaginationResponse {
messages?: Messages[];
}

export interface MatchedRequestParams extends PaginationParams {
keyword: string;
}

export interface MatchResponse {
roomMatches?: RoomMatched;
messageMatches?: MessageMatched;
}

export interface User {
userId: number;
name: string;
imageUrl: string;
studentNumber: string;
}

export interface Section {
clubId: number;
clubName: string;
users: User[];
}

export interface InvitableFriendRequestParams extends PaginationParams {
query: string;
sortBy: SortBy;
}

export interface InvitableFriend extends PaginationResponse {
sortBy: SortBy;
grouped: boolean;
users?: User[];
sections?: Section[];
}
28 changes: 28 additions & 0 deletions src/apis/chat/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ import type {
ChatRoomsResponse,
CreateChatRoomResponse,
SendChatMessageRequest,
MatchResponse,
MatchedRequestParams,
InvitableFriendRequestParams,
InvitableFriend,
} from './entity';

export const getChatRooms = async () => {
Expand All @@ -23,6 +27,14 @@ export const postChatRooms = async (userId: number) => {
return response;
};

export const postChatRoomsGroup = async (userIds: number[]) => {
const response = await apiClient.post<CreateChatRoomResponse>('chats/rooms/group', {
body: { userIds },
requiresAuth: true,
});
return response;
};

export const postChatMessage = async ({ chatRoomId, content }: SendChatMessageRequest) => {
return apiClient.post<ChatMessage>(`chats/rooms/${chatRoomId}/messages`, {
body: { content },
Expand Down Expand Up @@ -64,3 +76,19 @@ export const deleteChatRoom = async (chatRoomId: number) => {
});
return response;
};

export const getSearchChat = async ({ ...query }: MatchedRequestParams) => {
const response = await apiClient.get<MatchResponse>(`chats/rooms/search`, {
params: query,
requiresAuth: true,
});
return response;
};

export const getInvitableFriends = async ({ ...query }: InvitableFriendRequestParams) => {
const response = await apiClient.get<InvitableFriend>('chats/rooms/invitables', {
params: query,
requiresAuth: true,
});
return response;
};
7 changes: 7 additions & 0 deletions src/apis/chat/mutations.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { mutationOptions } from '@tanstack/react-query';
import {
patchChatRoomName,
postChatRoomsGroup,
postAdminChatRoom,
postChatMessage,
postChatMute,
Expand All @@ -11,6 +12,7 @@ import {
export const chatMutationKeys = {
createRoom: () => ['chat', 'createRoom'] as const,
createAdminRoom: () => ['chat', 'createAdminRoom'] as const,
createRoomGroup: () => ['chat', 'createRoomGroup'] as const,
sendMessage: () => ['chat', 'sendMessage'] as const,
toggleMute: (chatRoomId?: number) => ['chat', 'toggleMute', chatRoomId ?? 'unknown'] as const,
updateRoomName: () => ['chat', 'updateRoomName'] as const,
Expand All @@ -28,6 +30,11 @@ export const chatMutations = {
mutationKey: chatMutationKeys.createAdminRoom(),
mutationFn: postAdminChatRoom,
}),
createRoomGroup: () =>
mutationOptions({
mutationKey: chatMutationKeys.createRoomGroup(),
mutationFn: postChatRoomsGroup,
}),
sendMessage: () =>
mutationOptions({
mutationKey: chatMutationKeys.sendMessage(),
Expand Down
19 changes: 16 additions & 3 deletions src/apis/chat/queries.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import { infiniteQueryOptions, queryOptions } from '@tanstack/react-query';
import type { ChatMessagesResponse } from './entity';
import { getChatMessages, getChatRooms } from '.';
import { getChatMessages, getChatRooms, getSearchChat, getInvitableFriends } from '@/apis/chat';
import type { ChatMessagesResponse, SortBy } from '@/apis/chat/entity';

export const chatQueryKeys = {
all: ['chat'] as const,
rooms: () => [...chatQueryKeys.all, 'rooms'] as const,
messages: (chatRoomId: number) => [...chatQueryKeys.all, 'messages', chatRoomId] as const,
disabledMessages: () => [...chatQueryKeys.all, 'messages', 'disabled'] as const,
search: (keyword: string) => [...chatQueryKeys.all, 'search', keyword],
invite: (query: string, sortBy: SortBy) => [...chatQueryKeys.all, 'invite', query, sortBy],
};

export const chatQueries = {
Expand All @@ -15,12 +17,13 @@ export const chatQueries = {
queryKey: chatQueryKeys.rooms(),
queryFn: getChatRooms,
}),
messages: (chatRoomId?: number, limit = 20) =>
messages: (chatRoomId?: number, messageId?: number, limit = 20) =>
infiniteQueryOptions({
queryKey: chatRoomId ? chatQueryKeys.messages(chatRoomId) : chatQueryKeys.disabledMessages(),
queryFn: ({ pageParam }) =>
getChatMessages({
chatRoomId: chatRoomId!,
messageId: messageId,
page: pageParam,
limit,
}),
Expand All @@ -29,4 +32,14 @@ export const chatQueries = {
lastPage.currentPage < lastPage.totalPage ? lastPage.currentPage + 1 : undefined,
enabled: Boolean(chatRoomId),
}),
search: (keyword: string) =>
queryOptions({
queryKey: chatQueryKeys.search(keyword),
queryFn: () => getSearchChat({ keyword, page: 1, limit: 20 }),
}),
invite: (query: string, sortBy: SortBy) =>
queryOptions({
queryKey: chatQueryKeys.invite(query, sortBy),
queryFn: () => getInvitableFriends({ query, sortBy, page: 1, limit: 20 }),
}),
};
3 changes: 3 additions & 0 deletions src/assets/svg/check_color.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 5 additions & 2 deletions src/components/common/Dropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ interface DropdownProps<T extends string> extends Omit<HTMLAttributes<HTMLDivEle
value: T;
onChange: (value: T) => void;
menuClassName?: string;
triggerClassName?: string;
}

export default function Dropdown<T extends string>({
Expand All @@ -21,6 +22,7 @@ export default function Dropdown<T extends string>({
onChange,
className,
menuClassName,
triggerClassName,
...props
}: DropdownProps<T>) {
const [isOpen, setIsOpen] = useState(false);
Expand All @@ -45,8 +47,9 @@ export default function Dropdown<T extends string>({
type="button"
onClick={() => setIsOpen(!isOpen)}
className={cn(
'inline-flex h-7.25 items-center justify-center overflow-hidden rounded-full bg-[#69BFDF] px-2.5 text-[13px] leading-[1.6] font-medium text-white transition-opacity active:opacity-90',
isOpen && 'opacity-95'
'inline-flex h-7.25 items-center justify-evenly overflow-hidden rounded-full bg-[#69BFDF] px-2.5 text-[13px] leading-[1.6] font-medium text-white transition-opacity active:opacity-90',
isOpen && 'opacity-95',
triggerClassName
)}
>
<span>{selectedOption?.label}</span>
Expand Down
23 changes: 23 additions & 0 deletions src/components/layout/Header/components/ChatAddHeader.tsx
Comment thread
ParkSungju01 marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import BackTitleHeader from '@/components/layout/Header/components/BackTitleHeader';

interface ChatListHeaderProps {
title: string;
onConfirm: () => void;
}

export default function ChatAddHeader({ title, onConfirm }: ChatListHeaderProps) {
const confirm = (
<button type="button" onClick={onConfirm}>
<span className="text-primary-500">확인</span>
</button>
);

return (
<BackTitleHeader
title={title}
rightSlot={confirm}
headerClassName="h-13 rounded-b-3xl px-4 py-3 shadow-[0_0_20px_0_rgba(0,0,0,0.03)]"
rightSlotContainerClassName="flex items-center gap-2"
/>
);
}
6 changes: 4 additions & 2 deletions src/components/layout/Header/components/ChatListHeader.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { Ref } from 'react';
import { useNavigate } from 'react-router-dom';
import AddCircle from '@/assets/svg/add_circle.svg';
import Search from '@/assets/svg/big-search-icon.svg';
import BackTitleHeader from '@/components/layout/Header/components/BackTitleHeader';
Expand All @@ -9,12 +10,13 @@ interface ChatListHeaderProps {
}

export default function ChatListHeader({ title, headerRef }: ChatListHeaderProps) {
const navigate = useNavigate();
const rightSlot = (
<div className="flex gap-4">
<button type="button" aria-label="검색" className="shrink-0">
<button type="button" aria-label="검색" className="shrink-0" onClick={() => navigate('/chats/search')}>
<Search />
</button>
<button type="button" aria-label="채팅방 추가" className="shrink-0">
<button type="button" aria-label="채팅방 추가" className="shrink-0" onClick={() => navigate('/chats/add')}>
<AddCircle />
</button>
</div>
Expand Down
18 changes: 18 additions & 0 deletions src/components/layout/Header/components/ChatSearchHeader.tsx
Comment thread
ParkSungju01 marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import type { Ref } from 'react';
import BackTitleHeader from '@/components/layout/Header/components/BackTitleHeader';

interface ChatListHeaderProps {
title: string;
headerRef?: Ref<HTMLElement>;
}

export default function ChatSearchHeader({ title, headerRef }: ChatListHeaderProps) {
return (
<BackTitleHeader
title={title}
headerRef={headerRef}
headerClassName="h-13 rounded-b-3xl px-4 py-3 shadow-[0_0_20px_0_rgba(0,0,0,0.03)]"
rightSlotContainerClassName="flex items-center gap-2"
/>
);
}
8 changes: 4 additions & 4 deletions src/components/layout/Header/components/SubpageHeader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,25 @@ interface SubpageHeaderProps {
headerRef?: Ref<HTMLElement>;
rightSlot?: ReactNode;
shadowClassName?: string;
headerClassName?: string;
}

function SubpageHeader({
title,
headerRef,
rightSlot,
shadowClassName = 'shadow-[0_0_20px_rgba(0,0,0,0.03)]',
headerClassName,
}: SubpageHeaderProps) {
const headerStyle = {
minHeight: 'var(--subpage-header-height)',
} as CSSProperties;
const headerStyle = headerClassName ? undefined : ({ minHeight: 'var(--subpage-header-height)' } as CSSProperties);

return (
<BackTitleHeader
title={title}
headerRef={headerRef}
rightSlot={rightSlot}
reserveRightSlot
headerClassName={cn('justify-between rounded-b-3xl px-4 py-3', shadowClassName)}
headerClassName={cn('justify-between rounded-b-3xl px-4 py-3', shadowClassName, headerClassName)}
titleClassName="text-sub1 text-indigo-700"
style={headerStyle}
/>
Expand Down
8 changes: 8 additions & 0 deletions src/components/layout/Header/headerConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,14 @@ export const HEADER_CONFIGS: HeaderConfig[] = [
type: 'chatList',
match: (pathname) => pathname === '/chats',
},
{
type: 'none',
match: (pathname) => pathname === '/chats/add',
},
{
type: 'chatSearch',
match: (pathname) => pathname === '/chats/search',
},
{
type: 'chat',
match: (pathname) => /^\/chats\/\d+$/.test(pathname),
Expand Down
1 change: 1 addition & 0 deletions src/components/layout/Header/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ function Header({ headerRef }: HeaderProps) {
signup: ({ title, onBack, headerRef }) => <DefaultHeader title={title} headerRef={headerRef} onBack={onBack} />,
default: ({ title, headerRef }) => <DefaultHeader title={title} headerRef={headerRef} />,
manager: ({ title, headerRef }) => <ManagerHeader fallbackTitle={title} headerRef={headerRef} />,
chatSearch: ({ title, headerRef }) => <SubpageHeader title={title} headerRef={headerRef} headerClassName="h-13" />,
};

const onBack = headerType === 'signup' ? () => navigate('/') : undefined;
Expand Down
8 changes: 8 additions & 0 deletions src/components/layout/Header/routeTitles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,4 +64,12 @@ export const ROUTE_TITLES: RouteTitle[] = [
match: (pathname) => pathname === '/chats',
title: '채팅방',
},
{
match: (pathname) => pathname === '/chats/add',
title: '채팅방 추가',
},
{
match: (pathname) => pathname === '/chats/search',
title: '채팅방 검색',
},
];
3 changes: 2 additions & 1 deletion src/components/layout/Header/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ export type HeaderType =
| 'signup'
| 'schedule'
| 'manager'
| 'chatList';
| 'chatList'
| 'chatSearch';

export interface HeaderConfig {
type: HeaderType;
Expand Down
Loading
Loading