feat(735): added subscription confirmation and resulting steps (#315)

keep-around/af30b88d367751c9e05a735e4a0467a96238ef47
Mirlan 5 years ago committed by GitHub
parent 3ecbd9020f
commit 3da64ed59a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 6
      src/config/lexics/indexLexics.tsx
  2. 48
      src/features/BuyMatchPopup/components/ConfirmationStep/index.tsx
  3. 34
      src/features/BuyMatchPopup/components/ErrorStep/index.tsx
  4. 40
      src/features/BuyMatchPopup/components/SubscriptionSelectionStep/index.tsx
  5. 17
      src/features/BuyMatchPopup/components/Subscriptions/index.tsx
  6. 72
      src/features/BuyMatchPopup/components/SubscriptionsList/index.tsx
  7. 10
      src/features/BuyMatchPopup/components/SubscriptionsList/styled.tsx
  8. 36
      src/features/BuyMatchPopup/components/SuccessStep/index.tsx
  9. 16
      src/features/BuyMatchPopup/index.tsx
  10. 39
      src/features/BuyMatchPopup/store/hooks/index.tsx
  11. 50
      src/features/BuyMatchPopup/store/hooks/useSubscriptions.tsx
  12. 43
      src/features/BuyMatchPopup/styled.tsx
  13. 4
      src/features/PaymentPeriodTabs/index.tsx
  14. 18
      src/requests/buyMatchSubscriptions.tsx
  15. 2
      src/requests/getMatchSubscriptions.tsx
  16. 1
      src/requests/index.tsx

@ -21,13 +21,19 @@ const matchPopupLexics = {
}
const buyMatchPopupLexics = {
buy_for: 14095,
buy_subscription: 13565,
change_card: 13564,
choose_subscription: 13563,
error_not_enough_balance: 14098,
for_month: 13561,
for_year: 13562,
payment: 14096,
payment_confirmation: 14094,
per_month: 13573,
per_year: 13574,
subscription_done: 2668,
success_subscription: 14097,
}
export const indexLexics = {

@ -0,0 +1,48 @@
import { currencySymbols } from 'config'
import { T9n } from 'features/T9n'
import {
CloseButton,
Header,
HeaderActions,
HeaderTitle,
} from 'features/PopupComponents'
import { useBuyMatchPopupStore } from '../../store'
import { SubscriptionsList } from '../SubscriptionsList'
import {
Wrapper,
Footer,
Button,
Body,
} from '../../styled'
export const ConfirmationStep = () => {
const {
close,
selectedSubscriptions,
subscribeToMatches,
totalPrice,
} = useBuyMatchPopupStore()
return (
<Wrapper>
<Header height={50}>
<HeaderTitle>
<T9n t='payment_confirmation' />
</HeaderTitle>
<HeaderActions position='right'>
<CloseButton onClick={close} />
</HeaderActions>
</Header>
<Body marginTop={25}>
<SubscriptionsList subscriptions={selectedSubscriptions} />
</Body>
<Footer marginTop={35}>
<Button onClick={subscribeToMatches}>
<T9n t='buy_for' /> {totalPrice} {currencySymbols.ruble}
</Button>
</Footer>
</Wrapper>
)
}

@ -0,0 +1,34 @@
import { T9n } from 'features/T9n'
import {
Header,
HeaderTitle,
HeaderActions,
CloseButton,
} from 'features/PopupComponents'
import { useBuyMatchPopupStore } from '../../store'
import {
Wrapper,
Body,
ResultText,
} from '../../styled'
export const ErrorStep = () => {
const { close } = useBuyMatchPopupStore()
return (
<Wrapper width={517}>
<Header height={50}>
<HeaderTitle>
<T9n t='payment' />
</HeaderTitle>
<HeaderActions position='right'>
<CloseButton onClick={close} />
</HeaderActions>
</Header>
<Body marginTop={30} marginBottom={35}>
<ResultText t='error_not_enough_balance' />
</Body>
</Wrapper>
)
}

@ -1,11 +1,6 @@
import styled from 'styled-components/macro'
import isEmpty from 'lodash/isEmpty'
import { MDASH } from 'config'
import { T9n } from 'features/T9n'
import { useBuyMatchPopupStore } from 'features/BuyMatchPopup'
import {
CloseButton,
Header,
@ -15,27 +10,21 @@ import {
import { Name } from 'features/Name'
import { Steps } from 'features/BuyMatchPopup/types'
import { useBuyMatchPopupStore } from '../../store'
import { SelectedCard } from '../SelectedCard'
import { Subscriptions } from '../Subscriptions'
import { Button } from '../../styled'
const Wrapper = styled.div`
width: 870px;
height: 695px;
`
const Center = styled.div`
width: 100%;
display: flex;
justify-content: center;
`
import {
Wrapper,
Body,
Footer,
Button,
} from '../../styled'
export const MatchSubscriptionsStep = () => {
export const SubscriptionSelectionStep = () => {
const {
close,
goTo,
match,
selectedSubscriptions,
} = useBuyMatchPopupStore()
if (!match) return null
@ -52,16 +41,17 @@ export const MatchSubscriptionsStep = () => {
<CloseButton onClick={close} />
</HeaderActions>
</Header>
<Subscriptions />
<SelectedCard />
<Center>
<Body>
<Subscriptions />
<SelectedCard />
</Body>
<Footer>
<Button
disabled={isEmpty(selectedSubscriptions)}
onClick={(e) => goTo(e, Steps.Confirmation)}
onClick={(e) => goTo(Steps.Confirmation, e)}
>
<T9n t='buy_subscription' />
</Button>
</Center>
</Footer>
</Wrapper>
)
}

@ -1,9 +1,9 @@
import styled from 'styled-components/macro'
import { T9n } from 'features/T9n'
import { useBuyMatchPopupStore } from 'features/BuyMatchPopup'
import { PaymentPeriodTabs } from 'features/PaymentPeriodTabs'
import { useBuyMatchPopupStore } from '../../store'
import { SubscriptionsList } from '../SubscriptionsList'
const Wrapper = styled.div`
@ -22,7 +22,13 @@ const Title = styled.span`
`
export const Subscriptions = () => {
const { onPeriodSelect, selectedPeriod } = useBuyMatchPopupStore()
const {
onPeriodSelect,
onSubscriptionSelect,
selectedPeriod,
selectedSubscriptions,
subscriptions,
} = useBuyMatchPopupStore()
return (
<Wrapper>
<PaymentPeriodTabs
@ -32,7 +38,12 @@ export const Subscriptions = () => {
<Title>
<T9n t='choose_subscription' />
</Title>
<SubscriptionsList />
<SubscriptionsList
height={364}
subscriptions={subscriptions}
selectedSubscriptions={selectedSubscriptions}
onSelect={onSubscriptionSelect}
/>
</Wrapper>
)
}

@ -1,7 +1,7 @@
import map from 'lodash/map'
import includes from 'lodash/includes'
import { useBuyMatchPopupStore } from 'features/BuyMatchPopup/store'
import type { MatchSubscriptions, MatchSubscription } from 'requests'
import {
List,
@ -12,41 +12,39 @@ import {
Price,
} from './styled'
export const SubscriptionsList = () => {
const {
onSubscriptionSelect,
selectedSubscriptions,
subscriptions,
} = useBuyMatchPopupStore()
type Props = {
height?: number,
onSelect?: (subscription: MatchSubscription) => void,
selectedSubscriptions?: MatchSubscriptions,
subscriptions: MatchSubscriptions,
}
return (
<List>
{
map(subscriptions, ({
description,
header,
price,
subscription_id,
type,
}) => (
<Item
key={subscription_id}
onClick={() => onSubscriptionSelect(subscription_id)}
active={includes(selectedSubscriptions, subscription_id)}
>
<InfoWrapper>
<Header>
{header}
</Header>
<Description>
{description}
</Description>
</InfoWrapper>
export const SubscriptionsList = ({
height,
onSelect,
selectedSubscriptions,
subscriptions,
}: Props) => (
<List height={height}>
{
map(subscriptions, (subscription) => (
<Item
key={subscription.subscription_id}
onClick={() => onSelect?.(subscription)}
active={includes(selectedSubscriptions, subscription)}
>
<InfoWrapper>
<Header>
{subscription.header}
</Header>
<Description>
{subscription.description}
</Description>
</InfoWrapper>
<Price amount={price} perPeriod={`per_${type}`} />
</Item>
))
}
</List>
)
}
<Price amount={subscription.price} perPeriod={`per_${subscription.type}`} />
</Item>
))
}
</List>
)

@ -4,8 +4,14 @@ import { popupScrollbarStyles } from 'features/PopupComponents'
import { Price as BasePrice } from 'features/Price'
import { PriceAmount, PriceDetails } from 'features/Price/styled'
export const List = styled.ul`
height: 364px;
type ListProps = {
height?: number,
marginTop?: number,
}
export const List = styled.ul<ListProps>`
max-height: 364px;
height: ${({ height }) => (height ? `${height}px` : 'auto')};
display: flex;
flex-direction: column;
overflow-y: auto;

@ -0,0 +1,36 @@
import { T9n } from 'features/T9n'
import {
Header,
HeaderTitle,
} from 'features/PopupComponents'
import { useBuyMatchPopupStore } from '../../store'
import {
Wrapper,
Body,
Footer,
Button,
ResultText,
} from '../../styled'
export const SuccessStep = () => {
const { close } = useBuyMatchPopupStore()
return (
<Wrapper width={517}>
<Header height={50}>
<HeaderTitle>
<T9n t='payment' />
</HeaderTitle>
</Header>
<Body marginTop={30} marginBottom={40}>
<ResultText t='success_subscription' />
</Body>
<Footer>
<Button onClick={close}>
<T9n t='subscription_done' />
</Button>
</Footer>
</Wrapper>
)
}

@ -1,6 +1,8 @@
import { useBuyMatchPopupStore } from 'features/BuyMatchPopup'
import { MatchSubscriptionsStep } from './components/MatchSubscriptions'
import { useBuyMatchPopupStore } from './store'
import { SubscriptionSelectionStep } from './components/SubscriptionSelectionStep'
import { ConfirmationStep } from './components/ConfirmationStep'
import { SuccessStep } from './components/SuccessStep'
import { ErrorStep } from './components/ErrorStep'
import { Modal } from './styled'
import { Steps } from './types'
@ -9,11 +11,11 @@ export * from './store'
const Empty = () => null
const components = {
[Steps.Subscriptions]: MatchSubscriptionsStep,
[Steps.Subscriptions]: SubscriptionSelectionStep,
[Steps.CardSelection]: Empty,
[Steps.Confirmation]: Empty,
[Steps.Success]: Empty,
[Steps.Error]: Empty,
[Steps.Confirmation]: ConfirmationStep,
[Steps.Success]: SuccessStep,
[Steps.Error]: ErrorStep,
}
export const BuyMatchPopup = () => {

@ -1,5 +1,9 @@
import type { MouseEvent } from 'react'
import { useCallback, useState } from 'react'
import {
useCallback,
useState,
useEffect,
} from 'react'
import last from 'lodash/last'
@ -17,18 +21,10 @@ type MatchData = Pick<Match, (
export const useBuyMatchPopup = () => {
const [steps, setSteps] = useState<Array<Steps>>([])
const [match, setMatch] = useState<MatchData>(null)
const {
onPeriodSelect,
onSubscriptionSelect,
resetSubscriptions,
selectedPeriod,
selectedSubscriptions,
subscriptions,
} = useSubscriptions()
const goTo = useCallback(
(e: MouseEvent<HTMLButtonElement>, step: Steps) => setSteps((state) => {
e.stopPropagation()
(step: Steps, e?: MouseEvent<HTMLButtonElement>) => setSteps((state) => {
e?.stopPropagation()
return [...state, step]
}),
[],
@ -47,9 +43,25 @@ export const useBuyMatchPopup = () => {
const closePopup = () => {
setMatch(null)
setSteps([])
resetSubscriptions()
}
const {
fetchSubscriptions,
onPeriodSelect,
onSubscriptionSelect,
selectedPeriod,
selectedSubscriptions,
subscribeToMatches,
subscriptions,
totalPrice,
} = useSubscriptions(goTo)
useEffect(() => {
if (match) {
fetchSubscriptions()
}
}, [match, fetchSubscriptions])
return {
close: closePopup,
currentStep: last(steps),
@ -59,9 +71,10 @@ export const useBuyMatchPopup = () => {
onPeriodSelect,
onSubscriptionSelect,
open: openPopup,
resetSubscriptions,
selectedPeriod,
selectedSubscriptions,
subscribeToMatches,
subscriptions,
totalPrice,
}
}

@ -1,22 +1,30 @@
import type { MouseEvent } from 'react'
import {
useMemo,
useState,
useEffect,
useCallback,
} from 'react'
import sumBy from 'lodash/sumBy'
import filter from 'lodash/filter'
import without from 'lodash/without'
import includes from 'lodash/includes'
import type { MatchSubscriptions } from 'requests'
import { SubscriptionType, getMatchSubscriptions } from 'requests'
import type { MatchSubscriptions, MatchSubscription } from 'requests'
import {
SubscriptionType,
getMatchSubscriptions,
buyMatchSubscriptions,
} from 'requests'
import { Steps } from 'features/BuyMatchPopup/types'
export const useSubscriptions = () => {
export const useSubscriptions = (goTo: (step: Steps) => void) => {
const [selectedPeriod, setSelectedPeriod] = useState(SubscriptionType.Month)
const [subscriptionsList, setSubscriptionsList] = useState<MatchSubscriptions>([])
const [selectedSubscriptions, setSelectedSubscriptions] = useState<Array<number>>([])
const [selectedSubscriptions, setSelectedSubscriptions] = useState<MatchSubscriptions>([])
useEffect(() => {
const fetchSubscriptions = useCallback(() => {
getMatchSubscriptions().then(setSubscriptionsList)
}, [])
@ -25,16 +33,16 @@ export const useSubscriptions = () => {
[selectedPeriod, subscriptionsList],
)
const onSubscriptionSelect = (id: number) => {
if (includes(selectedSubscriptions, id)) {
const newSubscriptions = filter(
selectedSubscriptions,
(subscriptionId) => subscriptionId !== id,
)
setSelectedSubscriptions(newSubscriptions)
} else {
setSelectedSubscriptions([...selectedSubscriptions, id])
}
const totalPrice = useMemo(
() => sumBy(selectedSubscriptions, (subscription) => subscription.price),
[selectedSubscriptions],
)
const onSubscriptionSelect = (selected: MatchSubscription) => {
const newSubscriptions = includes(selectedSubscriptions, selected)
? without(selectedSubscriptions, selected)
: [...selectedSubscriptions, selected]
setSelectedSubscriptions(newSubscriptions)
}
const resetSubscriptions = useCallback(() => {
@ -42,12 +50,22 @@ export const useSubscriptions = () => {
setSelectedSubscriptions([])
}, [])
const subscribeToMatches = (e: MouseEvent) => {
e.stopPropagation()
buyMatchSubscriptions()
.then(() => goTo(Steps.Success))
.catch(() => goTo(Steps.Error))
}
return {
fetchSubscriptions,
onPeriodSelect: setSelectedPeriod,
onSubscriptionSelect,
resetSubscriptions,
selectedPeriod,
selectedSubscriptions,
subscribeToMatches,
subscriptions,
totalPrice,
}
}

@ -5,13 +5,14 @@ import { devices } from 'config'
import { Modal as BaseModal } from 'features/Modal'
import { ModalWindow } from 'features/Modal/styled'
import { ButtonSolid } from 'features/Common'
import { T9n } from 'features/T9n'
export const Modal = styled(BaseModal)`
background-color: rgba(0, 0, 0, 0.7);
${ModalWindow} {
min-width: 517px;
min-height: 310px;
min-height: auto;
padding: 20px 0;
background-color: #3F3F3F;
border-radius: 5px;
@ -37,3 +38,43 @@ export const Button = styled(ButtonSolid)`
opacity: 0.5;
}
`
type WrapperProps = {
width?: number,
}
export const Wrapper = styled.div<WrapperProps>`
width: ${({ width }) => (width ? `${width}px` : '870px')};
`
type BodyProps = {
marginBottom?: number,
marginTop?: number,
}
export const Body = styled.div<BodyProps>`
margin-top: ${({ marginTop }) => (marginTop ? `${marginTop}px` : '')};
margin-bottom: ${({ marginBottom }) => (marginBottom ? `${marginBottom}px` : '')};
`
type FooterProps = {
marginTop?: number,
}
export const Footer = styled.div<FooterProps>`
width: 100%;
display: flex;
justify-content: center;
margin-top: ${({ marginTop }) => (marginTop ? `${marginTop}px` : '')};
margin-bottom: 15px;
`
export const ResultText = styled(T9n)`
width: 100%;
display: inline-block;
text-align: center;
font-weight: normal;
font-size: 20px;
line-height: 27px;
`

@ -14,9 +14,7 @@ type ItemProps = {
active?: boolean,
}
export const Item = styled.li.attrs(() => ({
tabIndex: 0,
}))<ItemProps>`
export const Item = styled.li<ItemProps>`
width: 50%;
font-size: 20px;
line-height: 42px;

@ -0,0 +1,18 @@
import { API_ROOT } from 'config'
import { callApi } from 'helpers'
export const buyMatchSubscriptions = () => {
const config = {
body: {
_p_c_subscription_plan: 1,
_p_is_scheduled: 0,
},
}
callApi({
config,
url: `${API_ROOT}/account/purchase`,
})
return Promise.resolve()
}

@ -6,7 +6,7 @@ export enum SubscriptionType {
Year = 'year',
}
type MatchSubscription = {
export type MatchSubscription = {
description: string,
header: string,
price: number,

@ -25,3 +25,4 @@ export * from './getSportActions'
export * from './getMatchPlaylists'
export * from './getPlayerPlaylists'
export * from './getMatchSubscriptions'
export * from './buyMatchSubscriptions'

Loading…
Cancel
Save