Skip to content

Commit

Permalink
feat: multi upload
Browse files Browse the repository at this point in the history
  • Loading branch information
nguyenthanhanit committed Dec 16, 2023
1 parent 3ae6e00 commit 25e5550
Show file tree
Hide file tree
Showing 5 changed files with 154 additions and 195 deletions.
14 changes: 6 additions & 8 deletions app/admin/[slug]/new/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,56 +11,54 @@ import 'react-toastify/dist/ReactToastify.css';
import {capitalize} from "@/utils/letter";
import DynamicComponent from "@/components/dynamic-component";
import {Spinner} from "@nextui-org/spinner";
import MultipleUploadForm from "@/components/admin/multiple-upload-form";

const FIELDS: any = {
news: [
{
name: 'name',
label: 'Tên bài viết',
component: 'InputCustom',
default: ''
},
{
name: 'publish_at',
label: 'Ngày đăng',
component: 'DatePickerCustom',
default: ''
},
{
name: 'thumbnail',
label: 'Ảnh đại diện',
component: 'SingleFileUploadForm',
default: ''
},
{
name: 'content',
component: 'TextEditor',
default: ''
},
],
project: [
{
name: 'name',
label: 'Tên bài viết',
component: 'InputCustom',
default: ''
},
{
name: 'thumbnail',
label: 'Ảnh đại diện',
component: 'SingleFileUploadForm',
default: ''
},
{
name: 'cover',
label: 'Ảnh bìa',
component: 'SingleFileUploadForm',
default: ''
},
{
name: 'content',
component: 'TextEditor',
default: ''
},
{
name: 'images',
label: 'Ảnh chi tiết',
component: 'MultipleUploadForm',
},
],
}
Expand Down
144 changes: 144 additions & 0 deletions components/admin/multiple-upload-form.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import Image from "next/image";
import React, {ChangeEvent, useState} from "react";
import {toast, ToastContainer} from "react-toastify";
import 'react-toastify/dist/ReactToastify.css';

const MultipleUploadForm = () => {
const [file, setFile] = useState<File | null>(null);
const [previewUrls, setPreviewUrls] = useState<string[]>([]);

const onFilesUploadChange = async (e: ChangeEvent<HTMLInputElement>) => {
const fileInput = e.target;

if (!fileInput.files) {
toast.error("No file was chosen");
return;
}

if (!fileInput.files || fileInput.files.length === 0) {
toast.error("Files list is empty");
return;
}

/** Files validation */
const validFiles: File[] = [];
for (let i = 0; i < fileInput.files.length; i++) {
const file = fileInput.files[i];

if (!file.type.startsWith("image")) {
toast.error(`File ${i + 1} is invalid`);
continue;
}

validFiles.push(file);
}

if (!validFiles.length) {
toast.error('No valid files were chosen');
return;
}

/** Uploading files to the server */
try {
const formData = new FormData();
validFiles.forEach(file => formData.append("media", file));

const resUpload = await fetch("/admin/api/upload", {
method: "POST",
body: formData,
});

const {
data,
error,
}: {
data: {
urls: {};
} | null;
error: string | null;
} = await resUpload.json();

if (error || !data) {
toast.error(error || "Sorry! something went wrong.");
return;
}

setPreviewUrls(
validFiles.map(validFile => URL.createObjectURL(validFile))
); // we will use this to show the preview of the images

/** Reset file input */
fileInput.type = "text";
fileInput.type = "file";

console.log("Files were uploaded successfully:", data);
} catch (error) {
console.error(error);
toast.error("Sorry! something went wrong.");
}
};

return (
<>
<form
className="w-full p-3 border border-gray-500 border-dashed"
onSubmit={(e) => e.preventDefault()}
>
{previewUrls.length > 0 ? (
<>
<button
onClick={() => setPreviewUrls([])}
className="mb-3 text-sm font-medium text-gray-500 transition-colors duration-300 hover:text-gray-900"
>
Clear Previews
</button>

<div className="flex flex-wrap justify-start">
{previewUrls.map((previewUrl, idx) => (
<div key={idx} className="w-full p-1.5 md:w-1/2">
<Image
alt="file uploader preview"
objectFit="cover"
src={previewUrl}
width={320}
height={218}
layout="responsive"
/>
</div>
))}
</div>
</>
) : (
<label
className="flex flex-col items-center justify-center h-full py-8 transition-colors duration-150 cursor-pointer hover:text-gray-600">
<svg
xmlns="http://www.w3.org/2000/svg"
className="w-14 h-14"
fill="none"
viewBox="0 0 24 24"
stroke="currentColor"
strokeWidth={2}
>
<path
strokeLinecap="round"
strokeLinejoin="round"
d="M3 15a4 4 0 004 4h9a5 5 0 10-.1-9.999 5.002 5.002 0 10-9.78 2.096A4.001 4.001 0 003 15z"
/>
</svg>
<strong className="text-sm font-medium">Select images</strong>
<input
className="block w-0 h-0"
name="file"
type="file"
onChange={onFilesUploadChange}
multiple
/>
</label>
)}
</form>
<ToastContainer/>
</>
);
};

