feat(site): implement dynamic client OS negotiation for Hero and CTA download buttons
Some checks failed
deploy-site-windows / deploy-win (push) Waiting to run
deploy-site / deploy (push) Failing after 24s

This commit is contained in:
Yun Chan 2026-08-20 22:57:38 +09:00
parent 4865387d7f
commit 55d03baf6b
5 changed files with 280 additions and 16 deletions

View file

@ -0,0 +1,105 @@
import { useState, useEffect } from 'react'
export type ClientOSType = 'android' | 'ios' | 'windows' | 'macos' | 'linux'
export interface ClientOSInfo {
os: ClientOSType
osName: string
isMobile: boolean
downloadUrl: string
downloadFilename: string
badgeText: string
buttonLabelKo: string
buttonLabelEn: string
subtextKo: string
subtextEn: string
}
export const OS_CONFIGS: Record<ClientOSType, ClientOSInfo> = {
android: {
os: 'android',
osName: 'Android',
isMobile: true,
downloadUrl: 'https://d3ro.chanpaca.net/releases/1.0.0/d3ro-voice-v1.0.0-signed.apk',
downloadFilename: 'd3ro-voice-v1.0.0-signed.apk',
badgeText: 'ANDROID APK v1.0.0',
buttonLabelKo: 'Android용 무료 다운로드 (APK)',
buttonLabelEn: 'Download for Android (APK)',
subtextKo: 'v1.0.0 · 공식 서명 APK (47.5MB) · Google Play 호환',
subtextEn: 'v1.0.0 · Official Signed APK (47.5MB) · Play Protected',
},
ios: {
os: 'ios',
osName: 'iOS',
isMobile: true,
downloadUrl: '#download',
downloadFilename: '',
badgeText: 'iOS TESTFLIGHT / PWA',
buttonLabelKo: 'iOS 앱 / TestFlight 시작',
buttonLabelEn: 'Download for iOS (TestFlight)',
subtextKo: 'v1.0.0 · iPhone & iPad 지원 · PWA 즉시 실행',
subtextEn: 'v1.0.0 · iPhone & iPad Supported · Instant PWA',
},
windows: {
os: 'windows',
osName: 'Windows',
isMobile: false,
downloadUrl: 'https://d3ro.chanpaca.net/releases/1.0.0/D3RO-Voice-Setup-1.0.0-x64.exe',
downloadFilename: 'D3RO-Voice-Setup-1.0.0-x64.exe',
badgeText: 'WINDOWS x64 v1.0.0',
buttonLabelKo: 'Windows용 무료 다운로드',
buttonLabelEn: 'Download for Windows (x64)',
subtextKo: 'v1.0.0 · x64 공식 인스톨러 (102MB) · 온디바이스',
subtextEn: 'v1.0.0 · x64 Official Setup (102MB) · On-Device',
},
macos: {
os: 'macos',
osName: 'macOS',
isMobile: false,
downloadUrl: '#download',
downloadFilename: '',
badgeText: 'MACOS UNIVERSAL',
buttonLabelKo: 'macOS용 무료 다운로드',
buttonLabelEn: 'Download for macOS (Universal)',
subtextKo: 'v1.0.0 · Apple Silicon (M1/M2/M3/M4) & Intel 지원',
subtextEn: 'v1.0.0 · Apple Silicon & Intel Universal',
},
linux: {
os: 'linux',
osName: 'Linux',
isMobile: false,
downloadUrl: '#download',
downloadFilename: '',
badgeText: 'LINUX APPIMAGE / DEB',
buttonLabelKo: 'Linux용 무료 다운로드',
buttonLabelEn: 'Download for Linux (AppImage)',
subtextKo: 'v1.0.0 · Ubuntu / Debian / Arch 지원',
subtextEn: 'v1.0.0 · Ubuntu / Debian / Arch Supported',
},
}
export function useClientOS(): ClientOSInfo {
const [osInfo, setOsInfo] = useState<ClientOSInfo>(OS_CONFIGS.windows)
useEffect(() => {
if (typeof window === 'undefined' || !navigator) return
const ua = navigator.userAgent || ''
const platform = (navigator as any).userAgentData?.platform || navigator.platform || ''
if (/android/i.test(ua)) {
setOsInfo(OS_CONFIGS.android)
} else if (/iphone|ipad|ipod/i.test(ua) || (platform === 'MacIntel' && navigator.maxTouchPoints > 1)) {
setOsInfo(OS_CONFIGS.ios)
} else if (/macintosh|mac os x/i.test(ua) || /mac/i.test(platform)) {
setOsInfo(OS_CONFIGS.macos)
} else if (/linux/i.test(ua) || /linux/i.test(platform)) {
setOsInfo(OS_CONFIGS.linux)
} else if (/windows|win32|win64/i.test(ua) || /win/i.test(platform)) {
setOsInfo(OS_CONFIGS.windows)
} else {
setOsInfo(OS_CONFIGS.windows)
}
}, [])
return osInfo
}