fix(core): show past due in ui (#13477)

fix CLOUD-238, CLOUD-239

#### PR Dependency Tree


* **PR #13477** 👈

This tree was auto-generated by
[Charcoal](https://github.com/danerwilliams/charcoal)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **New Features**
* Payment method management is now always available directly within AI
and Pro plan cards.
* **Bug Fixes**
* Past-due subscriptions are now included in subscription status
results, ensuring they appear in billing views.
* **Style**
* Plan actions are moved inline within each plan’s description for a
cleaner, more compact layout.
  * Actions are grouped horizontally with improved spacing.
  * Minor class name and spacing tweaks for consistent styling.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Peng Xiao
2025-08-12 17:00:32 +08:00
committed by GitHub
parent 678dc15365
commit fda7e9008d
4 changed files with 53 additions and 44 deletions
@@ -461,7 +461,11 @@ export class UserSubscriptionResolver {
where: { where: {
targetId: user.id, targetId: user.id,
status: { status: {
in: [SubscriptionStatus.Active, SubscriptionStatus.Trialing], in: [
SubscriptionStatus.Active,
SubscriptionStatus.Trialing,
SubscriptionStatus.PastDue,
],
}, },
}, },
}); });
@@ -76,30 +76,36 @@ export const AIPlanCard = ({ onClick }: { onClick: () => void }) => {
status={subscription?.status} status={subscription?.status}
/> />
} }
desc={billingTip} desc={
<>
{billingTip}
<div className={styles.planActionContainer}>
{price?.yearlyAmount ? (
subscription ? (
isOnetime ? (
<AIRedeemCodeButton className={styles.planAction} />
) : subscription.canceledAt ? (
<AIResume className={styles.planAction} />
) : (
<AICancel className={styles.planAction} />
)
) : (
<AISubscribe className={styles.planAction}>
{t[
'com.affine.payment.billing-setting.ai.start-free-trial'
]()}
</AISubscribe>
)
) : null}
<PaymentMethodUpdater
inCardView
className={styles.managementInCard}
variant="primary"
/>
</div>
</>
}
/> />
{price?.yearlyAmount ? (
subscription ? (
isOnetime ? (
<AIRedeemCodeButton className={styles.planAction} />
) : subscription.canceledAt ? (
<AIResume className={styles.planAction} />
) : (
<AICancel className={styles.planAction} />
)
) : (
<AISubscribe className={styles.planAction}>
{t['com.affine.payment.billing-setting.ai.start-free-trial']()}
</AISubscribe>
)
) : null}
{subscription?.status === SubscriptionStatus.PastDue ? (
<PaymentMethodUpdater
inCardView
className={styles.manageMentInCard}
variant="primary"
/>
) : null}
</div> </div>
<p className={styles.planPrice}> <p className={styles.planPrice}>
{subscription ? priceReadable : '$0'} {subscription ? priceReadable : '$0'}
@@ -89,14 +89,14 @@ export const ProPlanCard = ({
}} }}
/> />
<CloudExpirationInfo /> <CloudExpirationInfo />
<PlanAction
plan={currentPlan}
subscriptionStatus={proSubscription?.status}
gotoPlansSetting={gotoCloudPlansSetting}
/>
</> </>
} }
/> />
<PlanAction
plan={currentPlan}
subscriptionStatus={proSubscription?.status}
gotoPlansSetting={gotoCloudPlansSetting}
/>
</div> </div>
<p className={styles.planPrice}> <p className={styles.planPrice}>
${amount} ${amount}
@@ -148,7 +148,6 @@ const CloudExpirationInfo = () => {
const PlanAction = ({ const PlanAction = ({
plan, plan,
subscriptionStatus,
gotoPlansSetting, gotoPlansSetting,
}: { }: {
plan: string; plan: string;
@@ -165,7 +164,7 @@ const PlanAction = ({
} }
return ( return (
<> <div className={styles.planActionContainer}>
<Button <Button
className={styles.planAction} className={styles.planAction}
variant="primary" variant="primary"
@@ -175,13 +174,11 @@ const PlanAction = ({
? t['com.affine.payment.billing-setting.change-plan']() ? t['com.affine.payment.billing-setting.change-plan']()
: t['com.affine.payment.billing-setting.upgrade']()} : t['com.affine.payment.billing-setting.upgrade']()}
</Button> </Button>
{subscriptionStatus === SubscriptionStatus.PastDue ? ( <PaymentMethodUpdater
<PaymentMethodUpdater inCardView
inCardView className={styles.managementInCard}
className={styles.manageMentInCard} variant="primary"
variant="primary" />
/> </div>
) : null}
</>
); );
}; };
@@ -23,7 +23,6 @@ export const currentPlan = style({
}); });
export const planAction = style({ export const planAction = style({
width: 'auto !important', width: 'auto !important',
marginTop: '8px',
}); });
export const planPrice = style({ export const planPrice = style({
fontSize: cssVar('fontH6'), fontSize: cssVar('fontH6'),
@@ -35,6 +34,12 @@ export const billingFrequency = style({
export const paymentMethod = style({ export const paymentMethod = style({
marginTop: '24px', marginTop: '24px',
}); });
export const planActionContainer = style({
display: 'flex',
gap: '12px',
marginTop: '12px',
});
globalStyle('.dangerous-setting .name', { globalStyle('.dangerous-setting .name', {
color: cssVarV2('status/error'), color: cssVarV2('status/error'),
}); });
@@ -129,7 +134,4 @@ export const cardLabelIcon = style({
width: '14px', width: '14px',
height: '14px', height: '14px',
}); });
export const manageMentInCard = style({ export const managementInCard = style({});
marginTop: '8px',
marginLeft: '12px',
});