Skip to content

Instantly share code, notes, and snippets.

@harrisrobin
Created September 26, 2024 12:50
Show Gist options
  • Select an option

  • Save harrisrobin/b245fb43f223f36bae553509be382506 to your computer and use it in GitHub Desktop.

Select an option

Save harrisrobin/b245fb43f223f36bae553509be382506 to your computer and use it in GitHub Desktop.
"use client"
import { GameChatMessagesListItem } from "@/app/[locale]/(dynamic)/cs/lobby/[room]/_components/game-chat-message-list-item"
import { Button } from "@/components/ui/button"
import { Textarea } from "@/components/ui/textarea"
import type { MatchForLobby } from "@/types/database.types"
import type { Message } from "@ably/chat"
import { useMessages } from "@ably/chat/react"
import { useUser } from "@supabase/auth-helpers-react"
import type { User } from "@supabase/supabase-js"
import { useCallback, useEffect, useRef, useState } from "react"
import { Virtuoso, type VirtuosoHandle } from "react-virtuoso"
interface GameChatProps {
match: MatchForLobby
initialUser: User | null
}
export function GameChat(props: GameChatProps) {
const { initialUser } = props
const [messages, setMessages] = useState<Message[]>([])
const [text, setText] = useState("")
const clientUser = useUser()
const user = clientUser ?? initialUser
const virtuoso = useRef<VirtuosoHandle>(null)
const scrollToBottom = useCallback(() => {
if (virtuoso.current) {
virtuoso.current.scrollToIndex({
index: messages.length - 1,
})
}
}, [messages.length])
useEffect(() => {
setTimeout(() => {
scrollToBottom()
}, 100)
}, [messages.length])
const { getPreviousMessages, send } = useMessages({
listener: (messageEvent) => {
setMessages((prev) => [...prev, messageEvent.message])
},
onDiscontinuity: (error) => {
console.log("Discontinuity detected:", error)
},
})
useEffect(() => {
if (getPreviousMessages) {
getPreviousMessages({ limit: 50 }).then((result) => {
setMessages(result.items)
})
}
}, [getPreviousMessages])
const publishMessage = async (text: string) => {
if (!user) {
return
}
await send({
text,
metadata: {
avatarUrl: user.user_metadata?.avatar_url,
username: user.user_metadata?.username,
userId: user.id,
},
})
}
const sendMessage = () => {
if (text.trim()) {
publishMessage(text)
setText("")
}
}
return (
<div className="flex flex-col w-full border-r border-l border-b border-t border-border rounded-lg">
<div className="flex-grow relative h-[375px] px-4">
<div className="absolute inset-x-0 top-0 h-10 bg-gradient-to-b from-background to-transparent z-10 pointer-events-none rounded-t-lg pr-6" />
<Virtuoso
data={messages}
style={{
display: "flex",
width: "100%",
height: "100%",
flexDirection: "column",
gap: "16px",
}}
ref={virtuoso}
itemContent={(_, message) => (
<GameChatMessagesListItem
key={message.timeserial}
message={message}
/>
)}
/>
<div className="absolute inset-x-0 bottom-0 h-10 bg-gradient-to-t from-background to-transparent z-10 pointer-events-none" />
</div>
<div className="flex w-full">
<div className="flex gap-2 items-center w-full border-t pr-4">
<Textarea
placeholder="Type your message here."
className="w-full h-30 border-none border-r resize-none h-30 p-4"
value={text}
onChange={(e) => setText(e.target.value)}
onKeyDown={(e) => {
if (e.key === "Enter") {
e.preventDefault()
sendMessage()
}
}}
/>
<Button onClick={sendMessage}>Send</Button>
</div>
</div>
</div>
)
}
import { LobbyRoomProvider } from "@/app/[locale]/(dynamic)/cs/lobby/[room]/_components/lobby-room-provider"
interface LobbyLayoutProps {
children: React.ReactNode
params: {
room: string
}
}
export default async function LobbyLayout({
children,
params,
}: LobbyLayoutProps) {
const { room } = params
console.log("room", room)
return <LobbyRoomProvider initialRoom={room}>{children}</LobbyRoomProvider>
}
"use client"
import { RoomOptionsDefaults } from "@ably/chat"
import { ChatRoomProvider } from "@ably/chat/react"
interface LobbyRoomProviderProps {
children: React.ReactNode
initialRoom: string
}
export function LobbyRoomProvider(props: LobbyRoomProviderProps) {
const { children, initialRoom } = props
console.log("initialRoom", initialRoom)
const ablyChatRoom = `lobby:${initialRoom}`
return (
<ChatRoomProvider id={ablyChatRoom} options={RoomOptionsDefaults}>
{children}
</ChatRoomProvider>
)
}
import { GameChat } from "@/app/[locale]/(dynamic)/cs/lobby/[room]/_components/game-chat"
import { GameInfo } from "@/app/[locale]/(dynamic)/cs/lobby/[room]/_components/game-info"
import { MatchmakingLoading } from "@/app/[locale]/(dynamic)/cs/lobby/[room]/_components/matchmaking-loading"
import { Button } from "@/components/ui/button"
import { getAuthUser, getMatchById } from "@repo/supabase/queries"
import GamePlayers from "./_components/game-players"
interface ServerPageProps {
params: {
room: string
}
}
export default async function Page(
props: ServerPageProps,
): Promise<JSX.Element> {
const { room } = props.params
const matchResult = await getMatchById(room)
const { data } = await getAuthUser()
const user = data?.user
if (!matchResult.data) {
return <MatchmakingLoading />
}
console.log("matchResult.data", matchResult.data)
return (
<main className="flex flex-col flex-1 w-full pt-16 pb-28 gap-6 rounded-lg">
<h1 className="text-3xl font-bold uppercase">Game Lobby</h1>
<div className="flex flex-col xs:flex-col sm:flex-row md:flex-row lg:flex-row xl:flex-row gap-8 justify-start">
<div className="flex flex-col justify-start sm:max-w-[350px] md:max-w-[350px] lg:max-w-[350px] xl:max-w-[350px]">
<GameInfo match={matchResult.data} />
<GamePlayers />
</div>
<div className="flex w-full flex-col gap-4">
<GameChat initialUser={user} match={matchResult.data} />
<Button className="w-full uppercase" variant="default" size="lg">
Connect
</Button>
</div>
</div>
</main>
)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment