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.

172 linesCodeBlameRaw
1import { ArrowRight, Check, Sparkles } from "lucide-react";
2import { Link, data, redirect } from "react-router";
3
4import type { Route } from "./+types/soon";
5import { page } from "../../lib/meta";
6import { ROADMAP, type RoadmapItem, roadmapIn, roadmapItem } from "../../lib/roadmap";
7import { Badge } from "../../components/ui/badge";
8import { Card } from "../../components/ui/card";
9
10export function meta({ params, ...args }: Route.MetaArgs) {
11 const item = roadmapItem(params.feature);
12 return page(args, {
13 title: `${item?.title ?? "Soon"} · ${params.owner}/${params.repo} · g1t`,
14 description: item ? `Soon on g1t: ${item.summary}` : null,
15 version: item ? [item.title, item.summary] : undefined,
16 });
17}
18
19/** Soon pages that have since been built, and where each is now. */
20const BUILT: Record<string, string> = {
21 environments: "settings/environments",
22 releases: "releases",
23 logs: "observability/logs",
24 errors: "observability/errors",
25};
26
27export function loader({ params }: Route.LoaderArgs) {
28 const built = BUILT[params.feature];
29 if (built) throw redirect(`/${params.owner}/${params.repo}/${built}`);
30 const item = roadmapItem(params.feature);
31 if (!item) throw data(null, { status: 404 });
32 return { item };
33}
34
35/**
36 * A page a project will have: what it is for, what it will do, and what
37 * to use until then. Reached from the project menu's Soon items.
38 */
39export default function Soon({ loaderData, params }: Route.ComponentProps) {
40 return <SoonView item={loaderData.item} base={`/${params.owner}/${params.repo}`} />;
41}
42
43/**
44 * A Soon page, for a project's (under `base`, `/<owner>/<project>`) or the
45 * workspace's (`/<owner>/-`).
46 */
47/**
48 * What is coming, and what to use until then. In a project, "until then"
49 * is a page of that project. Across a workspace it lives in each project,
50 * so `inProjects` lists them, or offers to make the first one.
51 */
52export function SoonView({
53 item,
54 base,
55 inProjects,
56 newProject,
57}: {
58 item: RoadmapItem;
59 base: string;
60 inProjects?: { name: string; to: string }[];
61 newProject?: string;
62}) {
63 const siblings = roadmapIn(item.section).filter((other) => other.key !== item.key);
64 const inWorkspace = item.section === "Workspace";
65 return (
66 <div className="max-w-3xl">
67 {/* A project's tabs already say where this is; the workspace's need saying. */}
68 {inWorkspace && (
69 <p className="mb-4 flex items-center gap-2 text-sm text-muted">
70 <span>Across projects</span>
71 <span className="text-line-strong">/</span>
72 <span className="text-fg">{item.title}</span>
73 </p>
74 )}
75 <div className="flex flex-wrap items-center gap-3">
76 <h1 className="text-3xl font-semibold tracking-tight">{item.title}</h1>
77 <Badge tone="accent" size="md" className="gap-1.5 px-2.5 font-medium">
78 <Sparkles size={12} />
79 Soon
80 </Badge>
81 </div>
82 <p className="mt-3 text-lg text-muted">{item.summary}</p>
83
84 <Card asChild className="mt-8 p-6">
85 <section>
86 <p className="leading-relaxed">{item.why}</p>
87 <h2 className="mt-6 text-sm font-medium text-muted">What it will do</h2>
88 <ul className="mt-3 space-y-2.5">
89 {item.plans.map((plan) => (
90 <li key={plan} className="flex gap-3 text-sm">
91 <Check size={16} className="mt-0.5 shrink-0 text-accent" />
92 <span>{plan}</span>
93 </li>
94 ))}
95 </ul>
96 </section>
97 </Card>
98
99 {item.today && inProjects && (
100 <Card asChild tone="plain" className="mt-4 px-5 py-4">
101 <section>
102 <span className="block text-xs text-faint">Until then: {item.today.label.toLowerCase()} in each project</span>
103 {inProjects.length > 0 ? (
104 <ul className="mt-2 divide-y divide-line">
105 {inProjects.map((project) => (
106 <li key={project.to}>
107 <Link to={`${project.to}/${item.today!.path}`} className="group flex items-center justify-between gap-4 py-2.5 text-sm">
108 <span className="font-medium">
109 {item.today!.label} in {project.name}
110 </span>
111 <ArrowRight size={16} className="text-faint transition-transform group-hover:translate-x-0.5 group-hover:text-fg" />
112 </Link>
113 </li>
114 ))}
115 </ul>
116 ) : (
117 <Link to={newProject ?? "/new"} className="group mt-2 flex items-center justify-between gap-4 text-sm">
118 <span>
119 <span className="font-medium">Create a project</span>
120 <span className="text-muted"> to plan {item.today.label.toLowerCase()} in it.</span>
121 </span>
122 <ArrowRight size={16} className="text-faint transition-transform group-hover:translate-x-0.5 group-hover:text-fg" />
123 </Link>
124 )}
125 </section>
126 </Card>
127 )}
128
129 {item.today && !inProjects && (
130 <Link
131 to={`${base}/${item.today.path}`}
132 className="group mt-4 flex items-center justify-between gap-4 rounded-xl border border-line px-5 py-4 transition-colors hover:border-line-strong hover:bg-surface"
133 >
134 <span>
135 <span className="block text-xs text-faint">Until then</span>
136 <span className="mt-0.5 block text-sm font-medium">{item.today.label}</span>
137 </span>
138 <ArrowRight size={16} className="text-faint transition-transform group-hover:translate-x-0.5 group-hover:text-fg" />
139 </Link>
140 )}
141
142 {siblings.length > 0 && (
143 <section className="mt-10">
144 <h2 className="text-sm font-medium text-muted">
145 Also coming {inWorkspace ? "across projects" : item.section === "Project" ? "to every project" : `to ${item.section}`}
146 </h2>
147 <ul className="mt-3 grid gap-3 sm:grid-cols-2">
148 {siblings.map((other) => (
149 <li key={other.key}>
150 <Link
151 to={`${base}/soon/${other.key}`}
152 className="block h-full rounded-xl border border-line p-4 transition-colors hover:border-line-strong hover:bg-surface"
153 >
154 <span className="text-sm font-medium">{other.title}</span>
155 <span className="mt-1 block text-sm text-muted">{other.summary}</span>
156 </Link>
157 </li>
158 ))}
159 </ul>
160 </section>
161 )}
162
163 <p className="mt-10 text-xs text-faint">
164 {ROADMAP.length} things are on the way to every project. Want this one sooner, or something else?{" "}
165 <a href="https://g1t.sh/flagon-io/-/work/new?repo=g1t" className="text-muted hover:text-fg">
166 Tell us
167 </a>
168 .
169 </p>
170 </div>
171 );
172}