Feat: add onboarding screens

This commit is contained in:
Harshith Mullapudi 2025-07-25 11:36:20 +05:30
parent 24038a4789
commit b02c03390a
22 changed files with 402 additions and 80 deletions

View File

@ -1,18 +1,30 @@
import { Button } from "../ui"; import { Button } from "../ui";
import Logo from "../logo/logo"; import Logo from "../logo/logo";
import { Theme, useTheme } from "remix-themes"; import { Theme, useTheme } from "remix-themes";
import { GalleryVerticalEnd } from "lucide-react";
export function LoginPageLayout({ children }: { children: React.ReactNode }) { export function LoginPageLayout({ children }: { children: React.ReactNode }) {
return ( return (
<div className="flex min-h-svh flex-col items-center justify-center gap-6 p-6 md:p-10"> <div className="grid min-h-svh lg:grid-cols-2">
<div className="flex w-full max-w-sm flex-col items-center gap-2"> <div className="flex flex-col gap-4 p-6 md:p-10">
<div className="flex size-10 items-center justify-center rounded-md"> <div className="flex justify-center gap-2 md:justify-start">
<Logo width={60} height={60} /> <a href="#" className="flex items-center gap-2 font-medium">
<div className="flex size-8 items-center justify-center rounded-md">
<Logo width={60} height={60} />
</div>
C.O.R.E.
</a>
</div> </div>
<a href="#" className="flex items-center gap-2 self-center font-medium"> <div className="flex flex-1 items-center justify-center">
<div className="font-mono">C.O.R.E.</div> <div className="w-full max-w-sm">{children}</div>
</a> </div>
{children} </div>
<div className="relative hidden lg:block">
<img
src="/login.png"
alt="Image"
className="absolute inset-0 h-full w-full object-cover"
/>
</div> </div>
</div> </div>
); );

View File

@ -0,0 +1,26 @@
import { Button } from "../ui";
import Logo from "../logo/logo";
import { Theme, useTheme } from "remix-themes";
export function LoginPageLayout({ children }: { children: React.ReactNode }) {
return (
<div
className="flex min-h-svh flex-col items-center justify-center gap-6 p-6 md:p-10"
style={{
backgroundImage: 'url("/back.png")',
backgroundSize: "cover",
backgroundPosition: "center",
}}
>
<div className="flex w-full max-w-sm flex-col items-center gap-2">
<div className="flex size-10 items-center justify-center rounded-md">
<Logo width={60} height={60} />
</div>
<a href="#" className="flex items-center gap-2 self-center font-medium">
<div className="font-mono">C.O.R.E.</div>
</a>
{children}
</div>
</div>
);
}

View File

@ -39,7 +39,7 @@ export function NavUser({ user }: { user: ExtendedUser }) {
<div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm"> <div className="flex items-center gap-2 px-1 py-1.5 text-left text-sm">
<div className="grid flex-1 text-left text-sm leading-tight"> <div className="grid flex-1 text-left text-sm leading-tight">
<span className="truncate font-medium"> <span className="truncate font-medium">
Harshith Mullapudi {user.displayName}
</span> </span>
<span className="text-muted-foreground truncate text-xs"> <span className="text-muted-foreground truncate text-xs">
{user.email} {user.email}

View File

@ -200,22 +200,20 @@ export async function getUserByEmail(email: User["email"]) {
export function updateUser({ export function updateUser({
id, id,
name,
email,
marketingEmails, marketingEmails,
referralSource, referralSource,
}: Pick<User, "id" | "name" | "email"> & { onboardingComplete,
}: Pick<User, "id" | "onboardingComplete"> & {
marketingEmails?: boolean; marketingEmails?: boolean;
referralSource?: string; referralSource?: string;
}) { }) {
return prisma.user.update({ return prisma.user.update({
where: { id }, where: { id },
data: { data: {
name,
email,
marketingEmails, marketingEmails,
referralSource, referralSource,
confirmedBasicDetails: true, confirmedBasicDetails: true,
onboardingComplete,
}, },
}); });
} }

View File

@ -2,7 +2,11 @@ import { redirect, type MetaFunction } from "@remix-run/node";
import { type LoaderFunctionArgs } from "@remix-run/server-runtime"; import { type LoaderFunctionArgs } from "@remix-run/server-runtime";
import { requireUser } from "~/services/session.server"; import { requireUser } from "~/services/session.server";
import { confirmBasicDetailsPath, dashboardPath } from "~/utils/pathBuilder"; import {
confirmBasicDetailsPath,
dashboardPath,
onboardingPath,
} from "~/utils/pathBuilder";
export const meta: MetaFunction = () => { export const meta: MetaFunction = () => {
return [ return [
@ -17,6 +21,8 @@ export const loader = async ({ request }: LoaderFunctionArgs) => {
//you have to confirm basic details before you can do anything //you have to confirm basic details before you can do anything
if (!user.confirmedBasicDetails) { if (!user.confirmedBasicDetails) {
return redirect(confirmBasicDetailsPath()); return redirect(confirmBasicDetailsPath());
} else if (!user.onboardingComplete) {
return redirect(onboardingPath());
} else { } else {
return redirect(dashboardPath()); return redirect(dashboardPath());
} }

View File

@ -1,10 +1,10 @@
import { json } from "@remix-run/node"; import { json } from "@remix-run/node";
import { createActionApiRoute } from "~/services/routeBuilders/apiBuilder.server"; import { createHybridActionApiRoute } from "~/services/routeBuilders/apiBuilder.server";
import { addToQueue } from "~/lib/ingest.server"; import { addToQueue } from "~/lib/ingest.server";
import { IngestBodyRequest } from "~/trigger/ingest/ingest"; import { IngestBodyRequest } from "~/trigger/ingest/ingest";
const { action, loader } = createActionApiRoute( const { action, loader } = createHybridActionApiRoute(
{ {
body: IngestBodyRequest, body: IngestBodyRequest,
allowJWT: true, allowJWT: true,

View File

@ -1,6 +1,10 @@
import { z } from "zod"; import { z } from "zod";
import { useActionData } from "@remix-run/react"; import { useActionData } from "@remix-run/react";
import { type ActionFunctionArgs, json } from "@remix-run/node"; import {
type ActionFunctionArgs,
json,
LoaderFunctionArgs,
} from "@remix-run/node";
import { useForm } from "@conform-to/react"; import { useForm } from "@conform-to/react";
import { getFieldsetConstraint, parse } from "@conform-to/zod"; import { getFieldsetConstraint, parse } from "@conform-to/zod";
import { LoginPageLayout } from "~/components/layout/login-page-layout"; import { LoginPageLayout } from "~/components/layout/login-page-layout";
@ -14,10 +18,11 @@ import {
import { Button } from "~/components/ui"; import { Button } from "~/components/ui";
import { Input } from "~/components/ui/input"; import { Input } from "~/components/ui/input";
import { useState } from "react"; import { useState } from "react";
import { requireUserId } from "~/services/session.server"; import { requireUser, requireUserId } from "~/services/session.server";
import { redirectWithSuccessMessage } from "~/models/message.server"; import { redirectWithSuccessMessage } from "~/models/message.server";
import { rootPath } from "~/utils/pathBuilder"; import { rootPath } from "~/utils/pathBuilder";
import { createWorkspace } from "~/models/workspace.server"; import { createWorkspace } from "~/models/workspace.server";
import { typedjson } from "remix-typedjson";
const schema = z.object({ const schema = z.object({
workspaceName: z workspaceName: z
@ -55,6 +60,14 @@ export async function action({ request }: ActionFunctionArgs) {
} }
} }
export const loader = async ({ request }: LoaderFunctionArgs) => {
const user = await requireUser(request);
return typedjson({
user,
});
};
export default function ConfirmBasicDetails() { export default function ConfirmBasicDetails() {
const lastSubmission = useActionData<typeof action>(); const lastSubmission = useActionData<typeof action>();

View File

@ -8,22 +8,31 @@ import { clearRedirectTo, commitSession } from "~/services/redirectTo.server";
import { AppSidebar } from "~/components/sidebar/app-sidebar"; import { AppSidebar } from "~/components/sidebar/app-sidebar";
import { SidebarInset, SidebarProvider } from "~/components/ui/sidebar"; import { SidebarInset, SidebarProvider } from "~/components/ui/sidebar";
import { FloatingIngestionStatus } from "~/components/ingestion/floating-ingestion-status"; import { FloatingIngestionStatus } from "~/components/ingestion/floating-ingestion-status";
import { redirect } from "@remix-run/node";
import { confirmBasicDetailsPath, onboardingPath } from "~/utils/pathBuilder";
export const loader = async ({ request }: LoaderFunctionArgs) => { export const loader = async ({ request }: LoaderFunctionArgs) => {
const user = await requireUser(request); const user = await requireUser(request);
const workspace = await requireWorkpace(request); const workspace = await requireWorkpace(request);
return typedjson( //you have to confirm basic details before you can do anything
{ if (!user.confirmedBasicDetails) {
user, return redirect(confirmBasicDetailsPath());
workspace, } else if (!user.onboardingComplete) {
}, return redirect(onboardingPath());
{ } else {
headers: { return typedjson(
"Set-Cookie": await commitSession(await clearRedirectTo(request)), {
user,
workspace,
}, },
}, {
); headers: {
"Set-Cookie": await commitSession(await clearRedirectTo(request)),
},
},
);
}
}; };
export default function Home() { export default function Home() {

View File

@ -57,10 +57,12 @@ export default function LoginPage() {
return ( return (
<LoginPageLayout> <LoginPageLayout>
<Card className="min-w-[300px] rounded-md p-3"> <Card className="w-full max-w-[350px] rounded-md bg-transparent p-3">
<CardHeader className="flex flex-col items-start"> <CardHeader className="flex flex-col items-start">
<CardTitle>Login to your account</CardTitle> <CardTitle className="text-xl">Welcome back</CardTitle>
<CardDescription>Create an account or login</CardDescription> <CardDescription className="text-md">
Create an account or login
</CardDescription>
</CardHeader> </CardHeader>
<CardContent className="pt-2"> <CardContent className="pt-2">
@ -69,7 +71,7 @@ export default function LoginPage() {
{data.showGoogleAuth && ( {data.showGoogleAuth && (
<Button <Button
type="submit" type="submit"
size="lg" size="xl"
variant="secondary" variant="secondary"
className="rounded-lg text-base" className="rounded-lg text-base"
data-action="continue with google" data-action="continue with google"
@ -83,7 +85,7 @@ export default function LoginPage() {
{data.emailLoginEnabled && ( {data.emailLoginEnabled && (
<Button <Button
variant="secondary" variant="secondary"
size="lg" size="xl"
data-action="continue with email" data-action="continue with email"
className="text-text-bright" className="text-text-bright"
onClick={() => (window.location.href = "/login/magic")} onClick={() => (window.location.href = "/login/magic")}

View File

@ -13,14 +13,13 @@ import {
CardTitle, CardTitle,
} from "~/components/ui/card"; } from "~/components/ui/card";
import { Form, useNavigation } from "@remix-run/react"; import { Form, useNavigation } from "@remix-run/react";
import { Inbox, Loader, LoaderCircle, Mail } from "lucide-react"; import { LoaderCircle, Mail } from "lucide-react";
import { typedjson, useTypedLoaderData } from "remix-typedjson"; import { typedjson, useTypedLoaderData } from "remix-typedjson";
import { z } from "zod"; import { z } from "zod";
import { LoginPageLayout } from "~/components/layout/login-page-layout"; import { LoginPageLayout } from "~/components/layout/login-page-layout";
import { Button } from "~/components/ui"; import { Button } from "~/components/ui";
import { Fieldset } from "~/components/ui/Fieldset"; import { Fieldset } from "~/components/ui/Fieldset";
import { FormButtons } from "~/components/ui/FormButtons"; import { FormButtons } from "~/components/ui/FormButtons";
import { Header1 } from "~/components/ui/Headers";
import { Input } from "~/components/ui/input"; import { Input } from "~/components/ui/input";
import { Paragraph } from "~/components/ui/Paragraph"; import { Paragraph } from "~/components/ui/Paragraph";
import { Cookie } from "@mjackson/headers"; import { Cookie } from "@mjackson/headers";
@ -145,12 +144,12 @@ export default function LoginMagicLinkPage() {
<Form method="post"> <Form method="post">
<div className="flex flex-col items-center justify-center"> <div className="flex flex-col items-center justify-center">
{data.magicLinkSent ? ( {data.magicLinkSent ? (
<Card className="min-w-[400px] rounded-md p-3"> <Card className="min-w-[400px] rounded-md bg-transparent p-3">
<CardHeader className="flex flex-col items-start"> <CardHeader className="flex flex-col items-start">
<CardTitle className="mb-0 text-lg"> <CardTitle className="mb-0 text-xl">
Check your magic link Check your magic link
</CardTitle> </CardTitle>
<CardDescription> <CardDescription className="text-md">
The magic link is printed in the container logs if you are The magic link is printed in the container logs if you are
using Docker, otherwise check your server logs. using Docker, otherwise check your server logs.
</CardDescription> </CardDescription>
@ -164,6 +163,7 @@ export default function LoginMagicLinkPage() {
type="submit" type="submit"
name="action" name="action"
value="reset" value="reset"
size="lg"
variant="secondary" variant="secondary"
data-action="re-enter email" data-action="re-enter email"
> >
@ -174,14 +174,14 @@ export default function LoginMagicLinkPage() {
</Fieldset> </Fieldset>
</Card> </Card>
) : ( ) : (
<Card className="min-w-[400px] rounded-md p-3"> <Card className="w-full max-w-[350px] rounded-md bg-transparent p-3">
<CardHeader className="flex flex-col items-start"> <CardHeader className="flex flex-col items-start">
<CardTitle className="mb-0 text-lg">Welcome</CardTitle> <CardTitle className="text-xl">Welcome back</CardTitle>
<CardDescription> <CardDescription className="text-md">
Create an account or login using email Create an account or login using email
</CardDescription> </CardDescription>
</CardHeader> </CardHeader>
<CardContent className="pt-2"> <CardContent className="pt-2 pl-2">
<Fieldset className="flex w-full flex-col items-center gap-y-2"> <Fieldset className="flex w-full flex-col items-center gap-y-2">
<Input <Input
type="email" type="email"
@ -193,28 +193,32 @@ export default function LoginMagicLinkPage() {
autoFocus autoFocus
/> />
<Button <div className="flex w-full">
name="action" <Button
value="send" name="action"
type="submit" value="send"
variant="secondary" type="submit"
size="lg" variant="secondary"
disabled={isLoading} full
data-action="send a magic link" size="xl"
> className="w-full"
{isLoading ? ( disabled={isLoading}
<LoaderCircle className="text-primary h-4 w-4 animate-spin" /> data-action="send a magic link"
) : ( >
<Mail className="text-text-bright mr-2 size-5" /> {isLoading ? (
)} <LoaderCircle className="text-primary h-4 w-4 animate-spin" />
{isLoading ? ( ) : (
<span className="text-text-bright">Sending</span> <Mail className="text-text-bright mr-2 size-5" />
) : ( )}
<span className="text-text-bright"> {isLoading ? (
Send a magic link <span className="text-text-bright">Sending</span>
</span> ) : (
)} <span className="text-text-bright">
</Button> Send a magic link
</span>
)}
</Button>
</div>
{data.magicLinkError && <>{data.magicLinkError}</>} {data.magicLinkError && <>{data.magicLinkError}</>}
</Fieldset> </Fieldset>
</CardContent> </CardContent>

View File

@ -0,0 +1,230 @@
import { z } from "zod";
import { useLoaderData, useActionData, useNavigate } from "@remix-run/react";
import {
type ActionFunctionArgs,
json,
type LoaderFunctionArgs,
redirect,
createCookie,
} from "@remix-run/node";
import { useForm } from "@conform-to/react";
import { getFieldsetConstraint, parse } from "@conform-to/zod";
import { LoginPageLayout } from "~/components/layout/login-page-layout";
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "~/components/ui/card";
import { Button } from "~/components/ui";
import { Textarea } from "~/components/ui/textarea";
import { Input } from "~/components/ui/input";
import { useState } from "react";
import { requireUserId } from "~/services/session.server";
import { updateUser } from "~/models/user.server";
import { Copy, Check } from "lucide-react";
import { addToQueue } from "~/lib/ingest.server";
const ONBOARDING_STEP_COOKIE = "onboardingStep";
const onboardingStepCookie = createCookie(ONBOARDING_STEP_COOKIE, {
path: "/",
httpOnly: true,
sameSite: "lax",
maxAge: 60 * 60 * 24 * 7, // 1 week
});
const schema = z.object({
aboutUser: z
.string()
.min(
10,
"Please tell us a bit more about yourself (at least 10 characters)",
)
.max(1000, "Please keep it under 1000 characters"),
});
export async function loader({ request }: LoaderFunctionArgs) {
await requireUserId(request);
// Read step from cookie
const cookieHeader = request.headers.get("Cookie");
const cookie = (await onboardingStepCookie.parse(cookieHeader)) || {};
const step = cookie.step || null;
return json({ step });
}
export async function action({ request }: ActionFunctionArgs) {
const userId = await requireUserId(request);
const formData = await request.formData();
const submission = parse(formData, { schema });
if (!submission.value || submission.intent !== "submit") {
return json(submission);
}
const { aboutUser } = submission.value;
try {
// Ingest memory via API call
const memoryResponse = await addToQueue(
{
source: "Core",
episodeBody: aboutUser,
referenceTime: new Date().toISOString(),
},
userId,
);
if (!memoryResponse.id) {
throw new Error("Failed to save memory");
}
// Update user's onboarding status
await updateUser({
id: userId,
onboardingComplete: true,
});
// Set step in cookie and redirect to GET (PRG pattern)
const cookie = await onboardingStepCookie.serialize({
step: "memory-link",
});
return redirect("/onboarding", {
headers: {
"Set-Cookie": cookie,
},
});
} catch (e: any) {
return json({ errors: { body: e.message } }, { status: 400 });
}
}
export default function Onboarding() {
const loaderData = useLoaderData<{ step: string | null }>();
const lastSubmission = useActionData<typeof action>();
const navigate = useNavigate();
const [copied, setCopied] = useState(false);
const [form, fields] = useForm({
lastSubmission: lastSubmission as any,
constraint: getFieldsetConstraint(schema),
onValidate({ formData }) {
return parse(formData, { schema });
},
});
const memoryUrl = "https://core.heysol.ai/api/v1/mcp/memory";
const copyToClipboard = async () => {
try {
await navigator.clipboard.writeText(memoryUrl);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
} catch (err) {
console.error("Failed to copy:", err);
}
};
// Show memory link step after successful submission (step persisted in cookie)
if (loaderData.step === "memory-link") {
return (
<LoginPageLayout>
<Card className="min-w-[400px] rounded-lg bg-transparent p-3 pt-1">
<CardHeader className="flex flex-col items-start px-0">
<CardTitle className="px-0 text-xl">Your Memory Link</CardTitle>
<CardDescription className="text-md">
Here's your personal memory API endpoint. Copy this URL to connect
with external tools (Claude, Cursor etc).
</CardDescription>
</CardHeader>
<CardContent className="pt-2 text-base">
<div className="space-y-4">
<div>
<div className="bg-background-3 flex items-center rounded">
<Input
type="text"
id="memoryUrl"
value={memoryUrl}
readOnly
className="bg-background-3 block w-full text-base"
/>
<Button
type="button"
variant="link"
size="sm"
onClick={copyToClipboard}
className="px-3"
>
{copied ? (
<Check className="h-4 w-4" />
) : (
<Copy className="h-4 w-4" />
)}
</Button>
</div>
</div>
<Button
type="button"
variant="secondary"
size="xl"
className="w-full rounded-lg px-4 py-2"
onClick={() => navigate("/")}
>
Continue to Dashboard
</Button>
</div>
</CardContent>
</Card>
</LoginPageLayout>
);
}
return (
<LoginPageLayout>
<Card className="bg-background-2 w-full max-w-[400px] rounded-lg p-3 pt-1">
<CardHeader className="flex flex-col items-start px-0"></CardHeader>
<CardContent className="text-base">
<form method="post" {...form.props}>
<div className="space-y-4 pl-1">
<CardTitle className="text-md mb-0 -ml-1 px-0 text-xl">
Tell me about you
</CardTitle>
<div>
<Textarea
id="aboutUser"
placeholder="I'm Steve Jobs, co-founder of Apple. I helped create the iPhone, iPad, and Mac. I'm passionate about design, technology, and making products that change the world. I spent much of my life in California, working on innovative devices and inspiring creativity. I enjoy simplicity, calligraphy, and thinking differently..."
name={fields.aboutUser.name}
className="block min-h-[120px] w-full bg-transparent px-0 text-base"
rows={10}
/>
{fields.aboutUser.error && (
<div className="text-sm text-red-500">
{fields.aboutUser.error}
</div>
)}
</div>
<div className="flex justify-end">
<Button
type="submit"
variant="secondary"
size="xl"
className="rounded-lg px-4 py-2"
>
Continue
</Button>
</div>
</div>
</form>
</CardContent>
</Card>
</LoginPageLayout>
);
}

View File

@ -61,6 +61,7 @@ export async function requireUser(request: Request) {
createdAt: user.createdAt, createdAt: user.createdAt,
updatedAt: user.updatedAt, updatedAt: user.updatedAt,
confirmedBasicDetails: user.confirmedBasicDetails, confirmedBasicDetails: user.confirmedBasicDetails,
onboardingComplete: user.onboardingComplete,
isImpersonating: !!impersonationId, isImpersonating: !!impersonationId,
}; };
} }

View File

@ -285,6 +285,13 @@ async function handleMessageResponse(
messageTypes: messages.map((m) => m.type), messageTypes: messages.map((m) => m.type),
}); });
const responses = {
activities: [],
state: undefined,
account: undefined,
unhandled: [],
} as any;
// Group messages by type // Group messages by type
const grouped: Record<string, Message[]> = {}; const grouped: Record<string, Message[]> = {};
for (const message of messages) { for (const message of messages) {
@ -296,48 +303,54 @@ async function handleMessageResponse(
// Handle "activity" messages // Handle "activity" messages
if (grouped["activity"]) { if (grouped["activity"]) {
await handleActivityMessage( const activities = await handleActivityMessage(
grouped["activity"], grouped["activity"],
integrationAccountId as string, integrationAccountId as string,
userId, userId,
); );
responses.activities = activities;
} }
// Handle "state" messages // Handle "state" messages
if (grouped["state"]) { if (grouped["state"]) {
await handleStateMessage( const state = await handleStateMessage(
grouped["state"], grouped["state"],
integrationAccountId as string, integrationAccountId as string,
); );
responses.state = state;
} }
// Handle "identifier" messages // Handle "identifier" messages
if (grouped["identifier"]) { if (grouped["identifier"]) {
await handleIdentifierMessage(grouped["identifier"][0]); const identifier = await handleIdentifierMessage(
grouped["identifier"][0],
);
return identifier;
} }
// Handle "account" messages (these may involve Prisma writes) // Handle "account" messages (these may involve Prisma writes)
if (grouped["account"]) { if (grouped["account"]) {
await handleAccountMessage( const account = await handleAccountMessage(
grouped["account"], grouped["account"],
integrationDefinition, integrationDefinition,
workspaceId, workspaceId,
userId, userId,
integrationAccountId as string, integrationAccountId as string,
); );
responses.account = account;
} }
// Warn for unknown message types // Warn for unknown message types
for (const type of Object.keys(grouped)) { for (const type of Object.keys(grouped)) {
if (!["activity", "state", "identifier", "account"].includes(type)) { if (!["activity", "state", "identifier", "account"].includes(type)) {
for (const message of grouped[type]) { responses.unhandled.push(grouped[type]);
logger.warn("Unknown message type", {
messageType: type,
message,
});
}
} }
} }
return responses;
} catch (error) { } catch (error) {
logger.error("Failed to handle CLI message response", { logger.error("Failed to handle CLI message response", {
error: error instanceof Error ? error.message : "Unknown error", error: error instanceof Error ? error.message : "Unknown error",

View File

@ -14,6 +14,7 @@ export type AuthenticatedUser = {
createdAt: Date; createdAt: Date;
updatedAt: Date; updatedAt: Date;
confirmedBasicDetails: boolean; confirmedBasicDetails: boolean;
onboardingComplete: boolean;
authMethod: 'session' | 'pat' | 'oauth2'; authMethod: 'session' | 'pat' | 'oauth2';
oauth2?: { oauth2?: {
clientId: string; clientId: string;
@ -47,6 +48,7 @@ export async function requireAuth(request: Request): Promise<AuthenticatedUser>
createdAt: user.createdAt, createdAt: user.createdAt,
updatedAt: user.updatedAt, updatedAt: user.updatedAt,
confirmedBasicDetails: user.confirmedBasicDetails, confirmedBasicDetails: user.confirmedBasicDetails,
onboardingComplete: user.onboardingComplete,
authMethod: 'pat', authMethod: 'pat',
}; };
} }
@ -65,6 +67,7 @@ export async function requireAuth(request: Request): Promise<AuthenticatedUser>
createdAt: accessToken.user.createdAt, createdAt: accessToken.user.createdAt,
updatedAt: accessToken.user.updatedAt, updatedAt: accessToken.user.updatedAt,
confirmedBasicDetails: accessToken.user.confirmedBasicDetails, confirmedBasicDetails: accessToken.user.confirmedBasicDetails,
onboardingComplete: accessToken.user.onboardingComplete,
authMethod: 'oauth2', authMethod: 'oauth2',
oauth2: { oauth2: {
clientId: accessToken.client.clientId, clientId: accessToken.client.clientId,
@ -89,6 +92,7 @@ export async function requireAuth(request: Request): Promise<AuthenticatedUser>
createdAt: sessionUser.createdAt, createdAt: sessionUser.createdAt,
updatedAt: sessionUser.updatedAt, updatedAt: sessionUser.updatedAt,
confirmedBasicDetails: sessionUser.confirmedBasicDetails, confirmedBasicDetails: sessionUser.confirmedBasicDetails,
onboardingComplete: sessionUser.onboardingComplete,
authMethod: 'session', authMethod: 'session',
}; };
} }

View File

@ -2,6 +2,10 @@ export function confirmBasicDetailsPath() {
return `/confirm-basic-details`; return `/confirm-basic-details`;
} }
export function onboardingPath() {
return `/onboarding`;
}
export function homePath() { export function homePath() {
return `/home`; return `/home`;
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.0 MiB

View File

@ -186,7 +186,6 @@ export async function initCommand() {
const parsed = parse(file); const parsed = parse(file);
const envVarsExpand = expand({ parsed, processEnv: {} }).parsed || {}; const envVarsExpand = expand({ parsed, processEnv: {} }).parsed || {};
console.log(envVarsExpand);
await executeCommandInteractive("docker compose up -d", { await executeCommandInteractive("docker compose up -d", {
cwd: rootDir, cwd: rootDir,
message: "Starting Core services with new Trigger.dev configuration...", message: "Starting Core services with new Trigger.dev configuration...",

View File

@ -10,8 +10,6 @@ export interface CommandOptions {
export function executeCommandInteractive(command: string, options: CommandOptions): Promise<void> { export function executeCommandInteractive(command: string, options: CommandOptions): Promise<void> {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
console.log(process.env);
const s = spinner(); const s = spinner();
s.start(options.message); s.start(options.message);

View File

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "User" ADD COLUMN "onboardingComplete" BOOLEAN NOT NULL DEFAULT false;

View File

@ -482,6 +482,7 @@ model User {
marketingEmails Boolean @default(true) marketingEmails Boolean @default(true)
confirmedBasicDetails Boolean @default(false) confirmedBasicDetails Boolean @default(false)
onboardingComplete Boolean @default(false)
referralSource String? referralSource String?

View File

@ -1,6 +1,6 @@
{ {
"name": "@redplanethq/sdk", "name": "@redplanethq/sdk",
"version": "0.1.1", "version": "0.1.2",
"description": "CORE Node.JS SDK", "description": "CORE Node.JS SDK",
"main": "./dist/index.js", "main": "./dist/index.js",
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",

View File

@ -78,7 +78,7 @@ export abstract class IntegrationCLI {
const messages: Message[] = await this.handleEvent({ const messages: Message[] = await this.handleEvent({
event: IntegrationEventType.PROCESS, event: IntegrationEventType.PROCESS,
eventBody: { eventData }, eventBody: eventData,
config, config,
}); });