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 }