Skip to content

Commit

Permalink
feat(user): implement subscription business logics
Browse files Browse the repository at this point in the history
  • Loading branch information
royschut committed Jul 23, 2021
1 parent 76111fe commit 8fa377b
Show file tree
Hide file tree
Showing 15 changed files with 368 additions and 122 deletions.
8 changes: 3 additions & 5 deletions src/components/Payment/Payment.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,9 @@
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);
background: theme.$panel-bg;
border-radius: 4px;

> strong {
line-height: 16px;
letter-spacing: 0.25px;
Expand All @@ -31,8 +32,5 @@

.cardDetails {
display: flex;
}

.expiryDate {
width: 250px;
margin-top: variables.$base-spacing * 2;
}
98 changes: 67 additions & 31 deletions src/components/Payment/Payment.tsx
Original file line number Diff line number Diff line change
@@ -1,60 +1,96 @@
import React from 'react';
import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import type { Subscription } from 'types/subscription';
import type { PaymentDetail, Subscription, Transaction } from 'types/subscription';

import { formatCurrencySymbol, formatSubscriptionDates } from '../../utils/formatting';
import Button from '../Button/Button';
import TextField from '../TextField/TextField';

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

type Props = {
subscription: Subscription;
onEditSubscriptionClick: (subscription: Subscription) => void;
subscriptions: Subscription[];
paymentDetails: PaymentDetail[];
transactions: Transaction[];
isLoading: boolean;
onUpdateSubscriptionSubmit: (subscriptions: Subscription) => void;
panelClassName?: string;
panelHeaderClassName?: string;
};

const Payment = ({ subscription, onEditSubscriptionClick, panelClassName, panelHeaderClassName }: Props): JSX.Element => {
//@todo: remove
const mockSubscriptions: Subscription[] = [
{
subscriptionId: 590680399,
offerId: 'S568296139_ZW',
status: 'active',
expiresAt: 1615897260,
nextPaymentPrice: 22.15,
nextPaymentCurrency: 'EUR',
paymentGateway: 'adyen',
paymentMethod: 'card',
offerTitle: 'Annual subscription (recurring) to pride&prejudice',
period: 'year',
totalPrice: 20,
} as Subscription,
];

const Payment = ({
paymentDetails,
subscriptions = mockSubscriptions, //@todo
transactions,
isLoading,
onUpdateSubscriptionSubmit,
panelClassName,
panelHeaderClassName,
}: Props): JSX.Element => {
const { t } = useTranslation('user');
const showAllTransactions = () => console.info('show all');

const subscriptionDates = useMemo(() => formatSubscriptionDates(subscriptions), [subscriptions]);

console.info('Variables still to use: ', transactions, isLoading, onUpdateSubscriptionSubmit);

return (
<>
<div className={panelClassName}>
<div className={panelHeaderClassName}>
<h3>{t('payment.subscription_details')}</h3>
</div>
<div className={styles.infoBox}>
<p>
<strong>{t('payment.monthly_subscription')}</strong> <br />
{t('payment.next_billing_date_on')}
{'<date>'}
</p>
<p className={styles.price}>
<strong>{'€ 14.76'}</strong>
{'/'}
{t('payment.month')}
</p>
</div>
<Button label={t('payment.edit_subscription')} onClick={() => onEditSubscriptionClick} />
{subscriptions &&
subscriptions.map((subscription: Subscription, index: number) => (
<div className={styles.infoBox} key={subscription.subscriptionId}>
<p>
<strong>{t('payment.monthly_subscription')}</strong> <br />
{`${t('payment.next_billing_date_on')} ${subscriptionDates[index]}`}
</p>
<p className={styles.price}>
<strong>{`${formatCurrencySymbol(subscription.nextPaymentCurrency)} ${subscription.totalPrice.toFixed(2)}`}</strong>
{'/'}
{t(`payment.month`)} {/** @todo: create dynamic translation keys by period */}
</p>
</div>
))}
<Button label={t('payment.edit_subscription')} onClick={() => null} />
</div>
<div className={panelClassName}>
<div className={panelHeaderClassName}>
<h3>{t('payment.payment_method')}</h3>
</div>
<div>
<strong>{t('payment.card_number')}</strong>
<p>xxxx xxxx xxxx 3456</p>
<div className={styles.cardDetails}>
<div className={styles.expiryDate}>
<strong>{t('payment.expiry_date')}</strong>
<p>{subscription.expiresAt}</p>
</div>
<div>
<strong>{t('payment.cvc_cvv')}</strong>
<p>******</p>
{paymentDetails &&
paymentDetails.map((payment: PaymentDetail) => (
<div key={payment.id}>
<TextField
label={t('payment.card_number')}
value={`•••• •••• •••• ${payment.paymentMethodSpecificParams.lastCardFourDigits || ''}`}
editing={false}
/>
<div className={styles.cardDetails}>
<TextField label={t('payment.expiry_date')} value={payment.paymentMethodSpecificParams.cardExpirationDate} editing={false} />
<TextField label={t('payment.cvc_cvv')} value={'******'} editing={false} />
</div>
</div>
</div>
</div>
))}
</div>
<div className={panelClassName}>
<div className={panelHeaderClassName}>
Expand Down
34 changes: 3 additions & 31 deletions src/components/Payment/__snapshots__/Payment.test.tsx.snap
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,17 @@ exports[`<Payment> renders and matches snapshot 1`] = `
</strong>
<br />
payment.next_billing_date_on
&lt;date&gt;
payment.next_billing_date_on 19 0 1970
</p>
<p
class="price"
>
<strong>
14.76
20.00
</strong>
/
payment.month
</p>
</div>
<button
Expand All @@ -44,34 +44,6 @@ exports[`<Payment> renders and matches snapshot 1`] = `
payment.payment_method
</h3>
</div>
<div>
<strong>
payment.card_number
</strong>
<p>
xxxx xxxx xxxx 3456
</p>
<div
class="cardDetails"
>
<div
class="expiryDate"
>
<strong>
payment.expiry_date
</strong>
<p />
</div>
<div>
<strong>
payment.cvc_cvv
</strong>
<p>
******
</p>
</div>
</div>
</div>
</div>
<div>
<div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ type Props = {
fetchConsents?: boolean;
};

const CustomerContainer = ({ children, fetchConsents = true }: Props): JSX.Element => {
const AccountContainer = ({ children, fetchConsents = true }: Props): JSX.Element => {
const customer = AccountStore.useState((state) => state.user);
const auth = AccountStore.useState((state) => state.auth);
const { config } = ConfigStore.getRawState();
Expand Down Expand Up @@ -90,4 +90,4 @@ const CustomerContainer = ({ children, fetchConsents = true }: Props): JSX.Eleme
} as ChildrenParams);
};

export default CustomerContainer;
export default AccountContainer;
93 changes: 93 additions & 0 deletions src/containers/Customer/AccountContainer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import type { Consent, Customer, CustomerConsent, UpdateCustomerConsentsPayload, UpdateCustomerPayload } from 'types/account';
import { useMutation, useQuery } from 'react-query';
import type { CustomerFormErrors, CustomerFormValues, FormErrors } from 'types/form';

import { ConfigStore } from '../../stores/ConfigStore';
import { AccountStore } from '../../stores/AccountStore';
import { getCustomerConsents, getPublisherConsents, updateCustomer, updateCustomerConsents } from '../../services/account.service';

type ChildrenParams = {
customer: Customer;
errors: FormErrors<UpdateCustomerPayload>;
isLoading: boolean;
consentsLoading: boolean;
publisherConsents?: Consent[];
customerConsents?: CustomerConsent[];
onUpdateEmailSubmit: (values: CustomerFormValues) => void;
onUpdateInfoSubmit: (values: CustomerFormValues) => void;
onUpdateConsentsSubmit: (consents: CustomerConsent[]) => void;
onReset: () => void;
};

type Props = {
children: (data: ChildrenParams) => JSX.Element;
fetchConsents?: boolean;
};

const AccountContainer = ({ children, fetchConsents = true }: Props): JSX.Element => {
const customer = AccountStore.useState((state) => state.user);
const auth = AccountStore.useState((state) => state.auth);
const { config } = ConfigStore.getRawState();
const { cleengId, cleengSandbox } = config;
const jwt = auth?.jwt || '';
const publisherId = cleengId || '';
const customerId = customer?.id || '';

const customerMutation = useMutation((values: CustomerFormValues) => updateCustomer(values, cleengSandbox, jwt));
const { mutate: mutateCustomer, isLoading: isMutateCustomerLoading, data: mutateCustomerData, reset } = customerMutation;

const mutation = useMutation((values: UpdateCustomerConsentsPayload) => updateCustomerConsents(values, cleengSandbox, jwt));
const { mutate: mutateConsents, isLoading: isMutateConsentsLoading, data: mutateConsentsData } = mutation;

const enabled = fetchConsents && !!publisherId && !!customer?.id;
const fetchPublicherConsents = useQuery(['publisherConsents'], () => getPublisherConsents({ publisherId }, cleengSandbox), { enabled });
const { data: publisherConsents, isLoading: publisherConsentsLoading } = fetchPublicherConsents;

const fetchCustomerConsents = useQuery(['customerConsents'], () => getCustomerConsents({ customerId }, cleengSandbox, jwt), { enabled });
const { data: customerConsents, isLoading: customerConsentsLoading } = fetchCustomerConsents;

const onUpdateEmailSubmit = ({ id, email, confirmationPassword }: CustomerFormValues) => mutateCustomer({ id, email, confirmationPassword });
const onUpdateInfoSubmit = ({ id, firstName, lastName }: CustomerFormValues) => mutateCustomer({ id, firstName, lastName });
const onUpdateConsentsSubmit = (consents: CustomerConsent[]) => mutateConsents({ id: customerId, consents });

const translateErrors = (errors?: string[]) => {
const formErrors: CustomerFormErrors = {};

errors?.map((error) => {
switch (error) {
case 'Invalid param email':
formErrors.email = 'Invalid email address!';
break;
case 'Customer email already exists':
formErrors.email = 'Email already exists!';
break;
case 'Please enter a valid e-mail address.':
formErrors.email = 'Please enter a valid e-mail address.';
break;
case 'Invalid confirmationPassword': {
formErrors.confirmationPassword = 'Password incorrect!';
break;
}
default:
console.info('Unknown error', error);
return;
}
});
return formErrors;
};

return children({
customer,
isLoading: isMutateCustomerLoading || isMutateConsentsLoading,
errors: translateErrors(mutateCustomerData?.errors || mutateConsentsData?.errors),
publisherConsents: publisherConsents?.responseData?.consents,
customerConsents: customerConsents?.responseData?.consents,
consentsLoading: publisherConsentsLoading || customerConsentsLoading,
onUpdateEmailSubmit,
onUpdateInfoSubmit,
onUpdateConsentsSubmit,
onReset: reset,
} as ChildrenParams);
};

export default AccountContainer;
31 changes: 0 additions & 31 deletions src/containers/Subscription/Subscription.ts

This file was deleted.

53 changes: 53 additions & 0 deletions src/containers/Subscription/SubscriptionContainer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { useMutation, useQuery } from 'react-query';
import type { PaymentDetail, Subscription, Transaction, UpdateSubscriptionPayload } from 'types/subscription';

import { getPaymentDetails, getSubscriptions, getTransactions, updateSubscriptions } from '../../services/subscription.service';
import { AccountStore } from '../../stores/AccountStore';
import { ConfigStore } from '../../stores/ConfigStore';

type ChildrenParams = {
subscriptions: Subscription[];
paymentDetails: PaymentDetail[];
transactions: Transaction[];
isLoading: boolean;
onUpdateSubscriptionSubmit: (subscriptions: Subscription) => void;
};

type Props = {
children: (data: ChildrenParams) => JSX.Element;
};

const SubscriptionContainer = ({ children }: Props): JSX.Element => {
const customer = AccountStore.useState((state) => state.user);
const auth = AccountStore.useState((state) => state.auth);
const { config } = ConfigStore.getRawState();
const { cleengSandbox: sandbox } = config;
const jwt = auth?.jwt || '';
const customerId = customer?.id || '';

const getSubscriptionsQuery = useQuery(['subscriptions', customerId], () => getSubscriptions({ customerId }, sandbox, jwt));
const { data: subscriptions, isLoading: isSubscriptionsLoading } = getSubscriptionsQuery;

const subscriptionMutation = useMutation((values: UpdateSubscriptionPayload) => updateSubscriptions(values, sandbox, jwt));
const { mutate: mutateSubscriptions, isLoading: isSubscriptionMutationLoading } = subscriptionMutation;

const getPaymentDetailsQuery = useQuery(['paymentDetails', customerId], () => getPaymentDetails({ customerId }, sandbox, jwt));
const { data: paymentDetails, isLoading: isPaymentDetailsLoading } = getPaymentDetailsQuery;

const getTransactionsQuery = useQuery(['transactions', customerId], () => getTransactions({ customerId }, sandbox, jwt));
const { data: transactions, isLoading: isTransactionsLoading } = getTransactionsQuery;

const onUpdateSubscriptionSubmit = ({ offerId, status }: Subscription, cancellationReason?: string) => {
mutateSubscriptions({ customerId, offerId, status, cancellationReason });
};

return children({
subscriptions: subscriptions?.responseData,
paymentDetails: paymentDetails?.responseData.paymentDetails,
transactions: transactions?.responseData,
isLoading: isSubscriptionsLoading || isPaymentDetailsLoading || isTransactionsLoading || isSubscriptionMutationLoading,
onUpdateSubscriptionSubmit,
} as ChildrenParams);
};

export default SubscriptionContainer;
Loading

0 comments on commit 8fa377b

Please sign in to comment.