diff --git a/cypress/e2e/SignIn.cy.ts b/cypress/e2e/SignIn.cy.ts index 1f635f40..56e092a6 100644 --- a/cypress/e2e/SignIn.cy.ts +++ b/cypress/e2e/SignIn.cy.ts @@ -9,8 +9,8 @@ describe('Name and Email Validation', () => { const { GRAASP, WRONG_EMAIL } = MEMBERS; cy.visit(SIGN_IN_PATH); // Signing in with a wrong email format - cy.signInAndCheck(WRONG_EMAIL); - // Siging in with a valid email - cy.signInAndCheck(GRAASP); + cy.signInByMailAndCheck(WRONG_EMAIL); + // Signing in with a valid email + cy.signInByMailAndCheck(GRAASP); }); }); diff --git a/cypress/e2e/SignInPassword.cy.ts b/cypress/e2e/SignInPassword.cy.ts index d4be371f..5e4dd789 100644 --- a/cypress/e2e/SignInPassword.cy.ts +++ b/cypress/e2e/SignInPassword.cy.ts @@ -1,6 +1,7 @@ import { API_ROUTES } from '@graasp/query-client'; import { SIGN_IN_PATH } from '../../src/config/paths'; +import { PASSWORD_SUCCESS_ALERT } from '../../src/config/selectors'; import { MEMBERS } from '../fixtures/members'; describe('Email and Password Validation', () => { @@ -15,10 +16,46 @@ describe('Email and Password Validation', () => { }, ).as('signInWithPassword'); + const { WRONG_EMAIL, GRAASP } = MEMBERS; + cy.visit(SIGN_IN_PATH); + // Signing in with wrong email + cy.signInPasswordAndCheck(WRONG_EMAIL); + + // Signing in with a valid email and password + cy.signInPasswordAndCheck(GRAASP); + + cy.url().should('contain', redirectionLink); + }); + + it('Sign In With Wrong Password', () => { + cy.intercept( + { + pathname: API_ROUTES.SIGN_IN_WITH_PASSWORD_ROUTE, + }, + (req) => { + req.reply({ statusCode: 500 }); + }, + ).as('signInWithPassword'); + + const { WRONG_PASSWORD } = MEMBERS; + cy.visit(SIGN_IN_PATH); + + // Signing in with a valid email but empty password + cy.signInPasswordAndCheck(WRONG_PASSWORD); + }); + + it('Sign In With Password shows success message if no redirect', () => { + cy.intercept( + { + pathname: API_ROUTES.SIGN_IN_WITH_PASSWORD_ROUTE, + }, + (req) => { + req.reply({ statusCode: 303 }); + }, + ).as('signInWithPassword'); + const { WRONG_EMAIL, WRONG_PASSWORD, GRAASP } = MEMBERS; cy.visit(SIGN_IN_PATH); - // Select sign in method - cy.signInPasswordMethodAndCheck(); // Signing in with wrong email cy.signInPasswordAndCheck(WRONG_EMAIL); // Signing in with a valid email but empty password @@ -26,6 +63,6 @@ describe('Email and Password Validation', () => { // Signing in with a valid email and password cy.signInPasswordAndCheck(GRAASP); - cy.url().should('contain', redirectionLink); + cy.get(`#${PASSWORD_SUCCESS_ALERT}`).should('be.visible'); }); }); diff --git a/cypress/e2e/SuccessContent.cy.ts b/cypress/e2e/SuccessContent.cy.ts index cc2b69dc..95901a81 100644 --- a/cypress/e2e/SuccessContent.cy.ts +++ b/cypress/e2e/SuccessContent.cy.ts @@ -6,7 +6,6 @@ import { SIGN_IN_PATH, SIGN_UP_PATH } from '../../src/config/paths'; import { BACK_BUTTON_ID, EMAIL_SIGN_IN_FIELD_ID, - EMAIL_SIGN_UP_FIELD_ID, RESEND_EMAIL_BUTTON_ID, SIGN_IN_HEADER_ID, SIGN_UP_HEADER_ID, @@ -29,7 +28,7 @@ describe('Success Content', () => { cy.get(`#${SUCCESS_CONTENT_ID}`).should('not.exist'); // Signing in with a valid email - cy.signInAndCheck(GRAASP); + cy.signInByMailAndCheck(GRAASP); cy.get(`#${SUCCESS_CONTENT_ID}`).should('be.visible'); @@ -42,7 +41,7 @@ describe('Success Content', () => { cy.get(`#${EMAIL_SIGN_IN_FIELD_ID}`).should('be.empty'); // check if it's possible to sign in and use back button again - cy.signInAndCheck(GRAASP_OTHER); + cy.signInByMailAndCheck(GRAASP_OTHER); cy.get(`#${SUCCESS_CONTENT_ID}`).should('be.visible'); @@ -66,10 +65,10 @@ describe('Success Content', () => { }); // Signing in with a valid email - cy.signInAndCheck(GRAASP_OTHER); + cy.signInByMailAndCheck(GRAASP_OTHER); cy.get(`#${BACK_BUTTON_ID}`).click(); - cy.signInAndCheck(GRAASP); + cy.signInByMailAndCheck(GRAASP); // checks so request body contains correct email cy.intercept(API_ROUTES.SIGN_IN_ROUTE, ({ body }) => { @@ -105,8 +104,6 @@ describe('Success Content', () => { cy.get(`#${SUCCESS_CONTENT_ID}`).should('not.exist'); cy.url().should('include', SIGN_UP_PATH); cy.get(`#${SIGN_UP_HEADER_ID}`).should('be.visible'); - // checks so email is still filled - cy.get(`#${EMAIL_SIGN_UP_FIELD_ID}`).should('have.value', GRAASP.email); // check if it's possible to sign up and use back button again cy.signUpAndCheck(GRAASP_OTHER, true); @@ -118,11 +115,6 @@ describe('Success Content', () => { cy.get(`#${SUCCESS_CONTENT_ID}`).should('not.exist'); cy.url().should('include', SIGN_UP_PATH); cy.get(`#${SIGN_UP_HEADER_ID}`).should('be.visible'); - // checks so email is still filled - cy.get(`#${EMAIL_SIGN_UP_FIELD_ID}`).should( - 'have.value', - GRAASP_OTHER.email, - ); }); it('Resend email', () => { diff --git a/cypress/e2e/util.ts b/cypress/e2e/util.ts index dca4dd6c..089389b1 100644 --- a/cypress/e2e/util.ts +++ b/cypress/e2e/util.ts @@ -1,10 +1,10 @@ import { EMAIL_SIGN_IN_FIELD_ID, + EMAIL_SIGN_IN_MAGIC_LINK_FIELD_ID, EMAIL_SIGN_UP_FIELD_ID, NAME_SIGN_UP_FIELD_ID, PASSWORD_SIGN_IN_BUTTON_ID, PASSWORD_SIGN_IN_FIELD_ID, - PASSWORD_SIGN_IN_METHOD_BUTTON_ID, SIGN_IN_BUTTON_ID, SIGN_UP_BUTTON_ID, } from '../../src/config/selectors'; @@ -37,8 +37,8 @@ export const checkInvitationFields = ({ .should('be.disabled'); }; -export const fillSignInLayout = ({ email }: { email?: string }) => { - cy.get(`#${EMAIL_SIGN_IN_FIELD_ID}`).clear().type(email); +export const fillSignInByMailLayout = ({ email }: { email?: string }) => { + cy.get(`#${EMAIL_SIGN_IN_MAGIC_LINK_FIELD_ID}`).clear().type(email); }; export const submitSignIn = () => { @@ -49,10 +49,6 @@ export const submitSignUp = () => { cy.get(`#${SIGN_UP_BUTTON_ID}`).click(); }; -export const passwordSignInMethod = () => { - cy.get(`#${PASSWORD_SIGN_IN_METHOD_BUTTON_ID}`).click(); -}; - export const fillPasswordSignInLayout = ({ email, password, diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index c080fc34..dfd8c275 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -11,17 +11,16 @@ import { Member } from '@graasp/sdk'; import { EMAIL_SIGN_IN_FIELD_ID, + EMAIL_SIGN_IN_MAGIC_LINK_FIELD_ID, EMAIL_SIGN_UP_FIELD_ID, NAME_SIGN_UP_FIELD_ID, PASSWORD_SIGN_IN_FIELD_ID, - PASSWORD_SIGN_IN_METHOD_BUTTON_ID, SIGN_UP_AGREEMENTS_CHECKBOX_ID, } from '../../src/config/selectors'; import { fillPasswordSignInLayout, - fillSignInLayout, + fillSignInByMailLayout, fillSignUpLayout, - passwordSignInMethod, submitPasswordSignIn, submitSignIn, submitSignUp, @@ -54,7 +53,7 @@ declare global { acceptAllTerms?: boolean, ): Chainable>; - signInAndCheck( + signInByMailAndCheck( value: Partial & { nameValid?: boolean; emailValid?: boolean; @@ -62,13 +61,12 @@ declare global { }, ): Chainable>; - signInPasswordMethodAndCheck(): Chainable>; - signInPasswordAndCheck( member: Member & { nameValid?: boolean; emailValid?: boolean; passwordValid?: boolean; + password?: string; }, ): Chainable>; @@ -112,23 +110,20 @@ Cypress.Commands.add('signUpAndCheck', (user, acceptAllTerms) => { cy.checkErrorTextField(EMAIL_SIGN_UP_FIELD_ID, user.emailValid); }); -Cypress.Commands.add('signInAndCheck', (user) => { - fillSignInLayout(user); +Cypress.Commands.add('signInByMailAndCheck', (user) => { + fillSignInByMailLayout(user); submitSignIn(); - cy.checkErrorTextField(EMAIL_SIGN_IN_FIELD_ID, user.emailValid); -}); - -Cypress.Commands.add('signInPasswordMethodAndCheck', () => { - passwordSignInMethod(); - cy.get(`#${PASSWORD_SIGN_IN_METHOD_BUTTON_ID}`).should('be.disabled'); + cy.checkErrorTextField(EMAIL_SIGN_IN_MAGIC_LINK_FIELD_ID, user.emailValid); }); Cypress.Commands.add('signInPasswordAndCheck', (user) => { fillPasswordSignInLayout(user); + if (user.password) { + submitPasswordSignIn(); + } if (!user.passwordValid) { cy.get(`#${PASSWORD_SIGN_IN_FIELD_ID}`).clear(); } - submitPasswordSignIn(); cy.checkErrorTextField(EMAIL_SIGN_IN_FIELD_ID, user.emailValid); cy.checkErrorTextField(PASSWORD_SIGN_IN_FIELD_ID, user.passwordValid); }); diff --git a/src/App.tsx b/src/App.tsx index cc7912da..12004605 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,6 +2,7 @@ import * as Sentry from '@sentry/react'; import { Route, BrowserRouter as Router, Routes } from 'react-router-dom'; import ErrorFallback from './components/ErrorFallback'; +import MagicLinkSuccessContent from './components/MagicLinkSuccessContent'; import MobileAuth from './components/MobileAuth'; import Redirection from './components/Redirection'; import SignIn from './components/SignIn'; @@ -9,6 +10,7 @@ import SignUp from './components/SignUp'; import { HOME_PATH, MOBILE_AUTH_PATH, + SIGN_IN_MAGIC_LINK_SUCCESS_PATH, SIGN_IN_PATH, SIGN_UP_PATH, } from './config/paths'; @@ -19,6 +21,10 @@ const App = () => ( } /> + } + /> } /> } /> } /> diff --git a/src/components/APIChecker.tsx b/src/components/APIChecker.tsx index 59719041..bb9987d1 100644 --- a/src/components/APIChecker.tsx +++ b/src/components/APIChecker.tsx @@ -25,7 +25,7 @@ const APIChecker = (): JSX.Element | false => { if (isError) { return ( - + {t(AUTH.API_UNAVAILABLE_TITLE)} {t(AUTH.API_UNAVAILABLE_EXPLANATION)} diff --git a/src/components/AgreementForm.tsx b/src/components/AgreementForm.tsx index 420aeab9..f7bf5b96 100644 --- a/src/components/AgreementForm.tsx +++ b/src/components/AgreementForm.tsx @@ -7,7 +7,6 @@ import { Typography, } from '@mui/material'; -import { MAX_CHECKBOX_LABEL_WITH_PX_SIGN_UP } from '../config/constants'; import { useAuthTranslation } from '../config/i18n'; import { SIGN_UP_AGREEMENTS_CHECKBOX_ID } from '../config/selectors'; import { UseAgreementForm } from '../hooks/useAgreementForm'; @@ -30,7 +29,7 @@ export const AgreementForm = ({ useAgreementForm }: Props) => { const errorColor = 'error'; return ( - + updateUserAgreements(checked)} diff --git a/src/components/EmailInput.tsx b/src/components/EmailInput.tsx index 9351c762..762a982a 100644 --- a/src/components/EmailInput.tsx +++ b/src/components/EmailInput.tsx @@ -1,10 +1,15 @@ +import { Mail } from 'lucide-react'; import React, { FC, useEffect, useState } from 'react'; +import { InputAdornment } from '@mui/material'; + import { useAuthTranslation } from '../config/i18n'; import { AUTH } from '../langs/constants'; import { emailValidator } from '../utils/validation'; import StyledTextField from './StyledTextField'; +const { EMAIL_INPUT_PLACEHOLDER } = AUTH; + type Props = { required?: boolean; value: string; @@ -13,16 +18,18 @@ type Props = { setValue: (str: string) => void; onKeyPress?: React.KeyboardEventHandler; shouldValidate: boolean; + autoFocus?: boolean; }; const EmailInput: FC = ({ - required = true, + required = false, value = '', id, disabled = false, setValue, onKeyPress, shouldValidate = true, + autoFocus = false, }) => { const { t } = useAuthTranslation(); const [error, setError] = useState(null); @@ -44,12 +51,21 @@ const EmailInput: FC = ({ return ( + + + ), + }} variant="outlined" - label={t(AUTH.EMAIL_FIELD_TEXT)} - required={required} value={value} error={Boolean(error)} helperText={t(error)} + placeholder={t( + `${EMAIL_INPUT_PLACEHOLDER}${required ? '_REQUIRED' : ''}`, + )} + autoFocus={autoFocus} onChange={handleEmailOnChange} id={id} type="email" diff --git a/src/components/EnableAnalyticsForm.tsx b/src/components/EnableAnalyticsForm.tsx index ca1f7a9d..44536374 100644 --- a/src/components/EnableAnalyticsForm.tsx +++ b/src/components/EnableAnalyticsForm.tsx @@ -6,7 +6,6 @@ import { Typography, } from '@mui/material'; -import { MAX_CHECKBOX_LABEL_WITH_PX_SIGN_UP } from '../config/constants'; import { useAuthTranslation } from '../config/i18n'; import { SIGN_UP_SAVE_ACTIONS_ID } from '../config/selectors'; import { AUTH } from '../langs/constants'; @@ -25,7 +24,7 @@ export const EnableAnalyticsForm = ({ return ( - + ( - - - - - - {children} - - -