import { useState, useEffect } from 'react';
import {
Box,
Container,
Typography,
Button,
Grid,
Card,
CardContent,
Chip,
Stack,
CircularProgress,
} from '@mui/material';
import DownloadIcon from '@mui/icons-material/Download';
import AppleIcon from '@mui/icons-material/Apple';
import CheckCircleIcon from '@mui/icons-material/CheckCircle';
import OpenInNewIcon from '@mui/icons-material/OpenInNew';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import ExpandLessIcon from '@mui/icons-material/ExpandLess';
import Layout from '../components/Layout';
// Platform configuration
const PLATFORMS = {
'darwin-aarch64': {
name: 'macOS',
subtitle: 'Apple Silicon',
arch: 'M1, M2, M3, M4',
format: '.dmg',
icon: AppleIcon,
color: '#a3a3a3',
},
'darwin-x86_64': {
name: 'macOS',
subtitle: 'Intel',
arch: 'x86_64',
format: '.dmg',
icon: AppleIcon,
color: '#a3a3a3',
},
'windows-x86_64': {
name: 'Windows',
subtitle: '64-bit',
arch: 'x86_64',
format: '.msi',
icon: null,
color: '#0078d4',
},
'linux-x86_64': {
name: 'Linux',
subtitle: 'Debian/Ubuntu',
arch: 'x86_64',
format: '.deb',
icon: null,
color: '#e95420',
},
};
// URL to fetch latest release info (using GitHub API for CORS support)
const GITHUB_RELEASES_API = 'https://api.github.com/repos/pollen-robotics/reachy-mini-desktop-app/releases/latest';
const GITHUB_RELEASES_LIST_API = 'https://api.github.com/repos/pollen-robotics/reachy-mini-desktop-app/releases?per_page=10';
// Detect user's platform
function detectPlatform() {
const ua = navigator.userAgent;
const platform = navigator.platform || '';
if (/Mac/.test(platform) || /Mac/.test(ua)) {
return 'darwin-aarch64';
}
if (/Win/.test(platform) || /Windows/.test(ua)) {
return 'windows-x86_64';
}
if (/Linux/.test(platform) || /Linux/.test(ua)) {
return 'linux-x86_64';
}
return 'darwin-aarch64';
}
// Format date
function formatDate(dateString) {
const date = new Date(dateString);
return date.toLocaleDateString('en-US', {
year: 'numeric',
month: 'short',
day: 'numeric',
});
}
// Parse release body and extract clean changes
function parseReleaseChanges(body) {
if (!body) return [];
const changes = [];
const lines = body.split('\n');
for (const line of lines) {
const trimmed = line.trim();
// Skip empty lines, headers, and meta content
if (!trimmed) continue;
if (trimmed.startsWith('##')) continue; // Skip all headers (## What's Changed, etc.)
if (trimmed.startsWith('**Full Changelog**')) continue;
if (trimmed.startsWith('**New Contributors**')) continue;
if (trimmed.includes('made their first contribution')) continue;
if (trimmed.startsWith('')) continue;
if (trimmed === 'See the assets to download this version and install.') continue;
// Parse change lines (starting with * or -)
if (trimmed.startsWith('*') || trimmed.startsWith('-')) {
let change = trimmed.replace(/^[\*\-]\s*/, '');
// Extract the description from markdown links: "fix: description by @user in https://..."
// We want to keep: "fix: description"
const byMatch = change.match(/^(.+?)\s+by\s+@\w+/i);
if (byMatch) {
change = byMatch[1].trim();
}
// Remove trailing "in https://..." links
change = change.replace(/\s+in\s+https:\/\/[^\s]+$/i, '');
// Clean up any remaining markdown link syntax [text](url) -> text
change = change.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1');
// Skip if it's just a contributor line or empty after cleaning
if (change && change.length > 3 && !change.includes('first contribution')) {
changes.push(change);
}
}
}
return changes;
}
// Windows Icon
function WindowsIcon({ sx = {} }) {
return (
);
}
// Linux Icon
function LinuxIcon({ sx = {} }) {
return (
);
}
// Platform Card component
function PlatformCard({ platformKey, url, isActive, onClick }) {
const platform = PLATFORMS[platformKey];
const Icon = platform?.icon;
const isComingSoon = platformKey.includes('windows') || platformKey.includes('linux');
return (
{/* Coming soon tag */}
{isComingSoon && (
)}
{Icon ? (
) : platformKey.includes('windows') ? (
) : (
)}
{platform?.name}
{platform?.subtitle}
);
}
export default function Download() {
const [releaseData, setReleaseData] = useState(null);
const [allReleases, setAllReleases] = useState([]);
const [detectedPlatform, setDetectedPlatform] = useState(null);
const [loading, setLoading] = useState(true);
const [showAllReleases, setShowAllReleases] = useState(false);
const [error, setError] = useState(null);
useEffect(() => {
setDetectedPlatform(detectPlatform());
// Fetch latest release info from GitHub API
async function fetchReleases() {
try {
// Fetch latest release for download buttons
const latestResponse = await fetch(GITHUB_RELEASES_API);
// Fetch all releases for changelog
const allResponse = await fetch(GITHUB_RELEASES_LIST_API);
if (latestResponse.ok) {
const data = await latestResponse.json();
// Transform GitHub API response to our format
const version = data.tag_name?.replace('v', '') || '';
const platforms = {};
// Map assets to platforms (prioritize user-friendly formats)
data.assets?.forEach(asset => {
const name = asset.name.toLowerCase();
const url = asset.browser_download_url;
// macOS Apple Silicon - prefer .dmg
if (name.includes('arm64.dmg')) {
platforms['darwin-aarch64'] = { url };
} else if (name.includes('darwin-aarch64') && !platforms['darwin-aarch64']) {
platforms['darwin-aarch64'] = { url };
}
// macOS Intel - prefer .dmg
if (name.includes('x64.dmg') && !name.includes('arm64')) {
platforms['darwin-x86_64'] = { url };
} else if (name.includes('darwin-x86_64') && !platforms['darwin-x86_64']) {
platforms['darwin-x86_64'] = { url };
}
// Windows - .msi
if (name.includes('.msi')) {
platforms['windows-x86_64'] = { url };
}
// Linux - .deb
if (name.includes('amd64.deb')) {
platforms['linux-x86_64'] = { url };
}
});
setReleaseData({
version,
pub_date: data.published_at,
platforms,
});
} else {
setError('Failed to fetch release info');
}
// Set all releases for changelog
if (allResponse.ok) {
const releases = await allResponse.json();
setAllReleases(releases.filter(r => !r.draft));
}
} catch (err) {
console.error('Error fetching release:', err);
setError('Failed to fetch release info');
} finally {
setLoading(false);
}
}
fetchReleases();
}, []);
if (loading) {
return (
);
}
if (error || !releaseData) {
return (
Unable to load release info
);
}
const currentPlatform = PLATFORMS[detectedPlatform];
const currentUrl = releaseData?.platforms[detectedPlatform]?.url;
return (
{/* Subtle gradient orbs - spread across the page */}
{/* Hero Section */}
{/* App icon */}
Reachy Mini Control
The official desktop app to control, program, and play with your Reachy Mini.
{/* Version info */}
}
label={`v${releaseData?.version}`}
sx={{
backgroundColor: 'rgba(16, 185, 129, 0.1)',
color: '#10b981',
fontWeight: 600,
border: '1px solid rgba(16, 185, 129, 0.2)',
}}
/>
Released {formatDate(releaseData?.pub_date)}
{/* Primary download button - different for macOS vs Windows/Linux */}
{detectedPlatform?.startsWith('darwin') ? (
<>
}
sx={{
px: 6,
py: 2,
fontSize: 17,
fontWeight: 600,
borderRadius: 3,
background: 'linear-gradient(135deg, #FF9500 0%, #764ba2 100%)',
boxShadow: '0 8px 32px rgba(255, 149, 0, 0.35)',
transition: 'all 0.3s ease',
'&:hover': {
boxShadow: '0 12px 48px rgba(59, 130, 246, 0.5)',
transform: 'translateY(-2px)',
},
}}
>
Download for {currentPlatform?.name}
{currentPlatform?.subtitle} • {currentPlatform?.format?.replace('.', '').toUpperCase()} package
>
) : (
<>
{/* Windows/Linux - Coming soon message */}
🚧 {currentPlatform?.name} support coming soon!
We're working hard to bring Reachy Mini Control to {currentPlatform?.name}.
In the meantime, macOS is fully supported!
>
)}
{/* App screenshot */}
{/* All platforms */}
Available for all platforms
{['darwin-aarch64', 'darwin-x86_64', 'windows-x86_64', 'linux-x86_64'].map((key) => (
setDetectedPlatform(key)}
/>
))}
{/* Platform support notice - only show on macOS */}
{detectedPlatform?.startsWith('darwin') && (
🚧 Windows & Linux support coming soon
)}
{/* Features / What's included */}
What's included
{[
'3D visualization of your robot',
'Real-time motor control',
'App Store with 30+ apps',
'Camera & microphone access',
'Record & playback movements',
'Full SDK integration',
].map((feature, i) => (
{feature}
))}
{/* Requirements */}
Requires macOS 11+, Windows 10+, or Debian/Ubuntu Linux
}
sx={{
color: 'rgba(255,255,255,0.5)',
'&:hover': { color: 'white' },
}}
>
View all releases on GitHub
{/* Release Notes */}
{allReleases.length > 0 && (
Release Notes
{(showAllReleases ? allReleases : allReleases.slice(0, 5))
.map((release) => {
const changes = parseReleaseChanges(release.body);
return (
0 ? 1 : 0 }}>
{release.tag_name}
{release.prerelease && (
)}
{changes.length > 0 && (
{changes.map((change, i) => (
{change}
))}
)}
);
})}
{allReleases.length > 5 && (
)}
)}
);
}