export default MultipleUploadForm;
49 changes: 1 addition & 48 deletions components/admin/single-upload-form.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,11 @@ import 'react-toastify/dist/ReactToastify.css';
type Props = {
value?: any
onChange?: any
uploadDirect?: boolean
isError?: boolean
label?: string
}

const SingleFileUploadForm = ({label, isError, uploadDirect, value, onChange, ...rest}: Props) => {
const SingleFileUploadForm = ({label, isError, value, onChange, ...rest}: Props) => {
const [file, setFile] = useState<File | null>(null);
const [previewUrl, setPreviewUrl] = useState<string | null>(value);

Expand Down Expand Up @@ -57,41 +56,6 @@ const SingleFileUploadForm = ({label, isError, uploadDirect, value, onChange, ..
setPreviewUrl(null);
};

const onUploadFile = async (e: MouseEvent<HTMLButtonElement>) => {
e.preventDefault();

if (!file) {
return;
}

try {
const formData = new FormData();
formData.append("media", file);

const res = await fetch("/admin/api/upload", {
method: "POST",
body: formData,
});

const {
data,
error,
}: {
data: {
url: string | string[];
} | null;
error: string | null;
} = await res.json();

if (error || !data) {
toast.error(error || "Sorry! something went wrong.");
return;
}
} catch (error) {
toast.error("Sorry! something went wrong.");
}
};

return (
<>
<form
Expand All @@ -110,17 +74,6 @@ const SingleFileUploadForm = ({label, isError, uploadDirect, value, onChange, ..
>
Huỷ
</Button>
{
uploadDirect && <Button
disabled={!previewUrl}
onClick={onUploadFile}
color="primary"
variant="bordered"
className="ml-2 shadow-sm hover:bg-blue-500 hover:text-white focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600"
>
Upload
</Button>
}
</div>
</div>
<div className="flex flex-col md:flex-row gap-1.5 md:py-4">
Expand Down
4 changes: 3 additions & 1 deletion components/dynamic-component.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
import React from 'react';
import SingleFileUploadForm from "@/components/admin/single-upload-form";
import MultipleUploadForm from "@/components/admin/multiple-upload-form";
import InputCustom from "@/components/admin/input";
import DatePickerCustom from "@/components/admin/datepicker";
import TextEditor from "@/components/admin/text-editor";

// Define the DynamicComponentProps with componentName as a string
type DynamicComponentProps = {
componentName: 'SingleFileUploadForm' | 'InputCustom' | 'DatePickerCustom' | 'TextEditor'
componentName: 'SingleFileUploadForm' | 'MultipleUploadForm' | 'InputCustom' | 'DatePickerCustom' | 'TextEditor'
isError?: boolean
label?: string
};
Expand All @@ -15,6 +16,7 @@ const DynamicComponent = ({componentName, isError, label, ...rest}: DynamicCompo
// Map component names to actual component references
const componentMap: Record<string, React.FC<any>> = {
SingleFileUploadForm,
MultipleUploadForm,
InputCustom,
DatePickerCustom,
TextEditor,
Expand Down
Loading

0 comments on commit 25e5550

Please sign in to comment.