diff --git a/src/account/accountProfileUtils.js b/src/account/accountProfileUtils.js
index 16bfc97be..fe9e1f721 100644
--- a/src/account/accountProfileUtils.js
+++ b/src/account/accountProfileUtils.js
@@ -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;
@@ -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();
@@ -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]);
};
diff --git a/src/account/components/AccountLogin.js b/src/account/components/AccountLogin.js
index 6f43f9d68..f5d66791f 100644
--- a/src/account/components/AccountLogin.js
+++ b/src/account/components/AccountLogin.js
@@ -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';
@@ -35,6 +33,7 @@ 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';
@@ -42,50 +41,25 @@ import logo from 'assets/logo.svg';
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 ;
@@ -124,7 +98,7 @@ const AccountForm = () => {
startIcon={
}
- href={appendReferrer(urls.google, referrer)}
+ href={appendReferrerBase64(urls.google)}
onClick={() =>
trackEvent('user.login', { props: { source: 'google' } })
}
@@ -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' } })
}
@@ -154,7 +128,7 @@ const AccountForm = () => {
}
- href={appendReferrer(urls.apple, referrer)}
+ href={appendReferrerBase64(urls.apple)}
onClick={() =>
trackEvent('user.login', { props: { source: 'apple' } })
}
diff --git a/src/account/components/AccountProfile.js b/src/account/components/AccountProfile.js
index 2a9727002..23b0ee0d7 100644
--- a/src/account/components/AccountProfile.js
+++ b/src/account/components/AccountProfile.js
@@ -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,
@@ -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';
@@ -175,7 +176,6 @@ const ProfilePicture = () => {
};
const AccountProfile = () => {
- const navigate = useNavigate();
const dispatch = useDispatch();
const { trackEvent } = useAnalytics();
const { t } = useTranslation();
@@ -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);
@@ -241,7 +243,7 @@ const AccountProfile = () => {
response => _.get('meta.requestStatus', response) === 'fulfilled'
);
if (allSuccess) {
- navigate('/account/profile');
+ goToReferrer('/account/profile');
}
});
};
diff --git a/src/account/components/AccountProfile.test.js b/src/account/components/AccountProfile.test.js
index 477290e1e..930f4710b 100644
--- a/src/account/components/AccountProfile.test.js
+++ b/src/account/components/AccountProfile.test.js
@@ -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';
@@ -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 (
@@ -63,6 +64,7 @@ const setup = async (
beforeEach(() => {
useNavigate.mockReturnValue(jest.fn());
useParams.mockReturnValue({});
+ useSearchParams.mockReturnValue([new URLSearchParams(), () => {}]);
});
test('AccountProfile: Display Avatar', async () => {
@@ -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: 'group@group.org',
+ profileImage: '',
+ preferences: { edges: [] },
+ },
+ {}
+ )
+ )
+ );
+
+ when(terrasoApi.requestGraphQL)
+ .calledWith(
+ expect.stringContaining('mutation updateUser'),
+ expect.anything()
+ )
+ .mockResolvedValue(
+ _.set(
+ 'updateUser.user',
+ {
+ id: '1',
+ firstName: 'Pablo',
+ lastName: 'Perez',
+ email: 'group@group.org',
+ 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,
+ });
});
diff --git a/src/account/components/RequireAuth.js b/src/account/components/RequireAuth.js
index f644cd5d9..824172765 100644
--- a/src/account/components/RequireAuth.js
+++ b/src/account/components/RequireAuth.js
@@ -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 }) => {
@@ -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 ;
};
diff --git a/src/account/components/RequireAuth.test.js b/src/account/components/RequireAuth.test.js
index ab2856eb8..be63f190a 100644
--- a/src/account/components/RequireAuth.test.js
+++ b/src/account/components/RequireAuth.test.js
@@ -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';
@@ -37,6 +42,7 @@ jest.mock('react-router-dom', () => ({
useParams: jest.fn(),
useLocation: jest.fn(),
useNavigate: jest.fn(),
+ useSearchParams: jest.fn(),
Navigate: props =>
To: {props.to}
,
}));
@@ -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 () => {
@@ -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(
+
+
+ ,
+ {
+ account: {
+ currentUser: {
+ data: {
+ email: 'test@test.com',
+ },
+ },
+ },
+ }
+ );
+
+ 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);
diff --git a/src/navigation/navigationUtils.js b/src/navigation/navigationUtils.js
index 034f31ccb..b5134502f 100644
--- a/src/navigation/navigationUtils.js
+++ b/src/navigation/navigationUtils.js
@@ -14,7 +14,10 @@
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see https://www.gnu.org/licenses/.
*/
+import { useCallback, useMemo } from 'react';
import _ from 'lodash/fp';
+import queryString from 'query-string';
+import { useNavigate, useSearchParams } from 'react-router-dom';
export const generateReferrerPath = location => {
const path = _.getOr('', 'pathname', location);
@@ -24,3 +27,62 @@ export const generateReferrerPath = location => {
.join('');
return referrer ? `/${referrer}` : null;
};
+
+export const generateReferrerUrl = (to, location) => {
+ const referrer = generateReferrerPath(location);
+
+ return referrer
+ ? queryString.stringifyUrl({
+ url: to,
+ query: {
+ referrer,
+ },
+ })
+ : to;
+};
+
+export const useReferrer = () => {
+ const navigate = useNavigate();
+ const [searchParams] = useSearchParams();
+
+ const referrer = searchParams.get('referrer');
+ const referrerBase64 = searchParams.get('referrerBase64');
+
+ const url = useMemo(() => {
+ return referrerBase64 ? atob(referrerBase64) : referrer;
+ }, [referrer, referrerBase64]);
+
+ const goToReferrer = useCallback(
+ (defaultUrl = '/') => {
+ navigate(url ? decodeURIComponent(url) : defaultUrl, {
+ replace: true,
+ });
+ },
+ [navigate, url]
+ );
+
+ const appendReferrerBase64 = useCallback(
+ url => {
+ 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,
+ },
+ });
+ },
+ [referrer]
+ );
+
+ return { goToReferrer, appendReferrerBase64 };
+};