feat: add database backed membership tiers
This commit is contained in:
@@ -35,6 +35,9 @@
|
||||
email: '',
|
||||
role: 'Member',
|
||||
});
|
||||
const membershipTierForm = reactive({
|
||||
membershipTierId: null,
|
||||
});
|
||||
const memberRoleOptions = ['Member', 'Admin', 'BillingManager', 'ConnectorManager'];
|
||||
|
||||
const organizationId = computed(() => route.params.organizationId);
|
||||
@@ -62,6 +65,18 @@
|
||||
permissions.value.includes(organizationPermissions.createWorkspaces)
|
||||
);
|
||||
const usageItems = computed(() => organization.value?.usage?.items ?? []);
|
||||
const membershipTierOptions = computed(() =>
|
||||
(organization.value?.availableMembershipTiers?.length
|
||||
? organization.value.availableMembershipTiers
|
||||
: organizationStore.membershipTiers
|
||||
).map(tier => ({
|
||||
title: tier.name,
|
||||
value: tier.id,
|
||||
props: {
|
||||
subtitle: formatTierSummary(tier),
|
||||
},
|
||||
}))
|
||||
);
|
||||
const visibleSections = computed(() => [
|
||||
{ key: 'members', icon: mdiAccountGroupOutline, visible: canViewMembers.value },
|
||||
{ key: 'usage', icon: mdiChartBar, visible: canViewUsage.value },
|
||||
@@ -79,7 +94,10 @@
|
||||
return;
|
||||
}
|
||||
|
||||
await organizationStore.fetchOrganization(organizationId.value);
|
||||
await Promise.all([
|
||||
organizationStore.fetchMembershipTiers(),
|
||||
organizationStore.fetchOrganization(organizationId.value),
|
||||
]);
|
||||
}
|
||||
|
||||
async function submitProfile() {
|
||||
@@ -156,6 +174,48 @@
|
||||
}
|
||||
}
|
||||
|
||||
async function submitMembershipTier() {
|
||||
settingsError.value = null;
|
||||
settingsStatus.value = null;
|
||||
|
||||
if (!membershipTierForm.membershipTierId) {
|
||||
settingsError.value = t('organizationSettings.errors.membershipTierRequired');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await organizationStore.updateMembershipTier(
|
||||
organizationId.value,
|
||||
membershipTierForm.membershipTierId
|
||||
);
|
||||
settingsStatus.value = t('organizationSettings.tierSaved');
|
||||
} catch (error) {
|
||||
console.error('Failed to update organization membership tier:', error);
|
||||
settingsError.value = t('organizationSettings.errors.tierSaveFailed');
|
||||
}
|
||||
}
|
||||
|
||||
function formatTierSummary(tier) {
|
||||
const price = tier.isCustom || tier.monthlyPriceCents === null || tier.monthlyPriceCents === undefined
|
||||
? t('organizationSettings.tiers.customPrice')
|
||||
: tier.monthlyPriceCents === 0
|
||||
? t('organizationSettings.tiers.freePrice')
|
||||
: t('organizationSettings.tiers.monthlyPrice', {
|
||||
price: `$${Math.round(tier.monthlyPriceCents / 100)}`,
|
||||
});
|
||||
|
||||
return t('organizationSettings.tiers.summary', {
|
||||
price,
|
||||
workspaces: formatLimit(tier.workspaceLimit),
|
||||
members: formatLimit(tier.memberLimit),
|
||||
activeContent: formatLimit(tier.activeContentLimit),
|
||||
});
|
||||
}
|
||||
|
||||
function formatLimit(limit) {
|
||||
return limit ?? t('organizationSettings.usage.unlimited');
|
||||
}
|
||||
|
||||
function usagePercent(item) {
|
||||
if (!item.limit) {
|
||||
return 0;
|
||||
@@ -171,6 +231,7 @@
|
||||
organization,
|
||||
currentOrganization => {
|
||||
profileForm.name = currentOrganization?.name ?? '';
|
||||
membershipTierForm.membershipTierId = currentOrganization?.membershipTier?.id ?? null;
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
@@ -408,8 +469,30 @@
|
||||
>
|
||||
<div class="usage-plan">
|
||||
<strong>{{ t('organizationSettings.sections.usage.planLabel') }}</strong>
|
||||
<span>{{ organization.usage?.planName ?? t('organizationSettings.sections.usage.planFallback') }}</span>
|
||||
<span>{{ organization.membershipTier?.name ?? organization.usage?.planName ?? t('organizationSettings.sections.usage.planFallback') }}</span>
|
||||
</div>
|
||||
<v-form
|
||||
v-if="canViewBilling"
|
||||
class="tier-form"
|
||||
@submit.prevent="submitMembershipTier"
|
||||
>
|
||||
<v-select
|
||||
v-model="membershipTierForm.membershipTierId"
|
||||
:items="membershipTierOptions"
|
||||
:label="t('organizationSettings.fields.membershipTier')"
|
||||
:loading="organizationStore.isLoadingMembershipTiers"
|
||||
:disabled="organizationStore.isUpdatingMembershipTier"
|
||||
variant="outlined"
|
||||
hide-details
|
||||
/>
|
||||
<v-btn
|
||||
color="primary"
|
||||
type="submit"
|
||||
:loading="organizationStore.isUpdatingMembershipTier"
|
||||
>
|
||||
{{ t('organizationSettings.saveTier') }}
|
||||
</v-btn>
|
||||
</v-form>
|
||||
<div
|
||||
v-for="item in usageItems"
|
||||
:key="item.key"
|
||||
@@ -717,6 +800,11 @@
|
||||
@apply flex flex-col gap-3;
|
||||
}
|
||||
|
||||
.tier-form {
|
||||
@apply grid gap-3 rounded-[0.75rem] p-4 md:grid-cols-[minmax(0,1fr)_auto] md:items-end;
|
||||
background: rgba(23, 32, 51, 0.04);
|
||||
}
|
||||
|
||||
.usage-plan,
|
||||
.usage-row {
|
||||
@apply rounded-[0.75rem] p-4;
|
||||
|
||||
Reference in New Issue
Block a user