import React, { useCallback, useContext, useState } from 'react'; import { useSelector } from 'react-redux'; import { VoucherResponse } from '../../shared/daemon-rpc-types'; import { messages } from '../../shared/gettext'; import { useScheduler } from '../../shared/scheduler'; import { useAppContext } from '../context'; import useActions from '../lib/actionsHook'; import accountActions from '../redux/account/actions'; import { IReduxState } from '../redux/store'; import * as AppButton from './AppButton'; import { ModalAlert } from './Modal'; import { StyledEmptyResponse, StyledErrorResponse, StyledInput, StyledLabel, StyledSpinner, StyledSuccessResponse, } from './RedeemVoucherStyles'; const MIN_VOUCHER_LENGTH = 16; interface IRedeemVoucherContextValue { onSubmit: () => void; value: string; setValue: (value: string) => void; valueValid: boolean; submitting: boolean; response?: VoucherResponse; } const contextProviderMissingError = new Error(' is missing'); const RedeemVoucherContext = React.createContext({ onSubmit() { throw contextProviderMissingError; }, get value(): string { throw contextProviderMissingError; }, setValue(_) { throw contextProviderMissingError; }, get valueValid(): boolean { throw contextProviderMissingError; }, get submitting(): boolean { throw contextProviderMissingError; }, get response(): VoucherResponse { throw contextProviderMissingError; }, }); interface IRedeemVoucherProps { onSubmit?: () => void; onSuccess?: () => void; onFailure?: () => void; children?: React.ReactNode; } export function RedeemVoucherContainer(props: IRedeemVoucherProps) { const { onSubmit, onSuccess, onFailure } = props; const closeScheduler = useScheduler(); const { submitVoucher } = useAppContext(); const { updateAccountExpiry } = useActions(accountActions); const [value, setValue] = useState(''); const [submitting, setSubmitting] = useState(false); const [response, setResponse] = useState(); const valueValid = value.length >= MIN_VOUCHER_LENGTH; const onSubmitWrapper = useCallback(async () => { if (!valueValid) { return; } setSubmitting(true); onSubmit?.(); const response = await submitVoucher(value); setSubmitting(false); setResponse(response); if (response.type === 'success') { setValue(''); closeScheduler.schedule(() => { updateAccountExpiry(response.newExpiry); onSuccess?.(); }, 1000); } else { onFailure?.(); } }, [value, valueValid, onSubmit, submitVoucher, updateAccountExpiry, onSuccess, onFailure]); return ( {props.children} ); } export function RedeemVoucherInput() { const { value, setValue, onSubmit, submitting, response } = useContext(RedeemVoucherContext); const disabled = submitting || response?.type === 'success'; const onChange = useCallback( (event: React.ChangeEvent) => { setValue(event.target.value); }, [setValue], ); const onKeyPress = useCallback( (event: React.KeyboardEvent) => { if (event.key === 'Enter') { onSubmit(); } }, [onSubmit], ); return ( ); } export function RedeemVoucherResponse() { const { response, submitting } = useContext(RedeemVoucherContext); if (submitting) { return ; } if (response) { switch (response.type) { case 'success': return ( {messages.pgettext('redeem-voucher-view', 'Voucher was successfully redeemed.')} ); case 'invalid': return ( {messages.pgettext('redeem-voucher-view', 'Voucher code is invalid.')} ); case 'already_used': return ( {messages.pgettext('redeem-voucher-view', 'Voucher code has already been used.')} ); case 'error': return ( {messages.pgettext('redeem-voucher-view', 'An error occurred.')} ); } } return ; } export function RedeemVoucherSubmitButton() { const { valueValid, onSubmit, submitting, response } = useContext(RedeemVoucherContext); const disabled = submitting || response?.type === 'success'; return ( {messages.pgettext('redeem-voucher-view', 'Redeem')} ); } interface IRedeemVoucherAlertProps { onClose?: () => void; } export function RedeemVoucherAlert(props: IRedeemVoucherAlertProps) { const { submitting, response } = useContext(RedeemVoucherContext); const cancelDisabled = submitting || response?.type === 'success'; return ( , {messages.pgettext('redeem-voucher-alert', 'Cancel')} , ]} close={props.onClose}> {messages.pgettext('redeem-voucher-alert', 'Enter voucher code')} ); } interface IRedeemVoucherButtonProps { className?: string; } export function RedeemVoucherButton(props: IRedeemVoucherButtonProps) { const isBlocked = useSelector((state: IReduxState) => state.connection.isBlocked); const [showAlert, setShowAlert] = useState(false); const onClick = useCallback(() => setShowAlert(true), []); const onClose = useCallback(() => setShowAlert(false), []); return ( <> {messages.pgettext('redeem-voucher-alert', 'Redeem voucher')} {showAlert && ( )} ); }