feat(#2428): Edit page of subsribtions
parent
423dc333a2
commit
e74c2f5303
@ -0,0 +1,100 @@ |
|||||||
|
import { ArrowLoader } from 'features/ArrowLoader' |
||||||
|
import { T9n } from 'features/T9n' |
||||||
|
|
||||||
|
import type { Subscribe } from 'requests/getUserSubscribes' |
||||||
|
|
||||||
|
import { format } from 'date-fns' |
||||||
|
|
||||||
|
import { |
||||||
|
Modal, |
||||||
|
Wrapper, |
||||||
|
Header, |
||||||
|
HeaderTitle, |
||||||
|
Body, |
||||||
|
Footer, |
||||||
|
ScSaveSubBtn, |
||||||
|
Text, |
||||||
|
ScCancelSub, |
||||||
|
ScStillCancelBtn, |
||||||
|
ScNotificationPopup, |
||||||
|
ScOkBtn, |
||||||
|
} from './styled' |
||||||
|
|
||||||
|
type Props = { |
||||||
|
cancelSub: () => void, |
||||||
|
error: string, |
||||||
|
handleModalClose: () => void, |
||||||
|
isFetching: boolean, |
||||||
|
isModalOpen: boolean, |
||||||
|
isSubCanceled: boolean, |
||||||
|
subscribe: Subscribe, |
||||||
|
} |
||||||
|
|
||||||
|
export const CancelSubPopup = (props: Props) => { |
||||||
|
const { |
||||||
|
cancelSub, |
||||||
|
error, |
||||||
|
handleModalClose, |
||||||
|
isFetching, |
||||||
|
isModalOpen, |
||||||
|
isSubCanceled, |
||||||
|
subscribe, |
||||||
|
} = props |
||||||
|
|
||||||
|
const { |
||||||
|
access_to, |
||||||
|
name, |
||||||
|
option_name, |
||||||
|
} = subscribe |
||||||
|
|
||||||
|
return ( |
||||||
|
<Modal isOpen={isModalOpen} withCloseButton={false}> |
||||||
|
<Wrapper> |
||||||
|
{isSubCanceled || error ? ( |
||||||
|
<ScNotificationPopup> |
||||||
|
<Text> |
||||||
|
<T9n t={error || 'sub_not_renewed'} /> |
||||||
|
</Text> |
||||||
|
<ScOkBtn onClick={() => handleModalClose()}>Ok</ScOkBtn> |
||||||
|
</ScNotificationPopup> |
||||||
|
) : ( |
||||||
|
<> |
||||||
|
<Header> |
||||||
|
<HeaderTitle> |
||||||
|
<T9n t='unsubscribe' /> |
||||||
|
</HeaderTitle> |
||||||
|
</Header> |
||||||
|
<Body> |
||||||
|
<Text> |
||||||
|
<T9n t='if_you_cancel' /> |
||||||
|
</Text> |
||||||
|
<ScCancelSub> |
||||||
|
{option_name} — {name} |
||||||
|
</ScCancelSub> |
||||||
|
<Text> |
||||||
|
<T9n t='after_canceling' /> |
||||||
|
|
||||||
|
{access_to && format(new Date(access_to), 'dd.MM.yyyy')} |
||||||
|
</Text> |
||||||
|
<Footer> |
||||||
|
<ScStillCancelBtn |
||||||
|
onClick={() => cancelSub()} |
||||||
|
isFetching={isFetching} |
||||||
|
> |
||||||
|
{isFetching ? ( |
||||||
|
<ArrowLoader /> |
||||||
|
) : ( |
||||||
|
<T9n t='still_cancel' /> |
||||||
|
)} |
||||||
|
</ScStillCancelBtn> |
||||||
|
<ScSaveSubBtn onClick={() => handleModalClose()}> |
||||||
|
<T9n t='save_sub' /> |
||||||
|
</ScSaveSubBtn> |
||||||
|
</Footer> |
||||||
|
</Body> |
||||||
|
</> |
||||||
|
)} |
||||||
|
</Wrapper> |
||||||
|
</Modal> |
||||||
|
) |
||||||
|
} |
||||||
@ -0,0 +1,195 @@ |
|||||||
|
import styled, { css } from 'styled-components/macro' |
||||||
|
|
||||||
|
import { isMobileDevice } from 'config/userAgent' |
||||||
|
import { devices } from 'config/devices' |
||||||
|
|
||||||
|
import { ModalWindow } from 'features/Modal/styled' |
||||||
|
import { Modal as BaseModal } from 'features/Modal' |
||||||
|
import { Header as BaseHeader } from 'features/PopupComponents' |
||||||
|
|
||||||
|
import { ButtonSolid, ButtonOutline } from 'features/Common' |
||||||
|
|
||||||
|
export const Modal = styled(BaseModal)` |
||||||
|
background-color: rgba(0, 0, 0, 0.7); |
||||||
|
|
||||||
|
${ModalWindow} { |
||||||
|
max-width: 642px; |
||||||
|
max-height: 340px; |
||||||
|
padding-top: 40px; |
||||||
|
background-color: #333333; |
||||||
|
border-radius: 5px; |
||||||
|
|
||||||
|
${isMobileDevice |
||||||
|
? css` |
||||||
|
height: auto; |
||||||
|
top: -7vh; |
||||||
|
width: 98%; |
||||||
|
max-height: none; |
||||||
|
padding: 0; |
||||||
|
` |
||||||
|
: ''}; |
||||||
|
} |
||||||
|
` |
||||||
|
|
||||||
|
type WrapperProps = { |
||||||
|
isFetching?: boolean, |
||||||
|
} |
||||||
|
|
||||||
|
export const Wrapper = styled.div<WrapperProps>` |
||||||
|
${({ isFetching }) => ( |
||||||
|
isFetching |
||||||
|
? css`pointer-events: none;` |
||||||
|
: '' |
||||||
|
)} |
||||||
|
` |
||||||
|
|
||||||
|
export const Header = styled(BaseHeader)` |
||||||
|
height: auto; |
||||||
|
justify-content: center; |
||||||
|
${isMobileDevice |
||||||
|
? css` |
||||||
|
@media ${devices.mobile}{ |
||||||
|
padding-top: 33px; |
||||||
|
} |
||||||
|
` |
||||||
|
: ''}; |
||||||
|
` |
||||||
|
|
||||||
|
export const HeaderTitle = styled.span` |
||||||
|
font-weight: 700; |
||||||
|
font-size: 24px; |
||||||
|
line-height: 24px; |
||||||
|
color: #FFFFFF; |
||||||
|
${isMobileDevice |
||||||
|
? css` |
||||||
|
font-size: 14px; |
||||||
|
line-height: 20px; |
||||||
|
|
||||||
|
@media (orientation: landscape) { |
||||||
|
font-size: 20px; |
||||||
|
} |
||||||
|
` |
||||||
|
: ''}; |
||||||
|
` |
||||||
|
|
||||||
|
export const Body = styled.div` |
||||||
|
padding: 20px 40px; |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
font-weight: normal; |
||||||
|
font-size: 20px; |
||||||
|
line-height: 27px; |
||||||
|
${isMobileDevice |
||||||
|
? css` |
||||||
|
padding: 13px 25px 0; |
||||||
|
flex-direction: column; |
||||||
|
|
||||||
|
@media (orientation: landscape){ |
||||||
|
padding: 22px 23px 0 29px; |
||||||
|
} |
||||||
|
` |
||||||
|
: ''}; |
||||||
|
` |
||||||
|
|
||||||
|
export const Footer = styled.div` |
||||||
|
width: 100%; |
||||||
|
display: flex; |
||||||
|
justify-content: center; |
||||||
|
margin-top: 25px; |
||||||
|
|
||||||
|
${isMobileDevice |
||||||
|
? css` |
||||||
|
flex-direction: column; |
||||||
|
` |
||||||
|
: ''}; |
||||||
|
` |
||||||
|
|
||||||
|
export const ScSaveSubBtn = styled(ButtonSolid)` |
||||||
|
max-width: 270px; |
||||||
|
border-radius: 5px; |
||||||
|
font-weight: 600; |
||||||
|
height: 50px; |
||||||
|
font-size: 20px; |
||||||
|
${isMobileDevice |
||||||
|
? css` |
||||||
|
max-width: none; |
||||||
|
width: 100%; |
||||||
|
min-height: 42px; |
||||||
|
margin-bottom: 20px; |
||||||
|
|
||||||
|
@media (orientation: landscape){ |
||||||
|
width: 290px; |
||||||
|
min-height: 42px; |
||||||
|
} |
||||||
|
` |
||||||
|
: ''}; |
||||||
|
` |
||||||
|
|
||||||
|
export const ScStillCancelBtn = styled(ButtonOutline)<{isFetching: boolean}>` |
||||||
|
max-width: 270px; |
||||||
|
height: 50px; |
||||||
|
border-radius: 5px; |
||||||
|
font-weight: 500; |
||||||
|
font-size: 20px; |
||||||
|
margin-right: 24px; |
||||||
|
|
||||||
|
${({ isFetching }) => (isFetching ? 'border: none' : '')}; |
||||||
|
|
||||||
|
${isMobileDevice |
||||||
|
? css` |
||||||
|
max-width: none; |
||||||
|
margin-bottom: 20px; |
||||||
|
width: 100%; |
||||||
|
|
||||||
|
@media (orientation: landscape){ |
||||||
|
width: 290px; |
||||||
|
} |
||||||
|
` |
||||||
|
: ''}; |
||||||
|
` |
||||||
|
|
||||||
|
export const Text = styled.span` |
||||||
|
display: block; |
||||||
|
color: rgba(255, 255, 255, 0.7); |
||||||
|
font-weight: 500; |
||||||
|
font-size: 16px; |
||||||
|
line-height: 20px; |
||||||
|
|
||||||
|
${isMobileDevice |
||||||
|
? css` |
||||||
|
font-size: 13px; |
||||||
|
` |
||||||
|
: ''}; |
||||||
|
` |
||||||
|
|
||||||
|
export const ScCancelSub = styled.div` |
||||||
|
max-width: 562px; |
||||||
|
height: 59px; |
||||||
|
background: linear-gradient( |
||||||
|
180deg, |
||||||
|
rgba(255, 255, 255, 0.1) 0%, |
||||||
|
rgba(255, 255, 255, 0) 100% |
||||||
|
), |
||||||
|
#3f3f3f; |
||||||
|
box-shadow: 0px 1px 1px rgba(0, 0, 0, 0.3); |
||||||
|
border-radius: 2px; |
||||||
|
font-weight: 500; |
||||||
|
font-size: 18px; |
||||||
|
line-height: 22px; |
||||||
|
padding: 20px; |
||||||
|
margin: 20px 0; |
||||||
|
overflow: hidden; |
||||||
|
text-overflow: ellipsis; |
||||||
|
white-space: nowrap; |
||||||
|
` |
||||||
|
|
||||||
|
export const ScNotificationPopup = styled.div` |
||||||
|
display: flex; |
||||||
|
flex-direction: column; |
||||||
|
align-items: center; |
||||||
|
` |
||||||
|
|
||||||
|
export const ScOkBtn = styled(ScSaveSubBtn)` |
||||||
|
max-width: 70px; |
||||||
|
margin-top: 40px; |
||||||
|
` |
||||||
@ -0,0 +1,36 @@ |
|||||||
|
import { useEffect } from 'react' |
||||||
|
|
||||||
|
import { useCardsStore } from 'features/CardsStore' |
||||||
|
import { CardStep } from 'features/BuyMatchPopup/components/CardStep' |
||||||
|
|
||||||
|
import { Modal } from './styled' |
||||||
|
|
||||||
|
type Props = { |
||||||
|
changeCardPopupOpen: boolean, |
||||||
|
setChangeCardPopupOpen: (open: boolean) => void, |
||||||
|
} |
||||||
|
|
||||||
|
export const ChangeCardPopup = ({ |
||||||
|
changeCardPopupOpen, |
||||||
|
setChangeCardPopupOpen, |
||||||
|
}: Props) => { |
||||||
|
const { fetchCards } = useCardsStore() |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
fetchCards() |
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []) |
||||||
|
|
||||||
|
return ( |
||||||
|
<Modal |
||||||
|
isOpen={changeCardPopupOpen} |
||||||
|
close={() => setChangeCardPopupOpen(false)} |
||||||
|
withCloseButton |
||||||
|
> |
||||||
|
<CardStep |
||||||
|
title='change_card' |
||||||
|
btnName='change' |
||||||
|
/> |
||||||
|
</Modal> |
||||||
|
) |
||||||
|
} |
||||||
@ -0,0 +1,25 @@ |
|||||||
|
import styled, { css } from 'styled-components/macro' |
||||||
|
import { ModalWindow } from 'features/Modal/styled' |
||||||
|
import { Modal as BaseModal } from 'features/Modal' |
||||||
|
import { isMobileDevice } from 'config/userAgent' |
||||||
|
|
||||||
|
export const Modal = styled(BaseModal)` |
||||||
|
background-color: rgba(0, 0, 0, 0.7); |
||||||
|
|
||||||
|
${ModalWindow} { |
||||||
|
padding: 0; |
||||||
|
background-color: #333333; |
||||||
|
border-radius: 5px; |
||||||
|
|
||||||
|
${isMobileDevice |
||||||
|
? css` |
||||||
|
width: calc(100vw - 60px); |
||||||
|
@media screen and (orientation: landscape){ |
||||||
|
max-width: calc(100vw - 80px); |
||||||
|
height: calc(100vh - 20px); |
||||||
|
overflow: auto; |
||||||
|
} |
||||||
|
` |
||||||
|
: ''}; |
||||||
|
} |
||||||
|
` |
||||||
@ -0,0 +1,62 @@ |
|||||||
|
import { useEffect, useState } from 'react' |
||||||
|
|
||||||
|
import { getUserSubscribes, Subscribe } from 'requests/getUserSubscribes' |
||||||
|
import { cancelSubscribe } from 'requests/cancelSubscribe' |
||||||
|
|
||||||
|
import { useAuthStore } from 'features/AuthStore' |
||||||
|
|
||||||
|
export const useUserSubscribes = () => { |
||||||
|
const [selectedSubscribe, setSelectedSubscribe] = useState<Subscribe>({} as Subscribe) |
||||||
|
const [subscribes, setSubscribes] = useState<any>([]) |
||||||
|
const [isCancelPopupOpen, setIsCancelPopupOpen] = useState(false) |
||||||
|
const [changeCardPopupOpen, setChangeCardPopupOpen] = useState(false) |
||||||
|
const [isSubCanceled, setSubCanceled] = useState(false) |
||||||
|
const [isFetching, setIsFetching] = useState(false) |
||||||
|
const [error, setError] = useState('') |
||||||
|
const { user } = useAuthStore() |
||||||
|
|
||||||
|
const handleClose = () => { |
||||||
|
setIsCancelPopupOpen(false) |
||||||
|
setChangeCardPopupOpen(false) |
||||||
|
setError('') |
||||||
|
} |
||||||
|
|
||||||
|
const cancelSub = async () => { |
||||||
|
try { |
||||||
|
setIsFetching(true) |
||||||
|
if (selectedSubscribe?.sub_id) { |
||||||
|
await cancelSubscribe(selectedSubscribe?.sub_id) |
||||||
|
setSubCanceled(true) |
||||||
|
} |
||||||
|
} catch { |
||||||
|
setError('error_payment_unsuccessful') |
||||||
|
} |
||||||
|
setIsFetching(false) |
||||||
|
} |
||||||
|
|
||||||
|
useEffect(() => { |
||||||
|
(async () => { |
||||||
|
setIsFetching(true) |
||||||
|
const allSubscribes = (await (getUserSubscribes(user?.profile?.email || ''))) |
||||||
|
.sort((a, b) => Date.parse(b.access_to) - Date.parse(a.access_to)) |
||||||
|
setSubscribes(allSubscribes) |
||||||
|
setIsFetching(false) |
||||||
|
})() |
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []) |
||||||
|
|
||||||
|
return { |
||||||
|
cancelSub, |
||||||
|
changeCardPopupOpen, |
||||||
|
error, |
||||||
|
handleClose, |
||||||
|
isCancelPopupOpen, |
||||||
|
isFetching, |
||||||
|
isSubCanceled, |
||||||
|
selectedSubscribe, |
||||||
|
setChangeCardPopupOpen, |
||||||
|
setIsCancelPopupOpen, |
||||||
|
setSelectedSubscribe, |
||||||
|
subscribes, |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,15 @@ |
|||||||
|
export const Edit = (): JSX.Element => ( |
||||||
|
<svg |
||||||
|
xmlns='http://www.w3.org/2000/svg' |
||||||
|
width='22' |
||||||
|
height='23' |
||||||
|
fill='true' |
||||||
|
viewBox='0 0 22 23' |
||||||
|
> |
||||||
|
<path |
||||||
|
fill='true' |
||||||
|
fillOpacity='0.3' |
||||||
|
d='M13.705 5.002L17.27 8.52 5.799 20.147l-4.017.507.452-4.023L13.705 5.002zM16.183 2.49a2.504 2.504 0 013.565 3.516L18.53 7.24l-3.565-3.517 1.217-1.234z' |
||||||
|
/> |
||||||
|
</svg> |
||||||
|
) |
||||||
@ -0,0 +1,15 @@ |
|||||||
|
import { API_ROOT } from 'config' |
||||||
|
import { callApi } from 'helpers' |
||||||
|
|
||||||
|
export const cancelSubscribe = (id: number): Promise<unknown> => { |
||||||
|
const config = { |
||||||
|
body: { |
||||||
|
id, |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
return callApi({ |
||||||
|
config, |
||||||
|
url: `${API_ROOT}/account/subscription/cancel`, |
||||||
|
}) |
||||||
|
} |
||||||
@ -0,0 +1,53 @@ |
|||||||
|
import { DATA_URL, PROCEDURES } from 'config' |
||||||
|
import { callApi } from 'helpers' |
||||||
|
|
||||||
|
const proc = PROCEDURES.get_user_subscribes |
||||||
|
|
||||||
|
export type Subscribe = { |
||||||
|
access_to: string, |
||||||
|
c_payment_service: number, |
||||||
|
c_subscription_option: number, |
||||||
|
card: string, |
||||||
|
currency_id: number, |
||||||
|
dt_next_billing: string | null, |
||||||
|
is_access: boolean, |
||||||
|
is_active: boolean, |
||||||
|
iso: string, |
||||||
|
name: string, |
||||||
|
option_name: string, |
||||||
|
option_sys_name: string, |
||||||
|
payment_type: string, |
||||||
|
price: number, |
||||||
|
purchase_type: string, |
||||||
|
sub_external_id: number | null, |
||||||
|
sub_id: number | null, |
||||||
|
sub_info: Array<Team>, |
||||||
|
ts_payment: string, |
||||||
|
} |
||||||
|
|
||||||
|
type Team = { |
||||||
|
id: number, |
||||||
|
name_en: string, |
||||||
|
name_ru: string, |
||||||
|
} |
||||||
|
|
||||||
|
type Subscribes = Array<Subscribe> |
||||||
|
|
||||||
|
export const getUserSubscribes = ( |
||||||
|
_p_email: string, |
||||||
|
) |
||||||
|
: Promise<Subscribes> => { |
||||||
|
const config = { |
||||||
|
body: { |
||||||
|
params: { |
||||||
|
_p_email, |
||||||
|
}, |
||||||
|
proc, |
||||||
|
}, |
||||||
|
} |
||||||
|
|
||||||
|
return callApi({ |
||||||
|
config, |
||||||
|
url: DATA_URL, |
||||||
|
}) |
||||||
|
} |
||||||
Loading…
Reference in new issue