feat(frontend): add per-page SEO, 404 page, OG tags, and sitemap updates
- Replace react-helmet-async (React 19 incompatible) with native React 19 document metadata hoisting across all pages - Add per-page <title>, <meta name="description">, <link rel="canonical">, og:title, og:description, og:url to HomePage, ProductsPage, ProductDetailPage, AboutPage, DisclaimerPage, PrivacyPolicyPage - Add JSON-LD SoftwareApplication schema to HomePage and BreadcrumbList to ProductDetailPage - Add 404 NotFoundPage with noindex meta and canonical to home - Add og:site_name, og:image, og:type, twitter:card/title/description/image to index.html as static fallback; remove duplicate static canonical - Update sitemap.xml with product IDs 3321, 3324 and lastmod dates - Add product screenshots to README and update description - Add og-image.png placeholder Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
d0a70496da
commit
86da27cb8c
16 changed files with 830 additions and 729 deletions
10
README.md
10
README.md
|
|
@ -21,9 +21,15 @@ MSDL replicates the session flow that Microsoft uses to serve ISO download links
|
|||
|
||||
---
|
||||
|
||||
## Screenshot
|
||||
## Screenshots
|
||||
|
||||
> _Coming soon_
|
||||
| Home | All Releases |
|
||||
|---|---|
|
||||
|  |  |
|
||||
|
||||
| Product detail — language selector | Product detail — download links |
|
||||
|---|---|
|
||||
|  |  |
|
||||
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -6,12 +6,17 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Windows ISO Downloader | Official Microsoft Images</title>
|
||||
<meta name="description" content="Download official Microsoft Windows ISO files for Windows 11, 10, and 8.1. Fast, free, no registration required." />
|
||||
<meta property="og:site_name" content="MSDL — Windows ISO Downloader" />
|
||||
<meta property="og:title" content="Windows ISO Downloader | Official Microsoft Images" />
|
||||
<meta property="og:description" content="Download official Microsoft Windows ISO files. Fast, free, no registration." />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://msdl.tech-latest.com/" />
|
||||
<meta property="og:image" content="https://msdl.tech-latest.com/og-image.png" />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<link rel="canonical" href="https://msdl.tech-latest.com/" />
|
||||
<meta name="theme-color" content="#111113" />
|
||||
<meta name="twitter:title" content="Windows ISO Downloader | Official Microsoft Images" />
|
||||
<meta name="twitter:description" content="Download official Microsoft Windows ISO files. Fast, free, no registration." />
|
||||
<meta name="twitter:image" content="https://msdl.tech-latest.com/og-image.png" />
|
||||
<meta name="theme-color" content="#111113" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
|
|
|||
BIN
frontend/public/og-image.png
Normal file
BIN
frontend/public/og-image.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 693 KiB |
BIN
frontend/public/screenshots/msdl-1.jpg
Normal file
BIN
frontend/public/screenshots/msdl-1.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 143 KiB |
BIN
frontend/public/screenshots/msdl-2.jpg
Normal file
BIN
frontend/public/screenshots/msdl-2.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 150 KiB |
BIN
frontend/public/screenshots/msdl-3.jpg
Normal file
BIN
frontend/public/screenshots/msdl-3.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 113 KiB |
BIN
frontend/public/screenshots/msdl-4.jpg
Normal file
BIN
frontend/public/screenshots/msdl-4.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 112 KiB |
|
|
@ -3,45 +3,52 @@
|
|||
<!-- Main Static Pages -->
|
||||
<url>
|
||||
<loc>https://msdl.tech-latest.com/</loc>
|
||||
<lastmod>2026-05-12</lastmod>
|
||||
<priority>1.0</priority>
|
||||
<changefreq>weekly</changefreq>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://msdl.tech-latest.com/products</loc>
|
||||
<lastmod>2026-05-12</lastmod>
|
||||
<priority>0.9</priority>
|
||||
<changefreq>weekly</changefreq>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://msdl.tech-latest.com/about</loc>
|
||||
<lastmod>2026-04-09</lastmod>
|
||||
<priority>0.5</priority>
|
||||
<changefreq>monthly</changefreq>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://msdl.tech-latest.com/privacy-policy</loc>
|
||||
<lastmod>2026-04-09</lastmod>
|
||||
<priority>0.3</priority>
|
||||
<changefreq>yearly</changefreq>
|
||||
</url>
|
||||
<url>
|
||||
<loc>https://msdl.tech-latest.com/disclaimer</loc>
|
||||
<lastmod>2026-04-09</lastmod>
|
||||
<priority>0.3</priority>
|
||||
<changefreq>yearly</changefreq>
|
||||
</url>
|
||||
|
||||
<!-- Product Pages -->
|
||||
<url><loc>https://msdl.tech-latest.com/product/48</loc><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
||||
<url><loc>https://msdl.tech-latest.com/product/52</loc><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
||||
<url><loc>https://msdl.tech-latest.com/product/2378</loc><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
||||
<url><loc>https://msdl.tech-latest.com/product/2618</loc><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
||||
<url><loc>https://msdl.tech-latest.com/product/3113</loc><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
||||
<url><loc>https://msdl.tech-latest.com/product/3114</loc><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
||||
<url><loc>https://msdl.tech-latest.com/product/3115</loc><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
||||
<url><loc>https://msdl.tech-latest.com/product/3131</loc><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
||||
<url><loc>https://msdl.tech-latest.com/product/3132</loc><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
||||
<url><loc>https://msdl.tech-latest.com/product/3133</loc><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
||||
<url><loc>https://msdl.tech-latest.com/product/3262</loc><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
||||
<url><loc>https://msdl.tech-latest.com/product/3263</loc><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
||||
<url><loc>https://msdl.tech-latest.com/product/3264</loc><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
||||
<url><loc>https://msdl.tech-latest.com/product/3265</loc><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
||||
<url><loc>https://msdl.tech-latest.com/product/3266</loc><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
||||
<url><loc>https://msdl.tech-latest.com/product/3267</loc><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
||||
<url><loc>https://msdl.tech-latest.com/product/48</loc><lastmod>2026-05-12</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
||||
<url><loc>https://msdl.tech-latest.com/product/52</loc><lastmod>2026-05-12</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
||||
<url><loc>https://msdl.tech-latest.com/product/2378</loc><lastmod>2026-05-12</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
||||
<url><loc>https://msdl.tech-latest.com/product/2618</loc><lastmod>2026-05-12</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
||||
<url><loc>https://msdl.tech-latest.com/product/3113</loc><lastmod>2026-05-12</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
||||
<url><loc>https://msdl.tech-latest.com/product/3114</loc><lastmod>2026-05-12</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
||||
<url><loc>https://msdl.tech-latest.com/product/3115</loc><lastmod>2026-05-12</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
||||
<url><loc>https://msdl.tech-latest.com/product/3131</loc><lastmod>2026-05-12</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
||||
<url><loc>https://msdl.tech-latest.com/product/3132</loc><lastmod>2026-05-12</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
||||
<url><loc>https://msdl.tech-latest.com/product/3133</loc><lastmod>2026-05-12</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
||||
<url><loc>https://msdl.tech-latest.com/product/3262</loc><lastmod>2026-05-12</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
||||
<url><loc>https://msdl.tech-latest.com/product/3263</loc><lastmod>2026-05-12</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
||||
<url><loc>https://msdl.tech-latest.com/product/3264</loc><lastmod>2026-05-12</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
||||
<url><loc>https://msdl.tech-latest.com/product/3265</loc><lastmod>2026-05-12</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
||||
<url><loc>https://msdl.tech-latest.com/product/3266</loc><lastmod>2026-05-12</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
||||
<url><loc>https://msdl.tech-latest.com/product/3267</loc><lastmod>2026-05-12</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
||||
<url><loc>https://msdl.tech-latest.com/product/3321</loc><lastmod>2026-05-12</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
||||
<url><loc>https://msdl.tech-latest.com/product/3324</loc><lastmod>2026-05-12</lastmod><priority>0.8</priority><changefreq>monthly</changefreq></url>
|
||||
</urlset>
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import { Routes, Route, Link } from 'react-router-dom'
|
||||
import { Routes, Route } from 'react-router-dom'
|
||||
import { Toaster } from 'sonner'
|
||||
import Dock from './components/Dock'
|
||||
import SiteFooter from './components/SiteFooter'
|
||||
|
|
@ -8,6 +8,7 @@ import ProductDetailPage from './pages/ProductDetailPage'
|
|||
import AboutPage from './pages/AboutPage'
|
||||
import PrivacyPolicyPage from './pages/PrivacyPolicyPage'
|
||||
import DisclaimerPage from './pages/DisclaimerPage'
|
||||
import NotFoundPage from './pages/NotFoundPage'
|
||||
import ScrollToTop from './components/ScrollToTop'
|
||||
|
||||
export default function App() {
|
||||
|
|
@ -22,15 +23,7 @@ export default function App() {
|
|||
<Route path="/about" element={<AboutPage />} />
|
||||
<Route path="/privacy-policy" element={<PrivacyPolicyPage />} />
|
||||
<Route path="/disclaimer" element={<DisclaimerPage />} />
|
||||
<Route path="*" element={
|
||||
<div className="flex flex-col items-center justify-center min-h-[60vh] text-center px-4">
|
||||
<h1 className="text-6xl font-bold text-white mb-4">404</h1>
|
||||
<p className="text-zinc-400 mb-8">The page you're looking for doesn't exist.</p>
|
||||
<Link to="/" className="px-6 py-2 bg-white text-black font-semibold rounded-full hover:bg-zinc-200 transition-colors">
|
||||
Go Home
|
||||
</Link>
|
||||
</div>
|
||||
} />
|
||||
<Route path="*" element={<NotFoundPage />} />
|
||||
</Routes>
|
||||
</main>
|
||||
<SiteFooter />
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { useEffect } from 'react'
|
||||
import { motion } from 'motion/react'
|
||||
|
||||
const SITE_URL = 'https://msdl.tech-latest.com'
|
||||
|
||||
function Section({ title, children }: { title: string; children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="space-y-2.5">
|
||||
|
|
@ -11,107 +12,110 @@ function Section({ title, children }: { title: string; children: React.ReactNode
|
|||
}
|
||||
|
||||
export default function AboutPage() {
|
||||
useEffect(() => {
|
||||
document.title = "About MSDL | Windows ISO Downloader"
|
||||
const metaDesc = document.querySelector('meta[name="description"]')
|
||||
if (metaDesc) metaDesc.setAttribute('content', "Learn about MSDL, an open-source tool for fetching official Windows ISOs directly from Microsoft.")
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="max-w-2xl mx-auto px-4 pt-12 pb-10">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 16 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.35 }}
|
||||
className="rounded-2xl border border-white/7 bg-[#111113] p-8 space-y-7"
|
||||
>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-white mb-1">About MSDL</h1>
|
||||
<p className="text-xs font-mono text-zinc-600">A TechLatest open-source project</p>
|
||||
</div>
|
||||
<>
|
||||
<title>About MSDL | Windows ISO Downloader</title>
|
||||
<meta name="description" content="Learn about MSDL, an open-source tool for fetching official Windows ISOs directly from Microsoft." />
|
||||
<link rel="canonical" href={`${SITE_URL}/about`} />
|
||||
<meta property="og:title" content="About MSDL | Windows ISO Downloader" />
|
||||
<meta property="og:description" content="Learn about MSDL, an open-source tool for fetching official Windows ISOs directly from Microsoft." />
|
||||
<meta property="og:url" content={`${SITE_URL}/about`} />
|
||||
|
||||
<Section title="What is this?">
|
||||
<p>
|
||||
MSDL (Microsoft Software Download Links) is a clean, open-source web tool for
|
||||
obtaining official Windows ISO files directly from Microsoft's content delivery network
|
||||
— without needing a Windows machine, the Media Creation Tool, or a browser lock check.
|
||||
</p>
|
||||
<p>
|
||||
There are no third-party hosts, no redirects, no registration, and no ads.
|
||||
Every link you receive is exactly the same signed URL you would get from
|
||||
Microsoft's own download page.
|
||||
</p>
|
||||
</Section>
|
||||
<div className="max-w-2xl mx-auto px-4 pt-12 pb-10">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 16 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.35 }}
|
||||
className="rounded-2xl border border-white/7 bg-[#111113] p-8 space-y-7"
|
||||
>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-white mb-1">About MSDL</h1>
|
||||
<p className="text-xs font-mono text-zinc-600">A TechLatest open-source project</p>
|
||||
</div>
|
||||
|
||||
<Section title="How it works">
|
||||
<p>
|
||||
Our backend replicates the session-based authentication flow that Microsoft uses to
|
||||
serve download links to end users. The same approach is used by{' '}
|
||||
<a
|
||||
href="https://github.com/pbatard/Fido"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-400 hover:text-blue-300 transition-colors"
|
||||
>
|
||||
Fido
|
||||
</a>{' '}
|
||||
(the PowerShell script bundled with Rufus). The flow is:
|
||||
</p>
|
||||
<ol className="list-decimal list-inside space-y-1 text-zinc-500 text-sm">
|
||||
<li>Register a session with Microsoft's tracking endpoint</li>
|
||||
<li>Fetch and parse the MDT fingerprinting script</li>
|
||||
<li>Call the SKU info API to retrieve available languages</li>
|
||||
<li>Call the download links API using the warmed session to get signed CDN URLs</li>
|
||||
</ol>
|
||||
<p>
|
||||
Links are IP-tied to the server and expire after 24 hours — this is standard
|
||||
Microsoft behaviour, not a limitation of MSDL.
|
||||
</p>
|
||||
</Section>
|
||||
<Section title="What is this?">
|
||||
<p>
|
||||
MSDL (Microsoft Software Download Links) is a clean, open-source web tool for
|
||||
obtaining official Windows ISO files directly from Microsoft's content delivery network
|
||||
— without needing a Windows machine, the Media Creation Tool, or a browser lock check.
|
||||
</p>
|
||||
<p>
|
||||
There are no third-party hosts, no redirects, no registration, and no ads.
|
||||
Every link you receive is exactly the same signed URL you would get from
|
||||
Microsoft's own download page.
|
||||
</p>
|
||||
</Section>
|
||||
|
||||
<Section title="Data source">
|
||||
<p>
|
||||
All product data (release names, build numbers, and available architectures) is sourced
|
||||
from Microsoft's official software download connector API:
|
||||
</p>
|
||||
<p>
|
||||
<code className="text-[11px] font-mono bg-white/5 border border-white/7 px-2 py-0.5 rounded text-zinc-400 break-all">
|
||||
www.microsoft.com/software-download-connector/api/
|
||||
</code>
|
||||
</p>
|
||||
<p>
|
||||
Product IDs are maintained manually based on Microsoft's release cadence.
|
||||
Windows 11 25H2, 24H2, Windows 10 22H2, and Windows 8.1 are currently listed.
|
||||
New releases are added as Microsoft publishes them.
|
||||
</p>
|
||||
</Section>
|
||||
<Section title="How it works">
|
||||
<p>
|
||||
Our backend replicates the session-based authentication flow that Microsoft uses to
|
||||
serve download links to end users. The same approach is used by{' '}
|
||||
<a
|
||||
href="https://github.com/pbatard/Fido"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-400 hover:text-blue-300 transition-colors"
|
||||
>
|
||||
Fido
|
||||
</a>{' '}
|
||||
(the PowerShell script bundled with Rufus). The flow is:
|
||||
</p>
|
||||
<ol className="list-decimal list-inside space-y-1 text-zinc-500 text-sm">
|
||||
<li>Register a session with Microsoft's tracking endpoint</li>
|
||||
<li>Fetch and parse the MDT fingerprinting script</li>
|
||||
<li>Call the SKU info API to retrieve available languages</li>
|
||||
<li>Call the download links API using the warmed session to get signed CDN URLs</li>
|
||||
</ol>
|
||||
<p>
|
||||
Links are IP-tied to the server and expire after 24 hours — this is standard
|
||||
Microsoft behaviour, not a limitation of MSDL.
|
||||
</p>
|
||||
</Section>
|
||||
|
||||
<Section title="Credits">
|
||||
<p>
|
||||
The underlying session flow is inspired by{' '}
|
||||
<a
|
||||
href="https://github.com/pbatard/Fido"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-400 hover:text-blue-300 transition-colors"
|
||||
>
|
||||
Fido
|
||||
</a>{' '}
|
||||
by Pete Batard, the same mechanism powering Rufus's ISO download feature.
|
||||
</p>
|
||||
<p>
|
||||
Built and maintained by{' '}
|
||||
<a
|
||||
href="https://tech-latest.com"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-400 hover:text-blue-300 transition-colors"
|
||||
>
|
||||
TechLatest
|
||||
</a>.
|
||||
</p>
|
||||
</Section>
|
||||
</motion.div>
|
||||
</div>
|
||||
<Section title="Data source">
|
||||
<p>
|
||||
All product data (release names, build numbers, and available architectures) is sourced
|
||||
from Microsoft's official software download connector API:
|
||||
</p>
|
||||
<p>
|
||||
<code className="text-[11px] font-mono bg-white/5 border border-white/7 px-2 py-0.5 rounded text-zinc-400 break-all">
|
||||
www.microsoft.com/software-download-connector/api/
|
||||
</code>
|
||||
</p>
|
||||
<p>
|
||||
Product IDs are maintained manually based on Microsoft's release cadence.
|
||||
Windows 11 25H2, 24H2, Windows 10 22H2, and Windows 8.1 are currently listed.
|
||||
New releases are added as Microsoft publishes them.
|
||||
</p>
|
||||
</Section>
|
||||
|
||||
<Section title="Credits">
|
||||
<p>
|
||||
The underlying session flow is inspired by{' '}
|
||||
<a
|
||||
href="https://github.com/pbatard/Fido"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-400 hover:text-blue-300 transition-colors"
|
||||
>
|
||||
Fido
|
||||
</a>{' '}
|
||||
by Pete Batard, the same mechanism powering Rufus's ISO download feature.
|
||||
</p>
|
||||
<p>
|
||||
Built and maintained by{' '}
|
||||
<a
|
||||
href="https://tech-latest.com"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-400 hover:text-blue-300 transition-colors"
|
||||
>
|
||||
TechLatest
|
||||
</a>.
|
||||
</p>
|
||||
</Section>
|
||||
</motion.div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { useEffect } from 'react'
|
||||
import { motion } from 'motion/react'
|
||||
|
||||
const SITE_URL = 'https://msdl.tech-latest.com'
|
||||
|
||||
function Section({ title, children }: { title: string; children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
|
|
@ -11,72 +12,75 @@ function Section({ title, children }: { title: string; children: React.ReactNode
|
|||
}
|
||||
|
||||
export default function DisclaimerPage() {
|
||||
useEffect(() => {
|
||||
document.title = "Disclaimer | Windows ISO Downloader"
|
||||
const metaDesc = document.querySelector('meta[name="description"]')
|
||||
if (metaDesc) metaDesc.setAttribute('content', "Legal disclaimer for MSDL. Not affiliated with Microsoft Corporation.")
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="max-w-2xl mx-auto px-4 pt-12 pb-10">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 16 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.35 }}
|
||||
className="rounded-2xl border border-white/7 bg-[#111113] p-8 space-y-7"
|
||||
>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-white mb-1">Disclaimer</h1>
|
||||
<p className="text-xs font-mono text-zinc-600">Last updated: April 9, 2026</p>
|
||||
</div>
|
||||
<>
|
||||
<title>Disclaimer | Windows ISO Downloader</title>
|
||||
<meta name="description" content="Legal disclaimer for MSDL. Not affiliated with Microsoft Corporation." />
|
||||
<link rel="canonical" href={`${SITE_URL}/disclaimer`} />
|
||||
<meta property="og:title" content="Disclaimer | Windows ISO Downloader" />
|
||||
<meta property="og:description" content="Legal disclaimer for MSDL. Not affiliated with Microsoft Corporation." />
|
||||
<meta property="og:url" content={`${SITE_URL}/disclaimer`} />
|
||||
|
||||
<div className="p-4 rounded-xl border border-amber-500/15 bg-amber-500/6 text-amber-400/80 text-[12px] leading-relaxed">
|
||||
This open-source project is <strong className="text-amber-400">not affiliated with, endorsed by, or sponsored by
|
||||
Microsoft Corporation</strong> in any way. Windows is a registered trademark of Microsoft Corporation.
|
||||
</div>
|
||||
<div className="max-w-2xl mx-auto px-4 pt-12 pb-10">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 16 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.35 }}
|
||||
className="rounded-2xl border border-white/7 bg-[#111113] p-8 space-y-7"
|
||||
>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-white mb-1">Disclaimer</h1>
|
||||
<p className="text-xs font-mono text-zinc-600">Last updated: April 9, 2026</p>
|
||||
</div>
|
||||
|
||||
<Section title="No affiliation with Microsoft">
|
||||
<p>
|
||||
MSDL is an independent, open-source project. It is not produced, approved, or supported
|
||||
by Microsoft Corporation. The name "Windows" and the Windows logo are registered
|
||||
trademarks of Microsoft Corporation.
|
||||
</p>
|
||||
</Section>
|
||||
<div className="p-4 rounded-xl border border-amber-500/15 bg-amber-500/6 text-amber-400/80 text-[12px] leading-relaxed">
|
||||
This open-source project is <strong className="text-amber-400">not affiliated with, endorsed by, or sponsored by
|
||||
Microsoft Corporation</strong> in any way. Windows is a registered trademark of Microsoft Corporation.
|
||||
</div>
|
||||
|
||||
<Section title="Purpose">
|
||||
<p>
|
||||
MSDL exists solely to make it easier for users to access official, unmodified Microsoft
|
||||
Windows ISO files that Microsoft already makes freely available for download. All files
|
||||
are served directly from Microsoft's own servers — we do not host, mirror, or modify
|
||||
any content.
|
||||
</p>
|
||||
</Section>
|
||||
<Section title="No affiliation with Microsoft">
|
||||
<p>
|
||||
MSDL is an independent, open-source project. It is not produced, approved, or supported
|
||||
by Microsoft Corporation. The name "Windows" and the Windows logo are registered
|
||||
trademarks of Microsoft Corporation.
|
||||
</p>
|
||||
</Section>
|
||||
|
||||
<Section title="No warranty">
|
||||
<p>
|
||||
This tool is provided "as is" without warranty of any kind. We make no guarantees
|
||||
about the availability of Microsoft's download links, the uptime of our backend, or
|
||||
the compatibility of any downloaded ISO with your hardware configuration.
|
||||
</p>
|
||||
</Section>
|
||||
<Section title="Purpose">
|
||||
<p>
|
||||
MSDL exists solely to make it easier for users to access official, unmodified Microsoft
|
||||
Windows ISO files that Microsoft already makes freely available for download. All files
|
||||
are served directly from Microsoft's own servers — we do not host, mirror, or modify
|
||||
any content.
|
||||
</p>
|
||||
</Section>
|
||||
|
||||
<Section title="Use at your own risk">
|
||||
<p>
|
||||
By using this tool, you agree that you are solely responsible for complying with
|
||||
Microsoft's End User License Agreement (EULA) and any applicable laws in your jurisdiction.
|
||||
Downloading Windows does not grant you a license to use it — a valid product key or
|
||||
digital license is required for activation.
|
||||
</p>
|
||||
</Section>
|
||||
<Section title="No warranty">
|
||||
<p>
|
||||
This tool is provided "as is" without warranty of any kind. We make no guarantees
|
||||
about the availability of Microsoft's download links, the uptime of our backend, or
|
||||
the compatibility of any downloaded ISO with your hardware configuration.
|
||||
</p>
|
||||
</Section>
|
||||
|
||||
<Section title="Open source">
|
||||
<p>
|
||||
The source code is publicly available. You are free to inspect, fork, and self-host
|
||||
this project under the terms of its open-source license. The project credits the
|
||||
open-source Fido script by Pete Batard for the underlying session flow.
|
||||
</p>
|
||||
</Section>
|
||||
</motion.div>
|
||||
</div>
|
||||
<Section title="Use at your own risk">
|
||||
<p>
|
||||
By using this tool, you agree that you are solely responsible for complying with
|
||||
Microsoft's End User License Agreement (EULA) and any applicable laws in your jurisdiction.
|
||||
Downloading Windows does not grant you a license to use it — a valid product key or
|
||||
digital license is required for activation.
|
||||
</p>
|
||||
</Section>
|
||||
|
||||
<Section title="Open source">
|
||||
<p>
|
||||
The source code is publicly available. You are free to inspect, fork, and self-host
|
||||
this project under the terms of its open-source license. The project credits the
|
||||
open-source Fido script by Pete Batard for the underlying session flow.
|
||||
</p>
|
||||
</Section>
|
||||
</motion.div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import HowItWorks from '../components/HowItWorks'
|
|||
import ComparisonTable from '../components/ComparisonTable'
|
||||
import FAQAccordion from '../components/FAQAccordion'
|
||||
|
||||
const SITE_URL = 'https://msdl.tech-latest.com'
|
||||
|
||||
const featured = [
|
||||
{
|
||||
id: '3262',
|
||||
|
|
@ -57,16 +59,21 @@ const cardVariant = {
|
|||
show: { opacity: 1, y: 0, transition: { type: 'spring' as const, stiffness: 280, damping: 28 } },
|
||||
}
|
||||
|
||||
const jsonLd = {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'SoftwareApplication',
|
||||
name: 'MSDL — Windows ISO Downloader',
|
||||
applicationCategory: 'UtilitiesApplication',
|
||||
operatingSystem: 'Web',
|
||||
url: SITE_URL,
|
||||
description: 'Download official Microsoft Windows ISO files for Windows 11, 10, and 8.1. Direct links from Microsoft CDN. Free, no registration.',
|
||||
offers: { '@type': 'Offer', price: '0', priceCurrency: 'USD' },
|
||||
}
|
||||
|
||||
export default function HomePage() {
|
||||
const [totalReleases, setTotalReleases] = useState(16)
|
||||
|
||||
useEffect(() => {
|
||||
document.title = "Windows ISO Downloader | Official Microsoft Images"
|
||||
const metaDesc = document.querySelector('meta[name="description"]')
|
||||
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: Record<string, { active?: boolean }>) => {
|
||||
|
|
@ -77,98 +84,108 @@ export default function HomePage() {
|
|||
}, [])
|
||||
|
||||
return (
|
||||
<div className="max-w-4xl mx-auto px-5 pt-20 pb-16 relative" style={{ overflowX: 'hidden' }}>
|
||||
<>
|
||||
<title>Windows ISO Downloader | Official Microsoft Images</title>
|
||||
<meta name="description" content="Download official Microsoft Windows ISO files for Windows 11, 10, and 8.1. Fast, free, no registration required." />
|
||||
<link rel="canonical" href={`${SITE_URL}/`} />
|
||||
<meta property="og:title" content="Windows ISO Downloader | Official Microsoft Images" />
|
||||
<meta property="og:description" content="Download official Microsoft Windows ISO files. Fast, free, no registration." />
|
||||
<meta property="og:url" content={`${SITE_URL}/`} />
|
||||
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }} />
|
||||
|
||||
{/* Ambient glow — radial-gradient used over blur-3xl to optimize Mobile Lighthouse (Speed Index bypass) */}
|
||||
<div
|
||||
className="fixed top-0 left-1/2 -translate-x-1/2 w-[800px] h-[400px] pointer-events-none -z-10"
|
||||
style={{ background: 'radial-gradient(ellipse at center, rgba(59,130,246,0.10) 0%, transparent 60%)' }}
|
||||
/>
|
||||
<div className="max-w-4xl mx-auto px-5 pt-20 pb-16 relative" style={{ overflowX: 'hidden' }}>
|
||||
|
||||
{/* Hero */}
|
||||
<motion.div
|
||||
className="text-center mb-6"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
>
|
||||
<p className="text-[11px] font-mono font-semibold tracking-[0.22em] uppercase text-blue-400 mb-5">
|
||||
Official Microsoft ISOs
|
||||
</p>
|
||||
<h1
|
||||
className="font-bold text-white tracking-tight leading-tight mb-4"
|
||||
style={{ fontSize: 'clamp(1.6rem, 7.5vw, 3.75rem)' }}
|
||||
{/* Ambient glow — radial-gradient used over blur-3xl to optimize Mobile Lighthouse (Speed Index bypass) */}
|
||||
<div
|
||||
className="fixed top-0 left-1/2 -translate-x-1/2 w-[800px] h-[400px] pointer-events-none -z-10"
|
||||
style={{ background: 'radial-gradient(ellipse at center, rgba(59,130,246,0.10) 0%, transparent 60%)' }}
|
||||
/>
|
||||
|
||||
{/* Hero */}
|
||||
<motion.div
|
||||
className="text-center mb-6"
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5 }}
|
||||
>
|
||||
Windows ISO <span className="text-white/50">Downloader</span>
|
||||
</h1>
|
||||
<p className="text-zinc-400 text-base max-w-sm mx-auto mb-6 leading-relaxed">
|
||||
Direct links from Microsoft's CDN. No ads. No registration.
|
||||
</p>
|
||||
<div className="flex items-center justify-center gap-4 text-[12px] text-zinc-500">
|
||||
<span className="flex items-center gap-1.5">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-green-500 inline-block" />
|
||||
Always official
|
||||
</span>
|
||||
<span className="text-zinc-700">·</span>
|
||||
<span className="flex items-center gap-1.5">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-blue-500 inline-block" />
|
||||
Real-time links
|
||||
</span>
|
||||
<span className="text-zinc-700">·</span>
|
||||
<span className="flex items-center gap-1.5">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-zinc-600 inline-block" />
|
||||
Free forever
|
||||
</span>
|
||||
<p className="text-[11px] font-mono font-semibold tracking-[0.22em] uppercase text-blue-400 mb-5">
|
||||
Official Microsoft ISOs
|
||||
</p>
|
||||
<h1
|
||||
className="font-bold text-white tracking-tight leading-tight mb-4"
|
||||
style={{ fontSize: 'clamp(1.6rem, 7.5vw, 3.75rem)' }}
|
||||
>
|
||||
Windows ISO <span className="text-white/50">Downloader</span>
|
||||
</h1>
|
||||
<p className="text-zinc-400 text-base max-w-sm mx-auto mb-6 leading-relaxed">
|
||||
Direct links from Microsoft's CDN. No ads. No registration.
|
||||
</p>
|
||||
<div className="flex items-center justify-center gap-4 text-[12px] text-zinc-500">
|
||||
<span className="flex items-center gap-1.5">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-green-500 inline-block" />
|
||||
Always official
|
||||
</span>
|
||||
<span className="text-zinc-700">·</span>
|
||||
<span className="flex items-center gap-1.5">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-blue-500 inline-block" />
|
||||
Real-time links
|
||||
</span>
|
||||
<span className="text-zinc-700">·</span>
|
||||
<span className="flex items-center gap-1.5">
|
||||
<span className="w-1.5 h-1.5 rounded-full bg-zinc-600 inline-block" />
|
||||
Free forever
|
||||
</span>
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Stats bar */}
|
||||
<div className="mb-10">
|
||||
<StatsBar />
|
||||
</div>
|
||||
</motion.div>
|
||||
|
||||
{/* Stats bar */}
|
||||
<div className="mb-10">
|
||||
<StatsBar />
|
||||
</div>
|
||||
|
||||
{/* Featured grid */}
|
||||
<motion.div
|
||||
className="grid grid-cols-1 sm:grid-cols-2 gap-3 mb-8"
|
||||
variants={container}
|
||||
initial="hidden"
|
||||
animate="show"
|
||||
>
|
||||
{featured.map(product => (
|
||||
<motion.div key={product.id} variants={cardVariant}>
|
||||
<Link to={`/product/${product.id}`} className="block outline-none">
|
||||
<ProductCard
|
||||
{...product}
|
||||
/>
|
||||
</Link>
|
||||
</motion.div>
|
||||
))}
|
||||
</motion.div>
|
||||
|
||||
{/* Browse all CTA */}
|
||||
<motion.div
|
||||
className="text-center"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 0.5 }}
|
||||
>
|
||||
<Link
|
||||
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"
|
||||
{/* Featured grid */}
|
||||
<motion.div
|
||||
className="grid grid-cols-1 sm:grid-cols-2 gap-3 mb-8"
|
||||
variants={container}
|
||||
initial="hidden"
|
||||
animate="show"
|
||||
>
|
||||
Browse all {totalReleases} releases
|
||||
<ArrowRight size={14} />
|
||||
</Link>
|
||||
</motion.div>
|
||||
{featured.map(product => (
|
||||
<motion.div key={product.id} variants={cardVariant}>
|
||||
<Link to={`/product/${product.id}`} className="block outline-none">
|
||||
<ProductCard
|
||||
{...product}
|
||||
/>
|
||||
</Link>
|
||||
</motion.div>
|
||||
))}
|
||||
</motion.div>
|
||||
|
||||
{/* How it works */}
|
||||
<HowItWorks />
|
||||
{/* Browse all CTA */}
|
||||
<motion.div
|
||||
className="text-center"
|
||||
initial={{ opacity: 0 }}
|
||||
animate={{ opacity: 1 }}
|
||||
transition={{ delay: 0.5 }}
|
||||
>
|
||||
<Link
|
||||
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 {totalReleases} releases
|
||||
<ArrowRight size={14} />
|
||||
</Link>
|
||||
</motion.div>
|
||||
|
||||
{/* Comparison table */}
|
||||
<ComparisonTable />
|
||||
{/* How it works */}
|
||||
<HowItWorks />
|
||||
|
||||
{/* FAQ */}
|
||||
<FAQAccordion />
|
||||
</div>
|
||||
{/* Comparison table */}
|
||||
<ComparisonTable />
|
||||
|
||||
{/* FAQ */}
|
||||
<FAQAccordion />
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
22
frontend/src/pages/NotFoundPage.tsx
Normal file
22
frontend/src/pages/NotFoundPage.tsx
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import { useEffect } from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
export default function NotFoundPage() {
|
||||
useEffect(() => { window.scrollTo(0, 0) }, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<title>404 Not Found | Windows ISO Downloader</title>
|
||||
<meta name="description" content="The page you are looking for does not exist." />
|
||||
<meta name="robots" content="noindex" />
|
||||
<link rel="canonical" href="https://msdl.tech-latest.com/" />
|
||||
<div className="flex flex-col items-center justify-center min-h-[60vh] text-center px-4">
|
||||
<h1 className="text-6xl font-bold text-white mb-4">404</h1>
|
||||
<p className="text-zinc-400 mb-8">The page you're looking for doesn't exist.</p>
|
||||
<Link to="/" className="px-6 py-2 bg-white text-black font-semibold rounded-full hover:bg-zinc-200 transition-colors">
|
||||
Go Home
|
||||
</Link>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
@ -1,6 +1,7 @@
|
|||
import { useEffect } from 'react'
|
||||
import { motion } from 'motion/react'
|
||||
|
||||
const SITE_URL = 'https://msdl.tech-latest.com'
|
||||
|
||||
function Section({ title, children }: { title: string; children: React.ReactNode }) {
|
||||
return (
|
||||
<div className="space-y-2">
|
||||
|
|
@ -11,75 +12,78 @@ function Section({ title, children }: { title: string; children: React.ReactNode
|
|||
}
|
||||
|
||||
export default function PrivacyPolicyPage() {
|
||||
useEffect(() => {
|
||||
document.title = "Privacy Policy | Windows ISO Downloader"
|
||||
const metaDesc = document.querySelector('meta[name="description"]')
|
||||
if (metaDesc) metaDesc.setAttribute('content', "Privacy policy for MSDL. We log no data, use no trackers, and proxy no files.")
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div className="max-w-2xl mx-auto px-4 pt-12 pb-10">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 16 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.35 }}
|
||||
className="rounded-2xl border border-white/7 bg-[#111113] p-8 space-y-7"
|
||||
>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-white mb-1">Privacy Policy</h1>
|
||||
<p className="text-xs font-mono text-zinc-600">Last updated: April 9, 2026</p>
|
||||
</div>
|
||||
<>
|
||||
<title>Privacy Policy | Windows ISO Downloader</title>
|
||||
<meta name="description" content="Privacy policy for MSDL. We log no data, use no trackers, and proxy no files." />
|
||||
<link rel="canonical" href={`${SITE_URL}/privacy-policy`} />
|
||||
<meta property="og:title" content="Privacy Policy | Windows ISO Downloader" />
|
||||
<meta property="og:description" content="Privacy policy for MSDL. We log no data, use no trackers, and proxy no files." />
|
||||
<meta property="og:url" content={`${SITE_URL}/privacy-policy`} />
|
||||
|
||||
<Section title="No data collected">
|
||||
<p>
|
||||
MSDL does not collect, store, log, or share any personally identifiable information.
|
||||
There are no user accounts, no cookies, no analytics trackers, and no third-party
|
||||
advertising scripts of any kind.
|
||||
</p>
|
||||
</Section>
|
||||
<div className="max-w-2xl mx-auto px-4 pt-12 pb-10">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 16 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.35 }}
|
||||
className="rounded-2xl border border-white/7 bg-[#111113] p-8 space-y-7"
|
||||
>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold text-white mb-1">Privacy Policy</h1>
|
||||
<p className="text-xs font-mono text-zinc-600">Last updated: April 9, 2026</p>
|
||||
</div>
|
||||
|
||||
<Section title="How download links work">
|
||||
<p>
|
||||
When you request a download link, our backend server contacts Microsoft's software
|
||||
download API on your behalf using a non-Windows browser user-agent (the same method
|
||||
used by open-source tools like Rufus/Fido). Microsoft's servers return a time-limited,
|
||||
IP-tied signed URL.
|
||||
</p>
|
||||
<p>
|
||||
Our backend does not log your IP address, does not store the generated link, and does
|
||||
not proxy the actual file download — the ISO is downloaded directly from Microsoft's CDN.
|
||||
</p>
|
||||
</Section>
|
||||
<Section title="No data collected">
|
||||
<p>
|
||||
MSDL does not collect, store, log, or share any personally identifiable information.
|
||||
There are no user accounts, no cookies, no analytics trackers, and no third-party
|
||||
advertising scripts of any kind.
|
||||
</p>
|
||||
</Section>
|
||||
|
||||
<Section title="Third-party services">
|
||||
<p>
|
||||
All Windows ISO files are hosted on Microsoft's official content delivery network
|
||||
(<code className="text-zinc-400 text-[11px] font-mono bg-white/5 px-1 py-0.5 rounded">software.download.prss.microsoft.com</code>).
|
||||
We are not responsible for Microsoft's privacy practices or link availability.
|
||||
</p>
|
||||
</Section>
|
||||
<Section title="How download links work">
|
||||
<p>
|
||||
When you request a download link, our backend server contacts Microsoft's software
|
||||
download API on your behalf using a non-Windows browser user-agent (the same method
|
||||
used by open-source tools like Rufus/Fido). Microsoft's servers return a time-limited,
|
||||
IP-tied signed URL.
|
||||
</p>
|
||||
<p>
|
||||
Our backend does not log your IP address, does not store the generated link, and does
|
||||
not proxy the actual file download — the ISO is downloaded directly from Microsoft's CDN.
|
||||
</p>
|
||||
</Section>
|
||||
|
||||
<Section title="Open source">
|
||||
<p>
|
||||
The full source code for both the frontend and backend is publicly available.
|
||||
You can inspect, audit, or self-host the entire stack.
|
||||
</p>
|
||||
</Section>
|
||||
<Section title="Third-party services">
|
||||
<p>
|
||||
All Windows ISO files are hosted on Microsoft's official content delivery network
|
||||
(<code className="text-zinc-400 text-[11px] font-mono bg-white/5 px-1 py-0.5 rounded">software.download.prss.microsoft.com</code>).
|
||||
We are not responsible for Microsoft's privacy practices or link availability.
|
||||
</p>
|
||||
</Section>
|
||||
|
||||
<Section title="Contact">
|
||||
<p>
|
||||
Questions? Reach us via{' '}
|
||||
<a
|
||||
href="https://tech-latest.com/contact-us/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-400 hover:text-blue-300 transition-colors"
|
||||
>
|
||||
TechLatest
|
||||
</a>.
|
||||
</p>
|
||||
</Section>
|
||||
</motion.div>
|
||||
</div>
|
||||
<Section title="Open source">
|
||||
<p>
|
||||
The full source code for both the frontend and backend is publicly available.
|
||||
You can inspect, audit, or self-host the entire stack.
|
||||
</p>
|
||||
</Section>
|
||||
|
||||
<Section title="Contact">
|
||||
<p>
|
||||
Questions? Reach us via{' '}
|
||||
<a
|
||||
href="https://tech-latest.com/contact-us/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-blue-400 hover:text-blue-300 transition-colors"
|
||||
>
|
||||
TechLatest
|
||||
</a>.
|
||||
</p>
|
||||
</Section>
|
||||
</motion.div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import Aria2Tip from '../components/Aria2Tip'
|
|||
import RelatedReleases from '../components/RelatedReleases'
|
||||
|
||||
const API_BASE = import.meta.env.VITE_API_URL ?? 'http://localhost:3002'
|
||||
const SITE_URL = 'https://msdl.tech-latest.com'
|
||||
|
||||
interface ProductCatalogEntry {
|
||||
name: string
|
||||
|
|
@ -21,7 +22,7 @@ interface ProductCatalogEntry {
|
|||
|
||||
function WinLogo({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg viewBox="0 0 88 88" className={className} fill="currentColor">
|
||||
<svg viewBox="0 0 88 88" className={className} fill="currentColor" aria-hidden="true">
|
||||
<path d="M0 12.402l35.687-4.86.016 34.423-35.67.203zm35.67 33.529l.028 34.453L.028 75.48.026 45.7zm4.326-39.025L87.314 0v41.527l-47.318.376zm47.329 39.349-.011 41.34-47.318-6.678-.066-34.739z" />
|
||||
</svg>
|
||||
)
|
||||
|
|
@ -56,10 +57,39 @@ export default function ProductDetailPage() {
|
|||
const [isNotFound, setIsNotFound] = useState(false)
|
||||
const [isValidated, setIsValidated] = useState(false)
|
||||
const [hasCatalogError, setHasCatalogError] = useState(false)
|
||||
|
||||
|
||||
const [meta, setMeta] = useState<{ badge: string; archs: string[]; active: boolean }>({ badge: '', archs: [], active: false })
|
||||
const [related, setRelated] = useState<{ id: string; label: string }[]>([])
|
||||
|
||||
// Derived SEO values
|
||||
const pageTitle = isNotFound
|
||||
? 'Product Not Found | Windows ISO Downloader'
|
||||
: hasCatalogError
|
||||
? 'Error | Windows ISO Downloader'
|
||||
: productName
|
||||
? `${productName}${!meta.active ? ' (Discontinued)' : ''} ISO Download | Windows ISO Downloader`
|
||||
: 'Loading... | Windows ISO Downloader'
|
||||
|
||||
const pageDescription = isNotFound
|
||||
? 'The requested product could not be found.'
|
||||
: hasCatalogError
|
||||
? 'Failed to load product catalog.'
|
||||
: productName
|
||||
? `Download official ISO image for ${productName}. Direct links pulled securely from Microsoft CDN servers.`
|
||||
: ''
|
||||
|
||||
const canonicalUrl = `${SITE_URL}/product/${productId}`
|
||||
|
||||
const breadcrumbJsonLd = productName && !isNotFound && !hasCatalogError ? {
|
||||
'@context': 'https://schema.org',
|
||||
'@type': 'BreadcrumbList',
|
||||
itemListElement: [
|
||||
{ '@type': 'ListItem', position: 1, name: 'Home', item: SITE_URL },
|
||||
{ '@type': 'ListItem', position: 2, name: 'All Releases', item: `${SITE_URL}/products` },
|
||||
{ '@type': 'ListItem', position: 3, name: productName, item: canonicalUrl },
|
||||
],
|
||||
} : null
|
||||
|
||||
// Load product name + build string
|
||||
useEffect(() => {
|
||||
setBuildStr('')
|
||||
|
|
@ -83,13 +113,10 @@ export default function ProductDetailPage() {
|
|||
})
|
||||
.then((data: Record<string, ProductCatalogEntry>) => {
|
||||
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
|
||||
}
|
||||
|
||||
|
|
@ -97,22 +124,15 @@ export default function ProductDetailPage() {
|
|||
setProductName(name)
|
||||
const isActive = product.active !== false
|
||||
setMeta({ badge: product.badge || '', archs: product.archs || [], active: isActive })
|
||||
|
||||
|
||||
setRelated((product.related || []).map(rId => ({
|
||||
id: rId,
|
||||
label: data[rId]?.name || `Product ${rId}`
|
||||
})))
|
||||
|
||||
|
||||
if (isActive) {
|
||||
setIsValidated(true)
|
||||
}
|
||||
|
||||
// Dynamic SEO injection
|
||||
document.title = `${name} ISO Download | Windows ISO Downloader`
|
||||
const descMeta = document.querySelector('meta[name="description"]')
|
||||
if (descMeta) {
|
||||
descMeta.setAttribute('content', `Download official ISO image for ${name}. Direct links pulled securely from Microsoft CDN servers.`)
|
||||
}
|
||||
|
||||
// Extract build from parentheses e.g. "(26200.6584)"
|
||||
const match = name.match(/\(([^)]+)\)/)
|
||||
|
|
@ -124,7 +144,7 @@ export default function ProductDetailPage() {
|
|||
})
|
||||
}, [productId])
|
||||
|
||||
// Fetch languages on mount
|
||||
// Fetch languages
|
||||
useEffect(() => {
|
||||
if (!productId || !isValidated || !meta.active) return
|
||||
setIsLoadingLangs(true)
|
||||
|
|
@ -138,7 +158,6 @@ export default function ProductDetailPage() {
|
|||
if (data.error) throw new Error(data.error)
|
||||
if (!data.Skus?.length) throw new Error('No languages found for this product.')
|
||||
setLanguages(data.Skus)
|
||||
// Default to English if present
|
||||
const english = data.Skus.find((s: Sku) =>
|
||||
s.LocalizedLanguage.toLowerCase().includes('english') &&
|
||||
!s.LocalizedLanguage.toLowerCase().includes('international')
|
||||
|
|
@ -176,301 +195,313 @@ export default function ProductDetailPage() {
|
|||
setTimeout(() => setCopiedUri(null), 2000)
|
||||
}
|
||||
|
||||
// First download link URI for aria2 tip
|
||||
const firstUri = downloadLinks[0]?.Uri
|
||||
|
||||
return (
|
||||
<div className="max-w-xl mx-auto px-5 pt-12 pb-10">
|
||||
{/* Back */}
|
||||
<button
|
||||
onClick={() => navigate(-1)}
|
||||
className="flex items-center gap-1.5 text-sm text-zinc-500 hover:text-zinc-300 mb-8 transition-colors"
|
||||
>
|
||||
<ArrowLeft size={15} />
|
||||
Back
|
||||
</button>
|
||||
<>
|
||||
<title>{pageTitle}</title>
|
||||
{pageDescription && <meta name="description" content={pageDescription} />}
|
||||
<link rel="canonical" href={canonicalUrl} />
|
||||
<meta property="og:title" content={pageTitle} />
|
||||
{pageDescription && <meta property="og:description" content={pageDescription} />}
|
||||
<meta property="og:url" content={canonicalUrl} />
|
||||
{isNotFound && <meta name="robots" content="noindex" />}
|
||||
{breadcrumbJsonLd && (
|
||||
<script type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(breadcrumbJsonLd) }} />
|
||||
)}
|
||||
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 14 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.35 }}
|
||||
>
|
||||
{/* Product header */}
|
||||
<div className="flex items-start gap-4 mb-8">
|
||||
<div className="w-12 h-12 rounded-xl bg-white/5 border border-white/8 flex items-center justify-center flex-shrink-0">
|
||||
<WinLogo className="w-6 h-6 text-white/50" />
|
||||
</div>
|
||||
<div className="min-w-0">
|
||||
<h1 className="text-2xl font-bold text-white leading-tight tracking-tight">
|
||||
{productName || `Product ${productId}`}
|
||||
</h1>
|
||||
{buildStr && (
|
||||
<p className="text-[12px] font-mono text-zinc-500 mt-0.5">Build {buildStr}</p>
|
||||
)}
|
||||
{meta.archs.length > 0 && (
|
||||
<div className="flex gap-1.5 mt-2">
|
||||
{meta.archs.map(arch => (
|
||||
<span key={arch} className="text-[10px] font-mono font-medium px-1.5 py-0.5 rounded bg-white/5 border border-white/8 text-zinc-500">
|
||||
{arch}
|
||||
</span>
|
||||
))}
|
||||
{meta.badge && (
|
||||
<span className="text-[10px] font-mono font-semibold px-2 py-0.5 rounded-full bg-blue-500/10 border border-blue-500/20 text-blue-400">
|
||||
{meta.badge}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="max-w-xl mx-auto px-5 pt-12 pb-10">
|
||||
{/* Back */}
|
||||
<button
|
||||
onClick={() => navigate(-1)}
|
||||
className="flex items-center gap-1.5 text-sm text-zinc-500 hover:text-zinc-300 mb-8 transition-colors"
|
||||
>
|
||||
<ArrowLeft size={15} />
|
||||
Back
|
||||
</button>
|
||||
|
||||
{/* Main card */}
|
||||
<div className="rounded-2xl border border-white/7 bg-[#111113] p-6 space-y-5">
|
||||
{!productName && !isNotFound && !hasCatalogError ? (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2.5 text-xs text-zinc-400 font-medium tracking-wide">
|
||||
<motion.span
|
||||
animate={{ rotate: 360 }}
|
||||
transition={{ duration: 1, repeat: Infinity, ease: 'linear' }}
|
||||
className="inline-block w-4 h-4 border-2 border-zinc-500/30 border-t-zinc-500 rounded-full"
|
||||
/>
|
||||
Loading catalog...
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<div className="h-3 w-20 bg-white/6 rounded animate-pulse" />
|
||||
<div className="h-11 bg-white/6 rounded-xl animate-pulse" />
|
||||
<div className="h-11 bg-white/5 rounded-xl animate-pulse" />
|
||||
</div>
|
||||
</div>
|
||||
) : hasCatalogError ? (
|
||||
<div className="flex flex-col items-center justify-center py-6 text-center">
|
||||
<WifiOff size={32} className="text-zinc-600 mb-4" />
|
||||
<h2 className="text-lg font-semibold text-white mb-2">Network Error</h2>
|
||||
<p className="text-zinc-500 text-sm max-w-xs px-4 leading-relaxed mb-6">
|
||||
Failed to load the product catalog. Please check your internet connection or try refreshing the page.
|
||||
</p>
|
||||
<button onClick={() => window.location.reload()} className="px-4 py-2 rounded-xl bg-white/5 border border-white/8 text-sm font-medium text-white hover:bg-white/10 transition-colors">
|
||||
Retry
|
||||
</button>
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 14 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.35 }}
|
||||
>
|
||||
{/* Product header */}
|
||||
<div className="flex items-start gap-4 mb-8">
|
||||
<div className="w-12 h-12 rounded-xl bg-white/5 border border-white/8 flex items-center justify-center flex-shrink-0">
|
||||
<WinLogo className="w-6 h-6 text-white/50" />
|
||||
</div>
|
||||
) : isNotFound ? (
|
||||
<div className="flex flex-col items-center justify-center py-6 text-center">
|
||||
<AlertTriangle size={32} className="text-zinc-600 mb-4" />
|
||||
<h2 className="text-lg font-semibold text-white mb-2">Product Not Found</h2>
|
||||
<p className="text-zinc-500 text-sm max-w-xs px-4 leading-relaxed mb-6">
|
||||
The product ID you requested ({productId}) does not exist or has been removed from the catalog.
|
||||
</p>
|
||||
<div className="flex items-center justify-center gap-3 w-full">
|
||||
<Link to="/" className="flex-1 max-w-[140px] px-4 py-2.5 rounded-xl bg-white/5 border border-white/8 text-sm font-medium text-white hover:bg-white/10 transition-colors">
|
||||
Home
|
||||
</Link>
|
||||
<Link to="/products" className="flex-1 max-w-[140px] px-4 py-2.5 rounded-xl bg-blue-500 text-sm font-medium text-white hover:bg-blue-600 transition-colors shadow-lg">
|
||||
Browse all
|
||||
<div className="min-w-0">
|
||||
<h1 className="text-2xl font-bold text-white leading-tight tracking-tight">
|
||||
{productName || `Product ${productId}`}
|
||||
</h1>
|
||||
{buildStr && (
|
||||
<p className="text-[12px] font-mono text-zinc-500 mt-0.5">Build {buildStr}</p>
|
||||
)}
|
||||
{meta.archs.length > 0 && (
|
||||
<div className="flex gap-1.5 mt-2">
|
||||
{meta.archs.map(arch => (
|
||||
<span key={arch} className="text-[10px] font-mono font-medium px-1.5 py-0.5 rounded bg-white/5 border border-white/8 text-zinc-500">
|
||||
{arch}
|
||||
</span>
|
||||
))}
|
||||
{meta.badge && (
|
||||
<span className="text-[10px] font-mono font-semibold px-2 py-0.5 rounded-full bg-blue-500/10 border border-blue-500/20 text-blue-400">
|
||||
{meta.badge}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Main card */}
|
||||
<div className="rounded-2xl border border-white/7 bg-[#111113] p-6 space-y-5">
|
||||
{!productName && !isNotFound && !hasCatalogError ? (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2.5 text-xs text-zinc-400 font-medium tracking-wide">
|
||||
<motion.span
|
||||
animate={{ rotate: 360 }}
|
||||
transition={{ duration: 1, repeat: Infinity, ease: 'linear' }}
|
||||
className="inline-block w-4 h-4 border-2 border-zinc-500/30 border-t-zinc-500 rounded-full"
|
||||
/>
|
||||
Loading catalog...
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<div className="h-3 w-20 bg-white/6 rounded animate-pulse" />
|
||||
<div className="h-11 bg-white/6 rounded-xl animate-pulse" />
|
||||
<div className="h-11 bg-white/5 rounded-xl animate-pulse" />
|
||||
</div>
|
||||
</div>
|
||||
) : hasCatalogError ? (
|
||||
<div className="flex flex-col items-center justify-center py-6 text-center">
|
||||
<WifiOff size={32} className="text-zinc-600 mb-4" />
|
||||
<h2 className="text-lg font-semibold text-white mb-2">Network Error</h2>
|
||||
<p className="text-zinc-500 text-sm max-w-xs px-4 leading-relaxed mb-6">
|
||||
Failed to load the product catalog. Please check your internet connection or try refreshing the page.
|
||||
</p>
|
||||
<button onClick={() => window.location.reload()} className="px-4 py-2 rounded-xl bg-white/5 border border-white/8 text-sm font-medium text-white hover:bg-white/10 transition-colors">
|
||||
Retry
|
||||
</button>
|
||||
</div>
|
||||
) : isNotFound ? (
|
||||
<div className="flex flex-col items-center justify-center py-6 text-center">
|
||||
<AlertTriangle size={32} className="text-zinc-600 mb-4" />
|
||||
<h2 className="text-lg font-semibold text-white mb-2">Product Not Found</h2>
|
||||
<p className="text-zinc-500 text-sm max-w-xs px-4 leading-relaxed mb-6">
|
||||
The product ID you requested ({productId}) does not exist or has been removed from the catalog.
|
||||
</p>
|
||||
<div className="flex items-center justify-center gap-3 w-full">
|
||||
<Link to="/" className="flex-1 max-w-[140px] px-4 py-2.5 rounded-xl bg-white/5 border border-white/8 text-sm font-medium text-white hover:bg-white/10 transition-colors">
|
||||
Home
|
||||
</Link>
|
||||
<Link to="/products" className="flex-1 max-w-[140px] px-4 py-2.5 rounded-xl bg-blue-500 text-sm font-medium text-white hover:bg-blue-600 transition-colors shadow-lg">
|
||||
Browse all
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
) : !meta.active ? (
|
||||
<div className="flex flex-col items-center justify-center py-6 text-center">
|
||||
<AlertTriangle size={32} className="text-amber-500/50 mb-4" />
|
||||
<h2 className="text-lg font-semibold text-white mb-2">Product Discontinued</h2>
|
||||
<p className="text-zinc-500 text-sm max-w-sm px-4 leading-relaxed mb-6">
|
||||
This release is no longer hosted on Microsoft's official CDN and cannot be downloaded.
|
||||
</p>
|
||||
<Link to="/products" className="px-4 py-2.5 rounded-xl bg-white/5 border border-white/8 text-sm font-medium text-white hover:bg-white/10 transition-colors">
|
||||
Browse other releases
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
) : !meta.active ? (
|
||||
<div className="flex flex-col items-center justify-center py-6 text-center">
|
||||
<AlertTriangle size={32} className="text-amber-500/50 mb-4" />
|
||||
<h2 className="text-lg font-semibold text-white mb-2">Product Discontinued</h2>
|
||||
<p className="text-zinc-500 text-sm max-w-sm px-4 leading-relaxed mb-6">
|
||||
This release is no longer hosted on Microsoft's official CDN and cannot be downloaded.
|
||||
</p>
|
||||
<Link to="/products" className="px-4 py-2.5 rounded-xl bg-white/5 border border-white/8 text-sm font-medium text-white hover:bg-white/10 transition-colors">
|
||||
Browse other releases
|
||||
</Link>
|
||||
</div>
|
||||
) : isLoadingLangs ? (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2.5 text-xs text-zinc-400 font-medium tracking-wide">
|
||||
<motion.span
|
||||
animate={{ rotate: 360 }}
|
||||
transition={{ duration: 1, repeat: Infinity, ease: 'linear' }}
|
||||
className="inline-block w-4 h-4 border-2 border-zinc-500/30 border-t-zinc-500 rounded-full"
|
||||
/>
|
||||
Connecting to Microsoft CDN...
|
||||
) : isLoadingLangs ? (
|
||||
<div className="space-y-4">
|
||||
<div className="flex items-center gap-2.5 text-xs text-zinc-400 font-medium tracking-wide">
|
||||
<motion.span
|
||||
animate={{ rotate: 360 }}
|
||||
transition={{ duration: 1, repeat: Infinity, ease: 'linear' }}
|
||||
className="inline-block w-4 h-4 border-2 border-zinc-500/30 border-t-zinc-500 rounded-full"
|
||||
/>
|
||||
Connecting to Microsoft CDN...
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<div className="h-3 w-20 bg-white/6 rounded animate-pulse" />
|
||||
<div className="h-11 bg-white/6 rounded-xl animate-pulse" />
|
||||
<div className="h-11 bg-white/5 rounded-xl animate-pulse" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<div className="h-3 w-20 bg-white/6 rounded animate-pulse" />
|
||||
<div className="h-11 bg-white/6 rounded-xl animate-pulse" />
|
||||
<div className="h-11 bg-white/5 rounded-xl animate-pulse" />
|
||||
) : error && !languages.length ? (
|
||||
<div className="flex items-start gap-3 p-4 rounded-xl bg-red-500/8 border border-red-500/15 text-red-400 text-sm">
|
||||
<AlertTriangle size={15} className="flex-shrink-0 mt-0.5" />
|
||||
<div>
|
||||
<p className="font-medium mb-0.5">Failed to load languages</p>
|
||||
<p className="text-red-400/70 text-xs">{error}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : error && !languages.length ? (
|
||||
<div className="flex items-start gap-3 p-4 rounded-xl bg-red-500/8 border border-red-500/15 text-red-400 text-sm">
|
||||
<AlertTriangle size={15} className="flex-shrink-0 mt-0.5" />
|
||||
<div>
|
||||
<p className="font-medium mb-0.5">Failed to load languages</p>
|
||||
<p className="text-red-400/70 text-xs">{error}</p>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{/* Language selector */}
|
||||
<div>
|
||||
<label className="block text-[10px] font-mono font-semibold uppercase tracking-widest text-zinc-600 mb-2">
|
||||
Language
|
||||
</label>
|
||||
<Select.Root
|
||||
value={selectedSku?.Id}
|
||||
onValueChange={val => {
|
||||
const sku = languages.find(l => l.Id === val)
|
||||
if (sku) setSelectedSku(sku)
|
||||
setDownloadLinks([])
|
||||
setError(null)
|
||||
}}
|
||||
) : (
|
||||
<>
|
||||
{/* Language selector */}
|
||||
<div>
|
||||
<label className="block text-[10px] font-mono font-semibold uppercase tracking-widest text-zinc-600 mb-2">
|
||||
Language
|
||||
</label>
|
||||
<Select.Root
|
||||
value={selectedSku?.Id}
|
||||
onValueChange={val => {
|
||||
const sku = languages.find(l => l.Id === val)
|
||||
if (sku) setSelectedSku(sku)
|
||||
setDownloadLinks([])
|
||||
setError(null)
|
||||
}}
|
||||
>
|
||||
<Select.Trigger className="w-full flex items-center justify-between px-4 py-3 rounded-xl bg-white/5 border border-white/7 text-sm text-white hover:bg-white/7 hover:border-white/12 focus:outline-none transition-all cursor-pointer">
|
||||
<Select.Value placeholder="Select language..." />
|
||||
<Select.Icon>
|
||||
<ChevronDown size={14} className="text-zinc-500" />
|
||||
</Select.Icon>
|
||||
</Select.Trigger>
|
||||
|
||||
<Select.Portal>
|
||||
<Select.Content
|
||||
className="z-[100] w-[var(--radix-select-trigger-width)] max-h-72 overflow-auto rounded-xl border border-white/9 bg-[#131316] backdrop-blur-xl shadow-2xl text-sm"
|
||||
position="popper"
|
||||
sideOffset={6}
|
||||
>
|
||||
<Select.Viewport className="p-1">
|
||||
{languages.map(lang => (
|
||||
<Select.Item
|
||||
key={lang.Id}
|
||||
value={lang.Id}
|
||||
className="relative flex items-center justify-between px-3 py-2.5 rounded-lg text-zinc-400 data-[highlighted]:text-white data-[highlighted]:bg-white/6 cursor-pointer outline-none transition-colors"
|
||||
>
|
||||
<Select.ItemText>{lang.LocalizedLanguage}</Select.ItemText>
|
||||
<Select.ItemIndicator>
|
||||
<Check size={13} className="text-blue-400" />
|
||||
</Select.ItemIndicator>
|
||||
</Select.Item>
|
||||
))}
|
||||
</Select.Viewport>
|
||||
</Select.Content>
|
||||
</Select.Portal>
|
||||
</Select.Root>
|
||||
</div>
|
||||
|
||||
{/* CTA */}
|
||||
<button
|
||||
onClick={handleGetLinks}
|
||||
disabled={!selectedSku || isFetching}
|
||||
className="w-full flex items-center justify-center gap-2 py-3 rounded-xl bg-blue-500 text-white text-sm font-semibold hover:bg-blue-600 disabled:opacity-40 disabled:cursor-not-allowed transition-all shadow-[0_0_20px_rgba(59,130,246,0.2)] hover:shadow-[0_0_24px_rgba(59,130,246,0.3)]"
|
||||
>
|
||||
<Select.Trigger className="w-full flex items-center justify-between px-4 py-3 rounded-xl bg-white/5 border border-white/7 text-sm text-white hover:bg-white/7 hover:border-white/12 focus:outline-none transition-all cursor-pointer">
|
||||
<Select.Value placeholder="Select language..." />
|
||||
<Select.Icon>
|
||||
<ChevronDown size={14} className="text-zinc-500" />
|
||||
</Select.Icon>
|
||||
</Select.Trigger>
|
||||
{isFetching ? (
|
||||
<>
|
||||
<motion.span
|
||||
animate={{ rotate: 360 }}
|
||||
transition={{ duration: 1, repeat: Infinity, ease: 'linear' }}
|
||||
className="inline-block w-4 h-4 border-2 border-white/30 border-t-white rounded-full"
|
||||
/>
|
||||
Fetching links…
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Download size={15} />
|
||||
Get Download Links
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
|
||||
<Select.Portal>
|
||||
<Select.Content
|
||||
className="z-[100] w-[var(--radix-select-trigger-width)] max-h-72 overflow-auto rounded-xl border border-white/9 bg-[#131316] backdrop-blur-xl shadow-2xl text-sm"
|
||||
position="popper"
|
||||
sideOffset={6}
|
||||
>
|
||||
<Select.Viewport className="p-1">
|
||||
{languages.map(lang => (
|
||||
<Select.Item
|
||||
key={lang.Id}
|
||||
value={lang.Id}
|
||||
className="relative flex items-center justify-between px-3 py-2.5 rounded-lg text-zinc-400 data-[highlighted]:text-white data-[highlighted]:bg-white/6 cursor-pointer outline-none transition-colors"
|
||||
>
|
||||
<Select.ItemText>{lang.LocalizedLanguage}</Select.ItemText>
|
||||
<Select.ItemIndicator>
|
||||
<Check size={13} className="text-blue-400" />
|
||||
</Select.ItemIndicator>
|
||||
</Select.Item>
|
||||
))}
|
||||
</Select.Viewport>
|
||||
</Select.Content>
|
||||
</Select.Portal>
|
||||
</Select.Root>
|
||||
</div>
|
||||
{/* Error after fetch */}
|
||||
<AnimatePresence>
|
||||
{error && languages.length > 0 && (
|
||||
<motion.div
|
||||
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"
|
||||
>
|
||||
<AlertTriangle size={15} className="flex-shrink-0 mt-0.5" />
|
||||
<span>{error}</span>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
{/* CTA */}
|
||||
<button
|
||||
onClick={handleGetLinks}
|
||||
disabled={!selectedSku || isFetching}
|
||||
className="w-full flex items-center justify-center gap-2 py-3 rounded-xl bg-blue-500 text-white text-sm font-semibold hover:bg-blue-600 disabled:opacity-40 disabled:cursor-not-allowed transition-all shadow-[0_0_20px_rgba(59,130,246,0.2)] hover:shadow-[0_0_24px_rgba(59,130,246,0.3)]"
|
||||
>
|
||||
{isFetching ? (
|
||||
<>
|
||||
<motion.span
|
||||
animate={{ rotate: 360 }}
|
||||
transition={{ duration: 1, repeat: Infinity, ease: 'linear' }}
|
||||
className="inline-block w-4 h-4 border-2 border-white/30 border-t-white rounded-full"
|
||||
/>
|
||||
Fetching links…
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Download size={15} />
|
||||
Get Download Links
|
||||
</>
|
||||
)}
|
||||
</button>
|
||||
{/* Download links */}
|
||||
<AnimatePresence>
|
||||
{downloadLinks.length > 0 && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, height: 0 }}
|
||||
animate={{ opacity: 1, height: 'auto' }}
|
||||
exit={{ opacity: 0, height: 0 }}
|
||||
transition={{ type: 'spring', stiffness: 300, damping: 28 }}
|
||||
>
|
||||
<div className="pt-4 border-t border-white/6 space-y-3">
|
||||
{/* Warning */}
|
||||
<div className="flex items-center gap-2 p-3 rounded-lg bg-amber-500/6 border border-amber-500/12 text-amber-400/80 text-[11px]">
|
||||
<AlertTriangle size={12} className="flex-shrink-0" />
|
||||
Links expire in 24 hours · IP-tied · Use a download manager for full speed
|
||||
</div>
|
||||
|
||||
{/* Links */}
|
||||
{downloadLinks.map(link => {
|
||||
const filename = link.Uri.split('/').pop()?.split('?')[0] ?? 'download.iso'
|
||||
const arch = link.Architecture || archFromUri(link.Uri)
|
||||
const isCopied = copiedUri === link.Uri
|
||||
return (
|
||||
<div
|
||||
key={link.Uri}
|
||||
className="flex items-center justify-between gap-3 p-3.5 rounded-xl bg-white/3 border border-white/5 hover:border-white/9 transition-colors"
|
||||
>
|
||||
<div className="min-w-0">
|
||||
<p className="text-[12px] font-mono text-white truncate">{filename}</p>
|
||||
<p className="text-[11px] text-zinc-600 mt-0.5">{arch}</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 flex-shrink-0">
|
||||
<button
|
||||
onClick={() => handleCopy(link.Uri)}
|
||||
className="p-2 rounded-lg text-zinc-600 hover:text-white hover:bg-white/6 transition-all"
|
||||
title="Copy link"
|
||||
>
|
||||
{isCopied
|
||||
? <Check size={13} className="text-green-400" />
|
||||
: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.8} className="w-3.5 h-3.5" aria-hidden="true">
|
||||
<rect x="9" y="9" width="13" height="13" rx="2" ry="2" />
|
||||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
|
||||
</svg>
|
||||
}
|
||||
</button>
|
||||
<a
|
||||
href={link.Uri}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-blue-500/12 border border-blue-500/20 text-blue-400 text-[11px] font-medium hover:bg-blue-500/20 transition-all"
|
||||
>
|
||||
<Download size={11} />
|
||||
{arch}
|
||||
<ExternalLink size={10} className="opacity-50" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
|
||||
{(!isNotFound && !hasCatalogError) && (
|
||||
<>
|
||||
{/* System requirements */}
|
||||
<SystemRequirements isWin11={isWin11(productId!)} />
|
||||
|
||||
{/* aria2 tip */}
|
||||
{meta.active && <Aria2Tip downloadUrl={firstUri} />}
|
||||
|
||||
{/* Related releases */}
|
||||
{related.length > 0 && (
|
||||
<RelatedReleases current={productId!} items={related} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
{/* Error after fetch */}
|
||||
<AnimatePresence>
|
||||
{error && languages.length > 0 && (
|
||||
<motion.div
|
||||
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"
|
||||
>
|
||||
<AlertTriangle size={15} className="flex-shrink-0 mt-0.5" />
|
||||
<span>{error}</span>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
|
||||
{/* Download links */}
|
||||
<AnimatePresence>
|
||||
{downloadLinks.length > 0 && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, height: 0 }}
|
||||
animate={{ opacity: 1, height: 'auto' }}
|
||||
exit={{ opacity: 0, height: 0 }}
|
||||
transition={{ type: 'spring', stiffness: 300, damping: 28 }}
|
||||
>
|
||||
<div className="pt-4 border-t border-white/6 space-y-3">
|
||||
{/* Warning */}
|
||||
<div className="flex items-center gap-2 p-3 rounded-lg bg-amber-500/6 border border-amber-500/12 text-amber-400/80 text-[11px]">
|
||||
<AlertTriangle size={12} className="flex-shrink-0" />
|
||||
Links expire in 24 hours · IP-tied · Use a download manager for full speed
|
||||
</div>
|
||||
|
||||
{/* Links */}
|
||||
{downloadLinks.map(link => {
|
||||
const filename = link.Uri.split('/').pop()?.split('?')[0] ?? 'download.iso'
|
||||
const arch = link.Architecture || archFromUri(link.Uri)
|
||||
const isCopied = copiedUri === link.Uri
|
||||
return (
|
||||
<div
|
||||
key={link.Uri}
|
||||
className="flex items-center justify-between gap-3 p-3.5 rounded-xl bg-white/3 border border-white/5 hover:border-white/9 transition-colors"
|
||||
>
|
||||
<div className="min-w-0">
|
||||
<p className="text-[12px] font-mono text-white truncate">{filename}</p>
|
||||
<p className="text-[11px] text-zinc-600 mt-0.5">{arch}</p>
|
||||
</div>
|
||||
<div className="flex items-center gap-2 flex-shrink-0">
|
||||
<button
|
||||
onClick={() => handleCopy(link.Uri)}
|
||||
className="p-2 rounded-lg text-zinc-600 hover:text-white hover:bg-white/6 transition-all"
|
||||
title="Copy link"
|
||||
>
|
||||
{isCopied
|
||||
? <Check size={13} className="text-green-400" />
|
||||
: <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={1.8} className="w-3.5 h-3.5">
|
||||
<rect x="9" y="9" width="13" height="13" rx="2" ry="2" />
|
||||
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1" />
|
||||
</svg>
|
||||
}
|
||||
</button>
|
||||
<a
|
||||
href={link.Uri}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center gap-1.5 px-3 py-1.5 rounded-lg bg-blue-500/12 border border-blue-500/20 text-blue-400 text-[11px] font-medium hover:bg-blue-500/20 transition-all"
|
||||
>
|
||||
<Download size={11} />
|
||||
{arch}
|
||||
<ExternalLink size={10} className="opacity-50" />
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</motion.div>
|
||||
)}
|
||||
</AnimatePresence>
|
||||
</div>
|
||||
|
||||
{(!isNotFound && !hasCatalogError) && (
|
||||
<>
|
||||
{/* System requirements */}
|
||||
<SystemRequirements isWin11={isWin11(productId!)} />
|
||||
|
||||
{/* aria2 tip */}
|
||||
{meta.active && <Aria2Tip downloadUrl={firstUri} />}
|
||||
|
||||
{/* Related releases */}
|
||||
{related.length > 0 && (
|
||||
<RelatedReleases current={productId!} items={related} />
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</motion.div>
|
||||
</div>
|
||||
</motion.div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import { motion } from 'motion/react'
|
|||
import { Search, ArrowRight } from 'lucide-react'
|
||||
import type { Product } from '../types'
|
||||
|
||||
const SITE_URL = 'https://msdl.tech-latest.com'
|
||||
|
||||
const quickSearches = [
|
||||
{ label: 'Windows 11 25H2', query: '25H2' },
|
||||
{ label: 'Windows 11 24H2', query: '24H2' },
|
||||
|
|
@ -15,11 +17,8 @@ export default function ProductsPage() {
|
|||
const [products, setProducts] = useState<Product[]>([])
|
||||
const [isLoading, setIsLoading] = useState(true)
|
||||
const [query, setQuery] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
document.title = "All Windows Releases | Windows ISO Downloader"
|
||||
const metaDesc = document.querySelector('meta[name="description"]')
|
||||
if (metaDesc) metaDesc.setAttribute('content', "Browse all official Microsoft Windows ISO releases available for direct download.")
|
||||
|
||||
fetch('/data/products.json')
|
||||
.then(r => r.json())
|
||||
.then((data: Record<string, { name: string; active?: boolean }>) => {
|
||||
|
|
@ -36,93 +35,102 @@ export default function ProductsPage() {
|
|||
)
|
||||
|
||||
return (
|
||||
<div className="max-w-3xl mx-auto px-4 pt-12 pb-10">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 16 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.4 }}
|
||||
>
|
||||
<h1 className="text-3xl font-bold text-white mb-1">All Products</h1>
|
||||
<p className="text-white/40 text-sm mb-6">
|
||||
{products.length} versions available
|
||||
</p>
|
||||
<>
|
||||
<title>All Windows Releases | Windows ISO Downloader</title>
|
||||
<meta name="description" content="Browse all official Microsoft Windows ISO releases available for direct download. Windows 11, 10, and 8.1." />
|
||||
<link rel="canonical" href={`${SITE_URL}/products`} />
|
||||
<meta property="og:title" content="All Windows ISO Releases | Windows ISO Downloader" />
|
||||
<meta property="og:description" content="Browse all official Microsoft Windows ISO releases available for direct download." />
|
||||
<meta property="og:url" content={`${SITE_URL}/products`} />
|
||||
|
||||
{/* Search */}
|
||||
<div className="relative mb-4">
|
||||
<Search size={16} className="absolute left-3.5 top-1/2 -translate-y-1/2 text-white/30" />
|
||||
<input
|
||||
type="text"
|
||||
value={query}
|
||||
onChange={e => setQuery(e.target.value)}
|
||||
placeholder="Search Windows versions..."
|
||||
className="w-full bg-white/5 border border-white/8 rounded-xl pl-10 pr-4 py-3 text-sm text-white placeholder:text-white/25 focus:outline-none focus:border-white/20 focus:bg-white/8 transition-all"
|
||||
/>
|
||||
</div>
|
||||
<div className="max-w-3xl mx-auto px-4 pt-12 pb-10">
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 16 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.4 }}
|
||||
>
|
||||
<h1 className="text-3xl font-bold text-white mb-1">All Windows ISO Releases</h1>
|
||||
<p className="text-white/40 text-sm mb-6">
|
||||
{products.length} versions available
|
||||
</p>
|
||||
|
||||
{/* Quick filters */}
|
||||
<div className="flex flex-wrap gap-2 mb-6">
|
||||
{quickSearches.map(s => (
|
||||
<button
|
||||
key={s.label}
|
||||
onClick={() => setQuery(s.query)}
|
||||
className="px-3 py-1.5 rounded-lg text-xs font-medium border border-white/8 bg-white/4 text-white/50 hover:text-white hover:border-white/15 hover:bg-white/8 transition-all"
|
||||
>
|
||||
{s.label}
|
||||
</button>
|
||||
))}
|
||||
{query && (
|
||||
<button
|
||||
onClick={() => setQuery('')}
|
||||
className="px-3 py-1.5 rounded-lg text-xs font-medium border border-white/8 bg-white/4 text-white/40 hover:text-white transition-all"
|
||||
>
|
||||
Clear ×
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* List */}
|
||||
{isLoading ? (
|
||||
<div className="space-y-2">
|
||||
{[...Array(8)].map((_, i) => (
|
||||
<div key={i} className="h-14 rounded-xl bg-white/4 animate-pulse" />
|
||||
))}
|
||||
{/* Search */}
|
||||
<div className="relative mb-4">
|
||||
<Search size={16} className="absolute left-3.5 top-1/2 -translate-y-1/2 text-white/30" />
|
||||
<input
|
||||
type="text"
|
||||
value={query}
|
||||
onChange={e => setQuery(e.target.value)}
|
||||
placeholder="Search Windows versions..."
|
||||
className="w-full bg-white/5 border border-white/8 rounded-xl pl-10 pr-4 py-3 text-sm text-white placeholder:text-white/25 focus:outline-none focus:border-white/20 focus:bg-white/8 transition-all"
|
||||
/>
|
||||
</div>
|
||||
) : filtered.length === 0 ? (
|
||||
<p className="text-white/30 text-sm text-center py-12">No products found.</p>
|
||||
) : (
|
||||
<motion.div
|
||||
className="space-y-1.5"
|
||||
initial="hidden"
|
||||
animate="show"
|
||||
variants={{ show: { transition: { staggerChildren: 0.03 } } }}
|
||||
>
|
||||
{filtered.map(product => (
|
||||
<motion.div
|
||||
key={product.id}
|
||||
variants={{
|
||||
hidden: { opacity: 0, x: -8 },
|
||||
show: { opacity: 1, x: 0, transition: { type: 'spring', stiffness: 300, damping: 28 } },
|
||||
}}
|
||||
|
||||
{/* Quick filters */}
|
||||
<div className="flex flex-wrap gap-2 mb-6">
|
||||
{quickSearches.map(s => (
|
||||
<button
|
||||
key={s.label}
|
||||
onClick={() => setQuery(s.query)}
|
||||
className="px-3 py-1.5 rounded-lg text-xs font-medium border border-white/8 bg-white/4 text-white/50 hover:text-white hover:border-white/15 hover:bg-white/8 transition-all"
|
||||
>
|
||||
<Link
|
||||
to={`/product/${product.id}`}
|
||||
className="group w-full flex items-center justify-between px-4 py-3.5 rounded-xl border border-white/6 bg-white/3 hover:bg-white/6 hover:border-white/12 transition-all text-left"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<svg viewBox="0 0 88 88" className="w-5 h-5 text-white/30 flex-shrink-0 group-hover:text-white/50 transition-colors" fill="currentColor">
|
||||
<path d="M0 12.402l35.687-4.86.016 34.423-35.67.203zm35.67 33.529l.028 34.453L.028 75.48.026 45.7zm4.326-39.025L87.314 0v41.527l-47.318.376zm47.329 39.349-.011 41.34-47.318-6.678-.066-34.739z" />
|
||||
</svg>
|
||||
<span className="text-sm text-white/70 group-hover:text-white transition-colors font-medium">
|
||||
{product.name}
|
||||
</span>
|
||||
</div>
|
||||
<ArrowRight size={14} className="text-white/20 group-hover:text-white/50 group-hover:translate-x-0.5 transition-all" />
|
||||
</Link>
|
||||
</motion.div>
|
||||
{s.label}
|
||||
</button>
|
||||
))}
|
||||
</motion.div>
|
||||
)}
|
||||
</motion.div>
|
||||
</div>
|
||||
{query && (
|
||||
<button
|
||||
onClick={() => setQuery('')}
|
||||
className="px-3 py-1.5 rounded-lg text-xs font-medium border border-white/8 bg-white/4 text-white/40 hover:text-white transition-all"
|
||||
>
|
||||
Clear ×
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* List */}
|
||||
{isLoading ? (
|
||||
<div className="space-y-2">
|
||||
{[...Array(8)].map((_, i) => (
|
||||
<div key={i} className="h-14 rounded-xl bg-white/4 animate-pulse" />
|
||||
))}
|
||||
</div>
|
||||
) : filtered.length === 0 ? (
|
||||
<p className="text-white/30 text-sm text-center py-12">No products found.</p>
|
||||
) : (
|
||||
<motion.div
|
||||
className="space-y-1.5"
|
||||
initial="hidden"
|
||||
animate="show"
|
||||
variants={{ show: { transition: { staggerChildren: 0.03 } } }}
|
||||
>
|
||||
{filtered.map(product => (
|
||||
<motion.div
|
||||
key={product.id}
|
||||
variants={{
|
||||
hidden: { opacity: 0, x: -8 },
|
||||
show: { opacity: 1, x: 0, transition: { type: 'spring', stiffness: 300, damping: 28 } },
|
||||
}}
|
||||
>
|
||||
<Link
|
||||
to={`/product/${product.id}`}
|
||||
className="group w-full flex items-center justify-between px-4 py-3.5 rounded-xl border border-white/6 bg-white/3 hover:bg-white/6 hover:border-white/12 transition-all text-left"
|
||||
>
|
||||
<div className="flex items-center gap-3">
|
||||
<svg viewBox="0 0 88 88" className="w-5 h-5 text-white/30 flex-shrink-0 group-hover:text-white/50 transition-colors" fill="currentColor" aria-hidden="true">
|
||||
<path d="M0 12.402l35.687-4.86.016 34.423-35.67.203zm35.67 33.529l.028 34.453L.028 75.48.026 45.7zm4.326-39.025L87.314 0v41.527l-47.318.376zm47.329 39.349-.011 41.34-47.318-6.678-.066-34.739z" />
|
||||
</svg>
|
||||
<span className="text-sm text-white/70 group-hover:text-white transition-colors font-medium">
|
||||
{product.name}
|
||||
</span>
|
||||
</div>
|
||||
<ArrowRight size={14} className="text-white/20 group-hover:text-white/50 group-hover:translate-x-0.5 transition-all" />
|
||||
</Link>
|
||||
</motion.div>
|
||||
))}
|
||||
</motion.div>
|
||||
)}
|
||||
</motion.div>
|
||||
</div>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue