-
Notifications
You must be signed in to change notification settings - Fork 69
feat(ui): improve onboarding UX #945
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 38 commits
3de8d8d
b0f1637
ce81cdb
2f2b437
3b72bfc
9524b2a
ea0a5b4
b09bd74
994aacd
d90837a
e43ad73
344f531
bfe0bc9
02d3452
d077842
47471f6
d6adc67
2a5ddd6
e9d5760
d569bda
3fc6bd8
c784606
a88f9b2
1dd3eda
9bdccfd
1505d20
1708e5b
907edb4
df57c07
886b1d4
3cf666c
fee4b29
f621d6b
85c624a
6363baf
f892179
98a81a3
abc7b44
13ef60f
79efb00
640e7ae
e50daec
2903a55
af656fb
96d49b7
b835855
c0754ba
5d4c9e1
2ddeff2
9a4fcb0
a3a531f
937e40e
6b7f359
b569e35
dfc274f
36b7f64
db58dfc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,14 @@ | ||
| #!/usr/bin/env bash | ||
| # Check if pnpm is available | ||
| if ! command -v pnpm >/dev/null 2>&1; then | ||
| echo "Error: pnpm not found in PATH" | ||
| echo "Please install pnpm or add it to your PATH" | ||
| exit 1 | ||
| fi | ||
|
|
||
| # Load fnm if available | ||
| if [ -f ~/.local/share/fnm/fnm ]; then | ||
| eval "$(~/.local/share/fnm/fnm env --shell bash)" | ||
| fi | ||
|
|
||
| pnpm commitlint --edit $1 | ||
thebeyondr marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,14 @@ | ||
| #!/usr/bin/env bash | ||
| # Check if pnpm is available | ||
| if ! command -v pnpm >/dev/null 2>&1; then | ||
| echo "Error: pnpm not found in PATH" | ||
| echo "Please install pnpm or add it to your PATH" | ||
| exit 1 | ||
| fi | ||
|
|
||
| # Load fnm if available | ||
| if [ -f ~/.local/share/fnm/fnm ]; then | ||
| eval "$(~/.local/share/fnm/fnm env --shell bash)" | ||
| fi | ||
thebeyondr marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
thebeyondr marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| pnpm lint-staged | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,22 +1,8 @@ | ||
| import { UserProvider } from "@/components/providers/user-provider"; | ||
| import { fetchServerData } from "@/lib/server-api"; | ||
|
|
||
| import { OnboardingClient } from "./onboarding-client"; | ||
|
|
||
| import type { User } from "@/lib/types"; | ||
|
|
||
| // Force dynamic rendering since this page uses server-side data fetching with cookies | ||
| // Force dynamic rendering since this page uses client-side data fetching | ||
| export const dynamic = "force-dynamic"; | ||
|
|
||
| export default async function OnboardingPage() { | ||
| const initialUserData = await fetchServerData<{ user: User }>( | ||
| "GET", | ||
| "/user/me", | ||
| ); | ||
|
|
||
| return ( | ||
| <UserProvider initialUserData={initialUserData}> | ||
| <OnboardingClient /> | ||
| </UserProvider> | ||
| ); | ||
| export default function OnboardingPage() { | ||
| return <OnboardingClient />; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,12 +6,16 @@ import { usePostHog } from "posthog-js/react"; | |
| import * as React from "react"; | ||
| import { useState } from "react"; | ||
|
|
||
| import { Card, CardContent } from "@/lib/components/card"; | ||
| import { | ||
| NavigationMenu, | ||
| NavigationMenuItem, | ||
| NavigationMenuList, | ||
| } from "@/lib/components/navigation-menu"; | ||
| import { Stepper } from "@/lib/components/stepper"; | ||
| import { useApi } from "@/lib/fetch-client"; | ||
| import Logo from "@/lib/icons/Logo"; | ||
| import { useStripe } from "@/lib/stripe"; | ||
|
|
||
| import { ApiKeyStep } from "./api-key-step"; | ||
| import { CreditsStep } from "./credits-step"; | ||
| import { PlanChoiceStep } from "./plan-choice-step"; | ||
| import { ProviderKeyStep } from "./provider-key-step"; | ||
|
|
@@ -27,16 +31,12 @@ const getSteps = (flowType: FlowType) => [ | |
| }, | ||
| { | ||
| id: "referral", | ||
| title: "How did you hear about us?", | ||
| title: "How did you find us?", | ||
| optional: true, | ||
| }, | ||
| { | ||
| id: "api-key", | ||
| title: "API Key", | ||
| }, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why was this removed? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Welcome step automatically makes the key or shows how many they have. Immediate reward and reduces the steps. Its something you'd have to try to see if it makes sense to you or not.
|
||
| { | ||
| id: "plan-choice", | ||
| title: "Choose Plan", | ||
| title: "Choose your approach", | ||
smakosh marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
| }, | ||
| { | ||
| id: flowType === "credits" ? "credits" : "provider-key", | ||
|
|
@@ -49,9 +49,11 @@ export function OnboardingWizard() { | |
| const [activeStep, setActiveStep] = useState(0); | ||
| const [flowType, setFlowType] = useState<FlowType>(null); | ||
| const [hasSelectedPlan, setHasSelectedPlan] = useState(false); | ||
| const [selectedPlanName, setSelectedPlanName] = useState<string>(""); | ||
| const [isPaymentSuccessful, setIsPaymentSuccessful] = useState(false); | ||
| const [referralSource, setReferralSource] = useState<string>(""); | ||
| const [referralDetails, setReferralDetails] = useState<string>(""); | ||
|
|
||
| const router = useRouter(); | ||
| const posthog = usePostHog(); | ||
| const { stripe, isLoading: stripeLoading } = useStripe(); | ||
|
|
@@ -65,8 +67,14 @@ export function OnboardingWizard() { | |
| const STEPS = getSteps(flowType); | ||
|
|
||
| const handleStepChange = async (step: number) => { | ||
| // Special handling for plan choice step (now at index 3) | ||
| if (activeStep === 3) { | ||
| // Handle backward navigation | ||
| if (step < activeStep) { | ||
| setActiveStep(step); | ||
| return; | ||
| } | ||
|
|
||
| // Special handling for plan choice step (now at index 2) | ||
| if (activeStep === 2) { | ||
| if (!hasSelectedPlan) { | ||
| // Skip to dashboard if no plan selected | ||
| posthog.capture("onboarding_skipped", { | ||
|
|
@@ -103,37 +111,47 @@ export function OnboardingWizard() { | |
| const handleSelectCredits = () => { | ||
| setFlowType("credits"); | ||
| setHasSelectedPlan(true); | ||
| setActiveStep(4); | ||
| setSelectedPlanName("Buy Credits"); | ||
| setActiveStep(3); | ||
| }; | ||
|
|
||
| const handleSelectBYOK = () => { | ||
| setFlowType("byok"); | ||
| setHasSelectedPlan(true); | ||
| setActiveStep(4); | ||
| setSelectedPlanName("Bring Your Own Keys"); | ||
| setActiveStep(3); | ||
| }; | ||
|
|
||
| const handleSelectFreePlan = () => { | ||
| setHasSelectedPlan(true); | ||
| setSelectedPlanName("Free Plan"); | ||
| // Continue to next step or complete onboarding | ||
| setActiveStep(3); | ||
| }; | ||
thebeyondr marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| const handleReferralComplete = (source: string, details?: string) => { | ||
| setReferralSource(source); | ||
| if (details) { | ||
| setReferralDetails(details); | ||
| } | ||
| setActiveStep(2); // Move to API Key step | ||
| setActiveStep(2); // Move to Plan Choice step | ||
| }; | ||
|
|
||
| // Special handling for PlanChoiceStep to pass callbacks | ||
| const renderCurrentStep = () => { | ||
| if (activeStep === 3) { | ||
| if (activeStep === 2) { | ||
| return ( | ||
| <PlanChoiceStep | ||
| onSelectCredits={handleSelectCredits} | ||
| onSelectBYOK={handleSelectBYOK} | ||
| onSelectFreePlan={handleSelectFreePlan} | ||
| hasSelectedPlan={hasSelectedPlan} | ||
| /> | ||
| ); | ||
| } | ||
|
|
||
| // For credits step, wrap with Stripe Elements | ||
| if (activeStep === 4 && flowType === "credits") { | ||
| if (activeStep === 3 && flowType === "credits") { | ||
| return stripeLoading ? ( | ||
| <div className="p-6 text-center">Loading payment form...</div> | ||
| ) : ( | ||
|
|
@@ -144,7 +162,7 @@ export function OnboardingWizard() { | |
| } | ||
|
|
||
| // For BYOK step | ||
| if (activeStep === 4 && flowType === "byok") { | ||
| if (activeStep === 3 && flowType === "byok") { | ||
| return <ProviderKeyStep />; | ||
| } | ||
|
|
||
|
|
@@ -157,24 +175,29 @@ export function OnboardingWizard() { | |
| return <ReferralStep onComplete={handleReferralComplete} />; | ||
| } | ||
|
|
||
| if (activeStep === 2) { | ||
| return <ApiKeyStep />; | ||
| } | ||
|
|
||
| return null; | ||
| }; | ||
|
|
||
| // Customize stepper steps to show appropriate button text | ||
| const getStepperSteps = () => { | ||
| return STEPS.map((step, index) => ({ | ||
| ...step, | ||
| // Make plan choice step show Skip when no selection | ||
| ...(index === 3 && | ||
| !hasSelectedPlan && { | ||
| customNextText: "Skip", | ||
| }), | ||
| // Welcome step shows dynamic text based on user state | ||
| ...(index === 0 && { | ||
| customNextText: "Next: How did you find us?", | ||
| }), | ||
| // Referral step shows dynamic text based on user state | ||
| ...(index === 1 && { | ||
| customNextText: "Next: Choose your approach", | ||
| }), | ||
| // Make plan choice step show dynamic text based on selected plan | ||
| ...(index === 2 && { | ||
| customNextText: hasSelectedPlan | ||
| ? `Continue with ${selectedPlanName}` | ||
| : "Skip", | ||
| }), | ||
| // Remove optional status from credits step when payment is successful | ||
| ...(index === 4 && | ||
| ...(index === 3 && | ||
| flowType === "credits" && | ||
| isPaymentSuccessful && { | ||
| optional: false, | ||
|
|
@@ -183,22 +206,31 @@ export function OnboardingWizard() { | |
| }; | ||
|
|
||
| return ( | ||
| <div className="container mx-auto max-w-3xl py-10"> | ||
| <Card> | ||
| <CardContent className="p-6 sm:p-8"> | ||
| <Stepper | ||
| steps={getStepperSteps()} | ||
| activeStep={activeStep} | ||
| onStepChange={handleStepChange} | ||
| className="mb-6" | ||
| nextButtonDisabled={ | ||
| activeStep === 4 && flowType === "credits" && !isPaymentSuccessful | ||
| } | ||
| > | ||
| {renderCurrentStep()} | ||
| </Stepper> | ||
| </CardContent> | ||
| </Card> | ||
| <div className="container mx-auto px-4 py-10"> | ||
| <NavigationMenu className="mx-auto"> | ||
| <NavigationMenuList> | ||
| <NavigationMenuItem asChild> | ||
| <div className="flex items-center space-x-2"> | ||
| <Logo className="h-8 w-8 rounded-full text-black dark:text-white" /> | ||
| <span className="text-xl font-bold tracking-tight text-zinc-900 dark:text-white"> | ||
| LLM Gateway | ||
| </span> | ||
| </div> | ||
| </NavigationMenuItem> | ||
| </NavigationMenuList> | ||
| </NavigationMenu> | ||
|
|
||
| <Stepper | ||
| steps={getStepperSteps()} | ||
| activeStep={activeStep} | ||
| onStepChange={handleStepChange} | ||
| className="mb-6" | ||
| nextButtonDisabled={ | ||
| activeStep === 3 && flowType === "credits" && !isPaymentSuccessful | ||
| } | ||
| > | ||
| {renderCurrentStep()} | ||
| </Stepper> | ||
| </div> | ||
| ); | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.