1 import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
2 import { Button } from "@/components/ui/button";
3 import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
4 import { apiRequest } from "@/lib/queryClient";
5 import type { RepairGuide } from "@shared/schema";
6
7 interface GuideDetailModalProps {
8 guideId: string;
9 onClose: () => void;
10 isPage?: boolean;
11 }
12
13 export default function GuideDetailModal({ guideId, onClose, isPage = false }: GuideDetailModalProps) {
14 const queryClient = useQueryClient();
15
16 const { data: guide, isLoading, error } = useQuery<RepairGuide>({
17 queryKey: ["/api/repair-guides", guideId],
18 enabled: !!guideId,
19 });
20
21 const bookmarkMutation = useMutation({
22 mutationFn: () => apiRequest("PATCH", `/api/repair-guides/${guideId}/bookmark`),
23 onSuccess: () => {
24 queryClient.invalidateQueries({ queryKey: ["/api/repair-guides"] });
25 },
26 });
27
28 if (isLoading) {
29 return (
30 <Dialog open={true} onOpenChange={() => onClose()}>
31 <DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
32 <div className="animate-pulse space-y-4">
33 <div className="h-8 bg-gray-200 rounded w-3/4"></div>
34 <div className="h-4 bg-gray-200 rounded w-full"></div>
35 <div className="h-4 bg-gray-200 rounded w-5/6"></div>
36 <div className="space-y-3">
37 {[...Array(3)].map((_, i) => (
38 <div key={i} className="h-24 bg-gray-200 rounded"></div>
39 ))}
40 </div>
41 </div>
42 </DialogContent>
43 </Dialog>
44 );
45 }
46
47 if (error || !guide) {
48 return (
49 <Dialog open={true} onOpenChange={() => onClose()}>
50 <DialogContent className="max-w-md">
51 <div className="text-center py-6">
52 <div className="text-gray-400 mb-4">
53 <i className="material-icons text-6xl">error_outline</i>
54 </div>
55 <h3 className="text-lg font-medium text-gray-900 mb-2">Guide Not Found</h3>
56 <p className="text-gray-600 mb-4">The repair guide you're looking for doesn't exist.</p>
57 <Button onClick={onClose}>Close</Button>
58 </div>
59 </DialogContent>
60 </Dialog>
61 );
62 }
63
64 const getDifficultyColor = (difficulty: string) => {
65 switch (difficulty) {
66 case "easy":
67 return "bg-success-50 text-success-600";
68 case "medium":
69 return "bg-warning-50 text-warning-700";
70 case "hard":
71 return "bg-danger-50 text-danger-700";
72 default:
73 return "bg-gray-50 text-gray-600";
74 }
75 };
76
77 const modalContent = (
78 <>
79 <DialogHeader className="sticky top-0 bg-white border-b border-gray-200 px-6 py-4 rounded-t-xl">
80 <div className="flex items-center justify-between">
81 <DialogTitle className="text-xl font-medium text-gray-900">{guide.title}</DialogTitle>
82 <div className="flex items-center space-x-2">
83 <Button
84 variant="ghost"
85 size="icon"
86 onClick={() => bookmarkMutation.mutate()}
87 disabled={bookmarkMutation.isPending}
88 >
89 <i className={`material-icons ${guide.isBookmarked ? "text-primary-700" : "text-gray-400"}`}>
90 {guide.isBookmarked ? "bookmark" : "bookmark_border"}
91 </i>
92 </Button>
93 {!isPage && (
94 <Button variant="ghost" size="icon" onClick={onClose}>
95 <i className="material-icons">close</i>
96 </Button>
97 )}
98 </div>
99 </div>
100 <div className="flex items-center space-x-4 mt-2">
101 <span className={`inline-block px-2 py-1 text-xs font-medium rounded-full ${getDifficultyColor(guide.difficulty)}`}>
102 {guide.difficulty.toUpperCase()}
103 </span>
104 <span className="inline-block px-2 py-1 bg-gray-100 text-gray-600 text-xs rounded-full">
105 {guide.estimatedTime}
106 </span>
107 <span className="text-sm text-gray-500">Updated recently</span>
108 </div>
109 </DialogHeader>
110
111 <div className="p-6">
112 {/* Safety Warning */}
113 {guide.safetyWarnings.length > 0 && (
114 <div className="bg-yellow-50 border-l-4 border-yellow-400 p-4 mb-6">
115 <div className="flex items-start space-x-3">
116 <i className="material-icons text-yellow-600 text-lg mt-0.5">warning</i>
117 <div>
118 <h3 className="font-medium text-yellow-800 mb-1">Safety Precautions</h3>
119 <ul className="text-sm text-yellow-700 space-y-1">
120 {guide.safetyWarnings.map((warning, index) => (
121 <li key={index}>• {warning}</li>
122 ))}
123 </ul>
124 </div>
125 </div>
126 </div>
127 )}
128
129 {/* Required Tools */}
130 <div className="mb-6">
131 <h3 className="font-medium text-gray-900 mb-3">Required Tools</h3>
132 <div className="grid grid-cols-1 md:grid-cols-3 gap-3">
133 {guide.toolsRequired.map((tool, index) => (
134 <div key={index} className="flex items-center space-x-3 p-3 bg-gray-50 rounded-lg">
135 <i className="material-icons text-gray-600">build</i>
136 <span className="text-sm">{tool}</span>
137 </div>
138 ))}
139 </div>
140 </div>
141
142 {/* Step-by-step Instructions */}
143 <div className="space-y-6">
144 <h3 className="font-medium text-gray-900">Installation Steps</h3>
145
146 {guide.steps.map((step) => (
147 <div key={step.stepNumber} className="flex space-x-4">
148 <div className="flex-shrink-0 w-8 h-8 bg-primary-700 text-white rounded-full flex items-center justify-center text-sm font-medium">
149 {step.stepNumber}
150 </div>
151 <div className="flex-1">
152 <h4 className="font-medium text-gray-900 mb-2">{step.title}</h4>
153 <p className="text-sm text-gray-600 mb-3">{step.description}</p>
154
155 {step.imageUrl && (
156 <img
157 src={step.imageUrl}
158 alt={step.title}
159 className="rounded-lg shadow-sm w-full max-w-md mb-3"
160 />
161 )}
162
163 {step.notes && step.notes.length > 0 && (
164 <div className="bg-blue-50 border border-blue-200 rounded-lg p-3 mb-3">
165 <p className="text-sm text-blue-800">
166 <strong>Note:</strong> {step.notes.join(", ")}
167 </p>
168 </div>
169 )}
170
171 {step.warnings && step.warnings.length > 0 && (
172 <div className="bg-red-50 border border-red-200 rounded-lg p-3">
173 <p className="text-sm text-red-800">
174 <strong>Warning:</strong> {step.warnings.join(", ")}
175 </p>
176 </div>
177 )}
178 </div>
179 </div>
180 ))}
181
182 {/* Alternative Solutions */}
183 {guide.alternativeSolutions && (
184 <div className="bg-gray-50 rounded-lg p-4">
185 <h4 className="font-medium text-gray-900 mb-2">Alternative Solutions</h4>
186 <p className="text-sm text-gray-600">{guide.alternativeSolutions}</p>
187 </div>
188 )}
189 </div>
190 </div>
191 </>
192 );
193
194 if (isPage) {
195 return (
196 <div className="max-w-4xl mx-auto px-4 py-8">
197 <div className="bg-white rounded-xl shadow-material-lg">
198 {modalContent}
199 </div>
200 </div>
201 );
202 }
203
204 return (
205 <Dialog open={true} onOpenChange={() => onClose()}>
206 <DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto p-0">
207 {modalContent}
208 </DialogContent>
209 </Dialog>
210 );
211 }