iter 2
This commit is contained in:
@@ -0,0 +1,141 @@
|
||||
"use client"
|
||||
|
||||
import type { IUploadStepProps } from "./UploadStep.d"
|
||||
import type { JSX } from "react"
|
||||
|
||||
import { Upload } from "lucide-react"
|
||||
import { FunctionComponent, useCallback, useRef, useState } from "react"
|
||||
|
||||
import cs from "classnames"
|
||||
|
||||
import { uploadFileWithProgress } from "@shared/api/uploadFile"
|
||||
import { useWizard } from "@shared/context/WizardContext"
|
||||
import { Button } from "@shared/ui"
|
||||
|
||||
import styles from "./UploadStep.module.scss"
|
||||
|
||||
const ACCEPTED_VIDEO_TYPES = "video/*"
|
||||
const ERROR_UPLOAD_FAILED = "Не удалось загрузить файл"
|
||||
|
||||
export const UploadStep: FunctionComponent<IUploadStepProps> = ({
|
||||
className,
|
||||
}): JSX.Element => {
|
||||
const { projectId, setFileKey, markStepCompleted, goNext } = useWizard()
|
||||
const [isDragging, setIsDragging] = useState(false)
|
||||
const [isUploading, setIsUploading] = useState(false)
|
||||
const [progress, setProgress] = useState(0)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const inputRef = useRef<HTMLInputElement>(null)
|
||||
|
||||
const handleUpload = useCallback(
|
||||
async (file: File) => {
|
||||
setIsUploading(true)
|
||||
setProgress(0)
|
||||
setError(null)
|
||||
|
||||
try {
|
||||
const result = await uploadFileWithProgress(
|
||||
file,
|
||||
`projects/${projectId}`,
|
||||
setProgress,
|
||||
)
|
||||
setFileKey(result.file_path, result.file_url, result.filename ?? null)
|
||||
markStepCompleted("upload")
|
||||
goNext()
|
||||
} catch {
|
||||
setError(ERROR_UPLOAD_FAILED)
|
||||
} finally {
|
||||
setIsUploading(false)
|
||||
}
|
||||
},
|
||||
[projectId, setFileKey, markStepCompleted, goNext],
|
||||
)
|
||||
|
||||
const handleFileChange = useCallback(
|
||||
(e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const file = e.target.files?.[0]
|
||||
if (file) handleUpload(file)
|
||||
/* Reset input so re-selecting the same file triggers change */
|
||||
e.target.value = ""
|
||||
},
|
||||
[handleUpload],
|
||||
)
|
||||
|
||||
const handleDrop = useCallback(
|
||||
(e: React.DragEvent) => {
|
||||
e.preventDefault()
|
||||
setIsDragging(false)
|
||||
const file = e.dataTransfer.files[0]
|
||||
if (file) handleUpload(file)
|
||||
},
|
||||
[handleUpload],
|
||||
)
|
||||
|
||||
const handleDragOver = useCallback((e: React.DragEvent) => {
|
||||
e.preventDefault()
|
||||
setIsDragging(true)
|
||||
}, [])
|
||||
|
||||
const handleDragLeave = useCallback((e: React.DragEvent) => {
|
||||
e.preventDefault()
|
||||
setIsDragging(false)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className={cs(styles.root, className)} data-testid="UploadStep">
|
||||
<div className={styles.content}>
|
||||
<div
|
||||
className={cs(styles.dropZone, {
|
||||
[styles.dropZoneActive]: isDragging,
|
||||
[styles.dropZoneUploading]: isUploading,
|
||||
})}
|
||||
onDrop={handleDrop}
|
||||
onDragOver={handleDragOver}
|
||||
onDragLeave={handleDragLeave}
|
||||
onClick={() => inputRef.current?.click()}
|
||||
>
|
||||
<input
|
||||
ref={inputRef}
|
||||
type="file"
|
||||
accept={ACCEPTED_VIDEO_TYPES}
|
||||
className={styles.fileInput}
|
||||
onChange={handleFileChange}
|
||||
disabled={isUploading}
|
||||
/>
|
||||
<Upload size={48} className={styles.icon} />
|
||||
|
||||
{isUploading ? (
|
||||
<>
|
||||
<p className={styles.title}>Загрузка файла...</p>
|
||||
<div className={styles.progressTrack}>
|
||||
<div
|
||||
className={styles.progressBar}
|
||||
style={{ width: `${progress}%` }}
|
||||
/>
|
||||
</div>
|
||||
<p className={styles.progressLabel}>{Math.round(progress)}%</p>
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<p className={styles.title}>Перетащите видеофайл сюда</p>
|
||||
<p className={styles.subtitle}>или нажмите для выбора файла</p>
|
||||
<Button
|
||||
variant="outline"
|
||||
size="sm"
|
||||
type="button"
|
||||
onClick={(e) => {
|
||||
e.stopPropagation()
|
||||
inputRef.current?.click()
|
||||
}}
|
||||
>
|
||||
Выбрать файл
|
||||
</Button>
|
||||
</>
|
||||
)}
|
||||
|
||||
{error && <p className={styles.error}>{error}</p>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user