Skip to content

Commit 9dd7fb2

Browse files
committed
Improved the onboarding experience
- Automatically refresh the jobs page when there are no jobs - Celebrate 🎊
1 parent c246578 commit 9dd7fb2

File tree

10 files changed

+154
-82
lines changed

10 files changed

+154
-82
lines changed

CONTRIBUTING.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,9 @@ pnpm --filter @trigger.dev/database generate
190190
191191
# Move trigger-cli bin to correct place
192192
pnpm install --frozen-lockfile
193+
194+
# Install playwrite browsers (ONE TIME ONLY)
195+
npx playwright install
193196
```
194197
195198
3. Set up the database

apps/webapp/app/components/helpContent/HelpContentText.tsx

Lines changed: 63 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import { ChatBubbleLeftRightIcon } from "@heroicons/react/20/solid";
2-
import { Link, useSearchParams } from "@remix-run/react";
2+
import { Link, useRevalidator } from "@remix-run/react";
3+
import { useEffect, useState } from "react";
4+
import { useEventSource } from "remix-utils";
35
import invariant from "tiny-invariant";
6+
import gradientBackground from "~/assets/images/gradient-background.png";
47
import { Paragraph } from "~/components/primitives/Paragraph";
58
import { StepNumber } from "~/components/primitives/StepNumber";
69
import { useAppOrigin } from "~/hooks/useAppOrigin";
@@ -9,7 +12,7 @@ import { useJob } from "~/hooks/useJob";
912
import { useOrganization } from "~/hooks/useOrganizations";
1013
import { useProject } from "~/hooks/useProject";
1114
import { IntegrationIcon } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations/route";
12-
import { jobTestPath } from "~/utils/pathBuilder";
15+
import { jobTestPath, projectStreamingPath } from "~/utils/pathBuilder";
1316
import { Feedback } from "../Feedback";
1417
import { CodeBlock } from "../code/CodeBlock";
1518
import { InlineCode } from "../code/InlineCode";
@@ -33,20 +36,60 @@ import { TextLink } from "../primitives/TextLink";
3336
import integrationButton from "./integration-button.png";
3437
import selectEnvironment from "./select-environment.png";
3538
import selectExample from "./select-example.png";
36-
import gradientBackground from "~/assets/images/gradient-background.png";
3739

38-
const existingProjectValue = "use-existing-project";
39-
const newProjectValue = "create-new-next-app";
40+
type SelectionChoices = "use-existing-project" | "create-new-next-app";
4041

4142
export function HowToSetupYourProject() {
43+
const project = useProject();
4244
const devEnvironment = useDevEnvironment();
4345
const appOrigin = useAppOrigin();
4446

45-
const [searchQuery, setSearchQuery] = useSearchParams();
46-
const selectedValue = searchQuery.get("selectedValue");
47+
const [selectedValue, setSelectedValue] = useState<SelectionChoices | null>(null);
4748

4849
invariant(devEnvironment, "devEnvironment is required");
4950

51+
const revalidator = useRevalidator();
52+
const events = useEventSource(projectStreamingPath(project.id), {
53+
event: "message",
54+
});
55+
56+
useEffect(() => {
57+
if (events !== null) {
58+
// This uses https://www.npmjs.com/package/canvas-confetti
59+
if ("confetti" in window && typeof window.confetti !== "undefined") {
60+
var duration = 2.5 * 1000;
61+
var end = Date.now() + duration;
62+
63+
(function frame() {
64+
// launch a few confetti from the left edge
65+
// @ts-ignore
66+
window.confetti({
67+
particleCount: 7,
68+
angle: 60,
69+
spread: 55,
70+
origin: { x: 0 },
71+
});
72+
// and launch a few from the right edge
73+
// @ts-ignore
74+
window.confetti({
75+
particleCount: 7,
76+
angle: 120,
77+
spread: 55,
78+
origin: { x: 1 },
79+
});
80+
81+
// keep going until we are out of time
82+
if (Date.now() < end) {
83+
requestAnimationFrame(frame);
84+
}
85+
})();
86+
}
87+
88+
revalidator.revalidate();
89+
}
90+
// WARNING Don't put the revalidator in the useEffect deps array or bad things will happen
91+
}, [events]); // eslint-disable-line react-hooks/exhaustive-deps
92+
5093
return (
5194
<div
5295
className="-ml-4 -mt-4 h-full w-[calc(100%+32px)] bg-cover bg-no-repeat pt-20"
@@ -55,7 +98,7 @@ export function HowToSetupYourProject() {
5598
<div className="mx-auto max-w-3xl">
5699
<div className="flex items-center justify-between">
57100
<Header1 spacing className="text-bright">
58-
Get setup in {selectedValue === newProjectValue ? "5" : "2"} minutes
101+
Get setup in {selectedValue === "create-new-next-app" ? "5" : "2"} minutes
59102
</Header1>
60103
<Feedback
61104
button={
@@ -68,30 +111,30 @@ export function HowToSetupYourProject() {
68111
</div>
69112
<RadioGroup
70113
className="mb-4 flex gap-x-2"
71-
onValueChange={(value) => setSearchQuery({ selectedValue: value })}
114+
onValueChange={(value) => setSelectedValue(value as SelectionChoices)}
72115
>
73116
<RadioGroupItem
74117
label="Use an existing Next.js project"
75118
description="Use Trigger.dev in an existing Next.js project in less than 2 mins."
76-
value={existingProjectValue}
77-
checked={selectedValue === existingProjectValue}
119+
value="use-existing-project"
120+
checked={selectedValue === "use-existing-project"}
78121
variant="icon"
79-
data-action={existingProjectValue}
122+
data-action="use-existing-project"
80123
icon={<NamedIcon className="h-12 w-12 text-green-600" name={"tree"} />}
81124
/>
82125
<RadioGroupItem
83126
label="Create a new Next.js project"
84127
description="This is the quickest way to try out Trigger.dev in a new Next.js project and takes 5 mins."
85-
value={newProjectValue}
86-
checked={selectedValue === newProjectValue}
128+
value="create-new-next-app"
129+
checked={selectedValue === "create-new-next-app"}
87130
variant="icon"
88-
data-action={newProjectValue}
131+
data-action="create-new-next-app"
89132
icon={<NamedIcon className="h-8 w-8 text-green-600" name={"sapling"} />}
90133
/>
91134
</RadioGroup>
92135
{selectedValue && (
93136
<>
94-
{selectedValue === newProjectValue ? (
137+
{selectedValue === "create-new-next-app" ? (
95138
<>
96139
<StepNumber stepNumber="1" title="Create a new Next.js project" />
97140
<StepContentContainer>
@@ -159,20 +202,9 @@ export function HowToSetupYourProject() {
159202
<StepContentContainer>
160203
<TriggerDevStep />
161204
</StepContentContainer>
162-
<StepNumber stepNumber="6" title="Check for Jobs" />
205+
<StepNumber stepNumber="6" title="Wait for Jobs" displaySpinner />
163206
<StepContentContainer>
164-
<Paragraph>
165-
Once you've run the CLI command, click Refresh to view your example Job in the
166-
list.
167-
</Paragraph>
168-
<Button
169-
variant="primary/medium"
170-
className="mt-4"
171-
LeadingIcon="refresh"
172-
onClick={() => window.location.reload()}
173-
>
174-
Refresh
175-
</Button>
207+
<Paragraph>This page will automatically refresh.</Paragraph>
176208
</StepContentContainer>
177209
</>
178210
) : (
@@ -198,20 +230,9 @@ export function HowToSetupYourProject() {
198230
<StepContentContainer>
199231
<TriggerDevStep />
200232
</StepContentContainer>
201-
<StepNumber stepNumber="4" title="Check for Jobs" />
233+
<StepNumber stepNumber="4" title="Wait for Jobs" displaySpinner />
202234
<StepContentContainer>
203-
<Paragraph>
204-
Once you've run the CLI command, click Refresh to view your example Job in the
205-
list.
206-
</Paragraph>
207-
<Button
208-
variant="primary/medium"
209-
className="mt-4"
210-
LeadingIcon="refresh"
211-
onClick={() => window.location.reload()}
212-
>
213-
Refresh
214-
</Button>
235+
<Paragraph>This page will automatically refresh.</Paragraph>
215236
</StepContentContainer>
216237
</>
217238
)}

apps/webapp/app/components/primitives/StepNumber.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
import { cn } from "~/utils/cn";
22
import { Header2 } from "./Headers";
3+
import { Spinner } from "./Spinner";
34

45
export function StepNumber({
56
stepNumber,
67
active = false,
78
complete = false,
9+
displaySpinner = false,
810
title,
911
className,
1012
}: {
@@ -13,6 +15,7 @@ export function StepNumber({
1315
complete?: boolean;
1416
title?: React.ReactNode;
1517
className?: string;
18+
displaySpinner?: boolean;
1619
}) {
1720
return (
1821
<div className={cn("mr-3", className)}>
@@ -28,7 +31,15 @@ export function StepNumber({
2831
<span className="flex h-6 w-6 items-center justify-center rounded border border-slate-700 bg-slate-800 py-1 text-xs font-semibold text-dimmed shadow">
2932
{complete ? "✓" : stepNumber}
3033
</span>
31-
<Header2>{title}</Header2>
34+
35+
{displaySpinner ? (
36+
<div className="flex items-center gap-x-2">
37+
<Header2>{title}</Header2>
38+
<Spinner />
39+
</div>
40+
) : (
41+
<Header2>{title}</Header2>
42+
)}
3243
</div>
3344
)}
3445
</div>

apps/webapp/app/root.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import { getUser } from "./services/session.server";
1616
import { appEnvTitleTag } from "./utils";
1717
import { ErrorBoundary as HighlightErrorBoundary } from "@highlight-run/react";
1818
import { useHighlight } from "./hooks/useHighlight";
19+
import { ExternalScripts } from "remix-utils";
1920

2021
export const links: LinksFunction = () => {
2122
return [{ rel: "stylesheet", href: tailwindStylesheetUrl }];
@@ -104,6 +105,7 @@ function App() {
104105
</HighlightErrorBoundary>
105106
<Toast />
106107
<ScrollRestoration />
108+
<ExternalScripts />
107109
<Scripts />
108110
<LiveReload />
109111
</body>

apps/webapp/app/routes/_app.orgs.$organizationSlug.projects.$projectParam._index/route.tsx

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,8 @@
11
import { ArrowUpIcon } from "@heroicons/react/24/solid";
2-
import { LoaderArgs } from "@remix-run/server-runtime";
3-
import { GitHubLightIcon, OpenAILightIcon, ResendIcon } from "@trigger.dev/companyicons";
4-
import { CalendarDaysIcon, ClockIcon, SlackIcon } from "lucide-react";
2+
import { LoaderArgs, SerializeFrom } from "@remix-run/server-runtime";
53
import useWindowSize from "react-use/lib/useWindowSize";
64
import { typedjson, useTypedLoaderData } from "remix-typedjson";
5+
import { ExternalScriptsFunction } from "remix-utils";
76
import { HowToSetupYourProject } from "~/components/helpContent/HelpContentText";
87
import { JobsTable } from "~/components/jobs/JobsTable";
98
import { PageBody, PageContainer } from "~/components/layout/AppLayout";
@@ -59,6 +58,12 @@ export const loader = async ({ request, params }: LoaderArgs) => {
5958
export const handle: Handle = {
6059
breadcrumb: (match) => <BreadcrumbLink to={trimTrailingSlash(match.pathname)} title="Jobs" />,
6160
expandSidebar: true,
61+
scripts: (match) => [
62+
{
63+
src: "https://cdn.jsdelivr.net/npm/[email protected]/dist/confetti.browser.min.js",
64+
crossOrigin: "anonymous",
65+
},
66+
],
6267
};
6368

6469
export default function Page() {
@@ -83,25 +88,6 @@ export default function Page() {
8388
</PageInfoRow>
8489
</PageHeader>
8590
<PageBody>
86-
{/* Todo: this confetti component needs to trigger when the example project is created, then never again. */}
87-
{/* <Confetti
88-
width={width}
89-
height={height}
90-
recycle={false}
91-
numberOfPieces={1000}
92-
colors={[
93-
"#E7FF52",
94-
"#41FF54",
95-
"rgb(245 158 11)",
96-
"rgb(22 163 74)",
97-
"rgb(37 99 235)",
98-
"rgb(67 56 202)",
99-
"rgb(219 39 119)",
100-
"rgb(225 29 72)",
101-
"rgb(217 70 239)",
102-
]}
103-
/> */}
104-
10591
<Help>
10692
{(open) => (
10793
<div className={cn("grid gap-4", open ? "h-full grid-cols-2" : " h-full grid-cols-1")}>
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import { LoaderArgs } from "@remix-run/server-runtime";
2+
import { z } from "zod";
3+
import { prisma } from "~/db.server";
4+
import { requireUserId } from "~/services/session.server";
5+
import { sse } from "~/utils/sse";
6+
7+
export async function loader({ request, params }: LoaderArgs) {
8+
await requireUserId(request);
9+
10+
const { projectId } = z.object({ projectId: z.string() }).parse(params);
11+
12+
const project = await projectForUpdates(projectId);
13+
14+
if (!project) {
15+
return new Response("Not found", { status: 404 });
16+
}
17+
18+
let lastSignals = calculateChangeSignals(project);
19+
20+
return sse({
21+
request,
22+
run: async (send, stop) => {
23+
const result = await projectForUpdates(projectId);
24+
if (!result) {
25+
return stop();
26+
}
27+
28+
const newSignals = calculateChangeSignals(result);
29+
30+
if (lastSignals.jobCount !== newSignals.jobCount) {
31+
send({ data: JSON.stringify(newSignals) });
32+
}
33+
34+
lastSignals = newSignals;
35+
},
36+
});
37+
}
38+
39+
function projectForUpdates(id: string) {
40+
return prisma.project.findUnique({
41+
where: {
42+
id,
43+
},
44+
include: {
45+
_count: {
46+
select: { jobs: true },
47+
},
48+
},
49+
});
50+
}
51+
52+
function calculateChangeSignals(
53+
project: NonNullable<Awaited<ReturnType<typeof projectForUpdates>>>
54+
) {
55+
const jobCount = project._count?.jobs ?? 0;
56+
57+
return {
58+
jobCount,
59+
};
60+
}

apps/webapp/app/utils/handle.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
import { ExternalScriptsFunction } from "remix-utils";
12
import { BreadcrumbItem } from "~/components/navigation/Breadcrumb";
23

34
export type Handle = {
45
breadcrumb?: BreadcrumbItem;
56
expandSidebar?: boolean;
7+
scripts?: ExternalScriptsFunction;
68
};

apps/webapp/app/utils/pathBuilder.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,10 @@ export function projectEnvironmentsPath(organization: OrgForPath, project: Proje
131131
return `${projectPath(organization, project)}/environments`;
132132
}
133133

134+
export function projectStreamingPath(id: string) {
135+
return `/resources/projects/${id}/jobs/stream`;
136+
}
137+
134138
export function projectEnvironmentsStreamingPath(
135139
organization: OrgForPath,
136140
project: ProjectForPath

apps/webapp/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,6 @@
9191
"prism-react-renderer": "^1.3.5",
9292
"prismjs": "^1.29.0",
9393
"react": "^18.2.0",
94-
"react-confetti": "^6.1.0",
9594
"react-dom": "^18.2.0",
9695
"react-hot-toast": "^2.4.0",
9796
"react-hotkeys-hook": "^3.4.7",

0 commit comments

Comments
 (0)