diff --git a/backend/.prettierignore b/backend/.prettierignore new file mode 100644 index 0000000..1b8ac88 --- /dev/null +++ b/backend/.prettierignore @@ -0,0 +1,3 @@ +# Ignore artifacts: +build +coverage diff --git a/backend/.prettierrc b/backend/.prettierrc new file mode 100644 index 0000000..0967ef4 --- /dev/null +++ b/backend/.prettierrc @@ -0,0 +1 @@ +{} diff --git a/backend/package-lock.json b/backend/package-lock.json index cb04950..8c99059 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -19,6 +19,7 @@ }, "devDependencies": { "@types/node": "^22.13.4", + "prettier": "3.5.1", "ts-node": "^10.9.2", "typescript": "^5.7.3" } @@ -758,6 +759,22 @@ "integrity": "sha512-e906FRY0+tV27iq4juKzSYPbUj2do2X2JX4EzSca1631EB2QJQUqGbDuERal7LCtOpxl6x3+nvo9NPZcmjkiFA==", "license": "MIT" }, + "node_modules/prettier": { + "version": "3.5.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.1.tgz", + "integrity": "sha512-hPpFQvHwL3Qv5AdRvBFMhnKo4tYxp0ReXiPn2bxkiohEX6mBeBwEpBSQTkD458RaaDKQMYSp4hX4UtfUTA5wDw==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, "node_modules/process-warning": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-4.0.1.tgz", diff --git a/backend/package.json b/backend/package.json index fbb889d..671e0b5 100644 --- a/backend/package.json +++ b/backend/package.json @@ -22,6 +22,7 @@ }, "devDependencies": { "@types/node": "^22.13.4", + "prettier": "3.5.1", "ts-node": "^10.9.2", "typescript": "^5.7.3" } diff --git a/backend/server.ts b/backend/server.ts index 03bb2e9..220d0e3 100644 --- a/backend/server.ts +++ b/backend/server.ts @@ -1,15 +1,17 @@ import Fastify from "fastify"; import cors from "@fastify/cors"; -import multipart from '@fastify/multipart'; +import multipart from "@fastify/multipart"; import { libreConvert } from "./src/routes/libreconvert.route"; +import { colorConvert } from "./src/routes/colorconvert.route"; const app = Fastify({ logger: true }); app.register(cors, { origin: "*" }); app.register(multipart); app.register(libreConvert); +app.register(colorConvert); const PORT = process.env.PORT || 4000; app.listen({ port: Number(PORT), host: "0.0.0.0" }, () => { - console.log(`🚀Fastify is live on http://localhost:${PORT}`); -}); \ No newline at end of file + console.log(`🚀Fastify is live on http://localhost:${PORT}`); +}); diff --git a/backend/src/routes/colorconvert.route.ts b/backend/src/routes/colorconvert.route.ts new file mode 100644 index 0000000..402e642 --- /dev/null +++ b/backend/src/routes/colorconvert.route.ts @@ -0,0 +1,29 @@ +import { FastifyInstance, FastifyReply, FastifyRequest } from "fastify"; + +interface RequestBody { + red: string; + green: string; + blue: string; +} + +export async function colorConvert(app: FastifyInstance) { + app.post( + "/api/color-convert", + async ( + request: FastifyRequest<{ Body: RequestBody }>, + reply: FastifyReply, + ) => { + try { + const data = request.body; + if (!data) { + return reply.status(400).send({ error: "No RGB declared!" }); + } + const hex = (`#${(+data.red).toString(16).padStart(2, "0")}${(+data.green).toString(16).padStart(2, "0")}${(+data.blue).toString(16).padStart(2, "0")}`).toUpperCase(); + reply.header("Content-Type", "application/json").send({ hex: hex }); + } catch (error) { + console.error("Convert error:", error); + reply.status(500).send({ error: "Error while converting!" }); + } + }, + ); +} diff --git a/backend/src/routes/libreconvert.route.ts b/backend/src/routes/libreconvert.route.ts index ac4c8af..d28705e 100644 --- a/backend/src/routes/libreconvert.route.ts +++ b/backend/src/routes/libreconvert.route.ts @@ -5,26 +5,29 @@ import { promisify } from "util"; const libreConvertAsync = promisify(libre.convert); export async function libreConvert(app: FastifyInstance) { - app.post("/api/libre-convert", async (request: FastifyRequest, reply: FastifyReply) => { - try { - const data = await request.file(); - if (!data) { - return reply.status(400).send({ error: "No file uploaded!" }); - } + app.post( + "/api/libre-convert", + async (request: FastifyRequest, reply: FastifyReply) => { + try { + const data = await request.file(); + if (!data) { + return reply.status(400).send({ error: "No file uploaded!" }); + } - const ext = ".pdf"; - const format = ext.substring(1); - const fileBuffer = await data.toBuffer(); + const ext = ".pdf"; + const format = ext.substring(1); + const fileBuffer = await data.toBuffer(); - const pdfBuffer = await libreConvertAsync(fileBuffer, ext, undefined); + const pdfBuffer = await libreConvertAsync(fileBuffer, ext, undefined); - reply + reply .header("Content-Type", "application/" + format) .header("Content-Disposition", "attachment; filename=converted" + ext) .send(pdfBuffer); - } catch (error) { - console.error("Convert error:", error); - reply.status(500).send({ error: "Error while converting!" }); - } - }); + } catch (error) { + console.error("Convert error:", error); + reply.status(500).send({ error: "Error while converting!" }); + } + }, + ); } diff --git a/frontend/src/app/doc-to-pdf/layout.tsx b/frontend/src/app/doc-converter/layout.tsx similarity index 77% rename from frontend/src/app/doc-to-pdf/layout.tsx rename to frontend/src/app/doc-converter/layout.tsx index e4ce8fe..e813701 100644 --- a/frontend/src/app/doc-to-pdf/layout.tsx +++ b/frontend/src/app/doc-converter/layout.tsx @@ -1,7 +1,9 @@ +import React from "react"; import type { Metadata } from "next"; +import { toolLinks } from "@/constants"; export const metadata: Metadata = { - title: "doc-to-pdf", + title: toolLinks[0].title, description: "Converter for documents to pdf format!", }; diff --git a/frontend/src/app/doc-converter/page.tsx b/frontend/src/app/doc-converter/page.tsx new file mode 100644 index 0000000..c275ce7 --- /dev/null +++ b/frontend/src/app/doc-converter/page.tsx @@ -0,0 +1,206 @@ +"use client"; + +import React, { useState } from "react"; +import Navbar from "../../components/Navbar"; +import Footer from "../../components/Footer"; +import Button from "../../components/Button"; +import Link from "next/link"; +import { ChevronDown, ChevronUp } from "lucide-react"; + +export default function Home() { + const [file, setFile] = useState(null); + const [downloadUrl, setDownloadUrl] = useState(""); + const [loading, setLoading] = useState(false); + const [tableOpen, setTableOpen] = useState(false); + + const handleFileChange = (event: React.ChangeEvent) => { + if (event.target.files && event.target.files.length > 0) { + setFile(event.target.files[0]); + setDownloadUrl(""); + } + }; + + const convertToPdf = async () => { + if (!file) { + alert("No file selected"); + return; + } + + const formData = new FormData(); + formData.append("file", file); + + setLoading(true); + + try { + const response = await fetch( + process.env.backend_url + "/api/libre-convert", + { + method: "POST", + body: formData, + }, + ); + + if (!response.ok) { + return new Error(`Error: ${response.statusText}`); + } + + const blob = await response.blob(); + const url = window.URL.createObjectURL(blob); + setDownloadUrl(url); + } catch (error) { + console.error("Error while converting:", error); + alert("Error while converting"); + } finally { + setLoading(false); + } + }; + + return ( +
+ +
+

