|
1
|
import { useQuery } from "@tanstack/react-query"; |
|
2
|
import { useAuth } from "@/hooks/useAuth"; |
|
3
|
import { Button } from "@/components/ui/button"; |
|
4
|
import Header from "@/components/layout/header"; |
|
5
|
import { useToast } from "@/hooks/use-toast"; |
|
6
|
import { useEffect } from "react"; |
|
7
|
import { isUnauthorizedError } from "@/lib/authUtils"; |
|
8
|
import type { Favorite } from "@shared/schema"; |
|
9
|
|
|
10
|
export default function Favorites() { |
|
11
|
const { toast } = useToast(); |
|
12
|
const { isAuthenticated, isLoading: authLoading } = useAuth(); |
|
13
|
|
|
14
|
// Redirect to login if not authenticated |
|
15
|
useEffect(() => { |
|
16
|
if (!authLoading && !isAuthenticated) { |
|
17
|
toast({ |
|
18
|
title: "Unauthorized", |
|
19
|
description: "You are logged out. Logging in again...", |
|
20
|
variant: "destructive", |
|
21
|
}); |
|
22
|
setTimeout(() => { |
|
23
|
window.location.href = "/api/login"; |
|
24
|
}, 500); |
|
25
|
return; |
|
26
|
} |
|
27
|
}, [isAuthenticated, authLoading, toast]); |
|
28
|
|
|
29
|
const { data: favorites = [], isLoading } = useQuery<Favorite[]>({ |
|
30
|
queryKey: ["/api/favorites"], |
|
31
|
enabled: isAuthenticated, |
|
32
|
retry: (failureCount, error) => { |
|
33
|
if (isUnauthorizedError(error as Error)) { |
|
34
|
return false; |
|
35
|
} |
|
36
|
return failureCount < 3; |
|
37
|
}, |
|
38
|
}); |
|
39
|
|
|
40
|
if (authLoading || !isAuthenticated) { |
|
41
|
return ( |
|
42
|
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 amoled:bg-black flex items-center justify-center"> |
|
43
|
<div className="text-center"> |
|
44
|
<i className="material-icons text-4xl text-primary-700 mb-4 animate-spin">refresh</i> |
|
45
|
<p className="text-gray-600 dark:text-gray-300 amoled:text-gray-300">Loading...</p> |
|
46
|
</div> |
|
47
|
</div> |
|
48
|
); |
|
49
|
} |
|
50
|
|
|
51
|
const getFavoriteIcon = (itemType: string) => { |
|
52
|
switch (itemType) { |
|
53
|
case 'guide': |
|
54
|
return 'book'; |
|
55
|
case 'video': |
|
56
|
return 'play_circle'; |
|
57
|
case 'troubleshooting': |
|
58
|
return 'troubleshoot'; |
|
59
|
default: |
|
60
|
return 'star'; |
|
61
|
} |
|
62
|
}; |
|
63
|
|
|
64
|
const getFavoriteTypeLabel = (itemType: string) => { |
|
65
|
switch (itemType) { |
|
66
|
case 'guide': |
|
67
|
return 'Repair Guide'; |
|
68
|
case 'video': |
|
69
|
return 'Video Tutorial'; |
|
70
|
case 'troubleshooting': |
|
71
|
return 'Troubleshooting Flow'; |
|
72
|
default: |
|
73
|
return 'Favorite'; |
|
74
|
} |
|
75
|
}; |
|
76
|
|
|
77
|
return ( |
|
78
|
<div className="min-h-screen bg-gray-50 dark:bg-gray-900 amoled:bg-black"> |
|
79
|
<Header /> |
|
80
|
|
|
81
|
<main className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-6"> |
|
82
|
<div className="mb-8"> |
|
83
|
<h1 className="text-3xl font-bold text-gray-900 dark:text-white amoled:text-white mb-2"> |
|
84
|
<i className="material-icons text-4xl align-bottom mr-2">favorite</i> |
|
85
|
My Favorites |
|
86
|
</h1> |
|
87
|
<p className="text-gray-600 dark:text-gray-300 amoled:text-gray-300"> |
|
88
|
Your saved repair guides, videos, and troubleshooting flows |
|
89
|
</p> |
|
90
|
</div> |
|
91
|
|
|
92
|
{isLoading ? ( |
|
93
|
<div className="text-center py-12"> |
|
94
|
<i className="material-icons text-4xl text-primary-700 mb-4 animate-spin">refresh</i> |
|
95
|
<p className="text-gray-600 dark:text-gray-300 amoled:text-gray-300">Loading your favorites...</p> |
|
96
|
</div> |
|
97
|
) : favorites.length === 0 ? ( |
|
98
|
<div className="text-center py-12"> |
|
99
|
<i className="material-icons text-6xl text-gray-400 mb-4">favorite_border</i> |
|
100
|
<h3 className="text-xl font-medium text-gray-900 dark:text-white amoled:text-white mb-2"> |
|
101
|
No favorites yet |
|
102
|
</h3> |
|
103
|
<p className="text-gray-600 dark:text-gray-300 amoled:text-gray-300 mb-6"> |
|
104
|
Start exploring repair guides and save your favorites for quick access |
|
105
|
</p> |
|
106
|
<Button |
|
107
|
onClick={() => window.location.href = '/'} |
|
108
|
className="bg-primary-700 hover:bg-primary-800 text-white" |
|
109
|
> |
|
110
|
<i className="material-icons mr-2">explore</i> |
|
111
|
Browse Guides |
|
112
|
</Button> |
|
113
|
</div> |
|
114
|
) : ( |
|
115
|
<div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3"> |
|
116
|
{favorites.map((favorite) => ( |
|
117
|
<div |
|
118
|
key={`${favorite.itemType}-${favorite.itemId}`} |
|
119
|
className="bg-white dark:bg-gray-800 amoled:bg-black rounded-xl shadow-material hover:shadow-material-lg transition-shadow" |
|
120
|
> |
|
121
|
{favorite.itemImageUrl && ( |
|
122
|
<img |
|
123
|
src={favorite.itemImageUrl} |
|
124
|
alt={favorite.itemTitle} |
|
125
|
className="w-full h-48 object-cover rounded-t-xl" |
|
126
|
/> |
|
127
|
)} |
|
128
|
|
|
129
|
<div className="p-6"> |
|
130
|
<div className="flex items-center justify-between mb-3"> |
|
131
|
<span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-primary-100 text-primary-800 dark:bg-primary-900 dark:text-primary-200"> |
|
132
|
<i className="material-icons text-sm mr-1">{getFavoriteIcon(favorite.itemType)}</i> |
|
133
|
{getFavoriteTypeLabel(favorite.itemType)} |
|
134
|
</span> |
|
135
|
|
|
136
|
<Button |
|
137
|
variant="ghost" |
|
138
|
size="sm" |
|
139
|
className="text-red-600 hover:text-red-700 hover:bg-red-50 dark:hover:bg-red-900/20" |
|
140
|
> |
|
141
|
<i className="material-icons text-lg">favorite</i> |
|
142
|
</Button> |
|
143
|
</div> |
|
144
|
|
|
145
|
<h3 className="font-medium text-gray-900 dark:text-white amoled:text-white mb-2"> |
|
146
|
{favorite.itemTitle} |
|
147
|
</h3> |
|
148
|
|
|
149
|
<p className="text-sm text-gray-500 dark:text-gray-400 amoled:text-gray-400 mb-4"> |
|
150
|
Added {new Date(favorite.createdAt!).toLocaleDateString()} |
|
151
|
</p> |
|
152
|
|
|
153
|
<Button |
|
154
|
className="w-full bg-primary-700 hover:bg-primary-800 text-white" |
|
155
|
onClick={() => { |
|
156
|
// Navigate to the item based on type |
|
157
|
if (favorite.itemType === 'guide') { |
|
158
|
window.location.href = `/guide/${favorite.itemId}`; |
|
159
|
} else if (favorite.itemType === 'troubleshooting') { |
|
160
|
window.location.href = `/troubleshooting/${favorite.itemId}`; |
|
161
|
} |
|
162
|
}} |
|
163
|
> |
|
164
|
<i className="material-icons mr-2">open_in_new</i> |
|
165
|
Open |
|
166
|
</Button> |
|
167
|
</div> |
|
168
|
</div> |
|
169
|
))} |
|
170
|
</div> |
|
171
|
)} |
|
172
|
</main> |
|
173
|
</div> |
|
174
|
); |
|
175
|
} |