Skip to content

Commit

Permalink
chrore: Improve Chat Area
Browse files Browse the repository at this point in the history
  • Loading branch information
devxprite committed Jan 20, 2025
1 parent aad7f37 commit 0972cb2
Show file tree
Hide file tree
Showing 6 changed files with 85 additions and 67 deletions.
91 changes: 51 additions & 40 deletions src/components/chat-area.tsx
Original file line number Diff line number Diff line change
@@ -1,33 +1,35 @@
import { Fragment, useEffect, useRef, useMemo, memo } from 'react';
import { LoaderCircle } from 'lucide-react';
import { useScrolling } from 'react-use';
import { ScrollArea } from './ui/scroll-area';
import MessageBubble from './ui/message-bubble';
import moment from 'moment';
import useChat from '@/hooks/useChat';
import LoadMore from './load-more-btn';
import { Fragment, useEffect, useRef } from 'react';
import useChat from '@/hooks/useChat';
import useTyping from '@/hooks/useTyping';
import MessageInput from './message-input';
import TypingBubble from './typing-bubble';
import { LoaderCircle } from 'lucide-react';
import { ScrollArea } from './ui/scroll-area';
import MessageBubble from './ui/message-bubble';
// import ScrollProgress from './ui/scroll-progress';
import { cn, formatDateCalendar } from '@/lib/utils';
import { useAuthContext } from '@/context/auth-context';
import { useSettingsContext } from '@/context/settings-context';
import { cn, formatDateCalendar } from '@/lib/utils';
import ScrollProgress from './ui/scroll-progress';

import type { Message } from '@/types';
import { useScrolling } from 'react-use';

const DateDivider = ({ date }: { date: Message['timestamp'] }) => (
const DateDivider = memo(({ date }: { date: Message['timestamp'] }) => (
<div className="border bg-muted text-muted-foreground w-fit mx-auto text-xs py-1 px-2 rounded-md mt-5 mb-3">
{formatDateCalendar(date?.toDate())}
</div>
);
));
DateDivider.displayName = 'DateDivider';

const MessageLoader = () => (
const MessageLoader = memo(() => (
<div className="w-full h-[80vh] flex flex-col gap-2 items-center justify-center text-muted-foreground/75">
<LoaderCircle className="size-8 animate-spin" />
<p className="animate-pulse">Loading Messages...</p>
</div>
);
));
MessageLoader.displayName = 'MessageLoader';

