From d16034e0a62385499d7193f49734e57253468fb0 Mon Sep 17 00:00:00 2001 From: Roy Schut Date: Mon, 19 Jul 2021 15:31:19 +0200 Subject: [PATCH] feat(user): add user screen with account and payment --- .commitlintrc.js | 1 + src/components/Account/Account.module.scss | 11 ++ src/components/Account/Account.test.tsx | 14 +++ src/components/Account/Account.tsx | 70 +++++++++++ .../__snapshots__/Account.test.tsx.snap | 108 +++++++++++++++++ src/components/Payment/Payment.module.scss | 38 ++++++ src/components/Payment/Payment.test.tsx | 15 +++ src/components/Payment/Payment.tsx | 80 +++++++++++++ .../__snapshots__/Payment.test.tsx.snap | 113 ++++++++++++++++++ src/components/Root/Root.tsx | 4 +- src/containers/Account/Account.test.ts | 11 ++ src/containers/Account/Account.ts | 17 +++ .../Subscription/Subscription.test.ts | 11 ++ src/containers/Subscription/Subscription.ts | 17 +++ src/i18n/locales/en_US.ts | 1 + src/i18n/locales/en_US/user.json | 30 +++++ src/i18n/locales/nl_NL.ts | 1 + src/i18n/locales/nl_NL/user.json | 30 +++++ src/icons/AccountCircle.tsx | 10 ++ src/icons/BalanceWallet.tsx | 10 ++ src/icons/Exit.tsx | 10 ++ src/screens/User/User.module.scss | 86 +++++++++++++ src/screens/User/User.test.tsx | 14 +++ src/screens/User/User.tsx | 74 ++++++++++++ src/styles/_theme.scss | 5 + types/account.d.ts | 6 + types/subscription.ts | 3 + 27 files changed, 788 insertions(+), 2 deletions(-) create mode 100644 src/components/Account/Account.module.scss create mode 100644 src/components/Account/Account.test.tsx create mode 100644 src/components/Account/Account.tsx create mode 100644 src/components/Account/__snapshots__/Account.test.tsx.snap create mode 100644 src/components/Payment/Payment.module.scss create mode 100644 src/components/Payment/Payment.test.tsx create mode 100644 src/components/Payment/Payment.tsx create mode 100644 src/components/Payment/__snapshots__/Payment.test.tsx.snap create mode 100644 src/containers/Account/Account.test.ts create mode 100644 src/containers/Account/Account.ts create mode 100644 src/containers/Subscription/Subscription.test.ts create mode 100644 src/containers/Subscription/Subscription.ts create mode 100644 src/i18n/locales/en_US/user.json create mode 100644 src/i18n/locales/nl_NL/user.json create mode 100644 src/icons/AccountCircle.tsx create mode 100644 src/icons/BalanceWallet.tsx create mode 100644 src/icons/Exit.tsx create mode 100644 src/screens/User/User.module.scss create mode 100644 src/screens/User/User.test.tsx create mode 100644 src/screens/User/User.tsx create mode 100644 types/account.d.ts create mode 100644 types/subscription.ts diff --git a/.commitlintrc.js b/.commitlintrc.js index c2bc21425..7e93fa35b 100644 --- a/.commitlintrc.js +++ b/.commitlintrc.js @@ -9,6 +9,7 @@ module.exports = { 'videodetail', 'series', 'search', + 'user', 'watchhistory', 'favorites', 'analytics', diff --git a/src/components/Account/Account.module.scss b/src/components/Account/Account.module.scss new file mode 100644 index 000000000..4026567a5 --- /dev/null +++ b/src/components/Account/Account.module.scss @@ -0,0 +1,11 @@ +@use '../../styles/variables'; +@use '../../styles/theme'; +@use '../../styles/mixins/responsive'; + +.checkbox { + display: flex; + align-items: center; + > input { + margin-right: 10px; + } +} diff --git a/src/components/Account/Account.test.tsx b/src/components/Account/Account.test.tsx new file mode 100644 index 000000000..e8aefa2a5 --- /dev/null +++ b/src/components/Account/Account.test.tsx @@ -0,0 +1,14 @@ +import React from 'react'; +import { render } from '@testing-library/react'; + +import Account from './Account'; + +describe('', () => { + test('renders and matches snapshot', () => { + const account = { email: 'test@test.com' } as Account; + const { container } = render( console.info(account)} />); + + // todo + expect(container).toMatchSnapshot(); + }); +}); diff --git a/src/components/Account/Account.tsx b/src/components/Account/Account.tsx new file mode 100644 index 000000000..c4ab1bf50 --- /dev/null +++ b/src/components/Account/Account.tsx @@ -0,0 +1,70 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +import Button from '../../components/Button/Button'; + +import styles from './Account.module.scss'; + +type Props = { + account: Account; + update: (account: Account) => void; + panelClassName?: string; + panelHeaderClassName?: string; +}; + +const Account = ({ account, update, panelClassName, panelHeaderClassName }: Props): JSX.Element => { + const { t } = useTranslation('user'); + + return ( + <> +
+
+

{t('account.email')}

+
+
+ {t('account.email')} +

{account.email}

+
+
+
+
+

{t('account.security')}

+
+
+ {t('account.password')} +

****************

+
+
+
+
+

{t('account.about_you')}

+
+
+ {t('account.firstname')} +

{account.firstname}

+ {t('account.lastname')} +

{account.lastname}

+
+
+
+
+

{'Terms & tracking'}

+
+
+ +
+
+ + ); +}; + +export default Account; diff --git a/src/components/Account/__snapshots__/Account.test.tsx.snap b/src/components/Account/__snapshots__/Account.test.tsx.snap new file mode 100644 index 000000000..cfb888144 --- /dev/null +++ b/src/components/Account/__snapshots__/Account.test.tsx.snap @@ -0,0 +1,108 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` renders and matches snapshot 1`] = ` +
+
+
+

+ account.email +

+
+
+ + account.email + +

+ test@test.com +

+ +
+
+
+
+

+ account.security +

+
+
+ + account.password + +

+ **************** +

+ +
+
+
+
+

+ account.about_you +

+
+
+ + account.firstname + +

+ + account.lastname + +

+ +

+
+
+
+

+ Terms & tracking +

+
+
+ + +
+
+
+`; diff --git a/src/components/Payment/Payment.module.scss b/src/components/Payment/Payment.module.scss new file mode 100644 index 000000000..ff3246570 --- /dev/null +++ b/src/components/Payment/Payment.module.scss @@ -0,0 +1,38 @@ +@use '../../styles/variables'; +@use '../../styles/theme'; +@use '../../styles/mixins/responsive'; + +.infoBox { + display: flex; + justify-content: space-between; + margin-bottom: variables.$base-spacing; + padding: variables.$base-spacing / 2 variables.$base-spacing; + + font-size: 14px; + line-height: 18px; + text-shadow: 0 2px 4px rgba(0, 0, 0, 0.14), 0 3px 4px rgba(0, 0, 0, 0.12), 0 1px 5px rgba(0, 0, 0, 0.2); + background: rgba(61, 59, 59, 0.08); + border-radius: 4px; + > strong { + line-height: 16px; + letter-spacing: 0.25px; + } +} + +.price { + font-size: 14px; + line-height: 18px; + > strong { + font-weight: bold; + font-size: 24px; + line-height: 26px; + } +} + +.cardDetails { + display: flex; +} + +.expiryDate { + width: 250px; +} diff --git a/src/components/Payment/Payment.test.tsx b/src/components/Payment/Payment.test.tsx new file mode 100644 index 000000000..215e2d18f --- /dev/null +++ b/src/components/Payment/Payment.test.tsx @@ -0,0 +1,15 @@ +import React from 'react'; +import { render } from '@testing-library/react'; + +import Payment from './Payment'; + +describe('', () => { + test('renders and matches snapshot', () => { + const subscription = {} as Subscription; + + const { container } = render( console.info(subscription)} />); + + // todo + expect(container).toMatchSnapshot(); + }); +}); diff --git a/src/components/Payment/Payment.tsx b/src/components/Payment/Payment.tsx new file mode 100644 index 000000000..be154267a --- /dev/null +++ b/src/components/Payment/Payment.tsx @@ -0,0 +1,80 @@ +import React from 'react'; +import { useTranslation } from 'react-i18next'; + +import Button from '../Button/Button'; + +import styles from './Payment.module.scss'; + +type Props = { + subscription: Subscription; + update: (subscription: Subscription) => void; + panelClassName?: string; + panelHeaderClassName?: string; +}; + +const Payment = ({ subscription, update, panelClassName, panelHeaderClassName }: Props): JSX.Element => { + const { t } = useTranslation('user'); + const showAllTransactions = () => console.info('show all'); + + return ( + <> +
+
+

{t('payment.subscription_details')}

+
+
+

+ {t('payment.monthly_subscription')}
+ {t('payment.next_billing_date_on')} + {''} +

+

+ {'€ 14.76'} + {'/'} + {t('payment.month')} +

+
+
+
+
+

{t('payment.payment_method')}

+
+
+ {t('payment.card_number')} +

xxxx xxxx xxxx 3456

+
+
+ {t('payment.expiry_date')} +

03/2030

+
+
+ {t('payment.cvc_cvv')} +

******

+
+
+
+
+
+
+

{t('payment.transactions')}

+
+
+

+ {t('payment.monthly_subscription')}
+ {t('payment.price_payed_with_card')} +

+

+ {''} +
+ {''} +

+
+

{t('payment.more_transactions', { amount: 4 })}

+
+ + ); +}; + +export default Payment; diff --git a/src/components/Payment/__snapshots__/Payment.test.tsx.snap b/src/components/Payment/__snapshots__/Payment.test.tsx.snap new file mode 100644 index 000000000..df8a0cb5f --- /dev/null +++ b/src/components/Payment/__snapshots__/Payment.test.tsx.snap @@ -0,0 +1,113 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[` renders and matches snapshot 1`] = ` +
+
+
+

+ payment.subscription_details +

+
+
+

+ + payment.monthly_subscription + + +
+ payment.next_billing_date_on + <date> +

+

+ + € 14.76 + + / + payment.month +

+
+ +
+
+
+

+ payment.payment_method +

+
+
+ + payment.card_number + +

+ xxxx xxxx xxxx 3456 +

+
+
+ + payment.expiry_date + +

+ 03/2030 +

+
+
+ + payment.cvc_cvv + +

+ ****** +

+
+
+
+
+
+
+

+ payment.transactions +

+
+
+

+ + payment.monthly_subscription + + +
+ payment.price_payed_with_card +

+

+ <Invoice code> +
+ <Date> +

+
+

+ payment.more_transactions +

+ +
+
+`; diff --git a/src/components/Root/Root.tsx b/src/components/Root/Root.tsx index 95665d101..9245fa914 100644 --- a/src/components/Root/Root.tsx +++ b/src/components/Root/Root.tsx @@ -2,11 +2,11 @@ import React, { FC } from 'react'; import { Route, Switch } from 'react-router-dom'; import { useTranslation } from 'react-i18next'; +import User from '../../screens/User/User'; import Series from '../../screens/Series/Series'; import Layout from '../Layout/Layout'; import Home from '../../screens/Home/Home'; import Playlist from '../../screens/Playlist/Playlist'; -import Settings from '../../screens/Settings/Settings'; import Movie from '../../screens/Movie/Movie'; import Search from '../../screens/Search/Search'; import ErrorPage from '../ErrorPage/ErrorPage'; @@ -31,10 +31,10 @@ const Root: FC = ({ error }: Props) => { - + ); diff --git a/src/containers/Account/Account.test.ts b/src/containers/Account/Account.test.ts new file mode 100644 index 000000000..00fe6fc00 --- /dev/null +++ b/src/containers/Account/Account.test.ts @@ -0,0 +1,11 @@ +// import React from 'react'; + +describe('', () => { + test('renders and matches snapshot', () => { + // todo + // const item = { + // }; + // const { container } = render( null} onPause={() => null} />); + // expect(container).toMatchSnapshot(); + }); +}); diff --git a/src/containers/Account/Account.ts b/src/containers/Account/Account.ts new file mode 100644 index 000000000..faf71c463 --- /dev/null +++ b/src/containers/Account/Account.ts @@ -0,0 +1,17 @@ +type ChildrenParams = { + account: Account; + update: () => void; +}; + +type Props = { + children: (data: ChildrenParams) => JSX.Element; +}; + +const Account = ({ children }: Props): JSX.Element => { + const account: Account = {}; + const update = (values: Account) => console.info('update', values); + + return children({ account, update } as ChildrenParams); +}; + +export default Account; diff --git a/src/containers/Subscription/Subscription.test.ts b/src/containers/Subscription/Subscription.test.ts new file mode 100644 index 000000000..00fe6fc00 --- /dev/null +++ b/src/containers/Subscription/Subscription.test.ts @@ -0,0 +1,11 @@ +// import React from 'react'; + +describe('', () => { + test('renders and matches snapshot', () => { + // todo + // const item = { + // }; + // const { container } = render( null} onPause={() => null} />); + // expect(container).toMatchSnapshot(); + }); +}); diff --git a/src/containers/Subscription/Subscription.ts b/src/containers/Subscription/Subscription.ts new file mode 100644 index 000000000..d21464f4f --- /dev/null +++ b/src/containers/Subscription/Subscription.ts @@ -0,0 +1,17 @@ +type ChildrenParams = { + subscription: Account; + update: () => void; +}; + +type Props = { + children: (data: ChildrenParams) => JSX.Element; +}; + +const Subscription = ({ children }: Props): JSX.Element => { + const subscription: Subscription = {}; + const update = (values: Subscription) => console.info('update', values); + + return children({ subscription, update } as ChildrenParams); +}; + +export default Subscription; diff --git a/src/i18n/locales/en_US.ts b/src/i18n/locales/en_US.ts index 76a9f9319..ec7be0340 100644 --- a/src/i18n/locales/en_US.ts +++ b/src/i18n/locales/en_US.ts @@ -6,3 +6,4 @@ export { default as error } from './en_US/error.json'; export { default as menu } from './en_US/menu.json'; export { default as search } from './en_US/search.json'; export { default as video } from './en_US/video.json'; +export { default as user } from './en_US/user.json'; diff --git a/src/i18n/locales/en_US/user.json b/src/i18n/locales/en_US/user.json new file mode 100644 index 000000000..36130cf50 --- /dev/null +++ b/src/i18n/locales/en_US/user.json @@ -0,0 +1,30 @@ +{ + "account": { + "email": "Email", + "edit_account": "Edit account", + "security": "Security", + "password": "Password", + "edit_password": "Edit password", + "about_you": "About you", + "firstname": "First name", + "lastname": "Last name", + "edit_information": "Edit information", + "terms_and_tracking": "Terms & tracking", + "update_consents": "Update consents" + }, + "payment": { + "subscription_details": "Subscription details", + "monthly_subscription": "Monthly subscription", + "next_billing_date_on": "Next billing date is on", + "month": "month", + "edit_subscription": "Edit subscription", + "payment_method": "Payment method", + "card_number": "Card number", + "expiry_date": "Expiry date", + "cvc_cvv": "CVC / CVV", + "transactions": "Transactions", + "price_payed_with_card": "Price payed with card", + "more_transactions": "{{ amount }} more transactions", + "show_all": "Show all" + } +} diff --git a/src/i18n/locales/nl_NL.ts b/src/i18n/locales/nl_NL.ts index 27379f9a7..dc79decca 100644 --- a/src/i18n/locales/nl_NL.ts +++ b/src/i18n/locales/nl_NL.ts @@ -6,3 +6,4 @@ export { default as error } from './nl_NL/error.json'; export { default as menu } from './nl_NL/menu.json'; export { default as search } from './nl_NL/search.json'; export { default as video } from './nl_NL/video.json'; +export { default as user } from './nl_NL/user.json'; diff --git a/src/i18n/locales/nl_NL/user.json b/src/i18n/locales/nl_NL/user.json new file mode 100644 index 000000000..1649cd42d --- /dev/null +++ b/src/i18n/locales/nl_NL/user.json @@ -0,0 +1,30 @@ +{ + "account": { + "email": "", + "edit_account": "", + "security": "", + "password": "", + "edit_password": "", + "about_you": "", + "firstname": "", + "lastname": "", + "edit_information": "", + "terms_and_tracking": "", + "update_consents": "" + }, + "payment": { + "subscription_details": "", + "monthly_subscription": "", + "next_billing_date_on": "", + "month": "", + "edit_subscription": "", + "payment_method": "", + "card_number": "", + "expiry_date": "", + "cvc_cvv": "", + "transactions": "", + "price_payed_with_card": "", + "more_transactions": "", + "show_all": "" + } +} diff --git a/src/icons/AccountCircle.tsx b/src/icons/AccountCircle.tsx new file mode 100644 index 000000000..c6f7be028 --- /dev/null +++ b/src/icons/AccountCircle.tsx @@ -0,0 +1,10 @@ +import React from 'react'; + +import createIcon from './Icon'; + +export default createIcon( + '0 0 24 24', + + + , +); diff --git a/src/icons/BalanceWallet.tsx b/src/icons/BalanceWallet.tsx new file mode 100644 index 000000000..2d845ec67 --- /dev/null +++ b/src/icons/BalanceWallet.tsx @@ -0,0 +1,10 @@ +import React from 'react'; + +import createIcon from './Icon'; + +export default createIcon( + '0 0 24 24', + + + , +); diff --git a/src/icons/Exit.tsx b/src/icons/Exit.tsx new file mode 100644 index 000000000..a72b125f9 --- /dev/null +++ b/src/icons/Exit.tsx @@ -0,0 +1,10 @@ +import React from 'react'; + +import createIcon from './Icon'; + +export default createIcon( + '0 0 24 24', + + + , +); diff --git a/src/screens/User/User.module.scss b/src/screens/User/User.module.scss new file mode 100644 index 000000000..ab481c008 --- /dev/null +++ b/src/screens/User/User.module.scss @@ -0,0 +1,86 @@ +@use '../../styles/variables'; +@use '../../styles/theme'; +@use '../../styles/mixins/responsive'; + +.user { + display: flex; + justify-content: center; + margin: variables.$base-spacing * 3.5 variables.$base-spacing * 4; + color: var(--primary-color); + font-family: var(--body-alt-font-family); + + @include responsive.mobile-only() { + margin: 0 variables.$base-spacing; + } + + @include responsive.tablet-only() { + margin: 0 variables.$base-spacing * 2; + } +} +.leftColumn { + width: 250px; + margin-right: 24px; + padding-left: 19px; + + font-weight: bold; + font-size: 18px; + font-style: normal; + line-height: 20px; + letter-spacing: 0.5px; +} +ul { + margin: 0; + padding: 0; + list-style-type: none; +} + +.button { + margin-bottom: variables.$base-spacing; +} +.logoutLi { + margin-bottom: 0; + padding-top: variables.$base-spacing; + border-top: 1px solid rgba(255, 255, 255, 0.32); + > a { + margin-bottom: 0; + } +} + +.mainColumn { + width: 100%; + max-width: 750px; +} + +.panel { + margin-bottom: variables.$base-spacing * 1.5; + padding: variables.$base-spacing; + + font-weight: normal; + font-size: 16px; + font-style: normal; + line-height: 18px; + letter-spacing: 0.15px; + background: theme.$panel-bg; + box-shadow: theme.$panel-box-shadow; +} + +.panelHeader { + margin-bottom: variables.$base-spacing; + padding-bottom: variables.$base-spacing; + border-bottom: theme.$panel-header-border-bottom; + + > h3 { + font-weight: bold; + font-size: 24px; + line-height: 26px; + text-shadow: 0 2px 4px rgba(0, 0, 0, 0.14), 0 3px 4px rgba(0, 0, 0, 0.12), 0 1px 5px rgba(0, 0, 0, 0.2); + } +} + +.checkbox { + display: flex; + align-items: center; + > input { + margin-right: 10px; + } +} diff --git a/src/screens/User/User.test.tsx b/src/screens/User/User.test.tsx new file mode 100644 index 000000000..fd83caa97 --- /dev/null +++ b/src/screens/User/User.test.tsx @@ -0,0 +1,14 @@ +// import React from 'react'; +// import { render } from '@testing-library/react'; + +import { mockMatchMedia } from '../../testUtils'; + +// import Home from './Home'; + +describe('Home Component tests', () => { + mockMatchMedia(); + test('dummy test', () => { + // render(); + // expect(screen.getByText('hello world')).toBeInTheDocument(); + }); +}); diff --git a/src/screens/User/User.tsx b/src/screens/User/User.tsx new file mode 100644 index 000000000..5074f958b --- /dev/null +++ b/src/screens/User/User.tsx @@ -0,0 +1,74 @@ +import React from 'react'; +import { Redirect, Route, Switch } from 'react-router-dom'; + +import AccountContainer from '../../containers/Account/Account'; +import SubscriptionContainer from '../../containers/Subscription/Subscription'; +import useBreakpoint, { Breakpoint } from '../../hooks/useBreakpoint'; +import Button from '../../components/Button/Button'; +import Account from '../../components/Account/Account'; +import Payment from '../../components/Payment/Payment'; +import AccountCircle from '../../icons/AccountCircle'; +import Favorite from '../../icons/Favorite'; +import BalanceWallet from '../../icons/BalanceWallet'; +import Exit from '../../icons/Exit'; + +import styles from './User.module.scss'; + +const User = (): JSX.Element => { + const breakpoint = useBreakpoint(); + const isLargeScreen = breakpoint >= Breakpoint.md; + + return ( +
+ {isLargeScreen && ( +
+
+
    +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
  • +
+
+
+ )} +
+ + + + {({ account, update }) => ( + + )} + + + +
Favorites
+
+ + + {({ subscription, update }) => ( + + )} + + + + + + + + +
+
+
+ ); +}; + +export default User; diff --git a/src/styles/_theme.scss b/src/styles/_theme.scss index 530fe5174..25c094839 100644 --- a/src/styles/_theme.scss +++ b/src/styles/_theme.scss @@ -168,3 +168,8 @@ $video-details-tag-hover-bg: darken($dark-color, 5%) !default; $video-details-tag-color: variables.$white !default; $video-details-tag-hover-color: variables.$white !default; $video-details-tag-shadow: 0 1px 0 variables.$black !default; + +// UserScreen +$panel-bg: rgba(255, 255, 255, 0.08); +$panel-box-shadow: 0 6px 10px rgb(0 0 0 / 14%), 0 1px 18px rgb(0 0 0 / 12%), 0 3px 5px rgb(0 0 0 / 20%); +$panel-header-border-bottom: 1px solid rgba(255, 255, 255, 0.32); diff --git a/types/account.d.ts b/types/account.d.ts new file mode 100644 index 000000000..3999c1eaf --- /dev/null +++ b/types/account.d.ts @@ -0,0 +1,6 @@ +type Account = { + email: string; + password: string | null; + firstname: string | null; + lastname: string | null; +}; diff --git a/types/subscription.ts b/types/subscription.ts new file mode 100644 index 000000000..27f956f61 --- /dev/null +++ b/types/subscription.ts @@ -0,0 +1,3 @@ +type Subscription = { + // +};