實作個人收藏、個人活動紀錄

This commit is contained in:
2025-09-11 17:40:07 +08:00
parent bc2104d374
commit 9c5dceb001
29 changed files with 3781 additions and 601 deletions

View File

@@ -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,