1 import { useQuery } from "@tanstack/react-query";
2 import { useAuth } from "@/hooks/useAuth";
3 import { Button } from "@/components/ui/button";
4 import { useToast } from "@/hooks/use-toast";
5 import { useLocation } from "wouter";
6 import { Heart, Compass, BookOpen, ExternalLink, RefreshCw } from "lucide-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 const [, setLocation] = useLocation();
14
15 const { data: favorites = [], isLoading } = useQuery<Favorite[]>({
16 queryKey: ["/api/favorites"],
17 enabled: isAuthenticated,
18 retry: (failureCount, error) => {
19 if (isUnauthorizedError(error as Error)) {
20 return false;
21 }
22 return failureCount < 3;
23 },
24 });
25
26 if (authLoading) {
27 return (
28 <div className="py-16 flex items-center justify-center">
29 <div className="text-center space-y-3">
30 <RefreshCw className="h-8 w-8 text-primary animate-spin mx-auto" />
31 <p className="text-muted-foreground">Loading your favorites...</p>
32 </div>
33 </div>
34 );
35 }
36
37 if (!isAuthenticated) {
38 return (
39 <div className="max-w-md mx-auto py-16 px-4 text-center space-y-4">
40 <div className="h-16 w-16 bg-primary/10 text-primary rounded-2xl flex items-center justify-center mx-auto">
41 <Heart className="h-8 w-8" />
42 </div>
43 <h2 className="text-2xl font-bold">Sign in to view favorites</h2>
44 <p className="text-muted-foreground text-sm">
45 Bookmark guides and troubleshooting paths to access them quickly anytime.
46 </p>
47 <Button onClick={() => setLocation("/auth")} className="rounded-full">
48 Sign In / Register
49 </Button>
50 </div>
51 );
52 }
53
54 return (
55 <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8 space-y-8">
56 <div className="space-y-2">
57 <h1 className="text-3xl font-extrabold tracking-tight flex items-center gap-3">
58 <Heart className="h-8 w-8 text-destructive fill-destructive" />
59 My Saved Favorites
60 </h1>
61 <p className="text-muted-foreground">
62 Your saved repair guides and interactive troubleshooting workflows
63 </p>
64 </div>
65
66 {isLoading ? (
67 <div className="text-center py-12">
68 <RefreshCw className="h-8 w-8 text-primary animate-spin mx-auto mb-3" />
69 <p className="text-muted-foreground">Loading your favorites...</p>
70 </div>
71 ) : favorites.length === 0 ? (
72 <div className="text-center py-16 bg-card/40 border border-border/40 rounded-3xl p-8 max-w-xl mx-auto space-y-4">
73 <Heart className="h-12 w-12 text-muted-foreground/40 mx-auto" />
74 <h3 className="text-xl font-bold">No favorites yet</h3>
75 <p className="text-muted-foreground text-sm">
76 Start exploring repair guides and click the bookmark icon on any guide to save it for quick access.
77 </p>
78 <Button
79 onClick={() => setLocation("/")}
80 className="rounded-full gap-2"
81 >
82 <Compass className="h-4 w-4" />
83 Browse Repair Guides
84 </Button>
85 </div>
86 ) : (
87 <div className="grid gap-6 md:grid-cols-2 lg:grid-cols-3">
88 {favorites.map((favorite) => (
89 <div
90 key={`${favorite.itemType}-${favorite.itemId}`}
91 className="bg-card border border-border/50 rounded-2xl overflow-hidden shadow-sm hover:shadow-md transition-shadow flex flex-col"
92 >
93 {favorite.itemImageUrl && (
94 <img
95 src={favorite.itemImageUrl}
96 alt={favorite.itemTitle}
97 className="w-full h-44 object-cover"
98 />
99 )}
100
101 <div className="p-6 flex-1 flex flex-col justify-between space-y-4">
102 <div>
103 <div className="flex items-center justify-between mb-2">
104 <span className="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-semibold bg-primary/10 text-primary uppercase">
105 {favorite.itemType}
106 </span>
107 <Heart className="h-4 w-4 text-destructive fill-destructive" />
108 </div>
109
110 <h3 className="font-bold text-foreground line-clamp-2">
111 {favorite.itemTitle}
112 </h3>
113
114 <p className="text-xs text-muted-foreground mt-1">
115 Added {new Date(favorite.createdAt!).toLocaleDateString()}
116 </p>
117 </div>
118
119 <Button
120 className="w-full rounded-full gap-2"
121 onClick={() => {
122 if (favorite.itemType === 'guide') {
123 setLocation(`/guide/${favorite.itemId}`);
124 } else if (favorite.itemType === 'troubleshooting') {
125 setLocation(`/troubleshooting/${favorite.itemId}`);
126 }
127 }}
128 >
129 <BookOpen className="h-4 w-4" />
130 Open Guide
131 </Button>
132 </div>
133 </div>
134 ))}
135 </div>
136 )}
137 </div>
138 );
139 }