diff --git a/package-lock.json b/package-lock.json index 46d250d..6d0e80a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,7 @@ "github-markdown-css": "^5.4.0", "jsonpath-plus": "^7.2.0", "jwt-decode": "^4.0.0", + "lorem-ipsum": "^2.0.8", "marked": "^10.0.0", "next": "13.5.6", "react": "^18", @@ -3823,6 +3824,31 @@ "loose-envify": "cli.js" } }, + "node_modules/lorem-ipsum": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/lorem-ipsum/-/lorem-ipsum-2.0.8.tgz", + "integrity": "sha512-5RIwHuCb979RASgCJH0VKERn9cQo/+NcAi2BMe9ddj+gp7hujl6BI+qdOG4nVsLDpwWEJwTVYXNKP6BGgbcoGA==", + "license": "ISC", + "dependencies": { + "commander": "^9.3.0" + }, + "bin": { + "lorem-ipsum": "dist/bin/lorem-ipsum.bin.js" + }, + "engines": { + "node": ">= 8.x", + "npm": ">= 5.x" + } + }, + "node_modules/lorem-ipsum/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "license": "MIT", + "engines": { + "node": "^12.20.0 || >=14" + } + }, "node_modules/lower-case": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", diff --git a/package.json b/package.json index 2f853bc..88b2c1e 100644 --- a/package.json +++ b/package.json @@ -21,6 +21,7 @@ "github-markdown-css": "^5.4.0", "jsonpath-plus": "^7.2.0", "jwt-decode": "^4.0.0", + "lorem-ipsum": "^2.0.8", "marked": "^10.0.0", "next": "13.5.6", "react": "^18", diff --git a/src/app/components/common/ToolList.tsx b/src/app/components/common/ToolList.tsx index 6aef38d..21a6b1e 100644 --- a/src/app/components/common/ToolList.tsx +++ b/src/app/components/common/ToolList.tsx @@ -68,6 +68,10 @@ export const toolList: ToolOption[] = [ name: "Line Sort And Dedupe", path: "/tools/line-sort-and-dedupe", }, + { + name: "Lorem Ipsum Generator", + path: "/tools/lorem-ipsum-generator", + }, { name: "Regex Checker", path: "/tools/regex-checker", diff --git a/src/app/tools/lorem-ipsum-generator/LoremIpsumGeneratorClientComponent.tsx b/src/app/tools/lorem-ipsum-generator/LoremIpsumGeneratorClientComponent.tsx new file mode 100644 index 0000000..b02c027 --- /dev/null +++ b/src/app/tools/lorem-ipsum-generator/LoremIpsumGeneratorClientComponent.tsx @@ -0,0 +1,108 @@ +"use client"; +import React, { useState, useEffect, useMemo } from "react"; +import { LoremIpsum } from "lorem-ipsum"; +import useDebounce from "@/app/hooks/useDebounce"; +import Selector from "@/app/components/common/Selector"; +import ReadOnlyTextArea from "@/app/components/common/ReadOnlyTextArea"; +import { User } from "@clerk/backend"; +import { saveHistory } from "@/utils/clientUtils"; +import { ToolType } from "@prisma/client"; + + +const loremIpsum = new LoremIpsum({ + sentencesPerParagraph: { + max: 8, + min: 4 + }, + wordsPerSentence: { + max: 16, + min: 4 + } +}); + +function generateLorem(type: "paragraphs" | "words" | "characters", quantity: number): string { + if (quantity < 1) return ""; + if (type === "paragraphs") { + // Ensure paragraphs are separated by double newlines for clear distinction + return loremIpsum.generateParagraphs(quantity).split('\n').join('\n\n'); + } + if (type === "words") { + return loremIpsum.generateWords(quantity); + } + // characters + // Generate more words than needed, then slice to the required character count + let text = ""; + while (text.length < quantity) { + text += loremIpsum.generateWords(Math.max(10, quantity / 5)) + " "; + } + return text.slice(0, quantity); +} + +const options = [ + { label: "Paragraphs", value: "paragraphs" }, + { label: "Words", value: "words" }, + { label: "Characters", value: "characters" }, +]; + + +export default function LoremIpsumGeneratorClientComponent(props: { user: User | null; isProUser: boolean }) { + const { user, isProUser } = props; + const [type, setType] = useState<"paragraphs" | "words" | "characters">("paragraphs"); + const [quantity, setQuantity] = useState(3); + const [error, setError] = useState(null); + + const lorem = useMemo(() => generateLorem(type, quantity), [type, quantity]); + const debouncedLorem = useDebounce(lorem, 1000); + + useEffect(() => { + if (isProUser && user && debouncedLorem) { + void saveHistory({ + user, + isProUser, + toolType: ToolType.LoremIpsumGenerator, + onError: setError, + metadata: { + type, + quantity, + debouncedLorem, + }, + }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [debouncedLorem, isProUser, user]); + + + return ( +
+

Input

+
+
+ + setQuantity(Math.max(1, Number(e.target.value))) } + className="border rounded px-2 py-1 w-28 text-gray-900" + aria-label="Quantity" + /> +
+
+ + setType(val.value as "paragraphs" | "words" | "characters")} + /> +
+
+

Output

+
+ +
+ {error &&
{error}
} +
+ ); +} \ No newline at end of file diff --git a/src/app/tools/lorem-ipsum-generator/page.tsx b/src/app/tools/lorem-ipsum-generator/page.tsx new file mode 100644 index 0000000..77c4834 --- /dev/null +++ b/src/app/tools/lorem-ipsum-generator/page.tsx @@ -0,0 +1,8 @@ +import { getUserAndSubscriptionState } from '@/actions/user'; +import LoremIpsumGeneratorClientComponent from './LoremIpsumGeneratorClientComponent'; + +const LoremIpsumGenerator = async () => { + const { user, isProUser } = await getUserAndSubscriptionState(); + return ; +}; +export default LoremIpsumGenerator;