feat: add database backed membership tiers
All checks were successful
deploy-socialize / image (push) Successful in 1m9s
deploy-socialize / deploy (push) Successful in 19s

This commit is contained in:
2026-05-07 20:29:53 -04:00
parent db16e79d9f
commit 6d92119c9c
23 changed files with 3512 additions and 30 deletions

View File

@@ -1,5 +1,5 @@
<script setup>
import { computed, reactive, ref } from 'vue';
import { computed, onMounted, reactive, ref, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import { useRouter } from 'vue-router';
import { mdiAccountArrowRightOutline, mdiDomainPlus, mdiEmailOutline } from '@mdi/js';
@@ -13,6 +13,7 @@
const createForm = reactive({
name: '',
membershipTierId: null,
});
const accessForm = reactive({
targetType: 'organization',
@@ -27,6 +28,29 @@
{ title: t('organizationOnboarding.request.types.organization'), value: 'organization' },
{ title: t('organizationOnboarding.request.types.workspace'), value: 'workspace' },
]);
const membershipTierOptions = computed(() =>
organizationStore.membershipTiers.map(tier => ({
title: tier.name,
value: tier.id,
props: {
subtitle: formatTierPrice(tier),
},
}))
);
function formatTierPrice(tier) {
if (tier.isCustom || tier.monthlyPriceCents === null || tier.monthlyPriceCents === undefined) {
return t('organizationOnboarding.create.tiers.customPrice');
}
if (tier.monthlyPriceCents === 0) {
return t('organizationOnboarding.create.tiers.freePrice');
}
return t('organizationOnboarding.create.tiers.monthlyPrice', {
price: `$${Math.round(tier.monthlyPriceCents / 100)}`,
});
}
async function createOrganization() {
if (organizationStore.isCreating) {
@@ -42,7 +66,10 @@
}
try {
await organizationStore.createOrganization({ name });
await organizationStore.createOrganization({
name,
membershipTierId: createForm.membershipTierId,
});
await Promise.all([
organizationStore.fetchOrganizations(),
workspaceStore.fetchWorkspaces(),
@@ -73,6 +100,20 @@
window.location.href = `mailto:${encodeURIComponent(accessForm.adminEmail.trim())}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
requestStatus.value = t('organizationOnboarding.request.sent');
}
onMounted(async () => {
const tiers = await organizationStore.fetchMembershipTiers();
createForm.membershipTierId = tiers[0]?.id ?? null;
});
watch(
() => organizationStore.membershipTiers,
tiers => {
if (!createForm.membershipTierId) {
createForm.membershipTierId = tiers[0]?.id ?? null;
}
}
);
</script>
<template>
@@ -114,6 +155,15 @@
variant="outlined"
hide-details
/>
<v-select
v-model="createForm.membershipTierId"
:items="membershipTierOptions"
:label="t('organizationOnboarding.create.fields.membershipTier')"
:loading="organizationStore.isLoadingMembershipTiers"
:disabled="organizationStore.isCreating"
variant="outlined"
hide-details
/>
<v-btn
:loading="organizationStore.isCreating"