Skip to content

Commit 1ffa292

Browse files
feat: new user flag (kamranahmedse#8070)
* feat: new user flag * feat: share icon event * fix: upload the query tag * fix: name and label --------- Co-authored-by: Kamran Ahmed <[email protected]>
1 parent 0bef28f commit 1ffa292

File tree

15 files changed

+271
-141
lines changed

15 files changed

+271
-141
lines changed

src/components/AuthenticationFlow/EmailLoginForm.tsx

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import Cookies from 'js-cookie';
22
import type { FormEvent } from 'react';
33
import { useId, useState } from 'react';
44
import { httpPost } from '../../lib/http';
5-
import { TOKEN_COOKIE_NAME, setAuthToken } from '../../lib/jwt';
5+
import { FIRST_LOGIN_PARAM, setAuthToken } from '../../lib/jwt';
66

77
type EmailLoginFormProps = {
88
isDisabled?: boolean;
@@ -24,19 +24,24 @@ export function EmailLoginForm(props: EmailLoginFormProps) {
2424
setIsDisabled?.(true);
2525
setError('');
2626

27-
const { response, error } = await httpPost<{ token: string }>(
28-
`${import.meta.env.PUBLIC_API_URL}/v1-login`,
29-
{
30-
email,
31-
password,
32-
},
33-
);
27+
const { response, error } = await httpPost<{
28+
token: string;
29+
isNewUser: boolean;
30+
}>(`${import.meta.env.PUBLIC_API_URL}/v1-login`, {
31+
email,
32+
password,
33+
});
3434

3535
// Log the user in and reload the page
3636
if (response?.token) {
3737
setAuthToken(response.token);
38-
window.location.reload();
3938

39+
const currentLocation = window.location.href;
40+
const url = new URL(currentLocation, window.location.origin);
41+
if (response?.isNewUser) {
42+
url.searchParams.set(FIRST_LOGIN_PARAM, '1');
43+
}
44+
window.location.href = url.toString();
4045
return;
4146
}
4247

src/components/AuthenticationFlow/GitHubButton.tsx

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,12 @@
11
import { useEffect, useState } from 'react';
2+
import { GitHubIcon } from '../ReactIcons/GitHubIcon.tsx';
3+
import {
4+
FIRST_LOGIN_PARAM,
5+
COURSE_PURCHASE_PARAM,
6+
setAuthToken,
7+
} from '../../lib/jwt';
28
import { cn } from '../../../editor/utils/classname.ts';
39
import { httpGet } from '../../lib/http';
4-
import { COURSE_PURCHASE_PARAM, setAuthToken } from '../../lib/jwt';
5-
import { GitHubIcon } from '../ReactIcons/GitHubIcon.tsx';
610
import { Spinner } from '../ReactIcons/Spinner.tsx';
711
import { CHECKOUT_AFTER_LOGIN_KEY } from './CourseLoginPopup.tsx';
812
import { triggerUtmRegistration } from '../../lib/browser.ts';
@@ -34,7 +38,7 @@ export function GitHubButton(props: GitHubButtonProps) {
3438

3539
setIsLoading(true);
3640
setIsDisabled?.(true);
37-
httpGet<{ token: string }>(
41+
httpGet<{ token: string; isNewUser: boolean }>(
3842
`${import.meta.env.PUBLIC_API_URL}/v1-github-callback${
3943
window.location.search
4044
}`,
@@ -51,7 +55,7 @@ export function GitHubButton(props: GitHubButtonProps) {
5155

5256
triggerUtmRegistration();
5357

54-
let redirectUrl = '/';
58+
let redirectUrl = new URL('/', window.location.origin);
5559
const gitHubRedirectAt = localStorage.getItem(GITHUB_REDIRECT_AT);
5660
const lastPageBeforeGithub = localStorage.getItem(GITHUB_LAST_PAGE);
5761

@@ -63,31 +67,36 @@ export function GitHubButton(props: GitHubButtonProps) {
6367
const timeSinceRedirect = now - socialRedirectAtTime;
6468

6569
if (timeSinceRedirect < 30 * 1000) {
66-
redirectUrl = lastPageBeforeGithub;
70+
redirectUrl = new URL(lastPageBeforeGithub, window.location.origin);
6771
}
6872
}
6973

7074
const authRedirectUrl = localStorage.getItem('authRedirect');
7175
if (authRedirectUrl) {
7276
localStorage.removeItem('authRedirect');
73-
redirectUrl = authRedirectUrl;
77+
redirectUrl = new URL(authRedirectUrl, window.location.origin);
7478
}
7579

7680
localStorage.removeItem(GITHUB_REDIRECT_AT);
7781
localStorage.removeItem(GITHUB_LAST_PAGE);
7882
setAuthToken(response.token);
7983

84+
if (response?.isNewUser) {
85+
redirectUrl.searchParams.set(FIRST_LOGIN_PARAM, '1');
86+
}
87+
8088
const shouldTriggerPurchase =
8189
localStorage.getItem(CHECKOUT_AFTER_LOGIN_KEY) !== '0';
82-
if (redirectUrl.includes('/courses/sql') && shouldTriggerPurchase) {
83-
const tempUrl = new URL(redirectUrl, window.location.origin);
84-
tempUrl.searchParams.set(COURSE_PURCHASE_PARAM, '1');
85-
redirectUrl = tempUrl.toString();
8690

91+
if (
92+
redirectUrl.pathname.includes('/courses/sql') &&
93+
shouldTriggerPurchase
94+
) {
95+
redirectUrl.searchParams.set(COURSE_PURCHASE_PARAM, '1');
8796
localStorage.removeItem(CHECKOUT_AFTER_LOGIN_KEY);
8897
}
8998

90-
window.location.href = redirectUrl;
99+
window.location.href = redirectUrl.toString();
91100
})
92101
.catch((err) => {
93102
setError('Something went wrong. Please try again later.');

src/components/AuthenticationFlow/GoogleButton.tsx

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,20 @@
11
import { useEffect, useState } from 'react';
2-
import { cn } from '../../lib/classname.ts';
2+
import Cookies from 'js-cookie';
3+
import {
4+
FIRST_LOGIN_PARAM,
5+
TOKEN_COOKIE_NAME,
6+
setAuthToken,
7+
} from '../../lib/jwt';
38
import { httpGet } from '../../lib/http';
4-
import { COURSE_PURCHASE_PARAM, setAuthToken } from '../../lib/jwt';
9+
import { COURSE_PURCHASE_PARAM } from '../../lib/jwt';
510
import { GoogleIcon } from '../ReactIcons/GoogleIcon.tsx';
611
import { Spinner } from '../ReactIcons/Spinner.tsx';
712
import { CHECKOUT_AFTER_LOGIN_KEY } from './CourseLoginPopup.tsx';
813
import {
914
getStoredUtmParams,
1015
triggerUtmRegistration,
1116
} from '../../lib/browser.ts';
17+
import { cn } from '../../lib/classname.ts';
1218

1319
type GoogleButtonProps = {
1420
isDisabled?: boolean;
@@ -37,14 +43,12 @@ export function GoogleButton(props: GoogleButtonProps) {
3743

3844
setIsLoading(true);
3945
setIsDisabled?.(true);
40-
httpGet<{ token: string }>(
46+
httpGet<{ token: string; isNewUser: boolean }>(
4147
`${import.meta.env.PUBLIC_API_URL}/v1-google-callback${
4248
window.location.search
4349
}`,
4450
)
4551
.then(({ response, error }) => {
46-
const utmParams = getStoredUtmParams();
47-
4852
if (!response?.token) {
4953
setError(error?.message || 'Something went wrong.');
5054
setIsLoading(false);
@@ -55,7 +59,7 @@ export function GoogleButton(props: GoogleButtonProps) {
5559

5660
triggerUtmRegistration();
5761

58-
let redirectUrl = '/';
62+
let redirectUrl = new URL('/', window.location.origin);
5963
const googleRedirectAt = localStorage.getItem(GOOGLE_REDIRECT_AT);
6064
const lastPageBeforeGoogle = localStorage.getItem(GOOGLE_LAST_PAGE);
6165

@@ -67,30 +71,36 @@ export function GoogleButton(props: GoogleButtonProps) {
6771
const timeSinceRedirect = now - socialRedirectAtTime;
6872

6973
if (timeSinceRedirect < 30 * 1000) {
70-
redirectUrl = lastPageBeforeGoogle;
74+
redirectUrl = new URL(lastPageBeforeGoogle, window.location.origin);
7175
}
7276
}
7377

7478
const authRedirectUrl = localStorage.getItem('authRedirect');
7579
if (authRedirectUrl) {
7680
localStorage.removeItem('authRedirect');
77-
redirectUrl = authRedirectUrl;
81+
redirectUrl = new URL(authRedirectUrl, window.location.origin);
82+
}
83+
84+
if (response?.isNewUser) {
85+
redirectUrl.searchParams.set(FIRST_LOGIN_PARAM, '1');
7886
}
7987

8088
const shouldTriggerPurchase =
8189
localStorage.getItem(CHECKOUT_AFTER_LOGIN_KEY) !== '0';
82-
if (redirectUrl.includes('/courses/sql') && shouldTriggerPurchase) {
83-
const tempUrl = new URL(redirectUrl, window.location.origin);
84-
tempUrl.searchParams.set(COURSE_PURCHASE_PARAM, '1');
85-
redirectUrl = tempUrl.toString();
90+
if (
91+
redirectUrl.pathname.includes('/courses/sql') &&
92+
shouldTriggerPurchase
93+
) {
94+
redirectUrl.searchParams.set(COURSE_PURCHASE_PARAM, '1');
8695

8796
localStorage.removeItem(CHECKOUT_AFTER_LOGIN_KEY);
8897
}
8998

9099
localStorage.removeItem(GOOGLE_REDIRECT_AT);
91100
localStorage.removeItem(GOOGLE_LAST_PAGE);
92101
setAuthToken(response.token);
93-
window.location.href = redirectUrl;
102+
103+
window.location.href = redirectUrl.toString();
94104
})
95105
.catch((err) => {
96106
setError('Something went wrong. Please try again later.');

src/components/AuthenticationFlow/LinkedInButton.tsx

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,13 @@
11
import { useEffect, useState } from 'react';
2+
import Cookies from 'js-cookie';
3+
import {
4+
FIRST_LOGIN_PARAM,
5+
COURSE_PURCHASE_PARAM,
6+
TOKEN_COOKIE_NAME,
7+
setAuthToken,
8+
} from '../../lib/jwt';
29
import { cn } from '../../lib/classname.ts';
310
import { httpGet } from '../../lib/http';
4-
import { COURSE_PURCHASE_PARAM, setAuthToken } from '../../lib/jwt';
511
import { LinkedInIcon } from '../ReactIcons/LinkedInIcon.tsx';
612
import { Spinner } from '../ReactIcons/Spinner.tsx';
713
import { CHECKOUT_AFTER_LOGIN_KEY } from './CourseLoginPopup.tsx';
@@ -34,7 +40,7 @@ export function LinkedInButton(props: LinkedInButtonProps) {
3440

3541
setIsLoading(true);
3642
setIsDisabled?.(true);
37-
httpGet<{ token: string }>(
43+
httpGet<{ token: string; isNewUser: boolean }>(
3844
`${import.meta.env.PUBLIC_API_URL}/v1-linkedin-callback${
3945
window.location.search
4046
}`,
@@ -50,7 +56,7 @@ export function LinkedInButton(props: LinkedInButtonProps) {
5056

5157
triggerUtmRegistration();
5258

53-
let redirectUrl = '/';
59+
let redirectUrl = new URL('/', window.location.origin);
5460
const linkedInRedirectAt = localStorage.getItem(LINKEDIN_REDIRECT_AT);
5561
const lastPageBeforeLinkedIn = localStorage.getItem(LINKEDIN_LAST_PAGE);
5662

@@ -62,30 +68,38 @@ export function LinkedInButton(props: LinkedInButtonProps) {
6268
const timeSinceRedirect = now - socialRedirectAtTime;
6369

6470
if (timeSinceRedirect < 30 * 1000) {
65-
redirectUrl = lastPageBeforeLinkedIn;
71+
redirectUrl = new URL(
72+
lastPageBeforeLinkedIn,
73+
window.location.origin,
74+
);
6675
}
6776
}
6877

6978
const authRedirectUrl = localStorage.getItem('authRedirect');
7079
if (authRedirectUrl) {
7180
localStorage.removeItem('authRedirect');
72-
redirectUrl = authRedirectUrl;
81+
redirectUrl = new URL(authRedirectUrl, window.location.origin);
82+
}
83+
84+
if (response?.isNewUser) {
85+
redirectUrl.searchParams.set(FIRST_LOGIN_PARAM, '1');
7386
}
7487

7588
const shouldTriggerPurchase =
7689
localStorage.getItem(CHECKOUT_AFTER_LOGIN_KEY) !== '0';
77-
if (redirectUrl.includes('/courses/sql') && shouldTriggerPurchase) {
78-
const tempUrl = new URL(redirectUrl, window.location.origin);
79-
tempUrl.searchParams.set(COURSE_PURCHASE_PARAM, '1');
80-
redirectUrl = tempUrl.toString();
81-
90+
if (
91+
redirectUrl.pathname.includes('/courses/sql') &&
92+
shouldTriggerPurchase
93+
) {
94+
redirectUrl.searchParams.set(COURSE_PURCHASE_PARAM, '1');
8295
localStorage.removeItem(CHECKOUT_AFTER_LOGIN_KEY);
8396
}
8497

8598
localStorage.removeItem(LINKEDIN_REDIRECT_AT);
8699
localStorage.removeItem(LINKEDIN_LAST_PAGE);
87100
setAuthToken(response.token);
88-
window.location.href = redirectUrl;
101+
102+
window.location.href = redirectUrl.toString();
89103
})
90104
.catch((err) => {
91105
setError('Something went wrong. Please try again later.');

src/components/AuthenticationFlow/TriggerVerifyAccount.tsx

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { useEffect, useState } from 'react';
22
import Cookies from 'js-cookie';
33
import { httpPost } from '../../lib/http';
4-
import { TOKEN_COOKIE_NAME, setAuthToken } from '../../lib/jwt';
4+
import { FIRST_LOGIN_PARAM, TOKEN_COOKIE_NAME, setAuthToken } from '../../lib/jwt';
55
import { Spinner } from '../ReactIcons/Spinner';
66
import { ErrorIcon2 } from '../ReactIcons/ErrorIcon2';
77
import { triggerUtmRegistration } from '../../lib/browser.ts';
@@ -13,7 +13,7 @@ export function TriggerVerifyAccount() {
1313
const triggerVerify = (code: string) => {
1414
setIsLoading(true);
1515

16-
httpPost<{ token: string }>(
16+
httpPost<{ token: string; isNewUser: boolean }>(
1717
`${import.meta.env.PUBLIC_API_URL}/v1-verify-account`,
1818
{
1919
code,
@@ -30,7 +30,12 @@ export function TriggerVerifyAccount() {
3030
triggerUtmRegistration();
3131

3232
setAuthToken(response.token);
33-
window.location.href = '/';
33+
34+
const url = new URL('/', window.location.origin);
35+
if (response?.isNewUser) {
36+
url.searchParams.set(FIRST_LOGIN_PARAM, '1');
37+
}
38+
window.location.href = url.toString();
3439
})
3540
.catch((err) => {
3641
setIsLoading(false);
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { cn } from '../../lib/classname';
2+
3+
interface HackerNewsIconProps {
4+
className?: string;
5+
}
6+
7+
export function HackerNewsIcon(props: HackerNewsIconProps) {
8+
const { className } = props;
9+
10+
return (
11+
<svg
12+
xmlns="http://www.w3.org/2000/svg"
13+
viewBox="0 0 448 512"
14+
fill="currentColor"
15+
className={cn('h-[26px] w-[26px]', className)}
16+
>
17+
<path d="M400 32H48C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48zM21.2 229.2H21c.1-.1.2-.3.3-.4 0 .1 0 .3-.1.4zm218 53.9V384h-31.4V281.3L128 128h37.3c52.5 98.3 49.2 101.2 59.3 125.6 12.3-27 5.8-24.4 60.6-125.6H320l-80.8 155.1z" />
18+
</svg>
19+
);
20+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { cn } from '../../lib/classname';
2+
3+
interface RedditIconProps {
4+
className?: string;
5+
}
6+
7+
export function RedditIcon(props: RedditIconProps) {
8+
const { className } = props;
9+
10+
return (
11+
<svg
12+
xmlns="http://www.w3.org/2000/svg"
13+
viewBox="0 0 448 512"
14+
fill="currentColor"
15+
className={cn('h-[26px] w-[26px]', className)}
16+
>
17+
<path d="M283.2 345.5c2.7 2.7 2.7 6.8 0 9.2-24.5 24.5-93.8 24.6-118.4 0-2.7-2.4-2.7-6.5 0-9.2 2.4-2.4 6.5-2.4 8.9 0 18.7 19.2 81 19.6 100.5 0 2.4-2.3 6.6-2.3 9 0zm-91.3-53.8c0-14.9-11.9-26.8-26.5-26.8a26.67 26.67 0 0 0-26.8 26.8c0 14.6 11.9 26.5 26.8 26.5 14.6 0 26.5-11.9 26.5-26.5zm90.7-26.8c-14.6 0-26.5 11.9-26.5 26.8 0 14.6 11.9 26.5 26.5 26.5 14.9 0 26.8-11.9 26.8-26.5a26.67 26.67 0 0 0-26.8-26.8zM448 80v352c0 26.5-21.5 48-48 48H48c-26.5 0-48-21.5-48-48V80c0-26.5 21.5-48 48-48h352c26.5 0 48 21.5 48 48zm-99.7 140.6c-10.1 0-19 4.2-25.6 10.7-24.1-16.7-56.5-27.4-92.5-28.6l18.7-84.2 59.5 13.4c0 14.6 11.9 26.5 26.5 26.5 14.9 0 26.8-12.2 26.8-26.8s-11.9-26.8-26.8-26.8c-10.4 0-19.3 6.2-23.8 14.9l-65.7-14.6c-3.3-.9-6.5 1.5-7.4 4.8l-20.5 92.8c-35.7 1.5-67.8 12.2-91.9 28.9-6.5-6.8-15.8-11-25.9-11-37.5 0-49.8 50.4-15.5 67.5-1.2 5.4-1.8 11-1.8 16.7 0 56.5 63.7 102.3 141.9 102.3 78.5 0 142.2-45.8 142.2-102.3 0-5.7-.6-11.6-2.1-17 33.6-17.2 21.2-67.2-16.1-67.2z" />
18+
</svg>
19+
);
20+
}

src/components/ReactIcons/TwitterIcon.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export function TwitterIcon(props: TwitterIconProps) {
1818
<rect width="23" height="23" rx="3" fill={boxColor} />
1919
<path
2020
d="M12.9285 10.3522L18.5135 4H17.1905L12.339 9.5144L8.467 4H4L9.8565 12.3395L4 19H5.323L10.443 13.1754L14.533 19H19M5.8005 4.97619H7.833L17.1895 18.0718H15.1565"
21-
fill='currentColor'
21+
fill="currentColor"
2222
/>
2323
</svg>
2424
);

0 commit comments

Comments
 (0)