1 import { useState } from "react";
2 import { useMutation, useQueryClient } from "@tanstack/react-query";
3 import { Button } from "@/components/ui/button";
4 import { DownloadModal } from "@/components/ui/download-modal";
5 import { apiRequest } from "@/lib/queryClient";
6 import { Download, Bookmark, Eye, Clock, Wrench, ArrowRight } from "lucide-react";
7 import type { RepairGuide } from "@shared/schema";
8 import { cn } from "@/lib/utils";
9
10 interface GuideCardProps {
11 guide: RepairGuide;
12 onViewGuide: () => void;
13 }
14
15 export default function GuideCard({ guide, onViewGuide }: GuideCardProps) {
16 const queryClient = useQueryClient();
17 const [showDownloadModal, setShowDownloadModal] = useState(false);
18
19 const bookmarkMutation = useMutation({
20 mutationFn: () => apiRequest("PATCH", `/api/repair-guides/${guide.id}/bookmark`),
21 onSuccess: () => {
22 queryClient.invalidateQueries({ queryKey: ["api", "repair-guides"] });
23 },
24 });
25
26 const getDifficultyStyles = (difficulty: string) => {
27 switch (difficulty) {
28 case "easy":
29 return "bg-emerald-500/10 text-emerald-500 border-emerald-500/20";
30 case "medium":
31 return "bg-amber-500/10 text-amber-500 border-amber-500/20";
32 case "hard":
33 return "bg-rose-500/10 text-rose-500 border-rose-500/20";
34 default:
35 return "bg-slate-500/10 text-slate-500 border-slate-500/20";
36 }
37 };
38
39 const formatCount = (count: number) => {
40 if (count >= 1000) return `${(count / 1000).toFixed(1)}k`;
41 return count.toString();
42 };
43
44 return (
45 <div className="group relative bg-card/40 backdrop-blur-sm rounded-[2rem] border border-border/40 overflow-hidden hover:border-primary/40 hover:shadow-glass transition-all duration-500 flex flex-col h-full">
46
47 {/* Image with Gradient Overlay */}
48 <div className="relative aspect-video overflow-hidden">
49 <img
50 src={guide.imageUrl}
51 alt={guide.title}
52 className="w-full h-full object-cover transition-transform duration-700 group-hover:scale-110"
53 />
54 <div className="absolute inset-0 bg-gradient-to-t from-black/60 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-500" />
55
56 {/* Floating Badges */}
57 <div className="absolute top-4 left-4 flex gap-2">
58 <span className={cn(
59 "px-3 py-1 text-[10px] font-black uppercase tracking-widest rounded-full border backdrop-blur-md",
60 getDifficultyStyles(guide.difficulty)
61 )}>
62 {guide.difficulty}
63 </span>
64 </div>
65
66 <button
67 onClick={(e) => {
68 e.stopPropagation();
69 bookmarkMutation.mutate();
70 }}
71 disabled={bookmarkMutation.isPending}
72 className="absolute top-4 right-4 h-10 w-10 rounded-full bg-black/20 backdrop-blur-md border border-white/20 flex items-center justify-center text-white hover:bg-primary transition-all duration-300"
73 >
74 <Bookmark className={cn("h-4 w-4", guide.isBookmarked && "fill-current")} />
75 </button>
76 </div>
77
78 <div className="p-6 flex flex-col flex-1">
79 <div className="flex items-center gap-2 text-[10px] font-bold text-muted-foreground uppercase tracking-widest mb-3">
80 <Clock className="h-3 w-3 text-primary" /> {guide.estimatedTime}
81 <span className="opacity-30">•</span>
82 <Wrench className="h-3 w-3 text-primary" /> {guide.toolsRequired.length} Tools
83 </div>
84
85 <h3 className="text-xl font-bold mb-2 group-hover:text-primary transition-colors line-clamp-1">{guide.title}</h3>
86 <p className="text-sm text-muted-foreground mb-6 line-clamp-2 leading-relaxed">{guide.description}</p>
87
88 <div className="mt-auto pt-4 border-t border-border/40 flex items-center justify-between">
89 <div className="flex items-center gap-3 text-[11px] font-medium text-muted-foreground">
90 <div className="flex items-center gap-1">
91 <Eye className="h-3.5 w-3.5" /> {formatCount(guide.viewCount || 0)}
92 </div>
93 <div className="flex items-center gap-1">
94 <Download className="h-3.5 w-3.5" /> {formatCount(guide.downloadCount || 0)}
95 </div>
96 </div>
97
98 <div className="flex items-center gap-2">
99 <Button
100 variant="ghost"
101 size="icon"
102 onClick={(e) => {
103 e.stopPropagation();
104 setShowDownloadModal(true);
105 }}
106 className="rounded-full h-9 w-9 text-muted-foreground hover:bg-primary/10 hover:text-primary transition-all"
107 >
108 <Download className="h-4 w-4" />
109 </Button>
110 <Button
111 onClick={onViewGuide}
112 className="rounded-full h-10 px-5 bg-primary hover:bg-primary/90 text-primary-foreground font-bold shadow-sm gap-2 text-xs group/btn"
113 >
114 Launch
115 <ArrowRight className="h-3 w-3 transition-transform group-hover/btn:translate-x-1" />
116 </Button>
117 </div>
118 </div>
119 </div>
120
121 <DownloadModal
122 open={showDownloadModal}
123 onOpenChange={setShowDownloadModal}
124 guide={guide}
125 />
126 </div>
127 );
128 }