chore: Update num of seats design

This commit is contained in:
iamsivin
2025-11-04 13:30:13 +05:30
parent 16ac2dba17
commit ebee38d4a5
3 changed files with 215 additions and 66 deletions
@@ -428,6 +428,7 @@
"CREDITS_INCLUDED": "Credits included/month",
"NUMBER_OF_SEATS": "Number of Seats",
"ADJUST_SEATS_HINT": "Adjust seats to scale your plan",
"UPDATE": "Update",
"VIEW_ALL_PLANS": "View All Plans",
"CANCEL_PLAN": "Cancel Plan"
},
@@ -4,6 +4,7 @@ import { useI18n } from 'vue-i18n';
import format from 'date-fns/format';
import Icon from 'dashboard/components-next/icon/Icon.vue';
import Button from 'dashboard/components-next/button/Button.vue';
import SeatManager from './SeatManager.vue';
const props = defineProps({
planName: {
@@ -70,32 +71,6 @@ const formatPrice = price => {
const formatNumber = num => {
return new Intl.NumberFormat('en-US').format(num);
};
const canDecreaseSeats = computed(() => {
return props.currentSeats > props.minSeats;
});
const canIncreaseSeats = computed(() => {
return props.currentSeats < 100; // Max 100 seats
});
const handleDecreaseSeats = () => {
if (canDecreaseSeats.value && !props.isUpdatingSeats) {
emit('updateSeats', {
quantity: props.currentSeats - 1,
direction: 'decrease',
});
}
};
const handleIncreaseSeats = () => {
if (canIncreaseSeats.value && !props.isUpdatingSeats) {
emit('updateSeats', {
quantity: props.currentSeats + 1,
direction: 'increase',
});
}
};
</script>
<template>
@@ -154,47 +129,15 @@ const handleIncreaseSeats = () => {
</div>
</div>
<div
<SeatManager
v-if="!isCancelling"
class="flex items-center justify-between p-3 rounded-lg bg-n-slate-2 dark:bg-n-solid-2 mb-4"
>
<div class="flex items-center gap-2">
<Icon icon="i-lucide-users" class="text-n-slate-11" />
<div>
<h6 class="text-sm font-semibold text-n-slate-12">
{{ t('BILLING_SETTINGS_V2.PLAN_SUMMARY.NUMBER_OF_SEATS') }}
</h6>
<p class="text-xs text-n-slate-11">
{{ t('BILLING_SETTINGS_V2.PLAN_SUMMARY.ADJUST_SEATS_HINT') }}
</p>
</div>
</div>
<div class="flex items-center gap-2">
<Button
faded
slate
sm
icon="i-lucide-minus"
type="button"
:is-loading="isUpdatingSeats && updatingDirection === 'decrease'"
:disabled="!canDecreaseSeats || isUpdatingSeats"
@click="handleDecreaseSeats"
/>
<span class="min-w-12 text-center text-lg font-bold text-n-slate-12">
{{ currentSeats }}
</span>
<Button
faded
slate
sm
icon="i-lucide-plus"
type="button"
:is-loading="isUpdatingSeats && updatingDirection === 'increase'"
:disabled="!canIncreaseSeats || isUpdatingSeats"
@click="handleIncreaseSeats"
/>
</div>
</div>
:current-seats="currentSeats"
:min-seats="minSeats"
:is-updating-seats="isUpdatingSeats"
:updating-direction="updatingDirection"
class="mb-4"
@update-seats="emit('updateSeats', $event)"
/>
<div
class="flex items-center justify-end gap-2 pt-4 border-t border-n-weak"
@@ -0,0 +1,205 @@
<script setup>
import { ref, computed, watch } from 'vue';
import { useI18n } from 'vue-i18n';
import Icon from 'dashboard/components-next/icon/Icon.vue';
import Button from 'dashboard/components-next/button/Button.vue';
import Input from 'dashboard/components-next/input/Input.vue';
const props = defineProps({
currentSeats: {
type: Number,
required: true,
},
minSeats: {
type: Number,
default: 1,
},
isUpdatingSeats: {
type: Boolean,
default: false,
},
updatingDirection: {
type: String,
default: null,
},
});
const emit = defineEmits(['updateSeats']);
const { t } = useI18n();
const MAX_SEATS = 999;
const inputSeats = ref(props.currentSeats);
const isEditing = ref(false);
const updatingViaButtons = ref(false);
// Sync input with prop changes
watch(
() => props.currentSeats,
newValue => {
inputSeats.value = newValue;
isEditing.value = false;
updatingViaButtons.value = false;
}
);
const canDecreaseSeats = computed(() => {
return props.currentSeats > props.minSeats;
});
const canIncreaseSeats = computed(() => {
return props.currentSeats < MAX_SEATS;
});
const isValidInput = computed(() => {
const numValue = parseInt(inputSeats.value, 10);
if (Number.isNaN(numValue)) return false;
return numValue >= props.minSeats && numValue <= MAX_SEATS;
});
const hasChanges = computed(() => {
const numValue = parseInt(inputSeats.value, 10);
if (Number.isNaN(numValue)) return false;
return numValue !== props.currentSeats;
});
const canUpdate = computed(() => {
return isEditing.value && hasChanges.value && isValidInput.value;
});
const handleInputFocus = () => {
isEditing.value = true;
};
const handleInputChange = event => {
inputSeats.value = event.target.value;
isEditing.value = true;
};
const handleInputBlur = () => {
// Don't reset if currently updating
if (props.isUpdatingSeats) return;
// Always reset to current seats on blur
inputSeats.value = props.currentSeats;
// Hide update button and show +/- buttons
isEditing.value = false;
};
const handleUpdate = () => {
if (!isValidInput.value || !hasChanges.value) return;
const numValue = parseInt(inputSeats.value, 10);
const direction = numValue > props.currentSeats ? 'increase' : 'decrease';
emit('updateSeats', {
quantity: numValue,
direction,
});
};
const handleDecreaseSeats = () => {
if (canDecreaseSeats.value && !props.isUpdatingSeats) {
// Mark that update is via buttons, not manual input
isEditing.value = false;
updatingViaButtons.value = true;
emit('updateSeats', {
quantity: props.currentSeats - 1,
direction: 'decrease',
});
}
};
const handleIncreaseSeats = () => {
if (canIncreaseSeats.value && !props.isUpdatingSeats) {
// Mark that update is via buttons, not manual input
isEditing.value = false;
updatingViaButtons.value = true;
emit('updateSeats', {
quantity: props.currentSeats + 1,
direction: 'increase',
});
}
};
</script>
<template>
<div
class="flex items-center justify-between p-4 rounded-lg bg-n-slate-2 dark:bg-n-solid-2"
>
<div class="flex items-center gap-3">
<div
class="flex items-center justify-center w-10 h-10 rounded-lg bg-n-blue-3 dark:bg-n-blue-4"
>
<Icon icon="i-lucide-users" class="text-n-blue-11" />
</div>
<div>
<h6 class="text-sm font-semibold text-n-slate-12">
{{ t('BILLING_SETTINGS_V2.PLAN_SUMMARY.NUMBER_OF_SEATS') }}
</h6>
<p class="text-xs text-n-slate-11">
{{ t('BILLING_SETTINGS_V2.PLAN_SUMMARY.ADJUST_SEATS_HINT') }}
</p>
</div>
</div>
<div class="flex items-center flex-shrink-0 gap-2">
<Button
v-if="!isEditing"
faded
slate
sm
icon="i-lucide-minus"
type="button"
:is-loading="
isUpdatingSeats &&
updatingViaButtons &&
updatingDirection === 'decrease'
"
:disabled="!canDecreaseSeats || isUpdatingSeats"
@click="handleDecreaseSeats"
/>
<Input
v-model="inputSeats"
type="number"
:min="String(minSeats)"
:max="String(MAX_SEATS)"
:disabled="isUpdatingSeats"
class="w-20 text-center text-lg [&>input]:!py-1 [&>input]:!h-8"
@focus="handleInputFocus"
@input="handleInputChange"
@blur="handleInputBlur"
/>
<Button
v-if="!isEditing"
faded
slate
sm
icon="i-lucide-plus"
type="button"
:is-loading="
isUpdatingSeats &&
updatingViaButtons &&
updatingDirection === 'increase'
"
:disabled="!canIncreaseSeats || isUpdatingSeats"
@click="handleIncreaseSeats"
/>
<Button
v-if="isEditing"
color="blue"
size="sm"
:disabled="!canUpdate || isUpdatingSeats"
:is-loading="isUpdatingSeats"
@mousedown.prevent
@click="handleUpdate"
>
{{ t('BILLING_SETTINGS_V2.PLAN_SUMMARY.UPDATE') }}
</Button>
</div>
</div>
</template>