From d2643522a43e317aaffb5cb61d02821e5cf41a01 Mon Sep 17 00:00:00 2001 From: Linkgls Date: Fri, 6 Sep 2024 18:43:39 +1000 Subject: [PATCH 1/2] chore: Add uploader component for file uploads in dashboard page --- app/dashboard/components/uploader.tsx | 153 ++++++++++++++++++++++++++ app/dashboard/page.tsx | 39 ++++--- components/ui/alert.tsx | 59 ++++++++++ components/ui/avatar.tsx | 50 +++++++++ components/ui/tooltip.tsx | 30 +++++ 5 files changed, 316 insertions(+), 15 deletions(-) create mode 100644 app/dashboard/components/uploader.tsx create mode 100644 components/ui/alert.tsx create mode 100644 components/ui/avatar.tsx create mode 100644 components/ui/tooltip.tsx diff --git a/app/dashboard/components/uploader.tsx b/app/dashboard/components/uploader.tsx new file mode 100644 index 0000000..5d7eba9 --- /dev/null +++ b/app/dashboard/components/uploader.tsx @@ -0,0 +1,153 @@ +import React, { useState, useEffect } from "react"; +import { AlertCircle, X, CloudUpload } from "lucide-react"; + +import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; + +const Uploader: React.FC = () => { + const [selectedFile, setSelectedFile] = useState(null); + const [showAlert, setShowAlert] = useState(false); + const allowedFileTypes = [".json", ".txt"]; // Allowed file extensions + const maxFileSize = 100 * 1024 * 1024; // 100MB + + useEffect(() => { + let timer: NodeJS.Timeout; + if (showAlert) { + timer = setTimeout(() => { + setShowAlert(false); + }, 2000); + } + return () => clearTimeout(timer); + }, [showAlert]); + + const handleFileChange = (event: React.ChangeEvent) => { + const file = event.target.files?.[0]; + if (file && allowedFileTypes.includes(file.name.split(".").pop() || "")) { + setSelectedFile(file); + setShowAlert(false); + } else { + setShowAlert(true); + } + }; + + const handleDragOver = (event: React.DragEvent) => { + event.preventDefault(); + }; + + const handleDrop = (event: React.DragEvent) => { + event.preventDefault(); + const file = event.dataTransfer.files?.[0]; + if ( + file && + allowedFileTypes.includes(file.name.split(".").pop() || "") && + file.size <= maxFileSize + ) { + setSelectedFile(file); + setShowAlert(false); + } else { + setShowAlert(true); + } + }; + + return ( +
+ {showAlert && ( +
+ +
+ +
+ Error + + Invalid file type or file size exceeds the limit. + +
+
+ +
+
+ )} + +
+

Upload your data

+

+ Use the dropzone below to upload your file. +

+ +
+ +

+ Drag or Browse{" "} + your files +

+

or

+ + Use a sample file + + + +

+ Supported files: raire-rs in .json, .txt | Upload limit: 100MB +

+
+ + {selectedFile && ( +
+

Selected file: {selectedFile.name}

+

File type: {selectedFile.type}

+
+ )} +
+
+ ); +}; + +export default Uploader; diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx index aa498ab..4be7c3d 100644 --- a/app/dashboard/page.tsx +++ b/app/dashboard/page.tsx @@ -1,27 +1,36 @@ "use client"; import React from "react"; +import Image from "next/image"; +import Uploader from "./components/uploader"; const Dashboard: React.FC = () => { return (
- {/* Left Tool Bar */} - - - {/* Main Work Space */}
-

Dashboard

+
+ Logo +

AuditVisualiser

+
-

TODO

- {/* Add more Stuff here*/} +

+ Show the effect of assertions +

+

+ Evaluate any audit result that is formatted as a JSON file. +

+ +

+ By sharing your files or using our service, you agree to our  + + Terms of Service + +  and  + + Privacy Policy + + . +

diff --git a/components/ui/alert.tsx b/components/ui/alert.tsx new file mode 100644 index 0000000..13219e7 --- /dev/null +++ b/components/ui/alert.tsx @@ -0,0 +1,59 @@ +import * as React from "react"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@/lib/utils"; + +const alertVariants = cva( + "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground", + { + variants: { + variant: { + default: "bg-background text-foreground", + destructive: + "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", + }, + }, + defaultVariants: { + variant: "default", + }, + }, +); + +const Alert = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes & VariantProps +>(({ className, variant, ...props }, ref) => ( +
+)); +Alert.displayName = "Alert"; + +const AlertTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +AlertTitle.displayName = "AlertTitle"; + +const AlertDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +AlertDescription.displayName = "AlertDescription"; + +export { Alert, AlertTitle, AlertDescription }; diff --git a/components/ui/avatar.tsx b/components/ui/avatar.tsx new file mode 100644 index 0000000..09cd14d --- /dev/null +++ b/components/ui/avatar.tsx @@ -0,0 +1,50 @@ +"use client"; + +import * as React from "react"; +import * as AvatarPrimitive from "@radix-ui/react-avatar"; + +import { cn } from "@/lib/utils"; + +const Avatar = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +Avatar.displayName = AvatarPrimitive.Root.displayName; + +const AvatarImage = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AvatarImage.displayName = AvatarPrimitive.Image.displayName; + +const AvatarFallback = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName; + +export { Avatar, AvatarImage, AvatarFallback }; diff --git a/components/ui/tooltip.tsx b/components/ui/tooltip.tsx new file mode 100644 index 0000000..2c42a2c --- /dev/null +++ b/components/ui/tooltip.tsx @@ -0,0 +1,30 @@ +"use client"; + +import * as React from "react"; +import * as TooltipPrimitive from "@radix-ui/react-tooltip"; + +import { cn } from "@/lib/utils"; + +const TooltipProvider = TooltipPrimitive.Provider; + +const Tooltip = TooltipPrimitive.Root; + +const TooltipTrigger = TooltipPrimitive.Trigger; + +const TooltipContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + +)); +TooltipContent.displayName = TooltipPrimitive.Content.displayName; + +export { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider }; From 6a4ec6ecea5fd4c6acdd041c9c7847b269690358 Mon Sep 17 00:00:00 2001 From: Linkgls Date: Fri, 27 Sep 2024 17:25:03 +1000 Subject: [PATCH 2/2] refactor(uploader): Modify Uploader component to accept className prop The Uploader component has been modified to accept an optional className prop. This allows for more flexibility in styling the component when it is used in different contexts. - Updated the Uploader component to include an interface UploaderProps with a className prop - Modified the JSX code to pass the className prop to the root div element Closes # --- app/dashboard/components/uploader.tsx | 82 +++++++++------------------ app/dashboard/page.tsx | 8 +-- 2 files changed, 31 insertions(+), 59 deletions(-) diff --git a/app/dashboard/components/uploader.tsx b/app/dashboard/components/uploader.tsx index 5d7eba9..a6bf374 100644 --- a/app/dashboard/components/uploader.tsx +++ b/app/dashboard/components/uploader.tsx @@ -3,7 +3,11 @@ import { AlertCircle, X, CloudUpload } from "lucide-react"; import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; -const Uploader: React.FC = () => { +interface UploaderProps { + className?: string; // Optional className prop +} + +const Uploader: React.FC = ({ className }) => { const [selectedFile, setSelectedFile] = useState(null); const [showAlert, setShowAlert] = useState(false); const allowedFileTypes = [".json", ".txt"]; // Allowed file extensions @@ -49,16 +53,9 @@ const Uploader: React.FC = () => { }; return ( -
+
{showAlert && ( -
+
{
)} -
-

Upload your data

-

- Use the dropzone below to upload your file. -

+

Upload your data

+

+ Use the dropzone below to upload your file. +

-
+
+

Drag or Browse{" "} @@ -117,35 +104,20 @@ const Uploader: React.FC = () => { type="file" accept=".json, .txt" onChange={handleFileChange} - style={{ - opacity: 0, - position: "absolute", - top: 0, - left: 0, - width: "100%", - height: "100%", - cursor: "pointer", - }} + className="top-0 left-0 w-full h-full opacity-0 cursor-pointer" /> -

- Supported files: raire-rs in .json, .txt | Upload limit: 100MB +

+ Supported files: .json, .txt | Upload limit: 100MB

- - {selectedFile && ( -
-

Selected file: {selectedFile.name}

-

File type: {selectedFile.type}

-
- )}
+ + {selectedFile && ( +
+

Selected file: {selectedFile.name}

+

File type: {selectedFile.type}

+
+ )}
); }; diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx index 4be7c3d..863eb6a 100644 --- a/app/dashboard/page.tsx +++ b/app/dashboard/page.tsx @@ -6,20 +6,20 @@ import Uploader from "./components/uploader"; const Dashboard: React.FC = () => { return ( -
-
+
+
Logo

AuditVisualiser

-
+

Show the effect of assertions

Evaluate any audit result that is formatted as a JSON file.

- +

By sharing your files or using our service, you agree to our