+146
-21
@@ -12,30 +12,56 @@ import { Server } from "@/types/types";
|
||||
import { motion } from "framer-motion";
|
||||
import {
|
||||
ChevronLeft,
|
||||
MessageCircle,
|
||||
Users,
|
||||
PlusCircle,
|
||||
Settings,
|
||||
ChevronRight,
|
||||
Users,
|
||||
Settings,
|
||||
TrendingUp,
|
||||
NetworkIcon,
|
||||
MessagesSquare,
|
||||
} from "lucide-react";
|
||||
import Link from "next/link";
|
||||
import StartNewDM from "./StartNewDM";
|
||||
import AsyncValue from "@/components/AsyncValue";
|
||||
import { useRouter } from "next/navigation";
|
||||
import Cookies from "js-cookie";
|
||||
import { toast } from "sonner";
|
||||
|
||||
export default function ChatHub() {
|
||||
const app = useApp();
|
||||
const router = useRouter();
|
||||
|
||||
const recentDMs = app.dms?.slice(0, 5) || [];
|
||||
// const activeServers = app.servers?.slice(0, 5) || [];
|
||||
const activeServers: Server[] = [];
|
||||
const app = useApp();
|
||||
const recentPeople = app.dms.slice(0, 3);
|
||||
const activeServers: Server[] = Object.values(app.servers).slice(0, 3);
|
||||
const trendingServers: Server[] = [
|
||||
{
|
||||
id: "cee6b20f-7e6a-4071-8df1-aab37f52bbb9",
|
||||
name: "ORUS",
|
||||
icon_url: "https://github.com/orus-dev.png",
|
||||
channels: [],
|
||||
},
|
||||
{
|
||||
id: "cee6b20f-7e6a-4071-8df1-aab37f52bbb9",
|
||||
name: "Saturn Client",
|
||||
icon_url: "https://github.com/saturnclientmc.png",
|
||||
channels: [],
|
||||
},
|
||||
{
|
||||
id: "cee6b20f-7e6a-4071-8df1-aab37f52bbb9",
|
||||
name: "OSUI Rust Hub",
|
||||
icon_url: "https://github.com/osui-rs.png",
|
||||
channels: [],
|
||||
},
|
||||
];
|
||||
const recentActivity = app.messages.slice(0, 3);
|
||||
|
||||
return (
|
||||
<AppLayout app={app}>
|
||||
<div className="h-svh w-full max-h-svh flex flex-col py-5 px-5 gap-5">
|
||||
<div className="h-svh w-full max-h-svh flex flex-col py-6 px-5 md:px-10 gap-6 overflow-y-auto">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 10 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.2 }}
|
||||
className="space-y-8"
|
||||
transition={{ duration: 0.3 }}
|
||||
className="space-y-10"
|
||||
>
|
||||
{/* Greeting */}
|
||||
<div onClick={() => app.setSidebarOpen(true)}>
|
||||
@@ -48,22 +74,23 @@ export default function ChatHub() {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Recent DMs + Active Servers */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||
{/* Recent DMs */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<MessageCircle className="w-5 h-5" />
|
||||
Recent DMs
|
||||
<Users className="w-5 h-5" />
|
||||
Recently chatted with
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
{recentDMs.length ? (
|
||||
recentDMs.map((dm) => (
|
||||
{recentPeople.length ? (
|
||||
recentPeople.map((dm) => (
|
||||
<Link
|
||||
href={`/chat/${dm.id}`}
|
||||
key={dm.id}
|
||||
className="flex justify-between items-center hover:bg-accent hover:text-accent-foreground rounded p-1"
|
||||
className="flex justify-between items-center hover:bg-accent hover:text-accent-foreground rounded p-2 transition"
|
||||
>
|
||||
<span className="flex gap-2">
|
||||
<ProfilePicture
|
||||
@@ -89,7 +116,7 @@ export default function ChatHub() {
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<Users className="w-5 h-5" />
|
||||
<NetworkIcon className="w-5 h-5" />
|
||||
Active Servers
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
@@ -99,10 +126,13 @@ export default function ChatHub() {
|
||||
<Link
|
||||
href={`/server/${srv.id}`}
|
||||
key={srv.id}
|
||||
className="flex justify-between items-center hover:bg-accent hover:text-accent-foreground rounded p-1"
|
||||
className="flex justify-between items-center hover:bg-accent hover:text-accent-foreground rounded p-2 transition"
|
||||
>
|
||||
<span className="flex gap-2">
|
||||
<ProfilePicture name={srv.name} url="" />
|
||||
<ProfilePicture
|
||||
name={srv.name}
|
||||
url={srv.icon_url || ""}
|
||||
/>
|
||||
<span className="truncate my-auto">{srv.name}</span>
|
||||
</span>
|
||||
<ChevronRight />
|
||||
@@ -117,11 +147,106 @@ export default function ChatHub() {
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Recent Activity Feed */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<MessagesSquare className="w-5 h-5" />
|
||||
Recent Activity
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
{recentActivity.length ? (
|
||||
recentActivity.map((msg) => (
|
||||
<AsyncValue
|
||||
key={msg.id + msg.channel_id}
|
||||
func={() => app.getUserById(msg.from)}
|
||||
render={({ value: user }) => (
|
||||
<Link
|
||||
href={`/chat/${msg.from}`}
|
||||
className="flex items-center justify-between hover:bg-accent rounded p-2 transition"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<ProfilePicture
|
||||
name={user?.display_name || ""}
|
||||
url={user?.avatar_url || ""}
|
||||
/>
|
||||
<div>
|
||||
<p className="font-medium">
|
||||
{user?.display_name || ""}
|
||||
</p>
|
||||
<p className="text-sm text-muted-foreground truncate w-[200px]">
|
||||
{msg.contents}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<ChevronRight className="opacity-70 w-4 h-4" />
|
||||
</Link>
|
||||
)}
|
||||
/>
|
||||
))
|
||||
) : (
|
||||
<p className="text-sm text-muted-foreground">
|
||||
No recent messages yet.
|
||||
</p>
|
||||
)}
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
{/* Server Discovery */}
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2">
|
||||
<TrendingUp className="w-5 h-5" />
|
||||
Trending Servers
|
||||
</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||
{trendingServers.map((srv) => (
|
||||
<motion.div
|
||||
key={srv.id}
|
||||
whileHover={{ scale: 1.03 }}
|
||||
transition={{ type: "spring", stiffness: 300 }}
|
||||
>
|
||||
<Card
|
||||
className="hover:bg-accent transition"
|
||||
onClick={async () => {
|
||||
try {
|
||||
Cookies.set(
|
||||
"servers",
|
||||
Object.keys({
|
||||
...app.servers,
|
||||
[srv.id]: srv,
|
||||
}).join(",")
|
||||
);
|
||||
|
||||
app.setServers((prev) => ({
|
||||
...prev,
|
||||
[srv.id]: srv,
|
||||
}));
|
||||
router.push(`/server/${srv.id}`);
|
||||
} catch (e: any) {
|
||||
toast.error(e);
|
||||
}
|
||||
}}
|
||||
>
|
||||
<CardContent className="p-4 flex flex-col items-center text-center gap-2">
|
||||
<ProfilePicture name={srv.name} url={srv.icon_url} />
|
||||
<h3 className="font-medium truncate">{srv.name}</h3>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</motion.div>
|
||||
))}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Separator />
|
||||
|
||||
{/* Quick Actions */}
|
||||
<div className="w-full h-full flex items-center justify-center">
|
||||
{/* <h2 className="text-xl font-semibold mb-4">Quick Actions</h2> */}
|
||||
<div className="w-full flex items-center justify-center">
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<StartNewDM />
|
||||
<Button variant="outline" className="gap-2">
|
||||
|
||||
@@ -173,3 +173,15 @@
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background-color: #4b5563;
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
/* Hide scrollbar for Chrome, Safari and Opera */
|
||||
.no-scrollbar::-webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
/* Hide scrollbar for IE, Edge and Firefox */
|
||||
.no-scrollbar {
|
||||
-ms-overflow-style: none; /* IE and Edge */
|
||||
scrollbar-width: none; /* Firefox */
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
"use client";
|
||||
|
||||
import useAsync from "@/hooks/use-async";
|
||||
|
||||
export default function AsyncValue<T, E = any>({
|
||||
func,
|
||||
render,
|
||||
}: Readonly<{
|
||||
func: () => Promise<T>;
|
||||
render: ({
|
||||
loading,
|
||||
}: {
|
||||
value: T | undefined;
|
||||
loading: boolean;
|
||||
error: E | undefined;
|
||||
}) => React.ReactNode;
|
||||
}>) {
|
||||
return render(useAsync(func));
|
||||
}
|
||||
@@ -20,17 +20,18 @@ export default function SidebarDMs({ app }: { app: App }) {
|
||||
if (!app.profile) return;
|
||||
|
||||
const load = async () => {
|
||||
const users = [
|
||||
...new Set(
|
||||
await Promise.all(
|
||||
app.messages
|
||||
.sort((a, b) => a.timestamp - b.timestamp)
|
||||
.flatMap((m) => [m.from, m.channel_id])
|
||||
.filter((id) => id !== app.profile?.id)
|
||||
.map((id) => app.getUserById(id))
|
||||
)
|
||||
),
|
||||
].filter((v) => !!v);
|
||||
const users = (
|
||||
await Promise.all(
|
||||
[
|
||||
...new Set(
|
||||
app.messages
|
||||
.sort((a, b) => a.timestamp - b.timestamp)
|
||||
.flatMap((m) => [m.from, m.channel_id])
|
||||
.filter((id) => id !== app.profile?.id)
|
||||
),
|
||||
].map((id) => app.getUserById(id))
|
||||
)
|
||||
).filter((v) => !!v);
|
||||
|
||||
app.setDms(users);
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ export default function SidebarServers({ app }: { app: App }) {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="w-16 flex flex-col items-center gap-4 py-4 border-r h-full">
|
||||
<div className="w-16 flex flex-col items-center gap-4 py-4 border-r h-full overflow-y-scroll no-scrollbar">
|
||||
<Button
|
||||
onClick={() => router.push(`/chat`)}
|
||||
className="bg-transparent text-accent-foreground hover:text-accent"
|
||||
@@ -50,9 +50,9 @@ export default function SidebarServers({ app }: { app: App }) {
|
||||
</Button>
|
||||
|
||||
{Object.values(app.servers).map(
|
||||
(srv) =>
|
||||
(srv, i) =>
|
||||
srv && (
|
||||
<Link href={`/server/${srv.id}`} key={srv.id}>
|
||||
<Link href={`/server/${srv.id}`} key={i}>
|
||||
<ProfilePicture name={srv.name} url={srv.icon_url} />
|
||||
</Link>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user