Feature Policies API + Invitations Policies (#375)

- Added Feature Policy API: a declarative system to enable/disable/modify default behavior in the SaaS kit
- Team invitation policies with pre-checks using the Feature Policy API: Invite Members dialog now shows loading, errors, and clear reasons when invitations are blocked
- Version bump to 2.16.0 and widespread dependency updates (Supabase, React types, react-i18next, etc.).
- Added comprehensive docs for the new policy system and orchestrators.
- Subscription cancellations now trigger immediate invoicing explicitly
This commit is contained in:
Giancarlo Buomprisco
2025-09-30 12:36:19 +08:00
committed by GitHub
parent 3c13b5ec1e
commit 1dd6fdad22
53 changed files with 3908 additions and 1128 deletions

View File

@@ -0,0 +1,70 @@
import { allow, definePolicy, deny } from '@kit/policies';
import { createPolicyRegistry } from '@kit/policies';
import { FeaturePolicyInvitationContext } from './feature-policy-invitation-context';
/**
* Feature-specific registry for invitation policies
*/
export const invitationPolicyRegistry = createPolicyRegistry();
/**
* Subscription required policy
* Checks if the account has an active subscription
*/
export const subscriptionRequiredInvitationsPolicy =
definePolicy<FeaturePolicyInvitationContext>({
id: 'subscription-required',
stages: ['preliminary', 'submission'],
evaluate: async ({ subscription }) => {
if (!subscription || !subscription.active) {
return deny({
code: 'SUBSCRIPTION_REQUIRED',
message: 'teams:policyErrors.subscriptionRequired',
remediation: 'teams:policyRemediation.subscriptionRequired',
});
}
return allow();
},
});
/**
* Paddle billing policy
* Checks if the account has a paddle subscription and is in a trial period
*/
export const paddleBillingInvitationsPolicy =
definePolicy<FeaturePolicyInvitationContext>({
id: 'paddle-billing',
stages: ['preliminary', 'submission'],
evaluate: async ({ subscription }) => {
// combine with subscriptionRequiredPolicy if subscription must be required
if (!subscription) {
return allow();
}
// Paddle specific constraint: cannot update subscription items during trial
if (
subscription.provider === 'paddle' &&
subscription.status === 'trialing'
) {
const hasPerSeatItems = subscription.items.some(
(item) => item.type === 'per_seat',
);
if (hasPerSeatItems) {
return deny({
code: 'PADDLE_TRIAL_RESTRICTION',
message: 'teams:policyErrors.paddleTrialRestriction',
remediation: 'teams:policyRemediation.paddleTrialRestriction',
});
}
}
return allow();
},
});
// register policies below to apply them
//
//