Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
60 linesCodeBlameRaw
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 */
5import { useEffect, useState } from "react";
6import { Link } from "react-router";
7
8import type { DirectoryPerson } from "@g1t/contracts";
9
10import { WithPresence } from "./presence";
11
12import { Avatar } from "./ui/avatar";
13import { Badge } from "./ui/badge";
14import { cn } from "../lib/cn";
15import { personName, personPath } from "../lib/people";
16import { localTime } from "../lib/time-zone";
17
18/** A person's avatar, with their presence in its corner. */
19export 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 */
31export 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. */
49export 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. */
54export 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}