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 ( -
{ - e.preventDefault(); - void handleSubmit(); - }} - className="flex flex-col gap-3" - > - setEmail(e.target.value)} - required - className="w-full rounded-2xl border border-(--color-primary-light) bg-white px-4 py-3 text-sm text-(--color-text) placeholder:text-(--color-text-muted) focus:outline-none focus:ring-2 focus:ring-(--color-primary)" - /> - setPassword(e.target.value)} - required - className="w-full rounded-2xl border border-(--color-primary-light) bg-white px-4 py-3 text-sm text-(--color-text) placeholder:text-(--color-text-muted) focus:outline-none focus:ring-2 focus:ring-(--color-primary)" - /> -
- - Forgot password? - -
- -
- ); -}; - -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 ( -
{ - e.preventDefault(); - void handleSubmit(); - }} - className="flex flex-col gap-3" - > - setName(e.target.value)} - required - className="w-full rounded-2xl border border-(--color-primary-light) bg-white px-4 py-3 text-sm text-(--color-text) placeholder:text-(--color-text-muted) focus:outline-none focus:ring-2 focus:ring-(--color-primary)" - /> - setEmail(e.target.value)} - required - className="w-full rounded-2xl border border-(--color-primary-light) bg-white px-4 py-3 text-sm text-(--color-text) placeholder:text-(--color-text-muted) focus:outline-none focus:ring-2 focus:ring-(--color-primary)" - /> - setPassword(e.target.value)} - required - minLength={8} - className="w-full rounded-2xl border border-(--color-primary-light) bg-white px-4 py-3 text-sm text-(--color-text) placeholder:text-(--color-text-muted) focus:outline-none focus:ring-2 focus:ring-(--color-primary)" - /> - -
- ); -}; - -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. -

-
- -
{ - e.preventDefault(); - void handleSubmit(); - }} - className="w-full flex flex-col gap-3" - > - setEmail(e.target.value)} - required - className="w-full rounded-2xl border border-(--color-primary-light) bg-white px-4 py-3 text-sm text-(--color-text) placeholder:text-(--color-text-muted) focus:outline-none focus:ring-2 focus:ring-(--color-primary)" - /> - -
- - - 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. -

-
- -
{ - e.preventDefault(); - void handleSubmit(); - }} - className="w-full flex flex-col gap-3" - > - setPassword(e.target.value)} - required - minLength={8} - className="w-full rounded-2xl border border-(--color-primary-light) bg-white px-4 py-3 text-sm text-(--color-text) placeholder:text-(--color-text-muted) focus:outline-none focus:ring-2 focus:ring-(--color-primary)" - /> - -
-
- ); -} - -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):