Skip to content

Commit

Permalink
feat(v2/storage): add download completion/cancelation modal screen (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
karrui authored Jul 20, 2022
1 parent 994bc18 commit 012c09e
Show file tree
Hide file tree
Showing 14 changed files with 534 additions and 155 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const StorageResponsesProvider = ({
}, [form])

const downloadParams = useMemo(() => {
if (!secretKey || !dateRangeResponsesCount) return null
if (!secretKey || dateRangeResponsesCount === undefined) return null

return {
secretKey,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { useCallback, useMemo, useState } from 'react'
import { useThrottle } from 'react-use'
import { Box, MenuButton, Text, useDisclosure } from '@chakra-ui/react'
import simplur from 'simplur'

import { BxsChevronDown } from '~assets/icons/BxsChevronDown'
import { BxsChevronUp } from '~assets/icons/BxsChevronUp'
import { useTimeout } from '~hooks/useTimeout'
import { useToast } from '~hooks/useToast'
import Badge from '~components/Badge'
import Button from '~components/Button'
import Menu from '~components/Menu'

import { useStorageResponsesContext } from '../StorageResponsesContext'
import { CanceledResult, DownloadResult } from '../types'
import useDecryptionWorkers from '../useDecryptionWorkers'

import { DownloadWithAttachmentModal } from './DownloadWithAttachmentModal'
Expand All @@ -20,12 +23,22 @@ export const DownloadButton = (): JSX.Element => {
isOpen: isDownloadModalOpen,
onClose: onDownloadModalClose,
onOpen: onDownloadModalOpen,
} = useDisclosure()
} = useDisclosure({
// Reset metadata if it exists.
onOpen: () => setDownloadMetadata(undefined),
})
const {
isOpen: isProgressModalOpen,
onClose: onProgressModalClose,
onOpen: onProgressModalOpen,
} = useDisclosure()
} = useDisclosure({
// Reset metadata if it exists.
onOpen: () => setDownloadMetadata(undefined),
})

const toast = useToast({
isClosable: true,
})

const [progressModalTimeout, setProgressModalTimeout] = useState<
number | null
Expand All @@ -43,14 +56,48 @@ export const DownloadButton = (): JSX.Element => {

useTimeout(onProgressModalOpen, progressModalTimeout)

const [downloadMetadata, setDownloadMetadata] = useState<
DownloadResult | CanceledResult
>()

const { handleExportCsvMutation, abortDecryption } = useDecryptionWorkers({
onProgress: setDownloadCount,
mutateProps: {
onSuccess: () => {
onDownloadModalClose()
onMutate: () => {
// Reset metadata if it exists.
setDownloadMetadata(undefined)
},
onSuccess: ({ successCount, expectedCount, errorCount }) => {
if (downloadParams?.responsesCount === 0) {
toast({
description: 'No responses to download',
})
return
}
if (errorCount > 0) {
toast({
status: 'warning',
description: simplur`Partial success. ${successCount}/${expectedCount} ${[
successCount,
]}response[|s] [was|were] decrypted. ${errorCount} failed.`,
})
return
}
toast({
description: simplur`Success. ${successCount}/${expectedCount} ${[
successCount,
]}response[|s] [was|were] decrypted.`,
})
},
onSettled: () => {
resetDownload()
onError: () => {
toast({
status: 'danger',
description: 'Failed to start download. Please try again later.',
})
},
onSettled: (decryptResult) => {
setProgressModalTimeout(null)
setDownloadMetadata(decryptResult)
},
},
})
Expand Down Expand Up @@ -79,30 +126,49 @@ export const DownloadButton = (): JSX.Element => {
onProgressModalClose()
}, [abortDecryption, onProgressModalClose])

const handleAbortDecryption = useCallback(() => {
const handleModalClose = useCallback(() => {
resetDownload()
onDownloadModalClose()
onProgressModalClose()
}, [onProgressModalClose, resetDownload])
setDownloadMetadata(undefined)
}, [onDownloadModalClose, onProgressModalClose, resetDownload])

const handleNoAttachmentsDownloadCancel = useCallback(() => {
handleModalClose()
toast({
status: 'warning',
description: 'Responses download has been canceled.',
})
setDownloadMetadata({ isCanceled: true })
}, [handleModalClose, toast])

const handleAttachmentsDownloadCancel = useCallback(() => {
resetDownload()
setDownloadMetadata({ isCanceled: true })
}, [resetDownload])

return (
<>
{dateRangeResponsesCount !== undefined && (
<DownloadWithAttachmentModal
responsesCount={dateRangeResponsesCount}
isOpen={isDownloadModalOpen}
onClose={onDownloadModalClose}
onClose={handleModalClose}
onDownload={handleExportCsvWithAttachments}
onCancel={resetDownload}
onCancel={handleAttachmentsDownloadCancel}
downloadPercentage={downloadPercentage}
isDownloading={handleExportCsvMutation.isLoading}
downloadMetadata={downloadMetadata}
/>
)}
{dateRangeResponsesCount !== undefined && (
<ProgressModal
isOpen={isProgressModalOpen}
onClose={handleAbortDecryption}
onClose={handleModalClose}
onCancel={handleNoAttachmentsDownloadCancel}
downloadPercentage={downloadPercentage}
isDownloading={handleExportCsvMutation.isLoading}
downloadMetadata={downloadMetadata}
>
<Text mb="1rem">
<b>{dateRangeResponsesCount.toLocaleString()}</b> responses are
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import {
Badge,
ModalBody,
ModalFooter,
ModalHeader,
Text,
Wrap,
} from '@chakra-ui/react'

import { useIsMobile } from '~hooks/useIsMobile'
import Button from '~components/Button'
import { ModalCloseButton } from '~components/Modal'

interface CanceledScreenProps {
onClose: () => void
}

export const CanceledScreen = ({
onClose,
}: CanceledScreenProps): JSX.Element => {
const isMobile = useIsMobile()

return (
<>
<ModalCloseButton />
<ModalHeader color="secondary.700" pr="4.5rem">
<Wrap shouldWrapChildren direction="row" align="center">
<Text>Download stopped</Text>
<Badge w="fit-content" colorScheme="success">
beta
</Badge>
</Wrap>
</ModalHeader>
<ModalBody whiteSpace="pre-line" color="secondary.500">
Your responses and attachments have not been downloaded successfully.
</ModalBody>
<ModalFooter>
<Button isFullWidth={isMobile} onClick={onClose}>
Back to responses
</Button>
</ModalFooter>
</>
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,12 @@ export const ConfirmationScreen = ({
</List>
</Stack>
</InlineMessage>
{responsesCount === 0 && (
<InlineMessage variant="warning">
The date range you selected does not contain any responses. Please
select a date range containing responses and try again.
</InlineMessage>
)}
</Stack>
</ModalBody>
<ModalFooter>
Expand All @@ -99,6 +105,7 @@ export const ConfirmationScreen = ({
isFullWidth={isMobile}
onClick={onDownload}
isLoading={isDownloading}
isDisabled={responsesCount === 0}
>
Start download
</Button>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,43 @@ DownloadingStateDesktop.args = {
export const DownloadingStateMobile = Template.bind({})
DownloadingStateMobile.args = DownloadingStateDesktop.args
DownloadingStateMobile.parameters = getMobileViewParameters()

export const CompleteStateDesktop = Template.bind({})
CompleteStateDesktop.args = {
downloadMetadata: {
errorCount: 0,
successCount: 12345,
expectedCount: 12345,
},
}

export const CompleteStateMobile = Template.bind({})
CompleteStateMobile.args = CompleteStateDesktop.args
CompleteStateMobile.parameters = getMobileViewParameters()

export const CanceledStateDesktop = Template.bind({})
CanceledStateDesktop.args = {
downloadMetadata: {
isCanceled: true,
},
}

export const PartialSuccessStateDesktop = Template.bind({})
PartialSuccessStateDesktop.args = {
downloadMetadata: {
errorCount: 10,
successCount: 12335,
expectedCount: 12345,
},
}

export const PartialSuccessStateMobile = Template.bind({})
PartialSuccessStateMobile.args = {
downloadMetadata: {
errorCount: 10,
successCount: 1,
expectedCount: 11,
},
}

PartialSuccessStateMobile.parameters = getMobileViewParameters()
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@ import {

import { XMotionBox } from '~templates/MotionBox'

import { ProgressModalContent } from '../ProgressModal'
import { CanceledResult, DownloadResult } from '../../types'
import { isCanceledResult } from '../../utils/typeguards'
import { CompleteScreen, ProgressModalContent } from '../ProgressModal'

import { CanceledScreen } from './CanceledScreen'
import { ConfirmationScreen } from './ConfirmationScreen'

export interface DownloadWithAttachmentModalProps
Expand All @@ -22,6 +25,7 @@ export interface DownloadWithAttachmentModalProps
responsesCount: number
downloadPercentage: number
initialState?: [DownloadWithAttachmentFlowStates, number]
downloadMetadata?: DownloadResult | CanceledResult
}

/** Exported for testing. */
Expand All @@ -44,6 +48,7 @@ export const DownloadWithAttachmentModal = ({
isDownloading,
responsesCount,
downloadPercentage,
downloadMetadata,
initialState = INITIAL_STEP_STATE,
}: DownloadWithAttachmentModalProps): JSX.Element => {
const modalSize = useBreakpointValue({
Expand All @@ -60,17 +65,17 @@ export const DownloadWithAttachmentModal = ({
}
}, [isOpen])

useEffect(() => {
if (isOpen && downloadMetadata) {
setCurrentStep([DownloadWithAttachmentFlowStates.Complete, 1])
}
}, [downloadMetadata, isOpen])

const handleDownload = useCallback(() => {
setCurrentStep([DownloadWithAttachmentFlowStates.Progress, 1])
return onDownload()
}, [onDownload])

const handleCancel = useCallback(() => {
// TODO: Move to conclusion page.
setCurrentStep([DownloadWithAttachmentFlowStates.Confirmation, 1])
return onCancel()
}, [onCancel])

return (
<Modal
isOpen={isOpen}
Expand All @@ -95,7 +100,7 @@ export const DownloadWithAttachmentModal = ({
<ProgressModalContent
downloadPercentage={downloadPercentage}
isDownloading={isDownloading}
onClose={handleCancel}
onCancel={onCancel}
>
<Text mb="1rem">
Up to <b>{responsesCount.toLocaleString()}</b> files are being
Expand All @@ -104,6 +109,16 @@ export const DownloadWithAttachmentModal = ({
</Text>
</ProgressModalContent>
)}
{currentStep === DownloadWithAttachmentFlowStates.Complete ? (
isCanceledResult(downloadMetadata) ? (
<CanceledScreen onClose={onClose} />
) : (
<CompleteScreen
downloadMetadata={downloadMetadata}
onClose={onClose}
/>
)
) : null}
</XMotionBox>
</ModalContent>
</Modal>
Expand Down
Loading

0 comments on commit 012c09e

Please sign in to comment.