From f9b19c464317e9f0b2d61e1706f65b0ba96595b4 Mon Sep 17 00:00:00 2001
From: Logan Ford <110533855+Logannford@users.noreply.github.com>
Date: Sun, 27 Apr 2025 11:23:37 +0100
Subject: [PATCH 01/14] fix: email sending on onboarding & start of new flow
---
src/actions/user/account/create-coupon.ts | 3 -
.../onboarding-first-question-selection.tsx | 75 ++++---------------
src/components/shared/scrolling-animation.tsx | 24 ++++++
src/hooks/use-onboarding-steps.ts | 21 +++++-
4 files changed, 58 insertions(+), 65 deletions(-)
create mode 100644 src/components/shared/scrolling-animation.tsx
diff --git a/src/actions/user/account/create-coupon.ts b/src/actions/user/account/create-coupon.ts
index 1277fe08d..cc530c425 100644
--- a/src/actions/user/account/create-coupon.ts
+++ b/src/actions/user/account/create-coupon.ts
@@ -7,9 +7,6 @@ import { getUser } from '../authed/get-user';
* Upon user signup, we create a 60% off their first 3 three
* months of TechBlitz premium.
* Eligible for 72 hours after signup.
- *
- * We are only hitting this action from within other actions,
- * so we can pass the userUid as a parameter.
*/
export const createCouponOnSignup = async () => {
const user = await getUser();
diff --git a/src/components/app/onboarding/onboarding-first-question-selection.tsx b/src/components/app/onboarding/onboarding-first-question-selection.tsx
index 59ff25f58..a4df40956 100644
--- a/src/components/app/onboarding/onboarding-first-question-selection.tsx
+++ b/src/components/app/onboarding/onboarding-first-question-selection.tsx
@@ -1,77 +1,32 @@
import { CardHeader } from '@/components/ui/card';
-import BookBookmark from '@/components/ui/icons/book-bookmark';
-import Star3 from '@/components/ui/icons/star';
import { useOnboardingContext } from '@/contexts/onboarding-context';
import { cn } from '@/lib/utils';
import { motion } from 'framer-motion';
export default function OnboardingFirstQuestionSelection() {
- const { itemVariants, firstQuestionSelection, setFirstQuestionSelection } =
- useOnboardingContext();
+ const { itemVariants } = useOnboardingContext();
return (
-
+
- Where would you like to begin?
+ Let's begin!
-
{
- setFirstQuestionSelection('startFromScratch');
- }}
- >
-
-
- Recommended
-
-
-
-
-
-
Start from scratch
-
- We'll start you off with a basic tutorial to get you started.
-
-
-
-
-
setFirstQuestionSelection('personalizeLearning')}
- >
-
-
-
-
Personalize your learning
-
- Select topics that interest you and get a set of recommended questions to get you
- started.
-
-
-
-
+
+
+ We're so excited to have you here! Your future in tech is bright, and we're here to help
+ you get there.
+
+
+ {/** 'infinite' scrolling animation of the study path buttons. */}
+ {/* Scrolling content */}
+
);
diff --git a/src/components/shared/scrolling-animation.tsx b/src/components/shared/scrolling-animation.tsx
new file mode 100644
index 000000000..61ea28acf
--- /dev/null
+++ b/src/components/shared/scrolling-animation.tsx
@@ -0,0 +1,24 @@
+import { cn } from '@/lib/utils';
+
+export default function ScrollingAnimation({
+ children,
+ className,
+ count,
+}: {
+ children: React.ReactNode;
+ className?: string;
+ count: number;
+}) {
+ return (
+ <>
+
+
+ {children}
+
+
+ >
+ );
+}
diff --git a/src/hooks/use-onboarding-steps.ts b/src/hooks/use-onboarding-steps.ts
index 2b76ead67..53a550bc4 100644
--- a/src/hooks/use-onboarding-steps.ts
+++ b/src/hooks/use-onboarding-steps.ts
@@ -6,6 +6,7 @@ import { useOnboardingContext } from '@/contexts/onboarding-context';
import { updateUser } from '@/actions/user/authed/update-user';
import { createCouponOnSignup } from '@/actions/user/account/create-coupon';
import { sendWelcomeEmail } from '@/actions/misc/send-welcome-email';
+import { useLocalStorage } from './use-local-storage';
export const STEPS = {
USER_DETAILS: 'USER_DETAILS', // get the users info
@@ -144,8 +145,24 @@ export function useOnboardingSteps() {
// if this is false, we need to create a coupon and send the welcome email
if (!user.hasCreatedCustomSignupCoupon) {
const coupon = await createCouponOnSignup();
- // send the welcome email
- await sendWelcomeEmail(serverUser, coupon?.name ?? '');
+
+ // Check if welcome email has already been sent using localStorage
+ // Use a generic key if serverUser doesn't have email
+ const storageKey = serverUser?.email
+ ? `welcome-email-sent_${serverUser.email}`
+ : 'welcome-email-sent';
+ const { value: welcomeEmailSent, setValue: setWelcomeEmailSent } = useLocalStorage({
+ defaultValue: false,
+ key: storageKey,
+ });
+
+ // Only send welcome email if it hasn't been sent before
+ if (!welcomeEmailSent) {
+ // send the welcome email
+ await sendWelcomeEmail(serverUser, coupon?.name ?? '');
+ // Mark that welcome email has been sent for this user
+ setWelcomeEmailSent(true);
+ }
}
setCurrentStepState(stepConfig[STEPS.USER_DETAILS].next as StepKey);
From a6dcaa9fdc39f05a1f5dc566136ab2883727c28b Mon Sep 17 00:00:00 2001
From: Logan Ford <110533855+Logannford@users.noreply.github.com>
Date: Sun, 27 Apr 2025 11:29:24 +0100
Subject: [PATCH 02/14] feat: allows admin users to skip initial question on
onboarding via a constant
---
src/hooks/use-onboarding-steps.ts | 13 ++++++++++++-
1 file changed, 12 insertions(+), 1 deletion(-)
diff --git a/src/hooks/use-onboarding-steps.ts b/src/hooks/use-onboarding-steps.ts
index 53a550bc4..a360996b0 100644
--- a/src/hooks/use-onboarding-steps.ts
+++ b/src/hooks/use-onboarding-steps.ts
@@ -8,6 +8,9 @@ import { createCouponOnSignup } from '@/actions/user/account/create-coupon';
import { sendWelcomeEmail } from '@/actions/misc/send-welcome-email';
import { useLocalStorage } from './use-local-storage';
+// Toggle for admin to skip initial questions
+const ADMIN_SKIP_INITIAL_QUESTIONS = false;
+
export const STEPS = {
USER_DETAILS: 'USER_DETAILS', // get the users info
INITIAL_QUESTIONS: 'INITIAL_QUESTIONS', // give the user 3 very simple multiple choice questions to gauge skill level and give them quick wins!
@@ -165,7 +168,15 @@ export function useOnboardingSteps() {
}
}
- setCurrentStepState(stepConfig[STEPS.USER_DETAILS].next as StepKey);
+ // If user is an admin and the feature toggle is enabled, skip the INITIAL_QUESTIONS step
+ if (ADMIN_SKIP_INITIAL_QUESTIONS && serverUser?.userLevel === 'ADMIN') {
+ console.log('Admin user detected - skipping initial questions');
+ // Skip to the TIME_COMMITMENT step (which is after INITIAL_QUESTIONS)
+ setCurrentStepState(STEPS.TIME_COMMITMENT);
+ } else {
+ // Regular user follows normal flow
+ setCurrentStepState(stepConfig[STEPS.USER_DETAILS].next as StepKey);
+ }
} else if (currentStep === STEPS.INITIAL_QUESTIONS) {
// Fix the userXp field by ensuring it's a numeric value
// Make sure to use the existing user object and only update the userXp field
From 3361f56f9265b9fbc57d49b7b0686a7e013299dc Mon Sep 17 00:00:00 2001
From: Logan Ford <110533855+Logannford@users.noreply.github.com>
Date: Sun, 27 Apr 2025 11:45:42 +0100
Subject: [PATCH 03/14] fix: cleaning up code in onboarding flow
---
.../app/onboarding/onboarding-footer.tsx | 50 +++++++++----------
.../app/onboarding/onboarding-form.tsx | 14 ++++--
src/hooks/use-onboarding-steps.ts | 25 ++++++++--
3 files changed, 55 insertions(+), 34 deletions(-)
diff --git a/src/components/app/onboarding/onboarding-footer.tsx b/src/components/app/onboarding/onboarding-footer.tsx
index 31f3da481..d16501185 100644
--- a/src/components/app/onboarding/onboarding-footer.tsx
+++ b/src/components/app/onboarding/onboarding-footer.tsx
@@ -11,6 +11,7 @@ interface OnboardingFooterProps {
currentStep: keyof typeof STEPS;
isLoading: boolean;
showSkipButton: boolean;
+ showBackButton: boolean;
onSkip: () => void;
onContinue: () => void;
onBack: () => void;
@@ -20,12 +21,24 @@ export default function OnboardingFooter({
currentStep,
isLoading,
showSkipButton,
+ showBackButton,
onSkip,
onContinue,
onBack,
}: OnboardingFooterProps) {
const { user, canContinue } = useOnboardingContext();
+ // Steps where skip button should be hidden
+ const hideSkipButton = ['FIRST_QUESTION_SELECTION', 'INITIAL_QUESTIONS', 'USER_DETAILS'].includes(
+ currentStep
+ );
+
+ // Conditions to disable continue button
+ const disableContinue =
+ isLoading ||
+ !canContinue ||
+ (currentStep === STEPS.USER_DETAILS && (!user?.username || !user?.howDidYouHearAboutTechBlitz));
+
return (
- {(currentStep === STEPS.PRICING ||
- currentStep === STEPS.INITIAL_QUESTIONS ||
- currentStep === STEPS.QUESTIONS ||
- currentStep === STEPS.TIME_COMMITMENT ||
- currentStep === STEPS.TAGS ||
- currentStep === STEPS.NOTIFICATIONS ||
- currentStep === STEPS.FIRST_QUESTION_SELECTION) && (
+ {showBackButton && (
- {showSkipButton &&
- // need to answer these two
- currentStep !== STEPS.FIRST_QUESTION_SELECTION &&
- currentStep !== STEPS.INITIAL_QUESTIONS && (
-
- Skip
-
- )}
- {/** disable if no username or how did you hear about us */}
-
+ {showSkipButton && !hideSkipButton && (
+
+ Skip
+
+ )}
+
Continue
{isLoading && }
+ {process.env.NODE_ENV === 'development' && (
+ DEBUG: {currentStep}
+ )}
);
diff --git a/src/components/app/onboarding/onboarding-form.tsx b/src/components/app/onboarding/onboarding-form.tsx
index acfb621be..0ed95712a 100644
--- a/src/components/app/onboarding/onboarding-form.tsx
+++ b/src/components/app/onboarding/onboarding-form.tsx
@@ -49,13 +49,20 @@ const stepComponents = {
export default function OnboardingForm() {
const { itemVariants } = useOnboardingContext();
- const { currentStep, isLoading, handleSkip, handleContinue, handleBack, showSkipButton } =
- useOnboardingSteps();
+ const {
+ currentStep,
+ isLoading,
+ handleSkip,
+ handleContinue,
+ handleBack,
+ showSkipButton,
+ showBackButton,
+ } = useOnboardingSteps();
const StepComponent = stepComponents[currentStep];
return (
-
+
{
+ // if we're on the first step, we shouldn't go back
+ if (currentStep === STEPS.USER_DETAILS) {
+ return;
+ }
+
// if the user is on the pricing page, they need to go back to the tags page only if they did not
// start from scratch
if (currentStep === STEPS.PRICING && firstQuestionSelection !== 'startFromScratch') {
- setCurrentStepState(STEPS.TAGS);
- return;
+ return setCurrentStepState(STEPS.TAGS);
}
const previousStep = Object.entries(stepConfig).find(
- ([, config]) => config.next === currentStep
+ ([_, config]) => config.next === currentStep
)?.[0];
if (previousStep) {
setCurrentStepState(previousStep as StepKey);
@@ -237,7 +241,19 @@ export function useOnboardingSteps() {
const isStepOne = currentStep === STEPS.USER_DETAILS;
const hasUsername = (user?.username?.length ?? 0) > 0;
- return refInUrl || (!isStepOne && hasUsername) || (isStepOne && refInUrl && hasUsername);
+ // Never show skip button on the first page (USER_DETAILS) regardless of other conditions
+ if (isStepOne) {
+ return false;
+ }
+
+ // For other steps, show the skip button if there's a ref in the URL or the user has a username
+ return refInUrl || hasUsername;
+ };
+
+ // Function to determine if back button should be shown
+ const showBackButton = () => {
+ // Don't show back button on the first step
+ return currentStep !== STEPS.USER_DETAILS;
};
return {
@@ -247,6 +263,7 @@ export function useOnboardingSteps() {
handleContinue,
handleBack,
showSkipButton,
+ showBackButton,
stepConfig,
};
}
From 3a0758a64751507f26c3d618376a5f5255c13521 Mon Sep 17 00:00:00 2001
From: Logan Ford <110533855+Logannford@users.noreply.github.com>
Date: Sun, 27 Apr 2025 12:02:31 +0100
Subject: [PATCH 04/14] code cleanup
---
.env.example | 3 -
.../app/onboarding/onboarding-footer.tsx | 10 +-
.../global/blocks/call-to-action-block.tsx | 36 +-----
src/hooks/use-onboarding-steps.ts | 107 ++++++++++--------
4 files changed, 74 insertions(+), 82 deletions(-)
diff --git a/.env.example b/.env.example
index 07a74e21e..57cf22a7a 100644
--- a/.env.example
+++ b/.env.example
@@ -52,9 +52,6 @@ NEXT_PRIVATE_OPEN_AI_ORG=
# Keys used to connect to Claude (only needed for ai development)
ANTHROPIC_API_KEY=
-# already preset.
-NEXT_PUBLIC_ENV=development
-
# secret used to authenticate cron jobs, not needed for dev
CRON_SECRET=
diff --git a/src/components/app/onboarding/onboarding-footer.tsx b/src/components/app/onboarding/onboarding-footer.tsx
index d16501185..d54d8d4f2 100644
--- a/src/components/app/onboarding/onboarding-footer.tsx
+++ b/src/components/app/onboarding/onboarding-footer.tsx
@@ -53,9 +53,15 @@ export default function OnboardingFooter({
animate={{ opacity: 1, x: 0 }}
exit={{ opacity: 0, x: -20 }}
>
-
+
- Back
)}
diff --git a/src/components/marketing/global/blocks/call-to-action-block.tsx b/src/components/marketing/global/blocks/call-to-action-block.tsx
index 2d74fa271..6d526d785 100644
--- a/src/components/marketing/global/blocks/call-to-action-block.tsx
+++ b/src/components/marketing/global/blocks/call-to-action-block.tsx
@@ -38,36 +38,12 @@ export default function CallToActionBlock(opts: {
- {process.env.NEXT_PUBLIC_ENV === 'development' ? (
- <>
-
- {leftCta?.title || 'Get Started'}
-
- {rightCta && (
-
- {rightCta?.title || 'Learn More'}
-
-
- )}
- >
- ) : (
- <>
-
-
- Start for free
-
- >
- )}
+ <>
+
+
+ Start for free
+
+ >
diff --git a/src/hooks/use-onboarding-steps.ts b/src/hooks/use-onboarding-steps.ts
index 12e2ca87f..4ffba1046 100644
--- a/src/hooks/use-onboarding-steps.ts
+++ b/src/hooks/use-onboarding-steps.ts
@@ -33,6 +33,7 @@ export function useOnboardingSteps() {
user,
handleGetOnboardingQuestions,
canContinue,
+ setCanContinue,
timeSpendingPerDay,
firstQuestionSelection,
FIRST_QUESTION_TUTORIAL_SLUG,
@@ -111,7 +112,7 @@ export function useOnboardingSteps() {
setIsLoading(true);
try {
- // if the user wants to start from scratch, the next page needs to be the pricing page, not the tags page
+ // Handle first question selection routing
if (
currentStep === STEPS.FIRST_QUESTION_SELECTION &&
firstQuestionSelection === 'startFromScratch'
@@ -120,37 +121,36 @@ export function useOnboardingSteps() {
return;
}
- // if we are on the last step and the user chose to start from scratch,
- // redirect them to the first question
- if (currentStep === STEPS.PRICING && firstQuestionSelection === 'startFromScratch') {
- // remove onboarding data
- localStorage.removeItem('onboarding');
- // then redirect
- router.push(`/question/${FIRST_QUESTION_TUTORIAL_SLUG}?tutorial=true`);
- return;
- } else if (
- currentStep === STEPS.PRICING &&
- firstQuestionSelection === 'personalizeLearning'
- ) {
- await handleGetOnboardingQuestions();
- // remove the onboarding data from local storage
- localStorage.removeItem('onboarding');
- setCurrentStepState(STEPS.QUESTIONS);
- return;
+ // Handle pricing step based on user's first question selection
+ if (currentStep === STEPS.PRICING) {
+ if (firstQuestionSelection === 'startFromScratch') {
+ localStorage.removeItem('onboarding');
+ router.push(`/question/${FIRST_QUESTION_TUTORIAL_SLUG}?tutorial=true`);
+ return;
+ } else if (firstQuestionSelection === 'personalizeLearning') {
+ await handleGetOnboardingQuestions();
+ localStorage.removeItem('onboarding');
+ setCurrentStepState(STEPS.QUESTIONS);
+ return;
+ }
}
+ // Handle time commitment step
if (currentStep === STEPS.TIME_COMMITMENT) {
await updateUser({ userDetails: { ...user, timeSpendingPerDay } });
setCurrentStepState(stepConfig[STEPS.TIME_COMMITMENT].next as StepKey);
- } else if (currentStep === STEPS.USER_DETAILS) {
+ return;
+ }
+
+ // Handle user details step
+ if (currentStep === STEPS.USER_DETAILS) {
await updateUser({ userDetails: user });
- // if this is false, we need to create a coupon and send the welcome email
+ // Create coupon and send welcome email if needed
if (!user.hasCreatedCustomSignupCoupon) {
const coupon = await createCouponOnSignup();
- // Check if welcome email has already been sent using localStorage
- // Use a generic key if serverUser doesn't have email
+ // Check if welcome email has already been sent
const storageKey = serverUser?.email
? `welcome-email-sent_${serverUser.email}`
: 'welcome-email-sent';
@@ -159,27 +159,24 @@ export function useOnboardingSteps() {
key: storageKey,
});
- // Only send welcome email if it hasn't been sent before
if (!welcomeEmailSent) {
- // send the welcome email
await sendWelcomeEmail(serverUser, coupon?.name ?? '');
- // Mark that welcome email has been sent for this user
setWelcomeEmailSent(true);
}
}
- // If user is an admin and the feature toggle is enabled, skip the INITIAL_QUESTIONS step
+ // Admin users can skip initial questions if feature flag is enabled
if (ADMIN_SKIP_INITIAL_QUESTIONS && serverUser?.userLevel === 'ADMIN') {
console.log('Admin user detected - skipping initial questions');
- // Skip to the TIME_COMMITMENT step (which is after INITIAL_QUESTIONS)
setCurrentStepState(STEPS.TIME_COMMITMENT);
} else {
- // Regular user follows normal flow
setCurrentStepState(stepConfig[STEPS.USER_DETAILS].next as StepKey);
}
- } else if (currentStep === STEPS.INITIAL_QUESTIONS) {
- // Fix the userXp field by ensuring it's a numeric value
- // Make sure to use the existing user object and only update the userXp field
+ return;
+ }
+
+ // Handle initial questions step
+ if (currentStep === STEPS.INITIAL_QUESTIONS) {
const userXpValue = typeof totalXp === 'number' && !isNaN(totalXp) ? totalXp : 0;
try {
@@ -190,24 +187,23 @@ export function useOnboardingSteps() {
},
});
console.log(`Successfully updated user with XP: ${userXpValue}`);
- setCurrentStepState(stepConfig[STEPS.INITIAL_QUESTIONS].next as StepKey);
} catch (error) {
console.error('Error updating user XP:', error);
- // We'll still proceed to the next step even if the XP update fails
- setCurrentStepState(stepConfig[STEPS.INITIAL_QUESTIONS].next as StepKey);
}
- } else {
- await updateUser({ userDetails: user });
- const nextStep = stepConfig[currentStep].next;
- if (nextStep === 'DASHBOARD') {
- localStorage.removeItem('onboarding');
- router.push('/dashboard?onboarding=true');
- } else if (nextStep === STEPS.PRICING) {
- setCurrentStepState(STEPS.PRICING);
- } else {
- setCurrentStepState(nextStep as StepKey);
- }
+ setCurrentStepState(stepConfig[STEPS.INITIAL_QUESTIONS].next as StepKey);
+ return;
+ }
+
+ // Handle all other steps
+ await updateUser({ userDetails: user });
+ const nextStep = stepConfig[currentStep].next;
+
+ if (nextStep === 'DASHBOARD') {
+ localStorage.removeItem('onboarding');
+ router.push('/dashboard?onboarding=true');
+ } else {
+ setCurrentStepState(nextStep as StepKey);
}
} catch (error) {
console.error('Error during continue:', error);
@@ -228,10 +224,17 @@ export function useOnboardingSteps() {
return setCurrentStepState(STEPS.TAGS);
}
+ // Get the previous step
const previousStep = Object.entries(stepConfig).find(
([_, config]) => config.next === currentStep
)?.[0];
+
if (previousStep) {
+ // If navigating back to USER_DETAILS, ensure the continue button is enabled
+ if (previousStep === STEPS.USER_DETAILS) {
+ setCanContinue(true);
+ }
+
setCurrentStepState(previousStep as StepKey);
}
};
@@ -252,8 +255,18 @@ export function useOnboardingSteps() {
// Function to determine if back button should be shown
const showBackButton = () => {
- // Don't show back button on the first step
- return currentStep !== STEPS.USER_DETAILS;
+ // Don't show back button on the first step (USER_DETAILS)
+ if (currentStep === STEPS.USER_DETAILS) {
+ return false;
+ }
+
+ // Don't show back button on final steps
+ if (currentStep === STEPS.QUESTIONS || currentStep === STEPS.PRICING) {
+ return false;
+ }
+
+ // Show back button on intermediate steps
+ return true;
};
return {
From b3617a2e845306b2c0373683884834ba0ecf788a Mon Sep 17 00:00:00 2001
From: Logan Ford <110533855+Logannford@users.noreply.github.com>
Date: Sun, 27 Apr 2025 12:05:22 +0100
Subject: [PATCH 05/14] remove debug code
---
src/components/app/onboarding/onboarding-footer.tsx | 3 ---
1 file changed, 3 deletions(-)
diff --git a/src/components/app/onboarding/onboarding-footer.tsx b/src/components/app/onboarding/onboarding-footer.tsx
index d54d8d4f2..52e39e8b5 100644
--- a/src/components/app/onboarding/onboarding-footer.tsx
+++ b/src/components/app/onboarding/onboarding-footer.tsx
@@ -76,9 +76,6 @@ export default function OnboardingFooter({
Continue
{isLoading &&
}
- {process.env.NODE_ENV === 'development' && (
-
DEBUG: {currentStep}
- )}
);
From 7426df8ff037845a8f1d25732f1ee735b2657dee Mon Sep 17 00:00:00 2001
From: Logan Ford <110533855+Logannford@users.noreply.github.com>
Date: Tue, 29 Apr 2025 11:37:56 +0100
Subject: [PATCH 06/14] fix: redirects non authed users in onboarding &
progress with new screen
---
src/app/(no_nav)/onboarding/page.tsx | 14 +-
.../onboarding-first-question-selection.tsx | 314 +++++++++++++++++-
src/components/app/study-paths/list.tsx | 14 -
3 files changed, 321 insertions(+), 21 deletions(-)
diff --git a/src/app/(no_nav)/onboarding/page.tsx b/src/app/(no_nav)/onboarding/page.tsx
index c27a9644e..acbdca9a6 100644
--- a/src/app/(no_nav)/onboarding/page.tsx
+++ b/src/app/(no_nav)/onboarding/page.tsx
@@ -1,9 +1,12 @@
+import Link from 'next/link';
+
+import { redirect } from 'next/navigation';
+
import Logo from '@/components/ui/logo';
import StarsBackground from '@/components/ui/stars-background';
+import OnboardingForm from '@/components/app/onboarding/onboarding-form';
-import Link from 'next/link';
import { UserOnboardingContextProvider } from '@/contexts/onboarding-context';
-import OnboardingForm from '@/components/app/onboarding/onboarding-form';
import { useUserServer } from '@/hooks/use-user-server';
export const metadata = {
@@ -11,7 +14,12 @@ export const metadata = {
};
export default async function OnboardingPage() {
- const [user] = await Promise.all([useUserServer()]);
+ const user = await useUserServer();
+
+ // throw the user back to login
+ if (!user) {
+ return redirect('/login');
+ }
return (
diff --git a/src/components/app/onboarding/onboarding-first-question-selection.tsx b/src/components/app/onboarding/onboarding-first-question-selection.tsx
index a4df40956..93a3ffc75 100644
--- a/src/components/app/onboarding/onboarding-first-question-selection.tsx
+++ b/src/components/app/onboarding/onboarding-first-question-selection.tsx
@@ -2,6 +2,284 @@ import { CardHeader } from '@/components/ui/card';
import { useOnboardingContext } from '@/contexts/onboarding-context';
import { cn } from '@/lib/utils';
import { motion } from 'framer-motion';
+import StudyPathsList from '../study-paths/list';
+import type { Question } from '@/types';
+import { StudyPath } from '@/utils/constants/study-paths';
+
+// Mock questions data similar to the storybook examples
+const questions: Partial
[] = [
+ {
+ uid: '1',
+ title: 'JavaScript Variables',
+ question: 'What is the correct way to declare a variable in JavaScript?',
+ description: 'Understanding variable declarations in JavaScript',
+ answers: [
+ {
+ uid: 'a1',
+ questionUid: '1',
+ answer: 'var x = 5;',
+ answerType: 'STANDARD',
+ isCodeSnippet: false,
+ },
+ {
+ uid: 'a2',
+ questionUid: '1',
+ answer: 'variable x = 5;',
+ answerType: 'STANDARD',
+ isCodeSnippet: false,
+ },
+ ],
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ questionDate: '2023-01-01',
+ correctAnswer: 'a1',
+ slug: 'javascript-variables',
+ questionType: 'MULTIPLE_CHOICE',
+ difficulty: 'BEGINNER',
+ userAnswers: [
+ {
+ uid: 'ua1',
+ userUid: 'user123',
+ questionUid: '1',
+ userAnswerUid: 'a1',
+ correctAnswer: true,
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ questionDate: '2023-01-01',
+ timeTaken: 15,
+ difficulty: 'EASY',
+ },
+ ],
+ },
+ {
+ uid: '2',
+ title: 'JavaScript Arrays',
+ question: 'Which method is used to add an element to the end of an array?',
+ description: 'Understanding array manipulation in JavaScript',
+ answers: [
+ {
+ uid: 'b1',
+ questionUid: '2',
+ answer: 'push()',
+ answerType: 'STANDARD',
+ isCodeSnippet: false,
+ },
+ {
+ uid: 'b2',
+ questionUid: '2',
+ answer: 'append()',
+ answerType: 'STANDARD',
+ isCodeSnippet: false,
+ },
+ ],
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ questionDate: '2023-01-02',
+ correctAnswer: 'b1',
+ slug: 'javascript-arrays',
+ questionType: 'MULTIPLE_CHOICE',
+ difficulty: 'EASY',
+ userAnswers: [
+ {
+ uid: 'ua2',
+ userUid: 'user123',
+ questionUid: '2',
+ userAnswerUid: 'b1',
+ correctAnswer: true,
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ questionDate: '2023-01-02',
+ timeTaken: 20,
+ difficulty: 'EASY',
+ },
+ ],
+ },
+ {
+ uid: '3',
+ title: 'JavaScript Functions',
+ question: 'What defines an arrow function in JavaScript?',
+ description: 'Understanding arrow functions in JavaScript',
+ answers: [
+ {
+ uid: 'c1',
+ questionUid: '3',
+ answer: '() => {}',
+ answerType: 'STANDARD',
+ isCodeSnippet: false,
+ },
+ {
+ uid: 'c2',
+ questionUid: '3',
+ answer: 'function() {}',
+ answerType: 'STANDARD',
+ isCodeSnippet: false,
+ },
+ ],
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ questionDate: '2023-01-03',
+ correctAnswer: 'c1',
+ slug: 'javascript-functions',
+ questionType: 'MULTIPLE_CHOICE',
+ difficulty: 'MEDIUM',
+ userAnswers: [
+ {
+ uid: 'ua3',
+ userUid: 'user123',
+ questionUid: '3',
+ userAnswerUid: 'c2',
+ correctAnswer: false,
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ questionDate: '2023-01-03',
+ timeTaken: 30,
+ difficulty: 'MEDIUM',
+ },
+ ],
+ },
+ {
+ uid: '4',
+ title: 'JavaScript Variables',
+ question: 'What is the correct way to declare a variable in JavaScript?',
+ description: 'Understanding variable declarations in JavaScript',
+ answers: [
+ {
+ uid: 'a1',
+ questionUid: '1',
+ answer: 'var x = 5;',
+ answerType: 'STANDARD',
+ isCodeSnippet: false,
+ },
+ {
+ uid: 'a2',
+ questionUid: '1',
+ answer: 'variable x = 5;',
+ answerType: 'STANDARD',
+ isCodeSnippet: false,
+ },
+ ],
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ questionDate: '2023-01-01',
+ correctAnswer: 'a1',
+ slug: 'javascript-variables',
+ questionType: 'MULTIPLE_CHOICE',
+ difficulty: 'BEGINNER',
+ userAnswers: [
+ {
+ uid: 'ua1',
+ userUid: 'user123',
+ questionUid: '1',
+ userAnswerUid: 'a1',
+ correctAnswer: true,
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ questionDate: '2023-01-01',
+ timeTaken: 15,
+ difficulty: 'EASY',
+ },
+ ],
+ },
+ {
+ uid: '5',
+ title: 'JavaScript Arrays',
+ question: 'Which method is used to add an element to the end of an array?',
+ description: 'Understanding array manipulation in JavaScript',
+ answers: [
+ {
+ uid: 'b1',
+ questionUid: '2',
+ answer: 'push()',
+ answerType: 'STANDARD',
+ isCodeSnippet: false,
+ },
+ {
+ uid: 'b2',
+ questionUid: '2',
+ answer: 'append()',
+ answerType: 'STANDARD',
+ isCodeSnippet: false,
+ },
+ ],
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ questionDate: '2023-01-02',
+ correctAnswer: 'b1',
+ slug: 'javascript-arrays',
+ questionType: 'MULTIPLE_CHOICE',
+ difficulty: 'EASY',
+ userAnswers: [
+ {
+ uid: 'ua2',
+ userUid: 'user123',
+ questionUid: '2',
+ userAnswerUid: 'b1',
+ correctAnswer: true,
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ questionDate: '2023-01-02',
+ timeTaken: 20,
+ difficulty: 'EASY',
+ },
+ ],
+ },
+ {
+ uid: '6',
+ title: 'JavaScript Functions',
+ question: 'What defines an arrow function in JavaScript?',
+ description: 'Understanding arrow functions in JavaScript',
+ answers: [
+ {
+ uid: 'c1',
+ questionUid: '3',
+ answer: '() => {}',
+ answerType: 'STANDARD',
+ isCodeSnippet: false,
+ },
+ {
+ uid: 'c2',
+ questionUid: '3',
+ answer: 'function() {}',
+ answerType: 'STANDARD',
+ isCodeSnippet: false,
+ },
+ ],
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ questionDate: '2023-01-03',
+ correctAnswer: 'c1',
+ slug: 'javascript-functions',
+ questionType: 'MULTIPLE_CHOICE',
+ difficulty: 'MEDIUM',
+ userAnswers: [
+ {
+ uid: 'ua3',
+ userUid: 'user123',
+ questionUid: '3',
+ userAnswerUid: 'c2',
+ correctAnswer: false,
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ questionDate: '2023-01-03',
+ timeTaken: 30,
+ difficulty: 'MEDIUM',
+ },
+ ],
+ },
+];
+
+// Triple the questions to ensure smooth infinite scroll
+const allQuestions = [...questions, ...questions, ...questions];
+
+// Mock study path
+const studyPath: Partial = {
+ slug: 'javascript-fundamentals',
+ title: 'JavaScript Fundamentals',
+ description: 'Learn the fundamentals of JavaScript programming',
+ heroChip: 'Master JavaScript from scratch',
+ questionSlugs: ['javascript-variables', 'javascript-arrays', 'javascript-functions'],
+ educationLevel: 'beginner',
+};
export default function OnboardingFirstQuestionSelection() {
const { itemVariants } = useOnboardingContext();
@@ -23,10 +301,38 @@ export default function OnboardingFirstQuestionSelection() {
{/** 'infinite' scrolling animation of the study path buttons. */}
{/* Scrolling content */}
-
+
+ {/** top fade effect */}
+
+
+
+ {/* First set of items */}
+
+
+ {/* Clone of the first set to create seamless loop */}
+
+
+
+
+
+ {/* Bottom fade effect */}
+
+
);
diff --git a/src/components/app/study-paths/list.tsx b/src/components/app/study-paths/list.tsx
index 4e7c515b6..ba06928bf 100644
--- a/src/components/app/study-paths/list.tsx
+++ b/src/components/app/study-paths/list.tsx
@@ -165,13 +165,6 @@ export function StudyPathsSubSectionList({
return getOffset(index, offsetType, offsetMultiplier);
};
- console.log('StudyPathsSubSectionList props:', {
- studyPathSlug: studyPath.slug,
- sectionNextQuestionIndex: nextQuestionIndex,
- subSectionsCount: subSections.length,
- subSectionIndices: subSections.map((sub) => sub.nextQuestionIndex),
- });
-
return (
{subSections.map((subSection, index) => {
@@ -183,13 +176,6 @@ export function StudyPathsSubSectionList({
? subSection.nextQuestionIndex
: nextQuestionIndex;
- console.log(`SubSection [${index}] ${subSection.sectionName}:`, {
- hasNextIndex: subSection.nextQuestionIndex !== undefined,
- subSectionNextIdx: subSection.nextQuestionIndex,
- fallbackNextIdx: nextQuestionIndex,
- finalNextIdx: subsectionNextQuestionIndex,
- });
-
return (
Date: Wed, 30 Apr 2025 00:25:20 +0100
Subject: [PATCH 07/14] chore: page cleanup and componentization
---
.../javascript-array-cheat-sheet/page.tsx | 4 -
.../regular-expression-cheat-sheet/page.tsx | 4 -
.../javascript-coding-test/page.tsx | 4 -
.../page.tsx | 4 -
.../how-does-javascript-work/page.tsx | 4 -
.../page.tsx | 4 -
.../how-to-write-javascript/page.tsx | 4 -
.../html-conditional-statement/page.tsx | 4 -
.../javascript-conditionals/page.tsx | 4 -
.../page.tsx | 4 -
.../javascript-naming-conventions/page.tsx | 4 -
.../javascript-nested-conditionals/page.tsx | 4 -
.../page.tsx | 4 -
.../primitive-types-in-javascript/page.tsx | 4 -
.../page.tsx | 4 -
.../(landing-pages)/coding-roadmap/page.tsx | 4 -
.../javascript-coding-questions/page.tsx | 4 -
.../javascript-for-beginners/page.tsx | 4 -
.../javascript-roadmap/page.tsx | 4 -
.../(landing-pages)/react-roadmap/page.tsx | 4 -
src/app/(marketing)/blog/[slug]/page.tsx | 4 -
.../(marketing)/features/leaderboard/page.tsx | 4 -
src/app/(marketing)/features/roadmap/page.tsx | 4 -
.../(marketing)/features/statistics/page.tsx | 8 +-
src/app/(no_nav)/onboarding/page.tsx | 4 +-
.../onboarding-first-question-selection.tsx | 313 +----------------
.../app/onboarding/onboarding-form.tsx | 18 +-
.../global/blocks/call-to-action-block.tsx | 16 +-
.../global/blocks/three-block-showcase.tsx | 12 +-
src/components/mdx/blog-post-layout.tsx | 4 -
.../infinite-scrolling-roadmap-cards.tsx | 320 ++++++++++++++++++
src/hooks/use-onboarding-steps.ts | 69 +---
32 files changed, 345 insertions(+), 511 deletions(-)
create mode 100644 src/components/shared/infinite-scrolling-roadmap-cards.tsx
diff --git a/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-cheat-sheet/javascript-array-cheat-sheet/page.tsx b/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-cheat-sheet/javascript-array-cheat-sheet/page.tsx
index 4eb963827..8b4156b32 100644
--- a/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-cheat-sheet/javascript-array-cheat-sheet/page.tsx
+++ b/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-cheat-sheet/javascript-array-cheat-sheet/page.tsx
@@ -121,10 +121,6 @@ export default async function JavascriptArrayCheatSheet({ params }: BlogPostPara
diff --git a/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-cheat-sheet/regular-expression-cheat-sheet/page.tsx b/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-cheat-sheet/regular-expression-cheat-sheet/page.tsx
index 0686610d4..7a509e14d 100644
--- a/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-cheat-sheet/regular-expression-cheat-sheet/page.tsx
+++ b/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-cheat-sheet/regular-expression-cheat-sheet/page.tsx
@@ -121,10 +121,6 @@ export default async function JavascriptRegularExpressionCheatSheet({ params }:
diff --git a/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-coding-interview-questions-and-answers/javascript-coding-test/page.tsx b/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-coding-interview-questions-and-answers/javascript-coding-test/page.tsx
index f100d0268..167ff1c8f 100644
--- a/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-coding-interview-questions-and-answers/javascript-coding-test/page.tsx
+++ b/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-coding-interview-questions-and-answers/javascript-coding-test/page.tsx
@@ -121,10 +121,6 @@ export default async function JavascriptCodingTest({ params }: BlogPostParams) {
diff --git a/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-coding-interview-questions-and-answers/javascript-interview-questions-for-senior-developers/page.tsx b/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-coding-interview-questions-and-answers/javascript-interview-questions-for-senior-developers/page.tsx
index de8171e1e..7e94146b1 100644
--- a/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-coding-interview-questions-and-answers/javascript-interview-questions-for-senior-developers/page.tsx
+++ b/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-coding-interview-questions-and-answers/javascript-interview-questions-for-senior-developers/page.tsx
@@ -127,10 +127,6 @@ export default async function JavascriptInterviewQuestionsForSeniorDevelopers({
diff --git a/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-fundamentals/how-does-javascript-work/page.tsx b/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-fundamentals/how-does-javascript-work/page.tsx
index 8e42bb1bc..bafe23a35 100644
--- a/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-fundamentals/how-does-javascript-work/page.tsx
+++ b/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-fundamentals/how-does-javascript-work/page.tsx
@@ -121,10 +121,6 @@ export default async function HowDoesJavascriptWork({ params }: BlogPostParams)
diff --git a/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-fundamentals/how-to-write-a-function-in-javascript/page.tsx b/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-fundamentals/how-to-write-a-function-in-javascript/page.tsx
index ba044ec4c..026947de3 100644
--- a/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-fundamentals/how-to-write-a-function-in-javascript/page.tsx
+++ b/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-fundamentals/how-to-write-a-function-in-javascript/page.tsx
@@ -121,10 +121,6 @@ export default async function HowToWriteAFunctionInJavascript({ params }: BlogPo
diff --git a/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-fundamentals/how-to-write-javascript/page.tsx b/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-fundamentals/how-to-write-javascript/page.tsx
index 5bca3df1d..b0c646afa 100644
--- a/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-fundamentals/how-to-write-javascript/page.tsx
+++ b/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-fundamentals/how-to-write-javascript/page.tsx
@@ -121,10 +121,6 @@ export default async function HowToWriteJavascript({ params }: BlogPostParams) {
diff --git a/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-fundamentals/html-conditional-statement/page.tsx b/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-fundamentals/html-conditional-statement/page.tsx
index 868ba7e63..b1f20a5d1 100644
--- a/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-fundamentals/html-conditional-statement/page.tsx
+++ b/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-fundamentals/html-conditional-statement/page.tsx
@@ -121,10 +121,6 @@ export default async function HtmlConditionalStatement({ params }: BlogPostParam
diff --git a/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-fundamentals/javascript-conditionals/page.tsx b/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-fundamentals/javascript-conditionals/page.tsx
index dc667d8cf..ec3edc1da 100644
--- a/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-fundamentals/javascript-conditionals/page.tsx
+++ b/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-fundamentals/javascript-conditionals/page.tsx
@@ -121,10 +121,6 @@ export default async function JavaScriptConditionals({ params }: BlogPostParams)
diff --git a/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-fundamentals/javascript-format-strings-with-variables/page.tsx b/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-fundamentals/javascript-format-strings-with-variables/page.tsx
index be04a2343..7dcd73586 100644
--- a/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-fundamentals/javascript-format-strings-with-variables/page.tsx
+++ b/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-fundamentals/javascript-format-strings-with-variables/page.tsx
@@ -121,10 +121,6 @@ export default async function JavaScriptFormatStringsWithVariables({ params }: B
diff --git a/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-fundamentals/javascript-naming-conventions/page.tsx b/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-fundamentals/javascript-naming-conventions/page.tsx
index 558bb29d7..74b7b020f 100644
--- a/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-fundamentals/javascript-naming-conventions/page.tsx
+++ b/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-fundamentals/javascript-naming-conventions/page.tsx
@@ -121,10 +121,6 @@ export default async function JavaScriptNamingConventions({ params }: BlogPostPa
diff --git a/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-fundamentals/javascript-nested-conditionals/page.tsx b/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-fundamentals/javascript-nested-conditionals/page.tsx
index 14e72826a..521e9b53c 100644
--- a/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-fundamentals/javascript-nested-conditionals/page.tsx
+++ b/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-fundamentals/javascript-nested-conditionals/page.tsx
@@ -121,10 +121,6 @@ export default async function JavaScriptNestedConditionals({ params }: BlogPostP
diff --git a/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-fundamentals/loose-vs-strict-equality-in-javascript/page.tsx b/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-fundamentals/loose-vs-strict-equality-in-javascript/page.tsx
index c1645eac0..b0071e61f 100644
--- a/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-fundamentals/loose-vs-strict-equality-in-javascript/page.tsx
+++ b/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-fundamentals/loose-vs-strict-equality-in-javascript/page.tsx
@@ -121,10 +121,6 @@ export default async function LooseVsStrictEqualityInJavascript({ params }: Blog
diff --git a/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-fundamentals/primitive-types-in-javascript/page.tsx b/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-fundamentals/primitive-types-in-javascript/page.tsx
index 6d5b31902..94725cddf 100644
--- a/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-fundamentals/primitive-types-in-javascript/page.tsx
+++ b/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-fundamentals/primitive-types-in-javascript/page.tsx
@@ -121,10 +121,6 @@ export default async function PrimitiveTypesInJavascript({ params }: BlogPostPar
diff --git a/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-projects-for-beginners/programming-challenges-for-beginners/page.tsx b/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-projects-for-beginners/programming-challenges-for-beginners/page.tsx
index 2a3ac2560..0ecd8f245 100644
--- a/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-projects-for-beginners/programming-challenges-for-beginners/page.tsx
+++ b/src/app/(marketing)/(landing-pages)/(pillar-pages)/javascript-projects-for-beginners/programming-challenges-for-beginners/page.tsx
@@ -123,10 +123,6 @@ export default async function JavascriptProgrammingChallengesForBeginners({
diff --git a/src/app/(marketing)/(landing-pages)/coding-roadmap/page.tsx b/src/app/(marketing)/(landing-pages)/coding-roadmap/page.tsx
index 6d464414c..81f963182 100644
--- a/src/app/(marketing)/(landing-pages)/coding-roadmap/page.tsx
+++ b/src/app/(marketing)/(landing-pages)/coding-roadmap/page.tsx
@@ -274,10 +274,6 @@ export default function CodingRoadmapPage() {
>
diff --git a/src/app/(marketing)/(landing-pages)/javascript-coding-questions/page.tsx b/src/app/(marketing)/(landing-pages)/javascript-coding-questions/page.tsx
index 2892f6cde..e5ccfc75a 100644
--- a/src/app/(marketing)/(landing-pages)/javascript-coding-questions/page.tsx
+++ b/src/app/(marketing)/(landing-pages)/javascript-coding-questions/page.tsx
@@ -272,10 +272,6 @@ export default function JavascriptCodingQuestionsPage() {
>
diff --git a/src/app/(marketing)/(landing-pages)/javascript-for-beginners/page.tsx b/src/app/(marketing)/(landing-pages)/javascript-for-beginners/page.tsx
index c95fde14b..4da3ca2b5 100644
--- a/src/app/(marketing)/(landing-pages)/javascript-for-beginners/page.tsx
+++ b/src/app/(marketing)/(landing-pages)/javascript-for-beginners/page.tsx
@@ -255,10 +255,6 @@ export default function JavascriptForBeginnersPage() {
>
diff --git a/src/app/(marketing)/(landing-pages)/javascript-roadmap/page.tsx b/src/app/(marketing)/(landing-pages)/javascript-roadmap/page.tsx
index d53a8534a..313f0b01e 100644
--- a/src/app/(marketing)/(landing-pages)/javascript-roadmap/page.tsx
+++ b/src/app/(marketing)/(landing-pages)/javascript-roadmap/page.tsx
@@ -274,10 +274,6 @@ export default function JavascriptRoadmapPage() {
>
diff --git a/src/app/(marketing)/(landing-pages)/react-roadmap/page.tsx b/src/app/(marketing)/(landing-pages)/react-roadmap/page.tsx
index afd4376f7..81936bf48 100644
--- a/src/app/(marketing)/(landing-pages)/react-roadmap/page.tsx
+++ b/src/app/(marketing)/(landing-pages)/react-roadmap/page.tsx
@@ -285,10 +285,6 @@ export default function ReactRoadmapPage() {
>
diff --git a/src/app/(marketing)/blog/[slug]/page.tsx b/src/app/(marketing)/blog/[slug]/page.tsx
index c1c885d17..00cc2eba8 100644
--- a/src/app/(marketing)/blog/[slug]/page.tsx
+++ b/src/app/(marketing)/blog/[slug]/page.tsx
@@ -143,10 +143,6 @@ export default async function BlogPost({ params }: BlogPostParams) {
diff --git a/src/app/(marketing)/features/leaderboard/page.tsx b/src/app/(marketing)/features/leaderboard/page.tsx
index 57c716819..6f6c7234b 100644
--- a/src/app/(marketing)/features/leaderboard/page.tsx
+++ b/src/app/(marketing)/features/leaderboard/page.tsx
@@ -252,10 +252,6 @@ export default function LeaderboardPage() {
>
diff --git a/src/app/(marketing)/features/roadmap/page.tsx b/src/app/(marketing)/features/roadmap/page.tsx
index bf038a006..dc76d3fb2 100644
--- a/src/app/(marketing)/features/roadmap/page.tsx
+++ b/src/app/(marketing)/features/roadmap/page.tsx
@@ -246,10 +246,6 @@ export default function FeatureDailyQuestionPage() {
>
diff --git a/src/app/(marketing)/features/statistics/page.tsx b/src/app/(marketing)/features/statistics/page.tsx
index 3af799d5f..573922d83 100644
--- a/src/app/(marketing)/features/statistics/page.tsx
+++ b/src/app/(marketing)/features/statistics/page.tsx
@@ -139,13 +139,7 @@ export default function StatisticsPage() {
title="Tracking coding progress made simple."
items={featureShowcaseItems}
/>
-
+
>
);
diff --git a/src/app/(no_nav)/onboarding/page.tsx b/src/app/(no_nav)/onboarding/page.tsx
index acbdca9a6..ee34ff9a2 100644
--- a/src/app/(no_nav)/onboarding/page.tsx
+++ b/src/app/(no_nav)/onboarding/page.tsx
@@ -29,9 +29,7 @@ export default async function OnboardingPage() {
-
-
-
+
diff --git a/src/components/app/onboarding/onboarding-first-question-selection.tsx b/src/components/app/onboarding/onboarding-first-question-selection.tsx
index 93a3ffc75..674508478 100644
--- a/src/components/app/onboarding/onboarding-first-question-selection.tsx
+++ b/src/components/app/onboarding/onboarding-first-question-selection.tsx
@@ -1,285 +1,9 @@
import { CardHeader } from '@/components/ui/card';
import { useOnboardingContext } from '@/contexts/onboarding-context';
-import { cn } from '@/lib/utils';
import { motion } from 'framer-motion';
-import StudyPathsList from '../study-paths/list';
import type { Question } from '@/types';
import { StudyPath } from '@/utils/constants/study-paths';
-
-// Mock questions data similar to the storybook examples
-const questions: Partial[] = [
- {
- uid: '1',
- title: 'JavaScript Variables',
- question: 'What is the correct way to declare a variable in JavaScript?',
- description: 'Understanding variable declarations in JavaScript',
- answers: [
- {
- uid: 'a1',
- questionUid: '1',
- answer: 'var x = 5;',
- answerType: 'STANDARD',
- isCodeSnippet: false,
- },
- {
- uid: 'a2',
- questionUid: '1',
- answer: 'variable x = 5;',
- answerType: 'STANDARD',
- isCodeSnippet: false,
- },
- ],
- createdAt: new Date(),
- updatedAt: new Date(),
- questionDate: '2023-01-01',
- correctAnswer: 'a1',
- slug: 'javascript-variables',
- questionType: 'MULTIPLE_CHOICE',
- difficulty: 'BEGINNER',
- userAnswers: [
- {
- uid: 'ua1',
- userUid: 'user123',
- questionUid: '1',
- userAnswerUid: 'a1',
- correctAnswer: true,
- createdAt: new Date(),
- updatedAt: new Date(),
- questionDate: '2023-01-01',
- timeTaken: 15,
- difficulty: 'EASY',
- },
- ],
- },
- {
- uid: '2',
- title: 'JavaScript Arrays',
- question: 'Which method is used to add an element to the end of an array?',
- description: 'Understanding array manipulation in JavaScript',
- answers: [
- {
- uid: 'b1',
- questionUid: '2',
- answer: 'push()',
- answerType: 'STANDARD',
- isCodeSnippet: false,
- },
- {
- uid: 'b2',
- questionUid: '2',
- answer: 'append()',
- answerType: 'STANDARD',
- isCodeSnippet: false,
- },
- ],
- createdAt: new Date(),
- updatedAt: new Date(),
- questionDate: '2023-01-02',
- correctAnswer: 'b1',
- slug: 'javascript-arrays',
- questionType: 'MULTIPLE_CHOICE',
- difficulty: 'EASY',
- userAnswers: [
- {
- uid: 'ua2',
- userUid: 'user123',
- questionUid: '2',
- userAnswerUid: 'b1',
- correctAnswer: true,
- createdAt: new Date(),
- updatedAt: new Date(),
- questionDate: '2023-01-02',
- timeTaken: 20,
- difficulty: 'EASY',
- },
- ],
- },
- {
- uid: '3',
- title: 'JavaScript Functions',
- question: 'What defines an arrow function in JavaScript?',
- description: 'Understanding arrow functions in JavaScript',
- answers: [
- {
- uid: 'c1',
- questionUid: '3',
- answer: '() => {}',
- answerType: 'STANDARD',
- isCodeSnippet: false,
- },
- {
- uid: 'c2',
- questionUid: '3',
- answer: 'function() {}',
- answerType: 'STANDARD',
- isCodeSnippet: false,
- },
- ],
- createdAt: new Date(),
- updatedAt: new Date(),
- questionDate: '2023-01-03',
- correctAnswer: 'c1',
- slug: 'javascript-functions',
- questionType: 'MULTIPLE_CHOICE',
- difficulty: 'MEDIUM',
- userAnswers: [
- {
- uid: 'ua3',
- userUid: 'user123',
- questionUid: '3',
- userAnswerUid: 'c2',
- correctAnswer: false,
- createdAt: new Date(),
- updatedAt: new Date(),
- questionDate: '2023-01-03',
- timeTaken: 30,
- difficulty: 'MEDIUM',
- },
- ],
- },
- {
- uid: '4',
- title: 'JavaScript Variables',
- question: 'What is the correct way to declare a variable in JavaScript?',
- description: 'Understanding variable declarations in JavaScript',
- answers: [
- {
- uid: 'a1',
- questionUid: '1',
- answer: 'var x = 5;',
- answerType: 'STANDARD',
- isCodeSnippet: false,
- },
- {
- uid: 'a2',
- questionUid: '1',
- answer: 'variable x = 5;',
- answerType: 'STANDARD',
- isCodeSnippet: false,
- },
- ],
- createdAt: new Date(),
- updatedAt: new Date(),
- questionDate: '2023-01-01',
- correctAnswer: 'a1',
- slug: 'javascript-variables',
- questionType: 'MULTIPLE_CHOICE',
- difficulty: 'BEGINNER',
- userAnswers: [
- {
- uid: 'ua1',
- userUid: 'user123',
- questionUid: '1',
- userAnswerUid: 'a1',
- correctAnswer: true,
- createdAt: new Date(),
- updatedAt: new Date(),
- questionDate: '2023-01-01',
- timeTaken: 15,
- difficulty: 'EASY',
- },
- ],
- },
- {
- uid: '5',
- title: 'JavaScript Arrays',
- question: 'Which method is used to add an element to the end of an array?',
- description: 'Understanding array manipulation in JavaScript',
- answers: [
- {
- uid: 'b1',
- questionUid: '2',
- answer: 'push()',
- answerType: 'STANDARD',
- isCodeSnippet: false,
- },
- {
- uid: 'b2',
- questionUid: '2',
- answer: 'append()',
- answerType: 'STANDARD',
- isCodeSnippet: false,
- },
- ],
- createdAt: new Date(),
- updatedAt: new Date(),
- questionDate: '2023-01-02',
- correctAnswer: 'b1',
- slug: 'javascript-arrays',
- questionType: 'MULTIPLE_CHOICE',
- difficulty: 'EASY',
- userAnswers: [
- {
- uid: 'ua2',
- userUid: 'user123',
- questionUid: '2',
- userAnswerUid: 'b1',
- correctAnswer: true,
- createdAt: new Date(),
- updatedAt: new Date(),
- questionDate: '2023-01-02',
- timeTaken: 20,
- difficulty: 'EASY',
- },
- ],
- },
- {
- uid: '6',
- title: 'JavaScript Functions',
- question: 'What defines an arrow function in JavaScript?',
- description: 'Understanding arrow functions in JavaScript',
- answers: [
- {
- uid: 'c1',
- questionUid: '3',
- answer: '() => {}',
- answerType: 'STANDARD',
- isCodeSnippet: false,
- },
- {
- uid: 'c2',
- questionUid: '3',
- answer: 'function() {}',
- answerType: 'STANDARD',
- isCodeSnippet: false,
- },
- ],
- createdAt: new Date(),
- updatedAt: new Date(),
- questionDate: '2023-01-03',
- correctAnswer: 'c1',
- slug: 'javascript-functions',
- questionType: 'MULTIPLE_CHOICE',
- difficulty: 'MEDIUM',
- userAnswers: [
- {
- uid: 'ua3',
- userUid: 'user123',
- questionUid: '3',
- userAnswerUid: 'c2',
- correctAnswer: false,
- createdAt: new Date(),
- updatedAt: new Date(),
- questionDate: '2023-01-03',
- timeTaken: 30,
- difficulty: 'MEDIUM',
- },
- ],
- },
-];
-
-// Triple the questions to ensure smooth infinite scroll
-const allQuestions = [...questions, ...questions, ...questions];
-
-// Mock study path
-const studyPath: Partial = {
- slug: 'javascript-fundamentals',
- title: 'JavaScript Fundamentals',
- description: 'Learn the fundamentals of JavaScript programming',
- heroChip: 'Master JavaScript from scratch',
- questionSlugs: ['javascript-variables', 'javascript-arrays', 'javascript-functions'],
- educationLevel: 'beginner',
-};
+import InfiniteScrollingRoadmapCards from '@/components/shared/infinite-scrolling-roadmap-cards';
export default function OnboardingFirstQuestionSelection() {
const { itemVariants } = useOnboardingContext();
@@ -299,40 +23,7 @@ export default function OnboardingFirstQuestionSelection() {
you get there.
- {/** 'infinite' scrolling animation of the study path buttons. */}
- {/* Scrolling content */}
-
- {/** top fade effect */}
-
-
-
- {/* First set of items */}
-
-
- {/* Clone of the first set to create seamless loop */}
-
-
-
-
-
- {/* Bottom fade effect */}
-
-
+
);
diff --git a/src/components/app/onboarding/onboarding-form.tsx b/src/components/app/onboarding/onboarding-form.tsx
index 0ed95712a..7cf52aff0 100644
--- a/src/components/app/onboarding/onboarding-form.tsx
+++ b/src/components/app/onboarding/onboarding-form.tsx
@@ -6,9 +6,6 @@ import { motion } from 'framer-motion';
import { Card } from '@/components/ui/card';
import OnboardingUserDetails from './onboarding-user-details';
import OnboardingTimeCommitment from './onboarding-time-commitment';
-import OnboardingTags from './onboarding-tags';
-import OnboardingQuestions from './onboarding-questions';
-import OnboardingPricing from './onboarding-pricing';
import OnboardingFooter from './onboarding-footer';
import OnboardingNotifications from './onboarding-notifications';
import OnboardingInitialQuestions from './onboarding-initial-questions';
@@ -42,9 +39,6 @@ const stepComponents = {
[STEPS.TIME_COMMITMENT]: OnboardingTimeCommitment,
[STEPS.NOTIFICATIONS]: OnboardingNotifications,
[STEPS.FIRST_QUESTION_SELECTION]: OnboardingFirstQuestionSelection,
- [STEPS.TAGS]: OnboardingTags,
- [STEPS.QUESTIONS]: OnboardingQuestions,
- [STEPS.PRICING]: OnboardingPricing,
};
export default function OnboardingForm() {
@@ -62,20 +56,18 @@ export default function OnboardingForm() {
const StepComponent = stepComponents[currentStep];
return (
-
+
- {' '}
- {studyPath && (
-
- )}
+
diff --git a/src/components/mdx/blog-post-layout.tsx b/src/components/mdx/blog-post-layout.tsx
index 4554d0794..e4b2bde19 100644
--- a/src/components/mdx/blog-post-layout.tsx
+++ b/src/components/mdx/blog-post-layout.tsx
@@ -82,10 +82,6 @@ export default function BlogPostLayout({
diff --git a/src/components/shared/infinite-scrolling-roadmap-cards.tsx b/src/components/shared/infinite-scrolling-roadmap-cards.tsx
new file mode 100644
index 000000000..603f1954e
--- /dev/null
+++ b/src/components/shared/infinite-scrolling-roadmap-cards.tsx
@@ -0,0 +1,320 @@
+import { Question } from '@/types';
+import StudyPathsList from '../app/study-paths/list';
+import { StudyPath } from '@prisma/client';
+import { cn } from '@/lib/utils';
+
+// Mock questions data similar to the storybook examples
+const questions: Partial[] = [
+ {
+ uid: '1',
+ title: 'JavaScript Variables',
+ question: 'What is the correct way to declare a variable in JavaScript?',
+ description: 'Understanding variable declarations in JavaScript',
+ answers: [
+ {
+ uid: 'a1',
+ questionUid: '1',
+ answer: 'var x = 5;',
+ answerType: 'STANDARD',
+ isCodeSnippet: false,
+ },
+ {
+ uid: 'a2',
+ questionUid: '1',
+ answer: 'variable x = 5;',
+ answerType: 'STANDARD',
+ isCodeSnippet: false,
+ },
+ ],
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ questionDate: '2023-01-01',
+ correctAnswer: 'a1',
+ slug: 'javascript-variables',
+ questionType: 'MULTIPLE_CHOICE',
+ difficulty: 'BEGINNER',
+ userAnswers: [
+ {
+ uid: 'ua1',
+ userUid: 'user123',
+ questionUid: '1',
+ userAnswerUid: 'a1',
+ correctAnswer: true,
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ questionDate: '2023-01-01',
+ timeTaken: 15,
+ difficulty: 'EASY',
+ },
+ ],
+ },
+ {
+ uid: '2',
+ title: 'JavaScript Arrays',
+ question: 'Which method is used to add an element to the end of an array?',
+ description: 'Understanding array manipulation in JavaScript',
+ answers: [
+ {
+ uid: 'b1',
+ questionUid: '2',
+ answer: 'push()',
+ answerType: 'STANDARD',
+ isCodeSnippet: false,
+ },
+ {
+ uid: 'b2',
+ questionUid: '2',
+ answer: 'append()',
+ answerType: 'STANDARD',
+ isCodeSnippet: false,
+ },
+ ],
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ questionDate: '2023-01-02',
+ correctAnswer: 'b1',
+ slug: 'javascript-arrays',
+ questionType: 'MULTIPLE_CHOICE',
+ difficulty: 'EASY',
+ userAnswers: [
+ {
+ uid: 'ua2',
+ userUid: 'user123',
+ questionUid: '2',
+ userAnswerUid: 'b1',
+ correctAnswer: true,
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ questionDate: '2023-01-02',
+ timeTaken: 20,
+ difficulty: 'EASY',
+ },
+ ],
+ },
+ {
+ uid: '3',
+ title: 'JavaScript Functions',
+ question: 'What defines an arrow function in JavaScript?',
+ description: 'Understanding arrow functions in JavaScript',
+ answers: [
+ {
+ uid: 'c1',
+ questionUid: '3',
+ answer: '() => {}',
+ answerType: 'STANDARD',
+ isCodeSnippet: false,
+ },
+ {
+ uid: 'c2',
+ questionUid: '3',
+ answer: 'function() {}',
+ answerType: 'STANDARD',
+ isCodeSnippet: false,
+ },
+ ],
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ questionDate: '2023-01-03',
+ correctAnswer: 'c1',
+ slug: 'javascript-functions',
+ questionType: 'MULTIPLE_CHOICE',
+ difficulty: 'MEDIUM',
+ userAnswers: [
+ {
+ uid: 'ua3',
+ userUid: 'user123',
+ questionUid: '3',
+ userAnswerUid: 'c2',
+ correctAnswer: false,
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ questionDate: '2023-01-03',
+ timeTaken: 30,
+ difficulty: 'MEDIUM',
+ },
+ ],
+ },
+ {
+ uid: '4',
+ title: 'JavaScript Variables',
+ question: 'What is the correct way to declare a variable in JavaScript?',
+ description: 'Understanding variable declarations in JavaScript',
+ answers: [
+ {
+ uid: 'a1',
+ questionUid: '1',
+ answer: 'var x = 5;',
+ answerType: 'STANDARD',
+ isCodeSnippet: false,
+ },
+ {
+ uid: 'a2',
+ questionUid: '1',
+ answer: 'variable x = 5;',
+ answerType: 'STANDARD',
+ isCodeSnippet: false,
+ },
+ ],
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ questionDate: '2023-01-01',
+ correctAnswer: 'a1',
+ slug: 'javascript-variables',
+ questionType: 'MULTIPLE_CHOICE',
+ difficulty: 'BEGINNER',
+ userAnswers: [
+ {
+ uid: 'ua1',
+ userUid: 'user123',
+ questionUid: '1',
+ userAnswerUid: 'a1',
+ correctAnswer: true,
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ questionDate: '2023-01-01',
+ timeTaken: 15,
+ difficulty: 'EASY',
+ },
+ ],
+ },
+ {
+ uid: '5',
+ title: 'JavaScript Arrays',
+ question: 'Which method is used to add an element to the end of an array?',
+ description: 'Understanding array manipulation in JavaScript',
+ answers: [
+ {
+ uid: 'b1',
+ questionUid: '2',
+ answer: 'push()',
+ answerType: 'STANDARD',
+ isCodeSnippet: false,
+ },
+ {
+ uid: 'b2',
+ questionUid: '2',
+ answer: 'append()',
+ answerType: 'STANDARD',
+ isCodeSnippet: false,
+ },
+ ],
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ questionDate: '2023-01-02',
+ correctAnswer: 'b1',
+ slug: 'javascript-arrays',
+ questionType: 'MULTIPLE_CHOICE',
+ difficulty: 'EASY',
+ userAnswers: [
+ {
+ uid: 'ua2',
+ userUid: 'user123',
+ questionUid: '2',
+ userAnswerUid: 'b1',
+ correctAnswer: true,
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ questionDate: '2023-01-02',
+ timeTaken: 20,
+ difficulty: 'EASY',
+ },
+ ],
+ },
+ {
+ uid: '6',
+ title: 'JavaScript Functions',
+ question: 'What defines an arrow function in JavaScript?',
+ description: 'Understanding arrow functions in JavaScript',
+ answers: [
+ {
+ uid: 'c1',
+ questionUid: '3',
+ answer: '() => {}',
+ answerType: 'STANDARD',
+ isCodeSnippet: false,
+ },
+ {
+ uid: 'c2',
+ questionUid: '3',
+ answer: 'function() {}',
+ answerType: 'STANDARD',
+ isCodeSnippet: false,
+ },
+ ],
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ questionDate: '2023-01-03',
+ correctAnswer: 'c1',
+ slug: 'javascript-functions',
+ questionType: 'MULTIPLE_CHOICE',
+ difficulty: 'MEDIUM',
+ userAnswers: [
+ {
+ uid: 'ua3',
+ userUid: 'user123',
+ questionUid: '3',
+ userAnswerUid: 'c2',
+ correctAnswer: false,
+ createdAt: new Date(),
+ updatedAt: new Date(),
+ questionDate: '2023-01-03',
+ timeTaken: 30,
+ difficulty: 'MEDIUM',
+ },
+ ],
+ },
+];
+
+// Triple the questions to ensure smooth infinite scroll
+const allQuestions = [...questions, ...questions, ...questions];
+
+// Mock study path
+const studyPath: Partial = {
+ slug: 'javascript-fundamentals',
+ title: 'JavaScript Fundamentals',
+ description: 'Learn the fundamentals of JavaScript programming',
+ heroChip: 'Master JavaScript from scratch',
+ questionSlugs: ['javascript-variables', 'javascript-arrays', 'javascript-functions'],
+ educationLevel: 'beginner',
+};
+
+export default function InfiniteScrollingRoadmapCards({ className }: { className?: string }) {
+ return (
+ <>
+ {/** 'infinite' scrolling animation of the study path buttons. */}
+ {/* Scrolling content */}
+
+ {/** top fade effect */}
+
+
+
+ {/* First set of items */}
+
+
+ {/* Clone of the first set to create seamless loop */}
+
+
+
+
+
+ {/* Bottom fade effect */}
+
+
+ >
+ );
+}
diff --git a/src/hooks/use-onboarding-steps.ts b/src/hooks/use-onboarding-steps.ts
index 4ffba1046..842931a5c 100644
--- a/src/hooks/use-onboarding-steps.ts
+++ b/src/hooks/use-onboarding-steps.ts
@@ -16,9 +16,6 @@ export const STEPS = {
INITIAL_QUESTIONS: 'INITIAL_QUESTIONS', // give the user 3 very simple multiple choice questions to gauge skill level and give them quick wins!
TIME_COMMITMENT: 'TIME_COMMITMENT', // get the users daily coding goal
NOTIFICATIONS: 'NOTIFICATIONS', // offer push notifications
- TAGS: 'TAGS', // get the users interests
- PRICING: 'PRICING', // get the users pricing plan
- QUESTIONS: 'QUESTIONS', // get the users questions
FIRST_QUESTION_SELECTION: 'FIRST_QUESTION_SELECTION', // get the users first question (either send them to the first question or the tag selection)
} as const;
@@ -31,12 +28,10 @@ export function useOnboardingSteps() {
const {
serverUser,
user,
- handleGetOnboardingQuestions,
canContinue,
setCanContinue,
timeSpendingPerDay,
firstQuestionSelection,
- FIRST_QUESTION_TUTORIAL_SLUG,
totalXp,
} = useOnboardingContext();
const [isLoading, setIsLoading] = useState(false);
@@ -74,34 +69,17 @@ export function useOnboardingSteps() {
component: 'OnboardingNotifications',
},
[STEPS.FIRST_QUESTION_SELECTION]: {
- next: STEPS.TAGS,
+ next: 'ROADMAP_PAGE',
component: 'OnboardingFirstQuestionSelection',
},
- [STEPS.TAGS]: {
- next: STEPS.PRICING,
- component: 'OnboardingTags',
- },
- [STEPS.PRICING]: {
- next: STEPS.QUESTIONS,
- component: 'OnboardingPricing',
- },
- [STEPS.QUESTIONS]: {
- next: 'DASHBOARD',
- component: 'OnboardingQuestions',
- },
} as const;
const handleSkip = () => {
- const nextStep = stepConfig[currentStep].next;
- if (nextStep === 'DASHBOARD') {
+ const nextStep = stepConfig[currentStep]?.next;
+ if (nextStep === 'ROADMAP_PAGE') {
localStorage.removeItem('onboarding');
window.location.hash = '';
- router.push('/dashboard?onboarding=true');
- }
- // if the user is skipping past the tags, redirect to the dashboard
- else if (nextStep === STEPS.TAGS) {
- localStorage.removeItem('onboarding');
- router.push('/dashboard?onboarding=true');
+ router.push('/roadmaps?onboarding=true');
} else {
setCurrentStepState(nextStep as StepKey);
}
@@ -117,24 +95,12 @@ export function useOnboardingSteps() {
currentStep === STEPS.FIRST_QUESTION_SELECTION &&
firstQuestionSelection === 'startFromScratch'
) {
- setCurrentStepState(STEPS.PRICING);
+ // Navigate to roadmap instead of QUESTIONS step
+ localStorage.removeItem('onboarding');
+ router.push('/roadmaps?onboarding=true');
return;
}
- // Handle pricing step based on user's first question selection
- if (currentStep === STEPS.PRICING) {
- if (firstQuestionSelection === 'startFromScratch') {
- localStorage.removeItem('onboarding');
- router.push(`/question/${FIRST_QUESTION_TUTORIAL_SLUG}?tutorial=true`);
- return;
- } else if (firstQuestionSelection === 'personalizeLearning') {
- await handleGetOnboardingQuestions();
- localStorage.removeItem('onboarding');
- setCurrentStepState(STEPS.QUESTIONS);
- return;
- }
- }
-
// Handle time commitment step
if (currentStep === STEPS.TIME_COMMITMENT) {
await updateUser({ userDetails: { ...user, timeSpendingPerDay } });
@@ -197,11 +163,11 @@ export function useOnboardingSteps() {
// Handle all other steps
await updateUser({ userDetails: user });
- const nextStep = stepConfig[currentStep].next;
+ const nextStep = stepConfig[currentStep]?.next;
- if (nextStep === 'DASHBOARD') {
+ if (nextStep === 'ROADMAP_PAGE') {
localStorage.removeItem('onboarding');
- router.push('/dashboard?onboarding=true');
+ router.push('/roadmaps?onboarding=true');
} else {
setCurrentStepState(nextStep as StepKey);
}
@@ -218,15 +184,9 @@ export function useOnboardingSteps() {
return;
}
- // if the user is on the pricing page, they need to go back to the tags page only if they did not
- // start from scratch
- if (currentStep === STEPS.PRICING && firstQuestionSelection !== 'startFromScratch') {
- return setCurrentStepState(STEPS.TAGS);
- }
-
// Get the previous step
const previousStep = Object.entries(stepConfig).find(
- ([_, config]) => config.next === currentStep
+ ([, config]) => config.next === currentStep
)?.[0];
if (previousStep) {
@@ -260,12 +220,7 @@ export function useOnboardingSteps() {
return false;
}
- // Don't show back button on final steps
- if (currentStep === STEPS.QUESTIONS || currentStep === STEPS.PRICING) {
- return false;
- }
-
- // Show back button on intermediate steps
+ // Show back button on all other steps
return true;
};
From d2dde459fbd5ded6a847139264d7a2217f7717c0 Mon Sep 17 00:00:00 2001
From: Logan Ford <110533855+Logannford@users.noreply.github.com>
Date: Wed, 30 Apr 2025 00:26:48 +0100
Subject: [PATCH 08/14] chore: updates question count
---
src/utils/constants/misc.ts | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/utils/constants/misc.ts b/src/utils/constants/misc.ts
index 6a1195bfc..25474c98e 100644
--- a/src/utils/constants/misc.ts
+++ b/src/utils/constants/misc.ts
@@ -1,3 +1,3 @@
-export const QUESTIONS_COUNT = 330;
+export const QUESTIONS_COUNT = 350;
export const ROADMAP_QUESTION_COUNT = 6;
From b8a849909f47952b6976c9d505dc4794868f4ab8 Mon Sep 17 00:00:00 2001
From: Logan Ford <110533855+Logannford@users.noreply.github.com>
Date: Sat, 3 May 2025 20:27:52 +0100
Subject: [PATCH 09/14] feat: adds new component to different areas
---
src/actions/user/authed/get-user.ts | 2 +-
.../onboarding/onboarding-first-question-selection.tsx | 2 --
.../marketing/global/blocks/three-block-showcase.tsx | 9 ---------
3 files changed, 1 insertion(+), 12 deletions(-)
diff --git a/src/actions/user/authed/get-user.ts b/src/actions/user/authed/get-user.ts
index 85207b727..b5d3b940f 100644
--- a/src/actions/user/authed/get-user.ts
+++ b/src/actions/user/authed/get-user.ts
@@ -5,7 +5,7 @@ import type { UserRecord } from '@/types';
import { revalidateTag } from 'next/cache';
/**
- * Get the user from the server - used in api routes, server componets & server actions
+ * Get the user from the server - used in api routes, server components & server actions
*
* @returns User | null
*/
diff --git a/src/components/app/onboarding/onboarding-first-question-selection.tsx b/src/components/app/onboarding/onboarding-first-question-selection.tsx
index 674508478..894cd3452 100644
--- a/src/components/app/onboarding/onboarding-first-question-selection.tsx
+++ b/src/components/app/onboarding/onboarding-first-question-selection.tsx
@@ -1,8 +1,6 @@
import { CardHeader } from '@/components/ui/card';
import { useOnboardingContext } from '@/contexts/onboarding-context';
import { motion } from 'framer-motion';
-import type { Question } from '@/types';
-import { StudyPath } from '@/utils/constants/study-paths';
import InfiniteScrollingRoadmapCards from '@/components/shared/infinite-scrolling-roadmap-cards';
export default function OnboardingFirstQuestionSelection() {
diff --git a/src/components/marketing/global/blocks/three-block-showcase.tsx b/src/components/marketing/global/blocks/three-block-showcase.tsx
index 87476e248..f57c9a528 100644
--- a/src/components/marketing/global/blocks/three-block-showcase.tsx
+++ b/src/components/marketing/global/blocks/three-block-showcase.tsx
@@ -1,9 +1,6 @@
import ArcheryTarget from '@/components/ui/icons/target';
import { cn } from '@/lib/utils';
import AnimatedAIQuestionHelpCard from '../../homepage/personalized/ai-help-demo';
-import StudyPathsList from '@/components/app/study-paths/list';
-import { getQuestions } from '@/actions/questions/admin/list';
-import { getStudyPath } from '@/utils/data/study-paths/get';
import InfiniteScrollingRoadmapCards from '@/components/shared/infinite-scrolling-roadmap-cards';
/**
@@ -46,12 +43,6 @@ export default async function ThreeBlockShowcase({
const cardDescriptionClasses =
'absolute bottom-0 left-6 translate-y-full transition-transform duration-300 md:group-hover:translate-y-[-1rem] md:group-hover:translate-y-[-1rem] text-sm text-gray-400';
- // hardcoded study path and questions
- const studyPath = await getStudyPath('javascript-fundamentals');
- const questions = await getQuestions({
- questionSlugs: studyPath?.questionSlugs.slice(0, 3) ?? [],
- });
-
return (
From 8e9dabd065800b29853daee723614287a5ea0ad4 Mon Sep 17 00:00:00 2001
From: Logan Ford <110533855+Logannford@users.noreply.github.com>
Date: Sat, 3 May 2025 21:17:02 +0100
Subject: [PATCH 10/14] feat: start of onboarding tour on roadmaps page
---
.../(roadmaps)/roadmaps/page.tsx | 77 ++++++++++++-------
.../(questions)/question/[slug]/layout.tsx | 63 ++-------------
src/hooks/use-onboarding-steps.ts | 2 +-
src/lib/onborda.tsx | 4 +-
4 files changed, 58 insertions(+), 88 deletions(-)
diff --git a/src/app/(app)/(default_layout)/(roadmaps)/roadmaps/page.tsx b/src/app/(app)/(default_layout)/(roadmaps)/roadmaps/page.tsx
index afd94b7e1..8b04f43c2 100644
--- a/src/app/(app)/(default_layout)/(roadmaps)/roadmaps/page.tsx
+++ b/src/app/(app)/(default_layout)/(roadmaps)/roadmaps/page.tsx
@@ -12,9 +12,12 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip
import { createMetadata } from '@/utils/seo';
import { getStudyPathsAndGroupByCategory } from '@/utils/data/study-paths/get';
import { getBaseUrl } from '@/utils';
+import { roadmapPageSteps } from '@/lib/onborda';
// types
import { WebPageJsonLd } from '@/types';
+import { Onborda, OnbordaProvider } from 'onborda';
+import { TourCard } from '@/components/app/shared/question/tour-card';
export async function generateMetadata() {
return createMetadata({
@@ -58,7 +61,14 @@ const heroDescription = (
);
-export default async function ExploreQuestionsPage() {
+export default async function ExploreQuestionsPage({
+ searchParams,
+}: {
+ searchParams: { [key: string]: string | string[] | undefined };
+}) {
+ // used to determine if the onboarding tour guide needs to be shown.
+ const { onboarding } = searchParams;
+
// create json ld
const jsonLd: WebPageJsonLd = {
'@context': 'https://schema.org',
@@ -109,35 +119,46 @@ export default async function ExploreQuestionsPage() {
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
/>
-
-
-
-
- {categories.map((category) => (
-
-
-
{category}
- {studyPathsByCategory[category][0].categoryToolTip && (
-
-
-
-
-
- {studyPathsByCategory[category][0].categoryToolTip}
-
-
- )}
-
-
- {studyPathsByCategory[category].map((studyPath) => (
-
- ))}
-
+
+
+
+
+
+
+ {categories.map((category) => (
+
+
+
{category}
+ {studyPathsByCategory[category][0].categoryToolTip && (
+
+
+
+
+
+ {studyPathsByCategory[category][0].categoryToolTip}
+
+
+ )}
+
+
+ {studyPathsByCategory[category].map((studyPath) => (
+
+ ))}
+
+
+ ))}
- ))}
+
-
-
+
+
>
);
}
diff --git a/src/app/(questions)/question/[slug]/layout.tsx b/src/app/(questions)/question/[slug]/layout.tsx
index a75de462a..148ca035e 100644
--- a/src/app/(questions)/question/[slug]/layout.tsx
+++ b/src/app/(questions)/question/[slug]/layout.tsx
@@ -7,23 +7,23 @@ import { redirect } from 'next/navigation';
import { QuestionSingleContextProvider } from '@/contexts/question-single-context';
// Actions & Utils
-import { createMetadata, getQuestionEducationLevel } from '@/utils/seo';
-import { capitalise, getBaseUrl } from '@/utils';
+import { createMetadata } from '@/utils/seo';
+import { capitalise } from '@/utils';
import { getQuestion } from '@/utils/data/questions/get';
import { getUser } from '@/actions/user/authed/get-user';
import { getRelatedQuestions } from '@/utils/data/questions/get-related';
import { getUserAnswer } from '@/utils/data/answers/get-user-answer';
import { getNextAndPreviousQuestion } from '@/utils/data/questions/question-navigation';
-import type { QuizJsonLd } from '@/types';
import { userHasAnsweredAnyQuestion } from '@/utils/data/questions/user-has-answered-any-question';
// Components
import { Onborda, OnbordaProvider } from 'onborda';
-import { steps } from '@/lib/onborda';
import { TourCard } from '@/components/app/shared/question/tour-card';
import { getSuggestions } from '@/utils/data/questions/get-suggestions';
import QuestionPageHeader from '@/components/app/layout/question-single/page-header';
+import { questionPageSteps } from '@/lib/onborda';
+
// Lazy Components
const PremiumQuestionDeniedAccess = lazy(
() => import('@/components/app/questions/premium-question-denied-access')
@@ -73,55 +73,6 @@ export default async function QuestionUidLayout({
return redirect('/coding-challenges');
}
- const defaultQuestionDescription = `Practice ${capitalise(
- question?.title || question?.slug?.replace(/-/g, ' ') || 'Coding Question'
- )} and improve your coding skills with interactive challenges on TechBlitz`;
-
- const description =
- question?.questionType === 'SIMPLE_MULTIPLE_CHOICE'
- ? question?.afterQuestionInfo || defaultQuestionDescription
- : defaultQuestionDescription;
-
- // create json ld
- const jsonLd: QuizJsonLd = {
- '@context': 'https://schema.org',
- '@type': 'Quiz',
- name: capitalise(question?.slug?.replace(/-/g, ' ') || ''),
- description,
- url: `${getBaseUrl()}/question/${slug}`,
- educationLevel: getQuestionEducationLevel(question?.difficulty || 'EASY'),
- educationalUse: 'practice',
- learningResourceType: ['quiz', 'learning activity'],
- creator: { '@type': 'Organization', name: 'TechBlitz', url: getBaseUrl() },
- assesses: ['coding'],
- dateCreated: question?.createdAt.toISOString() || '',
- dateModified: question?.updatedAt.toISOString() || '',
- datePublished: question?.questionDate || question?.createdAt.toISOString() || '',
- headline: question?.question || '',
- interactivityType: 'mixed',
- isAccessibleForFree: true,
- isFamilyFriendly: true,
- teaches: 'coding',
- potentialAction: {
- '@type': 'SearchAction',
- target: `${getBaseUrl()}/search?q={search_term_string}`,
- 'query-input': 'required name=search_term_string',
- },
- breadcrumb: {
- '@type': 'BreadcrumbList',
- itemListElement: [
- { '@type': 'ListItem', position: 1, name: 'Home', item: `${getBaseUrl()}` },
- { '@type': 'ListItem', position: 2, name: 'Questions', item: `${getBaseUrl()}/questions` },
- {
- '@type': 'ListItem',
- position: 3,
- name: question.slug,
- item: `${getBaseUrl()}/question/${slug}`,
- },
- ],
- },
- };
-
// not resolving the promises here - passing the promises and
// using 'use' to resolve them in their own components
const nextAndPreviousQuestion = getNextAndPreviousQuestion(question.uid); // cached
@@ -138,13 +89,9 @@ export default async function QuestionUidLayout({
return (
<>
-
[
+export const questionPageSteps = (): Tour[] => [
{
tour: 'question-tour',
steps: [
@@ -76,3 +76,5 @@ export const steps = (): Tour[] => [
],
},
];
+
+export const roadmapPageSteps = (): Tour[] => [];
From f1cd962f8b18a393452c6d09b09c4013f63d56a6 Mon Sep 17 00:00:00 2001
From: Logan Ford <110533855+Logannford@users.noreply.github.com>
Date: Sun, 4 May 2025 21:53:08 +0100
Subject: [PATCH 11/14] feat: minor changes to tour component to make it more
reusable + starting to add tour to roadmaps page
---
.../(roadmaps)/roadmaps/page.tsx | 4 +++
src/app/(app)/layout.tsx | 4 +--
src/app/(questions)/question/[slug]/page.tsx | 2 +-
.../[slug]/[subSection]/lesson/layout.tsx | 4 +--
.../learn/[slug]/[subSection]/lesson/page.tsx | 2 +-
.../app/shared/question/tour-start-modal.tsx | 31 ++++++++++++++-----
src/lib/onborda.tsx | 18 ++++++++++-
7 files changed, 50 insertions(+), 15 deletions(-)
diff --git a/src/app/(app)/(default_layout)/(roadmaps)/roadmaps/page.tsx b/src/app/(app)/(default_layout)/(roadmaps)/roadmaps/page.tsx
index 8b04f43c2..c1d0e8a33 100644
--- a/src/app/(app)/(default_layout)/(roadmaps)/roadmaps/page.tsx
+++ b/src/app/(app)/(default_layout)/(roadmaps)/roadmaps/page.tsx
@@ -18,6 +18,8 @@ import { roadmapPageSteps } from '@/lib/onborda';
import { WebPageJsonLd } from '@/types';
import { Onborda, OnbordaProvider } from 'onborda';
import { TourCard } from '@/components/app/shared/question/tour-card';
+import TourStartModal from '@/components/app/shared/question/tour-start-modal';
+import { useUserServer } from '@/hooks/use-user-server';
export async function generateMetadata() {
return createMetadata({
@@ -66,6 +68,7 @@ export default async function ExploreQuestionsPage({
}: {
searchParams: { [key: string]: string | string[] | undefined };
}) {
+ const user = await useUserServer();
// used to determine if the onboarding tour guide needs to be shown.
const { onboarding } = searchParams;
@@ -128,6 +131,7 @@ export default async function ExploreQuestionsPage({
cardComponent={TourCard}
cardTransition={{ duration: 0.3, type: 'tween' }}
>
+
diff --git a/src/app/(app)/layout.tsx b/src/app/(app)/layout.tsx
index 2c56c6888..491d67e8f 100644
--- a/src/app/(app)/layout.tsx
+++ b/src/app/(app)/layout.tsx
@@ -18,7 +18,7 @@ import { getSuggestions } from '@/utils/data/questions/get-suggestions';
// onboarding
import { Onborda, OnbordaProvider } from 'onborda';
import { TourCard } from '@/components/app/shared/question/tour-card';
-import { steps } from '@/lib/onborda';
+import { questionPageSteps } from '@/lib/onborda';
export async function generateMetadata() {
return createMetadata({
@@ -39,7 +39,7 @@ export default async function RootLayout({ children }: { children: React.ReactNo
-
+
-
+
{
// close the modal
setIsModalOpen(false);
// start the tour
- startOnborda('question-tour');
+ startOnborda(tourName);
};
// this opens if the url has '?tutorial=true'
@@ -38,7 +41,7 @@ export default function TourStartModal({ user }: TourStartModalProps) {
}
// if on modal, show the modal
- if (window.location.search.includes('tutorial=true')) {
+ if (window.location.search.includes(queryParam)) {
setIsModalOpen(true);
}
}, []);
@@ -46,17 +49,29 @@ export default function TourStartModal({ user }: TourStartModalProps) {
return (
-
-
+
+
{getUserDisplayName(user)}, welcome to TechBlitz!
-
+
We're excited to have you on board. We've prepared a quick tour to help you get
started.
- If you have any questions, please don't hesitate to reach out to us.
- Happy coding!
+
+ If you have any questions, please don't hesitate to reach out to us at{' '}
+
+ team@techblitz.dev
+
+ .
+
+
+ Or submit an issue on our{' '}
+
+ GitHub
+
+ .
+
diff --git a/src/lib/onborda.tsx b/src/lib/onborda.tsx
index 83c3cad08..1b12d5c64 100644
--- a/src/lib/onborda.tsx
+++ b/src/lib/onborda.tsx
@@ -77,4 +77,20 @@ export const questionPageSteps = (): Tour[] => [
},
];
-export const roadmapPageSteps = (): Tour[] => [];
+export const roadmapPageSteps = (): Tour[] => [
+ {
+ tour: 'roadmap-tour',
+ steps: [
+ {
+ content: 'Here you can find the roadmap description.',
+ selector: '#roadmap-description',
+ side: 'bottom-left',
+ showControls: true,
+ pointerPadding: -1,
+ pointerRadius: 24,
+ icon: <>>,
+ title: 'Roadmap Description',
+ },
+ ],
+ },
+];
From 6aa323f9f7e6251a26e21a032ebeab4dc572fde8 Mon Sep 17 00:00:00 2001
From: Logan Ford <110533855+Logannford@users.noreply.github.com>
Date: Sun, 4 May 2025 22:18:08 +0100
Subject: [PATCH 12/14] feat: more onboarding progress
---
.../(roadmaps)/roadmaps/page.tsx | 8 +++++--
.../app/shared/question/tour-start-modal.tsx | 9 ++++----
src/lib/onborda.tsx | 21 +++++++++++++++++--
3 files changed, 30 insertions(+), 8 deletions(-)
diff --git a/src/app/(app)/(default_layout)/(roadmaps)/roadmaps/page.tsx b/src/app/(app)/(default_layout)/(roadmaps)/roadmaps/page.tsx
index c1d0e8a33..fbc8053a6 100644
--- a/src/app/(app)/(default_layout)/(roadmaps)/roadmaps/page.tsx
+++ b/src/app/(app)/(default_layout)/(roadmaps)/roadmaps/page.tsx
@@ -124,7 +124,7 @@ export default async function ExploreQuestionsPage({
/>
{categories.map((category) => (
-
+
{category}
{studyPathsByCategory[category][0].categoryToolTip && (
diff --git a/src/components/app/shared/question/tour-start-modal.tsx b/src/components/app/shared/question/tour-start-modal.tsx
index fb847a92b..1cf863c11 100644
--- a/src/components/app/shared/question/tour-start-modal.tsx
+++ b/src/components/app/shared/question/tour-start-modal.tsx
@@ -15,6 +15,7 @@ import type { UserRecord } from '@/types';
import { getUserDisplayName } from '@/utils/user';
import { useOnborda } from 'onborda';
import Link from 'next/link';
+import GithubLogo from '@/components/ui/icons/github';
interface TourStartModalProps {
user: UserRecord | null;
@@ -65,12 +66,12 @@ export default function TourStartModal({ user, tourName, queryParam }: TourStart
.
-
- Or submit an issue on our{' '}
+
+ If you want to keep up with the latest updates, you can follow us on{' '}
- GitHub
+
- .
+ and join our community of developers!
diff --git a/src/lib/onborda.tsx b/src/lib/onborda.tsx
index 1b12d5c64..07f09de39 100644
--- a/src/lib/onborda.tsx
+++ b/src/lib/onborda.tsx
@@ -77,12 +77,27 @@ export const questionPageSteps = (): Tour[] => [
},
];
-export const roadmapPageSteps = (): Tour[] => [
+export const roadmapPageSteps = (nextRoute: string, prevRoute: string): Tour[] => [
{
tour: 'roadmap-tour',
steps: [
{
- content: 'Here you can find the roadmap description.',
+ content: (
+ <>
+ Each roadmap is a collection of coding challenges aimed to help you prepare for your
+ first job in tech.
+ >
+ ),
+ selector: '#first-roadmap-category',
+ side: 'bottom-left',
+ showControls: true,
+ pointerPadding: -1,
+ pointerRadius: 24,
+ icon: <>>,
+ title: 'Your first roadmap',
+ },
+ {
+ content: '',
selector: '#roadmap-description',
side: 'bottom-left',
showControls: true,
@@ -90,6 +105,8 @@ export const roadmapPageSteps = (): Tour[] => [
pointerRadius: 24,
icon: <>>,
title: 'Roadmap Description',
+ nextRoute,
+ prevRoute,
},
],
},
From c5e6f495dbc0fc4f6256045c1d2f6005b2cc6d8c Mon Sep 17 00:00:00 2001
From: Logan Ford <110533855+Logannford@users.noreply.github.com>
Date: Sun, 4 May 2025 22:29:54 +0100
Subject: [PATCH 13/14] feat: more on the first onboarding tour page
---
.../(roadmaps)/roadmaps/[slug]/page.tsx | 2 +-
.../app/shared/question/tour-start-modal.tsx | 31 +++++++++++++------
src/components/ui/icons/tiktok.tsx | 13 ++++++++
src/lib/onborda.tsx | 6 ++--
4 files changed, 39 insertions(+), 13 deletions(-)
create mode 100644 src/components/ui/icons/tiktok.tsx
diff --git a/src/app/(app)/(default_layout)/(roadmaps)/roadmaps/[slug]/page.tsx b/src/app/(app)/(default_layout)/(roadmaps)/roadmaps/[slug]/page.tsx
index ca11a588d..95d77e05d 100644
--- a/src/app/(app)/(default_layout)/(roadmaps)/roadmaps/[slug]/page.tsx
+++ b/src/app/(app)/(default_layout)/(roadmaps)/roadmaps/[slug]/page.tsx
@@ -138,7 +138,7 @@ function StudyPathSections({
}
return (
-
+
{studyPathSections.map((section) => {
// Skip sections with no content
if (
diff --git a/src/components/app/shared/question/tour-start-modal.tsx b/src/components/app/shared/question/tour-start-modal.tsx
index 1cf863c11..cdc5aaf3f 100644
--- a/src/components/app/shared/question/tour-start-modal.tsx
+++ b/src/components/app/shared/question/tour-start-modal.tsx
@@ -16,6 +16,8 @@ import { getUserDisplayName } from '@/utils/user';
import { useOnborda } from 'onborda';
import Link from 'next/link';
import GithubLogo from '@/components/ui/icons/github';
+import { InstagramLogoIcon } from '@radix-ui/react-icons';
+import TikTokLogo from '@/components/ui/icons/tiktok';
interface TourStartModalProps {
user: UserRecord | null;
@@ -67,21 +69,32 @@ export default function TourStartModal({ user, tourName, queryParam }: TourStart
.
- If you want to keep up with the latest updates, you can follow us on{' '}
+ If you'd like to show your support, you can star our{' '}
- and join our community of developers!
+ repository.
-
- setIsModalOpen(false)}>
- Skip
-
-
- Start tour
-
+
+ {/** socials */}
+
+
+
+
+
+
+
+
+
+ setIsModalOpen(false)}>
+ Skip
+
+
+ Start tour
+
+
diff --git a/src/components/ui/icons/tiktok.tsx b/src/components/ui/icons/tiktok.tsx
new file mode 100644
index 000000000..cf1d9d24a
--- /dev/null
+++ b/src/components/ui/icons/tiktok.tsx
@@ -0,0 +1,13 @@
+import { SVGProps } from 'react';
+
+export default function TikTokLogo(props: SVGProps) {
+ return (
+
+ {/* Icon from Google Material Icons by Material Design Authors - https://github.com/material-icons/material-icons/blob/master/LICENSE */}
+
+
+ );
+}
diff --git a/src/lib/onborda.tsx b/src/lib/onborda.tsx
index 07f09de39..8bd3cc1d4 100644
--- a/src/lib/onborda.tsx
+++ b/src/lib/onborda.tsx
@@ -95,17 +95,17 @@ export const roadmapPageSteps = (nextRoute: string, prevRoute: string): Tour[] =
pointerRadius: 24,
icon: <>>,
title: 'Your first roadmap',
+ nextRoute,
},
{
- content: '',
- selector: '#roadmap-description',
+ content: 'This is a mf test.',
+ selector: '#roadmap-list',
side: 'bottom-left',
showControls: true,
pointerPadding: -1,
pointerRadius: 24,
icon: <>>,
title: 'Roadmap Description',
- nextRoute,
prevRoute,
},
],
From e50dd1473c2b25598fe05a4b7e0e53c2aa22c135 Mon Sep 17 00:00:00 2001
From: Logan Ford <110533855+Logannford@users.noreply.github.com>
Date: Sat, 10 May 2025 20:15:32 +0100
Subject: [PATCH 14/14] feat: some minor style changes to onboarding flow
---
.../(default_layout)/(roadmaps)/roadmaps/[slug]/page.tsx | 4 ++--
.../app/onboarding/onboarding-first-question-selection.tsx | 5 ++---
src/components/app/onboarding/onboarding-form.tsx | 3 ++-
src/lib/onborda.tsx | 3 ++-
4 files changed, 8 insertions(+), 7 deletions(-)
diff --git a/src/app/(app)/(default_layout)/(roadmaps)/roadmaps/[slug]/page.tsx b/src/app/(app)/(default_layout)/(roadmaps)/roadmaps/[slug]/page.tsx
index 95d77e05d..639f602e8 100644
--- a/src/app/(app)/(default_layout)/(roadmaps)/roadmaps/[slug]/page.tsx
+++ b/src/app/(app)/(default_layout)/(roadmaps)/roadmaps/[slug]/page.tsx
@@ -138,7 +138,7 @@ function StudyPathSections({
}
return (
-
+
{studyPathSections.map((section) => {
// Skip sections with no content
if (
@@ -231,7 +231,7 @@ export default async function RoadmapPage({ params }: { params: { slug: string }
chip={
}
/>
-
+
}>
+
- We're so excited to have you here! Your future in tech is bright, and we're here to help
- you get there.
+ We're so excited to have you here!
diff --git a/src/components/app/onboarding/onboarding-form.tsx b/src/components/app/onboarding/onboarding-form.tsx
index 7cf52aff0..1d7759680 100644
--- a/src/components/app/onboarding/onboarding-form.tsx
+++ b/src/components/app/onboarding/onboarding-form.tsx
@@ -62,7 +62,8 @@ export default function OnboardingForm() {
className={cn(
'rounded-lg shadow-xl overflow-hidden min-w-fit relative',
currentStep === STEPS.TIME_COMMITMENT && 'lg:min-w-[58rem]',
- currentStep === STEPS.INITIAL_QUESTIONS ? 'border-none' : 'border border-black-50'
+ currentStep === STEPS.INITIAL_QUESTIONS ? 'border-none' : 'border border-black-50',
+ currentStep === STEPS.FIRST_QUESTION_SELECTION && 'w-72 lg:w-96'
)}
style={{
background:
diff --git a/src/lib/onborda.tsx b/src/lib/onborda.tsx
index 8bd3cc1d4..1d5b3cbbb 100644
--- a/src/lib/onborda.tsx
+++ b/src/lib/onborda.tsx
@@ -98,7 +98,8 @@ export const roadmapPageSteps = (nextRoute: string, prevRoute: string): Tour[] =
nextRoute,
},
{
- content: 'This is a mf test.',
+ content:
+ 'Here you can find each subsection of the roadmap. Giving you a brief concept of what this section is about.',
selector: '#roadmap-list',
side: 'bottom-left',
showControls: true,