first commit
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
"use client";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useSdk } from "@lynkd/messaging-inbox-sdk";
|
||||
import { useUi } from "@/lib/ui-state";
|
||||
import { Avatar, Chip } from "@/components/ui/primitives";
|
||||
import { X, MessageSquare, Phone, Clock, Mail, AtSign, Smile, Pencil } from "lucide-react";
|
||||
|
||||
export function ProfileDrawer() {
|
||||
const { profileUserId, closeProfile, startHuddle, toast, openModal } = useUi();
|
||||
const { userById, me, channels, client, refresh } = useSdk();
|
||||
const router = useRouter();
|
||||
if (!profileUserId) return null;
|
||||
const user = userById(profileUserId);
|
||||
if (!user) return null;
|
||||
const isMe = user.id === me?.id;
|
||||
|
||||
async function openDm() {
|
||||
if (!user) return;
|
||||
const existing = channels.find((c) => c.kind === "dm" && c.memberIds.includes(user.id));
|
||||
closeProfile();
|
||||
if (existing) { router.push(`/c/${existing.id}`); return; }
|
||||
const { channel } = await client.createChannel({ name: user.name, kind: "dm", memberIds: [user.id] });
|
||||
refresh();
|
||||
router.push(`/c/${channel.id}`);
|
||||
}
|
||||
|
||||
async function huddle() {
|
||||
if (!user) return;
|
||||
const existing = channels.find((c) => c.kind === "dm" && c.memberIds.includes(user.id));
|
||||
closeProfile();
|
||||
if (existing) startHuddle(existing.id);
|
||||
else toast(`Starting a huddle with ${user.name}…`, "success");
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="fixed inset-0 z-40 flex justify-end bg-black/40 animate-fade-in" onClick={closeProfile}>
|
||||
<div role="dialog" aria-modal="true" aria-label="Profile" className="flex h-full w-[min(380px,100vw)] flex-col border-l border-border bg-surface shadow-pop" onClick={(e) => e.stopPropagation()}>
|
||||
<div className="flex items-center justify-between border-b border-border px-4 py-3">
|
||||
<span className="text-[15px] font-bold">Profile</span>
|
||||
<button onClick={closeProfile} className="text-muted hover:text-ink"><X size={18} /></button>
|
||||
</div>
|
||||
|
||||
<div className="min-h-0 flex-1 overflow-y-auto p-5">
|
||||
<div className="flex flex-col items-center text-center">
|
||||
<Avatar user={user} size={96} showPresence={false} rounded="24px" />
|
||||
<h2 className="mt-3 text-[20px] font-black">{user.name}</h2>
|
||||
<div className="text-[13px] text-muted">{user.title}</div>
|
||||
{user.statusText && <div className="mt-2 rounded-pill bg-hover px-3 py-1 text-[12.5px]">{user.statusEmoji} {user.statusText}</div>}
|
||||
<div className="mt-2"><Chip tone={user.presence === "active" ? "success" : user.presence === "dnd" ? "danger" : "neutral"}>{user.presence}</Chip></div>
|
||||
</div>
|
||||
|
||||
{!isMe && (
|
||||
<div className="mt-5 grid grid-cols-2 gap-2">
|
||||
<button onClick={openDm} className="focus-ring flex items-center justify-center gap-2 rounded-control bg-accent px-3 py-2 text-[13px] font-semibold text-accent-fg"><MessageSquare size={15} /> Message</button>
|
||||
<button onClick={huddle} className="focus-ring flex items-center justify-center gap-2 rounded-control border border-border bg-panel px-3 py-2 text-[13px] font-semibold hover:bg-hover"><Phone size={15} /> Huddle</button>
|
||||
</div>
|
||||
)}
|
||||
{isMe && (
|
||||
<>
|
||||
<div className="mt-5">
|
||||
<div className="mb-1.5 text-[11px] font-bold uppercase tracking-wide text-dim">Availability</div>
|
||||
<div className="grid grid-cols-3 gap-2">
|
||||
{([
|
||||
{ p: "active", label: "Active", dot: "bg-success" },
|
||||
{ p: "away", label: "Away", dot: "bg-warning" },
|
||||
{ p: "dnd", label: "Do not disturb", dot: "bg-danger" },
|
||||
] as const).map((o) => (
|
||||
<button
|
||||
key={o.p}
|
||||
onClick={async () => { await client.updateMe({ presence: o.p }); refresh(); }}
|
||||
className={`focus-ring flex items-center justify-center gap-1.5 rounded-control border px-2 py-2 text-[12px] font-semibold ${user.presence === o.p ? "border-accent bg-accent-soft text-accent" : "border-border hover:bg-hover"}`}
|
||||
>
|
||||
<span className={`h-2 w-2 rounded-full ${o.dot}`} /> {o.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3 grid grid-cols-2 gap-2">
|
||||
<button onClick={() => { closeProfile(); openModal("set-status"); }} className="focus-ring flex items-center justify-center gap-2 rounded-control border border-border bg-panel px-3 py-2 text-[13px] font-semibold hover:bg-hover">
|
||||
<Smile size={15} /> {user.statusText ? "Status" : "Set status"}
|
||||
</button>
|
||||
<button onClick={() => { closeProfile(); openModal("edit-profile"); }} className="focus-ring flex items-center justify-center gap-2 rounded-control border border-border bg-panel px-3 py-2 text-[13px] font-semibold hover:bg-hover">
|
||||
<Pencil size={15} /> Edit profile
|
||||
</button>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div className="mt-6 space-y-3 text-[13px]">
|
||||
<Field icon={AtSign} label="Handle" value={`@${user.handle}`} />
|
||||
{user.email && <Field icon={Mail} label="Email" value={user.email} />}
|
||||
{user.timezone && <Field icon={Clock} label="Local time" value={`${user.localTime ?? ""} · ${user.timezone}`} />}
|
||||
{user.pronouns && <Field icon={AtSign} label="Pronouns" value={user.pronouns} />}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Field({ icon: Icon, label, value }: { icon: typeof Mail; label: string; value: string }) {
|
||||
return (
|
||||
<div className="flex items-center gap-3 rounded-control border border-border bg-panel px-3 py-2.5">
|
||||
<Icon size={16} className="text-muted" />
|
||||
<div><div className="text-[11px] uppercase tracking-wide text-dim">{label}</div><div className="text-ink">{value}</div></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user