Skip to content

Commit

Permalink
v2 redesign the whole pages
Browse files Browse the repository at this point in the history
  • Loading branch information
git-create-devben committed Aug 3, 2024
1 parent 45becc1 commit 108ec7c
Show file tree
Hide file tree
Showing 8 changed files with 269 additions and 162 deletions.
108 changes: 65 additions & 43 deletions app/chat/page.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,30 @@
"use client";
import Main from "@/components/main";
import Sidebar from "@/components/sidebar";
import { onAuthStateChanged } from "firebase/auth";
import { useEffect, useState } from "react";
import { useEffect, useRef, useState } from "react";
import { auth } from "@/lib/firebase";
import local from "@/public/png/logo-black.png";
import Image from "next/image";
import FirstVisitPopup from "@/components/firstvisitpopup";
import { SignOut } from "@/lib/signIn";
import { LogOut } from "lucide-react";
import { LogOut, MenuIcon, X } from "lucide-react";
import { useRouter } from "next/navigation";
import { doc, getDoc } from "firebase/firestore";
import { db } from "@/lib/firebase";
import { motion, AnimatePresence } from 'framer-motion';

interface UserData {
name?: string;
email?: string;
photoURL?: string;
}

export default function Chat() {
const [user, setUser] = useState<UserData | null>(null);
const [loading, setLoading] = useState(true);
const [isSidebarOpen, setIsSidebarOpen] = useState(false);
const router = useRouter();
const sidebarRef = useRef<HTMLDivElement>(null);

useEffect(() => {
const verifySession = async () => {
Expand Down Expand Up @@ -53,6 +56,18 @@ export default function Chat() {

verifySession();
}, [router]);
useEffect(() => {
const handleClickOutside = (event: MouseEvent) => {
if (sidebarRef.current && !sidebarRef.current.contains(event.target as Node)) {
setIsSidebarOpen(false);
}
};

document.addEventListener("mousedown", handleClickOutside);
return () => {
document.removeEventListener("mousedown", handleClickOutside);
};
}, []);

if (loading) {
return (
Expand All @@ -64,50 +79,57 @@ export default function Chat() {
}

if (!user) {
return router.push("/");
router.push("/");
return null;
}
// @ts-ignore

const image = user?.photoURL as string;
return (
<main className=" flex bg-black h-[100vh] ">
<FirstVisitPopup />
<div className=" hidden lg:block">
<Sidebar />
</div>
<div className=" bg-[#1212] min-h-[100vh] pb-[15vh] relative flex-1 ">
<div className="flex flex-col max-h-[830px] overflow-y-auto scroll-m-1">
<div className="sticky z-10 top-0 w-full shadow-2xl bg-black">
<nav className="flex justify-between p-4 ">
<span
className="text-[#caccce] font-medium text-3xl cursor-pointer"
onClick={() => (window.location.href = "/")}
>
Loca
</span>
<div className="flex gap-6 items-center">
<div
className="flex gap-1 cursor-pointer text-[#ccc] lg:hidden"
onClick={SignOut}
>
<LogOut />
<span className="animate-fadeIn xs:hidden">LogOut</span>
</div>
<Image
src={image}
alt="user"
className="rounded-full"
width={50}
height={50}
/>
</div>
</nav>
</div>

<div className="h-screen px-5 ">
<Main />
</div>
return (
<main className="flex h-screen bg-black overflow-hidden">
<FirstVisitPopup />
<div className="hidden lg:block">
<Sidebar />
</div>
<div className="flex-1 flex flex-col">
<nav className="flex justify-between p-4 sticky top-0 z-10">
<button className="lg:hidden text-white" onClick={() => setIsSidebarOpen(!isSidebarOpen)}>
<MenuIcon />
</button>
<span className="text-[#caccce] font-medium text-3xl cursor-pointer" onClick={() => window.location.href = "/"}>
Loca
</span>
<div className="flex gap-6 items-center">
<button className="lg:hidden text-[#ccc]" onClick={SignOut}>
<LogOut />
</button>
<Image src={image} alt="user" className="rounded-full" width={50} height={50} />
</div>
</nav>
<div className="flex-1 overflow-hidden">
<Main />
</div>
</div>
<AnimatePresence>
{isSidebarOpen && (
<motion.div
ref={sidebarRef}
initial={{ x: "-100%" }}
animate={{ x: 0 }}
exit={{ x: "-100%" }}
transition={{ type: "spring", stiffness: 300, damping: 30 }}
className="fixed inset-y-0 left-0 w-64 bg-gray-900 z-50 lg:hidden"
>
<button
className="absolute top-4 right-4 text-white"
onClick={() => setIsSidebarOpen(false)}
>
<X size={24} />
</button>
<Sidebar onClose={() => setIsSidebarOpen(false)} />
</motion.div>
)}
</AnimatePresence>
</main>
);
}
}
86 changes: 50 additions & 36 deletions components/chatInbox.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { SendHorizontalIcon } from "lucide-react";
import { motion } from 'framer-motion';

export const ChatInbox: React.FC<ChatInboxProps> = ({
textareaRef,
Expand All @@ -9,41 +10,54 @@ export const ChatInbox: React.FC<ChatInboxProps> = ({
locationError,
}) => {
return (
<main className="relative mt-2 bg-red-400 "> {/* Adjust positioning */}
<div className="px-3 w-full max-w-5xl mx-auto fixed bottom-0 left-0 right-0 bg-black ">
{locationError && (
<div className="mb-2">
<p className="text-red-500 mb-1">{locationError}</p>
</div>
)}
<div className="relative flex items-center gap-2 w-full rounded-md bg-[#1e1f20] p-3">
<textarea
ref={textareaRef}
value={userMessage}
onChange={handleInput}
onKeyPress={(e) =>
e.key === "Enter" && !isProcessing && handleSendMessage()
}
className="flex-1 rounded-full bg-[#1e1f20] text-[#ccc] p-2 px-4 outline-none cursor-text text-md resize-none overflow-auto max-h-[6rem]"
placeholder={`Looking for local service provider? ${
isProcessing ? "processing...." : ""
}`}
disabled={isProcessing}
rows={1}
style={{ maxHeight: "6rem" }} // Adjust this value as needed
/>
<SendHorizontalIcon
className={`text-[#ccc] cursor-pointer ${
isProcessing ? "opacity-50" : ""
}`}
onClick={() => !isProcessing && handleSendMessage()}
/>
</div>
<p className="text-[#ccc] text-xs text-center mt-2">
<b>LOCA</b> uses your input to fetch services. Keep your input brief
for more accurate results.
</p>
<motion.div
className="border-t border-gray-700 p-4"
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
>
{locationError && (
<motion.div
className="mb-2"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.3 }}
>
<p className="text-red-500 text-sm">{locationError}</p>
</motion.div>
)}
<div className="flex items-center gap-2">
<motion.textarea
ref={textareaRef}
value={userMessage}
onChange={handleInput}
onKeyPress={(e) => e.key === "Enter" && !isProcessing && handleSendMessage()}
className="flex-1 bg-gray-800 text-white rounded-full px-4 py-2 outline-none resize-none"
placeholder={isProcessing ? "Processing..." : "Looking for local service provider?"}
disabled={isProcessing}
rows={1}
style={{ maxHeight: "6rem" }}
whileFocus={{ scale: 1.02 }}
transition={{ duration: 0.2 }}
/>
<motion.button
className={`text-white p-2 rounded-full ${isProcessing ? 'opacity-50 cursor-not-allowed' : 'bg-blue-500 hover:bg-blue-600'}`}
onClick={() => !isProcessing && handleSendMessage()}
disabled={isProcessing}
whileHover={{ scale: 1.1 }}
whileTap={{ scale: 0.9 }}
>
<SendHorizontalIcon className="w-5 h-5" />
</motion.button>
</div>
</main>
<motion.p
className="text-gray-400 text-xs text-center mt-2"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.3, delay: 0.2 }}
>
<b>LOCA</b> uses your input to fetch services. Keep your input brief for more accurate results.
</motion.p>
</motion.div>
);
};
}
60 changes: 35 additions & 25 deletions components/chatpage.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { cn } from "@/lib/utils";
import { ScrollArea } from "@radix-ui/react-scroll-area";
import Image from "next/image";
import { motion } from 'framer-motion';
export const ChatPage: React.FC<ChatPageProps> = ({
message,
index,
Expand All @@ -10,31 +11,40 @@ export const ChatPage: React.FC<ChatPageProps> = ({

conversationEndRef,
}) => {
const isUser = message.sender === "user";
const bubbleVariants = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0 },
};

return (
<ScrollArea className="">
<main className="">
<div
key={index}
className="flex flex-col lg:flex-row lg:items-center gap-4 mb-8"
>
<Image
src={message.sender === "user" ? image : logo}
alt={message.sender === "user" ? "user" : "Loca AI image"}
width={50}
height={50}
className={cn(
message.sender === "user" ? "" : "",
"self-start rounded-full",
)}
/>
<p className="text-white " onCopy={(e) => !!e}>
{message.text}
</p>
<div ref={conversationEndRef} />
</div>
{/*
{isLoading && <SkeletonCard />} */}
</main>
</ScrollArea>
<motion.div
className={`flex ${isUser ? 'justify-end' : 'justify-start'} mb-4`}
initial="hidden"
animate="visible"
variants={bubbleVariants}
transition={{ duration: 0.3, delay: index * 0.1 }}
>
<div className={`flex ${isUser ? 'flex-row-reverse' : 'flex-row'} items-end max-w-[90%]`}>
<Image
src={isUser ? image : logo}
alt={isUser ? "user" : "Loca AI image"}
width={40}
height={40}
className="rounded-full"
/>
<motion.div
className={`px-4 py-2 rounded-2xl ${
isUser
? 'bg-blue-700 text-white mr-2'
: 'bg-gray-600 text-black ml-2'
}`}
whileHover={{ scale: 1.03 }}
// whileTap={{ scale: 0.95 }}
>
{message.text}
</motion.div>
</div>
</motion.div>
);
};
40 changes: 32 additions & 8 deletions components/loading.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,39 @@
import { Skeleton } from "@/components/ui/skeleton";
import { motion } from 'framer-motion';

export function SkeletonCard() {
const skeletonVariants = {
hidden: { opacity: 0, y: 20 },
visible: { opacity: 1, y: 0 },
};

return (
<div className="flex flex-col space-y-3 ">
<motion.div
className="flex flex-col space-y-3"
initial="hidden"
animate="visible"
variants={skeletonVariants}
transition={{ duration: 8 }}
>
<div className="space-y-2">
<Skeleton className="h-4 w-full max-w-[900px]" />
<Skeleton className="h-4 w-full max-w-[900px]" />
<Skeleton className="h-4 w-full max-w-[900px]" />
<Skeleton className="h-4 w-full max-w-[900px]" />
{[1, 2, 3, 4].map((i) => (
<motion.div
key={i}
initial={{ scaleX: 0 }}
animate={{ scaleX: 1 }}
transition={{ duration: 0.5, delay: i * 0.1 }}
>
<Skeleton className="h-4 w-full max-w-[900px]" />
</motion.div>
))}
</div>
<Skeleton className="h-[125px] max-w-[900px] rounded-xl" />
</div>
<motion.div
initial={{ scaleY: 0 }}
animate={{ scaleY: 1 }}
transition={{ duration: 0.5, delay: 0.5 }}
>
<Skeleton className="h-[125px] max-w-[900px] rounded-xl" />
</motion.div>
</motion.div>
);
}
}
Loading

0 comments on commit 108ec7c

Please sign in to comment.