Profile view #33

This commit is contained in:
2026-01-08 20:38:29 +01:00
parent 67cef72482
commit b08bd1dcac
11 changed files with 180 additions and 40 deletions
+38
View File
@@ -16,6 +16,7 @@
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-label": "^2.1.7",
"@radix-ui/react-popover": "^1.1.15",
"@radix-ui/react-scroll-area": "^1.2.10",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-separator": "^1.1.7",
@@ -1422,6 +1423,43 @@
}
}
},
"node_modules/@radix-ui/react-popover": {
"version": "1.1.15",
"resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz",
"integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==",
"license": "MIT",
"dependencies": {
"@radix-ui/primitive": "1.1.3",
"@radix-ui/react-compose-refs": "1.1.2",
"@radix-ui/react-context": "1.1.2",
"@radix-ui/react-dismissable-layer": "1.1.11",
"@radix-ui/react-focus-guards": "1.1.3",
"@radix-ui/react-focus-scope": "1.1.7",
"@radix-ui/react-id": "1.1.1",
"@radix-ui/react-popper": "1.2.8",
"@radix-ui/react-portal": "1.1.9",
"@radix-ui/react-presence": "1.1.5",
"@radix-ui/react-primitive": "2.1.3",
"@radix-ui/react-slot": "1.2.3",
"@radix-ui/react-use-controllable-state": "1.2.2",
"aria-hidden": "^1.2.4",
"react-remove-scroll": "^2.6.3"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-popper": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz",
+1
View File
@@ -19,6 +19,7 @@
"@radix-ui/react-dialog": "^1.1.15",
"@radix-ui/react-dropdown-menu": "^2.1.16",
"@radix-ui/react-label": "^2.1.7",
"@radix-ui/react-popover": "^1.1.15",
"@radix-ui/react-scroll-area": "^1.2.10",
"@radix-ui/react-select": "^2.2.6",
"@radix-ui/react-separator": "^1.1.7",
+5 -11
View File
@@ -1,6 +1,6 @@
"use client";
import ProfilePicture from "@/components/ProfilePicture";
import ProfilePreview, { ProfileImage } from "@/components/ProfilePicture";
import SettingsDialog from "@/components/settings/SettingsDialog";
import AppLayout from "@/components/app/AppLayout";
import { Button } from "@/components/ui/button";
@@ -93,10 +93,7 @@ export default function ChatHub() {
className="flex justify-between items-center hover:bg-accent hover:text-accent-foreground rounded p-2 transition"
>
<span className="flex gap-2">
<ProfilePicture
name={dm.display_name}
url={dm.avatar_url}
/>
<ProfilePreview profile={dm} />
<span className="truncate my-auto">
{dm.display_name || dm.id}
</span>
@@ -129,7 +126,7 @@ export default function ChatHub() {
className="flex justify-between items-center hover:bg-accent hover:text-accent-foreground rounded p-2 transition"
>
<span className="flex gap-2">
<ProfilePicture
<ProfileImage
name={srv.name}
url={srv.icon_url || ""}
/>
@@ -167,10 +164,7 @@ export default function ChatHub() {
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 || ""}
/>
<ProfilePreview profile={user || undefined} />
<div>
<p className="font-medium">
{user?.display_name || ""}
@@ -233,7 +227,7 @@ export default function ChatHub() {
}}
>
<CardContent className="p-4 flex flex-col items-center text-center gap-2">
<ProfilePicture name={srv.name} url={srv.icon_url} />
<ProfileImage name={srv.name} url={srv.icon_url} />
<h3 className="font-medium truncate">{srv.name}</h3>
</CardContent>
</Card>
+64 -3
View File
@@ -1,19 +1,80 @@
import { cn } from "@/lib/utils";
import { Avatar, AvatarFallback, AvatarImage } from "./ui/avatar";
import { Popover, PopoverTrigger, PopoverContent } from "./ui/popover";
import { Button } from "./ui/button";
import { UserProfile } from "@/hooks/get-user";
export default function ProfilePicture({
export function ProfileImage({
name,
url,
className,
}: {
name: string;
url: string | undefined;
url: string;
className?: string;
}) {
return (
<Avatar className={cn("h-8 w-8", className)}>
<Avatar className={cn("h-8 w-8 cursor-pointer", className)}>
<AvatarImage src={url} alt={name} />
<AvatarFallback>{name[0]}</AvatarFallback>
</Avatar>
);
}
export default function ProfilePreview({
className,
profile,
}: {
profile?: UserProfile;
className?: string;
}) {
if (!profile)
return (
<Avatar className={cn("h-8 w-8 cursor-pointer", className)}>
<AvatarFallback>Loading</AvatarFallback>
</Avatar>
);
return (
<Popover>
<PopoverTrigger asChild>
<Avatar className={cn("h-8 w-8 cursor-pointer", className)}>
<AvatarImage src={profile.avatar_url} alt={profile.display_name} />
<AvatarFallback>{profile.display_name[0]}</AvatarFallback>
</Avatar>
</PopoverTrigger>
<PopoverContent
side="right"
align="center"
sideOffset={8}
className="w-64 p-4"
>
<div className="flex items-center gap-4">
<Avatar className={cn("h-8 w-8 cursor-pointer", className)}>
<AvatarImage src={profile.avatar_url} alt={profile.display_name} />
<AvatarFallback>{profile.display_name[0]}</AvatarFallback>
</Avatar>
<div className="flex flex-col">
<span className="font-medium leading-none">
{profile.display_name}
</span>
<span className="text-sm text-muted-foreground">
@{profile.username}
</span>
</div>
</div>
<div className="mt-4 flex gap-2">
<Button size="sm" className="flex-1">
Message
</Button>
{/* <Button size="sm" variant="outline" className="flex-1">
Message
</Button> */}
</div>
</PopoverContent>
</Popover>
);
}
+1 -2
View File
@@ -91,9 +91,8 @@ export default function AppLayout({
<footer className="mt-auto pb-3 px-3">
<DMItem
name={app.profile?.display_name || "Loading.."}
profile={app.profile}
id={app.profile?.id || "me"}
avatar={app.profile?.avatar_url || ""}
status="online"
app={app}
settings
+13 -10
View File
@@ -1,24 +1,23 @@
"use client";
import Link from "next/link";
import ProfilePicture from "../ProfilePicture";
import ProfilePreview from "../ProfilePicture";
import SettingsDialog from "../settings/SettingsDialog";
import App from "@/types/app";
import { ProfileSettings } from "@/types/settings";
import { UserProfile } from "@/hooks/get-user";
export default function DMItem({
name,
id,
avatar,
status,
settings,
app,
profile,
id,
}: {
name: string;
id: string;
avatar: string;
status: string;
profile?: UserProfile | null;
settings?: boolean;
id: string;
status: string;
app: App;
}) {
return (
@@ -26,11 +25,15 @@ export default function DMItem({
href={settings ? "" : `/chat/${id}`}
className="p-2 flex items-center gap-2 cursor-pointer hover:bg-accent rounded-md"
>
<ProfilePicture name={name} url={avatar} />
{profile && (
<>
<ProfilePreview profile={profile} />
<div className="flex flex-col">
<span className="text-sm font-medium">{name}</span>
<span className="text-sm font-medium">{profile.display_name}</span>
<span className="text-xs text-muted-foreground">{status}</span>
</div>
</>
)}
{settings && (
<SettingsDialog
className="ml-auto w-7 h-7 p-0 flex items-center justify-center"
+1 -2
View File
@@ -65,9 +65,8 @@ export default function SidebarDMs({ app }: { app: App }) {
{dms.map((user) => (
<DMItem
key={user.id}
name={user.display_name}
profile={user}
id={user.id}
avatar={user.avatar_url}
status="online"
app={app}
/>
+2 -2
View File
@@ -18,7 +18,7 @@ import { Label } from "../ui/label";
import { toast } from "sonner";
import { useState } from "react";
import App from "@/types/app";
import ProfilePicture from "../ProfilePicture";
import ProfilePreview, { ProfileImage } from "../ProfilePicture";
import SettingsDialog from "../settings/SettingsDialog";
import { useRouter } from "next/navigation";
import { getServerById } from "@/actions/get-server";
@@ -52,7 +52,7 @@ export default function SidebarServers({ app }: { app: App }) {
(srv, i) =>
srv && (
<Link href={`/server/${srv.id}`} key={i}>
<ProfilePicture name={srv.name} url={srv.icon_url} />
<ProfileImage name={srv.name} url={srv.icon_url} />
</Link>
)
)}
+3 -6
View File
@@ -16,7 +16,7 @@ import { format, isToday, isYesterday } from "date-fns";
import ReactMarkdown from "react-markdown";
import remarkGfm from "remark-gfm";
import { UserProfile } from "@/hooks/get-user";
import ProfilePicture from "../ProfilePicture";
import ProfilePreview, { ProfileImage } from "../ProfilePicture";
import App, { SetState } from "@/types/app";
import { Textarea } from "../ui/textarea";
import {
@@ -60,10 +60,7 @@ function MessageContainer({
<div className="flex flex-col gap-3 rounded-lg p-4 hover:bg-accent">
<div className="flex gap-2">
{/* <div className="w-8 h-8 rounded-full bg-gray-400" /> */}
<ProfilePicture
url={profile?.avatar_url}
name={profile?.display_name || message.from}
/>
<ProfilePreview profile={profile || undefined} />
<div className="flex flex-col flex-1">
<span className="font-bold flex gap-1 items-center select-none">
{profile?.display_name || message.from}
@@ -379,7 +376,7 @@ export default function MessageBox({
<ChevronLeftIcon className="w-5 h-5 my-auto md:hidden" />
<span className="flex items-center gap-1.5">
{channelIcon && (
<ProfilePicture
<ProfileImage
name={channelName}
url={channelIcon}
className="size-7"
+1 -1
View File
@@ -42,7 +42,7 @@ export default function Settings({
schema={CLIENT_SCHEMA}
onSave={() => {
setClientSettings(settings);
router.refresh();
setTimeout(() => router.refresh(), 300);
}}
/>
</CardContent>
+48
View File
@@ -0,0 +1,48 @@
"use client"
import * as React from "react"
import * as PopoverPrimitive from "@radix-ui/react-popover"
import { cn } from "@/lib/utils"
function Popover({
...props
}: React.ComponentProps<typeof PopoverPrimitive.Root>) {
return <PopoverPrimitive.Root data-slot="popover" {...props} />
}
function PopoverTrigger({
...props
}: React.ComponentProps<typeof PopoverPrimitive.Trigger>) {
return <PopoverPrimitive.Trigger data-slot="popover-trigger" {...props} />
}
function PopoverContent({
className,
align = "center",
sideOffset = 4,
...props
}: React.ComponentProps<typeof PopoverPrimitive.Content>) {
return (
<PopoverPrimitive.Portal>
<PopoverPrimitive.Content
data-slot="popover-content"
align={align}
sideOffset={sideOffset}
className={cn(
"bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-72 origin-(--radix-popover-content-transform-origin) rounded-md border p-4 shadow-md outline-hidden",
className
)}
{...props}
/>
</PopoverPrimitive.Portal>
)
}
function PopoverAnchor({
...props
}: React.ComponentProps<typeof PopoverPrimitive.Anchor>) {
return <PopoverPrimitive.Anchor data-slot="popover-anchor" {...props} />
}
export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }