Skip to content
This repository has been archived by the owner on Sep 9, 2024. It is now read-only.

Commit

Permalink
Merge pull request #127 from OcularEngineering/files-page
Browse files Browse the repository at this point in the history
Files Dialog
  • Loading branch information
MichaelMoyoMushabati authored Jun 11, 2024
2 parents 07720db + 0b3755a commit d2d620c
Show file tree
Hide file tree
Showing 9 changed files with 231 additions and 152 deletions.
152 changes: 59 additions & 93 deletions packages/ocular-ui/components/files/file-uploader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import * as React from "react";
import Image from "next/image";

import { Cross2Icon, UploadIcon } from "@radix-ui/react-icons";
import Dropzone, { type DropzoneProps, type FileRejection } from "react-dropzone";
import { toast } from "sonner";
import { cn, formatBytes } from "@/lib/utils";
import { useControllableState } from "@/lib/hooks/files/use-controllable-state";
Expand All @@ -17,9 +16,9 @@ interface FileUploaderProps extends React.HTMLAttributes<HTMLDivElement> {
onValueChange?: React.Dispatch<React.SetStateAction<File[]>>;
onUpload?: (files: File[]) => Promise<void>;
progresses?: Record<string, number>;
accept?: DropzoneProps["accept"];
maxSize?: DropzoneProps["maxSize"];
maxFiles?: DropzoneProps["maxFiles"];
accept?: string;
maxSize?: number;
maxFiles?: number;
multiple?: boolean;
disabled?: boolean;
}
Expand All @@ -30,59 +29,55 @@ export function FileUploader(props: FileUploaderProps) {
onValueChange,
onUpload,
progresses,
accept = undefined,
maxSize = Infinity,
accept = undefined,
maxSize = Infinity,
maxFiles = Infinity,
multiple = true,
disabled = false,
className,
...dropzoneProps
...otherProps
} = props;

const [files, setFiles] = useControllableState({
prop: valueProp,
onChange: onValueChange,
});

const onDrop = React.useCallback(
(acceptedFiles: File[], rejectedFiles: FileRejection[]) => {

if ((files?.length ?? 0) + acceptedFiles.length > maxFiles) {
toast.error(`Cannot upload more than ${maxFiles} files`);
return;
}

const newFiles = acceptedFiles.map((file) =>
Object.assign(file, {
preview: URL.createObjectURL(file),
})
);

const updatedFiles = files ? [...files, ...newFiles] : newFiles;

setFiles(updatedFiles);

if (rejectedFiles.length > 0) {
rejectedFiles.forEach(({ file }) => {
toast.error(`File ${file.name} was rejected`);
});
}

if (onUpload && updatedFiles.length > 0 && updatedFiles.length <= maxFiles) {
const target = updatedFiles.length > 0 ? `${updatedFiles.length} files` : `file`;

toast.promise(onUpload(updatedFiles), {
loading: `Uploading ${target}...`,
success: () => {
setFiles([]);
return `${target} uploaded`;
},
error: `Failed to upload ${target}`,
});
}
},
[files, maxFiles, multiple, onUpload, setFiles]
);
const handleFiles = (selectedFiles: FileList) => {
const acceptedFiles = Array.from(selectedFiles);
if ((files?.length ?? 0) + acceptedFiles.length > maxFiles) {
toast.error(`Cannot upload more than ${maxFiles} files`);
return;
}

const newFiles = acceptedFiles.map((file) =>
Object.assign(file, {
preview: URL.createObjectURL(file),
})
);

const updatedFiles = files ? [...files, ...newFiles] : newFiles;
setFiles(updatedFiles);

if (onUpload && updatedFiles.length > 0 && updatedFiles.length <= maxFiles) {
const target = updatedFiles.length > 0 ? `${updatedFiles.length} files` : `file`;

toast.promise(onUpload(updatedFiles), {
loading: `Uploading ${target}...`,
success: () => {
setFiles([]);
return `${target} uploaded`;
},
error: `Failed to upload ${target}`,
});
}
};

const handleFileChange = (event: React.ChangeEvent<HTMLInputElement>) => {
if (event.target.files) {
handleFiles(event.target.files);
}
};

