Skip to content

Commit

Permalink
Improved the onboarding experience
Browse files Browse the repository at this point in the history
 - Automatically refresh the jobs page when there are no jobs
 - Celebrate 🎊
  • Loading branch information
ericallam committed Aug 11, 2023
1 parent c246578 commit 9dd7fb2
Show file tree
Hide file tree
Showing 10 changed files with 154 additions and 82 deletions.
3 changes: 3 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,9 @@ pnpm --filter @trigger.dev/database generate

# Move trigger-cli bin to correct place
pnpm install --frozen-lockfile

# Install playwrite browsers (ONE TIME ONLY)
npx playwright install
```

3. Set up the database
Expand Down
105 changes: 63 additions & 42 deletions apps/webapp/app/components/helpContent/HelpContentText.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { ChatBubbleLeftRightIcon } from "@heroicons/react/20/solid";
import { Link, useSearchParams } from "@remix-run/react";
import { Link, useRevalidator } from "@remix-run/react";
import { useEffect, useState } from "react";
import { useEventSource } from "remix-utils";
import invariant from "tiny-invariant";
import gradientBackground from "~/assets/images/gradient-background.png";
import { Paragraph } from "~/components/primitives/Paragraph";
import { StepNumber } from "~/components/primitives/StepNumber";
import { useAppOrigin } from "~/hooks/useAppOrigin";
Expand All @@ -9,7 +12,7 @@ import { useJob } from "~/hooks/useJob";
import { useOrganization } from "~/hooks/useOrganizations";
import { useProject } from "~/hooks/useProject";
import { IntegrationIcon } from "~/routes/_app.orgs.$organizationSlug.projects.$projectParam.integrations/route";
import { jobTestPath } from "~/utils/pathBuilder";
import { jobTestPath, projectStreamingPath } from "~/utils/pathBuilder";
import { Feedback } from "../Feedback";
import { CodeBlock } from "../code/CodeBlock";
import { InlineCode } from "../code/InlineCode";
Expand All @@ -33,20 +36,60 @@ import { TextLink } from "../primitives/TextLink";
import integrationButton from "./integration-button.png";
import selectEnvironment from "./select-environment.png";
import selectExample from "./select-example.png";
import gradientBackground from "~/assets/images/gradient-background.png";

const existingProjectValue = "use-existing-project";
const newProjectValue = "create-new-next-app";
type SelectionChoices = "use-existing-project" | "create-new-next-app";

export function HowToSetupYourProject() {
const project = useProject();
const devEnvironment = useDevEnvironment();
const appOrigin = useAppOrigin();

const [searchQuery, setSearchQuery] = useSearchParams();
const selectedValue = searchQuery.get("selectedValue");
const [selectedValue, setSelectedValue] = useState<SelectionChoices | null>(null);

invariant(devEnvironment, "devEnvironment is required");

const revalidator = useRevalidator();
const events = useEventSource(projectStreamingPath(project.id), {
event: "message",
});

useEffect(() => {
if (events !== null) {
// This uses https://www.npmjs.com/package/canvas-confetti
if ("confetti" in window && typeof window.confetti !== "undefined") {
var duration = 2.5 * 1000;
var end = Date.now() + duration;

(function frame() {
// launch a few confetti from the left edge
// @ts-ignore
window.confetti({
particleCount: 7,
angle: 60,
spread: 55,
origin: { x: 0 },
});
// and launch a few from the right edge
// @ts-ignore
window.confetti({
particleCount: 7,
angle: 120,
spread: 55,
origin: { x: 1 },
});

// keep going until we are out of time
if (Date.now() < end) {
requestAnimationFrame(frame);
}
})();
}

revalidator.revalidate();
}
// WARNING Don't put the revalidator in the useEffect deps array or bad things will happen
}, [events]); // eslint-disable-line react-hooks/exhaustive-deps

return (
<div
className="-ml-4 -mt-4 h-full w-[calc(100%+32px)] bg-cover bg-no-repeat pt-20"
Expand All @@ -55,7 +98,7 @@ export function HowToSetupYourProject() {
<div className="mx-auto max-w-3xl">
<div className="flex items-center justify-between">
<Header1 spacing className="text-bright">
Get setup in {selectedValue === newProjectValue ? "5" : "2"} minutes
Get setup in {selectedValue === "create-new-next-app" ? "5" : "2"} minutes
</Header1>
<Feedback
button={
Expand All @@ -68,30 +111,30 @@ export function HowToSetupYourProject() {
</div>
<RadioGroup
className="mb-4 flex gap-x-2"
onValueChange={(value) => setSearchQuery({ selectedValue: value })}
onValueChange={(value) => setSelectedValue(value as SelectionChoices)}
>
<RadioGroupItem
label="Use an existing Next.js project"
description="Use Trigger.dev in an existing Next.js project in less than 2 mins."
value={existingProjectValue}
checked={selectedValue === existingProjectValue}
value="use-existing-project"
checked={selectedValue === "use-existing-project"}
variant="icon"
data-action={existingProjectValue}
data-action="use-existing-project"
icon={<NamedIcon className="h-12 w-12 text-green-600" name={"tree"} />}
/>
<RadioGroupItem
label="Create a new Next.js project"
description="This is the quickest way to try out Trigger.dev in a new Next.js project and takes 5 mins."
value={newProjectValue}
checked={selectedValue === newProjectValue}
value="create-new-next-app"
checked={selectedValue === "create-new-next-app"}
variant="icon"
data-action={newProjectValue}
data-action="create-new-next-app"
icon={<NamedIcon className="h-8 w-8 text-green-600" name={"sapling"} />}
/>
</RadioGroup>
{selectedValue && (
<>
{selectedValue === newProjectValue ? (
{selectedValue === "create-new-next-app" ? (
<>
<StepNumber stepNumber="1" title="Create a new Next.js project" />
<StepContentContainer>
Expand Down Expand Up @@ -159,20 +202,9 @@ export function HowToSetupYourProject() {
<StepContentContainer>
<TriggerDevStep />
</StepContentContainer>
<StepNumber stepNumber="6" title="Check for Jobs" />
<StepNumber stepNumber="6" title="Wait for Jobs" displaySpinner />
<StepContentContainer>
<Paragraph>
Once you've run the CLI command, click Refresh to view your example Job in the
list.
</Paragraph>
<Button
variant="primary/medium"
className="mt-4"
LeadingIcon="refresh"
onClick={() => window.location.reload()}
>
Refresh
</Button>
<Paragraph>This page will automatically refresh.</Paragraph>
</StepContentContainer>
</>
) : (
Expand All @@ -198,20 +230,9 @@ export function HowToSetupYourProject() {
<StepContentContainer>
<TriggerDevStep />
</StepContentContainer>
<StepNumber stepNumber="4" title="Check for Jobs" />
<StepNumber stepNumber="4" title="Wait for Jobs" displaySpinner />
<StepContentContainer>
<Paragraph>
Once you've run the CLI command, click Refresh to view your example Job in the
list.
</Paragraph>
<Button
variant="primary/medium"
className="mt-4"
LeadingIcon="refresh"
onClick={() => window.location.reload()}
>
Refresh
</Button>
<Paragraph>This page will automatically refresh.</Paragraph>
</StepContentContainer>
</>
)}
Expand Down
13 changes: 12 additions & 1 deletion apps/webapp/app/components/primitives/StepNumber.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { cn } from "~/utils/cn";
import { Header2 } from "./Headers";
import { Spinner } from "./Spinner";

export function StepNumber({
stepNumber,
active = false,
complete = false,
displaySpinner = false,
title,
className,
}: {
Expand All @@ -13,6 +15,7 @@ export function StepNumber({
complete?: boolean;
title?: React.ReactNode;
className?: string;
displaySpinner?: boolean;
}) {
return (
<div className={cn("mr-3", className)}>
Expand All @@ -28,7 +31,15 @@ export function StepNumber({
<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">
{complete ? "✓" : stepNumber}
</span>
<Header2>{title}</Header2>

{displaySpinner ? (
<div className="flex items-center gap-x-2">
<Header2>{title}</Header2>
<Spinner />
</div>
) : (
<Header2>{title}</Header2>
)}
</div>
)}
</div>
Expand Down
2 changes: 2 additions & 0 deletions apps/webapp/app/root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { getUser } from "./services/session.server";
import { appEnvTitleTag } from "./utils";
import { ErrorBoundary as HighlightErrorBoundary } from "@highlight-run/react";
import { useHighlight } from "./hooks/useHighlight";
import { ExternalScripts } from "remix-utils";

export const links: LinksFunction = () => {
return [{ rel: "stylesheet", href: tailwindStylesheetUrl }];
Expand Down Expand Up @@ -104,6 +105,7 @@ function App() {
</HighlightErrorBoundary>
<Toast />
<ScrollRestoration />
<ExternalScripts />
<Scripts />
<LiveReload />
</body>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { ArrowUpIcon } from "@heroicons/react/24/solid";
import { LoaderArgs } from "@remix-run/server-runtime";
import { GitHubLightIcon, OpenAILightIcon, ResendIcon } from "@trigger.dev/companyicons";
import { CalendarDaysIcon, ClockIcon, SlackIcon } from "lucide-react";
import { LoaderArgs, SerializeFrom } from "@remix-run/server-runtime";
import useWindowSize from "react-use/lib/useWindowSize";
import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { ExternalScriptsFunction } from "remix-utils";
import { HowToSetupYourProject } from "~/components/helpContent/HelpContentText";
import { JobsTable } from "~/components/jobs/JobsTable";
import { PageBody, PageContainer } from "~/components/layout/AppLayout";
Expand Down Expand Up @@ -59,6 +58,12 @@ export const loader = async ({ request, params }: LoaderArgs) => {
export const handle: Handle = {
breadcrumb: (match) => <BreadcrumbLink to={trimTrailingSlash(match.pathname)} title="Jobs" />,
expandSidebar: true,
scripts: (match) => [
{
src: "https://cdn.jsdelivr.net/npm/canvas-confetti@1.5.1/dist/confetti.browser.min.js",
crossOrigin: "anonymous",
},
],
};

export default function Page() {
Expand All @@ -83,25 +88,6 @@ export default function Page() {
</PageInfoRow>
</PageHeader>
<PageBody>
{/* Todo: this confetti component needs to trigger when the example project is created, then never again. */}
{/* <Confetti
width={width}
height={height}
recycle={false}
numberOfPieces={1000}
colors={[
"#E7FF52",
"#41FF54",
"rgb(245 158 11)",
"rgb(22 163 74)",
"rgb(37 99 235)",
"rgb(67 56 202)",
"rgb(219 39 119)",
"rgb(225 29 72)",
"rgb(217 70 239)",
]}
/> */}

<Help>
{(open) => (
<div className={cn("grid gap-4", open ? "h-full grid-cols-2" : " h-full grid-cols-1")}>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { LoaderArgs } from "@remix-run/server-runtime";
import { z } from "zod";
import { prisma } from "~/db.server";
import { requireUserId } from "~/services/session.server";
import { sse } from "~/utils/sse";

export async function loader({ request, params }: LoaderArgs) {
await requireUserId(request);

const { projectId } = z.object({ projectId: z.string() }).parse(params);

const project = await projectForUpdates(projectId);

if (!project) {
return new Response("Not found", { status: 404 });
}

let lastSignals = calculateChangeSignals(project);

return sse({
request,
run: async (send, stop) => {
const result = await projectForUpdates(projectId);
if (!result) {
return stop();
}

const newSignals = calculateChangeSignals(result);

if (lastSignals.jobCount !== newSignals.jobCount) {
send({ data: JSON.stringify(newSignals) });
}

lastSignals = newSignals;
},
});
}

function projectForUpdates(id: string) {
return prisma.project.findUnique({
where: {
id,
},
include: {
_count: {
select: { jobs: true },
},
},
});
}

function calculateChangeSignals(
project: NonNullable<Awaited<ReturnType<typeof projectForUpdates>>>
) {
const jobCount = project._count?.jobs ?? 0;

return {
jobCount,
};
}
2 changes: 2 additions & 0 deletions apps/webapp/app/utils/handle.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { ExternalScriptsFunction } from "remix-utils";
import { BreadcrumbItem } from "~/components/navigation/Breadcrumb";

export type Handle = {
breadcrumb?: BreadcrumbItem;
expandSidebar?: boolean;
scripts?: ExternalScriptsFunction;
};
4 changes: 4 additions & 0 deletions apps/webapp/app/utils/pathBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,10 @@ export function projectEnvironmentsPath(organization: OrgForPath, project: Proje
return `${projectPath(organization, project)}/environments`;
}

export function projectStreamingPath(id: string) {
return `/resources/projects/${id}/jobs/stream`;
}

export function projectEnvironmentsStreamingPath(
organization: OrgForPath,
project: ProjectForPath
Expand Down
1 change: 0 additions & 1 deletion apps/webapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,6 @@
"prism-react-renderer": "^1.3.5",
"prismjs": "^1.29.0",
"react": "^18.2.0",
"react-confetti": "^6.1.0",
"react-dom": "^18.2.0",
"react-hot-toast": "^2.4.0",
"react-hotkeys-hook": "^3.4.7",
Expand Down
Loading

0 comments on commit 9dd7fb2

Please sign in to comment.