Skip to content

Commit

Permalink
feat: Custom toast with custom uploader
Browse files Browse the repository at this point in the history
  • Loading branch information
ismi-abbas committed Jul 23, 2023
1 parent 80d9e0b commit f0f2eb5
Show file tree
Hide file tree
Showing 6 changed files with 224 additions and 7 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"react-dom": "18.2.0",
"react-dropzone": "^14.2.3",
"react-select": "^5.7.0",
"react-toastify": "^9.1.3",
"sharp": "^0.32.1",
"superjson": "1.9.1",
"tsx": "^3.12.5",
Expand Down
10 changes: 6 additions & 4 deletions src/components/AddSurauForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import DistrictSelect from "./shared/DistrictSelect";
import { UploadDropzone } from "../utils/uploadthing";
import { CheckIcon, TrashIcon } from "@heroicons/react/24/outline";
import { utapi } from "uploadthing/server";
import CustomUpload from "./shared/CustomUpload";

const Select = dynamic(() => import("react-select"), {
ssr: true,
Expand Down Expand Up @@ -441,7 +442,7 @@ const AddSurauForm: FC<AddSurauFormProps> = ({ setOpen }) => {
<div className="mb-2 text-center text-xs font-light italic">
Upload image here
</div>
<UploadDropzone
{/* <UploadDropzone
endpoint="imageUploader"
onClientUploadComplete={(res) => {
// Do something with the response
Expand All @@ -455,10 +456,11 @@ const AddSurauForm: FC<AddSurauFormProps> = ({ setOpen }) => {
// Do something with the error.
alert(`ERROR! ${error.message}`);
}}
/>
/> */}
<CustomUpload />
</div>

<div className="">
{/* <div className="">
<div className="flex flex-col">
{imagePreviews.length > 0 ? (
<div className="mb-2 text-center text-xs font-light italic">
Expand Down Expand Up @@ -501,7 +503,7 @@ const AddSurauForm: FC<AddSurauFormProps> = ({ setOpen }) => {
))
: null}
</div>
</div>
</div> */}
</div>
<div className="flex flex-row items-end justify-end gap-2 bg-gray-50 px-4 py-3 text-right sm:px-6">
<button
Expand Down
43 changes: 43 additions & 0 deletions src/components/shared/CustomToast.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import React from "react";
import { ToastContainer } from "react-toastify";
import "react-toastify/dist/ReactToastify.css";
import { CheckCircleIcon } from "@heroicons/react/20/solid";
import { type } from "os";

type ToastType = "success" | "error" | "info" | "warning" | "default" | "dark";

const iconContext: { [key in ToastType]: string } = {
success: "text-green-500",
error: "text-red-500",
info: "text-blue-500",
warning: "text-yellow-500",
default: "text-gray-500",
dark: "text-gray-500",
};

const CustomToast = () => {
return (
<div>
<ToastContainer
className="flex flex-col items-center justify-center"
toastClassName={() =>
"bg-white shadow-md flex relative p-1 min-h-2 rounded-md justify-between overflow-hidden cursor-pointer"
}
bodyClassName={() =>
"text-md font-med p-4 text-gray-800 flex justify-between items-center"
}
position="top-center"
autoClose={2000}
newestOnTop={true}
icon={({ type }) =>
"success" ? (
<CheckCircleIcon className={`h-8 w-8 ${iconContext[type]}`} />
) : null
}
closeButton={false}
/>
</div>
);
};

export default CustomToast;
169 changes: 169 additions & 0 deletions src/components/shared/CustomUpload.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
import { useCallback, useState } from "react";
import { FileWithPath, useDropzone } from "react-dropzone";
import { useUploadThing } from "../../utils/uploadthing";
import { classNames, generateClientDropzoneAccept } from "uploadthing/client";
import Image from "next/image";
import { TrashIcon } from "@heroicons/react/24/outline";
import "@uploadthing/react/styles.css";
import { ToastContainer, toast } from "react-toastify";
import CustomToast from "./CustomToast";

type ValidFileTypes = "image" | "video" | "audio" | "blob";

const fileTypes: ValidFileTypes[] = ["image", "video", "audio", "blob"];

function CustomUpload() {
const [files, setFiles] = useState<File[]>([]);
const onDrop = useCallback((acceptedFiles: FileWithPath[]) => {
setFiles(acceptedFiles);
}, []);

const { getRootProps, getInputProps, acceptedFiles, isDragActive } =
useDropzone({
onDrop,
accept: fileTypes ? generateClientDropzoneAccept(fileTypes) : undefined,
});

const { startUpload, isUploading, permittedFileInfo } = useUploadThing(
"imageUploader",
{
onClientUploadComplete: () => {
toast.success("Upload complete!");
},
onUploadError: () => {
alert("error occurred while uploading");
},
}
);

const sizeConverter = (bytes: number) => {
const sizes = ["Bytes", "KB", "MB", "GB", "TB"];

if (bytes === 0) return "0 Byte";
const i = Math.floor(Math.log(bytes) / Math.log(1024));
return `${Math.round(bytes / Math.pow(1024, i))} ${sizes[i] || "Bytes"}`;
};

const removeFile = (file: FileWithPath) => {
setFiles(files.filter((f) => f !== file));
};

return (
<div>
<CustomToast />
<button onClick={() => toast.success("Upload complete!")}>
Toast Test
</button>
<button onClick={() => toast.warning("Upload complete!")}>
Toast Test
</button>
<button onClick={() => toast.error("Upload complete!")}>
Toast Test
</button>

<div
className={classNames(
"mt-2 flex justify-center rounded-lg border border-dashed border-gray-900/25 px-6 py-2",
isDragActive ? "bg-blue-600/10" : ""
)}
>
<div className="text-center" {...getRootProps()}>
<div className="flex text-sm leading-6 text-gray-600">
<label
htmlFor="file-upload"
className="relative cursor-pointer font-semibold text-blue-600 focus-within:outline-none focus-within:ring-2 focus-within:ring-blue-600 focus-within:ring-offset-2 hover:text-blue-500"
>
{`Choose files`}
<input className="sr-only" {...getInputProps()} />
</label>
<p className="pl-1">{`or drag and drop`}</p>
</div>
<div className="h-[1.25rem]">
<p className="text-xs leading-5 text-gray-600">todo size limit</p>
</div>
{files.length > 0 && (
<div className="mt-4 flex items-center justify-center">
<button
className="flex h-10 w-36 items-center justify-center rounded-md bg-blue-600"
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
if (!files) return;

void startUpload(files);
}}
>
<span className="px-3 py-2 text-white">
{isUploading ? (
<Spinner />
) : (
`Upload ${files.length} file${
files.length === 1 ? "" : "s"
}`
)}
</span>
</button>
</div>
)}
</div>
</div>
{files.length > 0 && !isUploading && (
<div>
<h4>Files</h4>
<ul className="mb-2 rounded-lg border">
{files.map((file: FileWithPath) => (
<li
id="imagePreviewDiv"
key={file.path}
className="m-1 flex items-start justify-between rounded-md p-2"
>
<div className="ml-2 w-[80%] justify-center overflow-hidden text-xs sm:text-sm">
<p className="overflow-hidden text-ellipsis">{file.name}</p>
<p className="text-slate-500">{sizeConverter(file.size)}</p>
</div>
<button onClick={() => removeFile(file)}>
<TrashIcon className="h-5 w-5 text-red-500" />
</button>
</li>
))}
</ul>
{/* <div>
{files.length > 0 && (
<>
<div className="flex flex-row gap-2">
<button className="rounded-md border bg-indigo-600 px-2 py-1 text-sm text-white">
Upload {files.length} files
</button>
<button
className="rounded-md border bg-indigo-600 px-2 py-1 text-sm text-white"
onClick={() => setFiles([])}
>
Remove all
</button>
</div>
</>
)}
</div> */}
</div>
)}
</div>
);
}

const Spinner = () => {
return (
<svg
className="h-5 w-5 animate-spin text-white"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 576 512"
>
<path
fill="currentColor"
d="M256 32C256 14.33 270.3 0 288 0C429.4 0 544 114.6 544 256C544 302.6 531.5 346.4 509.7 384C500.9 399.3 481.3 404.6 465.1 395.7C450.7 386.9 445.5 367.3 454.3 351.1C470.6 323.8 480 291 480 255.1C480 149.1 394 63.1 288 63.1C270.3 63.1 256 49.67 256 31.1V32z"
/>
</svg>
);
};

export default CustomUpload;
1 change: 0 additions & 1 deletion src/server/uploadthing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,3 @@ export const ourFileRouter = {
} satisfies FileRouter;

export type OurFileRouter = typeof ourFileRouter;

7 changes: 5 additions & 2 deletions src/utils/uploadthing.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { generateComponents } from "@uploadthing/react";

import { generateReactHelpers } from "@uploadthing/react/hooks";

import type { OurFileRouter } from "../server/uploadthing";

export const { UploadButton, UploadDropzone, Uploader } =
generateComponents<OurFileRouter>();

export const { useUploadThing } = generateReactHelpers<OurFileRouter>();

0 comments on commit f0f2eb5

Please sign in to comment.