From fd8b78362d6eca3653ee1d306997efdd99c5a90c Mon Sep 17 00:00:00 2001 From: kuekhaoyang Date: Mon, 1 Dec 2025 08:10:54 +0800 Subject: [PATCH] feat: remove Google Drive integration, associated hooks, components, API routes, and dependencies. --- app/api/oauth/authorize/route.ts | 19 -- app/api/oauth/callback/route.ts | 71 -------- app/settings/page.tsx | 10 -- components/settings/AccountSettings.tsx | 141 --------------- components/settings/SyncSettings.tsx | 139 --------------- lib/hooks/useGoogleDrive.ts | 226 ------------------------ lib/hooks/useSync.ts | 138 --------------- lib/store/auth-store.ts | 37 ---- package-lock.json | 71 -------- package.json | 6 +- 10 files changed, 1 insertion(+), 857 deletions(-) delete mode 100644 app/api/oauth/authorize/route.ts delete mode 100644 app/api/oauth/callback/route.ts delete mode 100644 components/settings/AccountSettings.tsx delete mode 100644 components/settings/SyncSettings.tsx delete mode 100644 lib/hooks/useGoogleDrive.ts delete mode 100644 lib/hooks/useSync.ts delete mode 100644 lib/store/auth-store.ts diff --git a/app/api/oauth/authorize/route.ts b/app/api/oauth/authorize/route.ts deleted file mode 100644 index fa692c1..0000000 --- a/app/api/oauth/authorize/route.ts +++ /dev/null @@ -1,19 +0,0 @@ -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()}`); -} diff --git a/app/api/oauth/callback/route.ts b/app/api/oauth/callback/route.ts deleted file mode 100644 index 66c2ad7..0000000 --- a/app/api/oauth/callback/route.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { NextRequest, NextResponse } from 'next/server'; - -export async function GET(request: NextRequest) { - const searchParams = request.nextUrl.searchParams; - const code = searchParams.get('code'); - - console.log('[OAuth Callback] Received callback with code:', code ? 'exists' : 'missing'); - - if (!code) { - console.error('[OAuth Callback] No code provided'); - 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) { - console.error('[OAuth Callback] Missing configuration'); - return NextResponse.redirect(new URL('/settings?error=config_missing', request.url)); - } - - try { - // 1. Exchange code for token - console.log('[OAuth Callback] Exchanging code for token...'); - const tokenResponse = await fetch('https://connect.linux.do/oauth2/token', { - method: 'POST', - headers: { - 'Content-Type': 'application/x-www-form-urlencoded', - }, - body: new URLSearchParams({ - client_id: CLIENT_ID, - client_secret: CLIENT_SECRET, - code, - redirect_uri: REDIRECT_URI, - grant_type: 'authorization_code', - }).toString(), - }); - - const tokenData = await tokenResponse.json(); - console.log('[OAuth Callback] Token response status:', tokenResponse.status); - console.log('[OAuth Callback] Token data:', { hasToken: !!tokenData.access_token }); - - if (!tokenData.access_token) { - console.error('[OAuth Callback] No access token in response:', tokenData); - throw new Error('Failed to get access token'); - } - - // 2. Get User Info - console.log('[OAuth Callback] Fetching user info...'); - const userResponse = await fetch('https://connect.linux.do/api/user', { - headers: { Authorization: `Bearer ${tokenData.access_token}` }, - }); - - const userData = await userResponse.json(); - console.log('[OAuth Callback] User response status:', userResponse.status); - console.log('[OAuth Callback] User data:', { id: userData.id, username: userData.username }); - - // 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)); - - console.log('[OAuth Callback] Redirecting to settings with auth data'); - return NextResponse.redirect(redirectUrl); - } catch (error) { - console.error('[OAuth Callback] Error:', error); - return NextResponse.redirect(new URL('/settings?error=auth_failed', request.url)); - } -} diff --git a/app/settings/page.tsx b/app/settings/page.tsx index ebb8646..3a4fccb 100644 --- a/app/settings/page.tsx +++ b/app/settings/page.tsx @@ -9,8 +9,6 @@ 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 { SyncSettings } from '@/components/settings/SyncSettings'; import { useSettingsPage } from './hooks/useSettingsPage'; export default function SettingsPage() { @@ -45,14 +43,6 @@ export default function SettingsPage() { {/* Header */} - {/* Account Settings */} - 加载中...}> - - - - {/* Google Drive Sync */} - - {/* Source Management */} { - setIsHydrated(true); - }, []); - - useEffect(() => { - const authSuccess = searchParams.get('auth_success'); - const token = searchParams.get('token'); - const userStr = searchParams.get('user'); - - console.log('[AccountSettings] Checking URL params:', { - authSuccess, - hasToken: !!token, - hasUser: !!userStr, - }); - - if (authSuccess && token && userStr) { - try { - const userData = JSON.parse(userStr) as LinuxDoUser; - console.log('[AccountSettings] Parsed user data:', { - id: userData.id, - username: userData.username, - }); - - clearHistory(); - clearSearchHistory(); - login(userData, token); - - console.log('[AccountSettings] Login complete, redirecting...'); - router.replace('/settings'); - } catch (e) { - console.error('[AccountSettings] Failed to parse user data:', e); - } - } - }, [searchParams, login, clearHistory, clearSearchHistory, router]); - - const handleLogin = () => { - clearHistory(); - clearSearchHistory(); - router.push('/api/oauth/authorize'); - }; - - const handleLogout = () => { - logout(); - clearHistory(); - clearSearchHistory(); - }; - - // Don't render until hydrated to prevent flash - if (!isHydrated) { - return ( -
-
- - - - -

账号

-
-
-
加载中...
-
-
- ); - } - - return ( -
-
- - - - -

账号

-
- -
- {!isAuthenticated ? ( -
-

登录 Linux DO 以同步您的账号信息

- -
- ) : ( -
-
- {user?.name -
-

{user?.name || user?.username}

-

@{user?.username}

-
- - Trust Level {user?.trust_level} - - {user?.active && ( - - Active - - )} -
-
-
- -
- -
-
- )} -
-
- ); -} diff --git a/components/settings/SyncSettings.tsx b/components/settings/SyncSettings.tsx deleted file mode 100644 index a8f5635..0000000 --- a/components/settings/SyncSettings.tsx +++ /dev/null @@ -1,139 +0,0 @@ -'use client'; - -import { useSync } from '@/lib/hooks/useSync'; -import { Icons } from '@/components/ui/Icon'; -import { Button } from '@/components/ui/Button'; - -export function SyncSettings() { - const { - user, - isInitialized, - isLoading, - error, - lastSynced, - autoSync, - toggleAutoSync, - signIn, - signOut, - syncNow, - restoreNow - } = useSync(); - - if (!isInitialized) { - if (error) { - return ( -
-

- - 云端同步 (Google Drive) -

-
-

配置缺失

-

请在 .env.local 文件中添加 Google Client ID:

- - NEXT_PUBLIC_GOOGLE_CLIENT_ID=你的客户端ID - -
-
- ); - } - - return ( -
-
-
- 正在初始化同步服务... -
-
- ); - } - - return ( -
-
-

- - 云端同步 (Google Drive) -

- {user && ( -
- - {user.email} - - -
- )} -
- - {error && ( -
- {error} -
- )} - - {!user ? ( -
-

- 登录 Google 账号以在设备间同步您的设置和历史记录。 - 数据将存储在您 Google Drive 的专用应用文件夹中。 -

- -
- ) : ( -
-
-
-

自动同步

-

- 更改设置或观看视频时自动同步 -

-
- -
- -
- - -
- - {lastSynced && ( -

- 上次同步: {lastSynced.toLocaleString()} -

- )} -
- )} -
- ); -} diff --git a/lib/hooks/useGoogleDrive.ts b/lib/hooks/useGoogleDrive.ts deleted file mode 100644 index 326a053..0000000 --- a/lib/hooks/useGoogleDrive.ts +++ /dev/null @@ -1,226 +0,0 @@ -import { useState, useEffect, useCallback } from 'react'; - -const DISCOVERY_DOCS = ['https://www.googleapis.com/discovery/v1/apis/drive/v3/rest']; -const SCOPES = 'https://www.googleapis.com/auth/drive.appdata'; -const APP_DATA_FILENAME = 'kvideo_data.json'; - -// Type declarations for gapi -declare const gapi: any; - -export interface GoogleUser { - name: string; - email: string; - imageUrl: string; -} - -export interface DriveFile { - id: string; - name: string; - modifiedTime?: string; -} - -export function useGoogleDrive() { - const [user, setUser] = useState(null); - const [isInitialized, setIsInitialized] = useState(false); - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(null); - - useEffect(() => { - // Only run on client side - if (typeof window === 'undefined') return; - - const initClient = async () => { - try { - console.log('[Google Drive] Starting initialization...'); - console.log('[Google Drive] Client ID:', process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID ? 'Present' : 'Missing'); - - if (!process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID) { - throw new Error('Missing Google Client ID'); - } - - // Dynamically load gapi script - console.log('[Google Drive] Loading gapi script...'); - if (!window.gapi) { - await new Promise((resolve, reject) => { - const script = document.createElement('script'); - script.src = 'https://apis.google.com/js/api.js'; - script.onload = () => { - console.log('[Google Drive] gapi script loaded'); - resolve(); - }; - script.onerror = (e) => { - console.error('[Google Drive] Failed to load gapi script', e); - reject(new Error('Failed to load Google API script')); - }; - document.body.appendChild(script); - }); - } - - console.log('[Google Drive] Loading client:auth2...'); - await new Promise((resolve, reject) => { - gapi.load('client:auth2', { - callback: () => { - console.log('[Google Drive] client:auth2 loaded'); - resolve(); - }, - onerror: (err: any) => { - console.error('[Google Drive] Failed to load client:auth2', err); - reject(new Error('Failed to load Google client libraries')); - } - }); - }); - - console.log('[Google Drive] Initializing client...'); - await gapi.client.init({ - apiKey: '', // No API key needed for OAuth - clientId: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID, - discoveryDocs: DISCOVERY_DOCS, - scope: SCOPES, - }); - - console.log('[Google Drive] Client initialized successfully'); - - // Listen for sign-in state changes. - gapi.auth2.getAuthInstance().isSignedIn.listen(updateSigninStatus); - - // Handle the initial sign-in state. - updateSigninStatus(gapi.auth2.getAuthInstance().isSignedIn.get()); - setIsInitialized(true); - console.log('[Google Drive] Initialization complete'); - } catch (err: any) { - console.error('[Google Drive] Error initializing Google API client', err); - const errorMessage = err?.message || err?.error || 'Failed to initialize Google API'; - setError(errorMessage); - } - }; - - initClient(); - }, []); - - const updateSigninStatus = (isSignedIn: boolean) => { - if (isSignedIn) { - const profile = gapi.auth2.getAuthInstance().currentUser.get().getBasicProfile(); - setUser({ - name: profile.getName(), - email: profile.getEmail(), - imageUrl: profile.getImageUrl(), - }); - } else { - setUser(null); - } - }; - - const signIn = async () => { - try { - await gapi.auth2.getAuthInstance().signIn(); - } catch (err: any) { - console.error('Error signing in', err); - setError(err.message || 'Failed to sign in'); - } - }; - - const signOut = async () => { - try { - await gapi.auth2.getAuthInstance().signOut(); - } catch (err: any) { - console.error('Error signing out', err); - setError(err.message || 'Failed to sign out'); - } - }; - - const findAppDataFile = useCallback(async (): Promise => { - try { - const response = await gapi.client.drive.files.list({ - spaces: 'appDataFolder', - fields: 'nextPageToken, files(id, name, modifiedTime)', - q: `name = '${APP_DATA_FILENAME}'`, - pageSize: 1, - }); - const files = response.result.files; - if (files && files.length > 0) { - return files[0] as DriveFile; - } - return null; - } catch (err: any) { - console.error('Error finding app data file', err); - throw err; - } - }, []); - - const uploadData = useCallback(async (data: string) => { - setIsLoading(true); - try { - const file = await findAppDataFile(); - const boundary = '-------314159265358979323846'; - const delimiter = "\r\n--" + boundary + "\r\n"; - const close_delim = "\r\n--" + boundary + "--"; - - const contentType = 'application/json'; - const metadata = { - 'name': APP_DATA_FILENAME, - 'mimeType': contentType, - 'parents': !file ? ['appDataFolder'] : undefined // Only set parent on create - }; - - const multipartRequestBody = - delimiter + - 'Content-Type: application/json\r\n\r\n' + - JSON.stringify(metadata) + - delimiter + - 'Content-Type: ' + contentType + '\r\n\r\n' + - data + - close_delim; - - const request = gapi.client.request({ - 'path': file ? `/upload/drive/v3/files/${file.id}` : '/upload/drive/v3/files', - 'method': file ? 'PATCH' : 'POST', - 'params': { 'uploadType': 'multipart' }, - 'headers': { - 'Content-Type': 'multipart/related; boundary="' + boundary + '"' - }, - 'body': multipartRequestBody - }); - - await request; - console.log('Data uploaded successfully'); - } catch (err: any) { - console.error('Error uploading data', err); - setError(err.message || 'Failed to upload data'); - } finally { - setIsLoading(false); - } - }, [findAppDataFile]); - - const downloadData = useCallback(async (): Promise => { - setIsLoading(true); - try { - const file = await findAppDataFile(); - if (!file) return null; - - const response = await gapi.client.drive.files.get({ - fileId: file.id, - alt: 'media', - }); - return JSON.stringify(response.result); // gapi returns the parsed JSON in result for 'media' alt? - // Actually gapi.client.drive.files.get with alt='media' returns the body in `body` or `result` depending on content type. - // For JSON it should be in result. - } catch (err: any) { - console.error('Error downloading data', err); - setError(err.message || 'Failed to download data'); - return null; - } finally { - setIsLoading(false); - } - }, [findAppDataFile]); - - return { - user, - isInitialized, - isLoading, - error, - signIn, - signOut, - uploadData, - downloadData, - }; -} diff --git a/lib/hooks/useSync.ts b/lib/hooks/useSync.ts deleted file mode 100644 index 4f78474..0000000 --- a/lib/hooks/useSync.ts +++ /dev/null @@ -1,138 +0,0 @@ -import { useEffect, useRef, useState } from 'react'; -import { useGoogleDrive } from './useGoogleDrive'; -import { settingsStore } from '@/lib/store/settings-store'; -import { useHistoryStore } from '@/lib/store/history-store'; -import { useSearchHistory } from './useSearchHistory'; - -export function useSync() { - const { user, isInitialized, isLoading: isDriveLoading, error: driveError, signIn, signOut, uploadData, downloadData } = useGoogleDrive(); - const [lastSynced, setLastSynced] = useState(null); - const [isSyncing, setIsSyncing] = useState(false); - const [syncError, setSyncError] = useState(null); - const [autoSync, setAutoSync] = useState(false); - - // Load auto-sync preference - useEffect(() => { - const stored = localStorage.getItem('kvideo-autosync'); - if (stored) { - setAutoSync(JSON.parse(stored)); - } - }, []); - - const toggleAutoSync = (enabled: boolean) => { - setAutoSync(enabled); - localStorage.setItem('kvideo-autosync', JSON.stringify(enabled)); - }; - - const exportAllData = () => { - const settings = settingsStore.getSettings(); - const searchHistory = localStorage.getItem('kvideo-search-history'); - const watchHistory = localStorage.getItem('kvideo-watch-history'); - - return JSON.stringify({ - settings, - searchHistory: searchHistory ? JSON.parse(searchHistory) : [], - watchHistory: watchHistory ? JSON.parse(watchHistory) : [], - timestamp: Date.now(), - }); - }; - - const importAllData = (jsonString: string) => { - try { - const data = JSON.parse(jsonString); - - if (data.settings) { - settingsStore.saveSettings(data.settings); - } - if (data.searchHistory) { - localStorage.setItem('kvideo-search-history', JSON.stringify(data.searchHistory)); - // Trigger reload or store update if necessary - } - if (data.watchHistory) { - localStorage.setItem('kvideo-watch-history', JSON.stringify(data.watchHistory)); - // Trigger reload or store update if necessary - useHistoryStore.getState().importHistory(data.watchHistory); - } - - setLastSynced(new Date(data.timestamp || Date.now())); - return true; - } catch (e) { - console.error('Failed to import data', e); - return false; - } - }; - - const handleSync = async () => { - if (!user) return; - setIsSyncing(true); - setSyncError(null); - try { - const data = exportAllData(); - await uploadData(data); - setLastSynced(new Date()); - } catch (e: any) { - setSyncError(e.message || 'Sync failed'); - } finally { - setIsSyncing(false); - } - }; - - const handleRestore = async () => { - if (!user) return; - setIsSyncing(true); - setSyncError(null); - try { - const data = await downloadData(); - if (data) { - // The data might be wrapped in a result object depending on how gapi returns it - // If downloadData returns the raw string body: - importAllData(typeof data === 'string' ? data : JSON.stringify(data)); - } - } catch (e: any) { - setSyncError(e.message || 'Restore failed'); - } finally { - setIsSyncing(false); - } - }; - - // Auto-sync logic (simple debounce) - useEffect(() => { - if (!autoSync || !user || !isInitialized) return; - - const timeout = setTimeout(() => { - handleSync(); - }, 30000); // Sync every 30s if changes detected? - // Real implementation would subscribe to stores. - // For now, let's just sync on mount/login if auto-sync is on, and maybe periodically? - // Better: subscribe to store changes. - - return () => clearTimeout(timeout); - }, [autoSync, user, isInitialized]); - - // Subscribe to settings changes for auto-sync - useEffect(() => { - if (!autoSync || !user) return; - - const unsubscribe = settingsStore.subscribe(() => { - // Debounce sync - const timeout = setTimeout(handleSync, 5000); - return () => clearTimeout(timeout); - }); - - return unsubscribe; - }, [autoSync, user]); - - return { - user, - isInitialized, - isLoading: isDriveLoading || isSyncing, - error: driveError || syncError, - lastSynced, - autoSync, - toggleAutoSync, - signIn, - signOut, - syncNow: handleSync, - restoreNow: handleRestore, - }; -} diff --git a/lib/store/auth-store.ts b/lib/store/auth-store.ts deleted file mode 100644 index c07824a..0000000 --- a/lib/store/auth-store.ts +++ /dev/null @@ -1,37 +0,0 @@ -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; - 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()( - 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', - } - ) -); diff --git a/package-lock.json b/package-lock.json index 7717de8..c297c06 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,10 +11,7 @@ "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", - "@types/gapi": "^0.0.47", - "@types/gapi.client.drive": "^3.0.15", "@vercel/analytics": "^1.5.0", - "gapi-script": "^1.2.0", "hls.js": "^1.6.15", "next": "16.0.5", "react": "19.2.0", @@ -23,7 +20,6 @@ }, "devDependencies": { "@tailwindcss/postcss": "^4", - "@types/gapi.auth2": "^0.0.61", "@types/node": "^24", "@types/react": "^19", "@types/react-dom": "^19", @@ -1085,26 +1081,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@maxim_mazurok/gapi.client.discovery-v1": { - "version": "0.4.20200806", - "resolved": "https://registry.npmjs.org/@maxim_mazurok/gapi.client.discovery-v1/-/gapi.client.discovery-v1-0.4.20200806.tgz", - "integrity": "sha512-Jeo/KZqK39DI6ExXHcJ4lqnn1O/wEqboQ6eQ8WnNpu5eJ7wUnX/C5KazOgs1aRhnIB/dVzDe8wm62nmtkMIoaw==", - "license": "MIT", - "dependencies": { - "@types/gapi.client": "*", - "@types/gapi.client.discovery-v1": "*" - } - }, - "node_modules/@maxim_mazurok/gapi.client.drive-v3": { - "version": "0.1.20251122", - "resolved": "https://registry.npmjs.org/@maxim_mazurok/gapi.client.drive-v3/-/gapi.client.drive-v3-0.1.20251122.tgz", - "integrity": "sha512-hi8A2jW6a/JZ5Hvweo1Ww0YIb004LqGOYv4/ifa7NCPBc51PHuVEeUDfQvf+INpN+JhC5OudENvzmAbFD/DoLQ==", - "license": "MIT", - "dependencies": { - "@types/gapi.client": "*", - "@types/gapi.client.discovery-v1": "*" - } - }, "node_modules/@napi-rs/wasm-runtime": { "version": "0.2.12", "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", @@ -1615,47 +1591,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/gapi": { - "version": "0.0.47", - "resolved": "https://registry.npmjs.org/@types/gapi/-/gapi-0.0.47.tgz", - "integrity": "sha512-/ZsLuq6BffMgbKMtZyDZ8vwQvTyKhKQ1G2K6VyWCgtHHhfSSXbk4+4JwImZiTjWNXfI2q1ZStAwFFHSkNoTkHA==", - "license": "MIT" - }, - "node_modules/@types/gapi.auth2": { - "version": "0.0.61", - "resolved": "https://registry.npmjs.org/@types/gapi.auth2/-/gapi.auth2-0.0.61.tgz", - "integrity": "sha512-cn+omiRoE/LTxZncnVl1QhcLggOT0sJ8Yz9RXIsw5R2zLyRf+0o6kaZzJ/Gr3Sxz6i7J/+PbXAF8yeZipCaiWw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/gapi": "*" - } - }, - "node_modules/@types/gapi.client": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/gapi.client/-/gapi.client-1.0.8.tgz", - "integrity": "sha512-qJQUmmumbYym3Amax0S8CVzuSngcXsC1fJdwRS2zeW5lM63zXkw4wJFP+bG0jzgi0R6EsJKoHnGNVTDbOyG1ng==", - "license": "MIT" - }, - "node_modules/@types/gapi.client.discovery-v1": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/@types/gapi.client.discovery-v1/-/gapi.client.discovery-v1-0.0.4.tgz", - "integrity": "sha512-uevhRumNE65F5mf2gABLaReOmbFSXONuzFZjNR3dYv6BmkHg+wciubHrfBAsp3554zNo3Dcg6dUAlwMqQfpwjQ==", - "license": "MIT", - "dependencies": { - "@maxim_mazurok/gapi.client.discovery-v1": "latest" - } - }, - "node_modules/@types/gapi.client.drive": { - "version": "3.0.15", - "resolved": "https://registry.npmjs.org/@types/gapi.client.drive/-/gapi.client.drive-3.0.15.tgz", - "integrity": "sha512-qEfI0LxUBadOLmym4FkaNGpI4ibBCBPJHiUFWKIv0GIp7yKT2d+wztJYKr9giIRecErUCF+jGSDw1fzTZ6hPVQ==", - "deprecated": "use @types/gapi.client.drive-v3 instead; see https://github.com/Maxim-Mazurok/google-api-typings-generator/issues/652 for details", - "license": "MIT", - "dependencies": { - "@maxim_mazurok/gapi.client.drive-v3": "latest" - } - }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -3791,12 +3726,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gapi-script": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gapi-script/-/gapi-script-1.2.0.tgz", - "integrity": "sha512-NKTVKiIwFdkO1j1EzcrWu/Pz7gsl1GmBmgh+qhuV2Ytls04W/Eg5aiBL91SCiBM9lU0PMu7p1hTVxhh1rPT5Lw==", - "license": "MIT" - }, "node_modules/generator-function": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", diff --git a/package.json b/package.json index b165e8f..a617e88 100644 --- a/package.json +++ b/package.json @@ -12,10 +12,7 @@ "@dnd-kit/core": "^6.3.1", "@dnd-kit/sortable": "^10.0.0", "@dnd-kit/utilities": "^3.2.2", - "@types/gapi": "^0.0.47", - "@types/gapi.client.drive": "^3.0.15", "@vercel/analytics": "^1.5.0", - "gapi-script": "^1.2.0", "hls.js": "^1.6.15", "next": "16.0.5", "react": "19.2.0", @@ -24,7 +21,6 @@ }, "devDependencies": { "@tailwindcss/postcss": "^4", - "@types/gapi.auth2": "^0.0.61", "@types/node": "^24", "@types/react": "^19", "@types/react-dom": "^19", @@ -34,4 +30,4 @@ "tailwindcss": "^4", "typescript": "^5" } -} +} \ No newline at end of file