Skip to content

Commit

Permalink
feat: Added referrer to account profile
Browse files Browse the repository at this point in the history
  • Loading branch information
josebui committed Mar 15, 2024
1 parent 8b77c44 commit c8dc5a1
Show file tree
Hide file tree
Showing 7 changed files with 195 additions and 55 deletions.
18 changes: 15 additions & 3 deletions src/account/accountProfileUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,12 @@
*/
import { useEffect, useState } from 'react';
import { jwtDecode } from 'jwt-decode';
import { useNavigate } from 'react-router-dom';
import { useLocation, useNavigate } from 'react-router-dom';
import { getToken } from 'terraso-client-shared/account/auth';
import { useSelector } from 'terrasoApi/store';

import { generateReferrerUrl } from 'navigation/navigationUtils';

const getIsFirstLogin = async () => {
const token = await getToken();
return token === undefined ? undefined : jwtDecode(token).isFirstLogin;
Expand Down Expand Up @@ -50,6 +52,7 @@ export const profileCompleted = email => {
};

export const useCompleteProfile = () => {
const location = useLocation();
const navigate = useNavigate();
const { data: user } = useSelector(state => state.account.currentUser);
const [isFirstLogin, setIsFirstLogin] = useState();
Expand All @@ -70,6 +73,15 @@ export const useCompleteProfile = () => {
return;
}

navigate('/account/profile/completeProfile');
}, [isFirstLogin, user?.email, navigate]);
if (location.pathname === '/account/profile/completeProfile') {
return;
}

const to = generateReferrerUrl(
'/account/profile/completeProfile',
location
);

navigate(to);
}, [isFirstLogin, user?.email, navigate, location]);
};
42 changes: 8 additions & 34 deletions src/account/components/AccountLogin.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,8 @@
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
import React, { useEffect } from 'react';
import queryString from 'query-string';
import { Trans, useTranslation } from 'react-i18next';
import { useSelector } from 'react-redux';
import { useNavigate, useSearchParams } from 'react-router-dom';
import { fetchAuthURLs } from 'terraso-client-shared/account/accountSlice';
import { ReactComponent as GoogleLogo } from 'terraso-client-shared/assets/google.svg';
import { ReactComponent as MicrosoftLogo } from 'terraso-client-shared/assets/microsoft.svg';
Expand All @@ -35,57 +33,33 @@ import PageHeader from 'layout/PageHeader';
import PageLoader from 'layout/PageLoader';
import LocalePicker from 'localization/components/LocalePicker';
import { useAnalytics } from 'monitoring/analytics';
import { useReferrer } from 'navigation/navigationUtils';

import logo from 'assets/logo.svg';

// ref: https://mui.com/material-ui/icons/#component-prop
const MicrosoftIcon = withProps(SvgIcon, { component: MicrosoftLogo });
const GoogleIcon = withProps(SvgIcon, { component: GoogleLogo });

const appendReferrer = (url, referrer) => {
if (!referrer) {
return url;
}
const parsedUrl = queryString.parseUrl(url);
const redirectUrl = queryString.stringifyUrl({
url: 'account',
query: {
referrerBase64: btoa(referrer),
},
});
return queryString.stringifyUrl({
...parsedUrl,
query: {
...parsedUrl.query,
state: redirectUrl,
},
});
};

const AccountForm = () => {
const { t } = useTranslation();
const { trackEvent } = useAnalytics();
const navigate = useNavigate();
const [searchParams] = useSearchParams();
const { fetching, urls } = useSelector(state => state.account.login);
const hasToken = useSelector(state => state.account.hasToken);
const referrer = searchParams.get('referrer');
const referrerBase64 = searchParams.get('referrerBase64');

useDocumentTitle(t('account.login_document_title'));
useDocumentDescription(t('account.login_document_description'));

useFetchData(fetchAuthURLs);

const { goToReferrer, appendReferrerBase64 } = useReferrer();

useEffect(() => {
if (!hasToken) {
return;
}
const url = referrerBase64 ? atob(referrerBase64) : referrer;
navigate(url ? decodeURIComponent(url) : '/', {
replace: true,
});
}, [hasToken, navigate, referrer, referrerBase64]);
goToReferrer();
}, [hasToken, goToReferrer]);

