From 8489e49fa54ab74979c186d1e089c6ec5afdecf5 Mon Sep 17 00:00:00 2001 From: Josh Daniel Date: Sat, 31 Aug 2024 23:36:38 +0800 Subject: [PATCH] chore: implement shared routes in www --- apps/www/package.json | 1 + apps/www/src/actions.ts | 259 ------------------ .../login/components/form.tsx | 43 --- .../login/components/login-button.tsx | 25 -- .../login/google/callback/route.ts | 159 +---------- .../(authentication)/login/google/route.ts | 32 +-- .../app/(authentication)/login/loading.tsx | 29 +- .../src/app/(authentication)/login/page.tsx | 45 +-- .../register/components/button.tsx | 25 -- .../register/components/form.tsx | 71 ----- .../app/(authentication)/register/loading.tsx | 45 +-- .../app/(authentication)/register/page.tsx | 52 +--- .../inbox/components/received/list.tsx | 4 +- .../inbox/components/received/menu.tsx | 4 +- .../inbox/components/received/reply.tsx | 4 +- .../(profile)/inbox/components/sent/list.tsx | 4 +- apps/www/src/app/(profile)/inbox/page.tsx | 2 +- apps/www/src/app/(profile)/inbox/queries.ts | 2 +- .../settings/components/account-form.tsx | 2 +- .../(profile)/settings/components/danger.tsx | 2 +- .../(profile)/settings/components/general.tsx | 4 +- .../(profile)/settings/components/privacy.tsx | 4 +- apps/www/src/app/(profile)/settings/page.tsx | 4 +- .../www/src/app/(profile)/settings/queries.ts | 2 +- apps/www/src/app/(user)/queries.ts | 2 +- .../(user)/to/[username]/components/form.tsx | 4 +- .../www/src/app/(user)/to/[username]/page.tsx | 2 +- apps/www/src/app/api/graphql/route.ts | 2 +- apps/www/src/app/components/navbar.tsx | 2 +- apps/www/src/app/notes/components/card.tsx | 2 +- .../src/app/notes/components/display-card.tsx | 2 +- apps/www/src/app/notes/components/form.tsx | 7 +- apps/www/src/app/notes/components/list.tsx | 4 +- .../src/app/notes/components/reply-drawer.tsx | 4 +- apps/www/src/app/notes/page.tsx | 2 +- apps/www/src/app/notes/queries.ts | 2 +- apps/www/src/app/social/components/card.tsx | 2 +- apps/www/src/lib/auth.ts | 84 ------ apps/www/src/lib/gql/client.ts | 17 -- apps/www/src/lib/gql/rsc.ts | 32 --- apps/www/src/lib/utils.ts | 78 ------ pnpm-lock.yaml | 12 +- 42 files changed, 55 insertions(+), 1029 deletions(-) delete mode 100644 apps/www/src/actions.ts delete mode 100644 apps/www/src/app/(authentication)/login/components/form.tsx delete mode 100644 apps/www/src/app/(authentication)/login/components/login-button.tsx delete mode 100644 apps/www/src/app/(authentication)/register/components/button.tsx delete mode 100644 apps/www/src/app/(authentication)/register/components/form.tsx delete mode 100644 apps/www/src/lib/auth.ts delete mode 100644 apps/www/src/lib/gql/client.ts delete mode 100644 apps/www/src/lib/gql/rsc.ts delete mode 100644 apps/www/src/lib/utils.ts diff --git a/apps/www/package.json b/apps/www/package.json index 5b5d1fb4..a3d03220 100644 --- a/apps/www/package.json +++ b/apps/www/package.json @@ -31,6 +31,7 @@ "@umamin/db": "workspace:*", "@umamin/gql": "workspace:*", "@umamin/ui": "workspace:*", + "@umamin/shared": "workspace:*", "@urql/core": "^5.0.5", "@urql/exchange-graphcache": "^7.1.1", "@urql/exchange-persisted": "^4.3.0", diff --git a/apps/www/src/actions.ts b/apps/www/src/actions.ts deleted file mode 100644 index ee86c9bd..00000000 --- a/apps/www/src/actions.ts +++ /dev/null @@ -1,259 +0,0 @@ -"use server"; - -import { nanoid } from "nanoid"; -import { db, eq } from "@umamin/db"; -import { cookies } from "next/headers"; -import { redirect } from "next/navigation"; -import { hash, verify } from "@node-rs/argon2"; -import { - user as userSchema, - account as accountSchema, -} from "@umamin/db/schema/user"; -import { note as noteSchema } from "@umamin/db/schema/note"; -import { message as messageSchema } from "@umamin/db/schema/message"; - -import { getSession, lucia } from "./lib/auth"; -import { z } from "zod"; - -export async function logout(): Promise { - const { session } = await getSession(); - - if (!session) { - throw new Error("Unauthorized"); - } - - await lucia.invalidateSession(session.id); - - const sessionCookie = lucia.createBlankSessionCookie(); - cookies().set( - sessionCookie.name, - sessionCookie.value, - sessionCookie.attributes, - ); - - return redirect("/login"); -} - -const signupSchema = z - .object({ - username: z - .string() - .min(5, { - message: "Username must be at least 5 characters", - }) - .max(20, { - message: "Username must not exceed 20 characters", - }) - .refine((url) => /^[a-zA-Z0-9_-]+$/.test(url), { - message: "Username must be alphanumeric with no spaces", - }), - password: z - .string() - .min(5, { - message: "Password must be at least 5 characters", - }) - .max(255, { - message: "Password must not exceed 255 characters", - }), - confirmPassword: z.string(), - }) - .refine( - (values) => { - return values.password === values.confirmPassword; - }, - { - message: "Password does not match", - path: ["confirmPassword"], - }, - ); - -export async function signup(_: any, formData: FormData) { - const validatedFields = signupSchema.safeParse({ - username: formData.get("username"), - password: formData.get("password"), - confirmPassword: formData.get("confirmPassword"), - }); - - if (!validatedFields.success) { - return { - errors: validatedFields.error.flatten().fieldErrors, - }; - } - - const passwordHash = await hash(validatedFields.data.password, { - memoryCost: 19456, - timeCost: 2, - outputLen: 32, - parallelism: 1, - }); - - const userId = nanoid(); - - try { - await db.insert(userSchema).values({ - id: userId, - username: validatedFields.data.username.toLowerCase(), - passwordHash, - }); - } catch (err: any) { - if (err.code === "SQLITE_CONSTRAINT") { - if (err.message.includes("user.username")) { - return { - errors: { - username: ["Username already taken"], - }, - }; - } - } - - throw new Error("Something went wrong"); - } - - const session = await lucia.createSession(userId, {}); - const sessionCookie = lucia.createSessionCookie(session.id); - - cookies().set( - sessionCookie.name, - sessionCookie.value, - sessionCookie.attributes, - ); - - return redirect("/inbox"); -} - -export async function login(_: any, formData: FormData): Promise { - const username = formData.get("username"); - - if ( - typeof username !== "string" || - username.length < 5 || - username.length > 20 || - !/^[a-zA-Z0-9_-]+$/.test(username) - ) { - return { - error: "Incorrect username or password", - }; - } - - const password = formData.get("password"); - - if ( - typeof password !== "string" || - password.length < 5 || - password.length > 255 - ) { - return { - error: "Incorrect username or password", - }; - } - - const existingUser = await db.query.user.findFirst({ - where: eq(userSchema.username, username.toLowerCase()), - }); - - if (!existingUser || !existingUser.passwordHash) { - return { - error: "Incorrect username or password", - }; - } - - const validPassword = await verify(existingUser.passwordHash, password, { - memoryCost: 19456, - timeCost: 2, - outputLen: 32, - parallelism: 1, - }); - - if (!validPassword) { - return { - error: "Incorrect username or password", - }; - } - - const session = await lucia.createSession(existingUser.id, {}); - const sessionCookie = lucia.createSessionCookie(session.id); - cookies().set( - sessionCookie.name, - sessionCookie.value, - sessionCookie.attributes, - ); - - return redirect("/inbox"); -} - -export async function updatePassword({ - currentPassword, - password, -}: { - currentPassword?: string; - password: string; -}): Promise { - const { user } = await getSession(); - - if (!user) { - throw new Error("Unauthorized"); - } - - if (currentPassword && user.passwordHash) { - const validPassword = await verify(user.passwordHash, currentPassword, { - memoryCost: 19456, - timeCost: 2, - outputLen: 32, - parallelism: 1, - }); - - if (!validPassword) { - return { - error: "Incorrect password", - }; - } - } - - const passwordHash = await hash(password, { - memoryCost: 19456, - timeCost: 2, - outputLen: 32, - parallelism: 1, - }); - - await db - .update(userSchema) - .set({ passwordHash }) - .where(eq(userSchema.id, user.id)); - - return redirect("/settings"); -} - -export async function deleteAccount() { - const { user } = await getSession(); - - if (!user) { - throw new Error("Unauthorized"); - } - - try { - await db.batch([ - db.delete(messageSchema).where(eq(messageSchema.receiverId, user.id)), - db.delete(accountSchema).where(eq(accountSchema.userId, user.id)), - db.delete(noteSchema).where(eq(noteSchema.userId, user.id)), - db.delete(userSchema).where(eq(userSchema.id, user.id)), - ]); - - await lucia.invalidateSession(user.id); - - const sessionCookie = lucia.createBlankSessionCookie(); - cookies().set( - sessionCookie.name, - sessionCookie.value, - sessionCookie.attributes, - ); - } catch (err) { - throw new Error("Failed to delete account"); - } - - return redirect("/login"); -} - -interface ActionResult { - error: string | null; -} diff --git a/apps/www/src/app/(authentication)/login/components/form.tsx b/apps/www/src/app/(authentication)/login/components/form.tsx deleted file mode 100644 index 3d0d937a..00000000 --- a/apps/www/src/app/(authentication)/login/components/form.tsx +++ /dev/null @@ -1,43 +0,0 @@ -"use client"; - -import { login } from "@/actions"; -import { useFormState } from "react-dom"; - -import { LoginButton } from "./login-button"; -import { Input } from "@umamin/ui/components/input"; -import { Label } from "@umamin/ui/components/label"; - -export function LoginForm() { - const [state, formAction] = useFormState(login, { error: "" }); - - return ( -
-
- - -
- -
- - - {!!state?.error && ( -

{state.error}

- )} -
- - - - ); -} diff --git a/apps/www/src/app/(authentication)/login/components/login-button.tsx b/apps/www/src/app/(authentication)/login/components/login-button.tsx deleted file mode 100644 index 1ca484aa..00000000 --- a/apps/www/src/app/(authentication)/login/components/login-button.tsx +++ /dev/null @@ -1,25 +0,0 @@ -"use client"; - -import Link from "next/link"; -import { Loader2 } from "lucide-react"; -import { useFormStatus } from "react-dom"; -import { Button } from "@umamin/ui/components/button"; - -export function LoginButton() { - const { pending } = useFormStatus(); - - return ( -
- - - -
- ); -} diff --git a/apps/www/src/app/(authentication)/login/google/callback/route.ts b/apps/www/src/app/(authentication)/login/google/callback/route.ts index 261b8560..00f5c9ad 100644 --- a/apps/www/src/app/(authentication)/login/google/callback/route.ts +++ b/apps/www/src/app/(authentication)/login/google/callback/route.ts @@ -1,158 +1 @@ -import { nanoid } from "nanoid"; -import { generateId } from "lucia"; -import { cookies } from "next/headers"; -import { db, and, eq } from "@umamin/db"; -import { OAuth2RequestError } from "arctic"; -import { - user as userSchema, - account as accountSchema, -} from "@umamin/db/schema/user"; - -import { getSession, google, lucia } from "@/lib/auth"; - -export async function GET(request: Request): Promise { - const url = new URL(request.url); - const code = url.searchParams.get("code"); - const state = url.searchParams.get("state"); - - const storedState = cookies().get("google_oauth_state")?.value ?? null; - const storedCodeVerifier = cookies().get("code_verifier")?.value ?? null; - - if ( - !code || - !state || - !storedState || - !storedCodeVerifier || - state !== storedState - ) { - return new Response(null, { - status: 400, - }); - } - - try { - const tokens = await google.validateAuthorizationCode( - code, - storedCodeVerifier, - ); - - const googleUserResponse = await fetch( - "https://openidconnect.googleapis.com/v1/userinfo", - { - headers: { - Authorization: `Bearer ${tokens.accessToken}`, - }, - }, - ); - - const googleUser: GoogleUser = await googleUserResponse.json(); - - const { user } = await getSession(); - - const existingUser = await db.query.account.findFirst({ - where: and( - eq(accountSchema.providerId, "google"), - eq(accountSchema.providerUserId, googleUser.sub), - ), - }); - - if (user && existingUser) { - return new Response(null, { - status: 302, - headers: { - Location: "/settings?error=already_linked", - }, - }); - } else if (user) { - await db - .update(userSchema) - .set({ - imageUrl: googleUser.picture, - }) - .where(eq(userSchema.id, user.id)); - - await db.insert(accountSchema).values({ - providerId: "google", - providerUserId: googleUser.sub, - userId: user.id, - picture: googleUser.picture, - email: googleUser.email, - }); - - return new Response(null, { - status: 302, - headers: { - Location: "/settings", - }, - }); - } - - if (existingUser) { - const session = await lucia.createSession(existingUser.userId, {}); - const sessionCookie = lucia.createSessionCookie(session.id); - - cookies().set( - sessionCookie.name, - sessionCookie.value, - sessionCookie.attributes, - ); - - return new Response(null, { - status: 302, - headers: { - Location: "/login", - }, - }); - } - - const usernameId = generateId(5); - const userId = nanoid(); - - await db.insert(userSchema).values({ - id: userId, - imageUrl: googleUser.picture, - username: `umamin_${usernameId}`, - }); - - await db.insert(accountSchema).values({ - providerId: "google", - providerUserId: googleUser.sub, - userId, - picture: googleUser.picture, - email: googleUser.email, - }); - - const session = await lucia.createSession(userId, {}); - const sessionCookie = lucia.createSessionCookie(session.id); - - cookies().set( - sessionCookie.name, - sessionCookie.value, - sessionCookie.attributes, - ); - - return new Response(null, { - status: 302, - headers: { - Location: "/login", - }, - }); - } catch (err: any) { - console.log(err); - if (err instanceof OAuth2RequestError) { - return new Response(null, { - status: 400, - }); - } - - return new Response(null, { - status: 500, - }); - } -} - -interface GoogleUser { - sub: string; - picture: string; - email: string; -} +export { GET } from "@umamin/shared/routes/google/callback/route"; diff --git a/apps/www/src/app/(authentication)/login/google/route.ts b/apps/www/src/app/(authentication)/login/google/route.ts index 88f65bc7..7c78acb6 100644 --- a/apps/www/src/app/(authentication)/login/google/route.ts +++ b/apps/www/src/app/(authentication)/login/google/route.ts @@ -1,31 +1 @@ -import { generateState, generateCodeVerifier } from "arctic"; -import { cookies } from "next/headers"; -import { google } from "@/lib/auth"; - -export async function GET(): Promise { - const state = generateState(); - const codeVerifier = generateCodeVerifier(); - const url = await google.createAuthorizationURL(state, codeVerifier, { - scopes: ["profile", "email"], - }); - - url.searchParams.set("access_type", "offline"); - - cookies().set("google_oauth_state", state, { - path: "/", - secure: process.env.NODE_ENV === "production", - httpOnly: true, - maxAge: 60 * 10, - sameSite: "lax", - }); - - cookies().set("code_verifier", codeVerifier, { - path: "/", - secure: process.env.NODE_ENV === "production", - httpOnly: true, - maxAge: 60 * 10, - sameSite: "lax", - }); - - return Response.redirect(url); -} +export { GET } from "@umamin/shared/routes/google/route"; diff --git a/apps/www/src/app/(authentication)/login/loading.tsx b/apps/www/src/app/(authentication)/login/loading.tsx index 5ba9d85f..8b290aa6 100644 --- a/apps/www/src/app/(authentication)/login/loading.tsx +++ b/apps/www/src/app/(authentication)/login/loading.tsx @@ -1,28 +1 @@ -import { Skeleton } from "@umamin/ui/components/skeleton"; - -export default function Loading() { - return ( -
-
- - -
- -
- - -
- -
- - -
- -
- - - -
-
- ); -} +export { default } from "@umamin/shared/pages/login/loading"; diff --git a/apps/www/src/app/(authentication)/login/page.tsx b/apps/www/src/app/(authentication)/login/page.tsx index 9c530f16..b0ee0089 100644 --- a/apps/www/src/app/(authentication)/login/page.tsx +++ b/apps/www/src/app/(authentication)/login/page.tsx @@ -1,14 +1,4 @@ -import Link from "next/link"; -import dynamic from "next/dynamic"; -import { getSession } from "@/lib/auth"; -import { redirect } from "next/navigation"; -import { LoginForm } from "./components/form"; -import { V1Link } from "@/app/components/v1-link"; - -const BrowserWarning = dynamic( - () => import("@umamin/ui/components/browser-warning"), - { ssr: false } -); +import Login from "@umamin/shared/pages/login/page"; export const metadata = { title: "Umamin — Login", @@ -38,35 +28,6 @@ export const metadata = { }, }; -export default async function Login() { - const { user } = await getSession(); - - if (user) { - redirect("/inbox"); - } - - return ( -
- - - -
-

- Umamin Account -

-

- Proceed with your Umamin v2.0 profile -

-
- - - -
- Don't have an account?{" "} - - Sign up - -
-
- ); +export default function Page() { + return ; } diff --git a/apps/www/src/app/(authentication)/register/components/button.tsx b/apps/www/src/app/(authentication)/register/components/button.tsx deleted file mode 100644 index 047ce82d..00000000 --- a/apps/www/src/app/(authentication)/register/components/button.tsx +++ /dev/null @@ -1,25 +0,0 @@ -"use client"; - -import Link from "next/link"; -import { Loader2 } from "lucide-react"; -import { useFormStatus } from "react-dom"; -import { Button } from "@umamin/ui/components/button"; - -export function RegisterButton() { - const { pending } = useFormStatus(); - - return ( -
- - - -
- ); -} diff --git a/apps/www/src/app/(authentication)/register/components/form.tsx b/apps/www/src/app/(authentication)/register/components/form.tsx deleted file mode 100644 index 03f325ac..00000000 --- a/apps/www/src/app/(authentication)/register/components/form.tsx +++ /dev/null @@ -1,71 +0,0 @@ -"use client"; - -import { signup } from "@/actions"; -import { useFormState } from "react-dom"; -import { RegisterButton } from "./button"; -import { cn } from "@umamin/ui/lib/utils"; -import { Input } from "@umamin/ui/components/input"; -import { Label } from "@umamin/ui/components/label"; - -export function RegisterForm() { - const [state, formAction] = useFormState(signup, { errors: {} }); - - return ( -
-
- - -

- {state.errors.username - ? state.errors.username[0] - : "You can still change this later"} -

-
- -
- - - {state.errors.password && ( -

- {state.errors.password[0]} -

- )} -
- -
- - - {state.errors.confirmPassword && ( -

- {state.errors.confirmPassword[0]} -

- )} -
- - - - ); -} diff --git a/apps/www/src/app/(authentication)/register/loading.tsx b/apps/www/src/app/(authentication)/register/loading.tsx index d623ffdd..f5cc2749 100644 --- a/apps/www/src/app/(authentication)/register/loading.tsx +++ b/apps/www/src/app/(authentication)/register/loading.tsx @@ -1,44 +1 @@ -import { Skeleton } from "@umamin/ui/components/skeleton"; - -export default function Loading() { - return ( -
-
-
- - -
- - -
-
- -
-
- - -
- -
- - -
-
-
- -
-
-
- - -
- - -
- - - -
-
- ); -} +export { default } from "@umamin/shared/pages/register/loading"; diff --git a/apps/www/src/app/(authentication)/register/page.tsx b/apps/www/src/app/(authentication)/register/page.tsx index 28cb0209..31d1ed89 100644 --- a/apps/www/src/app/(authentication)/register/page.tsx +++ b/apps/www/src/app/(authentication)/register/page.tsx @@ -1,14 +1,4 @@ -import Link from "next/link"; -import dynamic from "next/dynamic"; -import { getSession } from "@/lib/auth"; -import { redirect } from "next/navigation"; -import { RegisterForm } from "./components/form"; -import { V1Link } from "@/app/components/v1-link"; - -const BrowserWarning = dynamic( - () => import("@umamin/ui/components/browser-warning"), - { ssr: false } -); +import Register from "@umamin/shared/pages/register/page"; export const metadata = { title: "Umamin — Register", @@ -38,42 +28,6 @@ export const metadata = { }, }; -export default async function Register() { - const { user } = await getSession(); - - if (user) { - redirect("/inbox"); - } - - return ( -
- - - -
-

- Umamin Account -

-

- By creating an account, you agree to our{" "} - - Privacy Policy - {" "} - and{" "} - - Terms of Service - -

-
- - - -
- Already have an account?{" "} - - Login - -
-
- ); +export default function Page() { + return ; } diff --git a/apps/www/src/app/(profile)/inbox/components/received/list.tsx b/apps/www/src/app/(profile)/inbox/components/received/list.tsx index 67739f31..df43063f 100644 --- a/apps/www/src/app/(profile)/inbox/components/received/list.tsx +++ b/apps/www/src/app/(profile)/inbox/components/received/list.tsx @@ -6,9 +6,9 @@ import { graphql } from "gql.tada"; import { useInView } from "react-intersection-observer"; import { useCallback, useEffect, useState } from "react"; -import client from "@/lib/gql/client"; -import { formatError } from "@/lib/utils"; import type { InboxProps } from "../../queries"; +import client from "@umamin/shared/lib/gql/client"; +import { formatError } from "@umamin/shared/lib/utils"; import { Skeleton } from "@umamin/ui/components/skeleton"; import { useMessageStore } from "@/store/useMessageStore"; import { ReceivedMessageCard, receivedMessageFragment } from "./card"; diff --git a/apps/www/src/app/(profile)/inbox/components/received/menu.tsx b/apps/www/src/app/(profile)/inbox/components/received/menu.tsx index 6ff5de49..96148a82 100644 --- a/apps/www/src/app/(profile)/inbox/components/received/menu.tsx +++ b/apps/www/src/app/(profile)/inbox/components/received/menu.tsx @@ -3,12 +3,12 @@ import { toast } from "sonner"; import { useState } from "react"; import { graphql } from "gql.tada"; -import client from "@/lib/gql/client"; import { logEvent } from "firebase/analytics"; +import client from "@umamin/shared/lib/gql/client"; +import { formatError, onSaveImage } from "@umamin/shared/lib/utils"; import { ReplyDialog } from "./reply"; import { analytics } from "@/lib/firebase"; -import { formatError, onSaveImage } from "@/lib/utils"; import { AlertDialog, diff --git a/apps/www/src/app/(profile)/inbox/components/received/reply.tsx b/apps/www/src/app/(profile)/inbox/components/received/reply.tsx index d366d31d..89466d87 100644 --- a/apps/www/src/app/(profile)/inbox/components/received/reply.tsx +++ b/apps/www/src/app/(profile)/inbox/components/received/reply.tsx @@ -6,10 +6,10 @@ import { logEvent } from "firebase/analytics"; import { FormEventHandler, useState } from "react"; import { formatDistanceToNow, fromUnixTime } from "date-fns"; -import client from "@/lib/gql/client"; import { cn } from "@umamin/ui/lib/utils"; -import { formatError } from "@/lib/utils"; import { analytics } from "@/lib/firebase"; +import client from "@umamin/shared/lib/gql/client"; +import { formatError } from "@umamin/shared/lib/utils"; import type { ReceivedMenuProps } from "./menu"; import { Button } from "@umamin/ui/components/button"; diff --git a/apps/www/src/app/(profile)/inbox/components/sent/list.tsx b/apps/www/src/app/(profile)/inbox/components/sent/list.tsx index 70ec0663..200f04ff 100644 --- a/apps/www/src/app/(profile)/inbox/components/sent/list.tsx +++ b/apps/www/src/app/(profile)/inbox/components/sent/list.tsx @@ -6,9 +6,9 @@ import { graphql } from "gql.tada"; import { useInView } from "react-intersection-observer"; import { useCallback, useEffect, useState } from "react"; -import client from "@/lib/gql/client"; -import { formatError } from "@/lib/utils"; import type { InboxProps } from "../../queries"; +import client from "@umamin/shared/lib/gql/client"; +import { formatError } from "@umamin/shared/lib/utils"; import { Skeleton } from "@umamin/ui/components/skeleton"; import { useMessageStore } from "@/store/useMessageStore"; import { sentMessageFragment, SentMessageCard } from "./card"; diff --git a/apps/www/src/app/(profile)/inbox/page.tsx b/apps/www/src/app/(profile)/inbox/page.tsx index 4d4b362c..906af64a 100644 --- a/apps/www/src/app/(profile)/inbox/page.tsx +++ b/apps/www/src/app/(profile)/inbox/page.tsx @@ -1,7 +1,7 @@ import { Suspense } from "react"; import dynamic from "next/dynamic"; -import { getSession } from "@/lib/auth"; import { redirect } from "next/navigation"; +import { getSession } from "@umamin/shared/lib/auth"; import { Tabs, diff --git a/apps/www/src/app/(profile)/inbox/queries.ts b/apps/www/src/app/(profile)/inbox/queries.ts index 534052f6..4e4ca4a3 100644 --- a/apps/www/src/app/(profile)/inbox/queries.ts +++ b/apps/www/src/app/(profile)/inbox/queries.ts @@ -1,6 +1,6 @@ import { cache } from "react"; -import getClient from "@/lib/gql/rsc"; import { ResultOf, graphql } from "gql.tada"; +import getClient from "@umamin/shared/lib/gql/rsc"; import { sentMessageFragment } from "./components/sent/card"; import { receivedMessageFragment } from "./components/received/card"; diff --git a/apps/www/src/app/(profile)/settings/components/account-form.tsx b/apps/www/src/app/(profile)/settings/components/account-form.tsx index 1230bd4c..25a93850 100644 --- a/apps/www/src/app/(profile)/settings/components/account-form.tsx +++ b/apps/www/src/app/(profile)/settings/components/account-form.tsx @@ -21,7 +21,7 @@ import { FormLabel, FormMessage, } from "@umamin/ui/components/form"; -import { updatePassword } from "@/actions"; +import { updatePassword } from "@umamin/shared/actions"; const FormSchema = z .object({ diff --git a/apps/www/src/app/(profile)/settings/components/danger.tsx b/apps/www/src/app/(profile)/settings/components/danger.tsx index 2a318764..5b25aac7 100644 --- a/apps/www/src/app/(profile)/settings/components/danger.tsx +++ b/apps/www/src/app/(profile)/settings/components/danger.tsx @@ -18,9 +18,9 @@ import { AlertDescription, AlertTitle, } from "@umamin/ui/components/alert"; -import { deleteAccount } from "@/actions"; import { DeleteButton } from "./delete-button"; import { Button } from "@umamin/ui/components/button"; +import { deleteAccount } from "@umamin/shared/actions"; export function DangerSettings() { const [confirmText, setConfirmText] = useState(""); diff --git a/apps/www/src/app/(profile)/settings/components/general.tsx b/apps/www/src/app/(profile)/settings/components/general.tsx index f3eaaece..4f6a0545 100644 --- a/apps/www/src/app/(profile)/settings/components/general.tsx +++ b/apps/www/src/app/(profile)/settings/components/general.tsx @@ -11,10 +11,10 @@ import { logEvent } from "firebase/analytics"; import { Info, Loader2 } from "lucide-react"; import { zodResolver } from "@hookform/resolvers/zod"; -import client from "@/lib/gql/client"; -import { formatError } from "@/lib/utils"; +import client from "@umamin/shared/lib/gql/client"; import { Input } from "@umamin/ui/components/input"; import { Button } from "@umamin/ui/components/button"; +import { formatError } from "@umamin/shared/lib/utils"; import { Textarea } from "@umamin/ui/components/textarea"; import { diff --git a/apps/www/src/app/(profile)/settings/components/privacy.tsx b/apps/www/src/app/(profile)/settings/components/privacy.tsx index b662e9db..64ad9c14 100644 --- a/apps/www/src/app/(profile)/settings/components/privacy.tsx +++ b/apps/www/src/app/(profile)/settings/components/privacy.tsx @@ -7,11 +7,11 @@ import { useRouter } from "next/navigation"; import { logEvent } from "firebase/analytics"; import { CircleUserRound, MessageCircleOff } from "lucide-react"; -import client from "@/lib/gql/client"; -import { formatError } from "@/lib/utils"; import { analytics } from "@/lib/firebase"; +import client from "@umamin/shared/lib/gql/client"; import { Label } from "@umamin/ui/components/label"; import { Switch } from "@umamin/ui/components/switch"; +import { formatError } from "@umamin/shared/lib/utils"; import type { CurrentUserResult } from "../queries"; diff --git a/apps/www/src/app/(profile)/settings/page.tsx b/apps/www/src/app/(profile)/settings/page.tsx index ecf50f0a..c8a68327 100644 --- a/apps/www/src/app/(profile)/settings/page.tsx +++ b/apps/www/src/app/(profile)/settings/page.tsx @@ -1,6 +1,6 @@ -import { logout } from "@/actions"; -import { getSession } from "@/lib/auth"; import { redirect } from "next/navigation"; +import { logout } from "@umamin/shared/actions"; +import { getSession } from "@umamin/shared/lib/auth"; import { getCurrentUser } from "./queries"; import { GeneralSettings } from "./components/general"; diff --git a/apps/www/src/app/(profile)/settings/queries.ts b/apps/www/src/app/(profile)/settings/queries.ts index 385027f5..ad467f7d 100644 --- a/apps/www/src/app/(profile)/settings/queries.ts +++ b/apps/www/src/app/(profile)/settings/queries.ts @@ -1,6 +1,6 @@ import { cache } from "react"; -import getClient from "@/lib/gql/rsc"; import { graphql, ResultOf } from "gql.tada"; +import getClient from "@umamin/shared/lib/gql/rsc"; export const CURRENT_USER_QUERY = graphql(` query CurrentUser { diff --git a/apps/www/src/app/(user)/queries.ts b/apps/www/src/app/(user)/queries.ts index ce853087..1379548d 100644 --- a/apps/www/src/app/(user)/queries.ts +++ b/apps/www/src/app/(user)/queries.ts @@ -1,6 +1,6 @@ import { cache } from "react"; -import getClient from "@/lib/gql/rsc"; import { ResultOf, graphql } from "gql.tada"; +import getClient from "@umamin/shared/lib/gql/rsc"; export const USER_BY_USERNAME_QUERY = graphql(` query UserByUsername($username: String!) { diff --git a/apps/www/src/app/(user)/to/[username]/components/form.tsx b/apps/www/src/app/(user)/to/[username]/components/form.tsx index 2373b274..1e8abb97 100644 --- a/apps/www/src/app/(user)/to/[username]/components/form.tsx +++ b/apps/www/src/app/(user)/to/[username]/components/form.tsx @@ -7,11 +7,11 @@ import { analytics } from "@/lib/firebase"; import { Loader2, Send } from "lucide-react"; import { logEvent } from "firebase/analytics"; -import client from "@/lib/gql/client"; import { cn } from "@umamin/ui/lib/utils"; -import { formatError } from "@/lib/utils"; +import client from "@umamin/shared/lib/gql/client"; import { Button } from "@umamin/ui/components/button"; import { ChatList } from "@/app/components/chat-list"; +import { formatError } from "@umamin/shared/lib/utils"; import useBotDetection from "@/hooks/use-bot-detection"; import { Textarea } from "@umamin/ui/components/textarea"; import { useDynamicTextarea } from "@/hooks/use-dynamic-textarea"; diff --git a/apps/www/src/app/(user)/to/[username]/page.tsx b/apps/www/src/app/(user)/to/[username]/page.tsx index 9319e52d..8a86c421 100644 --- a/apps/www/src/app/(user)/to/[username]/page.tsx +++ b/apps/www/src/app/(user)/to/[username]/page.tsx @@ -2,8 +2,8 @@ import dynamic from "next/dynamic"; import { redirect } from "next/navigation"; import { BadgeCheck, Lock, MessageCircleOff } from "lucide-react"; -import { getSession } from "@/lib/auth"; import { getUserByUsername } from "../../queries"; +import { getSession } from "@umamin/shared/lib/auth"; import { ShareButton } from "@/app/components/share-button"; import { Card, CardHeader } from "@umamin/ui/components/card"; diff --git a/apps/www/src/app/api/graphql/route.ts b/apps/www/src/app/api/graphql/route.ts index aaf456a4..72778020 100644 --- a/apps/www/src/app/api/graphql/route.ts +++ b/apps/www/src/app/api/graphql/route.ts @@ -1,7 +1,7 @@ import { cookies } from "next/headers"; import { createYoga } from "graphql-yoga"; -import { getSession, lucia } from "@/lib/auth"; import { www_schema, initContextCache } from "@umamin/gql"; +import { getSession, lucia } from "@umamin/shared/lib/auth"; import persistedOperations from "@/persisted-operations.json"; import { useResponseCache } from "@graphql-yoga/plugin-response-cache"; import { useCSRFPrevention } from "@graphql-yoga/plugin-csrf-prevention"; diff --git a/apps/www/src/app/components/navbar.tsx b/apps/www/src/app/components/navbar.tsx index 69165cd8..dccab4c1 100644 --- a/apps/www/src/app/components/navbar.tsx +++ b/apps/www/src/app/components/navbar.tsx @@ -1,5 +1,6 @@ import Link from "next/link"; import { Badge } from "@umamin/ui/components/badge"; +import { getSession } from "@umamin/shared/lib/auth"; import { LinkIcon, LogIn, @@ -8,7 +9,6 @@ import { UserCog, } from "lucide-react"; -import { getSession } from "@/lib/auth"; import { Icons } from "./utilities/icons"; import { ToggleTheme } from "./utilities/toggle-theme"; import { ShareLinkDialog } from "./share-link-dialog"; diff --git a/apps/www/src/app/notes/components/card.tsx b/apps/www/src/app/notes/components/card.tsx index 2de4e830..062d50d0 100644 --- a/apps/www/src/app/notes/components/card.tsx +++ b/apps/www/src/app/notes/components/card.tsx @@ -16,8 +16,8 @@ import { AvatarFallback, AvatarImage, } from "@umamin/ui/components/avatar"; -import { onSaveImage, shortTimeAgo } from "@/lib/utils"; import { ReplyDrawer } from "./reply-drawer"; +import { onSaveImage, shortTimeAgo } from "@umamin/shared/lib/utils"; type Props = { note: NotesQueryResult[0]; diff --git a/apps/www/src/app/notes/components/display-card.tsx b/apps/www/src/app/notes/components/display-card.tsx index 15142cf7..28cc02d4 100644 --- a/apps/www/src/app/notes/components/display-card.tsx +++ b/apps/www/src/app/notes/components/display-card.tsx @@ -16,7 +16,7 @@ import { AvatarFallback, AvatarImage, } from "@umamin/ui/components/avatar"; -import { onSaveImage, shortTimeAgo } from "@/lib/utils"; +import { onSaveImage, shortTimeAgo } from "@umamin/shared/lib/utils"; type Props = { note: NonNullable; diff --git a/apps/www/src/app/notes/components/form.tsx b/apps/www/src/app/notes/components/form.tsx index 187f5fc9..23e7a73b 100644 --- a/apps/www/src/app/notes/components/form.tsx +++ b/apps/www/src/app/notes/components/form.tsx @@ -6,19 +6,18 @@ import { logEvent } from "firebase/analytics"; import { Loader2, Sparkles } from "lucide-react"; import { FormEventHandler, useState } from "react"; -import client from "@/lib/gql/client"; import { NoteCard } from "./display-card"; import { CurrentNoteQueryResult } from "../queries"; -import { formatError } from "@/lib/utils"; -import { analytics } from "@/lib/firebase"; - import { cn } from "@umamin/ui/lib/utils"; +import { analytics } from "@/lib/firebase"; +import client from "@umamin/shared/lib/gql/client"; import { SelectUser } from "@umamin/db/schema/user"; import { useNoteStore } from "@/store/useNoteStore"; import { Label } from "@umamin/ui/components/label"; import { Button } from "@umamin/ui/components/button"; import { Switch } from "@umamin/ui/components/switch"; +import { formatError } from "@umamin/shared/lib/utils"; import useBotDetection from "@/hooks/use-bot-detection"; import { Textarea } from "@umamin/ui/components/textarea"; import { useDynamicTextarea } from "@/hooks/use-dynamic-textarea"; diff --git a/apps/www/src/app/notes/components/list.tsx b/apps/www/src/app/notes/components/list.tsx index 84faad89..9dbec288 100644 --- a/apps/www/src/app/notes/components/list.tsx +++ b/apps/www/src/app/notes/components/list.tsx @@ -2,13 +2,13 @@ import { toast } from "sonner"; import { graphql } from "gql.tada"; -import client from "@/lib/gql/client"; +import client from "@umamin/shared/lib/gql/client"; import { useInView } from "react-intersection-observer"; import { useCallback, useEffect, useState } from "react"; import { NoteCard } from "./card"; -import { formatError } from "@/lib/utils"; import { NotesQueryResult } from "../queries"; +import { formatError } from "@umamin/shared/lib/utils"; import { Skeleton } from "@umamin/ui/components/skeleton"; const NOTES_FROM_CURSOR_QUERY = graphql(` diff --git a/apps/www/src/app/notes/components/reply-drawer.tsx b/apps/www/src/app/notes/components/reply-drawer.tsx index 067b4d8d..55061896 100644 --- a/apps/www/src/app/notes/components/reply-drawer.tsx +++ b/apps/www/src/app/notes/components/reply-drawer.tsx @@ -8,8 +8,8 @@ import { cn } from "@umamin/ui/lib/utils"; import { analytics } from "@/lib/firebase"; import { NotesQueryResult } from "../queries"; -import client from "@/lib/gql/client"; -import { formatError } from "@/lib/utils"; +import client from "@umamin/shared/lib/gql/client"; +import { formatError } from "@umamin/shared/lib/utils"; import { Button } from "@umamin/ui/components/button"; import { ChatList } from "@/app/components/chat-list"; import { useMediaQuery } from "@/hooks/use-media-query"; diff --git a/apps/www/src/app/notes/page.tsx b/apps/www/src/app/notes/page.tsx index 38477180..236dd325 100644 --- a/apps/www/src/app/notes/page.tsx +++ b/apps/www/src/app/notes/page.tsx @@ -2,9 +2,9 @@ import Link from "next/link"; import dynamic from "next/dynamic"; import { SquarePen } from "lucide-react"; -import { getSession } from "@/lib/auth"; import { NoteCard } from "./components/card"; import { NotesList } from "./components/list"; +import { getSession } from "@umamin/shared/lib/auth"; import { getNotes, getCurrentNote } from "./queries"; import { Button } from "@umamin/ui/components/button"; diff --git a/apps/www/src/app/notes/queries.ts b/apps/www/src/app/notes/queries.ts index 31eff34c..d1840518 100644 --- a/apps/www/src/app/notes/queries.ts +++ b/apps/www/src/app/notes/queries.ts @@ -1,6 +1,6 @@ import { cache } from "react"; -import getClient from "@/lib/gql/rsc"; import { graphql, ResultOf } from "gql.tada"; +import getClient from "@umamin/shared/lib/gql/rsc"; const NOTES_QUERY = graphql(` query Notes { diff --git a/apps/www/src/app/social/components/card.tsx b/apps/www/src/app/social/components/card.tsx index 67fe7fd7..fe468dab 100644 --- a/apps/www/src/app/social/components/card.tsx +++ b/apps/www/src/app/social/components/card.tsx @@ -5,7 +5,7 @@ import { AvatarImage, } from "@umamin/ui/components/avatar"; import { cn } from "@umamin/ui/lib/utils"; -import { shortTimeAgo } from "@/lib/utils"; +import { shortTimeAgo } from "@umamin/shared/lib/utils"; import { BadgeCheck, Heart, MessageCircle, ScanFace } from "lucide-react"; type Props = { diff --git a/apps/www/src/lib/auth.ts b/apps/www/src/lib/auth.ts deleted file mode 100644 index 05f347e7..00000000 --- a/apps/www/src/lib/auth.ts +++ /dev/null @@ -1,84 +0,0 @@ -import { cache } from "react"; -import { Google } from "arctic"; -import { cookies } from "next/headers"; -import { Lucia, Session, User } from "lucia"; -import { DrizzleSQLiteAdapter } from "@lucia-auth/adapter-drizzle"; - -import { db } from "@umamin/db"; -import { - SelectUser, - user as userSchema, - session as sessionSchema, -} from "@umamin/db/schema/user"; - -export const google = new Google( - process.env.GOOGLE_CLIENT_ID!, - process.env.GOOGLE_CLIENT_SECRET!, - process.env.GOOGLE_REDIRECT_URI!, -); - -const adapter = new DrizzleSQLiteAdapter(db, sessionSchema, userSchema); - -export const lucia = new Lucia(adapter, { - sessionCookie: { - expires: false, - attributes: { - secure: process.env.NODE_ENV === "production", - }, - }, - getUserAttributes: (attributes) => { - return { - ...attributes, - }; - }, -}); - -export const getSession = cache( - async (): Promise< - { user: User; session: Session } | { user: null; session: null } - > => { - const sessionId = cookies().get(lucia.sessionCookieName)?.value ?? null; - - if (!sessionId) { - return { - user: null, - session: null, - }; - } - - const result = await lucia.validateSession(sessionId); - - try { - if (result.session && result.session.fresh) { - const sessionCookie = lucia.createSessionCookie(result.session.id); - cookies().set( - sessionCookie.name, - sessionCookie.value, - sessionCookie.attributes, - ); - } - - if (!result.session) { - const sessionCookie = lucia.createBlankSessionCookie(); - cookies().set( - sessionCookie.name, - sessionCookie.value, - sessionCookie.attributes, - ); - } - } catch (err) { - console.log(err); - } - return result; - }, -); - -declare module "lucia" { - // eslint-disable-next-line no-unused-vars - interface Register { - Lucia: typeof lucia; - DatabaseUserAttributes: DatabaseUserAttributes; - } -} - -interface DatabaseUserAttributes extends SelectUser {} diff --git a/apps/www/src/lib/gql/client.ts b/apps/www/src/lib/gql/client.ts deleted file mode 100644 index a25fa2c4..00000000 --- a/apps/www/src/lib/gql/client.ts +++ /dev/null @@ -1,17 +0,0 @@ -import { Client, cacheExchange, fetchExchange } from "@urql/core"; -import { persistedExchange } from "@urql/exchange-persisted"; - -const client = new Client({ - url: process.env.NEXT_PUBLIC_GQL_URL!, - exchanges: [ - cacheExchange, - persistedExchange({ - enforcePersistedQueries: true, - enableForMutation: true, - generateHash: (_, document: any) => document.documentId, - }), - fetchExchange, - ], -}); - -export default client; diff --git a/apps/www/src/lib/gql/rsc.ts b/apps/www/src/lib/gql/rsc.ts deleted file mode 100644 index 30064a5c..00000000 --- a/apps/www/src/lib/gql/rsc.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { lucia } from "../auth"; -import { registerUrql } from "@urql/next/rsc"; -import { persistedExchange } from "@urql/exchange-persisted"; -import { cacheExchange, createClient, fetchExchange } from "@urql/core"; - -const getClient = (sessionId?: string) => { - const makeClient = () => { - return createClient({ - url: process.env.NEXT_PUBLIC_GQL_URL!, - exchanges: [ - cacheExchange, - persistedExchange({ - enforcePersistedQueries: true, - enableForMutation: true, - generateHash: (_, document: any) => document.documentId, - }), - fetchExchange, - ], - fetchOptions: () => ({ - headers: { - cookie: `${lucia.sessionCookieName}=${sessionId}`, - }, - }), - }); - }; - - const { getClient: _getClient } = registerUrql(makeClient); - - return _getClient(); -}; - -export default getClient; diff --git a/apps/www/src/lib/utils.ts b/apps/www/src/lib/utils.ts deleted file mode 100644 index 33836826..00000000 --- a/apps/www/src/lib/utils.ts +++ /dev/null @@ -1,78 +0,0 @@ -import { toast } from "sonner"; -import { domToPng } from "modern-screenshot"; -import { formatDistanceToNow, fromUnixTime } from "date-fns"; - -export const onSaveImage = (id: string) => { - const target = document.querySelector(`#${id}`); - - if (!target) { - toast.error("An error occured"); - return; - } - - toast.promise( - domToPng(target, { - quality: 1, - scale: 4, - backgroundColor: "#111113", - style: { - scale: "0.9", - display: "grid", - placeItems: "center", - }, - }) - .then((dataUrl) => { - const link = document.createElement("a"); - link.download = `${id}.png`; - link.href = dataUrl; - link.click(); - }) - .catch((err) => { - console.log(err); - }), - { - loading: "Saving...", - success: "Download ready", - error: "An error occured!", - } - ); -}; - -export const formatError = (err: string) => { - return err.replace("[GraphQL] ", ""); -}; - -export function shortTimeAgo(epoch: number) { - const distance = formatDistanceToNow(fromUnixTime(epoch)); - - if (distance === "less than a minute") { - return "just now"; - } - - const minutesMatch = distance.match(/(\d+)\s+min/); - if (minutesMatch) { - return `${minutesMatch[1]}m`; - } - - const hoursMatch = distance.match(/(\d+)\s+hour/); - if (hoursMatch) { - return `${hoursMatch[1]}h`; - } - - const daysMatch = distance.match(/(\d+)\s+day/); - if (daysMatch) { - return `${daysMatch[1]}d`; - } - - const monthsMatch = distance.match(/(\d+)\s+month/); - if (monthsMatch) { - return `${monthsMatch[1]}mo`; - } - - const yearsMatch = distance.match(/(\d+)\s+year/); - if (yearsMatch) { - return `${yearsMatch[1]}y`; - } - - return distance; -} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e096aa7f..3abcd195 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -355,6 +355,9 @@ importers: '@umamin/gql': specifier: workspace:* version: link:../../packages/gql + '@umamin/shared': + specifier: workspace:* + version: link:../../packages/shared '@umamin/ui': specifier: workspace:* version: link:../../packages/ui @@ -4578,7 +4581,6 @@ packages: libsql@0.3.19: resolution: {integrity: sha512-Aj5cQ5uk/6fHdmeW0TiXK42FqUlwx7ytmMLPSaUQPin5HKKKuUPD62MAbN4OEweGBBI7q1BekoEN4gPUEL6MZA==} - cpu: [x64, arm64, wasm32] os: [darwin, linux, win32] lilconfig@2.1.0: @@ -9292,7 +9294,7 @@ snapshots: eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.9.0(eslint@8.57.0) eslint-plugin-react: 7.35.0(eslint@8.57.0) eslint-plugin-react-hooks: 4.6.2(eslint@8.57.0) @@ -9310,7 +9312,7 @@ snapshots: eslint: 8.57.0 eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) eslint-plugin-jsx-a11y: 6.9.0(eslint@8.57.0) eslint-plugin-react: 7.35.0(eslint@8.57.0) eslint-plugin-react-hooks: 4.6.2(eslint@8.57.0) @@ -9364,7 +9366,7 @@ snapshots: enhanced-resolve: 5.17.1 eslint: 8.57.0 eslint-module-utils: 2.8.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) - eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0) + eslint-plugin-import: 2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0) fast-glob: 3.3.2 get-tsconfig: 4.7.6 is-core-module: 2.15.0 @@ -9439,7 +9441,7 @@ snapshots: - eslint-import-resolver-webpack - supports-color - eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1)(eslint@8.57.0): + eslint-plugin-import@2.29.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-typescript@3.6.1(@typescript-eslint/parser@7.2.0(eslint@8.57.0)(typescript@5.5.4))(eslint-import-resolver-node@0.3.9)(eslint-plugin-import@2.29.1(eslint@8.57.0))(eslint@8.57.0))(eslint@8.57.0): dependencies: array-includes: 3.1.8 array.prototype.findlastindex: 1.2.5