實作個人收藏、個人活動紀錄
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
"use client"
|
||||
|
||||
import { createContext, useContext, useState, useEffect, type ReactNode } from "react"
|
||||
import { createContext, useContext, useState, useEffect, useCallback, type ReactNode } from "react"
|
||||
|
||||
interface User {
|
||||
id: string
|
||||
@@ -49,6 +49,7 @@ interface AuthContextType {
|
||||
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 }>
|
||||
@@ -95,6 +96,18 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
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")
|
||||
@@ -104,7 +117,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
})
|
||||
|
||||
// New like system state with localStorage persistence
|
||||
const [userLikes, setUserLikes] = useState<Record<string, Array<{ appId: string; date: string }>>>(() => {
|
||||
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) : {}
|
||||
@@ -126,20 +139,29 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
if (typeof window !== 'undefined') {
|
||||
const storedUser = localStorage.getItem("user")
|
||||
if (storedUser) {
|
||||
setUser(JSON.parse(storedUser))
|
||||
const userData = JSON.parse(storedUser)
|
||||
setUser(userData)
|
||||
// 立即載入用戶的互動狀態,不使用 localStorage
|
||||
fetchUserInteractions(userData.id)
|
||||
}
|
||||
}
|
||||
setIsLoading(false)
|
||||
setIsInitialized(true)
|
||||
}, [])
|
||||
|
||||
// Save likes to localStorage when they change
|
||||
// 當用戶登入時載入互動狀態
|
||||
useEffect(() => {
|
||||
if (user) {
|
||||
fetchUserInteractions(user.id)
|
||||
}
|
||||
}, [user])
|
||||
|
||||
// Save old likes to localStorage when they change (保留舊系統的 localStorage)
|
||||
useEffect(() => {
|
||||
if (typeof window !== "undefined") {
|
||||
localStorage.setItem("userLikes", JSON.stringify(userLikes))
|
||||
localStorage.setItem("appLikesOld", JSON.stringify(appLikesOld))
|
||||
}
|
||||
}, [userLikes, appLikesOld])
|
||||
}, [appLikesOld])
|
||||
|
||||
const login = async (email: string, password: string): Promise<boolean> => {
|
||||
setIsLoading(true)
|
||||
@@ -164,6 +186,10 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
}
|
||||
setUser(userWithExtras)
|
||||
localStorage.setItem("user", JSON.stringify(userWithExtras))
|
||||
|
||||
// 載入用戶的互動狀態
|
||||
await fetchUserInteractions(data.user.id)
|
||||
|
||||
setIsLoading(false)
|
||||
return true
|
||||
} else {
|
||||
@@ -262,28 +288,89 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
const toggleFavorite = async (appId: string): Promise<boolean> => {
|
||||
if (!user) return false
|
||||
|
||||
const isFavorited = user.favoriteApps.includes(appId)
|
||||
const updatedFavorites = isFavorited
|
||||
? user.favoriteApps.filter((id) => id !== appId)
|
||||
: [...user.favoriteApps, appId]
|
||||
try {
|
||||
const isFavorited = userFavorites[user.id]?.includes(appId) || false
|
||||
|
||||
if (isFavorited) {
|
||||
// 移除收藏
|
||||
const response = await fetch(`/api/apps/${appId}/favorite?userId=${user.id}`, {
|
||||
method: 'DELETE'
|
||||
})
|
||||
|
||||
// Update global likes counter (keeping for backward compatibility)
|
||||
if (isFavorited) {
|
||||
appLikesCounter[appId] = Math.max(0, appLikesCounter[appId] - 1)
|
||||
} else {
|
||||
appLikesCounter[appId] = (appLikesCounter[appId] || 0) + 1
|
||||
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)
|
||||
}
|
||||
|
||||
const success = await updateProfile({
|
||||
favoriteApps: updatedFavorites,
|
||||
})
|
||||
|
||||
return success
|
||||
return false
|
||||
}
|
||||
|
||||
const isFavorite = (appId: string): boolean => {
|
||||
return user?.favoriteApps.includes(appId) || 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
|
||||
@@ -295,46 +382,93 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
}
|
||||
|
||||
const getAppLikes = (appId: string): number => {
|
||||
return appLikesCounter[appId] || 0
|
||||
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
|
||||
|
||||
const today = new Date().toISOString().split("T")[0]
|
||||
const userLikeHistory = userLikes[user.id] || []
|
||||
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
|
||||
})
|
||||
})
|
||||
|
||||
// Check if user has already liked this app today
|
||||
const hasLikedTodayOld = userLikeHistory.some((like) => like.appId === appId && like.date === today)
|
||||
|
||||
if (hasLikedTodayOld) {
|
||||
return false // Cannot like again today
|
||||
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)
|
||||
}
|
||||
|
||||
// Add like to user's history
|
||||
const updatedUserLikes = {
|
||||
...userLikes,
|
||||
[user.id]: [...userLikeHistory, { appId, date: today }],
|
||||
}
|
||||
setUserLikes(updatedUserLikes)
|
||||
|
||||
// Add like to app's likes
|
||||
const appLikeHistory = appLikesOld[appId] || []
|
||||
const updatedAppLikes = {
|
||||
...appLikesOld,
|
||||
[appId]: [...appLikeHistory, { userId: user.id, date: today }],
|
||||
}
|
||||
setAppLikesOld(updatedAppLikes)
|
||||
|
||||
return true
|
||||
return false
|
||||
}
|
||||
|
||||
const hasLikedTodayOld = (appId: string): boolean => {
|
||||
if (!user) return false
|
||||
|
||||
const today = new Date().toISOString().split("T")[0]
|
||||
const userLikeHistory = userLikes[user.id] || []
|
||||
const userLikeHistory = userLikesOld[user.id] || []
|
||||
|
||||
return userLikeHistory.some((like) => like.appId === appId && like.date === today)
|
||||
}
|
||||
@@ -348,22 +482,41 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
|
||||
const getUserLikeHistory = (): Array<{ appId: string; date: string }> => {
|
||||
if (!user) return []
|
||||
return userLikes[user.id] || []
|
||||
return userLikesOld[user.id] || []
|
||||
}
|
||||
|
||||
// View count functionality
|
||||
const incrementViewCount = (appId: string): void => {
|
||||
setAppViews((prev) => {
|
||||
const newViews = { ...prev, [appId]: (prev[appId] || 0) + 1 }
|
||||
if (typeof window !== "undefined") {
|
||||
localStorage.setItem("appViews", JSON.stringify(newViews))
|
||||
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
|
||||
}))
|
||||
}
|
||||
}
|
||||
return newViews
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('增加觀看次數錯誤:', error)
|
||||
}
|
||||
}
|
||||
|
||||
const getViewCount = (appId: string): number => {
|
||||
return appViews[appId] || 0
|
||||
return appStats[appId]?.viewsCount || 0
|
||||
}
|
||||
|
||||
// Rating functionality
|
||||
@@ -378,7 +531,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
}
|
||||
|
||||
const getAppRating = (appId: string): number => {
|
||||
return appRatings[appId] || 0
|
||||
return appStats[appId]?.rating || 0
|
||||
}
|
||||
|
||||
const getLikeCount = (appId: string): number => {
|
||||
@@ -432,6 +585,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
return user?.role === "admin"
|
||||
}
|
||||
|
||||
|
||||
return (
|
||||
<AuthContext.Provider
|
||||
value={{
|
||||
@@ -454,6 +608,7 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
||||
isInitialized,
|
||||
// New like functionality
|
||||
toggleLike,
|
||||
isLiked,
|
||||
hasLikedTodayOld,
|
||||
getAppLikesInPeriod,
|
||||
getUserLikeHistory,
|
||||
|
Reference in New Issue
Block a user