72f3a4b128
* v3 subscription endpoints * Use pnpm linked billing package during development * Moved v2 billing components into a subfolder * Select plan using the real data * Improved v3 plan display * Use new api response that doesn’t require a Stripe call * Free flow is working * Added GitHub modal and verified badge * Deleted old request v3 access component/route * Allow setting classes on the Tooltip button * Paid plans working * Redirect from select plan if you’ve got v3 enabled * Loading state improvements * New admin API endpoint to set concurrency across multiple environments * When projects are created, conditionally create staging based on the plan * New billing page working with side menu and stripe portal * Layout, formatting and some plan state improvements/fixes * Don’t show the period if you’re on the free plan * Temporary upgrade callout * Refactored the platform code so it’s easier to call and doesn’t require a isManagedCloud check * Send taskIdentifier to OpenMeter * Side menu * Upgrade prompts * More improvements to the app-wide usage indicators * Early work on usage graphs * Added the usage bar for v3 * Moved code to presenter and now using defer * Added the tasks table to usage * Improved the v3 usage bar if theres’ no usage on a paid plan * If no run data, still render a graph * Usage page errors when defered loading fails * Don’t show the public API key for v3, they’re not used and probably never will be * Improved the upgrade callout and API keys page layout * Only show the “reveal all” toggle if you have environment variables in the table * Replaced Upgrade callout with a more generic InfoPanel component * better panel width * Show conditional upgrade prompts based on plan and number of schedules used * Removed duplicate class * Wider blank state panels for the scheduled page * Wider info panel for the env var page * Blank state now using the info panel * Platform alerts prompt now using the InfoPanel * Deploy blank state uses InfoPanel * Github verified badge padding adjustment * Improved the layout of the page, some style tweaks, organized imports * Better default tooltip style * could be undefined fix * text fix + style updates * Changed the billing icon in the side menu * Billing page layout and style improvements * plan tooltips don’t use dark variant * Don’t highlight the plan on the billing page * Tooltip underlines stand out more * Fixed padding in the PageTitle * Tooltips use the correct cursor * Improved the plan banner on the billing page * Fixed Header1 inconsistent font weight * Fixed issue where input field focus states were being clipped * Fixed large button not having large text size * Added a link to the Get in touch copy and improved the connect to GitHub modal * Select plan page uses the MainCenteredContainer * Better logging from the Loops endpoint because this error finally got hit * Move the ingestion of compute to the platform * Reporting usage of invocations moved to the platform * Get the entitlement before triggering a non-dev task * Contact us enterprise plan button opens the feedback form * Removed Github discussions link from the Feedback panel * Swapped billing icon for credit card * Show a Unlock staging panel on the env var page * Updated staging environment colour * Show a prompt to upgrade to get staging in the new env var modal * Improved the edit env var modal * Implement ability to disable org concurrency * Use common logic for the plans * Use the billing server to get the schedule limits * Some schedules page fixes * More convenient way of getting a limit * Use the new schedule limit * Team member limiting * Made the limit visible on the team page * Limit alerts * Added an index for TaskRun.scheduleId * Remove console.log on schedules page * Added durations to the run table * Tabular numbers * Improved the usage page formatting * Only admins see the compute column on the run table * Include the base cost on the usage stats * Moved the status to the sidebar * Optional table header tooltip * Allow InfoIconTooltips to have customizable content styles * Added a tooltip to the duration header, changed no test to a dash * Removed all references to signing up to v3 from the docs * Switched @trigger.dev/billing to @trigger.dev/platform * Passing up the variant for the InfoIconTooltip * table tooltip max-width fixed * Switch to the published @trigger.dev/platform 1.0.11 * v2 usage page title changed to include “v2" * code theme has a transparent background so it works on any background * duration columns now grouped together nicely at wide screen size * Last duration column fills the width properly * Fix for the per run price being in cents not dollars * Show the total cost with 8 decimal places * Show 8 decimal places in the usage graph tooltip * Moved the UpgradePrompt to the v3 folder * Prepare to use Shadcns chart helpers * Much nicer chart * Small tweaks to the graph * Fix run table col spans for empty/loading messages * We don’t need isManagedCloud in createProject * Hide v3 usage/billing pages if there aren’t v3 projects in your org * Removed unused tooltipStyle * Usage bar now says “Included usage” instead of “Tier limit” if you’re paying * Get the plan/usage data in parallel * The usage page now has a month dropdown and all data is for that calendar month * Ensure the passed date is the 1st of the month * Use the machine presets from the platform package --------- Co-authored-by: James Ritchie <james@jamesritchie.co.uk> Co-authored-by: Eric Allam <eallam@icloud.com>
401 lines
11 KiB
TypeScript
401 lines
11 KiB
TypeScript
import { Clipboard, ClipboardCheck } from "lucide-react";
|
|
import type { Language, PrismTheme } from "prism-react-renderer";
|
|
import { Highlight, Prism } from "prism-react-renderer";
|
|
import { forwardRef, useCallback, useState } from "react";
|
|
import { cn } from "~/utils/cn";
|
|
import { Paragraph } from "../primitives/Paragraph";
|
|
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../primitives/Tooltip";
|
|
|
|
//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
|
|
|
|
async function setup() {
|
|
(typeof global !== "undefined" ? global : window).Prism = Prism;
|
|
//@ts-ignore
|
|
await import("prismjs/components/prism-json");
|
|
//@ts-ignore
|
|
await import("prismjs/components/prism-typescript");
|
|
}
|
|
setup();
|
|
|
|
type CodeBlockProps = {
|
|
/** Code which will be highlighted */
|
|
code: string;
|
|
|
|
/** Programming language that should be highlighted */
|
|
language?: Language;
|
|
|
|
/** Show copy to clipboard button */
|
|
showCopyButton?: boolean;
|
|
|
|
/** Display line numbers */
|
|
showLineNumbers?: boolean;
|
|
|
|
/** Highlight line at given line number with color from theme.colors */
|
|
highlightedRanges?: [number, number][];
|
|
|
|
/** Add/override classes on the overall element */
|
|
className?: string;
|
|
|
|
/** Add/override code theme */
|
|
theme?: PrismTheme;
|
|
|
|
/** Max lines */
|
|
maxLines?: number;
|
|
|
|
/** Whether to show the chrome, if you provide a string it will be used as the title, */
|
|
showChrome?: boolean;
|
|
|
|
/** filename */
|
|
fileName?: string;
|
|
|
|
/** title text for the Title row */
|
|
rowTitle?: string;
|
|
};
|
|
|
|
const dimAmount = 0.5;
|
|
const extraLinesWhenClipping = 0.35;
|
|
|
|
const defaultTheme: PrismTheme = {
|
|
plain: {
|
|
color: "#9C9AF2",
|
|
backgroundColor: "rgba(0, 0, 0, 0)",
|
|
},
|
|
styles: [
|
|
{
|
|
types: ["comment", "prolog", "doctype", "cdata"],
|
|
style: {
|
|
color: "#5F6570",
|
|
},
|
|
},
|
|
{
|
|
types: ["punctuation"],
|
|
style: {
|
|
color: "#878C99",
|
|
},
|
|
},
|
|
{
|
|
types: ["property", "tag", "boolean", "number", "constant", "symbol", "deleted"],
|
|
style: {
|
|
color: "#9B99FF",
|
|
},
|
|
},
|
|
{
|
|
types: ["selector", "attr-name", "string", "char", "builtin", "inserted"],
|
|
style: {
|
|
color: "#AFEC73",
|
|
},
|
|
},
|
|
{
|
|
types: ["operator", "entity", "url"],
|
|
style: {
|
|
color: "#D4D4D4",
|
|
},
|
|
},
|
|
{
|
|
types: ["variable"],
|
|
style: {
|
|
color: "#CCCBFF",
|
|
},
|
|
},
|
|
{
|
|
types: ["atrule", "attr-value", "keyword"],
|
|
style: {
|
|
color: "#E888F8",
|
|
},
|
|
},
|
|
{
|
|
types: ["function", "class-name"],
|
|
style: {
|
|
color: "#D9F07C",
|
|
},
|
|
},
|
|
{
|
|
types: ["regex"],
|
|
style: {
|
|
color: "#d16969",
|
|
},
|
|
},
|
|
{
|
|
types: ["important", "bold"],
|
|
style: {
|
|
fontWeight: "bold",
|
|
},
|
|
},
|
|
{
|
|
types: ["italic"],
|
|
style: {
|
|
fontStyle: "italic",
|
|
},
|
|
},
|
|
{
|
|
types: ["namespace"],
|
|
style: {
|
|
opacity: 0.7,
|
|
},
|
|
},
|
|
{
|
|
types: ["deleted"],
|
|
style: {
|
|
color: "#F85149",
|
|
},
|
|
},
|
|
{
|
|
types: ["boolean"],
|
|
style: {
|
|
color: "#9B99FF",
|
|
},
|
|
},
|
|
{
|
|
types: ["char"],
|
|
style: {
|
|
color: "#b5cea8",
|
|
},
|
|
},
|
|
{
|
|
types: ["tag"],
|
|
style: {
|
|
color: "#D7BA7D",
|
|
},
|
|
},
|
|
{
|
|
types: ["keyword.operator"],
|
|
style: {
|
|
color: "#8271ED",
|
|
},
|
|
},
|
|
{
|
|
types: ["meta.template.expression"],
|
|
style: {
|
|
color: "#d4d4d4",
|
|
},
|
|
},
|
|
],
|
|
};
|
|
|
|
export const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(
|
|
(
|
|
{
|
|
showCopyButton = true,
|
|
showLineNumbers = true,
|
|
highlightedRanges,
|
|
code,
|
|
className,
|
|
language = "typescript",
|
|
theme = defaultTheme,
|
|
maxLines,
|
|
showChrome = false,
|
|
fileName,
|
|
rowTitle,
|
|
...props
|
|
}: CodeBlockProps,
|
|
ref
|
|
) => {
|
|
const [mouseOver, setMouseOver] = useState(false);
|
|
const [copied, setCopied] = useState(false);
|
|
const onCopied = useCallback(
|
|
(event: React.MouseEvent<HTMLButtonElement>) => {
|
|
event.preventDefault();
|
|
event.stopPropagation();
|
|
navigator.clipboard.writeText(code);
|
|
setCopied(true);
|
|
setTimeout(() => {
|
|
setCopied(false);
|
|
}, 1500);
|
|
},
|
|
[code]
|
|
);
|
|
|
|
code = code.trim();
|
|
const lineCount = code.split("\n").length;
|
|
const maxLineWidth = lineCount.toString().length;
|
|
let maxHeight: string | undefined = undefined;
|
|
if (maxLines && lineCount > maxLines) {
|
|
maxHeight = `calc(${(maxLines + extraLinesWhenClipping) * 0.75 * 1.625}rem + 1.5rem )`;
|
|
}
|
|
|
|
const highlightLines = highlightedRanges?.flatMap(([start, end]) =>
|
|
Array.from({ length: end - start + 1 }, (_, i) => start + i)
|
|
);
|
|
|
|
// if there are more than 1000 lines, don't highlight
|
|
const shouldHighlight = lineCount <= 1000;
|
|
|
|
return (
|
|
<div
|
|
className={cn("relative overflow-hidden rounded-md border border-grid-bright", className)}
|
|
style={{
|
|
backgroundColor: theme.plain.backgroundColor,
|
|
}}
|
|
ref={ref}
|
|
{...props}
|
|
translate="no"
|
|
>
|
|
{showChrome && <Chrome title={fileName} />}
|
|
{rowTitle && <TitleRow title={rowTitle} />}
|
|
{showCopyButton && (
|
|
<TooltipProvider>
|
|
<Tooltip open={copied || mouseOver}>
|
|
<TooltipTrigger
|
|
onClick={onCopied}
|
|
onMouseEnter={() => setMouseOver(true)}
|
|
onMouseLeave={() => setMouseOver(false)}
|
|
className={cn(
|
|
"absolute right-3 z-50 transition-colors duration-100 hover:cursor-pointer",
|
|
showChrome ? "top-10" : "top-3",
|
|
copied ? "text-emerald-500" : "text-charcoal-500 hover:text-charcoal-300"
|
|
)}
|
|
>
|
|
{copied ? (
|
|
<ClipboardCheck className="h-5 w-5" />
|
|
) : (
|
|
<Clipboard className="h-5 w-5" />
|
|
)}
|
|
</TooltipTrigger>
|
|
<TooltipContent side="left" className="text-xs">
|
|
{copied ? "Copied" : "Copy"}
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
</TooltipProvider>
|
|
)}
|
|
|
|
{shouldHighlight ? (
|
|
<Highlight theme={theme} code={code} language={language}>
|
|
{({
|
|
className: inheritedClassName,
|
|
style: inheritedStyle,
|
|
tokens,
|
|
getLineProps,
|
|
getTokenProps,
|
|
}) => (
|
|
<div
|
|
dir="ltr"
|
|
className="overflow-auto px-2 py-3 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600"
|
|
style={{
|
|
maxHeight,
|
|
}}
|
|
>
|
|
<pre
|
|
className={cn(
|
|
"relative mr-2 font-mono text-xs leading-relaxed",
|
|
inheritedClassName
|
|
)}
|
|
style={inheritedStyle}
|
|
dir="ltr"
|
|
>
|
|
{tokens
|
|
.map((line, index) => {
|
|
if (
|
|
index === tokens.length - 1 &&
|
|
line.length === 1 &&
|
|
line[0].content === "\n"
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
const lineNumber = index + 1;
|
|
const lineProps = getLineProps({ line, key: index });
|
|
|
|
let hasAnyHighlights = highlightLines ? highlightLines.length > 0 : false;
|
|
|
|
let shouldDim = hasAnyHighlights;
|
|
if (hasAnyHighlights && highlightLines?.includes(lineNumber)) {
|
|
shouldDim = false;
|
|
}
|
|
|
|
return (
|
|
<div
|
|
key={lineNumber}
|
|
{...lineProps}
|
|
className={cn(
|
|
"flex w-full justify-start transition-opacity duration-500",
|
|
lineProps.className
|
|
)}
|
|
style={{
|
|
opacity: shouldDim ? dimAmount : undefined,
|
|
...lineProps.style,
|
|
}}
|
|
>
|
|
{showLineNumbers && (
|
|
<div
|
|
className={
|
|
"mr-2 flex-none select-none text-right text-charcoal-500 transition-opacity duration-500"
|
|
}
|
|
style={{
|
|
width: `calc(8 * ${maxLineWidth / 16}rem)`,
|
|
}}
|
|
>
|
|
{lineNumber}
|
|
</div>
|
|
)}
|
|
|
|
<div className="flex-1">
|
|
{line.map((token, key) => {
|
|
const tokenProps = getTokenProps({ token, key });
|
|
return (
|
|
<span
|
|
key={key}
|
|
{...tokenProps}
|
|
style={{
|
|
color: tokenProps?.style?.color as string,
|
|
...tokenProps.style,
|
|
}}
|
|
/>
|
|
);
|
|
})}
|
|
</div>
|
|
<div className="w-4 flex-none" />
|
|
</div>
|
|
);
|
|
})
|
|
.filter(Boolean)}
|
|
</pre>
|
|
</div>
|
|
)}
|
|
</Highlight>
|
|
) : (
|
|
<div
|
|
dir="ltr"
|
|
className="overflow-auto px-2 py-3 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-charcoal-600"
|
|
style={{
|
|
maxHeight,
|
|
}}
|
|
>
|
|
<pre className="relative mr-2 p-2 font-mono text-xs leading-relaxed" dir="ltr">
|
|
{code}
|
|
</pre>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|
|
);
|
|
|
|
CodeBlock.displayName = "CodeBlock";
|
|
|
|
function Chrome({ title }: { title?: string }) {
|
|
return (
|
|
<div className="grid h-7 grid-cols-[100px_auto_100px] border-b border-charcoal-800 bg-charcoal-900">
|
|
<div className="ml-2 flex items-center gap-2">
|
|
<div className="h-3 w-3 rounded-full bg-charcoal-700" />
|
|
<div className="h-3 w-3 rounded-full bg-charcoal-700" />
|
|
<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}</div>
|
|
</div>
|
|
<div></div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export 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>
|
|
);
|
|
}
|