diff --git a/src/components/layout/content.tsx b/src/components/layout/content.tsx index c2023df..127e0e2 100644 --- a/src/components/layout/content.tsx +++ b/src/components/layout/content.tsx @@ -1,11 +1,11 @@ import { useState } from 'react' import { Alert } from '@/components/ui/alert.tsx' -import type { Progress } from '@/types/upload-progress.ts' import { useUploadHistory } from '../../context/upload-history-context.tsx' import { useFilecoinPinContext } from '../../hooks/use-filecoin-pin-context.ts' import { useUploadExpansion } from '../../hooks/use-upload-expansion.ts' import { useUploadOrchestration } from '../../hooks/use-upload-orchestration.ts' import { useUploadUI } from '../../hooks/use-upload-ui.ts' +import type { StepState } from '../../types/upload/step.ts' import { formatFileSize } from '../../utils/format-file-size.ts' import { Heading } from '../ui/heading.tsx' import { LoadingState } from '../ui/loading-state.tsx' @@ -14,7 +14,7 @@ import DragNDrop from '../upload/drag-n-drop.tsx' import { UploadStatus } from '../upload/upload-status.tsx' // Completed state for displaying upload history -const COMPLETED_PROGRESS: Progress[] = [ +const COMPLETED_PROGRESS: StepState[] = [ { step: 'creating-car', status: 'completed', progress: 100 }, { step: 'checking-readiness', status: 'completed', progress: 100 }, { step: 'uploading-car', status: 'completed', progress: 100 }, @@ -94,7 +94,7 @@ export default function Content() { isExpanded={activeUploadExpanded} onToggleExpanded={() => setActiveUploadExpanded(!activeUploadExpanded)} pieceCid={activeUpload.pieceCid ?? ''} - progresses={activeUpload.progress} + stepStates={activeUpload.progress} transactionHash={activeUpload.transactionHash ?? ''} /> @@ -119,7 +119,7 @@ export default function Content() { key={upload.id} onToggleExpanded={() => toggleExpansion(upload.id)} pieceCid={upload.pieceCid} - progresses={COMPLETED_PROGRESS} + stepStates={COMPLETED_PROGRESS} transactionHash={upload.transactionHash} /> ))} diff --git a/src/components/ui/badge-status.tsx b/src/components/ui/badge-status.tsx index 06e1ee1..fe067d2 100644 --- a/src/components/ui/badge-status.tsx +++ b/src/components/ui/badge-status.tsx @@ -3,7 +3,7 @@ import { CircleCheck, LoaderCircle } from 'lucide-react' import type { Progress } from '../../types/upload-progress.ts' import { cn } from '../../utils/cn.ts' -export type Status = Progress['status'] | 'pinned' +export type Status = Progress['status'] | 'pinned' | 'published' type BadgeStatusProps = VariantProps & { status: Status @@ -15,6 +15,7 @@ const badgeVariants = cva('inline-flex items-center gap-1 pl-1.5 pr-2 py-0.5 rou 'in-progress': 'bg-badge-in-progress text-badge-in-progress-text border border-badge-in-progress-border', completed: 'bg-brand-950 text-brand-700 border border-brand-900', pinned: 'bg-brand-950 text-brand-700 border border-brand-900', + published: 'bg-yellow-600/30 border border-yellow-400/20 text-yellow-200', error: null, pending: 'bg-zinc-800 border border-zinc-700 text-zinc-300', }, @@ -28,6 +29,7 @@ const statusIcons: Record = { 'in-progress': , completed: , pinned: , + published: null, error: null, pending: null, } @@ -36,6 +38,7 @@ const statusLabels: Record = { 'in-progress': 'In progress', completed: 'Complete', pinned: 'Pinned', + published: 'Published', error: null, pending: 'Pending', } diff --git a/src/components/upload/car-upload-and-ipni-card.tsx b/src/components/upload/car-upload-and-ipni-card.tsx index 18f1889..136585c 100644 --- a/src/components/upload/car-upload-and-ipni-card.tsx +++ b/src/components/upload/car-upload-and-ipni-card.tsx @@ -1,8 +1,8 @@ import { getIpfsGatewayDownloadLink, getIpfsGatewayRenderLink } from '@/utils/links.ts' -import { COMBINED_STEPS } from '../../constants/upload-status.tsx' import { INPI_ERROR_MESSAGE } from '../../hooks/use-filecoin-upload.ts' import { useUploadProgress } from '../../hooks/use-upload-progress.ts' -import type { Progress } from '../../types/upload-progress.ts' +import { STAGE_STEPS } from '../../types/upload/stage.ts' +import type { StepState } from '../../types/upload/step.ts' import { Alert } from '../ui/alert.tsx' import { Card } from '../ui/card.tsx' import { DownloadButton } from '../ui/download-button.tsx' @@ -11,7 +11,7 @@ import { ProgressCard } from './progress-card.tsx' import { ProgressCardCombined } from './progress-card-combined.tsx' interface CarUploadAndIpniCardProps { - progresses: Progress[] + stepStates: StepState[] cid?: string fileName: string } @@ -22,14 +22,14 @@ interface CarUploadAndIpniCardProps { * It will shrink to a single card if the uploading-car and announcing-cids steps are completed. * Otherwise, it will display the progress of the uploading-car and announcing-cids steps. */ -export const CarUploadAndIpniCard = ({ progresses, cid, fileName }: CarUploadAndIpniCardProps) => { +export const CarUploadAndIpniCard = ({ stepStates, cid, fileName }: CarUploadAndIpniCardProps) => { // Use the upload progress hook to calculate all progress-related values - const { getCombinedFirstStageProgress, getCombinedFirstStageStatus, hasIpniFailure } = useUploadProgress( - progresses, - cid - ) - const uploadingStep = progresses.find((p) => p.step === 'uploading-car') - const announcingStep = progresses.find((p) => p.step === 'announcing-cids') + const { firstStageProgress, firstStageStatus, hasUploadIpniFailure } = useUploadProgress({ + stepStates, + cid, + }) + const uploadingStep = stepStates.find((stepState) => stepState.step === 'uploading-car') + const announcingStep = stepStates.find((stepState) => stepState.step === 'announcing-cids') const shouldShowCidCard = uploadingStep?.status === 'completed' && @@ -39,33 +39,33 @@ export const CarUploadAndIpniCard = ({ progresses, cid, fileName }: CarUploadAnd if (shouldShowCidCard) { return ( - {hasIpniFailure && } + {hasUploadIpniFailure && } } title="IPFS Root CID" > - {!hasIpniFailure && } + {!hasUploadIpniFailure && } ) } // Filter progresses to only include the combined steps for ProgressCardCombined - const combinedProgresses = progresses.filter((p) => COMBINED_STEPS.includes(p.step)) + const firstStageStates = stepStates.filter((stepState) => STAGE_STEPS['first-stage'].includes(stepState.step)) return ( <> - {announcingStep && } + {announcingStep && } ) } diff --git a/src/components/upload/progress-card-combined.tsx b/src/components/upload/progress-card-combined.tsx index 1375126..816d0e1 100644 --- a/src/components/upload/progress-card-combined.tsx +++ b/src/components/upload/progress-card-combined.tsx @@ -1,34 +1,27 @@ -import type { Progress } from '../../types/upload-progress.ts' -import { getEstimatedTime, getStepLabel } from '../../utils/upload-status.ts' +import type { StepState } from '../../types/upload/step.ts' +import { getStepEstimatedTime, getStepLabel } from '../../utils/upload/step.ts' import { Card } from '../ui/card.tsx' import { ProgressBar } from '../ui/progress-bar.tsx' interface ProgressCardCombinedProps { - progresses: Array - getCombinedFirstStageStatus: () => Progress['status'] - getCombinedFirstStageProgress: () => number + stepStates: Array + firstStageStatus: StepState['status'] + firstStageProgress: StepState['progress'] } -function ProgressCardCombined({ - progresses, - getCombinedFirstStageStatus, - getCombinedFirstStageProgress, -}: ProgressCardCombinedProps) { - const hasCreatingCarStep = progresses.find((progress) => progress.step === 'creating-car') - - const firstStagestatus = getCombinedFirstStageStatus() - const firstStageProgress = getCombinedFirstStageProgress() +function ProgressCardCombined({ stepStates, firstStageStatus, firstStageProgress }: ProgressCardCombinedProps) { + const hasCreatingCarStep = stepStates.find((stepState) => stepState.step === 'creating-car') if (!hasCreatingCarStep) return null return ( - {firstStagestatus === 'in-progress' && } + {firstStageStatus === 'in-progress' && } ) } diff --git a/src/components/upload/progress-card.tsx b/src/components/upload/progress-card.tsx index b611ed7..2490699 100644 --- a/src/components/upload/progress-card.tsx +++ b/src/components/upload/progress-card.tsx @@ -1,29 +1,29 @@ -import type { Progress } from '@/types/upload-progress.ts' -import { getEstimatedTime, getStepLabel } from '../../utils/upload-status.ts' +import type { StepState } from '../../types/upload/step.ts' +import { getStepEstimatedTime, getStepLabel } from '../../utils/upload/step.ts' import { Alert } from '../ui/alert.tsx' import { Card } from '../ui/card.tsx' import { TextWithCopyToClipboard } from '../ui/text-with-copy-to-clipboard.tsx' interface ProgressCardProps { - progress: Progress + stepState: StepState transactionHash?: string } -function ProgressCard({ progress, transactionHash }: ProgressCardProps) { +function ProgressCard({ stepState, transactionHash }: ProgressCardProps) { return ( - {progress.error && ( - + {stepState.error && ( + )} - {progress.step === 'finalizing-transaction' && transactionHash && ( + {stepState.step === 'finalizing-transaction' && transactionHash && ( + stepStates: Array transactionHash?: UploadStatusProps['transactionHash'] cid?: string fileName: string } -function UploadProgress({ progresses, transactionHash, cid, fileName }: UploadProgressProps) { - const finalizingStep = progresses.find((p) => p.step === 'finalizing-transaction') +function UploadProgress({ stepStates, transactionHash, cid, fileName }: UploadProgressProps) { + const finalizingStep = stepStates.find((stepState) => stepState.step === 'finalizing-transaction') return ( <> - - {finalizingStep && } + + {finalizingStep && } ) } diff --git a/src/components/upload/upload-status.tsx b/src/components/upload/upload-status.tsx index c79ff9f..d3d9afc 100644 --- a/src/components/upload/upload-status.tsx +++ b/src/components/upload/upload-status.tsx @@ -1,6 +1,6 @@ import type { DatasetPiece } from '../../hooks/use-dataset-pieces.ts' import { useUploadProgress } from '../../hooks/use-upload-progress.ts' -import type { Progress } from '../../types/upload-progress.ts' +import type { StepState } from '../../types/upload/step.ts' import { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from '../ui/accordion.tsx' import { FileInfo } from '../ui/file-info.tsx' import { UploadCompleted } from './upload-completed.tsx' @@ -9,7 +9,7 @@ import { UploadProgress } from './upload-progress.tsx' export interface UploadStatusProps { fileName: DatasetPiece['fileName'] fileSize: DatasetPiece['fileSize'] - progresses: Array + stepStates: Array isExpanded?: boolean onToggleExpanded?: () => void cid?: DatasetPiece['cid'] @@ -22,7 +22,7 @@ export interface UploadStatusProps { function UploadStatus({ fileName, fileSize, - progresses, + stepStates, isExpanded = true, onToggleExpanded, cid, @@ -31,7 +31,7 @@ function UploadStatus({ transactionHash, }: UploadStatusProps) { // Use the upload progress hook to calculate all progress-related values - const { isCompleted, badgeStatus } = useUploadProgress(progresses, cid) + const { isUploadSuccessful, uploadBadgeStatus } = useUploadProgress({ stepStates, cid }) return ( - + - {isCompleted && cid ? ( + {isUploadSuccessful && cid ? ( ) : ( - + )} diff --git a/src/constants/upload-status.tsx b/src/constants/upload-status.tsx deleted file mode 100644 index 899043f..0000000 --- a/src/constants/upload-status.tsx +++ /dev/null @@ -1,3 +0,0 @@ -import type { StepType } from '../types/upload-progress.ts' - -export const COMBINED_STEPS: StepType[] = ['creating-car', 'checking-readiness', 'uploading-car'] as const diff --git a/src/hooks/use-upload-orchestration.ts b/src/hooks/use-upload-orchestration.ts index 9e7d1b9..78531f8 100644 --- a/src/hooks/use-upload-orchestration.ts +++ b/src/hooks/use-upload-orchestration.ts @@ -52,7 +52,7 @@ export function useUploadOrchestration() { const { uploadState, uploadFile, resetUpload } = useFilecoinUpload() const { addUpload } = useUploadHistory() const { storageContext, providerInfo, wallet } = useFilecoinPinContext() - const { isCompleted } = useUploadProgress(uploadState.progress, uploadState.currentCid) + const { isUploadSuccessful } = useUploadProgress({ stepStates: uploadState.progress, cid: uploadState.currentCid }) // Track the file being uploaded (for displaying metadata like fileName) const [uploadedFile, setUploadedFile] = useState(null) @@ -71,7 +71,7 @@ export function useUploadOrchestration() { * 4. Force DragNDrop remount to clear its state */ useEffect(() => { - if (isCompleted && uploadState.pieceCid && uploadedFile && storageContext && providerInfo) { + if (isUploadSuccessful && uploadState.pieceCid && uploadedFile && storageContext && providerInfo) { console.debug('[UploadOrchestration] Upload completed, adding to history') // Mark this piece CID to be auto-expanded when it appears in history @@ -104,7 +104,7 @@ export function useUploadOrchestration() { setDragDropKey((prev) => prev + 1) } }, [ - isCompleted, + isUploadSuccessful, uploadState.pieceCid, uploadState.currentCid, uploadState.transactionHash, diff --git a/src/hooks/use-upload-progress.ts b/src/hooks/use-upload-progress.ts index 4d6341a..a3c5414 100644 --- a/src/hooks/use-upload-progress.ts +++ b/src/hooks/use-upload-progress.ts @@ -1,38 +1,21 @@ import { useMemo } from 'react' -import type { Progress } from '../types/upload-progress.ts' -import { createStepGroup } from '../utils/upload-status.ts' +import type { Status } from '@/components/ui/badge-status.tsx' +import type { StepState } from '../types/upload/step.ts' +import { getFirstStageProgress, getFirstStageStatus } from '../utils/upload/stage.ts' +import { getUploadBadgeStatus, getUploadOutcome } from '../utils/upload/upload.ts' export interface UploadProgressInfo { - /** - * Get the combined progress percentage for the first stage - * (creating-car + checking-readiness + uploading-car) - */ - getCombinedFirstStageProgress: () => number - - /** - * Get the combined status for the first stage - */ - getCombinedFirstStageStatus: () => Progress['status'] - - /** - * True if the IPNI announcement step failed - */ - hasIpniFailure: boolean - - /** - * True if all steps are completed (treating IPNI failures as acceptable) - */ - isCompleted: boolean - - /** - * True if any step has an error (excluding IPNI failures which are acceptable) - */ - hasError: boolean + firstStageProgress: StepState['progress'] + firstStageStatus: StepState['status'] + hasUploadIpniFailure: boolean + isUploadSuccessful: boolean + isUploadFailure: boolean + uploadBadgeStatus: Status +} - /** - * Badge status for the file card header - */ - badgeStatus: 'pinned' | 'error' | 'in-progress' | 'pending' +type useUploadProgressProps = { + stepStates: StepState[] + cid?: string } /** @@ -48,7 +31,7 @@ export interface UploadProgressInfo { * @example * ```tsx * function UploadCard({ progresses, cid }) { - * const { isCompleted, badgeStatus, hasIpniFailure } = useUploadProgress(progresses, cid) + * const { isCompleted, badgeStatus, hasUploadIpniFailure } = useUploadProgress(progresses, cid) * * return ( * @@ -58,78 +41,24 @@ export interface UploadProgressInfo { * } * ``` */ -export function useUploadProgress(progresses: Progress[], cid?: string): UploadProgressInfo { +export function useUploadProgress({ stepStates, cid }: useUploadProgressProps): UploadProgressInfo { return useMemo(() => { - // Calculate combined progress for the first stage (creating CAR + checking readiness + uploading) - const getCombinedFirstStageProgress = () => { - const { creatingCar, checkingReadiness, uploadingCar } = createStepGroup(progresses) - const total = creatingCar.progress + checkingReadiness.progress + uploadingCar.progress - return Math.round(total / 3) - } - - // Get the status for the combined first stage - const getCombinedFirstStageStatus = (): Progress['status'] => { - const { creatingCar, checkingReadiness, uploadingCar } = createStepGroup(progresses) - - // If any stage has error, show error - if ( - uploadingCar?.status === 'error' || - checkingReadiness?.status === 'error' || - creatingCar?.status === 'error' - ) { - return 'error' - } - - // If uploading-car is completed, the whole stage is completed - if (uploadingCar?.status === 'completed') { - return 'completed' - } - - // If any stage is in progress OR completed (but not all completed), show in-progress - const startedStages: Progress['status'][] = ['in-progress', 'completed'] - const hasStarted = - (uploadingCar?.status && startedStages.includes(uploadingCar.status)) || - (checkingReadiness?.status && startedStages.includes(checkingReadiness.status)) || - (creatingCar?.status && startedStages.includes(creatingCar.status)) - - if (hasStarted) { - return 'in-progress' - } - - // Otherwise pending - return 'pending' - } - - const hasIpniFailure = progresses.find((p) => p.step === 'announcing-cids')?.status === 'error' - - // Check if all steps are completed AND we have a CID (upload actually finished) - // BUT treat IPNI failures as still "completed" since file is stored on Filecoin - const isCompleted = - Boolean(cid) && - progresses.every((p) => { - return p.status === 'completed' || (p.step === 'announcing-cids' && p.status === 'error') - }) - - // Check if any step is in error (excluding IPNI failures) - const hasError = progresses.some((p) => p.status === 'error' && p.step !== 'announcing-cids') - - // Determine the badge status for the file card header - const getBadgeStatus = (): UploadProgressInfo['badgeStatus'] => { - if (isCompleted) return 'pinned' - if (hasError) return 'error' - // Check if any step is in progress - if (progresses.some((p) => p.status === 'in-progress')) return 'in-progress' - // If no steps are in progress but not all completed, must be pending - return 'pending' - } + const firstStageProgress = getFirstStageProgress(stepStates) + const firstStageStatus = getFirstStageStatus(stepStates) + const { hasUploadIpniFailure, isUploadSuccessful, isUploadFailure } = getUploadOutcome({ stepStates, cid }) + const uploadBadgeStatus = getUploadBadgeStatus({ + isUploadSuccessful, + isUploadFailure, + stepStates, + }) return { - getCombinedFirstStageProgress, - getCombinedFirstStageStatus, - hasIpniFailure, - isCompleted, - hasError, - badgeStatus: getBadgeStatus(), + firstStageProgress, + firstStageStatus, + hasUploadIpniFailure, + isUploadSuccessful, + isUploadFailure, + uploadBadgeStatus, } - }, [progresses, cid]) + }, [stepStates, cid]) } diff --git a/src/types/upload-progress.ts b/src/types/upload-progress.ts deleted file mode 100644 index e1cac31..0000000 --- a/src/types/upload-progress.ts +++ /dev/null @@ -1,8 +0,0 @@ -export interface Progress { - step: 'creating-car' | 'uploading-car' | 'checking-readiness' | 'announcing-cids' | 'finalizing-transaction' - progress: number // 0-100 - status: 'pending' | 'in-progress' | 'completed' | 'error' - error?: string -} - -export type StepType = Progress['step'] diff --git a/src/types/upload/stage.ts b/src/types/upload/stage.ts new file mode 100644 index 0000000..4fe8464 --- /dev/null +++ b/src/types/upload/stage.ts @@ -0,0 +1,14 @@ +import type { StepState } from '../upload/step.ts' + +export type StageId = 'first-stage' | 'second-stage' | 'third-stage' + +export const STAGE_STEPS: Record = { + 'first-stage': ['creating-car', 'checking-readiness', 'uploading-car'], + 'second-stage': ['announcing-cids'], + 'third-stage': ['finalizing-transaction'], +} as const + +export type FirstStageGroup = Record< + 'creatingCar' | 'checkingReadiness' | 'uploadingCar', + { progress: number; status: StepState['status'] } +> diff --git a/src/types/upload/step.ts b/src/types/upload/step.ts new file mode 100644 index 0000000..4684cfd --- /dev/null +++ b/src/types/upload/step.ts @@ -0,0 +1,17 @@ +export type StepName = + | 'creating-car' + | 'checking-readiness' + | 'uploading-car' + | 'announcing-cids' + | 'finalizing-transaction' + +export type StepStatus = 'pending' | 'in-progress' | 'completed' | 'error' + +export interface StepState { + step: StepName + progress: number // 0–100 + status: StepStatus + error?: string +} + +export type StepType = StepState['step'] diff --git a/src/utils/upload-status.ts b/src/utils/upload-status.ts deleted file mode 100644 index 8a8a7e8..0000000 --- a/src/utils/upload-status.ts +++ /dev/null @@ -1,63 +0,0 @@ -import type { Progress } from '@/types/upload-progress.ts' - -type FirstSteoGroupRecord = Record< - 'creatingCar' | 'checkingReadiness' | 'uploadingCar', - { progress: number; status: Progress['status'] } -> - -// simple type to help with searching for UploadProgress['step'] in the first step group -export const firstStepGroup: FirstSteoGroupRecord = { - creatingCar: { progress: 0, status: 'pending' }, - checkingReadiness: { progress: 0, status: 'pending' }, - uploadingCar: { progress: 0, status: 'pending' }, -} - -export function createStepGroup(progress: Progress[]) { - // Map kebab-case step names to camelCase keys - const stepMap: Record = { - 'creating-car': 'creatingCar', - 'checking-readiness': 'checkingReadiness', - 'uploading-car': 'uploadingCar', - } - - return progress.reduce( - (acc, p) => { - const key = stepMap[p.step] - if (key) { - acc[key] = { progress: p.progress, status: p.status } - } - return acc - }, - { - creatingCar: { progress: 0, status: 'pending' }, - checkingReadiness: { progress: 0, status: 'pending' }, - uploadingCar: { progress: 0, status: 'pending' }, - } - ) -} - -export function getStepLabel(step: Progress['step']) { - switch (step) { - case 'creating-car': - case 'checking-readiness': - case 'uploading-car': - return 'Preparing service, creating CAR file, and uploading to the Filecoin SP' - case 'announcing-cids': - return 'Announcing IPFS CIDs to IPNI' - case 'finalizing-transaction': - return 'Finalizing storage transaction on Calibration testnet' - } -} - -export function getEstimatedTime(step: Progress['step']) { - switch (step) { - case 'creating-car': - case 'checking-readiness': - case 'uploading-car': - return 'Estimated time: ~30 seconds' - case 'announcing-cids': - return 'Estimated time: ~30 seconds' - case 'finalizing-transaction': - return 'Estimated time: ~30-60 seconds' - } -} diff --git a/src/utils/upload/stage.ts b/src/utils/upload/stage.ts new file mode 100644 index 0000000..0b42379 --- /dev/null +++ b/src/utils/upload/stage.ts @@ -0,0 +1,55 @@ +import type { FirstStageGroup } from '../../types/upload/stage.ts' +import type { StepState } from '../../types/upload/step.ts' +import { stepHasActiveStatus } from './step.ts' + +export const firstStageGroup: FirstStageGroup = { + creatingCar: { progress: 0, status: 'pending' }, + checkingReadiness: { progress: 0, status: 'pending' }, + uploadingCar: { progress: 0, status: 'pending' }, +} + +function getFirstStageState(stepStates: StepState[]) { + const stepMap: Record = { + 'creating-car': 'creatingCar', + 'checking-readiness': 'checkingReadiness', + 'uploading-car': 'uploadingCar', + } + + return stepStates.reduce( + (acc, stepState) => { + const key = stepMap[stepState.step] + if (key) { + acc[key] = { progress: stepState.progress, status: stepState.status } + } + return acc + }, + { + creatingCar: { progress: 0, status: 'pending' }, + checkingReadiness: { progress: 0, status: 'pending' }, + uploadingCar: { progress: 0, status: 'pending' }, + } + ) +} + +// Calculate combined progress for the first stage (creating CAR + checking readiness + uploading) +export function getFirstStageProgress(stepStates: StepState[]) { + const { creatingCar, checkingReadiness, uploadingCar } = getFirstStageState(stepStates) + const total = creatingCar.progress + checkingReadiness.progress + uploadingCar.progress + return Math.round(total / 3) +} + +export function getFirstStageStatus(stepStates: StepState[]) { + const firstStageState = getFirstStageState(stepStates) + + const { creatingCar, checkingReadiness, uploadingCar } = firstStageState + + if (uploadingCar?.status === 'error' || checkingReadiness?.status === 'error' || creatingCar?.status === 'error') { + return 'error' + } + + if (uploadingCar?.status === 'completed') return 'completed' + + if (Object.values(firstStageState).some((s) => stepHasActiveStatus(s.status))) return 'in-progress' + + return 'pending' +} diff --git a/src/utils/upload/step.ts b/src/utils/upload/step.ts new file mode 100644 index 0000000..6efe87a --- /dev/null +++ b/src/utils/upload/step.ts @@ -0,0 +1,31 @@ +import type { StepState } from '../../types/upload/step.ts' + +export function getStepLabel(step: StepState['step']) { + switch (step) { + case 'creating-car': + case 'checking-readiness': + case 'uploading-car': + return 'Preparing service, creating CAR file, and uploading to the Filecoin SP' + case 'announcing-cids': + return 'Announcing IPFS CIDs to IPNI' + case 'finalizing-transaction': + return 'Finalizing storage transaction on Calibration testnet' + } +} + +export function getStepEstimatedTime(step: StepState['step']) { + switch (step) { + case 'creating-car': + case 'checking-readiness': + case 'uploading-car': + return 'Estimated time: ~30 seconds' + case 'announcing-cids': + return 'Estimated time: ~30 seconds' + case 'finalizing-transaction': + return 'Estimated time: ~30-60 seconds' + } +} + +export function stepHasActiveStatus(status?: StepState['status']) { + return status === 'completed' || status === 'in-progress' +} diff --git a/src/utils/upload/upload.ts b/src/utils/upload/upload.ts new file mode 100644 index 0000000..3de6835 --- /dev/null +++ b/src/utils/upload/upload.ts @@ -0,0 +1,41 @@ +import type { StepState } from '../../types/upload/step.ts' + +type getUploadOutcomeProps = { + stepStates: StepState[] + cid?: string +} + +export function getUploadOutcome({ stepStates, cid }: getUploadOutcomeProps) { + const hasUploadIpniFailure = stepStates.find((stepState) => stepState.step === 'announcing-cids')?.status === 'error' + + const isUploadFailure = stepStates.some( + (stepState) => stepState.status === 'error' && stepState.step !== 'announcing-cids' + ) + + const isUploadSuccessful = + Boolean(cid) && + stepStates.every((stepState) => { + return stepState.status === 'completed' || (stepState.step === 'announcing-cids' && stepState.status === 'error') + }) + + return { hasUploadIpniFailure, isUploadSuccessful, isUploadFailure } +} + +type getUploadBadgeStatusProps = { + isUploadSuccessful: boolean + isUploadFailure: boolean + stepStates: StepState[] +} + +export function getUploadBadgeStatus({ isUploadSuccessful, isUploadFailure, stepStates }: getUploadBadgeStatusProps) { + const finalizingTransactionStep = stepStates.find((stepState) => stepState.step === 'finalizing-transaction') + const announcingCidsStep = stepStates.find((stepState) => stepState.step === 'announcing-cids') + + if (isUploadSuccessful) return 'pinned' + if (isUploadFailure) return 'error' + if (finalizingTransactionStep?.status === 'completed' && announcingCidsStep?.status !== 'completed') { + return 'published' + } + if (stepStates.some((stepState) => stepState.status === 'in-progress')) return 'in-progress' + return 'pending' +}