mirror of
https://github.com/KuekHaoYang/KVideo.git
synced 2026-08-14 16:23:43 +08:00
feat: Implement new authentication and account management system, refactoring password gates and settings components.
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Auth Store - Simple module-level session management
|
||||
* NOT Zustand — needs to be synchronous at import time for store key generation
|
||||
*/
|
||||
|
||||
export interface AuthSession {
|
||||
profileId: string;
|
||||
name: string;
|
||||
role: 'admin' | 'viewer';
|
||||
}
|
||||
|
||||
const SESSION_KEY = 'kvideo-session';
|
||||
|
||||
export function getSession(): AuthSession | null {
|
||||
if (typeof window === 'undefined') return null;
|
||||
|
||||
// Check sessionStorage first, then localStorage (for persisted sessions)
|
||||
const raw = sessionStorage.getItem(SESSION_KEY) || localStorage.getItem(SESSION_KEY);
|
||||
if (!raw) return null;
|
||||
|
||||
try {
|
||||
const parsed = JSON.parse(raw);
|
||||
if (parsed && parsed.profileId && parsed.name && parsed.role) {
|
||||
return parsed as AuthSession;
|
||||
}
|
||||
} catch {
|
||||
// Invalid session data
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function setSession(session: AuthSession, persist: boolean): void {
|
||||
if (typeof window === 'undefined') return;
|
||||
const data = JSON.stringify(session);
|
||||
sessionStorage.setItem(SESSION_KEY, data);
|
||||
if (persist) {
|
||||
localStorage.setItem(SESSION_KEY, data);
|
||||
}
|
||||
}
|
||||
|
||||
export function clearSession(): void {
|
||||
if (typeof window === 'undefined') return;
|
||||
sessionStorage.removeItem(SESSION_KEY);
|
||||
localStorage.removeItem(SESSION_KEY);
|
||||
// Also clear old unlock keys for backward compat cleanup
|
||||
sessionStorage.removeItem('kvideo-unlocked');
|
||||
localStorage.removeItem('kvideo-unlocked');
|
||||
}
|
||||
|
||||
export function isAdmin(): boolean {
|
||||
const session = getSession();
|
||||
if (!session) return true; // No auth configured = full access
|
||||
return session.role === 'admin';
|
||||
}
|
||||
|
||||
export function getProfileId(): string {
|
||||
const session = getSession();
|
||||
return session?.profileId || '';
|
||||
}
|
||||
@@ -6,6 +6,7 @@
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import type { FavoriteItem } from '@/lib/types';
|
||||
import { profiledKey } from '@/lib/utils/profile-storage';
|
||||
|
||||
const MAX_FAVORITES = 100;
|
||||
|
||||
@@ -117,8 +118,8 @@ const createFavoritesStore = (name: string) =>
|
||||
)
|
||||
);
|
||||
|
||||
export const useFavoritesStore = createFavoritesStore('kvideo-favorites-store');
|
||||
export const usePremiumFavoritesStore = createFavoritesStore('kvideo-premium-favorites-store');
|
||||
export const useFavoritesStore = createFavoritesStore(profiledKey('kvideo-favorites-store'));
|
||||
export const usePremiumFavoritesStore = createFavoritesStore(profiledKey('kvideo-premium-favorites-store'));
|
||||
|
||||
/**
|
||||
* Helper hook to get the appropriate favorites store
|
||||
|
||||
@@ -7,6 +7,7 @@ import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import type { VideoHistoryItem, Episode } from '@/lib/types';
|
||||
import { clearSegmentsForUrl, clearAllCache } from '@/lib/utils/cacheManager';
|
||||
import { profiledKey } from '@/lib/utils/profile-storage';
|
||||
|
||||
const MAX_HISTORY_ITEMS = 50;
|
||||
|
||||
@@ -151,8 +152,8 @@ const createHistoryStore = (name: string) =>
|
||||
)
|
||||
);
|
||||
|
||||
export const useHistoryStore = createHistoryStore('kvideo-history-store');
|
||||
export const usePremiumHistoryStore = createHistoryStore('kvideo-premium-history-store');
|
||||
export const useHistoryStore = createHistoryStore(profiledKey('kvideo-history-store'));
|
||||
export const usePremiumHistoryStore = createHistoryStore(profiledKey('kvideo-premium-history-store'));
|
||||
|
||||
/**
|
||||
* Helper hook to get the appropriate history store
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import { profiledKey } from '@/lib/utils/profile-storage';
|
||||
|
||||
const MAX_HISTORY_ITEMS = 20;
|
||||
|
||||
@@ -113,8 +114,8 @@ const createSearchHistoryStore = (name: string) =>
|
||||
)
|
||||
);
|
||||
|
||||
export const useSearchHistoryStore = createSearchHistoryStore('kvideo-search-history');
|
||||
export const usePremiumSearchHistoryStore = createSearchHistoryStore('kvideo-premium-search-history');
|
||||
export const useSearchHistoryStore = createSearchHistoryStore(profiledKey('kvideo-search-history'));
|
||||
export const usePremiumSearchHistoryStore = createSearchHistoryStore(profiledKey('kvideo-premium-search-history'));
|
||||
|
||||
/**
|
||||
* Helper hook to get the appropriate search history store
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { AppSettings } from './settings-store';
|
||||
import { profiledKey } from '@/lib/utils/profile-storage';
|
||||
|
||||
export const SEARCH_HISTORY_KEY = 'kvideo-search-history';
|
||||
export const WATCH_HISTORY_KEY = 'kvideo-watch-history';
|
||||
@@ -20,8 +21,8 @@ export function exportSettings(settings: AppSettings, includeHistory: boolean =
|
||||
};
|
||||
|
||||
if (includeHistory && typeof window !== 'undefined') {
|
||||
const searchHistory = localStorage.getItem(SEARCH_HISTORY_KEY);
|
||||
const watchHistory = localStorage.getItem(WATCH_HISTORY_KEY);
|
||||
const searchHistory = localStorage.getItem(profiledKey(SEARCH_HISTORY_KEY));
|
||||
const watchHistory = localStorage.getItem(profiledKey(WATCH_HISTORY_KEY));
|
||||
|
||||
if (searchHistory) exportData.searchHistory = JSON.parse(searchHistory);
|
||||
if (watchHistory) exportData.watchHistory = JSON.parse(watchHistory);
|
||||
@@ -49,11 +50,11 @@ export function importSettings(
|
||||
|
||||
// Case 2: History only (can be independent)
|
||||
if (data.searchHistory && typeof window !== 'undefined') {
|
||||
localStorage.setItem(SEARCH_HISTORY_KEY, JSON.stringify(data.searchHistory));
|
||||
localStorage.setItem(profiledKey(SEARCH_HISTORY_KEY), JSON.stringify(data.searchHistory));
|
||||
imported = true;
|
||||
}
|
||||
if (data.watchHistory && typeof window !== 'undefined') {
|
||||
localStorage.setItem(WATCH_HISTORY_KEY, JSON.stringify(data.watchHistory));
|
||||
localStorage.setItem(profiledKey(WATCH_HISTORY_KEY), JSON.stringify(data.watchHistory));
|
||||
imported = true;
|
||||
}
|
||||
|
||||
|
||||
@@ -28,10 +28,6 @@ export interface AppSettings {
|
||||
sortBy: SortOption;
|
||||
searchHistory: boolean;
|
||||
watchHistory: boolean;
|
||||
passwordAccess: boolean;
|
||||
accessPasswords: string[];
|
||||
settingsPasswordEnabled: boolean;
|
||||
settingsPasswords: string[];
|
||||
// Player settings
|
||||
autoNextEpisode: boolean;
|
||||
autoSkipIntro: boolean;
|
||||
@@ -106,10 +102,6 @@ function getDefaultAppSettings(): AppSettings {
|
||||
sortBy: 'default',
|
||||
searchHistory: true,
|
||||
watchHistory: true,
|
||||
passwordAccess: false,
|
||||
accessPasswords: [],
|
||||
settingsPasswordEnabled: false,
|
||||
settingsPasswords: [],
|
||||
autoNextEpisode: true,
|
||||
autoSkipIntro: false,
|
||||
skipIntroSeconds: 0,
|
||||
@@ -186,10 +178,6 @@ export const settingsStore = {
|
||||
sortBy: parsed.sortBy || 'default',
|
||||
searchHistory: parsed.searchHistory !== undefined ? parsed.searchHistory : true,
|
||||
watchHistory: parsed.watchHistory !== undefined ? parsed.watchHistory : true,
|
||||
passwordAccess: parsed.passwordAccess !== undefined ? parsed.passwordAccess : false,
|
||||
accessPasswords: Array.isArray(parsed.accessPasswords) ? parsed.accessPasswords : [],
|
||||
settingsPasswordEnabled: parsed.settingsPasswordEnabled !== undefined ? parsed.settingsPasswordEnabled : false,
|
||||
settingsPasswords: Array.isArray(parsed.settingsPasswords) ? parsed.settingsPasswords : [],
|
||||
autoNextEpisode: parsed.autoNextEpisode !== undefined ? parsed.autoNextEpisode : true,
|
||||
autoSkipIntro: parsed.autoSkipIntro !== undefined ? parsed.autoSkipIntro : false,
|
||||
skipIntroSeconds: typeof parsed.skipIntroSeconds === 'number' ? parsed.skipIntroSeconds : 0,
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import { getProfileId } from '@/lib/store/auth-store';
|
||||
|
||||
/**
|
||||
* Returns a profiled localStorage key based on the current user's profileId.
|
||||
* If no session exists, returns the base key unchanged (backward compatible).
|
||||
*/
|
||||
export function profiledKey(baseKey: string): string {
|
||||
const id = getProfileId();
|
||||
return id ? `${baseKey}-p-${id}` : baseKey;
|
||||
}
|
||||
Reference in New Issue
Block a user