diff --git a/.env.example b/.env.example
index eba6428..de7629b 100644
--- a/.env.example
+++ b/.env.example
@@ -16,6 +16,3 @@ VITE_WS_URL=
UID=1000
GID=1000
-
-RESEND_API_KEY=
-EMAIL_FROM=mail@example.com
diff --git a/apps/api/package.json b/apps/api/package.json
index 1a7cdc3..12bdbe2 100644
--- a/apps/api/package.json
+++ b/apps/api/package.json
@@ -18,7 +18,6 @@
"express": "^5.2.1",
"express-rate-limit": "^8.4.0",
"helmet": "^8.1.0",
- "resend": "^6.12.2",
"ws": "^8.20.0"
},
"devDependencies": {
diff --git a/apps/api/src/lib/auth.ts b/apps/api/src/lib/auth.ts
index 601708e..8e2b818 100644
--- a/apps/api/src/lib/auth.ts
+++ b/apps/api/src/lib/auth.ts
@@ -1,12 +1,8 @@
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
-import { Resend } from "resend";
import { db } from "@lila/db";
import * as schema from "@lila/db/schema";
-const resend = new Resend(process.env["RESEND_API_KEY"]);
-const emailFrom = process.env["EMAIL_FROM"] ?? "noreply@lilastudy.com";
-
export const auth = betterAuth({
baseURL: process.env["BETTER_AUTH_URL"] || "http://localhost:3000",
advanced: {
@@ -20,42 +16,6 @@ export const auth = betterAuth({
},
},
database: drizzleAdapter(db, { provider: "pg", schema }),
- emailAndPassword: {
- enabled: true,
- requireEmailVerification: true,
- sendResetPassword: async ({
- user,
- url,
- }: {
- user: { email: string };
- url: string;
- }) => {
- await resend.emails.send({
- from: emailFrom,
- to: user.email,
- subject: "Reset your lila password",
- html: `
Click here to reset your password. This link expires in 1 hour.
`,
- });
- },
- },
- emailVerification: {
- sendOnSignUp: true,
- autoSignInAfterVerification: true,
- sendVerificationEmail: async ({
- user,
- url,
- }: {
- user: { email: string };
- url: string;
- }) => {
- await resend.emails.send({
- from: emailFrom,
- to: user.email,
- subject: "Verify your lila account",
- html: `Click here to verify your email address.
`,
- });
- },
- },
trustedOrigins: [process.env["CORS_ORIGIN"] || "http://localhost:5173"],
socialProviders: {
google: {
diff --git a/apps/web/package.json b/apps/web/package.json
index 8068c8f..363a83c 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -17,7 +17,6 @@
"better-auth": "^1.6.2",
"react": "^19.2.4",
"react-dom": "^19.2.4",
- "sonner": "^2.0.7",
"tailwindcss": "^4.2.2"
},
"devDependencies": {
diff --git a/apps/web/src/components/auth/AuthModal.tsx b/apps/web/src/components/auth/AuthModal.tsx
deleted file mode 100644
index 01b2c20..0000000
--- a/apps/web/src/components/auth/AuthModal.tsx
+++ /dev/null
@@ -1,249 +0,0 @@
-import { useState, useEffect } from "react";
-import { toast } from "sonner";
-import { authClient } from "../../lib/auth-client";
-
-type Tab = "login" | "register";
-
-type AuthModalProps = { onClose: () => void; onSuccess: () => void };
-
-type LoginFormProps = { onSuccess: () => void };
-
-const LoginForm = ({ onSuccess }: LoginFormProps) => {
- const [email, setEmail] = useState("");
- const [password, setPassword] = useState("");
- const [isPending, setIsPending] = useState(false);
-
- const handleSubmit = async () => {
- setIsPending(true);
- await authClient.signIn.email(
- { email, password },
- {
- onSuccess: () => {
- toast.success("Welcome back!");
- onSuccess();
- },
- onError: (ctx) => {
- toast.error(ctx.error.message ?? "Something went wrong.");
- setIsPending(false);
- },
- },
- );
- };
-
- return (
-
- );
-};
-
-type RegisterFormProps = { onSuccess: () => void };
-
-const RegisterForm = ({ onSuccess }: RegisterFormProps) => {
- const [name, setName] = useState("");
- const [email, setEmail] = useState("");
- const [password, setPassword] = useState("");
- const [isPending, setIsPending] = useState(false);
-
- const handleSubmit = async () => {
- setIsPending(true);
- await authClient.signUp.email(
- { name, email, password },
- {
- onSuccess: () => {
- toast.success("Check your email to verify your account.");
- onSuccess();
- },
- onError: (ctx) => {
- toast.error(ctx.error.message ?? "Something went wrong.");
- setIsPending(false);
- },
- },
- );
- };
-
- return (
-
- );
-};
-
-type SocialButtonsProps = { onSuccess: () => void };
-
-const SocialButtons = ({ onSuccess }: SocialButtonsProps) => {
- const handleSocial = (provider: "google" | "github") => {
- void authClient.signIn.social(
- { provider, callbackURL: window.location.origin },
- {
- onSuccess,
- onError: (ctx) => {
- toast.error(ctx.error.message ?? "Something went wrong.");
- },
- },
- );
- };
-
- return (
-
-
-
-
- or continue with
-
-
-
-
-
-
- );
-};
-
-export const AuthModal = ({ onClose, onSuccess }: AuthModalProps) => {
- const [tab, setTab] = useState("login");
-
- useEffect(() => {
- const handleKeyDown = (e: KeyboardEvent) => {
- if (e.key === "Escape") onClose();
- };
- document.addEventListener("keydown", handleKeyDown);
- return () => document.removeEventListener("keydown", handleKeyDown);
- }, [onClose]);
-
- return (
-
-
e.stopPropagation()}
- >
- {/* Close button */}
-
-
- {/* Header */}
-
-
- lila
-
-
-
- {/* Tabs */}
-
- {(["login", "register"] as Tab[]).map((t) => (
-
- ))}
-
-
- {tab === "login" ? (
-
- ) : (
-
- )}
-
- {/* Social */}
-
-
-
- );
-};
diff --git a/apps/web/src/components/landing/Hero.tsx b/apps/web/src/components/landing/Hero.tsx
index 238d313..81f7bba 100644
--- a/apps/web/src/components/landing/Hero.tsx
+++ b/apps/web/src/components/landing/Hero.tsx
@@ -66,15 +66,13 @@ const Hero = () => {
) : (
<>
Get started
Log in
diff --git a/apps/web/src/components/navbar/NavAuth.tsx b/apps/web/src/components/navbar/NavAuth.tsx
index f65f569..22b8479 100644
--- a/apps/web/src/components/navbar/NavAuth.tsx
+++ b/apps/web/src/components/navbar/NavAuth.tsx
@@ -24,14 +24,13 @@ const NavAuth = () => {
) : (
- Login
+ Sign in
)}
diff --git a/apps/web/src/components/navbar/NavLogin.tsx b/apps/web/src/components/navbar/NavLogin.tsx
new file mode 100644
index 0000000..f28bfdd
--- /dev/null
+++ b/apps/web/src/components/navbar/NavLogin.tsx
@@ -0,0 +1,17 @@
+import { Link } from "@tanstack/react-router";
+
+const NavLogin = () => {
+ return (
+
+ Login
+
+ );
+};
+
+export default NavLogin;
diff --git a/apps/web/src/routeTree.gen.ts b/apps/web/src/routeTree.gen.ts
index a85f2f2..96c3044 100644
--- a/apps/web/src/routeTree.gen.ts
+++ b/apps/web/src/routeTree.gen.ts
@@ -9,21 +9,15 @@
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
import { Route as rootRouteImport } from './routes/__root'
-import { Route as ResetPasswordRouteImport } from './routes/reset-password'
import { Route as PlayRouteImport } from './routes/play'
import { Route as MultiplayerRouteImport } from './routes/multiplayer'
-import { Route as ForgotPasswordRouteImport } from './routes/forgot-password'
+import { Route as LoginRouteImport } from './routes/login'
import { Route as AboutRouteImport } from './routes/about'
import { Route as IndexRouteImport } from './routes/index'
import { Route as MultiplayerIndexRouteImport } from './routes/multiplayer/index'
import { Route as MultiplayerLobbyCodeRouteImport } from './routes/multiplayer/lobby.$code'
import { Route as MultiplayerGameCodeRouteImport } from './routes/multiplayer/game.$code'
-const ResetPasswordRoute = ResetPasswordRouteImport.update({
- id: '/reset-password',
- path: '/reset-password',
- getParentRoute: () => rootRouteImport,
-} as any)
const PlayRoute = PlayRouteImport.update({
id: '/play',
path: '/play',
@@ -34,9 +28,9 @@ const MultiplayerRoute = MultiplayerRouteImport.update({
path: '/multiplayer',
getParentRoute: () => rootRouteImport,
} as any)
-const ForgotPasswordRoute = ForgotPasswordRouteImport.update({
- id: '/forgot-password',
- path: '/forgot-password',
+const LoginRoute = LoginRouteImport.update({
+ id: '/login',
+ path: '/login',
getParentRoute: () => rootRouteImport,
} as any)
const AboutRoute = AboutRouteImport.update({
@@ -68,10 +62,9 @@ const MultiplayerGameCodeRoute = MultiplayerGameCodeRouteImport.update({
export interface FileRoutesByFullPath {
'/': typeof IndexRoute
'/about': typeof AboutRoute
- '/forgot-password': typeof ForgotPasswordRoute
+ '/login': typeof LoginRoute
'/multiplayer': typeof MultiplayerRouteWithChildren
'/play': typeof PlayRoute
- '/reset-password': typeof ResetPasswordRoute
'/multiplayer/': typeof MultiplayerIndexRoute
'/multiplayer/game/$code': typeof MultiplayerGameCodeRoute
'/multiplayer/lobby/$code': typeof MultiplayerLobbyCodeRoute
@@ -79,9 +72,8 @@ export interface FileRoutesByFullPath {
export interface FileRoutesByTo {
'/': typeof IndexRoute
'/about': typeof AboutRoute
- '/forgot-password': typeof ForgotPasswordRoute
+ '/login': typeof LoginRoute
'/play': typeof PlayRoute
- '/reset-password': typeof ResetPasswordRoute
'/multiplayer': typeof MultiplayerIndexRoute
'/multiplayer/game/$code': typeof MultiplayerGameCodeRoute
'/multiplayer/lobby/$code': typeof MultiplayerLobbyCodeRoute
@@ -90,10 +82,9 @@ export interface FileRoutesById {
__root__: typeof rootRouteImport
'/': typeof IndexRoute
'/about': typeof AboutRoute
- '/forgot-password': typeof ForgotPasswordRoute
+ '/login': typeof LoginRoute
'/multiplayer': typeof MultiplayerRouteWithChildren
'/play': typeof PlayRoute
- '/reset-password': typeof ResetPasswordRoute
'/multiplayer/': typeof MultiplayerIndexRoute
'/multiplayer/game/$code': typeof MultiplayerGameCodeRoute
'/multiplayer/lobby/$code': typeof MultiplayerLobbyCodeRoute
@@ -103,10 +94,9 @@ export interface FileRouteTypes {
fullPaths:
| '/'
| '/about'
- | '/forgot-password'
+ | '/login'
| '/multiplayer'
| '/play'
- | '/reset-password'
| '/multiplayer/'
| '/multiplayer/game/$code'
| '/multiplayer/lobby/$code'
@@ -114,9 +104,8 @@ export interface FileRouteTypes {
to:
| '/'
| '/about'
- | '/forgot-password'
+ | '/login'
| '/play'
- | '/reset-password'
| '/multiplayer'
| '/multiplayer/game/$code'
| '/multiplayer/lobby/$code'
@@ -124,10 +113,9 @@ export interface FileRouteTypes {
| '__root__'
| '/'
| '/about'
- | '/forgot-password'
+ | '/login'
| '/multiplayer'
| '/play'
- | '/reset-password'
| '/multiplayer/'
| '/multiplayer/game/$code'
| '/multiplayer/lobby/$code'
@@ -136,21 +124,13 @@ export interface FileRouteTypes {
export interface RootRouteChildren {
IndexRoute: typeof IndexRoute
AboutRoute: typeof AboutRoute
- ForgotPasswordRoute: typeof ForgotPasswordRoute
+ LoginRoute: typeof LoginRoute
MultiplayerRoute: typeof MultiplayerRouteWithChildren
PlayRoute: typeof PlayRoute
- ResetPasswordRoute: typeof ResetPasswordRoute
}
declare module '@tanstack/react-router' {
interface FileRoutesByPath {
- '/reset-password': {
- id: '/reset-password'
- path: '/reset-password'
- fullPath: '/reset-password'
- preLoaderRoute: typeof ResetPasswordRouteImport
- parentRoute: typeof rootRouteImport
- }
'/play': {
id: '/play'
path: '/play'
@@ -165,11 +145,11 @@ declare module '@tanstack/react-router' {
preLoaderRoute: typeof MultiplayerRouteImport
parentRoute: typeof rootRouteImport
}
- '/forgot-password': {
- id: '/forgot-password'
- path: '/forgot-password'
- fullPath: '/forgot-password'
- preLoaderRoute: typeof ForgotPasswordRouteImport
+ '/login': {
+ id: '/login'
+ path: '/login'
+ fullPath: '/login'
+ preLoaderRoute: typeof LoginRouteImport
parentRoute: typeof rootRouteImport
}
'/about': {
@@ -229,10 +209,9 @@ const MultiplayerRouteWithChildren = MultiplayerRoute._addFileChildren(
const rootRouteChildren: RootRouteChildren = {
IndexRoute: IndexRoute,
AboutRoute: AboutRoute,
- ForgotPasswordRoute: ForgotPasswordRoute,
+ LoginRoute: LoginRoute,
MultiplayerRoute: MultiplayerRouteWithChildren,
PlayRoute: PlayRoute,
- ResetPasswordRoute: ResetPasswordRoute,
}
export const routeTree = rootRouteImport
._addFileChildren(rootRouteChildren)
diff --git a/apps/web/src/routes/__root.tsx b/apps/web/src/routes/__root.tsx
index 7df9998..c672ced 100644
--- a/apps/web/src/routes/__root.tsx
+++ b/apps/web/src/routes/__root.tsx
@@ -1,39 +1,16 @@
-import {
- createRootRoute,
- Outlet,
- useNavigate,
- useSearch,
-} from "@tanstack/react-router";
+import { createRootRoute, Outlet } from "@tanstack/react-router";
import { TanStackRouterDevtools } from "@tanstack/react-router-devtools";
-import { Toaster } from "sonner";
import Navbar from "../components/navbar/NavBar";
import NotFound from "../components/NotFound";
import RootError from "../components/RootError";
-import { AuthModal } from "../components/auth/AuthModal";
-import { AuthModalSearchSchema } from "@lila/shared";
const RootLayout = () => {
- const navigate = useNavigate();
- const { modal, redirect } = useSearch({ from: "__root__" });
-
- const handleClose = () => {
- void navigate({ to: "/", search: {} });
- };
-
- const handleSuccess = () => {
- void navigate({ to: (redirect as string) ?? "/", search: {} });
- };
-
return (
<>
- {modal === "auth" && (
-
- )}
-
>
);
@@ -43,5 +20,4 @@ export const Route = createRootRoute({
component: RootLayout,
notFoundComponent: NotFound,
errorComponent: RootError,
- validateSearch: AuthModalSearchSchema,
});
diff --git a/apps/web/src/routes/forgot-password.tsx b/apps/web/src/routes/forgot-password.tsx
deleted file mode 100644
index 9d929a8..0000000
--- a/apps/web/src/routes/forgot-password.tsx
+++ /dev/null
@@ -1,74 +0,0 @@
-import { useState } from "react";
-import { createFileRoute, Link } from "@tanstack/react-router";
-import { authClient } from "../lib/auth-client";
-import { toast } from "sonner";
-
-function ForgotPasswordPage() {
- const [email, setEmail] = useState("");
- const [isPending, setIsPending] = useState(false);
-
- const handleSubmit = async () => {
- setIsPending(true);
- await authClient.requestPasswordReset(
- { email, redirectTo: `${window.location.origin}/reset-password` },
- {
- onSuccess: () => {
- toast.success("Check your email for a reset link.");
- setIsPending(false);
- },
- onError: (ctx) => {
- toast.error(ctx.error.message ?? "Something went wrong.");
- setIsPending(false);
- },
- },
- );
- };
-
- return (
-
-
-
- Forgot password
-
-
- Enter your email and we'll send you a reset link.
-
-
-
-
-
-
- Back to home
-
-
- );
-}
-
-export const Route = createFileRoute("/forgot-password")({
- component: ForgotPasswordPage,
-});
diff --git a/apps/web/src/routes/login.tsx b/apps/web/src/routes/login.tsx
new file mode 100644
index 0000000..8451d41
--- /dev/null
+++ b/apps/web/src/routes/login.tsx
@@ -0,0 +1,46 @@
+import { createFileRoute, useNavigate } from "@tanstack/react-router";
+import { signIn, useSession } from "../lib/auth-client";
+
+const LoginPage = () => {
+ const { data: session, isPending } = useSession();
+ const navigate = useNavigate();
+
+ if (isPending) return Loading...
;
+
+ if (session) {
+ void navigate({ to: "/" });
+ return null;
+ }
+
+ return (
+
+
sign in to lila
+
+
+
+ );
+};
+
+export const Route = createFileRoute("/login")({ component: LoginPage });
diff --git a/apps/web/src/routes/multiplayer.tsx b/apps/web/src/routes/multiplayer.tsx
index 0008b37..7adffd6 100644
--- a/apps/web/src/routes/multiplayer.tsx
+++ b/apps/web/src/routes/multiplayer.tsx
@@ -14,10 +14,7 @@ export const Route = createFileRoute("/multiplayer")({
beforeLoad: async () => {
const { data: session } = await authClient.getSession();
if (!session) {
- throw redirect({
- to: "/",
- search: { modal: "auth", redirect: "/multiplayer" },
- });
+ throw redirect({ to: "/login" });
}
return { session };
},
diff --git a/apps/web/src/routes/play.tsx b/apps/web/src/routes/play.tsx
index bc4cde3..df4959d 100644
--- a/apps/web/src/routes/play.tsx
+++ b/apps/web/src/routes/play.tsx
@@ -132,7 +132,7 @@ export const Route = createFileRoute("/play")({
beforeLoad: async () => {
const { data: session } = await authClient.getSession();
if (!session) {
- throw redirect({ to: "/", search: { modal: "auth", redirect: "/play" } });
+ throw redirect({ to: "/login" });
}
},
});
diff --git a/apps/web/src/routes/reset-password.tsx b/apps/web/src/routes/reset-password.tsx
deleted file mode 100644
index 837949b..0000000
--- a/apps/web/src/routes/reset-password.tsx
+++ /dev/null
@@ -1,91 +0,0 @@
-import { useState } from "react";
-import { createFileRoute, Link, useNavigate } from "@tanstack/react-router";
-import { authClient } from "../lib/auth-client";
-import { toast } from "sonner";
-import { ResetPasswordSearchSchema } from "@lila/shared";
-
-function ResetPasswordPage() {
- const { token } = Route.useSearch();
- const navigate = useNavigate();
- const [password, setPassword] = useState("");
- const [isPending, setIsPending] = useState(false);
-
- if (!token) {
- return (
-
-
- Invalid link
-
-
- This reset link is invalid or has expired.
-
-
- Request a new one
-
-
- );
- }
-
- const handleSubmit = async () => {
- setIsPending(true);
- await authClient.resetPassword(
- { newPassword: password, token },
- {
- onSuccess: () => {
- toast.success("Password updated. You can now sign in.");
- void navigate({ to: "/" });
- },
- onError: (ctx) => {
- toast.error(ctx.error.message ?? "Something went wrong.");
- setIsPending(false);
- },
- },
- );
- };
-
- return (
-
-
-
- Reset password
-
-
- Enter your new password below.
-
-
-
-
-
- );
-}
-
-export const Route = createFileRoute("/reset-password")({
- component: ResetPasswordPage,
- validateSearch: ResetPasswordSearchSchema,
-});
diff --git a/eslint.config.mjs b/eslint.config.mjs
index a88b6f1..290fa14 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -10,6 +10,8 @@ export default defineConfig([
globalIgnores([
"**/dist/**",
"node_modules/",
+ "eslint.config.mjs",
+ "**/*.config.ts",
"routeTree.gen.ts",
"scripts/**",
"data-pipeline/**/*",
@@ -22,19 +24,12 @@ export default defineConfig([
{
languageOptions: {
parserOptions: {
- projectService: { allowDefaultProject: ["*.mjs", "*.ts"] },
+ projectService: true,
tsconfigRootDir: import.meta.dirname,
},
},
},
- {
- files: ["eslint.config.mjs"],
- rules: {
- "@typescript-eslint/no-unsafe-member-access": "off",
- "@typescript-eslint/no-unsafe-assignment": "off",
- "@typescript-eslint/no-unsafe-call": "off",
- },
- },
+
{
files: ["apps/web/**/*.{ts,tsx}"],
extends: [
@@ -48,9 +43,6 @@ export default defineConfig([
rules: {
"react-refresh/only-export-components": "off",
"@typescript-eslint/only-throw-error": "off",
- "@typescript-eslint/no-unsafe-assignment": "off",
- "@typescript-eslint/no-unsafe-member-access": "off",
- "@typescript-eslint/no-unsafe-call": "off",
},
},
{
diff --git a/packages/db/src/index.ts b/packages/db/src/index.ts
index 567a460..baa05e0 100644
--- a/packages/db/src/index.ts
+++ b/packages/db/src/index.ts
@@ -6,13 +6,10 @@ import { dirname } from "path";
import * as schema from "./db/schema.js";
config({
- path: resolve(dirname(fileURLToPath(import.meta.url)), "../../../../.env"),
+ path: resolve(dirname(fileURLToPath(import.meta.url)), "../../../.env"),
});
-export const db = drizzle(
- process.env["DATABASE_URL_LOCAL"] ?? process.env["DATABASE_URL"]!,
- { schema },
-);
+export const db = drizzle(process.env["DATABASE_URL"]!, { schema });
export * from "./models/termModel.js";
export * from "./models/lobbyModel.js";
diff --git a/packages/db/tsconfig.json b/packages/db/tsconfig.json
index c8c1b3a..af1fba6 100644
--- a/packages/db/tsconfig.json
+++ b/packages/db/tsconfig.json
@@ -10,7 +10,6 @@
"include": [
"src",
"vitest.config.ts",
- "drizzle.config.ts",
"../../data-pipeline/archive/packages-db-src-old-seeding-scripts/data"
]
}
diff --git a/packages/shared/src/index.ts b/packages/shared/src/index.ts
index 582394e..7dc79f5 100644
--- a/packages/shared/src/index.ts
+++ b/packages/shared/src/index.ts
@@ -1,4 +1,3 @@
export * from "./constants.js";
export * from "./schemas/game.js";
export * from "./schemas/lobby.js";
-export * from "./schemas/auth.js";
diff --git a/packages/shared/src/schemas/auth.ts b/packages/shared/src/schemas/auth.ts
deleted file mode 100644
index 6aaf35d..0000000
--- a/packages/shared/src/schemas/auth.ts
+++ /dev/null
@@ -1,14 +0,0 @@
-import * as z from "zod";
-
-export const ResetPasswordSearchSchema = z.object({
- token: z.string().catch(""),
-});
-
-export type ResetPasswordSearch = z.infer;
-
-export const AuthModalSearchSchema = z.object({
- modal: z.enum(["auth"]).optional().catch(undefined),
- redirect: z.string().optional().catch(undefined),
-});
-
-export type AuthModalSearch = z.infer;
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 4453586..1f44e67 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -74,9 +74,6 @@ importers:
helmet:
specifier: ^8.1.0
version: 8.1.0
- resend:
- specifier: ^6.12.2
- version: 6.12.2
ws:
specifier: ^8.20.0
version: 8.20.0
@@ -123,9 +120,6 @@ importers:
react-dom:
specifier: ^19.2.4
version: 19.2.4(react@19.2.4)
- sonner:
- specifier: ^2.0.7
- version: 2.0.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
tailwindcss:
specifier: ^4.2.2
version: 4.2.2
@@ -1096,9 +1090,6 @@ packages:
'@rolldown/pluginutils@1.0.0-rc.7':
resolution: {integrity: sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==}
- '@stablelib/base64@1.0.1':
- resolution: {integrity: sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==}
-
'@standard-schema/spec@1.1.0':
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
@@ -2125,9 +2116,6 @@ packages:
fast-safe-stringify@2.1.1:
resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==}
- fast-sha256@1.3.0:
- resolution: {integrity: sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==}
-
fdir@6.5.0:
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
engines: {node: '>=12.0.0'}
@@ -2715,9 +2703,6 @@ packages:
resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==}
engines: {node: '>=12'}
- postal-mime@2.7.4:
- resolution: {integrity: sha512-0WdnFQYUrPGGTFu1uOqD2s7omwua8xaeYGdO6rb88oD5yJ/4pPHDA4sdWqfD8wQVfCny563n/HQS7zTFft+f/g==}
-
postcss@8.5.8:
resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==}
engines: {node: ^10 || ^12 || >=14}
@@ -2809,15 +2794,6 @@ packages:
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
engines: {node: '>=0.10.0'}
- resend@6.12.2:
- resolution: {integrity: sha512-xwgmU4b0OqoabJsIoK/x0Whk0Fcs3bpbK4i/DEWPiE5hYJHyHl0TbB6QbI3gIr+bLdLUJ1GYm/fe41aVFuHXgw==}
- engines: {node: '>=20'}
- peerDependencies:
- '@react-email/render': '*'
- peerDependenciesMeta:
- '@react-email/render':
- optional: true
-
resolve-pkg-maps@1.0.0:
resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==}
@@ -2938,12 +2914,6 @@ packages:
resolution: {integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==}
engines: {node: '>=20'}
- sonner@2.0.7:
- resolution: {integrity: sha512-W6ZN4p58k8aDKA4XPcx2hpIQXBRAgyiWVkYhT7CvK6D3iAu7xjvVyhQHg2/iaKJZ1XVJ4r7XuwGL+WGEK37i9w==}
- peerDependencies:
- react: ^18.0.0 || ^19.0.0 || ^19.0.0-rc
- react-dom: ^18.0.0 || ^19.0.0 || ^19.0.0-rc
-
source-map-js@1.2.1:
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
engines: {node: '>=0.10.0'}
@@ -2970,9 +2940,6 @@ packages:
stackback@0.0.2:
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
- standardwebhooks@1.0.0:
- resolution: {integrity: sha512-BbHGOQK9olHPMvQNHWul6MYlrRTAOKn03rOe4A8O3CLWhNf4YHBqq2HJKKC+sfqpxiBY52pNeesD6jIiLDz8jg==}
-
statuses@2.0.2:
resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==}
engines: {node: '>= 0.8'}
@@ -3027,9 +2994,6 @@ packages:
resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==}
engines: {node: '>=10'}
- svix@1.90.0:
- resolution: {integrity: sha512-ljkZuyy2+IBEoESkIpn8sLM+sxJHQcPxlZFxU+nVDhltNfUMisMBzWX/UR8SjEnzoI28ZjCzMbmYAPwSTucoMw==}
-
symbol-tree@3.2.4:
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
@@ -3167,11 +3131,6 @@ packages:
util-deprecate@1.0.2:
resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==}
- uuid@10.0.0:
- resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==}
- deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).
- hasBin: true
-
vary@1.1.2:
resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
engines: {node: '>= 0.8'}
@@ -3978,8 +3937,6 @@ snapshots:
'@rolldown/pluginutils@1.0.0-rc.7': {}
- '@stablelib/base64@1.0.1': {}
-
'@standard-schema/spec@1.1.0': {}
'@tailwindcss/node@4.2.2':
@@ -5078,8 +5035,6 @@ snapshots:
fast-safe-stringify@2.1.1: {}
- fast-sha256@1.3.0: {}
-
fdir@6.5.0(picomatch@4.0.3):
optionalDependencies:
picomatch: 4.0.3
@@ -5587,8 +5542,6 @@ snapshots:
picomatch@4.0.3: {}
- postal-mime@2.7.4: {}
-
postcss@8.5.8:
dependencies:
nanoid: 3.3.11
@@ -5685,11 +5638,6 @@ snapshots:
require-from-string@2.0.2: {}
- resend@6.12.2:
- dependencies:
- postal-mime: 2.7.4
- svix: 1.90.0
-
resolve-pkg-maps@1.0.0: {}
restore-cursor@5.1.0:
@@ -5843,11 +5791,6 @@ snapshots:
ansi-styles: 6.2.3
is-fullwidth-code-point: 5.1.0
- sonner@2.0.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
- dependencies:
- react: 19.2.4
- react-dom: 19.2.4(react@19.2.4)
-
source-map-js@1.2.1: {}
source-map-support@0.5.21:
@@ -5867,11 +5810,6 @@ snapshots:
stackback@0.0.2: {}
- standardwebhooks@1.0.0:
- dependencies:
- '@stablelib/base64': 1.0.1
- fast-sha256: 1.3.0
-
statuses@2.0.2: {}
std-env@4.0.0: {}
@@ -5939,11 +5877,6 @@ snapshots:
dependencies:
has-flag: 4.0.0
- svix@1.90.0:
- dependencies:
- standardwebhooks: 1.0.0
- uuid: 10.0.0
-
symbol-tree@3.2.4: {}
tailwindcss@4.2.2: {}
@@ -6074,8 +6007,6 @@ snapshots:
util-deprecate@1.0.2: {}
- uuid@10.0.0: {}
-
vary@1.1.2: {}
vite@8.0.1(@types/node@24.12.0)(esbuild@0.27.4)(jiti@2.6.1)(tsx@4.21.0)(yaml@2.8.3):