Change plan feedback modal (#1393)

* Checkbox component can have its label styles

* Improved the dialog footer

* Handle sending feedback to Slack using Plain

* WIP making the modal conditional

* Moved the Plain form action into the select plan file

* removed comment

* Show a confirmation diaglog if you’re downgrading from Pro to Hobby

* Downgrading to Hobby works

* Use redirectWithErrorMessage instead of throw error

* The cancel form now submits the data correctly

* Modals don’t trigger when you upgrade

* Copy improvements

* Added a tooltip to explain the link to the pricing page

* Squashed commit of the following:

commit 886429b0ed
Author: Matt Aitken <matt@mattaitken.com>
Date:   Tue Oct 8 18:36:46 2024 +0100

    Removed emails, @trigger.dev/database and @trigger.dev/otlp-importer from changesets config

commit f65157a14f
Author: Matt Aitken <matt@mattaitken.com>
Date:   Tue Oct 8 18:31:27 2024 +0100

    Lockfile with run-engine removed

commit 3d67bb81e9
Author: Matt Aitken <matt@mattaitken.com>
Date:   Tue Oct 8 18:24:31 2024 +0100

    Removed run-engine from the webapp package.json/tsconfig

commit d30e9713f4
Author: Matt Aitken <matt@mattaitken.com>
Date:   Tue Oct 8 18:06:04 2024 +0100

    Dockerfile fix because the database package has been moved

commit f2babbf637
Author: Matt Aitken <matt@mattaitken.com>
Date:   Tue Oct 8 09:41:22 2024 -0700

    Internal packages (testcontainers, redis-worker and zod-worker) (#1392)

    * Some notes on the new run engine

    * lockfile with setup for the run engine

    * Documenting where TaskRun is currently mutated, to try figure out the shape of the new system

    * Added notes about how triggering currently works

    * Details about when triggering happens

    * Lots of notes about waitpoints

    * Started scaffolding the RunEngine

    * Sketch of Prisma waitpoint schema while it’s fresh in my mind

    * Got Prisma working with testcontainers

    * Use beforeEach/afterEach

    * Simple Prisma and Redis test

    * Return Redis options instead of a client

    * Simplified things

    * A very simple FIFO pull-based queue to check the tests working properly

    * Use vitest extend

    * Separate redis, postgres and combined tests for faster testing

    * Some fixes and test improvements

    * Pass a logger into the queue

    * A queue processor that processes items from the given queue as fast as it can

    * Test for retrying an item that wasn’t processed

    * First draft of waitpoints in the Prisma schema

    * Remove the custom logger from the test

    * Added a completedAt to Waitpoint

    * Notes on the flow for an execution starting

    * Added redlock, moved some files around

    * Starting point for the TaskRunExecutionSnapshot table

    * Added relationships to TaskRunExecutionSnapshot

    * Change some tsconfig

    * Moved some things around

    * Added some packages

    * WIP on the RunQueue

    * Fix for some imports

    * Key producer with some tests

    * Removed the nv type from the keys… it’s not useful to do global queries

    * Passing unit tests for all the public key producer functions

    * Some basic tests passing for the RunQueue

    * Simple enqueue test working

    * Enqueue and dequeue for dev is working

    * Don’t log everything during the tests

    * Enqueuing/dequeuing from the shared queue is working

    * Tests for getting a shared queue

    * The key producer sharedQueue can now be named, to allow multiple separate queues

    * The key producer uses the name of the queue as the input

    * Extra info in the Prisma schema

    * Dequeuing a message gets the payload and sets the task concurrency all in one Lua script

    * Adding more keys so we can read the concurrency from the queue

    * Setting the concurrency with dequeue and enquque is working

    * Improved the tests and fixed some bugs

    * Acking is resetting the concurrencies

    * Check the key has been removed after acking

    * Nacking is working

    * Changed the package to CommonJS + Node10 so it works with Redlock

    * Moved the database, otel and emails packages to be in internal-packages

    * Moved some Prisma code to the database package

    * Started using the RunEngine for triggering

    * Progress on run engine triggering, first waitpoint code

    * Create a delay waitpoint

    * Moved ZodWorker to an internal package so it can be used in the run engine as well as the webapp

    * Web app now uses the zod worker package

    * Added parseNaturalLanguageDuration to core/apps

    * internal-packages/zod-worker in the lockfile

    * Pass in the master queue, remove old rebalance workers code

    * Add masterQueue to TaskRun

    * Fixed the tests

    * Moved waitpoint code into the run engine, also the zod worker

    * Completing waitpoints

    * An experiment to create a new test container with environment

    * More changes to triggering

    * Started testing triggering

    * Test for a run getting triggered and being enqueued

    * Removed dequeueMessageInEnv

    * Update dev queue tests to use the shared queue function

    * Schema changes for TaskRunExecutionSnapshot

    * First execution snapshot when the run is created. Dequeue run function added to the engine

    * Separate internal package for testcontainers so they can be used elsewhere

    * Remove the simple queue and testcontainers from the run-engine. They’re going to be separate

    * Fix for the wrong path to the Prisma schem,a

    * Added the testcontainers package to the run-engine

    * redis-worker package, just a copy of the simple queue for now

    * The queue now uses Lua to enqueue dequeue

    * The queue now has a catalog and an invisible period after dequeuing

    * Added a visibility timeout and acking, with tests

    * Added more Redis connection logging, deleted todos

    * Visibility timeouts are now defined on the catalog and can be overridden when enqueuing

    * Dequeue multiple items at once

    * Test for dequeuing multiple items

    * Export some types to be used elsewhere

    * Partial refactor of the processor

    * First stab at a worker with concurrency and NodeWorkers

    * Don’t have a default visibility timeout in the queue

    * Worker setup and processing items in a simple test

    * Process jobs in parallel with retrying

    * Get the attempt when dequeuing

    * Workers do exponential backoff

    * Moved todos

    * DLQ functionality

    * DLQ tests

    * Same cluster for all keys in the same queue

    * Added DLQ tests

    * Whitespace

    * Redis pubsub to redrive from the worker

    * Fixed database paths

    * Fix for path to zod-worker

    * Fixes for typecheck errors, mostly with TS versions and module resolution

    * Redlock required a patch

    * Moved the new DB migrations to the new database package folder

    * Remove the run-engine package

    * Remove the RunEngine prisma schema changes

    * Delete triggerTaskV2

    * Remove zodworker test script (no tests)

    * Update test-containers readme

    * Generate the client first

    * Use a specific version of the prisma package

    * Generate the prisma client before running the unit tests

commit fc609470c4
Author: Dan <8297864+D-K-P@users.noreply.github.com>
Date:   Tue Oct 8 14:36:03 2024 +0100

    Supabase database webhook example upgrade (#1386)

    * Added overview for guides and examples section and split them all out

    * New supabase guide wip

    * Updated images and improved docs

    * Trimmed the supabase prereqs

    * Supabase guide wip

    * more updates

    * Replaced old database webhook guide

    * Created one intro page and removed snippets

    * Updated guide sidebar titles

    * Code updates

    * More improvements

    * Updates and added images

    * Compressed image

    * Updated guides descriptions and edge function basic

    * Removed bold

    * Updated redirects

    * Fixed broken links

    * Updated intro

commit 07f82eae54
Author: nicktrn <55853254+nicktrn@users.noreply.github.com>
Date:   Tue Oct 8 13:28:54 2024 +0100

    Release 3.0.11

commit 13ebfcc5b9
Author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Date:   Tue Oct 8 13:24:38 2024 +0100

    chore: Update version for release (#1381)

    Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

commit 2a04d17e1f
Author: nicktrn <55853254+nicktrn@users.noreply.github.com>
Date:   Tue Oct 8 09:24:23 2024 +0100

    Simplify showLogs expression

commit 002ae4bc0d
Author: nicktrn <55853254+nicktrn@users.noreply.github.com>
Date:   Tue Oct 8 09:22:29 2024 +0100

    Fix dotenv overrides for dev runs (#1388)

    * override dashboard dev env vars with local .env

    * add changeset

    * add simple task for testing env vars

commit 047cb00417
Author: nicktrn <55853254+nicktrn@users.noreply.github.com>
Date:   Tue Oct 8 09:22:05 2024 +0100

    Disable schedules for deleted orgs on next tick (#1383)

    * disable schedules for deleted orgs

    * add debug logs

commit 2c014f725a
Author: James Ritchie <james@trigger.dev>
Date:   Sun Oct 6 13:02:00 2024 -0700

    Override log retention (#1385)

    * set full log retention as admin

    * If run.logsDeletedAt is set, don’t bother getting the trace

commit a69e04f03a
Author: nicktrn <55853254+nicktrn@users.noreply.github.com>
Date:   Sat Oct 5 14:18:58 2024 +0100

    Include push output in logs for self-hosted deploys (#1382)

    * include push output in logs

    * changeset

commit c5488df8ca
Author: nicktrn <55853254+nicktrn@users.noreply.github.com>
Date:   Sat Oct 5 13:12:47 2024 +0100

    Fix CLI downgrade check (#1380)

    * fix downgrade detection

    * remove unused semver package from webapp

    * add changeset

commit 1caec27f72
Author: Eric Allam <eric@trigger.dev>
Date:   Fri Oct 4 15:33:35 2024 -0700

    docs: Max duration (#1379)

    * maxDuration docs

    * Update the init command to set the maxDuration and include a commented out maxDuration in the config file

commit e14c954e12
Author: Eric Allam <eallam@icloud.com>
Date:   Fri Oct 4 15:02:05 2024 -0700

    Release 3.0.10

commit 8e61f5d631
Author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Date:   Fri Oct 4 14:59:07 2024 -0700

    chore: Update version for release (#1378)

    Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

commit 08db565a72
Author: Eric Allam <eallam@icloud.com>
Date:   Thu Oct 3 12:38:25 2024 -0700

    improve the timed out description

commit 6d0884254e
Author: Eric Allam <eric@trigger.dev>
Date:   Thu Oct 3 12:43:26 2024 -0700

    feat: Add maxDuration to tasks (#1377)

    * WIP

    * Get max duration working on deployed runs

    * Actually set the timed out runs to status = TIMED_OUT

    * The client status for TIMED_OUT is now MAX_DURATION_EXCEEDED

    * New TimedOutIcon

    * Added new timedout icon

    * Add ability to opt-out of maxDuration with timeout.None

    * MAX_DURATION_EXCEEDED -> TIMED_OUT

    * changeset

    * Improved styling for the status tooltip content

    ---------

    Co-authored-by: James Ritchie <james@trigger.dev>

commit 665ccf88b7
Author: nicktrn <55853254+nicktrn@users.noreply.github.com>
Date:   Thu Oct 3 12:33:18 2024 +0100

    Update github actions and self-hosting docs

commit 1ff7b861ca
Author: Eric Allam <eric@trigger.dev>
Date:   Wed Oct 2 18:26:36 2024 -0700

    Add max queue depth limits (#1376)

    * Add runs to an env queue, as well as the actual queue

    * Add queue size limit guard on triggering tasks

commit c531a9d986
Author: Eric Allam <eric@trigger.dev>
Date:   Wed Oct 2 15:30:39 2024 -0700

    fix: cleanup ttl expire run graphile jobs (#1373)

    * fix: remove ttl expire run graphile jobs when a run is started or completed

    * Update expireEnqueuedRun.server.ts

commit 0bf500fa95
Author: Matt Aitken <matt@mattaitken.com>
Date:   Wed Oct 2 15:30:16 2024 -0700

    Prioritize finishing waited runs (#1375)

    * If a tree node is missing, estimate the size as zero

    * Task to test prioritizing finishing existing runs after triggerAndWaits

    * When requeuing a run with a checkpoint, put it in the queue with the parent run time so it’s correctly prioritized

    * The same change but if there’s no checkpoint

* Squashed commit of the following:

commit 886429b0ed
Author: Matt Aitken <matt@mattaitken.com>
Date:   Tue Oct 8 18:36:46 2024 +0100

    Removed emails, @trigger.dev/database and @trigger.dev/otlp-importer from changesets config

commit f65157a14f
Author: Matt Aitken <matt@mattaitken.com>
Date:   Tue Oct 8 18:31:27 2024 +0100

    Lockfile with run-engine removed

commit 3d67bb81e9
Author: Matt Aitken <matt@mattaitken.com>
Date:   Tue Oct 8 18:24:31 2024 +0100

    Removed run-engine from the webapp package.json/tsconfig

commit d30e9713f4
Author: Matt Aitken <matt@mattaitken.com>
Date:   Tue Oct 8 18:06:04 2024 +0100

    Dockerfile fix because the database package has been moved

commit f2babbf637
Author: Matt Aitken <matt@mattaitken.com>
Date:   Tue Oct 8 09:41:22 2024 -0700

    Internal packages (testcontainers, redis-worker and zod-worker) (#1392)

    * Some notes on the new run engine

    * lockfile with setup for the run engine

    * Documenting where TaskRun is currently mutated, to try figure out the shape of the new system

    * Added notes about how triggering currently works

    * Details about when triggering happens

    * Lots of notes about waitpoints

    * Started scaffolding the RunEngine

    * Sketch of Prisma waitpoint schema while it’s fresh in my mind

    * Got Prisma working with testcontainers

    * Use beforeEach/afterEach

    * Simple Prisma and Redis test

    * Return Redis options instead of a client

    * Simplified things

    * A very simple FIFO pull-based queue to check the tests working properly

    * Use vitest extend

    * Separate redis, postgres and combined tests for faster testing

    * Some fixes and test improvements

    * Pass a logger into the queue

    * A queue processor that processes items from the given queue as fast as it can

    * Test for retrying an item that wasn’t processed

    * First draft of waitpoints in the Prisma schema

    * Remove the custom logger from the test

    * Added a completedAt to Waitpoint

    * Notes on the flow for an execution starting

    * Added redlock, moved some files around

    * Starting point for the TaskRunExecutionSnapshot table

    * Added relationships to TaskRunExecutionSnapshot

    * Change some tsconfig

    * Moved some things around

    * Added some packages

    * WIP on the RunQueue

    * Fix for some imports

    * Key producer with some tests

    * Removed the nv type from the keys… it’s not useful to do global queries

    * Passing unit tests for all the public key producer functions

    * Some basic tests passing for the RunQueue

    * Simple enqueue test working

    * Enqueue and dequeue for dev is working

    * Don’t log everything during the tests

    * Enqueuing/dequeuing from the shared queue is working

    * Tests for getting a shared queue

    * The key producer sharedQueue can now be named, to allow multiple separate queues

    * The key producer uses the name of the queue as the input

    * Extra info in the Prisma schema

    * Dequeuing a message gets the payload and sets the task concurrency all in one Lua script

    * Adding more keys so we can read the concurrency from the queue

    * Setting the concurrency with dequeue and enquque is working

    * Improved the tests and fixed some bugs

    * Acking is resetting the concurrencies

    * Check the key has been removed after acking

    * Nacking is working

    * Changed the package to CommonJS + Node10 so it works with Redlock

    * Moved the database, otel and emails packages to be in internal-packages

    * Moved some Prisma code to the database package

    * Started using the RunEngine for triggering

    * Progress on run engine triggering, first waitpoint code

    * Create a delay waitpoint

    * Moved ZodWorker to an internal package so it can be used in the run engine as well as the webapp

    * Web app now uses the zod worker package

    * Added parseNaturalLanguageDuration to core/apps

    * internal-packages/zod-worker in the lockfile

    * Pass in the master queue, remove old rebalance workers code

    * Add masterQueue to TaskRun

    * Fixed the tests

    * Moved waitpoint code into the run engine, also the zod worker

    * Completing waitpoints

    * An experiment to create a new test container with environment

    * More changes to triggering

    * Started testing triggering

    * Test for a run getting triggered and being enqueued

    * Removed dequeueMessageInEnv

    * Update dev queue tests to use the shared queue function

    * Schema changes for TaskRunExecutionSnapshot

    * First execution snapshot when the run is created. Dequeue run function added to the engine

    * Separate internal package for testcontainers so they can be used elsewhere

    * Remove the simple queue and testcontainers from the run-engine. They’re going to be separate

    * Fix for the wrong path to the Prisma schem,a

    * Added the testcontainers package to the run-engine

    * redis-worker package, just a copy of the simple queue for now

    * The queue now uses Lua to enqueue dequeue

    * The queue now has a catalog and an invisible period after dequeuing

    * Added a visibility timeout and acking, with tests

    * Added more Redis connection logging, deleted todos

    * Visibility timeouts are now defined on the catalog and can be overridden when enqueuing

    * Dequeue multiple items at once

    * Test for dequeuing multiple items

    * Export some types to be used elsewhere

    * Partial refactor of the processor

    * First stab at a worker with concurrency and NodeWorkers

    * Don’t have a default visibility timeout in the queue

    * Worker setup and processing items in a simple test

    * Process jobs in parallel with retrying

    * Get the attempt when dequeuing

    * Workers do exponential backoff

    * Moved todos

    * DLQ functionality

    * DLQ tests

    * Same cluster for all keys in the same queue

    * Added DLQ tests

    * Whitespace

    * Redis pubsub to redrive from the worker

    * Fixed database paths

    * Fix for path to zod-worker

    * Fixes for typecheck errors, mostly with TS versions and module resolution

    * Redlock required a patch

    * Moved the new DB migrations to the new database package folder

    * Remove the run-engine package

    * Remove the RunEngine prisma schema changes

    * Delete triggerTaskV2

    * Remove zodworker test script (no tests)

    * Update test-containers readme

    * Generate the client first

    * Use a specific version of the prisma package

    * Generate the prisma client before running the unit tests

commit fc609470c4
Author: Dan <8297864+D-K-P@users.noreply.github.com>
Date:   Tue Oct 8 14:36:03 2024 +0100

    Supabase database webhook example upgrade (#1386)

    * Added overview for guides and examples section and split them all out

    * New supabase guide wip

    * Updated images and improved docs

    * Trimmed the supabase prereqs

    * Supabase guide wip

    * more updates

    * Replaced old database webhook guide

    * Created one intro page and removed snippets

    * Updated guide sidebar titles

    * Code updates

    * More improvements

    * Updates and added images

    * Compressed image

    * Updated guides descriptions and edge function basic

    * Removed bold

    * Updated redirects

    * Fixed broken links

    * Updated intro

commit 07f82eae54
Author: nicktrn <55853254+nicktrn@users.noreply.github.com>
Date:   Tue Oct 8 13:28:54 2024 +0100

    Release 3.0.11

commit 13ebfcc5b9
Author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Date:   Tue Oct 8 13:24:38 2024 +0100

    chore: Update version for release (#1381)

    Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

commit 2a04d17e1f
Author: nicktrn <55853254+nicktrn@users.noreply.github.com>
Date:   Tue Oct 8 09:24:23 2024 +0100

    Simplify showLogs expression

commit 002ae4bc0d
Author: nicktrn <55853254+nicktrn@users.noreply.github.com>
Date:   Tue Oct 8 09:22:29 2024 +0100

    Fix dotenv overrides for dev runs (#1388)

    * override dashboard dev env vars with local .env

    * add changeset

    * add simple task for testing env vars

commit 047cb00417
Author: nicktrn <55853254+nicktrn@users.noreply.github.com>
Date:   Tue Oct 8 09:22:05 2024 +0100

    Disable schedules for deleted orgs on next tick (#1383)

    * disable schedules for deleted orgs

    * add debug logs

commit 2c014f725a
Author: James Ritchie <james@trigger.dev>
Date:   Sun Oct 6 13:02:00 2024 -0700

    Override log retention (#1385)

    * set full log retention as admin

    * If run.logsDeletedAt is set, don’t bother getting the trace

commit a69e04f03a
Author: nicktrn <55853254+nicktrn@users.noreply.github.com>
Date:   Sat Oct 5 14:18:58 2024 +0100

    Include push output in logs for self-hosted deploys (#1382)

    * include push output in logs

    * changeset

commit c5488df8ca
Author: nicktrn <55853254+nicktrn@users.noreply.github.com>
Date:   Sat Oct 5 13:12:47 2024 +0100

    Fix CLI downgrade check (#1380)

    * fix downgrade detection

    * remove unused semver package from webapp

    * add changeset

commit 1caec27f72
Author: Eric Allam <eric@trigger.dev>
Date:   Fri Oct 4 15:33:35 2024 -0700

    docs: Max duration (#1379)

    * maxDuration docs

    * Update the init command to set the maxDuration and include a commented out maxDuration in the config file

commit e14c954e12
Author: Eric Allam <eallam@icloud.com>
Date:   Fri Oct 4 15:02:05 2024 -0700

    Release 3.0.10

commit 8e61f5d631
Author: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Date:   Fri Oct 4 14:59:07 2024 -0700

    chore: Update version for release (#1378)

    Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

commit 08db565a72
Author: Eric Allam <eallam@icloud.com>
Date:   Thu Oct 3 12:38:25 2024 -0700

    improve the timed out description

commit 6d0884254e
Author: Eric Allam <eric@trigger.dev>
Date:   Thu Oct 3 12:43:26 2024 -0700

    feat: Add maxDuration to tasks (#1377)

    * WIP

    * Get max duration working on deployed runs

    * Actually set the timed out runs to status = TIMED_OUT

    * The client status for TIMED_OUT is now MAX_DURATION_EXCEEDED

    * New TimedOutIcon

    * Added new timedout icon

    * Add ability to opt-out of maxDuration with timeout.None

    * MAX_DURATION_EXCEEDED -> TIMED_OUT

    * changeset

    * Improved styling for the status tooltip content

    ---------

    Co-authored-by: James Ritchie <james@trigger.dev>

commit 665ccf88b7
Author: nicktrn <55853254+nicktrn@users.noreply.github.com>
Date:   Thu Oct 3 12:33:18 2024 +0100

    Update github actions and self-hosting docs

commit 1ff7b861ca
Author: Eric Allam <eric@trigger.dev>
Date:   Wed Oct 2 18:26:36 2024 -0700

    Add max queue depth limits (#1376)

    * Add runs to an env queue, as well as the actual queue

    * Add queue size limit guard on triggering tasks

commit c531a9d986
Author: Eric Allam <eric@trigger.dev>
Date:   Wed Oct 2 15:30:39 2024 -0700

    fix: cleanup ttl expire run graphile jobs (#1373)

    * fix: remove ttl expire run graphile jobs when a run is started or completed

    * Update expireEnqueuedRun.server.ts

commit 0bf500fa95
Author: Matt Aitken <matt@mattaitken.com>
Date:   Wed Oct 2 15:30:16 2024 -0700

    Prioritize finishing waited runs (#1375)

    * If a tree node is missing, estimate the size as zero

    * Task to test prioritizing finishing existing runs after triggerAndWaits

    * When requeuing a run with a checkpoint, put it in the queue with the parent run time so it’s correctly prioritized

    * The same change but if there’s no checkpoint

* Revert "Squashed commit of the following:"

This reverts commit b837b5a558dc5187d89b09135276d16f6856f455.

* Removed console logs

* cleaned up conditionals

* Unlock free plan state

* Fixed subscribe button if you’re already github verified

* Simplified the downgrade reasons logic

* made periodEnd required

---------

Co-authored-by: nicktrn <55853254+nicktrn@users.noreply.github.com>
This commit is contained in:
James Ritchie
2024-10-10 14:12:15 +01:00
committed by GitHub
parent 25c7ff173f
commit cff2ebba49
6 changed files with 337 additions and 59 deletions
@@ -61,6 +61,7 @@ export type CheckboxProps = Omit<
description?: string;
badges?: string[];
className?: string;
labelClassName?: string;
onChange?: (isChecked: boolean) => void;
};
@@ -78,6 +79,7 @@ export const CheckboxWithLabel = React.forwardRef<HTMLInputElement, CheckboxProp
badges,
disabled,
className,
labelClassName: externalLabelClassName,
...props
},
ref
@@ -148,7 +150,8 @@ export const CheckboxWithLabel = React.forwardRef<HTMLInputElement, CheckboxProp
htmlFor={id}
className={cn(
props.readOnly || disabled ? "cursor-default" : "cursor-pointer",
labelClassName
labelClassName,
externalLabelClassName
)}
onClick={(e) => e.preventDefault()}
>
@@ -82,7 +82,7 @@ DialogHeader.displayName = "DialogHeader";
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-between sm:space-x-2", className)}
{...props}
/>
);
@@ -117,6 +117,7 @@ export default function ChoosePlanPage() {
subscription={v3Subscription}
organizationSlug={organizationSlug}
hasPromotedPlan={false}
periodEnd={periodEnd}
/>
</div>
</div>
@@ -5,7 +5,6 @@ import { z } from "zod";
import { RouteErrorDisplay } from "~/components/ErrorDisplay";
import { MainBody } from "~/components/layout/AppLayout";
import { SideMenu } from "~/components/navigation/SideMenu";
import { featuresForRequest } from "~/features.server";
import { useOptionalOrganization } from "~/hooks/useOrganizations";
import { useTypedMatchesData } from "~/hooks/useTypedMatchData";
import { useUser } from "~/hooks/useUser";
@@ -37,11 +37,15 @@ export async function loader({ params, request }: LoaderFunctionArgs) {
const currentPlan = await getCurrentPlan(organization.id);
return typedjson({ ...plans, ...currentPlan, organizationSlug });
const periodEnd = new Date();
periodEnd.setMonth(periodEnd.getMonth() + 1);
return typedjson({ ...plans, ...currentPlan, organizationSlug, periodEnd });
}
export default function ChoosePlanPage() {
const { plans, v3Subscription, organizationSlug } = useTypedLoaderData<typeof loader>();
const { plans, v3Subscription, organizationSlug, periodEnd } =
useTypedLoaderData<typeof loader>();
return (
<MainCenteredContainer className="flex max-w-[80rem] flex-col items-center gap-8 p-3">
@@ -52,6 +56,7 @@ export default function ChoosePlanPage() {
organizationSlug={organizationSlug}
hasPromotedPlan
showGithubVerificationBadge
periodEnd={periodEnd}
/>
</MainCenteredContainer>
);
@@ -1,11 +1,15 @@
import {
ArrowUpRightIcon,
CheckIcon,
ExclamationTriangleIcon,
ShieldCheckIcon,
XMarkIcon,
} from "@heroicons/react/20/solid";
import { ArrowDownCircleIcon } from "@heroicons/react/24/outline";
import { Form, useLocation, useNavigation } from "@remix-run/react";
import { ActionFunctionArgs } from "@remix-run/server-runtime";
import { PlainClient, uiComponent } from "@team-plain/typescript-sdk";
import { GitHubLightIcon } from "@trigger.dev/companyicons";
import {
FreePlanDefinition,
Limits,
@@ -14,11 +18,14 @@ import {
SetPlanBody,
SubscriptionResult,
} from "@trigger.dev/platform/v3";
import { GitHubLightIcon } from "@trigger.dev/companyicons";
import React, { useEffect, useState } from "react";
import { inspect } from "util";
import { z } from "zod";
import { DefinitionTip } from "~/components/DefinitionTooltip";
import { Feedback } from "~/components/Feedback";
import { Button, LinkButton } from "~/components/primitives/Buttons";
import { Button } from "~/components/primitives/Buttons";
import { CheckboxWithLabel } from "~/components/primitives/Checkbox";
import { DateTime } from "~/components/primitives/DateTime";
import {
Dialog,
DialogContent,
@@ -26,13 +33,17 @@ import {
DialogHeader,
DialogTrigger,
} from "~/components/primitives/Dialog";
import { Header2 } from "~/components/primitives/Headers";
import { Paragraph } from "~/components/primitives/Paragraph";
import { Spinner } from "~/components/primitives/Spinner";
import { TextArea } from "~/components/primitives/TextArea";
import { SimpleTooltip } from "~/components/primitives/Tooltip";
import { prisma } from "~/db.server";
import { env } from "~/env.server";
import { redirectWithErrorMessage } from "~/models/message.server";
import { logger } from "~/services/logger.server";
import { setPlan } from "~/services/platform.v3.server";
import { requireUserId } from "~/services/session.server";
import { requireUser } from "~/services/session.server";
import { cn } from "~/utils/cn";
const Params = z.object({
@@ -43,6 +54,8 @@ const schema = z.object({
type: z.enum(["free", "paid"]),
planCode: z.string().optional(),
callerPath: z.string(),
reasons: z.union([z.string(), z.array(z.string())]).optional(),
message: z.string().optional(),
});
export async function action({ request, params }: ActionFunctionArgs) {
@@ -51,11 +64,16 @@ export async function action({ request, params }: ActionFunctionArgs) {
}
const { organizationSlug } = Params.parse(params);
const user = await requireUser(request);
const formData = await request.formData();
const reasons = formData.getAll("reasons");
const message = formData.get("message");
const userId = await requireUserId(request);
const formData = Object.fromEntries(await request.formData());
const form = schema.parse(formData);
const form = schema.parse({
...Object.fromEntries(formData),
reasons,
message: message || undefined,
});
const organization = await prisma.organization.findUnique({
where: { slug: organizationSlug },
@@ -69,9 +87,106 @@ export async function action({ request, params }: ActionFunctionArgs) {
switch (form.type) {
case "free": {
try {
if (!env.PLAIN_API_KEY) {
throw new Error("PLAIN_API_KEY is not set");
}
const client = new PlainClient({
apiKey: env.PLAIN_API_KEY,
});
const upsertCustomerRes = await client.upsertCustomer({
identifier: {
emailAddress: user.email,
},
onCreate: {
externalId: user.id,
fullName: user.name ?? "",
email: {
email: user.email,
isVerified: true,
},
},
onUpdate: {
externalId: { value: user.id },
fullName: { value: user.name ?? "" },
email: {
email: user.email,
isVerified: true,
},
},
});
if (upsertCustomerRes.error) {
console.error(
inspect(upsertCustomerRes.error, {
showHidden: false,
depth: null,
colors: true,
})
);
throw redirectWithErrorMessage(form.callerPath, request, upsertCustomerRes.error.message);
}
// Only create a thread if there are reasons or a message
if (reasons.length > 0 || (message && message.toString().trim() !== "")) {
const createThreadRes = await client.createThread({
customerIdentifier: {
customerId: upsertCustomerRes.data.customer.id,
},
title: "Plan cancelation feedback",
components: [
uiComponent.text({
text: `${user.name} (${user.email}) just canceled their plan.`,
}),
uiComponent.divider({ spacingSize: "M" }),
...(reasons.length > 0
? [
uiComponent.spacer({ size: "L" }),
uiComponent.text({
size: "L",
color: "NORMAL",
text: "Reasons:",
}),
uiComponent.text({
text: reasons.join(", "),
}),
]
: []),
...(message
? [
uiComponent.spacer({ size: "L" }),
uiComponent.text({
size: "L",
color: "NORMAL",
text: "Comment:",
}),
uiComponent.text({
text: message.toString(),
}),
]
: []),
],
});
if (createThreadRes.error) {
console.error(
inspect(createThreadRes.error, {
showHidden: false,
depth: null,
colors: true,
})
);
throw redirectWithErrorMessage(form.callerPath, request, createThreadRes.error.message);
}
}
} catch (e) {
logger.error("Failed to submit to Plain the unsubscribe reason", { error: e });
}
payload = {
type: "free" as const,
userId,
userId: user.id,
};
break;
}
@@ -82,10 +197,13 @@ export async function action({ request, params }: ActionFunctionArgs) {
payload = {
type: "paid" as const,
planCode: form.planCode,
userId,
userId: user.id,
};
break;
}
default: {
throw new Error("Invalid form type");
}
}
return setPlan(organization, request, form.callerPath, payload);
@@ -134,6 +252,7 @@ type PricingPlansProps = {
organizationSlug: string;
hasPromotedPlan: boolean;
showGithubVerificationBadge?: boolean;
periodEnd: Date;
};
export function PricingPlans({
@@ -142,6 +261,7 @@ export function PricingPlans({
organizationSlug,
hasPromotedPlan,
showGithubVerificationBadge,
periodEnd,
}: PricingPlansProps) {
return (
<div className="flex w-full flex-col">
@@ -151,6 +271,7 @@ export function PricingPlans({
subscription={subscription}
organizationSlug={organizationSlug}
showGithubVerificationBadge={showGithubVerificationBadge}
periodEnd={periodEnd}
/>
<TierHobby
plan={plans.hobby}
@@ -172,23 +293,33 @@ export function TierFree({
subscription,
organizationSlug,
showGithubVerificationBadge,
periodEnd,
}: {
plan: FreePlanDefinition;
subscription?: SubscriptionResult;
organizationSlug: string;
showGithubVerificationBadge?: boolean;
periodEnd: Date;
}) {
const location = useLocation();
const navigation = useNavigation();
const formAction = `/resources/orgs/${organizationSlug}/select-plan`;
const isLoading = navigation.formAction === formAction;
const [isDialogOpen, setIsDialogOpen] = useState(false);
const [isLackingFeaturesChecked, setIsLackingFeaturesChecked] = useState(false);
const status = subscription?.freeTierStatus ?? "requires_connect";
useEffect(() => {
setIsDialogOpen(false);
}, [subscription]);
return (
<TierContainer>
<div className="relative">
<PricingHeader title={plan.title} cost={0} />
<TierLimit href="https://trigger.dev/pricing#computePricing">
${plan.limits.includedUsage / 100} free monthly usage
</TierLimit>
{showGithubVerificationBadge && status === "approved" && (
<SimpleTooltip
buttonClassName="absolute right-1 top-1"
@@ -242,16 +373,11 @@ export function TierFree({
</div>
</div>
) : (
<Form action={formAction} method="post" id="subscribe">
<input type="hidden" name="type" value="free" />
<input type="hidden" name="callerPath" value={location.pathname} />
<TierLimit href="https://trigger.dev/pricing#computePricing">
${plan.limits.includedUsage / 100} free usage
</TierLimit>
<div className="py-6">
{status === "requires_connect" ? (
<Dialog>
<DialogTrigger asChild>
<>
{status === "requires_connect" ? (
<Dialog>
<DialogTrigger asChild>
<div className="my-6">
<Button
type="button"
variant="tertiary/large"
@@ -260,12 +386,16 @@ export function TierFree({
disabled={isLoading}
LeadingIcon={isLoading ? Spinner : undefined}
>
Unlock free plan
Unlock Free plan
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-md">
</div>
</DialogTrigger>
<DialogContent className="sm:max-w-md">
<Form action={formAction} method="post" id="subscribe-free">
<input type="hidden" name="type" value="free" />
<input type="hidden" name="callerPath" value={location.pathname} />
<DialogHeader>Unlock the Free plan</DialogHeader>
<div className="mb-3 mt-4 flex flex-col items-center gap-4 px-6">
<div className="mb-5 mt-7 flex flex-col items-center gap-4 px-6">
<GitHubLightIcon className="size-16" />
<Paragraph variant="base/bright" className="text-center">
To unlock the Free plan, we need to verify that you have an active GitHub
@@ -282,16 +412,104 @@ export function TierFree({
fullWidth
disabled={isLoading}
LeadingIcon={isLoading ? Spinner : undefined}
form="subscribe"
form="subscribe-free"
>
Connect to GitHub
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
) : (
</Form>
</DialogContent>
</Dialog>
) : subscription?.plan !== undefined &&
subscription.plan.type !== "free" &&
subscription.canceledAt === undefined ? (
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen} key="cancel">
<DialogTrigger asChild>
<div className="my-6">
<Button variant="tertiary/large" fullWidth className="text-md font-medium">
{`Downgrade to ${plan.title}`}
</Button>
</div>
</DialogTrigger>
<DialogContent className="sm:max-w-md">
<Form action={formAction} method="post" id="subscribe">
<input type="hidden" name="type" value="free" />
<input type="hidden" name="callerPath" value={location.pathname} />
<DialogHeader>Downgrade plan?</DialogHeader>
<div className="flex items-start gap-3 pb-6 pr-2 pt-8">
<ArrowDownCircleIcon className="size-12 min-w-12 text-error" />
<Paragraph variant="base/bright" className="text-text-bright">
Are you sure you want to downgrade? You will lose access to your current
plan's features on{" "}
<DateTime
includeTime={false}
date={new Date(periodEnd.getTime() + 86400000)}
/>
.
</Paragraph>
</div>
<div>
<div className="mb-4">
<Header2 className="mb-1">Why are you thinking of downgrading?</Header2>
<ul className="space-y-1">
{[
"Subscription or usage costs too expensive",
"Bugs or technical issues",
"No longer need the service",
"Found a better alternative",
"Lacking features I need",
].map((label, index) => (
<li key={index}>
<CheckboxWithLabel
id={`reason-${index + 1}`}
name="reasons"
value={label}
variant="simple"
label={label}
labelClassName="text-text-dimmed"
onChange={(isChecked: boolean) => {
if (label === "Lacking features I need") {
setIsLackingFeaturesChecked(isChecked);
}
}}
/>
</li>
))}
</ul>
</div>
<div>
<Header2 className="mb-1">
{isLackingFeaturesChecked
? "What features do you need? Or how can we improve?"
: "What can we do to improve?"}
</Header2>
<TextArea id="improvement-suggestions" name="message" />
</div>
</div>
<DialogFooter className="mt-2">
<Button variant="tertiary/medium" onClick={() => setIsDialogOpen(false)}>
Dismiss
</Button>
<Button
variant="danger/medium"
disabled={isLoading}
LeadingIcon={isLoading ? () => <Spinner color="white" /> : undefined}
type="submit"
>
Downgrade plan
</Button>
</DialogFooter>
</Form>
</DialogContent>
</Dialog>
) : (
<Form action={formAction} method="post" id="subscribe-verified" className="my-6">
<input type="hidden" name="type" value="free" />
<input type="hidden" name="callerPath" value={location.pathname} />
<Button
variant="tertiary/large"
type="submit"
form="subscribe-verified"
fullWidth
className="text-md font-medium"
disabled={
@@ -305,12 +523,14 @@ export function TierFree({
>
{subscription?.plan === undefined
? "Select plan"
: subscription.plan.type === "free" || subscription.canceledAt !== undefined
: subscription.plan.type === "free"
? "Current plan"
: `Downgrade to ${plan.title}`}
: subscription.canceledAt !== undefined
? "Current plan"
: "Select plan"}
</Button>
)}
</div>
</Form>
)}
<ul className="flex flex-col gap-2.5">
<ConcurrentRuns limits={plan.limits} />
<FeatureItem checked>
@@ -329,7 +549,7 @@ export function TierFree({
<SupportLevel limits={plan.limits} />
<Alerts limits={plan.limits} />
</ul>
</Form>
</>
)}
</TierContainer>
);
@@ -350,6 +570,11 @@ export function TierHobby({
const navigation = useNavigation();
const formAction = `/resources/orgs/${organizationSlug}/select-plan`;
const isLoading = navigation.formAction === formAction;
const [isDialogOpen, setIsDialogOpen] = useState(false);
useEffect(() => {
setIsDialogOpen(false);
}, [subscription]);
return (
<TierContainer isHighlighted={isHighlighted}>
@@ -357,15 +582,52 @@ export function TierHobby({
<TierLimit href="https://trigger.dev/pricing#computePricing">
${plan.limits.includedUsage / 100} usage included
</TierLimit>
<Form action={formAction} method="post" id="subscribe">
<div className="py-6">
<input type="hidden" name="type" value="paid" />
<input type="hidden" name="planCode" value={plan.code} />
<input type="hidden" name="callerPath" value={location.pathname} />
<Form action={formAction} method="post" id="subscribe-hobby" className="py-6">
<input type="hidden" name="type" value="paid" />
<input type="hidden" name="planCode" value={plan.code} />
<input type="hidden" name="callerPath" value={location.pathname} />
{subscription?.plan !== undefined &&
subscription.plan.type !== "free" &&
subscription.canceledAt === undefined &&
subscription.plan.code !== plan.code ? (
<Dialog open={isDialogOpen} onOpenChange={setIsDialogOpen} key="downgrade">
<DialogTrigger asChild>
<Button variant="tertiary/large" fullWidth className="text-md font-medium">
{`Downgrade to ${plan.title}`}
</Button>
</DialogTrigger>
<DialogContent className="sm:max-w-md">
<DialogHeader>Downgrade plan?</DialogHeader>
<div className="mb-2 mt-4 flex items-start gap-3">
<span>
<ArrowDownCircleIcon className="size-12 text-blue-500" />
</span>
<Paragraph variant="base/bright" className="text-text-bright">
By downgrading you will lose access to your current plan's features and your
included credits will be reduced.
</Paragraph>
</div>
<DialogFooter>
<Button variant="tertiary/medium" onClick={() => setIsDialogOpen(false)}>
Dismiss
</Button>
<Button
variant="tertiary/medium"
disabled={isLoading}
LeadingIcon={isLoading ? () => <Spinner color="white" /> : undefined}
form="subscribe-hobby"
>
{`Downgrade to ${plan.title}`}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
) : (
<Button
variant={isHighlighted ? "primary/large" : "tertiary/large"}
fullWidth
className="text-md font-medium"
form="subscribe-hobby"
disabled={
isLoading ||
(subscription?.plan?.code === plan.code && subscription.canceledAt === undefined)
@@ -380,9 +642,9 @@ export function TierHobby({
? `Upgrade to ${plan.title}`
: subscription.plan.code === plan.code
? "Current plan"
: `Downgrade to ${plan.title}`}
: `Upgrade to ${plan.title}`}
</Button>
</div>
)}
</Form>
<ul className="flex flex-col gap-2.5">
<ConcurrentRuns limits={plan.limits} />
@@ -425,7 +687,7 @@ export function TierPro({
<TierLimit href="https://trigger.dev/pricing#computePricing">
${plan.limits.includedUsage / 100} usage included
</TierLimit>
<Form action={formAction} method="post" id="subscribe">
<Form action={formAction} method="post" id="subscribe-pro">
<div className="py-6">
<input type="hidden" name="type" value="paid" />
<input type="hidden" name="planCode" value={plan.code} />
@@ -433,6 +695,7 @@ export function TierPro({
<Button
variant="tertiary/large"
fullWidth
form="subscribe-pro"
className="text-md font-medium"
disabled={
isLoading ||
@@ -589,21 +852,28 @@ function PricingHeader({
function TierLimit({ children, href }: { children: React.ReactNode; href?: string }) {
return (
<>
<hr className="my-6 border-grid-bright" />
{href ? (
<div>
<hr className="my-6 border-grid-bright" />
<a
href={href}
className="hover:decoration-bright font-sans text-lg font-normal text-text-bright underline decoration-charcoal-500 underline-offset-4 transition"
>
{children}
</a>
</div>
<SimpleTooltip
buttonClassName="text-left w-fit"
disableHoverableContent
button={
<a
href={href}
className="text-left font-sans text-lg font-normal text-text-bright underline decoration-charcoal-500 underline-offset-4 transition hover:decoration-text-bright"
>
{children}
</a>
}
content={
<div className="flex items-center gap-1">
<Paragraph variant="small">View detailed compute pricing information</Paragraph>
<ArrowUpRightIcon className="size-4 text-text-dimmed" />
</div>
}
/>
) : (
<div>
<hr className="my-6 border-grid-bright" />
<div className="font-sans text-lg font-normal text-text-bright">{children}</div>
</div>
<div className="font-sans text-lg font-normal text-text-bright">{children}</div>
)}
</>
);