diff --git a/src/components/AuthenticationFlow/EmailLoginForm.tsx b/src/components/AuthenticationFlow/EmailLoginForm.tsx
index ca99e837b..2907433fb 100644
--- a/src/components/AuthenticationFlow/EmailLoginForm.tsx
+++ b/src/components/AuthenticationFlow/EmailLoginForm.tsx
@@ -2,7 +2,7 @@ import Cookies from 'js-cookie';
import type { FormEvent } from 'react';
import { useId, useState } from 'react';
import { httpPost } from '../../lib/http';
-import { TOKEN_COOKIE_NAME, setAuthToken } from '../../lib/jwt';
+import { FIRST_LOGIN_PARAM, setAuthToken } from '../../lib/jwt';
type EmailLoginFormProps = {
isDisabled?: boolean;
@@ -24,19 +24,24 @@ export function EmailLoginForm(props: EmailLoginFormProps) {
setIsDisabled?.(true);
setError('');
- const { response, error } = await httpPost<{ token: string }>(
- `${import.meta.env.PUBLIC_API_URL}/v1-login`,
- {
- email,
- password,
- },
- );
+ const { response, error } = await httpPost<{
+ token: string;
+ isNewUser: boolean;
+ }>(`${import.meta.env.PUBLIC_API_URL}/v1-login`, {
+ email,
+ password,
+ });
// Log the user in and reload the page
if (response?.token) {
setAuthToken(response.token);
- window.location.reload();
+ const currentLocation = window.location.href;
+ const url = new URL(currentLocation, window.location.origin);
+ if (response?.isNewUser) {
+ url.searchParams.set(FIRST_LOGIN_PARAM, '1');
+ }
+ window.location.href = url.toString();
return;
}
diff --git a/src/components/AuthenticationFlow/GitHubButton.tsx b/src/components/AuthenticationFlow/GitHubButton.tsx
index 48b0d0d08..5ea5017e2 100644
--- a/src/components/AuthenticationFlow/GitHubButton.tsx
+++ b/src/components/AuthenticationFlow/GitHubButton.tsx
@@ -1,8 +1,12 @@
import { useEffect, useState } from 'react';
+import { GitHubIcon } from '../ReactIcons/GitHubIcon.tsx';
+import {
+ FIRST_LOGIN_PARAM,
+ COURSE_PURCHASE_PARAM,
+ setAuthToken,
+} from '../../lib/jwt';
import { cn } from '../../../editor/utils/classname.ts';
import { httpGet } from '../../lib/http';
-import { COURSE_PURCHASE_PARAM, setAuthToken } from '../../lib/jwt';
-import { GitHubIcon } from '../ReactIcons/GitHubIcon.tsx';
import { Spinner } from '../ReactIcons/Spinner.tsx';
import { CHECKOUT_AFTER_LOGIN_KEY } from './CourseLoginPopup.tsx';
import { triggerUtmRegistration } from '../../lib/browser.ts';
@@ -34,7 +38,7 @@ export function GitHubButton(props: GitHubButtonProps) {
setIsLoading(true);
setIsDisabled?.(true);
- httpGet<{ token: string }>(
+ httpGet<{ token: string; isNewUser: boolean }>(
`${import.meta.env.PUBLIC_API_URL}/v1-github-callback${
window.location.search
}`,
@@ -51,7 +55,7 @@ export function GitHubButton(props: GitHubButtonProps) {
triggerUtmRegistration();
- let redirectUrl = '/';
+ let redirectUrl = new URL('/', window.location.origin);
const gitHubRedirectAt = localStorage.getItem(GITHUB_REDIRECT_AT);
const lastPageBeforeGithub = localStorage.getItem(GITHUB_LAST_PAGE);
@@ -63,31 +67,36 @@ export function GitHubButton(props: GitHubButtonProps) {
const timeSinceRedirect = now - socialRedirectAtTime;
if (timeSinceRedirect < 30 * 1000) {
- redirectUrl = lastPageBeforeGithub;
+ redirectUrl = new URL(lastPageBeforeGithub, window.location.origin);
}
}
const authRedirectUrl = localStorage.getItem('authRedirect');
if (authRedirectUrl) {
localStorage.removeItem('authRedirect');
- redirectUrl = authRedirectUrl;
+ redirectUrl = new URL(authRedirectUrl, window.location.origin);
}
localStorage.removeItem(GITHUB_REDIRECT_AT);
localStorage.removeItem(GITHUB_LAST_PAGE);
setAuthToken(response.token);
+ if (response?.isNewUser) {
+ redirectUrl.searchParams.set(FIRST_LOGIN_PARAM, '1');
+ }
+
const shouldTriggerPurchase =
localStorage.getItem(CHECKOUT_AFTER_LOGIN_KEY) !== '0';
- if (redirectUrl.includes('/courses/sql') && shouldTriggerPurchase) {
- const tempUrl = new URL(redirectUrl, window.location.origin);
- tempUrl.searchParams.set(COURSE_PURCHASE_PARAM, '1');
- redirectUrl = tempUrl.toString();
+ if (
+ redirectUrl.pathname.includes('/courses/sql') &&
+ shouldTriggerPurchase
+ ) {
+ redirectUrl.searchParams.set(COURSE_PURCHASE_PARAM, '1');
localStorage.removeItem(CHECKOUT_AFTER_LOGIN_KEY);
}
- window.location.href = redirectUrl;
+ window.location.href = redirectUrl.toString();
})
.catch((err) => {
setError('Something went wrong. Please try again later.');
diff --git a/src/components/AuthenticationFlow/GoogleButton.tsx b/src/components/AuthenticationFlow/GoogleButton.tsx
index 8453b0afc..7af4a8d24 100644
--- a/src/components/AuthenticationFlow/GoogleButton.tsx
+++ b/src/components/AuthenticationFlow/GoogleButton.tsx
@@ -1,7 +1,12 @@
import { useEffect, useState } from 'react';
-import { cn } from '../../lib/classname.ts';
+import Cookies from 'js-cookie';
+import {
+ FIRST_LOGIN_PARAM,
+ TOKEN_COOKIE_NAME,
+ setAuthToken,
+} from '../../lib/jwt';
import { httpGet } from '../../lib/http';
-import { COURSE_PURCHASE_PARAM, setAuthToken } from '../../lib/jwt';
+import { COURSE_PURCHASE_PARAM } from '../../lib/jwt';
import { GoogleIcon } from '../ReactIcons/GoogleIcon.tsx';
import { Spinner } from '../ReactIcons/Spinner.tsx';
import { CHECKOUT_AFTER_LOGIN_KEY } from './CourseLoginPopup.tsx';
@@ -9,6 +14,7 @@ import {
getStoredUtmParams,
triggerUtmRegistration,
} from '../../lib/browser.ts';
+import { cn } from '../../lib/classname.ts';
type GoogleButtonProps = {
isDisabled?: boolean;
@@ -37,14 +43,12 @@ export function GoogleButton(props: GoogleButtonProps) {
setIsLoading(true);
setIsDisabled?.(true);
- httpGet<{ token: string }>(
+ httpGet<{ token: string; isNewUser: boolean }>(
`${import.meta.env.PUBLIC_API_URL}/v1-google-callback${
window.location.search
}`,
)
.then(({ response, error }) => {
- const utmParams = getStoredUtmParams();
-
if (!response?.token) {
setError(error?.message || 'Something went wrong.');
setIsLoading(false);
@@ -55,7 +59,7 @@ export function GoogleButton(props: GoogleButtonProps) {
triggerUtmRegistration();
- let redirectUrl = '/';
+ let redirectUrl = new URL('/', window.location.origin);
const googleRedirectAt = localStorage.getItem(GOOGLE_REDIRECT_AT);
const lastPageBeforeGoogle = localStorage.getItem(GOOGLE_LAST_PAGE);
@@ -67,22 +71,27 @@ export function GoogleButton(props: GoogleButtonProps) {
const timeSinceRedirect = now - socialRedirectAtTime;
if (timeSinceRedirect < 30 * 1000) {
- redirectUrl = lastPageBeforeGoogle;
+ redirectUrl = new URL(lastPageBeforeGoogle, window.location.origin);
}
}
const authRedirectUrl = localStorage.getItem('authRedirect');
if (authRedirectUrl) {
localStorage.removeItem('authRedirect');
- redirectUrl = authRedirectUrl;
+ redirectUrl = new URL(authRedirectUrl, window.location.origin);
+ }
+
+ if (response?.isNewUser) {
+ redirectUrl.searchParams.set(FIRST_LOGIN_PARAM, '1');
}
const shouldTriggerPurchase =
localStorage.getItem(CHECKOUT_AFTER_LOGIN_KEY) !== '0';
- if (redirectUrl.includes('/courses/sql') && shouldTriggerPurchase) {
- const tempUrl = new URL(redirectUrl, window.location.origin);
- tempUrl.searchParams.set(COURSE_PURCHASE_PARAM, '1');
- redirectUrl = tempUrl.toString();
+ if (
+ redirectUrl.pathname.includes('/courses/sql') &&
+ shouldTriggerPurchase
+ ) {
+ redirectUrl.searchParams.set(COURSE_PURCHASE_PARAM, '1');
localStorage.removeItem(CHECKOUT_AFTER_LOGIN_KEY);
}
@@ -90,7 +99,8 @@ export function GoogleButton(props: GoogleButtonProps) {
localStorage.removeItem(GOOGLE_REDIRECT_AT);
localStorage.removeItem(GOOGLE_LAST_PAGE);
setAuthToken(response.token);
- window.location.href = redirectUrl;
+
+ window.location.href = redirectUrl.toString();
})
.catch((err) => {
setError('Something went wrong. Please try again later.');
diff --git a/src/components/AuthenticationFlow/LinkedInButton.tsx b/src/components/AuthenticationFlow/LinkedInButton.tsx
index 9a3b378c9..32c6e2b8d 100644
--- a/src/components/AuthenticationFlow/LinkedInButton.tsx
+++ b/src/components/AuthenticationFlow/LinkedInButton.tsx
@@ -1,7 +1,13 @@
import { useEffect, useState } from 'react';
+import Cookies from 'js-cookie';
+import {
+ FIRST_LOGIN_PARAM,
+ COURSE_PURCHASE_PARAM,
+ TOKEN_COOKIE_NAME,
+ setAuthToken,
+} from '../../lib/jwt';
import { cn } from '../../lib/classname.ts';
import { httpGet } from '../../lib/http';
-import { COURSE_PURCHASE_PARAM, setAuthToken } from '../../lib/jwt';
import { LinkedInIcon } from '../ReactIcons/LinkedInIcon.tsx';
import { Spinner } from '../ReactIcons/Spinner.tsx';
import { CHECKOUT_AFTER_LOGIN_KEY } from './CourseLoginPopup.tsx';
@@ -34,7 +40,7 @@ export function LinkedInButton(props: LinkedInButtonProps) {
setIsLoading(true);
setIsDisabled?.(true);
- httpGet<{ token: string }>(
+ httpGet<{ token: string; isNewUser: boolean }>(
`${import.meta.env.PUBLIC_API_URL}/v1-linkedin-callback${
window.location.search
}`,
@@ -50,7 +56,7 @@ export function LinkedInButton(props: LinkedInButtonProps) {
triggerUtmRegistration();
- let redirectUrl = '/';
+ let redirectUrl = new URL('/', window.location.origin);
const linkedInRedirectAt = localStorage.getItem(LINKEDIN_REDIRECT_AT);
const lastPageBeforeLinkedIn = localStorage.getItem(LINKEDIN_LAST_PAGE);
@@ -62,30 +68,38 @@ export function LinkedInButton(props: LinkedInButtonProps) {
const timeSinceRedirect = now - socialRedirectAtTime;
if (timeSinceRedirect < 30 * 1000) {
- redirectUrl = lastPageBeforeLinkedIn;
+ redirectUrl = new URL(
+ lastPageBeforeLinkedIn,
+ window.location.origin,
+ );
}
}
const authRedirectUrl = localStorage.getItem('authRedirect');
if (authRedirectUrl) {
localStorage.removeItem('authRedirect');
- redirectUrl = authRedirectUrl;
+ redirectUrl = new URL(authRedirectUrl, window.location.origin);
+ }
+
+ if (response?.isNewUser) {
+ redirectUrl.searchParams.set(FIRST_LOGIN_PARAM, '1');
}
const shouldTriggerPurchase =
localStorage.getItem(CHECKOUT_AFTER_LOGIN_KEY) !== '0';
- if (redirectUrl.includes('/courses/sql') && shouldTriggerPurchase) {
- const tempUrl = new URL(redirectUrl, window.location.origin);
- tempUrl.searchParams.set(COURSE_PURCHASE_PARAM, '1');
- redirectUrl = tempUrl.toString();
-
+ if (
+ redirectUrl.pathname.includes('/courses/sql') &&
+ shouldTriggerPurchase
+ ) {
+ redirectUrl.searchParams.set(COURSE_PURCHASE_PARAM, '1');
localStorage.removeItem(CHECKOUT_AFTER_LOGIN_KEY);
}
localStorage.removeItem(LINKEDIN_REDIRECT_AT);
localStorage.removeItem(LINKEDIN_LAST_PAGE);
setAuthToken(response.token);
- window.location.href = redirectUrl;
+
+ window.location.href = redirectUrl.toString();
})
.catch((err) => {
setError('Something went wrong. Please try again later.');
diff --git a/src/components/AuthenticationFlow/TriggerVerifyAccount.tsx b/src/components/AuthenticationFlow/TriggerVerifyAccount.tsx
index 0978e38d1..2524645f6 100644
--- a/src/components/AuthenticationFlow/TriggerVerifyAccount.tsx
+++ b/src/components/AuthenticationFlow/TriggerVerifyAccount.tsx
@@ -1,7 +1,7 @@
import { useEffect, useState } from 'react';
import Cookies from 'js-cookie';
import { httpPost } from '../../lib/http';
-import { TOKEN_COOKIE_NAME, setAuthToken } from '../../lib/jwt';
+import { FIRST_LOGIN_PARAM, TOKEN_COOKIE_NAME, setAuthToken } from '../../lib/jwt';
import { Spinner } from '../ReactIcons/Spinner';
import { ErrorIcon2 } from '../ReactIcons/ErrorIcon2';
import { triggerUtmRegistration } from '../../lib/browser.ts';
@@ -13,7 +13,7 @@ export function TriggerVerifyAccount() {
const triggerVerify = (code: string) => {
setIsLoading(true);
- httpPost<{ token: string }>(
+ httpPost<{ token: string; isNewUser: boolean }>(
`${import.meta.env.PUBLIC_API_URL}/v1-verify-account`,
{
code,
@@ -30,7 +30,12 @@ export function TriggerVerifyAccount() {
triggerUtmRegistration();
setAuthToken(response.token);
- window.location.href = '/';
+
+ const url = new URL('/', window.location.origin);
+ if (response?.isNewUser) {
+ url.searchParams.set(FIRST_LOGIN_PARAM, '1');
+ }
+ window.location.href = url.toString();
})
.catch((err) => {
setIsLoading(false);
diff --git a/src/components/ReactIcons/HackerNewsIcon.tsx b/src/components/ReactIcons/HackerNewsIcon.tsx
new file mode 100644
index 000000000..bd2994df2
--- /dev/null
+++ b/src/components/ReactIcons/HackerNewsIcon.tsx
@@ -0,0 +1,20 @@
+import { cn } from '../../lib/classname';
+
+interface HackerNewsIconProps {
+ className?: string;
+}
+
+export function HackerNewsIcon(props: HackerNewsIconProps) {
+ const { className } = props;
+
+ return (
+
+ );
+}
diff --git a/src/components/ReactIcons/RedditIcon.tsx b/src/components/ReactIcons/RedditIcon.tsx
new file mode 100644
index 000000000..506615454
--- /dev/null
+++ b/src/components/ReactIcons/RedditIcon.tsx
@@ -0,0 +1,20 @@
+import { cn } from '../../lib/classname';
+
+interface RedditIconProps {
+ className?: string;
+}
+
+export function RedditIcon(props: RedditIconProps) {
+ const { className } = props;
+
+ return (
+
+ );
+}
diff --git a/src/components/ReactIcons/TwitterIcon.tsx b/src/components/ReactIcons/TwitterIcon.tsx
index 05e6a5683..bb8c1f3d6 100644
--- a/src/components/ReactIcons/TwitterIcon.tsx
+++ b/src/components/ReactIcons/TwitterIcon.tsx
@@ -18,7 +18,7 @@ export function TwitterIcon(props: TwitterIconProps) {
Frontend development is a vast field with a lot of tools and - technologies. We have the frontend roadmap - which is filled with a lot of free and good resources to help you learn. But sometimes it helps to have a minimalistic list of courses - and project recommendations to help you get started. + technologies. We have the frontend roadmap + which is filled with a lot of free and good resources to help you learn. But sometimes it helps to have a minimalistic + list of courses and project recommendations to help you get started.
-- Below are some of the best courses (paid) and projects to help you learn frontend development. These are handpicked and are a great way to get started. +
+ Below are some of the best courses (paid) and projects to help you + learn frontend development. These are handpicked and are a great way + to get started.
- Please note that these are paid courses curated from external platforms. We earn a small commission if you purchase the course using the links below. This helps us maintain the website and keep it free for everyone. + Please note that these are paid courses curated from external + platforms. We earn a small commission if you purchase the course + using the links below. This helps us maintain the website and keep + it free for everyone.
- If you are looking for free resources, you can check out the frontend roadmap. Also, we have a list of projects that you can work on to enhance your skills. + If you are looking for free resources, you can check out the frontend roadmap. Also, we have a list of projects that you can work on to enhance your skills.