Skip to content

Commit 31918f7

Browse files
feat: uniform post page login (#4046)
* redirect on social click * handle email case * add logic for banner as well * linting memes * lint * linter decided to not work anymore :) * update loginbutton test * update trigger type * import as type * keep old params * delete authmodal * useAuthContext * refactor showLogin * add after auth param key * remove toString on params * remove auth trigger param from onboarding * redirect correclty to signin page * update test * lint
1 parent 7b7ec38 commit 31918f7

File tree

10 files changed

+123
-172
lines changed

10 files changed

+123
-172
lines changed

packages/extension/src/newtab/App.tsx

+2-21
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,10 @@
11
import type { ReactElement } from 'react';
2-
import React, { useCallback, useContext, useEffect, useState } from 'react';
2+
import React, { useCallback, useEffect, useState } from 'react';
33
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
4-
import dynamic from 'next/dynamic';
54
import Modal from 'react-modal';
65
import 'focus-visible';
76
import { ProgressiveEnhancementContextProvider } from '@dailydotdev/shared/src/contexts/ProgressiveEnhancementContext';
8-
import AuthContext, {
9-
useAuthContext,
10-
} from '@dailydotdev/shared/src/contexts/AuthContext';
7+
import { useAuthContext } from '@dailydotdev/shared/src/contexts/AuthContext';
118
import { SubscriptionContextProvider } from '@dailydotdev/shared/src/contexts/SubscriptionContext';
129
import browser from 'webextension-polyfill';
1310
import type { BootDataProviderProps } from '@dailydotdev/shared/src/contexts/BootProvider';
@@ -53,13 +50,6 @@ structuredCloneJsonPolyfill();
5350
const DEFAULT_TAB_TITLE = 'New Tab';
5451
const router = new CustomRouter();
5552
const queryClient = new QueryClient(defaultQueryClientConfig);
56-
const AuthModal = dynamic(
57-
() =>
58-
import(
59-
/* webpackChunkName: "authModal" */ '@dailydotdev/shared/src/components/auth/AuthModal'
60-
),
61-
);
62-
6353
Modal.setAppElement('#__next');
6454
Modal.defaultStyles = {};
6555

@@ -75,7 +65,6 @@ function InternalApp(): ReactElement {
7565
null,
7666
);
7767
const { unreadCount } = useNotificationContext();
78-
const { closeLogin, shouldShowLogin, loginState } = useContext(AuthContext);
7968
const { contentScriptGranted } = useContentScriptStatus();
8069
const { hostGranted, isFetching: isCheckingHostPermissions } =
8170
useHostStatus();
@@ -147,14 +136,6 @@ function InternalApp(): ReactElement {
147136
return (
148137
<DndContextProvider>
149138
<MainFeedPage onPageChanged={onPageChanged} />
150-
{shouldShowLogin && (
151-
<AuthModal
152-
isOpen={shouldShowLogin}
153-
onRequestClose={closeLogin}
154-
contentLabel="Login Modal"
155-
{...loginState}
156-
/>
157-
)}
158139
</DndContextProvider>
159140
);
160141
}
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,73 @@
11
/* eslint-disable no-console */
22
import React from 'react';
33
import type { RenderResult } from '@testing-library/react';
4-
import { fireEvent, render, screen } from '@testing-library/react';
4+
import { fireEvent, render, screen, act } from '@testing-library/react';
55
import { QueryClient } from '@tanstack/react-query';
66
import LoginButton from './LoginButton';
77
import { TestBootProvider } from '../../__tests__/helpers/boot';
88
import type { LoggedUser } from '../lib/user';
9+
import { AuthContextProvider } from '../contexts/AuthContext';
10+
import { AuthTriggers } from '../lib/auth';
911

