pr_01m47d15m3e54sn21z27rpy5n9/apps/web/app/routes/repo/soon.tsx

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