diff --git a/packages/common/src/controllers/CheckoutController.ts b/packages/common/src/controllers/CheckoutController.ts index ffe195d50..917699af9 100644 --- a/packages/common/src/controllers/CheckoutController.ts +++ b/packages/common/src/controllers/CheckoutController.ts @@ -103,6 +103,8 @@ export default class CheckoutController { useCheckoutStore.getState().setOrder(null); } else if (error.message === 'Invalid coupon code') { throw new FormValidationError({ couponCode: [i18next.t('account:checkout.coupon_not_valid')] }); + } else if (error.message === 'Invalid coupon code for this offer') { + throw new FormValidationError({ couponCode: [i18next.t('account:checkout.coupon_not_valid_for_offer')] }); } } diff --git a/packages/common/src/services/integrations/cleeng/CleengCheckoutService.ts b/packages/common/src/services/integrations/cleeng/CleengCheckoutService.ts index 1dc3bce05..6aa89be0e 100644 --- a/packages/common/src/services/integrations/cleeng/CleengCheckoutService.ts +++ b/packages/common/src/services/integrations/cleeng/CleengCheckoutService.ts @@ -97,6 +97,10 @@ export default class CleengCheckoutService extends CheckoutService { if (response.errors[0].includes(`Coupon ${payload.couponCode} not found`)) { throw new Error('Invalid coupon code'); } + + if (response.errors[0].includes(`Coupon ${payload.couponCode} cannot be applied on this offer`)) { + throw new Error('Invalid coupon code for this offer'); + } } return response; diff --git a/packages/ui-react/src/components/Header/Header.module.scss b/packages/ui-react/src/components/Header/Header.module.scss index eb66f4de2..18c5d00cc 100644 --- a/packages/ui-react/src/components/Header/Header.module.scss +++ b/packages/ui-react/src/components/Header/Header.module.scss @@ -77,7 +77,17 @@ flex: 1; align-items: center; - > a { + > ul { + margin: 0; + padding: 0; + list-style-type: none; + + li { + display: inline-block; + } + } + + a { height: 36px; min-height: 36px; margin: 0 6px; @@ -177,6 +187,24 @@ } } +.navButton { + overflow: visible; + + &::after { + position: absolute; + bottom: calc(((variables.$header-height - 36px) / 2) * -1); + left: 0; + width: 100%; + height: 2px; + background-color: variables.$white; + content: ''; + } + + body:global(.is-tabbing) &:focus::after { + display: none; + } +} + // // mediaQueries // -------------------------------- diff --git a/packages/ui-react/src/components/Header/Header.test.tsx b/packages/ui-react/src/components/Header/Header.test.tsx index 2cd961f57..138c85367 100644 --- a/packages/ui-react/src/components/Header/Header.test.tsx +++ b/packages/ui-react/src/components/Header/Header.test.tsx @@ -19,7 +19,7 @@ describe('
', () => { }); test('renders header', () => { - const playlistMenuItems = [ + + + + +
+ +`; diff --git a/packages/ui-react/src/components/Logo/Logo.tsx b/packages/ui-react/src/components/Logo/Logo.tsx index a6eef375c..77b88bdb9 100644 --- a/packages/ui-react/src/components/Logo/Logo.tsx +++ b/packages/ui-react/src/components/Logo/Logo.tsx @@ -5,6 +5,7 @@ import styles from './Logo.module.scss'; type Props = { src: string; + alt?: string; onLoad: () => void; }; @@ -13,7 +14,7 @@ type ImgRef = { width?: number; }; -const Logo: React.FC = ({ src, onLoad }: Props) => { +const Logo: React.FC = ({ src, alt = 'logo', onLoad }: Props) => { const [imgDimensions, updateImgDimensions] = useState({ height: undefined, width: undefined }); const onLoadHandler = (event: React.SyntheticEvent) => { @@ -24,7 +25,7 @@ const Logo: React.FC = ({ src, onLoad }: Props) => { return ( - logo + {alt} ); }; diff --git a/packages/ui-react/src/components/RegistrationForm/RegistrationForm.test.tsx b/packages/ui-react/src/components/RegistrationForm/RegistrationForm.test.tsx index 30e894e3d..2bc21f757 100644 --- a/packages/ui-react/src/components/RegistrationForm/RegistrationForm.test.tsx +++ b/packages/ui-react/src/components/RegistrationForm/RegistrationForm.test.tsx @@ -1,11 +1,25 @@ import React from 'react'; -import { renderWithRouter } from '../../../test/utils'; +import { renderWithRouter, waitForWithFakeTimers } from '../../../test/utils'; import RegistrationForm from './RegistrationForm'; +// The SocialButton component contains an SVG import that results in an absolute path on the current machine +// This results in snapshot inconsistencies per machine +vi.mock('../SocialButton/SocialButton.tsx', () => ({ + default: (props: { href: string }) => { + return Social Button; + }, +})); + +const socialLoginURLs = { + twitter: 'https://staging-v2.inplayer.com/', + facebook: 'https://www.facebook.com/', + google: 'https://accounts.google.com/', +}; + describe('', () => { - test('renders and matches snapshot', () => { + test('renders and matches snapshot', async () => { const { container } = renderWithRouter( ', () => { consentValues={{}} loading={false} onConsentChange={vi.fn()} + socialLoginURLs={socialLoginURLs} />, ); + await waitForWithFakeTimers(); + expect(container).toMatchSnapshot(); }); }); diff --git a/packages/ui-react/src/components/RegistrationForm/RegistrationForm.tsx b/packages/ui-react/src/components/RegistrationForm/RegistrationForm.tsx index 1f56b636d..85c6d943f 100644 --- a/packages/ui-react/src/components/RegistrationForm/RegistrationForm.tsx +++ b/packages/ui-react/src/components/RegistrationForm/RegistrationForm.tsx @@ -5,6 +5,7 @@ import DOMPurify from 'dompurify'; import type { FormErrors } from '@jwp/ott-common/types/form'; import type { CustomFormField, RegistrationFormData } from '@jwp/ott-common/types/account'; import { testId } from '@jwp/ott-common/src/utils/common'; +import type { SocialLoginURLs } from '@jwp/ott-hooks-react/src/useSocialLoginUrls'; import useToggle from '@jwp/ott-hooks-react/src/useToggle'; import Visibility from '@jwp/ott-theme/assets/icons/visibility.svg?react'; import VisibilityOff from '@jwp/ott-theme/assets/icons/visibility_off.svg?react'; @@ -20,6 +21,7 @@ import LoadingOverlay from '../LoadingOverlay/LoadingOverlay'; import Link from '../Link/Link'; import Icon from '../Icon/Icon'; import { modalURLFromLocation } from '../../utils/location'; +import SocialButtonsList from '../SocialButtonsList/SocialButtonsList'; import styles from './RegistrationForm.module.scss'; @@ -36,6 +38,7 @@ type Props = { submitting: boolean; validationError?: boolean; publisherConsents: CustomFormField[] | null; + socialLoginURLs: SocialLoginURLs | null; }; const RegistrationForm: React.FC = ({ @@ -51,6 +54,7 @@ const RegistrationForm: React.FC = ({ consentValues, onConsentChange, consentErrors, + socialLoginURLs, }: Props) => { const [viewPassword, toggleViewPassword] = useToggle(); @@ -80,7 +84,6 @@ const RegistrationForm: React.FC = ({ return (
-

{t('registration.sign_up')}

{errors.form ? ( @@ -88,6 +91,8 @@ const RegistrationForm: React.FC = ({ ) : null}
+ +

{t('registration.sign_up')}

> renders and matches snapshot 1`] = ` data-testid="registration-form" novalidate="" > +
+

registration.sign_up

-
diff --git a/packages/ui-react/src/containers/AccountModal/forms/Registration.tsx b/packages/ui-react/src/containers/AccountModal/forms/Registration.tsx index ba403fbae..c7443a1c5 100644 --- a/packages/ui-react/src/containers/AccountModal/forms/Registration.tsx +++ b/packages/ui-react/src/containers/AccountModal/forms/Registration.tsx @@ -6,8 +6,9 @@ import type { RegistrationFormData } from '@jwp/ott-common/types/account'; import { getModule } from '@jwp/ott-common/src/modules/container'; import AccountController from '@jwp/ott-common/src/controllers/AccountController'; import { checkConsentsFromValues, extractConsentValues, formatConsentsFromValues } from '@jwp/ott-common/src/utils/collection'; +import useSocialLoginUrls from '@jwp/ott-hooks-react/src/useSocialLoginUrls'; import useForm from '@jwp/ott-hooks-react/src/useForm'; -import { modalURLFromLocation } from '@jwp/ott-ui-react/src/utils/location'; +import { modalURLFromLocation, modalURLFromWindowLocation } from '@jwp/ott-ui-react/src/utils/location'; import { useAccountStore } from '@jwp/ott-common/src/stores/AccountStore'; import RegistrationForm from '../../../components/RegistrationForm/RegistrationForm'; @@ -54,6 +55,8 @@ const Registration = () => { setConsentValues(extractConsentValues(publisherConsents)); }, [accountController, publisherConsents]); + const socialLoginURLs = useSocialLoginUrls(modalURLFromWindowLocation('personal-details')); + const { handleSubmit, handleChange, handleBlur, values, errors, validationSchemaError, submitting } = useForm({ initialValues: { email: '', password: '' }, validationSchema: object().shape({ @@ -79,7 +82,6 @@ const Registration = () => { announce(t('registration.success'), 'success'); navigate(modalURLFromLocation(location, 'personal-details')); }, - onSubmitError: ({ resetValue }) => resetValue('password'), }); return ( @@ -96,6 +98,7 @@ const Registration = () => { consentValues={consentValues} publisherConsents={publisherConsents} loading={loading || publisherConsentsLoading} + socialLoginURLs={socialLoginURLs} /> ); }; diff --git a/packages/ui-react/src/containers/Layout/Layout.module.scss b/packages/ui-react/src/containers/Layout/Layout.module.scss index 3c8262988..19d703a84 100644 --- a/packages/ui-react/src/containers/Layout/Layout.module.scss +++ b/packages/ui-react/src/containers/Layout/Layout.module.scss @@ -19,25 +19,3 @@ .main { height: 100%; } - -.headerButton { - overflow: visible; - - &::after { - position: absolute; - bottom: calc(((variables.$header-height - 36px) / 2) * -1); - left: 0; - width: 100%; - height: 2px; - background-color: variables.$white; - content: ''; - } - - body:global(.is-tabbing) & { - &:focus { - &::after { - display: none; - } - } - } -} diff --git a/packages/ui-react/src/containers/Layout/Layout.tsx b/packages/ui-react/src/containers/Layout/Layout.tsx index 845f79cc3..f4bba8739 100644 --- a/packages/ui-react/src/containers/Layout/Layout.tsx +++ b/packages/ui-react/src/containers/Layout/Layout.tsx @@ -147,6 +147,8 @@ const Layout = () => { ); }; + const navItems = [{ label: t('home'), to: '/' }, ...menu.map((item) => ({ label: item.label, to: playlistURL(item.contentId) }))]; + const containerProps = { inert: sideBarOpen ? '' : undefined }; // inert is not yet officially supported in react return ( @@ -189,6 +191,7 @@ const Layout = () => { canLogin={canLogin} showPaymentsMenuItem={accessModel !== ACCESS_MODEL.AVOD} favoritesEnabled={favoritesEnabled} + siteName={siteName} profilesData={{ currentProfile: profile, profiles, @@ -196,12 +199,8 @@ const Layout = () => { selectProfile: ({ avatarUrl, id }) => selectProfile.mutate({ id, avatarUrl }), isSelectingProfile: selectProfile.isLoading, }} - > -