doc-converter

+ +
+ {downloadUrl ? ( + +
+ { + // TODO: Fix Table Head widths (fixed and the same as the max body width) + // TODO: Add hover animation to table head (scale-110, blue text, blue border) + // TODO: Fix general site height and add scrolling + // TODO: Add animated pop in for the table body + } +
+
setTableOpen(!tableOpen)} + > + + + + + + + +
📥 Input Format +

📤 Output Format

+ {tableOpen ? ( + + ) : ( + + )} +
+
+ + {tableOpen && ( + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
.doc (MS Word) + .pdf, .docx, .odt, .txt, .rtf, .html, .epub +
.docx (MS Word) + .pdf, .odt, .txt, .rtf, .html, .epub +
+ .odt (OpenDocument Text) + + .pdf, .doc, .docx, .txt, .rtf, .html, .epub +
+ .rtf (Rich Text Format) + + .pdf, .doc, .docx, .odt, .txt, .html +
.txt (Text) + .pdf, .doc, .docx, .odt, .txt, .html +
.html (Webseite) + .pdf, .doc, .docx, .odt, .rtf, .txt +
.epub (E-Book) + .pdf, .doc, .docx, .odt, .rtf, .txt +
.xls (MS Excel).pdf, .xlsx, .ods, .csv
.xlsx (MS Excel).pdf, .xls, .ods, .csv
+ .ods (OpenDocument Spreadsheet) + .pdf, .xls, .xlsx, .csv
+ .csv (Comma-Separated Values) + .pdf, .xls, .xlsx, .ods
.ppt (MS PowerPoint).pdf, .pptx, .odp
.pptx (MS PowerPoint).pdf, .ppt, .odp
+ .odp (OpenDocument Presentation) + .pdf, .ppt, .pptx
+ )} +
+
+
+
+ ); +} diff --git a/frontend/src/app/doc-to-pdf/page.tsx b/frontend/src/app/doc-to-pdf/page.tsx deleted file mode 100644 index 4af8bb0..0000000 --- a/frontend/src/app/doc-to-pdf/page.tsx +++ /dev/null @@ -1,111 +0,0 @@ -"use client"; - -import React, { useState } from "react"; -import Navbar from "../../components/Navbar"; -import Footer from "../../components/Footer"; -import Button from "../../components/Button"; -import Link from "next/link"; - -export default function Home() { - const [file, setFile] = useState(null); - const [downloadUrl, setDownloadUrl] = useState(""); - const [loading, setLoading] = useState(false); - - const handleFileChange = (event: React.ChangeEvent) => { - if (event.target.files && event.target.files.length > 0) { - setFile(event.target.files[0]); - setDownloadUrl(""); - } - }; - - const convertToPdf = async () => { - if (!file) { - alert("No file selected"); - return; - } - - const formData = new FormData(); - formData.append("file", file); - - setLoading(true); - - try { - const response = await fetch( - process.env.backend_url + "/api/libre-convert", - { - method: "POST", - body: formData, - }, - ); - - if (!response.ok) { - return new Error(`Error: ${response.statusText}`); - } - - const blob = await response.blob(); - const url = window.URL.createObjectURL(blob); - setDownloadUrl(url); - } catch (error) { - console.error("Error while converting:", error); - alert("Error while converting"); - } finally { - setLoading(false); - } - }; - - return ( -
- -
-

doc-to-pdf

- - - - - - - - - - - - - - - - - - - - - -
Input FormatOutput Format
docxpdf
docpdf
odtpdf
- -
- {downloadUrl ? ( - -
-
-
-
- ); -} diff --git a/frontend/src/app/rgb-to-hex/layout.tsx b/frontend/src/app/rgb-to-hex/layout.tsx new file mode 100644 index 0000000..04c7606 --- /dev/null +++ b/frontend/src/app/rgb-to-hex/layout.tsx @@ -0,0 +1,20 @@ +import React from "react"; +import type { Metadata } from "next"; +import { toolLinks } from "@/constants"; + +export const metadata: Metadata = { + title: toolLinks[2].title, + description: "Converter for rgb to hex color format!", +}; + +export default function RootLayout({ + children, + }: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + {children} + + ); +} \ No newline at end of file diff --git a/frontend/src/app/rgb-to-hex/page.tsx b/frontend/src/app/rgb-to-hex/page.tsx new file mode 100644 index 0000000..cdef0e4 --- /dev/null +++ b/frontend/src/app/rgb-to-hex/page.tsx @@ -0,0 +1,133 @@ +"use client"; + +import React, { useState } from "react"; +import Navbar from "../../components/Navbar"; +import Footer from "../../components/Footer"; +import Button from "../../components/Button"; + +export default function RgbToHex() { + + const [loading, setLoading] = useState(false); + const [hex, setHex] = useState(""); + + const convertToHex = async () => { + + setLoading(true); + + const red = (document.getElementById("red") as HTMLInputElement).value; + const green = (document.getElementById("green") as HTMLInputElement).value; + const blue = (document.getElementById("blue") as HTMLInputElement).value; + + try { + const response = await fetch( + process.env.backend_url + "/api/color-convert", + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ red: red, green: green, blue: blue }), + }, + ); + if (!response.ok) { + return new Error(`Error: ${response.statusText}`); + } + const hex: string = await response.text(); + setHex(hex); + + } catch (error) { + console.error("Error while converting:", error); + alert("Error while converting"); + } finally { + setLoading(false); + } + }; + + const clearInAndOutput = () => { + setHex(""); + const red = document.getElementById("red") as HTMLInputElement; + const green = document.getElementById("green") as HTMLInputElement; + const blue = document.getElementById("blue") as HTMLInputElement; + red.value = ""; + green.value = ""; + blue.value = ""; + }; + + const checkInput = (event: React.ChangeEvent) => { + const color = (document.getElementById(event.target.id) as HTMLInputElement); + const colorValue = +color.value; + + if (colorValue < 0 || colorValue > 255 ) { + alert("Invalid input. Please enter a number between 0 and 255."); + } + if (colorValue < 0) { + color.value = "0"; + } else if (colorValue > 255) { + color.value = "255"; + } + } + + return ( +
+ +
+

rgb-to-hex

+
+ + + + + + +
+
+
+
+ {hex} +
+
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/constants/index.ts b/frontend/src/constants/index.ts index 53b8a58..62ea718 100644 --- a/frontend/src/constants/index.ts +++ b/frontend/src/constants/index.ts @@ -1,19 +1,19 @@ export const toolLinks = [ { - title: "doc-to-pdf", - link: "/doc-to-pdf", + title: "doc-converter", + link: "/doc-converter", }, { - title: "img-to-png", - link: "/img-to-png", + title: "img-converter", + link: "/img-converter", }, { title: "rgb-to-hex", link: "/rgb-to-hex", }, { - title: "video-to-gif", - link: "/video-to-gif", + title: "data-visualizer", + link: "/data-visualizer", }, { title: "qr-code-generator",