Better hub
This commit is contained in:
+107
-13
@@ -12,30 +12,42 @@ import { Server } from "@/types/types";
|
|||||||
import { motion } from "framer-motion";
|
import { motion } from "framer-motion";
|
||||||
import {
|
import {
|
||||||
ChevronLeft,
|
ChevronLeft,
|
||||||
|
ChevronRight,
|
||||||
MessageCircle,
|
MessageCircle,
|
||||||
Users,
|
Users,
|
||||||
PlusCircle,
|
|
||||||
Settings,
|
Settings,
|
||||||
ChevronRight,
|
PlusCircle,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import StartNewDM from "./StartNewDM";
|
import StartNewDM from "./StartNewDM";
|
||||||
|
import AsyncValue from "@/components/AsyncValue";
|
||||||
|
|
||||||
export default function ChatHub() {
|
export default function ChatHub() {
|
||||||
const app = useApp();
|
const app = useApp();
|
||||||
|
|
||||||
const recentDMs = app.dms?.slice(0, 5) || [];
|
const recentDMs = app.dms?.slice(0, 5) || [];
|
||||||
// const activeServers = app.servers?.slice(0, 5) || [];
|
const activeServers: Server[] = Object.values(app.servers).slice(0, 5) || [];
|
||||||
const activeServers: Server[] = [];
|
const suggestedServers: Server[] = [
|
||||||
|
{ id: "1", name: "Frontend Devs", icon_url: "", channels: [] },
|
||||||
|
{ id: "2", name: "Gamers Lounge", icon_url: "", channels: [] },
|
||||||
|
{ id: "3", name: "Music Hub", icon_url: "", channels: [] },
|
||||||
|
];
|
||||||
|
const recentActivity = app.messages.slice(0, 5);
|
||||||
|
|
||||||
|
const stats = {
|
||||||
|
dms: app.dms?.length || 0,
|
||||||
|
servers: app.servers?.length || 0,
|
||||||
|
// friends: app.friends?.length || 0,
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppLayout app={app}>
|
<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
|
<motion.div
|
||||||
initial={{ opacity: 0, y: 10 }}
|
initial={{ opacity: 0, y: 10 }}
|
||||||
animate={{ opacity: 1, y: 0 }}
|
animate={{ opacity: 1, y: 0 }}
|
||||||
transition={{ duration: 0.2 }}
|
transition={{ duration: 0.3 }}
|
||||||
className="space-y-8"
|
className="space-y-10"
|
||||||
>
|
>
|
||||||
{/* Greeting */}
|
{/* Greeting */}
|
||||||
<div onClick={() => app.setSidebarOpen(true)}>
|
<div onClick={() => app.setSidebarOpen(true)}>
|
||||||
@@ -44,10 +56,11 @@ export default function ChatHub() {
|
|||||||
Hey {app.profile?.display_name || "there"}!
|
Hey {app.profile?.display_name || "there"}!
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-muted-foreground">
|
<p className="text-muted-foreground">
|
||||||
Here's what's happening in your network today.
|
Here’s what’s happening in your network today.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Recent DMs + Active Servers */}
|
||||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
|
||||||
{/* Recent DMs */}
|
{/* Recent DMs */}
|
||||||
<Card>
|
<Card>
|
||||||
@@ -63,7 +76,7 @@ export default function ChatHub() {
|
|||||||
<Link
|
<Link
|
||||||
href={`/chat/${dm.id}`}
|
href={`/chat/${dm.id}`}
|
||||||
key={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">
|
<span className="flex gap-2">
|
||||||
<ProfilePicture
|
<ProfilePicture
|
||||||
@@ -99,10 +112,13 @@ export default function ChatHub() {
|
|||||||
<Link
|
<Link
|
||||||
href={`/server/${srv.id}`}
|
href={`/server/${srv.id}`}
|
||||||
key={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">
|
<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 className="truncate my-auto">{srv.name}</span>
|
||||||
</span>
|
</span>
|
||||||
<ChevronRight />
|
<ChevronRight />
|
||||||
@@ -117,11 +133,89 @@ export default function ChatHub() {
|
|||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Recent Activity Feed */}
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
<MessageCircle 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">
|
||||||
|
<Users className="w-5 h-5" />
|
||||||
|
Discover Servers
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-4">
|
||||||
|
{suggestedServers.map((srv) => (
|
||||||
|
<motion.div
|
||||||
|
key={srv.id}
|
||||||
|
whileHover={{ scale: 1.03 }}
|
||||||
|
transition={{ type: "spring", stiffness: 300 }}
|
||||||
|
>
|
||||||
|
<Card className="hover:bg-accent transition">
|
||||||
|
<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>
|
||||||
|
<Button size="sm" className="gap-2">
|
||||||
|
<PlusCircle className="w-4 h-4" />
|
||||||
|
Join
|
||||||
|
</Button>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</motion.div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
<Separator />
|
<Separator />
|
||||||
|
|
||||||
{/* Quick Actions */}
|
{/* Quick Actions */}
|
||||||
<div className="w-full h-full flex items-center justify-center">
|
<div className="w-full flex items-center justify-center">
|
||||||
{/* <h2 className="text-xl font-semibold mb-4">Quick Actions</h2> */}
|
|
||||||
<div className="flex flex-wrap gap-3">
|
<div className="flex flex-wrap gap-3">
|
||||||
<StartNewDM />
|
<StartNewDM />
|
||||||
<Button variant="outline" className="gap-2">
|
<Button variant="outline" className="gap-2">
|
||||||
|
|||||||
@@ -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));
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user