const ChatArea = () => {
const { user } = useAuthContext();
Expand All @@ -37,48 +39,57 @@ const ChatArea = () => {
const isScrolling = useScrolling(chatContainerRef);
const { messages, sendMessage, isSending, isLoading, hasMore, loadMore } = useChat(user);

const shouldShowDate = (msg: Message, lastMsg: Message) => {
if (!lastMsg) return true;
return !moment(msg.timestamp?.toDate()).isSame(lastMsg.timestamp?.toDate(), 'day');
};
const shouldShowDate = useMemo(() => {
return (msg: Message, lastMsg: Message) => {
// console.log('Show Date?');
if (!lastMsg?.timestamp) return true;
return !moment(msg.timestamp?.toDate()).isSame(lastMsg.timestamp?.toDate(), 'day');
};
}, []);

useEffect(() => {
if (messages.length > 0 && chatContainerRef.current && settings.autoScroll && !isScrolling) {
const shouldAutoScroll = messages.length > 0 && chatContainerRef.current && settings.autoScroll && !isScrolling;

if (shouldAutoScroll) {
const chatContainer = chatContainerRef.current;
chatContainer.scrollTo({
top: chatContainer.scrollHeight,
behavior: 'smooth',
requestAnimationFrame(() => {
chatContainer.scrollTo({
top: chatContainer.scrollHeight,
behavior: 'smooth',
});
});
}
}, [messages[messages.length - 1]]);
}, [messages[messages.length - 1]?.id, settings.autoScroll]);

const messageList = useMemo(() => {
return messages.map((msg, i) => (
<Fragment key={msg.id}>
{shouldShowDate(msg, messages[i - 1]) && <DateDivider date={msg.timestamp} />}
<MessageBubble
{...msg}
isOwnMessage={msg.userId === user?.id}
showName={messages[i - 1]?.userId !== msg.userId || shouldShowDate(msg, messages[i - 1])}
/>
</Fragment>
));
}, [messages.length, user?.id, shouldShowDate]);

// console.log('Render...');

if (isLoading && messages.length < 2) return <MessageLoader />;

return (
<div className="w-full border-r h-full overflow-hidden flex bg-muted/35 md:relative">
{/* {settings.scrollIndicator && messages.length > 0 && (
{settings.scrollIndicator && messages.length > 0 && (
<ScrollProgress container={chatContainerRef} className="max-md:top-12 h-[1px]" />
)} */}

{isLoading && messages.length < 2 && <MessageLoader />}

)}
<ScrollArea
ref={chatContainerRef}
className={cn('w-full px-2 overflow-y-auto relative', isLoading && messages.length === 0 && 'hidden')}
>
<div className="grid gap-1 w-full max-w-screen-md mx-auto mb-80 mt-6">
{hasMore && <LoadMore onClick={loadMore} isLoading={isLoading} />}

{messages.map((msg, i) => (
<Fragment key={msg.id}>
{shouldShowDate(msg, messages[i - 1]) && <DateDivider date={msg.timestamp} />}

<MessageBubble
{...msg}
isOwnMessage={msg.userId === user?.id}
showName={messages[i - 1]?.userId !== msg.userId || shouldShowDate(msg, messages[i - 1])}
/>
</Fragment>
))}

{messageList}
<TypingBubble typingUsers={typingUsers} />
</div>
</ScrollArea>
Expand All @@ -88,4 +99,4 @@ const ChatArea = () => {
);
};

export default ChatArea;
export default memo(ChatArea);
29 changes: 14 additions & 15 deletions src/components/loader.tsx
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@

const Loader = () => {
return (
<div className="h-[92vh] w-screen flex flex-col items-center justify-center gap-4 relative">
<svg className="pl" viewBox="0 0 200 200" width="200" height="200" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="pl-grad1" x1="1" y1="0.5" x2="0" y2="0.5">
<stop offset="0%" stop-color="hsl(313,90%,55%)" />
<stop offset="100%" stop-color="hsl(223,90%,55%)" />
{/* <stop offset="0%" stop-color="#f43f5e" />
<stop offset="50%" stop-color="#a855f7" />
<stop offset="100%" stop-color="#3b82f6" /> */}
<stop offset="0%" stopColor="hsl(313,90%,55%)" />
<stop offset="100%" stopColor="hsl(223,90%,55%)" />
{/* <stop offset="0%" stopColor="#f43f5e" />
<stop offset="50%" stopColor="#a855f7" />
<stop offset="100%" stopColor="#3b82f6" /> */}
</linearGradient>
<linearGradient id="pl-grad2" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="hsl(313,90%,55%)" />
<stop offset="100%" stop-color="hsl(223,90%,55%)" />
<stop offset="0%" stopColor="hsl(313,90%,55%)" />
<stop offset="100%" stopColor="hsl(223,90%,55%)" />
</linearGradient>
</defs>
<circle
Expand All @@ -23,10 +22,10 @@ const Loader = () => {
r="82"
fill="none"
stroke="url(#pl-grad1)"
stroke-width="30"
stroke-dasharray="0 257 1 257"
stroke-dashoffset="0.01"
stroke-linecap="round"
strokeWidth="30"
strokeDasharray="0 257 1 257"
strokeDashoffset="0.01"
strokeLinecap="round"
transform="rotate(-90,100,100)"
/>
<line
Expand All @@ -36,9 +35,9 @@ const Loader = () => {
y1="18"
x2="100.01"
y2="182"
stroke-width="30"
stroke-dasharray="1 165"
stroke-linecap="round"
strokeWidth="30"
strokeDasharray="1 165"
strokeLinecap="round"
/>
</svg>

Expand Down
3 changes: 1 addition & 2 deletions src/components/message-input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,7 @@ const MessageInput = ({ onSubmit, isSending }: Props) => {
placeholder="Write your message here..."
className="bg-muted/75"
minLength={1}
max={160}
maxLength={160}
maxLength={250}
required
disabled={isSending}
onChange={handleInputChange}
Expand Down
9 changes: 7 additions & 2 deletions src/hooks/useAuth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { collection, doc, getDocs, query, serverTimestamp, setDoc, where } from
import { ref, set, onDisconnect, serverTimestamp as serverTimestampRtdb } from 'firebase/database';
import { User } from '../types';
import { db, rtdb } from '../lib/firebase';
import toast from 'react-hot-toast';

const COOKIE_EXPIRY_DAYS = 30;

Expand All @@ -12,7 +13,11 @@ const getUserAgent = () => encodeURIComponent(navigator.userAgent);
const fetchCountryCode = async () => {
try {
const response = await fetch(`https://ipinfo.io/json?token=${import.meta.env.VITE_IPINFO_TOKEN}`);
if (!response.ok) throw new Error('Failed to fetch country info');

if (!response.ok) {
toast.error('Unable to determine your location. This might be due to browser settings or permissions. Please try again');
throw new Error('Failed to fetch country info');
}

const data: { country: string } = await response.json();
return data.country as User['country'];
Expand Down Expand Up @@ -154,4 +159,4 @@ const useAuth = () => {
return { user, isLoading, isInitializing, error, initializeUser, logout };
};

export default useAuth;
export default useAuth;
10 changes: 3 additions & 7 deletions src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import Loader from './components/loader.tsx';
import { createRoot } from 'react-dom/client';
import { lazy, StrictMode, Suspense } from 'react';
import { SettingsProvider } from './context/settings-context.tsx';
import { AnimatePresence } from 'motion/react';
import { Toaster } from 'react-hot-toast';
import { Analytics } from '@vercel/analytics/react';
import { SpeedInsights } from '@vercel/speed-insights/react';
Expand All @@ -22,13 +21,10 @@ createRoot(document.getElementById('root')!).render(
<Suspense key={'layout'} fallback={<Loader />}>
<AuthProvider>
<SettingsProvider>
<AnimatePresence>
<App />

<Toaster />
</AnimatePresence>
<App />
<Toaster />
</SettingsProvider>
</AuthProvider>
</Suspense>
</StrictMode>
);
);
10 changes: 9 additions & 1 deletion src/pages/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,15 @@ const Home = ({}: Props) => {
<CardDescription>Enter your name to join the chat</CardDescription>
</CardHeader>
<CardContent>
<Input name="name" placeholder="Enter your name" className="bg-muted/50" minLength={2} maxLength={30} required />
<Input
name="name"
placeholder="Enter your name"
className="bg-muted/50"
minLength={2}
maxLength={30}
autoComplete="name"
required
/>
</CardContent>
<CardFooter>
<Button className="ml-auto">
Expand Down

0 comments on commit 0972cb2

Please sign in to comment.