mirror of
https://github.com/Significant-Gravitas/AutoGPT.git
synced 2026-01-28 08:28:00 -05:00
feat(platform): disable onboarding redirects and add $5 signup bonus (#11862)
Disable automatic onboarding redirects on signup/login while keeping the
checklist/wallet functional. Users now receive $5 (500 credits) on their
first visit to /copilot.
### Changes 🏗️
- **Frontend**: `shouldShowOnboarding()` now returns `false`, disabling
auto-redirects to `/onboarding`
- **Backend**: Added `VISIT_COPILOT` onboarding step with 500 credit
($5) reward
- **Frontend**: Copilot page automatically completes `VISIT_COPILOT`
step on mount
- **Database**: Migration to add `VISIT_COPILOT` to `OnboardingStep`
enum
NOTE: /onboarding/1-welcome -> /library now as shouldShowOnboardin is
always false
Users land directly on `/copilot` after signup/login and receive $5
invisibly (not shown in checklist UI).
### Checklist 📋
#### For code changes:
- [x] I have clearly listed my changes in the PR description
- [x] I have made a test plan
- [x] I have tested my changes according to the test plan:
- [x] New user signup (email/password) → lands on `/copilot`, wallet
shows 500 credits
- [x] Verified credits are only granted once (idempotent via onboarding
reward mechanism)
- [x] Existing user login (already granted flag set) → lands on
`/copilot`, no duplicate credits
- [x] Checklist/wallet remains functional
#### For configuration changes:
- [x] `.env.default` is updated or already compatible with my changes
- [x] `docker-compose.yml` is updated or already compatible with my
changes
- [x] I have included a list of my configuration changes in the PR
description (under **Changes**)
No configuration changes required.
---
OPEN-2967
🤖 Generated with [Claude Code](https://claude.ai/code)
<!-- CURSOR_SUMMARY -->
---
> [!NOTE]
> Introduces a new onboarding step and adjusts onboarding flow.
>
> - Adds `VISIT_COPILOT` onboarding step (+500 credits) with DB enum
migration and API/type updates
> - Copilot page auto-completes `VISIT_COPILOT` on mount to grant the
welcome bonus
> - Changes `/onboarding/enabled` to require user context and return
`false` when `CHAT` feature is enabled (skips legacy onboarding)
> - Wallet now refreshes credits on any onboarding `step_completed`
notification; confetti limited to visible tasks
> - Test flows updated to accept redirects to `copilot`/`library` and
verify authenticated state
>
> <sup>Written by [Cursor
Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit
ec5a5a4dfd. This will update automatically
on new commits. Configure
[here](https://cursor.com/dashboard?tab=bugbot).</sup>
<!-- /CURSOR_SUMMARY -->
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: Nicholas Tindle <ntindle@users.noreply.github.com>
This commit is contained in:
@@ -265,9 +265,13 @@ async def get_onboarding_agents(
|
||||
"/onboarding/enabled",
|
||||
summary="Is onboarding enabled",
|
||||
tags=["onboarding", "public"],
|
||||
dependencies=[Security(requires_user)],
|
||||
)
|
||||
async def is_onboarding_enabled() -> bool:
|
||||
async def is_onboarding_enabled(
|
||||
user_id: Annotated[str, Security(get_user_id)],
|
||||
) -> bool:
|
||||
# If chat is enabled for user, skip legacy onboarding
|
||||
if await is_feature_enabled(Flag.CHAT, user_id, False):
|
||||
return False
|
||||
return await onboarding_enabled()
|
||||
|
||||
|
||||
|
||||
@@ -41,6 +41,7 @@ FrontendOnboardingStep = Literal[
|
||||
OnboardingStep.AGENT_NEW_RUN,
|
||||
OnboardingStep.AGENT_INPUT,
|
||||
OnboardingStep.CONGRATS,
|
||||
OnboardingStep.VISIT_COPILOT,
|
||||
OnboardingStep.MARKETPLACE_VISIT,
|
||||
OnboardingStep.BUILDER_OPEN,
|
||||
]
|
||||
@@ -122,6 +123,9 @@ async def update_user_onboarding(user_id: str, data: UserOnboardingUpdate):
|
||||
async def _reward_user(user_id: str, onboarding: UserOnboarding, step: OnboardingStep):
|
||||
reward = 0
|
||||
match step:
|
||||
# Welcome bonus for visiting copilot ($5 = 500 credits)
|
||||
case OnboardingStep.VISIT_COPILOT:
|
||||
reward = 500
|
||||
# Reward user when they clicked New Run during onboarding
|
||||
# This is because they need credits before scheduling a run (next step)
|
||||
# This is seen as a reward for the GET_RESULTS step in the wallet
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
-- AlterEnum
|
||||
ALTER TYPE "OnboardingStep" ADD VALUE 'VISIT_COPILOT';
|
||||
@@ -81,6 +81,7 @@ enum OnboardingStep {
|
||||
AGENT_INPUT
|
||||
CONGRATS
|
||||
// First Wins
|
||||
VISIT_COPILOT
|
||||
GET_RESULTS
|
||||
MARKETPLACE_VISIT
|
||||
MARKETPLACE_ADD_AGENT
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
import { useToast } from "@/components/molecules/Toast/use-toast";
|
||||
import { getHomepageRoute } from "@/lib/constants";
|
||||
import { useSupabase } from "@/lib/supabase/hooks/useSupabase";
|
||||
import { useOnboarding } from "@/providers/onboarding/onboarding-provider";
|
||||
import {
|
||||
Flag,
|
||||
type FlagValues,
|
||||
@@ -25,12 +26,20 @@ export function useCopilotPage() {
|
||||
const queryClient = useQueryClient();
|
||||
const { user, isLoggedIn, isUserLoading } = useSupabase();
|
||||
const { toast } = useToast();
|
||||
const { completeStep } = useOnboarding();
|
||||
|
||||
const { urlSessionId, setUrlSessionId } = useCopilotSessionId();
|
||||
const setIsStreaming = useCopilotStore((s) => s.setIsStreaming);
|
||||
const isCreating = useCopilotStore((s) => s.isCreatingSession);
|
||||
const setIsCreating = useCopilotStore((s) => s.setIsCreatingSession);
|
||||
|
||||
// Complete VISIT_COPILOT onboarding step to grant $5 welcome bonus
|
||||
useEffect(() => {
|
||||
if (isLoggedIn) {
|
||||
completeStep("VISIT_COPILOT");
|
||||
}
|
||||
}, [completeStep, isLoggedIn]);
|
||||
|
||||
const isChatEnabled = useGetFlag(Flag.CHAT);
|
||||
const flags = useFlags<FlagValues>();
|
||||
const homepageRoute = getHomepageRoute(isChatEnabled);
|
||||
|
||||
@@ -4594,6 +4594,7 @@
|
||||
"AGENT_NEW_RUN",
|
||||
"AGENT_INPUT",
|
||||
"CONGRATS",
|
||||
"VISIT_COPILOT",
|
||||
"MARKETPLACE_VISIT",
|
||||
"BUILDER_OPEN"
|
||||
],
|
||||
@@ -8754,6 +8755,7 @@
|
||||
"AGENT_NEW_RUN",
|
||||
"AGENT_INPUT",
|
||||
"CONGRATS",
|
||||
"VISIT_COPILOT",
|
||||
"GET_RESULTS",
|
||||
"MARKETPLACE_VISIT",
|
||||
"MARKETPLACE_ADD_AGENT",
|
||||
|
||||
@@ -255,13 +255,18 @@ export function Wallet() {
|
||||
(notification: WebSocketNotification) => {
|
||||
if (
|
||||
notification.type !== "onboarding" ||
|
||||
notification.event !== "step_completed" ||
|
||||
!walletRef.current
|
||||
notification.event !== "step_completed"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Only trigger confetti for tasks that are in groups
|
||||
// Always refresh credits when any onboarding step completes
|
||||
fetchCredits();
|
||||
|
||||
// Only trigger confetti for tasks that are in displayed groups
|
||||
if (!walletRef.current) {
|
||||
return;
|
||||
}
|
||||
const taskIds = groups
|
||||
.flatMap((group) => group.tasks)
|
||||
.map((task) => task.id);
|
||||
@@ -274,7 +279,6 @@ export function Wallet() {
|
||||
return;
|
||||
}
|
||||
|
||||
fetchCredits();
|
||||
party.confetti(walletRef.current, {
|
||||
count: 30,
|
||||
spread: 120,
|
||||
@@ -284,7 +288,7 @@ export function Wallet() {
|
||||
modules: [fadeOut],
|
||||
});
|
||||
},
|
||||
[fetchCredits, fadeOut],
|
||||
[fetchCredits, fadeOut, groups],
|
||||
);
|
||||
|
||||
// WebSocket setup for onboarding notifications
|
||||
|
||||
@@ -1003,6 +1003,7 @@ export type OnboardingStep =
|
||||
| "AGENT_INPUT"
|
||||
| "CONGRATS"
|
||||
// First Wins
|
||||
| "VISIT_COPILOT"
|
||||
| "GET_RESULTS"
|
||||
| "MARKETPLACE_VISIT"
|
||||
| "MARKETPLACE_ADD_AGENT"
|
||||
|
||||
@@ -37,9 +37,13 @@ export class LoginPage {
|
||||
this.page.on("load", (page) => console.log(`ℹ️ Now at URL: ${page.url()}`));
|
||||
|
||||
// Start waiting for navigation before clicking
|
||||
// Wait for redirect to marketplace, onboarding, library, or copilot (new landing pages)
|
||||
const leaveLoginPage = this.page
|
||||
.waitForURL(
|
||||
(url) => /^\/(marketplace|onboarding(\/.*)?)?$/.test(url.pathname),
|
||||
(url: URL) =>
|
||||
/^\/(marketplace|onboarding(\/.*)?|library|copilot)?$/.test(
|
||||
url.pathname,
|
||||
),
|
||||
{ timeout: 10_000 },
|
||||
)
|
||||
.catch((reason) => {
|
||||
|
||||
@@ -36,14 +36,16 @@ export async function signupTestUser(
|
||||
const signupButton = getButton("Sign up");
|
||||
await signupButton.click();
|
||||
|
||||
// Wait for successful signup - could redirect to onboarding or marketplace
|
||||
// Wait for successful signup - could redirect to various pages depending on onboarding state
|
||||
|
||||
try {
|
||||
// Wait for either onboarding or marketplace redirect
|
||||
await Promise.race([
|
||||
page.waitForURL(/\/onboarding/, { timeout: 15000 }),
|
||||
page.waitForURL(/\/marketplace/, { timeout: 15000 }),
|
||||
]);
|
||||
// Wait for redirect to onboarding, marketplace, copilot, or library
|
||||
// Use a single waitForURL with a callback to avoid Promise.race race conditions
|
||||
await page.waitForURL(
|
||||
(url: URL) =>
|
||||
/\/(onboarding|marketplace|copilot|library)/.test(url.pathname),
|
||||
{ timeout: 15000 },
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
"❌ Timeout waiting for redirect, current URL:",
|
||||
@@ -54,14 +56,19 @@ export async function signupTestUser(
|
||||
|
||||
const currentUrl = page.url();
|
||||
|
||||
// Handle onboarding or marketplace redirect
|
||||
// Handle onboarding redirect if needed
|
||||
if (currentUrl.includes("/onboarding") && ignoreOnboarding) {
|
||||
await page.goto("http://localhost:3000/marketplace");
|
||||
await page.waitForLoadState("domcontentloaded", { timeout: 10000 });
|
||||
}
|
||||
|
||||
// Verify we're on the expected final page
|
||||
if (ignoreOnboarding || currentUrl.includes("/marketplace")) {
|
||||
// Verify we're on an expected final page and user is authenticated
|
||||
if (currentUrl.includes("/copilot") || currentUrl.includes("/library")) {
|
||||
// For copilot/library landing pages, just verify user is authenticated
|
||||
await page
|
||||
.getByTestId("profile-popout-menu-trigger")
|
||||
.waitFor({ state: "visible", timeout: 10000 });
|
||||
} else if (ignoreOnboarding || currentUrl.includes("/marketplace")) {
|
||||
// Verify we're on marketplace
|
||||
await page
|
||||
.getByText(
|
||||
|
||||
Reference in New Issue
Block a user