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:
+1
-1
@@ -1 +1 @@
|
||||
{"workspaceId":"63e5e42daf9a537ba8d9503c"}
|
||||
{ "workspaceId": "63e5e42daf9a537ba8d9503c" }
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
node_modules
|
||||
.env
|
||||
.env.local
|
||||
pnpm-lock.yaml
|
||||
tailwind.css
|
||||
.babelrc.json
|
||||
**/.react-email/
|
||||
**/storybook-static/
|
||||
**/.changeset/
|
||||
**/build/
|
||||
**/dist/
|
||||
Vendored
+1
-8
@@ -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",
|
||||
|
||||
Vendored
-19
@@ -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
@@ -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>
|
||||
```
|
||||
|
||||
@@ -11,9 +11,11 @@
|
||||
|
||||
[](https://twitter.com/triggerdotdev)
|
||||
[](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,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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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,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,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"
|
||||
|
||||
@@ -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
|
||||
)}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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>
|
||||
You’ll notice a new folder in your project called 'jobs'. We’ve added
|
||||
a very simple example Job in <InlineCode>examples.ts</InlineCode> to
|
||||
help you get started.
|
||||
You’ll notice a new folder in your project called 'jobs'. We’ve 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 you’d like the test to run against.
|
||||
</Paragraph>
|
||||
<Paragraph spacing>Select the environment you’d 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 you’re happy with the payload, click Run test.
|
||||
</Paragraph>
|
||||
<Paragraph spacing>When you’re 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. It’s where you can test your Jobs before deploying
|
||||
them to servers.
|
||||
The <InlineCode>DEV</InlineCode> environment should only be used for local development.
|
||||
It’s 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
|
||||
you’re working locally you should use the Test feature to trigger any
|
||||
scheduled Jobs.
|
||||
Scheduled Triggers do not trigger Jobs in the DEV Environment. When you’re 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. It’s where you can run your Jobs against real data.
|
||||
The <InlineCode>PROD</InlineCode> environment is where your Jobs will run in production.
|
||||
It’s 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 haven’t 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
|
||||
haven’t 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 don’t 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 don’t 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 haven’t 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
|
||||
haven’t 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) => {
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
@@ -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 };
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)} />;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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)} />;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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 haven’t 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 haven’t 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 haven’t 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 haven’t granted, that task
|
||||
will fail.
|
||||
</Paragraph>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user