diff --git a/apps/web/next-env.d.ts b/apps/web/next-env.d.ts index 4f11a03..fd36f94 100644 --- a/apps/web/next-env.d.ts +++ b/apps/web/next-env.d.ts @@ -1,5 +1,6 @@ /// /// +/// // NOTE: This file should not be edited // see https://nextjs.org/docs/basic-features/typescript for more information. diff --git a/apps/web/package.json b/apps/web/package.json index c0f78c4..6580dd1 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -7,20 +7,32 @@ "dev": "next dev", "build": "next build", "start": "next start", - "lint": "eslint . --max-warnings 0" + "lint": "eslint . --max-warnings 0", + "db:post-install": "prisma generate", + "db:generate": "prisma generate", + "db:push": "prisma db push --skip-generate", + "db:migrate-dev": "prisma migrate dev", + "db:migrate-deploy": "prisma migrate deploy", + "db:studio": "prisma studio" }, "dependencies": { "@auth/prisma-adapter": "^1.4.0", - "@unsend/db": "workspace:*", - "@unsend/ui": "workspace:*", + "@aws-sdk/client-sesv2": "^3.535.0", + "@aws-sdk/client-sns": "^3.540.0", + "@prisma/client": "^5.11.0", "@t3-oss/env-nextjs": "^0.9.2", "@tanstack/react-query": "^5.25.0", "@trpc/client": "next", "@trpc/next": "next", "@trpc/react-query": "next", "@trpc/server": "next", + "@unsend/ui": "workspace:*", + "install": "^0.13.0", + "lucide-react": "^0.359.0", "next": "^14.1.3", "next-auth": "^4.24.6", + "pnpm": "^8.15.5", + "prisma": "^5.11.0", "react": "18.2.0", "react-dom": "18.2.0", "server-only": "^0.0.1", diff --git a/apps/web/prisma/schema.prisma b/apps/web/prisma/schema.prisma new file mode 100644 index 0000000..8f1fd3e --- /dev/null +++ b/apps/web/prisma/schema.prisma @@ -0,0 +1,159 @@ +// This is your Prisma schema file, +// learn more about it in the docs: https://pris.ly/d/prisma-schema + +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + // NOTE: When using mysql or sqlserver, uncomment the @db.Text annotations in model Account below + // Further reading: + // https://next-auth.js.org/adapters/prisma#create-the-prisma-schema + // https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#string + url = env("DATABASE_URL") +} + +model AppSetting { + key String @id + value String +} + +// Necessary for Next auth +model Account { + id String @id @default(cuid()) + userId Int + type String + provider String + providerAccountId String + refresh_token String? // @db.Text + access_token String? // @db.Text + refresh_token_expires_in Int? + expires_at Int? + token_type String? + scope String? + id_token String? // @db.Text + session_state String? + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@unique([provider, providerAccountId]) +} + +model Session { + id String @id @default(cuid()) + sessionToken String @unique + userId Int + expires DateTime + user User @relation(fields: [userId], references: [id], onDelete: Cascade) +} + +model VerificationToken { + identifier String + token String @unique + expires DateTime + + @@unique([identifier, token]) +} + +model User { + id Int @id @default(autoincrement()) + name String? + email String? @unique + emailVerified DateTime? + image String? + accounts Account[] + sessions Session[] + teamUsers TeamUser[] +} + +model Team { + id Int @id @default(autoincrement()) + name String + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + teamUsers TeamUser[] + domains Domain[] + apiKeys ApiKey[] + emails Email[] +} + +enum Role { + ADMIN + MEMBER +} + +model TeamUser { + teamId Int + userId Int + role Role + team Team @relation(fields: [teamId], references: [id], onDelete: Cascade) + user User @relation(fields: [userId], references: [id], onDelete: Cascade) + + @@unique([teamId, userId]) +} + +enum DomainStatus { + NOT_STARTED + PENDING + SUCCESS + FAILED + TEMPORARY_FAILURE +} + +model Domain { + id Int @id @default(autoincrement()) + name String @unique + teamId Int + status DomainStatus @default(PENDING) + region String @default("us-east-1") + publicKey String + dkimStatus String? + spfDetails String? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + team Team @relation(fields: [teamId], references: [id], onDelete: Cascade) +} + +enum ApiPermission { + FULL + SENDING +} + +model ApiKey { + id Int @id @default(autoincrement()) + tokenHash String @unique + partialToken String + name String + permission ApiPermission @default(SENDING) + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + lastUsed DateTime? + teamId Int + team Team @relation(fields: [teamId], references: [id], onDelete: Cascade) +} + +model Email { + id String @id + to String + from String + subject String + text String? + html String? + latestStatus String? + teamId Int + domainId Int? + createdAt DateTime @default(now()) + updatedAt DateTime @updatedAt + team Team @relation(fields: [teamId], references: [id], onDelete: Cascade) + emailEvents EmailEvent[] +} + +model EmailEvent { + emailId String + status String + data Json? + createdAt DateTime @default(now()) + email Email @relation(fields: [emailId], references: [id], onDelete: Cascade) + + @@unique([emailId, status]) +} diff --git a/apps/web/src/app/(dashboard)/api-keys/add-api-key.tsx b/apps/web/src/app/(dashboard)/api-keys/add-api-key.tsx new file mode 100644 index 0000000..297f6c4 --- /dev/null +++ b/apps/web/src/app/(dashboard)/api-keys/add-api-key.tsx @@ -0,0 +1,104 @@ +"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, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@unsend/ui/src/dialog"; + +import { api } from "~/trpc/react"; +import { useState } from "react"; + +export default function AddApiKey() { + const [open, setOpen] = useState(false); + const [name, setName] = useState(""); + const [apiKey, setApiKey] = useState(""); + const addDomainMutation = api.apiKey.createToken.useMutation(); + + const utils = api.useUtils(); + + function handleSave() { + addDomainMutation.mutate( + { + name, + permission: "FULL", + }, + { + onSuccess: (data) => { + utils.apiKey.invalidate(); + setApiKey(data); + }, + } + ); + } + + function handleCopy() { + navigator.clipboard.writeText(apiKey); + setApiKey(""); + setName(""); + setOpen(false); + } + + return ( + (_open !== open ? setOpen(_open) : null)} + > + + + + {apiKey ? ( + + + Copy API key + +
{apiKey}
+ + + +
+ ) : ( + + + Create a new API key + +
+ + setName(e.target.value)} + value={name} + /> +
+ + + +
+ )} +
+ ); +} diff --git a/apps/web/src/app/(dashboard)/api-keys/api-list.tsx b/apps/web/src/app/(dashboard)/api-keys/api-list.tsx new file mode 100644 index 0000000..ed19eff --- /dev/null +++ b/apps/web/src/app/(dashboard)/api-keys/api-list.tsx @@ -0,0 +1,26 @@ +"use client"; + +import Link from "next/link"; +import { api } from "~/trpc/react"; + +export default function ApiList() { + const apiKeysQuery = api.apiKey.getApiKeys.useQuery(); + + return ( +
+
+ {!apiKeysQuery.isLoading && apiKeysQuery.data?.length ? ( + apiKeysQuery.data?.map((apiKey) => ( +
+

{apiKey.name}

+

{apiKey.permission}

+

{apiKey.partialToken}

+
+ )) + ) : ( +
No API keys added
+ )} +
+
+ ); +} diff --git a/apps/web/src/app/(dashboard)/api-keys/page.tsx b/apps/web/src/app/(dashboard)/api-keys/page.tsx new file mode 100644 index 0000000..144cf0a --- /dev/null +++ b/apps/web/src/app/(dashboard)/api-keys/page.tsx @@ -0,0 +1,15 @@ +import type { Metadata } from "next"; +import ApiList from "./api-list"; +import AddApiKey from "./add-api-key"; + +export default async function ApiKeysPage() { + return ( +
+
+

API Keys

+ +
+ +
+ ); +} diff --git a/apps/web/src/app/(dashboard)/dashboard/page.tsx b/apps/web/src/app/(dashboard)/dashboard/page.tsx new file mode 100644 index 0000000..6ff7149 --- /dev/null +++ b/apps/web/src/app/(dashboard)/dashboard/page.tsx @@ -0,0 +1,5 @@ +import type { Metadata } from "next"; + +export default async function DashboardPage() { + return
Hello world
; +} diff --git a/apps/web/src/app/(dashboard)/domains/[domainId]/page.tsx b/apps/web/src/app/(dashboard)/domains/[domainId]/page.tsx new file mode 100644 index 0000000..9e726b9 --- /dev/null +++ b/apps/web/src/app/(dashboard)/domains/[domainId]/page.tsx @@ -0,0 +1,62 @@ +"use client"; + +import type { Metadata } from "next"; +import { api } from "~/trpc/react"; + +export default function DomainItemPage({ + params, +}: { + params: { domainId: string }; +}) { + const domainQuery = api.domain.getDomain.useQuery({ + id: Number(params.domainId), + }); + + return ( +
+ {domainQuery.isLoading ? ( +

Loading...

+ ) : ( + <> +
+
+

{domainQuery.data?.name}

+
+ + {domainQuery.data?.status.toLowerCase()} + +
+
+

DNS records

+
+
+

TXT

+

{`unsend._domainkey.${domainQuery.data?.name}`}

+

{`p=${domainQuery.data?.publicKey}`}

+

+ {domainQuery.data?.dkimStatus?.toLowerCase()} +

+
+
+

TXT

+

{`send.${domainQuery.data?.name}`}

+

{`"v=spf1 include:amazonses.com ~all"`}

+

+ {domainQuery.data?.spfDetails?.toLowerCase()} +

+
+
+

MX

+

{`send.${domainQuery.data?.name}`}

+

{`feedback-smtp.${domainQuery.data?.region}.amazonses.com`}

+

+ {domainQuery.data?.spfDetails?.toLowerCase()} +

+
+
+
+ + )} +
+ ); +} diff --git a/apps/web/src/app/(dashboard)/domains/add-domain.tsx b/apps/web/src/app/(dashboard)/domains/add-domain.tsx new file mode 100644 index 0000000..60abc83 --- /dev/null +++ b/apps/web/src/app/(dashboard)/domains/add-domain.tsx @@ -0,0 +1,78 @@ +"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, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle, + DialogTrigger, +} from "@unsend/ui/src/dialog"; + +import { api } from "~/trpc/react"; +import { useState } from "react"; + +export default function AddDomain() { + const [open, setOpen] = useState(false); + const [domainName, setDomainName] = useState(""); + const addDomainMutation = api.domain.createDomain.useMutation(); + + const utils = api.useUtils(); + + function handleSave() { + addDomainMutation.mutate( + { + name: domainName, + }, + { + onSuccess: () => { + utils.domain.domains.invalidate(); + setOpen(false); + }, + } + ); + } + + return ( + (_open !== open ? setOpen(_open) : null)} + > + + + + + + Add a new domain + This creates a new domain + +
+ + setDomainName(e.target.value)} + value={domainName} + /> +
+ + + +
+
+ ); +} diff --git a/apps/web/src/app/(dashboard)/domains/domain-list.tsx b/apps/web/src/app/(dashboard)/domains/domain-list.tsx new file mode 100644 index 0000000..a13b8ed --- /dev/null +++ b/apps/web/src/app/(dashboard)/domains/domain-list.tsx @@ -0,0 +1,27 @@ +"use client"; + +import Link from "next/link"; +import { api } from "~/trpc/react"; + +export default function DomainsList() { + const domainsQuery = api.domain.domains.useQuery(); + + return ( +
+
+ {!domainsQuery.isLoading && domainsQuery.data?.length ? ( + domainsQuery.data?.map((domain) => ( + +
+

{domain.name}

+

{domain.status.toLowerCase()}

+
+ + )) + ) : ( +
No domains
+ )} +
+
+ ); +} diff --git a/apps/web/src/app/(dashboard)/domains/page.tsx b/apps/web/src/app/(dashboard)/domains/page.tsx new file mode 100644 index 0000000..534cea5 --- /dev/null +++ b/apps/web/src/app/(dashboard)/domains/page.tsx @@ -0,0 +1,15 @@ +import type { Metadata } from "next"; +import DomainsList from "./domain-list"; +import AddDomain from "./add-domain"; + +export default async function DomainsPage() { + return ( +
+
+

Domains

+ +
+ +
+ ); +} diff --git a/apps/web/src/app/(dashboard)/emails/email-list.tsx b/apps/web/src/app/(dashboard)/emails/email-list.tsx new file mode 100644 index 0000000..dec95b0 --- /dev/null +++ b/apps/web/src/app/(dashboard)/emails/email-list.tsx @@ -0,0 +1,31 @@ +"use client"; + +import Link from "next/link"; +import { api } from "~/trpc/react"; + +export default function DomainsList() { + const emailsQuery = api.email.emails.useQuery(); + + return ( +
+
+ {!emailsQuery.isLoading && emailsQuery.data?.length ? ( + emailsQuery.data?.map((email) => ( + +
+

{email.to}

+

+ {email.latestStatus?.toLowerCase()} +

+

{email.subject}

+

{email.createdAt.toLocaleDateString()}

+
+ + )) + ) : ( +
No domains
+ )} +
+
+ ); +} diff --git a/apps/web/src/app/(dashboard)/emails/page.tsx b/apps/web/src/app/(dashboard)/emails/page.tsx new file mode 100644 index 0000000..401f972 --- /dev/null +++ b/apps/web/src/app/(dashboard)/emails/page.tsx @@ -0,0 +1,13 @@ +import type { Metadata } from "next"; +import EmailList from "./email-list"; + +export default async function EmailsPage() { + return ( +
+
+

Emails

+
+ +
+ ); +} diff --git a/apps/web/src/app/(dashboard)/layout.tsx b/apps/web/src/app/(dashboard)/layout.tsx new file mode 100644 index 0000000..7c92a7f --- /dev/null +++ b/apps/web/src/app/(dashboard)/layout.tsx @@ -0,0 +1,42 @@ +import Link from "next/link"; +import { redirect } from "next/navigation"; +import { NextAuthProvider } from "~/providers/next-auth"; +import { getServerAuthSession } from "~/server/auth"; + +export const metadata = { + title: "Unsend", + description: "Generated by create-t3-app", + icons: [{ rel: "icon", url: "/favicon.ico" }], +}; + +export default async function AuthenticatedDashboardLayout({ + children, +}: { + children: React.ReactNode; +}) { + const session = await getServerAuthSession(); + + if (!session?.user) { + redirect("/"); + } + + return ( + +
+ +
+
{children}
+
+
+
+ ); +} diff --git a/apps/web/src/app/_components/create-post.tsx b/apps/web/src/app/_components/create-post.tsx deleted file mode 100644 index da3a1c8..0000000 --- a/apps/web/src/app/_components/create-post.tsx +++ /dev/null @@ -1,43 +0,0 @@ -"use client"; - -import { useRouter } from "next/navigation"; -import { useState } from "react"; - -import { api } from "~/trpc/react"; - -export function CreatePost() { - const router = useRouter(); - const [name, setName] = useState(""); - - const createPost = api.post.create.useMutation({ - onSuccess: () => { - router.refresh(); - setName(""); - }, - }); - - return ( -
{ - e.preventDefault(); - createPost.mutate({ name }); - }} - className="flex flex-col gap-2" - > - setName(e.target.value)} - className="w-full rounded-full px-4 py-2 text-black" - /> - -
- ); -} diff --git a/apps/web/src/app/api/email/route.ts b/apps/web/src/app/api/email/route.ts new file mode 100644 index 0000000..71e6dcf --- /dev/null +++ b/apps/web/src/app/api/email/route.ts @@ -0,0 +1,36 @@ +import { headers } from "next/headers"; +import { hashToken } from "~/server/auth"; +import { db } from "~/server/db"; +import { sendEmail } from "~/server/service/email-service"; + +export async function GET() { + return Response.json({ data: "Hello" }); +} + +export async function POST(req: Request) { + const token = headers().get("authorization")?.split(" ")[1]; + console.log(token); + if (!token) { + return new Response("authorization token is required", { + status: 401, + }); + } + const hashedToken = hashToken(token); + const team = await db.team.findFirst({ + where: { + apiKeys: { + some: { + tokenHash: hashedToken, + }, + }, + }, + }); + + const data = await req.json(); + try { + const email = await sendEmail({ ...data, teamId: team?.id }); + return Response.json({ data: email }); + } catch (e) { + return Response.json({ error: (e as Error).message }, { status: 500 }); + } +} diff --git a/apps/web/src/app/api/health/route.ts b/apps/web/src/app/api/health/route.ts new file mode 100644 index 0000000..660ef7b --- /dev/null +++ b/apps/web/src/app/api/health/route.ts @@ -0,0 +1,6 @@ +import { setupAws } from "~/server/aws/setup"; + +export async function GET() { + await setupAws(); + return Response.json({ data: "Healthy" }); +} diff --git a/apps/web/src/app/api/ses_callback/route.ts b/apps/web/src/app/api/ses_callback/route.ts new file mode 100644 index 0000000..f226bd1 --- /dev/null +++ b/apps/web/src/app/api/ses_callback/route.ts @@ -0,0 +1,82 @@ +import { headers } from "next/headers"; +import { hashToken } from "~/server/auth"; +import { db } from "~/server/db"; + +export async function GET(req: Request) { + console.log("GET", req); + return Response.json({ data: "Hello" }); +} + +export async function POST(req: Request) { + const data = await req.json(); + + if (data.Type === "SubscriptionConfirmation") { + return handleSubscription(data); + } + + console.log(data, data.Message); + + let message = null; + + try { + message = JSON.parse(data.Message || "{}"); + } catch (e) { + console.log(e); + } + + const emailId = message?.mail.messageId; + + console.log(emailId, message); + + if (!emailId) { + return Response.json({ data: "Email not found" }); + } + + const email = await db.email.findUnique({ + where: { + id: emailId, + }, + }); + + if (!email || !message.mail) { + return Response.json({ data: "Email not found" }); + } + + console.log("FOund email", email); + + await db.email.update({ + where: { + id: email.id, + }, + data: { + latestStatus: message.eventType, + }, + }); + + await db.emailEvent.upsert({ + where: { + emailId_status: { + emailId, + status: message.eventType, + }, + }, + update: { + data: message[message.eventType.toLowerCase()], + }, + create: { + emailId, + status: message.eventType, + data: message[message.eventType.toLowerCase()], + }, + }); + + return Response.json({ data: "Hello" }); +} + +async function handleSubscription(message: any) { + const subResponse = await fetch(message.SubscribeURL, { + method: "GET", + }); + + return Response.json({ data: "Hello" }); +} diff --git a/apps/web/src/app/page.tsx b/apps/web/src/app/page.tsx index 5acaf8d..899a712 100644 --- a/apps/web/src/app/page.tsx +++ b/apps/web/src/app/page.tsx @@ -1,84 +1,34 @@ import Link from "next/link"; -import { CreatePost } from "~/app/_components/create-post"; import { getServerAuthSession } from "~/server/auth"; import { api } from "~/trpc/server"; import { Button } from "@unsend/ui/src/button"; +import { SendHorizonal } from "lucide-react"; export default async function Home() { - const hello = await api.post.hello({ text: "from tRPC" }); const session = await getServerAuthSession(); return ( -
-
-

- Create T3 App -

-
- -

First Steps →

-
- Just the basics - Everything you need to know to set up your - database and authentication. -
- - -

Documentation →

-
- Learn more about Create T3 App, the libraries it uses, and how to - deploy it. -
- -
- -
-

- {hello ? hello.greeting : "Loading tRPC query..."} -

- -
-

- {session && Logged in as {session.user?.name}} -

- - {session ? "Sign out" : "Sign in"} +
+

+ + Send emails in minutes. Completely open source +

+
+ {session?.user ? ( +
-
- - + + ) : ( + + )}
); } - -async function CrudShowcase() { - const session = await getServerAuthSession(); - if (!session?.user) return null; - - const latestPost = await api.post.getLatest(); - - return ( -
- {latestPost ? ( -

Your most recent post: {latestPost.name}

- ) : ( -

You have no posts yet.

- )} - - -
- ); -} diff --git a/apps/web/src/env.js b/apps/web/src/env.js index f642243..d1867c0 100644 --- a/apps/web/src/env.js +++ b/apps/web/src/env.js @@ -28,6 +28,12 @@ export const env = createEnv({ // VERCEL_URL doesn't include `https` so it cant be validated as a URL process.env.VERCEL ? z.string() : z.string().url() ), + GITHUB_ID: z.string(), + GITHUB_SECRET: z.string(), + AWS_ACCESS_KEY: z.string(), + AWS_SECRET_KEY: z.string(), + APP_URL: z.string().optional(), + SNS_TOPIC: z.string(), }, /** @@ -48,6 +54,12 @@ export const env = createEnv({ NODE_ENV: process.env.NODE_ENV, NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET, NEXTAUTH_URL: process.env.NEXTAUTH_URL, + GITHUB_ID: process.env.GITHUB_ID, + GITHUB_SECRET: process.env.GITHUB_SECRET, + AWS_ACCESS_KEY: process.env.AWS_ACCESS_KEY, + AWS_SECRET_KEY: process.env.AWS_SECRET_KEY, + APP_URL: process.env.APP_URL, + SNS_TOPIC: process.env.SNS_TOPIC, }, /** * Run `build` or `dev` with `SKIP_ENV_VALIDATION` to skip env validation. This is especially diff --git a/apps/web/src/providers/next-auth.tsx b/apps/web/src/providers/next-auth.tsx new file mode 100644 index 0000000..4ee71a6 --- /dev/null +++ b/apps/web/src/providers/next-auth.tsx @@ -0,0 +1,18 @@ +"use client"; + +import React from "react"; + +import type { Session } from "next-auth"; +import { SessionProvider } from "next-auth/react"; + +export type NextAuthProviderProps = { + session?: Session | null; + children: React.ReactNode; +}; + +export const NextAuthProvider = ({ + session, + children, +}: NextAuthProviderProps) => { + return {children}; +}; diff --git a/apps/web/src/server/api/root.ts b/apps/web/src/server/api/root.ts index b341fc4..81d2142 100644 --- a/apps/web/src/server/api/root.ts +++ b/apps/web/src/server/api/root.ts @@ -1,5 +1,7 @@ -import { postRouter } from "~/server/api/routers/post"; +import { domainRouter } from "~/server/api/routers/domain"; import { createCallerFactory, createTRPCRouter } from "~/server/api/trpc"; +import { apiRouter } from "./routers/api"; +import { emailRouter } from "./routers/email"; /** * This is the primary router for your server. @@ -7,7 +9,9 @@ import { createCallerFactory, createTRPCRouter } from "~/server/api/trpc"; * All routers added in /api/routers should be manually added here. */ export const appRouter = createTRPCRouter({ - post: postRouter, + domain: domainRouter, + apiKey: apiRouter, + email: emailRouter, }); // export type definition of API diff --git a/apps/web/src/server/api/routers/api.ts b/apps/web/src/server/api/routers/api.ts new file mode 100644 index 0000000..5145d16 --- /dev/null +++ b/apps/web/src/server/api/routers/api.ts @@ -0,0 +1,42 @@ +import { z } from "zod"; + +import { + createTRPCRouter, + protectedProcedure, + publicProcedure, + teamProcedure, +} from "~/server/api/trpc"; +import { db } from "~/server/db"; +import { addApiKey } from "~/server/service/api-service"; +import { createDomain, getDomain } from "~/server/service/domain-service"; + +export const apiRouter = createTRPCRouter({ + createToken: teamProcedure + .input( + z.object({ name: z.string(), permission: z.enum(["FULL", "SENDING"]) }) + ) + .mutation(async ({ ctx, input }) => { + return addApiKey({ + name: input.name, + permission: input.permission, + teamId: ctx.team.id, + }); + }), + + getApiKeys: teamProcedure.query(async ({ ctx }) => { + const keys = await ctx.db.apiKey.findMany({ + where: { + teamId: ctx.team.id, + }, + select: { + id: true, + name: true, + permission: true, + partialToken: true, + lastUsed: true, + }, + }); + + return keys; + }), +}); diff --git a/apps/web/src/server/api/routers/domain.ts b/apps/web/src/server/api/routers/domain.ts new file mode 100644 index 0000000..4a09465 --- /dev/null +++ b/apps/web/src/server/api/routers/domain.ts @@ -0,0 +1,34 @@ +import { z } from "zod"; + +import { + createTRPCRouter, + protectedProcedure, + publicProcedure, + teamProcedure, +} from "~/server/api/trpc"; +import { db } from "~/server/db"; +import { createDomain, getDomain } from "~/server/service/domain-service"; + +export const domainRouter = createTRPCRouter({ + createDomain: teamProcedure + .input(z.object({ name: z.string() })) + .mutation(async ({ ctx, input }) => { + return createDomain(ctx.team.id, input.name); + }), + + domains: teamProcedure.query(async ({ ctx }) => { + const domains = await db.domain.findMany({ + where: { + teamId: ctx.team.id, + }, + }); + + return domains; + }), + + getDomain: teamProcedure + .input(z.object({ id: z.number() })) + .query(async ({ ctx, input }) => { + return getDomain(input.id); + }), +}); diff --git a/apps/web/src/server/api/routers/email.ts b/apps/web/src/server/api/routers/email.ts new file mode 100644 index 0000000..a7d4dca --- /dev/null +++ b/apps/web/src/server/api/routers/email.ts @@ -0,0 +1,41 @@ +import { z } from "zod"; + +import { + createTRPCRouter, + protectedProcedure, + publicProcedure, + teamProcedure, +} from "~/server/api/trpc"; +import { db } from "~/server/db"; +import { createDomain, getDomain } from "~/server/service/domain-service"; + +export const emailRouter = createTRPCRouter({ + emails: teamProcedure.query(async ({ ctx }) => { + const emails = await db.email.findMany({ + where: { + teamId: ctx.team.id, + }, + }); + + return emails; + }), + + getEmail: teamProcedure + .input(z.object({ id: z.string() })) + .query(async ({ ctx, input }) => { + const email = await db.email.findUnique({ + where: { + id: input.id, + }, + include: { + emailEvents: { + orderBy: { + createdAt: "desc", + }, + }, + }, + }); + + return email; + }), +}); diff --git a/apps/web/src/server/api/routers/post.ts b/apps/web/src/server/api/routers/post.ts deleted file mode 100644 index 3994691..0000000 --- a/apps/web/src/server/api/routers/post.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { z } from "zod"; - -import { - createTRPCRouter, - protectedProcedure, - publicProcedure, -} from "~/server/api/trpc"; - -export const postRouter = createTRPCRouter({ - hello: publicProcedure - .input(z.object({ text: z.string() })) - .query(({ input }) => { - return { - greeting: `Hello ${input.text}`, - }; - }), - - create: protectedProcedure - .input(z.object({ name: z.string().min(1) })) - .mutation(async ({ ctx, input }) => { - // simulate a slow db call - await new Promise((resolve) => setTimeout(resolve, 1000)); - - return ctx.db.post.create({ - data: { - name: input.name, - createdBy: { connect: { id: ctx.session.user.id } }, - }, - }); - }), - - getLatest: protectedProcedure.query(({ ctx }) => { - return ctx.db.post.findFirst({ - orderBy: { createdAt: "desc" }, - where: { createdBy: { id: ctx.session.user.id } }, - }); - }), - - getSecretMessage: protectedProcedure.query(() => { - return "you can now see this secret message!"; - }), -}); diff --git a/apps/web/src/server/api/trpc.ts b/apps/web/src/server/api/trpc.ts index 486a816..43054b8 100644 --- a/apps/web/src/server/api/trpc.ts +++ b/apps/web/src/server/api/trpc.ts @@ -9,7 +9,7 @@ import { initTRPC, TRPCError } from "@trpc/server"; import superjson from "superjson"; -import { ZodError } from "zod"; +import { z, ZodError } from "zod"; import { getServerAuthSession } from "~/server/auth"; import { db } from "~/server/db"; @@ -106,3 +106,21 @@ export const protectedProcedure = t.procedure.use(({ ctx, next }) => { }, }); }); + +export const teamProcedure = protectedProcedure.use( + async ({ ctx, next, input }) => { + const teamUser = await db.teamUser.findFirst({ + where: { userId: ctx.session.user.id }, + include: { team: true }, + }); + if (!teamUser) { + throw new TRPCError({ code: "NOT_FOUND", message: "Team not found" }); + } + return next({ + ctx: { + team: teamUser.team, + session: { ...ctx.session, user: ctx.session.user }, + }, + }); + } +); diff --git a/apps/web/src/server/auth.ts b/apps/web/src/server/auth.ts index 3f792c7..6acc15d 100644 --- a/apps/web/src/server/auth.ts +++ b/apps/web/src/server/auth.ts @@ -5,7 +5,7 @@ import { type NextAuthOptions, } from "next-auth"; import { type Adapter } from "next-auth/adapters"; -import DiscordProvider from "next-auth/providers/discord"; +import GitHubProvider from "next-auth/providers/github"; import { env } from "~/env"; import { db } from "~/server/db"; @@ -19,16 +19,15 @@ import { db } from "~/server/db"; declare module "next-auth" { interface Session extends DefaultSession { user: { - id: string; + id: number; // ...other properties // role: UserRole; } & DefaultSession["user"]; } - // interface User { - // // ...other properties - // // role: UserRole; - // } + interface User { + id: number; + } } /** @@ -57,6 +56,10 @@ export const authOptions: NextAuthOptions = { * * @see https://next-auth.js.org/providers/github */ + GitHubProvider({ + clientId: env.GITHUB_ID, + clientSecret: env.GITHUB_SECRET, + }), ], }; @@ -66,3 +69,15 @@ export const authOptions: NextAuthOptions = { * @see https://next-auth.js.org/configuration/nextjs */ export const getServerAuthSession = () => getServerSession(authOptions); + +import { createHash } from "crypto"; + +/** + * Hashes a token using SHA-256. + * + * @param {string} token - The token to be hashed. + * @returns {string} The hashed token. + */ +export function hashToken(token: string) { + return createHash("sha256").update(token).digest("hex"); +} diff --git a/apps/web/src/server/aws/setup.ts b/apps/web/src/server/aws/setup.ts new file mode 100644 index 0000000..bebbc0b --- /dev/null +++ b/apps/web/src/server/aws/setup.ts @@ -0,0 +1,95 @@ +import { JsonValue } from "@prisma/client/runtime/library"; +import { db } from "../db"; +import { APP_SETTINGS } from "~/utils/constants"; +import { createTopic, subscribeEndpoint } from "./sns"; +import { env } from "~/env"; +import { AppSettingsService } from "~/server/service/app-settings-service"; +import { addWebhookConfiguration } from "../ses"; +import { EventType } from "@aws-sdk/client-sesv2"; + +const GENERAL_EVENTS: EventType[] = [ + "BOUNCE", + "COMPLAINT", + "DELIVERY", + "DELIVERY_DELAY", + "REJECT", + "RENDERING_FAILURE", + "SEND", + "SUBSCRIPTION", +]; + +export async function setupAws() { + AppSettingsService.initializeCache(); + let snsTopicArn = await AppSettingsService.getSetting( + APP_SETTINGS.SNS_TOPIC_ARN + ); + console.log("Setting up AWS"); + + if (!snsTopicArn) { + console.log("SNS topic not present, creating..."); + snsTopicArn = await createUnsendSNSTopic(); + } + + await setupSESConfiguration(); +} + +async function createUnsendSNSTopic() { + const topicArn = await createTopic(env.SNS_TOPIC); + if (!topicArn) { + console.error("Failed to create SNS topic"); + return; + } + + await subscribeEndpoint( + topicArn, + `${env.APP_URL ?? env.NEXTAUTH_URL}/api/ses_callback` + ); + + return await AppSettingsService.setSetting( + APP_SETTINGS.SNS_TOPIC_ARN, + topicArn + ); +} + +async function setupSESConfiguration() { + const topicArn = ( + await AppSettingsService.getSetting(APP_SETTINGS.SNS_TOPIC_ARN) + )?.toString(); + + if (!topicArn) { + return; + } + console.log("Setting up SES webhook configuration"); + + await setWebhookConfiguration( + APP_SETTINGS.SES_CONFIGURATION_GENERAL, + topicArn, + GENERAL_EVENTS + ); + + await setWebhookConfiguration( + APP_SETTINGS.SES_CONFIGURATION_CLICK_TRACKING, + topicArn, + [...GENERAL_EVENTS, "CLICK"] + ); + + await setWebhookConfiguration( + APP_SETTINGS.SES_CONFIGURATION_OPEN_TRACKING, + topicArn, + [...GENERAL_EVENTS, "OPEN"] + ); +} + +async function setWebhookConfiguration( + setting: string, + topicArn: string, + eventTypes: EventType[] +) { + const sesConfigurationGeneral = await AppSettingsService.getSetting(setting); + + if (!sesConfigurationGeneral) { + console.log(`Setting up SES webhook configuration for ${setting}`); + const status = await addWebhookConfiguration(setting, topicArn, eventTypes); + await AppSettingsService.setSetting(setting, status.toString()); + } +} diff --git a/apps/web/src/server/aws/sns.ts b/apps/web/src/server/aws/sns.ts new file mode 100644 index 0000000..ebd8959 --- /dev/null +++ b/apps/web/src/server/aws/sns.ts @@ -0,0 +1,39 @@ +import { + SNSClient, + CreateTopicCommand, + SubscribeCommand, +} from "@aws-sdk/client-sns"; +import { env } from "~/env"; + +function getSnsClient(region = "us-east-1") { + return new SNSClient({ + region: region, + credentials: { + accessKeyId: env.AWS_ACCESS_KEY, + secretAccessKey: env.AWS_SECRET_KEY, + }, + }); +} + +export async function createTopic(topic: string) { + const client = getSnsClient(); + const command = new CreateTopicCommand({ + Name: topic, + }); + + const data = await client.send(command); + return data.TopicArn; +} + +export async function subscribeEndpoint(topicArn: string, endpointUrl: string) { + const subscribeCommand = new SubscribeCommand({ + Protocol: "https", + TopicArn: topicArn, + Endpoint: endpointUrl, + }); + const client = getSnsClient(); + + const data = await client.send(subscribeCommand); + console.log(data.SubscriptionArn); + return data.SubscriptionArn; +} diff --git a/apps/web/src/server/service/api-service.ts b/apps/web/src/server/service/api-service.ts new file mode 100644 index 0000000..f87dcf4 --- /dev/null +++ b/apps/web/src/server/service/api-service.ts @@ -0,0 +1,59 @@ +import { ApiPermission } from "@prisma/client"; +import { db } from "../db"; +import { randomBytes } from "crypto"; +import { hashToken } from "../auth"; + +export async function addApiKey({ + name, + permission, + teamId, +}: { + name: string; + permission: ApiPermission; + teamId: number; +}) { + try { + const token = `us_${randomBytes(20).toString("hex")}`; + const hashedToken = hashToken(token); + + await db.apiKey.create({ + data: { + name, + permission: permission, + teamId, + tokenHash: hashedToken, + partialToken: `${token.slice(0, 8)}...${token.slice(-5)}`, + }, + }); + return token; + } catch (error) { + console.error("Error adding API key:", error); + throw error; + } +} + +export async function retrieveApiKey(token: string) { + const hashedToken = hashToken(token); + + try { + const apiKey = await db.apiKey.findUnique({ + where: { + tokenHash: hashedToken, + }, + select: { + id: true, + name: true, + permission: true, + teamId: true, + partialToken: true, + }, + }); + if (!apiKey) { + throw new Error("API Key not found"); + } + return apiKey; + } catch (error) { + console.error("Error retrieving API key:", error); + throw error; + } +} diff --git a/apps/web/src/server/service/app-settings-service.ts b/apps/web/src/server/service/app-settings-service.ts new file mode 100644 index 0000000..5d8ac64 --- /dev/null +++ b/apps/web/src/server/service/app-settings-service.ts @@ -0,0 +1,38 @@ +import { db } from "../db"; +import { JsonValue } from "@prisma/client/runtime/library"; + +export class AppSettingsService { + private static cache: Record = {}; + + public static async getSetting(key: string) { + if (!this.cache[key]) { + const setting = await db.appSetting.findUnique({ + where: { key }, + }); + if (setting) { + this.cache[key] = setting.value; + } else { + return null; + } + } + return this.cache[key]; + } + + public static async setSetting(key: string, value: string) { + await db.appSetting.upsert({ + where: { key }, + update: { value }, + create: { key, value }, + }); + this.cache[key] = value; + + return value; + } + + public static async initializeCache(): Promise { + const settings = await db.appSetting.findMany(); + settings.forEach((setting) => { + this.cache[setting.key] = setting.value; + }); + } +} diff --git a/apps/web/src/server/service/domain-service.ts b/apps/web/src/server/service/domain-service.ts new file mode 100644 index 0000000..044a859 --- /dev/null +++ b/apps/web/src/server/service/domain-service.ts @@ -0,0 +1,69 @@ +import { addDomain, getDomainIdentity } from "~/server/ses"; +import { db } from "~/server/db"; + +export async function createDomain(teamId: number, name: string) { + console.log("Creating domain:", name); + const publicKey = await addDomain(name); + + const domain = await db.domain.create({ + data: { + name, + publicKey, + teamId, + }, + }); + + return domain; +} + +export async function getDomain(id: number) { + let domain = await db.domain.findUnique({ + where: { + id, + }, + }); + + if (!domain) { + throw new Error("Domain not found"); + } + + if (domain.status !== "SUCCESS") { + const domainIdentity = await getDomainIdentity(domain.name, domain.region); + + const dkimStatus = domainIdentity.DkimAttributes?.Status; + const spfDetails = domainIdentity.MailFromAttributes?.MailFromDomainStatus; + const verificationError = domainIdentity.VerificationInfo?.ErrorType; + const verificationStatus = domainIdentity.VerificationStatus; + const lastCheckedTime = + domainIdentity.VerificationInfo?.LastCheckedTimestamp; + + console.log(domainIdentity); + + if ( + domain.dkimStatus !== dkimStatus || + domain.spfDetails !== spfDetails || + domain.status !== verificationStatus + ) { + domain = await db.domain.update({ + where: { + id, + }, + data: { + dkimStatus, + spfDetails, + status: verificationStatus ?? "NOT_STARTED", + }, + }); + } + + return { + ...domain, + dkimStatus, + spfDetails, + verificationError, + lastCheckedTime, + }; + } + + return domain; +} diff --git a/apps/web/src/server/service/email-service.ts b/apps/web/src/server/service/email-service.ts new file mode 100644 index 0000000..8883e5b --- /dev/null +++ b/apps/web/src/server/service/email-service.ts @@ -0,0 +1,49 @@ +import { EmailContent } from "~/types"; +import { db } from "../db"; +import { sendEmailThroughSes } from "../ses"; + +export async function sendEmail( + emailContent: EmailContent & { teamId: number } +) { + 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); + if (!domain) { + throw new Error("Domain not found. Add domain to unsend first"); + } + + if (domain.status !== "SUCCESS") { + throw new Error("Domain is not verified"); + } + + const messageId = await sendEmailThroughSes({ + to, + from, + subject, + text, + html, + region: domain.region, + }); + + if (messageId) { + return await db.email.create({ + data: { + to, + from, + subject, + text, + html, + id: messageId, + teamId, + domainId: domain.id, + }, + }); + } +} diff --git a/apps/web/src/server/ses.ts b/apps/web/src/server/ses.ts new file mode 100644 index 0000000..818d0eb --- /dev/null +++ b/apps/web/src/server/ses.ts @@ -0,0 +1,176 @@ +import { + SESv2Client, + CreateEmailIdentityCommand, + DeleteEmailIdentityCommand, + GetEmailIdentityCommand, + PutEmailIdentityMailFromAttributesCommand, + SendEmailCommand, + CreateConfigurationSetEventDestinationCommand, + CreateConfigurationSetCommand, + EventType, +} from "@aws-sdk/client-sesv2"; +import { generateKeyPairSync } from "crypto"; +import { env } from "~/env"; +import { EmailContent } from "~/types"; +import { APP_SETTINGS } from "~/utils/constants"; + +function getSesClient(region = "us-east-1") { + return new SESv2Client({ + region: region, + credentials: { + accessKeyId: env.AWS_ACCESS_KEY, + secretAccessKey: env.AWS_SECRET_KEY, + }, + }); +} + +function generateKeyPair() { + const { privateKey, publicKey } = generateKeyPairSync("rsa", { + modulusLength: 2048, // Length of your key in bits + publicKeyEncoding: { + type: "spki", // Recommended to be 'spki' by the Node.js docs + format: "pem", + }, + privateKeyEncoding: { + type: "pkcs8", // Recommended to be 'pkcs8' by the Node.js docs + format: "pem", + }, + }); + + const base64PrivateKey = privateKey + .replace("-----BEGIN PRIVATE KEY-----", "") + .replace("-----END PRIVATE KEY-----", "") + .replace(/\n/g, ""); + + const base64PublicKey = publicKey + .replace("-----BEGIN PUBLIC KEY-----", "") + .replace("-----END PUBLIC KEY-----", "") + .replace(/\n/g, ""); + + return { privateKey: base64PrivateKey, publicKey: base64PublicKey }; +} + +export async function addDomain(domain: string, region = "us-east-1") { + const sesClient = getSesClient(region); + + const { privateKey, publicKey } = generateKeyPair(); + const command = new CreateEmailIdentityCommand({ + EmailIdentity: domain, + DkimSigningAttributes: { + DomainSigningSelector: "unsend", + DomainSigningPrivateKey: privateKey, + }, + ConfigurationSetName: APP_SETTINGS.SES_CONFIGURATION_GENERAL, + }); + const response = await sesClient.send(command); + + const emailIdentityCommand = new PutEmailIdentityMailFromAttributesCommand({ + EmailIdentity: domain, + MailFromDomain: `send.${domain}`, + }); + + const emailIdentityResponse = await sesClient.send(emailIdentityCommand); + + if ( + response.$metadata.httpStatusCode !== 200 || + emailIdentityResponse.$metadata.httpStatusCode !== 200 + ) { + throw new Error("Failed to create email identity"); + } + + return publicKey; +} + +export async function getDomainIdentity(domain: string, region = "us-east-1") { + const sesClient = getSesClient(region); + const command = new GetEmailIdentityCommand({ + EmailIdentity: domain, + }); + const response = await sesClient.send(command); + return response; +} + +export async function sendEmailThroughSes({ + to, + from, + subject, + text, + html, + region = "us-east-1", +}: EmailContent & { + region?: string; +}) { + const sesClient = getSesClient(region); + const command = new SendEmailCommand({ + FromEmailAddress: from, + Destination: { + ToAddresses: [to], + }, + Content: { + // EmailContent + Simple: { + // Message + Subject: { + // Content + Data: subject, // required + Charset: "UTF-8", + }, + Body: { + // Body + Text: { + Data: text, // required + Charset: "UTF-8", + }, + Html: { + Data: html, // required + Charset: "UTF-8", + }, + }, + }, + }, + }); + + try { + const response = await sesClient.send(command); + console.log("Email sent! Message ID:", response.MessageId); + return response.MessageId; + } catch (error) { + console.error("Failed to send email", error); + throw new Error("Failed to send email"); + } +} + +export async function addWebhookConfiguration( + configName: string, + topicArn: string, + eventTypes: EventType[], + region = "us-east-1" +) { + const sesClient = getSesClient(region); + + const configSetCommand = new CreateConfigurationSetCommand({ + ConfigurationSetName: configName, + }); + + const configSetResponse = await sesClient.send(configSetCommand); + + if (configSetResponse.$metadata.httpStatusCode !== 200) { + throw new Error("Failed to create configuration set"); + } + + const command = new CreateConfigurationSetEventDestinationCommand({ + ConfigurationSetName: configName, // required + EventDestinationName: "unsend_destination", // required + EventDestination: { + // EventDestinationDefinition + Enabled: true, + MatchingEventTypes: eventTypes, + SnsDestination: { + TopicArn: topicArn, + }, + }, + }); + + const response = await sesClient.send(command); + return response.$metadata.httpStatusCode === 200; +} diff --git a/apps/web/src/types/index.ts b/apps/web/src/types/index.ts new file mode 100644 index 0000000..b84cf73 --- /dev/null +++ b/apps/web/src/types/index.ts @@ -0,0 +1,7 @@ +export type EmailContent = { + to: string; + from: string; + subject: string; + text: string; + html: string; +}; diff --git a/apps/web/src/utils/constants.ts b/apps/web/src/utils/constants.ts new file mode 100644 index 0000000..33d6c44 --- /dev/null +++ b/apps/web/src/utils/constants.ts @@ -0,0 +1,8 @@ +import { env } from "~/env"; + +export const APP_SETTINGS = { + SNS_TOPIC_ARN: "SNS_TOPIC_ARN", + SES_CONFIGURATION_GENERAL: `SES_CONFIGURATION_GENERAL_${env.NODE_ENV}`, + SES_CONFIGURATION_CLICK_TRACKING: `SES_CONFIGURATION_CLICK_TRACKING_${env.NODE_ENV}`, + SES_CONFIGURATION_OPEN_TRACKING: `SES_CONFIGURATION_OPEN_TRACKING_${env.NODE_ENV}`, +}; diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json index 81209be..88a706a 100644 --- a/apps/web/tsconfig.json +++ b/apps/web/tsconfig.json @@ -3,15 +3,17 @@ "compilerOptions": { "baseUrl": ".", "paths": { - "~/*": ["./src/*"] + "~/*": [ + "./src/*" + ] }, "plugins": [ { "name": "next" } - ] + ], + "strictNullChecks": true }, - "include": [ "next-env.d.ts", "**/*.ts", @@ -20,5 +22,7 @@ "**/*.js", ".next/types/**/*.ts" ], - "exclude": ["node_modules"] + "exclude": [ + "node_modules" + ] } diff --git a/package.json b/package.json index 27735a7..c0d06d9 100644 --- a/package.json +++ b/package.json @@ -3,15 +3,15 @@ "private": true, "scripts": { "build": "turbo build", - "dev": "turbo dev --filter=web", + "dev": "pnpm load-env -- turbo dev --filter=web", "lint": "turbo lint", "format": "prettier --write \"**/*.{ts,tsx,md}\"", - "db:generate": "pnpm db generate", - "db:push": "pnpm db push", - "db:migrate-dev": "pnpm db migrate-dev", - "db:migrate-deploy": "pnpm db migrate-deploy", - "db:studio": "pnpm db studio", - "db": "pnpm load-env -- pnpm --filter @unsend/db", + "db:generate": "pnpm db db:generate", + "db:push": "pnpm db db:push", + "db:migrate-dev": "pnpm db db:migrate-dev", + "db:migrate-deploy": "pnpm db db:migrate-deploy", + "db:studio": "pnpm db db:studio", + "db": "pnpm load-env -- pnpm --filter=web", "load-env": "dotenv -e .env" }, "devDependencies": { diff --git a/packages/db/.gitignore b/packages/db/.gitignore deleted file mode 100644 index 11ddd8d..0000000 --- a/packages/db/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -node_modules -# Keep environment variables out of version control -.env diff --git a/packages/db/client.ts b/packages/db/client.ts deleted file mode 100644 index fcab163..0000000 --- a/packages/db/client.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "@prisma/client"; diff --git a/packages/db/index.ts b/packages/db/index.ts deleted file mode 100644 index 19bb95e..0000000 --- a/packages/db/index.ts +++ /dev/null @@ -1,21 +0,0 @@ -import { PrismaClient } from "@prisma/client"; - -declare global { - // We need `var` to declare a global variable in TypeScript - // eslint-disable-next-line no-var - var prisma: PrismaClient | undefined; -} - -if (!globalThis.prisma) { - globalThis.prisma = new PrismaClient({ - datasourceUrl: process.env.DATABASE_URL, - }); -} - -export const prisma = - globalThis.prisma || - new PrismaClient({ - datasourceUrl: process.env.DATABASE_URL, - }); - -export const getPrismaClient = () => prisma; diff --git a/packages/db/package.json b/packages/db/package.json deleted file mode 100644 index 32b8c51..0000000 --- a/packages/db/package.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "@unsend/db", - "version": "0.0.0", - "main": "./index.ts", - "types": "./index.ts", - "dependencies": { - "@prisma/client": "^5.11.0", - "prisma": "^5.11.0" - }, - "scripts": { - "post-install": "prisma generate", - "generate": "prisma generate", - "push": "prisma db push --skip-generate", - "migrate-dev": "prisma migrate dev", - "migrate-deploy": "prisma migrate deploy", - "studio": "prisma studio" - } -} \ No newline at end of file diff --git a/packages/db/prisma/schema.prisma b/packages/db/prisma/schema.prisma deleted file mode 100644 index 81d10e7..0000000 --- a/packages/db/prisma/schema.prisma +++ /dev/null @@ -1,73 +0,0 @@ -// This is your Prisma schema file, -// learn more about it in the docs: https://pris.ly/d/prisma-schema - -generator client { - provider = "prisma-client-js" -} - -datasource db { - provider = "postgresql" - // NOTE: When using mysql or sqlserver, uncomment the @db.Text annotations in model Account below - // Further reading: - // https://next-auth.js.org/adapters/prisma#create-the-prisma-schema - // https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#string - url = env("DATABASE_URL") -} - -model Post { - id Int @id @default(autoincrement()) - name String - createdAt DateTime @default(now()) - updatedAt DateTime @updatedAt - - createdBy User @relation(fields: [createdById], references: [id]) - createdById String - - @@index([name]) -} - -// Necessary for Next auth -model Account { - id String @id @default(cuid()) - userId String - type String - provider String - providerAccountId String - refresh_token String? // @db.Text - access_token String? // @db.Text - expires_at Int? - token_type String? - scope String? - id_token String? // @db.Text - session_state String? - user User @relation(fields: [userId], references: [id], onDelete: Cascade) - - @@unique([provider, providerAccountId]) -} - -model Session { - id String @id @default(cuid()) - sessionToken String @unique - userId String - expires DateTime - user User @relation(fields: [userId], references: [id], onDelete: Cascade) -} - -model User { - id String @id @default(cuid()) - name String? - email String? @unique - emailVerified DateTime? - image String? - accounts Account[] - sessions Session[] - posts Post[] -} - -model VerificationToken { - identifier String - token String @unique - expires DateTime - - @@unique([identifier, token]) -} diff --git a/packages/ui/package.json b/packages/ui/package.json index 219955c..790316c 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -27,6 +27,8 @@ "typescript": "^5.3.3" }, "dependencies": { + "@radix-ui/react-dialog": "^1.0.5", + "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-slot": "^1.0.2", "add": "^2.0.6", "class-variance-authority": "^0.7.0", diff --git a/packages/ui/src/dialog.tsx b/packages/ui/src/dialog.tsx new file mode 100644 index 0000000..01dace8 --- /dev/null +++ b/packages/ui/src/dialog.tsx @@ -0,0 +1,122 @@ +"use client"; + +import * as React from "react"; +import * as DialogPrimitive from "@radix-ui/react-dialog"; +import { X } from "lucide-react"; + +import { cn } from "../lib/utils"; + +const Dialog = DialogPrimitive.Root; + +const DialogTrigger = DialogPrimitive.Trigger; + +const DialogPortal = DialogPrimitive.Portal; + +const DialogClose = DialogPrimitive.Close; + +const DialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; + +const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)); +DialogContent.displayName = DialogPrimitive.Content.displayName; + +const DialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +DialogHeader.displayName = "DialogHeader"; + +const DialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +DialogFooter.displayName = "DialogFooter"; + +const DialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogTitle.displayName = DialogPrimitive.Title.displayName; + +const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogDescription.displayName = DialogPrimitive.Description.displayName; + +export { + Dialog, + DialogPortal, + DialogOverlay, + DialogClose, + DialogTrigger, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, +}; diff --git a/packages/ui/src/input.tsx b/packages/ui/src/input.tsx new file mode 100644 index 0000000..7e22ae6 --- /dev/null +++ b/packages/ui/src/input.tsx @@ -0,0 +1,25 @@ +import * as React from "react"; + +import { cn } from "../lib/utils"; + +export interface InputProps + extends React.InputHTMLAttributes {} + +const Input = React.forwardRef( + ({ className, type, ...props }, ref) => { + return ( + + ); + } +); +Input.displayName = "Input"; + +export { Input }; diff --git a/packages/ui/src/label.tsx b/packages/ui/src/label.tsx new file mode 100644 index 0000000..c27686d --- /dev/null +++ b/packages/ui/src/label.tsx @@ -0,0 +1,26 @@ +"use client"; + +import * as React from "react"; +import * as LabelPrimitive from "@radix-ui/react-label"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "../lib/utils"; + +const labelVariants = cva( + "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" +); + +const Label = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps +>(({ className, ...props }, ref) => ( + +)); +Label.displayName = LabelPrimitive.Root.displayName; + +export { Label }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b9fdb95..ab70a39 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -30,6 +30,15 @@ importers: '@auth/prisma-adapter': specifier: ^1.4.0 version: 1.5.0(@prisma/client@5.11.0) + '@aws-sdk/client-sesv2': + specifier: ^3.535.0 + version: 3.535.0 + '@aws-sdk/client-sns': + specifier: ^3.540.0 + version: 3.540.0 + '@prisma/client': + specifier: ^5.11.0 + version: 5.11.0(prisma@5.11.0) '@t3-oss/env-nextjs': specifier: ^0.9.2 version: 0.9.2(typescript@5.4.2)(zod@3.22.4) @@ -48,18 +57,27 @@ importers: '@trpc/server': specifier: next version: 11.0.0-next-beta.318 - '@unsend/db': - specifier: workspace:* - version: link:../../packages/db '@unsend/ui': specifier: workspace:* version: link:../../packages/ui + install: + specifier: ^0.13.0 + version: 0.13.0 + lucide-react: + specifier: ^0.359.0 + version: 0.359.0(react@18.2.0) next: specifier: ^14.1.3 version: 14.1.3(react-dom@18.2.0)(react@18.2.0) next-auth: specifier: ^4.24.6 version: 4.24.7(next@14.1.3)(react-dom@18.2.0)(react@18.2.0) + pnpm: + specifier: ^8.15.5 + version: 8.15.5 + prisma: + specifier: ^5.11.0 + version: 5.11.0 react: specifier: 18.2.0 version: 18.2.0 @@ -158,6 +176,15 @@ importers: specifier: ^5.3.3 version: 5.4.2 + packages/lib: + devDependencies: + '@unsend/eslint-config': + specifier: workspace:* + version: link:../eslint-config + '@unsend/typescript-config': + specifier: workspace:* + version: link:../typescript-config + packages/tailwind-config: dependencies: tailwindcss-animate: @@ -181,6 +208,12 @@ importers: packages/ui: dependencies: + '@radix-ui/react-dialog': + specifier: ^1.0.5 + version: 1.0.5(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-label': + specifier: ^2.0.2 + version: 2.0.2(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) '@radix-ui/react-slot': specifier: ^1.0.2 version: 1.0.2(@types/react@18.2.66)(react@18.2.0) @@ -307,6 +340,788 @@ packages: - nodemailer dev: false + /@aws-crypto/crc32@3.0.0: + resolution: {integrity: sha512-IzSgsrxUcsrejQbPVilIKy16kAT52EwB6zSaI+M3xxIhKh5+aldEyvI+z6erM7TCLB2BJsFrtHjp6/4/sr+3dA==} + dependencies: + '@aws-crypto/util': 3.0.0 + '@aws-sdk/types': 3.535.0 + tslib: 1.14.1 + dev: false + + /@aws-crypto/ie11-detection@3.0.0: + resolution: {integrity: sha512-341lBBkiY1DfDNKai/wXM3aujNBkXR7tq1URPQDL9wi3AUbI80NR74uF1TXHMm7po1AcnFk8iu2S2IeU/+/A+Q==} + dependencies: + tslib: 1.14.1 + dev: false + + /@aws-crypto/sha256-browser@3.0.0: + resolution: {integrity: sha512-8VLmW2B+gjFbU5uMeqtQM6Nj0/F1bro80xQXCW6CQBWgosFWXTx77aeOF5CAIAmbOK64SdMBJdNr6J41yP5mvQ==} + dependencies: + '@aws-crypto/ie11-detection': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-crypto/supports-web-crypto': 3.0.0 + '@aws-crypto/util': 3.0.0 + '@aws-sdk/types': 3.535.0 + '@aws-sdk/util-locate-window': 3.535.0 + '@aws-sdk/util-utf8-browser': 3.259.0 + tslib: 1.14.1 + dev: false + + /@aws-crypto/sha256-js@3.0.0: + resolution: {integrity: sha512-PnNN7os0+yd1XvXAy23CFOmTbMaDxgxXtTKHybrJ39Y8kGzBATgBFibWJKH6BhytLI/Zyszs87xCOBNyBig6vQ==} + dependencies: + '@aws-crypto/util': 3.0.0 + '@aws-sdk/types': 3.535.0 + tslib: 1.14.1 + dev: false + + /@aws-crypto/supports-web-crypto@3.0.0: + resolution: {integrity: sha512-06hBdMwUAb2WFTuGG73LSC0wfPu93xWwo5vL2et9eymgmu3Id5vFAHBbajVWiGhPO37qcsdCap/FqXvJGJWPIg==} + dependencies: + tslib: 1.14.1 + dev: false + + /@aws-crypto/util@3.0.0: + resolution: {integrity: sha512-2OJlpeJpCR48CC8r+uKVChzs9Iungj9wkZrl8Z041DWEWvyIHILYKCPNzJghKsivj+S3mLo6BVc7mBNzdxA46w==} + dependencies: + '@aws-sdk/types': 3.535.0 + '@aws-sdk/util-utf8-browser': 3.259.0 + tslib: 1.14.1 + dev: false + + /@aws-sdk/client-sesv2@3.535.0: + resolution: {integrity: sha512-K0WZf/o4R+A20RFHJW+YyMg0cMK8GMHe2MHURpPvLJoq5JF0RpCdsMTgRiYIoAFCLoy8qFH9G0MebxR8JZvD+A==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/client-sts': 3.535.0(@aws-sdk/credential-provider-node@3.535.0) + '@aws-sdk/core': 3.535.0 + '@aws-sdk/credential-provider-node': 3.535.0 + '@aws-sdk/middleware-host-header': 3.535.0 + '@aws-sdk/middleware-logger': 3.535.0 + '@aws-sdk/middleware-recursion-detection': 3.535.0 + '@aws-sdk/middleware-user-agent': 3.535.0 + '@aws-sdk/region-config-resolver': 3.535.0 + '@aws-sdk/types': 3.535.0 + '@aws-sdk/util-endpoints': 3.535.0 + '@aws-sdk/util-user-agent-browser': 3.535.0 + '@aws-sdk/util-user-agent-node': 3.535.0 + '@smithy/config-resolver': 2.2.0 + '@smithy/core': 1.4.0 + '@smithy/fetch-http-handler': 2.5.0 + '@smithy/hash-node': 2.2.0 + '@smithy/invalid-dependency': 2.2.0 + '@smithy/middleware-content-length': 2.2.0 + '@smithy/middleware-endpoint': 2.5.0 + '@smithy/middleware-retry': 2.2.0 + '@smithy/middleware-serde': 2.3.0 + '@smithy/middleware-stack': 2.2.0 + '@smithy/node-config-provider': 2.3.0 + '@smithy/node-http-handler': 2.5.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/smithy-client': 2.5.0 + '@smithy/types': 2.12.0 + '@smithy/url-parser': 2.2.0 + '@smithy/util-base64': 2.3.0 + '@smithy/util-body-length-browser': 2.2.0 + '@smithy/util-body-length-node': 2.3.0 + '@smithy/util-defaults-mode-browser': 2.2.0 + '@smithy/util-defaults-mode-node': 2.3.0 + '@smithy/util-endpoints': 1.2.0 + '@smithy/util-middleware': 2.2.0 + '@smithy/util-retry': 2.2.0 + '@smithy/util-utf8': 2.3.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/client-sns@3.540.0: + resolution: {integrity: sha512-xAYpxzCzcSL2Lirm9hyC7kpkVVWo+zewnvMGzNRv2iKlexa7MBcNMr8J3YJmTH3in8eCN1CQ6hK+zsVmUT37Dw==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/client-sts': 3.540.0(@aws-sdk/credential-provider-node@3.540.0) + '@aws-sdk/core': 3.535.0 + '@aws-sdk/credential-provider-node': 3.540.0 + '@aws-sdk/middleware-host-header': 3.535.0 + '@aws-sdk/middleware-logger': 3.535.0 + '@aws-sdk/middleware-recursion-detection': 3.535.0 + '@aws-sdk/middleware-user-agent': 3.540.0 + '@aws-sdk/region-config-resolver': 3.535.0 + '@aws-sdk/types': 3.535.0 + '@aws-sdk/util-endpoints': 3.540.0 + '@aws-sdk/util-user-agent-browser': 3.535.0 + '@aws-sdk/util-user-agent-node': 3.535.0 + '@smithy/config-resolver': 2.2.0 + '@smithy/core': 1.4.0 + '@smithy/fetch-http-handler': 2.5.0 + '@smithy/hash-node': 2.2.0 + '@smithy/invalid-dependency': 2.2.0 + '@smithy/middleware-content-length': 2.2.0 + '@smithy/middleware-endpoint': 2.5.0 + '@smithy/middleware-retry': 2.2.0 + '@smithy/middleware-serde': 2.3.0 + '@smithy/middleware-stack': 2.2.0 + '@smithy/node-config-provider': 2.3.0 + '@smithy/node-http-handler': 2.5.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/smithy-client': 2.5.0 + '@smithy/types': 2.12.0 + '@smithy/url-parser': 2.2.0 + '@smithy/util-base64': 2.3.0 + '@smithy/util-body-length-browser': 2.2.0 + '@smithy/util-body-length-node': 2.3.0 + '@smithy/util-defaults-mode-browser': 2.2.0 + '@smithy/util-defaults-mode-node': 2.3.0 + '@smithy/util-endpoints': 1.2.0 + '@smithy/util-middleware': 2.2.0 + '@smithy/util-retry': 2.2.0 + '@smithy/util-utf8': 2.3.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/client-sso-oidc@3.535.0(@aws-sdk/credential-provider-node@3.535.0): + resolution: {integrity: sha512-M2cG4EQXDpAJQyq33ORIr6abmdX9p9zX0ssVy8XwFNB7lrgoIKxuVoGL+fX+XMgecl24x7ELz6b4QlILOevbCw==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@aws-sdk/credential-provider-node': ^3.535.0 + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/client-sts': 3.535.0(@aws-sdk/credential-provider-node@3.535.0) + '@aws-sdk/core': 3.535.0 + '@aws-sdk/credential-provider-node': 3.535.0 + '@aws-sdk/middleware-host-header': 3.535.0 + '@aws-sdk/middleware-logger': 3.535.0 + '@aws-sdk/middleware-recursion-detection': 3.535.0 + '@aws-sdk/middleware-user-agent': 3.535.0 + '@aws-sdk/region-config-resolver': 3.535.0 + '@aws-sdk/types': 3.535.0 + '@aws-sdk/util-endpoints': 3.535.0 + '@aws-sdk/util-user-agent-browser': 3.535.0 + '@aws-sdk/util-user-agent-node': 3.535.0 + '@smithy/config-resolver': 2.2.0 + '@smithy/core': 1.4.0 + '@smithy/fetch-http-handler': 2.5.0 + '@smithy/hash-node': 2.2.0 + '@smithy/invalid-dependency': 2.2.0 + '@smithy/middleware-content-length': 2.2.0 + '@smithy/middleware-endpoint': 2.5.0 + '@smithy/middleware-retry': 2.2.0 + '@smithy/middleware-serde': 2.3.0 + '@smithy/middleware-stack': 2.2.0 + '@smithy/node-config-provider': 2.3.0 + '@smithy/node-http-handler': 2.5.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/smithy-client': 2.5.0 + '@smithy/types': 2.12.0 + '@smithy/url-parser': 2.2.0 + '@smithy/util-base64': 2.3.0 + '@smithy/util-body-length-browser': 2.2.0 + '@smithy/util-body-length-node': 2.3.0 + '@smithy/util-defaults-mode-browser': 2.2.0 + '@smithy/util-defaults-mode-node': 2.3.0 + '@smithy/util-endpoints': 1.2.0 + '@smithy/util-middleware': 2.2.0 + '@smithy/util-retry': 2.2.0 + '@smithy/util-utf8': 2.3.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/client-sso-oidc@3.540.0(@aws-sdk/credential-provider-node@3.540.0): + resolution: {integrity: sha512-LZYK0lBRQK8D8M3Sqc96XiXkAV2v70zhTtF6weyzEpgwxZMfSuFJjs0jFyhaeZBZbZv7BBghIdhJ5TPavNxGMQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@aws-sdk/credential-provider-node': ^3.540.0 + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/client-sts': 3.540.0(@aws-sdk/credential-provider-node@3.540.0) + '@aws-sdk/core': 3.535.0 + '@aws-sdk/credential-provider-node': 3.540.0 + '@aws-sdk/middleware-host-header': 3.535.0 + '@aws-sdk/middleware-logger': 3.535.0 + '@aws-sdk/middleware-recursion-detection': 3.535.0 + '@aws-sdk/middleware-user-agent': 3.540.0 + '@aws-sdk/region-config-resolver': 3.535.0 + '@aws-sdk/types': 3.535.0 + '@aws-sdk/util-endpoints': 3.540.0 + '@aws-sdk/util-user-agent-browser': 3.535.0 + '@aws-sdk/util-user-agent-node': 3.535.0 + '@smithy/config-resolver': 2.2.0 + '@smithy/core': 1.4.0 + '@smithy/fetch-http-handler': 2.5.0 + '@smithy/hash-node': 2.2.0 + '@smithy/invalid-dependency': 2.2.0 + '@smithy/middleware-content-length': 2.2.0 + '@smithy/middleware-endpoint': 2.5.0 + '@smithy/middleware-retry': 2.2.0 + '@smithy/middleware-serde': 2.3.0 + '@smithy/middleware-stack': 2.2.0 + '@smithy/node-config-provider': 2.3.0 + '@smithy/node-http-handler': 2.5.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/smithy-client': 2.5.0 + '@smithy/types': 2.12.0 + '@smithy/url-parser': 2.2.0 + '@smithy/util-base64': 2.3.0 + '@smithy/util-body-length-browser': 2.2.0 + '@smithy/util-body-length-node': 2.3.0 + '@smithy/util-defaults-mode-browser': 2.2.0 + '@smithy/util-defaults-mode-node': 2.3.0 + '@smithy/util-endpoints': 1.2.0 + '@smithy/util-middleware': 2.2.0 + '@smithy/util-retry': 2.2.0 + '@smithy/util-utf8': 2.3.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/client-sso@3.535.0: + resolution: {integrity: sha512-h9eQRdFnjDRVBnPJIKXuX7D+isSAioIfZPC4PQwsL5BscTRlk4c90DX0R0uk64YUtp7LZu8TNtrosFZ/1HtTrQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/core': 3.535.0 + '@aws-sdk/middleware-host-header': 3.535.0 + '@aws-sdk/middleware-logger': 3.535.0 + '@aws-sdk/middleware-recursion-detection': 3.535.0 + '@aws-sdk/middleware-user-agent': 3.535.0 + '@aws-sdk/region-config-resolver': 3.535.0 + '@aws-sdk/types': 3.535.0 + '@aws-sdk/util-endpoints': 3.535.0 + '@aws-sdk/util-user-agent-browser': 3.535.0 + '@aws-sdk/util-user-agent-node': 3.535.0 + '@smithy/config-resolver': 2.2.0 + '@smithy/core': 1.4.0 + '@smithy/fetch-http-handler': 2.5.0 + '@smithy/hash-node': 2.2.0 + '@smithy/invalid-dependency': 2.2.0 + '@smithy/middleware-content-length': 2.2.0 + '@smithy/middleware-endpoint': 2.5.0 + '@smithy/middleware-retry': 2.2.0 + '@smithy/middleware-serde': 2.3.0 + '@smithy/middleware-stack': 2.2.0 + '@smithy/node-config-provider': 2.3.0 + '@smithy/node-http-handler': 2.5.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/smithy-client': 2.5.0 + '@smithy/types': 2.12.0 + '@smithy/url-parser': 2.2.0 + '@smithy/util-base64': 2.3.0 + '@smithy/util-body-length-browser': 2.2.0 + '@smithy/util-body-length-node': 2.3.0 + '@smithy/util-defaults-mode-browser': 2.2.0 + '@smithy/util-defaults-mode-node': 2.3.0 + '@smithy/util-endpoints': 1.2.0 + '@smithy/util-middleware': 2.2.0 + '@smithy/util-retry': 2.2.0 + '@smithy/util-utf8': 2.3.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/client-sso@3.540.0: + resolution: {integrity: sha512-rrQZMuw4sxIo3eyAUUzPQRA336mPRnrAeSlSdVHBKZD8Fjvoy0lYry2vNhkPLpFZLso1J66KRyuIv4LzRR3v1Q==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/core': 3.535.0 + '@aws-sdk/middleware-host-header': 3.535.0 + '@aws-sdk/middleware-logger': 3.535.0 + '@aws-sdk/middleware-recursion-detection': 3.535.0 + '@aws-sdk/middleware-user-agent': 3.540.0 + '@aws-sdk/region-config-resolver': 3.535.0 + '@aws-sdk/types': 3.535.0 + '@aws-sdk/util-endpoints': 3.540.0 + '@aws-sdk/util-user-agent-browser': 3.535.0 + '@aws-sdk/util-user-agent-node': 3.535.0 + '@smithy/config-resolver': 2.2.0 + '@smithy/core': 1.4.0 + '@smithy/fetch-http-handler': 2.5.0 + '@smithy/hash-node': 2.2.0 + '@smithy/invalid-dependency': 2.2.0 + '@smithy/middleware-content-length': 2.2.0 + '@smithy/middleware-endpoint': 2.5.0 + '@smithy/middleware-retry': 2.2.0 + '@smithy/middleware-serde': 2.3.0 + '@smithy/middleware-stack': 2.2.0 + '@smithy/node-config-provider': 2.3.0 + '@smithy/node-http-handler': 2.5.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/smithy-client': 2.5.0 + '@smithy/types': 2.12.0 + '@smithy/url-parser': 2.2.0 + '@smithy/util-base64': 2.3.0 + '@smithy/util-body-length-browser': 2.2.0 + '@smithy/util-body-length-node': 2.3.0 + '@smithy/util-defaults-mode-browser': 2.2.0 + '@smithy/util-defaults-mode-node': 2.3.0 + '@smithy/util-endpoints': 1.2.0 + '@smithy/util-middleware': 2.2.0 + '@smithy/util-retry': 2.2.0 + '@smithy/util-utf8': 2.3.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/client-sts@3.535.0(@aws-sdk/credential-provider-node@3.535.0): + resolution: {integrity: sha512-ii9OOm3TJwP3JmO1IVJXKWIShVKPl0VtdlgROc/SkDglO/kuAw9eDdlROgc+qbFl+gm6bBTguOVTUXt3tS3flw==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@aws-sdk/credential-provider-node': ^3.535.0 + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/core': 3.535.0 + '@aws-sdk/credential-provider-node': 3.535.0 + '@aws-sdk/middleware-host-header': 3.535.0 + '@aws-sdk/middleware-logger': 3.535.0 + '@aws-sdk/middleware-recursion-detection': 3.535.0 + '@aws-sdk/middleware-user-agent': 3.535.0 + '@aws-sdk/region-config-resolver': 3.535.0 + '@aws-sdk/types': 3.535.0 + '@aws-sdk/util-endpoints': 3.535.0 + '@aws-sdk/util-user-agent-browser': 3.535.0 + '@aws-sdk/util-user-agent-node': 3.535.0 + '@smithy/config-resolver': 2.2.0 + '@smithy/core': 1.4.0 + '@smithy/fetch-http-handler': 2.5.0 + '@smithy/hash-node': 2.2.0 + '@smithy/invalid-dependency': 2.2.0 + '@smithy/middleware-content-length': 2.2.0 + '@smithy/middleware-endpoint': 2.5.0 + '@smithy/middleware-retry': 2.2.0 + '@smithy/middleware-serde': 2.3.0 + '@smithy/middleware-stack': 2.2.0 + '@smithy/node-config-provider': 2.3.0 + '@smithy/node-http-handler': 2.5.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/smithy-client': 2.5.0 + '@smithy/types': 2.12.0 + '@smithy/url-parser': 2.2.0 + '@smithy/util-base64': 2.3.0 + '@smithy/util-body-length-browser': 2.2.0 + '@smithy/util-body-length-node': 2.3.0 + '@smithy/util-defaults-mode-browser': 2.2.0 + '@smithy/util-defaults-mode-node': 2.3.0 + '@smithy/util-endpoints': 1.2.0 + '@smithy/util-middleware': 2.2.0 + '@smithy/util-retry': 2.2.0 + '@smithy/util-utf8': 2.3.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/client-sts@3.540.0(@aws-sdk/credential-provider-node@3.540.0): + resolution: {integrity: sha512-ITHUQxvpqfQX6obfpIi3KYGzZYfe/I5Ixjfxoi5lB7ISCtmxqObKB1fzD93wonkMJytJ7LUO8panZl/ojiJ1uw==} + engines: {node: '>=14.0.0'} + peerDependencies: + '@aws-sdk/credential-provider-node': ^3.540.0 + dependencies: + '@aws-crypto/sha256-browser': 3.0.0 + '@aws-crypto/sha256-js': 3.0.0 + '@aws-sdk/core': 3.535.0 + '@aws-sdk/credential-provider-node': 3.540.0 + '@aws-sdk/middleware-host-header': 3.535.0 + '@aws-sdk/middleware-logger': 3.535.0 + '@aws-sdk/middleware-recursion-detection': 3.535.0 + '@aws-sdk/middleware-user-agent': 3.540.0 + '@aws-sdk/region-config-resolver': 3.535.0 + '@aws-sdk/types': 3.535.0 + '@aws-sdk/util-endpoints': 3.540.0 + '@aws-sdk/util-user-agent-browser': 3.535.0 + '@aws-sdk/util-user-agent-node': 3.535.0 + '@smithy/config-resolver': 2.2.0 + '@smithy/core': 1.4.0 + '@smithy/fetch-http-handler': 2.5.0 + '@smithy/hash-node': 2.2.0 + '@smithy/invalid-dependency': 2.2.0 + '@smithy/middleware-content-length': 2.2.0 + '@smithy/middleware-endpoint': 2.5.0 + '@smithy/middleware-retry': 2.2.0 + '@smithy/middleware-serde': 2.3.0 + '@smithy/middleware-stack': 2.2.0 + '@smithy/node-config-provider': 2.3.0 + '@smithy/node-http-handler': 2.5.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/smithy-client': 2.5.0 + '@smithy/types': 2.12.0 + '@smithy/url-parser': 2.2.0 + '@smithy/util-base64': 2.3.0 + '@smithy/util-body-length-browser': 2.2.0 + '@smithy/util-body-length-node': 2.3.0 + '@smithy/util-defaults-mode-browser': 2.2.0 + '@smithy/util-defaults-mode-node': 2.3.0 + '@smithy/util-endpoints': 1.2.0 + '@smithy/util-middleware': 2.2.0 + '@smithy/util-retry': 2.2.0 + '@smithy/util-utf8': 2.3.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/core@3.535.0: + resolution: {integrity: sha512-+Yusa9HziuaEDta1UaLEtMAtmgvxdxhPn7jgfRY6PplqAqgsfa5FR83sxy5qr2q7xjQTwHtV4MjQVuOjG9JsLw==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/core': 1.4.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/signature-v4': 2.2.0 + '@smithy/smithy-client': 2.5.0 + '@smithy/types': 2.12.0 + fast-xml-parser: 4.2.5 + tslib: 2.6.2 + dev: false + + /@aws-sdk/credential-provider-env@3.535.0: + resolution: {integrity: sha512-XppwO8c0GCGSAvdzyJOhbtktSEaShg14VJKg8mpMa1XcgqzmcqqHQjtDWbx5rZheY1VdpXZhpEzJkB6LpQejpA==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.535.0 + '@smithy/property-provider': 2.2.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/credential-provider-http@3.535.0: + resolution: {integrity: sha512-kdj1wCmOMZ29jSlUskRqN04S6fJ4dvt0Nq9Z32SA6wO7UG8ht6Ot9h/au/eTWJM3E1somZ7D771oK7dQt9b8yw==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.535.0 + '@smithy/fetch-http-handler': 2.5.0 + '@smithy/node-http-handler': 2.5.0 + '@smithy/property-provider': 2.2.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/smithy-client': 2.5.0 + '@smithy/types': 2.12.0 + '@smithy/util-stream': 2.2.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/credential-provider-ini@3.535.0(@aws-sdk/credential-provider-node@3.535.0): + resolution: {integrity: sha512-bm3XOYlyCjtAb8eeHXLrxqRxYVRw2Iqv9IufdJb4gM13TbNSYniUT1WKaHxGIZ5p+FuNlXVhvk1OpHFM13+gXA==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/client-sts': 3.535.0(@aws-sdk/credential-provider-node@3.535.0) + '@aws-sdk/credential-provider-env': 3.535.0 + '@aws-sdk/credential-provider-process': 3.535.0 + '@aws-sdk/credential-provider-sso': 3.535.0(@aws-sdk/credential-provider-node@3.535.0) + '@aws-sdk/credential-provider-web-identity': 3.535.0(@aws-sdk/credential-provider-node@3.535.0) + '@aws-sdk/types': 3.535.0 + '@smithy/credential-provider-imds': 2.3.0 + '@smithy/property-provider': 2.2.0 + '@smithy/shared-ini-file-loader': 2.4.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + transitivePeerDependencies: + - '@aws-sdk/credential-provider-node' + - aws-crt + dev: false + + /@aws-sdk/credential-provider-ini@3.540.0(@aws-sdk/credential-provider-node@3.540.0): + resolution: {integrity: sha512-igN/RbsnulIBwqXbwsWmR3srqmtbPF1dm+JteGvUY31FW65fTVvWvSr945Y/cf1UbhPmIQXntlsqESqpkhTHwg==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/client-sts': 3.540.0(@aws-sdk/credential-provider-node@3.540.0) + '@aws-sdk/credential-provider-env': 3.535.0 + '@aws-sdk/credential-provider-process': 3.535.0 + '@aws-sdk/credential-provider-sso': 3.540.0(@aws-sdk/credential-provider-node@3.540.0) + '@aws-sdk/credential-provider-web-identity': 3.540.0(@aws-sdk/credential-provider-node@3.540.0) + '@aws-sdk/types': 3.535.0 + '@smithy/credential-provider-imds': 2.3.0 + '@smithy/property-provider': 2.2.0 + '@smithy/shared-ini-file-loader': 2.4.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + transitivePeerDependencies: + - '@aws-sdk/credential-provider-node' + - aws-crt + dev: false + + /@aws-sdk/credential-provider-node@3.535.0: + resolution: {integrity: sha512-6JXp/EuL6euUkH5k4d+lQFF6gBwukrcCOWfNHCmq14mNJf/cqT3HAX1VMtWFRSK20am0IxfYQGccb0/nZykdKg==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/credential-provider-env': 3.535.0 + '@aws-sdk/credential-provider-http': 3.535.0 + '@aws-sdk/credential-provider-ini': 3.535.0(@aws-sdk/credential-provider-node@3.535.0) + '@aws-sdk/credential-provider-process': 3.535.0 + '@aws-sdk/credential-provider-sso': 3.535.0(@aws-sdk/credential-provider-node@3.535.0) + '@aws-sdk/credential-provider-web-identity': 3.535.0(@aws-sdk/credential-provider-node@3.535.0) + '@aws-sdk/types': 3.535.0 + '@smithy/credential-provider-imds': 2.3.0 + '@smithy/property-provider': 2.2.0 + '@smithy/shared-ini-file-loader': 2.4.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/credential-provider-node@3.540.0: + resolution: {integrity: sha512-HKQZJbLHlrHX9A0B1poiYNXIIQfy8whTjuosTCYKPDBhhUyVAQfxy/KG726j0v43IhaNPLgTGZCJve4hAsazSw==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/credential-provider-env': 3.535.0 + '@aws-sdk/credential-provider-http': 3.535.0 + '@aws-sdk/credential-provider-ini': 3.540.0(@aws-sdk/credential-provider-node@3.540.0) + '@aws-sdk/credential-provider-process': 3.535.0 + '@aws-sdk/credential-provider-sso': 3.540.0(@aws-sdk/credential-provider-node@3.540.0) + '@aws-sdk/credential-provider-web-identity': 3.540.0(@aws-sdk/credential-provider-node@3.540.0) + '@aws-sdk/types': 3.535.0 + '@smithy/credential-provider-imds': 2.3.0 + '@smithy/property-provider': 2.2.0 + '@smithy/shared-ini-file-loader': 2.4.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/credential-provider-process@3.535.0: + resolution: {integrity: sha512-9O1OaprGCnlb/kYl8RwmH7Mlg8JREZctB8r9sa1KhSsWFq/SWO0AuJTyowxD7zL5PkeS4eTvzFFHWCa3OO5epA==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.535.0 + '@smithy/property-provider': 2.2.0 + '@smithy/shared-ini-file-loader': 2.4.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/credential-provider-sso@3.535.0(@aws-sdk/credential-provider-node@3.535.0): + resolution: {integrity: sha512-2Dw0YIr8ETdFpq65CC4zK8ZIEbX78rXoNRZXUGNQW3oSKfL0tj8O8ErY6kg1IdEnYbGnEQ35q6luZ5GGNKLgDg==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/client-sso': 3.535.0 + '@aws-sdk/token-providers': 3.535.0(@aws-sdk/credential-provider-node@3.535.0) + '@aws-sdk/types': 3.535.0 + '@smithy/property-provider': 2.2.0 + '@smithy/shared-ini-file-loader': 2.4.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + transitivePeerDependencies: + - '@aws-sdk/credential-provider-node' + - aws-crt + dev: false + + /@aws-sdk/credential-provider-sso@3.540.0(@aws-sdk/credential-provider-node@3.540.0): + resolution: {integrity: sha512-tKkFqK227LF5ajc5EL6asXS32p3nkofpP8G7NRpU7zOEOQCg01KUc4JRX+ItI0T007CiN1J19yNoFqHLT/SqHg==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/client-sso': 3.540.0 + '@aws-sdk/token-providers': 3.540.0(@aws-sdk/credential-provider-node@3.540.0) + '@aws-sdk/types': 3.535.0 + '@smithy/property-provider': 2.2.0 + '@smithy/shared-ini-file-loader': 2.4.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + transitivePeerDependencies: + - '@aws-sdk/credential-provider-node' + - aws-crt + dev: false + + /@aws-sdk/credential-provider-web-identity@3.535.0(@aws-sdk/credential-provider-node@3.535.0): + resolution: {integrity: sha512-t2/JWrKY0H66A7JW7CqX06/DG2YkJddikt5ymdQvx/Q7dRMJ3d+o/vgjoKr7RvEx/pNruCeyM1599HCvwrVMrg==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/client-sts': 3.535.0(@aws-sdk/credential-provider-node@3.535.0) + '@aws-sdk/types': 3.535.0 + '@smithy/property-provider': 2.2.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + transitivePeerDependencies: + - '@aws-sdk/credential-provider-node' + - aws-crt + dev: false + + /@aws-sdk/credential-provider-web-identity@3.540.0(@aws-sdk/credential-provider-node@3.540.0): + resolution: {integrity: sha512-OpDm9w3A168B44hSjpnvECP4rvnFzD86rN4VYdGADuCvEa5uEcdA/JuT5WclFPDqdWEmFBqS1pxBIJBf0g2Q9Q==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/client-sts': 3.540.0(@aws-sdk/credential-provider-node@3.540.0) + '@aws-sdk/types': 3.535.0 + '@smithy/property-provider': 2.2.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + transitivePeerDependencies: + - '@aws-sdk/credential-provider-node' + - aws-crt + dev: false + + /@aws-sdk/middleware-host-header@3.535.0: + resolution: {integrity: sha512-0h6TWjBWtDaYwHMQJI9ulafeS4lLaw1vIxRjbpH0svFRt6Eve+Sy8NlVhECfTU2hNz/fLubvrUxsXoThaLBIew==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.535.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-logger@3.535.0: + resolution: {integrity: sha512-huNHpONOrEDrdRTvSQr1cJiRMNf0S52NDXtaPzdxiubTkP+vni2MohmZANMOai/qT0olmEVX01LhZ0ZAOgmg6A==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.535.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-recursion-detection@3.535.0: + resolution: {integrity: sha512-am2qgGs+gwqmR4wHLWpzlZ8PWhm4ktj5bYSgDrsOfjhdBlWNxvPoID9/pDAz5RWL48+oH7I6SQzMqxXsFDikrw==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.535.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-user-agent@3.535.0: + resolution: {integrity: sha512-Uvb2WJ+zdHdCOtsWVPI/M0BcfNrjOYsicDZWtaljucRJKLclY5gNWwD+RwIC+8b5TvfnVOlH+N5jhvpi5Impog==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.535.0 + '@aws-sdk/util-endpoints': 3.535.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/middleware-user-agent@3.540.0: + resolution: {integrity: sha512-8Rd6wPeXDnOYzWj1XCmOKcx/Q87L0K1/EHqOBocGjLVbN3gmRxBvpmR1pRTjf7IsWfnnzN5btqtcAkfDPYQUMQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.535.0 + '@aws-sdk/util-endpoints': 3.540.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/region-config-resolver@3.535.0: + resolution: {integrity: sha512-IXOznDiaItBjsQy4Fil0kzX/J3HxIOknEphqHbOfUf+LpA5ugcsxuQQONrbEQusCBnfJyymrldBvBhFmtlU9Wg==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.535.0 + '@smithy/node-config-provider': 2.3.0 + '@smithy/types': 2.12.0 + '@smithy/util-config-provider': 2.3.0 + '@smithy/util-middleware': 2.2.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/token-providers@3.535.0(@aws-sdk/credential-provider-node@3.535.0): + resolution: {integrity: sha512-4g+l/B9h1H/SiDtFRosW3pMwc+3PTXljZit+5NUBcET2XqcdUyHmgj3lBdu+CJ9CHdIMggRalYMAFXnRFe3Psg==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/client-sso-oidc': 3.535.0(@aws-sdk/credential-provider-node@3.535.0) + '@aws-sdk/types': 3.535.0 + '@smithy/property-provider': 2.2.0 + '@smithy/shared-ini-file-loader': 2.4.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + transitivePeerDependencies: + - '@aws-sdk/credential-provider-node' + - aws-crt + dev: false + + /@aws-sdk/token-providers@3.540.0(@aws-sdk/credential-provider-node@3.540.0): + resolution: {integrity: sha512-9BvtiVEZe5Ev88Wa4ZIUbtT6BVcPwhxmVInQ6c12MYNb0WNL54BN6wLy/eknAfF05gpX2/NDU2pUDOyMPdm/+g==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/client-sso-oidc': 3.540.0(@aws-sdk/credential-provider-node@3.540.0) + '@aws-sdk/types': 3.535.0 + '@smithy/property-provider': 2.2.0 + '@smithy/shared-ini-file-loader': 2.4.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + transitivePeerDependencies: + - '@aws-sdk/credential-provider-node' + - aws-crt + dev: false + + /@aws-sdk/types@3.535.0: + resolution: {integrity: sha512-aY4MYfduNj+sRR37U7XxYR8wemfbKP6lx00ze2M2uubn7mZotuVrWYAafbMSXrdEMSToE5JDhr28vArSOoLcSg==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 2.12.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-endpoints@3.535.0: + resolution: {integrity: sha512-c8TlaQsiPchOOmTTR6qvHCO2O7L7NJwlKWAoQJ2GqWDZuC5es/fyuF2rp1h+ZRrUVraUomS0YdGkAmaDC7hJQg==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.535.0 + '@smithy/types': 2.12.0 + '@smithy/util-endpoints': 1.2.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-endpoints@3.540.0: + resolution: {integrity: sha512-1kMyQFAWx6f8alaI6UT65/5YW/7pDWAKAdNwL6vuJLea03KrZRX3PMoONOSJpAS5m3Ot7HlWZvf3wZDNTLELZw==} + engines: {node: '>=14.0.0'} + dependencies: + '@aws-sdk/types': 3.535.0 + '@smithy/types': 2.12.0 + '@smithy/util-endpoints': 1.2.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-locate-window@3.535.0: + resolution: {integrity: sha512-PHJ3SL6d2jpcgbqdgiPxkXpu7Drc2PYViwxSIqvvMKhDwzSB1W3mMvtpzwKM4IE7zLFodZo0GKjJ9AsoXndXhA==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-user-agent-browser@3.535.0: + resolution: {integrity: sha512-RWMcF/xV5n+nhaA/Ff5P3yNP3Kur/I+VNZngog4TEs92oB/nwOdAg/2JL8bVAhUbMrjTjpwm7PItziYFQoqyig==} + dependencies: + '@aws-sdk/types': 3.535.0 + '@smithy/types': 2.12.0 + bowser: 2.11.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-user-agent-node@3.535.0: + resolution: {integrity: sha512-dRek0zUuIT25wOWJlsRm97nTkUlh1NDcLsQZIN2Y8KxhwoXXWtJs5vaDPT+qAg+OpcNj80i1zLR/CirqlFg/TQ==} + engines: {node: '>=14.0.0'} + peerDependencies: + aws-crt: '>=1.0.0' + peerDependenciesMeta: + aws-crt: + optional: true + dependencies: + '@aws-sdk/types': 3.535.0 + '@smithy/node-config-provider': 2.3.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + dev: false + + /@aws-sdk/util-utf8-browser@3.259.0: + resolution: {integrity: sha512-UvFa/vR+e19XookZF8RzFZBrw2EUkQWxiBW0yYQAhvk3C+QVGl0H3ouca8LDBlBfQKXwmW3huo/59H8rwb1wJw==} + dependencies: + tslib: 2.6.2 + dev: false + /@babel/code-frame@7.23.5: resolution: {integrity: sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==} engines: {node: '>=6.9.0'} @@ -799,6 +1614,12 @@ packages: '@prisma/debug': 5.11.0 dev: false + /@radix-ui/primitive@1.0.1: + resolution: {integrity: sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==} + dependencies: + '@babel/runtime': 7.24.0 + dev: false + /@radix-ui/react-compose-refs@1.0.1(@types/react@18.2.66)(react@18.2.0): resolution: {integrity: sha512-fDSBgd44FKHa1FRMU59qBMPFcl2PZE+2nmqunj+BWFyYYjnhIDWL2ItDs3rrbJDQOtzt5nIebLCQc4QRfz6LJw==} peerDependencies: @@ -813,6 +1634,216 @@ packages: react: 18.2.0 dev: false + /@radix-ui/react-context@1.0.1(@types/react@18.2.66)(react@18.2.0): + resolution: {integrity: sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.24.0 + '@types/react': 18.2.66 + react: 18.2.0 + dev: false + + /@radix-ui/react-dialog@1.0.5(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==} + 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-compose-refs': 1.0.1(@types/react@18.2.66)(react@18.2.0) + '@radix-ui/react-context': 1.0.1(@types/react@18.2.66)(react@18.2.0) + '@radix-ui/react-dismissable-layer': 1.0.5(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0) + '@radix-ui/react-focus-guards': 1.0.1(@types/react@18.2.66)(react@18.2.0) + '@radix-ui/react-focus-scope': 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-id': 1.0.1(@types/react@18.2.66)(react@18.2.0) + '@radix-ui/react-portal': 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-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-slot': 1.0.2(@types/react@18.2.66)(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 + aria-hidden: 1.2.4 + react: 18.2.0 + react-dom: 18.2.0(react@18.2.0) + react-remove-scroll: 2.5.5(@types/react@18.2.66)(react@18.2.0) + dev: false + + /@radix-ui/react-dismissable-layer@1.0.5(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==} + 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-compose-refs': 1.0.1(@types/react@18.2.66)(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-use-callback-ref': 1.0.1(@types/react@18.2.66)(react@18.2.0) + '@radix-ui/react-use-escape-keydown': 1.0.3(@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-focus-guards@1.0.1(@types/react@18.2.66)(react@18.2.0): + resolution: {integrity: sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.24.0 + '@types/react': 18.2.66 + react: 18.2.0 + dev: false + + /@radix-ui/react-focus-scope@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-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==} + 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/react-compose-refs': 1.0.1(@types/react@18.2.66)(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-use-callback-ref': 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-id@1.0.1(@types/react@18.2.66)(react@18.2.0): + resolution: {integrity: sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.24.0 + '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.66)(react@18.2.0) + '@types/react': 18.2.66 + react: 18.2.0 + dev: false + + /@radix-ui/react-label@2.0.2(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-N5ehvlM7qoTLx7nWPodsPYPgMzA5WM8zZChQg8nyFJKnDO5WHdba1vv5/H6IO5LtJMfD2Q3wh1qHFGNtK0w3bQ==} + 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/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) + '@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-portal@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-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==} + 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/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) + '@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-presence@1.0.1(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==} + 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/react-compose-refs': 1.0.1(@types/react@18.2.66)(react@18.2.0) + '@radix-ui/react-use-layout-effect': 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-primitive@1.0.3(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0): + resolution: {integrity: sha512-yi58uVyoAcK/Nq1inRY56ZSjKypBNKTa/1mcL8qdl6oJeEaDbOldlzrGn7P6Q3Id5d+SYNGc5AJgc4vGhjs5+g==} + 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/react-slot': 1.0.2(@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-slot@1.0.2(@types/react@18.2.66)(react@18.2.0): resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==} peerDependencies: @@ -828,10 +1859,441 @@ packages: 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: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.24.0 + '@types/react': 18.2.66 + react: 18.2.0 + dev: false + + /@radix-ui/react-use-controllable-state@1.0.1(@types/react@18.2.66)(react@18.2.0): + resolution: {integrity: sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.24.0 + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.66)(react@18.2.0) + '@types/react': 18.2.66 + react: 18.2.0 + dev: false + + /@radix-ui/react-use-escape-keydown@1.0.3(@types/react@18.2.66)(react@18.2.0): + resolution: {integrity: sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.24.0 + '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@18.2.66)(react@18.2.0) + '@types/react': 18.2.66 + react: 18.2.0 + dev: false + + /@radix-ui/react-use-layout-effect@1.0.1(@types/react@18.2.66)(react@18.2.0): + resolution: {integrity: sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@babel/runtime': 7.24.0 + '@types/react': 18.2.66 + react: 18.2.0 + dev: false + /@rushstack/eslint-patch@1.7.2: resolution: {integrity: sha512-RbhOOTCNoCrbfkRyoXODZp75MlpiHMgbE5MEBZAnnnLyQNgrigEj4p0lzsMDyc1zVsJDLrivB58tgg3emX0eEA==} dev: true + /@smithy/abort-controller@2.2.0: + resolution: {integrity: sha512-wRlta7GuLWpTqtFfGo+nZyOO1vEvewdNR1R4rTxpC8XU6vG/NDyrFBhwLZsqg1NUoR1noVaXJPC/7ZK47QCySw==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 2.12.0 + tslib: 2.6.2 + dev: false + + /@smithy/config-resolver@2.2.0: + resolution: {integrity: sha512-fsiMgd8toyUba6n1WRmr+qACzXltpdDkPTAaDqc8QqPBUzO+/JKwL6bUBseHVi8tu9l+3JOK+tSf7cay+4B3LA==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/node-config-provider': 2.3.0 + '@smithy/types': 2.12.0 + '@smithy/util-config-provider': 2.3.0 + '@smithy/util-middleware': 2.2.0 + tslib: 2.6.2 + dev: false + + /@smithy/core@1.4.0: + resolution: {integrity: sha512-uu9ZDI95Uij4qk+L6kyFjdk11zqBkcJ3Lv0sc6jZrqHvLyr0+oeekD3CnqMafBn/5PRI6uv6ulW3kNLRBUHeVw==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/middleware-endpoint': 2.5.0 + '@smithy/middleware-retry': 2.2.0 + '@smithy/middleware-serde': 2.3.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/smithy-client': 2.5.0 + '@smithy/types': 2.12.0 + '@smithy/util-middleware': 2.2.0 + tslib: 2.6.2 + dev: false + + /@smithy/credential-provider-imds@2.3.0: + resolution: {integrity: sha512-BWB9mIukO1wjEOo1Ojgl6LrG4avcaC7T/ZP6ptmAaW4xluhSIPZhY+/PI5YKzlk+jsm+4sQZB45Bt1OfMeQa3w==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/node-config-provider': 2.3.0 + '@smithy/property-provider': 2.2.0 + '@smithy/types': 2.12.0 + '@smithy/url-parser': 2.2.0 + tslib: 2.6.2 + dev: false + + /@smithy/eventstream-codec@2.2.0: + resolution: {integrity: sha512-8janZoJw85nJmQZc4L8TuePp2pk1nxLgkxIR0TUjKJ5Dkj5oelB9WtiSSGXCQvNsJl0VSTvK/2ueMXxvpa9GVw==} + dependencies: + '@aws-crypto/crc32': 3.0.0 + '@smithy/types': 2.12.0 + '@smithy/util-hex-encoding': 2.2.0 + tslib: 2.6.2 + dev: false + + /@smithy/fetch-http-handler@2.5.0: + resolution: {integrity: sha512-BOWEBeppWhLn/no/JxUL/ghTfANTjT7kg3Ww2rPqTUY9R4yHPXxJ9JhMe3Z03LN3aPwiwlpDIUcVw1xDyHqEhw==} + dependencies: + '@smithy/protocol-http': 3.3.0 + '@smithy/querystring-builder': 2.2.0 + '@smithy/types': 2.12.0 + '@smithy/util-base64': 2.3.0 + tslib: 2.6.2 + dev: false + + /@smithy/hash-node@2.2.0: + resolution: {integrity: sha512-zLWaC/5aWpMrHKpoDF6nqpNtBhlAYKF/7+9yMN7GpdR8CzohnWfGtMznPybnwSS8saaXBMxIGwJqR4HmRp6b3g==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 2.12.0 + '@smithy/util-buffer-from': 2.2.0 + '@smithy/util-utf8': 2.3.0 + tslib: 2.6.2 + dev: false + + /@smithy/invalid-dependency@2.2.0: + resolution: {integrity: sha512-nEDASdbKFKPXN2O6lOlTgrEEOO9NHIeO+HVvZnkqc8h5U9g3BIhWsvzFo+UcUbliMHvKNPD/zVxDrkP1Sbgp8Q==} + dependencies: + '@smithy/types': 2.12.0 + tslib: 2.6.2 + dev: false + + /@smithy/is-array-buffer@2.2.0: + resolution: {integrity: sha512-GGP3O9QFD24uGeAXYUjwSTXARoqpZykHadOmA8G5vfJPK0/DC67qa//0qvqrJzL1xc8WQWX7/yc7fwudjPHPhA==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.2 + dev: false + + /@smithy/middleware-content-length@2.2.0: + resolution: {integrity: sha512-5bl2LG1Ah/7E5cMSC+q+h3IpVHMeOkG0yLRyQT1p2aMJkSrZG7RlXHPuAgb7EyaFeidKEnnd/fNaLLaKlHGzDQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/protocol-http': 3.3.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + dev: false + + /@smithy/middleware-endpoint@2.5.0: + resolution: {integrity: sha512-OBhI9ZEAG8Xen0xsFJwwNOt44WE2CWkfYIxTognC8x42Lfsdf0VN/wCMqpdkySMDio/vts10BiovAxQp0T0faA==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/middleware-serde': 2.3.0 + '@smithy/node-config-provider': 2.3.0 + '@smithy/shared-ini-file-loader': 2.4.0 + '@smithy/types': 2.12.0 + '@smithy/url-parser': 2.2.0 + '@smithy/util-middleware': 2.2.0 + tslib: 2.6.2 + dev: false + + /@smithy/middleware-retry@2.2.0: + resolution: {integrity: sha512-PsjDOLpbevgn37yJbawmfVoanru40qVA8UEf2+YA1lvOefmhuhL6ZbKtGsLAWDRnE1OlAmedsbA/htH6iSZjNA==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/node-config-provider': 2.3.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/service-error-classification': 2.1.5 + '@smithy/smithy-client': 2.5.0 + '@smithy/types': 2.12.0 + '@smithy/util-middleware': 2.2.0 + '@smithy/util-retry': 2.2.0 + tslib: 2.6.2 + uuid: 8.3.2 + dev: false + + /@smithy/middleware-serde@2.3.0: + resolution: {integrity: sha512-sIADe7ojwqTyvEQBe1nc/GXB9wdHhi9UwyX0lTyttmUWDJLP655ZYE1WngnNyXREme8I27KCaUhyhZWRXL0q7Q==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 2.12.0 + tslib: 2.6.2 + dev: false + + /@smithy/middleware-stack@2.2.0: + resolution: {integrity: sha512-Qntc3jrtwwrsAC+X8wms8zhrTr0sFXnyEGhZd9sLtsJ/6gGQKFzNB+wWbOcpJd7BR8ThNCoKt76BuQahfMvpeA==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 2.12.0 + tslib: 2.6.2 + dev: false + + /@smithy/node-config-provider@2.3.0: + resolution: {integrity: sha512-0elK5/03a1JPWMDPaS726Iw6LpQg80gFut1tNpPfxFuChEEklo2yL823V94SpTZTxmKlXFtFgsP55uh3dErnIg==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/property-provider': 2.2.0 + '@smithy/shared-ini-file-loader': 2.4.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + dev: false + + /@smithy/node-http-handler@2.5.0: + resolution: {integrity: sha512-mVGyPBzkkGQsPoxQUbxlEfRjrj6FPyA3u3u2VXGr9hT8wilsoQdZdvKpMBFMB8Crfhv5dNkKHIW0Yyuc7eABqA==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/abort-controller': 2.2.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/querystring-builder': 2.2.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + dev: false + + /@smithy/property-provider@2.2.0: + resolution: {integrity: sha512-+xiil2lFhtTRzXkx8F053AV46QnIw6e7MV8od5Mi68E1ICOjCeCHw2XfLnDEUHnT9WGUIkwcqavXjfwuJbGlpg==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 2.12.0 + tslib: 2.6.2 + dev: false + + /@smithy/protocol-http@3.3.0: + resolution: {integrity: sha512-Xy5XK1AFWW2nlY/biWZXu6/krgbaf2dg0q492D8M5qthsnU2H+UgFeZLbM76FnH7s6RO/xhQRkj+T6KBO3JzgQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 2.12.0 + tslib: 2.6.2 + dev: false + + /@smithy/querystring-builder@2.2.0: + resolution: {integrity: sha512-L1kSeviUWL+emq3CUVSgdogoM/D9QMFaqxL/dd0X7PCNWmPXqt+ExtrBjqT0V7HLN03Vs9SuiLrG3zy3JGnE5A==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 2.12.0 + '@smithy/util-uri-escape': 2.2.0 + tslib: 2.6.2 + dev: false + + /@smithy/querystring-parser@2.2.0: + resolution: {integrity: sha512-BvHCDrKfbG5Yhbpj4vsbuPV2GgcpHiAkLeIlcA1LtfpMz3jrqizP1+OguSNSj1MwBHEiN+jwNisXLGdajGDQJA==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 2.12.0 + tslib: 2.6.2 + dev: false + + /@smithy/service-error-classification@2.1.5: + resolution: {integrity: sha512-uBDTIBBEdAQryvHdc5W8sS5YX7RQzF683XrHePVdFmAgKiMofU15FLSM0/HU03hKTnazdNRFa0YHS7+ArwoUSQ==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 2.12.0 + dev: false + + /@smithy/shared-ini-file-loader@2.4.0: + resolution: {integrity: sha512-WyujUJL8e1B6Z4PBfAqC/aGY1+C7T0w20Gih3yrvJSk97gpiVfB+y7c46T4Nunk+ZngLq0rOIdeVeIklk0R3OA==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 2.12.0 + tslib: 2.6.2 + dev: false + + /@smithy/signature-v4@2.2.0: + resolution: {integrity: sha512-+B5TNzj/fRZzVW3z8UUJOkNx15+4E0CLuvJmJUA1JUIZFp3rdJ/M2H5r2SqltaVPXL0oIxv/6YK92T9TsFGbFg==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/eventstream-codec': 2.2.0 + '@smithy/is-array-buffer': 2.2.0 + '@smithy/types': 2.12.0 + '@smithy/util-hex-encoding': 2.2.0 + '@smithy/util-middleware': 2.2.0 + '@smithy/util-uri-escape': 2.2.0 + '@smithy/util-utf8': 2.3.0 + tslib: 2.6.2 + dev: false + + /@smithy/smithy-client@2.5.0: + resolution: {integrity: sha512-DDXWHWdimtS3y/Kw1Jo46KQ0ZYsDKcldFynQERUGBPDpkW1lXOTHy491ALHjwfiBQvzsVKVxl5+ocXNIgJuX4g==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/middleware-endpoint': 2.5.0 + '@smithy/middleware-stack': 2.2.0 + '@smithy/protocol-http': 3.3.0 + '@smithy/types': 2.12.0 + '@smithy/util-stream': 2.2.0 + tslib: 2.6.2 + dev: false + + /@smithy/types@2.12.0: + resolution: {integrity: sha512-QwYgloJ0sVNBeBuBs65cIkTbfzV/Q6ZNPCJ99EICFEdJYG50nGIY/uYXp+TbsdJReIuPr0a0kXmCvren3MbRRw==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.2 + dev: false + + /@smithy/url-parser@2.2.0: + resolution: {integrity: sha512-hoA4zm61q1mNTpksiSWp2nEl1dt3j726HdRhiNgVJQMj7mLp7dprtF57mOB6JvEk/x9d2bsuL5hlqZbBuHQylQ==} + dependencies: + '@smithy/querystring-parser': 2.2.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + dev: false + + /@smithy/util-base64@2.3.0: + resolution: {integrity: sha512-s3+eVwNeJuXUwuMbusncZNViuhv2LjVJ1nMwTqSA0XAC7gjKhqqxRdJPhR8+YrkoZ9IiIbFk/yK6ACe/xlF+hw==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/util-buffer-from': 2.2.0 + '@smithy/util-utf8': 2.3.0 + tslib: 2.6.2 + dev: false + + /@smithy/util-body-length-browser@2.2.0: + resolution: {integrity: sha512-dtpw9uQP7W+n3vOtx0CfBD5EWd7EPdIdsQnWTDoFf77e3VUf05uA7R7TGipIo8e4WL2kuPdnsr3hMQn9ziYj5w==} + dependencies: + tslib: 2.6.2 + dev: false + + /@smithy/util-body-length-node@2.3.0: + resolution: {integrity: sha512-ITWT1Wqjubf2CJthb0BuT9+bpzBfXeMokH/AAa5EJQgbv9aPMVfnM76iFIZVFf50hYXGbtiV71BHAthNWd6+dw==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.2 + dev: false + + /@smithy/util-buffer-from@2.2.0: + resolution: {integrity: sha512-IJdWBbTcMQ6DA0gdNhh/BwrLkDR+ADW5Kr1aZmd4k3DIF6ezMV4R2NIAmT08wQJ3yUK82thHWmC/TnK/wpMMIA==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/is-array-buffer': 2.2.0 + tslib: 2.6.2 + dev: false + + /@smithy/util-config-provider@2.3.0: + resolution: {integrity: sha512-HZkzrRcuFN1k70RLqlNK4FnPXKOpkik1+4JaBoHNJn+RnJGYqaa3c5/+XtLOXhlKzlRgNvyaLieHTW2VwGN0VQ==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.2 + dev: false + + /@smithy/util-defaults-mode-browser@2.2.0: + resolution: {integrity: sha512-2okTdZaCBvOJszAPU/KSvlimMe35zLOKbQpHhamFJmR7t95HSe0K3C92jQPjKY3PmDBD+7iMkOnuW05F5OlF4g==} + engines: {node: '>= 10.0.0'} + dependencies: + '@smithy/property-provider': 2.2.0 + '@smithy/smithy-client': 2.5.0 + '@smithy/types': 2.12.0 + bowser: 2.11.0 + tslib: 2.6.2 + dev: false + + /@smithy/util-defaults-mode-node@2.3.0: + resolution: {integrity: sha512-hfKXnNLmsW9cmLb/JXKIvtuO6Cf4SuqN5PN1C2Ru/TBIws+m1wSgb+A53vo0r66xzB6E82inKG2J7qtwdi+Kkw==} + engines: {node: '>= 10.0.0'} + dependencies: + '@smithy/config-resolver': 2.2.0 + '@smithy/credential-provider-imds': 2.3.0 + '@smithy/node-config-provider': 2.3.0 + '@smithy/property-provider': 2.2.0 + '@smithy/smithy-client': 2.5.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + dev: false + + /@smithy/util-endpoints@1.2.0: + resolution: {integrity: sha512-BuDHv8zRjsE5zXd3PxFXFknzBG3owCpjq8G3FcsXW3CykYXuEqM3nTSsmLzw5q+T12ZYuDlVUZKBdpNbhVtlrQ==} + engines: {node: '>= 14.0.0'} + dependencies: + '@smithy/node-config-provider': 2.3.0 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + dev: false + + /@smithy/util-hex-encoding@2.2.0: + resolution: {integrity: sha512-7iKXR+/4TpLK194pVjKiasIyqMtTYJsgKgM242Y9uzt5dhHnUDvMNb+3xIhRJ9QhvqGii/5cRUt4fJn3dtXNHQ==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.2 + dev: false + + /@smithy/util-middleware@2.2.0: + resolution: {integrity: sha512-L1qpleXf9QD6LwLCJ5jddGkgWyuSvWBkJwWAZ6kFkdifdso+sk3L3O1HdmPvCdnCK3IS4qWyPxev01QMnfHSBw==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/types': 2.12.0 + tslib: 2.6.2 + dev: false + + /@smithy/util-retry@2.2.0: + resolution: {integrity: sha512-q9+pAFPTfftHXRytmZ7GzLFFrEGavqapFc06XxzZFcSIGERXMerXxCitjOG1prVDR9QdjqotF40SWvbqcCpf8g==} + engines: {node: '>= 14.0.0'} + dependencies: + '@smithy/service-error-classification': 2.1.5 + '@smithy/types': 2.12.0 + tslib: 2.6.2 + dev: false + + /@smithy/util-stream@2.2.0: + resolution: {integrity: sha512-17faEXbYWIRst1aU9SvPZyMdWmqIrduZjVOqCPMIsWFNxs5yQQgFrJL6b2SdiCzyW9mJoDjFtgi53xx7EH+BXA==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/fetch-http-handler': 2.5.0 + '@smithy/node-http-handler': 2.5.0 + '@smithy/types': 2.12.0 + '@smithy/util-base64': 2.3.0 + '@smithy/util-buffer-from': 2.2.0 + '@smithy/util-hex-encoding': 2.2.0 + '@smithy/util-utf8': 2.3.0 + tslib: 2.6.2 + dev: false + + /@smithy/util-uri-escape@2.2.0: + resolution: {integrity: sha512-jtmJMyt1xMD/d8OtbVJ2gFZOSKc+ueYJZPW20ULW1GOp/q/YIM0wNh+u8ZFao9UaIGz4WoPW8hC64qlWLIfoDA==} + engines: {node: '>=14.0.0'} + dependencies: + tslib: 2.6.2 + dev: false + + /@smithy/util-utf8@2.3.0: + resolution: {integrity: sha512-R8Rdn8Hy72KKcebgLiv8jQcQkXoLMOGGv5uI1/k0l+snqkOzQ1R0ChUBCxWMlBsFMekWjq0wRudIweFs7sKT5A==} + engines: {node: '>=14.0.0'} + dependencies: + '@smithy/util-buffer-from': 2.2.0 + tslib: 2.6.2 + dev: false + /@swc/helpers@0.5.2: resolution: {integrity: sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==} dependencies: @@ -971,7 +2433,6 @@ packages: resolution: {integrity: sha512-fHkBXPeNtfvri6gdsMYyW+dW7RXFo6Ad09nLFK0VQWR7yGLai/Cyvyj696gbwYvBnhGtevUG9cET0pmUbMtoPQ==} dependencies: '@types/react': 18.2.66 - dev: true /@types/react@18.2.66: resolution: {integrity: sha512-OYTmMI4UigXeFMF/j4uv0lBBEbongSgptPrHBxqME44h9+yNov+oL6Z3ocJKo0WyXR84sQUNeyIp9MRfckvZpg==} @@ -1433,6 +2894,13 @@ packages: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} dev: true + /aria-hidden@1.2.4: + resolution: {integrity: sha512-y+CcFFwelSXpLZk/7fMB2mUbGtX9lKycf1MWJ7CaTIERyitVlyQx6C+sxcROU2BAJ24OiZyK+8wj2i8AlBoS3A==} + engines: {node: '>=10'} + dependencies: + tslib: 2.6.2 + dev: false + /aria-query@5.3.0: resolution: {integrity: sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==} dependencies: @@ -1600,6 +3068,10 @@ packages: resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} engines: {node: '>=8'} + /bowser@2.11.0: + resolution: {integrity: sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA==} + dev: false + /brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} dependencies: @@ -1855,6 +3327,10 @@ packages: engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} dev: true + /detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + dev: false + /didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} @@ -2585,6 +4061,13 @@ packages: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} dev: true + /fast-xml-parser@4.2.5: + resolution: {integrity: sha512-B9/wizE4WngqQftFPmdaMYlXoJlJOYxGQOanC77fq9k8+Z0v5dDSVh+3glErdIROP//s/jgb7ZuxKfB8nVyo0g==} + hasBin: true + dependencies: + strnum: 1.0.5 + dev: false + /fastq@1.17.1: resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==} dependencies: @@ -2693,6 +4176,11 @@ packages: hasown: 2.0.2 dev: true + /get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + dev: false + /get-stdin@9.0.0: resolution: {integrity: sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==} engines: {node: '>=12'} @@ -2887,6 +4375,11 @@ packages: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} dev: true + /install@0.13.0: + resolution: {integrity: sha512-zDml/jzr2PKU9I8J/xyZBQn8rPCAY//UOYNmR01XwNwyfhEWObo2SWfSl1+0tm1u6PhxLwDnfsT/6jB7OUxqFA==} + engines: {node: '>= 0.10'} + dev: false + /internal-slot@1.0.7: resolution: {integrity: sha512-NGnrKwXzSms2qUUih/ILZ5JBqNTSa1+ZmP6flaIp6KmSElgE9qdndzS3cqjrDovwFdmwsGsLdeFgB6suw+1e9g==} engines: {node: '>= 0.4'} @@ -2896,6 +4389,12 @@ packages: side-channel: 1.0.6 dev: true + /invariant@2.2.4: + resolution: {integrity: sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==} + dependencies: + loose-envify: 1.4.0 + dev: false + /is-array-buffer@3.0.4: resolution: {integrity: sha512-wcjaerHw0ydZwfhiKbXJWLDY8A7yV7KhjQOpb83hGgGfId/aQa4TOvwyzn2PuswW2gPCYEL/nEAiSVpdOj1lXw==} engines: {node: '>= 0.4'} @@ -3888,6 +5387,58 @@ packages: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} dev: true + /react-remove-scroll-bar@2.3.6(@types/react@18.2.66)(react@18.2.0): + resolution: {integrity: sha512-DtSYaao4mBmX+HDo5YWYdBWQwYIQQshUV/dVxFxK+KM26Wjwp1gZ6rv6OC3oujI6Bfu6Xyg3TwK533AQutsn/g==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.2.66 + react: 18.2.0 + react-style-singleton: 2.2.1(@types/react@18.2.66)(react@18.2.0) + tslib: 2.6.2 + dev: false + + /react-remove-scroll@2.5.5(@types/react@18.2.66)(react@18.2.0): + resolution: {integrity: sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.2.66 + react: 18.2.0 + react-remove-scroll-bar: 2.3.6(@types/react@18.2.66)(react@18.2.0) + react-style-singleton: 2.2.1(@types/react@18.2.66)(react@18.2.0) + tslib: 2.6.2 + use-callback-ref: 1.3.2(@types/react@18.2.66)(react@18.2.0) + use-sidecar: 1.1.2(@types/react@18.2.66)(react@18.2.0) + dev: false + + /react-style-singleton@2.2.1(@types/react@18.2.66)(react@18.2.0): + resolution: {integrity: sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.2.66 + get-nonce: 1.0.1 + invariant: 2.2.4 + react: 18.2.0 + tslib: 2.6.2 + dev: false + /react@18.2.0: resolution: {integrity: sha512-/3IjMdb2L9QbBdWiW5e3P2/npwMBaU9mHCSCUzNln0ZCYbcfTsGbTJrU/kGemdH2IWmB2ioZ+zkxtmq6g09fGQ==} engines: {node: '>=0.10.0'} @@ -4250,6 +5801,10 @@ packages: engines: {node: '>=8'} dev: true + /strnum@1.0.5: + resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} + dev: false + /styled-jsx@5.1.1(react@18.2.0): resolution: {integrity: sha512-pW7uC1l4mBZ8ugbiZrcIsiIvVx1UmTfw7UkC3Um2tmfUq9Bhk8IiyEIPl6F8agHgjzku6j0xQEZbfA5uSgSaCw==} engines: {node: '>= 12.0.0'} @@ -4411,7 +5966,6 @@ packages: /tslib@1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} - dev: true /tslib@2.6.2: resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} @@ -4587,6 +6141,37 @@ packages: punycode: 2.3.1 dev: true + /use-callback-ref@1.3.2(@types/react@18.2.66)(react@18.2.0): + resolution: {integrity: sha512-elOQwe6Q8gqZgDA8mrh44qRTQqpIHDcZ3hXTLjBe1i4ph8XpNJnO+aQf3NaG+lriLopI4HMx9VjQLfPQ6vhnoA==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.8.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.2.66 + react: 18.2.0 + tslib: 2.6.2 + dev: false + + /use-sidecar@1.1.2(@types/react@18.2.66)(react@18.2.0): + resolution: {integrity: sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': ^16.9.0 || ^17.0.0 || ^18.0.0 + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + dependencies: + '@types/react': 18.2.66 + detect-node-es: 1.1.0 + react: 18.2.0 + tslib: 2.6.2 + dev: false + /util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}