if (fetching) {
return <PageLoader />;
Expand Down Expand Up @@ -124,7 +98,7 @@ const AccountForm = () => {
startIcon={
<GoogleIcon sx={{ paddingLeft: '3px', paddingRight: '5px' }} />
}
href={appendReferrer(urls.google, referrer)}
href={appendReferrerBase64(urls.google)}
onClick={() =>
trackEvent('user.login', { props: { source: 'google' } })
}
Expand All @@ -141,7 +115,7 @@ const AccountForm = () => {
sx={{ paddingLeft: '24px', paddingRight: '5px' }}
/>
}
href={appendReferrer(urls.microsoft, referrer)}
href={appendReferrerBase64(urls.microsoft)}
onClick={() =>
trackEvent('user.login', { props: { source: 'microsoft' } })
}
Expand All @@ -154,7 +128,7 @@ const AccountForm = () => {
<Button
variant="outlined"
startIcon={<AppleIcon sx={{ paddingRight: '5px' }} />}
href={appendReferrer(urls.apple, referrer)}
href={appendReferrerBase64(urls.apple)}
onClick={() =>
trackEvent('user.login', { props: { source: 'apple' } })
}
Expand Down
8 changes: 5 additions & 3 deletions src/account/components/AccountProfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import React, { useCallback, useEffect } from 'react';
import _ from 'lodash/fp';
import { useTranslation } from 'react-i18next';
import { useDispatch, useSelector } from 'react-redux';
import { useNavigate, useParams } from 'react-router-dom';
import { useParams } from 'react-router-dom';
import {
fetchProfile,
savePreference,
Expand All @@ -44,6 +44,7 @@ import PageHeader from 'layout/PageHeader';
import PageLoader from 'layout/PageLoader';
import LocalePickerSelect from 'localization/components/LocalePickerSelect';
import { useAnalytics } from 'monitoring/analytics';
import { useReferrer } from 'navigation/navigationUtils';
import { profileCompleted } from 'account/accountProfileUtils';

import AccountAvatar from './AccountAvatar';
Expand Down Expand Up @@ -175,7 +176,6 @@ const ProfilePicture = () => {
};

const AccountProfile = () => {
const navigate = useNavigate();
const dispatch = useDispatch();
const { trackEvent } = useAnalytics();
const { t } = useTranslation();
Expand All @@ -187,6 +187,8 @@ const AccountProfile = () => {
useDocumentTitle(t('account.profile_document_title'));
useDocumentDescription(t('account.profile_document_description'));

const { goToReferrer } = useReferrer();

useEffect(
() => () => {
profileCompleted(user?.email);
Expand Down Expand Up @@ -241,7 +243,7 @@ const AccountProfile = () => {
response => _.get('meta.requestStatus', response) === 'fulfilled'
);
if (allSuccess) {
navigate('/account/profile');
goToReferrer('/account/profile');
}
});
};
Expand Down
68 changes: 66 additions & 2 deletions src/account/components/AccountProfile.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import React from 'react';
import { when } from 'jest-when';
import _ from 'lodash/fp';
import { act } from 'react-dom/test-utils';
import { useNavigate, useParams } from 'react-router-dom';
import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
import * as terrasoApi from 'terraso-client-shared/terrasoApi/api';

import AccountProfile from 'account/components/AccountProfile';
Expand All @@ -30,6 +30,7 @@ jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
useParams: jest.fn(),
useNavigate: jest.fn(),
useSearchParams: jest.fn(),
}));

const setup = async (
Expand Down Expand Up @@ -63,6 +64,7 @@ const setup = async (
beforeEach(() => {
useNavigate.mockReturnValue(jest.fn());
useParams.mockReturnValue({});
useSearchParams.mockReturnValue([new URLSearchParams(), () => {}]);
});

test('AccountProfile: Display Avatar', async () => {
Expand Down Expand Up @@ -438,5 +440,67 @@ test('AccountProfile: Complete profile', async () => {
await act(async () =>
fireEvent.click(screen.getByRole('button', { name: 'Save Profile' }))
);
expect(navigate).toHaveBeenCalledWith('/account/profile');
expect(navigate).toHaveBeenCalledWith('/account/profile', { replace: true });
});

test('AccountProfile: Navigate to referrer after complete profile', async () => {
const navigate = jest.fn();
useNavigate.mockReturnValue(navigate);
const searchParams = new URLSearchParams();
const referrer = encodeURIComponent('groups?sort=-name&other=1');
searchParams.set('referrer', referrer);
useSearchParams.mockReturnValue([searchParams]);
useParams.mockReturnValue({
completeProfile: 'completeProfile',
});

when(terrasoApi.requestGraphQL)
.calledWith(expect.stringContaining('query userProfile'), expect.anything())
.mockReturnValue(
Promise.resolve(
_.set(
'users.edges[0].node',
{
id: 'user-id',
firstName: 'John',
lastName: 'Doe',
email: '[email protected]',
profileImage: '',
preferences: { edges: [] },
},
{}
)
)
);

when(terrasoApi.requestGraphQL)
.calledWith(
expect.stringContaining('mutation updateUser'),
expect.anything()
)
.mockResolvedValue(
_.set(
'updateUser.user',
{
id: '1',
firstName: 'Pablo',
lastName: 'Perez',
email: '[email protected]',
profileImage: 'https://www.group.org/image.jpg',
preferences: {
edges: [{ node: { key: 'language', value: 'es-ES' } }],
},
},
{}
)
);

await setup();

await act(async () =>
fireEvent.click(screen.getByRole('button', { name: 'Save Profile' }))
);
expect(navigate).toHaveBeenCalledWith('groups?sort=-name&other=1', {
replace: true,
});
});
14 changes: 2 additions & 12 deletions src/account/components/RequireAuth.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,13 @@
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
import React, { useCallback } from 'react';
import queryString from 'query-string';
import { useSelector } from 'react-redux';
import { Navigate, useLocation } from 'react-router-dom';
import { fetchUser } from 'terraso-client-shared/account/accountSlice';
import { useFetchData } from 'terraso-client-shared/store/utils';

import PageLoader from 'layout/PageLoader';
import { generateReferrerPath } from 'navigation/navigationUtils';
import { generateReferrerUrl } from 'navigation/navigationUtils';
import { useCompleteProfile } from 'account/accountProfileUtils';

const RequireAuth = ({ children }) => {
Expand Down Expand Up @@ -50,16 +49,7 @@ const RequireAuth = ({ children }) => {
return children;
}

const referrer = generateReferrerPath(location);

const to = referrer
? queryString.stringifyUrl({
url: '/account',
query: {
referrer,
},
})
: '/account';
const to = generateReferrerUrl('/account', location);

return <Navigate to={to} replace />;
};
Expand Down
38 changes: 37 additions & 1 deletion src/account/components/RequireAuth.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,12 @@
import { render, screen } from 'tests/utils';
import React from 'react';
import _ from 'lodash/fp';
import { useLocation, useNavigate, useParams } from 'react-router-dom';
import {
useLocation,
useNavigate,
useParams,
useSearchParams,
} from 'react-router-dom';
import { getToken, getUserEmail } from 'terraso-client-shared/account/auth';
import * as terrasoApi from 'terraso-client-shared/terrasoApi/api';

Expand All @@ -37,6 +42,7 @@ jest.mock('react-router-dom', () => ({
useParams: jest.fn(),
useLocation: jest.fn(),
useNavigate: jest.fn(),
useSearchParams: jest.fn(),
Navigate: props => <div>To: {props.to}</div>,
}));

Expand All @@ -47,6 +53,7 @@ const IS_FIRST_LOGIN_TOKEN =
beforeEach(() => {
global.fetch = jest.fn();
useNavigate.mockReturnValue(jest.fn());
useSearchParams.mockReturnValue([new URLSearchParams(), () => {}]);
});

test('Auth: test redirect', async () => {
Expand Down Expand Up @@ -216,6 +223,35 @@ test('Auth: Test redirect complete profile', async () => {
expect(navigate).toHaveBeenCalledWith('/account/profile/completeProfile');
});

test('Auth: Test redirect to profile with referrer', async () => {
useLocation.mockReturnValue({
pathname: REDIRECT_PATHNAME,
search: REDIRECT_SEARCH,
});

const navigate = jest.fn();
useNavigate.mockReturnValue(navigate);
getToken.mockResolvedValue(IS_FIRST_LOGIN_TOKEN);
await render(
<RequireAuth>
<div />
</RequireAuth>,
{
account: {
currentUser: {
data: {
email: '[email protected]',
},
},
},
}
);

expect(navigate).toHaveBeenCalledWith(
'/account/profile/completeProfile?referrer=%2Fgroups%3Fsort%3D-name%26other%3D1'
);
});

test('Auth: Avoid redirect if profile complete already displayed for user', async () => {
const navigate = jest.fn();
useNavigate.mockReturnValue(navigate);
Expand Down
Loading

0 comments on commit c8dc5a1

Please sign in to comment.