124_webcalibre2/src/app/upload/upload-view.tsx

198 lines
6.7 KiB
TypeScript
Raw Normal View History

'use client';
import { useState } from "react";
import {
Box, Button, Typography, Paper, LinearProgress, Stack,
TextField, MenuItem, IconButton, Tooltip, Divider
} from "@mui/material";
import CloudUploadIcon from "@mui/icons-material/CloudUpload";
import CreateNewFolderIcon from "@mui/icons-material/CreateNewFolder";
import { uploadFileAction, createFolderAction } from "./_actions";
/**
* Format bytes to human readable string (MiB/KiB)
*/
const formatFileSize = (bytes: number) => {
if (bytes === 0) return '0 Bytes';
const k = 1024;
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
const i = Math.floor(Math.log(bytes) / Math.log(k));
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
};
export default function UploadView({ user, folders = [] }: { user: any, folders?: any[] }) {
const [file, setFile] = useState<File | null>(null);
const [description, setDescription] = useState("");
const [parentId, setParentId] = useState("");
const [status, setStatus] = useState<'idle' | 'uploading' | 'success'>('idle');
// Folder Creation State
const [showFolderInput, setShowFolderInput] = useState(false);
const [newFolderName, setNewFolderName] = useState("");
// --- RESTORED FILE SIZE LIMITS ---
const MAX_FILE_SIZE = 150 * 1024 * 1024; // 150MiB Limit
const handleCreateFolder = async () => {
if (!newFolderName.trim()) return;
try {
await createFolderAction(newFolderName);
setNewFolderName("");
setShowFolderInput(false);
} catch (err) {
alert("Error creating folder");
}
};
const handleUpload = async () => {
if (!file) return;
// RESTORED: Client-side size validation
if (file.size > MAX_FILE_SIZE) {
alert(`File is too large! Maximum size allowed is ${formatFileSize(MAX_FILE_SIZE)}.`);
return;
}
setStatus('uploading');
const formData = new FormData();
formData.append("file", file);
formData.append("description", description);
formData.append("parentId", parentId);
try {
await uploadFileAction(formData);
setStatus('success');
setFile(null);
setDescription("");
} catch (err) {
console.error(err);
alert("Upload failed. Ensure file size is within limits and check server logs.");
setStatus('idle');
}
};
return (
<Paper sx={{ p: { xs: 3, md: 6 }, borderRadius: 4 }} elevation={3}>
<Typography variant="h4" fontWeight={900} color="primary" gutterBottom align="center">
Add to Library
</Typography>
<Stack spacing={4} sx={{ mt: 4 }}>
{/* Section 1: Folder Selection */}
<Box>
<Typography variant="subtitle2" gutterBottom fontWeight="bold">
1. Select Target Project / Folder
</Typography>
<Stack direction="row" spacing={1}>
<TextField
select
fullWidth
label="Choose Folder"
value={parentId}
onChange={(e) => setParentId(e.target.value)}
disabled={status === 'uploading'}
>
<MenuItem value=""><em>None (Root)</em></MenuItem>
{folders.map((f) => (
<MenuItem key={f.id} value={f.id}>{f.name}</MenuItem>
))}
</TextField>
<Tooltip title="Create New Folder">
<IconButton
color="primary"
onClick={() => setShowFolderInput(!showFolderInput)}
sx={{ border: '1px solid', borderColor: 'divider', borderRadius: 1 }}
>
<CreateNewFolderIcon />
</IconButton>
</Tooltip>
</Stack>
{showFolderInput && (
<Stack direction="row" spacing={1} sx={{ mt: 2, p: 2, bgcolor: 'grey.50', borderRadius: 2 }}>
<TextField
size="small"
fullWidth
placeholder="New folder name..."
value={newFolderName}
onChange={(e) => setNewFolderName(e.target.value)}
autoFocus
/>
<Button variant="contained" onClick={handleCreateFolder}>Create</Button>
</Stack>
)}
</Box>
<Divider />
{/* Section 2: File Selection */}
<Box>
<Typography variant="subtitle2" gutterBottom fontWeight="bold">
2. Upload File
</Typography>
<Box sx={{ p: 4, border: '2px dashed #ccc', borderRadius: 2, textAlign: 'center', bgcolor: '#fcfcfc' }}>
<input
type="file" id="file-input" hidden
accept=".pdf,.epub,.mobi,.azw3,.txt,.png,.jpeg"
onChange={(e) => {
setFile(e.target.files?.[0] || null);
setStatus('idle');
}}
/>
<label htmlFor="file-input">
<Button variant="outlined" component="span" startIcon={<CloudUploadIcon />}>
{file ? "Change File" : "Choose Book/Image"}
</Button>
</label>
{file && (
<Box mt={2}>
<Typography variant="body2" sx={{ color: 'text.secondary' }}>
Selected: <strong>{file.name}</strong>
</Typography>
<Typography variant="caption" sx={{ color: file.size > MAX_FILE_SIZE ? 'error.main' : 'text.disabled' }}>
Size: {formatFileSize(file.size)} {file.size > MAX_FILE_SIZE && "(Too Large)"}
</Typography>
</Box>
)}
</Box>
</Box>
{/* Section 3: Description */}
<TextField
label="Notes / Description"
multiline rows={2} fullWidth
value={description}
onChange={(e) => setDescription(e.target.value)}
disabled={status === 'uploading'}
/>
{/* Status & Action */}
{status === 'uploading' && (
<Box>
<LinearProgress sx={{ borderRadius: 5, height: 10 }} />
<Typography variant="caption" color="primary" sx={{ mt: 1, display: 'block', textAlign: 'center' }}>
Streaming to OneDrive storage...
</Typography>
</Box>
)}
<Button
variant="contained" size="large" fullWidth
disabled={!file || status === 'uploading' || file.size > MAX_FILE_SIZE}
onClick={handleUpload}
sx={{ py: 2, fontWeight: 'bold' }}
>
{status === 'uploading' ? 'Uploading...' : 'Confirm Upload'}
</Button>
{status === 'success' && (
<Typography align="center" color="success.main" fontWeight="bold">
Successfully Added!
</Typography>
)}
</Stack>
</Paper>
);
}