登录权限

This commit is contained in:
北枳 2025-07-05 21:39:13 +08:00
parent 6e6bdebb64
commit b83ab834ef
7 changed files with 808 additions and 69 deletions

View File

@ -10,6 +10,12 @@ const OAuthCallbackHandler = dynamic(
{ ssr: false }
);
// Import AuthGuard dynamically for client-side only
const AuthGuard = dynamic(
() => import('@/components/auth/auth-guard'),
{ ssr: false }
);
// Import dev helper in development environment only
const DevHelper = dynamic(
() => import('@/utils/dev-helper').then(() => ({ default: () => null })),
@ -35,7 +41,9 @@ export default function RootLayout({
enableSystem
disableTransitionOnChange
>
<AuthGuard>
{children}
</AuthGuard>
<Toaster />
<OAuthCallbackHandler />
{process.env.NODE_ENV === 'development' && <DevHelper />}

View File

@ -0,0 +1,73 @@
'use client';
import { useEffect, useState } from 'react';
import { useRouter, usePathname } from 'next/navigation';
import { checkAuth, isAuthenticated } from '@/lib/auth';
interface AuthGuardProps {
children: React.ReactNode;
}
export default function AuthGuard({ children }: AuthGuardProps) {
const [isLoading, setIsLoading] = useState(true);
const [isAuthorized, setIsAuthorized] = useState(false);
const router = useRouter();
const pathname = usePathname();
// 不需要鉴权的页面
const publicPaths = ['/login', '/signup', '/forgot-password'];
const isPublicPath = publicPaths.includes(pathname);
useEffect(() => {
const verifyAuth = async () => {
// 如果是公共页面,不需要鉴权
if (isPublicPath) {
setIsAuthorized(true);
setIsLoading(false);
return;
}
// 检查是否有token
if (!isAuthenticated()) {
router.push('/login');
return;
}
try {
// 调用鉴权接口验证token
const isValid = await checkAuth();
if (isValid) {
setIsAuthorized(true);
} else {
// Token无效重定向到登录页
router.push('/login');
}
} catch (error) {
console.error('Auth verification failed:', error);
router.push('/login');
} finally {
setIsLoading(false);
}
};
verifyAuth();
}, [pathname, router, isPublicPath]);
// 显示加载状态
if (isLoading) {
return (
<div className="flex items-center justify-center min-h-screen">
<div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600"></div>
</div>
);
}
// 如果未授权返回null会由router处理跳转
if (!isAuthorized) {
return null;
}
// 授权通过,渲染子组件
return <>{children}</>;
}

View File

@ -51,10 +51,19 @@ export default function Login() {
try {
await loginUser(email, password);
// 登录成功后跳转到首页
router.push('/');
} catch (error) {
} catch (error: any) {
console.error('Login failed:', error);
// 根据错误类型显示不同的错误消息
if (error.message) {
setFormError(error.message);
} else if (error.msg) {
setFormError(error.msg);
} else {
setFormError('登录失败,请检查您的凭据后重试。');
}
} finally {
setIsSubmitting(false);
}
@ -85,12 +94,12 @@ export default function Login() {
)}
<div className="mb-3">
<label className="form-label"></label>
<label className="form-label"></label>
<input
placeholder="电子邮箱地址"
placeholder="请输入用户名"
required
className="form-control"
type="email"
type="text"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
@ -98,7 +107,7 @@ export default function Login() {
<div className="mb-3">
<label className="form-label"></label>
<input
placeholder="密码"
placeholder="请输入密码"
required
className="form-control"
type="password"
@ -111,7 +120,9 @@ export default function Login() {
</div>
{formError && (
<div className="text-red-500 text-sm mb-3">{formError}</div>
<div className="bg-red-500/20 text-red-300 p-3 mb-4 rounded-lg border border-red-500/20">
{formError}
</div>
)}
<button

123
lib/api.ts Normal file
View File

@ -0,0 +1,123 @@
import { getToken, clearAuthData } from './auth';
// API基础URL
const API_BASE_URL = 'https://77.api.qikongjian.com';
/**
* API请求方法
*/
export const apiRequest = async (
endpoint: string,
options: RequestInit = {}
): Promise<any> => {
const token = getToken();
// 构建请求头
const headers: HeadersInit = {
'Accept': 'application/json',
'Content-Type': 'application/json',
...options.headers,
};
// 添加token到请求头如果存在
if (token) {
headers['X-EASE-ADMIN-TOKEN'] = token;
}
try {
const response = await fetch(`${API_BASE_URL}${endpoint}`, {
...options,
headers,
});
// 检查响应状态
if (!response.ok) {
if (response.status === 401) {
// Token过期或无效
clearAuthData();
window.location.href = '/login';
throw new Error('身份验证失败,请重新登录');
}
throw new Error(`请求失败: ${response.status}`);
}
const data = await response.json();
// 检查业务状态码
if (data.code === '401' || data.status === 401) {
clearAuthData();
window.location.href = '/login';
throw new Error('身份验证失败,请重新登录');
}
return data;
} catch (error) {
console.error('API request failed:', error);
throw error;
}
};
/**
* GET请求
*/
export const apiGet = (endpoint: string, options: RequestInit = {}) => {
return apiRequest(endpoint, {
...options,
method: 'GET',
});
};
/**
* POST请求
*/
export const apiPost = (endpoint: string, data?: any, options: RequestInit = {}) => {
return apiRequest(endpoint, {
...options,
method: 'POST',
body: data ? JSON.stringify(data) : undefined,
});
};
/**
* PUT请求
*/
export const apiPut = (endpoint: string, data?: any, options: RequestInit = {}) => {
return apiRequest(endpoint, {
...options,
method: 'PUT',
body: data ? JSON.stringify(data) : undefined,
});
};
/**
* DELETE请求
*/
export const apiDelete = (endpoint: string, options: RequestInit = {}) => {
return apiRequest(endpoint, {
...options,
method: 'DELETE',
});
};
/**
*
*/
export const apiUpload = (endpoint: string, formData: FormData, options: RequestInit = {}) => {
const token = getToken();
const headers: HeadersInit = {
...options.headers,
};
// 添加token到请求头如果存在
if (token) {
headers['X-EASE-ADMIN-TOKEN'] = token;
}
return apiRequest(endpoint, {
...options,
method: 'POST',
headers,
body: formData,
});
};

View File

@ -1,6 +1,202 @@
import { BASE_URL } from "@/api/constants";
// Mock Google OAuth configuration
// API配置
const API_BASE_URL = 'https://77.api.qikongjian.com';
// Token存储键
const TOKEN_KEY = 'X-EASE-ADMIN-TOKEN';
const USER_KEY = 'currentUser';
/**
*
*/
export const loginUser = async (email: string, password: string) => {
try {
const response = await fetch(`${API_BASE_URL}/admin/login/signIn`, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
},
body: JSON.stringify({
userName: email,
password: password,
}),
});
const data = await response.json();
if (data.code === '200' && data.status === 200) {
// 保存token到本地存储
setToken(data.body.token);
// 保存用户信息
const user = {
id: data.body.userId,
name: data.body.userName,
email: email,
token: data.body.token,
};
setUser(user);
return user;
} else {
throw new Error(data.msg || '登录失败');
}
} catch (error) {
console.error('Login failed:', error);
throw error;
}
};
/**
*
*/
export const checkAuth = async (): Promise<boolean> => {
const token = getToken();
if (!token) {
return false;
}
try {
const response = await fetch(`${API_BASE_URL}/admin/login/auth`, {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-EASE-ADMIN-TOKEN': token,
},
});
const data = await response.json();
if (data.code === '401' || data.status === 401) {
// Token无效清除本地存储
clearAuthData();
return false;
}
return data.code === '200' && data.status === 200;
} catch (error) {
console.error('Auth check failed:', error);
return false;
}
};
/**
* token
*/
export const getToken = (): string | null => {
if (typeof window === 'undefined') return null;
return localStorage.getItem(TOKEN_KEY);
};
/**
* token
*/
export const setToken = (token: string) => {
if (typeof window === 'undefined') return;
localStorage.setItem(TOKEN_KEY, token);
};
/**
*
*/
export const getCurrentUser = () => {
if (typeof window === 'undefined') return null;
const userJson = localStorage.getItem(USER_KEY);
if (!userJson) return null;
try {
return JSON.parse(userJson);
} catch (error) {
console.error('Failed to parse user data from storage', error);
return null;
}
};
/**
*
*/
export const setUser = (user: any) => {
if (typeof window === 'undefined') return;
localStorage.setItem(USER_KEY, JSON.stringify(user));
};
/**
*
*/
export const clearAuthData = () => {
if (typeof window === 'undefined') return;
localStorage.removeItem(TOKEN_KEY);
localStorage.removeItem(USER_KEY);
};
/**
*
*/
export const logoutUser = () => {
clearAuthData();
window.location.href = '/login';
};
/**
*
*/
export const isAuthenticated = (): boolean => {
return !!getToken();
};
/**
* token的请求头
*/
export const getAuthHeaders = () => {
const token = getToken();
return {
'Accept': 'application/json',
'Content-Type': 'application/json',
...(token && { 'X-EASE-ADMIN-TOKEN': token }),
};
};
/**
* token处理的fetch封装
*/
export const authFetch = async (url: string, options: RequestInit = {}) => {
const token = getToken();
if (!token) {
throw new Error('No token available');
}
const headers = {
'Accept': 'application/json',
'Content-Type': 'application/json',
'X-EASE-ADMIN-TOKEN': token,
...options.headers,
};
const response = await fetch(url, {
...options,
headers,
});
// 检查是否token过期
if (response.status === 401) {
const data = await response.json();
if (data.code === '401') {
// Token过期清除本地数据并重定向到登录页
clearAuthData();
window.location.href = '/login';
throw new Error('Token expired');
}
}
return response;
};
// Google OAuth相关函数保持不变
const GOOGLE_CLIENT_ID = '1016208801816-qtvcvki2jobmcin1g4e7u4sotr0p8g3u.apps.googleusercontent.com';
const GOOGLE_REDIRECT_URI = typeof window !== 'undefined'
? BASE_URL+'/users/oauth/callback'
@ -25,55 +221,6 @@ export const signInWithGoogle = () => {
window.location.href = `https://accounts.google.com/o/oauth2/v2/auth?${params.toString()}`;
};
/**
* Gets the current user from session storage (mock implementation)
*/
export const getCurrentUser = () => {
if (typeof window === 'undefined') return null;
const userJson = sessionStorage.getItem('currentUser');
if (!userJson) return null;
try {
return JSON.parse(userJson);
} catch (error) {
console.error('Failed to parse user data from session', error);
return null;
}
};
/**
* Handles user login (mock implementation)
*/
export const loginUser = async (email: string, password: string) => {
// This is a mock implementation
// In a real app, you would make an API call to your backend
return new Promise((resolve) => {
setTimeout(() => {
// Mock user data
const user = {
id: '123',
name: 'Test User',
email,
avatar: 'https://i.pravatar.cc/150?u=' + email,
};
// Store in session storage (just for demo purposes)
sessionStorage.setItem('currentUser', JSON.stringify(user));
resolve(user);
}, 1000);
});
};
/**
* Handles user logout
*/
export const logoutUser = () => {
if (typeof window === 'undefined') return;
sessionStorage.removeItem('currentUser');
window.location.href = '/login';
};
/**
* Generates and stores a state parameter for OAuth to prevent CSRF attacks
*/

395
package-lock.json generated
View File

@ -41,6 +41,8 @@
"@radix-ui/react-toggle": "^1.1.0",
"@radix-ui/react-toggle-group": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.2",
"@tensorflow-models/coco-ssd": "^2.2.3",
"@tensorflow/tfjs": "^4.22.0",
"@types/gsap": "^1.20.2",
"@types/node": "20.6.2",
"@types/react": "18.2.22",
@ -59,7 +61,7 @@
"embla-carousel-react": "^8.3.0",
"eslint": "8.49.0",
"eslint-config-next": "13.5.1",
"framer-motion": "^12.19.1",
"framer-motion": "^12.23.0",
"gsap": "^3.13.0",
"input-otp": "^1.2.4",
"lodash": "^4.17.21",
@ -6758,6 +6760,144 @@
"tslib": "^2.4.0"
}
},
"node_modules/@tensorflow-models/coco-ssd": {
"version": "2.2.3",
"resolved": "https://registry.npmmirror.com/@tensorflow-models/coco-ssd/-/coco-ssd-2.2.3.tgz",
"integrity": "sha512-iCLGktG/XhHbP6h2FWxqCKMp/Px0lCp6MZU1fjNhjDHeaWEC9G7S7cZrnPXsfH+NewCM53YShlrHnknxU3SQig==",
"license": "Apache-2.0",
"peerDependencies": {
"@tensorflow/tfjs-converter": "^4.10.0",
"@tensorflow/tfjs-core": "^4.10.0"
}
},
"node_modules/@tensorflow/tfjs": {
"version": "4.22.0",
"resolved": "https://registry.npmmirror.com/@tensorflow/tfjs/-/tfjs-4.22.0.tgz",
"integrity": "sha512-0TrIrXs6/b7FLhLVNmfh8Sah6JgjBPH4mZ8JGb7NU6WW+cx00qK5BcAZxw7NCzxj6N8MRAIfHq+oNbPUNG5VAg==",
"license": "Apache-2.0",
"dependencies": {
"@tensorflow/tfjs-backend-cpu": "4.22.0",
"@tensorflow/tfjs-backend-webgl": "4.22.0",
"@tensorflow/tfjs-converter": "4.22.0",
"@tensorflow/tfjs-core": "4.22.0",
"@tensorflow/tfjs-data": "4.22.0",
"@tensorflow/tfjs-layers": "4.22.0",
"argparse": "^1.0.10",
"chalk": "^4.1.0",
"core-js": "3.29.1",
"regenerator-runtime": "^0.13.5",
"yargs": "^16.0.3"
},
"bin": {
"tfjs-custom-module": "dist/tools/custom_module/cli.js"
}
},
"node_modules/@tensorflow/tfjs-backend-cpu": {
"version": "4.22.0",
"resolved": "https://registry.npmmirror.com/@tensorflow/tfjs-backend-cpu/-/tfjs-backend-cpu-4.22.0.tgz",
"integrity": "sha512-1u0FmuLGuRAi8D2c3cocHTASGXOmHc/4OvoVDENJayjYkS119fcTcQf4iHrtLthWyDIPy3JiPhRrZQC9EwnhLw==",
"license": "Apache-2.0",
"dependencies": {
"@types/seedrandom": "^2.4.28",
"seedrandom": "^3.0.5"
},
"engines": {
"yarn": ">= 1.3.2"
},
"peerDependencies": {
"@tensorflow/tfjs-core": "4.22.0"
}
},
"node_modules/@tensorflow/tfjs-backend-webgl": {
"version": "4.22.0",
"resolved": "https://registry.npmmirror.com/@tensorflow/tfjs-backend-webgl/-/tfjs-backend-webgl-4.22.0.tgz",
"integrity": "sha512-H535XtZWnWgNwSzv538czjVlbJebDl5QTMOth4RXr2p/kJ1qSIXE0vZvEtO+5EC9b00SvhplECny2yDewQb/Yg==",
"license": "Apache-2.0",
"dependencies": {
"@tensorflow/tfjs-backend-cpu": "4.22.0",
"@types/offscreencanvas": "~2019.3.0",
"@types/seedrandom": "^2.4.28",
"seedrandom": "^3.0.5"
},
"engines": {
"yarn": ">= 1.3.2"
},
"peerDependencies": {
"@tensorflow/tfjs-core": "4.22.0"
}
},
"node_modules/@tensorflow/tfjs-converter": {
"version": "4.22.0",
"resolved": "https://registry.npmmirror.com/@tensorflow/tfjs-converter/-/tfjs-converter-4.22.0.tgz",
"integrity": "sha512-PT43MGlnzIo+YfbsjM79Lxk9lOq6uUwZuCc8rrp0hfpLjF6Jv8jS84u2jFb+WpUeuF4K33ZDNx8CjiYrGQ2trQ==",
"license": "Apache-2.0",
"peerDependencies": {
"@tensorflow/tfjs-core": "4.22.0"
}
},
"node_modules/@tensorflow/tfjs-core": {
"version": "4.22.0",
"resolved": "https://registry.npmmirror.com/@tensorflow/tfjs-core/-/tfjs-core-4.22.0.tgz",
"integrity": "sha512-LEkOyzbknKFoWUwfkr59vSB68DMJ4cjwwHgicXN0DUi3a0Vh1Er3JQqCI1Hl86GGZQvY8ezVrtDIvqR1ZFW55A==",
"license": "Apache-2.0",
"dependencies": {
"@types/long": "^4.0.1",
"@types/offscreencanvas": "~2019.7.0",
"@types/seedrandom": "^2.4.28",
"@webgpu/types": "0.1.38",
"long": "4.0.0",
"node-fetch": "~2.6.1",
"seedrandom": "^3.0.5"
},
"engines": {
"yarn": ">= 1.3.2"
}
},
"node_modules/@tensorflow/tfjs-core/node_modules/@types/offscreencanvas": {
"version": "2019.7.3",
"resolved": "https://registry.npmmirror.com/@types/offscreencanvas/-/offscreencanvas-2019.7.3.tgz",
"integrity": "sha512-ieXiYmgSRXUDeOntE1InxjWyvEelZGP63M+cGuquuRLuIKKT1osnkXjxev9B7d1nXSug5vpunx+gNlbVxMlC9A==",
"license": "MIT"
},
"node_modules/@tensorflow/tfjs-core/node_modules/@webgpu/types": {
"version": "0.1.38",
"resolved": "https://registry.npmmirror.com/@webgpu/types/-/types-0.1.38.tgz",
"integrity": "sha512-7LrhVKz2PRh+DD7+S+PVaFd5HxaWQvoMqBbsV9fNJO1pjUs1P8bM2vQVNfk+3URTqbuTI7gkXi0rfsN0IadoBA==",
"license": "BSD-3-Clause"
},
"node_modules/@tensorflow/tfjs-data": {
"version": "4.22.0",
"resolved": "https://registry.npmmirror.com/@tensorflow/tfjs-data/-/tfjs-data-4.22.0.tgz",
"integrity": "sha512-dYmF3LihQIGvtgJrt382hSRH4S0QuAp2w1hXJI2+kOaEqo5HnUPG0k5KA6va+S1yUhx7UBToUKCBHeLHFQRV4w==",
"license": "Apache-2.0",
"dependencies": {
"@types/node-fetch": "^2.1.2",
"node-fetch": "~2.6.1",
"string_decoder": "^1.3.0"
},
"peerDependencies": {
"@tensorflow/tfjs-core": "4.22.0",
"seedrandom": "^3.0.5"
}
},
"node_modules/@tensorflow/tfjs-layers": {
"version": "4.22.0",
"resolved": "https://registry.npmmirror.com/@tensorflow/tfjs-layers/-/tfjs-layers-4.22.0.tgz",
"integrity": "sha512-lybPj4ZNj9iIAPUj7a8ZW1hg8KQGfqWLlCZDi9eM/oNKCCAgchiyzx8OrYoWmRrB+AM6VNEeIT+2gZKg5ReihA==",
"license": "Apache-2.0 AND MIT",
"peerDependencies": {
"@tensorflow/tfjs-core": "4.22.0"
}
},
"node_modules/@tensorflow/tfjs/node_modules/argparse": {
"version": "1.0.10",
"resolved": "https://registry.npmmirror.com/argparse/-/argparse-1.0.10.tgz",
"integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
"license": "MIT",
"dependencies": {
"sprintf-js": "~1.0.2"
}
},
"node_modules/@tweenjs/tween.js": {
"version": "23.1.3",
"resolved": "https://registry.npmmirror.com/@tweenjs/tween.js/-/tween.js-23.1.3.tgz",
@ -6897,11 +7037,33 @@
"dev": true,
"license": "MIT"
},
"node_modules/@types/long": {
"version": "4.0.2",
"resolved": "https://registry.npmmirror.com/@types/long/-/long-4.0.2.tgz",
"integrity": "sha512-MqTGEo5bj5t157U6fA/BiDynNkn0YknVdh48CMPkTSpFTVmvao5UQmm7uEF6xBEo7qIMAlY/JSleYaE6VOdpaA==",
"license": "MIT"
},
"node_modules/@types/node": {
"version": "20.6.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.6.2.tgz",
"integrity": "sha512-Y+/1vGBHV/cYk6OI1Na/LHzwnlNCAfU3ZNGrc1LdRe/LAIbdDPTTv/HU3M7yXN448aTVDq3eKRm2cg7iKLb8gw=="
},
"node_modules/@types/node-fetch": {
"version": "2.6.12",
"resolved": "https://registry.npmmirror.com/@types/node-fetch/-/node-fetch-2.6.12.tgz",
"integrity": "sha512-8nneRWKCg3rMtF69nLQJnOYUcbafYeFSjqkw3jCRLsqkWFlHaoQrr5mXmofFGOx3DKn7UfmBMyov8ySvLRVldA==",
"license": "MIT",
"dependencies": {
"@types/node": "*",
"form-data": "^4.0.0"
}
},
"node_modules/@types/offscreencanvas": {
"version": "2019.3.0",
"resolved": "https://registry.npmmirror.com/@types/offscreencanvas/-/offscreencanvas-2019.3.0.tgz",
"integrity": "sha512-esIJx9bQg+QYF0ra8GnvfianIY8qWB0GBx54PK5Eps6m+xTj86KLavHv6qDhzKcu5UUOgNfJ2pWaIIV7TRUd9Q==",
"license": "MIT"
},
"node_modules/@types/prop-types": {
"version": "15.7.13",
"resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz",
@ -6961,6 +7123,12 @@
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.23.0.tgz",
"integrity": "sha512-YIoDCTH3Af6XM5VuwGG/QL/CJqga1Zm3NkU3HZ4ZHK2fRMPYP1VczsTUqtsf43PH/iJNVlPHAo2oWX7BSdB2Hw=="
},
"node_modules/@types/seedrandom": {
"version": "2.4.34",
"resolved": "https://registry.npmmirror.com/@types/seedrandom/-/seedrandom-2.4.34.tgz",
"integrity": "sha512-ytDiArvrn/3Xk6/vtylys5tlY6eo7Ane0hvcx++TKo6RxQXuVfW0AF/oeWqAj9dN29SyhtawuXstgmPlwNcv/A==",
"license": "MIT"
},
"node_modules/@types/stats.js": {
"version": "0.17.4",
"resolved": "https://registry.npmmirror.com/@types/stats.js/-/stats.js-0.17.4.tgz",
@ -8018,6 +8186,54 @@
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA=="
},
"node_modules/cliui": {
"version": "7.0.4",
"resolved": "https://registry.npmmirror.com/cliui/-/cliui-7.0.4.tgz",
"integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
"license": "ISC",
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.0",
"wrap-ansi": "^7.0.0"
}
},
"node_modules/cliui/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"license": "MIT"
},
"node_modules/cliui/node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/cliui/node_modules/wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmmirror.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/clsx": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
@ -8161,6 +8377,17 @@
"toggle-selection": "^1.0.6"
}
},
"node_modules/core-js": {
"version": "3.29.1",
"resolved": "https://registry.npmmirror.com/core-js/-/core-js-3.29.1.tgz",
"integrity": "sha512-+jwgnhg6cQxKYIIjGtAHq2nwUOolo9eoFZ4sHfUH09BLXBgxnH4gA0zEd+t+BO2cNB8idaBtZFcFTRjQJRJmAw==",
"hasInstallScript": true,
"license": "MIT",
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/core-js"
}
},
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@ -9457,12 +9684,12 @@
}
},
"node_modules/framer-motion": {
"version": "12.19.2",
"resolved": "https://registry.npmmirror.com/framer-motion/-/framer-motion-12.19.2.tgz",
"integrity": "sha512-0cWMLkYr+i0emeXC4hkLF+5aYpzo32nRdQ0D/5DI460B3O7biQ3l2BpDzIGsAHYuZ0fpBP0DC8XBkVf6RPAlZw==",
"version": "12.23.0",
"resolved": "https://registry.npmmirror.com/framer-motion/-/framer-motion-12.23.0.tgz",
"integrity": "sha512-xf6NxTGAyf7zR4r2KlnhFmsRfKIbjqeBupEDBAaEtVIBJX96sAon00kMlsKButSIRwPSHjbRrAPnYdJJ9kyhbA==",
"license": "MIT",
"dependencies": {
"motion-dom": "^12.19.0",
"motion-dom": "^12.22.0",
"motion-utils": "^12.19.0",
"tslib": "^2.4.0"
},
@ -9544,6 +9771,15 @@
"node": ">=6.9.0"
}
},
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmmirror.com/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"license": "ISC",
"engines": {
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/get-intrinsic": {
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
@ -10509,6 +10745,12 @@
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
},
"node_modules/long": {
"version": "4.0.0",
"resolved": "https://registry.npmmirror.com/long/-/long-4.0.0.tgz",
"integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==",
"license": "Apache-2.0"
},
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
@ -10657,9 +10899,9 @@
}
},
"node_modules/motion-dom": {
"version": "12.19.0",
"resolved": "https://registry.npmmirror.com/motion-dom/-/motion-dom-12.19.0.tgz",
"integrity": "sha512-m96uqq8VbwxFLU0mtmlsIVe8NGGSdpBvBSHbnnOJQxniPaabvVdGgxSamhuDwBsRhwX7xPxdICgVJlOpzn/5bw==",
"version": "12.22.0",
"resolved": "https://registry.npmmirror.com/motion-dom/-/motion-dom-12.22.0.tgz",
"integrity": "sha512-ooH7+/BPw9gOsL9VtPhEJHE2m4ltnhMlcGMhEqA0YGNhKof7jdaszvsyThXI6LVIKshJUZ9/CP6HNqQhJfV7kw==",
"license": "MIT",
"dependencies": {
"motion-utils": "^12.19.0"
@ -10814,6 +11056,26 @@
"url": "https://github.com/sponsors/colinhacks"
}
},
"node_modules/node-fetch": {
"version": "2.6.13",
"resolved": "https://registry.npmmirror.com/node-fetch/-/node-fetch-2.6.13.tgz",
"integrity": "sha512-StxNAxh15zr77QvvkmveSQ8uCQ4+v5FkvNTj0OESmiHu+VRi/gXArXtkWMElOsOUNLtUEvI4yS+rdtOHZTwlQA==",
"license": "MIT",
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/node-releases": {
"version": "2.0.18",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz",
@ -12434,6 +12696,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/regenerator-runtime": {
"version": "0.13.11",
"resolved": "https://registry.npmmirror.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
"integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==",
"license": "MIT"
},
"node_modules/regexp.prototype.flags": {
"version": "1.5.3",
"resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.3.tgz",
@ -12451,6 +12719,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/require-from-string": {
"version": "2.0.2",
"resolved": "https://registry.npmmirror.com/require-from-string/-/require-from-string-2.0.2.tgz",
@ -12580,8 +12857,7 @@
"url": "https://feross.org/support"
}
],
"license": "MIT",
"peer": true
"license": "MIT"
},
"node_modules/safe-regex-test": {
"version": "1.0.3",
@ -12685,6 +12961,12 @@
"integrity": "sha512-bnnvJL28/Rtz/kz2+4wpBjHzWoEzXhVg/TE8BeVGJHUqE8THNIRnDxDWMktwM+qahvlRdvlLdsQfYe+cuqfZeA==",
"license": "ISC"
},
"node_modules/seedrandom": {
"version": "3.0.5",
"resolved": "https://registry.npmmirror.com/seedrandom/-/seedrandom-3.0.5.tgz",
"integrity": "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg==",
"license": "MIT"
},
"node_modules/semver": {
"version": "7.6.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
@ -12836,6 +13118,12 @@
"source-map": "^0.6.0"
}
},
"node_modules/sprintf-js": {
"version": "1.0.3",
"resolved": "https://registry.npmmirror.com/sprintf-js/-/sprintf-js-1.0.3.tgz",
"integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
"license": "BSD-3-Clause"
},
"node_modules/stop-iteration-iterator": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz",
@ -12855,6 +13143,15 @@
"node": ">=10.0.0"
}
},
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmmirror.com/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"license": "MIT",
"dependencies": {
"safe-buffer": "~5.2.0"
}
},
"node_modules/string-convert": {
"version": "0.2.1",
"resolved": "https://registry.npmmirror.com/string-convert/-/string-convert-0.2.1.tgz",
@ -13441,6 +13738,12 @@
"integrity": "sha512-BiZS+C1OS8g/q2RRbJmy59xpyghNBqrr6k5L/uKBGRsTfxmu3ffiRnd8mlGPUVayg8pvfi5urfnu8TU7DVOkLQ==",
"license": "MIT"
},
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmmirror.com/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"license": "MIT"
},
"node_modules/tree-changes": {
"version": "0.11.3",
"resolved": "https://registry.npmmirror.com/tree-changes/-/tree-changes-0.11.3.tgz",
@ -13803,6 +14106,12 @@
"integrity": "sha512-8O/zu+RC7yjikxiuhsXzRZ8vvjV+Qq4PUKZBQsLLcq6fqbrSF3Vh99l7fT8zeEjKjDBNH2Qxsxq5mRJIuBmM3Q==",
"license": "BSD-3-Clause"
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmmirror.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"license": "BSD-2-Clause"
},
"node_modules/webpack": {
"version": "5.99.9",
"resolved": "https://registry.npmmirror.com/webpack/-/webpack-5.99.9.tgz",
@ -13899,6 +14208,16 @@
"node": ">=10.13.0"
}
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmmirror.com/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"license": "MIT",
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@ -14088,6 +14407,15 @@
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmmirror.com/y18n/-/y18n-5.0.8.tgz",
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
"license": "ISC",
"engines": {
"node": ">=10"
}
},
"node_modules/yallist": {
"version": "3.1.1",
"resolved": "https://registry.npmmirror.com/yallist/-/yallist-3.1.1.tgz",
@ -14106,6 +14434,53 @@
"node": ">= 14"
}
},
"node_modules/yargs": {
"version": "16.2.0",
"resolved": "https://registry.npmmirror.com/yargs/-/yargs-16.2.0.tgz",
"integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
"license": "MIT",
"dependencies": {
"cliui": "^7.0.2",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
"string-width": "^4.2.0",
"y18n": "^5.0.5",
"yargs-parser": "^20.2.2"
},
"engines": {
"node": ">=10"
}
},
"node_modules/yargs-parser": {
"version": "20.2.9",
"resolved": "https://registry.npmmirror.com/yargs-parser/-/yargs-parser-20.2.9.tgz",
"integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
"license": "ISC",
"engines": {
"node": ">=10"
}
},
"node_modules/yargs/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmmirror.com/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"license": "MIT"
},
"node_modules/yargs/node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmmirror.com/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",

View File

@ -42,6 +42,8 @@
"@radix-ui/react-toggle": "^1.1.0",
"@radix-ui/react-toggle-group": "^1.1.0",
"@radix-ui/react-tooltip": "^1.1.2",
"@tensorflow-models/coco-ssd": "^2.2.3",
"@tensorflow/tfjs": "^4.22.0",
"@types/gsap": "^1.20.2",
"@types/node": "20.6.2",
"@types/react": "18.2.22",
@ -60,7 +62,7 @@
"embla-carousel-react": "^8.3.0",
"eslint": "8.49.0",
"eslint-config-next": "13.5.1",
"framer-motion": "^12.19.1",
"framer-motion": "^12.23.0",
"gsap": "^3.13.0",
"input-otp": "^1.2.4",
"lodash": "^4.17.21",