632 lines
17 KiB
TypeScript
632 lines
17 KiB
TypeScript
"use client"
|
||
|
||
import { createContext, useContext, useState, useEffect, useCallback, type ReactNode } from "react"
|
||
|
||
interface User {
|
||
id: string
|
||
name: string
|
||
email: string
|
||
avatar?: string
|
||
department: string
|
||
role: "user" | "developer" | "admin"
|
||
join_date: string
|
||
favoriteApps: string[]
|
||
recentApps: string[]
|
||
total_likes: number
|
||
total_views: number
|
||
is_active: boolean
|
||
last_login?: string
|
||
phone?: string
|
||
location?: string
|
||
bio?: string
|
||
created_at: string
|
||
updated_at: string
|
||
}
|
||
|
||
interface AppLike {
|
||
appId: string
|
||
userId: string
|
||
likedAt: string
|
||
}
|
||
|
||
interface AuthContextType {
|
||
user: User | null
|
||
login: (email: string, password: string) => Promise<boolean>
|
||
register: (userData: RegisterData) => Promise<boolean>
|
||
logout: () => void
|
||
updateProfile: (userData: Partial<User>) => Promise<boolean>
|
||
toggleFavorite: (appId: string) => Promise<boolean>
|
||
isFavorite: (appId: string) => boolean
|
||
addToRecentApps: (appId: string) => void
|
||
getAppLikes: (appId: string) => number
|
||
incrementViewCount: (appId: string) => void
|
||
getViewCount: (appId: string) => number
|
||
updateAppRating: (appId: string, rating: number) => void
|
||
getAppRating: (appId: string) => number
|
||
canSubmitApp: () => boolean
|
||
canAccessAdmin: () => boolean
|
||
isLoading: boolean
|
||
isInitialized: boolean
|
||
// New like functionality
|
||
toggleLike: (appId: string) => Promise<boolean>
|
||
isLiked: (appId: string) => boolean
|
||
hasLikedToday: (appId: string) => boolean
|
||
getAppLikesInPeriod: (appId: string, startDate: string, endDate: string) => number
|
||
getUserLikeHistory: () => Array<{ appId: string; date: string }>
|
||
getLikeCount: (appId: string) => number
|
||
likeApp: (appId: string) => Promise<boolean>
|
||
}
|
||
|
||
interface RegisterData {
|
||
name: string
|
||
email: string
|
||
password: string
|
||
department: string
|
||
role?: string
|
||
}
|
||
|
||
const AuthContext = createContext<AuthContextType | undefined>(undefined)
|
||
|
||
// Mock users data with new role system
|
||
const mockUsers: User[] = []
|
||
|
||
// Global app likes counter - in a real app this would be in a database
|
||
const appLikesCounter: Record<string, number> = {}
|
||
|
||
export function AuthProvider({ children }: { children: ReactNode }) {
|
||
const [user, setUser] = useState<User | null>(null)
|
||
const [isLoading, setIsLoading] = useState(false)
|
||
const [isInitialized, setIsInitialized] = useState(false)
|
||
|
||
// View count state with localStorage persistence
|
||
const [appViews, setAppViews] = useState<Record<string, number>>(() => {
|
||
if (typeof window !== "undefined") {
|
||
const saved = localStorage.getItem("appViews")
|
||
return saved ? JSON.parse(saved) : {}
|
||
}
|
||
return {}
|
||
})
|
||
|
||
// App ratings state with localStorage persistence
|
||
const [appRatings, setAppRatings] = useState<Record<string, number>>(() => {
|
||
if (typeof window !== "undefined") {
|
||
const saved = localStorage.getItem("appRatings")
|
||
return saved ? JSON.parse(saved) : {}
|
||
}
|
||
return {}
|
||
})
|
||
|
||
// App statistics state (from database)
|
||
const [appStats, setAppStats] = useState<Record<string, {
|
||
likesCount: number
|
||
viewsCount: number
|
||
rating: number
|
||
reviewsCount: number
|
||
}>>({})
|
||
|
||
// User interaction states (from database) - 不使用 localStorage,總是從資料庫載入
|
||
const [userLikes, setUserLikes] = useState<Record<string, string[]>>({})
|
||
const [userFavorites, setUserFavorites] = useState<Record<string, string[]>>({})
|
||
|
||
const [appLikes, setAppLikes] = useState<Record<string, AppLike[]>>(() => {
|
||
if (typeof window !== "undefined") {
|
||
const saved = localStorage.getItem("appLikes")
|
||
return saved ? JSON.parse(saved) : {}
|
||
}
|
||
return {}
|
||
})
|
||
|
||
// New like system state with localStorage persistence
|
||
const [userLikesOld, setUserLikesOld] = useState<Record<string, Array<{ appId: string; date: string }>>>(() => {
|
||
if (typeof window !== "undefined") {
|
||
const saved = localStorage.getItem("userLikes")
|
||
return saved ? JSON.parse(saved) : {}
|
||
}
|
||
return {}
|
||
})
|
||
|
||
// App likes with date tracking
|
||
const [appLikesOld, setAppLikesOld] = useState<Record<string, Array<{ userId: string; date: string }>>>(() => {
|
||
if (typeof window !== "undefined") {
|
||
const saved = localStorage.getItem("appLikesOld")
|
||
return saved ? JSON.parse(saved) : {}
|
||
}
|
||
return {}
|
||
})
|
||
|
||
useEffect(() => {
|
||
// Check for stored user session only on client side
|
||
if (typeof window !== 'undefined') {
|
||
const storedUser = localStorage.getItem("user")
|
||
if (storedUser) {
|
||
const userData = JSON.parse(storedUser)
|
||
setUser(userData)
|
||
// 立即載入用戶的互動狀態,不使用 localStorage
|
||
fetchUserInteractions(userData.id)
|
||
}
|
||
}
|
||
setIsLoading(false)
|
||
setIsInitialized(true)
|
||
}, [])
|
||
|
||
// 當用戶登入時載入互動狀態
|
||
useEffect(() => {
|
||
if (user) {
|
||
fetchUserInteractions(user.id)
|
||
}
|
||
}, [user])
|
||
|
||
// Save old likes to localStorage when they change (保留舊系統的 localStorage)
|
||
useEffect(() => {
|
||
if (typeof window !== "undefined") {
|
||
localStorage.setItem("appLikesOld", JSON.stringify(appLikesOld))
|
||
}
|
||
}, [appLikesOld])
|
||
|
||
const login = async (email: string, password: string): Promise<boolean> => {
|
||
setIsLoading(true)
|
||
|
||
try {
|
||
const response = await fetch('/api/auth/login', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({ email, password }),
|
||
})
|
||
|
||
const data = await response.json()
|
||
|
||
if (data.success && data.user) {
|
||
// 添加前端需要的額外字段
|
||
const userWithExtras = {
|
||
...data.user,
|
||
favoriteApps: [],
|
||
recentApps: []
|
||
}
|
||
setUser(userWithExtras)
|
||
localStorage.setItem("user", JSON.stringify(userWithExtras))
|
||
|
||
// 載入用戶的互動狀態
|
||
await fetchUserInteractions(data.user.id)
|
||
|
||
setIsLoading(false)
|
||
return true
|
||
} else {
|
||
setIsLoading(false)
|
||
return false
|
||
}
|
||
} catch (error) {
|
||
console.error('登入錯誤:', error)
|
||
setIsLoading(false)
|
||
return false
|
||
}
|
||
}
|
||
|
||
const register = async (userData: RegisterData): Promise<boolean> => {
|
||
setIsLoading(true)
|
||
|
||
try {
|
||
const response = await fetch('/api/auth/register', {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify(userData),
|
||
})
|
||
|
||
const data = await response.json()
|
||
|
||
if (data.success && data.user) {
|
||
// 添加前端需要的額外字段
|
||
const userWithExtras = {
|
||
...data.user,
|
||
favoriteApps: [],
|
||
recentApps: []
|
||
}
|
||
setUser(userWithExtras)
|
||
localStorage.setItem("user", JSON.stringify(userWithExtras))
|
||
setIsLoading(false)
|
||
return true
|
||
} else {
|
||
setIsLoading(false)
|
||
return false
|
||
}
|
||
} catch (error) {
|
||
console.error('註冊錯誤:', error)
|
||
setIsLoading(false)
|
||
return false
|
||
}
|
||
}
|
||
|
||
const logout = () => {
|
||
setUser(null)
|
||
localStorage.removeItem("user")
|
||
}
|
||
|
||
const updateProfile = async (userData: Partial<User>): Promise<boolean> => {
|
||
if (!user) return false
|
||
|
||
setIsLoading(true)
|
||
|
||
try {
|
||
const response = await fetch('/api/auth/profile', {
|
||
method: 'PUT',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({
|
||
userId: user.id,
|
||
...userData
|
||
}),
|
||
})
|
||
|
||
const data = await response.json()
|
||
|
||
if (data.success && data.user) {
|
||
// 保持前端需要的額外字段
|
||
const userWithExtras = {
|
||
...data.user,
|
||
favoriteApps: user.favoriteApps,
|
||
recentApps: user.recentApps
|
||
}
|
||
setUser(userWithExtras)
|
||
localStorage.setItem("user", JSON.stringify(userWithExtras))
|
||
setIsLoading(false)
|
||
return true
|
||
} else {
|
||
setIsLoading(false)
|
||
return false
|
||
}
|
||
} catch (error) {
|
||
console.error('更新資料錯誤:', error)
|
||
setIsLoading(false)
|
||
return false
|
||
}
|
||
}
|
||
|
||
const toggleFavorite = async (appId: string): Promise<boolean> => {
|
||
if (!user) return false
|
||
|
||
try {
|
||
const isFavorited = userFavorites[user.id]?.includes(appId) || false
|
||
|
||
if (isFavorited) {
|
||
// 移除收藏
|
||
const response = await fetch(`/api/apps/${appId}/favorite?userId=${user.id}`, {
|
||
method: 'DELETE'
|
||
})
|
||
|
||
if (response.ok) {
|
||
const data = await response.json()
|
||
if (data.success) {
|
||
// 更新本地狀態
|
||
setUserFavorites(prev => ({
|
||
...prev,
|
||
[user.id]: (prev[user.id] || []).filter(id => id !== appId)
|
||
}))
|
||
|
||
// 更新用戶的 favoriteApps
|
||
const updatedFavorites = user.favoriteApps.filter((id) => id !== appId)
|
||
await updateProfile({
|
||
favoriteApps: updatedFavorites,
|
||
})
|
||
|
||
return false
|
||
}
|
||
}
|
||
} else {
|
||
// 添加收藏
|
||
const response = await fetch(`/api/apps/${appId}/favorite`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({
|
||
userId: user.id
|
||
})
|
||
})
|
||
|
||
if (response.ok) {
|
||
const data = await response.json()
|
||
if (data.success) {
|
||
// 更新本地狀態
|
||
setUserFavorites(prev => ({
|
||
...prev,
|
||
[user.id]: [...(prev[user.id] || []), appId]
|
||
}))
|
||
|
||
// 更新用戶的 favoriteApps
|
||
const updatedFavorites = [...user.favoriteApps, appId]
|
||
await updateProfile({
|
||
favoriteApps: updatedFavorites,
|
||
})
|
||
|
||
return true
|
||
}
|
||
} else if (response.status === 409) {
|
||
// 已經收藏過,更新本地狀態
|
||
setUserFavorites(prev => ({
|
||
...prev,
|
||
[user.id]: [...(prev[user.id] || []), appId]
|
||
}))
|
||
return true
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('切換收藏狀態錯誤:', error)
|
||
}
|
||
|
||
return false
|
||
}
|
||
|
||
const isFavorite = useCallback((appId: string): boolean => {
|
||
if (!user) return false
|
||
const userFavs = userFavorites[user.id] || []
|
||
return userFavs.includes(appId)
|
||
}, [user, userFavorites])
|
||
|
||
const isLiked = useCallback((appId: string): boolean => {
|
||
if (!user) return false
|
||
const userLikedApps = userLikes[user.id] || []
|
||
return userLikedApps.includes(appId)
|
||
}, [user, userLikes])
|
||
|
||
const addToRecentApps = (appId: string): void => {
|
||
if (!user) return
|
||
|
||
const updatedRecent = [appId, ...user.recentApps.filter((id) => id !== appId)].slice(0, 10)
|
||
updateProfile({
|
||
recentApps: updatedRecent,
|
||
})
|
||
}
|
||
|
||
const getAppLikes = (appId: string): number => {
|
||
return appStats[appId]?.likesCount || 0
|
||
}
|
||
|
||
// 從資料庫獲取應用統計數據
|
||
const fetchAppStats = async (appId: string) => {
|
||
try {
|
||
const response = await fetch(`/api/apps/${appId}/interactions`)
|
||
if (response.ok) {
|
||
const data = await response.json()
|
||
if (data.success) {
|
||
setAppStats(prev => ({
|
||
...prev,
|
||
[appId]: data.data
|
||
}))
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('獲取應用統計數據錯誤:', error)
|
||
}
|
||
}
|
||
|
||
// 從資料庫獲取用戶的按讚和收藏狀態
|
||
const fetchUserInteractions = async (userId: string) => {
|
||
try {
|
||
const response = await fetch(`/api/user/interactions?userId=${userId}`)
|
||
if (response.ok) {
|
||
const data = await response.json()
|
||
if (data.success) {
|
||
setUserLikes(prev => ({ ...prev, [userId]: data.data.likes }))
|
||
setUserFavorites(prev => ({ ...prev, [userId]: data.data.favorites }))
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('獲取用戶互動狀態錯誤:', error)
|
||
}
|
||
}
|
||
|
||
// New like functionality
|
||
const toggleLike = async (appId: string): Promise<boolean> => {
|
||
if (!user) return false
|
||
|
||
try {
|
||
const isCurrentlyLiked = userLikes[user.id]?.includes(appId) || false
|
||
|
||
const response = await fetch(`/api/apps/${appId}/interactions`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({
|
||
action: 'like',
|
||
userId: user.id
|
||
})
|
||
})
|
||
|
||
if (response.ok) {
|
||
const data = await response.json()
|
||
if (data.success) {
|
||
setAppStats(prev => ({
|
||
...prev,
|
||
[appId]: data.data
|
||
}))
|
||
|
||
// 更新用戶的按讚狀態
|
||
const newLikedState = !isCurrentlyLiked
|
||
setUserLikes(prev => ({
|
||
...prev,
|
||
[user.id]: newLikedState
|
||
? [...(prev[user.id] || []), appId]
|
||
: (prev[user.id] || []).filter(id => id !== appId)
|
||
}))
|
||
|
||
return newLikedState
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('切換按讚狀態錯誤:', error)
|
||
}
|
||
|
||
return false
|
||
}
|
||
|
||
const hasLikedTodayOld = (appId: string): boolean => {
|
||
if (!user) return false
|
||
|
||
const today = new Date().toISOString().split("T")[0]
|
||
const userLikeHistory = userLikesOld[user.id] || []
|
||
|
||
return userLikeHistory.some((like) => like.appId === appId && like.date === today)
|
||
}
|
||
|
||
const getAppLikesInPeriod = (appId: string, startDate: string, endDate: string): number => {
|
||
const appLikeHistory = appLikesOld[appId] || []
|
||
return appLikeHistory.filter((like) => {
|
||
return like.date >= startDate && like.date <= endDate
|
||
}).length
|
||
}
|
||
|
||
const getUserLikeHistory = (): Array<{ appId: string; date: string }> => {
|
||
if (!user) return []
|
||
return userLikesOld[user.id] || []
|
||
}
|
||
|
||
// View count functionality
|
||
const incrementViewCount = async (appId: string): Promise<void> => {
|
||
if (!user) return
|
||
|
||
try {
|
||
const response = await fetch(`/api/apps/${appId}/interactions`, {
|
||
method: 'POST',
|
||
headers: {
|
||
'Content-Type': 'application/json',
|
||
},
|
||
body: JSON.stringify({
|
||
action: 'view',
|
||
userId: user.id
|
||
})
|
||
})
|
||
|
||
if (response.ok) {
|
||
const data = await response.json()
|
||
if (data.success) {
|
||
setAppStats(prev => ({
|
||
...prev,
|
||
[appId]: data.data
|
||
}))
|
||
}
|
||
}
|
||
} catch (error) {
|
||
console.error('增加觀看次數錯誤:', error)
|
||
}
|
||
}
|
||
|
||
const getViewCount = (appId: string): number => {
|
||
return appStats[appId]?.viewsCount || 0
|
||
}
|
||
|
||
// Rating functionality
|
||
const updateAppRating = (appId: string, rating: number): void => {
|
||
setAppRatings((prev) => {
|
||
const newRatings = { ...prev, [appId]: rating }
|
||
if (typeof window !== "undefined") {
|
||
localStorage.setItem("appRatings", JSON.stringify(newRatings))
|
||
}
|
||
return newRatings
|
||
})
|
||
}
|
||
|
||
const getAppRating = (appId: string): number => {
|
||
return appStats[appId]?.rating || 0
|
||
}
|
||
|
||
const getLikeCount = (appId: string): number => {
|
||
return appLikes[appId]?.length || 0
|
||
}
|
||
|
||
const hasLikedToday = (appId: string): boolean => {
|
||
if (!user) return false
|
||
|
||
const today = new Date().toISOString().split("T")[0]
|
||
return appLikes[appId]?.some((like) => like.userId === user.id && like.likedAt.startsWith(today)) || false
|
||
}
|
||
|
||
const likeApp = async (appId: string): Promise<boolean> => {
|
||
if (!user) return false
|
||
|
||
const today = new Date().toISOString().split("T")[0]
|
||
|
||
// Check if user already liked today
|
||
if (hasLikedToday(appId)) return false
|
||
|
||
const newLike: AppLike = {
|
||
appId,
|
||
userId: user.id,
|
||
likedAt: new Date().toISOString(),
|
||
}
|
||
|
||
setAppLikes((prev) => {
|
||
const updatedLikes = { ...prev }
|
||
if (!updatedLikes[appId]) {
|
||
updatedLikes[appId] = []
|
||
}
|
||
updatedLikes[appId] = [...updatedLikes[appId], newLike]
|
||
|
||
if (typeof window !== "undefined") {
|
||
localStorage.setItem("appLikes", JSON.stringify(updatedLikes))
|
||
}
|
||
|
||
return updatedLikes
|
||
})
|
||
|
||
return true
|
||
}
|
||
|
||
// Permission check functions
|
||
const canSubmitApp = (): boolean => {
|
||
return user?.role === "developer"
|
||
}
|
||
|
||
const canAccessAdmin = (): boolean => {
|
||
return user?.role === "admin"
|
||
}
|
||
|
||
|
||
return (
|
||
<AuthContext.Provider
|
||
value={{
|
||
user,
|
||
login,
|
||
register,
|
||
logout,
|
||
updateProfile,
|
||
toggleFavorite,
|
||
isFavorite,
|
||
addToRecentApps,
|
||
getAppLikes,
|
||
incrementViewCount,
|
||
getViewCount,
|
||
updateAppRating,
|
||
getAppRating,
|
||
canSubmitApp,
|
||
canAccessAdmin,
|
||
isLoading,
|
||
isInitialized,
|
||
// New like functionality
|
||
toggleLike,
|
||
isLiked,
|
||
hasLikedTodayOld,
|
||
getAppLikesInPeriod,
|
||
getUserLikeHistory,
|
||
getLikeCount,
|
||
hasLikedToday,
|
||
likeApp,
|
||
}}
|
||
>
|
||
{children}
|
||
</AuthContext.Provider>
|
||
)
|
||
}
|
||
|
||
export function useAuth() {
|
||
const context = useContext(AuthContext)
|
||
if (context === undefined) {
|
||
throw new Error("useAuth must be used within an AuthProvider")
|
||
}
|
||
return context
|
||
}
|