Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

creates interstitial components and redirect #32064

Merged
merged 23 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions src/applications/auth/containers/AuthApp.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import recordEvent from 'platform/monitoring/record-event';
import { toggleLoginModal } from 'platform/site-wide/user-nav/actions';
import { useFeatureToggle } from '~/platform/utilities/feature-toggles/useFeatureToggle';
import {
AUTH_EVENTS,
AUTHN_SETTINGS,
Expand All @@ -30,18 +31,18 @@
['/auth/login/callback', '/session-expired'].join('|'),
);

export default function AuthApp({ location }) {

Check warning on line 34 in src/applications/auth/containers/AuthApp.jsx

View workflow job for this annotation

GitHub Actions / Linting (Files Changed)

src/applications/auth/containers/AuthApp.jsx:34:35:'location' is missing in props validation
const [
{ auth, errorCode, returnUrl, loginType, state, requestId },
setAuthState,
] = useState({
auth: location?.query?.auth,

Check warning on line 39 in src/applications/auth/containers/AuthApp.jsx

View workflow job for this annotation

GitHub Actions / Linting (Files Changed)

src/applications/auth/containers/AuthApp.jsx:39:21:'location.query' is missing in props validation

Check warning on line 39 in src/applications/auth/containers/AuthApp.jsx

View workflow job for this annotation

GitHub Actions / Linting (Files Changed)

src/applications/auth/containers/AuthApp.jsx:39:28:'location.query.auth' is missing in props validation
errorCode: location?.query?.code || '',

Check warning on line 40 in src/applications/auth/containers/AuthApp.jsx

View workflow job for this annotation

GitHub Actions / Linting (Files Changed)

src/applications/auth/containers/AuthApp.jsx:40:26:'location.query' is missing in props validation

Check warning on line 40 in src/applications/auth/containers/AuthApp.jsx

View workflow job for this annotation

GitHub Actions / Linting (Files Changed)

src/applications/auth/containers/AuthApp.jsx:40:33:'location.query.code' is missing in props validation
loginType: location?.query?.type || 'Login type not found',

Check warning on line 41 in src/applications/auth/containers/AuthApp.jsx

View workflow job for this annotation

GitHub Actions / Linting (Files Changed)

src/applications/auth/containers/AuthApp.jsx:41:26:'location.query' is missing in props validation

Check warning on line 41 in src/applications/auth/containers/AuthApp.jsx

View workflow job for this annotation

GitHub Actions / Linting (Files Changed)

src/applications/auth/containers/AuthApp.jsx:41:33:'location.query.type' is missing in props validation
requestId:
location?.query?.request_id || 'No corresponding Request ID was found',

Check warning on line 43 in src/applications/auth/containers/AuthApp.jsx

View workflow job for this annotation

GitHub Actions / Linting (Files Changed)

src/applications/auth/containers/AuthApp.jsx:43:17:'location.query' is missing in props validation

Check warning on line 43 in src/applications/auth/containers/AuthApp.jsx

View workflow job for this annotation

GitHub Actions / Linting (Files Changed)

src/applications/auth/containers/AuthApp.jsx:43:24:'location.query.request_id' is missing in props validation
returnUrl: sessionStorage.getItem(AUTHN_SETTINGS.RETURN_URL) ?? '',
state: location?.query?.state || '',

Check warning on line 45 in src/applications/auth/containers/AuthApp.jsx

View workflow job for this annotation

GitHub Actions / Linting (Files Changed)

src/applications/auth/containers/AuthApp.jsx:45:22:'location.query' is missing in props validation
});
const [hasError, setHasError] = useState(auth === 'fail');

Expand All @@ -67,7 +68,17 @@
setHasError(true);
};

const { useToggleValue, TOGGLE_NAMES } = useFeatureToggle();
const isInterstital = useToggleValue(TOGGLE_NAMES.mhvInterstitialEnabled);

