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..afa79a7 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; @@ -80,23 +81,39 @@ export const useFileUploader = () => { const handleFileUpload = (event: ChangeEvent) => { const file = event.target.files?.[0]; - if (file) { - 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); + + if (!file) { + toast.error("Error loading file!", { + description: + "Please try uploading the file again or pick a different one.", + }); + + 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 07832e0..4e3c4a4 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); @@ -21,10 +22,26 @@ export const SquareTool: React.FC = () => { const handleImageUpload = (event: ChangeEvent) => { const file = event.target.files?.[0]; - if (file) { - setImageFile(file); - setImageMetadata({ width: 0, height: 0, name: file.name }); + + if (!file) { + toast.error("Error loading file!", { + description: + "Please try uploading the file again or pick a different one.", + }); + + 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 eb9232c..efba39c 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,23 +85,39 @@ export const useFileUploader = () => { const handleFileUpload = (event: ChangeEvent) => { const file = event.target.files?.[0]; - if (file) { - 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); + + if (!file) { + toast.error("Error loading file!", { + description: + "Please try uploading the file again or pick a different one.", + }); + + return; + } + + if (file.type !== "image/svg+xml") { + toast.error("Error uploading file!", { + description: "Only SVGs are supported.", + }); + + 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 = () => { @@ -112,8 +128,6 @@ export const useFileUploader = () => { return { svgContent, imageMetadata, handleFileUpload, cancel }; }; -import React from "react"; - interface SVGRendererProps { svgContent: string; } @@ -190,7 +204,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} + );