From aac8f5d990176a39859098c47d7e8fde4b919492 Mon Sep 17 00:00:00 2001 From: Shekhar Vaidya Date: Sat, 11 Apr 2026 11:17:54 +0530 Subject: [PATCH] Modernize UI, unify product catalog, and backend optimization - Migrated products.json to structured metadata format. - Refactored ProductDetailPage to eagerly reset state and validate product IDs against local catalog. - Dynamic data fetching in HomePage and StatsBar for total release counts. - Adjusted UI layout in App.tsx and UX states (404/Loading) for missing dependencies. - Shortened backend tracking HTTP timeouts to prevent local Pi-Hole hangs. --- backend/main.go | 2 +- frontend/public/data/products.json | 124 ++++++++++++++--- frontend/src/App.tsx | 4 +- frontend/src/components/StatsBar.tsx | 22 ++- frontend/src/pages/HomePage.tsx | 10 +- frontend/src/pages/ProductDetailPage.tsx | 163 ++++++++++++----------- frontend/src/pages/ProductsPage.tsx | 4 +- msdls_v3.py | 35 ++++- 8 files changed, 257 insertions(+), 107 deletions(-) diff --git a/backend/main.go b/backend/main.go index a17b2b6..39955b7 100644 --- a/backend/main.go +++ b/backend/main.go @@ -53,7 +53,7 @@ func getReferer(productId string) string { // Replicate Fido session tracking func setupSession() (string, error) { sessionID := uuid.New().String() - client := &http.Client{Timeout: 10 * time.Second} + client := &http.Client{Timeout: 3 * time.Second} // Step 1: Register session req1, _ := http.NewRequest("GET", fmt.Sprintf("https://vlscppe.microsoft.com/tags?org_id=%s&session_id=%s", ORG_ID, sessionID), nil) diff --git a/frontend/public/data/products.json b/frontend/public/data/products.json index ee8ad0f..9d929d8 100644 --- a/frontend/public/data/products.json +++ b/frontend/public/data/products.json @@ -1,18 +1,110 @@ { - "48": "Windows 8.1 Single Language (9600.17415)", - "52": "Windows 8.1 (9600.17415)", - "2378": "Windows 10 22H2 Home China (19045.2006)", - "2618": "Windows 10 22H2 v1 (19045.2965)", - "3113": "Windows 11 24H2 (26100.1742)", - "3114": "Windows 11 24H2 Home China (26100.1742)", - "3115": "Windows 11 24H2 Pro China (26100.1742)", - "3131": "Windows 11 Arm64 24H2 (26100.1742)", - "3132": "Windows 11 Arm64 24H2 Home China (26100.1742)", - "3133": "Windows 11 Arm64 24H2 Pro China (26100.1742)", - "3262": "Windows 11 25H2 (26200.6584)", - "3263": "Windows 11 25H2 Home China (26200.6584)", - "3264": "Windows 11 25H2 Pro China (26200.6584)", - "3265": "Windows 11 Arm64 25H2 (26200.6584)", - "3266": "Windows 11 Arm64 25H2 Home China (26200.6584)", - "3267": "Windows 11 Arm64 25H2 Pro China (26200.6584)" + "48": { + "name": "Windows 8.1 Single Language (9600.17415)", + "badge": "LEGACY", + "archs": ["x64", "x86"], + "related": ["52"] + }, + "52": { + "name": "Windows 8.1 (9600.17415)", + "badge": "LEGACY", + "archs": ["x64", "x86"], + "related": ["48", "2618"] + }, + "2378": { + "name": "Windows 10 22H2 Home China (19045.2006)", + "badge": "EOL SOON", + "archs": ["x64"], + "related": ["2618"] + }, + "2618": { + "name": "Windows 10 22H2 v1 (19045.2965)", + "badge": "EOL SOON", + "archs": ["x64", "x86"], + "related": ["3113", "3262", "52"] + }, + "3113": { + "name": "Windows 11 24H2 (26100.1742)", + "badge": "STABLE", + "archs": ["x64"], + "related": ["3262", "3131", "2618"] + }, + "3114": { + "name": "Windows 11 24H2 Home China (26100.1742)", + "badge": "STABLE", + "archs": ["x64"], + "related": ["3113", "3115"] + }, + "3115": { + "name": "Windows 11 24H2 Pro China (26100.1742)", + "badge": "STABLE", + "archs": ["x64"], + "related": ["3113", "3114"] + }, + "3131": { + "name": "Windows 11 Arm64 24H2 (26100.1742)", + "badge": "STABLE", + "archs": ["ARM64"], + "related": ["3265", "3113"] + }, + "3132": { + "name": "Windows 11 Arm64 24H2 Home China (26100.1742)", + "badge": "STABLE", + "archs": ["ARM64"], + "related": ["3131", "3133"] + }, + "3133": { + "name": "Windows 11 Arm64 24H2 Pro China (26100.1742)", + "badge": "STABLE", + "archs": ["ARM64"], + "related": ["3131", "3132"] + }, + "3262": { + "name": "Windows 11 25H2 (26200.6584)", + "badge": "LATEST", + "archs": ["x64"], + "related": ["3265", "3321", "3113"] + }, + "3263": { + "name": "Windows 11 25H2 Home China (26200.6584)", + "badge": "LATEST", + "archs": ["x64"], + "related": ["3262", "3264"] + }, + "3264": { + "name": "Windows 11 25H2 Pro China (26200.6584)", + "badge": "LATEST", + "archs": ["x64"], + "related": ["3262", "3263"] + }, + "3265": { + "name": "Windows 11 Arm64 25H2 (26200.6584)", + "badge": "LATEST", + "archs": ["ARM64"], + "related": ["3262", "3321", "3131"] + }, + "3266": { + "name": "Windows 11 Arm64 25H2 Home China (26200.6584)", + "badge": "LATEST", + "archs": ["ARM64"], + "related": ["3265", "3267"] + }, + "3267": { + "name": "Windows 11 Arm64 25H2 Pro China (26200.6584)", + "badge": "LATEST", + "archs": ["ARM64"], + "related": ["3265", "3266"] + }, + "3321": { + "name": "Windows 11 25H2 (Updated Oct)", + "badge": "LATEST", + "archs": ["x64"], + "related": ["3262", "3324", "3113"] + }, + "3324": { + "name": "Windows 11 Arm64 25H2 (Updated Oct)", + "badge": "LATEST", + "archs": ["ARM64"], + "related": ["3321", "3262", "3265"] + } } \ No newline at end of file diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index c5c3dfb..ea818fd 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -12,9 +12,9 @@ import ScrollToTop from './components/ScrollToTop' export default function App() { return ( -
+
-
+
} /> } /> diff --git a/frontend/src/components/StatsBar.tsx b/frontend/src/components/StatsBar.tsx index 52c41fb..22db93b 100644 --- a/frontend/src/components/StatsBar.tsx +++ b/frontend/src/components/StatsBar.tsx @@ -1,17 +1,27 @@ import { motion } from 'motion/react' +import { useState, useEffect } from 'react' interface Stat { value: string label: string } -const stats: Stat[] = [ - { value: '17', label: 'releases available' }, - { value: '38', label: 'languages' }, - { value: '0', label: 'intermediaries' }, -] - export default function StatsBar() { + const [totalReleases, setTotalReleases] = useState('17') + + useEffect(() => { + fetch('/data/products.json') + .then(r => r.json()) + .then(data => setTotalReleases(Object.keys(data).length.toString())) + .catch(() => {}) + }, []) + + const stats: Stat[] = [ + { value: totalReleases, label: 'releases available' }, + { value: '38', label: 'languages' }, + { value: '0', label: 'intermediaries' }, + ] + return ( { document.title = "Windows ISO Downloader | Official Microsoft Images" @@ -65,6 +66,11 @@ export default function HomePage() { if (metaDesc) { metaDesc.setAttribute('content', "Download official Microsoft Windows ISO files. Fast, free, no registration required.") } + + fetch('/data/products.json') + .then(r => r.json()) + .then(data => setTotalReleases(Object.keys(data).length)) + .catch(() => {}) }, []) return ( @@ -147,7 +153,7 @@ export default function HomePage() { to="/products" className="inline-flex items-center gap-2 px-5 py-2.5 rounded-xl border border-white/8 bg-white/4 text-sm text-zinc-400 hover:text-white hover:border-white/15 hover:bg-white/7 transition-all duration-200" > - Browse all {featured.length > 0 ? '16' : ''} releases + Browse all {totalReleases} releases diff --git a/frontend/src/pages/ProductDetailPage.tsx b/frontend/src/pages/ProductDetailPage.tsx index 41a1b80..533b335 100644 --- a/frontend/src/pages/ProductDetailPage.tsx +++ b/frontend/src/pages/ProductDetailPage.tsx @@ -1,5 +1,5 @@ import { useState, useEffect } from 'react' -import { useParams, useNavigate } from 'react-router-dom' +import { useParams, useNavigate, Link } from 'react-router-dom' import { motion, AnimatePresence } from 'motion/react' import * as Select from '@radix-ui/react-select' import { ArrowLeft, ChevronDown, Download, AlertTriangle, Check, ExternalLink } from 'lucide-react' @@ -11,61 +11,11 @@ import RelatedReleases from '../components/RelatedReleases' const API_BASE = import.meta.env.VITE_API_URL ?? 'http://localhost:3002' -// Map product ID → metadata -const PRODUCT_META: Record = { - '3262': { badge: 'LATEST', archs: ['x64'] }, - '3265': { badge: 'LATEST', archs: ['ARM64'] }, - '3321': { badge: 'LATEST', archs: ['x64'] }, - '3324': { badge: 'LATEST', archs: ['ARM64'] }, - '3113': { badge: 'STABLE', archs: ['x64'] }, - '3131': { badge: 'STABLE', archs: ['ARM64'] }, - '2618': { badge: 'EOL SOON', archs: ['x64', 'x86'] }, - '2378': { badge: 'EOL SOON', archs: ['x64'] }, - '52': { badge: 'LEGACY', archs: ['x64', 'x86'] }, - '48': { badge: 'LEGACY', archs: ['x64', 'x86'] }, -} - -// Sibling release groups for "Also available" -const RELATED_GROUPS: Record = { - '3262': [ - { id: '3265', label: 'Win 11 25H2 ARM64' }, - { id: '3321', label: 'Win 11 25H2 (updated)' }, - { id: '3113', label: 'Win 11 24H2' }, - ], - '3265': [ - { id: '3262', label: 'Win 11 25H2 x64' }, - { id: '3321', label: 'Win 11 25H2 (updated)' }, - { id: '3131', label: 'Win 11 24H2 ARM64' }, - ], - '3321': [ - { id: '3262', label: 'Win 11 25H2' }, - { id: '3324', label: 'Win 11 25H2 ARM64' }, - { id: '3113', label: 'Win 11 24H2' }, - ], - '3324': [ - { id: '3321', label: 'Win 11 25H2 x64' }, - { id: '3262', label: 'Win 11 25H2' }, - { id: '3265', label: 'Win 11 25H2 ARM64 (alt)' }, - ], - '3113': [ - { id: '3262', label: 'Win 11 25H2' }, - { id: '3131', label: 'Win 11 24H2 ARM64' }, - { id: '2618', label: 'Win 10 22H2' }, - ], - '3131': [ - { id: '3265', label: 'Win 11 25H2 ARM64' }, - { id: '3113', label: 'Win 11 24H2 x64' }, - ], - '2618': [ - { id: '3113', label: 'Win 11 24H2' }, - { id: '3262', label: 'Win 11 25H2' }, - { id: '52', label: 'Win 8.1' }, - ], - - '52': [ - { id: '48', label: 'Win 8.1 Single Language' }, - { id: '2618', label: 'Win 10 22H2' }, - ], +interface ProductCatalogEntry { + name: string + badge: string + archs: string[] + related: string[] } function WinLogo({ className }: { className?: string }) { @@ -102,17 +52,45 @@ export default function ProductDetailPage() { const [isFetching, setIsFetching] = useState(false) const [error, setError] = useState(null) const [copiedUri, setCopiedUri] = useState(null) - - const meta = PRODUCT_META[productId!] ?? { badge: '', archs: ['x64'] } - const related = RELATED_GROUPS[productId!] ?? [] + const [isNotFound, setIsNotFound] = useState(false) + const [isValidated, setIsValidated] = useState(false) + + const [meta, setMeta] = useState<{ badge: string; archs: string[] }>({ badge: '', archs: [] }) + const [related, setRelated] = useState<{ id: string; label: string }[]>([]) // Load product name + build string useEffect(() => { + setBuildStr('') + setIsNotFound(false) + setIsValidated(false) + setProductName('') + setMeta({ badge: '', archs: [] }) + setRelated([]) + fetch('/data/products.json') .then(r => r.json()) - .then((data: Record) => { - const name = data[productId!] ?? `Product ${productId}` + .then((data: Record) => { + const product = data[productId!] + + if (!product || !product.name) { + setIsNotFound(true) + setProductName('Product Not Found') + document.title = 'Product Not Found | Windows ISO Downloader' + const descMeta = document.querySelector('meta[name="description"]') + if (descMeta) descMeta.setAttribute('content', 'The requested product could not be found.') + return + } + + const name = product.name setProductName(name) + setMeta({ badge: product.badge || '', archs: product.archs || [] }) + + setRelated((product.related || []).map(rId => ({ + id: rId, + label: data[rId]?.name || `Product ${rId}` + }))) + + setIsValidated(true) // Dynamic SEO injection document.title = `${name} ISO Download | Windows ISO Downloader` @@ -125,12 +103,15 @@ export default function ProductDetailPage() { const match = name.match(/\(([^)]+)\)/) if (match) setBuildStr(match[1]) }) - .catch(() => setProductName(`Product ${productId}`)) + .catch(() => { + setIsNotFound(true) + setProductName('Error Loading Product') + }) }, [productId]) // Fetch languages on mount useEffect(() => { - if (!productId) return + if (!productId || !isValidated) return setIsLoadingLangs(true) setError(null) setDownloadLinks([]) @@ -151,7 +132,7 @@ export default function ProductDetailPage() { }) .catch(e => setError(e.message)) .finally(() => setIsLoadingLangs(false)) - }, [productId]) + }, [productId, isValidated]) async function handleGetLinks() { if (!selectedSku || !productId) return @@ -230,11 +211,37 @@ export default function ProductDetailPage() { {/* Main card */}
- {isLoadingLangs ? ( -
-
-
-
+ {isNotFound ? ( +
+ +

Product Not Found

+

+ The product ID you requested ({productId}) does not exist or has been removed from the catalog. +

+
+ + Home + + + Browse all + +
+
+ ) : isLoadingLangs ? ( +
+
+ + Connecting to Microsoft CDN... +
+
+
+
+
+
) : error && !languages.length ? (
@@ -396,15 +403,19 @@ export default function ProductDetailPage() {
- {/* System requirements */} - + {!isNotFound && ( + <> + {/* System requirements */} + - {/* aria2 tip */} - + {/* aria2 tip */} + - {/* Related releases */} - {related.length > 0 && ( - + {/* Related releases */} + {related.length > 0 && ( + + )} + )}
diff --git a/frontend/src/pages/ProductsPage.tsx b/frontend/src/pages/ProductsPage.tsx index a56aee8..09a70c5 100644 --- a/frontend/src/pages/ProductsPage.tsx +++ b/frontend/src/pages/ProductsPage.tsx @@ -22,8 +22,8 @@ export default function ProductsPage() { fetch('/data/products.json') .then(r => r.json()) - .then((data: Record) => { - setProducts(Object.entries(data).map(([id, name]) => ({ id, name }))) + .then((data: Record) => { + setProducts(Object.entries(data).map(([id, product]) => ({ id, name: product.name }))) }) .finally(() => setIsLoading(false)) }, []) diff --git a/msdls_v3.py b/msdls_v3.py index 4270923..70b054e 100644 --- a/msdls_v3.py +++ b/msdls_v3.py @@ -101,8 +101,39 @@ if __name__ == "__main__": time.sleep(0.5) if args.write: + import os + catalog = {} + if os.path.exists(args.write): + try: + with open(args.write, 'r', encoding='utf-8') as f: + catalog = json.load(f) + except Exception as e: + logging.error(f"Failed to read existing catalog: {e}") + + # Merge new products, preserving existing extra metadata if present + for pid, name in products.items(): + if pid in catalog: + # Update name but keep other fields like badge, archs, etc. + if isinstance(catalog[pid], dict): + catalog[pid]["name"] = name + else: + # Upgrade from old flat format + catalog[pid] = { + "name": name, + "badge": "", + "archs": [], + "related": [] + } + else: + catalog[pid] = { + "name": name, + "badge": "", + "archs": [], + "related": [] + } + with open(args.write, 'w', encoding='utf-8') as f: - json.dump(products, f, indent=4) - logging.info(f"\nSaved {len(products)} products to {args.write}") + json.dump(catalog, f, indent=4) + logging.info(f"\nSaved {len(catalog)} products to {args.write} (merged with existing)") logging.info("Done.")