diff --git a/backend/main.go b/backend/main.go index 1cbeb06..fca7b8c 100644 --- a/backend/main.go +++ b/backend/main.go @@ -940,7 +940,7 @@ func cleanupSessions() { func enableCORS(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { origin := r.Header.Get("Origin") - if origin == "https://msdl.tech-latest.com" || strings.HasPrefix(origin, "http://localhost:") { + if origin == "https://msdl.tech-latest.com" || strings.HasPrefix(origin, "http://localhost:") || strings.HasPrefix(origin, "http://100.") || strings.HasPrefix(origin, "http://192.168.") { w.Header().Set("Access-Control-Allow-Origin", origin) } w.Header().Set("Access-Control-Allow-Methods", "GET, POST, OPTIONS") diff --git a/frontend/src/components/CliHandoff.tsx b/frontend/src/components/CliHandoff.tsx new file mode 100644 index 0000000..1b21873 --- /dev/null +++ b/frontend/src/components/CliHandoff.tsx @@ -0,0 +1,142 @@ +import { useState } from 'react' +import { motion, AnimatePresence } from 'motion/react' +import { Terminal, ChevronRight, Copy, Check, ExternalLink } from 'lucide-react' +import { toast } from 'sonner' + +interface Props { + productId: string + langName?: string + langDisplay?: string + defaultOpen?: boolean + highlight?: boolean +} + +const RELEASES_URL = 'https://github.com/starkSV/windows-iso-downloader/releases/latest' + +function InstallSteps() { + return ( +
+

1. Install

+ + + Download msdl from GitHub Releases + +

+ Rename the downloaded file and add its folder to your PATH:
+ Windows → msdl.exe + · + macOS/Linux → msdl +

+
+ ) +} + +function RunStep({ command, langDisplay, onCopy, copied }: { + command: string + langDisplay?: string + onCopy: () => void + copied: boolean +}) { + return ( +
+

2. Run

+
+
+ {langDisplay ?? 'will prompt for language'} + +
+
+ + $ + {command} + +
+
+
+ ) +} + +export default function CliHandoff({ productId, langName, langDisplay, defaultOpen = false, highlight = false }: Props) { + const [open, setOpen] = useState(defaultOpen) + const [copied, setCopied] = useState(false) + + const command = langName + ? `msdl --id ${productId} --lang "${langName}"` + : `msdl --id ${productId}` + + function handleCopy() { + navigator.clipboard.writeText(command) + setCopied(true) + toast.success('Command copied!') + setTimeout(() => setCopied(false), 2000) + } + + if (highlight) { + return ( +
+
+ +

Use the MSDL CLI instead

+ + Recommended + +
+

+ Our server is currently blocked by Microsoft. The CLI fetches the link directly from your machine — no server involved. +

+ + +
+ ) + } + + return ( +
+ + + + {open && ( + +
+

+ The MSDL CLI runs the lookup directly from your machine — no server in the middle. +

+ + +
+
+ )} +
+
+ ) +} diff --git a/frontend/src/pages/ProductDetailPage.tsx b/frontend/src/pages/ProductDetailPage.tsx index 02ed985..c264f10 100644 --- a/frontend/src/pages/ProductDetailPage.tsx +++ b/frontend/src/pages/ProductDetailPage.tsx @@ -9,6 +9,7 @@ import SystemRequirements from '../components/SystemRequirements' import CliCommand from '../components/CliCommand' import RelatedReleases from '../components/RelatedReleases' import OfficialFallback from '../components/OfficialFallback' +import CliHandoff from '../components/CliHandoff' import { addRecentEntry, updateRecentExpiry } from '../components/RecentlyViewed' const API_BASE = import.meta.env.VITE_API_URL ?? 'http://localhost:3002' @@ -526,10 +527,21 @@ export default function ProductDetailPage() { initial={{ opacity: 0, height: 0 }} animate={{ opacity: 1, height: 'auto' }} exit={{ opacity: 0, height: 0 }} - className="flex items-start gap-3 p-4 rounded-xl bg-red-500/8 border border-red-500/15 text-red-400 text-sm" + className="space-y-3" > - - {error} +
+ + {error} +
+ {selectedSku && ( + + )} )} @@ -637,12 +649,26 @@ export default function ProductDetailPage() { {(!isNotFound && !hasCatalogError) && ( <> - {/* Official fallback — shown when backend is WAF-blocked (stale) */} - {meta.active && linkStatus === 'stale' && selectedSku && ( - + {/* Fallbacks — shown when stale or any error */} + {meta.active && (linkStatus === 'stale' || error !== null) && ( + <> + + {/* Collapsed CLI only when the highlighted card isn't already shown inside */} + {!(error !== null && languages.length > 0) && ( + selectedSku ? ( + + ) : ( + + ) + )} + )} {/* System requirements */}