1 import React, { useState } from "react";
2 import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog";
3 import { Button } from "@/components/ui/button";
4 import { Checkbox } from "@/components/ui/checkbox";
5 import { Label } from "@/components/ui/label";
6 import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
7 import { Progress } from "@/components/ui/progress";
8 import { useToast } from "@/hooks/use-toast";
9 import { downloadGuide, downloadVideo, getEstimatedSize, isDownloadSupported, type DownloadOptions } from "@/lib/download-utils";
10 import { Download, FileText, Image, Video, HardDrive } from "lucide-react";
11 import type { RepairGuide } from "@shared/schema";
12
13 interface DownloadModalProps {
14 open: boolean;
15 onOpenChange: (open: boolean) => void;
16 guide: RepairGuide;
17 videoUrl?: string;
18 }
19
20 export function DownloadModal({ open, onOpenChange, guide, videoUrl }: DownloadModalProps) {
21 const { toast } = useToast();
22 const [options, setOptions] = useState<DownloadOptions>({
23 format: 'html',
24 includeImages: true,
25 includeVideos: false,
26 });
27 const [isDownloading, setIsDownloading] = useState(false);
28 const [downloadProgress, setDownloadProgress] = useState(0);
29
30 const handleDownload = async () => {
31 if (!isDownloadSupported()) {
32 toast({
33 title: "Download Not Supported",
34 description: "Your browser doesn't support file downloads.",
35 variant: "destructive",
36 });
37 return;
38 }
39
40 setIsDownloading(true);
41 setDownloadProgress(10);
42
43 try {
44 // Download guide
45 const guideSuccess = await downloadGuide(guide, options);
46 setDownloadProgress(50);
47
48 if (!guideSuccess) {
49 throw new Error("Failed to download guide");
50 }
51
52 // Download video if requested
53 if (options.includeVideos && videoUrl) {
54 setDownloadProgress(75);
55 const videoSuccess = await downloadVideo(videoUrl, `${guide.title}_video`);
56 if (!videoSuccess) {
57 toast({
58 title: "Video Download Failed",
59 description: "The guide was downloaded, but the video failed to download.",
60 variant: "destructive",
61 });
62 }
63 }
64
65 setDownloadProgress(100);
66
67 toast({
68 title: "Download Complete",
69 description: `${guide.title} has been downloaded successfully.`,
70 });
71
72 onOpenChange(false);
73 } catch (error) {
74 console.error("Download failed:", error);
75 toast({
76 title: "Download Failed",
77 description: "There was an error downloading the guide. Please try again.",
78 variant: "destructive",
79 });
80 } finally {
81 setIsDownloading(false);
82 setDownloadProgress(0);
83 }
84 };
85
86 const estimatedSize = getEstimatedSize(guide, options);
87
88 return (
89 <Dialog open={open} onOpenChange={onOpenChange}>
90 <DialogContent className="sm:max-w-md">
91 <DialogHeader>
92 <DialogTitle className="flex items-center gap-2">
93 <Download className="h-5 w-5" />
94 Download Guide
95 </DialogTitle>
96 <DialogDescription>
97 Download "{guide.title}" for offline access
98 </DialogDescription>
99 </DialogHeader>
100
101 <div className="space-y-6">
102 {/* Format Selection */}
103 <div className="space-y-3">
104 <Label className="text-sm font-medium">Download Format</Label>
105 <RadioGroup
106 value={options.format}
107 onValueChange={(value) => setOptions({ ...options, format: value as 'html' | 'json' | 'pdf' })}
108 >
109 <div className="flex items-center space-x-2">
110 <RadioGroupItem value="html" id="html" />
111 <Label htmlFor="html" className="flex items-center gap-2">
112 <FileText className="h-4 w-4" />
113 HTML (Recommended)
114 </Label>
115 </div>
116 <div className="flex items-center space-x-2">
117 <RadioGroupItem value="json" id="json" />
118 <Label htmlFor="json" className="flex items-center gap-2">
119 <FileText className="h-4 w-4" />
120 JSON (Raw Data)
121 </Label>
122 </div>
123 <div className="flex items-center space-x-2">
124 <RadioGroupItem value="pdf" id="pdf" />
125 <Label htmlFor="pdf" className="flex items-center gap-2">
126 <FileText className="h-4 w-4" />
127 PDF (Print-ready)
128 </Label>
129 </div>
130 </RadioGroup>
131 </div>
132
133 {/* Include Options */}
134 <div className="space-y-3">
135 <Label className="text-sm font-medium">Include Content</Label>
136 <div className="space-y-2">
137 <div className="flex items-center space-x-2">
138 <Checkbox
139 id="include-images"
140 checked={options.includeImages}
141 onCheckedChange={(checked) =>
142 setOptions({ ...options, includeImages: checked as boolean })
143 }
144 />
145 <Label htmlFor="include-images" className="flex items-center gap-2">
146 <Image className="h-4 w-4" />
147 Include Images
148 </Label>
149 </div>
150 {videoUrl && (
151 <div className="flex items-center space-x-2">
152 <Checkbox
153 id="include-videos"
154 checked={options.includeVideos}
155 onCheckedChange={(checked) =>
156 setOptions({ ...options, includeVideos: checked as boolean })
157 }
158 />
159 <Label htmlFor="include-videos" className="flex items-center gap-2">
160 <Video className="h-4 w-4" />
161 Include Video
162 </Label>
163 </div>
164 )}
165 </div>
166 </div>
167
168 {/* Size Estimate */}
169 <div className="flex items-center gap-2 text-sm text-muted-foreground">
170 <HardDrive className="h-4 w-4" />
171 Estimated size: {estimatedSize}
172 </div>
173
174 {/* Download Progress */}
175 {isDownloading && (
176 <div className="space-y-2">
177 <div className="flex justify-between text-sm">
178 <span>Downloading...</span>
179 <span>{downloadProgress}%</span>
180 </div>
181 <Progress value={downloadProgress} className="h-2" />
182 </div>
183 )}
184
185 {/* Action Buttons */}
186 <div className="flex justify-end gap-2">
187 <Button
188 variant="outline"
189 onClick={() => onOpenChange(false)}
190 disabled={isDownloading}
191 >
192 Cancel
193 </Button>
194 <Button
195 onClick={handleDownload}
196 disabled={isDownloading}
197 className="flex items-center gap-2"
198 >
199 <Download className="h-4 w-4" />
200 {isDownloading ? "Downloading..." : "Download"}
201 </Button>
202 </div>
203 </div>
204 </DialogContent>
205 </Dialog>
206 );
207 }