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.

Commit

Move production actions into the project card header

syntaqxcommitted Parent0c9deaeBrowse files
3 files+71−480/3 viewed
+5−0
5757 of it; anyone else sees what the project shares publicly: production, its
5858 checks, who is working on it, its deployments and About.
5959
60+The production card's header keeps **Visit**, deployment history, and
61+deployment settings together, with management controls shown according to
62+your permissions. Hosted projects also offer **Redeploy** there. **Instant
63+rollback** is marked **Soon** and disabled; it does not change production.
64+
6065 | Part | What it shows |
6166 | --- | --- |
6267 | **What it is** | A badge, such as **App, deploys itself** or **Library**, beside its homepage and docs. People who can change its settings open the badge to change [what it is](#what-a-project-is) in one click. |
+0−8
208208 )}
209209 <DeployQuietly base={base} canDeploy={canDeploy} what="it" />
210210 </div>
211− {url && (
212− <div className="flex shrink-0 items-center gap-2">
213− <ButtonLink to={url} variant="accent" reloadDocument target="_blank" rel="noopener noreferrer">
214− Visit
215− <ArrowUpRight size={14} />
216− </ButtonLink>
217− </div>
218− )}
219211 </div>
220212 </div>
221213 );
+66−40
1212 Radio,
1313 Rocket,
1414 RotateCw,
15+ RotateCcw,
16+ Settings2,
1517 } from "lucide-react";
1618 import { type ReactNode, Suspense } from "react";
1719 import { Await, Form, Link } from "react-router";
2830 import { ProductionShot } from "../../components/production-shot";
2931 import { GithubLinkStrip } from "../../components/github";
3032 import { githubApp } from "../../lib/github.server";
31−import { ButtonLink, CopyLine, SubmitButton, TimeAgo } from "../../components/ui";
33+import { ButtonLink, CopyLine, SoonPill, SubmitButton, TimeAgo } from "../../components/ui";
3234 import { Card } from "../../components/ui/card";
35+import { Button } from "../../components/ui/button";
3336 import { type LivePull, LiveNowList, StartHere } from "../../components/coordination";
3437 import { awakeAndIdle, liveByIssue } from "../../lib/coordination";
3538 import { type Need, ageBuckets, rankNeeds, runHealth } from "../../lib/mission";
644647 );
645648 }
646649
647−/** Production on g1t.page: its picture, address and state, and for members, Visit and Redeploy. */
650+/** Production on g1t.page: its picture, address and state. Actions live in the card header. */
648651 function ProductionHead({ loaderData, base, name, upload, addImage }: {
649652 loaderData: Loaded;
650653 base: string;
652655 upload: { url: string; preferred: boolean } | null;
653656 addImage: string | null;
654657 }) {
655− const { member, settings, production, latestProductionBuild, productionLatest, can } = loaderData;
658+ const { member, settings, production, latestProductionBuild, productionLatest } = loaderData;
656659 // The project's own domain, once it is active, is where production is visited.
657660 const productionUrl = production ? (settings?.primaryDomain ? `https://${settings.primaryDomain}` : production.url) : null;
658661 return (
723726 </>
724727 )}
725728 </div>
726− {member && (
727− <div className="flex shrink-0 items-center gap-2">
728− {productionUrl && (
729− <ButtonLink to={productionUrl} variant="accent" reloadDocument target="_blank" rel="noopener noreferrer">
730− Visit
731− <ArrowUpRight size={14} />
732− </ButtonLink>
733− )}
734− {settings?.enabled
735− ? can.run && (
736− <Form method="post">
737− <Hint label="Build production again from the default branch">
738− <SubmitButton variant="outline" name="intent" value="redeploy" pending="Redeploying…">
739− <RotateCw size={14} />
740− Redeploy
741− </SubmitButton>
742− </Hint>
743− </Form>
744− )
745− : can.manage_integrations && (
746− <ButtonLink to={`${base}/settings/deployments`} variant="accent">
747− <Rocket size={14} />
748− Turn on deployments
749− </ButtonLink>
750− )}
751− </div>
752− )}
753− {!member && productionUrl && (
754− <ButtonLink to={productionUrl} variant="accent" reloadDocument target="_blank" rel="noopener noreferrer" className="shrink-0">
755− Visit
756− <ArrowUpRight size={14} />
729+ </div>
730+ </div>
731+ );
732+}
733+
734+/** Production controls share a header, whether g1t or a workflow hosts it. */
735+function ProductionActions({ loaderData, base, elsewhere }: { loaderData: Loaded; base: string; elsewhere: boolean }) {
736+ const { member, settings, production, project, can } = loaderData;
737+ const url = elsewhere
738+ ? loaderData.deployment?.url ?? project?.productionUrl
739+ : production ? (settings?.primaryDomain ? `https://${settings.primaryDomain}` : production.url) : null;
740+ return (
741+ <div className="flex flex-wrap items-center gap-2">
742+ {member && (
743+ <Hint label="Deployment history">
744+ <ButtonLink to={`${base}/deployments`} variant="outline" size="icon-sm" aria-label="Deployment history">
745+ <Rocket size={15} />
746+ </ButtonLink>
747+ </Hint>
748+ )}
749+ {member && (can.run || can.manage_integrations) && (
750+ <Hint disabled label="Instant rollback is coming soon. It is not available yet.">
751+ <Button disabled variant="outline" size="sm">
752+ <RotateCcw size={14} />
753+ Instant rollback <SoonPill />
754+ </Button>
755+ </Hint>
756+ )}
757+ {!elsewhere && member && settings?.enabled && can.run && (
758+ <Form method="post">
759+ <Hint label="Build production again from the default branch">
760+ <SubmitButton variant="outline" size="sm" name="intent" value="redeploy" pending="Redeploying…">
761+ <RotateCw size={14} /> Redeploy
762+ </SubmitButton>
763+ </Hint>
764+ </Form>
765+ )}
766+ {!elsewhere && member && !settings?.enabled && can.manage_integrations && (
767+ <ButtonLink to={`${base}/settings/deployments`} variant="accent" size="sm">
768+ <Rocket size={14} /> Turn on deployments
769+ </ButtonLink>
770+ )}
771+ {member && can.manage_integrations && (
772+ <Hint label="Deployment settings">
773+ <ButtonLink to={`${base}/settings/deployments`} variant="outline" size="icon-sm" aria-label="Deployment settings">
774+ <Settings2 size={15} />
757775 </ButtonLink>
758− )}
759− </div>
776+ </Hint>
777+ )}
778+ {url && (
779+ <ButtonLink to={url} variant="accent" size="sm" reloadDocument target="_blank" rel="noopener noreferrer">
780+ Visit <ArrowUpRight size={14} />
781+ </ButtonLink>
782+ )}
760783 </div>
761784 );
762785 }
801824 {/* What the project is, whether it is running and healthy, and a line to its code. */}
802825 <Card asChild className="overflow-hidden rounded-2xl">
803826 <section aria-label="Production">
804− {project && (
805− <div className="flex flex-wrap items-center gap-x-4 gap-y-2 border-b border-line px-5 py-2.5 text-xs sm:px-6">
806− <KindMenu project={project} label={kindLabel(project)} canChange={canChange} />
807−
827+ {(project || plan === "production") && (
828+ <div className="flex flex-wrap items-center justify-between gap-3 border-b border-line px-5 py-3 text-xs sm:px-6">
829+ <div className="flex flex-wrap items-center gap-3">
830+ {(plan === "production" || plan === "elsewhere") && <h2 className="text-sm font-semibold">Production deployment</h2>}
831+ {project && <KindMenu project={project} label={kindLabel(project)} canChange={canChange} />}
832+ </div>
833+ {(plan === "production" || plan === "elsewhere") && <ProductionActions loaderData={loaderData} base={base} elsewhere={plan === "elsewhere"} />}
808834 </div>
809835 )}
810836 {plan === "elsewhere" && project ? (

Comments

Sign in to comment.