chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:32:57 +08:00
commit cd420f9332
4811 changed files with 884702 additions and 0 deletions
@@ -0,0 +1,510 @@
import { getFormProps, getInputProps, useForm } from "@conform-to/react";
import { parseWithZod } from "@conform-to/zod";
import { CommandLineIcon, FolderIcon } from "@heroicons/react/20/solid";
import { json, type ActionFunction, type LoaderFunctionArgs } from "@remix-run/node";
import { Form, useActionData, useNavigation } from "@remix-run/react";
import type { Prisma } from "@trigger.dev/database";
import React, { useEffect, useState } from "react";
import { redirect, typedjson, useTypedLoaderData } from "remix-typedjson";
import invariant from "tiny-invariant";
import { z } from "zod";
import { BackgroundWrapper } from "~/components/BackgroundWrapper";
import { Feedback } from "~/components/Feedback";
import { AppContainer, MainCenteredContainer } from "~/components/layout/AppLayout";
import { TechnologyPicker } from "~/components/onboarding/TechnologyPicker";
import { Button, LinkButton } from "~/components/primitives/Buttons";
import { Callout } from "~/components/primitives/Callout";
import { Fieldset } from "~/components/primitives/Fieldset";
import { FormButtons } from "~/components/primitives/FormButtons";
import { FormError } from "~/components/primitives/FormError";
import { FormTitle } from "~/components/primitives/FormTitle";
import { Input } from "~/components/primitives/Input";
import { InputGroup } from "~/components/primitives/InputGroup";
import { Label } from "~/components/primitives/Label";
import { Select, SelectItem } from "~/components/primitives/Select";
import { prisma } from "~/db.server";
import { featuresForRequest } from "~/features.server";
import { redirectWithErrorMessage, redirectWithSuccessMessage } from "~/models/message.server";
import { createProject, ExceededProjectLimitError } from "~/models/project.server";
import { requireUserId } from "~/services/session.server";
import {
newProjectPath,
OrganizationParamsSchema,
organizationPath,
selectPlanPath,
v3ProjectPath,
} from "~/utils/pathBuilder";
import { generateVercelOAuthState } from "~/v3/vercel/vercelOAuthState.server";
const WORKING_ON_OTHER = "Other/not sure yet";
const GOALS_OTHER = "Other/not sure yet";
const workingOnOptions = [
"AI agent",
"Media processing pipeline",
"Media generation with AI",
"Event-driven workflow",
"Realtime streaming",
"Internal tool or background job",
WORKING_ON_OTHER,
] as const;
const goalOptions = [
"Ship a production workflow",
"Prototype or explore",
"Migrate an existing system",
"Learn how Trigger works",
"Evaluate against alternatives",
GOALS_OTHER,
] as const;
function shuffleArray<T>(arr: T[]): T[] {
const shuffled = [...arr];
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
}
return shuffled;
}
function MultiSelectField({
value,
setValue,
items,
icon,
}: {
value: string[];
setValue: (value: string[]) => void;
items: string[];
icon: React.ReactNode;
}) {
return (
<Select<string[], string>
value={value}
setValue={setValue}
placeholder="Select some options"
variant="secondary/small"
dropdownIcon
icon={icon}
items={items}
className="h-8 min-w-0 border-0 bg-background-hover pl-2 text-sm text-text-dimmed ring-border-bright transition hover:bg-secondary hover:text-text-dimmed hover:ring-1"
text={(v) =>
v.length === 0 ? undefined : (
<span className="flex min-w-0 items-center text-text-bright">
<span className="truncate">{v.slice(0, 2).join(", ")}</span>
{v.length > 2 && <span className="ml-1 flex-none">+{v.length - 2} more</span>}
</span>
)
}
>
{(items) =>
items.map((item) => (
<SelectItem key={item} value={item} checkPosition="left">
<span className="text-text-bright">{item}</span>
</SelectItem>
))
}
</Select>
);
}
export async function loader({ params, request }: LoaderFunctionArgs) {
const userId = await requireUserId(request);
const { organizationSlug } = OrganizationParamsSchema.parse(params);
const organization = await prisma.organization.findFirst({
where: { slug: organizationSlug, members: { some: { userId } } },
select: {
id: true,
title: true,
isActivated: true,
v2Enabled: true,
hasRequestedV3: true,
_count: {
select: {
projects: {
where: {
deletedAt: null,
},
},
},
},
},
});
if (!organization) {
throw new Response(null, { status: 404, statusText: "Organization not found" });
}
const { isManagedCloud } = featuresForRequest(request);
if (isManagedCloud && !organization.isActivated) {
return redirect(selectPlanPath({ slug: organizationSlug }));
}
const url = new URL(request.url);
const message = url.searchParams.get("message");
return typedjson({
organization: {
id: organization.id,
title: organization.title,
slug: organizationSlug,
projectsCount: organization._count.projects,
isActivated: organization.isActivated,
v2Enabled: organization.v2Enabled,
hasRequestedV3: organization.hasRequestedV3,
},
defaultVersion: url.searchParams.get("version") ?? "v2",
message: message ? decodeURIComponent(message) : undefined,
});
}
const schema = z.object({
projectName: z.string().min(3, "Project name must have at least 3 characters").max(50),
projectVersion: z.enum(["v2", "v3"]),
workingOn: z.string().optional(),
workingOnOther: z.string().optional(),
technologies: z.string().optional(),
technologiesOther: z.string().optional(),
goals: z.string().optional(),
goalsOther: z.string().optional(),
workingOnPositions: z.string().optional(),
goalsPositions: z.string().optional(),
});
export const action: ActionFunction = async ({ request, params }) => {
const userId = await requireUserId(request);
const { organizationSlug } = params;
invariant(organizationSlug, "organizationSlug is required");
const formData = await request.formData();
const submission = parseWithZod(formData, { schema });
if (submission.status !== "success") {
return json(submission.reply());
}
const url = new URL(request.url);
const code = url.searchParams.get("code");
const configurationId = url.searchParams.get("configurationId");
const next = url.searchParams.get("next");
const stringArraySchema = z.array(z.string());
function safeParseStringArray(value: string | undefined): string[] | undefined {
if (!value) return undefined;
try {
const result = stringArraySchema.safeParse(JSON.parse(value));
return result.success && result.data.length > 0 ? result.data : undefined;
} catch {
return undefined;
}
}
const numberArraySchema = z.array(z.number());
function safeParseNumberArray(value: string | undefined): number[] | undefined {
if (!value) return undefined;
try {
const result = numberArraySchema.safeParse(JSON.parse(value));
return result.success && result.data.length > 0 ? result.data : undefined;
} catch {
return undefined;
}
}
const onboardingData: Record<string, Prisma.InputJsonValue> = {};
const workingOn = safeParseStringArray(submission.value.workingOn);
if (workingOn) {
onboardingData.workingOn = workingOn;
const workingOnPositions = safeParseNumberArray(submission.value.workingOnPositions);
if (workingOnPositions) onboardingData.workingOnPositions = workingOnPositions;
}
if (submission.value.workingOnOther) {
onboardingData.workingOnOther = submission.value.workingOnOther;
}
const technologies = safeParseStringArray(submission.value.technologies);
if (technologies) onboardingData.technologies = technologies;
const technologiesOther = safeParseStringArray(submission.value.technologiesOther);
if (technologiesOther) onboardingData.technologiesOther = technologiesOther;
const goals = safeParseStringArray(submission.value.goals);
if (goals) {
onboardingData.goals = goals;
const goalsPositions = safeParseNumberArray(submission.value.goalsPositions);
if (goalsPositions) onboardingData.goalsPositions = goalsPositions;
}
if (submission.value.goalsOther) {
onboardingData.goalsOther = submission.value.goalsOther;
}
try {
const project = await createProject({
organizationSlug: organizationSlug,
name: submission.value.projectName,
userId,
version: submission.value.projectVersion,
onboardingData: Object.keys(onboardingData).length > 0 ? onboardingData : undefined,
});
if (code && configurationId) {
const environment = await prisma.runtimeEnvironment.findFirst({
where: {
projectId: project.id,
slug: "prod",
archivedAt: null,
},
});
if (!environment) {
return redirectWithErrorMessage(
newProjectPath({ slug: organizationSlug }),
request,
"Failed to find project environment."
);
}
const state = await generateVercelOAuthState({
organizationId: project.organization.id,
projectId: project.id,
environmentSlug: environment.slug,
organizationSlug: project.organization.slug,
projectSlug: project.slug,
});
const params = new URLSearchParams({
state,
code,
configurationId,
origin: "marketplace",
});
if (next) {
params.set("next", next);
}
return redirect(`/vercel/connect?${params.toString()}`);
}
return redirectWithSuccessMessage(
v3ProjectPath(project.organization, project),
request,
`${submission.value.projectName} created`
);
} catch (error) {
if (error instanceof ExceededProjectLimitError) {
return redirectWithErrorMessage(
newProjectPath({ slug: organizationSlug }),
request,
error.message,
{
title: "Failed to create project",
action: {
label: "Request more projects",
variant: "secondary/small",
action: { type: "help", feedbackType: "help" },
},
}
);
}
return redirectWithErrorMessage(
newProjectPath({ slug: organizationSlug }),
request,
error instanceof Error ? error.message : "Something went wrong",
{ ephemeral: false }
);
}
};
export default function Page() {
const { organization, message } = useTypedLoaderData<typeof loader>();
const lastSubmission = useActionData();
const canCreateV3Projects = organization.isActivated;
const [form, { projectName, projectVersion }] = useForm({
id: "create-project",
lastResult: lastSubmission as any,
onValidate({ formData }) {
return parseWithZod(formData, { schema });
},
});
const navigation = useNavigation();
const isLoading = navigation.state === "submitting" || navigation.state === "loading";
const [selectedWorkingOn, setSelectedWorkingOn] = useState<string[]>([]);
const [workingOnOther, setWorkingOnOther] = useState("");
const [selectedTechnologies, setSelectedTechnologies] = useState<string[]>([]);
const [customTechnologies, setCustomTechnologies] = useState<string[]>([]);
const [selectedGoals, setSelectedGoals] = useState<string[]>([]);
const [goalsOther, setGoalsOther] = useState("");
const [shuffledWorkingOn, setShuffledWorkingOn] = useState<string[]>([...workingOnOptions]);
const [shuffledGoals, setShuffledGoals] = useState<string[]>([...goalOptions]);
useEffect(() => {
const nonOther = workingOnOptions.filter((o) => o !== WORKING_ON_OTHER);
setShuffledWorkingOn([...shuffleArray(nonOther), WORKING_ON_OTHER]);
const nonOtherGoals = goalOptions.filter((o) => o !== GOALS_OTHER);
setShuffledGoals([...shuffleArray(nonOtherGoals), GOALS_OTHER]);
}, []);
const showWorkingOnOther = selectedWorkingOn.includes(WORKING_ON_OTHER);
const showGoalsOther = selectedGoals.includes(GOALS_OTHER);
return (
<AppContainer className="bg-background-deep">
<BackgroundWrapper>
<MainCenteredContainer
variant="onboarding"
className="max-w-116 rounded-lg border border-grid-bright bg-background-dimmed p-5 shadow-lg"
>
<div>
<FormTitle
LeadingIcon={<FolderIcon className="size-7 text-indigo-500" />}
title="Create a new project"
description={`This will create a new project in your "${organization.title}" organization.`}
/>
<Form method="post" {...getFormProps(form)}>
{message && (
<Callout variant="success" className="mb-4">
{message}
</Callout>
)}
<Fieldset>
<InputGroup>
<Label htmlFor={projectName.id}>
Project name <span className="text-text-bright">*</span>
</Label>
<Input
{...getInputProps(projectName, { type: "text" })}
placeholder="Your project name"
icon={FolderIcon}
autoFocus
/>
<FormError id={projectName.errorId}>{projectName.errors}</FormError>
</InputGroup>
{canCreateV3Projects ? (
<input
{...getInputProps(projectVersion, { type: "hidden" })}
defaultValue={"v3"}
/>
) : (
<input
{...getInputProps(projectVersion, { type: "hidden" })}
defaultValue={"v2"}
/>
)}
<div className="border-t border-grid-bright" />
<InputGroup>
<Label>What are you working on?</Label>
<input type="hidden" name="workingOn" value={JSON.stringify(selectedWorkingOn)} />
<input
type="hidden"
name="workingOnPositions"
value={JSON.stringify(
selectedWorkingOn.map((v) => shuffledWorkingOn.indexOf(v) + 1)
)}
/>
<MultiSelectField
value={selectedWorkingOn}
setValue={setSelectedWorkingOn}
items={shuffledWorkingOn}
icon={<CommandLineIcon className="mr-1 size-4 text-text-dimmed" />}
/>
{showWorkingOnOther && (
<>
<input type="hidden" name="workingOnOther" value={workingOnOther} />
<Input
type="text"
variant="small"
value={workingOnOther}
onChange={(e) => setWorkingOnOther(e.target.value)}
placeholder="Tell us what you're working on"
spellCheck={false}
containerClassName="h-8"
/>
</>
)}
</InputGroup>
<InputGroup>
<Label>What technologies are you using?</Label>
<input
type="hidden"
name="technologies"
value={JSON.stringify(selectedTechnologies)}
/>
<input
type="hidden"
name="technologiesOther"
value={JSON.stringify(customTechnologies)}
/>
<TechnologyPicker
value={selectedTechnologies}
onChange={setSelectedTechnologies}
customValues={customTechnologies}
onCustomValuesChange={setCustomTechnologies}
/>
</InputGroup>
<InputGroup>
<Label>What are you trying to do with Trigger.dev?</Label>
<input type="hidden" name="goals" value={JSON.stringify(selectedGoals)} />
<input
type="hidden"
name="goalsPositions"
value={JSON.stringify(selectedGoals.map((v) => shuffledGoals.indexOf(v) + 1))}
/>
<MultiSelectField
value={selectedGoals}
setValue={setSelectedGoals}
items={shuffledGoals}
icon={<CommandLineIcon className="mr-1 size-4 text-text-dimmed" />}
/>
{showGoalsOther && (
<>
<input type="hidden" name="goalsOther" value={goalsOther} />
<Input
type="text"
variant="small"
value={goalsOther}
onChange={(e) => setGoalsOther(e.target.value)}
placeholder="Tell us what you're trying to do with Trigger.dev"
spellCheck={false}
containerClassName="h-8"
/>
</>
)}
</InputGroup>
<FormButtons
confirmButton={
<Button type="submit" variant={"primary/small"} isLoading={isLoading}>
Create
</Button>
}
cancelButton={
organization.projectsCount > 0 ? (
<LinkButton to={organizationPath(organization)} variant={"secondary/small"}>
Cancel
</LinkButton>
) : undefined
}
/>
</Fieldset>
</Form>
</div>
<Feedback button={<></>} />
</MainCenteredContainer>
</BackgroundWrapper>
</AppContainer>
);
}