From 293277ed3201e2dd4a12129929bbe761a5c5a5f6 Mon Sep 17 00:00:00 2001 From: KMKoushik Date: Sat, 13 Apr 2024 07:34:13 +1000 Subject: [PATCH] Update domain page UI --- .../(dashboard)/domains/[domainId]/page.tsx | 3 +- .../domains/[domainId]/send-test-mail.tsx | 125 +++++++++++- apps/web/src/server/api/routers/domain.ts | 34 ++++ apps/web/src/server/service/email-service.ts | 14 +- packages/ui/code-theme.ts | 148 ++++++++++++++ packages/ui/package.json | 3 + packages/ui/src/code.tsx | 97 ++++++++++ packages/ui/src/tabs.tsx | 55 ++++++ pnpm-lock.yaml | 181 ++++++++++++++++++ 9 files changed, 643 insertions(+), 17 deletions(-) create mode 100644 packages/ui/code-theme.ts create mode 100644 packages/ui/src/code.tsx create mode 100644 packages/ui/src/tabs.tsx diff --git a/apps/web/src/app/(dashboard)/domains/[domainId]/page.tsx b/apps/web/src/app/(dashboard)/domains/[domainId]/page.tsx index 4340026..2cc6506 100644 --- a/apps/web/src/app/(dashboard)/domains/[domainId]/page.tsx +++ b/apps/web/src/app/(dashboard)/domains/[domainId]/page.tsx @@ -290,8 +290,7 @@ const DomainSettings: React.FC<{ domain: Domain }> = ({ domain }) => {

Danger

- Deleting a domain will remove all of its DNS records and stop sending - emails. + Deleting a domain will stop sending emails with this domain.

diff --git a/apps/web/src/app/(dashboard)/domains/[domainId]/send-test-mail.tsx b/apps/web/src/app/(dashboard)/domains/[domainId]/send-test-mail.tsx index 24e6006..4acde30 100644 --- a/apps/web/src/app/(dashboard)/domains/[domainId]/send-test-mail.tsx +++ b/apps/web/src/app/(dashboard)/domains/[domainId]/send-test-mail.tsx @@ -1,8 +1,6 @@ "use client"; import { Button } from "@unsend/ui/src/button"; -import { Input } from "@unsend/ui/src/input"; -import { Label } from "@unsend/ui/src/label"; import { Dialog, DialogContent, @@ -18,27 +16,120 @@ import { Domain } from "@prisma/client"; import { useRouter } from "next/navigation"; import { toast } from "@unsend/ui/src/toaster"; import { Send, SendHorizonal } from "lucide-react"; +import { Code } from "@unsend/ui/src/code"; + +const jsCode = `const requestOptions = { + method: "POST", + headers: { + "Accept": "application/json", + "Content-Type": "application/json", + "Authorization": "Bearer us_ad9a79256e366399c747cbf0b38eca3c472e8a2e" + }, + body: JSON.stringify({ + "to": "koushikmohan1996@gmail.com", + "from": "hello@test.splitpro.app", + "subject": "Test mail", + "html": "

Hello this is a test mail

" + }), + redirect: "follow" +}; + +fetch("http://localhost:3000/api/v1/emails", requestOptions) + .then(response => response.text()) + .then(result => console.log(result)) + .catch(error => console.error(error)); +`; + +const pythonCode = `import requests +import json + +url = "http://localhost:3000/api/v1/emails" + +payload = json.dumps({ + "to": "koushikmohan1996@gmail.com", + "from": "hello@test.splitpro.app", + "subject": "Test mail", + "html": "

Hello this is a test mail

" +}) +headers = { + 'Accept': 'application/json', + 'Content-Type': 'application/json', + 'Authorization': 'Bearer us_ad9a79256e366399c747cbf0b38eca3c472e8a2e' +} + +response = requests.request("POST", url, headers=headers, data=payload) + +print(response.text)`; + +const rubyCode = `require 'uri' +require 'net/http' +require 'json' + +url = URI("http://localhost:3000/api/v1/emails") + +http = Net::HTTP.new(url.host, url.port) +request = Net::HTTP::Post.new(url) +request["Accept"] = 'application/json' +request["Content-Type"] = 'application/json' +request["Authorization"] = 'Bearer us_ad9a79256e366399c747cbf0b38eca3c472e8a2e' +request.body = JSON.dump({ + "to" => "koushikmohan1996@gmail.com", + "from" => "hello@test.splitpro.app", + "subject" => "Test mail", + "html" => "

Hello this is a test mail

" +}) + +response = http.request(request) +puts response.read_body`; + +const phpCode = `$url = "http://localhost:3000/api/v1/emails"; + +$payload = json_encode(array( + "to" => "koushikmohan1996@gmail.com", + "from" => "hello@test.splitpro.app", + "subject" => "Test mail", + "html" => "

Hello this is a test mail

" +)); + +$headers = array( + "Accept: application/json", + "Content-Type: application/json", + "Authorization: Bearer us_ad9a79256e366399c747cbf0b38eca3c472e8a2e" +); + +$ch = curl_init($url); +curl_setopt($ch, CURLOPT_POST, true); +curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); +curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); +curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + +$response = curl_exec($ch); +if (curl_errno($ch)) { + echo 'Error:' . curl_error($ch); +} else { + echo $response; +}`; export const SendTestMail: React.FC<{ domain: Domain }> = ({ domain }) => { const [open, setOpen] = useState(false); const [domainName, setDomainName] = useState(""); - const deleteDomainMutation = api.domain.deleteDomain.useMutation(); + const sendTestEmailFromDomainMutation = + api.domain.sendTestEmailFromDomain.useMutation(); const utils = api.useUtils(); const router = useRouter(); - function handleSave() { - deleteDomainMutation.mutate( + function handleSendTestEmail() { + sendTestEmailFromDomainMutation.mutate( { id: domain.id, }, { onSuccess: () => { utils.domain.domains.invalidate(); + toast.success(`Test email sent`); setOpen(false); - toast.success(`Domain ${domain.name} deleted`); - router.replace("/domains"); }, } ); @@ -55,10 +146,28 @@ export const SendTestMail: React.FC<{ domain: Domain }> = ({ domain }) => { Send test email - + Send test email + +
+ +
); diff --git a/apps/web/src/server/api/routers/domain.ts b/apps/web/src/server/api/routers/domain.ts index 9abdd28..81c9a7c 100644 --- a/apps/web/src/server/api/routers/domain.ts +++ b/apps/web/src/server/api/routers/domain.ts @@ -13,6 +13,7 @@ import { getDomain, updateDomain, } from "~/server/service/domain-service"; +import { sendEmail } from "~/server/service/email-service"; export const domainRouter = createTRPCRouter({ createDomain: teamProcedure @@ -61,4 +62,37 @@ export const domainRouter = createTRPCRouter({ await deleteDomain(input.id); return { success: true }; }), + + sendTestEmailFromDomain: teamProcedure + .input(z.object({ id: z.number() })) + .mutation( + async ({ + ctx: { + session: { user }, + team, + }, + input, + }) => { + const domain = await db.domain.findFirst({ + where: { id: input.id, teamId: team.id }, + }); + + if (!domain) { + throw new Error("Domain not found"); + } + + if (!user.email) { + throw new Error("User email not found"); + } + + return sendEmail({ + teamId: team.id, + to: user.email, + from: `hello@${domain.name}`, + subject: "Test mail", + text: "Hello this is a test mail", + html: "

Hello this is a test mail

", + }); + } + ), }); diff --git a/apps/web/src/server/service/email-service.ts b/apps/web/src/server/service/email-service.ts index 489cbb5..267097c 100644 --- a/apps/web/src/server/service/email-service.ts +++ b/apps/web/src/server/service/email-service.ts @@ -8,16 +8,16 @@ export async function sendEmail( ) { const { to, from, subject, text, html, teamId } = emailContent; - const domains = await db.domain.findMany({ where: { teamId } }); - const fromDomain = from.split("@")[1]; - if (!fromDomain) { - throw new Error("From email is not valid"); - } - const domain = domains.find((domain) => domain.name === fromDomain); + const domain = await db.domain.findFirst({ + where: { teamId, name: fromDomain }, + }); + if (!domain) { - throw new Error("Domain not found. Add domain to unsend first"); + throw new Error( + "Domain of from email is wrong. Use the email verified by unsend" + ); } if (domain.status !== "SUCCESS") { diff --git a/packages/ui/code-theme.ts b/packages/ui/code-theme.ts new file mode 100644 index 0000000..acc9bf7 --- /dev/null +++ b/packages/ui/code-theme.ts @@ -0,0 +1,148 @@ +const codeTheme: { + [key: string]: React.CSSProperties; +} = { + hljs: { + display: "block", + overflowX: "auto", + padding: "0.5em", + background: "#070808", + color: "#d6deeb", + }, + "hljs-keyword": { + color: "#c792ea", + fontStyle: "italic", + }, + "hljs-built_in": { + color: "#addb67", + fontStyle: "italic", + }, + "hljs-type": { + color: "#82aaff", + }, + "hljs-literal": { + color: "#ff5874", + }, + "hljs-number": { + color: "#F78C6C", + }, + "hljs-regexp": { + color: "#5ca7e4", + }, + "hljs-string": { + color: "#ecc48d", + }, + "hljs-subst": { + color: "#d3423e", + }, + "hljs-symbol": { + color: "#82aaff", + }, + "hljs-class": { + color: "#ffcb8b", + }, + "hljs-function": { + color: "#82AAFF", + }, + "hljs-title": { + color: "#DCDCAA", + fontStyle: "italic", + }, + "hljs-params": { + color: "#7fdbca", + }, + "hljs-comment": { + color: "#637777", + fontStyle: "italic", + }, + "hljs-doctag": { + color: "#7fdbca", + }, + "hljs-meta": { + color: "#82aaff", + }, + "hljs-meta-keyword": { + color: "#82aaff", + }, + "hljs-meta-string": { + color: "#ecc48d", + }, + "hljs-section": { + color: "#82b1ff", + }, + "hljs-tag": { + color: "#7fdbca", + }, + "hljs-name": { + color: "#7fdbca", + }, + "hljs-builtin-name": { + color: "#7fdbca", + }, + "hljs-attr": { + color: "#7fdbca", + }, + "hljs-attribute": { + color: "#80cbc4", + }, + "hljs-variable": { + color: "#addb67", + }, + "hljs-bullet": { + color: "#d9f5dd", + }, + "hljs-code": { + color: "#80CBC4", + }, + "hljs-emphasis": { + color: "#c792ea", + fontStyle: "italic", + }, + "hljs-strong": { + color: "#addb67", + fontWeight: "bold", + }, + "hljs-formula": { + color: "#c792ea", + }, + "hljs-link": { + color: "#ff869a", + }, + "hljs-quote": { + color: "#697098", + fontStyle: "italic", + }, + "hljs-selector-tag": { + color: "#ff6363", + }, + "hljs-selector-id": { + color: "#fad430", + }, + "hljs-selector-class": { + color: "#addb67", + fontStyle: "italic", + }, + "hljs-selector-attr": { + color: "#c792ea", + fontStyle: "italic", + }, + "hljs-selector-pseudo": { + color: "#c792ea", + fontStyle: "italic", + }, + "hljs-template-tag": { + color: "#c792ea", + }, + "hljs-template-variable": { + color: "#addb67", + }, + "hljs-addition": { + color: "#addb67ff", + fontStyle: "italic", + }, + "hljs-deletion": { + color: "#EF535090", + fontStyle: "italic", + }, +}; + +export default codeTheme; diff --git a/packages/ui/package.json b/packages/ui/package.json index 89d9776..1a1169d 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -15,6 +15,7 @@ "@types/node": "^20.11.24", "@types/react": "^18.2.61", "@types/react-dom": "^18.2.19", + "@types/react-syntax-highlighter": "^15.5.11", "@unsend/eslint-config": "workspace:*", "@unsend/tailwind-config": "workspace:*", "@unsend/typescript-config": "workspace:*", @@ -32,12 +33,14 @@ "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-switch": "^1.0.3", + "@radix-ui/react-tabs": "^1.0.4", "add": "^2.0.6", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "lucide-react": "^0.359.0", "next-themes": "^0.3.0", "pnpm": "^8.15.5", + "react-syntax-highlighter": "^15.5.0", "sonner": "^1.4.41", "tailwind-merge": "^2.2.2", "tailwindcss-animate": "^1.0.7" diff --git a/packages/ui/src/code.tsx b/packages/ui/src/code.tsx new file mode 100644 index 0000000..5c282f4 --- /dev/null +++ b/packages/ui/src/code.tsx @@ -0,0 +1,97 @@ +import { Light as SyntaxHighlighter } from "react-syntax-highlighter"; +import js from "react-syntax-highlighter/dist/esm/languages/hljs/javascript"; +import ruby from "react-syntax-highlighter/dist/esm/languages/hljs/ruby"; +import php from "react-syntax-highlighter/dist/esm/languages/hljs/php"; +import python from "react-syntax-highlighter/dist/esm/languages/hljs/python"; +// import { nightOwl } from "react-syntax-highlighter/dist/esm/styles/hljs"; +import codeTheme from "../code-theme"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "./tabs"; +import { Button } from "./button"; +import { ClipboardCopy, Check } from "lucide-react"; +import { useState } from "react"; + +type Language = "js" | "ruby" | "php" | "python"; + +type CodeProps = { + codeBlocks: { + language: Language; + code: string; + display?: string; + }[]; +}; + +SyntaxHighlighter.registerLanguage("js", js); +SyntaxHighlighter.registerLanguage("ruby", ruby); +SyntaxHighlighter.registerLanguage("php", php); +SyntaxHighlighter.registerLanguage("python", python); + +export const Code: React.FC = ({ codeBlocks }) => { + const [selectedTab, setSelectedTab] = useState( + codeBlocks[0]?.language ?? "js" + ); + const [isCopied, setIsCopied] = useState(false); + + const copyToClipboard = async (code: string) => { + try { + await navigator.clipboard.writeText(code); + setIsCopied(true); + setTimeout(() => setIsCopied(false), 2000); // Reset the icon back to clipboard after 2 seconds + } catch (err) { + alert("Failed to copy code"); + } + }; + + return ( +
+ setSelectedTab(val as Language)} + > +
+ +
+ {codeBlocks.map((block) => ( + + {block.language} + + ))} +
+
+ +
+ {codeBlocks.map((block) => ( + +
+ + {block.code} + +
+
+ ))} +
+
+ ); +}; diff --git a/packages/ui/src/tabs.tsx b/packages/ui/src/tabs.tsx new file mode 100644 index 0000000..35e1ef8 --- /dev/null +++ b/packages/ui/src/tabs.tsx @@ -0,0 +1,55 @@ +"use client"; + +import * as React from "react"; +import * as TabsPrimitive from "@radix-ui/react-tabs"; + +import { cn } from "../lib/utils"; + +const Tabs = TabsPrimitive.Root; + +const TabsList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +TabsList.displayName = TabsPrimitive.List.displayName; + +const TabsTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +TabsTrigger.displayName = TabsPrimitive.Trigger.displayName; + +const TabsContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +TabsContent.displayName = TabsPrimitive.Content.displayName; + +export { Tabs, TabsList, TabsTrigger, TabsContent }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 931d84a..84dd738 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -278,6 +278,9 @@ importers: '@radix-ui/react-switch': specifier: ^1.0.3 version: 1.0.3(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-tabs': + specifier: ^1.0.4 + version: 1.0.4(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) add: specifier: ^2.0.6 version: 2.0.6 @@ -296,6 +299,9 @@ importers: pnpm: specifier: ^8.15.5 version: 8.15.5 + react-syntax-highlighter: + specifier: ^15.5.0 + version: 15.5.0(react@18.2.0) sonner: specifier: ^1.4.41 version: 1.4.41(react-dom@18.2.0)(react@18.2.0) @@ -318,6 +324,9 @@ importers: '@types/react-dom': specifier: ^18.2.19 version: 18.2.22 + '@types/react-syntax-highlighter': + specifier: ^15.5.11 + version: 15.5.11 '@unsend/eslint-config': specifier: workspace:* version: link:../eslint-config @@ -2297,6 +2306,34 @@ packages: react-dom: 18.2.0(react@18.2.0) dev: false + /@radix-ui/react-tabs@1.0.4(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-egZfYY/+wRNCflXNHx+dePvnz9FbmssDTJBtgRfDY7e8SE5oIo3Py2eCB1ckAbh1Q7cQ/6yJZThJ++sgbxibog==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + dependencies: + '@babel/runtime': 7.24.0 + '@radix-ui/primitive': 1.0.1 + '@radix-ui/react-context': 1.0.1(@types/react@18.2.66)(react@18.2.0) + '@radix-ui/react-direction': 1.0.1(@types/react@18.2.66)(react@18.2.0) + '@radix-ui/react-id': 1.0.1(@types/react@18.2.66)(react@18.2.0) + '@radix-ui/react-presence': 1.0.1(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-primitive': 1.0.3(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-roving-focus': 1.0.4(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@18.2.66)(react@18.2.0) + '@types/react': 18.2.66 + '@types/react-dom': 18.2.22 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + dev: false + /@radix-ui/react-use-callback-ref@1.0.1(@types/react@18.2.66)(react@18.2.0): resolution: {integrity: sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==} peerDependencies: @@ -2896,6 +2933,12 @@ packages: resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} dev: true + /@types/hast@2.3.10: + resolution: {integrity: sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==} + dependencies: + '@types/unist': 2.0.10 + dev: false + /@types/json-schema@7.0.15: resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} dev: true @@ -2922,6 +2965,12 @@ packages: dependencies: '@types/react': 18.2.66 + /@types/react-syntax-highlighter@15.5.11: + resolution: {integrity: sha512-ZqIJl+Pg8kD+47kxUjvrlElrraSUrYa4h0dauY/U/FTUuprSCqvUj+9PNQNQzVc6AJgIWUUxn87/gqsMHNbRjw==} + dependencies: + '@types/react': 18.2.66 + dev: true + /@types/react@18.2.66: resolution: {integrity: sha512-OYTmMI4UigXeFMF/j4uv0lBBEbongSgptPrHBxqME44h9+yNov+oL6Z3ocJKo0WyXR84sQUNeyIp9MRfckvZpg==} dependencies: @@ -2936,6 +2985,10 @@ packages: resolution: {integrity: sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==} dev: true + /@types/unist@2.0.10: + resolution: {integrity: sha512-IfYcSBWE3hLpBg8+X2SEa8LVkJdJEkT2Ese2aaLs3ptGdVtABxndrMaxuFlQ1qdFf9Q5rDvDpxI3WwgvKFAsQA==} + dev: false + /@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0)(eslint@8.57.0)(typescript@5.4.2): resolution: {integrity: sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==} engines: {node: ^16.0.0 || >=18.0.0} @@ -3657,6 +3710,18 @@ packages: supports-color: 7.2.0 dev: true + /character-entities-legacy@1.1.4: + resolution: {integrity: sha512-3Xnr+7ZFS1uxeiUDvV02wQ+QDbc55o97tIV5zHScSPJpcLm/r0DFPcoY3tYRp+VZukxuMeKgXYmsXQHO05zQeA==} + dev: false + + /character-entities@1.2.4: + resolution: {integrity: sha512-iBMyeEHxfVnIakwOuDXpVkc54HijNgCyQB2w0VfGQThle6NXn50zU6V/u+LDhxHcDUPojn6Kpga3PTAD8W1bQw==} + dev: false + + /character-reference-invalid@1.1.4: + resolution: {integrity: sha512-mKKUkUbhPpQlCOfIuZkvSEgktjPFIsZKRRbC6KWVEMvlzblj3i3asQv5ODsrwt0N3pHAEvjP8KTQPHkp0+6jOg==} + dev: false + /chokidar@3.6.0: resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} engines: {node: '>= 8.10.0'} @@ -3722,6 +3787,10 @@ packages: /color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + /comma-separated-tokens@1.0.8: + resolution: {integrity: sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw==} + dev: false + /commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} @@ -4668,6 +4737,12 @@ packages: dependencies: reusify: 1.0.4 + /fault@1.0.4: + resolution: {integrity: sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==} + dependencies: + format: 0.2.2 + dev: false + /file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -4723,6 +4798,11 @@ packages: cross-spawn: 7.0.3 signal-exit: 4.1.0 + /format@0.2.2: + resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} + engines: {node: '>=0.4.x'} + dev: false + /fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} dev: true @@ -4951,6 +5031,24 @@ packages: dependencies: function-bind: 1.1.2 + /hast-util-parse-selector@2.2.5: + resolution: {integrity: sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==} + dev: false + + /hastscript@6.0.0: + resolution: {integrity: sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==} + dependencies: + '@types/hast': 2.3.10 + comma-separated-tokens: 1.0.8 + hast-util-parse-selector: 2.2.5 + property-information: 5.6.0 + space-separated-tokens: 1.1.5 + dev: false + + /highlight.js@10.7.3: + resolution: {integrity: sha512-tzcUFauisWKNHaRkN4Wjl/ZA07gENAjFl3J/c480dprkGTg5EQstgaNFqBfUqCq54kZRIEcreTsAgF/m2quD7A==} + dev: false + /hono@4.2.2: resolution: {integrity: sha512-mDmjBHF6uBNN3TASdAbDCFsN9FLbrlgXyFZkhLEkU7hUgk0+T9hcsUrL/nho4qV+Xk0RDHx7gop4Q1gelZZVRw==} engines: {node: '>=16.0.0'} @@ -5014,6 +5112,17 @@ packages: loose-envify: 1.4.0 dev: false + /is-alphabetical@1.0.4: + resolution: {integrity: sha512-DwzsA04LQ10FHTZuL0/grVDk4rFoVH1pjAToYwBrHSxcrBIGQuXrQMtD5U1b0U2XVgKZCTLLP8u2Qxqhy3l2Vg==} + dev: false + + /is-alphanumerical@1.0.4: + resolution: {integrity: sha512-UzoZUr+XfVz3t3v4KyGEniVL9BDRoQtY7tOyrRybkVNjDFWyo1yhXNGrrBTQxp3ib9BLAWs7k2YKBQsFRkZG9A==} + dependencies: + is-alphabetical: 1.0.4 + is-decimal: 1.0.4 + dev: false + /is-array-buffer@3.0.4: resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} engines: {node: '>= 0.4'} @@ -5077,6 +5186,10 @@ packages: has-tostringtag: 1.0.2 dev: true + /is-decimal@1.0.4: + resolution: {integrity: sha512-RGdriMmQQvZ2aqaQq3awNA6dCGtKpiDFcOzrTWrDAT2MiWrKQVPmxLGHl7Y2nNu6led0kEyoX0enY0qXYsv9zw==} + dev: false + /is-extglob@2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -5104,6 +5217,10 @@ packages: dependencies: is-extglob: 2.1.1 + /is-hexadecimal@1.0.4: + resolution: {integrity: sha512-gyPJuv83bHMpocVYoqof5VDiZveEoGoFL8m3BXNb2VW8Xs+rz9kqO8LOQ5DH6EsuvilT1ApazU0pyl+ytbPtlw==} + dev: false + /is-map@2.0.3: resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} engines: {node: '>= 0.4'} @@ -5371,6 +5488,13 @@ packages: dependencies: js-tokens: 4.0.0 + /lowlight@1.20.0: + resolution: {integrity: sha512-8Ktj+prEb1RoCPkEOrPMYUN/nCggB7qAWe3a7OpMjWQkh3l2RD5wKRQ+o8Q8YuI9RG/xs95waaI/E6ym/7NsTw==} + dependencies: + fault: 1.0.4 + highlight.js: 10.7.3 + dev: false + /lru-cache@10.2.0: resolution: {integrity: sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==} engines: {node: 14 || >=16.14} @@ -5752,6 +5876,17 @@ packages: callsites: 3.1.0 dev: true + /parse-entities@2.0.0: + resolution: {integrity: sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==} + dependencies: + character-entities: 1.2.4 + character-entities-legacy: 1.1.4 + character-reference-invalid: 1.1.4 + is-alphanumerical: 1.0.4 + is-decimal: 1.0.4 + is-hexadecimal: 1.0.4 + dev: false + /parse-json@5.2.0: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} @@ -6021,6 +6156,16 @@ packages: '@prisma/engines': 5.11.0 dev: false + /prismjs@1.27.0: + resolution: {integrity: sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA==} + engines: {node: '>=6'} + dev: false + + /prismjs@1.29.0: + resolution: {integrity: sha512-Kx/1w86q/epKcmte75LNrEoT+lX8pBpavuAbvJWRXar7Hz8jrtF+e3vY751p0R8H9HdArwaCTNDDzHg/ScJK1Q==} + engines: {node: '>=6'} + dev: false + /prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} dependencies: @@ -6029,6 +6174,12 @@ packages: react-is: 16.13.1 dev: true + /property-information@5.6.0: + resolution: {integrity: sha512-YUHSPk+A30YPv+0Qf8i9Mbfe/C0hdPXk1s1jPVToV8pk8BQtpw10ct89Eo7OWkutrwqvT0eicAxlOg3dOAu8JA==} + dependencies: + xtend: 4.0.2 + dev: false + /punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -6103,6 +6254,19 @@ packages: tslib: 2.6.2 dev: false + /react-syntax-highlighter@15.5.0(react@18.2.0): + resolution: {integrity: sha512-+zq2myprEnQmH5yw6Gqc8lD55QHnpKaU8TOcFeC/Lg/MQSs8UknEA0JC4nTZGFAXC2J2Hyj/ijJ7NlabyPi2gg==} + peerDependencies: + react: '>= 0.14.0' + dependencies: + '@babel/runtime': 7.24.0 + highlight.js: 10.7.3 + lowlight: 1.20.0 + prismjs: 1.29.0 + react: 18.2.0 + refractor: 3.6.0 + dev: false + /react@18.2.0: resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} engines: {node: '>=0.10.0'} @@ -6152,6 +6316,14 @@ packages: which-builtin-type: 1.1.3 dev: true + /refractor@3.6.0: + resolution: {integrity: sha512-MY9W41IOWxxk31o+YvFCNyNzdkc9M20NoZK5vq6jkv4I/uh2zkWcfudj0Q1fovjUQJrNewS9NMzeTtqPf+n5EA==} + dependencies: + hastscript: 6.0.0 + parse-entities: 2.0.0 + prismjs: 1.27.0 + dev: false + /regenerator-runtime@0.14.1: resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} @@ -6365,6 +6537,10 @@ packages: resolution: {integrity: sha512-9vC2SfsJzlej6MAaMPLu8HiBSHGdRAJ9hVFYN1ibZoNkeanmDmLUcIrj6G9DGL7XMJ54AKg/G75akXl1/izTOw==} engines: {node: '>=0.10.0'} + /space-separated-tokens@1.1.5: + resolution: {integrity: sha512-q/JSVd1Lptzhf5bkYm4ob4iWPjx0KiRe3sRFBNrVqbJkFaBm5vbbowy1mymoPNLRa52+oadOhJ+K49wsSeSjTA==} + dev: false + /spdx-correct@3.2.0: resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} dependencies: @@ -6949,6 +7125,11 @@ packages: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} dev: true + /xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + dev: false + /yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} dev: true