added chat messages component and query provider
This commit is contained in:
parent
55ad18e04a
commit
70141f46fd
@ -4,6 +4,7 @@ import { db } from "@/lib/db";
|
||||
import { redirect } from "next/navigation";
|
||||
import { ChatHeader } from "@/components/chat/chat-header";
|
||||
import { ChatInput } from "@/components/chat/chat-input";
|
||||
import { ChatMessages } from "@/components/chat/chat-messages";
|
||||
|
||||
interface ChannelIdPageProps {
|
||||
params: {
|
||||
@ -42,7 +43,20 @@ const ChannelIdPage = async ({
|
||||
return (
|
||||
<div className="bg-white dark:bg-[#313338] flex flex-col h-full">
|
||||
<ChatHeader name={channel.name} serverId={channel.serverId} type={"channel"}/>
|
||||
<div className="flex-1">Future Messages</div>
|
||||
<ChatMessages
|
||||
member={member}
|
||||
name={channel.name}
|
||||
chatId={channel.id}
|
||||
type="channel"
|
||||
apiUrl="/api/messages"
|
||||
socketUrl="/api/socket/messages"
|
||||
socketQuery={{
|
||||
channelId: channel.id,
|
||||
serverId: channel.serverId,
|
||||
}}
|
||||
paramKey="channelId"
|
||||
paramValue={channel.id}
|
||||
/>
|
||||
<ChatInput name={channel.name} type="channel" apiUrl="/api/socket/messages" query={{
|
||||
channelId: channel.id,
|
||||
serverId: channel.serverId,
|
||||
|
83
app/api/messages/route.ts
Normal file
83
app/api/messages/route.ts
Normal file
@ -0,0 +1,83 @@
|
||||
import { currentProfile } from "@/lib/current-profile";
|
||||
import { db } from "@/lib/db";
|
||||
import { Message } from "@prisma/client";
|
||||
|
||||
import { NextResponse } from "next/server";
|
||||
|
||||
const MESSAGES_BATCH = 10;
|
||||
|
||||
export async function GET(
|
||||
req: Request
|
||||
) {
|
||||
try {
|
||||
const profile = await currentProfile();
|
||||
const { searchParams } = new URL(req.url);
|
||||
|
||||
const cursor = searchParams.get("cursor");
|
||||
const channelId = searchParams.get("channelId");
|
||||
|
||||
if (!profile) {
|
||||
return new NextResponse("Unauthorized", { status: 401 });
|
||||
}
|
||||
|
||||
if (!channelId) {
|
||||
return new NextResponse("Channel ID missing", { status: 400 });
|
||||
}
|
||||
|
||||
let messages: Message[] = [];
|
||||
|
||||
if (cursor) {
|
||||
messages = await db.message.findMany({
|
||||
take: MESSAGES_BATCH,
|
||||
skip: 1,
|
||||
cursor: {
|
||||
id: cursor,
|
||||
},
|
||||
where: {
|
||||
channelId,
|
||||
},
|
||||
include: {
|
||||
member: {
|
||||
include: {
|
||||
profile: true,
|
||||
}
|
||||
}
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: "desc",
|
||||
},
|
||||
});
|
||||
} else {
|
||||
messages = await db.message.findMany({
|
||||
take: MESSAGES_BATCH,
|
||||
where: {
|
||||
channelId,
|
||||
},
|
||||
include: {
|
||||
member: {
|
||||
include: {
|
||||
profile: true,
|
||||
}
|
||||
}
|
||||
},
|
||||
orderBy: {
|
||||
createdAt: "desc",
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
let nextCursor = null;
|
||||
|
||||
if (messages.length === MESSAGES_BATCH) {
|
||||
nextCursor = messages[MESSAGES_BATCH - 1].id;
|
||||
}
|
||||
|
||||
return NextResponse.json({
|
||||
items: messages,
|
||||
nextCursor,
|
||||
});
|
||||
} catch (error) {
|
||||
console.log("[MESSAGES_GET]", error);
|
||||
return new NextResponse("Internal Error", { status: 500 })
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ import { cn } from '@/lib/utils'
|
||||
import { ThemeProvider } from '@/components/providers/theme-provider'
|
||||
import { ModalProvider } from '@/components/providers/modal-provider'
|
||||
import { SocketProvider } from '@/components/providers/socket-provider'
|
||||
import { QueryProvider } from '@/components/providers/query-provider'
|
||||
|
||||
const font = Open_Sans({ subsets: ['latin'] })
|
||||
|
||||
@ -35,7 +36,9 @@ export default function RootLayout({
|
||||
>
|
||||
<SocketProvider>
|
||||
<ModalProvider/>
|
||||
<QueryProvider>
|
||||
{children}
|
||||
</QueryProvider>
|
||||
</SocketProvider>
|
||||
</ThemeProvider>
|
||||
</body>
|
||||
|
96
components/chat/chat-messages.tsx
Normal file
96
components/chat/chat-messages.tsx
Normal file
@ -0,0 +1,96 @@
|
||||
"use client";
|
||||
|
||||
import { Member, Message, Profile } from "@prisma/client";
|
||||
|
||||
import { ChatWelcome } from "./chat-welcome";
|
||||
import { useChatQuery } from "@/hooks/use-chat-query";
|
||||
import { Loader2, ServerCrash } from "lucide-react";
|
||||
import { Fragment } from "react";
|
||||
|
||||
type MessageWithMemberWithProfile = Message & {
|
||||
member: Member & {
|
||||
profile: Profile
|
||||
}
|
||||
}
|
||||
|
||||
interface ChatMessagesProps {
|
||||
name: string;
|
||||
member: Member;
|
||||
chatId: string;
|
||||
apiUrl: string;
|
||||
socketUrl: string;
|
||||
socketQuery: Record<string, string>;
|
||||
paramKey: "channelId" | "conversationId";
|
||||
paramValue: string;
|
||||
type: "channel" | "conversation";
|
||||
}
|
||||
|
||||
export const ChatMessages = ({
|
||||
name,
|
||||
member,
|
||||
chatId,
|
||||
apiUrl,
|
||||
socketUrl,
|
||||
socketQuery,
|
||||
paramKey,
|
||||
paramValue,
|
||||
type,
|
||||
}: ChatMessagesProps) => {
|
||||
const queryKey = `chat:${chatId}`;
|
||||
|
||||
const {
|
||||
data,
|
||||
fetchNextPage,
|
||||
hasNextPage,
|
||||
isFetchingNextPage,
|
||||
status,
|
||||
} = useChatQuery({
|
||||
queryKey,
|
||||
apiUrl,
|
||||
paramKey,
|
||||
paramValue,
|
||||
})
|
||||
|
||||
if (status === "pending") {
|
||||
return (
|
||||
<div className="flex flex-col flex-1 justify-center items-center">
|
||||
<Loader2 className="h-7 w-7 text-zinc-500 animate-spin my-4"/>
|
||||
<p className="text-xs text-zinc-500 dark:text-zinc-400">
|
||||
Loading Messages...
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
if (status === "error") {
|
||||
return (
|
||||
<div className="flex flex-col flex-1 justify-center items-center">
|
||||
<ServerCrash className="h-7 w-7 text-zinc-500 my-4"/>
|
||||
<p className="text-xs text-zinc-500 dark:text-zinc-400">
|
||||
Something went wrong!
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="flex-1 flex flex-col py-4 overflow-y-auto">
|
||||
<div className="flex-1"/>
|
||||
<ChatWelcome
|
||||
type={type}
|
||||
name={name}
|
||||
/>
|
||||
<div className="flex flex-col-reverse mt-auto">
|
||||
{data?.pages.map((group, i) => (
|
||||
<Fragment key={i}>
|
||||
{group.items.map((message: MessageWithMemberWithProfile) => (
|
||||
<div key={message.id}>
|
||||
{message.content}
|
||||
</div>
|
||||
))}
|
||||
</Fragment>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
28
components/chat/chat-welcome.tsx
Normal file
28
components/chat/chat-welcome.tsx
Normal file
@ -0,0 +1,28 @@
|
||||
import { Hash } from "lucide-react";
|
||||
|
||||
interface ChatWelcomeProps {
|
||||
name: string;
|
||||
type: "channel" | "conversation";
|
||||
}
|
||||
|
||||
export const ChatWelcome = ({
|
||||
name,
|
||||
type,
|
||||
}: ChatWelcomeProps) => {
|
||||
return (
|
||||
<div className="space-y-2 px-4 mb-4">
|
||||
{type === "channel" && (
|
||||
<div className="h-[75px] w-[75px] rounded-full bg-zinc-500 dark:bg-zinc-700 flex items-center justify-center">
|
||||
<Hash className="h-12 w-12 text-white"/>
|
||||
</div>
|
||||
)}
|
||||
<p className="text-xl md:text-3l font-bold">
|
||||
{type === "channel" ? "Welcome to #" : ""}{name}
|
||||
</p>
|
||||
<p className="text-zinc-600 dark:text-zinc-400 text-sm">
|
||||
{type === "channel" ? `This is the start of the #${name} channel.` : `This is the start of your conversation with ${name}`}
|
||||
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
21
components/providers/query-provider.tsx
Normal file
21
components/providers/query-provider.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
"use client"
|
||||
|
||||
import {
|
||||
QueryClient,
|
||||
QueryClientProvider,
|
||||
}from "@tanstack/react-query";
|
||||
import { useState } from "react";
|
||||
|
||||
export const QueryProvider = ({
|
||||
children
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
}) => {
|
||||
const [queryClient] = useState(() => new QueryClient());
|
||||
|
||||
return (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
{children}
|
||||
</QueryClientProvider>
|
||||
)
|
||||
}
|
54
hooks/use-chat-query.ts
Normal file
54
hooks/use-chat-query.ts
Normal file
@ -0,0 +1,54 @@
|
||||
import qs from "query-string";
|
||||
import { useInfiniteQuery } from "@tanstack/react-query";
|
||||
|
||||
import { useSocket } from "@/components/providers/socket-provider";
|
||||
|
||||
interface ChatQueryProps {
|
||||
queryKey: string;
|
||||
apiUrl: string;
|
||||
paramKey: "channelId" | "conversationId";
|
||||
paramValue: string;
|
||||
};
|
||||
|
||||
export const useChatQuery = ({
|
||||
queryKey,
|
||||
apiUrl,
|
||||
paramKey,
|
||||
paramValue
|
||||
}: ChatQueryProps) => {
|
||||
const { isConnected } = useSocket();
|
||||
|
||||
const fetchMessages = async ({ pageParam = undefined }) => {
|
||||
const url = qs.stringifyUrl({
|
||||
url: apiUrl,
|
||||
query: {
|
||||
cursor: pageParam,
|
||||
[paramKey]: paramValue,
|
||||
}
|
||||
}, { skipNull: true });
|
||||
|
||||
const res = await fetch(url);
|
||||
return res.json();
|
||||
};
|
||||
|
||||
const {
|
||||
data,
|
||||
fetchNextPage,
|
||||
hasNextPage,
|
||||
isFetchingNextPage,
|
||||
status,
|
||||
} = useInfiniteQuery({
|
||||
queryKey: [queryKey],
|
||||
queryFn: fetchMessages,
|
||||
getNextPageParam: (lastPage) => lastPage?.nextCursor,
|
||||
refetchInterval: isConnected ? false : 1000,
|
||||
});
|
||||
|
||||
return {
|
||||
data,
|
||||
fetchNextPage,
|
||||
hasNextPage,
|
||||
isFetchingNextPage,
|
||||
status,
|
||||
};
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user