mirror of
https://github.com/stablyai/orca.git
synced 2026-09-22 00:02:31 +00:00
fix: address review findings (#6602)
Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
@@ -680,6 +680,42 @@
|
||||
border-bottom-color: var(--foreground);
|
||||
}
|
||||
|
||||
/* iOS release-channel switch (Preview vs Stable), shown above the install CTA */
|
||||
.mobile-page-root .mp-channel-toggle {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
margin: -8px 0 18px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.mobile-page-root .mp-channel-toggle button {
|
||||
border: 1px solid var(--border);
|
||||
background: transparent;
|
||||
padding: 4px 12px;
|
||||
border-radius: 999px;
|
||||
color: var(--muted-foreground);
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
transition:
|
||||
background-color 140ms ease,
|
||||
color 140ms ease,
|
||||
border-color 140ms ease;
|
||||
}
|
||||
|
||||
.mobile-page-root .mp-channel-toggle button.is-active {
|
||||
background: color-mix(in srgb, var(--foreground) 8%, transparent);
|
||||
border-color: color-mix(in srgb, var(--foreground) 20%, transparent);
|
||||
color: var(--foreground);
|
||||
}
|
||||
|
||||
.mobile-page-root .mp-channel-tagline {
|
||||
margin-left: 6px;
|
||||
color: var(--muted-foreground);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.mobile-page-root .mp-inline-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -3,6 +3,7 @@ import { cn } from '../../lib/utils'
|
||||
import type { MobileNetworkInterface } from '../settings/mobile-network-interface-selection'
|
||||
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from '../ui/select'
|
||||
import { AndroidLogo, IosBrandIcon } from './MobileBrandIcons'
|
||||
import { getChannelTagline, type InstallCopy, type IosChannel } from './mobile-platform-copy'
|
||||
export { HeroIntro } from './MobileHeroIntro'
|
||||
export { HeroPaired, type PairedDevice } from './MobileHeroPairedDevices'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
@@ -27,7 +28,9 @@ type HeroFlowProps = {
|
||||
platform: Platform
|
||||
onPlatformChange: (next: Platform) => void
|
||||
installQrUrl: string | null
|
||||
installCopy: { description: string; ctaLabel: string; url: string }
|
||||
installCopy: InstallCopy
|
||||
iosChannel: IosChannel
|
||||
onIosChannelChange: (next: IosChannel) => void
|
||||
onOpenInstallUrl: () => void
|
||||
onCopyInstallUrl: () => void
|
||||
pairQrDataUrl: string | null
|
||||
@@ -51,6 +54,8 @@ export function HeroFlow({
|
||||
onPlatformChange,
|
||||
installQrUrl,
|
||||
installCopy,
|
||||
iosChannel,
|
||||
onIosChannelChange,
|
||||
onOpenInstallUrl,
|
||||
onCopyInstallUrl,
|
||||
pairQrDataUrl,
|
||||
@@ -110,6 +115,36 @@ export function HeroFlow({
|
||||
{translate('auto.components.mobile.MobileHero.ac1eb64952', 'Android')}
|
||||
</button>
|
||||
</div>
|
||||
{platform === 'ios' ? (
|
||||
<div
|
||||
className="mp-channel-toggle"
|
||||
role="radiogroup"
|
||||
aria-label={translate(
|
||||
'auto.components.mobile.MobileHero.channel.group',
|
||||
'Release channel'
|
||||
)}
|
||||
>
|
||||
<button
|
||||
type="button"
|
||||
role="radio"
|
||||
aria-checked={iosChannel === 'preview'}
|
||||
className={cn(iosChannel === 'preview' && 'is-active')}
|
||||
onClick={() => onIosChannelChange('preview')}
|
||||
>
|
||||
{translate('auto.components.mobile.MobileHero.channel.preview', 'Preview')}
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
role="radio"
|
||||
aria-checked={iosChannel === 'stable'}
|
||||
className={cn(iosChannel === 'stable' && 'is-active')}
|
||||
onClick={() => onIosChannelChange('stable')}
|
||||
>
|
||||
{translate('auto.components.mobile.MobileHero.channel.stable', 'Stable')}
|
||||
</button>
|
||||
<span className="mp-channel-tagline">{getChannelTagline(iosChannel)}</span>
|
||||
</div>
|
||||
) : null}
|
||||
<div className="mp-inline-actions">
|
||||
<button type="button" className="mp-ghost-action" onClick={onOpenInstallUrl}>
|
||||
{installCopy.ctaLabel}
|
||||
|
||||
@@ -3,7 +3,7 @@ import { toast } from 'sonner'
|
||||
import { useMountedRef } from '@/hooks/useMountedRef'
|
||||
import { useAppStore } from '@/store'
|
||||
import { type PairedDevice, type Platform, type StepIndex } from './MobileHero'
|
||||
import { PLATFORM_COPY } from './mobile-platform-copy'
|
||||
import { getInstallCopy, type IosChannel } from './mobile-platform-copy'
|
||||
import {
|
||||
selectRefreshedNetworkAddress,
|
||||
type MobileNetworkInterface
|
||||
@@ -19,12 +19,13 @@ import { MobilePageContent } from './MobilePageContent'
|
||||
import { useMobileInstallQr } from './use-mobile-install-qr'
|
||||
|
||||
export default function MobilePage(): React.JSX.Element {
|
||||
// Why: stage starts unresolved so we don't flash the intro before we know
|
||||
// whether any devices are already paired.
|
||||
const [stage, setStage] = useState<FlowStage | null>(null)
|
||||
const [stepIdx, setStepIdx] = useState<StepIndex>(0)
|
||||
|
||||
const [platform, setPlatform] = useState<Platform>('ios')
|
||||
// Default iOS users to the preview track — it ships daily, so newcomers land
|
||||
// on the freshest build unless they deliberately pick the public release.
|
||||
const [iosChannel, setIosChannel] = useState<IosChannel>('preview')
|
||||
|
||||
const [pairQrDataUrl, setPairQrDataUrl] = useState<string | null>(null)
|
||||
const [pairingUrl, setPairingUrl] = useState<string | null>(null)
|
||||
@@ -42,7 +43,7 @@ export default function MobilePage(): React.JSX.Element {
|
||||
const closeMobilePage = useAppStore((s) => s.closeMobilePage)
|
||||
const showMobileButton = useAppStore((s) => s.settings?.showMobileButton !== false)
|
||||
const updateSettings = useAppStore((s) => s.updateSettings)
|
||||
const installQrUrl = useMobileInstallQr(stage, platform)
|
||||
const installQrUrl = useMobileInstallQr(stage, platform, iosChannel)
|
||||
|
||||
const setPairingDeviceBaseline = useCallback(
|
||||
(count: number | null): void => {
|
||||
@@ -332,12 +333,12 @@ export default function MobilePage(): React.JSX.Element {
|
||||
}
|
||||
|
||||
const openInstallUrl = (): void => {
|
||||
void window.api.shell.openUrl(PLATFORM_COPY[platform].url)
|
||||
void window.api.shell.openUrl(getInstallCopy(platform, iosChannel).url)
|
||||
}
|
||||
|
||||
const copyInstallUrl = async (): Promise<void> => {
|
||||
try {
|
||||
await window.api.ui.writeClipboardText(PLATFORM_COPY[platform].url)
|
||||
await window.api.ui.writeClipboardText(getInstallCopy(platform, iosChannel).url)
|
||||
if (mountedRef.current) {
|
||||
toast.success(
|
||||
translate('auto.components.mobile.MobilePage.fad833de8d', 'Install link copied')
|
||||
@@ -380,6 +381,8 @@ export default function MobilePage(): React.JSX.Element {
|
||||
handleBack={handleBack}
|
||||
handleContinue={handleContinue}
|
||||
installQrUrl={installQrUrl}
|
||||
iosChannel={iosChannel}
|
||||
setIosChannel={setIosChannel}
|
||||
loadNetworkInterfaces={() => void loadNetworkInterfaces()}
|
||||
networkInterfaces={networkInterfaces}
|
||||
openInstallUrl={openInstallUrl}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { translate } from '@/i18n/i18n'
|
||||
import type { MobileNetworkInterface } from '../settings/mobile-network-interface-selection'
|
||||
import { HeroFlow, HeroIntro, HeroPaired, type PairedDevice, type Platform } from './MobileHero'
|
||||
import type { StepIndex } from './MobileHero'
|
||||
import { PLATFORM_COPY } from './mobile-platform-copy'
|
||||
import { getInstallCopy, type IosChannel } from './mobile-platform-copy'
|
||||
import type { MobilePageStage } from './mobile-page-stage'
|
||||
import { MobilePageToolbar } from './MobilePageToolbar'
|
||||
import { PhoneCarousel } from './PhoneCarousel'
|
||||
@@ -18,6 +18,8 @@ type MobilePageContentProps = {
|
||||
handleBack: () => void
|
||||
handleContinue: () => void
|
||||
installQrUrl: string | null
|
||||
iosChannel: IosChannel
|
||||
setIosChannel: (channel: IosChannel) => void
|
||||
loadNetworkInterfaces: () => void
|
||||
networkInterfaces: MobileNetworkInterface[]
|
||||
openInstallUrl: () => void
|
||||
@@ -49,6 +51,8 @@ export function MobilePageContent({
|
||||
handleBack,
|
||||
handleContinue,
|
||||
installQrUrl,
|
||||
iosChannel,
|
||||
setIosChannel,
|
||||
loadNetworkInterfaces,
|
||||
networkInterfaces,
|
||||
openInstallUrl,
|
||||
@@ -92,7 +96,9 @@ export function MobilePageContent({
|
||||
platform={platform}
|
||||
onPlatformChange={setPlatform}
|
||||
installQrUrl={installQrUrl}
|
||||
installCopy={PLATFORM_COPY[platform]}
|
||||
installCopy={getInstallCopy(platform, iosChannel)}
|
||||
iosChannel={iosChannel}
|
||||
onIosChannelChange={setIosChannel}
|
||||
onOpenInstallUrl={openInstallUrl}
|
||||
onCopyInstallUrl={copyInstallUrl}
|
||||
pairQrDataUrl={pairQrDataUrl}
|
||||
|
||||
@@ -1,28 +1,40 @@
|
||||
import type { Platform } from './MobileHero'
|
||||
import { translate } from '@/i18n/i18n'
|
||||
|
||||
export const PLATFORM_COPY: Record<
|
||||
Platform,
|
||||
{ description: string; ctaLabel: string; url: string }
|
||||
> = {
|
||||
ios: {
|
||||
get description() {
|
||||
return translate(
|
||||
'auto.components.mobile.mobile.platform.copy.432db52b73',
|
||||
'Scan with your iPhone camera to open the App Store.'
|
||||
)
|
||||
},
|
||||
// iOS ships two App Store tracks: the public App Store build (slower, ~weekly)
|
||||
// and the TestFlight preview build (daily). Android only ships one APK track.
|
||||
export type IosChannel = 'stable' | 'preview'
|
||||
|
||||
export type InstallCopy = { ctaLabel: string; url: string }
|
||||
|
||||
const IOS_CHANNEL_COPY: Record<IosChannel, InstallCopy> = {
|
||||
stable: {
|
||||
ctaLabel: 'Open App Store',
|
||||
url: 'https://apps.apple.com/app/orca-ide/id6766130217'
|
||||
},
|
||||
android: {
|
||||
get description() {
|
||||
return translate(
|
||||
'auto.components.mobile.mobile.platform.copy.2a532d6fd7',
|
||||
'Scan with your Android camera to download the latest APK from GitHub Releases.'
|
||||
)
|
||||
},
|
||||
ctaLabel: 'Download APK',
|
||||
url: 'https://github.com/stablyai/orca/releases/download/mobile-android-v0.0.16/app-release.apk'
|
||||
preview: {
|
||||
ctaLabel: 'Open TestFlight',
|
||||
url: 'https://testflight.apple.com/join/YjeGMQBA'
|
||||
}
|
||||
}
|
||||
|
||||
const ANDROID_COPY: InstallCopy = {
|
||||
ctaLabel: 'Download APK',
|
||||
url: 'https://github.com/stablyai/orca/releases/download/mobile-android-v0.0.16/app-release.apk'
|
||||
}
|
||||
|
||||
export function getInstallCopy(platform: Platform, iosChannel: IosChannel): InstallCopy {
|
||||
return platform === 'ios' ? IOS_CHANNEL_COPY[iosChannel] : ANDROID_COPY
|
||||
}
|
||||
|
||||
export function getChannelTagline(iosChannel: IosChannel): string {
|
||||
return iosChannel === 'preview'
|
||||
? translate(
|
||||
'auto.components.mobile.mobile.platform.copy.preview.tagline',
|
||||
'Newest features, updated daily.'
|
||||
)
|
||||
: translate(
|
||||
'auto.components.mobile.mobile.platform.copy.stable.tagline',
|
||||
'The public release, updated weekly.'
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import QRCodeBrowser from 'qrcode/lib/browser'
|
||||
import type { Platform } from './MobileHero'
|
||||
import { PLATFORM_COPY } from './mobile-platform-copy'
|
||||
import { getInstallCopy, type IosChannel } from './mobile-platform-copy'
|
||||
import type { MobilePageStage } from './mobile-page-stage'
|
||||
|
||||
async function renderQrDataUrl(text: string): Promise<string> {
|
||||
@@ -14,7 +14,8 @@ async function renderQrDataUrl(text: string): Promise<string> {
|
||||
|
||||
export function useMobileInstallQr(
|
||||
stage: MobilePageStage | null,
|
||||
platform: Platform
|
||||
platform: Platform,
|
||||
iosChannel: IosChannel
|
||||
): string | null {
|
||||
const [installQrUrl, setInstallQrUrl] = useState<string | null>(null)
|
||||
|
||||
@@ -28,7 +29,7 @@ export function useMobileInstallQr(
|
||||
let cancelled = false
|
||||
void (async () => {
|
||||
try {
|
||||
const dataUrl = await renderQrDataUrl(PLATFORM_COPY[platform].url)
|
||||
const dataUrl = await renderQrDataUrl(getInstallCopy(platform, iosChannel).url)
|
||||
if (!cancelled) {
|
||||
setInstallQrUrl(dataUrl)
|
||||
}
|
||||
@@ -41,7 +42,7 @@ export function useMobileInstallQr(
|
||||
return () => {
|
||||
cancelled = true
|
||||
}
|
||||
}, [platform, stage])
|
||||
}, [platform, iosChannel, stage])
|
||||
|
||||
return installQrUrl
|
||||
}
|
||||
|
||||
@@ -9739,7 +9739,12 @@
|
||||
"a6cffbbb0b": "Generate code",
|
||||
"e59a252eca": "Regenerate code",
|
||||
"d0b52871ce": "Your phones are paired.",
|
||||
"051978a785": "Your phone is paired."
|
||||
"051978a785": "Your phone is paired.",
|
||||
"channel": {
|
||||
"group": "Release channel",
|
||||
"preview": "Preview",
|
||||
"stable": "Stable"
|
||||
}
|
||||
},
|
||||
"MobilePage": {
|
||||
"e17393c6a3": "Phone preview",
|
||||
@@ -9773,7 +9778,13 @@
|
||||
"platform": {
|
||||
"copy": {
|
||||
"2a532d6fd7": "Scan with your Android camera to download the latest APK from GitHub Releases.",
|
||||
"432db52b73": "Scan with your iPhone camera to open the App Store."
|
||||
"432db52b73": "Scan with your iPhone camera to open the App Store.",
|
||||
"preview": {
|
||||
"tagline": "Newest features, updated daily."
|
||||
},
|
||||
"stable": {
|
||||
"tagline": "The public release, updated weekly."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -9739,7 +9739,12 @@
|
||||
"a6cffbbb0b": "Generar código",
|
||||
"e59a252eca": "regenerar código",
|
||||
"d0b52871ce": "Tus teléfonos están emparejados.",
|
||||
"051978a785": "Tu teléfono está emparejado."
|
||||
"051978a785": "Tu teléfono está emparejado.",
|
||||
"channel": {
|
||||
"group": "Canal de versión",
|
||||
"preview": "Vista previa",
|
||||
"stable": "Estable"
|
||||
}
|
||||
},
|
||||
"MobilePage": {
|
||||
"e17393c6a3": "Vista previa del teléfono",
|
||||
@@ -9773,7 +9778,13 @@
|
||||
"platform": {
|
||||
"copy": {
|
||||
"2a532d6fd7": "Escanee con su cámara Android para descargar el último APK de GitHub Releases.",
|
||||
"432db52b73": "Escanee con la cámara de su iPhone para abrir la App Store."
|
||||
"432db52b73": "Escanee con la cámara de su iPhone para abrir la App Store.",
|
||||
"preview": {
|
||||
"tagline": "Las funciones más recientes, actualizadas a diario."
|
||||
},
|
||||
"stable": {
|
||||
"tagline": "La versión pública, actualizada semanalmente."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -9739,7 +9739,12 @@
|
||||
"a6cffbbb0b": "コードを生成する",
|
||||
"e59a252eca": "コードを再生成する",
|
||||
"d0b52871ce": "スマートフォンはペアリングされています。",
|
||||
"051978a785": "スマートフォンはペアリングされています。"
|
||||
"051978a785": "スマートフォンはペアリングされています。",
|
||||
"channel": {
|
||||
"group": "リリースチャンネル",
|
||||
"preview": "プレビュー",
|
||||
"stable": "安定版"
|
||||
}
|
||||
},
|
||||
"MobilePage": {
|
||||
"e17393c6a3": "スマートフォンプレビュー",
|
||||
@@ -9773,7 +9778,13 @@
|
||||
"platform": {
|
||||
"copy": {
|
||||
"2a532d6fd7": "Android カメラでスキャンして、GitHub リリースから最新の APK をダウンロードします。",
|
||||
"432db52b73": "iPhone のカメラでスキャンして App Store を開きます。"
|
||||
"432db52b73": "iPhone のカメラでスキャンして App Store を開きます。",
|
||||
"preview": {
|
||||
"tagline": "最新機能を毎日更新。"
|
||||
},
|
||||
"stable": {
|
||||
"tagline": "一般公開版、毎週更新。"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -9739,7 +9739,12 @@
|
||||
"a6cffbbb0b": "코드 생성",
|
||||
"e59a252eca": "코드 재생성",
|
||||
"d0b52871ce": "휴대전화가 페어링되었습니다.",
|
||||
"051978a785": "휴대전화가 페어링되었습니다."
|
||||
"051978a785": "휴대전화가 페어링되었습니다.",
|
||||
"channel": {
|
||||
"group": "릴리스 채널",
|
||||
"preview": "미리 보기",
|
||||
"stable": "안정 버전"
|
||||
}
|
||||
},
|
||||
"MobilePage": {
|
||||
"e17393c6a3": "휴대폰 미리보기",
|
||||
@@ -9773,7 +9778,13 @@
|
||||
"platform": {
|
||||
"copy": {
|
||||
"2a532d6fd7": "Android 카메라로 스캔하여 GitHub 릴리스에서 최신 APK를 다운로드하세요.",
|
||||
"432db52b73": "iPhone 카메라로 스캔하여 App Store를 엽니다."
|
||||
"432db52b73": "iPhone 카메라로 스캔하여 App Store를 엽니다.",
|
||||
"preview": {
|
||||
"tagline": "최신 기능, 매일 업데이트."
|
||||
},
|
||||
"stable": {
|
||||
"tagline": "정식 출시 버전, 매주 업데이트."
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -9739,7 +9739,12 @@
|
||||
"a6cffbbb0b": "生成代码",
|
||||
"e59a252eca": "重新生成代码",
|
||||
"d0b52871ce": "您的手机已配对。",
|
||||
"051978a785": "您的手机已配对。"
|
||||
"051978a785": "您的手机已配对。",
|
||||
"channel": {
|
||||
"group": "发布渠道",
|
||||
"preview": "预览版",
|
||||
"stable": "稳定版"
|
||||
}
|
||||
},
|
||||
"MobilePage": {
|
||||
"e17393c6a3": "手机预览",
|
||||
@@ -9773,7 +9778,13 @@
|
||||
"platform": {
|
||||
"copy": {
|
||||
"2a532d6fd7": "使用 Android 相机扫描以从 GitHub Releases 下载最新的 APK。",
|
||||
"432db52b73": "使用 iPhone 相机扫描以打开 App Store。"
|
||||
"432db52b73": "使用 iPhone 相机扫描以打开 App Store。",
|
||||
"preview": {
|
||||
"tagline": "最新功能,每日更新。"
|
||||
},
|
||||
"stable": {
|
||||
"tagline": "公开发布版,每周更新。"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user