changed upload limit
This commit is contained in:
parent
b59f0e7816
commit
6e8bf31900
4 changed files with 74 additions and 76 deletions
|
@ -1,4 +1,6 @@
|
||||||
|
|
||||||
import type {NextConfig} from 'next';
|
import type {NextConfig} from 'next';
|
||||||
|
import { UPLOAD_LIMIT_MB } from '@/lib/config';
|
||||||
|
|
||||||
const nextConfig: NextConfig = {
|
const nextConfig: NextConfig = {
|
||||||
/* config options here */
|
/* config options here */
|
||||||
|
@ -20,7 +22,7 @@ const nextConfig: NextConfig = {
|
||||||
},
|
},
|
||||||
experimental: {
|
experimental: {
|
||||||
serverActions: {
|
serverActions: {
|
||||||
bodySizeLimit: '50mb',
|
bodySizeLimit: `${UPLOAD_LIMIT_MB}mb`,
|
||||||
allowedOrigins: [
|
allowedOrigins: [
|
||||||
"https://weexnes.dev",
|
"https://weexnes.dev",
|
||||||
"https://weexnes.dev:6837",
|
"https://weexnes.dev:6837",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
|
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useRef, useState, useEffect, useActionState } from 'react'; // Updated import
|
import { useRef, useState, useEffect, useActionState } from 'react';
|
||||||
import { useFormStatus } from 'react-dom';
|
import { useFormStatus } from 'react-dom';
|
||||||
import { uploadFileAction } from '@/lib/actions/file.actions';
|
import { uploadFileAction } from '@/lib/actions/file.actions';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
|
@ -11,59 +11,54 @@ import { Progress } from '@/components/ui/progress';
|
||||||
import { useToast } from '@/hooks/use-toast';
|
import { useToast } from '@/hooks/use-toast';
|
||||||
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||||
import { UploadCloud, CheckCircle, XCircle, Loader2, File as FileIconLucide } from "lucide-react";
|
import { UploadCloud, CheckCircle, XCircle, Loader2, File as FileIconLucide } from "lucide-react";
|
||||||
|
import { UPLOAD_LIMIT_MB } from '@/lib/config';
|
||||||
|
|
||||||
function SubmitButton() {
|
function SubmitButton() {
|
||||||
const { pending } = useFormStatus();
|
const { pending } = useFormStatus();
|
||||||
return (
|
return (
|
||||||
<Button type="submit" className="w-full mt-4" disabled={pending}>
|
<Button type="submit" className="w-full mt-4" disabled={pending}>
|
||||||
{pending ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : <UploadCloud className="mr-2 h-4 w-4" />}
|
{pending ? <Loader2 className="mr-2 h-4 w-4 animate-spin" /> : <UploadCloud className="mr-2 h-4 w-4" />}
|
||||||
Upload File
|
Upload File
|
||||||
</Button>
|
</Button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function FileUploadForm() {
|
export default function FileUploadForm() {
|
||||||
const initialState = { message: null, error: false, file: null };
|
const initialState = { message: null, error: false, file: null };
|
||||||
// Updated to useActionState
|
const [state, dispatch, isPending] = useActionState(uploadFileAction, initialState);
|
||||||
const [state, dispatch, isPending] = useActionState(uploadFileAction, initialState);
|
|
||||||
const { toast } = useToast();
|
const { toast } = useToast();
|
||||||
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
||||||
const [uploadProgress, setUploadProgress] = useState(0); // Mock progress
|
const [uploadProgress, setUploadProgress] = useState(0);
|
||||||
const formRef = useRef<HTMLFormElement>(null);
|
const formRef = useRef<HTMLFormElement>(null);
|
||||||
|
|
||||||
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
const file = event.target.files?.[0];
|
const file = event.target.files?.[0];
|
||||||
if (file) {
|
if (file) {
|
||||||
setSelectedFile(file);
|
setSelectedFile(file);
|
||||||
setUploadProgress(0); // Reset progress
|
setUploadProgress(0);
|
||||||
} else {
|
} else {
|
||||||
setSelectedFile(null);
|
setSelectedFile(null);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Simulate upload progress for UX
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let interval: NodeJS.Timeout;
|
let interval: NodeJS.Timeout;
|
||||||
// Check if isPending (from useActionState) is true and selectedFile exists
|
if (isPending && selectedFile && !state?.message) {
|
||||||
if (isPending && selectedFile && !state?.message) {
|
|
||||||
let progress = 0;
|
let progress = 0;
|
||||||
// Simulate progress quickly at first, then slower
|
|
||||||
interval = setInterval(() => {
|
interval = setInterval(() => {
|
||||||
progress += Math.random() * 15 + 5; // Random progress increment
|
progress += Math.random() * 15 + 5;
|
||||||
if (progress >= 95 && !state?.message) { // Stall at 95% if no response yet
|
if (progress >= 95 && !state?.message) {
|
||||||
setUploadProgress(95);
|
setUploadProgress(95);
|
||||||
} else if (progress <= 100) {
|
} else if (progress <= 100) {
|
||||||
setUploadProgress(Math.min(progress, 100));
|
setUploadProgress(Math.min(progress, 100));
|
||||||
}
|
}
|
||||||
if (progress >= 100 || state?.message) {
|
if (progress >= 100 || state?.message) {
|
||||||
clearInterval(interval);
|
clearInterval(interval);
|
||||||
}
|
}
|
||||||
}, 150);
|
}, 150);
|
||||||
} else if (state?.message && !state.error) {
|
} else if (state?.message && !state.error) {
|
||||||
// If successful, jump to 100%
|
|
||||||
setUploadProgress(100);
|
setUploadProgress(100);
|
||||||
} else if (state?.message && state.error) {
|
} else if (state?.message && state.error) {
|
||||||
// If error, reset progress or show error state (e.g., 0 or specific error indicator)
|
|
||||||
setUploadProgress(0);
|
setUploadProgress(0);
|
||||||
}
|
}
|
||||||
return () => clearInterval(interval);
|
return () => clearInterval(interval);
|
||||||
|
@ -85,63 +80,62 @@ export default function FileUploadForm() {
|
||||||
description: state.message,
|
description: state.message,
|
||||||
icon: <CheckCircle className="h-5 w-5 text-green-500" />,
|
icon: <CheckCircle className="h-5 w-5 text-green-500" />,
|
||||||
});
|
});
|
||||||
formRef.current?.reset();
|
formRef.current?.reset();
|
||||||
setSelectedFile(null);
|
setSelectedFile(null);
|
||||||
setUploadProgress(100);
|
setUploadProgress(100);
|
||||||
// Optionally reset progress to 0 after a short delay if user stays on page
|
|
||||||
setTimeout(() => setUploadProgress(0), 2000);
|
setTimeout(() => setUploadProgress(0), 2000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [state, toast]);
|
}, [state, toast]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form action={dispatch} ref={formRef} className="space-y-6">
|
<form action={dispatch} ref={formRef} className="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<Label htmlFor="file" className="sr-only">Choose file</Label>
|
<Label htmlFor="file" className="sr-only">Choose file</Label>
|
||||||
<div className="mt-2 flex justify-center rounded-lg border border-dashed border-border px-6 py-10 hover:border-primary transition-colors duration-200">
|
<div className="mt-2 flex justify-center rounded-lg border border-dashed border-border px-6 py-10 hover:border-primary transition-colors duration-200">
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
{selectedFile ? (
|
{selectedFile ? (
|
||||||
<FileIconLucide className="mx-auto h-12 w-12 text-muted-foreground" aria-hidden="true" />
|
<FileIconLucide className="mx-auto h-12 w-12 text-muted-foreground" aria-hidden="true" />
|
||||||
) : (
|
) : (
|
||||||
<UploadCloud className="mx-auto h-12 w-12 text-muted-foreground" aria-hidden="true" />
|
<UploadCloud className="mx-auto h-12 w-12 text-muted-foreground" aria-hidden="true" />
|
||||||
)}
|
)}
|
||||||
<div className="mt-4 flex text-sm leading-6 text-muted-foreground">
|
<div className="mt-4 flex text-sm leading-6 text-muted-foreground">
|
||||||
<label
|
<label
|
||||||
htmlFor="file-upload"
|
htmlFor="file-upload"
|
||||||
className="relative cursor-pointer rounded-md font-semibold text-primary focus-within:outline-none focus-within:ring-2 focus-within:ring-primary focus-within:ring-offset-2 hover:text-accent"
|
className="relative cursor-pointer rounded-md font-semibold text-primary focus-within:outline-none focus-within:ring-2 focus-within:ring-primary focus-within:ring-offset-2 hover:text-accent"
|
||||||
>
|
>
|
||||||
<span>{selectedFile ? "Change file" : "Upload a file"}</span>
|
<span>{selectedFile ? "Change file" : "Upload a file"}</span>
|
||||||
<Input id="file-upload" name="file" type="file" className="sr-only" onChange={handleFileChange} required />
|
<Input id="file-upload" name="file" type="file" className="sr-only" onChange={handleFileChange} required />
|
||||||
</label>
|
</label>
|
||||||
<p className="pl-1">{selectedFile ? "" : "or drag and drop"}</p>
|
<p className="pl-1">{selectedFile ? "" : "or drag and drop"}</p>
|
||||||
|
</div>
|
||||||
|
{selectedFile ? (
|
||||||
|
<p className="text-xs leading-5 text-muted-foreground mt-1">{selectedFile.name} ({(selectedFile.size / 1024 / 1024).toFixed(2)} MB)</p>
|
||||||
|
) : (
|
||||||
|
<p className="text-xs leading-5 text-muted-foreground">PNG, JPG, PDF, MP4 up to {UPLOAD_LIMIT_MB}MB</p>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
{selectedFile ? (
|
|
||||||
<p className="text-xs leading-5 text-muted-foreground mt-1">{selectedFile.name} ({(selectedFile.size / 1024 / 1024).toFixed(2)} MB)</p>
|
|
||||||
) : (
|
|
||||||
<p className="text-xs leading-5 text-muted-foreground">PNG, JPG, PDF, MP4 up to 50MB</p>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
{selectedFile && (isPending || uploadProgress > 0) && (
|
{selectedFile && (isPending || uploadProgress > 0) && (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Progress value={uploadProgress} className="w-full h-3" />
|
<Progress value={uploadProgress} className="w-full h-3" />
|
||||||
<p className="text-sm text-muted-foreground text-center">
|
<p className="text-sm text-muted-foreground text-center">
|
||||||
{isPending && uploadProgress < 100 ? `${uploadProgress}% uploading...` : state?.error ? 'Error' : uploadProgress === 100 && !isPending ? 'Complete' : `${uploadProgress}%`}
|
{isPending && uploadProgress < 100 ? `${uploadProgress}% uploading...` : state?.error ? 'Error' : uploadProgress === 100 && !isPending ? 'Complete' : `${uploadProgress}%`}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
|
||||||
|
|
||||||
{state?.error && state.message && !isPending && (
|
|
||||||
<Alert variant="destructive">
|
|
||||||
<XCircle className="h-4 w-4" />
|
|
||||||
<AlertTitle>Error</AlertTitle>
|
|
||||||
<AlertDescription>{state.message}</AlertDescription>
|
|
||||||
</Alert>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<SubmitButton />
|
{state?.error && state.message && !isPending && (
|
||||||
</form>
|
<Alert variant="destructive">
|
||||||
|
<XCircle className="h-4 w-4" />
|
||||||
|
<AlertTitle>Error</AlertTitle>
|
||||||
|
<AlertDescription>{state.message}</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<SubmitButton />
|
||||||
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,8 +6,9 @@ import { revalidatePath } from 'next/cache';
|
||||||
import { z } from 'zod';
|
import { z } from 'zod';
|
||||||
import fs from 'node:fs';
|
import fs from 'node:fs';
|
||||||
import path from 'node:path';
|
import path from 'node:path';
|
||||||
|
import { UPLOAD_LIMIT_MB } from '@/lib/config';
|
||||||
|
|
||||||
const MAX_FILE_SIZE = 50 * 1024 * 1024; // 50MB
|
const MAX_FILE_SIZE = UPLOAD_LIMIT_MB * 1024 * 1024; // 500MB
|
||||||
// It's generally better to allow a wider range of types and let the application handle them,
|
// It's generally better to allow a wider range of types and let the application handle them,
|
||||||
// or use more robust server-side type checking if strict limitations are needed.
|
// or use more robust server-side type checking if strict limitations are needed.
|
||||||
// For this prototype, we'll be more permissive on the client-side Zod schema for MIME types.
|
// For this prototype, we'll be more permissive on the client-side Zod schema for MIME types.
|
||||||
|
@ -16,10 +17,10 @@ const MAX_FILE_SIZE = 50 * 1024 * 1024; // 50MB
|
||||||
|
|
||||||
const fileSchema = z.object({
|
const fileSchema = z.object({
|
||||||
file: z
|
file: z
|
||||||
.custom<File>((val) => val instanceof File, "Input is not a file")
|
.custom<File>((val) => val instanceof File, "Input is not a file")
|
||||||
.refine((file) => file.size > 0, "File cannot be empty.")
|
.refine((file) => file.size > 0, "File cannot be empty.")
|
||||||
.refine((file) => file.size <= MAX_FILE_SIZE, `File size should be less than 50MB.`)
|
.refine((file) => file.size <= MAX_FILE_SIZE, `File size should be less than ${UPLOAD_LIMIT_MB}MB.`)
|
||||||
// .refine((file) => ACCEPTED_FILE_TYPES_REGEX.test(file.type), "Unsupported file type.") // Relaxing this for broader prototype compatibility
|
// .refine((file) => ACCEPTED_FILE_TYPES_REGEX.test(file.type), "Unsupported file type.") // Relaxing this for broader prototype compatibility
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
@ -29,7 +30,7 @@ export async function uploadFileAction(prevState: any, formData: FormData) {
|
||||||
if (!uploadedFile || uploadedFile.name === 'undefined' || uploadedFile.size === 0) { // Handle edge case where file might be 'undefined' string or empty
|
if (!uploadedFile || uploadedFile.name === 'undefined' || uploadedFile.size === 0) { // Handle edge case where file might be 'undefined' string or empty
|
||||||
return { message: 'No file selected or file is empty.', error: true, file: null };
|
return { message: 'No file selected or file is empty.', error: true, file: null };
|
||||||
}
|
}
|
||||||
|
|
||||||
const validatedFields = fileSchema.safeParse({ file: uploadedFile });
|
const validatedFields = fileSchema.safeParse({ file: uploadedFile });
|
||||||
|
|
||||||
if (!validatedFields.success) {
|
if (!validatedFields.success) {
|
||||||
|
@ -40,7 +41,7 @@ export async function uploadFileAction(prevState: any, formData: FormData) {
|
||||||
file: null,
|
file: null,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const newFileEntry = await addFile({
|
const newFileEntry = await addFile({
|
||||||
name: uploadedFile.name,
|
name: uploadedFile.name,
|
||||||
|
@ -58,7 +59,7 @@ export async function uploadFileAction(prevState: any, formData: FormData) {
|
||||||
const fileBuffer = Buffer.from(await uploadedFile.arrayBuffer());
|
const fileBuffer = Buffer.from(await uploadedFile.arrayBuffer());
|
||||||
fs.writeFileSync(filePathInCdn, fileBuffer);
|
fs.writeFileSync(filePathInCdn, fileBuffer);
|
||||||
|
|
||||||
revalidatePath('/browse');
|
revalidatePath('/browse');
|
||||||
revalidatePath(`/files/${newFileEntry.id}`);
|
revalidatePath(`/files/${newFileEntry.id}`);
|
||||||
revalidatePath(`/cdn/${newFileEntry.id}`);
|
revalidatePath(`/cdn/${newFileEntry.id}`);
|
||||||
|
|
||||||
|
@ -67,7 +68,7 @@ export async function uploadFileAction(prevState: any, formData: FormData) {
|
||||||
console.error("Upload error:", e);
|
console.error("Upload error:", e);
|
||||||
let errorMessage = 'Failed to upload file. Please try again.';
|
let errorMessage = 'Failed to upload file. Please try again.';
|
||||||
if (e instanceof Error) {
|
if (e instanceof Error) {
|
||||||
errorMessage = `Failed to upload file: ${e.message}`;
|
errorMessage = `Failed to upload file: ${e.message}`;
|
||||||
}
|
}
|
||||||
return { message: errorMessage, error: true, file: null };
|
return { message: errorMessage, error: true, file: null };
|
||||||
}
|
}
|
||||||
|
|
1
src/lib/config.ts
Normal file
1
src/lib/config.ts
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export const UPLOAD_LIMIT_MB = 500;
|
Loading…
Add table
Reference in a new issue