function onRemove(index: number) {
if (!files) return;
Expand All @@ -106,55 +101,26 @@ export function FileUploader(props: FileUploaderProps) {

return (
<div className="relative flex flex-col gap-6 overflow-hidden">
<Dropzone
onDrop={onDrop}
<input
type="file"
accept={accept}
maxSize={maxSize}
maxFiles={maxFiles}
multiple={maxFiles > 1 || multiple}
multiple={multiple}
onChange={handleFileChange}
className="hidden"
disabled={isDisabled}
>
{({ getRootProps, getInputProps, isDragActive }) => (
<div
{...getRootProps()}
className={cn(
"group relative grid h-52 w-full cursor-pointer place-items-center rounded-lg border-2 border-dashed border-muted-foreground/25 px-5 py-2.5 text-center transition hover:bg-muted/25",
"ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2",
isDragActive && "border-muted-foreground/50",
isDisabled && "pointer-events-none opacity-60",
className
)}
{...dropzoneProps}
>
<input {...getInputProps()} />
{isDragActive ? (
<div className="flex flex-col items-center justify-center gap-4 sm:px-5">
<div className="rounded-full border border-dashed p-3">
<UploadIcon className="size-7 text-muted-foreground" aria-hidden="true" />
</div>
<p className="font-medium text-muted-foreground">Drop the files here</p>
</div>
) : (
<div className="flex flex-col items-center justify-center gap-4 sm:px-5">
<div className="rounded-full border border-dashed p-3">
<UploadIcon className="size-7 text-muted-foreground" aria-hidden="true" />
</div>
<div className="space-y-px">
<p className="font-medium text-muted-foreground">
Drag {'n'} drop files here, or click to select files
</p>
<p className="text-sm text-muted-foreground/70">
You can upload
{maxFiles > 1
? ` ${maxFiles === Infinity ? "multiple" : maxFiles} files of any size`
: ` a file of any size`}
</p>
</div>
</div>
)}
</div>
)}
</Dropzone>
id="file-upload"
/>
<div className="flex items-end justify-end">
<Button
type="button"
variant="outline"
className="flex items-center gap-2 rounded-3xl"
onClick={() => document.getElementById("file-upload")?.click()}
disabled={isDisabled}
>
Upload files
</Button>
</div>
{files?.length ? (
<ScrollArea className="h-fit w-full px-3">
<div className="max-h-48 space-y-4">
Expand Down
89 changes: 89 additions & 0 deletions packages/ocular-ui/components/files/files-dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
"use client";

import { useRouter } from 'next/router';
import { buttonVariants, Button } from "@/components/ui/button";
import {
Dialog,
DialogClose,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "@/components/ui/dialog";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
TooltipProvider
} from "@/components/ui/tooltip";
import {
File
} from "lucide-react"


import SectionContainer from "@/components/section-container"
import { UploadedFilesCard } from "@/components/files/uploaded-files-card"
import { useUploadFile } from "@/lib/hooks/files/use-upload-file"
import { FileUploader } from "@/components/files/file-uploader"

export default function FilesDialog({ link }) {
const router = useRouter();
const isActive = (href) => router.pathname === href;

const { uploadFiles, progresses, uploadedFiles, isUploading } = useUploadFile(
"fileUploader",
{ defaultUploadedFiles: [] }
)

return (
<Dialog>
<Tooltip>
<div className="flex items-center space-x-3">
<TooltipTrigger asChild>
<DialogTrigger asChild>
<div className="flex items-center space-x-3 cursor-pointer">
<div
className={`${
buttonVariants({ variant: link.variant, size: "icon" })
} h-9 w-9 ${
isActive(link.link) ? 'bg-accent rounded-md' : 'bg-transparent'
} ${
link.variant === "default" && "dark:bg-muted dark:text-muted-foreground dark:hover:bg-muted dark:hover:text-white"
}`}
>
<File style={{ height: '19px', width: '19px' }} />
<span className="sr-only">{link.title}</span>
</div>
</div>
</DialogTrigger>
</TooltipTrigger>
<TooltipContent side="right" className="flex items-center gap-4">
{link.title}
{link.label && (
<span className="text-muted-foreground ml-auto">
{link.label}
</span>
)}
</TooltipContent>
</div>
</Tooltip>

<DialogContent className="flex flex-col w-[60vw] h-[90vh] max-h-[90vh] justify-between overflow-auto">

<UploadedFilesCard uploadedFiles={uploadedFiles} />

<DialogFooter className="justify-end sticky bottom-0">
<FileUploader
maxFiles={Infinity}
maxSize={Infinity}
progresses={progresses}
onUpload={uploadFiles}
disabled={isUploading}
/>
</DialogFooter>
</DialogContent>
</Dialog>
);
}
77 changes: 39 additions & 38 deletions packages/ocular-ui/components/files/uploaded-files-card.tsx
Original file line number Diff line number Diff line change
@@ -1,54 +1,55 @@
import Image from "next/image"
import type { UploadedFile } from "@/types/types"

import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from "@/components/ui/card"
import type { Files } from "@/types/types"
import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"
import { EmptyCard } from "@/components/files/empty-card"

interface UploadedFilesCardProps {
uploadedFiles: UploadedFile[]
uploadedFiles: Files[]
}

export function UploadedFilesCard({ uploadedFiles }: UploadedFilesCardProps) {

console.log("File Object: ", uploadedFiles)
return (
<Card>
<CardHeader>
<CardTitle>Uploaded files</CardTitle>
<CardDescription>View the uploaded files here</CardDescription>
</CardHeader>
<CardContent>
{uploadedFiles.length > 0 ? (
<ScrollArea className="pb-4">
<div className="flex w-max space-x-2.5">
{uploadedFiles.map((file) => (
<div key={file.key} className="relative aspect-video w-64">
<Image
src={file.url}
alt={file.name}
<div>
{uploadedFiles.length > 0 ? (
<ScrollArea className="pb-4">
<div className="flex flex-col space-y-4">
{uploadedFiles.map((file, key) => (
<div key={key} className="flex items-center space-x-4 p-4 hover:bg-gray-100/50 rounded-2xl">
<div className="w-12 h-12 bg-gray-100 rounded-xl relative">
{/* <Image
src={""}
alt={file.title}
fill
sizes="(min-width: 640px) 640px, 100vw"
loading="lazy"
className="rounded-md object-cover"
/>
/> */}
</div>
<div className="flex flex-col space-y-1">
<p className="text-md font-semibold text-gray-800">{file.title}</p>
<p className="text-sm text-gray-500">
{new Date(file.created_at).toLocaleDateString(undefined, {
day: 'numeric',
month: 'long',
hour: '2-digit',
minute: '2-digit'
})}
</p>
</div>
))}
</div>
<ScrollBar orientation="horizontal" />
</ScrollArea>
) : (
<EmptyCard
title="No files uploaded"
description="Upload some files to see them here"
className="w-full"
/>
)}
</CardContent>
</Card>
</div>
))}
</div>
<ScrollBar orientation="vertical" />
</ScrollArea>
) : (
<EmptyCard
title="No files uploaded"
description="Upload some files to see them here"
className="w-full"
/>
)}
</div>
)
}
Loading

0 comments on commit d2d620c

Please sign in to comment.