fix: block non-upgrade package selection
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled
tests / ui (push) Has been cancelled

This commit is contained in:
Codex Agent
2026-01-12 12:40:18 +01:00
parent c1be7dd1ef
commit e93a00f0fc
3 changed files with 18 additions and 13 deletions

View File

@@ -182,8 +182,8 @@ function PackageShopCard({
const { t } = useTranslation('management'); const { t } = useTranslation('management');
const statusLabel = getPackageStatusLabel({ t, isActive, owned }); const statusLabel = getPackageStatusLabel({ t, isActive, owned });
const isSubdued = Boolean(isDowngrade && !isActive); const isSubdued = Boolean((isDowngrade || !isUpgrade) && !isActive);
const canSelect = canSelectPackage(isDowngrade, isActive); const canSelect = canSelectPackage(isUpgrade, isActive);
return ( return (
<MobileCard <MobileCard
@@ -246,12 +246,12 @@ function PackageShopCard({
label={ label={
isActive isActive
? t('shop.manage', 'Manage Plan') ? t('shop.manage', 'Manage Plan')
: isDowngrade : isUpgrade
? t('shop.selectDisabled', 'Not available') ? t('shop.select', 'Select')
: t('shop.select', 'Select') : t('shop.selectDisabled', 'Not available')
} }
onPress={canSelect ? onSelect : undefined} onPress={canSelect ? onSelect : undefined}
tone={isActive || isDowngrade ? 'ghost' : 'primary'} tone={isActive || !isUpgrade ? 'ghost' : 'primary'}
disabled={!canSelect} disabled={!canSelect}
/> />
</MobileCard> </MobileCard>
@@ -425,12 +425,12 @@ function PackageShopCompareView({
<XStack paddingTop="$2"> <XStack paddingTop="$2">
<YStack width={labelWidth} /> <YStack width={labelWidth} />
{entries.map((entry) => { {entries.map((entry) => {
const canSelect = canSelectPackage(entry.isDowngrade, entry.isActive); const canSelect = canSelectPackage(entry.isUpgrade, entry.isActive);
const label = entry.isActive const label = entry.isActive
? t('shop.manage', 'Manage Plan') ? t('shop.manage', 'Manage Plan')
: entry.isDowngrade : entry.isUpgrade
? t('shop.selectDisabled', 'Not available') ? t('shop.select', 'Select')
: t('shop.select', 'Select'); : t('shop.selectDisabled', 'Not available');
return ( return (
<YStack key={`cta-${entry.pkg.id}`} width={columnWidth} paddingHorizontal="$2"> <YStack key={`cta-${entry.pkg.id}`} width={columnWidth} paddingHorizontal="$2">
@@ -470,8 +470,8 @@ function getPackageStatusLabel({
return null; return null;
} }
function canSelectPackage(isDowngrade?: boolean, isActive?: boolean): boolean { function canSelectPackage(isUpgrade?: boolean, isActive?: boolean): boolean {
return !isDowngrade || Boolean(isActive); return Boolean(isActive || isUpgrade);
} }
function CheckoutConfirmation({ pkg, onCancel }: { pkg: Package; onCancel: () => void }) { function CheckoutConfirmation({ pkg, onCancel }: { pkg: Package; onCancel: () => void }) {

View File

@@ -29,6 +29,11 @@ describe('classifyPackageChange', () => {
const candidate = { ...active, id: 3, max_photos: 50, features: { advanced_analytics: false } } as any; const candidate = { ...active, id: 3, max_photos: 50, features: { advanced_analytics: false } } as any;
expect(classifyPackageChange(candidate, active)).toEqual({ isUpgrade: false, isDowngrade: true }); expect(classifyPackageChange(candidate, active)).toEqual({ isUpgrade: false, isDowngrade: true });
}); });
it('treats mixed changes as downgrade', () => {
const candidate = { ...active, id: 4, max_photos: 200, gallery_days: 10, features: { advanced_analytics: false } } as any;
expect(classifyPackageChange(candidate, active)).toEqual({ isUpgrade: false, isDowngrade: true });
});
}); });
describe('selectRecommendedPackageId', () => { describe('selectRecommendedPackageId', () => {

View File

@@ -86,7 +86,7 @@ export function classifyPackageChange(pkg: Package, active: Package | null): Pac
const hasUpgrade = hasFeatureUpgrade || hasLimitUpgrade; const hasUpgrade = hasFeatureUpgrade || hasLimitUpgrade;
const hasDowngrade = hasFeatureDowngrade || hasLimitDowngrade; const hasDowngrade = hasFeatureDowngrade || hasLimitDowngrade;
if (hasUpgrade) { if (hasUpgrade && !hasDowngrade) {
return { isUpgrade: true, isDowngrade: false }; return { isUpgrade: true, isDowngrade: false };
} }