Merge branch 'main' of github.com:flagon-io/g1t
3 files+76−250/3 viewed
| 1 | 1 | --- | |
| 2 | − | // The header's right side: where people go from the docs, then g1t on X and the source. | |
| 2 | + | // The header's right side: where people go from the docs, then the source. | |
| 3 | + | // g1t's social accounts live in g1t.sh's footer, not here. | |
| 3 | 4 | import { GitBranch } from '@lucide/astro'; | |
| 4 | 5 | --- | |
| 5 | 6 | ||
| 6 | 7 | <nav class="g1t-nav" aria-label="g1t"> | |
| 7 | 8 | <a href="https://g1t.sh/">g1t.sh</a> | |
| 8 | 9 | <a href="https://g1t.sh/register" class="g1t-nav-cta">Sign up</a> | |
| 9 | − | <a href="https://x.com/g1t_sh" aria-label="g1t on X" title="g1t on X" class="g1t-nav-icon"> | |
| 10 | − | <svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true"><path d="M14.234 10.162 22.977 0h-2.072l-7.591 8.824L7.251 0H.258l9.168 13.343L.258 24H2.33l8.016-9.318L16.749 24h6.993zm-2.837 3.299-.929-1.329L3.076 1.56h3.182l5.965 8.532.929 1.329 7.754 11.09h-3.182z" /></svg> | |
| 11 | − | </a> | |
| 12 | 10 | <a href="https://g1t.sh/flagon-io/g1t" aria-label="Source on g1t" title="Source on g1t" class="g1t-nav-icon"> | |
| 13 | 11 | <GitBranch /> | |
| 14 | 12 | </a> |
| 1 | 1 | /** | |
| 2 | − | * The site's footer: a few links worth having, who makes g1t, and a row of | |
| 2 | + | * The site's footer: the logo, what g1t is and its accounts elsewhere as | |
| 3 | + | * icons beside a few links worth having, then who makes g1t, and a row of | |
| 3 | 4 | * the trust pages with the live status dot, the account, and the legal | |
| 4 | 5 | * line. Marketing and signed-out pages show it; the app's account menu | |
| 5 | 6 | * has its own rows (components/shell.tsx). | |
| 6 | 7 | */ | |
| 8 | + | import { GitBranch } from "lucide-react"; | |
| 7 | 9 | import { useEffect, useState } from "react"; | |
| 8 | 10 | import { Link } from "react-router"; | |
| 9 | 11 | ||
| 10 | 12 | import type { User } from "@g1t/contracts"; | |
| 11 | 13 | ||
| 12 | 14 | import { useAsksFirst } from "./analytics-consent"; | |
| 13 | − | import { Mark } from "./logo"; | |
| 15 | + | import { Logo, Mark } from "./logo"; | |
| 16 | + | import { Button } from "./ui/button"; | |
| 17 | + | import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip"; | |
| 14 | 18 | import { choose } from "../lib/analytics-consent"; | |
| 15 | 19 | import { COMPANY, MAKER_PRODUCTS, SOCIAL, copyright, listed } from "../lib/legal"; | |
| 16 | 20 | import { type OverallState, STATUS_JSON_URL, STATUS_URL, STATUS_WORDS, type StatusReport, dotClass } from "../lib/status"; | |
| ⋯ | |||
| 97 | 101 | title: "Company", | |
| 98 | 102 | links: [ | |
| 99 | 103 | ["Flagon, Inc.", COMPANY.url], | |
| 100 | − | ...SOCIAL.map((account): [string, string] => [account.label, account.url]), | |
| 101 | 104 | ["Support", "/support"], | |
| 102 | 105 | ["Security", "/security"], | |
| 103 | 106 | ["Status", STATUS_URL], | |
| ⋯ | |||
| 113 | 116 | }, | |
| 114 | 117 | ]; | |
| 115 | 118 | ||
| 119 | + | /** What the footer says under the logo. */ | |
| 120 | + | const TAGLINE = "Your team and its agents, working in one place."; | |
| 121 | + | ||
| 122 | + | /** The icons under it: g1t's accounts elsewhere (lib/legal), then its source. */ | |
| 123 | + | const ICON_LINKS: { label: string; url: string; icon: React.ReactNode }[] = [ | |
| 124 | + | ...SOCIAL.map((account) => ({ | |
| 125 | + | label: `g1t on ${account.label}`, | |
| 126 | + | url: account.url, | |
| 127 | + | icon: ( | |
| 128 | + | <svg viewBox="0 0 24 24" aria-hidden="true" fill="currentColor" className="size-4"> | |
| 129 | + | <path d={account.icon.path} /> | |
| 130 | + | </svg> | |
| 131 | + | ), | |
| 132 | + | })), | |
| 133 | + | { label: "Source on g1t", url: "/flagon-io/g1t", icon: <GitBranch aria-hidden className="size-4" /> }, | |
| 134 | + | ]; | |
| 135 | + | ||
| 136 | + | /** The footer's first column: the logo, the tagline and the icons. */ | |
| 137 | + | function BrandColumn() { | |
| 138 | + | return ( | |
| 139 | + | <div className="col-span-2 sm:col-span-4 lg:col-span-2 lg:pr-8"> | |
| 140 | + | <Link to="/" aria-label="g1t home" className="inline-flex rounded-sm"> | |
| 141 | + | <Logo /> | |
| 142 | + | </Link> | |
| 143 | + | <p className="mt-3 max-w-xs text-sm leading-6 text-muted text-balance">{TAGLINE}</p> | |
| 144 | + | <ul className="mt-4 -ml-2 flex items-center gap-1"> | |
| 145 | + | {ICON_LINKS.map((link) => ( | |
| 146 | + | <li key={link.url}> | |
| 147 | + | <Tooltip> | |
| 148 | + | <TooltipTrigger asChild> | |
| 149 | + | <Button asChild variant="ghost" size="icon-sm"> | |
| 150 | + | <a href={link.url} aria-label={link.label}> | |
| 151 | + | {link.icon} | |
| 152 | + | </a> | |
| 153 | + | </Button> | |
| 154 | + | </TooltipTrigger> | |
| 155 | + | <TooltipContent>{link.label}</TooltipContent> | |
| 156 | + | </Tooltip> | |
| 157 | + | </li> | |
| 158 | + | ))} | |
| 159 | + | </ul> | |
| 160 | + | </div> | |
| 161 | + | ); | |
| 162 | + | } | |
| 163 | + | ||
| 116 | 164 | /** The row's own links, after Status. */ | |
| 117 | 165 | const ROW_LINKS: [string, string][] = [ | |
| 118 | 166 | ["Support", "/support"], | |
| ⋯ | |||
| 249 | 297 | export function SiteFooter({ user }: { user: User | null | undefined }) { | |
| 250 | 298 | return ( | |
| 251 | 299 | <footer className="mt-24 border-t border-line"> | |
| 252 | − | <nav aria-label="Footer" className="mx-auto grid max-w-6xl grid-cols-2 gap-x-8 gap-y-8 px-4 pt-12 pb-10 sm:grid-cols-4"> | |
| 253 | − | {FOOTER_LINKS.map((group) => ( | |
| 254 | − | <div key={group.title}> | |
| 255 | − | <p className="text-sm font-medium">{group.title}</p> | |
| 256 | − | <ul className="mt-3 space-y-2 text-sm text-muted"> | |
| 257 | − | {group.links.map(([label, to]) => ( | |
| 258 | − | <li key={to}> | |
| 259 | − | <a href={to} className="transition-colors hover:text-fg"> | |
| 260 | − | {label} | |
| 261 | − | </a> | |
| 262 | − | </li> | |
| 263 | − | ))} | |
| 264 | − | </ul> | |
| 265 | − | </div> | |
| 266 | − | ))} | |
| 267 | − | </nav> | |
| 300 | + | <div className="mx-auto grid max-w-6xl grid-cols-2 gap-x-8 gap-y-10 px-4 pt-12 pb-10 sm:grid-cols-4 lg:grid-cols-6"> | |
| 301 | + | <BrandColumn /> | |
| 302 | + | {/* The link columns sit in the same grid as the brand column. */} | |
| 303 | + | <nav aria-label="Footer" className="contents"> | |
| 304 | + | {FOOTER_LINKS.map((group) => ( | |
| 305 | + | <div key={group.title}> | |
| 306 | + | <p className="text-sm font-medium">{group.title}</p> | |
| 307 | + | <ul className="mt-3 space-y-2 text-sm text-muted"> | |
| 308 | + | {group.links.map(([label, to]) => ( | |
| 309 | + | <li key={to}> | |
| 310 | + | <a href={to} className="transition-colors hover:text-fg"> | |
| 311 | + | {label} | |
| 312 | + | </a> | |
| 313 | + | </li> | |
| 314 | + | ))} | |
| 315 | + | </ul> | |
| 316 | + | </div> | |
| 317 | + | ))} | |
| 318 | + | </nav> | |
| 319 | + | </div> | |
| 268 | 320 | <div className="border-t border-line"> | |
| 269 | 321 | <div className="mx-auto max-w-3xl space-y-4 px-4 py-10 text-center"> | |
| 270 | 322 | <MakerLine /> | |
| 3 | 3 | * one place the footer, the policy pages, /security, /support and | |
| 4 | 4 | * security.txt read these from. | |
| 5 | 5 | */ | |
| 6 | + | import { type SimpleIcon, siX } from "simple-icons"; | |
| 6 | 7 | ||
| 7 | 8 | /** The company behind g1t. */ | |
| 8 | 9 | export const COMPANY = { | |
| ⋯ | |||
| 14 | 15 | founder: "Chase Pierce", | |
| 15 | 16 | }; | |
| 16 | 17 | ||
| 17 | − | /** g1t's own accounts elsewhere. */ | |
| 18 | − | export const SOCIAL: { label: string; url: string }[] = [{ label: "X", url: "https://x.com/g1t_sh" }]; | |
| 18 | + | /** g1t's own accounts elsewhere, drawn as their marks in the footer. */ | |
| 19 | + | export const SOCIAL: { label: string; url: string; icon: SimpleIcon }[] = [{ label: "X", url: "https://x.com/g1t_sh", icon: siX }]; | |
| 19 | 20 | ||
| 20 | 21 | /** | |
| 21 | 22 | * Flagon's other launched products, which the footer's maker line names | |