Desktop D3 UI: studios and store accounts (health checks with fixes, import apps), one card per app across studios (automatic iOS / Android pairing, manual pair / unpair, links, studio moves), store-only apps read-only, ignore list, portfolio search / studio filter / sort / grouping, collapsible studio sidebar, complete Settings, notifications, fixtures with 3 studios and 5 accounts

This commit is contained in:
CRTkafa 2026-10-11 04:15:30 +08:00
parent e058d09683
commit 7b2cc8da0a
24 changed files with 2225 additions and 312 deletions

View file

@ -1,6 +1,7 @@
// 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.
import { api, type Project, type ReleaseTrain } from '../../api';
import { api, type ReleaseTrain } from '../../api';
import type { UnifiedApp } from '../../lib/identity';
import { type Key, t } from '../../i18n';
import { ago, date, share } from '../../lib/format';
import { type Lane, STAGES, type TestFlightStatus, trainLanes, versionHistory } from '../../lib/model';
@ -60,10 +61,11 @@ function Train({ title, train, tf, tone }: { title: string; train: ReleaseTrain;
);
}
export function ReleasesTab({ project }: { project: Project }) {
export function ReleasesTab({ app }: { app: UnifiedApp }) {
const d = useData();
const l = d.liveOf(project);
const tf = useRead<TestFlightStatus>(project.id, 'apple.testflight_status', { network: true, title: 'TestFlight' });
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(() => {}); };
if (!l) {
@ -71,7 +73,7 @@ export function ReleasesTab({ project }: { project: Project }) {
<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.playAccount && !project.appleAccount && <span className="small muted">{t('releases.noAccounts')}</span>}
{project && !project.playAccount && !project.appleAccount && <span className="small muted">{t('releases.noAccounts')}</span>}
</>}>{t('releases.notReadText')}</Empty>
</Card>
);