Project-wide Prettier setup (#237)

* Setup project-wide prettier

* Remove old workspace file

* Remove old debugging directives

* New top-level .prettierignore

* Updated Prettier config settings

* Contrubuting guide: Fix for some bad code blocks

* Added more ignores

* Improved the format script command

* printWidth set to 100

* Formatted entire repo (pnpm run format)
This commit is contained in:
Matt Aitken
2023-08-01 10:21:22 +01:00
committed by GitHub
parent 36ec16babe
commit 9ff4c0dbd5
388 changed files with 3285 additions and 7698 deletions
+1 -1
View File
@@ -1 +1 @@
{"workspaceId":"63e5e42daf9a537ba8d9503c"}
{ "workspaceId": "63e5e42daf9a537ba8d9503c" }
+11
View File
@@ -0,0 +1,11 @@
node_modules
.env
.env.local
pnpm-lock.yaml
tailwind.css
.babelrc.json
**/.react-email/
**/storybook-static/
**/.changeset/
**/build/
**/dist/
+1 -8
View File
@@ -5,19 +5,12 @@
"version": "0.2.0",
"configurations": [
{
"command": "pnpm run idev --filter webapp",
"command": "pnpm run dev --filter webapp",
"name": "Run webapp",
"request": "launch",
"type": "node-terminal",
"cwd": "${workspaceFolder}"
},
{
"command": "pnpm run dev --filter integrations",
"name": "Run integrations",
"request": "launch",
"type": "node-terminal",
"cwd": "${workspaceFolder}"
},
{
"type": "chrome",
"request": "launch",
-19
View File
@@ -1,19 +0,0 @@
{
"folders": [
{
"name": "trigger.dev",
"path": "../"
},
{
"name": "Webapp",
"path": "../apps/webapp"
},
{
"name": "Integrations",
"path": "../apps/integrations"
}
],
"settings": {
"vitest.commandLine": "pnpm exec vitest"
}
}
+51 -41
View File
@@ -81,15 +81,20 @@ branch are tagged into a release monthly.
2. Once the app is running click the magic link button and enter your email.
3. Check your terminal, the magic link email should have printed out as following:
`webapp:dev: Log in to Trigger.dev
webapp:dev:
webapp:dev: Click here to log in with this magic link
webapp:dev: [http://localhost:3030/magic?token=U2FsdGVkX18OvB0JxgaswTLCSbaRz%2FY82TN0EZWhSzFyZYwgG%2BIzKVTkeiaOtWfotPw7F8RwFzCHh53aBpMEu%2B%2B%2FItb%2FcJYh89MSjc3Pz92bevoEjqxSQ%2Ff%2BZbks09JOpqlBbYC3FzGWC8vuSVFBlxqLXxteSDLthZSUaC%2BS2LaA%2BJgp%2BLO7hgjAaC2lXbCHrM7MTgTdXOFt7i0Dvvuwz6%2BWY25RnfomZOPqDsyH0xz8Q2rzPTz0Xu53WSXrZ1hd]
webapp:dev:
webapp:dev: If you didn't try to log in, you can safely ignore this email.`
4. Paste the magic link shown in your terminal into your browser to login.
```sh
webapp:dev: Log in to Trigger.dev
webapp:dev:
webapp:dev: Click here to log in with this magic link
webapp:dev: [http://localhost:3030/magic?token=U2FsdGVkX18OvB0JxgaswTLCSbaRz%2FY82TN0EZWhSzFyZYwgG%2BIzKVTkeiaOtWfotPw7F8RwFzCHh53aBpMEu%2B%2B%2FItb%2FcJYh89MSjc3Pz92bevoEjqxSQ%2Ff%2BZbks09JOpqlBbYC3FzGWC8vuSVFBlxqLXxteSDLthZSUaC%2BS2LaA%2BJgp%2BLO7hgjAaC2lXbCHrM7MTgTdXOFt7i0Dvvuwz6%2BWY25RnfomZOPqDsyH0xz8Q2rzPTz0Xu53WSXrZ1hd]
webapp:dev:
webapp:dev: If you didn't try to log in, you can safely ignore this email.
```
Paste the magic link shown in your terminal into your browser to login.
## Adding and running migrations
1. Modify packages/database/prisma/schema.prisma file
2. Change directory to the packages/database folder
```sh
@@ -100,6 +105,7 @@ webapp:dev: If you didn't try to log in, you can safely ignore this email.`
```sh
pnpm run generate
```
The above updates the prisma client generated into node_modules/.prisma/client folder. This helps with typing of relevant prisma models. It ensures typescript
recognizes fields added or removed from a model and type-checks appropriately.
@@ -108,6 +114,7 @@ webapp:dev: If you didn't try to log in, you can safely ignore this email.`
```
pnpm run db:migrate:dev
```
This creates a migration file and executes the migrations against your database and applies changes to the database schema(s)
5. Commit generated migrations as well as changes to the schema.prisma file
@@ -119,44 +126,44 @@ To test CLI changes, follow the steps below:
1. Build the CLI and watch for changes
```
cd packages/cli
pnpm run dev
```
```sh
cd packages/cli
pnpm run dev
```
2. Open a new Terminal window and run the webapp locally and then create a new project in the dashboard. Copy out the dev API key.
3. Create a new temporary Next.js app in examples directory
```
pnpm create next-app@latest
```
```sh
pnpm create next-app@latest
```
Follow the prompts to create a TypeScript project using the App Directory.
Follow the prompts to create a TypeScript project using the App Directory.
4. Then once that's finished, add the `@trigger.dev/cli` to the `devDependencies` of the newly created Next.js app's `package.json` file, like so:
```
{
"devDependencies": { "@trigger.dev/cli": "workspace:*" }
}
```
```json
{
"devDependencies": { "@trigger.dev/cli": "workspace:*" }
}
```
5. Open a new terminal window, navigate into the example, and initialize the CLI:
```
cd examples/your-newly-created-nextjs-project
pnpm i
pnpm exec trigger-cli init
```
```sh
cd examples/your-newly-created-nextjs-project
pnpm i
pnpm exec trigger-cli init
```
6. When prompted, select `self-hosted` and enter `localhost:3030` for your local version of the webapp. When asked for an API key, use the key you copied earlier.
7. Run the CLI
```
pnpm exec trigger-cli dev
```
```sh
pnpm exec trigger-cli dev
```
8. After running the CLI, start your newly created Next.js project. You should now be able to see the changes.
@@ -170,19 +177,19 @@ The [examples/jobs-starter](./examples/jobs-starter/) project defines simple job
2. Create a `.env.local` file with the following content,
replacing `[TRIGGER_DEV_API_KEY]` with an actual key:
```
TRIGGER_API_KEY=[TRIGGER_DEV_API_KEY]
TRIGGER_API_URL=http://localhost:3030
```
```env
TRIGGER_API_KEY=[TRIGGER_DEV_API_KEY]
TRIGGER_API_URL=http://localhost:3030
```
`TRIGGER_API_URL` is used to configure the URL for your Trigger.dev instance,
where the jobs will be registered.
`TRIGGER_API_URL` is used to configure the URL for your Trigger.dev instance,
where the jobs will be registered.
3. Run the `jobs-starter` app:
```
pnpm dev
```
```sh
pnpm dev
```
4. Navigate to your trigger.dev instance ([http://localhost:3030](http://localhost:3030/)), to see the jobs.
You can use the test feature to trigger them.
@@ -204,7 +211,7 @@ If you are contributing a change to any packages in this monorepo (anything in e
To add a changeset, run the following command in the root of the repo
```shell
```sh
pnpm run changeset:add
```
@@ -221,15 +228,18 @@ Most of the time the changes you'll make are likely to be categorized as patch r
### EADDRINUSE: address already in use :::3030
When receiving the following error message:
`webapp:dev: Error: listen EADDRINUSE: address already in use :::3030`
```sh
webapp:dev: Error: listen EADDRINUSE: address already in use :::3030
```
The process running on port `3030` should be destroyed.
1. Get the `PID` of the process running on PORT `3030`
```
```sh
lsof -i :3030
```
2. Kill the process
```
```sh
sudo kill -9 <PID>
```
+3 -2
View File
@@ -11,9 +11,11 @@
[![Twitter](https://img.shields.io/twitter/url/https/twitter.com/triggerdotdev.svg?style=social&label=Follow%20%40trigger.dev)](https://twitter.com/triggerdotdev)
[![GitHub Repo stars](https://img.shields.io/github/stars/triggerdotdev/trigger.dev?style=social)](https://github.com/triggerdotdev/trigger.dev)
</div>
# About Trigger.dev
Create long-running jobs directly in your codebase with features like API integrations, webhooks, scheduling and delays.
### Long running Jobs on serverless
@@ -60,5 +62,4 @@ We provide an official trigger.dev docker image you can use to easily self-host
## Development
To setup and develop locally or contribute to the open source project, follow our [developement guide](./CONTRIBUTING.md).
To setup and develop locally or contribute to the open source project, follow our [developement guide](./CONTRIBUTING.md).
+1 -5
View File
@@ -1,9 +1,5 @@
{
"extends": [
"@remix-run/eslint-config",
"@remix-run/eslint-config/node",
"prettier"
],
"extends": ["@remix-run/eslint-config", "@remix-run/eslint-config/node", "prettier"],
"rules": {
"@typescript-eslint/strict-boolean-expressions": [
"error",
+1 -4
View File
@@ -20,10 +20,7 @@ const config: StorybookConfig = {
},
};
},
stories: [
"../app/**/stories/*.mdx",
"../app/**/stories/*.stories.@(js|jsx|ts|tsx)",
],
stories: ["../app/**/stories/*.mdx", "../app/**/stories/*.stories.@(js|jsx|ts|tsx)"],
addons: [
"@storybook/addon-links",
"@storybook/addon-essentials",
+1 -3
View File
@@ -35,9 +35,7 @@ const preview: Preview = {
return (
<OperatingSystemContextProvider platform="mac">
<LocaleContextProvider
locales={window.navigator.languages as string[]}
>
<LocaleContextProvider locales={window.navigator.languages as string[]}>
<RemixStub initialEntries={["/"]} />
</LocaleContextProvider>
</OperatingSystemContextProvider>
@@ -1,11 +1,6 @@
export function DynamicTriggerIcon({ className }: { className?: string }) {
return (
<svg
viewBox="0 0 21 21"
fill="none"
xmlns="http://www.w3.org/2000/svg"
className={className}
>
<svg viewBox="0 0 21 21" fill="none" xmlns="http://www.w3.org/2000/svg" className={className}>
<path
fillRule="evenodd"
clipRule="evenodd"
+1 -6
View File
@@ -1,11 +1,6 @@
export function ErrorIcon({ className }: { className?: string }) {
return (
<svg
className={className}
viewBox="0 0 20 20"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<svg className={className} viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="10" cy="10" r="7" fill="white" />
<path
fillRule="evenodd"
+1 -6
View File
@@ -1,11 +1,6 @@
export function WebhookIcon({ className }: { className?: string }) {
return (
<svg
className={className}
viewBox="0 0 190 180"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<svg className={className} viewBox="0 0 190 180" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
fill="currentColor"
fillRule="evenodd"
+1 -4
View File
@@ -16,10 +16,7 @@ export function ComingSoon({ title, description, icon }: ComingSoonProps) {
<div className="flex flex-col justify-center rounded border border-slate-800 bg-slate-850">
<div className="flex items-center gap-2 border-b border-slate-750 px-4 py-4">
{typeof icon === "string" ? (
<NamedIconInBox
name={icon}
className={"h-10 w-10 bg-midnight-800"}
/>
<NamedIconInBox name={icon} className={"h-10 w-10 bg-midnight-800"} />
) : (
icon
)}
+3 -12
View File
@@ -19,19 +19,14 @@ export function RouteErrorDisplay(options?: ErrorDisplayOptions) {
<ErrorDisplay
title={friendlyErrorDisplay(error.status, error.statusText).title}
message={
error.data.message ??
friendlyErrorDisplay(error.status, error.statusText).message
error.data.message ?? friendlyErrorDisplay(error.status, error.statusText).message
}
{...options}
/>
) : error instanceof Error ? (
<ErrorDisplay title={error.name} message={error.message} {...options} />
) : (
<ErrorDisplay
title="Oops"
message={JSON.stringify(error)}
{...options}
/>
<ErrorDisplay title="Oops" message={JSON.stringify(error)} {...options} />
)}
</>
);
@@ -47,11 +42,7 @@ export function ErrorDisplay({ title, message, button }: DisplayOptionsProps) {
<div className="p-4">
<Header1 className="mb-4 border-b border-slate-800 pb-4">{title}</Header1>
{message && <Header3>{message}</Header3>}
<LinkButton
to={button ? button.to : "/"}
variant="primary/medium"
className="mt-8"
>
<LinkButton to={button ? button.to : "/"} variant="primary/medium" className="mt-8">
{button ? button.title : "Home"}
</LinkButton>
</div>
+8 -31
View File
@@ -1,17 +1,8 @@
import { conform, useForm } from "@conform-to/react";
import { parse } from "@conform-to/zod";
import {
Form,
useActionData,
useLocation,
useNavigation,
} from "@remix-run/react";
import { Form, useActionData, useLocation, useNavigation } from "@remix-run/react";
import { ReactNode, useState } from "react";
import {
FeedbackType,
feedbackTypeLabel,
schema,
} from "~/routes/resources.feedback";
import { FeedbackType, feedbackTypeLabel, schema } from "~/routes/resources.feedback";
import { Button } from "./primitives/Buttons";
import { Fieldset } from "./primitives/Fieldset";
import { FormButtons } from "./primitives/FormButtons";
@@ -27,13 +18,7 @@ import {
SelectTrigger,
SelectValue,
} from "./primitives/Select";
import {
Sheet,
SheetBody,
SheetContent,
SheetHeader,
SheetTrigger,
} from "./primitives/Sheet";
import { Sheet, SheetBody, SheetContent, SheetHeader, SheetTrigger } from "./primitives/Sheet";
import { TextArea } from "./primitives/TextArea";
type FeedbackProps = {
@@ -71,22 +56,16 @@ export function Feedback({ button, defaultValue = "bug" }: FeedbackProps) {
<SheetHeader className="justify-between">Help & feedback</SheetHeader>
<SheetBody>
<Paragraph variant="small" className="mb-4">
Use this form to ask for help or give us feedback. We read every
message and will get back to you as soon as we can.
Use this form to ask for help or give us feedback. We read every message and will get
back to you as soon as we can.
</Paragraph>
<Form method="post" action="/resources/feedback" {...form.props}>
<Fieldset>
<input
value={location.pathname}
{...conform.input(path, { type: "hidden" })}
/>
<input value={location.pathname} {...conform.input(path, { type: "hidden" })} />
<InputGroup>
<Label>How can we help?</Label>
<SelectGroup>
<Select
{...conform.input(feedbackType)}
defaultValue={defaultValue}
>
<Select {...conform.input(feedbackType)} defaultValue={defaultValue}>
<SelectTrigger size="medium" width="full">
<SelectValue placeholder="Type" />
</SelectTrigger>
@@ -99,9 +78,7 @@ export function Feedback({ button, defaultValue = "bug" }: FeedbackProps) {
</SelectContent>
</Select>
</SelectGroup>
<FormError id={feedbackType.errorId}>
{feedbackType.error}
</FormError>
<FormError id={feedbackType.errorId}>{feedbackType.error}</FormError>
</InputGroup>
<InputGroup>
<Label>Message</Label>
@@ -2,17 +2,12 @@ import { Form } from "@remix-run/react";
import { Paragraph } from "./primitives/Paragraph";
import { Button } from "./primitives/Buttons";
export function ImpersonationBanner({
impersonationId,
}: {
impersonationId: string;
}) {
export function ImpersonationBanner({ impersonationId }: { impersonationId: string }) {
return (
<div className="grid h-8 grid-cols-3 items-center bg-gradient-to-r from-amber-500 via-amber-400 to-amber-500 px-4 text-center">
<span></span>
<Paragraph variant="small">
You are impersonating{" "}
<span className="font-semibold">{impersonationId}</span>
You are impersonating <span className="font-semibold">{impersonationId}</span>
</Paragraph>
<Form
action="/resources/impersonation"
@@ -10,9 +10,7 @@ export function NoMobileOverlay() {
<div className="absolute mx-8 flex flex-col items-center gap-6 rounded-lg bg-slate-850 px-8 py-10 text-center">
<XMarkIcon className="absolute top-14 h-8 w-8 text-rose-600" />
<DevicePhoneMobileIcon className="h-16 w-16 text-slate-500" />
<Paragraph>
Trigger.dev is currently only available on desktop.
</Paragraph>
<Paragraph>Trigger.dev is currently only available on desktop.</Paragraph>
<LinkButton to="https://trigger.dev" variant="primary/medium">
Back Home
</LinkButton>
@@ -4,13 +4,7 @@ import { cn } from "~/utils/cn";
export function UserProfilePhoto({ className }: { className?: string }) {
const user = useOptionalUser();
return (
<UserAvatar
avatarUrl={user?.avatarUrl}
name={user?.name}
className={className}
/>
);
return <UserAvatar avatarUrl={user?.avatarUrl} name={user?.name} className={className} />;
}
export function UserAvatar({
+8 -33
View File
@@ -2,16 +2,8 @@ import type { Language, PrismTheme } from "prism-react-renderer";
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 {
ClipboardDocumentCheckIcon,
ClipboardIcon,
} from "@heroicons/react/24/solid";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "../primitives/Tooltip";
import { ClipboardDocumentCheckIcon, ClipboardIcon } from "@heroicons/react/24/solid";
import { Clipboard, ClipboardCheck, ClipboardCheckIcon } from "lucide-react";
//This is a fork of https://github.com/mantinedev/mantine/blob/master/src/mantine-prism/src/Prism/Prism.tsx
@@ -193,9 +185,7 @@ export const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(
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 )`;
maxHeight = `calc(${(maxLines + extraLinesWhenClipping) * 0.75 * 1.625}rem + 1.5rem )`;
}
const highlightLines = highlightedRanges?.flatMap(([start, end]) =>
@@ -204,10 +194,7 @@ export const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(
return (
<div
className={cn(
"relative overflow-hidden rounded-md border border-slate-800",
className
)}
className={cn("relative overflow-hidden rounded-md border border-slate-800", className)}
style={{
backgroundColor: theme.plain.backgroundColor,
}}
@@ -226,9 +213,7 @@ export const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(
className={cn(
"absolute right-3 z-50 transition-colors duration-100 hover:cursor-pointer",
showChrome ? "top-10" : "top-3",
copied
? "text-emerald-500"
: "text-slate-500 hover:text-slate-300"
copied ? "text-emerald-500" : "text-slate-500 hover:text-slate-300"
)}
>
{copied ? (
@@ -244,12 +229,7 @@ export const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(
</TooltipProvider>
)}
<Highlight
{...defaultProps}
theme={theme}
code={code}
language={language}
>
<Highlight {...defaultProps} theme={theme} code={code} language={language}>
{({
className: inheritedClassName,
style: inheritedStyle,
@@ -285,15 +265,10 @@ export const CodeBlock = forwardRef<HTMLDivElement, CodeBlockProps>(
const lineNumber = index + 1;
const lineProps = getLineProps({ line, key: index });
let hasAnyHighlights = highlightLines
? highlightLines.length > 0
: false;
let hasAnyHighlights = highlightLines ? highlightLines.length > 0 : false;
let shouldDim = hasAnyHighlights;
if (
hasAnyHighlights &&
highlightLines?.includes(lineNumber)
) {
if (hasAnyHighlights && highlightLines?.includes(lineNumber)) {
shouldDim = false;
}
@@ -1,9 +1,6 @@
import { json as jsonLang } from "@codemirror/lang-json";
import type { ViewUpdate } from "@codemirror/view";
import type {
ReactCodeMirrorProps,
UseCodeMirror,
} from "@uiw/react-codemirror";
import type { ReactCodeMirrorProps, UseCodeMirror } from "@uiw/react-codemirror";
import { useCodeMirror } from "@uiw/react-codemirror";
import { useRef, useEffect } from "react";
import { getEditorSetup } from "./codeMirrorSetup";
@@ -30,10 +30,7 @@ export function getViewerSetup(): Array<Extension> {
return [drawSelection(), dropCursor(), bracketMatching(), lineNumbers()];
}
export function getEditorSetup(
showLineNumbers = true,
showHighlights = true
): Array<Extension> {
export function getEditorSetup(showLineNumbers = true, showHighlights = true): Array<Extension> {
const options = [
drawSelection(),
dropCursor(),
@@ -36,8 +36,9 @@ export function darkTheme(): Extension {
},
".cm-cursor, .cm-dropCursor": { borderLeftColor: cursor },
"&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection":
{ backgroundColor: selection },
"&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection": {
backgroundColor: selection,
},
".cm-panels": { backgroundColor: darkBackground, color: ivory },
".cm-panels.cm-panels-top": { borderBottom: "2px solid black" },
@@ -101,13 +102,7 @@ export function darkTheme(): Extension {
const jsonHeroHighlightStyle = HighlightStyle.define([
{ tag: tags.keyword, color: violet },
{
tag: [
tags.name,
tags.deleted,
tags.character,
tags.propertyName,
tags.macroName,
],
tag: [tags.name, tags.deleted, tags.character, tags.propertyName, tags.macroName],
color: coral,
},
{ tag: [tags.function(tags.variableName), tags.labelName], color: malibu },
@@ -194,8 +189,9 @@ export function lightTheme(): Extension[] {
},
".cm-cursor, .cm-dropCursor": { borderLeftColor: cursor },
"&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection":
{ backgroundColor: selection },
"&.cm-focused .cm-selectionBackground, .cm-selectionBackground, .cm-content ::selection": {
backgroundColor: selection,
},
".cm-panels": { backgroundColor: darkBackground, color: ivory },
".cm-panels.cm-panels-top": { borderBottom: "2px solid black" },
@@ -259,13 +255,7 @@ export function lightTheme(): Extension[] {
const jsonHeroHighlightStyle = tagHighlighter([
{ tag: tags.keyword, class: violet },
{
tag: [
tags.name,
tags.deleted,
tags.character,
tags.propertyName,
tags.macroName,
],
tag: [tags.name, tags.deleted, tags.character, tags.propertyName, tags.macroName],
class: variableColor,
},
{
@@ -34,10 +34,7 @@ export function HowToSetupYourProject() {
const appOrigin = useAppOrigin();
return (
<>
<StepNumber
stepNumber="1"
title="Run the CLI 'init' command in a Next.js project"
/>
<StepNumber stepNumber="1" title="Run the CLI 'init' command in a Next.js project" />
<StepContentContainer>
<ClientTabs defaultValue="npm">
<ClientTabsList>
@@ -72,9 +69,8 @@ export function HowToSetupYourProject() {
</ClientTabs>
<Paragraph spacing>
Youll notice a new folder in your project called 'jobs'. Weve added
a very simple example Job in <InlineCode>examples.ts</InlineCode> to
help you get started.
Youll notice a new folder in your project called 'jobs'. Weve added a very simple
example Job in <InlineCode>examples.ts</InlineCode> to help you get started.
</Paragraph>
</StepContentContainer>
<StepNumber stepNumber="2" title="Run your Next.js app" />
@@ -87,36 +83,20 @@ export function HowToSetupYourProject() {
<ClientTabsTrigger value={"yarn"}>yarn</ClientTabsTrigger>
</ClientTabsList>
<ClientTabsContent value={"npm"}>
<ClipboardField
variant="primary/medium"
className="mb-4"
value={`npm run dev`}
/>
<ClipboardField variant="primary/medium" className="mb-4" value={`npm run dev`} />
</ClientTabsContent>
<ClientTabsContent value={"pnpm"}>
<ClipboardField
variant="primary/medium"
className="mb-4"
value={`pnpm run dev`}
/>
<ClipboardField variant="primary/medium" className="mb-4" value={`pnpm run dev`} />
</ClientTabsContent>
<ClientTabsContent value={"yarn"}>
<ClipboardField
variant="primary/medium"
className="mb-4"
value={`yarn run dev`}
/>
<ClipboardField variant="primary/medium" className="mb-4" value={`yarn run dev`} />
</ClientTabsContent>
</ClientTabs>
</StepContentContainer>
<StepNumber stepNumber="3" title="Run the CLI 'dev' command" />
<StepContentContainer>
<Paragraph spacing>
In a{" "}
<strong className="text-bright">
separate terminal window or tab
</strong>{" "}
run:
In a <strong className="text-bright">separate terminal window or tab</strong> run:
</Paragraph>
<ClientTabs defaultValue="npm">
<ClientTabsList>
@@ -151,15 +131,13 @@ export function HowToSetupYourProject() {
<InlineCode>--port 3001</InlineCode> to the end.
</Paragraph>
<Paragraph spacing variant="small">
You should leave the <InlineCode>dev</InlineCode> command running when
you're developing.
You should leave the <InlineCode>dev</InlineCode> command running when you're developing.
</Paragraph>
</StepContentContainer>
<StepNumber stepNumber="4" title="Check for Jobs" />
<StepContentContainer>
<Paragraph>
Once you've run the CLI command, click Refresh to view your example
Job in the list.
Once you've run the CLI command, click Refresh to view your example Job in the list.
</Paragraph>
<Button
variant="primary/medium"
@@ -183,8 +161,8 @@ export function HowToRunYourJob() {
<>
<Callout variant="info" className="mb-6">
<Paragraph variant={"small"} className={variantClasses.info.textColor}>
Scheduled Triggers <strong>do not</strong> trigger Jobs in the DEV
Environment. When developing locally you should use the{" "}
Scheduled Triggers <strong>do not</strong> trigger Jobs in the DEV Environment. When
developing locally you should use the{" "}
<Link
to={jobTestPath(organization, project, job)}
className="underline underline-offset-2 transition hover:text-blue-100"
@@ -200,8 +178,8 @@ export function HowToRunYourJob() {
<StepNumber stepNumber="1" 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.
You can perform a Run with any payload you want, or use one of our examples, on the test
page.
</Paragraph>
<LinkButton
to={jobTestPath(organization, project, job)}
@@ -216,8 +194,7 @@ export function HowToRunYourJob() {
<StepNumber stepNumber="2" title="Trigger your Job for real" />
<StepContentContainer>
<Paragraph spacing>
Performing a real run depends on the type of Trigger your Job is
using.
Performing a real run depends on the type of Trigger your Job is using.
</Paragraph>
<LinkButton
@@ -242,30 +219,22 @@ export function HowToRunATest() {
"
/>
<StepContentContainer>
<Paragraph spacing>
Select the environment youd like the test to run against.
</Paragraph>
<Paragraph spacing>Select the environment youd like the test to run against.</Paragraph>
<img src={selectEnvironment} className="mt-2 w-52" />
</StepContentContainer>
<StepNumber stepNumber="2" title="Write your test payload" />
<StepContentContainer>
<Paragraph spacing>
Write your own payload specific to your Job. Some Triggers also
provide example payloads that you can select from. This will populate
the code editor below.
Write your own payload specific to your Job. Some Triggers also provide example payloads
that you can select from. This will populate the code editor below.
</Paragraph>
<img src={selectExample} className="mt-2 h-40" />
</StepContentContainer>
<StepNumber stepNumber="3" title="Run your test" />
<StepContentContainer>
<Paragraph spacing>
When youre happy with the payload, click Run test.
</Paragraph>
<Paragraph spacing>When youre happy with the payload, click Run test.</Paragraph>
</StepContentContainer>
<Callout
variant="docs"
to="https://trigger.dev/docs/documentation/guides/testing-jobs"
>
<Callout variant="docs" to="https://trigger.dev/docs/documentation/guides/testing-jobs">
Learn more about running tests.
</Callout>
</>
@@ -279,53 +248,38 @@ export function HowToConnectAnIntegration() {
<StepContentContainer>
<Paragraph>
APIs marked with a
<span
className="mx-2 -mt-1 inline-flex"
aria-label="Trigger.dev Integration icon"
>
<span className="mx-2 -mt-1 inline-flex" aria-label="Trigger.dev Integration icon">
<IntegrationIcon />
</span>
are Trigger.dev Integrations. These Integrations make connecting to
the API easier by offering OAuth or API key authentication. All APIs
can also be used with fetch or an SDK.
are Trigger.dev Integrations. These Integrations make connecting to the API easier by
offering OAuth or API key authentication. All APIs can also be used with fetch or an SDK.
</Paragraph>
<img src={integrationButton} className="mt-2 h-10" />
</StepContentContainer>
<StepNumber stepNumber="2" title="Choose how you want to connect" />
<StepContentContainer>
<Paragraph>
Follow the instructions for your chosen connection method in the
popover form. If no Integration exists yet, you can request one by
clicking the "I want an Integration" button.
Follow the instructions for your chosen connection method in the popover form. If no
Integration exists yet, you can request one by clicking the "I want an Integration"
button.
</Paragraph>
</StepContentContainer>
<StepNumber
stepNumber="3"
title="Your connection will appear in the list"
/>
<StepNumber stepNumber="3" title="Your connection will appear in the list" />
<StepContentContainer>
<Paragraph>
Once you've connected your API, it will appear in the list of
Integrations below. You can view details and manage your connection by
selecting it from the table.
Once you've connected your API, it will appear in the list of Integrations below. You can
view details and manage your connection by selecting it from the table.
</Paragraph>
</StepContentContainer>
<Callout
variant={"docs"}
to="https://trigger.dev/docs/integrations/introduction"
>
View the Integration docs page for more information on connecting an API
using an Integration or other method.
<Callout variant={"docs"} to="https://trigger.dev/docs/integrations/introduction">
View the Integration docs page for more information on connecting an API using an
Integration or other method.
</Callout>
</>
);
}
export function HowToUseThisIntegration({
integration,
help,
integrationClient,
}: HelpPanelProps) {
export function HowToUseThisIntegration({ integration, help, integrationClient }: HelpPanelProps) {
return (
<>
<StepNumber stepNumber="1" title="Install the package" />
@@ -352,13 +306,12 @@ export function HowToUseApiKeysAndEndpoints() {
return (
<>
<Paragraph spacing>
Environments and Endpoints are used to connect your server to the
Trigger.dev platform.
Environments and Endpoints are used to connect your server to the Trigger.dev platform.
</Paragraph>
<Header2 spacing>Environments</Header2>
<Paragraph spacing>
Each environment has API Keys associated with it. The Server API Key is
used to authenticate your Jobs with the Trigger.dev platform.
Each environment has API Keys associated with it. The Server API Key is used to authenticate
your Jobs with the Trigger.dev platform.
</Paragraph>
<Paragraph spacing>
The Server API Key you use for your{" "}
@@ -388,14 +341,12 @@ export function HowToUseApiKeysAndEndpoints() {
/>
<StepContentContainer>
<Paragraph>
The <InlineCode>DEV</InlineCode> environment should only be used for
local development. Its where you can test your Jobs before deploying
them to servers.
The <InlineCode>DEV</InlineCode> environment should only be used for local development.
Its where you can test your Jobs before deploying them to servers.
</Paragraph>
<Callout variant="warning" className="my-2">
Scheduled Triggers do not trigger Jobs in the DEV Environment. When
youre working locally you should use the Test feature to trigger any
scheduled Jobs.
Scheduled Triggers do not trigger Jobs in the DEV Environment. When youre working locally
you should use the Test feature to trigger any scheduled Jobs.
</Callout>
</StepContentContainer>
<StepNumber
@@ -409,34 +360,28 @@ export function HowToUseApiKeysAndEndpoints() {
/>
<StepContentContainer>
<Paragraph spacing>
The <InlineCode>PROD</InlineCode> environment is where your Jobs will
run in production. Its where you can run your Jobs against real data.
The <InlineCode>PROD</InlineCode> environment is where your Jobs will run in production.
Its where you can run your Jobs against real data.
</Paragraph>
</StepContentContainer>
<Header2 spacing>Endpoints</Header2>
<Paragraph spacing>
An Endpoint is a URL on your server that Trigger.dev can connect to.
This URL is used to register Jobs, start them and orchestrate runs and
retries.
An Endpoint is a URL on your server that Trigger.dev can connect to. This URL is used to
register Jobs, start them and orchestrate runs and retries.
</Paragraph>
<Paragraph spacing>
<InlineCode>DEV</InlineCode> has multiple endpoints associated with it
one for each team member. This allows each team member to run their own
Jobs, without interfering with each other.
<InlineCode>DEV</InlineCode> has multiple endpoints associated with it one for each team
member. This allows each team member to run their own Jobs, without interfering with each
other.
</Paragraph>
<Paragraph spacing>
All other environments have just a single endpoint (with a single URL)
associated with them.
All other environments have just a single endpoint (with a single URL) associated with them.
</Paragraph>
<Header2 spacing>Deployment</Header2>
<Paragraph spacing>
Deployment uses Environments and Endpoints to connect your Jobs to the
Trigger.dev platform.
Deployment uses Environments and Endpoints to connect your Jobs to the Trigger.dev platform.
</Paragraph>
<Callout
variant="docs"
to="https://trigger.dev/docs/documentation/guides/deployment"
>
<Callout variant="docs" to="https://trigger.dev/docs/documentation/guides/deployment">
Read the deployment guide to learn more.
</Callout>
</>
@@ -5,10 +5,7 @@ import { Paragraph } from "../primitives/Paragraph";
import { HelpInstall } from "./HelpInstall";
import { HelpSamples, ReplacementData } from "./HelpSamples";
export type HelpPanelIntegration = Pick<
Integration,
"name" | "packageName" | "identifier"
>;
export type HelpPanelIntegration = Pick<Integration, "name" | "packageName" | "identifier">;
export type HelpPanelProps = {
integration: HelpPanelIntegration;
@@ -16,31 +13,21 @@ export type HelpPanelProps = {
integrationClient?: ReplacementData;
};
export function ApiKeyHelp({
integration,
help,
integrationClient,
}: HelpPanelProps) {
export function ApiKeyHelp({ integration, help, integrationClient }: HelpPanelProps) {
return (
<div className="mt-4">
<Header1 className="mb-2">
How to use {integration.name} with API keys
</Header1>
<Header1 className="mb-2">How to use {integration.name} with API keys</Header1>
<Paragraph spacing>
You can use API keys to authenticate with {integration.name}. Your API
keys won't leave your server, we'll never see them.
You can use API keys to authenticate with {integration.name}. Your API keys won't leave your
server, we'll never see them.
</Paragraph>
<Paragraph spacing>
First install the <InlineCode>{integration.packageName}</InlineCode>{" "}
package using your preferred package manager. For example:
First install the <InlineCode>{integration.packageName}</InlineCode> package using your
preferred package manager. For example:
</Paragraph>
<HelpInstall packageName={integration.packageName} />
{help && (
<HelpSamples
help={help}
integration={integration}
integrationClient={integrationClient}
/>
<HelpSamples help={help} integration={integration} integrationClient={integrationClient} />
)}
</div>
);
@@ -1,21 +1,12 @@
import React, { useState } from "react";
import {
ApiAuthenticationMethodApiKey,
Integration,
} from "~/services/externalApis/types";
import { ApiAuthenticationMethodApiKey, Integration } from "~/services/externalApis/types";
import { docsIntegrationPath } from "~/utils/pathBuilder";
import { LinkButton } from "../primitives/Buttons";
import { Header1, Header2 } from "../primitives/Headers";
import { NamedIconInBox } from "../primitives/NamedIcon";
import { Paragraph } from "../primitives/Paragraph";
import { RadioGroup, RadioGroupItem } from "../primitives/RadioButton";
import {
Sheet,
SheetBody,
SheetContent,
SheetHeader,
SheetTrigger,
} from "../primitives/Sheet";
import { Sheet, SheetBody, SheetContent, SheetHeader, SheetTrigger } from "../primitives/Sheet";
import { ApiKeyHelp } from "./ApiKeyHelp";
import { CustomHelp } from "./CustomHelp";
import { SelectOAuthMethod } from "./SelectOAuthMethod";
@@ -37,9 +28,9 @@ export function ConnectToIntegrationSheet({
className?: string;
icon?: string;
}) {
const [integrationMethod, setIntegrationMethod] = useState<
IntegrationMethod | undefined
>(undefined);
const [integrationMethod, setIntegrationMethod] = useState<IntegrationMethod | undefined>(
undefined
);
const authMethods = Object.values(integration.authenticationMethods);
const hasApiKeyOption = authMethods.some((s) => s.type === "apikey");
@@ -50,10 +41,7 @@ export function ConnectToIntegrationSheet({
<SheetTrigger className={className}>{button}</SheetTrigger>
<SheetContent size="lg" className="relative">
<SheetHeader>
<NamedIconInBox
name={icon ?? integration.identifier}
className="h-9 w-9"
/>
<NamedIconInBox name={icon ?? integration.identifier} className="h-9 w-9" />
<div className="grow">
<Header1>{integration.name}</Header1>
{integration.description && (
@@ -8,11 +8,7 @@ import simplur from "simplur";
import { useFeatures } from "~/hooks/useFeatures";
import { useTextFilter } from "~/hooks/useTextFilter";
import { createSchema } from "~/routes/resources.connection.$organizationId.oauth2";
import {
ApiAuthenticationMethodOAuth2,
Integration,
Scope,
} from "~/services/externalApis/types";
import { ApiAuthenticationMethodOAuth2, Integration, Scope } from "~/services/externalApis/types";
import { cn } from "~/utils/cn";
import { CodeBlock } from "../code/CodeBlock";
import { Button } from "../primitives/Buttons";
@@ -48,33 +44,22 @@ export function ConnectToOAuthForm({
const fetcher = useFetcher();
const { isManagedCloud } = useFeatures();
const [
form,
{
title,
slug,
scopes,
hasCustomClient,
customClientId,
customClientSecret,
},
] = useForm({
lastSubmission: fetcher.data,
shouldRevalidate: "onSubmit",
onValidate({ formData }) {
return parse(formData, {
// Create the schema without any constraint defined
schema: createSchema(),
});
},
});
const [form, { title, slug, scopes, hasCustomClient, customClientId, customClientSecret }] =
useForm({
lastSubmission: fetcher.data,
shouldRevalidate: "onSubmit",
onValidate({ formData }) {
return parse(formData, {
// Create the schema without any constraint defined
schema: createSchema(),
});
},
});
const location = useLocation();
const [selectedScopes, setSelectedScopes] = useState<Set<string>>(
new Set(
authMethod.scopes.filter((s) => s.defaultChecked).map((s) => s.name)
)
new Set(authMethod.scopes.filter((s) => s.defaultChecked).map((s) => s.name))
);
const requiresCustomOAuthApp = clientType === "EXTERNAL" || !isManagedCloud;
@@ -85,10 +70,7 @@ export function ConnectToOAuthForm({
items: authMethod.scopes,
filter: (scope, text) => {
if (scope.name.toLowerCase().includes(text.toLowerCase())) return true;
if (
scope.description &&
scope.description.toLowerCase().includes(text.toLowerCase())
)
if (scope.description && scope.description.toLowerCase().includes(text.toLowerCase()))
return true;
return false;
@@ -104,16 +86,8 @@ export function ConnectToOAuthForm({
>
<Fieldset>
<input type="hidden" name="id" value={id} />
<input
type="hidden"
name="integrationIdentifier"
value={integration.identifier}
/>
<input
type="hidden"
name="integrationAuthMethod"
value={authMethodKey}
/>
<input type="hidden" name="integrationIdentifier" value={integration.identifier} />
<input type="hidden" name="integrationAuthMethod" value={authMethodKey} />
<input type="hidden" name="redirectTo" value={location.pathname} />
<InputGroup>
<FormError>{form.error}</FormError>
@@ -127,8 +101,8 @@ export function ConnectToOAuthForm({
placeholder={`e.g. ${integration.identifier}`}
/>
<Hint>
This is used in your code to reference this connection. It must be
unique for this project.
This is used in your code to reference this connection. It must be unique for this
project.
</Hint>
<FormError>{slug.error}</FormError>
</InputGroup>
@@ -146,8 +120,7 @@ export function ConnectToOAuthForm({
<div>
<Header2>Use my OAuth App</Header2>
<Paragraph variant="small" className="mb-2">
To use your own OAuth app, check the option below and insert the
details.
To use your own OAuth app, check the option below and insert the details.
</Paragraph>
<Checkbox
id="hasCustomClient"
@@ -161,17 +134,13 @@ export function ConnectToOAuthForm({
{useMyOAuthApp && (
<div className="ml-6 mt-2">
<Paragraph variant="small" className="mb-2">
Set the callback url to{" "}
<CodeBlock code={callbackUrl} showLineNumbers={false} />
Set the callback url to <CodeBlock code={callbackUrl} showLineNumbers={false} />
</Paragraph>
<div className="flex flex-col gap-2">
<div className="flex gap-2">
<InputGroup fullWidth>
<Label variant="small">Client ID</Label>
<Input
fullWidth
{...conform.input(customClientId, { type: "text" })}
/>
<Input fullWidth {...conform.input(customClientId, { type: "text" })} />
</InputGroup>
<InputGroup fullWidth>
<Label variant="small">Client secret</Label>
@@ -192,10 +161,9 @@ export function ConnectToOAuthForm({
<div>
<Header2>Scopes</Header2>
<Paragraph variant="small" className="mb-4">
Select the scopes you want to grant to {integration.name} in order
for it to access your data. Note: If you try and perform an action
in a Job that requires a scope you havent granted, that task will
fail.
Select the scopes you want to grant to {integration.name} in order for it to access
your data. Note: If you try and perform an action in a Job that requires a scope you
havent granted, that task will fail.
</Paragraph>
{/* <Header3 className="mb-2">
Select from popular scope collections
@@ -240,11 +208,7 @@ export function ConnectToOAuthForm({
badges={s.annotations?.map((a) => a.label)}
description={s.description}
variant="description"
className={cn(
filteredItems.find((f) => f.name === s.name)
? ""
: "hidden"
)}
className={cn(filteredItems.find((f) => f.name === s.name) ? "" : "hidden")}
onChange={(isChecked) => {
if (isChecked) {
setSelectedScopes((selected) => {
@@ -5,17 +5,13 @@ import { Paragraph } from "../primitives/Paragraph";
export function CustomHelp({ name }: { name: string }) {
return (
<div className="mt-4">
<Header1 className="mb-2">
You can use any API with requests or an SDK
</Header1>
<Header1 className="mb-2">You can use any API with requests or an SDK</Header1>
<Header2 className="mb-2">How to use an SDK</Header2>
<Paragraph spacing>
You can call SDK methods from inside the run function, but you should
wrap them in a Task to make sure they're resumable.
</Paragraph>
<Paragraph spacing>
Here's an example with the official GitHub SDK
You can call SDK methods from inside the run function, but you should wrap them in a Task to
make sure they're resumable.
</Paragraph>
<Paragraph spacing>Here's an example with the official GitHub SDK</Paragraph>
<CodeBlock
code={`
client.defineJob({
@@ -48,9 +44,8 @@ client.defineJob({
/>
<Header2 className="mb-2">How to use fetch</Header2>
<Paragraph spacing>
You can use the fetch API to make requests to any API. Or a different
request library like axios if you'd prefer. Again wrapping the request
in a Task will make sure it's resumable.
You can use the fetch API to make requests to any API. Or a different request library like
axios if you'd prefer. Again wrapping the request in a Task will make sure it's resumable.
</Paragraph>
<CodeBlock
code={`
@@ -7,28 +7,16 @@ export type ReplacementData = {
slug: string;
};
export function HelpSamples({
help,
integrationClient,
integration,
}: HelpPanelProps) {
export function HelpSamples({ help, integrationClient, integration }: HelpPanelProps) {
return (
<>
{help &&
help.samples.map((sample, i) => {
const code = runReplacers(
sample.code,
integrationClient,
integration
);
const code = runReplacers(sample.code, integrationClient, integration);
return (
<div key={i}>
<Paragraph spacing>{sample.title}</Paragraph>
<CodeBlock
code={code}
className="mb-4"
highlightedRanges={sample.highlight}
/>
<CodeBlock code={code} className="mb-4" highlightedRanges={sample.highlight} />
</div>
);
})}
@@ -39,10 +27,7 @@ export function HelpSamples({
const replacements = [
{
match: /__SLUG__/g,
replacement: (
data: ReplacementData | undefined,
integration: HelpPanelIntegration
) => {
replacement: (data: ReplacementData | undefined, integration: HelpPanelIntegration) => {
if (data) return data.slug;
return integration.identifier;
},
@@ -3,13 +3,7 @@ import { LinkButton } from "../primitives/Buttons";
import { Header1 } from "../primitives/Headers";
import { NamedIconInBox } from "../primitives/NamedIcon";
import { Paragraph } from "../primitives/Paragraph";
import {
Sheet,
SheetBody,
SheetContent,
SheetHeader,
SheetTrigger,
} from "../primitives/Sheet";
import { Sheet, SheetBody, SheetContent, SheetHeader, SheetTrigger } from "../primitives/Sheet";
import { SelectOAuthMethod } from "./SelectOAuthMethod";
import { Integration } from "~/services/externalApis/types";
import { Client } from "~/presenters/IntegrationsPresenter.server";
@@ -4,13 +4,7 @@ import { Button } from "../primitives/Buttons";
import { Callout } from "../primitives/Callout";
import { Header1 } from "../primitives/Headers";
import { NamedIconInBox } from "../primitives/NamedIcon";
import {
Sheet,
SheetBody,
SheetContent,
SheetHeader,
SheetTrigger,
} from "../primitives/Sheet";
import { Sheet, SheetBody, SheetContent, SheetHeader, SheetTrigger } from "../primitives/Sheet";
import { CustomHelp } from "./CustomHelp";
import { CheckIcon } from "@heroicons/react/24/solid";
import { useFetcher } from "@remix-run/react";
@@ -45,27 +39,22 @@ export function NoIntegrationSheet({
</Paragraph>
</div>
) : (
<fetcher.Form
method="post"
action={`/resources/apivote/${api.identifier}`}
>
<fetcher.Form method="post" action={`/resources/apivote/${api.identifier}`}>
<Button
variant="primary/small"
disabled={isLoading}
LeadingIcon={isLoading ? "spinner-white" : undefined}
>
{isLoading
? "Saving…"
: `I want an Integration for ${api.name}`}
{isLoading ? "Saving…" : `I want an Integration for ${api.name}`}
</Button>
</fetcher.Form>
)}
</SheetHeader>
<SheetBody>
<Callout variant="info">
We dont have an Integration for the {api.name} API yet but you can
request one by clicking the button above. In the meantime, connect
to {api.name} using one of the methods below.
We dont have an Integration for the {api.name} API yet but you can request one by
clicking the button above. In the meantime, connect to {api.name} using one of the
methods below.
</Callout>
<CustomHelp name={api.name} />
</SheetBody>
@@ -1,8 +1,5 @@
import { useState } from "react";
import {
ApiAuthenticationMethodOAuth2,
Integration,
} from "~/services/externalApis/types";
import { ApiAuthenticationMethodOAuth2, Integration } from "~/services/externalApis/types";
import { RadioGroup, RadioGroupItem } from "../primitives/RadioButton";
import type { ConnectionType } from "@trigger.dev/database";
import { Header2 } from "../primitives/Headers";
@@ -29,14 +26,10 @@ export function SelectOAuthMethod({
const [oAuthKey, setOAuthKey] = useState<string | undefined>(
oAuthMethods.length === 1 ? oAuthMethods[0][0] : undefined
);
const [connectionType, setConnectionType] = useState<
ConnectionType | undefined
>();
const [connectionType, setConnectionType] = useState<ConnectionType | undefined>();
const selectedOAuthMethod = oAuthKey
? (integration.authenticationMethods[
oAuthKey
] as ApiAuthenticationMethodOAuth2)
? (integration.authenticationMethods[oAuthKey] as ApiAuthenticationMethodOAuth2)
: undefined;
return (
@@ -65,9 +58,7 @@ export function SelectOAuthMethod({
)}
{selectedOAuthMethod && (
<>
<Header2 className="mb-2 mt-4">
Who is connecting to {integration.name}
</Header2>
<Header2 className="mb-2 mt-4">Who is connecting to {integration.name}</Header2>
<RadioGroup
name="connection-type"
className="flex gap-2"
@@ -119,12 +110,9 @@ export function SelectOAuthMethod({
<>
<Header2 className="mb-1 mt-4">User OAuth coming soon</Header2>
<Paragraph spacing>
End-user OAuth is going to be released soon. If you are interested
in being an early beta tester then please{" "}
<a
href="mailto:founders@trigger.dev"
className="text-indigo-500 underline"
>
End-user OAuth is going to be released soon. If you are interested in being an early
beta tester then please{" "}
<a href="mailto:founders@trigger.dev" className="text-indigo-500 underline">
message us
</a>
.
@@ -6,11 +6,7 @@ import { useState } from "react";
import simplur from "simplur";
import { useFeatures } from "~/hooks/useFeatures";
import { useTextFilter } from "~/hooks/useTextFilter";
import {
ApiAuthenticationMethodOAuth2,
Integration,
Scope,
} from "~/services/externalApis/types";
import { ApiAuthenticationMethodOAuth2, Integration, Scope } from "~/services/externalApis/types";
import { cn } from "~/utils/cn";
import { CodeBlock } from "../code/CodeBlock";
import { Button } from "../primitives/Buttons";
@@ -48,10 +44,7 @@ export function UpdateOAuthForm({
const fetcher = useFetcher();
const { isManagedCloud } = useFeatures();
const [
form,
{ title, scopes, hasCustomClient, customClientId, customClientSecret },
] = useForm({
const [form, { title, scopes, hasCustomClient, customClientId, customClientSecret }] = useForm({
lastSubmission: fetcher.data,
onValidate({ formData }) {
return parse(formData, {
@@ -63,9 +56,7 @@ export function UpdateOAuthForm({
const location = useLocation();
const [selectedScopes, setSelectedScopes] = useState<Set<string>>(
new Set(
authMethod.scopes.filter((s) => s.defaultChecked).map((s) => s.name)
)
new Set(authMethod.scopes.filter((s) => s.defaultChecked).map((s) => s.name))
);
const requiresCustomOAuthApp = clientType === "EXTERNAL" || !isManagedCloud;
@@ -76,10 +67,7 @@ export function UpdateOAuthForm({
items: authMethod.scopes,
filter: (scope, text) => {
if (scope.name.toLowerCase().includes(text.toLowerCase())) return true;
if (
scope.description &&
scope.description.toLowerCase().includes(text.toLowerCase())
)
if (scope.description && scope.description.toLowerCase().includes(text.toLowerCase()))
return true;
return false;
@@ -95,16 +83,8 @@ export function UpdateOAuthForm({
>
<Fieldset>
<input type="hidden" name="id" value={existingIntegration.id} />
<input
type="hidden"
name="integrationIdentifier"
value={integration.identifier}
/>
<input
type="hidden"
name="integrationAuthMethod"
value={authMethodKey}
/>
<input type="hidden" name="integrationIdentifier" value={integration.identifier} />
<input type="hidden" name="integrationAuthMethod" value={authMethodKey} />
<input type="hidden" name="redirectTo" value={location.pathname} />
<InputGroup>
<FormError>{form.error}</FormError>
@@ -130,8 +110,7 @@ export function UpdateOAuthForm({
<div>
<Header2>Use my OAuth App</Header2>
<Paragraph variant="small" className="mb-2">
To use your own OAuth app, check the option below and insert the
details.
To use your own OAuth app, check the option below and insert the details.
</Paragraph>
<Checkbox
id="hasCustomClient"
@@ -145,17 +124,13 @@ export function UpdateOAuthForm({
{useMyOAuthApp && (
<div className="ml-6 mt-2">
<Paragraph variant="small" className="mb-2">
Set the callback url to{" "}
<CodeBlock code={callbackUrl} showLineNumbers={false} />
Set the callback url to <CodeBlock code={callbackUrl} showLineNumbers={false} />
</Paragraph>
<div className="flex flex-col gap-2">
<div className="flex gap-2">
<InputGroup fullWidth>
<Label variant="small">Client ID</Label>
<Input
fullWidth
{...conform.input(customClientId, { type: "text" })}
/>
<Input fullWidth {...conform.input(customClientId, { type: "text" })} />
</InputGroup>
<InputGroup fullWidth>
<Label variant="small">Client secret</Label>
@@ -176,10 +151,9 @@ export function UpdateOAuthForm({
<div>
<Header2>Scopes</Header2>
<Paragraph variant="small" className="mb-4">
Select the scopes you want to grant to {integration.name} in order
for it to access your data. Note: If you try and perform an action
in a Job that requires a scope you havent granted, that task will
fail.
Select the scopes you want to grant to {integration.name} in order for it to access
your data. Note: If you try and perform an action in a Job that requires a scope you
havent granted, that task will fail.
</Paragraph>
{/* <Header3 className="mb-2">
Select from popular scope collections
@@ -224,11 +198,7 @@ export function UpdateOAuthForm({
badges={s.annotations?.map((a) => a.label)}
description={s.description}
variant="description"
className={cn(
filteredItems.find((f) => f.name === s.name)
? ""
: "hidden"
)}
className={cn(filteredItems.find((f) => f.name === s.name) ? "" : "hidden")}
onChange={(isChecked) => {
if (isChecked) {
setSelectedScopes((selected) => {
+7 -29
View File
@@ -21,13 +21,7 @@ import { useOrganization } from "~/hooks/useOrganizations";
import { JobRunStatus } from "~/models/job.server";
import { cn } from "~/utils/cn";
export function JobsTable({
jobs,
noResultsText,
}: {
jobs: ProjectJob[];
noResultsText: string;
}) {
export function JobsTable({ jobs, noResultsText }: { jobs: ProjectJob[]; noResultsText: string }) {
const organization = useOrganization();
const project = useProject();
@@ -50,9 +44,7 @@ export function JobsTable({
return (
<TableRow
key={job.id}
className={cn(
job.hasIntegrationsRequiringAction && "bg-rose-500/30"
)}
className={cn(job.hasIntegrationsRequiringAction && "bg-rose-500/30")}
>
<TableCell to={path}>
<span className="flex items-center gap-2">
@@ -63,8 +55,7 @@ export function JobsTable({
job.dynamic ? (
<span className="flex items-center gap-0.5">
<NamedIcon name="dynamic" className="h-4 w-4" />{" "}
<span className="uppercase">Dynamic:</span>{" "}
{job.event.title}
<span className="uppercase">Dynamic:</span> {job.event.title}
</span>
) : (
job.event.title
@@ -75,11 +66,7 @@ export function JobsTable({
</span>
</TableCell>
<TableCell to={path}>
<LabelValueStack
label={job.slug}
value={`v${job.version}`}
variant="primary"
/>
<LabelValueStack label={job.slug} value={`v${job.version}`} variant="primary" />
</TableCell>
<TableCell to={path}>
{job.integrations.map((integration) => (
@@ -87,15 +74,9 @@ export function JobsTable({
key={integration.key}
button={
<div className="relative">
<NamedIcon
name={integration.icon}
className="h-6 w-6"
/>
<NamedIcon name={integration.icon} className="h-6 w-6" />
{integration.setupStatus === "MISSING_FIELDS" && (
<NamedIcon
name="error"
className="absolute -left-1 -top-1 h-4 w-4"
/>
<NamedIcon name="error" className="absolute -left-1 -top-1 h-4 w-4" />
)}
</div>
}
@@ -166,10 +147,7 @@ export function JobsTable({
})
) : (
<TableBlankRow colSpan={6}>
<Paragraph
variant="small"
className="flex items-center justify-center"
>
<Paragraph variant="small" className="flex items-center justify-center">
{noResultsText}
</Paragraph>
</TableBlankRow>
@@ -11,9 +11,7 @@ export function AppContainer({
}) {
return (
<BackgroundGradient showBackgroundGradient={showBackgroundGradient}>
<div className={cn("grid h-full w-full grid-rows-[2.75rem_auto]")}>
{children}
</div>
<div className={cn("grid h-full w-full grid-rows-[2.75rem_auto]")}>{children}</div>
</BackgroundGradient>
);
}
@@ -43,9 +41,7 @@ export function BackgroundGradient({
/** This container should be placed around the content on a page */
export function PageContainer({ children }: { children: React.ReactNode }) {
return (
<div className={cn("grid h-full grid-rows-[auto_1fr]")}>{children}</div>
);
return <div className={cn("grid h-full grid-rows-[auto_1fr]")}>{children}</div>;
}
export function PageBody({
@@ -56,11 +52,7 @@ export function PageBody({
scrollable?: boolean;
}) {
return (
<div
className={cn(scrollable ? "overflow-y-auto p-4 " : "overflow-hidden")}
>
{children}
</div>
<div className={cn(scrollable ? "overflow-y-auto p-4 " : "overflow-hidden")}>{children}</div>
);
}
@@ -68,11 +60,7 @@ export function PageBodyPadding({ children }: { children: React.ReactNode }) {
return <div className="p-4">{children}</div>;
}
export function MainCenteredContainer({
children,
}: {
children: React.ReactNode;
}) {
export function MainCenteredContainer({ children }: { children: React.ReactNode }) {
return (
<div className="h-full w-full">
<div className="mx-auto mt-[30vh] max-w-xs">{children}</div>
@@ -2,10 +2,7 @@ import { RouteMatch, useMatches } from "@remix-run/react";
import { Fragment, ReactNode } from "react";
import { BreadcrumbIcon } from "../primitives/BreadcrumbIcon";
export type BreadcrumbItem = (
match: RouteMatch,
allMatches: RouteMatch[]
) => ReactNode;
export type BreadcrumbItem = (match: RouteMatch, allMatches: RouteMatch[]) => ReactNode;
export function Breadcrumb() {
const matches = useMatches();
@@ -53,9 +53,7 @@ export function JobsMenu({ matches }: { matches: RouteMatch[] }) {
variant="primary"
className="w-full"
/>
{isSelected && (
<NamedIcon name="check" className="mr-1 h-6 w-6" />
)}
{isSelected && <NamedIcon name="check" className="mr-1 h-6 w-6" />}
</Link>
);
})}
@@ -1,8 +1,5 @@
import { Popover, Transition } from "@headlessui/react";
import {
BookOpenIcon,
ChatBubbleLeftRightIcon,
} from "@heroicons/react/20/solid";
import { BookOpenIcon, ChatBubbleLeftRightIcon } from "@heroicons/react/20/solid";
import { Link } from "@remix-run/react";
import { Fragment } from "react";
import { cn } from "~/utils/cn";
@@ -23,11 +20,7 @@ export function NavBar() {
<MobileDropdownMenu />
</div>
<div className="hidden items-center gap-2 sm:flex">
<LinkButton
to={docsRoot()}
variant="secondary/small"
LeadingIcon={BookOpenIcon}
>
<LinkButton to={docsRoot()} variant="secondary/small" LeadingIcon={BookOpenIcon}>
Documentation
</LinkButton>
<Feedback
@@ -128,10 +121,7 @@ function MobileNavIcon({ open }: { open: boolean }) {
/>
<path
d="M2 2L12 12M12 2L2 12"
className={cn(
"origin-center transition",
!open && "scale-90 opacity-0"
)}
className={cn("origin-center transition", !open && "scale-90 opacity-0")}
/>
</svg>
);
@@ -20,11 +20,7 @@ import { NamedIcon, type IconNames } from "../primitives/NamedIcon";
import { SimpleTooltip } from "../primitives/Tooltip";
export function SideMenuContainer({ children }: { children: React.ReactNode }) {
return (
<div className="flex h-full w-full justify-stretch overflow-hidden">
{children}
</div>
);
return <div className="flex h-full w-full justify-stretch overflow-hidden">{children}</div>;
}
const expandedWith = "14rem";
@@ -54,8 +50,7 @@ export function ProjectSideMenu() {
const job = useOptionalJob();
const jobsActive =
job !== undefined ||
deepestMatch?.id ===
"routes/_app.orgs.$organizationSlug.projects.$projectParam._index";
deepestMatch?.id === "routes/_app.orgs.$organizationSlug.projects.$projectParam._index";
return (
<motion.div
@@ -180,9 +175,7 @@ function SideMenuItem({
>
{name}
</motion.span>
{hasWarning && (
<NamedIcon name="error" className="absolute left-1 top-1 h-4 w-4" />
)}
{hasWarning && <NamedIcon name="error" className="absolute left-1 top-1 h-4 w-4" />}
</NavLinkButton>
}
content={name}
@@ -2,16 +2,9 @@ import { RouteMatch } from "@remix-run/react";
import { Fragment, useState } from "react";
import simplur from "simplur";
import { Badge } from "~/components/primitives/Badge";
import {
useIsNewOrganizationPage,
useOrganizations,
} from "~/hooks/useOrganizations";
import { useIsNewOrganizationPage, useOrganizations } from "~/hooks/useOrganizations";
import { useOptionalProject } from "~/hooks/useProject";
import {
newOrganizationPath,
newProjectPath,
projectPath,
} from "~/utils/pathBuilder";
import { newOrganizationPath, newProjectPath, projectPath } from "~/utils/pathBuilder";
import {
Popover,
PopoverArrowTrigger,
@@ -53,9 +46,7 @@ export function ProjectsMenu({ matches }: { matches: RouteMatch[] }) {
to={projectPath(organization, project)}
title={
<div className="flex w-full items-center justify-between pl-1 text-bright">
<span className="grow truncate text-left">
{project.name}
</span>
<span className="grow truncate text-left">{project.name}</span>
<Badge className="mr-0.5">{simplur`${project._count.jobs} job[|s]`}</Badge>
</div>
}
@@ -54,29 +54,14 @@ const AlertContent = React.forwardRef<
));
AlertContent.displayName = AlertDialogPrimitive.Content.displayName;
const AlertHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-2 text-center sm:text-left",
className
)}
{...props}
/>
const AlertHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn("flex flex-col space-y-2 text-center sm:text-left", className)} {...props} />
);
AlertHeader.displayName = "AlertDialogHeader";
const AlertFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
const AlertFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)}
{...props}
/>
);
@@ -118,11 +103,7 @@ const AlertCancel = React.forwardRef<
React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Cancel
ref={ref}
className={cn("mt-2 sm:mt-0", className)}
{...props}
/>
<AlertDialogPrimitive.Cancel ref={ref} className={cn("mt-2 sm:mt-0", className)} {...props} />
));
AlertCancel.displayName = AlertDialogPrimitive.Cancel.displayName;
@@ -12,10 +12,6 @@ type BadgeProps = React.HTMLAttributes<HTMLDivElement> & {
variant?: keyof typeof variants;
};
export function Badge({
className,
variant = "default",
...props
}: BadgeProps) {
export function Badge({ className, variant = "default", ...props }: BadgeProps) {
return <div className={cn(variants[variant], className)} {...props} />;
}
@@ -1,10 +1,5 @@
import { Link, LinkProps, NavLink, NavLinkProps } from "@remix-run/react";
import React, {
ReactComponentElement,
forwardRef,
useImperativeHandle,
useRef,
} from "react";
import React, { ReactComponentElement, forwardRef, useImperativeHandle, useRef } from "react";
import { ShortcutDefinition, useShortcutKeys } from "~/hooks/useShortcutKeys";
import { cn } from "~/utils/cn";
import { IconNamesOrString, NamedIcon } from "./NamedIcon";
@@ -12,8 +7,7 @@ import { ShortcutKey } from "./ShortcutKey";
const variant = {
"primary/small": {
textColor:
"text-bright group-hover:text-white transition group-disabled:text-bright/80",
textColor: "text-bright group-hover:text-white transition group-disabled:text-bright/80",
button:
"h-6 px-[5px] text-xs bg-indigo-600 group-hover:bg-indigo-500/90 group-disabled:opacity-50 group-disabled:pointer-events-none",
icon: "h-3.5",
@@ -22,8 +16,7 @@ const variant = {
"ml-1 -mr-0.5 border-bright/40 text-bright group-hover:border-bright/60 justify-self-center",
},
"secondary/small": {
textColor:
"text-dimmed group-hover:text-bright transition group-disabled:text-dimmed/80",
textColor: "text-dimmed group-hover:text-bright transition group-disabled:text-dimmed/80",
button:
"h-6 px-[5px] text-xs bg-slate-800 group-hover:bg-slate-700/70 disabled:opacity-50 group-disabled:pointer-events-none",
icon: "h-3.5",
@@ -32,8 +25,7 @@ const variant = {
"ml-1 -mr-0.5 border-dimmed/40 text-dimmed group-hover:text-bright/80 group-hover:border-dimmed/60",
},
"tertiary/small": {
textColor:
"text-dimmed group-hover:text-bright transition group-disabled:text-dimmed/80",
textColor: "text-dimmed group-hover:text-bright transition group-disabled:text-dimmed/80",
button:
"h-6 px-[5px] text-xs bg-transparent group-hover:bg-slate-850 disabled:opacity-50 group-disabled:pointer-events-none",
icon: "h-3.5",
@@ -42,58 +34,45 @@ const variant = {
"ml-1 -mr-0.5 border-dimmed/40 text-dimmed group-hover:text-bright/80 group-hover:border-dimmed/60",
},
"danger/small": {
textColor:
"text-bright group-hover:text-white transition group-disabled:text-bright/80",
textColor: "text-bright group-hover:text-white transition group-disabled:text-bright/80",
button:
"h-6 px-[5px] text-xs bg-rose-600 group-hover:bg-rose-500 disabled:opacity-50 group-disabled:pointer-events-none",
icon: "h-3.5",
shortcutVariant: "small" as const,
shortcut:
"ml-1 -mr-0.5 border-bright/40 text-bright group-hover:border-bright/60",
shortcut: "ml-1 -mr-0.5 border-bright/40 text-bright group-hover:border-bright/60",
},
"primary/medium": {
textColor:
"text-bright group-hover:text-white transition group-disabled:text-bright/80",
button:
"h-8 px-2 text-sm bg-indigo-600 group-hover:bg-indigo-500/90 disabled:opacity-50",
textColor: "text-bright group-hover:text-white transition group-disabled:text-bright/80",
button: "h-8 px-2 text-sm bg-indigo-600 group-hover:bg-indigo-500/90 disabled:opacity-50",
icon: "h-4",
shortcutVariant: "medium" as const,
shortcut:
"ml-1.5 -mr-0.5 border-bright/40 text-bright group-hover:border-bright/60",
shortcut: "ml-1.5 -mr-0.5 border-bright/40 text-bright group-hover:border-bright/60",
},
"secondary/medium": {
textColor:
"text-dimmed group-hover:text-bright transition group-disabled:text-dimmed/80",
button:
"h-8 px-2 text-sm bg-slate-800 group-hover:bg-slate-700/70 disabled:opacity-50",
textColor: "text-dimmed group-hover:text-bright transition group-disabled:text-dimmed/80",
button: "h-8 px-2 text-sm bg-slate-800 group-hover:bg-slate-700/70 disabled:opacity-50",
icon: "h-4",
shortcutVariant: "medium" as const,
shortcut:
"ml-1.5 -mr-0.5 border-dimmed/40 text-dimmed group-hover:border-dimmed group-hover:text-bright",
},
"tertiary/medium": {
textColor:
"text-dimmed group-hover:text-bright transition group-disabled:text-dimmed/80",
button:
"h-8 px-2 text-sm bg-transparent group-hover:bg-slate-850 disabled:opacity-50",
textColor: "text-dimmed group-hover:text-bright transition group-disabled:text-dimmed/80",
button: "h-8 px-2 text-sm bg-transparent group-hover:bg-slate-850 disabled:opacity-50",
icon: "h-4",
shortcutVariant: "medium" as const,
shortcut:
"ml-1.5 -mr-0.5 border-bright/40 text-dimmed group-hover:border-bright/60 group-hover:text-bright",
},
"danger/medium": {
textColor:
"text-bright group-hover:text-white transition group-disabled:text-bright/80",
button:
"h-8 px-2 text-sm bg-rose-600 group-hover:bg-rose-500 disabled:opacity-50",
textColor: "text-bright group-hover:text-white transition group-disabled:text-bright/80",
button: "h-8 px-2 text-sm bg-rose-600 group-hover:bg-rose-500 disabled:opacity-50",
icon: "h-4",
shortcutVariant: "medium" as const,
shortcut:
"ml-1.5 -mr-0.5 border-bright/40 text-bright group-hover:border-bright/60",
shortcut: "ml-1.5 -mr-0.5 border-bright/40 text-bright group-hover:border-bright/60",
},
"primary/large": {
textColor:
"text-bright group-hover:text-white transition group-disabled:text-dimmed/80",
textColor: "text-bright group-hover:text-white transition group-disabled:text-dimmed/80",
button:
"h-10 px-2 text-sm font-medium bg-indigo-600 group-hover:bg-indigo-500/90 disabled:opacity-50",
icon: "h-5",
@@ -157,13 +136,7 @@ export function ButtonContent(props: ButtonContentPropsType) {
const textColorClassName = variation.textColor;
return (
<div
className={cn(
fullWidth ? "flex" : "inline-flex text-xxs",
btnClassName,
className
)}
>
<div className={cn(fullWidth ? "flex" : "inline-flex text-xxs", btnClassName, className)}>
<div
className={cn(
textAlignLeft ? "text-left" : "justify-center",
@@ -174,11 +147,7 @@ export function ButtonContent(props: ButtonContentPropsType) {
(typeof LeadingIcon === "string" ? (
<NamedIcon
name={LeadingIcon}
className={cn(
iconClassName,
leadingIconClassName,
"shrink-0 justify-start"
)}
className={cn(iconClassName, leadingIconClassName, "shrink-0 justify-start")}
/>
) : (
<LeadingIcon
@@ -193,12 +162,7 @@ export function ButtonContent(props: ButtonContentPropsType) {
{text &&
(typeof text === "string" ? (
<span
className={cn(
"mx-auto grow self-center truncate px-1",
textColorClassName
)}
>
<span className={cn("mx-auto grow self-center truncate px-1", textColorClassName)}>
{text}
</span>
) : (
@@ -209,11 +173,7 @@ export function ButtonContent(props: ButtonContentPropsType) {
(typeof TrailingIcon === "string" ? (
<NamedIcon
name={TrailingIcon}
className={cn(
iconClassName,
trailingIconClassName,
"shrink-0 justify-end"
)}
className={cn(iconClassName, trailingIconClassName, "shrink-0 justify-end")}
/>
) : (
<TrailingIcon
@@ -277,8 +237,7 @@ export const Button = forwardRef<HTMLButtonElement, ButtonPropsType>(
}
);
type LinkPropsType = Pick<LinkProps, "to" | "target"> &
React.ComponentProps<typeof ButtonContent>;
type LinkPropsType = Pick<LinkProps, "to" | "target"> & React.ComponentProps<typeof ButtonContent>;
export const LinkButton = ({ to, ...props }: LinkPropsType) => {
const innerRef = useRef<HTMLAnchorElement>(null);
if (props.shortcut) {
@@ -317,26 +276,13 @@ export const LinkButton = ({ to, ...props }: LinkPropsType) => {
type NavLinkPropsType = Pick<NavLinkProps, "to" | "target"> &
Omit<React.ComponentProps<typeof ButtonContent>, "className"> & {
className?: (props: {
isActive: boolean;
isPending: boolean;
}) => string | undefined;
className?: (props: { isActive: boolean; isPending: boolean }) => string | undefined;
};
export const NavLinkButton = ({
to,
className,
...props
}: NavLinkPropsType) => {
export const NavLinkButton = ({ to, className, ...props }: NavLinkPropsType) => {
return (
<NavLink
to={to}
className={cn("group outline-none", props.fullWidth ? "w-full" : "")}
>
<NavLink to={to} className={cn("group outline-none", props.fullWidth ? "w-full" : "")}>
{({ isActive, isPending }) => (
<ButtonContent
className={className && className({ isActive, isPending })}
{...props}
/>
<ButtonContent className={className && className({ isActive, isPending })} {...props} />
)}
</NavLink>
);
@@ -23,9 +23,7 @@ export const variantClasses = {
},
warning: {
className: "border-yellow-400/20 bg-yellow-400/30",
icon: (
<ExclamationTriangleIcon className="h-5 w-5 shrink-0 text-yellow-400" />
),
icon: <ExclamationTriangleIcon className="h-5 w-5 shrink-0 text-yellow-400" />,
textColor: "text-yellow-200",
linkClassName: "transition hover:bg-yellow-400/40",
},
@@ -87,19 +85,14 @@ export function Callout({
{icon ? icon : variantDefinition.icon}
{typeof children === "string" ? (
<Paragraph
variant={"small"}
className={variantDefinition.textColor}
>
<Paragraph variant={"small"} className={variantDefinition.textColor}>
{children}
</Paragraph>
) : (
children
)}
</div>
<ArrowTopRightOnSquareIcon
className={cn("h-5 w-5", variantDefinition.textColor)}
/>
<ArrowTopRightOnSquareIcon className={cn("h-5 w-5", variantDefinition.textColor)} />
</a>
);
} else {
@@ -117,19 +110,14 @@ export function Callout({
{icon ? icon : variantDefinition.icon}
{typeof children === "string" ? (
<Paragraph
variant={"small"}
className={variantDefinition.textColor}
>
<Paragraph variant={"small"} className={variantDefinition.textColor}>
{children}
</Paragraph>
) : (
children
)}
</div>
<ChevronRightIcon
className={cn("h-5 w-5", variantDefinition.textColor)}
/>
<ChevronRightIcon className={cn("h-5 w-5", variantDefinition.textColor)} />
</Link>
);
}
@@ -82,9 +82,7 @@ export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
},
ref
) => {
const [isChecked, setIsChecked] = useState<boolean>(
defaultChecked ?? false
);
const [isChecked, setIsChecked] = useState<boolean>(defaultChecked ?? false);
const [isDisabled, setIsDisabled] = useState<boolean>(disabled ?? false);
const buttonClassName = variants[variant].button;
@@ -157,10 +155,7 @@ export const Checkbox = React.forwardRef<HTMLInputElement, CheckboxProps>(
)}
</div>
{variant === "description" && (
<Paragraph
variant="small"
className={cn("mt-0.5", descriptionClassName)}
>
<Paragraph variant="small" className={cn("mt-0.5", descriptionClassName)}>
{description}
</Paragraph>
)}
@@ -12,10 +12,7 @@ const ClientTabsList = React.forwardRef<
>(({ className, ...props }, ref) => (
<TabsPrimitive.List
ref={ref}
className={cn(
"inline-flex items-center justify-center transition duration-100",
className
)}
className={cn("inline-flex items-center justify-center transition duration-100", className)}
{...props}
/>
));
@@ -106,31 +106,19 @@ export function ClipboardField({
const inputIcon = useRef<HTMLInputElement>(null);
return (
<span
className={cn(container, fullWidth ? "w-full" : "max-w-max", className)}
>
<span className={cn(container, fullWidth ? "w-full" : "max-w-max", className)}>
{icon && (
<span
onClick={() => inputIcon.current && inputIcon.current.focus()}
className={cn(iconPosition, "flex items-center")}
>
{typeof icon === "string" ? (
<NamedIcon name={icon} className={iconClassName} />
) : (
icon
)}
{typeof icon === "string" ? <NamedIcon name={icon} className={iconClassName} /> : icon}
</span>
)}
<input
type="text"
ref={inputIcon}
value={
isSecure
? typeof secure === "string"
? secure
: "•".repeat(value.length)
: value
}
value={isSecure ? (typeof secure === "string" ? secure : "•".repeat(value.length)) : value}
readOnly={true}
className={cn("shrink grow select-all overflow-x-auto", input)}
size={value.length}
@@ -146,11 +134,7 @@ export function ClipboardField({
}
}}
/>
<Button
variant={buttonVariant}
onClick={copy}
className={cn("shrink-0 grow-0", button)}
>
<Button variant={buttonVariant} onClick={copy} className={cn("shrink-0 grow-0", button)}>
{copied ? <CheckIcon className="h-4 w-4 text-green-500" /> : "Copy"}
</Button>
</span>
@@ -7,44 +7,24 @@ type DateTimeProps = {
includeSeconds?: boolean;
};
export const DateTime = ({
date,
timeZone = "UTC",
includeSeconds = true,
}: DateTimeProps) => {
export const DateTime = ({ date, timeZone = "UTC", includeSeconds = true }: DateTimeProps) => {
const locales = useLocales();
const realDate = typeof date === "string" ? new Date(date) : date;
const initialFormattedDateTime = formatDateTime(
realDate,
timeZone,
locales,
includeSeconds
);
const initialFormattedDateTime = formatDateTime(realDate, timeZone, locales, includeSeconds);
const [formattedDateTime, setFormattedDateTime] = useState<string>(
initialFormattedDateTime
);
const [formattedDateTime, setFormattedDateTime] = useState<string>(initialFormattedDateTime);
useEffect(() => {
const resolvedOptions = Intl.DateTimeFormat().resolvedOptions();
setFormattedDateTime(
formatDateTime(
realDate,
resolvedOptions.timeZone,
locales,
includeSeconds
)
formatDateTime(realDate, resolvedOptions.timeZone, locales, includeSeconds)
);
}, [locales, includeSeconds, realDate]);
return (
<Fragment>
{formattedDateTime.replace(/\s/g, String.fromCharCode(32))}
</Fragment>
);
return <Fragment>{formattedDateTime.replace(/\s/g, String.fromCharCode(32))}</Fragment>;
};
function formatDateTime(
@@ -69,36 +49,20 @@ export const DateTimeAccurate = ({ date, timeZone = "UTC" }: DateTimeProps) => {
const realDate = typeof date === "string" ? new Date(date) : date;
const initialFormattedDateTime = formatDateTimeAccurate(
realDate,
timeZone,
locales
);
const initialFormattedDateTime = formatDateTimeAccurate(realDate, timeZone, locales);
const [formattedDateTime, setFormattedDateTime] = useState<string>(
initialFormattedDateTime
);
const [formattedDateTime, setFormattedDateTime] = useState<string>(initialFormattedDateTime);
useEffect(() => {
const resolvedOptions = Intl.DateTimeFormat().resolvedOptions();
setFormattedDateTime(
formatDateTimeAccurate(realDate, resolvedOptions.timeZone, locales)
);
setFormattedDateTime(formatDateTimeAccurate(realDate, resolvedOptions.timeZone, locales));
}, [locales, realDate]);
return (
<Fragment>
{formattedDateTime.replace(/\s/g, String.fromCharCode(32))}
</Fragment>
);
return <Fragment>{formattedDateTime.replace(/\s/g, String.fromCharCode(32))}</Fragment>;
};
function formatDateTimeAccurate(
date: Date,
timeZone: string,
locales: string[]
): string {
function formatDateTimeAccurate(date: Date, timeZone: string, locales: string[]): string {
const milliseconds = `00${date.getMilliseconds()}`.slice(-3);
const formattedDateTime = new Intl.DateTimeFormat(locales, {
@@ -9,11 +9,7 @@ const Dialog = DialogPrimitive.Root;
const DialogTrigger = DialogPrimitive.Trigger;
const DialogPortal = ({
className,
children,
...props
}: DialogPrimitive.DialogPortalProps) => (
const DialogPortal = ({ className, children, ...props }: DialogPrimitive.DialogPortalProps) => (
<DialogPrimitive.Portal className={cn(className)} {...props}>
<div className="fixed inset-0 z-50 flex items-start justify-center sm:items-center">
{children}
@@ -61,29 +57,14 @@ const DialogContent = React.forwardRef<
));
DialogContent.displayName = DialogPrimitive.Content.displayName;
const DialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-1.5 text-center sm:text-left",
className
)}
{...props}
/>
const DialogHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div className={cn("flex flex-col space-y-1.5 text-center sm:text-left", className)} {...props} />
);
DialogHeader.displayName = "DialogHeader";
const DialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
const DialogFooter = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
className={cn("flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2", className)}
{...props}
/>
);
@@ -95,10 +76,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-lg font-semibold leading-none tracking-tight", className)}
{...props}
/>
));
@@ -7,7 +7,5 @@ export function Fieldset({
children: React.ReactNode;
className?: string;
}) {
return (
<div className={cn("flex flex-col gap-y-5", className)}>{children}</div>
);
return <div className={cn("flex flex-col gap-y-5", className)}>{children}</div>;
}
@@ -3,13 +3,7 @@ import { Paragraph } from "./Paragraph";
import { NamedIcon } from "./NamedIcon";
import { motion } from "framer-motion";
export function FormError({
children,
id,
}: {
children: React.ReactNode;
id?: string;
}) {
export function FormError({ children, id }: { children: React.ReactNode; id?: string }) {
return (
<>
{children && (
@@ -29,13 +23,7 @@ export function FormError({
);
}
export function ZodFormErrors({
errors,
path,
}: {
errors: z.ZodIssue[];
path: string[];
}) {
export function ZodFormErrors({ errors, path }: { errors: z.ZodIssue[]; path: string[] }) {
if (errors.length === 0) {
return null;
}
@@ -54,10 +54,7 @@ export default function FormSegmentedControl({
<div className="text-sm">
<RadioGroup.Label
as="p"
className={cn(
"font-normal",
checked ? "text-bright" : "text-dimmed"
)}
className={cn("font-normal", checked ? "text-bright" : "text-dimmed")}
>
{option.label}
</RadioGroup.Label>
@@ -40,9 +40,7 @@ export function Header1({
className={cn(
headerVariants.header1.text,
spacing === true && headerVariants.header1.spacing,
textColor === "bright"
? textColorVariants.bright
: textColorVariants.dimmed,
textColor === "bright" ? textColorVariants.bright : textColorVariants.dimmed,
className
)}
>
@@ -62,9 +60,7 @@ export function Header2({
className={cn(
headerVariants.header2.text,
spacing === true && headerVariants.header2.spacing,
textColor === "bright"
? textColorVariants.bright
: textColorVariants.dimmed,
textColor === "bright" ? textColorVariants.bright : textColorVariants.dimmed,
className
)}
>
@@ -84,9 +80,7 @@ export function Header3({
className={cn(
headerVariants.header3.text,
spacing === true && headerVariants.header3.spacing,
textColor === "bright"
? textColorVariants.bright
: textColorVariants.dimmed,
textColor === "bright" ? textColorVariants.bright : textColorVariants.dimmed,
className
)}
>
@@ -29,11 +29,7 @@ function useHelp() {
return React.useContext(HelpContext);
}
export function Help({
defaultOpen,
allowDismissing = true,
children,
}: HelpProps) {
export function Help({ defaultOpen, allowDismissing = true, children }: HelpProps) {
const [open, setOpen] = React.useState(defaultOpen || false);
return (
@@ -50,18 +50,7 @@ export type InputProps = React.InputHTMLAttributes<HTMLInputElement> & {
};
const Input = React.forwardRef<HTMLInputElement, InputProps>(
(
{
className,
type,
shortcut,
fullWidth = true,
variant = "medium",
icon,
...props
},
ref
) => {
({ className, type, shortcut, fullWidth = true, variant = "medium", icon, ...props }, ref) => {
const inputClassName = variants[variant].input;
const iconClassName = variants[variant].iconSize;
const iconOffsetClassName = variants[variant].iconOffset;
@@ -70,25 +59,16 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
<div className={cn("relative", fullWidth ? "w-full" : "max-w-max")}>
{icon && (
<div className="pointer-events-none absolute inset-y-0 left-0 flex items-center">
<NamedIcon
name={icon}
className={cn(iconClassName, "text-dimmed")}
/>
<NamedIcon name={icon} className={cn(iconClassName, "text-dimmed")} />
</div>
)}
<input
type={type}
className={cn(
inputClassName,
icon ? iconOffsetClassName : "",
className
)}
className={cn(inputClassName, icon ? iconOffsetClassName : "", className)}
ref={ref}
{...props}
/>
{shortcut && (
<div className={cn(shortcutClassName, "absolute")}>{shortcut}</div>
)}
{shortcut && <div className={cn(shortcutClassName, "absolute")}>{shortcut}</div>}
</div>
);
}
@@ -6,11 +6,7 @@ type InputGroupProps = {
fullWidth?: boolean;
};
export function InputGroup({
children,
className,
fullWidth,
}: InputGroupProps) {
export function InputGroup({ children, className, fullWidth }: InputGroupProps) {
return (
<div
className={cn(
@@ -19,12 +19,7 @@ type LabelProps = React.AllHTMLAttributes<HTMLLabelElement> & {
variant?: keyof typeof labelVariants;
};
export function Label({
className,
children,
variant = "medium",
...props
}: LabelProps) {
export function Label({ className, children, variant = "medium", ...props }: LabelProps) {
const variation = labelVariants[variant];
return (
<label className={cn(variation.text, className)} {...props}>
@@ -49,11 +49,7 @@ export function LabelValueStack({
side="bottom"
button={
<Paragraph variant={variation.value}>
<a
href={href}
className="underline underline-offset-2"
target="_blank"
>
<a href={href} className="underline underline-offset-2" target="_blank">
{value}
<ArrowTopRightOnSquareIcon className="ml-1 inline-block h-4 w-4 text-dimmed" />
</a>
@@ -6,11 +6,7 @@ type Props = Parameters<typeof Link>[0] & {
disabledClassName?: string;
};
export function LinkDisabled({
disabled = false,
disabledClassName,
...props
}: Props) {
export function LinkDisabled({ disabled = false, disabledClassName, ...props }: Props) {
if (disabled) {
return (
<span {...props} className={cn(props.className, disabledClassName)}>
@@ -13,10 +13,7 @@ type LocaleContextProviderProps = {
const Context = createContext<LocaleContext | null>(null);
export const LocaleContextProvider = ({
locales,
children,
}: LocaleContextProviderProps) => {
export const LocaleContextProvider = ({ locales, children }: LocaleContextProviderProps) => {
const value = { locales };
return <Context.Provider value={value}>{children}</Context.Provider>;
@@ -60,114 +60,60 @@ import { LogoIcon } from "../LogoIcon";
import { Spinner } from "./Spinner";
const icons = {
account: (className: string) => (
<UserCircleIcon className={cn("text-slate-400", className)} />
),
active: (className: string) => (
<CheckCircleIcon className={cn("text-green-500", className)} />
),
"arrow-right": (className: string) => (
<ArrowRightIcon className={cn("text-white", className)} />
),
"arrow-left": (className: string) => (
<ArrowLeftIcon className={cn("text-white", className)} />
),
background: (className: string) => (
<CloudIcon className={cn("text-sky-400", className)} />
),
beaker: (className: string) => (
<BeakerIcon className={cn("text-purple-500", className)} />
),
billing: (className: string) => (
<CreditCardIcon className={cn("text-teal-500", className)} />
),
account: (className: string) => <UserCircleIcon className={cn("text-slate-400", className)} />,
active: (className: string) => <CheckCircleIcon className={cn("text-green-500", className)} />,
"arrow-right": (className: string) => <ArrowRightIcon className={cn("text-white", className)} />,
"arrow-left": (className: string) => <ArrowLeftIcon className={cn("text-white", className)} />,
background: (className: string) => <CloudIcon className={cn("text-sky-400", className)} />,
beaker: (className: string) => <BeakerIcon className={cn("text-purple-500", className)} />,
billing: (className: string) => <CreditCardIcon className={cn("text-teal-500", className)} />,
calendar: (className: string) => (
<CalendarDaysIcon className={cn("text-purple-500", className)} />
),
check: (className: string) => (
<CheckIcon className={cn("text-dimmed", className)} />
),
check: (className: string) => <CheckIcon className={cn("text-dimmed", className)} />,
"chevron-down": (className: string) => (
<ChevronDownIcon className={cn("text-dimmed", className)} />
),
"chevron-up": (className: string) => (
<ChevronUpIcon className={cn("text-dimmed", className)} />
),
"chevron-up": (className: string) => <ChevronUpIcon className={cn("text-dimmed", className)} />,
"chevron-left": (className: string) => (
<ChevronLeftIcon className={cn("text-dimmed", className)} />
),
"chevron-right": (className: string) => (
<ChevronRightIcon className={cn("text-dimmed", className)} />
),
countdown: (className: string) => (
<HourglassIcon className={cn("text-amber-400", className)} />
),
clock: (className: string) => (
<ClockIcon className={cn("text-cyan-500", className)} />
),
close: (className: string) => (
<XMarkIcon className={cn("text-dimmed", className)} />
),
countdown: (className: string) => <HourglassIcon className={cn("text-amber-400", className)} />,
clock: (className: string) => <ClockIcon className={cn("text-cyan-500", className)} />,
close: (className: string) => <XMarkIcon className={cn("text-dimmed", className)} />,
"connection-alert": (className: string) => (
<BellAlertIcon className={cn("text-amber-500", className)} />
),
docs: (className: string) => (
<BookOpenIcon className={cn("text-slate-400", className)} />
),
dynamic: (className: string) => (
<DynamicTriggerIcon className={cn("text-cyan-500", className)} />
),
error: (className: string) => (
<ErrorIcon className={cn("text-rose-500", className)} />
),
docs: (className: string) => <BookOpenIcon className={cn("text-slate-400", className)} />,
dynamic: (className: string) => <DynamicTriggerIcon className={cn("text-cyan-500", className)} />,
error: (className: string) => <ErrorIcon className={cn("text-rose-500", className)} />,
"external-link": (className: string) => (
<ArrowTopRightOnSquareIcon className={cn("text-dimmed", className)} />
),
flag: (className: string) => (
<FlagIcon className={cn("text-sky-500", className)} />
),
folder: (className: string) => (
<FolderIcon className={cn("text-indigo-600", className)} />
),
envelope: (className: string) => (
<EnvelopeIcon className={cn("text-cyan-500", className)} />
),
environment: (className: string) => (
<KeyIcon className={cn("text-yellow-500", className)} />
),
globe: (className: string) => (
<GlobeAltIcon className={cn("text-fuchsia-600", className)} />
),
flag: (className: string) => <FlagIcon className={cn("text-sky-500", className)} />,
folder: (className: string) => <FolderIcon className={cn("text-indigo-600", className)} />,
envelope: (className: string) => <EnvelopeIcon className={cn("text-cyan-500", className)} />,
environment: (className: string) => <KeyIcon className={cn("text-yellow-500", className)} />,
globe: (className: string) => <GlobeAltIcon className={cn("text-fuchsia-600", className)} />,
"hand-raised": (className: string) => (
<HandRaisedIcon className={cn("text-amber-400", className)} />
),
heart: (className: string) => (
<HeartIcon className={cn("text-rose-500", className)} />
),
id: (className: string) => (
<FingerPrintIcon className={cn("text-rose-200", className)} />
),
inactive: (className: string) => (
<XCircleIcon className={cn("text-rose-500", className)} />
),
info: (className: string) => (
<InformationCircleIcon className={cn("text-blue-500", className)} />
),
heart: (className: string) => <HeartIcon className={cn("text-rose-500", className)} />,
id: (className: string) => <FingerPrintIcon className={cn("text-rose-200", className)} />,
inactive: (className: string) => <XCircleIcon className={cn("text-rose-500", className)} />,
info: (className: string) => <InformationCircleIcon className={cn("text-blue-500", className)} />,
integration: (className: string) => (
<SquaresPlusIcon className={cn("text-teal-500", className)} />
),
"invite-member": (className: string) => (
<UserPlusIcon className={cn("text-indigo-500", className)} />
),
job: (className: string) => (
<WrenchScrewdriverIcon className={cn("text-teal-500", className)} />
),
key: (className: string) => (
<KeyIcon className={cn("text-amber-400", className)} />
),
lightbulb: (className: string) => (
<LightBulbIcon className={cn("text-amber-400", className)} />
),
job: (className: string) => <WrenchScrewdriverIcon className={cn("text-teal-500", className)} />,
key: (className: string) => <KeyIcon className={cn("text-amber-400", className)} />,
lightbulb: (className: string) => <LightBulbIcon className={cn("text-amber-400", className)} />,
log: (className: string) => (
<ChatBubbleLeftEllipsisIcon className={cn("text-slate-400", className)} />
),
@@ -175,48 +121,22 @@ const icons = {
organization: (className: string) => (
<BuildingOffice2Icon className={cn("text-fuchsia-600", className)} />
),
plus: (className: string) => (
<PlusIcon className={cn("text-green-600", className)} />
),
plus: (className: string) => <PlusIcon className={cn("text-green-600", className)} />,
"plus-small": (className: string) => (
<PlusSmallIcon className={cn("text-green-600", className)} />
),
property: (className: string) => (
<Cog8ToothIcon className={cn("text-slate-600", className)} />
),
"qr-code": (className: string) => (
<QrCodeIcon className={cn("text-amber-400", className)} />
),
refresh: (className: string) => (
<ArrowPathIcon className={cn("text-bright", className)} />
),
search: (className: string) => (
<MagnifyingGlassIcon className={cn("text-dimmed", className)} />
),
settings: (className: string) => (
<Cog8ToothIcon className={cn("text-slate-600", className)} />
),
spinner: (className: string) => (
<Spinner className={className} color="blue" />
),
"spinner-white": (className: string) => (
<Spinner className={className} color="white" />
),
star: (className: string) => (
<StarIcon className={cn("text-yellow-500", className)} />
),
stop: (className: string) => (
<StopIcon className={cn("text-rose-500", className)} />
),
team: (className: string) => (
<UserGroupIcon className={cn("text-blue-500", className)} />
),
trigger: (className: string) => (
<BoltIcon className={cn("text-orange-500", className)} />
),
user: (className: string) => (
<UserIcon className={cn("text-blue-600", className)} />
),
property: (className: string) => <Cog8ToothIcon className={cn("text-slate-600", className)} />,
"qr-code": (className: string) => <QrCodeIcon className={cn("text-amber-400", className)} />,
refresh: (className: string) => <ArrowPathIcon className={cn("text-bright", className)} />,
search: (className: string) => <MagnifyingGlassIcon className={cn("text-dimmed", className)} />,
settings: (className: string) => <Cog8ToothIcon className={cn("text-slate-600", className)} />,
spinner: (className: string) => <Spinner className={className} color="blue" />,
"spinner-white": (className: string) => <Spinner className={className} color="white" />,
star: (className: string) => <StarIcon className={cn("text-yellow-500", className)} />,
stop: (className: string) => <StopIcon className={cn("text-rose-500", className)} />,
team: (className: string) => <UserGroupIcon className={cn("text-blue-500", className)} />,
trigger: (className: string) => <BoltIcon className={cn("text-orange-500", className)} />,
user: (className: string) => <UserIcon className={cn("text-blue-600", className)} />,
warning: (className: string) => (
<ExclamationTriangleIcon className={cn("text-amber-400", className)} />
),
@@ -236,9 +156,7 @@ const icons = {
"schedule-dynamic": (className: string) => (
<ScheduleIcon className={cn("text-sky-500", className)} />
),
webhook: (className: string) => (
<WebhookIcon className={cn("text-pink-500", className)} />
),
webhook: (className: string) => <WebhookIcon className={cn("text-pink-500", className)} />,
};
export type IconNames = keyof typeof icons;
@@ -260,12 +178,7 @@ export function NamedIcon({
if (hasIcon(name)) {
return (
<span
className={cn(
"grid aspect-square min-h-fit place-items-center",
className
)}
>
<span className={cn("grid aspect-square min-h-fit place-items-center", className)}>
<CompanyIcon
name={name}
className={"h-full w-full p-[7%]"}
@@ -306,11 +219,7 @@ export function NamedIconInBox({
className
)}
>
<NamedIcon
name={name}
fallback={fallback}
className={cn("h-6 w-6", iconClassName)}
/>
<NamedIcon name={name} fallback={fallback} className={cn("h-6 w-6", iconClassName)} />
</div>
);
}
@@ -23,9 +23,7 @@ export const OperatingSystemContextProvider = ({
};
const throwIfNoProvider = () => {
throw new Error(
"Please wrap your application in an OperatingSystemContextProvider."
);
throw new Error("Please wrap your application in an OperatingSystemContextProvider.");
};
export const useOperatingSystem = () => {
@@ -11,17 +11,9 @@ type WithChildren = {
children: React.ReactNode;
};
export function PageHeader({
children,
hideBorder,
}: WithChildren & { hideBorder?: boolean }) {
export function PageHeader({ children, hideBorder }: WithChildren & { hideBorder?: boolean }) {
return (
<div
className={cn(
"mx-4 pt-4",
hideBorder ? "" : "border-b border-slate-800 pb-4"
)}
>
<div className={cn("mx-4 pt-4", hideBorder ? "" : "border-b border-slate-800 pb-4")}>
{children}
</div>
);
@@ -49,10 +41,7 @@ export function PageTitle({ title, backButton }: PageTitleProps) {
className="flex items-center gap-1 text-slate-400 transition group-hover:text-white"
>
<ChevronLeftIcon className="h-6" />
<Header1
textColor="dimmed"
className="transition group-hover:text-white"
>
<Header1 textColor="dimmed" className="transition group-hover:text-white">
{backButton.text}
</Header1>
</Link>
@@ -107,16 +96,9 @@ export function PageInfoProperty({
}) {
return (
<div className="flex items-center gap-1">
{icon && typeof icon === "string" ? (
<NamedIcon name={icon} className="h-4 w-4" />
) : (
icon
)}
{icon && typeof icon === "string" ? <NamedIcon name={icon} className="h-4 w-4" /> : icon}
{label && (
<Paragraph
variant="extra-small/caps"
className="mt-0.5 whitespace-nowrap"
>
<Paragraph variant="extra-small/caps" className="mt-0.5 whitespace-nowrap">
{label}:
</Paragraph>
)}
@@ -43,11 +43,8 @@ export function PaginationControls({
<div className="hidden sm:flex sm:flex-1 sm:items-center sm:justify-between">
<div>
<p className="text-sm text-slate-400">
Showing{" "}
<span className="font-medium">
{(currentPage - 1) * pageSize + 1}
</span>{" "}
to <span className="font-medium">{currentPage * pageSize}</span> of{" "}
Showing <span className="font-medium">{(currentPage - 1) * pageSize + 1}</span> to{" "}
<span className="font-medium">{currentPage * pageSize}</span> of{" "}
<span className="font-medium">{totalResults}</span> results
</p>
</div>
@@ -86,10 +83,7 @@ export function PaginationControls({
);
}
function pageUrl(
location: ReturnType<typeof useLocation>,
page: number
): string {
function pageUrl(location: ReturnType<typeof useLocation>, page: number): string {
const search = new URLSearchParams(location.search);
search.set("page", String(page));
@@ -101,8 +95,7 @@ const baseClass =
"relative inline-flex items-center border px-3.5 py-2 text-xs font-medium focus:z-20 transition";
const unselectedClass =
"bg-slate-700/20 border-slate-500 text-slate-400 hover:bg-slate-400 hover:text-slate-900";
const selectedClass =
"z-10 bg-slate-500 border-slate-500 hover:bg-slate-400 text-slate-900";
const selectedClass = "z-10 bg-slate-500 border-slate-500 hover:bg-slate-400 text-slate-900";
function PageLinkComponent({
page,
@@ -114,19 +107,13 @@ function PageLinkComponent({
if (page.type === "specific") {
if (page.isCurrent) {
return (
<Link
to={pageUrl(location, page.page)}
className={cn(baseClass, selectedClass)}
>
<Link to={pageUrl(location, page.page)} className={cn(baseClass, selectedClass)}>
{page.page}
</Link>
);
} else {
return (
<Link
to={pageUrl(location, page.page)}
className={cn(baseClass, unselectedClass)}
>
<Link to={pageUrl(location, page.page)} className={cn(baseClass, unselectedClass)}>
{page.page}
</Link>
);
@@ -154,10 +141,7 @@ type SpecificPageLink = {
// If there are less than or equal to 6 pages, just show all the pages.
// If there are more than 5 pages, show the first 3, the current page, and the last 3.
function calculatePageLinks(
currentPage: number,
totalPages: number
): Array<PageLink> {
function calculatePageLinks(currentPage: number, totalPages: number): Array<PageLink> {
const pageLinks: Array<PageLink> = [];
if (totalPages <= 10) {
@@ -35,10 +35,7 @@ PopoverContent.displayName = PopoverPrimitive.Content.displayName;
function PopoverSectionHeader({ title }: { title: string }) {
return (
<Paragraph
variant="extra-extra-small/bright/caps"
className="bg-slate-900 px-2.5 py-2"
>
<Paragraph variant="extra-extra-small/bright/caps" className="bg-slate-900 px-2.5 py-2">
{title}
</Paragraph>
);
@@ -63,9 +60,7 @@ function PopoverMenuItem({
fullWidth
textAlignLeft
TrailingIcon={isSelected ? "check" : undefined}
className={
isSelected ? "bg-slate-750 group-hover:bg-slate-750" : undefined
}
className={isSelected ? "bg-slate-750 group-hover:bg-slate-750" : undefined}
>
{title}
</LinkButton>
@@ -77,9 +72,7 @@ function PopoverArrowTrigger({
children,
className,
...props
}: { isOpen?: boolean } & React.ComponentPropsWithoutRef<
typeof PopoverTrigger
>) {
}: { isOpen?: boolean } & React.ComponentPropsWithoutRef<typeof PopoverTrigger>) {
return (
<PopoverTrigger
{...props}
@@ -88,17 +81,11 @@ function PopoverArrowTrigger({
className
)}
>
<Paragraph
variant="extra-small"
className="transition group-hover:text-bright"
>
<Paragraph variant="extra-small" className="transition group-hover:text-bright">
{children}
</Paragraph>
<ChevronDownIcon
className={cn(
"h-3 w-3 transition group-hover:text-bright",
isOpen && "-rotate-180"
)}
className={cn("h-3 w-3 transition group-hover:text-bright", isOpen && "-rotate-180")}
/>
</PopoverTrigger>
);
@@ -45,9 +45,7 @@ export const RadioGroup = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof RadioGroupPrimitive.Root>
>(({ className, ...props }, ref) => {
return (
<RadioGroupPrimitive.Root className={className} {...props} ref={ref} />
);
return <RadioGroupPrimitive.Root className={className} {...props} ref={ref} />;
});
type RadioGroupItemProps = Omit<
@@ -64,67 +62,48 @@ type RadioGroupItemProps = Omit<
export const RadioGroupItem = React.forwardRef<
React.ElementRef<typeof RadioGroupPrimitive.Item>,
RadioGroupItemProps
>(
(
{
className,
children,
variant = "simple",
label,
description,
badges,
...props
},
ref
) => {
const variation = variants[variant];
>(({ className, children, variant = "simple", label, description, badges, ...props }, ref) => {
const variation = variants[variant];
return (
<RadioGroupPrimitive.Item
ref={ref}
return (
<RadioGroupPrimitive.Item
ref={ref}
className={cn(
"group flex cursor-pointer items-start gap-x-2 transition",
variation.button,
className
)}
{...props}
>
<div
className={cn(
"group flex cursor-pointer items-start gap-x-2 transition",
variation.button,
className
"aspect-square h-4 w-4 shrink-0 overflow-hidden rounded-sm border border-slate-700 ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
variation.inputPosition
)}
{...props}
>
<div
className={cn(
"aspect-square h-4 w-4 shrink-0 overflow-hidden rounded-sm border border-slate-700 ring-offset-background focus:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
variation.inputPosition
)}
>
<RadioGroupPrimitive.Indicator className="flex h-full w-full items-center justify-center bg-indigo-700">
<Circle className="h-1.5 w-1.5 fill-white text-white" />
</RadioGroupPrimitive.Indicator>
</div>
<div>
<div className="flex items-center gap-x-2">
<label
htmlFor={props.id}
className={cn("cursor-pointer", variation.label)}
>
{label}
</label>
{badges && (
<span className="-mr-2 flex gap-x-1.5">
{badges.map((badge) => (
<Badge key={badge}>{badge}</Badge>
))}
</span>
)}
</div>
{variant === "description" && (
<Paragraph
variant="small"
className={cn("mt-0.5", variation.description)}
>
{description}
</Paragraph>
<RadioGroupPrimitive.Indicator className="flex h-full w-full items-center justify-center bg-indigo-700">
<Circle className="h-1.5 w-1.5 fill-white text-white" />
</RadioGroupPrimitive.Indicator>
</div>
<div>
<div className="flex items-center gap-x-2">
<label htmlFor={props.id} className={cn("cursor-pointer", variation.label)}>
{label}
</label>
{badges && (
<span className="-mr-2 flex gap-x-1.5">
{badges.map((badge) => (
<Badge key={badge}>{badge}</Badge>
))}
</span>
)}
</div>
</RadioGroupPrimitive.Item>
);
}
);
{variant === "description" && (
<Paragraph variant="small" className={cn("mt-0.5", variation.description)}>
{description}
</Paragraph>
)}
</div>
</RadioGroupPrimitive.Item>
);
});
@@ -6,8 +6,7 @@ import { Check, ChevronDown } from "lucide-react";
import { cn } from "~/utils/cn";
const sizes = {
"secondary/small":
"text-xs h-6 bg-tertiary hover:bg-tertiary-foreground pr-2 pl-1 bg-slate-800",
"secondary/small": "text-xs h-6 bg-tertiary hover:bg-tertiary-foreground pr-2 pl-1 bg-slate-800",
medium:
"text-sm h-8 bg-slate-850 border border-slate-800 hover:bg-slate-800 hover:border-slate-750 px-2.5",
};
@@ -23,41 +22,30 @@ const SelectValue = SelectPrimitive.Value;
const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger> & SelectProps
>(
(
{
className,
children,
width = "content",
size = "secondary/small",
...props
},
ref
) => {
const sizeClassName = sizes[size];
return (
<SelectPrimitive.Trigger
ref={ref}
className={cn(
"group flex items-center justify-between gap-x-2 rounded text-dimmed ring-offset-background transition placeholder:text-dimmed hover:text-bright focus-visible:bg-tertiary-foreground focus-visible:text-bright focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-50",
width === "full" ? "w-full" : "w-min",
sizeClassName,
className
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDown
className={cn(
"h-4 w-4 text-dimmed transition group-hover:text-bright group-focus:text-bright"
)}
/>
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
);
}
);
>(({ className, children, width = "content", size = "secondary/small", ...props }, ref) => {
const sizeClassName = sizes[size];
return (
<SelectPrimitive.Trigger
ref={ref}
className={cn(
"group flex items-center justify-between gap-x-2 rounded text-dimmed ring-offset-background transition placeholder:text-dimmed hover:text-bright focus-visible:bg-tertiary-foreground focus-visible:text-bright focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring focus-visible:ring-offset-0 disabled:cursor-not-allowed disabled:opacity-50",
width === "full" ? "w-full" : "w-min",
sizeClassName,
className
)}
{...props}
>
{children}
<SelectPrimitive.Icon asChild>
<ChevronDown
className={cn(
"h-4 w-4 text-dimmed transition group-hover:text-bright group-focus:text-bright"
)}
/>
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
);
});
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
const SelectContent = React.forwardRef<
@@ -28,12 +28,7 @@ interface SheetPortalProps
extends SheetPrimitive.DialogPortalProps,
VariantProps<typeof portalVariants> {}
const SheetPortal = ({
position,
className,
children,
...props
}: SheetPortalProps) => (
const SheetPortal = ({ position, className, children, ...props }: SheetPortalProps) => (
<SheetPrimitive.Portal className={cn(className)} {...props}>
<div className={portalVariants({ position })}>{children}</div>
</SheetPrimitive.Portal>
@@ -166,19 +161,14 @@ const SheetContent = React.forwardRef<
</SheetPrimitive.Close>
<ShortcutKey shortcut={{ key: "esc" }} variant="small" />
</div>
<div className="flex max-h-full flex-col overflow-hidden">
{children}
</div>
<div className="flex max-h-full flex-col overflow-hidden">{children}</div>
</div>
</SheetPrimitive.Content>
</SheetPortal>
));
SheetContent.displayName = SheetPrimitive.Content.displayName;
export const SheetBody = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
export const SheetBody = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"overflow-y-auto p-4 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-slate-700",
@@ -188,10 +178,7 @@ export const SheetBody = ({
/>
);
export const SheetHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
export const SheetHeader = ({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"mx-4 flex shrink-0 items-center gap-4 border-b border-slate-800 py-3.5",
@@ -18,15 +18,10 @@ type ShortcutKeyProps = {
className?: string;
};
export function ShortcutKey({
shortcut,
variant,
className,
}: ShortcutKeyProps) {
export function ShortcutKey({ shortcut, variant, className }: ShortcutKeyProps) {
const { platform } = useOperatingSystem();
const isMac = platform === "mac";
let relevantShortcut =
"mac" in shortcut ? (isMac ? shortcut.mac : shortcut.windows) : shortcut;
let relevantShortcut = "mac" in shortcut ? (isMac ? shortcut.mac : shortcut.windows) : shortcut;
const modifiers = relevantShortcut.modifiers ?? [];
const character = relevantShortcut.key;
@@ -29,15 +29,7 @@ export function Spinner({
xmlns="http://www.w3.org/2000/svg"
className={cn("animate-spin motion-reduce:hidden", className)}
>
<rect
x="2"
y="2"
width="16"
height="16"
rx="8"
stroke={currentColor.light}
strokeWidth="3"
/>
<rect x="2" y="2" width="16" height="16" rx="8" stroke={currentColor.light} strokeWidth="3" />
<path
d="M10 18C5.58172 18 2 14.4183 2 10C2 5.58172 5.58172 2 10 2"
stroke={currentColor.dark}
@@ -8,57 +8,47 @@ const variations = {
large: {
container: "flex items-center gap-x-2 rounded-md hover:bg-slate-850 p-2",
root: "h-6 w-11",
thumb:
"h-5 w-5 data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0",
thumb: "h-5 w-5 data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0",
text: "text-sm text-slate-400 group-hover:text-slate-200",
},
small: {
container:
"flex items-center gap-x-1.5 rounded hover:bg-slate-850 pr-1 py-[0.1rem] pl-1.5",
container: "flex items-center gap-x-1.5 rounded hover:bg-slate-850 pr-1 py-[0.1rem] pl-1.5",
root: "h-3 w-6",
thumb:
"h-2.5 w-2.5 data-[state=checked]:translate-x-2.5 data-[state=unchecked]:translate-x-0",
thumb: "h-2.5 w-2.5 data-[state=checked]:translate-x-2.5 data-[state=unchecked]:translate-x-0",
text: "text-xs text-slate-400 group-hover:text-slate-200 mt-0.5",
},
};
type SwitchProps = React.ComponentPropsWithoutRef<
typeof SwitchPrimitives.Root
> & {
type SwitchProps = React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root> & {
label?: React.ReactNode;
variant: keyof typeof variations;
};
export const Switch = React.forwardRef<
React.ElementRef<typeof SwitchPrimitives.Root>,
SwitchProps
>(({ className, variant, label, ...props }, ref) => {
const { container, root, thumb, text } = variations[variant];
export const Switch = React.forwardRef<React.ElementRef<typeof SwitchPrimitives.Root>, SwitchProps>(
({ className, variant, label, ...props }, ref) => {
const { container, root, thumb, text } = variations[variant];
return (
<SwitchPrimitives.Root
className={cn("group", container, className)}
{...props}
ref={ref}
>
{label ? (
<label className={cn("text", text)}>
{typeof label === "string" ? <span>{label}</span> : label}
</label>
) : null}
<div
className={cn(
"peer inline-flex shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors group-focus-visible:ring-2 group-focus-visible:ring-ring group-focus-visible:ring-offset-2 group-focus-visible:ring-offset-background group-disabled:cursor-not-allowed group-disabled:opacity-50 group-data-[state=checked]:bg-primary group-data-[state=unchecked]:bg-slate-700 focus-visible:outline-none",
root
)}
>
<SwitchPrimitives.Thumb
return (
<SwitchPrimitives.Root className={cn("group", container, className)} {...props} ref={ref}>
{label ? (
<label className={cn("text", text)}>
{typeof label === "string" ? <span>{label}</span> : label}
</label>
) : null}
<div
className={cn(
thumb,
"pointer-events-none block rounded-full bg-slate-200 shadow-lg ring-0 transition-transform"
"peer inline-flex shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors group-focus-visible:ring-2 group-focus-visible:ring-ring group-focus-visible:ring-offset-2 group-focus-visible:ring-offset-background group-disabled:cursor-not-allowed group-disabled:opacity-50 group-data-[state=checked]:bg-primary group-data-[state=unchecked]:bg-slate-700 focus-visible:outline-none",
root
)}
/>
</div>
</SwitchPrimitives.Root>
);
});
>
<SwitchPrimitives.Thumb
className={cn(
thumb,
"pointer-events-none block rounded-full bg-slate-200 shadow-lg ring-0 transition-transform"
)}
/>
</div>
</SwitchPrimitives.Root>
);
}
);
+28 -55
View File
@@ -18,10 +18,7 @@ export const Table = forwardRef<HTMLTableElement, TableProps>(
containerClassName
)}
>
<table
ref={ref}
className={cn("w-full divide-y bg-midnight-850", className)}
>
<table ref={ref} className={cn("w-full divide-y bg-midnight-850", className)}>
{children}
</table>
</div>
@@ -34,23 +31,18 @@ type TableHeaderProps = {
children: ReactNode;
};
export const TableHeader = forwardRef<
HTMLTableSectionElement,
TableHeaderProps
>(({ className, children }, ref) => {
return (
<thead
ref={ref}
className={cn(
"rounded-t-md",
"relative divide-y divide-slate-850",
className
)}
>
{children}
</thead>
);
});
export const TableHeader = forwardRef<HTMLTableSectionElement, TableHeaderProps>(
({ className, children }, ref) => {
return (
<thead
ref={ref}
className={cn("rounded-t-md", "relative divide-y divide-slate-850", className)}
>
{children}
</thead>
);
}
);
type TableBodyProps = {
className?: string;
@@ -60,10 +52,7 @@ type TableBodyProps = {
export const TableBody = forwardRef<HTMLTableSectionElement, TableBodyProps>(
({ className, children }, ref) => {
return (
<tbody
ref={ref}
className={cn("relative divide-y divide-slate-850", className)}
>
<tbody ref={ref} className={cn("relative divide-y divide-slate-850", className)}>
{children}
</tbody>
);
@@ -96,14 +85,8 @@ type TableHeaderCellProps = TableCellBasicProps & {
hiddenLabel?: boolean;
};
export const TableHeaderCell = forwardRef<
HTMLTableCellElement,
TableHeaderCellProps
>(
(
{ className, alignment = "left", children, colSpan, hiddenLabel = false },
ref
) => {
export const TableHeaderCell = forwardRef<HTMLTableCellElement, TableHeaderCellProps>(
({ className, alignment = "left", children, colSpan, hiddenLabel = false }, ref) => {
let alignmentClassName = "text-left";
switch (alignment) {
case "center":
@@ -193,13 +176,7 @@ export const TableCellChevron = forwardRef<
}
>(({ className, to, onClick }, ref) => {
return (
<TableCell
className={className}
to={to}
onClick={onClick}
ref={ref}
alignment="right"
>
<TableCell className={className} to={to} onClick={onClick} ref={ref} alignment="right">
<ChevronRightIcon className="h-4 w-4 text-slate-700 transition group-hover:text-bright" />
</TableCell>
);
@@ -211,18 +188,14 @@ type TableBlankRowProps = {
children: ReactNode;
};
export const TableBlankRow = forwardRef<
HTMLTableRowElement,
TableBlankRowProps
>(({ children, colSpan, className }, ref) => {
return (
<tr ref={ref}>
<td
colSpan={colSpan}
className={cn("py-6 text-center text-sm", className)}
>
{children}
</td>
</tr>
);
});
export const TableBlankRow = forwardRef<HTMLTableRowElement, TableBlankRowProps>(
({ children, colSpan, className }, ref) => {
return (
<tr ref={ref}>
<td colSpan={colSpan} className={cn("py-6 text-center text-sm", className)}>
{children}
</td>
</tr>
);
}
);
+3 -16
View File
@@ -12,19 +12,9 @@ export type TabsProps = {
export function Tabs({ tabs, className }: TabsProps) {
return (
<div
className={cn(
`flex flex-row gap-x-6 border-b border-slate-800`,
className
)}
>
<div className={cn(`flex flex-row gap-x-6 border-b border-slate-800`, className)}>
{tabs.map((tab, index) => (
<NavLink
key={index}
to={tab.to}
className="group flex flex-col items-center pt-1"
end
>
<NavLink key={index} to={tab.to} className="group flex flex-col items-center pt-1" end>
{({ isActive, isPending }) => (
<>
<span
@@ -36,10 +26,7 @@ export function Tabs({ tabs, className }: TabsProps) {
{tab.label}
</span>
{isActive || isPending ? (
<motion.div
layoutId="underline"
className="mt-1 h-0.5 w-full bg-indigo-500"
/>
<motion.div layoutId="underline" className="mt-1 h-0.5 w-full bg-indigo-500" />
) : (
<div className="mt-1 h-0.5 w-full bg-slate-500 opacity-0 transition duration-200 group-hover:opacity-100" />
)}
@@ -34,20 +34,14 @@ export function TextLink({
<Link to={to} className={cn(classes, className)} {...props}>
{children}{" "}
{trailingIcon && (
<NamedIcon
name={trailingIcon}
className={cn("h-4 w-4", trailingIconClassName)}
/>
<NamedIcon name={trailingIcon} className={cn("h-4 w-4", trailingIconClassName)} />
)}
</Link>
) : href ? (
<a href={href} className={cn(classes, className)} {...props}>
{children}{" "}
{trailingIcon && (
<NamedIcon
name={trailingIcon}
className={cn("h-4 w-4", trailingIconClassName)}
/>
<NamedIcon name={trailingIcon} className={cn("h-4 w-4", trailingIconClassName)} />
)}
</a>
) : (
@@ -21,16 +21,12 @@ export function Toast() {
switch (type) {
case "success":
toast.success(message, {
duration: options.ephemeral
? defaultToastDuration
: permanentToastDuration,
duration: options.ephemeral ? defaultToastDuration : permanentToastDuration,
});
break;
case "error":
toast.error(message, {
duration: options.ephemeral
? defaultToastDuration
: permanentToastDuration,
duration: options.ephemeral ? defaultToastDuration : permanentToastDuration,
});
break;
default:
@@ -9,9 +9,7 @@ const TooltipProvider = TooltipPrimitive.Provider;
const TooltipArrow = React.forwardRef<
React.ElementRef<typeof TooltipPrimitive.Arrow>,
React.ComponentPropsWithoutRef<typeof TooltipPrimitive.Arrow>
>(({ ...props }, ref) => (
<TooltipPrimitive.Arrow className="z-50 fill-popover" {...props} />
));
>(({ ...props }, ref) => <TooltipPrimitive.Arrow className="z-50 fill-popover" {...props} />);
TooltipArrow.displayName = TooltipPrimitive.Arrow.displayName;
const Tooltip = React.forwardRef<
@@ -63,11 +61,4 @@ function SimpleTooltip({
);
}
export {
Tooltip,
TooltipTrigger,
TooltipContent,
TooltipProvider,
TooltipArrow,
SimpleTooltip,
};
export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider, TooltipArrow, SimpleTooltip };
+9 -41
View File
@@ -63,22 +63,12 @@ export function RunPanelHeader({
<div
className={cn(
"flex items-center justify-between px-2",
styleName === "normal"
? "h-10 border-b border-slate-850 bg-midnight-850 py-2"
: "pt-2"
styleName === "normal" ? "h-10 border-b border-slate-850 bg-midnight-850 py-2" : "pt-2"
)}
>
<div className="flex items-center gap-2">
{typeof icon === "string" ? (
<NamedIcon name={icon} className="h-5 w-5" />
) : (
icon
)}
{typeof title === "string" ? (
<Paragraph variant="small/bright">{title}</Paragraph>
) : (
title
)}
{typeof icon === "string" ? <NamedIcon name={icon} className="h-5 w-5" /> : icon}
{typeof title === "string" ? <Paragraph variant="small/bright">{title}</Paragraph> : title}
</div>
<div className="flex items-center gap-2">{accessory}</div>
</div>
@@ -111,18 +101,9 @@ const variantClasses: Record<string, string> = {
debug: "",
};
export function RunPanelDescription({
text,
variant,
}: {
text: string;
variant?: string;
}) {
export function RunPanelDescription({ text, variant }: { text: string; variant?: string }) {
return (
<Paragraph
variant="small"
className={cn(variant && variantClasses[variant])}
>
<Paragraph variant="small" className={cn(variant && variantClasses[variant])}>
{text}
</Paragraph>
);
@@ -155,11 +136,7 @@ export function RunPanelIconSection({
children: React.ReactNode;
className?: string;
}) {
return (
<div className={cn("flex flex-wrap gap-x-8 gap-y-2", className)}>
{children}
</div>
);
return <div className={cn("flex flex-wrap gap-x-8 gap-y-2", className)}>{children}</div>;
}
export function RunPanelDivider() {
@@ -178,11 +155,7 @@ export function RunPanelIconProperty({
return (
<div className="flex items-center gap-2">
<div className="flex h-8 w-8 items-center justify-center rounded-sm border border-slate-800 bg-slate-850">
{typeof icon === "string" ? (
<NamedIcon name={icon} className="h-5 w-5" />
) : (
icon
)}
{typeof icon === "string" ? <NamedIcon name={icon} className="h-5 w-5" /> : icon}
</div>
<div className="flex flex-col gap-0.5">
<Paragraph variant="extra-extra-small/caps">{label}</Paragraph>
@@ -205,9 +178,7 @@ export function RunPanelProperties({
<div
className={cn(
"flex items-baseline",
layout === "horizontal"
? "flex-wrap gap-x-8 gap-y-2"
: "flex-col gap-y-3",
layout === "horizontal" ? "flex-wrap gap-x-8 gap-y-2" : "flex-col gap-y-3",
className
)}
>
@@ -220,10 +191,7 @@ export function RunPanelProperties({
export function TaskSeparator({ depth }: { depth: number }) {
return (
<div
className="h-4 w-4 border-r border-slate-600"
style={{ marginLeft: `${depth}rem` }}
/>
<div className="h-4 w-4 border-r border-slate-600" style={{ marginLeft: `${depth}rem` }} />
);
}
@@ -52,18 +52,11 @@ export function RunCompletedDetail({ run }: { run: MatchedRun }) {
)}
</RunPanelIconSection>
<RunPanelDivider />
{run.error && (
<RunPanelError
text={run.error.message}
stackTrace={run.error.stack}
/>
)}
{run.error && <RunPanelError text={run.error.message} stackTrace={run.error.stack} />}
{run.output ? (
<CodeBlock language="json" code={run.output} />
) : (
run.output === null && (
<Paragraph variant="small">This run returned nothing</Paragraph>
)
run.output === null && <Paragraph variant="small">This run returned nothing</Paragraph>
)}
</RunPanelBody>
</RunPanel>
+24 -87
View File
@@ -17,11 +17,7 @@ import { cancelSchema } from "~/routes/resources.runs.$runId.cancel";
import { schema } from "~/routes/resources.runs.$runId.rerun";
import { formatDuration } from "~/utils";
import { cn } from "~/utils/cn";
import {
runCompletedPath,
runTaskPath,
runTriggerPath,
} from "~/utils/pathBuilder";
import { runCompletedPath, runTaskPath, runTriggerPath } from "~/utils/pathBuilder";
import { CodeBlock } from "../code/CodeBlock";
import { EnvironmentLabel } from "../environments/EnvironmentLabel";
import { PageBody, PageContainer } from "../layout/AppLayout";
@@ -78,12 +74,7 @@ type RunOverviewProps = {
const taskPattern = /\/tasks\/(.*)/;
export function RunOverview({
run,
trigger,
showRerun,
paths,
}: RunOverviewProps) {
export function RunOverview({ run, trigger, showRerun, paths }: RunOverviewProps) {
const navigate = useNavigate();
const pathName = usePathName();
@@ -144,19 +135,9 @@ export function RunOverview({
<PageInfoProperty
icon={"calendar"}
label={"Started"}
value={
run.startedAt ? (
<DateTime date={run.startedAt} />
) : (
"Not started yet"
)
}
/>
<PageInfoProperty
icon={"property"}
label={"Version"}
value={`v${run.version}`}
value={run.startedAt ? <DateTime date={run.startedAt} /> : "Not started yet"}
/>
<PageInfoProperty icon={"property"} label={"Version"} value={`v${run.version}`} />
<PageInfoProperty
label={"Env"}
value={<EnvironmentLabel environment={run.environment} />}
@@ -186,10 +167,7 @@ export function RunOverview({
<RunPanelHeader icon={trigger.icon} title={trigger.title} />
<RunPanelBody>
<RunPanelProperties
properties={[
{ label: "Event name", text: run.event.name },
...run.properties,
]}
properties={[{ label: "Event name", text: run.event.name }, ...run.properties]}
/>
</RunPanelBody>
</RunPanel>
@@ -226,12 +204,7 @@ export function RunOverview({
onClick={() => navigate(runCompletedPath(paths.run))}
>
<RunPanelHeader
icon={
<RunStatusIcon
status={run.status}
className={"h-5 w-5"}
/>
}
icon={<RunStatusIcon status={run.status} className={"h-5 w-5"} />}
title={
<Paragraph variant="small/bright">
<RunStatusLabel status={run.status} />
@@ -258,34 +231,21 @@ export function RunOverview({
<RunPanelIconProperty
icon="clock"
label="Total duration"
value={formatDuration(
run.startedAt,
run.completedAt,
{
style: "long",
}
)}
value={formatDuration(run.startedAt, run.completedAt, {
style: "long",
})}
/>
)}
</RunPanelIconSection>
<RunPanelDivider />
{run.error && (
<RunPanelError
text={run.error.message}
stackTrace={run.error.stack}
/>
<RunPanelError text={run.error.message} stackTrace={run.error.stack} />
)}
{run.output ? (
<CodeBlock
language="json"
code={run.output}
maxLines={10}
/>
<CodeBlock language="json" code={run.output} maxLines={10} />
) : (
run.output === null && (
<Paragraph variant="small">
This Run returned nothing.
</Paragraph>
<Paragraph variant="small">This Run returned nothing.</Paragraph>
)
)}
</RunPanelBody>
@@ -297,11 +257,7 @@ export function RunOverview({
{/* Detail view */}
<div className="overflow-y-auto py-4 pr-4 scrollbar-thin scrollbar-track-transparent scrollbar-thumb-slate-700">
<Header2 className="mb-2">Detail</Header2>
{selectedId ? (
<Outlet />
) : (
<Callout variant="info">Select a task or trigger</Callout>
)}
{selectedId ? <Outlet /> : <Callout variant="info">Select a task or trigger</Callout>}
</div>
</div>
</PageBody>
@@ -318,9 +274,7 @@ function BlankTasks({
}) {
switch (basicStatus) {
case "COMPLETED":
return (
<Paragraph variant="small">There were no tasks for this run.</Paragraph>
);
return <Paragraph variant="small">There were no tasks for this run.</Paragraph>;
case "FAILED":
return <Paragraph variant="small">No tasks were run.</Paragraph>;
case "WAITING":
@@ -335,9 +289,7 @@ function BlankTasks({
</div>
);
default:
return (
<Paragraph variant="small">There were no tasks for this run.</Paragraph>
);
return <Paragraph variant="small">There were no tasks for this run.</Paragraph>;
}
}
@@ -370,15 +322,8 @@ function RerunPopover({
</Button>
</PopoverTrigger>
<PopoverContent className="flex w-80 flex-col gap-2 p-4" align="end">
<Form
method="post"
action={`/resources/runs/${runId}/rerun`}
{...form.props}
>
<input
{...conform.input(successRedirect, { type: "hidden" })}
defaultValue={runsPath}
/>
<Form method="post" action={`/resources/runs/${runId}/rerun`} {...form.props}>
<input {...conform.input(successRedirect, { type: "hidden" })} defaultValue={runsPath} />
{environmentType === "PRODUCTION" && (
<Callout variant="warning">
This will rerun this Job in your Production environment.
@@ -399,8 +344,8 @@ function RerunPopover({
</Button>
<Paragraph variant="extra-small" className="mt-2">
Start a brand new job run with the same Trigger data as this
one. This will re-do every task.
Start a brand new job run with the same Trigger data as this one. This will re-do
every task.
</Paragraph>
</div>
{status === "FAILED" && (
@@ -417,8 +362,8 @@ function RerunPopover({
</Button>
<Paragraph variant="extra-small" className="mt-2">
Continue running this job run from where it left off. This
will skip any task that has already been completed.
Continue running this job run from where it left off. This will skip any task that
has already been completed.
</Paragraph>
</div>
)}
@@ -442,19 +387,11 @@ export function CancelRun({ runId }: { runId: string }) {
},
});
const isLoading =
navigation.state === "submitting" && navigation.formData !== undefined;
const isLoading = navigation.state === "submitting" && navigation.formData !== undefined;
return (
<Form
method="post"
action={`/resources/runs/${runId}/cancel`}
{...form.props}
>
<input
{...conform.input(redirectUrl, { type: "hidden" })}
defaultValue={location.pathname}
/>
<Form method="post" action={`/resources/runs/${runId}/cancel`} {...form.props}>
<input {...conform.input(redirectUrl, { type: "hidden" })} defaultValue={location.pathname} />
<Button
type="submit"
LeadingIcon={isLoading ? "spinner-white" : "stop"}
@@ -1,8 +1,4 @@
import {
CheckCircleIcon,
ClockIcon,
XCircleIcon,
} from "@heroicons/react/24/solid";
import { CheckCircleIcon, ClockIcon, XCircleIcon } from "@heroicons/react/24/solid";
import type { TaskAttemptStatus } from "@trigger.dev/database";
import { Paragraph } from "~/components/primitives/Paragraph";
import { Spinner } from "~/components/primitives/Spinner";
@@ -13,53 +9,27 @@ type TaskAttemptStatusProps = {
className?: string;
};
export function TaskAttemptStatusLabel({
status,
}: {
status: TaskAttemptStatus;
}) {
export function TaskAttemptStatusLabel({ status }: { status: TaskAttemptStatus }) {
return (
<span className="inline-flex items-center gap-0.5">
<TaskAttemptStatusIcon status={status} className="h-4 w-4" />
<Paragraph
variant="extra-small"
className={taskAttemptStatusClassNameColor(status)}
>
<Paragraph variant="extra-small" className={taskAttemptStatusClassNameColor(status)}>
{taskAttemptStatusTitle(status)}
</Paragraph>
</span>
);
}
export function TaskAttemptStatusIcon({
status,
className,
}: TaskAttemptStatusProps) {
export function TaskAttemptStatusIcon({ status, className }: TaskAttemptStatusProps) {
switch (status) {
case "COMPLETED":
return (
<CheckCircleIcon
className={cn(taskAttemptStatusClassNameColor(status), className)}
/>
);
return <CheckCircleIcon className={cn(taskAttemptStatusClassNameColor(status), className)} />;
case "PENDING":
return (
<ClockIcon
className={cn(taskAttemptStatusClassNameColor(status), className)}
/>
);
return <ClockIcon className={cn(taskAttemptStatusClassNameColor(status), className)} />;
case "STARTED":
return (
<Spinner
className={cn(taskAttemptStatusClassNameColor(status), className)}
/>
);
return <Spinner className={cn(taskAttemptStatusClassNameColor(status), className)} />;
case "ERRORED":
return (
<XCircleIcon
className={cn(taskAttemptStatusClassNameColor(status), className)}
/>
);
return <XCircleIcon className={cn(taskAttemptStatusClassNameColor(status), className)} />;
}
}
+10 -43
View File
@@ -55,11 +55,7 @@ export function TaskCard({
return (
<Fragment>
<div style={{ marginLeft: `${depth}rem` }}>
<RunPanel
selected={isSelected}
onClick={() => selectedTask(id)}
styleName={style?.style}
>
<RunPanel selected={isSelected} onClick={() => selectedTask(id)} styleName={style?.style}>
<RunPanelHeader
icon={
status === "COMPLETED" ? (
@@ -72,41 +68,19 @@ export function TaskCard({
/>
)
}
title={
status === "COMPLETED" ? (
name
) : (
<RunPanelIconTitle icon={icon} title={name} />
)
}
title={status === "COMPLETED" ? name : <RunPanelIconTitle icon={icon} title={name} />}
accessory={
<Paragraph variant="extra-small">
<UpdatingDuration
start={startedAt ?? undefined}
end={completedAt ?? undefined}
/>
<UpdatingDuration start={startedAt ?? undefined} end={completedAt ?? undefined} />
</Paragraph>
}
styleName={style?.style}
/>
<RunPanelBody>
{error && (
<RunPanelError text={error.message} stackTrace={error.stack} />
)}
{description && (
<RunPanelDescription
text={description}
variant={style?.variant}
/>
)}
{error && <RunPanelError text={error.message} stackTrace={error.stack} />}
{description && <RunPanelDescription text={description} variant={style?.variant} />}
<RunPanelIconSection>
{displayKey && (
<RunPanelIconProperty
icon="key"
label="Key"
value={displayKey}
/>
)}
{displayKey && <RunPanelIconProperty icon="key" label="Key" value={displayKey} />}
{delayUntil && !completedAt && (
<>
<UpdatingDelay delayUntil={delayUntil} />
@@ -125,8 +99,7 @@ export function TaskCard({
{connection && (
<RunPanelIconProperty
icon={
connection.integration.definition.icon ??
connection.integration.definitionId
connection.integration.definition.icon ?? connection.integration.definitionId
}
label="Connection"
value={connection.integration.slug}
@@ -145,9 +118,7 @@ export function TaskCard({
<div className="flex items-center gap-2">
<Square2StackIcon className="h-5 w-5 text-slate-400" />
<Paragraph variant="small">
{simplur`${expanded ? "Hide" : "Show"} ${
subtasks.length
} subtask[|s]`}
{simplur`${expanded ? "Hide" : "Show"} ${subtasks.length} subtask[|s]`}
</Paragraph>
</div>
<motion.span
@@ -158,17 +129,13 @@ export function TaskCard({
expanded: { rotate: 180, transition: { ease: "anticipate" } },
}}
>
<ChevronDownIcon
className={"h-5 w-5 text-slate-400 transition"}
/>
<ChevronDownIcon className={"h-5 w-5 text-slate-400 transition"} />
</motion.span>
</button>
)}
</RunPanel>
</div>
{(!isLast || expanded) && (
<TaskSeparator depth={depth + (expanded ? 1 : 0)} />
)}
{(!isLast || expanded) && <TaskSeparator depth={depth + (expanded ? 1 : 0)} />}
{subtasks &&
subtasks.length > 0 &&
@@ -5,9 +5,7 @@ export function TaskCardSkeleton() {
<RunPanel>
<RunPanelHeader
icon={undefined}
title={
<div className="h-5 w-36 max-w-full rounded border bg-slate-800" />
}
title={<div className="h-5 w-36 max-w-full rounded border bg-slate-800" />}
/>
<RunPanelBody>
<div className="flex w-full flex-col justify-between gap-y-1">
+4 -18
View File
@@ -49,20 +49,11 @@ export function TaskDetail({ task }: { task: DetailedTask }) {
return (
<RunPanel selected={false}>
<RunPanelHeader
icon={
<TaskStatusIcon
status={status}
minimal={true}
className={cn("h-5 w-5")}
/>
}
icon={<TaskStatusIcon status={status} minimal={true} className={cn("h-5 w-5")} />}
title={<RunPanelIconTitle icon={icon} title={name} />}
accessory={
<Paragraph variant="extra-small">
<UpdatingDuration
start={startedAt ?? undefined}
end={completedAt ?? undefined}
/>
<UpdatingDuration start={startedAt ?? undefined} end={completedAt ?? undefined} />
</Paragraph>
}
/>
@@ -104,9 +95,7 @@ export function TaskDetail({ task }: { task: DetailedTask }) {
)}
</RunPanelIconSection>
<RunPanelDivider />
{description && (
<RunPanelDescription text={description} variant={style?.variant} />
)}
{description && <RunPanelDescription text={description} variant={style?.variant} />}
{properties.length > 0 && (
<div className="mt-4 flex flex-col gap-2">
<Header3>Properties</Header3>
@@ -153,10 +142,7 @@ export function TaskDetail({ task }: { task: DetailedTask }) {
<div className="mt-4 flex flex-col gap-2">
<Header3>Input</Header3>
{params ? (
<CodeBlock
code={JSON.stringify(params, sensitiveDataReplacer, 2)}
maxLines={35}
/>
<CodeBlock code={JSON.stringify(params, sensitiveDataReplacer, 2)} maxLines={35} />
) : (
<Paragraph variant="small">No input</Paragraph>
)}
+8 -32
View File
@@ -15,48 +15,24 @@ type TaskStatusIconProps = {
minimal?: boolean;
};
export function TaskStatusIcon({
status,
className,
minimal = false,
}: TaskStatusIconProps) {
export function TaskStatusIcon({ status, className, minimal = false }: TaskStatusIconProps) {
switch (status) {
case "COMPLETED":
return minimal ? (
<CheckIcon
className={cn(taskStatusClassNameColor(status), className)}
/>
<CheckIcon className={cn(taskStatusClassNameColor(status), className)} />
) : (
<CheckCircleIcon
className={cn(taskStatusClassNameColor(status), className)}
/>
<CheckCircleIcon className={cn(taskStatusClassNameColor(status), className)} />
);
case "PENDING":
return (
<ClockIcon
className={cn(taskStatusClassNameColor(status), className)}
/>
);
return <ClockIcon className={cn(taskStatusClassNameColor(status), className)} />;
case "WAITING":
return (
<Spinner className={cn(taskStatusClassNameColor(status), className)} />
);
return <Spinner className={cn(taskStatusClassNameColor(status), className)} />;
case "RUNNING":
return (
<Spinner className={cn(taskStatusClassNameColor(status), className)} />
);
return <Spinner className={cn(taskStatusClassNameColor(status), className)} />;
case "ERRORED":
return (
<XCircleIcon
className={cn(taskStatusClassNameColor(status), className)}
/>
);
return <XCircleIcon className={cn(taskStatusClassNameColor(status), className)} />;
case "CANCELED":
return (
<NoSymbolIcon
className={cn(taskStatusClassNameColor(status), className)}
/>
);
return <NoSymbolIcon className={cn(taskStatusClassNameColor(status), className)} />;
}
}
+13 -60
View File
@@ -31,82 +31,35 @@ export function RunStatus({ status }: { status: JobRunStatus }) {
}
export function RunStatusLabel({ status }: { status: JobRunStatus }) {
return (
<span className={runStatusClassNameColor(status)}>
{runStatusTitle(status)}
</span>
);
return <span className={runStatusClassNameColor(status)}>{runStatusTitle(status)}</span>;
}
export function RunStatusIcon({
status,
className,
}: {
status: JobRunStatus;
className: string;
}) {
export function RunStatusIcon({ status, className }: { status: JobRunStatus; className: string }) {
switch (status) {
case "SUCCESS":
return (
<CheckCircleIcon
className={cn(runStatusClassNameColor(status), className)}
/>
);
return <CheckCircleIcon className={cn(runStatusClassNameColor(status), className)} />;
case "PENDING":
return (
<ClockIcon className={cn(runStatusClassNameColor(status), className)} />
);
return <ClockIcon className={cn(runStatusClassNameColor(status), className)} />;
case "QUEUED":
return (
<ClockIcon className={cn(runStatusClassNameColor(status), className)} />
);
return <ClockIcon className={cn(runStatusClassNameColor(status), className)} />;
case "STARTED":
return (
<Spinner className={cn(runStatusClassNameColor(status), className)} />
);
return <Spinner className={cn(runStatusClassNameColor(status), className)} />;
case "FAILURE":
return (
<XCircleIcon
className={cn(runStatusClassNameColor(status), className)}
/>
);
return <XCircleIcon className={cn(runStatusClassNameColor(status), className)} />;
case "TIMED_OUT":
return (
<ExclamationTriangleIcon
className={cn(runStatusClassNameColor(status), className)}
/>
);
return <ExclamationTriangleIcon className={cn(runStatusClassNameColor(status), className)} />;
case "WAITING_ON_CONNECTIONS":
return (
<WrenchIcon
className={cn(runStatusClassNameColor(status), className)}
/>
);
return <WrenchIcon className={cn(runStatusClassNameColor(status), className)} />;
case "ABORTED":
return (
<XCircleIcon
className={cn(runStatusClassNameColor(status), className)}
/>
);
return <XCircleIcon className={cn(runStatusClassNameColor(status), className)} />;
case "PREPROCESSING":
return (
<Spinner className={cn(runStatusClassNameColor(status), className)} />
);
return <Spinner className={cn(runStatusClassNameColor(status), className)} />;
case "CANCELED":
return (
<NoSymbolIcon
className={cn(runStatusClassNameColor(status), className)}
/>
);
return <NoSymbolIcon className={cn(runStatusClassNameColor(status), className)} />;
}
}
export type RunBasicStatus =
| "WAITING"
| "PENDING"
| "RUNNING"
| "COMPLETED"
| "FAILED";
export type RunBasicStatus = "WAITING" | "PENDING" | "RUNNING" | "COMPLETED" | "FAILED";
export function runBasicStatus(status: JobRunStatus): RunBasicStatus {
switch (status) {
@@ -5,12 +5,7 @@ import { useOrganization } from "~/hooks/useOrganizations";
import { useProject } from "~/hooks/useProject";
import { RunList } from "~/presenters/RunListPresenter.server";
import { formatDuration } from "~/utils";
import {
JobForPath,
OrgForPath,
ProjectForPath,
jobRunDashboardPath,
} from "~/utils/pathBuilder";
import { JobForPath, OrgForPath, ProjectForPath, jobRunDashboardPath } from "~/utils/pathBuilder";
import { EnvironmentLabel } from "../environments/EnvironmentLabel";
import { Callout } from "../primitives/Callout";
import { DateTime } from "../primitives/DateTime";
@@ -1,13 +1,5 @@
import {
ArrowLeftIcon,
ArrowRightIcon,
ExclamationTriangleIcon,
} from "@heroicons/react/20/solid";
import {
ArrowUturnLeftIcon,
LightBulbIcon,
NoSymbolIcon,
} from "@heroicons/react/24/solid";
import { ArrowLeftIcon, ArrowRightIcon, ExclamationTriangleIcon } from "@heroicons/react/20/solid";
import { ArrowUturnLeftIcon, LightBulbIcon, NoSymbolIcon } from "@heroicons/react/24/solid";
import type { Meta, StoryObj } from "@storybook/react";
import { withDesign } from "storybook-addon-designs";
import { Button } from "../primitives/Buttons";
@@ -85,10 +77,7 @@ function ButtonList({ primary }: { primary: string }) {
</div>
<div className="flex flex-col items-start gap-2">
<Header3 className="mb-1 uppercase">Shortcut</Header3>
<Button
variant="primary/small"
shortcut={{ key: "s", modifiers: ["meta"] }}
>
<Button variant="primary/small" shortcut={{ key: "s", modifiers: ["meta"] }}>
Primary button
</Button>
<Button variant="secondary/small" shortcut={{ key: "f" }}>
@@ -97,10 +86,7 @@ function ButtonList({ primary }: { primary: string }) {
<Button variant="tertiary/small" shortcut={{ key: "i" }}>
Tertiary button
</Button>
<Button
variant="danger/small"
shortcut={{ key: "s", modifiers: ["meta"] }}
>
<Button variant="danger/small" shortcut={{ key: "s", modifiers: ["meta"] }}>
Danger button
</Button>
</div>
@@ -154,10 +140,7 @@ function ButtonList({ primary }: { primary: string }) {
<Button variant="primary/small" LeadingIcon={ArrowRightIcon} />
<Button variant="secondary/small" LeadingIcon={LightBulbIcon} />
<Button variant="tertiary/small" LeadingIcon="warning" />
<Button
variant="danger/small"
LeadingIcon={ExclamationTriangleIcon}
/>
<Button variant="danger/small" LeadingIcon={ExclamationTriangleIcon} />
</div>
</div>
<Header1 className="mb-2 mt-8">Medium buttons</Header1>
@@ -201,28 +184,16 @@ function ButtonList({ primary }: { primary: string }) {
</div>
<div className="flex flex-col items-start gap-2">
<Header3 className="mb-1 uppercase">Shortcut</Header3>
<Button
variant="primary/medium"
shortcut={{ key: "s", modifiers: ["meta"] }}
>
<Button variant="primary/medium" shortcut={{ key: "s", modifiers: ["meta"] }}>
Primary button
</Button>
<Button
variant="secondary/medium"
shortcut={{ key: "s", modifiers: ["meta"] }}
>
<Button variant="secondary/medium" shortcut={{ key: "s", modifiers: ["meta"] }}>
Secondary button
</Button>
<Button
variant="tertiary/medium"
shortcut={{ key: "s", modifiers: ["meta"] }}
>
<Button variant="tertiary/medium" shortcut={{ key: "s", modifiers: ["meta"] }}>
Tertiary button
</Button>
<Button
variant="danger/medium"
shortcut={{ key: "s", modifiers: ["meta"] }}
>
<Button variant="danger/medium" shortcut={{ key: "s", modifiers: ["meta"] }}>
Danger button
</Button>
</div>
@@ -276,10 +247,7 @@ function ButtonList({ primary }: { primary: string }) {
<Button variant="primary/medium" LeadingIcon={ArrowRightIcon} />
<Button variant="secondary/medium" LeadingIcon={LightBulbIcon} />
<Button variant="tertiary/medium" LeadingIcon="warning" />
<Button
variant="danger/medium"
LeadingIcon={ExclamationTriangleIcon}
/>
<Button variant="danger/medium" LeadingIcon={ExclamationTriangleIcon} />
</div>
</div>
<Header1 className="mb-2 mt-8">Large buttons</Header1>
@@ -305,20 +273,10 @@ function ButtonList({ primary }: { primary: string }) {
<Header1 className="mb-2 mt-8">Menu items</Header1>
<div className="grid grid-cols-1">
<div className="flex flex-col items-start gap-1 rounded border border-slate-800 bg-slate-850 p-1">
<Button
variant="menu-item"
fullWidth
textAlignLeft
LeadingIcon="folder"
>
<Button variant="menu-item" fullWidth textAlignLeft LeadingIcon="folder">
Acme Inc.
</Button>
<Button
variant="menu-item"
fullWidth
textAlignLeft
LeadingIcon="plus"
>
<Button variant="menu-item" fullWidth textAlignLeft LeadingIcon="plus">
New Project
</Button>
<Button
@@ -327,26 +285,14 @@ function ButtonList({ primary }: { primary: string }) {
textAlignLeft
LeadingIcon="globe"
TrailingIcon={isSelected ? "check" : undefined}
className={
isSelected ? "bg-slate-750 group-hover:bg-slate-750" : undefined
}
className={isSelected ? "bg-slate-750 group-hover:bg-slate-750" : undefined}
>
Item enabled
</Button>
<Button
variant="menu-item"
fullWidth
textAlignLeft
LeadingIcon="slack"
>
<Button variant="menu-item" fullWidth textAlignLeft LeadingIcon="slack">
When a Stripe payment fails re-engage the customer
</Button>
<Button
variant="menu-item"
fullWidth
textAlignLeft
LeadingIcon="spinner"
>
<Button variant="menu-item" fullWidth textAlignLeft LeadingIcon="spinner">
In Progress
</Button>
<Button
@@ -27,10 +27,7 @@ function CalloutsSet() {
<Callout variant="error">This is an error callout</Callout>
<Callout variant="idea">This is an idea callout</Callout>
<Callout variant="docs">This is a docs callout</Callout>
<Callout
variant="idea"
icon={<EnvelopeIcon className="h-5 w-5 text-green-400" />}
>
<Callout variant="idea" icon={<EnvelopeIcon className="h-5 w-5 text-green-400" />}>
This has a custom icon
</Callout>
</div>
@@ -50,11 +47,7 @@ function CalloutsSet() {
<Callout to="#" variant="docs">
This is a docs callout
</Callout>
<Callout
to="#"
variant="idea"
icon={<EnvelopeIcon className="h-5 w-5 text-green-400" />}
>
<Callout to="#" variant="idea" icon={<EnvelopeIcon className="h-5 w-5 text-green-400" />}>
This has a custom icon
</Callout>
</div>
@@ -28,11 +28,7 @@ function ClipboardFieldExample() {
variant="tertiary/small"
icon={<EnvironmentLabel environment={{ type: "PRODUCTION" }} />}
/>
<ClipboardField
value="with leadingIcon"
variant="tertiary/small"
icon="search"
/>
<ClipboardField value="with leadingIcon" variant="tertiary/small" icon="search" />
<ClipboardField value="copy paste me" variant="primary/medium" />
<ClipboardField value="copy paste me" variant="secondary/medium" />
<ClipboardField value="copy paste me" variant="tertiary/medium" />
@@ -41,28 +37,12 @@ function ClipboardFieldExample() {
variant="tertiary/medium"
icon={<EnvironmentLabel environment={{ type: "DEVELOPMENT" }} />}
/>
<ClipboardField
value="with leadingIcon"
variant="tertiary/medium"
icon="search"
/>
<ClipboardField value="with leadingIcon" variant="tertiary/medium" icon="search" />
</div>
<div className="flex flex-col items-start gap-y-8 p-8">
<ClipboardField
value="copy paste me"
variant="primary/small"
secure={true}
/>
<ClipboardField
value="copy paste me"
variant="secondary/small"
secure={true}
/>
<ClipboardField
value="copy paste me"
variant="tertiary/small"
secure={true}
/>
<ClipboardField value="copy paste me" variant="primary/small" secure={true} />
<ClipboardField value="copy paste me" variant="secondary/small" secure={true} />
<ClipboardField value="copy paste me" variant="tertiary/small" secure={true} />
<ClipboardField
value="with leadingIcon"
variant="tertiary/small"
@@ -75,21 +55,9 @@ function ClipboardFieldExample() {
icon="search"
secure={true}
/>
<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="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"
@@ -104,21 +72,9 @@ function ClipboardFieldExample() {
/>
</div>
<div className="flex flex-col items-start gap-y-8 p-8">
<ClipboardField
value="npx abcdefghi"
variant="primary/small"
secure="npx ••••••••"
/>
<ClipboardField
value="npx abcdefghi"
variant="secondary/small"
secure="npx ••••••••"
/>
<ClipboardField
value="npx abcdefghi"
variant="tertiary/small"
secure="npx ••••••••"
/>
<ClipboardField value="npx abcdefghi" variant="primary/small" secure="npx ••••••••" />
<ClipboardField value="npx abcdefghi" variant="secondary/small" secure="npx ••••••••" />
<ClipboardField value="npx abcdefghi" variant="tertiary/small" secure="npx ••••••••" />
<ClipboardField
value="npx abcdefghi"
variant="tertiary/small"
@@ -131,21 +87,9 @@ function ClipboardFieldExample() {
icon="search"
secure="npx ••••••••"
/>
<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 ••••••••"
/>
<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 ••••••••" />
<ClipboardField
value="npx abcdefghi"
variant="tertiary/medium"
@@ -159,9 +159,7 @@ function AnimatedHighlight() {
}}
className={cn(
"transition-bg text-xl transition-colors duration-300",
region.range
? "cursor-pointer rounded px-1 text-indigo-500"
: "text-slate-500",
region.range ? "cursor-pointer rounded px-1 text-indigo-500" : "text-slate-500",
highlighted === index && "bg-indigo-500 text-white"
)}
>
@@ -176,9 +174,7 @@ function AnimatedHighlight() {
}}
onMouseLeave={() => {
setShouldAnimate(true);
setHighlighted(
highlightedRegions.findIndex((region) => region.range)
);
setHighlighted(highlightedRegions.findIndex((region) => region.range));
}}
>
<CodeBlock code={code} highlightedRanges={highlightedRanges} />
@@ -34,8 +34,8 @@ export const Basic: Story = {
<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.
This action cannot be undone. This will permanently delete your account and remove your
data from our servers.
</DialogDescription>
</DialogHeader>
</DialogContent>
@@ -81,22 +81,14 @@ function Forms() {
<InputGroup>
<Label>Project name</Label>
<Input
placeholder="Your Project name"
required={true}
icon="folder"
/>
<Input placeholder="Your Project name" required={true} icon="folder" />
<FormError>You must enter a project name</FormError>
<Hint>Your Jobs will live inside this Project.</Hint>
</InputGroup>
<FormButtons
confirmButton={
<Button
type="submit"
variant={"primary/small"}
TrailingIcon="arrow-right"
>
<Button type="submit" variant={"primary/small"} TrailingIcon="arrow-right">
Create
</Button>
}
@@ -129,8 +121,7 @@ function LoginForm() {
Continue with Email
</Button>
<Paragraph variant="small" className="mt-2 text-center">
By creating an account you agree to our{" "}
<TextLink href="#">terms</TextLink> and{" "}
By creating an account you agree to our <TextLink href="#">terms</TextLink> and{" "}
<TextLink href="#">privacy</TextLink> policies.
</Paragraph>
</Fieldset>
@@ -148,12 +139,7 @@ function SearchForm() {
<Fieldset>
<InputGroup>
<Label>Medium search input</Label>
<Input
placeholder="Search"
required={true}
icon="search"
shortcut="⌘K"
/>
<Input placeholder="Search" required={true} icon="search" shortcut="⌘K" />
</InputGroup>
<InputGroup>
<Label>Small search input</Label>
@@ -178,30 +164,15 @@ function SearchForm() {
</InputGroup>
<InputGroup>
<Label variant="small">This is a small label</Label>
<Input
placeholder="Search"
required={true}
icon="search"
shortcut="⌘K"
/>
<Input placeholder="Search" required={true} icon="search" shortcut="⌘K" />
</InputGroup>
<InputGroup>
<Label variant="medium">This is a medium label</Label>
<Input
placeholder="Search"
required={true}
icon="search"
shortcut="⌘K"
/>
<Input placeholder="Search" required={true} icon="search" shortcut="⌘K" />
</InputGroup>
<InputGroup>
<Label variant="large">This is a large label</Label>
<Input
placeholder="Search"
required={true}
icon="search"
shortcut="⌘K"
/>
<Input placeholder="Search" required={true} icon="search" shortcut="⌘K" />
</InputGroup>
</Fieldset>
</Form>
@@ -132,21 +132,11 @@ function PopoverMenu() {
<div className="flex flex-col gap-1 p-1">
<PopoverMenuItem to="#" title="My Blog" icon="folder" />
<PopoverMenuItem
to="#"
title="New Project"
isSelected={false}
icon="plus"
/>
<PopoverMenuItem to="#" title="New Project" isSelected={false} icon="plus" />
</div>
</Fragment>
<div className="border-t border-slate-800 p-1">
<PopoverMenuItem
to="#"
title="New Organization"
isSelected={false}
icon="plus"
/>
<PopoverMenuItem to="#" title="New Organization" isSelected={false} icon="plus" />
</div>
</PopoverContent>
</Popover>
@@ -42,34 +42,22 @@ function PageHeaders() {
<PageTitleRow>
<PageTitle title="Organizations" />
<PageButtons>
<LinkButton
to={""}
variant="primary/small"
shortcut={{ key: "n" }}
>
<LinkButton to={""} variant="primary/small" shortcut={{ key: "n" }}>
Create a new Organization
</LinkButton>
</PageButtons>
</PageTitleRow>
<PageDescription>
Create new Organizations and new Projects to help organize your
Jobs.
Create new Organizations and new Projects to help organize your Jobs.
</PageDescription>
</PageHeader>
</div>
<div className="bg-[#0B1018]">
<PageHeader>
<PageTitleRow>
<PageTitle
title="Your Organizations"
backButton={{ to: "#", text: "Orgs" }}
/>
<PageTitle title="Your Organizations" backButton={{ to: "#", text: "Orgs" }} />
<PageButtons>
<LinkButton
to={""}
variant="primary/small"
shortcut={{ key: "n" }}
>
<LinkButton to={""} variant="primary/small" shortcut={{ key: "n" }}>
Create a new Organization
</LinkButton>
</PageButtons>
@@ -31,19 +31,9 @@ function RadioGroupExample() {
</Button>
<form>
<RadioGroup name="simple" disabled={isDisabled} className="grid gap-2">
<RadioGroupItem
id="r2"
label="Simple small"
value={"1"}
variant="simple/small"
/>
<RadioGroupItem id="r2" label="Simple small" value={"1"} variant="simple/small" />
<RadioGroupItem id="r3" label="Simple" value={"2"} variant="simple" />
<RadioGroupItem
id="r4"
label="Button small"
value={"3"}
variant="button/small"
/>
<RadioGroupItem id="r4" label="Button small" value={"3"} variant="button/small" />
<RadioGroupItem id="r5" label="Button" value={"4"} variant="button" />
<RadioGroupItem
id="r6"
@@ -55,10 +55,7 @@ function Tooltips() {
</div>
<div className="flex gap-4">
<Header2>Simple Tooltip:</Header2>
<SimpleTooltip
button={<ClipboardIcon className="h-5 w-5" />}
content="Copy"
/>
<SimpleTooltip button={<ClipboardIcon className="h-5 w-5" />} content="Copy" />
</div>
</div>
);
@@ -60,52 +60,48 @@ function Typography({ header1, header2, header3, paragraph }: TypographyProps) {
<Paragraph variant="extra-extra-small">{paragraph}</Paragraph>
<Paragraph variant="extra-extra-small/bright">{paragraph}</Paragraph>
<Paragraph variant="extra-extra-small/caps">{paragraph}</Paragraph>
<Paragraph variant="extra-extra-small/bright/caps">
{paragraph}
</Paragraph>
<Paragraph variant="extra-extra-small/bright/caps">{paragraph}</Paragraph>
</div>
<div>
<Header2>Text Link</Header2>
<Paragraph>
This is an <TextLink href="#">anchor tag component</TextLink> called
TextLink. It takes an href and children.
This is an <TextLink href="#">anchor tag component</TextLink> called TextLink. It takes an
href and children.
</Paragraph>
<Paragraph>
Learn how to get{" "}
<TextLink href="#" trailingIcon="external-link">
started quickly
</TextLink>{" "}
using the included some example Jobs which are great as a quick start
project. You can check them out in your project here in
triggerdotdev/jobs/examples. You can also see the examples in more
detail in the docs.
using the included some example Jobs which are great as a quick start project. You can
check them out in your project here in triggerdotdev/jobs/examples. You can also see the
examples in more detail in the docs.
</Paragraph>
</div>
<div>
<Header2>Custom event JSON payload</Header2>
<Paragraph>
Write your Job code. Jobs can be triggered on a schedule, via a
webhook, custom event and have delays of up to 1 year. Learn how to
create your first Job in code using the docs here.
Write your Job code. Jobs can be triggered on a schedule, via a webhook, custom event and
have delays of up to 1 year. Learn how to create your first Job in code using the docs
here.
</Paragraph>
<Paragraph>
Learn how to get started quickly using the included some example Jobs
which are great as a quick start project. You can check them out in
your project here in triggerdotdev/jobs/examples. You can also see the
examples in more detail in the docs.
Learn how to get started quickly using the included some example Jobs which are great as a
quick start project. You can check them out in your project here in
triggerdotdev/jobs/examples. You can also see the examples in more detail in the docs.
</Paragraph>
</div>
<div>
<Header2>Scopes</Header2>
<Paragraph variant="small">
Select the scopes you want to grant to Slack in order for it to access
your data. If you try and perform an action in a Job that requires a
scope you havent granted, that task will fail.
Select the scopes you want to grant to Slack in order for it to access your data. If you
try and perform an action in a Job that requires a scope you havent granted, that task
will fail.
</Paragraph>
<Paragraph variant="small">
Select the scopes you want to grant to Slack in order for it to access
your data. If you try and perform an action in a Job that requires a
scope you havent granted, that task will fail.
Select the scopes you want to grant to Slack in order for it to access your data. If you
try and perform an action in a Job that requires a scope you havent granted, that task
will fail.
</Paragraph>
</div>
</div>
+4 -13
View File
@@ -10,20 +10,13 @@ export type PrismaTransactionClient = Omit<
export type PrismaClientOrTransaction = PrismaClient | PrismaTransactionClient;
function isTransactionClient(
prisma: PrismaClientOrTransaction
): prisma is PrismaTransactionClient {
function isTransactionClient(prisma: PrismaClientOrTransaction): prisma is PrismaTransactionClient {
return !("$transaction" in prisma);
}
function isPrismaKnownError(
error: unknown
): error is Prisma.PrismaClientKnownRequestError {
function isPrismaKnownError(error: unknown): error is Prisma.PrismaClientKnownRequestError {
return (
typeof error === "object" &&
error !== null &&
"code" in error &&
typeof error.code === "string"
typeof error === "object" && error !== null && "code" in error && typeof error.code === "string"
);
}
@@ -95,9 +88,7 @@ function getClient() {
const urlWithoutCredentials = new URL(DATABASE_URL);
urlWithoutCredentials.password = "";
console.log(
`🔌 setting up prisma client to ${urlWithoutCredentials.toString()}`
);
console.log(`🔌 setting up prisma client to ${urlWithoutCredentials.toString()}`);
const client = new PrismaClient({
datasources: {
+5 -20
View File
@@ -28,9 +28,7 @@ export default function handleRequest(
});
//get whether it's a mac or pc from the headers
const platform: OperatingSystemPlatform = request.headers
.get("user-agent")
?.includes("Mac")
const platform: OperatingSystemPlatform = request.headers.get("user-agent")?.includes("Mac")
? "mac"
: "windows";
@@ -70,11 +68,7 @@ function serveTheBots(
const { pipe, abort } = renderToPipeableStream(
<OperatingSystemContextProvider platform={platform}>
<LocaleContextProvider locales={locales}>
<RemixServer
context={remixContext}
url={request.url}
abortDelay={ABORT_DELAY}
/>
<RemixServer context={remixContext} url={request.url} abortDelay={ABORT_DELAY} />
</LocaleContextProvider>
</OperatingSystemContextProvider>,
{
@@ -113,11 +107,7 @@ function serveBrowsers(
const { pipe, abort } = renderToPipeableStream(
<OperatingSystemContextProvider platform={platform}>
<LocaleContextProvider locales={locales}>
<RemixServer
context={remixContext}
url={request.url}
abortDelay={ABORT_DELAY}
/>
<RemixServer context={remixContext} url={request.url} abortDelay={ABORT_DELAY} />
</LocaleContextProvider>
</OperatingSystemContextProvider>,
{
@@ -153,10 +143,7 @@ if (env.HIGHLIGHT_PROJECT_ID) {
H.init({ projectID: env.HIGHLIGHT_PROJECT_ID });
}
export function handleError(
error: unknown,
{ request, params, context }: DataFunctionArgs
) {
export function handleError(error: unknown, { request, params, context }: DataFunctionArgs) {
logError(error, request);
}
@@ -166,9 +153,7 @@ Worker.init().catch((error) => {
function logError(error: unknown, request?: Request) {
if (env.HIGHLIGHT_PROJECT_ID) {
const parsed = request
? H.parseHeaders(Object.fromEntries(request.headers))
: undefined;
const parsed = request ? H.parseHeaders(Object.fromEntries(request.headers)) : undefined;
if (error instanceof Error) {
H.consumeError(error, parsed?.secureSessionId, parsed?.requestId);
} else {
+1 -5
View File
@@ -2,11 +2,7 @@ import { z } from "zod";
import { SecretStoreOptionsSchema } from "./services/secrets/secretStore.server";
const EnvironmentSchema = z.object({
NODE_ENV: z.union([
z.literal("development"),
z.literal("production"),
z.literal("test"),
]),
NODE_ENV: z.union([z.literal("development"), z.literal("production"), z.literal("test")]),
DATABASE_URL: z.string(),
SESSION_SECRET: z.string(),
MAGIC_LINK_SECRET: z.string(),
+19 -23
View File
@@ -2,31 +2,27 @@ import { ProjectJob } from "./useJobs";
import { useTextFilter } from "./useTextFilter";
export function useFilterJobs(jobs: ProjectJob[]) {
const { filterText, setFilterText, filteredItems } =
useTextFilter<ProjectJob>({
items: jobs,
filter: (job, text) => {
if (job.slug.toLowerCase().includes(text.toLowerCase())) return true;
if (job.title.toLowerCase().includes(text.toLowerCase())) return true;
if (job.event.title.toLowerCase().includes(text.toLowerCase()))
return true;
if (
job.integrations.some((integration) =>
integration.title.toLowerCase().includes(text.toLowerCase())
)
const { filterText, setFilterText, filteredItems } = useTextFilter<ProjectJob>({
items: jobs,
filter: (job, text) => {
if (job.slug.toLowerCase().includes(text.toLowerCase())) return true;
if (job.title.toLowerCase().includes(text.toLowerCase())) return true;
if (job.event.title.toLowerCase().includes(text.toLowerCase())) return true;
if (
job.integrations.some((integration) =>
integration.title.toLowerCase().includes(text.toLowerCase())
)
return true;
if (
job.properties &&
job.properties.some((property) =>
property.text.toLowerCase().includes(text.toLowerCase())
)
)
return true;
)
return true;
if (
job.properties &&
job.properties.some((property) => property.text.toLowerCase().includes(text.toLowerCase()))
)
return true;
return false;
},
});
return false;
},
});
return { filterText, setFilterText, filteredItems };
}

Some files were not shown because too many files have changed in this diff Show More