V3 style improvements (#926)
* Added more clipboard variants to storybook and improved styling * Improvements to the clipboard components * Code blocks match the design * Improved the code block styles * Dialog modal improvements * Improved the free plan usage bar * Better alignment of the shortcut icon in the button * Side menu displays ‘v3’ next to the project header * Page header improvements * Project dropdown displays a badge if it’s a v3 project * billing page now shows message * Added the v3 badge to the project list page * version 3 projects skip the select plan onboarding * Added a new blank state to the runs page * Conditionally set the blank state on the runs page * Button padding tweak * Updated the task and attempt icons * Added a new blank state to the runs page * Added more buttons variants to the button storybook * Added new blank onboarding state for the tasks page * Added a blank state to the test page * Fix for bad svg attribute in TaskIcon * Onboarding tweaks * Add the projectRef to the init command * Task icon is blue * Added a v3 Project settings page * Fix for typecheck error * Added the new CodeBlocks with rowTitles to the span panel --------- Co-authored-by: Matt Aitken <matt@mattaitken.com>
This commit is contained in:
@@ -1,12 +1,29 @@
|
||||
export function AttemptIcon({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg className={className} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M9.0711 0.585818C8.29001 -0.195272 7.02361 -0.195273 6.24252 0.585818L0.585818 6.24252C-0.195272 7.02361 -0.195273 8.29001 0.585818 9.0711L6.24252 14.7278C7.02361 15.5089 8.29001 15.5089 9.0711 14.7278L14.7278 9.0711C15.5089 8.29001 15.5089 7.02361 14.7278 6.24252L9.0711 0.585818ZM6.85833 4.07179L4.29833 11.1718H5.65833L6.24833 9.49179H9.05833L9.64833 11.1718H11.0083L8.44833 4.07179H6.85833ZM8.67833 8.38179H6.62833L7.65833 5.38179L8.67833 8.38179Z"
|
||||
/>
|
||||
<svg
|
||||
className={className}
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g clipPath="url(#clip0_9964_113464)">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M16 0H0V16H16V0ZM7.09906 4.4L4.53906 11.5H6.11906L6.63906 10H9.35906L9.87906 11.5H11.4591L8.89906 4.4H7.09906ZM7.99906 6L8.92906 8.73H7.06906L7.99906 6Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_9964_113464">
|
||||
<path
|
||||
d="M0 2C0 0.895431 0.895431 0 2 0H14C15.1046 0 16 0.895431 16 2V14C16 15.1046 15.1046 16 14 16H2C0.895431 16 0 15.1046 0 14V2Z"
|
||||
fill="white"
|
||||
/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,22 +1,23 @@
|
||||
export function TaskIcon({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg className={className} viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M9.0711 0.585818C8.29001 -0.195272 7.02361 -0.195273 6.24252 0.585818L0.585818 6.24252C-0.195272 7.02361 -0.195273 8.29001 0.585818 9.0711L6.24252 14.7278C7.02361 15.5089 8.29001 15.5089 9.0711 14.7278L14.7278 9.0711C15.5089 8.29001 15.5089 7.02361 14.7278 6.24252L9.0711 0.585818ZM6.85833 4.07179L4.29833 11.1718H5.65833L6.24833 9.49179H9.05833L9.64833 11.1718H11.0083L8.44833 4.07179H6.85833ZM8.67833 8.38179H6.62833L7.65833 5.38179L8.67833 8.38179Z"
|
||||
/>
|
||||
|
||||
<g clipPath="url(#clip0_8651_301570)">
|
||||
<svg
|
||||
className={className}
|
||||
width="16"
|
||||
height="16"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g clipPath="url(#clip0_9221_99732)">
|
||||
<path
|
||||
fill="currentColor"
|
||||
fillRule="evenodd"
|
||||
clipRule="evenodd"
|
||||
d="M16 0H0V16H16V0ZM10.8099 6.05039V4.90039H5.18994V6.05039H7.33994V12.0004H8.65994V6.05039H10.8099Z"
|
||||
d="M16 0H0V16H16V0ZM10.8901 5.73995V4.44995H5.11011V5.73995H7.23011V11.55H8.77011V5.73995H10.8901Z"
|
||||
fill="currentColor"
|
||||
/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_8651_301570">
|
||||
<clipPath id="clip0_9221_99732">
|
||||
<path
|
||||
d="M0 2C0 0.895431 0.895431 0 2 0H14C15.1046 0 16 0.895431 16 2V14C16 15.1046 15.1046 16 14 16H2C0.895431 16 0 15.1046 0 14V2Z"
|
||||
fill="white"
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { cn } from "~/utils/cn";
|
||||
import { Header2 } from "./primitives/Headers";
|
||||
|
||||
export function BlankstateInstructions({
|
||||
children,
|
||||
className,
|
||||
title,
|
||||
}: {
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
title?: string;
|
||||
}) {
|
||||
return (
|
||||
<div className={cn("flex flex-col gap-2", className)}>
|
||||
{title && (
|
||||
<div className="flex h-10 items-center border-b border-grid-bright">
|
||||
<Header2>{title}</Header2>
|
||||
</div>
|
||||
)}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { useProject } from "~/hooks/useProject";
|
||||
import { InlineCode } from "./code/InlineCode";
|
||||
import {
|
||||
ClientTabs,
|
||||
@@ -128,3 +129,79 @@ export function TriggerDevStep({ extra }: { extra?: string }) {
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
// Trigger.dev version 3 setup commands
|
||||
|
||||
export function InitCommandV3() {
|
||||
const project = useProject();
|
||||
const projectRef = `proj_${project.ref}`;
|
||||
return (
|
||||
<ClientTabs defaultValue="npm">
|
||||
<ClientTabsList>
|
||||
<ClientTabsTrigger value={"npm"}>npm</ClientTabsTrigger>
|
||||
<ClientTabsTrigger value={"pnpm"}>pnpm</ClientTabsTrigger>
|
||||
<ClientTabsTrigger value={"yarn"}>yarn</ClientTabsTrigger>
|
||||
</ClientTabsList>
|
||||
<ClientTabsContent value={"npm"}>
|
||||
<ClipboardField
|
||||
variant="primary/medium"
|
||||
iconButton
|
||||
className="mb-4"
|
||||
value={`npx trigger.dev@latest init -p ${projectRef}`}
|
||||
/>
|
||||
</ClientTabsContent>
|
||||
<ClientTabsContent value={"pnpm"}>
|
||||
<ClipboardField
|
||||
variant="primary/medium"
|
||||
iconButton
|
||||
className="mb-4"
|
||||
value={`pnpm dlx trigger.dev@latest init -p ${projectRef}`}
|
||||
/>
|
||||
</ClientTabsContent>
|
||||
<ClientTabsContent value={"yarn"}>
|
||||
<ClipboardField
|
||||
variant="primary/medium"
|
||||
iconButton
|
||||
className="mb-4"
|
||||
value={`yarn dlx trigger.dev@latest init -p ${projectRef}`}
|
||||
/>
|
||||
</ClientTabsContent>
|
||||
</ClientTabs>
|
||||
);
|
||||
}
|
||||
|
||||
export function TriggerDevStepV3() {
|
||||
return (
|
||||
<ClientTabs defaultValue="npm">
|
||||
<ClientTabsList>
|
||||
<ClientTabsTrigger value={"npm"}>npm</ClientTabsTrigger>
|
||||
<ClientTabsTrigger value={"pnpm"}>pnpm</ClientTabsTrigger>
|
||||
<ClientTabsTrigger value={"yarn"}>yarn</ClientTabsTrigger>
|
||||
</ClientTabsList>
|
||||
<ClientTabsContent value={"npm"}>
|
||||
<ClipboardField
|
||||
variant="primary/medium"
|
||||
iconButton
|
||||
className="mb-4"
|
||||
value={`npx trigger.dev@latest dev`}
|
||||
/>
|
||||
</ClientTabsContent>
|
||||
<ClientTabsContent value={"pnpm"}>
|
||||
<ClipboardField
|
||||
variant="primary/medium"
|
||||
iconButton
|
||||
className="mb-4"
|
||||
value={`pnpm dlx trigger.dev@latest dev`}
|
||||
/>
|
||||
</ClientTabsContent>
|
||||
<ClientTabsContent value={"yarn"}>
|
||||
<ClipboardField
|
||||
variant="primary/medium"
|
||||
iconButton
|
||||
className="mb-4"
|
||||
value={`yarn dlx trigger.dev@latest dev`}
|
||||
/>
|
||||
</ClientTabsContent>
|
||||
</ClientTabs>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ export function FreePlanUsage({ to, percentage }: { to: string; percentage: numb
|
||||
<div
|
||||
className={cn(
|
||||
"rounded border border-charcoal-700 bg-charcoal-750 p-2.5",
|
||||
hasHitLimit && "border-rose-800/60"
|
||||
hasHitLimit && "border-error/40"
|
||||
)}
|
||||
>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
@@ -27,11 +27,11 @@ export function FreePlanUsage({ to, percentage }: { to: string; percentage: numb
|
||||
<ArrowUpCircleIcon className="h-5 w-5 text-text-dimmed" />
|
||||
<Paragraph className="text-2sm text-text-bright">Free Plan</Paragraph>
|
||||
</div>
|
||||
<Link to={to} className="text-2sm text-indigo-500">
|
||||
<Link to={to} className="text-2sm text-text-link">
|
||||
Learn more
|
||||
</Link>
|
||||
</div>
|
||||
<div className="relative mt-3 h-1 rounded-full bg-[#0B1018]">
|
||||
<div className="relative mt-3 h-1 rounded-full bg-background-dimmed">
|
||||
<motion.div
|
||||
initial={{ width: 0 }}
|
||||
animate={{ width: cappedPercentage * 100 + "%" }}
|
||||
|
||||
@@ -4,6 +4,7 @@ import Highlight, { defaultProps } from "prism-react-renderer";
|
||||
import { forwardRef, useCallback, useState } from "react";
|
||||
import { cn } from "~/utils/cn";
|
||||
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../primitives/Tooltip";
|
||||
import { Paragraph } from "../primitives/Paragraph";
|
||||
|
||||
//This is a fork of https://github.com/mantinedev/mantine/blob/master/src/mantine-prism/src/Prism/Prism.tsx
|
||||
//it didn't support highlighting lines by dimming the rest of the code, or animations on the highlighting
|
||||
@@ -38,6 +39,9 @@ type CodeBlockProps = {
|
||||
|
||||
/** filename */
|
||||
fileName?: string;
|
||||
|
||||
/** title text for the Title row */
|
||||
rowTitle?: string;
|
||||
};
|
||||
|
||||
const dimAmount = 0.5;
|
||||
@@ -46,7 +50,7 @@ const extraLinesWhenClipping = 0.35;
|
||||
const defaultTheme: PrismTheme = {
|
||||
plain: {
|
||||
color: "#9CDCFE",
|
||||
backgroundColor: "#1A1B1F",
|
||||
backgroundColor: "rgba(0, 0, 0, 0)",
|
||||
},
|
||||
styles: [
|
||||
{
|
||||
@@ -160,6 +164,7 @@ export const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(
|
||||
maxLines,
|
||||
showChrome = false,
|
||||
fileName,
|
||||
rowTitle,
|
||||
...props
|
||||
}: CodeBlockProps,
|
||||
ref
|
||||
@@ -196,7 +201,7 @@ export const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(
|
||||
|
||||
return (
|
||||
<div
|
||||
className={cn("relative overflow-hidden rounded-md border border-charcoal-800", className)}
|
||||
className={cn("relative overflow-hidden rounded-md border border-grid-bright", className)}
|
||||
style={{
|
||||
backgroundColor: theme.plain.backgroundColor,
|
||||
}}
|
||||
@@ -205,6 +210,7 @@ export const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(
|
||||
translate="no"
|
||||
>
|
||||
{showChrome && <Chrome title={fileName} />}
|
||||
{rowTitle && <TitleRow title={rowTitle} />}
|
||||
{showCopyButton && (
|
||||
<TooltipProvider>
|
||||
<Tooltip open={copied || mouseOver}>
|
||||
@@ -354,16 +360,19 @@ function Chrome({ title }: { title?: string }) {
|
||||
<div className="h-3 w-3 rounded-full bg-charcoal-700" />
|
||||
</div>
|
||||
<div className="flex items-center justify-center">
|
||||
<div
|
||||
className={cn(
|
||||
"rounded-sm px-3 py-0.5 text-xs text-charcoal-500",
|
||||
title && "bg-background-dimmed"
|
||||
)}
|
||||
>
|
||||
{title}
|
||||
</div>
|
||||
<div className={cn("rounded-sm px-3 py-0.5 text-xs text-charcoal-500")}>{title}</div>
|
||||
</div>
|
||||
<div></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function TitleRow({ title }: { title: string }) {
|
||||
return (
|
||||
<div className="flex items-center justify-between px-4">
|
||||
<Paragraph variant="base/bright" className="w-full border-b border-grid-dimmed py-2.5">
|
||||
{title}
|
||||
</Paragraph>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -42,6 +42,7 @@ import {
|
||||
v3ApiKeysPath,
|
||||
v3EnvironmentVariablesPath,
|
||||
v3ProjectPath,
|
||||
v3ProjectSettingsPath,
|
||||
v3RunsPath,
|
||||
v3TestPath,
|
||||
} from "~/utils/pathBuilder";
|
||||
@@ -67,6 +68,8 @@ import {
|
||||
import { StepNumber } from "../primitives/StepNumber";
|
||||
import { SideMenuHeader } from "./SideMenuHeader";
|
||||
import { MenuCount, SideMenuItem } from "./SideMenuItem";
|
||||
import { Badge } from "../primitives/Badge";
|
||||
import { TaskIcon } from "~/assets/icons/TaskIcon";
|
||||
|
||||
type SideMenuUser = Pick<User, "email" | "admin"> & { isImpersonating: boolean };
|
||||
type SideMenuProject = Pick<
|
||||
@@ -247,13 +250,24 @@ export function SideMenu({ user, project, organization, organizations }: SideMen
|
||||
data-action="join our discord"
|
||||
target="_blank"
|
||||
/>
|
||||
<SideMenuItem
|
||||
name="Documentation"
|
||||
icon="docs"
|
||||
to="https://trigger.dev/docs"
|
||||
data-action="documentation"
|
||||
target="_blank"
|
||||
/>
|
||||
{project.version === "V2" ? (
|
||||
<SideMenuItem
|
||||
name="Documentation"
|
||||
icon="docs"
|
||||
to="https://trigger.dev/docs"
|
||||
data-action="documentation"
|
||||
target="_blank"
|
||||
/>
|
||||
) : (
|
||||
<SideMenuItem
|
||||
name="Documentation (v3)"
|
||||
icon="docs"
|
||||
to="https://trigger.dev/docs"
|
||||
data-action="documentation"
|
||||
target="_blank"
|
||||
/>
|
||||
)}
|
||||
|
||||
<SideMenuItem
|
||||
name="Changelog"
|
||||
icon="star"
|
||||
@@ -330,7 +344,11 @@ function ProjectSelector({
|
||||
title={
|
||||
<div className="flex w-full items-center justify-between text-text-bright">
|
||||
<span className="grow truncate text-left">{p.name}</span>
|
||||
<MenuCount count={p.jobCount} />
|
||||
{p.version === "V2" ? (
|
||||
<MenuCount count={p.jobCount} />
|
||||
) : (
|
||||
<Badge variant="v3">v3</Badge>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
isSelected={isSelected}
|
||||
@@ -497,11 +515,11 @@ function V3ProjectSideMenu({
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<SideMenuHeader title={"Project"} />
|
||||
<SideMenuHeader title={"Project (v3)"} />
|
||||
<SideMenuItem
|
||||
name="Tasks"
|
||||
icon="job"
|
||||
iconColor="text-indigo-500"
|
||||
icon={TaskIcon}
|
||||
iconColor="text-blue-500"
|
||||
count={project.jobCount}
|
||||
to={v3ProjectPath(organization, project)}
|
||||
data-action="tasks"
|
||||
@@ -533,14 +551,13 @@ function V3ProjectSideMenu({
|
||||
to={v3EnvironmentVariablesPath(organization, project)}
|
||||
data-action="environment variables"
|
||||
/>
|
||||
{/*
|
||||
<SideMenuItem
|
||||
name="Project settings"
|
||||
icon="settings"
|
||||
iconColor="text-teal-500"
|
||||
to={projectSettingsPath(organization, project)}
|
||||
to={v3ProjectSettingsPath(organization, project)}
|
||||
data-action="project-settings"
|
||||
/> */}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -11,14 +11,14 @@ const sizes = {
|
||||
icon: "h-3.5 -mx-1",
|
||||
iconSpacing: "gap-x-2.5",
|
||||
shortcutVariant: "small" as const,
|
||||
shortcut: "ml-2 -mr-1.5 justify-self-center",
|
||||
shortcut: "-ml-0.5 -mr-1.5 justify-self-center",
|
||||
},
|
||||
medium: {
|
||||
button: "h-8 px-2.5 text-sm",
|
||||
button: "h-8 px-3 text-sm",
|
||||
icon: "h-4 -mx-1",
|
||||
iconSpacing: "gap-x-2.5",
|
||||
shortcutVariant: "medium" as const,
|
||||
shortcut: "ml-2 -mr-1.5 rounded justify-self-center",
|
||||
shortcut: "-ml-0.5 -mr-1.5 rounded justify-self-center",
|
||||
},
|
||||
large: {
|
||||
button: "h-10 px-2 text-sm font-medium",
|
||||
|
||||
@@ -3,66 +3,66 @@ import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { cn } from "~/utils/cn";
|
||||
import { Button } from "./Buttons";
|
||||
import { IconNames, NamedIcon } from "./NamedIcon";
|
||||
import { ClipboardCheckIcon, ClipboardIcon } from "lucide-react";
|
||||
|
||||
const variants = {
|
||||
"primary/small": {
|
||||
container:
|
||||
"flex items-center text-text-dimmed font-mono rounded border bg-charcoal-850 text-xs transition hover:bg-charcoal-700 focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0 focus:border-transparent focus:outline-none focus:ring-0 focus:ring-transparent",
|
||||
"flex items-center text-text-dimmed font-mono rounded border bg-charcoal-750 text-xs transition hover:bg-charcoal-700 focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0 focus:border-transparent focus:outline-none focus:ring-0 focus:ring-transparent",
|
||||
input:
|
||||
"bg-charcoal-750 border-0 text-xs px-2 w-auto rounded-l h-6 leading-6 focus:ring-transparent",
|
||||
"bg-transparent border-0 text-xs px-2 w-auto rounded-l h-6 leading-6 focus:ring-transparent",
|
||||
buttonVariant: "primary/small" as const,
|
||||
button: "rounded-l-none min-w-[3.1rem]",
|
||||
button: "rounded-l-none",
|
||||
iconSize: "h-3 w-3",
|
||||
iconPadding: "pl-1",
|
||||
},
|
||||
"secondary/small": {
|
||||
container:
|
||||
"flex items-center text-text-dimmed font-mono rounded border bg-transparent text-xs transition hover:bg-charcoal-800 focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0 focus:border-transparent focus:outline-none focus:ring-0 focus:ring-transparent",
|
||||
"flex items-center text-text-dimmed font-mono rounded border bg-charcoal-750 text-xs transition hover:bg-charcoal-700 focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0 focus:border-transparent focus:outline-none focus:ring-0 focus:ring-transparent",
|
||||
input:
|
||||
"bg-charcoal-750 border-0 text-xs px-2 w-auto rounded-l h-6 leading-6 focus:ring-transparent",
|
||||
"bg-transparent border-0 text-xs px-2 w-auto rounded-l h-6 leading-6 focus:ring-transparent",
|
||||
buttonVariant: "tertiary/small" as const,
|
||||
button: "rounded-l-none border-l border-charcoal-750 min-w-[3.1rem]",
|
||||
button: "rounded-l-none border-l border-charcoal-750",
|
||||
iconSize: "h-3 w-3",
|
||||
iconPadding: "pl-1",
|
||||
},
|
||||
"tertiary/small": {
|
||||
container:
|
||||
"group flex items-center text-text-dimmed font-mono rounded bg-transparent text-xs transition hover:border-charcoal-700 hover:bg-tertiary focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0 focus:border-transparent focus:outline-none focus:ring-0 focus:ring-transparent",
|
||||
"group flex items-center text-text-dimmed font-mono rounded bg-transparent border border-transparent text-xs transition duration-150 hover:border-charcoal-700 focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0 focus:border-transparent focus:outline-none focus:ring-0 focus:ring-transparent",
|
||||
input:
|
||||
"bg-transparent border-0 text-xs px-2 w-auto rounded-l h-6 leading-6 focus:ring-transparent",
|
||||
buttonVariant: "minimal/small" as const,
|
||||
button:
|
||||
"rounded-l-none border-l border-transparent group-hover:border-charcoal-700 min-w-[3.1rem]",
|
||||
button: "rounded-l-none border-l border-transparent transition group-hover:border-charcoal-700",
|
||||
iconSize: "h-3 w-3",
|
||||
iconPadding: "pl-1",
|
||||
},
|
||||
"primary/medium": {
|
||||
container:
|
||||
"flex items-center text-text-dimmed font-mono rounded border border-charcoal-800 bg-charcoal-850 text-sm transition hover:border-charcoal-750 hover:bg-charcoal-800 focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0 focus:border-transparent focus:outline-none focus:ring-0 focus:ring-transparent",
|
||||
"flex items-center text-text-dimmed font-mono rounded border bg-charcoal-750 text-sm transition hover:bg-charcoal-700 focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0 focus:border-transparent focus:outline-none focus:ring-0 focus:ring-transparent",
|
||||
input:
|
||||
"bg-charcoal-850 border-0 text-sm px-3 w-auto rounded-l h-8 leading-6 focus:ring-transparent",
|
||||
"bg-transparent border-0 text-sm px-3 w-auto rounded-l h-8 leading-6 focus:ring-transparent",
|
||||
buttonVariant: "primary/medium" as const,
|
||||
button: "rounded-l-none min-w-[4rem]",
|
||||
button: "rounded-l-none",
|
||||
iconSize: "h-4 w-4",
|
||||
iconPadding: "pl-2",
|
||||
},
|
||||
"secondary/medium": {
|
||||
container:
|
||||
"flex items-center text-text-dimmed font-mono rounded border border-charcoal-750 bg-charcoal-850 text-sm transition hover:border-charcoal-750 hover:bg-charcoal-800 focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0 focus:border-transparent focus:outline-none focus:ring-0 focus:ring-transparent",
|
||||
"flex items-center text-text-dimmed font-mono rounded bg-charcoal-750 text-sm transition hover:bg-charcoal-700 focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0 focus:border-transparent focus:outline-none focus:ring-0 focus:ring-transparent",
|
||||
input:
|
||||
"bg-charcoal-850 border-0 text-sm px-3 w-auto rounded-l h-8 leading-6 focus:ring-transparent",
|
||||
buttonVariant: "secondary/medium" as const,
|
||||
button: "rounded-l-none border-l border-charcoal-750 min-w-[4rem]",
|
||||
"bg-transparent border-0 text-sm px-3 w-auto rounded-l h-8 leading-6 focus:ring-transparent",
|
||||
buttonVariant: "tertiary/medium" as const,
|
||||
button: "rounded-l-none border-l border-charcoal-750",
|
||||
iconSize: "h-4 w-4",
|
||||
iconPadding: "pl-2",
|
||||
},
|
||||
"tertiary/medium": {
|
||||
container:
|
||||
"flex items-center text-text-dimmed font-mono rounded border border-charcoal-850 bg-transparent text-sm transition hover:border-charcoal-800 hover:bg-charcoal-950 focus:border-4 focus:border-solid focus:border-l-transparent",
|
||||
"group flex items-center text-text-dimmed font-mono rounded bg-transparent border border-transparent text-sm transition hover:border-charcoal-700 focus-visible:outline-none focus-visible:ring-0 focus-visible:ring-offset-0 focus:border-transparent focus:outline-none focus:ring-0 focus:ring-transparent",
|
||||
input:
|
||||
"bg-transparent border-0 text-sm px-3 w-auto rounded-l h-8 leading-6 focus:ring-transparent",
|
||||
buttonVariant: "tertiary/medium" as const,
|
||||
button: "rounded-l-none border-l border-charcoal-850 min-w-[4rem]",
|
||||
buttonVariant: "minimal/medium" as const,
|
||||
button: "rounded-l-none border-l border-transparent transition group-hover:border-charcoal-700",
|
||||
iconSize: "h-4 w-4",
|
||||
iconPadding: "pl-2",
|
||||
},
|
||||
@@ -74,6 +74,7 @@ type ClipboardFieldProps = {
|
||||
variant: keyof typeof variants;
|
||||
className?: string;
|
||||
icon?: IconNames | React.ReactNode;
|
||||
iconButton?: boolean;
|
||||
fullWidth?: boolean;
|
||||
};
|
||||
|
||||
@@ -83,6 +84,7 @@ export function ClipboardField({
|
||||
variant,
|
||||
className,
|
||||
icon,
|
||||
iconButton = false,
|
||||
fullWidth = true,
|
||||
}: ClipboardFieldProps) {
|
||||
const [isSecure, setIsSecure] = useState(secure !== undefined && secure);
|
||||
@@ -139,9 +141,37 @@ export function ClipboardField({
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Button variant={buttonVariant} onClick={copy} className={cn("shrink-0 grow-0", button)}>
|
||||
{copied ? <CheckIcon className="h-4 w-4 text-green-500" /> : "Copy"}
|
||||
</Button>
|
||||
{iconButton ? (
|
||||
<Button
|
||||
variant={buttonVariant}
|
||||
onClick={copy}
|
||||
className={cn("shrink grow-0 px-1.5", button)}
|
||||
>
|
||||
{copied ? (
|
||||
<ClipboardCheckIcon
|
||||
className={cn(
|
||||
"h-4 w-4",
|
||||
buttonVariant === "primary/small" || buttonVariant === "primary/medium"
|
||||
? "text-background-dimmed"
|
||||
: "text-green-500"
|
||||
)}
|
||||
/>
|
||||
) : (
|
||||
<ClipboardIcon
|
||||
className={cn(
|
||||
"h-4 w-4",
|
||||
buttonVariant === "primary/small" || buttonVariant === "primary/medium"
|
||||
? "text-background-dimmed"
|
||||
: "text-text-dimmed"
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</Button>
|
||||
) : (
|
||||
<Button variant={buttonVariant} onClick={copy} className={cn("shrink-0 grow-0", button)}>
|
||||
{copied ? <CheckIcon className="mx-[0.4rem] h-4 w-4 text-green-500" /> : "Copy"}
|
||||
</Button>
|
||||
)}
|
||||
</span>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -43,12 +43,12 @@ const DialogContent = React.forwardRef<
|
||||
<DialogPrimitive.Content
|
||||
ref={ref}
|
||||
className={cn(
|
||||
"fixed z-50 grid w-full gap-4 rounded-b-lg border bg-background-dimmed px-4 pb-6 pt-3 shadow-lg animate-in data-[state=open]:fade-in-90 data-[state=open]:slide-in-from-bottom-10 sm:max-w-lg sm:rounded-lg sm:zoom-in-90 data-[state=open]:sm:slide-in-from-bottom-0",
|
||||
"fixed z-50 grid w-full gap-4 rounded-b-lg border bg-background-dimmed px-4 pb-6 pt-3.5 shadow-lg animate-in data-[state=open]:fade-in-90 data-[state=open]:slide-in-from-bottom-10 sm:max-w-lg sm:rounded-lg sm:zoom-in-90 data-[state=open]:sm:slide-in-from-bottom-0",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
>
|
||||
<hr className="absolute left-0 top-12 w-full" />
|
||||
<hr className="absolute left-0 top-11 w-full" />
|
||||
{children}
|
||||
<DialogPrimitive.Close className="ring-offset-background data-[state=open]:bg-accent data-[state=open]:text-muted-foreground focus-visible:ring-ring absolute right-3 top-3 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none">
|
||||
<div className="flex gap-x-2">
|
||||
@@ -94,7 +94,7 @@ const DialogTitle = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Title
|
||||
ref={ref}
|
||||
className={cn("text-lg font-semibold leading-none tracking-tight", className)}
|
||||
className={cn("text-base font-normal leading-none tracking-tight text-text-bright", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
@@ -106,7 +106,7 @@ const DialogDescription = React.forwardRef<
|
||||
>(({ className, ...props }, ref) => (
|
||||
<DialogPrimitive.Description
|
||||
ref={ref}
|
||||
className={cn("text-muted-foreground text-sm", className)}
|
||||
className={cn("text-muted-foreground pt-4 text-sm", className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { ArrowUpRightIcon } from "@heroicons/react/20/solid";
|
||||
import { ChevronLeftIcon } from "@heroicons/react/24/solid";
|
||||
import { Link, useNavigation } from "@remix-run/react";
|
||||
import { useOptionalOrganization } from "~/hooks/useOrganizations";
|
||||
import { cn } from "~/utils/cn";
|
||||
@@ -7,11 +6,11 @@ import { plansPath } from "~/utils/pathBuilder";
|
||||
import { UpgradePrompt, useShowUpgradePrompt } from "../billing/UpgradePrompt";
|
||||
import { BreadcrumbIcon } from "./BreadcrumbIcon";
|
||||
import { LinkButton } from "./Buttons";
|
||||
import { Header2, Header3 } from "./Headers";
|
||||
import { Header2 } from "./Headers";
|
||||
import { LoadingBarDivider } from "./LoadingBarDivider";
|
||||
import { NamedIcon } from "./NamedIcon";
|
||||
import { Paragraph } from "./Paragraph";
|
||||
import { Tabs, TabsProps } from "./Tabs";
|
||||
import { LoadingBarDivider } from "./LoadingBarDivider";
|
||||
|
||||
type WithChildren = {
|
||||
children: React.ReactNode;
|
||||
@@ -52,10 +51,10 @@ export function PageTitle({ title, backButton }: PageTitleProps) {
|
||||
return (
|
||||
<div className="flex items-center gap-2">
|
||||
{backButton && (
|
||||
<div className="group flex items-center gap-2">
|
||||
<div className="group -ml-1.5 flex items-center gap-0">
|
||||
<Link
|
||||
to={backButton.to}
|
||||
className="text-xs text-text-dimmed transition group-hover:text-text-bright"
|
||||
className="rounded px-1.5 py-1 text-xs text-text-dimmed transition group-hover:bg-charcoal-700 group-hover:text-text-bright"
|
||||
>
|
||||
{backButton.text}
|
||||
</Link>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { cn } from "~/utils/cn";
|
||||
import { Header2 } from "./Headers";
|
||||
import { Spinner } from "./Spinner";
|
||||
import { CheckIcon } from "@heroicons/react/24/solid";
|
||||
|
||||
export function StepNumber({
|
||||
stepNumber,
|
||||
@@ -21,15 +22,15 @@ export function StepNumber({
|
||||
<div className={cn("mr-3", className)}>
|
||||
{active ? (
|
||||
<div className="flex items-center gap-x-3">
|
||||
<span className="flex h-6 w-6 items-center justify-center rounded bg-green-600 py-1 text-xs font-semibold text-charcoal-900 shadow">
|
||||
<span className="flex h-6 w-6 items-center justify-center rounded bg-success py-1 text-xs font-semibold text-charcoal-900">
|
||||
{stepNumber}
|
||||
</span>
|
||||
<Header2>{title}</Header2>
|
||||
</div>
|
||||
) : (
|
||||
<div className="flex items-center gap-x-3">
|
||||
<span className="flex h-6 w-6 items-center justify-center rounded border border-charcoal-700 bg-charcoal-800 py-1 text-xs font-semibold text-text-dimmed shadow">
|
||||
{complete ? "✓" : stepNumber}
|
||||
<span className="flex h-6 w-6 items-center justify-center rounded border border-charcoal-700 bg-charcoal-800 py-1 text-xs font-semibold text-text-dimmed">
|
||||
{complete ? <CheckIcon className="size-4" /> : stepNumber}
|
||||
</span>
|
||||
|
||||
{displaySpinner ? (
|
||||
|
||||
@@ -192,14 +192,9 @@ export class OrganizationsPresenter {
|
||||
deletedAt: null,
|
||||
},
|
||||
},
|
||||
sources: {
|
||||
where: {
|
||||
active: false,
|
||||
},
|
||||
},
|
||||
httpEndpoints: true,
|
||||
},
|
||||
},
|
||||
version: true,
|
||||
},
|
||||
orderBy: { name: "asc" },
|
||||
},
|
||||
@@ -225,6 +220,7 @@ export class OrganizationsPresenter {
|
||||
slug: project.slug,
|
||||
name: project.name,
|
||||
jobCount: project._count.jobs,
|
||||
version: project.version,
|
||||
})),
|
||||
hasUnconfiguredIntegrations: org._count.integrations > 0,
|
||||
runsEnabled: org.runsEnabled,
|
||||
|
||||
@@ -25,6 +25,7 @@ export class ProjectPresenter {
|
||||
updatedAt: true,
|
||||
deletedAt: true,
|
||||
version: true,
|
||||
externalRef: true,
|
||||
_count: {
|
||||
select: {
|
||||
sources: {
|
||||
@@ -65,6 +66,7 @@ export class ProjectPresenter {
|
||||
return {
|
||||
id: project.id,
|
||||
slug: project.slug,
|
||||
ref: project.externalRef,
|
||||
name: project.name,
|
||||
organizationId: project.organizationId,
|
||||
createdAt: project.createdAt,
|
||||
|
||||
@@ -50,6 +50,12 @@ export class RunListPresenter {
|
||||
const hasStatusFilters =
|
||||
filterByEnqueuedStatus || (statuses !== undefined && statuses.length > 0);
|
||||
|
||||
const hasFilters =
|
||||
tasks !== undefined ||
|
||||
versions !== undefined ||
|
||||
hasStatusFilters ||
|
||||
environments !== undefined;
|
||||
|
||||
// Find the project scoped to the organization
|
||||
const project = await this.#prismaClient.project.findFirstOrThrow({
|
||||
select: {
|
||||
@@ -226,11 +232,12 @@ export class RunListPresenter {
|
||||
},
|
||||
};
|
||||
}),
|
||||
possibleTasks: possibleTasks.map((task) => task.slug),
|
||||
pagination: {
|
||||
next,
|
||||
previous,
|
||||
},
|
||||
possibleTasks: possibleTasks.map((task) => task.slug),
|
||||
hasFilters,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Link } from "@remix-run/react";
|
||||
import simplur from "simplur";
|
||||
import { PageBody, PageContainer } from "~/components/layout/AppLayout";
|
||||
import { Badge } from "~/components/primitives/Badge";
|
||||
import { LinkButton } from "~/components/primitives/Buttons";
|
||||
import { Header3 } from "~/components/primitives/Headers";
|
||||
import { NamedIcon } from "~/components/primitives/NamedIcon";
|
||||
@@ -23,6 +24,9 @@ export default function Page() {
|
||||
<NavBar>
|
||||
<PageTitle title={`${organization.title} projects`} />
|
||||
<PageAccessories>
|
||||
<Paragraph variant="extra-small" className="text-charcoal-500">
|
||||
Org UID: {organization.id}
|
||||
</Paragraph>
|
||||
<LinkButton
|
||||
to={newProjectPath(organization)}
|
||||
variant="primary/small"
|
||||
@@ -33,13 +37,6 @@ export default function Page() {
|
||||
</PageAccessories>
|
||||
</NavBar>
|
||||
<PageBody>
|
||||
<PageInfoRow>
|
||||
<PageInfoGroup alignment="right">
|
||||
<Paragraph variant="extra-small" className="text-charcoal-600">
|
||||
UID: {organization.id}
|
||||
</Paragraph>
|
||||
</PageInfoGroup>
|
||||
</PageInfoRow>
|
||||
<ul className="grid grid-cols-1 gap-4 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
|
||||
{organization.projects.length > 0 ? (
|
||||
organization.projects.map((project) => {
|
||||
@@ -52,7 +49,13 @@ export default function Page() {
|
||||
<NamedIcon name="folder" className="h-10 w-10 flex-none" />
|
||||
<div className="flex flex-col">
|
||||
<Header3>{project.name}</Header3>
|
||||
<Paragraph variant="small">{simplur`${project.jobCount} Job[|s]`}</Paragraph>
|
||||
{project.version === "V2" ? (
|
||||
<Paragraph variant="small">{simplur`${project.jobCount} Job[|s]`}</Paragraph>
|
||||
) : (
|
||||
<Badge className="max-w-max" variant="v3">
|
||||
v3
|
||||
</Badge>
|
||||
)}
|
||||
</div>
|
||||
</Link>
|
||||
</li>
|
||||
|
||||
@@ -19,6 +19,7 @@ import { useFeatures } from "~/hooks/useFeatures";
|
||||
import { useOrganization } from "~/hooks/useOrganizations";
|
||||
import { plansPath, stripePortalPath, usagePath } from "~/utils/pathBuilder";
|
||||
import { useCurrentPlan } from "../_app.orgs.$organizationSlug/route";
|
||||
import { Callout } from "~/components/primitives/Callout";
|
||||
|
||||
function planLabel(subscription: ActiveSubscription | undefined, periodEnd: Date) {
|
||||
if (!subscription) {
|
||||
@@ -47,6 +48,10 @@ export default function Page() {
|
||||
const { isManagedCloud } = useFeatures();
|
||||
const currentPlan = useCurrentPlan();
|
||||
|
||||
const hasV3Project = organization.projects.some((p) => p.version === "V3");
|
||||
const hasV2Project = organization.projects.some((p) => p.version === "V2");
|
||||
const allV3Projects = organization.projects.every((p) => p.version === "V3");
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<NavBar>
|
||||
@@ -64,14 +69,16 @@ export default function Page() {
|
||||
</LinkButton>
|
||||
</>
|
||||
)}
|
||||
<LinkButton
|
||||
to={plansPath(organization)}
|
||||
variant="primary/small"
|
||||
LeadingIcon={ArrowUpCircleIcon}
|
||||
leadingIconClassName="px-0"
|
||||
>
|
||||
Upgrade
|
||||
</LinkButton>
|
||||
{hasV2Project && (
|
||||
<LinkButton
|
||||
to={plansPath(organization)}
|
||||
variant="primary/small"
|
||||
LeadingIcon={ArrowUpCircleIcon}
|
||||
leadingIconClassName="px-0"
|
||||
>
|
||||
Upgrade
|
||||
</LinkButton>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</PageAccessories>
|
||||
@@ -79,30 +86,44 @@ export default function Page() {
|
||||
<PageBody scrollable={false}>
|
||||
<div className="grid h-full grid-rows-[auto_1fr] overflow-hidden">
|
||||
<div className="px-4 pt-4">
|
||||
<PageInfoRow>
|
||||
<PageInfoGroup>
|
||||
{currentPlan?.subscription && (
|
||||
<PageInfoProperty
|
||||
icon={<ReceiptRefundIcon className="h-4 w-4 text-green-600" />}
|
||||
value={planLabel(currentPlan.subscription, currentPlan.usage.periodEnd)}
|
||||
/>
|
||||
)}
|
||||
{currentPlan?.subscription?.isPaying && (
|
||||
<PageInfoProperty
|
||||
icon={<CalendarDaysIcon className="h-4 w-4 text-green-600" />}
|
||||
label={"Billing period"}
|
||||
value={
|
||||
<>
|
||||
<DateTime date={currentPlan.usage.periodStart} includeTime={false} /> to{" "}
|
||||
<DateTime date={currentPlan.usage.periodEnd} includeTime={false} /> (
|
||||
{formatDurationInDays(currentPlan.usage.periodRemainingDuration)} remaining)
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</PageInfoGroup>
|
||||
</PageInfoRow>
|
||||
{isManagedCloud && (
|
||||
{allV3Projects ? (
|
||||
<Callout variant="warning" className="mb-3">
|
||||
This organization only has v3 projects. These are currently free. Usage data and
|
||||
billing will be available soon.
|
||||
</Callout>
|
||||
) : hasV3Project ? (
|
||||
<Callout variant="warning" className="mb-3">
|
||||
This organization has a mix of v2 and v3 projects – v3 projects do not count towards
|
||||
your usage (below) and are currently free.
|
||||
</Callout>
|
||||
) : null}
|
||||
{hasV2Project && (
|
||||
<PageInfoRow>
|
||||
<PageInfoGroup>
|
||||
{currentPlan?.subscription && (
|
||||
<PageInfoProperty
|
||||
icon={<ReceiptRefundIcon className="h-4 w-4 text-green-600" />}
|
||||
value={planLabel(currentPlan.subscription, currentPlan.usage.periodEnd)}
|
||||
/>
|
||||
)}
|
||||
{currentPlan?.subscription?.isPaying && (
|
||||
<PageInfoProperty
|
||||
icon={<CalendarDaysIcon className="h-4 w-4 text-green-600" />}
|
||||
label={"Billing period"}
|
||||
value={
|
||||
<>
|
||||
<DateTime date={currentPlan.usage.periodStart} includeTime={false} /> to{" "}
|
||||
<DateTime date={currentPlan.usage.periodEnd} includeTime={false} /> (
|
||||
{formatDurationInDays(currentPlan.usage.periodRemainingDuration)}{" "}
|
||||
remaining)
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</PageInfoGroup>
|
||||
</PageInfoRow>
|
||||
)}
|
||||
{hasV2Project && isManagedCloud && (
|
||||
<PageTabs
|
||||
tabs={[
|
||||
{
|
||||
@@ -118,9 +139,11 @@ export default function Page() {
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<div className="overflow-y-auto pb-4 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600">
|
||||
<Outlet />
|
||||
</div>
|
||||
{hasV2Project && (
|
||||
<div className="overflow-y-auto pb-4 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600">
|
||||
<Outlet />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</PageBody>
|
||||
</PageContainer>
|
||||
|
||||
+53
-3
@@ -1,11 +1,20 @@
|
||||
import { ChatBubbleLeftRightIcon } from "@heroicons/react/20/solid";
|
||||
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
|
||||
import { TaskRunAttemptStatus } from "@trigger.dev/database";
|
||||
import { typedjson, useTypedLoaderData } from "remix-typedjson";
|
||||
import invariant from "tiny-invariant";
|
||||
import { Feedback } from "~/components/Feedback";
|
||||
import { InitCommandV3, TriggerDevStepV3 } from "~/components/SetupCommands";
|
||||
import { StepContentContainer } from "~/components/StepContentContainer";
|
||||
import { InlineCode } from "~/components/code/InlineCode";
|
||||
import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel";
|
||||
import { PageBody, PageContainer } from "~/components/layout/AppLayout";
|
||||
import { MainCenteredContainer, PageBody, PageContainer } from "~/components/layout/AppLayout";
|
||||
import { Button } from "~/components/primitives/Buttons";
|
||||
import { DateTime } from "~/components/primitives/DateTime";
|
||||
import { Header1 } from "~/components/primitives/Headers";
|
||||
import { NavBar, PageTitle } from "~/components/primitives/PageHeader";
|
||||
import { Paragraph } from "~/components/primitives/Paragraph";
|
||||
import { StepNumber } from "~/components/primitives/StepNumber";
|
||||
import {
|
||||
Table,
|
||||
TableBlankRow,
|
||||
@@ -18,6 +27,8 @@ import {
|
||||
} from "~/components/primitives/Table";
|
||||
import { TaskPath } from "~/components/runs/v3/TaskPath";
|
||||
import { TaskRunStatus } from "~/components/runs/v3/TaskRunStatus";
|
||||
import { useAppOrigin } from "~/hooks/useAppOrigin";
|
||||
import { useDevEnvironment } from "~/hooks/useEnvironments";
|
||||
import { useOrganization } from "~/hooks/useOrganizations";
|
||||
import { useProject } from "~/hooks/useProject";
|
||||
import { useUser } from "~/hooks/useUser";
|
||||
@@ -137,7 +148,7 @@ export default function Page() {
|
||||
</Table>
|
||||
</div>
|
||||
) : (
|
||||
<Paragraph>You have no background workers… yet</Paragraph>
|
||||
<CreateTaskInstructions />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
@@ -149,8 +160,47 @@ export default function Page() {
|
||||
function classForTaskAttemptStatus(status: TaskRunAttemptStatus) {
|
||||
switch (status) {
|
||||
case "FAILED":
|
||||
return "text-red-500";
|
||||
return "text-error";
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
function CreateTaskInstructions() {
|
||||
const devEnvironment = useDevEnvironment();
|
||||
invariant(devEnvironment, "Dev environment must be defined");
|
||||
return (
|
||||
<MainCenteredContainer className="max-w-prose">
|
||||
<div className="mb-6 flex items-center justify-between border-b">
|
||||
<Header1 spacing>Get setup in 3 minutes</Header1>
|
||||
<div className="flex items-center gap-2">
|
||||
<Feedback
|
||||
button={
|
||||
<Button variant="minimal/small" LeadingIcon={ChatBubbleLeftRightIcon}>
|
||||
I'm stuck!
|
||||
</Button>
|
||||
}
|
||||
defaultValue="help"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<StepNumber stepNumber="1" title="Run the CLI 'init' command in your project" />
|
||||
<StepContentContainer>
|
||||
<InitCommandV3 />
|
||||
<Paragraph spacing>
|
||||
You’ll notice a new folder in your project called{" "}
|
||||
<InlineCode variant="small">trigger</InlineCode>. We’ve added a very simple example task
|
||||
in here to help you get started.
|
||||
</Paragraph>
|
||||
</StepContentContainer>
|
||||
<StepNumber stepNumber="2" title="Run the CLI 'dev' command" />
|
||||
<StepContentContainer>
|
||||
<TriggerDevStepV3 />
|
||||
</StepContentContainer>
|
||||
<StepNumber stepNumber="3" title="Waiting for tasks" displaySpinner />
|
||||
<StepContentContainer>
|
||||
<Paragraph>This page will automatically refresh.</Paragraph>
|
||||
</StepContentContainer>
|
||||
</MainCenteredContainer>
|
||||
);
|
||||
}
|
||||
|
||||
+5
-16
@@ -123,25 +123,14 @@ export default function Page() {
|
||||
</PropertyTable>
|
||||
|
||||
{event.events !== undefined && <SpanEvents spanEvents={event.events} />}
|
||||
|
||||
{event.payload && (
|
||||
<div>
|
||||
<Header2 spacing>Payload</Header2>
|
||||
<CodeBlock code={event.payload} maxLines={20} />
|
||||
</div>
|
||||
{event.payload !== undefined && (
|
||||
<CodeBlock rowTitle="Payload" code={event.payload} maxLines={20} />
|
||||
)}
|
||||
|
||||
{event.output && (
|
||||
<div>
|
||||
<Header2 spacing>Output</Header2>
|
||||
<CodeBlock code={event.output} maxLines={20} />
|
||||
</div>
|
||||
{event.output !== undefined && (
|
||||
<CodeBlock rowTitle="Output" code={event.output} maxLines={20} />
|
||||
)}
|
||||
{event.properties !== undefined && (
|
||||
<div>
|
||||
<Header2 spacing>Properties</Header2>
|
||||
<CodeBlock code={event.properties} maxLines={20} />
|
||||
</div>
|
||||
<CodeBlock rowTitle="Properties" code={event.properties} maxLines={20} />
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
+107
-22
@@ -1,17 +1,27 @@
|
||||
import { BeakerIcon, BookOpenIcon } from "@heroicons/react/24/solid";
|
||||
import { useNavigation } from "@remix-run/react";
|
||||
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
|
||||
import { typedjson, useTypedLoaderData } from "remix-typedjson";
|
||||
import { TaskIcon } from "~/assets/icons/TaskIcon";
|
||||
import { BlankstateInstructions } from "~/components/BlankstateInstructions";
|
||||
import { StepContentContainer } from "~/components/StepContentContainer";
|
||||
import { MainCenteredContainer, PageBody } from "~/components/layout/AppLayout";
|
||||
import { LinkButton } from "~/components/primitives/Buttons";
|
||||
import { Header1 } from "~/components/primitives/Headers";
|
||||
import { NavBar, PageTitle } from "~/components/primitives/PageHeader";
|
||||
import { Paragraph } from "~/components/primitives/Paragraph";
|
||||
import { StepNumber } from "~/components/primitives/StepNumber";
|
||||
import { RunsFilters, TaskRunListSearchFilters } from "~/components/runs/v3/RunFilters";
|
||||
import { TaskRunsTable } from "~/components/runs/v3/TaskRunsTable";
|
||||
import { useOrganization } from "~/hooks/useOrganizations";
|
||||
import { useProject } from "~/hooks/useProject";
|
||||
import { useUser } from "~/hooks/useUser";
|
||||
import { RunListPresenter } from "~/presenters/v3/RunListPresenter.server";
|
||||
import { requireUserId } from "~/services/session.server";
|
||||
import { cn } from "~/utils/cn";
|
||||
import { ProjectParamSchema } from "~/utils/pathBuilder";
|
||||
import { ProjectParamSchema, v3ProjectPath, v3TestPath } from "~/utils/pathBuilder";
|
||||
import { ListPagination } from "../../components/ListPagination";
|
||||
import { PageBody } from "~/components/layout/AppLayout";
|
||||
import { NavBar, PageTitle } from "~/components/primitives/PageHeader";
|
||||
import { TextLink } from "~/components/primitives/TextLink";
|
||||
|
||||
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
|
||||
const userId = await requireUserId(request);
|
||||
@@ -54,29 +64,104 @@ export default function Page() {
|
||||
<PageTitle title="Runs" />
|
||||
</NavBar>
|
||||
<PageBody>
|
||||
<div className={cn("grid h-fit grid-cols-1 gap-4")}>
|
||||
<div>
|
||||
<div className="mb-2 flex items-center justify-between gap-x-2">
|
||||
<RunsFilters
|
||||
possibleEnvironments={project.environments}
|
||||
possibleTasks={list.possibleTasks}
|
||||
/>
|
||||
<div className="flex items-center justify-end gap-x-2">
|
||||
<ListPagination list={list} />
|
||||
{list.possibleTasks.length === 0 ? (
|
||||
<CreateFirstTaskInstructions />
|
||||
) : list.runs.length === 0 && !list.hasFilters ? (
|
||||
<RunTaskInstructions />
|
||||
) : (
|
||||
<div className={cn("grid h-fit grid-cols-1 gap-4")}>
|
||||
<div>
|
||||
<div className="mb-2 flex items-center justify-between gap-x-2">
|
||||
<RunsFilters
|
||||
possibleEnvironments={project.environments}
|
||||
possibleTasks={list.possibleTasks}
|
||||
/>
|
||||
<div className="flex items-center justify-end gap-x-2">
|
||||
<ListPagination list={list} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<TaskRunsTable
|
||||
total={list.runs.length}
|
||||
hasFilters={false}
|
||||
runs={list.runs}
|
||||
isLoading={isLoading}
|
||||
currentUser={user}
|
||||
/>
|
||||
<ListPagination list={list} className="mt-2 justify-end" />
|
||||
<TaskRunsTable
|
||||
total={list.runs.length}
|
||||
hasFilters={list.hasFilters}
|
||||
runs={list.runs}
|
||||
isLoading={isLoading}
|
||||
currentUser={user}
|
||||
/>
|
||||
<ListPagination list={list} className="mt-2 justify-end" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</PageBody>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function CreateFirstTaskInstructions() {
|
||||
const organization = useOrganization();
|
||||
const project = useProject();
|
||||
return (
|
||||
<MainCenteredContainer className="max-w-prose">
|
||||
<BlankstateInstructions title="Create your first task">
|
||||
<Paragraph spacing>
|
||||
Before running a task, you must first create one. Follow the instructions on the{" "}
|
||||
<TextLink to={v3ProjectPath(organization, project)}>Tasks</TextLink> page to create a
|
||||
task, then return here to run it.
|
||||
</Paragraph>
|
||||
<LinkButton
|
||||
to={v3ProjectPath(organization, project)}
|
||||
variant="primary/medium"
|
||||
LeadingIcon={TaskIcon}
|
||||
className="inline-flex"
|
||||
>
|
||||
Create your first task
|
||||
</LinkButton>
|
||||
</BlankstateInstructions>
|
||||
</MainCenteredContainer>
|
||||
);
|
||||
}
|
||||
|
||||
function RunTaskInstructions() {
|
||||
const organization = useOrganization();
|
||||
const project = useProject();
|
||||
return (
|
||||
<MainCenteredContainer className="max-w-prose">
|
||||
<Header1 className="mb-6 border-b py-2">How to run your tasks</Header1>
|
||||
<StepNumber stepNumber="A" title="Trigger a test run" />
|
||||
<StepContentContainer>
|
||||
<Paragraph spacing>
|
||||
You can perform a Run with any payload you want, or use one of our examples on the test
|
||||
page.
|
||||
</Paragraph>
|
||||
<LinkButton
|
||||
to={v3TestPath(organization, project)}
|
||||
variant="primary/medium"
|
||||
LeadingIcon={BeakerIcon}
|
||||
className="inline-flex"
|
||||
>
|
||||
Test
|
||||
</LinkButton>
|
||||
<div className="mt-6 flex items-center gap-2">
|
||||
<hr className="w-full" />
|
||||
<Paragraph variant="extra-extra-small/dimmed/caps">OR</Paragraph>
|
||||
<hr className="w-full" />
|
||||
</div>
|
||||
</StepContentContainer>
|
||||
|
||||
<StepNumber stepNumber="B" title="Trigger your task for real" />
|
||||
<StepContentContainer>
|
||||
<Paragraph spacing>
|
||||
Performing a real run depends on the type of Trigger your Task is using.
|
||||
</Paragraph>
|
||||
<LinkButton
|
||||
to="https://trigger.dev/docs"
|
||||
variant="primary/medium"
|
||||
LeadingIcon={BookOpenIcon}
|
||||
className="inline-flex"
|
||||
>
|
||||
How to run a task
|
||||
</LinkButton>
|
||||
</StepContentContainer>
|
||||
</MainCenteredContainer>
|
||||
);
|
||||
}
|
||||
|
||||
+169
@@ -0,0 +1,169 @@
|
||||
import { conform, useForm } from "@conform-to/react";
|
||||
import { parse } from "@conform-to/zod";
|
||||
import { Form, useActionData, useNavigation } from "@remix-run/react";
|
||||
import { ActionFunction, json } from "@remix-run/server-runtime";
|
||||
import { redirect } from "remix-typedjson";
|
||||
import { z } from "zod";
|
||||
import { InlineCode } from "~/components/code/InlineCode";
|
||||
import { PageBody, PageContainer } from "~/components/layout/AppLayout";
|
||||
import { Button } from "~/components/primitives/Buttons";
|
||||
import { ClipboardField } from "~/components/primitives/ClipboardField";
|
||||
import { Fieldset } from "~/components/primitives/Fieldset";
|
||||
import { FormButtons } from "~/components/primitives/FormButtons";
|
||||
import { FormError } from "~/components/primitives/FormError";
|
||||
import { Header2 } from "~/components/primitives/Headers";
|
||||
import { Hint } from "~/components/primitives/Hint";
|
||||
import { Input } from "~/components/primitives/Input";
|
||||
import { InputGroup } from "~/components/primitives/InputGroup";
|
||||
import { Label } from "~/components/primitives/Label";
|
||||
import { NavBar, PageTitle } from "~/components/primitives/PageHeader";
|
||||
import { prisma } from "~/db.server";
|
||||
import { useProject } from "~/hooks/useProject";
|
||||
import { redirectWithErrorMessage, redirectWithSuccessMessage } from "~/models/message.server";
|
||||
import {
|
||||
clearCurrentProjectId,
|
||||
commitCurrentProjectSession,
|
||||
} from "~/services/currentProject.server";
|
||||
import { DeleteProjectService } from "~/services/deleteProject.server";
|
||||
import { logger } from "~/services/logger.server";
|
||||
import { requireUserId } from "~/services/session.server";
|
||||
import { organizationPath, projectPath, v3ProjectPath } from "~/utils/pathBuilder";
|
||||
|
||||
export function createSchema(
|
||||
constraints: {
|
||||
getSlugMatch?: (slug: string) => { isMatch: boolean; projectSlug: string };
|
||||
} = {}
|
||||
) {
|
||||
return z.discriminatedUnion("action", [
|
||||
z.object({
|
||||
action: z.literal("rename"),
|
||||
projectName: z.string().min(3, "Project name must have at least 3 characters").max(50),
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
export const action: ActionFunction = async ({ request, params }) => {
|
||||
const userId = await requireUserId(request);
|
||||
const { organizationSlug, projectParam } = params;
|
||||
if (!organizationSlug || !projectParam) {
|
||||
return json({ errors: { body: "organizationSlug is required" } }, { status: 400 });
|
||||
}
|
||||
|
||||
const formData = await request.formData();
|
||||
|
||||
const schema = createSchema({
|
||||
getSlugMatch: (slug) => {
|
||||
return { isMatch: slug === projectParam, projectSlug: projectParam };
|
||||
},
|
||||
});
|
||||
const submission = parse(formData, { schema });
|
||||
|
||||
if (!submission.value || submission.intent !== "submit") {
|
||||
return json(submission);
|
||||
}
|
||||
|
||||
try {
|
||||
switch (submission.value.action) {
|
||||
case "rename": {
|
||||
await prisma.project.update({
|
||||
where: {
|
||||
slug: projectParam,
|
||||
organization: {
|
||||
members: {
|
||||
some: {
|
||||
userId,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
data: {
|
||||
name: submission.value.projectName,
|
||||
},
|
||||
});
|
||||
|
||||
return redirectWithSuccessMessage(
|
||||
v3ProjectPath({ slug: organizationSlug }, { slug: projectParam }),
|
||||
request,
|
||||
`Project renamed to ${submission.value.projectName}`
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error: any) {
|
||||
return json({ errors: { body: error.message } }, { status: 400 });
|
||||
}
|
||||
};
|
||||
|
||||
export default function Page() {
|
||||
const project = useProject();
|
||||
const lastSubmission = useActionData();
|
||||
const navigation = useNavigation();
|
||||
|
||||
const [renameForm, { projectName }] = useForm({
|
||||
id: "rename-project",
|
||||
// TODO: type this
|
||||
lastSubmission: lastSubmission as any,
|
||||
shouldRevalidate: "onSubmit",
|
||||
onValidate({ formData }) {
|
||||
return parse(formData, {
|
||||
schema: createSchema(),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
const isRenameLoading =
|
||||
navigation.formData?.get("action") === "rename" &&
|
||||
(navigation.state === "submitting" || navigation.state === "loading");
|
||||
|
||||
return (
|
||||
<PageContainer>
|
||||
<NavBar>
|
||||
<PageTitle title={`${project.name} project settings`} />
|
||||
</NavBar>
|
||||
|
||||
<PageBody>
|
||||
<div className="flex flex-col gap-4">
|
||||
<div className="flex flex-col gap-4">
|
||||
<Fieldset>
|
||||
<InputGroup>
|
||||
<Label>Project ref</Label>
|
||||
<ClipboardField value={`proj_${project.ref}`} variant={"secondary/small"} />
|
||||
<Hint>
|
||||
This goes in your <InlineCode>trigger.config</InlineCode> file.
|
||||
</Hint>
|
||||
</InputGroup>
|
||||
</Fieldset>
|
||||
|
||||
<Form method="post" {...renameForm.props} className="max-w-md">
|
||||
<input type="hidden" name="action" value="rename" />
|
||||
<Fieldset>
|
||||
<InputGroup>
|
||||
<Label htmlFor={projectName.id}>Rename your project</Label>
|
||||
<Input
|
||||
{...conform.input(projectName, { type: "text" })}
|
||||
defaultValue={project.name}
|
||||
placeholder="Your project name"
|
||||
icon="folder"
|
||||
autoFocus
|
||||
/>
|
||||
<FormError id={projectName.errorId}>{projectName.error}</FormError>
|
||||
</InputGroup>
|
||||
<FormButtons
|
||||
confirmButton={
|
||||
<Button
|
||||
type="submit"
|
||||
variant={"primary/small"}
|
||||
disabled={isRenameLoading}
|
||||
LeadingIcon={isRenameLoading ? "spinner-white" : undefined}
|
||||
>
|
||||
Rename project
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
</Fieldset>
|
||||
</Form>
|
||||
</div>
|
||||
</div>
|
||||
</PageBody>
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
+39
-7
@@ -1,24 +1,28 @@
|
||||
import { NavLink, Outlet, useNavigation, useParams } from "@remix-run/react";
|
||||
import { NavLink, Outlet, useParams } from "@remix-run/react";
|
||||
import { LoaderFunctionArgs } from "@remix-run/server-runtime";
|
||||
import { typedjson, useTypedLoaderData } from "remix-typedjson";
|
||||
import { TaskIcon } from "~/assets/icons/TaskIcon";
|
||||
import { BlankstateInstructions } from "~/components/BlankstateInstructions";
|
||||
import { EnvironmentLabel } from "~/components/environments/EnvironmentLabel";
|
||||
import { PageBody, PageContainer } from "~/components/layout/AppLayout";
|
||||
import { LinkButton } from "~/components/primitives/Buttons";
|
||||
import { Header2 } from "~/components/primitives/Headers";
|
||||
import { NavBar, PageTitle } from "~/components/primitives/PageHeader";
|
||||
import { Paragraph } from "~/components/primitives/Paragraph";
|
||||
import { RadioButtonCircle } from "~/components/primitives/RadioButton";
|
||||
import {
|
||||
ResizableHandle,
|
||||
ResizablePanel,
|
||||
ResizablePanelGroup,
|
||||
} from "~/components/primitives/Resizable";
|
||||
import { TextLink } from "~/components/primitives/TextLink";
|
||||
import { TaskPath } from "~/components/runs/v3/TaskPath";
|
||||
import { useOrganization } from "~/hooks/useOrganizations";
|
||||
import { useProject } from "~/hooks/useProject";
|
||||
import { useUser } from "~/hooks/useUser";
|
||||
import { TaskListItem, TestPresenter } from "~/presenters/v3/TestPresenter.server";
|
||||
import { requireUserId } from "~/services/session.server";
|
||||
import { cn } from "~/utils/cn";
|
||||
import { ProjectParamSchema, v3TestTaskPath } from "~/utils/pathBuilder";
|
||||
import { ProjectParamSchema, v3ProjectPath, v3TestTaskPath } from "~/utils/pathBuilder";
|
||||
|
||||
export const loader = async ({ request, params }: LoaderFunctionArgs) => {
|
||||
const userId = await requireUserId(request);
|
||||
@@ -49,10 +53,16 @@ export default function Page() {
|
||||
<ResizablePanelGroup direction="horizontal" className="h-full max-h-full">
|
||||
<ResizablePanel order={1} minSize={20} defaultSize={30}>
|
||||
<div className="flex flex-col px-3">
|
||||
<div className="flex h-10 items-center border-b border-grid-dimmed">
|
||||
<Header2>Select a task</Header2>
|
||||
</div>
|
||||
<TaskSelector tasks={tasks} />
|
||||
{tasks.length === 0 ? (
|
||||
<NoTaskInstructions />
|
||||
) : (
|
||||
<>
|
||||
<div className="flex h-10 items-center border-b border-grid-dimmed">
|
||||
<Header2>Select a task</Header2>
|
||||
</div>
|
||||
<TaskSelector tasks={tasks} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
</ResizablePanel>
|
||||
<ResizableHandle withHandle />
|
||||
@@ -97,3 +107,25 @@ function TaskSelector({ tasks }: { tasks: TaskListItem[] }) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function NoTaskInstructions() {
|
||||
const organization = useOrganization();
|
||||
const project = useProject();
|
||||
return (
|
||||
<BlankstateInstructions title="Create your first task">
|
||||
<Paragraph spacing variant="small">
|
||||
Before testing, you must first create a task. Follow the instructions on the{" "}
|
||||
<TextLink to={v3ProjectPath(organization, project)}>Tasks</TextLink> page to create a task
|
||||
then return here to run a test.
|
||||
</Paragraph>
|
||||
<LinkButton
|
||||
to={v3ProjectPath(organization, project)}
|
||||
variant="primary/small"
|
||||
LeadingIcon={TaskIcon}
|
||||
className="inline-flex"
|
||||
>
|
||||
Create your first task
|
||||
</LinkButton>
|
||||
</BlankstateInstructions>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -78,7 +78,7 @@ export const action: ActionFunction = async ({ request }) => {
|
||||
"Set-Cookie": await commitCurrentProjectSession(session),
|
||||
};
|
||||
|
||||
if (isManagedCloud) {
|
||||
if (isManagedCloud && submission.value.projectVersion === "v2") {
|
||||
return redirect(selectPlanPath(organization), {
|
||||
headers,
|
||||
});
|
||||
|
||||
@@ -7,7 +7,12 @@ import {
|
||||
NoSymbolIcon,
|
||||
} from "@heroicons/react/20/solid";
|
||||
import { EnvelopeIcon } from "@heroicons/react/24/solid";
|
||||
import { AirtableIcon, GitHubDarkIcon, SlackIcon } from "@trigger.dev/companyicons";
|
||||
import {
|
||||
AirtableIcon,
|
||||
GitHubDarkIcon,
|
||||
GitHubLightIcon,
|
||||
SlackIcon,
|
||||
} from "@trigger.dev/companyicons";
|
||||
import { Button } from "~/components/primitives/Buttons";
|
||||
import { Header1, Header3 } from "~/components/primitives/Headers";
|
||||
import { NamedIcon } from "~/components/primitives/NamedIcon";
|
||||
@@ -269,7 +274,7 @@ export default function Story() {
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-col items-start gap-2">
|
||||
<Button variant="primary/large" fullWidth>
|
||||
<GitHubDarkIcon className={"mr-1.5 h-4 w-4"} />
|
||||
<GitHubDarkIcon className={"mr-1.5 size-[1.2rem]"} />
|
||||
<span className="text-charcoal-900">Continue with GitHub</span>
|
||||
</Button>
|
||||
<Button variant="secondary/large" fullWidth>
|
||||
@@ -278,6 +283,10 @@ export default function Story() {
|
||||
/>
|
||||
<span className="text-primary group-hover:text-apple-200">Continue with Email</span>
|
||||
</Button>
|
||||
<Button variant="tertiary/large" fullWidth>
|
||||
<GitHubLightIcon className={"mr-1.5 size-[1.2rem]"} />
|
||||
<span className="text-text-bright">Continue with GitHub</span>
|
||||
</Button>
|
||||
<Button variant="danger/large" fullWidth>
|
||||
<NamedIcon
|
||||
name={"trash-can"}
|
||||
@@ -304,6 +313,10 @@ export default function Story() {
|
||||
/>
|
||||
<span className="text-primary group-hover:text-apple-200">Continue with Email</span>
|
||||
</Button>
|
||||
<Button variant="tertiary/extra-large" fullWidth>
|
||||
<GitHubLightIcon className={"mr-1.5 h-5 w-5"} />
|
||||
<span className="text-text-bright">Continue with GitHub</span>
|
||||
</Button>
|
||||
<Button variant="danger/extra-large" fullWidth>
|
||||
<NamedIcon
|
||||
name={"trash-can"}
|
||||
|
||||
@@ -5,21 +5,31 @@ import { Paragraph } from "~/components/primitives/Paragraph";
|
||||
|
||||
export default function Story() {
|
||||
return (
|
||||
<div className="flex gap-8">
|
||||
<div className="flex flex-col items-start gap-y-[2.58rem] whitespace-nowrap p-8">
|
||||
<Header2>Clipboards</Header2>
|
||||
<Paragraph variant="small">primary/small</Paragraph>
|
||||
<Paragraph variant="small">secondary/small</Paragraph>
|
||||
<Paragraph variant="small">tertiary/small</Paragraph>
|
||||
<Paragraph variant="small">tertiary/small + LeadingIcon</Paragraph>
|
||||
<Paragraph variant="small">tertiary/small + LeadingIcon</Paragraph>
|
||||
<Paragraph variant="small">primary/medium</Paragraph>
|
||||
<Paragraph variant="small">secondary/medium</Paragraph>
|
||||
<Paragraph variant="small">tertiary/medium</Paragraph>
|
||||
<Paragraph variant="small">tertiary/medium + LeadingIcon</Paragraph>
|
||||
<Paragraph variant="small">tertiary/medium + LeadingIcon</Paragraph>
|
||||
<div className="flex gap-x-8 p-8">
|
||||
<div className="flex flex-col items-start whitespace-nowrap">
|
||||
<Header2 className="mb-[2.2rem]">Variants</Header2>
|
||||
<div className="mb-10 space-y-[2.33rem]">
|
||||
<Paragraph variant="small/bright">primary/small</Paragraph>
|
||||
<Paragraph variant="small/bright">secondary/small</Paragraph>
|
||||
<Paragraph variant="small/bright">tertiary/small</Paragraph>
|
||||
<Paragraph variant="small/bright">tertiary/small + LeadingIcon</Paragraph>
|
||||
<Paragraph variant="small/bright">tertiary/small + LeadingIcon</Paragraph>
|
||||
<Paragraph variant="small/bright">primary/small + iconButton</Paragraph>
|
||||
<Paragraph variant="small/bright">secondary/small + iconButton</Paragraph>
|
||||
<Paragraph variant="small/bright">tertiary/small + iconButton</Paragraph>
|
||||
</div>
|
||||
<div className="space-y-[2.9rem]">
|
||||
<Paragraph variant="small/bright">primary/medium</Paragraph>
|
||||
<Paragraph variant="small/bright">secondary/medium</Paragraph>
|
||||
<Paragraph variant="small/bright">tertiary/medium</Paragraph>
|
||||
<Paragraph variant="small/bright">tertiary/medium + LeadingIcon</Paragraph>
|
||||
<Paragraph variant="small/bright">tertiary/medium + LeadingIcon</Paragraph>
|
||||
<Paragraph variant="small/bright">primary/medium + iconButton</Paragraph>
|
||||
<Paragraph variant="small/bright">secondary/medium + iconButton</Paragraph>
|
||||
<Paragraph variant="small/bright">tertiary/medium + iconButton</Paragraph>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-col items-start gap-y-8 p-8">
|
||||
<div className="flex flex-col items-start gap-y-8">
|
||||
<Header2>Default</Header2>
|
||||
<ClipboardField value="copy paste me" variant="primary/small" />
|
||||
<ClipboardField value="copy paste me" variant="secondary/small" />
|
||||
@@ -27,9 +37,12 @@ export default function Story() {
|
||||
<ClipboardField
|
||||
value="with leadingIcon"
|
||||
variant="tertiary/small"
|
||||
icon={<EnvironmentLabel environment={{ type: "PRODUCTION" }} />}
|
||||
icon={<EnvironmentLabel environment={{ type: "DEVELOPMENT" }} />}
|
||||
/>
|
||||
<ClipboardField value="with leadingIcon" variant="tertiary/small" icon="search" />
|
||||
<ClipboardField value="with iconButton" variant="primary/small" iconButton />
|
||||
<ClipboardField value="with iconButton" variant="secondary/small" iconButton />
|
||||
<ClipboardField value="with iconButton" variant="tertiary/small" iconButton />
|
||||
<ClipboardField value="copy paste me" variant="primary/medium" />
|
||||
<ClipboardField value="copy paste me" variant="secondary/medium" />
|
||||
<ClipboardField value="copy paste me" variant="tertiary/medium" />
|
||||
@@ -39,8 +52,11 @@ export default function Story() {
|
||||
icon={<EnvironmentLabel environment={{ type: "DEVELOPMENT" }} />}
|
||||
/>
|
||||
<ClipboardField value="with leadingIcon" variant="tertiary/medium" icon="search" />
|
||||
<ClipboardField value="with iconButton" variant="primary/medium" iconButton />
|
||||
<ClipboardField value="with iconButton" variant="secondary/medium" iconButton />
|
||||
<ClipboardField value="with iconButton" variant="tertiary/medium" iconButton />
|
||||
</div>
|
||||
<div className="flex flex-col items-start gap-y-8 p-8">
|
||||
<div className="flex flex-col items-start gap-y-8">
|
||||
<Header2>Secure value</Header2>
|
||||
<ClipboardField value="copy paste me" variant="primary/small" secure={true} />
|
||||
<ClipboardField value="copy paste me" variant="secondary/small" secure={true} />
|
||||
@@ -48,8 +64,8 @@ export default function Story() {
|
||||
<ClipboardField
|
||||
value="with leadingIcon"
|
||||
variant="tertiary/small"
|
||||
icon={<EnvironmentLabel environment={{ type: "STAGING" }} />}
|
||||
secure={true}
|
||||
icon={<EnvironmentLabel environment={{ type: "STAGING" }} />}
|
||||
/>
|
||||
<ClipboardField
|
||||
value="with leadingIcon"
|
||||
@@ -57,13 +73,22 @@ export default function Story() {
|
||||
icon="search"
|
||||
secure={true}
|
||||
/>
|
||||
<ClipboardField value="with iconButton" variant="primary/small" iconButton secure={true} />
|
||||
<ClipboardField
|
||||
value="with iconButton"
|
||||
variant="secondary/small"
|
||||
iconButton
|
||||
secure={true}
|
||||
/>
|
||||
<ClipboardField value="with iconButton" variant="tertiary/small" iconButton secure={true} />
|
||||
{/* Medium size */}
|
||||
<ClipboardField value="copy paste me" variant="primary/medium" secure={true} />
|
||||
<ClipboardField value="copy paste me" variant="secondary/medium" secure={true} />
|
||||
<ClipboardField value="copy paste me" variant="tertiary/medium" secure={true} />
|
||||
<ClipboardField
|
||||
value="with leadingIcon"
|
||||
variant="tertiary/medium"
|
||||
icon={<EnvironmentLabel environment={{ type: "PRODUCTION" }} />}
|
||||
icon={<EnvironmentLabel environment={{ type: "STAGING" }} />}
|
||||
secure={true}
|
||||
/>
|
||||
<ClipboardField
|
||||
@@ -72,8 +97,21 @@ export default function Story() {
|
||||
icon="search"
|
||||
secure={true}
|
||||
/>
|
||||
<ClipboardField value="with iconButton" variant="primary/medium" iconButton secure={true} />
|
||||
<ClipboardField
|
||||
value="with iconButton"
|
||||
variant="secondary/medium"
|
||||
iconButton
|
||||
secure={true}
|
||||
/>
|
||||
<ClipboardField
|
||||
value="with iconButton"
|
||||
variant="tertiary/medium"
|
||||
iconButton
|
||||
secure={true}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex flex-col items-start gap-y-8 p-8">
|
||||
<div className="flex flex-col items-start gap-y-8">
|
||||
<Header2>Custom secure value</Header2>
|
||||
<ClipboardField value="npx abcdefghi" variant="primary/small" secure="npx ••••••••" />
|
||||
<ClipboardField value="npx abcdefghi" variant="secondary/small" secure="npx ••••••••" />
|
||||
@@ -81,7 +119,7 @@ export default function Story() {
|
||||
<ClipboardField
|
||||
value="npx abcdefghi"
|
||||
variant="tertiary/small"
|
||||
icon={<EnvironmentLabel environment={{ type: "STAGING" }} />}
|
||||
icon={<EnvironmentLabel environment={{ type: "PRODUCTION" }} />}
|
||||
secure="npx ••••••••"
|
||||
/>
|
||||
<ClipboardField
|
||||
@@ -90,6 +128,25 @@ export default function Story() {
|
||||
icon="search"
|
||||
secure="npx ••••••••"
|
||||
/>
|
||||
<ClipboardField
|
||||
value="npx abcdefghi"
|
||||
variant="primary/small"
|
||||
iconButton
|
||||
secure="npx ••••••••"
|
||||
/>
|
||||
<ClipboardField
|
||||
value="npx abcdefghi"
|
||||
variant="secondary/small"
|
||||
iconButton
|
||||
secure="npx ••••••••"
|
||||
/>
|
||||
<ClipboardField
|
||||
value="npx abcdefghi"
|
||||
variant="tertiary/small"
|
||||
iconButton
|
||||
secure="npx ••••••••"
|
||||
/>
|
||||
{/* Medium size */}
|
||||
<ClipboardField value="npx abcdefghi" variant="primary/medium" secure="npx ••••••••" />
|
||||
<ClipboardField value="npx abcdefghi" variant="secondary/medium" secure="npx ••••••••" />
|
||||
<ClipboardField value="npx abcdefghi" variant="tertiary/medium" secure="npx ••••••••" />
|
||||
@@ -105,6 +162,24 @@ export default function Story() {
|
||||
icon="search"
|
||||
secure="npx ••••••••"
|
||||
/>
|
||||
<ClipboardField
|
||||
value="npx abcdefghi"
|
||||
variant="primary/medium"
|
||||
iconButton
|
||||
secure="npx ••••••••"
|
||||
/>
|
||||
<ClipboardField
|
||||
value="npx abcdefghi"
|
||||
variant="secondary/medium"
|
||||
iconButton
|
||||
secure="npx ••••••••"
|
||||
/>
|
||||
<ClipboardField
|
||||
value="npx abcdefghi"
|
||||
variant="tertiary/medium"
|
||||
iconButton
|
||||
secure="npx ••••••••"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1,39 +1,100 @@
|
||||
import { CodeBlock } from "~/components/code/CodeBlock";
|
||||
import { Header2 } from "~/components/primitives/Headers";
|
||||
|
||||
export default function Story() {
|
||||
return (
|
||||
<div className="flex flex-col items-start gap-y-8 p-8">
|
||||
<CodeBlock
|
||||
code={`{ id: "my-first-job" }`}
|
||||
highlightedRanges={[
|
||||
[6, 8],
|
||||
[12, 14],
|
||||
]}
|
||||
/>
|
||||
<CodeBlock
|
||||
code={`export const client = new TriggerClient("smoke-test", {
|
||||
apiUrl: "http://localhost:3000",
|
||||
endpoint: "http://localhost:3007/__trigger/entry",
|
||||
logLevel: "debug",
|
||||
longLine: "This is a long line that will scroll off the edge of the screen and cause a horizontal scrollbar",
|
||||
onLog: (log) => {
|
||||
console.log(log);
|
||||
},
|
||||
onLogError: (log) => {
|
||||
console.error(log);
|
||||
},
|
||||
onLogWarning: (log) => {
|
||||
console.warn(log);
|
||||
},
|
||||
onLogInfo: (log) => {
|
||||
console.info(log);
|
||||
},
|
||||
});`}
|
||||
highlightedRanges={[
|
||||
[6, 8],
|
||||
[12, 14],
|
||||
]}
|
||||
/>
|
||||
<div>
|
||||
<Header2>Inline field</Header2>
|
||||
<CodeBlock code={`{ id: "my-first-job" }`} />
|
||||
</div>
|
||||
<div>
|
||||
<Header2 className="mb-2">With title row</Header2>
|
||||
<CodeBlock
|
||||
rowTitle="Trigger client"
|
||||
code={`export const client = new TriggerClient("smoke-test", {
|
||||
apiUrl: "http://localhost:3000",
|
||||
endpoint: "http://localhost:3007/__trigger/entry",
|
||||
logLevel: "debug",
|
||||
longLine: "This is a long line that will scroll off the edge of the screen and cause a horizontal scrollbar",
|
||||
});`}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Header2 className="mb-2">showChrome</Header2>
|
||||
<CodeBlock
|
||||
showChrome
|
||||
fileName="trigger-client.ts"
|
||||
code={`export const client = new TriggerClient("smoke-test", {
|
||||
apiUrl: "http://localhost:3000",
|
||||
endpoint: "http://localhost:3007/__trigger/entry",
|
||||
logLevel: "debug",
|
||||
longLine: "This is a long line that will scroll off the edge of the screen and cause a horizontal scrollbar",
|
||||
onLog: (log) => {
|
||||
console.log(log);
|
||||
},
|
||||
onLogError: (log) => {
|
||||
console.error(log);
|
||||
},
|
||||
onLogWarning: (log) => {
|
||||
console.warn(log);
|
||||
},
|
||||
onLogInfo: (log) => {
|
||||
console.info(log);
|
||||
},
|
||||
});`}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Header2 className="mb-2">Inline copy button</Header2>
|
||||
<CodeBlock
|
||||
code={`export const client = new TriggerClient("smoke-test", {
|
||||
apiUrl: "http://localhost:3000",
|
||||
endpoint: "http://localhost:3007/__trigger/entry",
|
||||
logLevel: "debug",
|
||||
longLine: "This is a long line that will scroll off the edge of the screen and cause a horizontal scrollbar",
|
||||
onLog: (log) => {
|
||||
console.log(log);
|
||||
},
|
||||
onLogError: (log) => {
|
||||
console.error(log);
|
||||
},
|
||||
onLogWarning: (log) => {
|
||||
console.warn(log);
|
||||
},
|
||||
onLogInfo: (log) => {
|
||||
console.info(log);
|
||||
},
|
||||
});`}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<Header2 className="mb-2">Highlighted range</Header2>
|
||||
<CodeBlock
|
||||
code={`export const client = new TriggerClient("smoke-test", {
|
||||
apiUrl: "http://localhost:3000",
|
||||
endpoint: "http://localhost:3007/__trigger/entry",
|
||||
logLevel: "debug",
|
||||
longLine: "This is a long line that will scroll off the edge of the screen and cause a horizontal scrollbar",
|
||||
onLog: (log) => {
|
||||
console.log(log);
|
||||
},
|
||||
onLogError: (log) => {
|
||||
console.error(log);
|
||||
},
|
||||
onLogWarning: (log) => {
|
||||
console.warn(log);
|
||||
},
|
||||
onLogInfo: (log) => {
|
||||
console.info(log);
|
||||
},
|
||||
});`}
|
||||
highlightedRanges={[
|
||||
[6, 8],
|
||||
[12, 14],
|
||||
]}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -15,11 +15,11 @@ export default function Story() {
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>Are you sure absolutely sure?</DialogTitle>
|
||||
<DialogDescription>
|
||||
This action cannot be undone. This will permanently delete your account and remove
|
||||
your data from our servers.
|
||||
</DialogDescription>
|
||||
</DialogHeader>
|
||||
<DialogDescription>
|
||||
This action cannot be undone. This will permanently delete your account and remove your
|
||||
data from our servers.
|
||||
</DialogDescription>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
</div>
|
||||
|
||||
@@ -1,28 +1,19 @@
|
||||
import { FreePlanUsage } from "~/components/billing/FreePlanUsage";
|
||||
import { MatchedOrganization } from "~/hooks/useOrganizations";
|
||||
import { organizationBillingPath } from "~/utils/pathBuilder";
|
||||
|
||||
const mockOrganization: MatchedOrganization = {
|
||||
id: "mockID",
|
||||
title: "mockTitle",
|
||||
slug: "mockSlug",
|
||||
projects: [
|
||||
{ id: "mockId1", slug: "mockSlug1", name: "mockName1", jobCount: 1 },
|
||||
{ id: "mockId2", slug: "mockSlug2", name: "mockName2", jobCount: 2 },
|
||||
],
|
||||
hasUnconfiguredIntegrations: false,
|
||||
runsEnabled: true,
|
||||
};
|
||||
|
||||
type FreePlanUsageBarProps = {
|
||||
organization: MatchedOrganization;
|
||||
};
|
||||
|
||||
export default function Story({ organization = mockOrganization }: FreePlanUsageBarProps) {
|
||||
export default function Story() {
|
||||
return (
|
||||
<div className="flex h-screen flex-col items-center justify-center p-12">
|
||||
<div className="flex h-screen flex-col items-center justify-center gap-4 p-12">
|
||||
<div className="w-fit">
|
||||
<FreePlanUsage to={organizationBillingPath(organization)} percentage={0.75} />
|
||||
<FreePlanUsage to={""} percentage={0.1} />
|
||||
</div>
|
||||
<div className="w-fit">
|
||||
<FreePlanUsage to={""} percentage={0.5} />
|
||||
</div>
|
||||
<div className="w-fit">
|
||||
<FreePlanUsage to={""} percentage={0.75} />
|
||||
</div>
|
||||
<div className="w-fit">
|
||||
<FreePlanUsage to={""} percentage={1} />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -4,7 +4,7 @@ import { NavBar, PageAccessories, PageTitle } from "~/components/primitives/Page
|
||||
export default function Story() {
|
||||
return (
|
||||
<div className="flex flex-col gap-4 bg-charcoal-800 p-4">
|
||||
<div className="bg-[#0B1018]">
|
||||
<div className="bg-background-bright">
|
||||
<NavBar>
|
||||
<PageTitle title="Organizations" />
|
||||
<PageAccessories>
|
||||
@@ -14,7 +14,7 @@ export default function Story() {
|
||||
</PageAccessories>
|
||||
</NavBar>
|
||||
</div>
|
||||
<div className="bg-[#0B1018]">
|
||||
<div className="bg-background-bright">
|
||||
<NavBar>
|
||||
<PageTitle title="Your Organizations" backButton={{ to: "#", text: "Orgs" }} />
|
||||
<PageAccessories>
|
||||
|
||||
@@ -350,6 +350,10 @@ export function v3RunStreamingPath(
|
||||
return `${v3RunPath(organization, project, run)}/stream`;
|
||||
}
|
||||
|
||||
export function v3ProjectSettingsPath(organization: OrgForPath, project: ProjectForPath) {
|
||||
return `${v3ProjectPath(organization, project)}/settings`;
|
||||
}
|
||||
|
||||
// Integration
|
||||
export function integrationClientPath(organization: OrgForPath, client: IntegrationForPath) {
|
||||
return `${organizationIntegrationsPath(organization)}/${clientParam(client)}`;
|
||||
|
||||
Reference in New Issue
Block a user