1012
describe('LoginButton', () => {
1113
const showLogin = jest.fn();
14+
const logEvent = jest.fn();
1215

1316
beforeEach(() => {
1417
showLogin.mockReset();
18+
logEvent.mockReset();
1519
});
1620

1721
const renderLayout = (user: LoggedUser = null): RenderResult => {
1822
const client = new QueryClient();
1923

2024
return render(
21-
<TestBootProvider client={client} auth={{ user, showLogin }}>
22-
<LoginButton />
23-
</TestBootProvider>,
25+
<AuthContextProvider
26+
user={user}
27+
updateUser={jest.fn()}
28+
tokenRefreshed
29+
getRedirectUri={jest.fn()}
30+
loadingUser={false}
31+
loadedUserFromCache
32+
>
33+
<TestBootProvider client={client} auth={{ user, showLogin }}>
34+
<LoginButton />
35+
</TestBootProvider>
36+
</AuthContextProvider>,
2437
);
2538
};
2639

27-
it('should show login when clicking on the button', async () => {
40+
it('should call showLogin when clicking the login button', async () => {
2841
renderLayout();
29-
const el = await screen.findByText('Log in');
42+
const loginButton = await screen.findByText('Log in');
43+
expect(loginButton).toBeInTheDocument();
3044

31-
fireEvent.click(el);
45+
await act(async () => {
46+
fireEvent.click(loginButton);
47+
});
3248

33-
expect(showLogin).toBeCalledTimes(1);
49+
expect(showLogin).toHaveBeenCalledWith({
50+
trigger: AuthTriggers.MainButton,
51+
options: {
52+
isLogin: true,
53+
},
54+
});
55+
});
56+
57+
it('should call showLogin when clicking the signup button', async () => {
58+
renderLayout();
59+
const signupButton = await screen.findByText('Sign up');
60+
expect(signupButton).toBeInTheDocument();
61+
62+
await act(async () => {
63+
fireEvent.click(signupButton);
64+
});
65+
66+
expect(showLogin).toHaveBeenCalledWith({
67+
trigger: AuthTriggers.MainButton,
68+
options: {
69+
isLogin: false,
70+
},
71+
});
3472
});
3573
});

packages/shared/src/components/LoginButton.tsx

+6-5
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,11 @@ import type { ReactElement } from 'react';
22
import React, { useContext } from 'react';
33
import classNames from 'classnames';
44
import { Button, ButtonVariant } from './buttons/Button';
5-
import AuthContext from '../contexts/AuthContext';
65
import LogContext from '../contexts/LogContext';
76
import type { LogEvent } from '../hooks/log/useLogQueue';
8-
import { AuthTriggers } from '../lib/auth';
97
import { TargetType } from '../lib/log';
8+
import { useAuthContext } from '../contexts/AuthContext';
9+
import { AuthTriggers } from '../lib/auth';
1010

1111
interface ClassName {
1212
container?: string;
@@ -33,14 +33,15 @@ const getLogEvent = (copy: ButtonCopy): LogEvent => ({
3333
export default function LoginButton({
3434
className = {},
3535
}: LoginButtonProps): ReactElement {
36-
const { showLogin } = useContext(AuthContext);
3736
const { logEvent } = useContext(LogContext);
38-
37+
const { showLogin } = useAuthContext();
3938
const onClick = (copy: ButtonCopy) => {
4039
logEvent(getLogEvent(copy));
4140
showLogin({
4241
trigger: AuthTriggers.MainButton,
43-
options: { isLogin: copy === ButtonCopy.Login },
42+
options: {
43+
isLogin: copy === ButtonCopy.Login,
44+
},
4445
});
4546
};
4647

packages/shared/src/components/auth/AuthModal.tsx

-88
This file was deleted.

packages/shared/src/components/auth/AuthenticationBanner.tsx

+4-8
Original file line numberDiff line numberDiff line change
@@ -58,16 +58,12 @@ export function AuthenticationBanner({
5858
className={{
5959
onboardingSignup: '!gap-4',
6060
}}
61-
onAuthStateUpdate={(props) =>
61+
onAuthStateUpdate={(props) => {
6262
showLogin({
6363
trigger: AuthTriggers.Onboarding,
64-
options: {
65-
formValues: {
66-
email: props?.email,
67-
},
68-
},
69-
})
70-
}
64+
options: { isLogin: true, formValues: props },
65+
});
66+
}}
7167
onboardingSignupButton={{
7268
variant: ButtonVariant.Primary,
7369
}}

packages/shared/src/components/auth/common.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ export interface Provider {
99
value: string;
1010
}
1111

12+
export const AFTER_AUTH_PARAM = 'after_auth';
13+
1214
export enum SocialProvider {
1315
// Twitter = 'twitter',
1416
Facebook = 'facebook',

packages/shared/src/contexts/AuthContext.tsx

+21-10
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { ReactElement, ReactNode } from 'react';
22
import React, { useCallback, useContext, useState } from 'react';
33
import type { QueryObserverResult } from '@tanstack/react-query';
4+
import { useRouter } from 'next/router';
45
import type { AnonymousUser, LoggedUser } from '../lib/user';
56
import {
67
deleteAccount,
@@ -13,6 +14,7 @@ import type { AuthTriggersType } from '../lib/auth';
1314
import { AuthTriggers } from '../lib/auth';
1415
import type { Squad } from '../graphql/sources';
1516
import { checkIsExtension, isNullOrUndefined } from '../lib/func';
17+
import { AFTER_AUTH_PARAM } from '../components/auth/common';
1618
import { Continent, outsideGdpr } from '../lib/geo';
1719

1820
export interface LoginState {
@@ -140,7 +142,7 @@ export const AuthContextProvider = ({
140142
const endUser = user && 'providers' in user ? user : null;
141143
const referral = user?.referralId || user?.referrer;
142144
const referralOrigin = user?.referralOrigin;
143-
145+
const router = useRouter();
144146
if (firstLoad === true && endUser && !endUser?.infoConfirmed) {
145147
logout(LogoutReason.IncomleteOnboarding);
146148
}
@@ -160,15 +162,24 @@ export const AuthContextProvider = ({
160162
firstVisit: user?.firstVisit,
161163
trackingId: user?.id,
162164
shouldShowLogin: loginState !== null,
163-
showLogin: useCallback(({ trigger, options = {} }) => {
164-
const hasCompanion = !!isCompanionActivated();
165-
if (hasCompanion) {
166-
const signup = `${process.env.NEXT_PUBLIC_WEBAPP_URL}signup?close=true`;
167-
window.open(signup);
168-
} else {
169-
setLoginState({ ...options, trigger });
170-
}
171-
}, []),
165+
showLogin: useCallback(
166+
({ trigger, options = {} }) => {
167+
const hasCompanion = !!isCompanionActivated();
168+
if (hasCompanion) {
169+
const signup = `${process.env.NEXT_PUBLIC_WEBAPP_URL}signup?close=true`;
170+
window.open(signup);
171+
} else {
172+
const params = new URLSearchParams(globalThis?.location.search);
173+
174+
setLoginState({ ...options, trigger });
175+
if (!params.get(AFTER_AUTH_PARAM)) {
176+
params.set(AFTER_AUTH_PARAM, window.location.pathname);
177+
}
178+
router.push(`/onboarding?${params.toString()}`);
179+
}
180+
},
181+
[router],
182+
),
172183
closeLogin: useCallback(() => setLoginState(null), []),
173184
loginState,
174185
updateUser,

packages/shared/src/hooks/useRegistration.ts

+8-2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import { getUserDefaultTimezone } from '../lib/timezones';
3030
import LogContext from '../contexts/LogContext';
3131
import { Origin } from '../lib/log';
3232
import { LogoutReason } from '../lib/user';
33+
import { AFTER_AUTH_PARAM } from '../components/auth/common';
3334

3435
type ParamKeys = keyof RegistrationParameters;
3536

@@ -86,11 +87,16 @@ const useRegistration = ({
8687
origin: Origin.InitializeRegistrationFlow,
8788
}),
8889
});
90+
const params = new URLSearchParams(window.location.search);
91+
const afterAuth = params.get(AFTER_AUTH_PARAM);
8992
/**
90-
* In case a valid session exists on kratos, but not FE we should logout the user
93+
* In case a valid session exists on kratos, but not FE we should logout the user.
94+
* We ignore it if 'after_auth' param exists, because it means we manually redirected the user here, and that will trigger this error.
9195
*/
9296
if (
93-
registration.error?.id === KRATOS_ERROR_MESSAGE.SESSION_ALREADY_AVAILABLE
97+
registration.error?.id ===
98+
KRATOS_ERROR_MESSAGE.SESSION_ALREADY_AVAILABLE &&
99+
!afterAuth
94100
) {
95101
logout(LogoutReason.KratosSessionAlreadyAvailable);
96102
}

0 commit comments

Comments
 (0)