@@ -17,11 +17,19 @@ import { Input } from "@/components/ui/input";
1717import { SidebarInset , SidebarTrigger } from "@/components/ui/sidebar" ;
1818import { Spinner } from "@/components/ui/spinner" ;
1919import { useAuth } from "@/contexts/AuthContext" ;
20- import { useFavorites } from "@/hooks/use-favorites" ;
20+ import { useFavorites } from "@/contexts/FavoritesContext" ;
21+ import { Favorite , Folder } from "@/db/schema" ;
2122import { Paper } from "@/lib/types" ;
2223import { Check , Pencil , Trash2 , X } from "lucide-react" ;
2324import { useRouter , useSearchParams } from "next/navigation" ;
24- import { Suspense , useMemo , useState } from "react" ;
25+ import { Suspense , useEffect , useMemo , useState } from "react" ;
26+ import { toast } from "sonner" ;
27+
28+ // FavoriteWithPaper type definition for local use
29+ type FavoriteWithPaper = Favorite & {
30+ paper : Paper ;
31+ folder : Folder | null ;
32+ } ;
2533
2634export default function FavoritesPage ( ) {
2735 return (
@@ -40,19 +48,83 @@ export default function FavoritesPage() {
4048function FavoritesContent ( ) {
4149 const { user, loading : authLoading } = useAuth ( ) ;
4250 const {
43- favorites,
44- loading : favoritesLoading ,
4551 folders,
52+ loading : contextLoading ,
4653 renameFolder,
4754 deleteFolder,
4855 } = useFavorites ( ) ;
56+
4957 const searchParams = useSearchParams ( ) ;
5058 const router = useRouter ( ) ;
5159 const folderIdParam = searchParams . get ( "folderId" ) ;
60+
61+ // Local state for full favorite papers
62+ const [ favorites , setFavorites ] = useState < FavoriteWithPaper [ ] > ( [ ] ) ;
63+ const [ favoritesLoading , setFavoritesLoading ] = useState ( true ) ;
64+
5265 const [ isEditing , setIsEditing ] = useState ( false ) ;
5366 const [ editName , setEditName ] = useState ( "" ) ;
5467 const [ showDeleteDialog , setShowDeleteDialog ] = useState ( false ) ;
5568
69+ const papers = useMemo ( ( ) => {
70+ const uniqueMap = new Map < number , Paper > ( ) ;
71+ favorites . forEach ( ( f ) => {
72+ if ( ! uniqueMap . has ( f . paperId ) ) {
73+ uniqueMap . set ( f . paperId , {
74+ ...f . paper ,
75+ cosineSimilarity : null ,
76+ } ) ;
77+ }
78+ } ) ;
79+ return Array . from ( uniqueMap . values ( ) ) ;
80+ } , [ favorites ] ) ;
81+
82+ // Fetch favorites when folderIdParam changes
83+ useEffect ( ( ) => {
84+ if ( ! user ) return ;
85+
86+ const fetchFavorites = async ( ) => {
87+ setFavoritesLoading ( true ) ;
88+ try {
89+ const token = await user . getIdToken ( ) ;
90+ const url = new URL ( "/api/favorites" , window . location . href ) ;
91+ if ( folderIdParam ) {
92+ url . searchParams . set ( "folderId" , folderIdParam ) ;
93+ }
94+
95+ const res = await fetch ( url . toString ( ) , {
96+ headers : { Authorization : `Bearer ${ token } ` } ,
97+ } ) ;
98+
99+ if ( res . ok ) {
100+ const data = await res . json ( ) ;
101+ // 「すべて」表示の際は、論文が重複しないようにクライアント側でユニークにする
102+ if ( ! folderIdParam ) {
103+ const uniqueMap = new Map < number , FavoriteWithPaper > ( ) ;
104+ data . forEach ( ( f : FavoriteWithPaper ) => {
105+ if ( ! uniqueMap . has ( f . paperId ) ) {
106+ uniqueMap . set ( f . paperId , f ) ;
107+ }
108+ } ) ;
109+ setFavorites ( Array . from ( uniqueMap . values ( ) ) ) ;
110+ } else {
111+ setFavorites ( data ) ;
112+ }
113+ } else {
114+ console . error ( "Failed to fetch favorites" ) ;
115+ toast . error ( "お気に入りの取得に失敗しました" ) ;
116+ }
117+ } catch ( error ) {
118+ console . error ( "Failed to fetch favorites:" , error ) ;
119+ toast . error ( "お気に入りの取得に失敗しました" ) ;
120+ } finally {
121+ setFavoritesLoading ( false ) ;
122+ }
123+ } ;
124+
125+ fetchFavorites ( ) ;
126+ } , [ user , folderIdParam ] ) ;
127+
56128 const canRename = useMemo ( ( ) => {
57129 if ( ! folderIdParam || folderIdParam === "null" ) return false ;
58130 const fid = parseInt ( folderIdParam ) ;
@@ -76,32 +148,15 @@ function FavoritesContent() {
76148 }
77149 } ;
78150
79- const filteredFavorites = useMemo ( ( ) => {
80- if ( ! folderIdParam ) {
81- const uniqueMap = new Map ( ) ;
82- favorites . forEach ( ( f ) => {
83- if ( ! uniqueMap . has ( f . paperId ) ) {
84- uniqueMap . set ( f . paperId , f ) ;
85- }
86- } ) ;
87- return Array . from ( uniqueMap . values ( ) ) ;
88- }
89- if ( folderIdParam === "null" ) {
90- return favorites . filter ( ( f ) => f . folderId === null ) ;
91- }
92- const fid = parseInt ( folderIdParam ) ;
93- if ( isNaN ( fid ) ) return [ ] ;
94- return favorites . filter ( ( f ) => f . folderId === fid ) ;
95- } , [ favorites , folderIdParam ] ) ;
96-
97151 const currentFolderName = useMemo ( ( ) => {
98152 if ( ! folderIdParam ) return "すべて" ;
99153 if ( folderIdParam === "null" ) return "デフォルト" ;
100154 const folder = folders . find ( ( f ) => f . id === parseInt ( folderIdParam ) ) ;
101155 return folder ? folder . name : "不明なフォルダ" ;
102156 } , [ folders , folderIdParam ] ) ;
103157
104- if ( authLoading || favoritesLoading ) {
158+ if ( authLoading || ( contextLoading && folders . length === 0 ) ) {
159+ // Wait for auth and initial folder load (at least to know if they exist)
105160 return (
106161 < div className = "flex bg-sidebar h-screen w-full items-center justify-center" >
107162 < Spinner className = "h-8 w-8" />
@@ -117,13 +172,6 @@ function FavoritesContent() {
117172 ) ;
118173 }
119174
120- const papers = filteredFavorites . map (
121- ( f ) : Paper => ( {
122- ...f . paper ,
123- cosineSimilarity : null ,
124- } )
125- ) ;
126-
127175 return (
128176 < SidebarInset >
129177 < header className = "flex h-16 shrink-0 items-center px-4 mb-5 sticky top-0 z-50 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60 border-b justify-between" >
@@ -162,7 +210,7 @@ function FavoritesContent() {
162210 </ div >
163211 ) : (
164212 < div className = "flex items-center gap-1" >
165- { currentFolderName } ({ papers . length } )
213+ { currentFolderName } ({ favoritesLoading ? "..." : papers . length } )
166214 { canRename && (
167215 < >
168216 < Button
@@ -196,7 +244,11 @@ function FavoritesContent() {
196244 </ header >
197245
198246 < div className = "flex flex-col items-center gap-8 w-full max-w-7xl px-4 mx-auto pb-24" >
199- { papers . length > 0 ? (
247+ { favoritesLoading ? (
248+ < div className = "flex justify-center py-12" >
249+ < Spinner className = "h-8 w-8" />
250+ </ div >
251+ ) : papers . length > 0 ? (
200252 < PapersTable
201253 papers = { papers }
202254 selectedPapers = { new Set ( ) }
0 commit comments