From bc6c6aa288220c8b2c6baeea4fa7683dea5a2e33 Mon Sep 17 00:00:00 2001 From: Abraham Date: Sun, 10 Nov 2024 01:44:37 -0800 Subject: [PATCH 1/3] feat: Add simple file validation --- package.json | 10 +++++---- pnpm-lock.yaml | 20 +++++++++++++++--- .../(tools)/rounded-border/rounded-tool.tsx | 15 ++++++++++--- src/app/(tools)/square-image/square-tool.tsx | 13 ++++++++++-- src/app/(tools)/svg-to-png/svg-tool.tsx | 21 +++++++++++++------ src/app/layout.tsx | 4 +++- 6 files changed, 64 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index 78bfe51..8c0bb45 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,8 @@ "next": "15.0.0-rc.1", "next-plausible": "^3.12.2", "react": "19.0.0-rc-cd22717c-20241013", - "react-dom": "19.0.0-rc-cd22717c-20241013" + "react-dom": "19.0.0-rc-cd22717c-20241013", + "sonner": "^1.7.0" }, "devDependencies": { "@types/eslint": "^8.56.10", @@ -31,9 +32,9 @@ "concurrently": "^9.1.0", "eslint": "^8", "eslint-config-next": "15.0.0-rc.1", + "postcss": "^8", "prettier": "^3.3.3", "prettier-plugin-tailwindcss": "^0.6.8", - "postcss": "^8", "tailwindcss": "^3.4.1", "typescript": "^5" }, @@ -42,5 +43,6 @@ "@types/react": "npm:types-react@19.0.0-rc.1", "@types/react-dom": "npm:types-react-dom@19.0.0-rc.1" } - } -} + }, + "packageManager": "pnpm@9.12.3" +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 710dabe..c7486fc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -27,6 +27,9 @@ importers: react-dom: specifier: 19.0.0-rc-cd22717c-20241013 version: 19.0.0-rc-cd22717c-20241013(react@19.0.0-rc-cd22717c-20241013) + sonner: + specifier: ^1.7.0 + version: 1.7.0(react-dom@19.0.0-rc-cd22717c-20241013(react@19.0.0-rc-cd22717c-20241013))(react@19.0.0-rc-cd22717c-20241013) devDependencies: '@types/eslint': specifier: ^8.56.10 @@ -1635,6 +1638,12 @@ packages: simple-swizzle@0.2.2: resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} + sonner@1.7.0: + resolution: {integrity: sha512-W6dH7m5MujEPyug3lpI2l3TC3Pp1+LTgK0Efg+IHDrBbtEjyCmCHHo6yfNBOsf1tFZ6zf+jceWwB38baC8yO9g==} + peerDependencies: + react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc + source-map-js@1.2.1: resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} engines: {node: '>=0.10.0'} @@ -2697,7 +2706,7 @@ snapshots: debug: 4.3.7 enhanced-resolve: 5.17.1 eslint: 8.57.1 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) fast-glob: 3.3.2 get-tsconfig: 4.8.1 is-bun-module: 1.2.1 @@ -2710,7 +2719,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1): + eslint-module-utils@2.12.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1): dependencies: debug: 3.2.7 optionalDependencies: @@ -2732,7 +2741,7 @@ snapshots: doctrine: 2.1.0 eslint: 8.57.1 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.31.0)(eslint@8.57.1))(eslint@8.57.1) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@8.13.0(eslint@8.57.1)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.3)(eslint@8.57.1) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -3613,6 +3622,11 @@ snapshots: is-arrayish: 0.3.2 optional: true + sonner@1.7.0(react-dom@19.0.0-rc-cd22717c-20241013(react@19.0.0-rc-cd22717c-20241013))(react@19.0.0-rc-cd22717c-20241013): + dependencies: + react: 19.0.0-rc-cd22717c-20241013 + react-dom: 19.0.0-rc-cd22717c-20241013(react@19.0.0-rc-cd22717c-20241013) + source-map-js@1.2.1: {} source-map@0.5.7: {} diff --git a/src/app/(tools)/rounded-border/rounded-tool.tsx b/src/app/(tools)/rounded-border/rounded-tool.tsx index 8c85dc4..906eadf 100644 --- a/src/app/(tools)/rounded-border/rounded-tool.tsx +++ b/src/app/(tools)/rounded-border/rounded-tool.tsx @@ -1,9 +1,10 @@ "use client"; + +import { useLocalStorage } from "@/hooks/use-local-storage"; import { usePlausible } from "next-plausible"; -import { useMemo, useState } from "react"; import type { ChangeEvent } from "react"; -import { useLocalStorage } from "@/hooks/use-local-storage"; -import React from "react"; +import React, { useMemo, useState } from "react"; +import { toast } from "sonner"; type Radius = 2 | 4 | 8 | 16 | 32 | 64; @@ -81,6 +82,14 @@ export const useFileUploader = () => { const handleFileUpload = (event: ChangeEvent) => { const file = event.target.files?.[0]; if (file) { + if(!file.type.startsWith("image/")) { + toast.error("Error uploading file!", { + description: "Only Images are supported.", + }); + + return; + } + const reader = new FileReader(); reader.onload = (e) => { const content = e.target?.result as string; diff --git a/src/app/(tools)/square-image/square-tool.tsx b/src/app/(tools)/square-image/square-tool.tsx index 07832e0..4523042 100644 --- a/src/app/(tools)/square-image/square-tool.tsx +++ b/src/app/(tools)/square-image/square-tool.tsx @@ -1,8 +1,9 @@ "use client"; -import React, { useState, useEffect, type ChangeEvent } from "react"; -import { usePlausible } from "next-plausible"; import { useLocalStorage } from "@/hooks/use-local-storage"; +import { usePlausible } from "next-plausible"; +import React, { useEffect, useState, type ChangeEvent } from "react"; +import { toast } from "sonner"; export const SquareTool: React.FC = () => { const [imageFile, setImageFile] = useState(null); @@ -22,6 +23,14 @@ export const SquareTool: React.FC = () => { const handleImageUpload = (event: ChangeEvent) => { const file = event.target.files?.[0]; if (file) { + if(!file.type.startsWith("image/")) { + toast.error("Error uploading file!", { + description: "Only Images are supported.", + }); + + return; + } + setImageFile(file); setImageMetadata({ width: 0, height: 0, name: file.name }); } diff --git a/src/app/(tools)/svg-to-png/svg-tool.tsx b/src/app/(tools)/svg-to-png/svg-tool.tsx index eb9232c..6488e6c 100644 --- a/src/app/(tools)/svg-to-png/svg-tool.tsx +++ b/src/app/(tools)/svg-to-png/svg-tool.tsx @@ -1,9 +1,9 @@ "use client"; -import { usePlausible } from "next-plausible"; -import { useMemo, useState } from "react"; -import { useLocalStorage } from "@/hooks/use-local-storage"; -import { type ChangeEvent } from "react"; +import { useLocalStorage } from "@/hooks/use-local-storage"; +import { usePlausible } from "next-plausible"; +import React, { useMemo, useState, type ChangeEvent } from "react"; +import { toast } from "sonner"; type Scale = 1 | 2 | 4 | 8 | 16 | 32 | 64; @@ -85,7 +85,16 @@ export const useFileUploader = () => { const handleFileUpload = (event: ChangeEvent) => { const file = event.target.files?.[0]; + if (file) { + if(file.type !== "image/svg+xml") { + toast.error("Error uploading file!", { + description: "Only SVG's are supported.", + }); + + return; + } + const reader = new FileReader(); reader.onload = (e) => { const content = e.target?.result as string; @@ -112,7 +121,7 @@ export const useFileUploader = () => { return { svgContent, imageMetadata, handleFileUpload, cancel }; }; -import React from "react"; + interface SVGRendererProps { svgContent: string; @@ -190,7 +199,7 @@ export function SVGTool() { diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 2613334..d2781bf 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,7 +1,8 @@ import type { Metadata } from "next"; +import PlausibleProvider from "next-plausible"; import localFont from "next/font/local"; +import { Toaster } from "sonner"; import "./globals.css"; -import PlausibleProvider from "next-plausible"; const geistSans = localFont({ src: "./fonts/GeistVF.woff", @@ -36,6 +37,7 @@ export default function RootLayout({ className={`${geistSans.variable} ${geistMono.variable} antialiased`} > {children} + ); From 993cb0177e5353a333eb4e4ec5f6efeaba1ffee3 Mon Sep 17 00:00:00 2001 From: Abraham Date: Sun, 10 Nov 2024 01:46:30 -0800 Subject: [PATCH 2/3] fix: Remove SVG apostrophe --- src/app/(tools)/svg-to-png/svg-tool.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/(tools)/svg-to-png/svg-tool.tsx b/src/app/(tools)/svg-to-png/svg-tool.tsx index 6488e6c..d4e6822 100644 --- a/src/app/(tools)/svg-to-png/svg-tool.tsx +++ b/src/app/(tools)/svg-to-png/svg-tool.tsx @@ -89,7 +89,7 @@ export const useFileUploader = () => { if (file) { if(file.type !== "image/svg+xml") { toast.error("Error uploading file!", { - description: "Only SVG's are supported.", + description: "Only SVGs are supported.", }); return; From 06136c33ef3028dc2f1c37d6b6b9aa1660e354da Mon Sep 17 00:00:00 2001 From: Abraham Date: Sun, 10 Nov 2024 02:14:24 -0800 Subject: [PATCH 3/3] feat: Add validation for empty uploaded --- .../(tools)/rounded-border/rounded-tool.tsx | 52 ++++++++++-------- src/app/(tools)/square-image/square-tool.tsx | 26 +++++---- src/app/(tools)/svg-to-png/svg-tool.tsx | 53 ++++++++++--------- 3 files changed, 76 insertions(+), 55 deletions(-) diff --git a/src/app/(tools)/rounded-border/rounded-tool.tsx b/src/app/(tools)/rounded-border/rounded-tool.tsx index 906eadf..afa79a7 100644 --- a/src/app/(tools)/rounded-border/rounded-tool.tsx +++ b/src/app/(tools)/rounded-border/rounded-tool.tsx @@ -81,31 +81,39 @@ export const useFileUploader = () => { const handleFileUpload = (event: ChangeEvent) => { const file = event.target.files?.[0]; - if (file) { - if(!file.type.startsWith("image/")) { - toast.error("Error uploading file!", { - description: "Only Images are supported.", - }); - return; - } + if (!file) { + toast.error("Error loading file!", { + description: + "Please try uploading the file again or pick a different one.", + }); - const reader = new FileReader(); - reader.onload = (e) => { - const content = e.target?.result as string; - const img = new Image(); - img.onload = () => { - setImageMetadata({ - width: img.width, - height: img.height, - name: file.name, - }); - setImageContent(content); - }; - img.src = content; - }; - reader.readAsDataURL(file); + return; + } + + if (!file.type.startsWith("image/")) { + toast.error("Error uploading file!", { + description: "Only Images are supported.", + }); + + return; } + + const reader = new FileReader(); + reader.onload = (e) => { + const content = e.target?.result as string; + const img = new Image(); + img.onload = () => { + setImageMetadata({ + width: img.width, + height: img.height, + name: file.name, + }); + setImageContent(content); + }; + img.src = content; + }; + reader.readAsDataURL(file); }; const cancel = () => { diff --git a/src/app/(tools)/square-image/square-tool.tsx b/src/app/(tools)/square-image/square-tool.tsx index 4523042..4e3c4a4 100644 --- a/src/app/(tools)/square-image/square-tool.tsx +++ b/src/app/(tools)/square-image/square-tool.tsx @@ -22,18 +22,26 @@ export const SquareTool: React.FC = () => { const handleImageUpload = (event: ChangeEvent) => { const file = event.target.files?.[0]; - if (file) { - if(!file.type.startsWith("image/")) { - toast.error("Error uploading file!", { - description: "Only Images are supported.", - }); - return; - } + if (!file) { + toast.error("Error loading file!", { + description: + "Please try uploading the file again or pick a different one.", + }); - setImageFile(file); - setImageMetadata({ width: 0, height: 0, name: file.name }); + return; } + + if (!file.type.startsWith("image/")) { + toast.error("Error uploading file!", { + description: "Only Images are supported.", + }); + + return; + } + + setImageFile(file); + setImageMetadata({ width: 0, height: 0, name: file.name }); }; const handleBackgroundColorChange = ( diff --git a/src/app/(tools)/svg-to-png/svg-tool.tsx b/src/app/(tools)/svg-to-png/svg-tool.tsx index d4e6822..efba39c 100644 --- a/src/app/(tools)/svg-to-png/svg-tool.tsx +++ b/src/app/(tools)/svg-to-png/svg-tool.tsx @@ -86,31 +86,38 @@ export const useFileUploader = () => { const handleFileUpload = (event: ChangeEvent) => { const file = event.target.files?.[0]; - if (file) { - if(file.type !== "image/svg+xml") { - toast.error("Error uploading file!", { - description: "Only SVGs are supported.", - }); + if (!file) { + toast.error("Error loading file!", { + description: + "Please try uploading the file again or pick a different one.", + }); - return; - } + return; + } + + if (file.type !== "image/svg+xml") { + toast.error("Error uploading file!", { + description: "Only SVGs are supported.", + }); - const reader = new FileReader(); - reader.onload = (e) => { - const content = e.target?.result as string; - - // Extract width and height from SVG content - const parser = new DOMParser(); - const svgDoc = parser.parseFromString(content, "image/svg+xml"); - const svgElement = svgDoc.documentElement; - const width = parseInt(svgElement.getAttribute("width") ?? "300"); - const height = parseInt(svgElement.getAttribute("height") ?? "150"); - - setSvgContent(content); - setImageMetadata({ width, height, name: file.name }); - }; - reader.readAsText(file); + return; } + + const reader = new FileReader(); + reader.onload = (e) => { + const content = e.target?.result as string; + + // Extract width and height from SVG content + const parser = new DOMParser(); + const svgDoc = parser.parseFromString(content, "image/svg+xml"); + const svgElement = svgDoc.documentElement; + const width = parseInt(svgElement.getAttribute("width") ?? "300"); + const height = parseInt(svgElement.getAttribute("height") ?? "150"); + + setSvgContent(content); + setImageMetadata({ width, height, name: file.name }); + }; + reader.readAsText(file); }; const cancel = () => { @@ -121,8 +128,6 @@ export const useFileUploader = () => { return { svgContent, imageMetadata, handleFileUpload, cancel }; }; - - interface SVGRendererProps { svgContent: string; }