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/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/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/app/(app)/(default_layout)/(roadmaps)/roadmaps/[slug]/page.tsx b/src/app/(app)/(default_layout)/(roadmaps)/roadmaps/[slug]/page.tsx
index ca11a588d..639f602e8 100644
--- a/src/app/(app)/(default_layout)/(roadmaps)/roadmaps/[slug]/page.tsx
+++ b/src/app/(app)/(default_layout)/(roadmaps)/roadmaps/[slug]/page.tsx
@@ -231,7 +231,7 @@ export default async function RoadmapPage({ params }: { params: { slug: string }
chip={ }
/>
-
+
}>
);
-export default async function ExploreQuestionsPage() {
+export default async function ExploreQuestionsPage({
+ searchParams,
+}: {
+ 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;
+
// create json ld
const jsonLd: WebPageJsonLd = {
'@context': 'https://schema.org',
@@ -109,35 +122,51 @@ 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/(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
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 c27a9644e..ee34ff9a2 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 (
@@ -21,9 +29,7 @@ export default async function OnboardingPage() {
-
-
-
+
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 (
<>
-
-
+
-
+
-
+
+
- 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!
+
+
+
);
diff --git a/src/components/app/onboarding/onboarding-footer.tsx b/src/components/app/onboarding/onboarding-footer.tsx
index 31f3da481..52e39e8b5 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 && (
-
+
- Back
)}
- {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 && }
diff --git a/src/components/app/onboarding/onboarding-form.tsx b/src/components/app/onboarding/onboarding-form.tsx
index acfb621be..1d7759680 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,33 +39,36 @@ 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() {
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 (
-
+
{
// close the modal
setIsModalOpen(false);
// start the tour
- startOnborda('question-tour');
+ startOnborda(tourName);
};
// this opens if the url has '?tutorial=true'
@@ -38,7 +44,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,26 +52,49 @@ 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
+
+ .
+
+
+ If you'd like to show your support, you can star our{' '}
+
+
+
+ repository.
+
-
- setIsModalOpen(false)}>
- Skip
-
-
- Start tour
-
+
+ {/** socials */}
+
+
+
+
+
+
+
+
+
+ setIsModalOpen(false)}>
+ Skip
+
+
+ Start tour
+
+
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 (
- {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/components/marketing/global/blocks/three-block-showcase.tsx b/src/components/marketing/global/blocks/three-block-showcase.tsx
index 126ccc0fb..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,7 @@
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';
/**
*
@@ -45,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 (
@@ -133,16 +125,7 @@ export default async function ThreeBlockShowcase({
{right || (
<>
- {' '}
- {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/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/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/hooks/use-onboarding-steps.ts b/src/hooks/use-onboarding-steps.ts
index 2b76ead67..2d0a2e50c 100644
--- a/src/hooks/use-onboarding-steps.ts
+++ b/src/hooks/use-onboarding-steps.ts
@@ -6,15 +6,16 @@ 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';
+
+// Toggle for admin to skip initial questions
+const ADMIN_SKIP_INITIAL_QUESTIONS = true;
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!
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;
@@ -27,11 +28,10 @@ export function useOnboardingSteps() {
const {
serverUser,
user,
- handleGetOnboardingQuestions,
canContinue,
+ setCanContinue,
timeSpendingPerDay,
firstQuestionSelection,
- FIRST_QUESTION_TUTORIAL_SLUG,
totalXp,
} = useOnboardingContext();
const [isLoading, setIsLoading] = useState(false);
@@ -69,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);
}
@@ -107,51 +90,59 @@ 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'
) {
- setCurrentStepState(STEPS.PRICING);
- 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
+ // Navigate to roadmap instead of QUESTIONS step
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);
+ router.push('/roadmaps?onboarding=true');
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();
- // send the welcome email
- await sendWelcomeEmail(serverUser, coupon?.name ?? '');
+
+ // Check if welcome email has already been sent
+ const storageKey = serverUser?.email
+ ? `welcome-email-sent_${serverUser.email}`
+ : 'welcome-email-sent';
+ const { value: welcomeEmailSent, setValue: setWelcomeEmailSent } = useLocalStorage({
+ defaultValue: false,
+ key: storageKey,
+ });
+
+ if (!welcomeEmailSent) {
+ await sendWelcomeEmail(serverUser, coupon?.name ?? '');
+ setWelcomeEmailSent(true);
+ }
+ }
+
+ // 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');
+ setCurrentStepState(STEPS.TIME_COMMITMENT);
+ } else {
+ setCurrentStepState(stepConfig[STEPS.USER_DETAILS].next as StepKey);
}
+ return;
+ }
- 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
+ // Handle initial questions step
+ if (currentStep === STEPS.INITIAL_QUESTIONS) {
const userXpValue = typeof totalXp === 'number' && !isNaN(totalXp) ? totalXp : 0;
try {
@@ -162,24 +153,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 === 'ROADMAP_PAGE') {
+ localStorage.removeItem('onboarding');
+ router.push('/roadmaps?onboarding=true');
+ } else {
+ setCurrentStepState(nextStep as StepKey);
}
} catch (error) {
console.error('Error during continue:', error);
@@ -189,17 +179,22 @@ export function useOnboardingSteps() {
};
const handleBack = () => {
- // 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);
+ // if we're on the first step, we shouldn't go back
+ if (currentStep === STEPS.USER_DETAILS) {
return;
}
+ // 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);
}
};
@@ -209,7 +204,24 @@ 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 (USER_DETAILS)
+ if (currentStep === STEPS.USER_DETAILS) {
+ return false;
+ }
+
+ // Show back button on all other steps
+ return true;
};
return {
@@ -219,6 +231,7 @@ export function useOnboardingSteps() {
handleContinue,
handleBack,
showSkipButton,
+ showBackButton,
stepConfig,
};
}
diff --git a/src/lib/onborda.tsx b/src/lib/onborda.tsx
index 643794fa6..1d5b3cbbb 100644
--- a/src/lib/onborda.tsx
+++ b/src/lib/onborda.tsx
@@ -1,6 +1,6 @@
import { Tour } from 'onborda/dist/types';
-export const steps = (): Tour[] => [
+export const questionPageSteps = (): Tour[] => [
{
tour: 'question-tour',
steps: [
@@ -76,3 +76,39 @@ export const steps = (): Tour[] => [
],
},
];
+
+export const roadmapPageSteps = (nextRoute: string, prevRoute: string): Tour[] => [
+ {
+ tour: 'roadmap-tour',
+ steps: [
+ {
+ 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',
+ nextRoute,
+ },
+ {
+ 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,
+ pointerPadding: -1,
+ pointerRadius: 24,
+ icon: <>>,
+ title: 'Roadmap Description',
+ prevRoute,
+ },
+ ],
+ },
+];
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;