feat: Implement Linux DO OAuth authentication flow and integrate account settings component.

This commit is contained in:
kuekhaoyang
2025-11-29 14:07:14 +08:00
parent c5cb7fd1e3
commit d9963c8543
8 changed files with 316 additions and 121 deletions
+19
View File
@@ -0,0 +1,19 @@
import { redirect } from 'next/navigation';
export async function GET() {
const CLIENT_ID = process.env.LINUX_DO_CLIENT_ID;
const REDIRECT_URI = `${process.env.NEXT_PUBLIC_APP_URL}/api/oauth/callback`;
if (!CLIENT_ID || !process.env.NEXT_PUBLIC_APP_URL) {
return new Response('Missing configuration', { status: 500 });
}
const params = new URLSearchParams({
client_id: CLIENT_ID,
redirect_uri: REDIRECT_URI,
response_type: 'code',
scope: 'user', // As per user guide
});
redirect(`https://connect.linux.do/oauth2/authorize?${params.toString()}`);
}
+57
View File
@@ -0,0 +1,57 @@
import { NextRequest, NextResponse } from 'next/server';
export async function GET(request: NextRequest) {
const searchParams = request.nextUrl.searchParams;
const code = searchParams.get('code');
if (!code) {
return NextResponse.redirect(new URL('/settings?error=no_code', request.url));
}
const CLIENT_ID = process.env.LINUX_DO_CLIENT_ID;
const CLIENT_SECRET = process.env.LINUX_DO_CLIENT_SECRET;
const REDIRECT_URI = `${process.env.NEXT_PUBLIC_APP_URL}/api/oauth/callback`;
if (!CLIENT_ID || !CLIENT_SECRET || !process.env.NEXT_PUBLIC_APP_URL) {
return NextResponse.redirect(new URL('/settings?error=config_missing', request.url));
}
try {
// 1. Exchange code for token
const tokenResponse = await fetch('https://connect.linux.do/oauth2/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
client_id: CLIENT_ID,
client_secret: CLIENT_SECRET,
code,
redirect_uri: REDIRECT_URI,
grant_type: 'authorization_code',
}),
});
const tokenData = await tokenResponse.json();
if (!tokenData.access_token) {
throw new Error('Failed to get access token');
}
// 2. Get User Info
const userResponse = await fetch('https://connect.linux.do/api/user', {
headers: { Authorization: `Bearer ${tokenData.access_token}` },
});
const userData = await userResponse.json();
// 3. Redirect with data
const redirectUrl = new URL('/settings', request.url);
redirectUrl.searchParams.set('auth_success', 'true');
redirectUrl.searchParams.set('token', tokenData.access_token);
redirectUrl.searchParams.set('user', JSON.stringify(userData));
return NextResponse.redirect(redirectUrl);
} catch (error) {
console.error('OAuth Error:', error);
return NextResponse.redirect(new URL('/settings?error=auth_failed', request.url));
}
}
+5 -1
View File
@@ -8,6 +8,7 @@ import { SourceSettings } from '@/components/settings/SourceSettings';
import { SortSettings } from '@/components/settings/SortSettings';
import { DataSettings } from '@/components/settings/DataSettings';
import { SettingsHeader } from '@/components/settings/SettingsHeader';
import { AccountSettings } from '@/components/settings/AccountSettings';
import { useSettingsPage } from './hooks/useSettingsPage';
export default function SettingsPage() {
@@ -38,10 +39,13 @@ export default function SettingsPage() {
return (
<div className="min-h-screen bg-[var(--bg-color)] bg-[image:var(--bg-image)] bg-fixed">
<div className="container mx-auto px-4 py-8 max-w-4xl">
<div className="container mx-auto px-4 py-8 max-w-4xl space-y-8">
{/* Header */}
<SettingsHeader />
{/* Account Settings */}
<AccountSettings />
{/* Source Management */}
<SourceSettings
sources={sources}
+109
View File
@@ -0,0 +1,109 @@
'use client';
import { useEffect } from 'react';
import { useSearchParams, useRouter } from 'next/navigation';
import { useAuthStore, type LinuxDoUser } from '@/lib/store/auth-store';
import { useHistoryStore } from '@/lib/store/history-store';
import { useSearchHistoryStore } from '@/lib/store/search-history-store';
export function AccountSettings() {
const searchParams = useSearchParams();
const router = useRouter();
const { user, isAuthenticated, login, logout } = useAuthStore();
const { clearHistory } = useHistoryStore();
const { clearSearchHistory } = useSearchHistoryStore();
useEffect(() => {
const authSuccess = searchParams.get('auth_success');
const token = searchParams.get('token');
const userStr = searchParams.get('user');
if (authSuccess && token && userStr) {
try {
const userData = JSON.parse(userStr) as LinuxDoUser;
// Clear existing data before logging in new user
clearHistory();
clearSearchHistory();
login(userData, token);
// Clean URL
router.replace('/settings');
} catch (e) {
console.error('Failed to parse user data', e);
}
}
}, [searchParams, login, clearHistory, clearSearchHistory, router]);
const handleLogin = () => {
// Clear data before redirecting to ensure clean state
clearHistory();
clearSearchHistory();
router.push('/api/oauth/authorize');
};
const handleLogout = () => {
logout();
clearHistory();
clearSearchHistory();
};
return (
<section className="space-y-6">
<div className="flex items-center gap-3 pb-2 border-b border-[var(--glass-border)]">
<svg className="w-6 h-6 text-[var(--accent-color)]" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
<path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" />
<circle cx="12" cy="7" r="4" />
</svg>
<h2 className="text-xl font-semibold text-[var(--text-color)]"></h2>
</div>
<div className="p-6 rounded-[var(--radius-2xl)] bg-[var(--glass-bg)] border border-[var(--glass-border)] backdrop-blur-xl shadow-[var(--shadow-md)]">
{!isAuthenticated ? (
<div className="flex flex-col items-center gap-4 text-center">
<p className="text-[var(--text-color-secondary)]"> Linux DO </p>
<button
onClick={handleLogin}
className="px-6 py-3 rounded-[var(--radius-2xl)] bg-[var(--accent-color)] text-white font-semibold hover:brightness-110 transition-all shadow-[var(--shadow-sm)] hover:shadow-[var(--shadow-md)] active:scale-95"
>
使 Linux DO
</button>
</div>
) : (
<div className="flex flex-col gap-6">
<div className="flex items-center gap-4">
<img
src={user?.avatar_template.replace('{size}', '100')}
alt={user?.name || user?.username}
className="w-16 h-16 rounded-[var(--radius-full)] border-2 border-[var(--glass-border)]"
/>
<div>
<h3 className="text-lg font-bold text-[var(--text-color)]">{user?.name || user?.username}</h3>
<p className="text-[var(--text-color-secondary)]">@{user?.username}</p>
<div className="flex gap-2 mt-2">
<span className="px-3 py-1 text-xs font-medium rounded-[var(--radius-full)] bg-[var(--accent-color)] text-white">
Trust Level {user?.trust_level}
</span>
{user?.active && (
<span className="px-3 py-1 text-xs font-medium rounded-[var(--radius-full)] bg-green-500/20 text-green-600 border border-green-500/30">
Active
</span>
)}
</div>
</div>
</div>
<div className="pt-4 border-t border-[var(--glass-border)]">
<button
onClick={handleLogout}
className="w-full px-4 py-2 text-red-500 hover:bg-red-500/10 rounded-[var(--radius-2xl)] transition-colors font-medium"
>
退
</button>
</div>
</div>
)}
</div>
</section>
);
}
+1 -1
View File
@@ -4,7 +4,7 @@ export function SettingsHeader() {
const router = useRouter();
return (
<div className="mb-8">
<div>
<button
onClick={() => router.back()}
className="inline-flex items-center gap-2 text-[var(--accent-color)] hover:underline mb-4 cursor-pointer"
+37
View File
@@ -0,0 +1,37 @@
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
export interface LinuxDoUser {
id: number;
username: string;
name: string;
avatar_template: string;
active: boolean;
trust_level: number;
silenced: boolean;
external_ids?: Record<string, unknown>;
api_key?: string;
}
interface AuthStore {
user: LinuxDoUser | null;
accessToken: string | null;
isAuthenticated: boolean;
login: (user: LinuxDoUser, accessToken: string) => void;
logout: () => void;
}
export const useAuthStore = create<AuthStore>()(
persist(
(set) => ({
user: null,
accessToken: null,
isAuthenticated: false,
login: (user, accessToken) => set({ user, accessToken, isAuthenticated: true }),
logout: () => set({ user: null, accessToken: null, isAuthenticated: false }),
}),
{
name: 'kvideo-auth-storage',
}
)
);
+87 -118
View File
@@ -1,12 +1,12 @@
{
"name": "kvideo",
"version": "1.0.0",
"version": "2.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "kvideo",
"version": "1.0.0",
"version": "2.0.0",
"dependencies": {
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/sortable": "^10.0.0",
@@ -453,9 +453,9 @@
}
},
"node_modules/@eslint/eslintrc": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.1.tgz",
"integrity": "sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==",
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz",
"integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -465,7 +465,7 @@
"globals": "^14.0.0",
"ignore": "^5.2.0",
"import-fresh": "^3.2.1",
"js-yaml": "^4.1.0",
"js-yaml": "^4.1.1",
"minimatch": "^3.1.2",
"strip-json-comments": "^3.1.1"
},
@@ -1616,13 +1616,13 @@
}
},
"node_modules/@types/react": {
"version": "19.2.5",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.5.tgz",
"integrity": "sha512-keKxkZMqnDicuvFoJbzrhbtdLSPhj/rZThDlKWCDbgXmUg0rEUFtRssDXKYmtXluZlIqiC5VqkCgRwzuyLHKHw==",
"version": "19.2.7",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz",
"integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
"devOptional": true,
"license": "MIT",
"dependencies": {
"csstype": "^3.0.2"
"csstype": "^3.2.2"
}
},
"node_modules/@types/react-dom": {
@@ -1636,17 +1636,17 @@
}
},
"node_modules/@typescript-eslint/eslint-plugin": {
"version": "8.46.4",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.46.4.tgz",
"integrity": "sha512-R48VhmTJqplNyDxCyqqVkFSZIx1qX6PzwqgcXn1olLrzxcSBDlOsbtcnQuQhNtnNiJ4Xe5gREI1foajYaYU2Vg==",
"version": "8.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.48.0.tgz",
"integrity": "sha512-XxXP5tL1txl13YFtrECECQYeZjBZad4fyd3cFV4a19LkAY/bIp9fev3US4S5fDVV2JaYFiKAZ/GRTOLer+mbyQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/regexpp": "^4.10.0",
"@typescript-eslint/scope-manager": "8.46.4",
"@typescript-eslint/type-utils": "8.46.4",
"@typescript-eslint/utils": "8.46.4",
"@typescript-eslint/visitor-keys": "8.46.4",
"@typescript-eslint/scope-manager": "8.48.0",
"@typescript-eslint/type-utils": "8.48.0",
"@typescript-eslint/utils": "8.48.0",
"@typescript-eslint/visitor-keys": "8.48.0",
"graphemer": "^1.4.0",
"ignore": "^7.0.0",
"natural-compare": "^1.4.0",
@@ -1660,7 +1660,7 @@
"url": "https://opencollective.com/typescript-eslint"
},
"peerDependencies": {
"@typescript-eslint/parser": "^8.46.4",
"@typescript-eslint/parser": "^8.48.0",
"eslint": "^8.57.0 || ^9.0.0",
"typescript": ">=4.8.4 <6.0.0"
}
@@ -1676,16 +1676,16 @@
}
},
"node_modules/@typescript-eslint/parser": {
"version": "8.46.4",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.46.4.tgz",
"integrity": "sha512-tK3GPFWbirvNgsNKto+UmB/cRtn6TZfyw0D6IKrW55n6Vbs7KJoZtI//kpTKzE/DUmmnAFD8/Ca46s7Obs92/w==",
"version": "8.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.48.0.tgz",
"integrity": "sha512-jCzKdm/QK0Kg4V4IK/oMlRZlY+QOcdjv89U2NgKHZk1CYTj82/RVSx1mV/0gqCVMJ/DA+Zf/S4NBWNF8GQ+eqQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/scope-manager": "8.46.4",
"@typescript-eslint/types": "8.46.4",
"@typescript-eslint/typescript-estree": "8.46.4",
"@typescript-eslint/visitor-keys": "8.46.4",
"@typescript-eslint/scope-manager": "8.48.0",
"@typescript-eslint/types": "8.48.0",
"@typescript-eslint/typescript-estree": "8.48.0",
"@typescript-eslint/visitor-keys": "8.48.0",
"debug": "^4.3.4"
},
"engines": {
@@ -1701,14 +1701,14 @@
}
},
"node_modules/@typescript-eslint/project-service": {
"version": "8.46.4",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.46.4.tgz",
"integrity": "sha512-nPiRSKuvtTN+no/2N1kt2tUh/HoFzeEgOm9fQ6XQk4/ApGqjx0zFIIaLJ6wooR1HIoozvj2j6vTi/1fgAz7UYQ==",
"version": "8.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.48.0.tgz",
"integrity": "sha512-Ne4CTZyRh1BecBf84siv42wv5vQvVmgtk8AuiEffKTUo3DrBaGYZueJSxxBZ8fjk/N3DrgChH4TOdIOwOwiqqw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/tsconfig-utils": "^8.46.4",
"@typescript-eslint/types": "^8.46.4",
"@typescript-eslint/tsconfig-utils": "^8.48.0",
"@typescript-eslint/types": "^8.48.0",
"debug": "^4.3.4"
},
"engines": {
@@ -1723,14 +1723,14 @@
}
},
"node_modules/@typescript-eslint/scope-manager": {
"version": "8.46.4",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.46.4.tgz",
"integrity": "sha512-tMDbLGXb1wC+McN1M6QeDx7P7c0UWO5z9CXqp7J8E+xGcJuUuevWKxuG8j41FoweS3+L41SkyKKkia16jpX7CA==",
"version": "8.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.48.0.tgz",
"integrity": "sha512-uGSSsbrtJrLduti0Q1Q9+BF1/iFKaxGoQwjWOIVNJv0o6omrdyR8ct37m4xIl5Zzpkp69Kkmvom7QFTtue89YQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.46.4",
"@typescript-eslint/visitor-keys": "8.46.4"
"@typescript-eslint/types": "8.48.0",
"@typescript-eslint/visitor-keys": "8.48.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1741,9 +1741,9 @@
}
},
"node_modules/@typescript-eslint/tsconfig-utils": {
"version": "8.46.4",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.46.4.tgz",
"integrity": "sha512-+/XqaZPIAk6Cjg7NWgSGe27X4zMGqrFqZ8atJsX3CWxH/jACqWnrWI68h7nHQld0y+k9eTTjb9r+KU4twLoo9A==",
"version": "8.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.48.0.tgz",
"integrity": "sha512-WNebjBdFdyu10sR1M4OXTt2OkMd5KWIL+LLfeH9KhgP+jzfDV/LI3eXzwJ1s9+Yc0Kzo2fQCdY/OpdusCMmh6w==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1758,15 +1758,15 @@
}
},
"node_modules/@typescript-eslint/type-utils": {
"version": "8.46.4",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.46.4.tgz",
"integrity": "sha512-V4QC8h3fdT5Wro6vANk6eojqfbv5bpwHuMsBcJUJkqs2z5XnYhJzyz9Y02eUmF9u3PgXEUiOt4w4KHR3P+z0PQ==",
"version": "8.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.48.0.tgz",
"integrity": "sha512-zbeVaVqeXhhab6QNEKfK96Xyc7UQuoFWERhEnj3mLVnUWrQnv15cJNseUni7f3g557gm0e46LZ6IJ4NJVOgOpw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.46.4",
"@typescript-eslint/typescript-estree": "8.46.4",
"@typescript-eslint/utils": "8.46.4",
"@typescript-eslint/types": "8.48.0",
"@typescript-eslint/typescript-estree": "8.48.0",
"@typescript-eslint/utils": "8.48.0",
"debug": "^4.3.4",
"ts-api-utils": "^2.1.0"
},
@@ -1783,9 +1783,9 @@
}
},
"node_modules/@typescript-eslint/types": {
"version": "8.46.4",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.46.4.tgz",
"integrity": "sha512-USjyxm3gQEePdUwJBFjjGNG18xY9A2grDVGuk7/9AkjIF1L+ZrVnwR5VAU5JXtUnBL/Nwt3H31KlRDaksnM7/w==",
"version": "8.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.48.0.tgz",
"integrity": "sha512-cQMcGQQH7kwKoVswD1xdOytxQR60MWKM1di26xSUtxehaDs/32Zpqsu5WJlXTtTTqyAVK8R7hvsUnIXRS+bjvA==",
"dev": true,
"license": "MIT",
"engines": {
@@ -1797,21 +1797,20 @@
}
},
"node_modules/@typescript-eslint/typescript-estree": {
"version": "8.46.4",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.46.4.tgz",
"integrity": "sha512-7oV2qEOr1d4NWNmpXLR35LvCfOkTNymY9oyW+lUHkmCno7aOmIf/hMaydnJBUTBMRCOGZh8YjkFOc8dadEoNGA==",
"version": "8.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.48.0.tgz",
"integrity": "sha512-ljHab1CSO4rGrQIAyizUS6UGHHCiAYhbfcIZ1zVJr5nMryxlXMVWS3duFPSKvSUbFPwkXMFk1k0EMIjub4sRRQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/project-service": "8.46.4",
"@typescript-eslint/tsconfig-utils": "8.46.4",
"@typescript-eslint/types": "8.46.4",
"@typescript-eslint/visitor-keys": "8.46.4",
"@typescript-eslint/project-service": "8.48.0",
"@typescript-eslint/tsconfig-utils": "8.48.0",
"@typescript-eslint/types": "8.48.0",
"@typescript-eslint/visitor-keys": "8.48.0",
"debug": "^4.3.4",
"fast-glob": "^3.3.2",
"is-glob": "^4.0.3",
"minimatch": "^9.0.4",
"semver": "^7.6.0",
"tinyglobby": "^0.2.15",
"ts-api-utils": "^2.1.0"
},
"engines": {
@@ -1835,36 +1834,6 @@
"balanced-match": "^1.0.0"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/fast-glob": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
"integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@nodelib/fs.stat": "^2.0.2",
"@nodelib/fs.walk": "^1.2.3",
"glob-parent": "^5.1.2",
"merge2": "^1.3.0",
"micromatch": "^4.0.8"
},
"engines": {
"node": ">=8.6.0"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"license": "ISC",
"dependencies": {
"is-glob": "^4.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": {
"version": "9.0.5",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
@@ -1895,16 +1864,16 @@
}
},
"node_modules/@typescript-eslint/utils": {
"version": "8.46.4",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.46.4.tgz",
"integrity": "sha512-AbSv11fklGXV6T28dp2Me04Uw90R2iJ30g2bgLz529Koehrmkbs1r7paFqr1vPCZi7hHwYxYtxfyQMRC8QaVSg==",
"version": "8.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.48.0.tgz",
"integrity": "sha512-yTJO1XuGxCsSfIVt1+1UrLHtue8xz16V8apzPYI06W0HbEbEWHxHXgZaAgavIkoh+GeV6hKKd5jm0sS6OYxWXQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"@eslint-community/eslint-utils": "^4.7.0",
"@typescript-eslint/scope-manager": "8.46.4",
"@typescript-eslint/types": "8.46.4",
"@typescript-eslint/typescript-estree": "8.46.4"
"@typescript-eslint/scope-manager": "8.48.0",
"@typescript-eslint/types": "8.48.0",
"@typescript-eslint/typescript-estree": "8.48.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -1919,13 +1888,13 @@
}
},
"node_modules/@typescript-eslint/visitor-keys": {
"version": "8.46.4",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.46.4.tgz",
"integrity": "sha512-/++5CYLQqsO9HFGLI7APrxBJYo+5OCMpViuhV8q5/Qa3o5mMrF//eQHks+PXcsAVaLdn817fMuS7zqoXNNZGaw==",
"version": "8.48.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.48.0.tgz",
"integrity": "sha512-T0XJMaRPOH3+LBbAfzR2jalckP1MSG/L9eUtY0DEzUyVaXJ/t6zN0nR7co5kz0Jko/nkSYCBRkz1djvjajVTTg==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/types": "8.46.4",
"@typescript-eslint/types": "8.48.0",
"eslint-visitor-keys": "^4.2.1"
},
"engines": {
@@ -2537,9 +2506,9 @@
"license": "MIT"
},
"node_modules/baseline-browser-mapping": {
"version": "2.8.28",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.28.tgz",
"integrity": "sha512-gYjt7OIqdM0PcttNYP2aVrr2G0bMALkBaoehD4BuRGjAOtipg0b6wHg1yNL+s5zSnLZZrGHOw4IrND8CD+3oIQ==",
"version": "2.8.32",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.8.32.tgz",
"integrity": "sha512-OPz5aBThlyLFgxyhdwf/s2+8ab3OvT7AdTNvKHBwpXomIYeXqpUUuT8LrdtxZSsWJ4R4CU1un4XGh5Ez3nlTpw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
@@ -2665,9 +2634,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001754",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001754.tgz",
"integrity": "sha512-x6OeBXueoAceOmotzx3PO4Zpt4rzpeIFsSr6AAePTZxSkXiYDUmpypEl7e2+8NCd9bD7bXjqyef8CJYPC1jfxg==",
"version": "1.0.30001757",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001757.tgz",
"integrity": "sha512-r0nnL/I28Zi/yjk1el6ilj27tKcdjLsNqAOZr0yVjWPrSQyHgKI2INaEWw21bAQSv2LXRt1XuCS/GomNpWOxsQ==",
"funding": [
{
"type": "opencollective",
@@ -2757,9 +2726,9 @@
}
},
"node_modules/csstype": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.1.tgz",
"integrity": "sha512-98XGutrXoh75MlgLihlNxAGbUuFQc7l1cqcnEZlLNKc0UrVdPndgmaDmYTDDh929VS/eqTZV0rozmhu2qqT1/g==",
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
"devOptional": true,
"license": "MIT"
},
@@ -2924,9 +2893,9 @@
}
},
"node_modules/electron-to-chromium": {
"version": "1.5.253",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.253.tgz",
"integrity": "sha512-O0tpQ/35rrgdiGQ0/OFWhy1itmd9A6TY9uQzlqj3hKSu/aYpe7UIn5d7CU2N9myH6biZiWF3VMZVuup8pw5U9w==",
"version": "1.5.262",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.262.tgz",
"integrity": "sha512-NlAsMteRHek05jRUxUR0a5jpjYq9ykk6+kO0yRaMi5moe7u0fVIOeQ3Y30A8dIiWFBNUoQGi1ljb1i5VtS9WQQ==",
"dev": true,
"license": "ISC"
},
@@ -6394,16 +6363,16 @@
}
},
"node_modules/typescript-eslint": {
"version": "8.46.4",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.46.4.tgz",
"integrity": "sha512-KALyxkpYV5Ix7UhvjTwJXZv76VWsHG+NjNlt/z+a17SOQSiOcBdUXdbJdyXi7RPxrBFECtFOiPwUJQusJuCqrg==",
"version": "8.48.0",
"resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.48.0.tgz",
"integrity": "sha512-fcKOvQD9GUn3Xw63EgiDqhvWJ5jsyZUaekl3KVpGsDJnN46WJTe3jWxtQP9lMZm1LJNkFLlTaWAxK2vUQR+cqw==",
"dev": true,
"license": "MIT",
"dependencies": {
"@typescript-eslint/eslint-plugin": "8.46.4",
"@typescript-eslint/parser": "8.46.4",
"@typescript-eslint/typescript-estree": "8.46.4",
"@typescript-eslint/utils": "8.46.4"
"@typescript-eslint/eslint-plugin": "8.48.0",
"@typescript-eslint/parser": "8.48.0",
"@typescript-eslint/typescript-estree": "8.48.0",
"@typescript-eslint/utils": "8.48.0"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@@ -6655,9 +6624,9 @@
}
},
"node_modules/zod": {
"version": "4.1.12",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.1.12.tgz",
"integrity": "sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==",
"version": "4.1.13",
"resolved": "https://registry.npmjs.org/zod/-/zod-4.1.13.tgz",
"integrity": "sha512-AvvthqfqrAhNH9dnfmrfKzX5upOdjUVJYFqNSlkmGf64gRaTzlPwz99IHYnVs28qYAybvAlBV+H7pn0saFY4Ig==",
"dev": true,
"license": "MIT",
"funding": {
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "kvideo",
"version": "1.0.0",
"version": "2.0.0",
"private": true,
"scripts": {
"dev": "next dev",