add useAuth context

This commit is contained in:
tedraykov 2024-06-21 11:50:58 +03:00
parent cc9def4035
commit 19065c1d82
12 changed files with 181 additions and 109 deletions

2
.nvmrc
View File

@ -1 +1 @@
18
20

View File

@ -1,5 +1,7 @@
import { headers } from 'next/headers';
export const runtime = 'edge';
export default async function AuthorizationPage() {
const headersList = headers();
const access = headersList.get('x-shop-access');

View File

@ -1,5 +1,6 @@
import { LoginMessage } from 'components/auth/login-message';
export const runtime = 'edge'; //this needs to be here on thie page. I don't know why
export const runtime = 'edge';
export default async function LoginPage() {
return (

View File

@ -4,6 +4,7 @@ import { GeistSans } from 'geist/font/sans';
import { ensureStartsWith } from 'lib/utils';
import { ReactNode, Suspense } from 'react';
import './globals.css';
import { AuthProvider } from 'contexts/auth-context';
const { TWITTER_CREATOR, TWITTER_SITE, SITE_NAME } = process.env;
const baseUrl = process.env.NEXT_PUBLIC_VERCEL_URL
@ -36,13 +37,17 @@ export default async function RootLayout({ children }: { children: ReactNode })
return (
<html lang="en" className={GeistSans.variable}>
<body className="bg-white text-black selection:bg-teal-300 dark:bg-neutral-900 dark:text-white dark:selection:bg-pink-500 dark:selection:text-white">
<header>
<Banner />
<Navbar />
</header>
<Suspense>
<main>{children}</main>
</Suspense>
<AuthProvider>
<div>
<header>
<Banner />
<Navbar />
</header>
<Suspense>
<main>{children}</main>
</Suspense>
</div>
</AuthProvider>
</body>
</html>
);

View File

@ -1,5 +1,3 @@
//See https://react.dev/reference/react-dom/hooks/useFormState
//https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations#forms
'use server';
import { redirect } from 'next/navigation';
@ -18,10 +16,8 @@ export async function doLogin(_: any) {
const clientId = CUSTOMER_API_CLIENT_ID;
const origin = ORIGIN_URL;
const loginUrl = new URL(`${customerAccountApiUrl}/auth/oauth/authorize`);
//console.log ("previous", prevState)
try {
//await addToCart(cartId, [{ merchandiseId: selectedVariantId, quantity: 1 }]);
loginUrl.searchParams.set('client_id', clientId);
loginUrl.searchParams.append('response_type', 'code');
loginUrl.searchParams.append('redirect_uri', `${origin}/authorize`);
@ -30,34 +26,33 @@ export async function doLogin(_: any) {
'openid email https://api.customers.com/auth/customer.graphql'
);
const verifier = await generateCodeVerifier();
//const newVerifier = verifier.replace("+", '_').replace("-",'_').replace("/",'_').trim()
const challenge = await generateCodeChallenge(verifier);
cookies().set('shop_verifier', verifier as string, {
// @ts-ignore
//expires: auth?.expires, //not necessary here
});
cookies().set('shop_verifier', verifier as string, {});
const state = await generateRandomString();
const nonce = await generateRandomString();
cookies().set('shop_state', state as string, {
// @ts-ignore
//expires: auth?.expires, //not necessary here
});
cookies().set('shop_nonce', nonce as string, {
// @ts-ignore
//expires: auth?.expires, //not necessary here
});
cookies().set('shop_state', state as string, {});
cookies().set('shop_nonce', nonce as string, {});
loginUrl.searchParams.append('state', state);
loginUrl.searchParams.append('nonce', nonce);
loginUrl.searchParams.append('code_challenge', challenge);
loginUrl.searchParams.append('code_challenge_method', 'S256');
//console.log ("loginURL", loginUrl)
//throw new Error ("Error") //this is how you throw an error, if you want to. Then the catch will execute
} catch (e) {
console.log('Error', e);
//you can throw error here or return - return goes back to form b/c of state, throw will throw the error boundary
//throw new Error ("Error")
return 'Error logging in. Please try again';
}
redirect(`${loginUrl}`); // Navigate to the new post page
}
export async function isLoggedIn() {
const customerToken = cookies().get('shop_customer_token')?.value;
const refreshToken = cookies().get('shop_refresh_token')?.value;
if (!customerToken && !refreshToken) {
return false;
} else {
return true;
}
}

View File

@ -6,9 +6,6 @@ export default async function Login() {
const customerToken = cookies().get('shop_customer_token')?.value;
const refreshToken = cookies().get('shop_refresh_token')?.value;
let isLoggedIn;
//obviously just checking for the cookies without verifying the cookie itself is not ideal. However, the cookie is validated on the
//account page, so a "fake" cookie does nothing, except show the UI and then it would be deleted when clicking on account
//so for now, just checking the cookie for the UI is sufficient. Alternatively, we can do a query here, or a custom JWT
if (!customerToken && !refreshToken) {
isLoggedIn = false;
} else {

View File

@ -1,6 +1,5 @@
'use client';
import { Popover, PopoverButton, PopoverPanel, Transition } from '@headlessui/react';
import { CloseButton, Popover, PopoverButton, PopoverPanel, Transition } from '@headlessui/react';
import { ArrowRightIcon } from '@heroicons/react/16/solid';
import { Menu } from 'lib/shopify/types';
import { Fragment } from 'react';
@ -8,6 +7,8 @@ import OpenProfile from './open-profile';
import { useFormState, useFormStatus } from 'react-dom';
import { doLogin } from 'components/auth/actions';
import { Button } from 'components/button';
import useAuth from 'hooks/use-auth';
import Link from 'next/link';
type ProfilePopoverProps = {
menu: Menu[];
@ -34,7 +35,8 @@ function SubmitButton(props: any) {
);
}
const ProfilePopover = ({ menu }: ProfilePopoverProps) => {
const [message, formAction] = useFormState(doLogin, null);
const [message] = useFormState(doLogin, null);
const { isAuthenticated, loading } = useAuth();
return (
<Popover className="relative">
@ -53,22 +55,29 @@ const ProfilePopover = ({ menu }: ProfilePopoverProps) => {
<PopoverPanel className="absolute -right-10 z-50 mt-2 w-72 max-w-lg px-4 sm:px-0 lg:right-0">
<div className="flex flex-col gap-2 overflow-hidden rounded-md bg-white px-4 py-3 text-black shadow-xl ring-1 ring-black/5">
<span className="text-sm font-medium">My Account</span>
<form action={formAction}>
<SubmitButton message={message} />
<p aria-live="polite" className="sr-only" role="status">
{message}
</p>
</form>
{!isAuthenticated && !loading && <SubmitButton message={message} />}
{menu.length ? (
<ul className="mt-2 flex w-full flex-col divide-y text-sm">
<ul className="flex w-full flex-col divide-y text-sm">
{isAuthenticated && (
<li className="cursor-pointer py-2 hover:underline">
<CloseButton
as={Link}
className="flex w-full flex-row items-center justify-between"
href="/account"
>
My Orders <ArrowRightIcon className="h-3" />
</CloseButton>
</li>
)}
{menu.map((menuItem) => (
<li className="cursor-pointer py-2 hover:underline" key={menuItem.title}>
<a
<CloseButton
as={Link}
className="flex w-full flex-row items-center justify-between"
href={menuItem.path}
>
{menuItem.title} <ArrowRightIcon className="h-3" />
</a>
</CloseButton>
</li>
))}
</ul>

38
contexts/auth-context.tsx Normal file
View File

@ -0,0 +1,38 @@
'use client';
import { isLoggedIn } from 'components/auth/actions';
import { createContext, useState, useEffect } from 'react';
type AuthContextType = {
isAuthenticated: boolean;
loading: boolean;
};
const AuthContext = createContext<AuthContextType>({
isAuthenticated: false,
loading: true
});
type AuthProviderProps = {
children: React.ReactNode;
};
export function AuthProvider({ children }: AuthProviderProps) {
const [isAuthenticated, setIsAuthenticated] = useState(false);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function checkAuth() {
const isLogged = await isLoggedIn();
setIsAuthenticated(isLogged);
setLoading(false);
}
checkAuth();
}, []);
return (
<AuthContext.Provider value={{ isAuthenticated, loading }}>{children}</AuthContext.Provider>
);
}
export default AuthContext;

11
hooks/use-auth.ts Normal file
View File

@ -0,0 +1,11 @@
import AuthContext from 'contexts/auth-context';
import { useContext } from 'react';
export default function useAuth() {
const context = useContext(AuthContext);
if (context === undefined) {
throw new Error('useAuth must be used within an AuthProvider');
}
return context;
}

View File

@ -105,14 +105,13 @@ export async function initialAccessToken(
headersNew.append('User-Agent', userAgent);
headersNew.append('Origin', newOrigin || '');
const tokenRequestUrl = `${customerAccountApiUrl}/auth/oauth/token`;
console.log('sending request to', tokenRequestUrl);
const response = await fetch(tokenRequestUrl, {
method: 'POST',
headers: headersNew,
body
});
console.log('ok', response.ok);
if (!response.ok) {
const error = await response.text();
console.log('data response error auth', error);
@ -336,7 +335,6 @@ export async function isLoggedIn(request: NextRequest, origin: string) {
const refreshToken = request.cookies.get('shop_refresh_token');
const refreshTokenValue = refreshToken?.value;
console.log('customer token', customerTokenValue);
const newHeaders = new Headers(request.headers);
if (!customerTokenValue && !refreshTokenValue) {
const redirectUrl = new URL(`${origin}`);

View File

@ -2,7 +2,7 @@
"private": true,
"packageManager": "pnpm@9.1.2",
"engines": {
"node": ">=18",
"node": ">=20",
"pnpm": ">=7"
},
"scripts": {
@ -32,7 +32,7 @@
"lodash.get": "^4.4.2",
"lodash.kebabcase": "^4.1.1",
"lodash.startcase": "^4.4.0",
"next": "14.1.4",
"next": "14.2.4",
"react": "18.2.0",
"react-dom": "18.2.0",
"react-hook-form": "^7.51.5",
@ -55,7 +55,7 @@
"@vercel/git-hooks": "^1.0.0",
"autoprefixer": "^10.4.19",
"eslint": "^8.57.0",
"eslint-config-next": "^14.1.4",
"eslint-config-next": "^14.2.4",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-unicorn": "^51.0.1",
"lint-staged": "^15.2.2",

132
pnpm-lock.yaml generated
View File

@ -28,7 +28,7 @@ importers:
version: 2.1.0
geist:
specifier: ^1.3.0
version: 1.3.0(next@14.1.4(react-dom@18.2.0(react@18.2.0))(react@18.2.0))
version: 1.3.0(next@14.2.4(react-dom@18.2.0(react@18.2.0))(react@18.2.0))
lodash.get:
specifier: ^4.4.2
version: 4.4.2
@ -39,8 +39,8 @@ importers:
specifier: ^4.4.0
version: 4.4.0
next:
specifier: 14.1.4
version: 14.1.4(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
specifier: 14.2.4
version: 14.2.4(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
react:
specifier: 18.2.0
version: 18.2.0
@ -103,8 +103,8 @@ importers:
specifier: ^8.57.0
version: 8.57.0
eslint-config-next:
specifier: ^14.1.4
version: 14.1.4(eslint@8.57.0)(typescript@5.4.3)
specifier: ^14.2.4
version: 14.2.4(eslint@8.57.0)(typescript@5.4.3)
eslint-config-prettier:
specifier: ^9.1.0
version: 9.1.0(eslint@8.57.0)
@ -245,62 +245,62 @@ packages:
'@jridgewell/trace-mapping@0.3.25':
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
'@next/env@14.1.4':
resolution: {integrity: sha512-e7X7bbn3Z6DWnDi75UWn+REgAbLEqxI8Tq2pkFOFAMpWAWApz/YCUhtWMWn410h8Q2fYiYL7Yg5OlxMOCfFjJQ==}
'@next/env@14.2.4':
resolution: {integrity: sha512-3EtkY5VDkuV2+lNmKlbkibIJxcO4oIHEhBWne6PaAp+76J9KoSsGvNikp6ivzAT8dhhBMYrm6op2pS1ApG0Hzg==}
'@next/eslint-plugin-next@14.1.4':
resolution: {integrity: sha512-n4zYNLSyCo0Ln5b7qxqQeQ34OZKXwgbdcx6kmkQbywr+0k6M3Vinft0T72R6CDAcDrne2IAgSud4uWCzFgc5HA==}
'@next/eslint-plugin-next@14.2.4':
resolution: {integrity: sha512-svSFxW9f3xDaZA3idQmlFw7SusOuWTpDTAeBlO3AEPDltrraV+lqs7mAc6A27YdnpQVVIA3sODqUAAHdWhVWsA==}
'@next/swc-darwin-arm64@14.1.4':
resolution: {integrity: sha512-ubmUkbmW65nIAOmoxT1IROZdmmJMmdYvXIe8211send9ZYJu+SqxSnJM4TrPj9wmL6g9Atvj0S/2cFmMSS99jg==}
'@next/swc-darwin-arm64@14.2.4':
resolution: {integrity: sha512-AH3mO4JlFUqsYcwFUHb1wAKlebHU/Hv2u2kb1pAuRanDZ7pD/A/KPD98RHZmwsJpdHQwfEc/06mgpSzwrJYnNg==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [darwin]
'@next/swc-darwin-x64@14.1.4':
resolution: {integrity: sha512-b0Xo1ELj3u7IkZWAKcJPJEhBop117U78l70nfoQGo4xUSvv0PJSTaV4U9xQBLvZlnjsYkc8RwQN1HoH/oQmLlQ==}
'@next/swc-darwin-x64@14.2.4':
resolution: {integrity: sha512-QVadW73sWIO6E2VroyUjuAxhWLZWEpiFqHdZdoQ/AMpN9YWGuHV8t2rChr0ahy+irKX5mlDU7OY68k3n4tAZTg==}
engines: {node: '>= 10'}
cpu: [x64]
os: [darwin]
'@next/swc-linux-arm64-gnu@14.1.4':
resolution: {integrity: sha512-457G0hcLrdYA/u1O2XkRMsDKId5VKe3uKPvrKVOyuARa6nXrdhJOOYU9hkKKyQTMru1B8qEP78IAhf/1XnVqKA==}
'@next/swc-linux-arm64-gnu@14.2.4':
resolution: {integrity: sha512-KT6GUrb3oyCfcfJ+WliXuJnD6pCpZiosx2X3k66HLR+DMoilRb76LpWPGb4tZprawTtcnyrv75ElD6VncVamUQ==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
'@next/swc-linux-arm64-musl@14.1.4':
resolution: {integrity: sha512-l/kMG+z6MB+fKA9KdtyprkTQ1ihlJcBh66cf0HvqGP+rXBbOXX0dpJatjZbHeunvEHoBBS69GYQG5ry78JMy3g==}
'@next/swc-linux-arm64-musl@14.2.4':
resolution: {integrity: sha512-Alv8/XGSs/ytwQcbCHwze1HmiIkIVhDHYLjczSVrf0Wi2MvKn/blt7+S6FJitj3yTlMwMxII1gIJ9WepI4aZ/A==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
'@next/swc-linux-x64-gnu@14.1.4':
resolution: {integrity: sha512-BapIFZ3ZRnvQ1uWbmqEGJuPT9cgLwvKtxhK/L2t4QYO7l+/DxXuIGjvp1x8rvfa/x1FFSsipERZK70pewbtJtw==}
'@next/swc-linux-x64-gnu@14.2.4':
resolution: {integrity: sha512-ze0ShQDBPCqxLImzw4sCdfnB3lRmN3qGMB2GWDRlq5Wqy4G36pxtNOo2usu/Nm9+V2Rh/QQnrRc2l94kYFXO6Q==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
'@next/swc-linux-x64-musl@14.1.4':
resolution: {integrity: sha512-mqVxTwk4XuBl49qn2A5UmzFImoL1iLm0KQQwtdRJRKl21ylQwwGCxJtIYo2rbfkZHoSKlh/YgztY0qH3wG1xIg==}
'@next/swc-linux-x64-musl@14.2.4':
resolution: {integrity: sha512-8dwC0UJoc6fC7PX70csdaznVMNr16hQrTDAMPvLPloazlcaWfdPogq+UpZX6Drqb1OBlwowz8iG7WR0Tzk/diQ==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
'@next/swc-win32-arm64-msvc@14.1.4':
resolution: {integrity: sha512-xzxF4ErcumXjO2Pvg/wVGrtr9QQJLk3IyQX1ddAC/fi6/5jZCZ9xpuL9Tzc4KPWMFq8GGWFVDMshZOdHGdkvag==}
'@next/swc-win32-arm64-msvc@14.2.4':
resolution: {integrity: sha512-jxyg67NbEWkDyvM+O8UDbPAyYRZqGLQDTPwvrBBeOSyVWW/jFQkQKQ70JDqDSYg1ZDdl+E3nkbFbq8xM8E9x8A==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [win32]
'@next/swc-win32-ia32-msvc@14.1.4':
resolution: {integrity: sha512-WZiz8OdbkpRw6/IU/lredZWKKZopUMhcI2F+XiMAcPja0uZYdMTZQRoQ0WZcvinn9xZAidimE7tN9W5v9Yyfyw==}
'@next/swc-win32-ia32-msvc@14.2.4':
resolution: {integrity: sha512-twrmN753hjXRdcrZmZttb/m5xaCBFa48Dt3FbeEItpJArxriYDunWxJn+QFXdJ3hPkm4u7CKxncVvnmgQMY1ag==}
engines: {node: '>= 10'}
cpu: [ia32]
os: [win32]
'@next/swc-win32-x64-msvc@14.1.4':
resolution: {integrity: sha512-4Rto21sPfw555sZ/XNLqfxDUNeLhNYGO2dlPqsnuCg8N8a2a9u1ltqBOPQ4vj1Gf7eJC0W2hHG2eYUHuiXgY2w==}
'@next/swc-win32-x64-msvc@14.2.4':
resolution: {integrity: sha512-tkLrjBzqFTP8DVrAAQmZelEahfR9OxWpFR++vAI9FBhCiIxtwHwBHC23SBHCTURBtwB4kc/x44imVOnkKGNVGg==}
engines: {node: '>= 10'}
cpu: [x64]
os: [win32]
@ -469,9 +469,15 @@ packages:
'@rushstack/eslint-patch@1.8.0':
resolution: {integrity: sha512-0HejFckBN2W+ucM6cUOlwsByTKt9/+0tWhqUffNIcHqCXkthY/mZ7AuYPK/2IIaGWhdl0h+tICDO0ssLMd6XMQ==}
'@swc/counter@0.1.3':
resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==}
'@swc/helpers@0.5.2':
resolution: {integrity: sha512-E4KcWTpoLHqwPHLxidpOqQbcrZVgi0rsmmZXUle1jXmJfuIf/UWpczUJ7MZZ5tlxytgJXyp0w4PGkkeLiuIdZw==}
'@swc/helpers@0.5.5':
resolution: {integrity: sha512-KGYxvIOXcceOAbEk4bi/dVLEK9z8sZ0uBB3Il5b1rhfClSpcX0yfRO0KmTkqR2cnQDymwLB+25ZyMzICg/cm/A==}
'@tailwindcss/aspect-ratio@0.4.2':
resolution: {integrity: sha512-8QPrypskfBa7QIMuKHg2TA7BqES6vhBrDLOv8Unb6FcFyd3TjKbc6lcmb9UPQHxfl24sXoJ41ux/H7qQQvfaSQ==}
peerDependencies:
@ -939,8 +945,8 @@ packages:
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
engines: {node: '>=10'}
eslint-config-next@14.1.4:
resolution: {integrity: sha512-cihIahbhYAWwXJwZkAaRPpUi5t9aOi/HdfWXOjZeUOqNWXHD8X22kd1KG58Dc3MVaRx3HoR/oMGk2ltcrqDn8g==}
eslint-config-next@14.2.4:
resolution: {integrity: sha512-Qr0wMgG9m6m4uYy2jrYJmyuNlYZzPRQq5Kvb9IDlYwn+7yq6W6sfMNFgb+9guM1KYwuIo6TIaiFhZJ6SnQ/Efw==}
peerDependencies:
eslint: ^7.23.0 || ^8.0.0
typescript: '>=3.3.1'
@ -1568,18 +1574,21 @@ packages:
natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
next@14.1.4:
resolution: {integrity: sha512-1WTaXeSrUwlz/XcnhGTY7+8eiaFvdet5z9u3V2jb+Ek1vFo0VhHKSAIJvDWfQpttWjnyw14kBeq28TPq7bTeEQ==}
next@14.2.4:
resolution: {integrity: sha512-R8/V7vugY+822rsQGQCjoLhMuC9oFj9SOi4Cl4b2wjDrseD0LRZ10W7R6Czo4w9ZznVSshKjuIomsRjvm9EKJQ==}
engines: {node: '>=18.17.0'}
hasBin: true
peerDependencies:
'@opentelemetry/api': ^1.1.0
'@playwright/test': ^1.41.2
react: ^18.2.0
react-dom: ^18.2.0
sass: ^1.3.0
peerDependenciesMeta:
'@opentelemetry/api':
optional: true
'@playwright/test':
optional: true
sass:
optional: true
@ -2406,37 +2415,37 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.4.15
'@next/env@14.1.4': {}
'@next/env@14.2.4': {}
'@next/eslint-plugin-next@14.1.4':
'@next/eslint-plugin-next@14.2.4':
dependencies:
glob: 10.3.10
'@next/swc-darwin-arm64@14.1.4':
'@next/swc-darwin-arm64@14.2.4':
optional: true
'@next/swc-darwin-x64@14.1.4':
'@next/swc-darwin-x64@14.2.4':
optional: true
'@next/swc-linux-arm64-gnu@14.1.4':
'@next/swc-linux-arm64-gnu@14.2.4':
optional: true
'@next/swc-linux-arm64-musl@14.1.4':
'@next/swc-linux-arm64-musl@14.2.4':
optional: true
'@next/swc-linux-x64-gnu@14.1.4':
'@next/swc-linux-x64-gnu@14.2.4':
optional: true
'@next/swc-linux-x64-musl@14.1.4':
'@next/swc-linux-x64-musl@14.2.4':
optional: true
'@next/swc-win32-arm64-msvc@14.1.4':
'@next/swc-win32-arm64-msvc@14.2.4':
optional: true
'@next/swc-win32-ia32-msvc@14.1.4':
'@next/swc-win32-ia32-msvc@14.2.4':
optional: true
'@next/swc-win32-x64-msvc@14.1.4':
'@next/swc-win32-x64-msvc@14.2.4':
optional: true
'@nodelib/fs.scandir@2.1.5':
@ -2597,10 +2606,17 @@ snapshots:
'@rushstack/eslint-patch@1.8.0': {}
'@swc/counter@0.1.3': {}
'@swc/helpers@0.5.2':
dependencies:
tslib: 2.6.2
'@swc/helpers@0.5.5':
dependencies:
'@swc/counter': 0.1.3
tslib: 2.6.2
'@tailwindcss/aspect-ratio@0.4.2(tailwindcss@3.4.1)':
dependencies:
tailwindcss: 3.4.1
@ -3148,9 +3164,9 @@ snapshots:
escape-string-regexp@4.0.0: {}
eslint-config-next@14.1.4(eslint@8.57.0)(typescript@5.4.3):
eslint-config-next@14.2.4(eslint@8.57.0)(typescript@5.4.3):
dependencies:
'@next/eslint-plugin-next': 14.1.4
'@next/eslint-plugin-next': 14.2.4
'@rushstack/eslint-patch': 1.8.0
'@typescript-eslint/parser': 6.21.0(eslint@8.57.0)(typescript@5.4.3)
eslint: 8.57.0
@ -3454,9 +3470,9 @@ snapshots:
functions-have-names@1.2.3: {}
geist@1.3.0(next@14.1.4(react-dom@18.2.0(react@18.2.0))(react@18.2.0)):
geist@1.3.0(next@14.2.4(react-dom@18.2.0(react@18.2.0))(react@18.2.0)):
dependencies:
next: 14.1.4(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
next: 14.2.4(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
get-east-asian-width@1.2.0: {}
@ -3869,10 +3885,10 @@ snapshots:
natural-compare@1.4.0: {}
next@14.1.4(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
next@14.2.4(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@next/env': 14.1.4
'@swc/helpers': 0.5.2
'@next/env': 14.2.4
'@swc/helpers': 0.5.5
busboy: 1.6.0
caniuse-lite: 1.0.30001600
graceful-fs: 4.2.11
@ -3881,15 +3897,15 @@ snapshots:
react-dom: 18.2.0(react@18.2.0)
styled-jsx: 5.1.1(react@18.2.0)
optionalDependencies:
'@next/swc-darwin-arm64': 14.1.4
'@next/swc-darwin-x64': 14.1.4
'@next/swc-linux-arm64-gnu': 14.1.4
'@next/swc-linux-arm64-musl': 14.1.4
'@next/swc-linux-x64-gnu': 14.1.4
'@next/swc-linux-x64-musl': 14.1.4
'@next/swc-win32-arm64-msvc': 14.1.4
'@next/swc-win32-ia32-msvc': 14.1.4
'@next/swc-win32-x64-msvc': 14.1.4
'@next/swc-darwin-arm64': 14.2.4
'@next/swc-darwin-x64': 14.2.4
'@next/swc-linux-arm64-gnu': 14.2.4
'@next/swc-linux-arm64-musl': 14.2.4
'@next/swc-linux-x64-gnu': 14.2.4
'@next/swc-linux-x64-musl': 14.2.4
'@next/swc-win32-arm64-msvc': 14.2.4
'@next/swc-win32-ia32-msvc': 14.2.4
'@next/swc-win32-x64-msvc': 14.2.4
transitivePeerDependencies:
- '@babel/core'
- babel-plugin-macros