+132
-11
@@ -1,24 +1,145 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
|
import ProfilePicture from "@/components/ProfilePicture";
|
||||||
|
import SettingsDialog from "@/components/settings/SettingsDialog";
|
||||||
import AppSidebar from "@/components/Sidebar";
|
import AppSidebar from "@/components/Sidebar";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import { useIsMobile } from "@/hooks/is-mobile";
|
import { Separator } from "@/components/ui/separator";
|
||||||
import useApp from "@/hooks/use-app";
|
import useApp from "@/hooks/use-app";
|
||||||
|
import { ProfileSettings } from "@/types/settings";
|
||||||
|
import { Server } from "@/types/types";
|
||||||
|
import { motion } from "framer-motion";
|
||||||
|
import {
|
||||||
|
ChevronLeft,
|
||||||
|
MessageCircle,
|
||||||
|
Users,
|
||||||
|
PlusCircle,
|
||||||
|
Settings,
|
||||||
|
ChevronRight,
|
||||||
|
} from "lucide-react";
|
||||||
|
import Link from "next/link";
|
||||||
|
|
||||||
export default function Chat() {
|
export default function ChatHub() {
|
||||||
const app = useApp();
|
const app = useApp();
|
||||||
const isMobile = useIsMobile();
|
|
||||||
|
const recentDMs = app.dms?.slice(0, 5) || [];
|
||||||
|
// const activeServers = app.servers?.slice(0, 5) || [];
|
||||||
|
const activeServers: Server[] = [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<AppSidebar app={app}>
|
<AppSidebar app={app}>
|
||||||
<Card className="bg-background w-full">
|
<div className="h-svh w-full max-h-svh flex flex-col py-5 px-5 gap-5">
|
||||||
<CardHeader>
|
<motion.div
|
||||||
<CardTitle onClick={() => isMobile && app.setSidebarOpen(true)}>
|
initial={{ opacity: 0, y: 10 }}
|
||||||
Direct Messages
|
animate={{ opacity: 1, y: 0 }}
|
||||||
</CardTitle>
|
transition={{ duration: 0.2 }}
|
||||||
</CardHeader>
|
className="space-y-8"
|
||||||
<CardContent></CardContent>
|
>
|
||||||
</Card>
|
{/* Greeting */}
|
||||||
|
<div onClick={() => app.setSidebarOpen(true)}>
|
||||||
|
<h1 className="text-3xl font-bold flex cursor-pointer gap-2">
|
||||||
|
<ChevronLeft className="my-auto w-5" />
|
||||||
|
Hey {app.profile?.display_name || "there"}
|
||||||
|
</h1>
|
||||||
|
<p className="text-muted-foreground">
|
||||||
|
Here's what's happening in your network today.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<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
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-3">
|
||||||
|
{recentDMs.length ? (
|
||||||
|
recentDMs.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"
|
||||||
|
>
|
||||||
|
<span className="flex gap-2">
|
||||||
|
<ProfilePicture
|
||||||
|
name={dm.display_name}
|
||||||
|
url={dm.avatar_url}
|
||||||
|
/>
|
||||||
|
<span className="truncate my-auto">
|
||||||
|
{dm.display_name || dm.id}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
<ChevronRight />
|
||||||
|
</Link>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
No recent DMs yet.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
|
||||||
|
{/* Active Servers */}
|
||||||
|
<Card>
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle className="flex items-center gap-2">
|
||||||
|
<Users className="w-5 h-5" />
|
||||||
|
Active Servers
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-3">
|
||||||
|
{activeServers.length ? (
|
||||||
|
activeServers.map((srv) => (
|
||||||
|
<Link
|
||||||
|
href={`/server/${srv.id}`}
|
||||||
|
key={srv.id}
|
||||||
|
className="flex justify-between items-center hover:bg-accent hover:text-accent-foreground rounded p-1"
|
||||||
|
>
|
||||||
|
<span className="flex gap-2">
|
||||||
|
<ProfilePicture name={srv.name} url="" />
|
||||||
|
<span className="truncate my-auto">{srv.name}</span>
|
||||||
|
</span>
|
||||||
|
<ChevronRight />
|
||||||
|
</Link>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<p className="text-sm text-muted-foreground">
|
||||||
|
No servers joined yet.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<Separator />
|
||||||
|
|
||||||
|
{/* Quick Actions */}
|
||||||
|
<div>
|
||||||
|
<h2 className="text-xl font-semibold mb-4">Quick Actions</h2>
|
||||||
|
<div className="flex flex-wrap gap-3">
|
||||||
|
<Button variant="outline" className="gap-2">
|
||||||
|
<PlusCircle className="w-4 h-4" />
|
||||||
|
Start New DM
|
||||||
|
</Button>
|
||||||
|
<Button variant="outline" className="gap-2">
|
||||||
|
<Users className="w-4 h-4" />
|
||||||
|
Create Server
|
||||||
|
</Button>
|
||||||
|
<SettingsDialog profile={app.profile as ProfileSettings}>
|
||||||
|
<Button variant="outline" className="gap-2">
|
||||||
|
<Settings className="w-4 h-4" />
|
||||||
|
Settings
|
||||||
|
</Button>
|
||||||
|
</SettingsDialog>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</motion.div>
|
||||||
|
</div>
|
||||||
</AppSidebar>
|
</AppSidebar>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,15 +11,13 @@ import {
|
|||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
} from "./ui/dropdown-menu";
|
} from "./ui/dropdown-menu";
|
||||||
import { ChevronLeftIcon, SendIcon, SmilePlusIcon } from "lucide-react";
|
import { ChevronLeftIcon, SendIcon, SmilePlusIcon } from "lucide-react";
|
||||||
import { useEffect, useRef, useState } from "react";
|
import { useRef, useState } from "react";
|
||||||
import { format, isToday, isYesterday } from "date-fns";
|
import { format, isToday, isYesterday } from "date-fns";
|
||||||
import ReactMarkdown from "react-markdown";
|
import ReactMarkdown from "react-markdown";
|
||||||
import remarkGfm from "remark-gfm";
|
import remarkGfm from "remark-gfm";
|
||||||
import { UserProfile } from "@/hooks/get-user";
|
import { UserProfile } from "@/hooks/get-user";
|
||||||
import axios from "axios";
|
|
||||||
import ProfilePicture from "./ProfilePicture";
|
import ProfilePicture from "./ProfilePicture";
|
||||||
import App from "@/types/app";
|
import App from "@/types/app";
|
||||||
import { useIsMobile } from "@/hooks/use-mobile";
|
|
||||||
import { Textarea } from "./ui/textarea";
|
import { Textarea } from "./ui/textarea";
|
||||||
|
|
||||||
function MessageContainer({ message, app }: { message: Message; app: App }) {
|
function MessageContainer({ message, app }: { message: Message; app: App }) {
|
||||||
@@ -130,7 +128,6 @@ export default function MessageBox({
|
|||||||
messages: Message[];
|
messages: Message[];
|
||||||
}) {
|
}) {
|
||||||
const [text, setText] = useState("");
|
const [text, setText] = useState("");
|
||||||
const isMobile = useIsMobile();
|
|
||||||
const inputRef = useRef<any>(null);
|
const inputRef = useRef<any>(null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -138,7 +135,7 @@ export default function MessageBox({
|
|||||||
{channelName && (
|
{channelName && (
|
||||||
<header className="h-12 py-4 flex items-center border-b text-sm font-semibold">
|
<header className="h-12 py-4 flex items-center border-b text-sm font-semibold">
|
||||||
<span
|
<span
|
||||||
onClick={() => isMobile && app.setSidebarOpen(true)}
|
onClick={() => app.setSidebarOpen(true)}
|
||||||
className="cursor-pointer flex"
|
className="cursor-pointer flex"
|
||||||
>
|
>
|
||||||
<ChevronLeftIcon className="w-5 h-5" />
|
<ChevronLeftIcon className="w-5 h-5" />
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ import { cn } from "@/lib/utils";
|
|||||||
import App from "@/types/app";
|
import App from "@/types/app";
|
||||||
import { ProfileSettings } from "@/types/settings";
|
import { ProfileSettings } from "@/types/settings";
|
||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
|
import { useIsMobile } from "@/hooks/is-mobile";
|
||||||
|
|
||||||
export default function AppSidebar({
|
export default function AppSidebar({
|
||||||
children,
|
children,
|
||||||
@@ -54,7 +55,7 @@ export default function AppSidebar({
|
|||||||
const [newServer, setNewServer] = useState({ name: "", ip: "" });
|
const [newServer, setNewServer] = useState({ name: "", ip: "" });
|
||||||
const [query, setQuery] = useState("");
|
const [query, setQuery] = useState("");
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [filteredDms, setFilteredDms] = useState<UserProfile[]>([]);
|
const isMobile = useIsMobile();
|
||||||
|
|
||||||
const dms = useMemo(() => {
|
const dms = useMemo(() => {
|
||||||
return Array.from(
|
return Array.from(
|
||||||
@@ -69,7 +70,7 @@ export default function AppSidebar({
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const load = async () => {
|
const load = async () => {
|
||||||
const users = await Promise.all(dms.map((id) => app.getUserById(id)));
|
const users = await Promise.all(dms.map((id) => app.getUserById(id)));
|
||||||
setFilteredDms(
|
app.setDms(
|
||||||
users.filter(
|
users.filter(
|
||||||
(user) =>
|
(user) =>
|
||||||
user !== null &&
|
user !== null &&
|
||||||
@@ -139,7 +140,7 @@ export default function AppSidebar({
|
|||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"border-r transition-all duration-300 ease-in-out overflow-hidden flex",
|
"border-r transition-all duration-300 ease-in-out overflow-hidden flex",
|
||||||
app.sidebarOpen ? "w-5xl md:w-md" : "w-0 md:w-md"
|
app.sidebarOpen && isMobile ? "w-5xl md:w-md" : "w-0 md:w-md"
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="w-16 flex flex-col items-center gap-4 py-4 bg-muted border-r">
|
<div className="w-16 flex flex-col items-center gap-4 py-4 bg-muted border-r">
|
||||||
@@ -258,7 +259,7 @@ export default function AppSidebar({
|
|||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
{filteredDms.map((user) => (
|
{app.dms.map((user) => (
|
||||||
<DMItem
|
<DMItem
|
||||||
name={user.display_name}
|
name={user.display_name}
|
||||||
id={user.id}
|
id={user.id}
|
||||||
@@ -268,7 +269,7 @@ export default function AppSidebar({
|
|||||||
app={app}
|
app={app}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{filteredDms.length === 0 && query.trim() !== "" ? (
|
{app.dms.length === 0 && query.trim() !== "" ? (
|
||||||
<div className="px-3 py-2">
|
<div className="px-3 py-2">
|
||||||
<Button
|
<Button
|
||||||
variant="ghost"
|
variant="ghost"
|
||||||
@@ -303,7 +304,7 @@ export default function AppSidebar({
|
|||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{app.sidebarOpen ? (
|
{app.sidebarOpen && isMobile ? (
|
||||||
<div
|
<div
|
||||||
className="w-full h-full overflow-x-hidden"
|
className="w-full h-full overflow-x-hidden"
|
||||||
onClick={() => app.setSidebarOpen(false)}
|
onClick={() => app.setSidebarOpen(false)}
|
||||||
|
|||||||
@@ -10,28 +10,34 @@ import {
|
|||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import Settings from "./Settings";
|
import Settings from "./Settings";
|
||||||
import { ProfileSettings } from "@/types/settings";
|
import { ProfileSettings } from "@/types/settings";
|
||||||
import { useState } from "react";
|
import { ReactNode, useState } from "react";
|
||||||
|
|
||||||
export default function SettingsDialog({
|
export default function SettingsDialog({
|
||||||
className,
|
className,
|
||||||
tab,
|
tab,
|
||||||
profile,
|
profile,
|
||||||
|
children,
|
||||||
}: {
|
}: {
|
||||||
className?: string;
|
className?: string;
|
||||||
tab?: string;
|
tab?: string;
|
||||||
profile: ProfileSettings;
|
profile: ProfileSettings;
|
||||||
|
children?: ReactNode;
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<Dialog>
|
<Dialog>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button
|
{children ? (
|
||||||
className={cn(
|
children
|
||||||
"bg-transparent text-accent-foreground hover:text-accent",
|
) : (
|
||||||
className
|
<Button
|
||||||
)}
|
className={cn(
|
||||||
>
|
"bg-transparent text-accent-foreground hover:text-accent",
|
||||||
<SettingsIcon />
|
className
|
||||||
</Button>
|
)}
|
||||||
|
>
|
||||||
|
<SettingsIcon />
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
<DialogContent>
|
<DialogContent>
|
||||||
<DialogHeader>
|
<DialogHeader>
|
||||||
|
|||||||
@@ -13,8 +13,11 @@ export default function useApp(): App {
|
|||||||
const [sidebarOpen, setSidebarOpen] = useState(false);
|
const [sidebarOpen, setSidebarOpen] = useState(false);
|
||||||
const profile = useUser();
|
const profile = useUser();
|
||||||
const [clientSettings, setClientSettings] = useClientSettings();
|
const [clientSettings, setClientSettings] = useClientSettings();
|
||||||
|
const [dms, setDms] = useState<UserProfile[]>([]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
dms,
|
||||||
|
setDms,
|
||||||
node,
|
node,
|
||||||
clientSettings,
|
clientSettings,
|
||||||
setClientSettings,
|
setClientSettings,
|
||||||
|
|||||||
@@ -23,4 +23,7 @@ export default interface App {
|
|||||||
setSidebarOpen: SetState<boolean>;
|
setSidebarOpen: SetState<boolean>;
|
||||||
|
|
||||||
getUserById: (userId: string) => Promise<UserProfile | null>;
|
getUserById: (userId: string) => Promise<UserProfile | null>;
|
||||||
|
|
||||||
|
dms: UserProfile[];
|
||||||
|
setDms: SetState<UserProfile[]>;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user