add billable rate restriction interfaces for billing extension

This commit is contained in:
Gregor Vostrak
2026-10-06 17:20:33 +02:00
parent d54296e66a
commit e793b81b35
36 changed files with 635 additions and 48 deletions

View File

@@ -23,8 +23,10 @@ import ProjectVisibilitySelect from '@/packages/ui/src/Project/ProjectVisibility
import { isAllowedToPerformPremiumAction } from '@/utils/billing';
import { useOrganizationQuery } from '@/utils/useOrganizationQuery';
import { getCurrentOrganizationId } from '@/utils/useUser';
import { useBillableRatesLock } from '@/packages/ui/src/utils/useBillableRatesLock';
const { updateProject } = useProjectsStore();
const { locked: billableRatesLocked } = useBillableRatesLock();
const { clients } = useClientsQuery();
const { organization } = useOrganizationQuery(getCurrentOrganizationId()!);
const show = defineModel('show', { default: false });
@@ -48,6 +50,11 @@ const project = ref<CreateProjectBody>({
is_public: props.originalProject.is_public,
});
// Locked organizations do not receive the rate of the project, so sending it back would remove the existing rate
function getUpdateBody() {
const { billable_rate: billableRate, ...body } = project.value;
return billableRatesLocked.value ? body : { ...body, billable_rate: billableRate };
}
async function submit() {
if (props.originalProject.billable_rate !== project.value.billable_rate) {
// make sure that the alert modal is not immediately submitted when user presses enter
@@ -56,7 +63,7 @@ async function submit() {
}, 0);
return;
}
await updateProject(props.originalProject.id, project.value);
await updateProject(props.originalProject.id, getUpdateBody());
show.value = false;
}
@@ -72,7 +79,7 @@ const currentClientName = computed(() => {
});
async function submitBillableRate() {
await updateProject(props.originalProject.id, project.value);
await updateProject(props.originalProject.id, getUpdateBody());
show.value = false;
showBillableRateModal.value = false;
}

View File

@@ -1,4 +1,6 @@
<script setup lang="ts">
import UpgradeLockedBadge from '@/packages/ui/src/UpgradeLockedBadge.vue';
import { useBillableRatesLock } from '@/packages/ui/src/utils/useBillableRatesLock';
import { computed } from 'vue';
import TableHeading from '@/Components/Common/TableHeading.vue';
import SortableTableHeaderCell from '@/Components/Common/SortableTableHeaderCell.vue';
@@ -25,6 +27,8 @@ const sortState = computed(() => ({
function handleSort(column: SortColumn) {
emit('sort', column);
}
const { locked: billableRatesLocked } = useBillableRatesLock();
</script>
<template>
@@ -50,7 +54,10 @@ function handleSort(column: SortColumn) {
column="billable_rate"
v-bind="sortState"
@sort="handleSort">
Billable Rate
<span class="inline-flex items-center gap-2 whitespace-nowrap">
Billable Rate
<UpgradeLockedBadge v-if="billableRatesLocked" />
</span>
</SortableTableHeaderCell>
<SortableTableHeaderCell column="status" v-bind="sortState" @sort="handleSort">
Status

View File

@@ -1,4 +1,5 @@
<script setup lang="ts">
import { useBillableRatesLock } from '@/packages/ui/src/utils/useBillableRatesLock';
import ProjectMoreOptionsDropdown from '@/Components/Common/Project/ProjectMoreOptionsDropdown.vue';
import type { Project } from '@/packages/api/src';
import { computed, ref, inject, type ComputedRef } from 'vue';
@@ -15,6 +16,7 @@ import { useTasksQuery } from '@/utils/useTasksQuery';
import { useProjectsStore } from '@/utils/useProjects';
import TableRow from '@/Components/TableRow.vue';
import ProjectEditModal from '@/Components/Common/Project/ProjectEditModal.vue';
import UpgradeLockedBadge from '@/packages/ui/src/UpgradeLockedBadge.vue';
import { formatCents } from '@/packages/ui/src/utils/money';
import { getOrganizationCurrencyString } from '@/utils/money';
import EstimatedTimeProgress from '@/packages/ui/src/EstimatedTimeProgress.vue';
@@ -52,8 +54,11 @@ function deleteProject() {
}
function archiveProject() {
// Locked organizations do not receive the rate of the project, so sending it back would remove the existing rate
const { billable_rate: billableRate, ...project } = props.project;
useProjectsStore().updateProject(props.project.id, {
...props.project,
...project,
...(billableRatesLocked.value ? {} : { billable_rate: billableRate }),
is_archived: !props.project.is_archived,
});
}
@@ -78,6 +83,8 @@ const billableRateInfo = computed(() => {
});
const showEditProjectModal = ref(false);
const { locked: billableRatesLocked } = useBillableRatesLock();
</script>
<template>
@@ -129,7 +136,8 @@ const showEditProjectModal = ref(false);
<div
v-if="showBillableRate"
class="whitespace-nowrap px-3 py-4 text-sm text-text-primary">
<span v-if="billableRateInfo">{{ billableRateInfo }}</span>
<UpgradeLockedBadge v-if="billableRatesLocked" />
<span v-else-if="billableRateInfo">{{ billableRateInfo }}</span>
<span v-else class="text-text-tertiary">--</span>
</div>
<div