| 1 | /** |
| 2 | * Pieces of People's pages: a person's face with their presence, |
| 3 | * someone's local time, and a label for what is coming. |
| 4 | */ |
| 5 | import { useEffect, useState } from "react"; |
| 6 | import { Link } from "react-router"; |
| 7 | |
| 8 | import type { DirectoryPerson } from "@g1t/contracts"; |
| 9 | |
| 10 | import { WithPresence } from "./presence"; |
| 11 | |
| 12 | import { Avatar } from "./ui/avatar"; |
| 13 | import { Badge } from "./ui/badge"; |
| 14 | import { cn } from "../lib/cn"; |
| 15 | import { personName, personPath } from "../lib/people"; |
| 16 | import { localTime } from "../lib/time-zone"; |
| 17 | |
| 18 | /** A person's avatar, with their presence in its corner. */ |
| 19 | export function PersonFace({ person, size = 28, ring }: { person: Pick<DirectoryPerson, "user_id" | "username" | "avatar">; size?: number; ring?: string }) { |
| 20 | return ( |
| 21 | <WithPresence person={{ id: person.user_id, username: person.username }} size={size} ring={ring}> |
| 22 | <Avatar name={person.username} image={person.avatar} size={size} /> |
| 23 | </WithPresence> |
| 24 | ); |
| 25 | } |
| 26 | |
| 27 | /** |
| 28 | * Someone's local time, worked out in the browser (the server's clock is |
| 29 | * not theirs), and kept current. Nothing when they gave no time zone. |
| 30 | */ |
| 31 | export function LocalTime({ zone, className, suffix = "local" }: { zone: string | null; className?: string; suffix?: string }) { |
| 32 | const [now, setNow] = useState<number | null>(null); |
| 33 | useEffect(() => { |
| 34 | setNow(Date.now()); |
| 35 | const timer = setInterval(() => setNow(Date.now()), 30_000); |
| 36 | return () => clearInterval(timer); |
| 37 | }, []); |
| 38 | if (!zone || now == null) return null; |
| 39 | const time = localTime(zone, now); |
| 40 | if (!time) return null; |
| 41 | return ( |
| 42 | <span className={className}> |
| 43 | {time} {suffix} |
| 44 | </span> |
| 45 | ); |
| 46 | } |
| 47 | |
| 48 | /** Something that is planned and not here yet. */ |
| 49 | export function Coming({ children = "Coming" }: { children?: string }) { |
| 50 | return <Badge className="border-dashed">{children}</Badge>; |
| 51 | } |
| 52 | |
| 53 | /** A person's name, linked to their profile. */ |
| 54 | export function PersonLink({ workspace, person, className }: { workspace: string; person: Pick<DirectoryPerson, "username" | "name" | "display_username">; className?: string }) { |
| 55 | return ( |
| 56 | <Link to={personPath(workspace, person.username)} prefetch="intent" className={cn("hover:text-accent", className)}> |
| 57 | {personName(person)} |
| 58 | </Link> |
| 59 | ); |
| 60 | } |