desktop: Releases > Send for review (App Store submission state, builds by number, Submit / Resubmit / in-app purchases; Play versionCodes by number, Send to production, managed publishing, console plans for countries and Send changes for review), plan panel before every owner dialog with streamed progress; App info console steps for every console plan form with verify; Catalog price-change owner actions and the declined state; Data safety deletion answer; TestFlight create missing groups; Android SDK in Dependencies; upload progress in job toasts; boot check send-for-review cases on D1 and D3 state

This commit is contained in:
CRTkafa 2026-10-12 12:07:59 +08:00
parent 33eb24a3dc
commit 2b101f0fd8
22 changed files with 1527 additions and 57 deletions

View file

@ -1,7 +1,9 @@
// Releases: the release timeline (one version on both stores on one track: build -> testing -> review -> live ->
// rollout %), then every version read from the stores. Submit and release stay in the consoles.
// rollout %), then every version read from the stores, then sending for review (Submit.tsx): App Store submit /
// resubmit / in-app purchases and the Google Play production release as owner actions, the Play steps without an API
// as console plans. Releasing an approved version (manual release) stays in App Store Connect.
import { useState } from 'react';
import { api, type ReleaseTrain, type StoreRelease } from '../../api';
import type { ReleaseTrain, StoreRelease } from '../../api';
import type { UnifiedApp } from '../../lib/identity';
import { WriteButton } from '../../ui/WriteButton';
import { type Key, t } from '../../i18n';
@ -11,7 +13,7 @@ import { useData, useRead, useWrite } from '../../store';
import { Icon, StoreGlyph } from '../../ui/icons';
import { RolloutBar } from '../../ui/charts';
import { Card, Chip, Empty, Notice, PhaseChip, storeName } from '../../ui/kit';
import { ascUrl, PLAY_CONSOLE } from '../AppView';
import { AppleSubmitCard, PlayProductionCard } from './Submit';
const STAGE_KEY: Record<(typeof STAGES)[number], Key> = { build: 'stage.build', testing: 'stage.testing', review: 'stage.review', live: 'stage.live', rollout: 'stage.rollout' };
@ -121,16 +123,29 @@ export function ReleasesTab({ app }: { app: UnifiedApp }) {
const l = d.liveFor(app);
const project = app.project;
const tf = useRead<TestFlightStatus>(app.target, 'apple.testflight_status', { network: true, title: 'TestFlight', skip: !app.ios && !d.statusFor(app)?.app?.platforms.ios });
const open = (url: string) => { api.openExternal(url).catch(() => {}); };
const sum = project ? d.statusOf(project)?.summary : null;
const submit = project && (sum?.apple || sum?.play) ? (
<section className="stack" aria-labelledby="sub-section" data-section="submit">
<h2 id="sub-section" className="section-h row" style={{ gap: 8 }}><Icon name="send" />{t('sub.section')}</h2>
<div className="grid g2" style={{ alignItems: 'start' }}>
{sum?.apple && <AppleSubmitCard app={app} project={project} />}
{sum?.play && <PlayProductionCard app={app} project={project} />}
</div>
<p className="small muted">{t('releases.ownerNote')}</p>
</section>
) : null;
if (!l) {
return (
<>
<Card pad={false}>
<Empty illo="cloud" title={t('releases.notReadTitle')} actions={<>
<button className="btn btn-primary" onClick={() => d.refreshPortfolio(true)} disabled={!!d.portfolioBusy}><Icon name="refresh" />{t('action.readStores')}</button>
{project && !project.playAccount && !project.appleAccount && <span className="small muted">{t('releases.noAccounts')}</span>}
</>}>{t('releases.notReadText')}</Empty>
</Card>
{submit}
</>
);
}
const history = versionHistory(l.release.all ?? []);
@ -164,10 +179,7 @@ export function ReleasesTab({ app }: { app: UnifiedApp }) {
) : <Empty compact illo="unknown" title={t('releases.noVersions')} />}
</Card>
<Notice tone="info" icon="shield" action={<div className="actions">
{l.app?.platforms.ios && <button className="btn btn-sm" onClick={() => open(ascUrl(l.app?.platforms.ios?.ascAppId))}><Icon name="external" />App Store Connect</button>}
{l.app?.platforms.android && <button className="btn btn-sm" onClick={() => open(PLAY_CONSOLE)}><Icon name="external" />Play Console</button>}
</div>}>{t('releases.consoleNote')}</Notice>
{submit}
</>
);
}