Add attachment support
This commit is contained in:
@@ -34,10 +34,12 @@
|
|||||||
"hono": "^4.2.2",
|
"hono": "^4.2.2",
|
||||||
"install": "^0.13.0",
|
"install": "^0.13.0",
|
||||||
"lucide-react": "^0.359.0",
|
"lucide-react": "^0.359.0",
|
||||||
"next": "^14.1.3",
|
"mime-types": "^2.1.35",
|
||||||
|
"next": "^14.2.1",
|
||||||
"next-auth": "^4.24.6",
|
"next-auth": "^4.24.6",
|
||||||
"pnpm": "^8.15.5",
|
"pnpm": "^8.15.5",
|
||||||
"prisma": "^5.11.0",
|
"prisma": "^5.11.0",
|
||||||
|
"query-string": "^9.0.0",
|
||||||
"react": "18.2.0",
|
"react": "18.2.0",
|
||||||
"react-dom": "18.2.0",
|
"react-dom": "18.2.0",
|
||||||
"recharts": "^2.12.5",
|
"recharts": "^2.12.5",
|
||||||
@@ -48,6 +50,7 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/eslint": "^8.56.2",
|
"@types/eslint": "^8.56.2",
|
||||||
|
"@types/mime-types": "^2.1.4",
|
||||||
"@types/node": "^20.11.20",
|
"@types/node": "^20.11.20",
|
||||||
"@types/react": "^18.2.57",
|
"@types/react": "^18.2.57",
|
||||||
"@types/react-dom": "^18.2.19",
|
"@types/react-dom": "^18.2.19",
|
||||||
|
BIN
apps/web/public/Logo-1.png
Normal file
BIN
apps/web/public/Logo-1.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.1 KiB |
BIN
apps/web/public/Logo-2.png
Normal file
BIN
apps/web/public/Logo-2.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 13 KiB |
103
apps/web/src/app/(dashboard)/emails/email-details.tsx
Normal file
103
apps/web/src/app/(dashboard)/emails/email-details.tsx
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import { api } from "~/trpc/react";
|
||||||
|
import { Separator } from "@unsend/ui/src/separator";
|
||||||
|
import { EmailStatusBadge, EmailStatusIcon } from "./email-status-badge";
|
||||||
|
import { formatDate } from "date-fns";
|
||||||
|
import { EmailStatus } from "@prisma/client";
|
||||||
|
import { JsonValue } from "@prisma/client/runtime/library";
|
||||||
|
import { SesDeliveryDelay } from "~/types/aws-types";
|
||||||
|
import { DELIVERY_DELAY_ERRORS } from "~/lib/constants/ses-errors";
|
||||||
|
|
||||||
|
export default function EmailDetails({ emailId }: { emailId: string }) {
|
||||||
|
const emailQuery = api.email.getEmail.useQuery({ id: emailId });
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<div className="flex justify-between items-center">
|
||||||
|
<div className="flex gap-4 items-center">
|
||||||
|
<h1 className="font-bold text-lg">{emailQuery.data?.to}</h1>
|
||||||
|
<EmailStatusBadge status={emailQuery.data?.latestStatus ?? "SENT"} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-8 mt-10 items-start">
|
||||||
|
<div className="p-2 rounded-lg border flex flex-col gap-4 ">
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<span className="w-[65px] text-muted-foreground ">From</span>
|
||||||
|
<span>{emailQuery.data?.from}</span>
|
||||||
|
</div>
|
||||||
|
<Separator />
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<span className="w-[65px] text-muted-foreground ">To</span>
|
||||||
|
<span>{emailQuery.data?.to}</span>
|
||||||
|
</div>
|
||||||
|
<Separator />
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<span className="w-[65px] text-muted-foreground ">Subject</span>
|
||||||
|
<span>{emailQuery.data?.subject}</span>
|
||||||
|
</div>
|
||||||
|
<div className=" dark:bg-slate-200 h-[350px] overflow-auto text-black rounded">
|
||||||
|
<div
|
||||||
|
className="px-4 py-4 overflow-auto"
|
||||||
|
dangerouslySetInnerHTML={{ __html: emailQuery.data?.html ?? "" }}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div className=" border rounded-lg w-full ">
|
||||||
|
<div className=" p-4 flex flex-col gap-8">
|
||||||
|
<div className="font-medium">Events History</div>
|
||||||
|
<div className="flex items-stretch px-4">
|
||||||
|
<div className="border-r border-dashed" />
|
||||||
|
<div className="flex flex-col gap-12">
|
||||||
|
{emailQuery.data?.emailEvents.map((evt) => (
|
||||||
|
<div key={evt.status} className="flex gap-5 items-start">
|
||||||
|
<div className=" -ml-2.5">
|
||||||
|
<EmailStatusIcon status={evt.status} />
|
||||||
|
</div>
|
||||||
|
<div className="-mt-1">
|
||||||
|
<div className=" capitalize font-medium">
|
||||||
|
<EmailStatusBadge status={evt.status} />
|
||||||
|
</div>
|
||||||
|
<div className="text-xs text-muted-foreground mt-2">
|
||||||
|
{formatDate(evt.createdAt, "MMM dd, hh:mm a")}
|
||||||
|
</div>
|
||||||
|
<div className="mt-1 text-primary/70">
|
||||||
|
<EmailStatusText status={evt.status} data={evt.data} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const EmailStatusText = ({
|
||||||
|
status,
|
||||||
|
data,
|
||||||
|
}: {
|
||||||
|
status: EmailStatus;
|
||||||
|
data: JsonValue;
|
||||||
|
}) => {
|
||||||
|
if (status === "SENT") {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
We received your request and sent the email to recipient's server.
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
} else if (status === "DELIVERED") {
|
||||||
|
return <div>Mail is successfully delivered to the recipient.</div>;
|
||||||
|
} else if (status === "DELIVERY_DELAYED") {
|
||||||
|
const _errorData = data as unknown as SesDeliveryDelay;
|
||||||
|
const errorMessage = DELIVERY_DELAY_ERRORS[_errorData.delayType];
|
||||||
|
|
||||||
|
return <div>{errorMessage}</div>;
|
||||||
|
}
|
||||||
|
return <div>{status}</div>;
|
||||||
|
};
|
@@ -3,7 +3,6 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import {
|
import {
|
||||||
Table,
|
Table,
|
||||||
TableCaption,
|
|
||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
TableHead,
|
TableHead,
|
||||||
@@ -20,15 +19,85 @@ import {
|
|||||||
MailWarning,
|
MailWarning,
|
||||||
MailX,
|
MailX,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { formatDistance, formatDistanceToNow } from "date-fns";
|
import { formatDistanceToNow } from "date-fns";
|
||||||
import { EmailStatus } from "@prisma/client";
|
import { EmailStatus } from "@prisma/client";
|
||||||
|
import { EmailStatusBadge } from "./email-status-badge";
|
||||||
|
import { useState } from "react";
|
||||||
|
import EmailDetails from "./email-details";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import { useSearchParams } from "next/navigation"; // Adjust the import based on your project setup
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
import { useUrlState } from "~/hooks/useUrlState";
|
||||||
|
import { Button } from "@unsend/ui/src/button";
|
||||||
|
import {
|
||||||
|
Select,
|
||||||
|
SelectContent,
|
||||||
|
SelectItem,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectValue,
|
||||||
|
} from "@unsend/ui/src/select";
|
||||||
|
|
||||||
|
/* Stupid hydrating error. And I so stupid to understand the stupid NextJS docs. Because they stupid change it everyday */
|
||||||
|
const DynamicSheetWithNoSSR = dynamic(
|
||||||
|
() => import("@unsend/ui/src/sheet").then((mod) => mod.Sheet),
|
||||||
|
{ ssr: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
const DynamicSheetContentWithNoSSR = dynamic(
|
||||||
|
() => import("@unsend/ui/src/sheet").then((mod) => mod.SheetContent),
|
||||||
|
{ ssr: false }
|
||||||
|
);
|
||||||
|
|
||||||
export default function EmailsList() {
|
export default function EmailsList() {
|
||||||
const emailsQuery = api.email.emails.useQuery();
|
const [selectedEmail, setSelectedEmail] = useUrlState("emailId");
|
||||||
|
const [page, setPage] = useUrlState("page", "1");
|
||||||
|
const [status, setStatus] = useUrlState("status");
|
||||||
|
|
||||||
|
const pageNumber = Number(page);
|
||||||
|
|
||||||
|
const emailsQuery = api.email.emails.useQuery({
|
||||||
|
page: pageNumber,
|
||||||
|
status: status?.toUpperCase() as EmailStatus,
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSelectEmail = (emailId: string) => {
|
||||||
|
setSelectedEmail(emailId);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSheetChange = (isOpen: boolean) => {
|
||||||
|
if (!isOpen) {
|
||||||
|
setSelectedEmail(null);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mt-10">
|
<div className="mt-10 flex flex-col gap-4">
|
||||||
<div className="flex rounded-xl border shadow">
|
<div className="flex justify-end">
|
||||||
|
<Select
|
||||||
|
value={status ?? "All statuses"}
|
||||||
|
onValueChange={(val) =>
|
||||||
|
setStatus(val === "All statuses" ? null : val)
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<SelectTrigger className="w-[180px] capitalize">
|
||||||
|
{status ? status.toLowerCase().replace("_", " ") : "All statuses"}
|
||||||
|
</SelectTrigger>
|
||||||
|
<SelectContent>
|
||||||
|
<SelectItem value="All statuses" className=" capitalize">
|
||||||
|
All statuses
|
||||||
|
</SelectItem>
|
||||||
|
{Object.values(EmailStatus).map((status) => (
|
||||||
|
<SelectItem value={status} className=" capitalize">
|
||||||
|
{status.toLowerCase().replace("_", " ")}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
{/* <SelectItem value="light">Light</SelectItem>
|
||||||
|
<SelectItem value="dark">Dark</SelectItem>
|
||||||
|
<SelectItem value="system">System</SelectItem> */}
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col rounded-xl border shadow">
|
||||||
<Table className="">
|
<Table className="">
|
||||||
<TableHeader className="">
|
<TableHeader className="">
|
||||||
<TableRow className=" bg-muted/30">
|
<TableRow className=" bg-muted/30">
|
||||||
@@ -41,26 +110,62 @@ export default function EmailsList() {
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHeader>
|
</TableHeader>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{emailsQuery.data?.map((email) => (
|
{emailsQuery.data?.emails.length ? (
|
||||||
<TableRow key={email.id}>
|
emailsQuery.data?.emails.map((email) => (
|
||||||
<TableCell className="font-medium flex gap-4 items-center">
|
<TableRow
|
||||||
<EmailIcon status={email.latestStatus ?? "Send"} />
|
key={email.id}
|
||||||
<p>{email.to}</p>
|
onClick={() => handleSelectEmail(email.id)}
|
||||||
</TableCell>
|
className=" cursor-pointer"
|
||||||
<TableCell>
|
>
|
||||||
<EmailStatusBadge status={email.latestStatus ?? "Sent"} />
|
<TableCell className="font-medium">
|
||||||
{/* <Badge className="w-[100px] flex py-1 justify-center text-emerald-400 hover:bg-emerald-500/10 bg-emerald-500/10 rounded">
|
<div className="flex gap-4 items-center">
|
||||||
{email.latestStatus ?? "Sent"}
|
<EmailIcon status={email.latestStatus ?? "Send"} />
|
||||||
</Badge> */}
|
<p>{email.to}</p>
|
||||||
</TableCell>
|
</div>
|
||||||
<TableCell>{email.subject}</TableCell>
|
</TableCell>
|
||||||
<TableCell className="text-right">
|
<TableCell>
|
||||||
{formatDistanceToNow(email.createdAt, { addSuffix: true })}
|
<EmailStatusBadge status={email.latestStatus ?? "Sent"} />
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>{email.subject}</TableCell>
|
||||||
|
<TableCell className="text-right">
|
||||||
|
{formatDistanceToNow(email.createdAt, { addSuffix: true })}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<TableRow className="h-32">
|
||||||
|
<TableCell colSpan={4} className="text-center">
|
||||||
|
No emails found
|
||||||
</TableCell>
|
</TableCell>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
))}
|
)}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
</Table>
|
</Table>
|
||||||
|
|
||||||
|
<DynamicSheetWithNoSSR
|
||||||
|
open={!!selectedEmail}
|
||||||
|
onOpenChange={handleSheetChange}
|
||||||
|
>
|
||||||
|
<DynamicSheetContentWithNoSSR className=" sm:max-w-3xl">
|
||||||
|
{selectedEmail ? <EmailDetails emailId={selectedEmail} /> : null}
|
||||||
|
</DynamicSheetContentWithNoSSR>
|
||||||
|
</DynamicSheetWithNoSSR>
|
||||||
|
</div>
|
||||||
|
<div className="flex gap-4 justify-end">
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setPage((pageNumber - 1).toString())}
|
||||||
|
disabled={pageNumber === 1}
|
||||||
|
>
|
||||||
|
Previous
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setPage((pageNumber + 1).toString())}
|
||||||
|
disabled={pageNumber >= (emailsQuery.data?.totalPage ?? 0)}
|
||||||
|
>
|
||||||
|
Next
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -113,39 +218,3 @@ const EmailIcon: React.FC<{ status: EmailStatus }> = ({ status }) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const EmailStatusBadge: React.FC<{ status: EmailStatus }> = ({ status }) => {
|
|
||||||
let badgeColor = "bg-gray-400/10 text-gray-500 border-gray-400/10"; // Default color
|
|
||||||
switch (status) {
|
|
||||||
case "SENT":
|
|
||||||
badgeColor = "bg-gray-400/10 text-gray-500 border-gray-400/10";
|
|
||||||
break;
|
|
||||||
case "DELIVERED":
|
|
||||||
badgeColor = "bg-emerald-500/10 text-emerald-500 border-emerald-600/10";
|
|
||||||
break;
|
|
||||||
case "BOUNCED":
|
|
||||||
badgeColor = "bg-red-500/10 text-red-800 border-red-600/10";
|
|
||||||
break;
|
|
||||||
case "CLICKED":
|
|
||||||
badgeColor = "bg-cyan-500/10 text-cyan-600 border-cyan-600/10";
|
|
||||||
break;
|
|
||||||
case "OPENED":
|
|
||||||
badgeColor = "bg-indigo-500/10 text-indigo-600 border-indigo-600/10";
|
|
||||||
break;
|
|
||||||
case "DELIVERY_DELAYED":
|
|
||||||
badgeColor = "bg-yellow-500/10 text-yellow-600 border-yellow-600/10";
|
|
||||||
case "COMPLAINED":
|
|
||||||
badgeColor = "bg-yellow-500/10 text-yellow-600 border-yellow-600/10";
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
badgeColor = "bg-gray-400/10 text-gray-500 border-gray-400/10";
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={` text-center w-[130px] rounded capitalize py-0.5 text-xs ${badgeColor}`}
|
|
||||||
>
|
|
||||||
{status.toLowerCase().split("_").join(" ")}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
83
apps/web/src/app/(dashboard)/emails/email-status-badge.tsx
Normal file
83
apps/web/src/app/(dashboard)/emails/email-status-badge.tsx
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
import { EmailStatus } from "@prisma/client";
|
||||||
|
|
||||||
|
export const EmailStatusBadge: React.FC<{ status: EmailStatus }> = ({
|
||||||
|
status,
|
||||||
|
}) => {
|
||||||
|
let badgeColor = "bg-gray-400/10 text-gray-400 border-gray-400/10"; // Default color
|
||||||
|
switch (status) {
|
||||||
|
case "SENT":
|
||||||
|
badgeColor = "bg-gray-400/10 text-gray-400 border-gray-400/10";
|
||||||
|
break;
|
||||||
|
case "DELIVERED":
|
||||||
|
badgeColor = "bg-emerald-500/10 text-emerald-500 border-emerald-600/10";
|
||||||
|
break;
|
||||||
|
case "BOUNCED":
|
||||||
|
badgeColor = "bg-red-500/10 text-red-800 border-red-600/10";
|
||||||
|
break;
|
||||||
|
case "CLICKED":
|
||||||
|
badgeColor = "bg-cyan-500/10 text-cyan-600 border-cyan-600/10";
|
||||||
|
break;
|
||||||
|
case "OPENED":
|
||||||
|
badgeColor = "bg-indigo-500/10 text-indigo-600 border-indigo-600/10";
|
||||||
|
break;
|
||||||
|
case "DELIVERY_DELAYED":
|
||||||
|
badgeColor = "bg-yellow-500/10 text-yellow-600 border-yellow-600/10";
|
||||||
|
case "COMPLAINED":
|
||||||
|
badgeColor = "bg-yellow-500/10 text-yellow-600 border-yellow-600/10";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
badgeColor = "bg-gray-400/10 text-gray-400 border-gray-400/10";
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={` text-center w-[130px] rounded capitalize py-1 text-xs ${badgeColor}`}
|
||||||
|
>
|
||||||
|
{status.toLowerCase().split("_").join(" ")}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EmailStatusIcon: React.FC<{ status: EmailStatus }> = ({
|
||||||
|
status,
|
||||||
|
}) => {
|
||||||
|
let outsideColor = "bg-gray-600";
|
||||||
|
let insideColor = "bg-gray-600/50";
|
||||||
|
|
||||||
|
switch (status) {
|
||||||
|
case "DELIVERED":
|
||||||
|
outsideColor = "bg-emerald-500/40";
|
||||||
|
insideColor = "bg-emerald-500";
|
||||||
|
break;
|
||||||
|
case "BOUNCED":
|
||||||
|
outsideColor = "bg-red-500/40";
|
||||||
|
insideColor = "bg-red-500";
|
||||||
|
break;
|
||||||
|
case "CLICKED":
|
||||||
|
outsideColor = "bg-cyan-500/40";
|
||||||
|
insideColor = "bg-cyan-500";
|
||||||
|
break;
|
||||||
|
case "OPENED":
|
||||||
|
outsideColor = "bg-indigo-500/40";
|
||||||
|
insideColor = "bg-indigo-500";
|
||||||
|
break;
|
||||||
|
case "DELIVERY_DELAYED":
|
||||||
|
outsideColor = "bg-yellow-500/40";
|
||||||
|
insideColor = "bg-yellow-500";
|
||||||
|
case "COMPLAINED":
|
||||||
|
outsideColor = "bg-yellow-500/40";
|
||||||
|
insideColor = "bg-yellow-500";
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
outsideColor = "bg-gray-600/40";
|
||||||
|
insideColor = "bg-gray-600";
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={`flex justify-center items-center p-1.5 ${outsideColor} rounded-full`}
|
||||||
|
>
|
||||||
|
<div className={`h-2 w-2 rounded-full ${insideColor}`}></div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
@@ -1,13 +1,20 @@
|
|||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
import EmailList from "./email-list";
|
import { Suspense } from "react";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
|
||||||
|
const EmailList = dynamic(
|
||||||
|
() => import("./email-list").then((mod) => mod.default),
|
||||||
|
{ ssr: false }
|
||||||
|
);
|
||||||
export default async function EmailsPage() {
|
export default async function EmailsPage() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<h1 className="font-bold text-lg">Emails</h1>
|
<h1 className="font-bold text-lg">Emails</h1>
|
||||||
</div>
|
</div>
|
||||||
|
{/* <Suspense fallback={<div>Loading...</div>}> */}
|
||||||
<EmailList />
|
<EmailList />
|
||||||
|
{/* </Suspense> */}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@@ -3,7 +3,9 @@ import { redirect } from "next/navigation";
|
|||||||
import {
|
import {
|
||||||
Bell,
|
Bell,
|
||||||
BellRing,
|
BellRing,
|
||||||
|
BookUser,
|
||||||
CircleUser,
|
CircleUser,
|
||||||
|
Code,
|
||||||
Globe,
|
Globe,
|
||||||
Home,
|
Home,
|
||||||
KeyRound,
|
KeyRound,
|
||||||
@@ -17,6 +19,7 @@ import {
|
|||||||
Search,
|
Search,
|
||||||
ShoppingCart,
|
ShoppingCart,
|
||||||
Users,
|
Users,
|
||||||
|
Volume2,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { Button } from "@unsend/ui/src/button";
|
import { Button } from "@unsend/ui/src/button";
|
||||||
|
|
||||||
@@ -61,12 +64,8 @@ export default async function AuthenticatedDashboardLayout({
|
|||||||
<div className="flex h-full max-h-screen flex-col gap-2">
|
<div className="flex h-full max-h-screen flex-col gap-2">
|
||||||
<div className="flex h-14 items-center px-4 lg:h-[60px] lg:px-6">
|
<div className="flex h-14 items-center px-4 lg:h-[60px] lg:px-6">
|
||||||
<Link href="/" className="flex items-center gap-2 font-semibold">
|
<Link href="/" className="flex items-center gap-2 font-semibold">
|
||||||
<Image
|
<Image src="/Logo-1.png" alt="Unsend" width={40} height={40} />
|
||||||
src="/unsend_white_new.png"
|
|
||||||
alt="Unsend"
|
|
||||||
width={25}
|
|
||||||
height={25}
|
|
||||||
/>
|
|
||||||
<span className=" ">Unsend</span>
|
<span className=" ">Unsend</span>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
@@ -87,6 +86,16 @@ export default async function AuthenticatedDashboardLayout({
|
|||||||
Domains
|
Domains
|
||||||
</NavButton>
|
</NavButton>
|
||||||
|
|
||||||
|
<NavButton href="/contacts" comingSoon>
|
||||||
|
<BookUser className="h-4 w-4" />
|
||||||
|
Contacts
|
||||||
|
</NavButton>
|
||||||
|
|
||||||
|
<NavButton href="/contacts" comingSoon>
|
||||||
|
<Volume2 className="h-4 w-4" />
|
||||||
|
Marketing
|
||||||
|
</NavButton>
|
||||||
|
|
||||||
<NavButton href="/sms" comingSoon>
|
<NavButton href="/sms" comingSoon>
|
||||||
<MessageSquareMore className="h-4 w-4" />
|
<MessageSquareMore className="h-4 w-4" />
|
||||||
SMS
|
SMS
|
||||||
@@ -98,8 +107,8 @@ export default async function AuthenticatedDashboardLayout({
|
|||||||
</NavButton>
|
</NavButton>
|
||||||
|
|
||||||
<NavButton href="/api-keys">
|
<NavButton href="/api-keys">
|
||||||
<KeyRound className="h-4 w-4" />
|
<Code className="h-4 w-4" />
|
||||||
API keys
|
Developer settings
|
||||||
</NavButton>
|
</NavButton>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
@@ -1,6 +1,3 @@
|
|||||||
import { headers } from "next/headers";
|
|
||||||
import { hashToken } from "~/server/auth";
|
|
||||||
import { db } from "~/server/db";
|
|
||||||
import { parseSesHook } from "~/server/service/ses-hook-parser";
|
import { parseSesHook } from "~/server/service/ses-hook-parser";
|
||||||
|
|
||||||
export async function GET(req: Request) {
|
export async function GET(req: Request) {
|
||||||
@@ -22,14 +19,15 @@ export async function POST(req: Request) {
|
|||||||
try {
|
try {
|
||||||
message = JSON.parse(data.Message || "{}");
|
message = JSON.parse(data.Message || "{}");
|
||||||
const status = await parseSesHook(message);
|
const status = await parseSesHook(message);
|
||||||
|
console.log("Error is parsing hook", status);
|
||||||
if (!status) {
|
if (!status) {
|
||||||
return Response.json({ data: "Error is parsing hook" }, { status: 400 });
|
return Response.json({ data: "Error is parsing hook" });
|
||||||
}
|
}
|
||||||
|
|
||||||
return Response.json({ data: "Success" });
|
return Response.json({ data: "Success" });
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
return Response.json({ data: "Error is parsing hook" }, { status: 400 });
|
return Response.json({ data: "Error is parsing hook" });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
36
apps/web/src/hooks/useUrlState.ts
Normal file
36
apps/web/src/hooks/useUrlState.ts
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import { useCallback, useState } from "react";
|
||||||
|
import qs from "query-string";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A custom hook to use URL as state
|
||||||
|
* @param key The query parameter key.
|
||||||
|
*/
|
||||||
|
export function useUrlState(key: string, defaultValue: string | null = null) {
|
||||||
|
const [state, setState] = useState<string | null>(() => {
|
||||||
|
if (typeof window === "undefined") return null;
|
||||||
|
const queryValue = qs.parse(window.location.search)[key];
|
||||||
|
if (queryValue !== undefined) {
|
||||||
|
return (Array.isArray(queryValue) ? queryValue[0] : queryValue) ?? null;
|
||||||
|
}
|
||||||
|
return defaultValue;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update URL when state changes
|
||||||
|
const setUrlState = useCallback(
|
||||||
|
(newValue: string | null) => {
|
||||||
|
setState(newValue);
|
||||||
|
const newQuery = {
|
||||||
|
...qs.parse(window.location.search),
|
||||||
|
[key]: newValue,
|
||||||
|
};
|
||||||
|
const newUrl = qs.stringifyUrl({
|
||||||
|
url: window.location.href,
|
||||||
|
query: newQuery,
|
||||||
|
});
|
||||||
|
window.history.replaceState({}, "", newUrl);
|
||||||
|
},
|
||||||
|
[key]
|
||||||
|
);
|
||||||
|
|
||||||
|
return [state, setUrlState] as const;
|
||||||
|
}
|
46
apps/web/src/lib/constants/ses-errors.ts
Normal file
46
apps/web/src/lib/constants/ses-errors.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
export const DELIVERY_DELAY_ERRORS = {
|
||||||
|
InternalFailure: "An internal Unsend issue caused the message to be delayed.",
|
||||||
|
General: "A generic failure occurred during the SMTP conversation.",
|
||||||
|
MailboxFull:
|
||||||
|
"The recipient's mailbox is full and is unable to receive additional messages.",
|
||||||
|
SpamDetected:
|
||||||
|
"The recipient's mail server has detected a large amount of unsolicited email from your account.",
|
||||||
|
RecipientServerError:
|
||||||
|
"A temporary issue with the recipient's email server is preventing the delivery of the message.",
|
||||||
|
IPFailure:
|
||||||
|
"The IP address that's sending the message is being blocked or throttled by the recipient's email provider.",
|
||||||
|
TransientCommunicationFailure:
|
||||||
|
"There was a temporary communication failure during the SMTP conversation with the recipient's email provider.",
|
||||||
|
BYOIPHostNameLookupUnavailable:
|
||||||
|
"Unsend was unable to look up the DNS hostname for your IP addresses. This type of delay only occurs when you use Bring Your Own IP.",
|
||||||
|
Undetermined:
|
||||||
|
"Unsend wasn't able to determine the reason for the delivery delay.",
|
||||||
|
SendingDeferral:
|
||||||
|
"Unsend has deemed it appropriate to internally defer the message.",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const BOUNCE_ERROR_MESSAGES = {
|
||||||
|
Undetermined: "Unsend was unable to determine a specific bounce reason.",
|
||||||
|
Permanent: {
|
||||||
|
General:
|
||||||
|
"Unsend received a general hard bounce. If you receive this type of bounce, you should remove the recipient's email address from your mailing list.",
|
||||||
|
NoEmail:
|
||||||
|
"Unsend received a permanent hard bounce because the target email address does not exist. If you receive this type of bounce, you should remove the recipient's email address from your mailing list.",
|
||||||
|
Suppressed:
|
||||||
|
"Unsend has suppressed sending to this address because it has a recent history of bouncing as an invalid address. To override the global suppression list, see Using the Unsend account-level suppression list.",
|
||||||
|
OnAccountSuppressionList:
|
||||||
|
"Unsend has suppressed sending to this address because it is on the account-level suppression list. This does not count toward your bounce rate metric.",
|
||||||
|
},
|
||||||
|
Transient: {
|
||||||
|
General:
|
||||||
|
"Unsend received a general bounce. You may be able to successfully send to this recipient in the future.",
|
||||||
|
MailboxFull:
|
||||||
|
"Unsend received a mailbox full bounce. You may be able to successfully send to this recipient in the future.",
|
||||||
|
MessageTooLarge:
|
||||||
|
"Unsend received a message too large bounce. You may be able to successfully send to this recipient if you reduce the size of the message.",
|
||||||
|
ContentRejected:
|
||||||
|
"Unsend received a content rejected bounce. You may be able to successfully send to this recipient if you change the content of the message.",
|
||||||
|
AttachmentRejected:
|
||||||
|
"Unsend received an attachment rejected bounce. You may be able to successfully send to this recipient if you remove or change the attachment.",
|
||||||
|
},
|
||||||
|
};
|
@@ -1,24 +1,55 @@
|
|||||||
|
import { EmailStatus } from "@prisma/client";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import {
|
import { createTRPCRouter, teamProcedure } from "~/server/api/trpc";
|
||||||
createTRPCRouter,
|
|
||||||
protectedProcedure,
|
|
||||||
publicProcedure,
|
|
||||||
teamProcedure,
|
|
||||||
} from "~/server/api/trpc";
|
|
||||||
import { db } from "~/server/db";
|
import { db } from "~/server/db";
|
||||||
import { createDomain, getDomain } from "~/server/service/domain-service";
|
|
||||||
|
const statuses = Object.values(EmailStatus) as [EmailStatus];
|
||||||
|
|
||||||
|
const DEFAULT_LIMIT = 30;
|
||||||
|
|
||||||
export const emailRouter = createTRPCRouter({
|
export const emailRouter = createTRPCRouter({
|
||||||
emails: teamProcedure.query(async ({ ctx }) => {
|
emails: teamProcedure
|
||||||
const emails = await db.email.findMany({
|
.input(
|
||||||
where: {
|
z.object({
|
||||||
teamId: ctx.team.id,
|
page: z.number().optional(),
|
||||||
},
|
status: z.enum(statuses).optional().nullable(),
|
||||||
});
|
domain: z.number().optional(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.query(async ({ ctx, input }) => {
|
||||||
|
const page = input.page || 1;
|
||||||
|
const limit = DEFAULT_LIMIT;
|
||||||
|
const offset = (page - 1) * limit;
|
||||||
|
|
||||||
return emails;
|
const whereConditions = {
|
||||||
}),
|
teamId: ctx.team.id,
|
||||||
|
...(input.status ? { latestStatus: input.status } : {}),
|
||||||
|
...(input.domain ? { domainId: input.domain } : {}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const countP = db.email.count({ where: whereConditions });
|
||||||
|
|
||||||
|
const emailsP = db.email.findMany({
|
||||||
|
where: whereConditions,
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
createdAt: true,
|
||||||
|
latestStatus: true,
|
||||||
|
subject: true,
|
||||||
|
to: true,
|
||||||
|
},
|
||||||
|
orderBy: {
|
||||||
|
createdAt: "desc",
|
||||||
|
},
|
||||||
|
skip: offset,
|
||||||
|
take: limit,
|
||||||
|
});
|
||||||
|
|
||||||
|
const [emails, count] = await Promise.all([emailsP, countP]);
|
||||||
|
|
||||||
|
return { emails, totalPage: Math.ceil(count / limit) };
|
||||||
|
}),
|
||||||
|
|
||||||
getEmail: teamProcedure
|
getEmail: teamProcedure
|
||||||
.input(z.object({ id: z.string() }))
|
.input(z.object({ id: z.string() }))
|
||||||
@@ -30,7 +61,7 @@ export const emailRouter = createTRPCRouter({
|
|||||||
include: {
|
include: {
|
||||||
emailEvents: {
|
emailEvents: {
|
||||||
orderBy: {
|
orderBy: {
|
||||||
createdAt: "desc",
|
createdAt: "asc",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@@ -10,6 +10,7 @@ import {
|
|||||||
EventType,
|
EventType,
|
||||||
} from "@aws-sdk/client-sesv2";
|
} from "@aws-sdk/client-sesv2";
|
||||||
import { generateKeyPairSync } from "crypto";
|
import { generateKeyPairSync } from "crypto";
|
||||||
|
import mime from "mime-types";
|
||||||
import { env } from "~/env";
|
import { env } from "~/env";
|
||||||
import { EmailContent } from "~/types";
|
import { EmailContent } from "~/types";
|
||||||
import { APP_SETTINGS } from "~/utils/constants";
|
import { APP_SETTINGS } from "~/utils/constants";
|
||||||
@@ -154,6 +155,63 @@ export async function sendEmailThroughSes({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function sendEmailWithAttachments({
|
||||||
|
to,
|
||||||
|
from,
|
||||||
|
subject,
|
||||||
|
text,
|
||||||
|
html,
|
||||||
|
attachments,
|
||||||
|
region = "us-east-1",
|
||||||
|
configurationSetName,
|
||||||
|
}: EmailContent & {
|
||||||
|
region?: string;
|
||||||
|
configurationSetName: string;
|
||||||
|
attachments: { filename: string; content: string }[];
|
||||||
|
}) {
|
||||||
|
const sesClient = getSesClient(region);
|
||||||
|
const boundary = "NextPart";
|
||||||
|
let rawEmail = `From: ${from}\n`;
|
||||||
|
rawEmail += `To: ${to}\n`;
|
||||||
|
rawEmail += `Subject: ${subject}\n`;
|
||||||
|
rawEmail += `MIME-Version: 1.0\n`;
|
||||||
|
rawEmail += `Content-Type: multipart/mixed; boundary="${boundary}"\n\n`;
|
||||||
|
rawEmail += `--${boundary}\n`;
|
||||||
|
rawEmail += `Content-Type: text/html; charset="UTF-8"\n\n`;
|
||||||
|
rawEmail += `${html}\n\n`;
|
||||||
|
|
||||||
|
for (const attachment of attachments) {
|
||||||
|
const content = attachment.content; // Convert buffer to base64
|
||||||
|
const mimeType =
|
||||||
|
mime.lookup(attachment.filename) || "application/octet-stream";
|
||||||
|
rawEmail += `--${boundary}\n`;
|
||||||
|
rawEmail += `Content-Type: ${mimeType}; name="${attachment.filename}"\n`;
|
||||||
|
rawEmail += `Content-Disposition: attachment; filename="${attachment.filename}"\n`;
|
||||||
|
rawEmail += `Content-Transfer-Encoding: base64\n\n`;
|
||||||
|
rawEmail += `${content}\n\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
rawEmail += `--${boundary}--`;
|
||||||
|
|
||||||
|
const command = new SendEmailCommand({
|
||||||
|
Content: {
|
||||||
|
Raw: {
|
||||||
|
Data: Buffer.from(rawEmail),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
ConfigurationSetName: configurationSetName,
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await sesClient.send(command);
|
||||||
|
console.log("Email with attachments sent! Message ID:", response.MessageId);
|
||||||
|
return response.MessageId;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Failed to send email with attachments", error);
|
||||||
|
throw new Error("Failed to send email with attachments");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function addWebhookConfiguration(
|
export async function addWebhookConfiguration(
|
||||||
configName: string,
|
configName: string,
|
||||||
topicArn: string,
|
topicArn: string,
|
||||||
|
@@ -19,6 +19,14 @@ const route = createRoute({
|
|||||||
subject: z.string(),
|
subject: z.string(),
|
||||||
text: z.string().optional(),
|
text: z.string().optional(),
|
||||||
html: z.string().optional(),
|
html: z.string().optional(),
|
||||||
|
attachments: z
|
||||||
|
.array(
|
||||||
|
z.object({
|
||||||
|
filename: z.string(),
|
||||||
|
content: z.string(),
|
||||||
|
})
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@@ -1,12 +1,12 @@
|
|||||||
import { EmailContent } from "~/types";
|
import { EmailContent } from "~/types";
|
||||||
import { db } from "../db";
|
import { db } from "../db";
|
||||||
import { sendEmailThroughSes } from "../aws/ses";
|
import { sendEmailThroughSes, sendEmailWithAttachments } from "../aws/ses";
|
||||||
import { APP_SETTINGS } from "~/utils/constants";
|
import { APP_SETTINGS } from "~/utils/constants";
|
||||||
|
|
||||||
export async function sendEmail(
|
export async function sendEmail(
|
||||||
emailContent: EmailContent & { teamId: number }
|
emailContent: EmailContent & { teamId: number }
|
||||||
) {
|
) {
|
||||||
const { to, from, subject, text, html, teamId } = emailContent;
|
const { to, from, subject, text, html, teamId, attachments } = emailContent;
|
||||||
|
|
||||||
const fromDomain = from.split("@")[1];
|
const fromDomain = from.split("@")[1];
|
||||||
|
|
||||||
@@ -24,18 +24,33 @@ export async function sendEmail(
|
|||||||
throw new Error("Domain is not verified");
|
throw new Error("Domain is not verified");
|
||||||
}
|
}
|
||||||
|
|
||||||
const messageId = await sendEmailThroughSes({
|
const messageId = attachments
|
||||||
to,
|
? await sendEmailWithAttachments({
|
||||||
from,
|
to,
|
||||||
subject,
|
from,
|
||||||
text,
|
subject,
|
||||||
html,
|
text,
|
||||||
region: domain.region,
|
html,
|
||||||
configurationSetName: getConfigurationSetName(
|
region: domain.region,
|
||||||
domain.clickTracking,
|
configurationSetName: getConfigurationSetName(
|
||||||
domain.openTracking
|
domain.clickTracking,
|
||||||
),
|
domain.openTracking
|
||||||
});
|
),
|
||||||
|
attachments,
|
||||||
|
})
|
||||||
|
: await sendEmailThroughSes({
|
||||||
|
to,
|
||||||
|
from,
|
||||||
|
subject,
|
||||||
|
text,
|
||||||
|
html,
|
||||||
|
region: domain.region,
|
||||||
|
configurationSetName: getConfigurationSetName(
|
||||||
|
domain.clickTracking,
|
||||||
|
domain.openTracking
|
||||||
|
),
|
||||||
|
attachments,
|
||||||
|
});
|
||||||
|
|
||||||
if (messageId) {
|
if (messageId) {
|
||||||
return await db.email.create({
|
return await db.email.create({
|
||||||
|
@@ -28,8 +28,16 @@ export interface SesMail {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface SesBounce {
|
export interface SesBounce {
|
||||||
bounceType: string;
|
bounceType: "Transient" | "Permanent" | "Undetermined";
|
||||||
bounceSubType: string;
|
bounceSubType:
|
||||||
|
| "General"
|
||||||
|
| "NoEmail"
|
||||||
|
| "Suppressed"
|
||||||
|
| "OnAccountSuppressionList "
|
||||||
|
| "MailboxFull"
|
||||||
|
| "MessageTooLarge"
|
||||||
|
| "ContentRejected"
|
||||||
|
| "AttachmentRejected";
|
||||||
bouncedRecipients: Array<{
|
bouncedRecipients: Array<{
|
||||||
emailAddress: string;
|
emailAddress: string;
|
||||||
action: string;
|
action: string;
|
||||||
@@ -94,7 +102,17 @@ export interface SesRenderingFailure {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface SesDeliveryDelay {
|
export interface SesDeliveryDelay {
|
||||||
delayType: string;
|
delayType:
|
||||||
|
| "InternalFailure"
|
||||||
|
| "General"
|
||||||
|
| "MailboxFull"
|
||||||
|
| "SpamDetected"
|
||||||
|
| "RecipientServerError"
|
||||||
|
| "IPFailure"
|
||||||
|
| "TransientCommunicationFailure"
|
||||||
|
| "BYOIPHostNameLookupUnavailable"
|
||||||
|
| "Undetermined"
|
||||||
|
| "SendingDeferral";
|
||||||
expirationTime: string;
|
expirationTime: string;
|
||||||
delayedRecipients: string[];
|
delayedRecipients: string[];
|
||||||
timestamp: string;
|
timestamp: string;
|
||||||
|
@@ -4,4 +4,8 @@ export type EmailContent = {
|
|||||||
subject: string;
|
subject: string;
|
||||||
text?: string;
|
text?: string;
|
||||||
html?: string;
|
html?: string;
|
||||||
|
attachments?: {
|
||||||
|
filename: string;
|
||||||
|
content: string;
|
||||||
|
}[];
|
||||||
};
|
};
|
||||||
|
@@ -31,6 +31,8 @@
|
|||||||
"@radix-ui/react-dialog": "^1.0.5",
|
"@radix-ui/react-dialog": "^1.0.5",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
||||||
"@radix-ui/react-label": "^2.0.2",
|
"@radix-ui/react-label": "^2.0.2",
|
||||||
|
"@radix-ui/react-select": "^2.0.0",
|
||||||
|
"@radix-ui/react-separator": "^1.0.3",
|
||||||
"@radix-ui/react-slot": "^1.0.2",
|
"@radix-ui/react-slot": "^1.0.2",
|
||||||
"@radix-ui/react-switch": "^1.0.3",
|
"@radix-ui/react-switch": "^1.0.3",
|
||||||
"@radix-ui/react-tabs": "^1.0.4",
|
"@radix-ui/react-tabs": "^1.0.4",
|
||||||
|
160
packages/ui/src/select.tsx
Normal file
160
packages/ui/src/select.tsx
Normal file
@@ -0,0 +1,160 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
|
import * as SelectPrimitive from "@radix-ui/react-select";
|
||||||
|
import { Check, ChevronDown, ChevronUp } from "lucide-react";
|
||||||
|
|
||||||
|
import { cn } from "../lib/utils";
|
||||||
|
|
||||||
|
const Select = SelectPrimitive.Root;
|
||||||
|
|
||||||
|
const SelectGroup = SelectPrimitive.Group;
|
||||||
|
|
||||||
|
const SelectValue = SelectPrimitive.Value;
|
||||||
|
|
||||||
|
const SelectTrigger = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<SelectPrimitive.Trigger
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex h-9 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring disabled:cursor-not-allowed disabled:opacity-50 [&>span]:line-clamp-1",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
<SelectPrimitive.Icon asChild>
|
||||||
|
<ChevronDown className="h-4 w-4 opacity-50" />
|
||||||
|
</SelectPrimitive.Icon>
|
||||||
|
</SelectPrimitive.Trigger>
|
||||||
|
));
|
||||||
|
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
|
||||||
|
|
||||||
|
const SelectScrollUpButton = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SelectPrimitive.ScrollUpButton
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex cursor-default items-center justify-center py-1",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ChevronUp className="h-4 w-4" />
|
||||||
|
</SelectPrimitive.ScrollUpButton>
|
||||||
|
));
|
||||||
|
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName;
|
||||||
|
|
||||||
|
const SelectScrollDownButton = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SelectPrimitive.ScrollDownButton
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex cursor-default items-center justify-center py-1",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ChevronDown className="h-4 w-4" />
|
||||||
|
</SelectPrimitive.ScrollDownButton>
|
||||||
|
));
|
||||||
|
SelectScrollDownButton.displayName =
|
||||||
|
SelectPrimitive.ScrollDownButton.displayName;
|
||||||
|
|
||||||
|
const SelectContent = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.Content>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
||||||
|
>(({ className, children, position = "popper", ...props }, ref) => (
|
||||||
|
<SelectPrimitive.Portal>
|
||||||
|
<SelectPrimitive.Content
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative z-50 max-h-96 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
|
||||||
|
position === "popper" &&
|
||||||
|
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
position={position}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<SelectScrollUpButton />
|
||||||
|
<SelectPrimitive.Viewport
|
||||||
|
className={cn(
|
||||||
|
"p-1",
|
||||||
|
position === "popper" &&
|
||||||
|
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</SelectPrimitive.Viewport>
|
||||||
|
<SelectScrollDownButton />
|
||||||
|
</SelectPrimitive.Content>
|
||||||
|
</SelectPrimitive.Portal>
|
||||||
|
));
|
||||||
|
SelectContent.displayName = SelectPrimitive.Content.displayName;
|
||||||
|
|
||||||
|
const SelectLabel = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.Label>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SelectPrimitive.Label
|
||||||
|
ref={ref}
|
||||||
|
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
SelectLabel.displayName = SelectPrimitive.Label.displayName;
|
||||||
|
|
||||||
|
const SelectItem = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.Item>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
||||||
|
>(({ className, children, ...props }, ref) => (
|
||||||
|
<SelectPrimitive.Item
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
|
||||||
|
<SelectPrimitive.ItemIndicator>
|
||||||
|
<Check className="h-4 w-4" />
|
||||||
|
</SelectPrimitive.ItemIndicator>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
||||||
|
</SelectPrimitive.Item>
|
||||||
|
));
|
||||||
|
SelectItem.displayName = SelectPrimitive.Item.displayName;
|
||||||
|
|
||||||
|
const SelectSeparator = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SelectPrimitive.Separator>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Separator>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SelectPrimitive.Separator
|
||||||
|
ref={ref}
|
||||||
|
className={cn("-mx-1 my-1 h-px bg-muted", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
|
||||||
|
|
||||||
|
export {
|
||||||
|
Select,
|
||||||
|
SelectGroup,
|
||||||
|
SelectValue,
|
||||||
|
SelectTrigger,
|
||||||
|
SelectContent,
|
||||||
|
SelectLabel,
|
||||||
|
SelectItem,
|
||||||
|
SelectSeparator,
|
||||||
|
SelectScrollUpButton,
|
||||||
|
SelectScrollDownButton,
|
||||||
|
};
|
31
packages/ui/src/separator.tsx
Normal file
31
packages/ui/src/separator.tsx
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import * as React from "react";
|
||||||
|
import * as SeparatorPrimitive from "@radix-ui/react-separator";
|
||||||
|
|
||||||
|
import { cn } from "../lib/utils";
|
||||||
|
|
||||||
|
const Separator = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
|
||||||
|
>(
|
||||||
|
(
|
||||||
|
{ className, orientation = "horizontal", decorative = true, ...props },
|
||||||
|
ref
|
||||||
|
) => (
|
||||||
|
<SeparatorPrimitive.Root
|
||||||
|
ref={ref}
|
||||||
|
decorative={decorative}
|
||||||
|
orientation={orientation}
|
||||||
|
className={cn(
|
||||||
|
"shrink-0 bg-border",
|
||||||
|
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
Separator.displayName = SeparatorPrimitive.Root.displayName;
|
||||||
|
|
||||||
|
export { Separator };
|
@@ -62,7 +62,7 @@
|
|||||||
|
|
||||||
--border: 217.2 32.6% 17.5%;
|
--border: 217.2 32.6% 17.5%;
|
||||||
--input: 217.2 32.6% 17.5%;
|
--input: 217.2 32.6% 17.5%;
|
||||||
--ring: 212.7 26.8% 83.9%;
|
--ring: 217.2 32.6% 17.5%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
326
pnpm-lock.yaml
generated
326
pnpm-lock.yaml
generated
@@ -114,7 +114,7 @@ importers:
|
|||||||
version: 11.0.0-next-beta.318(@trpc/server@11.0.0-next-beta.318)
|
version: 11.0.0-next-beta.318(@trpc/server@11.0.0-next-beta.318)
|
||||||
'@trpc/next':
|
'@trpc/next':
|
||||||
specifier: next
|
specifier: next
|
||||||
version: 11.0.0-next-beta.318(@tanstack/react-query@5.28.4)(@trpc/client@11.0.0-next-beta.318)(@trpc/react-query@11.0.0-next-beta.318)(@trpc/server@11.0.0-next-beta.318)(next@14.1.3)(react-dom@18.2.0)(react@18.2.0)
|
version: 11.0.0-next-beta.318(@tanstack/react-query@5.28.4)(@trpc/client@11.0.0-next-beta.318)(@trpc/react-query@11.0.0-next-beta.318)(@trpc/server@11.0.0-next-beta.318)(next@14.2.1)(react-dom@18.2.0)(react@18.2.0)
|
||||||
'@trpc/react-query':
|
'@trpc/react-query':
|
||||||
specifier: next
|
specifier: next
|
||||||
version: 11.0.0-next-beta.318(@tanstack/react-query@5.28.4)(@trpc/client@11.0.0-next-beta.318)(@trpc/server@11.0.0-next-beta.318)(react-dom@18.2.0)(react@18.2.0)
|
version: 11.0.0-next-beta.318(@tanstack/react-query@5.28.4)(@trpc/client@11.0.0-next-beta.318)(@trpc/server@11.0.0-next-beta.318)(react-dom@18.2.0)(react@18.2.0)
|
||||||
@@ -136,18 +136,24 @@ importers:
|
|||||||
lucide-react:
|
lucide-react:
|
||||||
specifier: ^0.359.0
|
specifier: ^0.359.0
|
||||||
version: 0.359.0(react@18.2.0)
|
version: 0.359.0(react@18.2.0)
|
||||||
|
mime-types:
|
||||||
|
specifier: ^2.1.35
|
||||||
|
version: 2.1.35
|
||||||
next:
|
next:
|
||||||
specifier: ^14.1.3
|
specifier: ^14.2.1
|
||||||
version: 14.1.3(react-dom@18.2.0)(react@18.2.0)
|
version: 14.2.1(react-dom@18.2.0)(react@18.2.0)
|
||||||
next-auth:
|
next-auth:
|
||||||
specifier: ^4.24.6
|
specifier: ^4.24.6
|
||||||
version: 4.24.7(next@14.1.3)(react-dom@18.2.0)(react@18.2.0)
|
version: 4.24.7(next@14.2.1)(react-dom@18.2.0)(react@18.2.0)
|
||||||
pnpm:
|
pnpm:
|
||||||
specifier: ^8.15.5
|
specifier: ^8.15.5
|
||||||
version: 8.15.5
|
version: 8.15.5
|
||||||
prisma:
|
prisma:
|
||||||
specifier: ^5.11.0
|
specifier: ^5.11.0
|
||||||
version: 5.11.0
|
version: 5.11.0
|
||||||
|
query-string:
|
||||||
|
specifier: ^9.0.0
|
||||||
|
version: 9.0.0
|
||||||
react:
|
react:
|
||||||
specifier: 18.2.0
|
specifier: 18.2.0
|
||||||
version: 18.2.0
|
version: 18.2.0
|
||||||
@@ -173,6 +179,9 @@ importers:
|
|||||||
'@types/eslint':
|
'@types/eslint':
|
||||||
specifier: ^8.56.2
|
specifier: ^8.56.2
|
||||||
version: 8.56.5
|
version: 8.56.5
|
||||||
|
'@types/mime-types':
|
||||||
|
specifier: ^2.1.4
|
||||||
|
version: 2.1.4
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^20.11.20
|
specifier: ^20.11.20
|
||||||
version: 20.11.27
|
version: 20.11.27
|
||||||
@@ -275,6 +284,12 @@ importers:
|
|||||||
'@radix-ui/react-label':
|
'@radix-ui/react-label':
|
||||||
specifier: ^2.0.2
|
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)
|
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-select':
|
||||||
|
specifier: ^2.0.0
|
||||||
|
version: 2.0.0(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0)
|
||||||
|
'@radix-ui/react-separator':
|
||||||
|
specifier: ^1.0.3
|
||||||
|
version: 1.0.3(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0)
|
||||||
'@radix-ui/react-slot':
|
'@radix-ui/react-slot':
|
||||||
specifier: ^1.0.2
|
specifier: ^1.0.2
|
||||||
version: 1.0.2(@types/react@18.2.66)(react@18.2.0)
|
version: 1.0.2(@types/react@18.2.66)(react@18.2.0)
|
||||||
@@ -1588,14 +1603,14 @@ packages:
|
|||||||
resolution: {integrity: sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug==}
|
resolution: {integrity: sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@next/env@14.1.3:
|
|
||||||
resolution: {integrity: sha512-VhgXTvrgeBRxNPjyfBsDIMvgsKDxjlpw4IAUsHCX8Gjl1vtHUYRT3+xfQ/wwvLPDd/6kqfLqk9Pt4+7gysuCKQ==}
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/@next/env@14.1.4:
|
/@next/env@14.1.4:
|
||||||
resolution: {integrity: sha512-e7X7bbn3Z6DWnDi75UWn+REgAbLEqxI8Tq2pkFOFAMpWAWApz/YCUhtWMWn410h8Q2fYiYL7Yg5OlxMOCfFjJQ==}
|
resolution: {integrity: sha512-e7X7bbn3Z6DWnDi75UWn+REgAbLEqxI8Tq2pkFOFAMpWAWApz/YCUhtWMWn410h8Q2fYiYL7Yg5OlxMOCfFjJQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@next/env@14.2.1:
|
||||||
|
resolution: {integrity: sha512-qsHJle3GU3CmVx7pUoXcghX4sRN+vINkbLdH611T8ZlsP//grzqVW87BSUgOZeSAD4q7ZdZicdwNe/20U2janA==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@next/eslint-plugin-next@14.1.3:
|
/@next/eslint-plugin-next@14.1.3:
|
||||||
resolution: {integrity: sha512-VCnZI2cy77Yaj3L7Uhs3+44ikMM1VD/fBMwvTBb3hIaTIuqa+DmG4dhUDq+MASu3yx97KhgsVJbsas0XuiKyww==}
|
resolution: {integrity: sha512-VCnZI2cy77Yaj3L7Uhs3+44ikMM1VD/fBMwvTBb3hIaTIuqa+DmG4dhUDq+MASu3yx97KhgsVJbsas0XuiKyww==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -1608,15 +1623,6 @@ packages:
|
|||||||
glob: 10.3.10
|
glob: 10.3.10
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@next/swc-darwin-arm64@14.1.3:
|
|
||||||
resolution: {integrity: sha512-LALu0yIBPRiG9ANrD5ncB3pjpO0Gli9ZLhxdOu6ZUNf3x1r3ea1rd9Q+4xxUkGrUXLqKVK9/lDkpYIJaCJ6AHQ==}
|
|
||||||
engines: {node: '>= 10'}
|
|
||||||
cpu: [arm64]
|
|
||||||
os: [darwin]
|
|
||||||
requiresBuild: true
|
|
||||||
dev: false
|
|
||||||
optional: true
|
|
||||||
|
|
||||||
/@next/swc-darwin-arm64@14.1.4:
|
/@next/swc-darwin-arm64@14.1.4:
|
||||||
resolution: {integrity: sha512-ubmUkbmW65nIAOmoxT1IROZdmmJMmdYvXIe8211send9ZYJu+SqxSnJM4TrPj9wmL6g9Atvj0S/2cFmMSS99jg==}
|
resolution: {integrity: sha512-ubmUkbmW65nIAOmoxT1IROZdmmJMmdYvXIe8211send9ZYJu+SqxSnJM4TrPj9wmL6g9Atvj0S/2cFmMSS99jg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
@@ -1626,10 +1632,10 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-darwin-x64@14.1.3:
|
/@next/swc-darwin-arm64@14.2.1:
|
||||||
resolution: {integrity: sha512-E/9WQeXxkqw2dfcn5UcjApFgUq73jqNKaE5bysDm58hEUdUGedVrnRhblhJM7HbCZNhtVl0j+6TXsK0PuzXTCg==}
|
resolution: {integrity: sha512-kGjnjcIJehEcd3rT/3NAATJQndAEELk0J9GmGMXHSC75TMnvpOhONcjNHbjtcWE5HUQnIHy5JVkatrnYm1QhVw==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [arm64]
|
||||||
os: [darwin]
|
os: [darwin]
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: false
|
dev: false
|
||||||
@@ -1644,11 +1650,11 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-linux-arm64-gnu@14.1.3:
|
/@next/swc-darwin-x64@14.2.1:
|
||||||
resolution: {integrity: sha512-USArX9B+3rZSXYLFvgy0NVWQgqh6LHWDmMt38O4lmiJNQcwazeI6xRvSsliDLKt+78KChVacNiwvOMbl6g6BBw==}
|
resolution: {integrity: sha512-dAdWndgdQi7BK2WSXrx4lae7mYcOYjbHJUhvOUnJjMNYrmYhxbbvJ2xElZpxNxdfA6zkqagIB9He2tQk+l16ew==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [darwin]
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
@@ -1662,8 +1668,8 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-linux-arm64-musl@14.1.3:
|
/@next/swc-linux-arm64-gnu@14.2.1:
|
||||||
resolution: {integrity: sha512-esk1RkRBLSIEp1qaQXv1+s6ZdYzuVCnDAZySpa62iFTMGTisCyNQmqyCTL9P+cLJ4N9FKCI3ojtSfsyPHJDQNw==}
|
resolution: {integrity: sha512-2ZctfnyFOGvTkoD6L+DtQtO3BfFz4CapoHnyLTXkOxbZkVRgg3TQBUjTD/xKrO1QWeydeo8AWfZRg8539qNKrg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
@@ -1680,10 +1686,10 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-linux-x64-gnu@14.1.3:
|
/@next/swc-linux-arm64-musl@14.2.1:
|
||||||
resolution: {integrity: sha512-8uOgRlYEYiKo0L8YGeS+3TudHVDWDjPVDUcST+z+dUzgBbTEwSSIaSgF/vkcC1T/iwl4QX9iuUyUdQEl0Kxalg==}
|
resolution: {integrity: sha512-jazZXctiaanemy4r+TPIpFP36t1mMwWCKMsmrTRVChRqE6putyAxZA4PDujx0SnfvZHosjdkx9xIq9BzBB5tWg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [arm64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: false
|
dev: false
|
||||||
@@ -1698,8 +1704,8 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-linux-x64-musl@14.1.3:
|
/@next/swc-linux-x64-gnu@14.2.1:
|
||||||
resolution: {integrity: sha512-DX2zqz05ziElLoxskgHasaJBREC5Y9TJcbR2LYqu4r7naff25B4iXkfXWfcp69uD75/0URmmoSgT8JclJtrBoQ==}
|
resolution: {integrity: sha512-VjCHWCjsAzQAAo8lkBOLEIkBZFdfW+Z18qcQ056kL4KpUYc8o59JhLDCBlhg+hINQRgzQ2UPGma2AURGOH0+Qg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [x64]
|
||||||
os: [linux]
|
os: [linux]
|
||||||
@@ -1716,11 +1722,11 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-win32-arm64-msvc@14.1.3:
|
/@next/swc-linux-x64-musl@14.2.1:
|
||||||
resolution: {integrity: sha512-HjssFsCdsD4GHstXSQxsi2l70F/5FsRTRQp8xNgmQs15SxUfUJRvSI9qKny/jLkY3gLgiCR3+6A7wzzK0DBlfA==}
|
resolution: {integrity: sha512-7HZKYKvAp4nAHiHIbY04finRqjeYvkITOGOurP1aLMexIFG/1+oCnqhGogBdc4lao/lkMW1c+AkwWSzSlLasqw==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [arm64]
|
cpu: [x64]
|
||||||
os: [win32]
|
os: [linux]
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
@@ -1734,10 +1740,10 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-win32-ia32-msvc@14.1.3:
|
/@next/swc-win32-arm64-msvc@14.2.1:
|
||||||
resolution: {integrity: sha512-DRuxD5axfDM1/Ue4VahwSxl1O5rn61hX8/sF0HY8y0iCbpqdxw3rB3QasdHn/LJ6Wb2y5DoWzXcz3L1Cr+Thrw==}
|
resolution: {integrity: sha512-YGHklaJ/Cj/F0Xd8jxgj2p8po4JTCi6H7Z3Yics3xJhm9CPIqtl8erlpK1CLv+HInDqEWfXilqatF8YsLxxA2Q==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [ia32]
|
cpu: [arm64]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: false
|
dev: false
|
||||||
@@ -1752,10 +1758,10 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
/@next/swc-win32-x64-msvc@14.1.3:
|
/@next/swc-win32-ia32-msvc@14.2.1:
|
||||||
resolution: {integrity: sha512-uC2DaDoWH7h1P/aJ4Fok3Xiw6P0Lo4ez7NbowW2VGNXw/Xv6tOuLUcxhBYZxsSUJtpeknCi8/fvnSpyCFp4Rcg==}
|
resolution: {integrity: sha512-o+ISKOlvU/L43ZhtAAfCjwIfcwuZstiHVXq/BDsZwGqQE0h/81td95MPHliWCnFoikzWcYqh+hz54ZB2FIT8RA==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
cpu: [x64]
|
cpu: [ia32]
|
||||||
os: [win32]
|
os: [win32]
|
||||||
requiresBuild: true
|
requiresBuild: true
|
||||||
dev: false
|
dev: false
|
||||||
@@ -1770,6 +1776,15 @@ packages:
|
|||||||
dev: false
|
dev: false
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
/@next/swc-win32-x64-msvc@14.2.1:
|
||||||
|
resolution: {integrity: sha512-GmRoTiLcvCLifujlisknv4zu9/C4i9r0ktsA8E51EMqJL4bD4CpO7lDYr7SrUxCR0tS4RVcrqKmCak24T0ohaw==}
|
||||||
|
engines: {node: '>= 10'}
|
||||||
|
cpu: [x64]
|
||||||
|
os: [win32]
|
||||||
|
requiresBuild: true
|
||||||
|
dev: false
|
||||||
|
optional: true
|
||||||
|
|
||||||
/@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1:
|
/@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1:
|
||||||
resolution: {integrity: sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==}
|
resolution: {integrity: sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -1854,6 +1869,12 @@ packages:
|
|||||||
'@prisma/debug': 5.11.0
|
'@prisma/debug': 5.11.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/number@1.0.1:
|
||||||
|
resolution: {integrity: sha512-T5gIdVO2mmPW3NNhjNgEP3cqMXjXL9UbO0BzWcXfvdBs+BohbQxvd/K5hSVKmn9/lbTdsQVKbUcP5WLCwvUbBg==}
|
||||||
|
dependencies:
|
||||||
|
'@babel/runtime': 7.24.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@radix-ui/primitive@1.0.1:
|
/@radix-ui/primitive@1.0.1:
|
||||||
resolution: {integrity: sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==}
|
resolution: {integrity: sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -2267,6 +2288,68 @@ packages:
|
|||||||
react-dom: 18.2.0(react@18.2.0)
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-select@2.0.0(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-RH5b7af4oHtkcHS7pG6Sgv5rk5Wxa7XI8W5gvB1N/yiuDGZxko1ynvOiVhFM7Cis2A8zxF9bTOUVbRDzPepe6w==}
|
||||||
|
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/number': 1.0.1
|
||||||
|
'@radix-ui/primitive': 1.0.1
|
||||||
|
'@radix-ui/react-collection': 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-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-direction': 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-popper': 1.1.3(@types/react-dom@18.2.22)(@types/react@18.2.66)(react-dom@18.2.0)(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-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-callback-ref': 1.0.1(@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)
|
||||||
|
'@radix-ui/react-use-layout-effect': 1.0.1(@types/react@18.2.66)(react@18.2.0)
|
||||||
|
'@radix-ui/react-use-previous': 1.0.1(@types/react@18.2.66)(react@18.2.0)
|
||||||
|
'@radix-ui/react-visually-hidden': 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
|
||||||
|
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-separator@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-itYmTy/kokS21aiV5+Z56MZB54KrhPgn6eHDKkFeOLR34HMN2s8PaN47qZZAGnvupcjxHaFZnW4pQEh0BvvVuw==}
|
||||||
|
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-slot@1.0.2(@types/react@18.2.66)(react@18.2.0):
|
/@radix-ui/react-slot@1.0.2(@types/react@18.2.66)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==}
|
resolution: {integrity: sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -2439,6 +2522,27 @@ packages:
|
|||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@radix-ui/react-visually-hidden@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-D4w41yN5YRKtu464TLnByKzMDG/JlMPHtfZgQAu9v6mNakUqGUI9vUrfQKz8NK41VMm/xbZbh76NUTVtIYqOMA==}
|
||||||
|
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/rect@1.0.1:
|
/@radix-ui/rect@1.0.1:
|
||||||
resolution: {integrity: sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==}
|
resolution: {integrity: sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -2822,12 +2926,23 @@ packages:
|
|||||||
tslib: 2.6.2
|
tslib: 2.6.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@swc/counter@0.1.3:
|
||||||
|
resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@swc/helpers@0.5.2:
|
/@swc/helpers@0.5.2:
|
||||||
resolution: {integrity: sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==}
|
resolution: {integrity: sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==}
|
||||||
dependencies:
|
dependencies:
|
||||||
tslib: 2.6.2
|
tslib: 2.6.2
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/@swc/helpers@0.5.5:
|
||||||
|
resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==}
|
||||||
|
dependencies:
|
||||||
|
'@swc/counter': 0.1.3
|
||||||
|
tslib: 2.6.2
|
||||||
|
dev: false
|
||||||
|
|
||||||
/@t3-oss/env-core@0.9.2(typescript@5.4.2)(zod@3.22.4):
|
/@t3-oss/env-core@0.9.2(typescript@5.4.2)(zod@3.22.4):
|
||||||
resolution: {integrity: sha512-KgWXljUTHgO3o7GMZQPAD5+P+HqpauMNNHowlm7V2b9IeMitSUpNKwG6xQrup/xARWHTdxRVIl0mSI4wCevQhQ==}
|
resolution: {integrity: sha512-KgWXljUTHgO3o7GMZQPAD5+P+HqpauMNNHowlm7V2b9IeMitSUpNKwG6xQrup/xARWHTdxRVIl0mSI4wCevQhQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
@@ -2876,7 +2991,7 @@ packages:
|
|||||||
'@trpc/server': 11.0.0-next-beta.318
|
'@trpc/server': 11.0.0-next-beta.318
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/@trpc/next@11.0.0-next-beta.318(@tanstack/react-query@5.28.4)(@trpc/client@11.0.0-next-beta.318)(@trpc/react-query@11.0.0-next-beta.318)(@trpc/server@11.0.0-next-beta.318)(next@14.1.3)(react-dom@18.2.0)(react@18.2.0):
|
/@trpc/next@11.0.0-next-beta.318(@tanstack/react-query@5.28.4)(@trpc/client@11.0.0-next-beta.318)(@trpc/react-query@11.0.0-next-beta.318)(@trpc/server@11.0.0-next-beta.318)(next@14.2.1)(react-dom@18.2.0)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-qeWfJ1vPm7GchLmMZz5Gj+mBka0CRci0bCKEhGoG8RSvI/+9GbbhZHKRRDnlsN81CgexJ2e2nULET9ESO6rt+Q==}
|
resolution: {integrity: sha512-qeWfJ1vPm7GchLmMZz5Gj+mBka0CRci0bCKEhGoG8RSvI/+9GbbhZHKRRDnlsN81CgexJ2e2nULET9ESO6rt+Q==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
'@tanstack/react-query': ^5.25.0
|
'@tanstack/react-query': ^5.25.0
|
||||||
@@ -2896,7 +3011,7 @@ packages:
|
|||||||
'@trpc/client': 11.0.0-next-beta.318(@trpc/server@11.0.0-next-beta.318)
|
'@trpc/client': 11.0.0-next-beta.318(@trpc/server@11.0.0-next-beta.318)
|
||||||
'@trpc/react-query': 11.0.0-next-beta.318(@tanstack/react-query@5.28.4)(@trpc/client@11.0.0-next-beta.318)(@trpc/server@11.0.0-next-beta.318)(react-dom@18.2.0)(react@18.2.0)
|
'@trpc/react-query': 11.0.0-next-beta.318(@tanstack/react-query@5.28.4)(@trpc/client@11.0.0-next-beta.318)(@trpc/server@11.0.0-next-beta.318)(react-dom@18.2.0)(react@18.2.0)
|
||||||
'@trpc/server': 11.0.0-next-beta.318
|
'@trpc/server': 11.0.0-next-beta.318
|
||||||
next: 14.1.3(react-dom@18.2.0)(react@18.2.0)
|
next: 14.2.1(react-dom@18.2.0)(react@18.2.0)
|
||||||
react: 18.2.0
|
react: 18.2.0
|
||||||
react-dom: 18.2.0(react@18.2.0)
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
dev: false
|
dev: false
|
||||||
@@ -2992,6 +3107,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
|
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/@types/mime-types@2.1.4:
|
||||||
|
resolution: {integrity: sha512-lfU4b34HOri+kAY5UheuFMWPDOI+OPceBSHZKp69gEyTL/mmJ4cnU6Y/rlme3UL3GyOn6Y42hyIEw0/q8sWx5w==}
|
||||||
|
dev: true
|
||||||
|
|
||||||
/@types/node@20.11.27:
|
/@types/node@20.11.27:
|
||||||
resolution: {integrity: sha512-qyUZfMnCg1KEz57r7pzFtSGt49f6RPkPBis3Vo4PbS7roQEDn22hiHzl/Lo1q4i4hDEgBJmBF/NTNg2XR0HbFg==}
|
resolution: {integrity: sha512-qyUZfMnCg1KEz57r7pzFtSGt49f6RPkPBis3Vo4PbS7roQEDn22hiHzl/Lo1q4i4hDEgBJmBF/NTNg2XR0HbFg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -3987,6 +4106,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==}
|
resolution: {integrity: sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/decode-uri-component@0.4.1:
|
||||||
|
resolution: {integrity: sha512-+8VxcR21HhTy8nOt6jf20w0c9CADrw1O8d+VZ/YzzCt4bJ3uBjw+D1q2osAB8RnpwwaeYBxy0HyKQxD5JBMuuQ==}
|
||||||
|
engines: {node: '>=14.16'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/deep-is@0.1.4:
|
/deep-is@0.1.4:
|
||||||
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
|
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
|
||||||
dev: true
|
dev: true
|
||||||
@@ -4892,6 +5016,11 @@ packages:
|
|||||||
dependencies:
|
dependencies:
|
||||||
to-regex-range: 5.0.1
|
to-regex-range: 5.0.1
|
||||||
|
|
||||||
|
/filter-obj@5.1.0:
|
||||||
|
resolution: {integrity: sha512-qWeTREPoT7I0bifpPUXtxkZJ1XJzxWtfoWWkdVGqa+eCr3SHW/Ocp89o8vLvbUuQnadybJpjOKu4V+RwO6sGng==}
|
||||||
|
engines: {node: '>=14.16'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/find-up@4.1.0:
|
/find-up@4.1.0:
|
||||||
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
|
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -5670,6 +5799,18 @@ packages:
|
|||||||
braces: 3.0.2
|
braces: 3.0.2
|
||||||
picomatch: 2.3.1
|
picomatch: 2.3.1
|
||||||
|
|
||||||
|
/mime-db@1.52.0:
|
||||||
|
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
|
/mime-types@2.1.35:
|
||||||
|
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
dependencies:
|
||||||
|
mime-db: 1.52.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/min-indent@1.0.1:
|
/min-indent@1.0.1:
|
||||||
resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
|
resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@@ -5719,7 +5860,7 @@ packages:
|
|||||||
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/next-auth@4.24.7(next@14.1.3)(react-dom@18.2.0)(react@18.2.0):
|
/next-auth@4.24.7(next@14.2.1)(react-dom@18.2.0)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-iChjE8ov/1K/z98gdKbn2Jw+2vLgJtVV39X+rCP5SGnVQuco7QOr19FRNGMIrD8d3LYhHWV9j9sKLzq1aDWWQQ==}
|
resolution: {integrity: sha512-iChjE8ov/1K/z98gdKbn2Jw+2vLgJtVV39X+rCP5SGnVQuco7QOr19FRNGMIrD8d3LYhHWV9j9sKLzq1aDWWQQ==}
|
||||||
peerDependencies:
|
peerDependencies:
|
||||||
next: ^12.2.5 || ^13 || ^14
|
next: ^12.2.5 || ^13 || ^14
|
||||||
@@ -5734,7 +5875,7 @@ packages:
|
|||||||
'@panva/hkdf': 1.1.1
|
'@panva/hkdf': 1.1.1
|
||||||
cookie: 0.5.0
|
cookie: 0.5.0
|
||||||
jose: 4.15.5
|
jose: 4.15.5
|
||||||
next: 14.1.3(react-dom@18.2.0)(react@18.2.0)
|
next: 14.2.1(react-dom@18.2.0)(react@18.2.0)
|
||||||
oauth: 0.9.15
|
oauth: 0.9.15
|
||||||
openid-client: 5.6.5
|
openid-client: 5.6.5
|
||||||
preact: 10.19.6
|
preact: 10.19.6
|
||||||
@@ -5754,45 +5895,6 @@ packages:
|
|||||||
react-dom: 18.2.0(react@18.2.0)
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/next@14.1.3(react-dom@18.2.0)(react@18.2.0):
|
|
||||||
resolution: {integrity: sha512-oexgMV2MapI0UIWiXKkixF8J8ORxpy64OuJ/J9oVUmIthXOUCcuVEZX+dtpgq7wIfIqtBwQsKEDXejcjTsan9g==}
|
|
||||||
engines: {node: '>=18.17.0'}
|
|
||||||
hasBin: true
|
|
||||||
peerDependencies:
|
|
||||||
'@opentelemetry/api': ^1.1.0
|
|
||||||
react: ^18.2.0
|
|
||||||
react-dom: ^18.2.0
|
|
||||||
sass: ^1.3.0
|
|
||||||
peerDependenciesMeta:
|
|
||||||
'@opentelemetry/api':
|
|
||||||
optional: true
|
|
||||||
sass:
|
|
||||||
optional: true
|
|
||||||
dependencies:
|
|
||||||
'@next/env': 14.1.3
|
|
||||||
'@swc/helpers': 0.5.2
|
|
||||||
busboy: 1.6.0
|
|
||||||
caniuse-lite: 1.0.30001597
|
|
||||||
graceful-fs: 4.2.11
|
|
||||||
postcss: 8.4.31
|
|
||||||
react: 18.2.0
|
|
||||||
react-dom: 18.2.0(react@18.2.0)
|
|
||||||
styled-jsx: 5.1.1(react@18.2.0)
|
|
||||||
optionalDependencies:
|
|
||||||
'@next/swc-darwin-arm64': 14.1.3
|
|
||||||
'@next/swc-darwin-x64': 14.1.3
|
|
||||||
'@next/swc-linux-arm64-gnu': 14.1.3
|
|
||||||
'@next/swc-linux-arm64-musl': 14.1.3
|
|
||||||
'@next/swc-linux-x64-gnu': 14.1.3
|
|
||||||
'@next/swc-linux-x64-musl': 14.1.3
|
|
||||||
'@next/swc-win32-arm64-msvc': 14.1.3
|
|
||||||
'@next/swc-win32-ia32-msvc': 14.1.3
|
|
||||||
'@next/swc-win32-x64-msvc': 14.1.3
|
|
||||||
transitivePeerDependencies:
|
|
||||||
- '@babel/core'
|
|
||||||
- babel-plugin-macros
|
|
||||||
dev: false
|
|
||||||
|
|
||||||
/next@14.1.4(react-dom@18.2.0)(react@18.2.0):
|
/next@14.1.4(react-dom@18.2.0)(react@18.2.0):
|
||||||
resolution: {integrity: sha512-1WTaXeSrUwlz/XcnhGTY7+8eiaFvdet5z9u3V2jb+Ek1vFo0VhHKSAIJvDWfQpttWjnyw14kBeq28TPq7bTeEQ==}
|
resolution: {integrity: sha512-1WTaXeSrUwlz/XcnhGTY7+8eiaFvdet5z9u3V2jb+Ek1vFo0VhHKSAIJvDWfQpttWjnyw14kBeq28TPq7bTeEQ==}
|
||||||
engines: {node: '>=18.17.0'}
|
engines: {node: '>=18.17.0'}
|
||||||
@@ -5832,6 +5934,48 @@ packages:
|
|||||||
- babel-plugin-macros
|
- babel-plugin-macros
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
|
/next@14.2.1(react-dom@18.2.0)(react@18.2.0):
|
||||||
|
resolution: {integrity: sha512-SF3TJnKdH43PMkCcErLPv+x/DY1YCklslk3ZmwaVoyUfDgHKexuKlf9sEfBQ69w+ue8jQ3msLb+hSj1T19hGag==}
|
||||||
|
engines: {node: '>=18.17.0'}
|
||||||
|
hasBin: true
|
||||||
|
peerDependencies:
|
||||||
|
'@opentelemetry/api': ^1.1.0
|
||||||
|
'@playwright/test': ^1.41.2
|
||||||
|
react: ^18.2.0
|
||||||
|
react-dom: ^18.2.0
|
||||||
|
sass: ^1.3.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
'@opentelemetry/api':
|
||||||
|
optional: true
|
||||||
|
'@playwright/test':
|
||||||
|
optional: true
|
||||||
|
sass:
|
||||||
|
optional: true
|
||||||
|
dependencies:
|
||||||
|
'@next/env': 14.2.1
|
||||||
|
'@swc/helpers': 0.5.5
|
||||||
|
busboy: 1.6.0
|
||||||
|
caniuse-lite: 1.0.30001597
|
||||||
|
graceful-fs: 4.2.11
|
||||||
|
postcss: 8.4.31
|
||||||
|
react: 18.2.0
|
||||||
|
react-dom: 18.2.0(react@18.2.0)
|
||||||
|
styled-jsx: 5.1.1(react@18.2.0)
|
||||||
|
optionalDependencies:
|
||||||
|
'@next/swc-darwin-arm64': 14.2.1
|
||||||
|
'@next/swc-darwin-x64': 14.2.1
|
||||||
|
'@next/swc-linux-arm64-gnu': 14.2.1
|
||||||
|
'@next/swc-linux-arm64-musl': 14.2.1
|
||||||
|
'@next/swc-linux-x64-gnu': 14.2.1
|
||||||
|
'@next/swc-linux-x64-musl': 14.2.1
|
||||||
|
'@next/swc-win32-arm64-msvc': 14.2.1
|
||||||
|
'@next/swc-win32-ia32-msvc': 14.2.1
|
||||||
|
'@next/swc-win32-x64-msvc': 14.2.1
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- '@babel/core'
|
||||||
|
- babel-plugin-macros
|
||||||
|
dev: false
|
||||||
|
|
||||||
/node-releases@2.0.14:
|
/node-releases@2.0.14:
|
||||||
resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==}
|
resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==}
|
||||||
dev: true
|
dev: true
|
||||||
@@ -6324,6 +6468,15 @@ packages:
|
|||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/query-string@9.0.0:
|
||||||
|
resolution: {integrity: sha512-4EWwcRGsO2H+yzq6ddHcVqkCQ2EFUSfDMEjF8ryp8ReymyZhIuaFRGLomeOQLkrzacMHoyky2HW0Qe30UbzkKw==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
dependencies:
|
||||||
|
decode-uri-component: 0.4.1
|
||||||
|
filter-obj: 5.1.0
|
||||||
|
split-on-first: 3.0.0
|
||||||
|
dev: false
|
||||||
|
|
||||||
/queue-microtask@1.2.3:
|
/queue-microtask@1.2.3:
|
||||||
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
|
||||||
|
|
||||||
@@ -6753,6 +6906,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==}
|
resolution: {integrity: sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
|
/split-on-first@3.0.0:
|
||||||
|
resolution: {integrity: sha512-qxQJTx2ryR0Dw0ITYyekNQWpz6f8dGd7vffGNflQQ3Iqj9NJ6qiZ7ELpZsJ/QBhIVAiDfXdag3+Gp8RvWa62AA==}
|
||||||
|
engines: {node: '>=12'}
|
||||||
|
dev: false
|
||||||
|
|
||||||
/streamsearch@1.1.0:
|
/streamsearch@1.1.0:
|
||||||
resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
|
resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
|
||||||
engines: {node: '>=10.0.0'}
|
engines: {node: '>=10.0.0'}
|
||||||
|
Reference in New Issue
Block a user