Skip to content

Commit

Permalink
Merge pull request #55 from jwplayer/feat/add-zustand-and-refactor-ac…
Browse files Browse the repository at this point in the history
…count-store

Add Zustand and refactor the AccountStore
  • Loading branch information
ChristiaanScheermeijer authored May 17, 2022
2 parents 6fcd9d8 + e8ae6e8 commit c98f8ae
Show file tree
Hide file tree
Showing 32 changed files with 719 additions and 530 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@
"react-router-dom": "^5.2.0",
"react-virtualized": "^9.22.3",
"wicg-inert": "^3.1.1",
"yup": "^0.32.9"
"yup": "^0.32.9",
"zustand": "^3.6.9"
},
"devDependencies": {
"@codeceptjs/configure": "^0.8.0",
Expand Down
2 changes: 1 addition & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import ConfigProvider from '#src/providers/ConfigProvider';
import QueryProvider from '#src/providers/QueryProvider';
import { restoreWatchHistory } from '#src/stores/WatchHistoryStore';
import { initializeFavorites } from '#src/stores/FavoritesStore';
import { initializeAccount } from '#src/stores/AccountStore';
import '#src/i18n/config';
import '#src/styles/main.scss';
import { initializeAccount } from '#src/stores/AccountController';

interface State {
error: Error | null;
Expand Down
8 changes: 4 additions & 4 deletions src/components/Account/Account.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ import React from 'react';
import { render } from '@testing-library/react';

import customer from '../../fixtures/customer.json';
import { AccountStore } from '../../stores/AccountStore';
import { useAccountStore } from '../../stores/AccountStore';

import Account from './Account';

import type { Consent } from '#types/account';

describe('<Account>', () => {
test('renders and matches snapshot', () => {
AccountStore.update((s) => {
s.user = customer;
s.publisherConsents = Array.of({ name: 'marketing', label: 'Receive Marketing Emails' } as Consent);
useAccountStore.setState({
user: customer,
publisherConsents: Array.of({ name: 'marketing', label: 'Receive Marketing Emails' } as Consent),
});

const { container } = render(<Account panelClassName={'panel-class'} panelHeaderClassName={'header-class'} />);
Expand Down
37 changes: 29 additions & 8 deletions src/components/Account/Account.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useHistory } from 'react-router-dom';
import shallow from 'zustand/shallow';

import { formatConsentsFromValues, formatConsentValues } from '../../utils/collection';
import type { FormSectionContentArgs, FormSectionProps } from '../Form/FormSection';
import Visibility from '../../icons/Visibility';
import VisibilityOff from '../../icons/VisibilityOff';
import useToggle from '../../hooks/useToggle';
Expand All @@ -11,11 +12,13 @@ import Form from '../Form/Form';
import IconButton from '../IconButton/IconButton';
import TextField from '../TextField/TextField';
import Checkbox from '../Checkbox/Checkbox';
import { addQueryParam } from '../../utils/history';
import { AccountStore, updateConsents, updateUser } from '../../stores/AccountStore';
import HelperText from '../HelperText/HelperText';
import type { FormSectionContentArgs, FormSectionProps } from '../Form/FormSection';
import { logDev } from '../../utils/common';

import { formatConsentsFromValues, formatConsentValues } from '#src/utils/collection';
import { addQueryParam } from '#src/utils/history';
import { useAccountStore } from '#src/stores/AccountStore';
import { logDev } from '#src/utils/common';
import { updateConsents, updateUser } from '#src/stores/AccountController';

type Props = {
panelClassName?: string;
Expand All @@ -35,10 +38,24 @@ const Account = ({ panelClassName, panelHeaderClassName }: Props): JSX.Element =
const history = useHistory();
const [viewPassword, toggleViewPassword] = useToggle();

const { user: customer, customerConsents, publisherConsents } = AccountStore.useState((state) => state);
const { customer, customerConsents, publisherConsents } = useAccountStore(
({ user, customerConsents, publisherConsents }) => ({
customer: user,
customerConsents,
publisherConsents,
}),
shallow,
);

const consentValues = useMemo(() => formatConsentValues(publisherConsents, customerConsents), [publisherConsents, customerConsents]);
const initialValues = useMemo(() => ({ ...customer, consents: consentValues, confirmationPassword: '' }), [customer, consentValues]);
const initialValues = useMemo(
() => ({
...customer,
consents: consentValues,
confirmationPassword: '',
}),
[customer, consentValues],
);

const formatConsentLabel = (label: string): string | JSX.Element => {
// @todo sanitize consent label to prevent XSS
Expand Down Expand Up @@ -123,7 +140,11 @@ const Account = ({ panelClassName, panelHeaderClassName }: Props): JSX.Element =
{[
formSection({
label: t('account.email'),
onSubmit: (values) => updateUser({ email: values.email || '', confirmationPassword: values.confirmationPassword }),
onSubmit: (values) =>
updateUser({
email: values.email || '',
confirmationPassword: values.confirmationPassword,
}),
canSave: (values) => !!(values.email && values.confirmationPassword),
editButton: t('account.edit_account'),
content: (section) => (
Expand Down
2 changes: 1 addition & 1 deletion src/components/UserMenu/UserMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import Favorite from '../../icons/Favorite';
import BalanceWallet from '../../icons/BalanceWallet';
import Exit from '../../icons/Exit';
import MenuButton from '../MenuButton/MenuButton';
import { logout } from '../../stores/AccountStore';

import styles from './UserMenu.module.scss';
import { logout } from '#src/stores/AccountController';

type Props = {
inPopover?: boolean;
Expand Down
9 changes: 5 additions & 4 deletions src/containers/AccountModal/AccountModal.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import React, { useEffect, useState } from 'react';
import { useHistory } from 'react-router';
import shallow from 'zustand/shallow';

import Dialog from '../../components/Dialog/Dialog';
import useQueryParam from '../../hooks/useQueryParam';
import { addQueryParam, removeQueryParam } from '../../utils/history';
import PaymentFailed from '../../components/PaymentFailed/PaymentFailed';
import Welcome from '../../components/Welcome/Welcome';
import { AccountStore } from '../../stores/AccountStore';
import { useAccountStore } from '../../stores/AccountStore';
import LoadingOverlay from '../../components/LoadingOverlay/LoadingOverlay';
import { ConfigStore } from '../../stores/ConfigStore';
import { useConfigStore } from '../../stores/ConfigStore';

import styles from './AccountModal.module.scss';
import Login from './forms/Login';
Expand All @@ -28,8 +29,8 @@ const AccountModal = () => {
const viewParam = useQueryParam('u');
const [view, setView] = useState(viewParam);
const message = useQueryParam('message');
const { loading, auth } = AccountStore.useState((s) => s);
const config = ConfigStore.useState((s) => s.config);
const { loading, auth } = useAccountStore(({ loading, auth }) => ({ loading, auth }), shallow);
const config = useConfigStore((s) => s.config);
const {
assets: { banner },
siteName,
Expand Down
5 changes: 3 additions & 2 deletions src/containers/AccountModal/forms/CancelSubscription.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ import { useHistory } from 'react-router';
import CancelSubscriptionForm from '../../../components/CancelSubscriptionForm/CancelSubscriptionForm';
import { removeQueryParam } from '../../../utils/history';
import LoadingOverlay from '../../../components/LoadingOverlay/LoadingOverlay';
import { AccountStore, updateSubscription } from '../../../stores/AccountStore';
import { useAccountStore } from '../../../stores/AccountStore';
import SubscriptionCancelled from '../../../components/SubscriptionCancelled/SubscriptionCancelled';
import { formatDate } from '../../../utils/formatting';
import { updateSubscription } from '#src/stores/AccountController';

const CancelSubscription = () => {
const { t } = useTranslation('account');
const history = useHistory();
const subscription = AccountStore.useState((s) => s.subscription);
const subscription = useAccountStore((s) => s.subscription);
const [cancelled, setCancelled] = useState(false);
const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);
Expand Down
8 changes: 4 additions & 4 deletions src/containers/AccountModal/forms/Checkout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,19 @@ import { useHistory } from 'react-router';
import { useTranslation } from 'react-i18next';

import CheckoutForm from '../../../components/CheckoutForm/CheckoutForm';
import { CheckoutStore, createOrder, updateOrder, getPaymentMethods, paymentWithoutDetails, adyenPayment, paypalPayment } from '../../../stores/CheckoutStore';
import { adyenPayment, CheckoutStore, createOrder, getPaymentMethods, paymentWithoutDetails, paypalPayment, updateOrder } from '../../../stores/CheckoutStore';
import { addQueryParam } from '../../../utils/history';
import useForm from '../../../hooks/useForm';
import LoadingOverlay from '../../../components/LoadingOverlay/LoadingOverlay';
import Adyen from '../../../components/Adyen/Adyen';
import PayPal from '../../../components/PayPal/PayPal';
import NoPaymentRequired from '../../../components/NoPaymentRequired/NoPaymentRequired';
import { addQueryParams } from '../../../utils/formatting';
import { reloadActiveSubscription } from '../../../stores/AccountStore';
import { ConfigStore } from '../../../stores/ConfigStore';
import { useConfigStore } from '../../../stores/ConfigStore';
import { reloadActiveSubscription } from '#src/stores/AccountController';

const Checkout = () => {
const { cleengSandbox } = ConfigStore.useState((s) => s.config);
const { cleengSandbox } = useConfigStore((s) => s.config);
const { t } = useTranslation('account');
const history = useHistory();
const [paymentError, setPaymentError] = useState<string | undefined>(undefined);
Expand Down
8 changes: 4 additions & 4 deletions src/containers/AccountModal/forms/ChooseOffer.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,25 @@
import React, { useEffect } from 'react';
import { object, SchemaOf, mixed } from 'yup';
import { mixed, object, SchemaOf } from 'yup';
import { useTranslation } from 'react-i18next';
import { useQuery } from 'react-query';
import { useHistory } from 'react-router';
import shallow from 'zustand/shallow';

import useForm, { UseFormOnSubmitHandler } from '../../../hooks/useForm';
import ChooseOfferForm from '../../../components/ChooseOfferForm/ChooseOfferForm';
import { getOffer } from '../../../services/checkout.service';
import LoadingOverlay from '../../../components/LoadingOverlay/LoadingOverlay';
import { CheckoutStore } from '../../../stores/CheckoutStore';
import { addQueryParam, removeQueryParam } from '../../../utils/history';
import { ConfigStore } from '../../../stores/ConfigStore';
import { useConfigStore } from '../../../stores/ConfigStore';

import type { ChooseOfferFormData, OfferPeriodicity } from '#types/account';

const ChooseOffer = () => {
const history = useHistory();
const { t } = useTranslation('account');
const config = ConfigStore.useState((s) => s.config);
const { config, accessModel } = useConfigStore(({ config, accessModel }) => ({ config, accessModel }), shallow);
const { cleengSandbox, json } = config;
const accessModel = ConfigStore.useState((s) => s.accessModel);
const hasOffer = accessModel === 'SVOD';
const offer = CheckoutStore.useState((s) => s.offer);

Expand Down
12 changes: 6 additions & 6 deletions src/containers/AccountModal/forms/EditPassword.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import { useHistory } from 'react-router-dom';
import { object, string } from 'yup';
import { useTranslation } from 'react-i18next';

import { changePassword } from '../../../stores/AccountStore';
import useForm, { UseFormOnSubmitHandler } from '../../../hooks/useForm';
import type { EditPasswordFormData } from '../../../../types/account';
import EditPasswordForm from '../../../components/EditPasswordForm/EditPasswordForm';
import useQueryParam from '../../../hooks/useQueryParam';
import { addQueryParams } from '../../../utils/formatting';
import type { EditPasswordFormData } from '#types/account';
import EditPasswordForm from '#src/components/EditPasswordForm/EditPasswordForm';
import { changePassword } from '#src/stores/AccountController';
import useQueryParam from '#src/hooks/useQueryParam';
import useForm, { UseFormOnSubmitHandler } from '#src/hooks/useForm';
import { addQueryParams } from '#src/utils/formatting';

const ResetPassword: React.FC = () => {
const { t } = useTranslation('account');
Expand Down
8 changes: 4 additions & 4 deletions src/containers/AccountModal/forms/Login.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import React from 'react';
import { object, string, SchemaOf } from 'yup';
import { object, SchemaOf, string } from 'yup';
import { useTranslation } from 'react-i18next';
import { useHistory } from 'react-router';

import LoginForm from '../../../components/LoginForm/LoginForm';
import { login } from '../../../stores/AccountStore';
import useForm, { UseFormOnSubmitHandler } from '../../../hooks/useForm';
import { removeQueryParam } from '../../../utils/history';
import { ConfigStore } from '../../../stores/ConfigStore';
import { useConfigStore } from '../../../stores/ConfigStore';

import type { LoginFormData } from '#types/account';
import { login } from '#src/stores/AccountController';

const Login = () => {
const { siteName } = ConfigStore.useState((s) => s.config);
const { siteName } = useConfigStore((s) => s.config);
const history = useHistory();
const { t } = useTranslation('account');
const loginSubmitHandler: UseFormOnSubmitHandler<LoginFormData> = async (formData, { setErrors, setSubmitting, setValue }) => {
Expand Down
6 changes: 3 additions & 3 deletions src/containers/AccountModal/forms/PersonalDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import { useQuery } from 'react-query';
import PersonalDetailsForm from '../../../components/PersonalDetailsForm/PersonalDetailsForm';
import useForm, { UseFormOnSubmitHandler } from '../../../hooks/useForm';
import { addQueryParam } from '../../../utils/history';
import { getCaptureStatus, updateCaptureAnswers } from '../../../stores/AccountStore';
import LoadingOverlay from '../../../components/LoadingOverlay/LoadingOverlay';
import { ConfigStore } from '../../../stores/ConfigStore';
import { useConfigStore } from '../../../stores/ConfigStore';

import type { CaptureCustomAnswer, CleengCaptureQuestionField, PersonalDetailsFormData } from '#types/account';
import { getCaptureStatus, updateCaptureAnswers } from '#src/stores/AccountController';

const yupConditional = (required: boolean, message: string) => {
return required ? string().required(message) : mixed().notRequired();
Expand All @@ -20,7 +20,7 @@ const yupConditional = (required: boolean, message: string) => {
const PersonalDetails = () => {
const history = useHistory();
const { t } = useTranslation('account');
const accessModel = ConfigStore.useState((s) => s.accessModel);
const accessModel = useConfigStore((s) => s.accessModel);
const { data, isLoading } = useQuery('captureStatus', () => getCaptureStatus());
const [questionValues, setQuestionValues] = useState<Record<string, string>>({});
const [questionErrors, setQuestionErrors] = useState<Record<string, string>>({});
Expand Down
2 changes: 1 addition & 1 deletion src/containers/AccountModal/forms/Registration.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import RegistrationForm from '../../../components/RegistrationForm/RegistrationF
import useForm, { UseFormOnSubmitHandler } from '../../../hooks/useForm';
import { addQueryParam } from '../../../utils/history';
import { extractConsentValues, checkConsentsFromValues } from '../../../utils/collection';
import { getPublisherConsents, register, updateConsents } from '../../../stores/AccountStore';

import type { RegistrationFormData } from '#types/account';
import { getPublisherConsents, register, updateConsents } from '#src/stores/AccountController';

const Registration = () => {
const history = useHistory();
Expand Down
7 changes: 5 additions & 2 deletions src/containers/AccountModal/forms/RenewSubscription.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
import React, { useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useHistory } from 'react-router';
import shallow from 'zustand/shallow';

import { removeQueryParam } from '../../../utils/history';
import LoadingOverlay from '../../../components/LoadingOverlay/LoadingOverlay';
import { AccountStore, updateSubscription } from '../../../stores/AccountStore';
import { useAccountStore } from '../../../stores/AccountStore';
import RenewSubscriptionForm from '../../../components/RenewSubscriptionForm/RenewSubscriptionForm';
import SubscriptionRenewed from '../../../components/SubscriptionRenewed/SubscriptionRenewed';

import { updateSubscription } from '#src/stores/AccountController';

const RenewSubscription = () => {
const { t } = useTranslation('account');
const history = useHistory();
const { subscription, user } = AccountStore.useState((s) => s);
const { subscription, user } = useAccountStore(({ subscription, user }) => ({ subscription, user }), shallow);
const [renewed, setRenewed] = useState(false);
const [submitting, setSubmitting] = useState(false);
const [error, setError] = useState<string | null>(null);
Expand Down
8 changes: 5 additions & 3 deletions src/containers/AccountModal/forms/ResetPassword.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { useHistory } from 'react-router-dom';
import { object, string } from 'yup';
import { useTranslation } from 'react-i18next';

import { resetPassword, AccountStore, logout } from '../../../stores/AccountStore';
import { removeQueryParam, addQueryParam } from '../../../utils/history';
import { useAccountStore } from '../../../stores/AccountStore';
import { addQueryParam, removeQueryParam } from '../../../utils/history';
import ResetPasswordForm from '../../../components/ResetPasswordForm/ResetPasswordForm';
import useForm, { UseFormOnSubmitHandler } from '../../../hooks/useForm';
import ForgotPasswordForm from '../../../components/ForgotPasswordForm/ForgotPasswordForm';
Expand All @@ -14,14 +14,16 @@ import LoadingOverlay from '../../../components/LoadingOverlay/LoadingOverlay';
import { addQueryParams } from '../../../utils/formatting';
import { logDev } from '../../../utils/common';

import { logout, resetPassword } from '#src/stores/AccountController';

type Prop = {
type: 'confirmation' | 'forgot' | 'reset' | 'edit';
};

const ResetPassword: React.FC<Prop> = ({ type }: Prop) => {
const { t } = useTranslation('account');
const history = useHistory();
const user = AccountStore.useState((state) => state.user);
const user = useAccountStore((state) => state.user);
const [resetPasswordSubmitting, setResetPasswordSubmitting] = useState<boolean>(false);

const cancelClickHandler = () => {
Expand Down
10 changes: 5 additions & 5 deletions src/containers/Layout/Layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import React, { FC, ReactNode, useEffect, useRef, useState } from 'react';
import { Helmet } from 'react-helmet';
import { useTranslation } from 'react-i18next';
import { useHistory } from 'react-router';
import shallow from 'zustand/shallow';

import { AccountStore } from '../../stores/AccountStore';
import { useAccountStore } from '../../stores/AccountStore';
import useSearchQueryUpdater from '../../hooks/useSearchQueryUpdater';
import { UIStore } from '../../stores/UIStore';
import Button from '../../components/Button/Button';
Expand All @@ -14,7 +15,7 @@ import DynamicBlur from '../../components/DynamicBlur/DynamicBlur';
import MenuButton from '../../components/MenuButton/MenuButton';
import { addQueryParam } from '../../utils/history';
import UserMenu from '../../components/UserMenu/UserMenu';
import { ConfigStore } from '../../stores/ConfigStore';
import { useConfigStore } from '../../stores/ConfigStore';

import styles from './Layout.module.scss';

Expand All @@ -25,15 +26,14 @@ type LayoutProps = {
const Layout: FC<LayoutProps> = ({ children }) => {
const history = useHistory();
const { t } = useTranslation('common');
const config = ConfigStore.useState((s) => s.config);
const { config, accessModel } = useConfigStore(({ config, accessModel }) => ({ config, accessModel }), shallow);
const { menu, assets, options, siteName, description, footerText, searchPlaylist, cleengId } = config;
const accessModel = ConfigStore.useState((s) => s.accessModel);
const blurImage = UIStore.useState((s) => s.blurImage);
const searchQuery = UIStore.useState((s) => s.searchQuery);
const searchActive = UIStore.useState((s) => s.searchActive);
const userMenuOpen = UIStore.useState((s) => s.userMenuOpen);
const { updateSearchQuery, resetSearchQuery } = useSearchQueryUpdater();
const isLoggedIn = !!AccountStore.useState((state) => state.user);
const isLoggedIn = !!useAccountStore((state) => state.user);

const searchInputRef = useRef<HTMLInputElement>(null) as React.MutableRefObject<HTMLInputElement>;

Expand Down
Loading

0 comments on commit c98f8ae

Please sign in to comment.