const redirect = () => {
if (
isInterstital &&
(loginType === 'mhv' || loginType === 'myhealthevet')
) {
window.location.replace('/sign-in-changes-reminder');
asg5704 marked this conversation as resolved.
Show resolved Hide resolved
}

// remove from session storage
sessionStorage.removeItem(AUTHN_SETTINGS.RETURN_URL);

Expand Down
22 changes: 15 additions & 7 deletions src/applications/auth/tests/AuthApp.unit.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ describe('AuthApp', () => {
const mockStore = {
dispatch: sinon.spy(),
subscribe: sinon.spy(),
getState: () => {},
getState: () => ({
featureToggles: {
// eslint-disable-next-line camelcase
asg5704 marked this conversation as resolved.
Show resolved Hide resolved
mhv_interstitial_enabled: false,
},
}),
};

before(() => {
Expand Down Expand Up @@ -145,8 +150,9 @@ describe('AuthApp', () => {
/>
</Provider>,
);

expect(queryByTestId('loading')).to.not.be.null;
await waitFor(() => {
expect(queryByTestId('loading')).to.not.be.null;
});
});

it('should call skipToRedirect when no error and return URL is external', async () => {
Expand All @@ -172,8 +178,9 @@ describe('AuthApp', () => {
/>
</Provider>,
);

expect(queryByTestId('loading')).to.not.be.null;
await waitFor(() => {
expect(queryByTestId('loading')).to.not.be.null;
});
});

it('should call the handleTokenRequest', async () => {
Expand Down Expand Up @@ -209,8 +216,9 @@ describe('AuthApp', () => {
/>
</Provider>,
);

expect(queryByTestId('loading')).to.not.be.null;
await waitFor(() => {
expect(queryByTestId('loading')).to.not.be.null;
});
});

it('should not call terms of use provisioning if a user is not authenticating with ssoe', async () => {
Expand Down
32 changes: 32 additions & 0 deletions src/applications/sign-in-changes/components/AccountSwitch.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import React from 'react';
import PropTypes from 'prop-types';
import { LoginButton } from '~/platform/user/exportsFile';
CaitHawk marked this conversation as resolved.
Show resolved Hide resolved
import { useSelector } from 'react-redux';
import { selectProfile } from 'platform/user/selectors';
import { maskEmail } from '../helpers';

export default function AccountSwitch({ hasLogingov }) {
const userEmail = useSelector(state => selectProfile(state)?.email);
const maskedEmail = maskEmail(userEmail);
return (
<div>
<h2 className="vads-u-margin-y--0">
Start using your <strong>{hasLogingov ? 'Login.gov' : 'ID.me'}</strong>{' '}
account now
</h2>
<p>
We found an existing{' '}
<strong>{hasLogingov ? 'Login.gov' : 'ID.me'}</strong> account for you
associated with this email:
</p>
<p>
<strong>{maskedEmail}</strong>
</p>
<LoginButton csp={hasLogingov ? 'logingov' : 'idme'} />
</div>
);
}

AccountSwitch.propTypes = {
hasLogingov: PropTypes.bool,
};
28 changes: 28 additions & 0 deletions src/applications/sign-in-changes/components/CreateAccount.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import React from 'react';
import { LoginButton } from '~/platform/user/exportsFile';
import { VaLink } from '@department-of-veterans-affairs/component-library/dist/react-bindings';

export default function CreateAccount() {
const cspInfo = ['logingov', 'idme'];
return (
<div
className="vads-u-display--flex vads-u-flex-direction--column"
id="create-account-div"
>
<h2 className="vads-u-margin-y--0">Create a different account now</h2>
<p>
Create an identity-verified <strong>Login.gov</strong> or{' '}
<strong>ID.me</strong> account now, so you're ready for the change.
</p>
<VaLink
text="Learn about why we are making changes to sign in"
href="https://www.va.gov/initiatives/prepare-for-vas-secure-sign-in-changes/"
/>
<div id="button-div">
{cspInfo.map(c => (
<LoginButton csp={c} key={c} />
))}
</div>
</div>
);
}
10 changes: 0 additions & 10 deletions src/applications/sign-in-changes/containers/App.jsx

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import React from 'react';
import { useSelector } from 'react-redux';
import { VaLink } from '@department-of-veterans-affairs/component-library/dist/react-bindings';
import { selectProfile } from 'platform/user/selectors';
import { AUTHN_SETTINGS } from '@department-of-veterans-affairs/platform-user/exports';
import CreateAccount from '../components/CreateAccount';
import AccountSwitch from '../components/AccountSwitch';

export default function InterstitialChanges() {
const userHasLogingov =
useSelector(state => selectProfile(state)?.logingovUuid) !== null;
const userHasIdme =
useSelector(state => selectProfile(state)?.idmeUuid) !== null;
const showAccount = userHasLogingov || userHasIdme;
const returnUrl = sessionStorage.getItem(AUTHN_SETTINGS.RETURN_URL) || '/';
return (
<div className="row medium-screen:vads-u-max-width--900px login vads-u-margin-y--6 vads-u-margin-x--2">
<h1
id="signin-changes-title"
className="vads-u-margin-top--2 medium-screen:vads-u-margin-top--1 medium-screen:vads-u-margin-bottom--2"
>
You’ll need to sign in with a different account after January 31, 2025
</h1>
<p className="vads-u-font-size--base section-content">
After this date, we'll remove the <strong>My HealtheVet</strong> sign-in
option. You’ll need to sign in using a <strong>Login.gov</strong> or{' '}
<strong>ID.me</strong> account.
</p>
{showAccount ? (
<AccountSwitch hasLogingov={userHasLogingov} />
) : (
<CreateAccount />
)}
<h2 className="vads-u-margin-y--0">Or continue using your old account</h2>
<p className="vads-u-font-size--base">
You’ll can use you <strong>My HealtheVet</strong> account to sign in
until <strong>January 31, 2025</strong>.
</p>
<VaLink
text="Continue with your My HealtheVet account for now"
href={returnUrl}
/>
</div>
);
}
14 changes: 14 additions & 0 deletions src/applications/sign-in-changes/helpers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
export const maskEmail = email => {
if (!email) {
return '';
}
const [name, domain] = email.split('@');
const maskedName =
name.length > 1
? name
.split('')
.map((n, idx) => (idx <= 2 ? n : '*'))
.join('')
: name;
return `${`${maskedName}@${domain}`}`;
};
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import '@department-of-veterans-affairs/platform-polyfills';
import 'platform/polyfills';

import { startAppFromIndex } from '@department-of-veterans-affairs/platform-startup/exports';

Expand Down
2 changes: 1 addition & 1 deletion src/applications/sign-in-changes/manifest.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"appName": "Sign-in changes reminder",
"entryFile": "./app-entry.jsx",
"entryFile": "./interstitial-changes-entry.jsx",
"entryName": "sign-in-changes",
"rootUrl": "/sign-in-changes-reminder",
"productId": "01924557-c71b-4d5a-81e6-73d48fef0b3f"
Expand Down
4 changes: 2 additions & 2 deletions src/applications/sign-in-changes/routes.jsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import App from './containers/App';
import InterstitialChanges from './containers/InterstitialChanges';

const routes = {
path: '/',
component: App,
component: InterstitialChanges,
};

export default routes;
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import React from 'react';
import { render } from '@testing-library/react';
import { Provider } from 'react-redux';
import { expect } from 'chai';
import AccountSwitch from '../components/AccountSwitch';

const mockStore = {
getState: () => {
return {
user: {
profile: {
email: '[email protected]',
},
},
};
},
subscribe: () => {},
dispatch: () => {},
};

describe('AccountSwitch', () => {
it('renders Login.gov when hasLogingov is true', () => {
const screen = render(
<Provider store={mockStore}>
<AccountSwitch hasLogingov />
</Provider>,
);
const loginGovButton = screen.getByRole('button', {
'data-csp': /'logingov'/i,
});
expect(loginGovButton).to.not.be.null;
expect(screen.getByRole('heading', { level: 2 }).textContent).to.include(
'Start using your Login.gov account now',
);
expect(screen.getByText('tes*@example.com')).to.exist;
});

it('renders ID.me when hasLogingov is false', () => {
const screen = render(
<Provider store={mockStore}>
<AccountSwitch hasLogingov={false} />
</Provider>,
);
const idmeButton = screen.getByRole('button', {
'data-csp': /'idme'/i,
});
expect(idmeButton).to.not.be.null;
expect(screen.getByRole('heading', { level: 2 }).textContent).to.include(
'Start using your ID.me account now',
);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import React from 'react';
import { render } from '@testing-library/react';
import { expect } from 'chai';
import { Provider } from 'react-redux';
import CreateAccount from '../components/CreateAccount';

const generateStore = () => ({
getState: () => {},
subscribe: () => {},
dispatch: () => {},
});

describe('CreateAccount', () => {
it('renders the static content correctly', () => {
const store = generateStore();
const screen = render(
<Provider store={store}>
<CreateAccount />
</Provider>,
);

expect(screen.getByText(/Create a different account now/i)).to.exist;

expect(screen.getByText(/Create an identity-verified/i)).to.exist;
});

it('renders two LoginButton components with correct csp prop', () => {
const store = generateStore();
const screen = render(
<Provider store={store}>
<CreateAccount />
</Provider>,
);

const buttons = screen.getAllByRole('button');
expect(buttons).to.have.lengthOf(2);
});
});
Loading
Loading