Hello from MCP server
<template>
<BaseLayout :title="'Confirm Your Selection'">
<div class="confirmation-container" v-if="selectedTier">
<div class="content-section">
<div class="tier-card" :class="getTierClass(selectedTier.name)">
<!-- Tier Badge -->
<div class="tier-badge" :class="getTierBadgeClass(selectedTier.name)">
<span class="tier-name">{{ selectedTier.name }}</span>
</div>
<!-- Tier Content -->
<div class="tier-content">
<h3 class="tier-title">{{ selectedTier.title?.toUpperCase() || selectedTier.name }}</h3>
<!-- Features List -->
<div class="features-section">
<template v-if="selectedTier.menuCopy">
<div
v-for="copy in selectedTier.menuCopy"
:key="copy.id"
class="feature-item"
>
<div
v-for="item in copy.contentItems"
:key="item.id"
class="feature-text"
>
<ion-icon :icon="checkmarkCircle" class="feature-icon"></ion-icon>
<span>{{ item.content }}</span>
</div>
</div>
</template>
<template v-else-if="selectedTier.contentItems">
<div
v-for="item in selectedTier.contentItems"
:key="item.id"
v-show="!item.refId?.includes('_title')"
class="feature-text"
>
<ion-icon :icon="checkmarkCircle" class="feature-icon"></ion-icon>
<span>{{ item.content }}</span>
</div>
</template>
</div>
<!-- Pricing Section -->
<div class="pricing-section">
<div class="price-amount">
<show-currency :currencyIn="selectedTier.price" />
</div>
<div class="warranty-text" v-if="selectedTier.warranty">
{{ selectedTier.warranty }}
</div>
</div>
</div>
</div>
<!-- Action Buttons -->
<div class="actions-section">
<ion-button
expand="block"
color="success"
size="large"
@click="confirmSelection"
v-if="isInSession"
>
<ion-icon :icon="checkmarkCircle" slot="start"></ion-icon>
Confirm Selection
</ion-button>
<ion-button
expand="block"
fill="outline"
color="primary"
size="large"
@click="goBack"
>
<ion-icon :icon="arrowBack" slot="start"></ion-icon>
Back to Menu
</ion-button>
<div v-if="!isInSession" class="browse-message">
<p>This is a preview. Start a session to make selections.</p>
</div>
</div>
</div>
</div>
</BaseLayout>
</template>
<script setup lang="ts">
import { onMounted, ref, computed } from "vue";
import { useRoute, useRouter } from "vue-router";
import { getDb } from "@/dataAccess/getDb";
import BaseLayout from "@/components/BaseLayout.vue";
import ShowCurrency from "@/components/ShowCurrency.vue";
import calculatePrice from "@/framework/calculatePrice";
import legacyFormula from "@/lib/legacyFormula";
import { useSessionStore } from "@/stores/session";
import { useOrganizationStore } from "@/stores/organization";
import {
IonIcon,
IonButton,
} from "@ionic/vue";
import { checkmarkCircle, arrowBack } from "ionicons/icons";
const sessionStore = useSessionStore();
const orgStore = useOrganizationStore();
const route = useRoute();
const router = useRouter();
const selectedTier = ref<any>(null);
const isInSession = computed(() => {
return (route.name as string) === "offers.confirm.job";
});
function getTierClass(tierName: string): string {
const name = tierName?.toLowerCase() || "";
if (name.includes("platinum")) return "tier-platinum";
if (name.includes("gold")) return "tier-gold";
if (name.includes("silver")) return "tier-silver";
if (name.includes("bronze")) return "tier-bronze";
if (name.includes("band")) return "tier-bandaid";
return "";
}
function getTierBadgeClass(tierName: string): string {
const name = tierName?.toLowerCase() || "";
if (name.includes("platinum")) return "badge-platinum";
if (name.includes("gold")) return "badge-gold";
if (name.includes("silver")) return "badge-silver";
if (name.includes("bronze")) return "badge-bronze";
if (name.includes("band")) return "badge-bandaid";
return "";
}
function populateFormulaVars(f: any, offer: any, extraCostMultiplier: number) {
f.vars.hourlyFee = orgStore.hourlyFee;
f.vars.serviceCallFee = orgStore.serviceCallFee;
f.vars.saDiscount = orgStore.saDiscount;
f.vars.salesTax = orgStore.saDiscount;
f.vars.multiplier = parseFloat(offer.multiplier);
f.vars.extraCostMultiplier = extraCostMultiplier;
return f;
}
async function confirmSelection() {
if (!isInSession.value || !selectedTier.value) return;
const jobId = route.params.jobId as string;
// Calculate the tier's base hours from costsTime
const tierBaseHours = selectedTier.value.offer?.costsTime?.reduce(
(sum: number, cost: any) => sum + (cost.hours || 0),
0
) || 0;
await sessionStore.setSelectedOffer(
jobId,
selectedTier.value.offer,
selectedTier.value.price.toString(),
selectedTier.value.name,
selectedTier.value.title,
tierBaseHours
);
router.push('/cart');
}
function goBack() {
router.back();
}
onMounted(async () => {
try {
const db = await getDb();
await orgStore.loadVariables();
await sessionStore.load();
const tierId = route.params.tierId as string;
const jobId = route.params.jobId as string;
let menuId = route.params.menuId as string;
let job = null;
if (isInSession.value && jobId) {
job = sessionStore.jobs.find((e) => e.id === jobId);
if (job && job.problem && (job.problem as any).menus) {
menuId = (job.problem as any).menus[0];
}
}
if (!menuId) {
console.error("No menuId found");
return;
}
if (db) {
const menuResponse = await db.menus.byMenuId(menuId);
if (menuResponse) {
const tier = menuResponse.tiers.find((t: any) => t.id === tierId);
if (tier) {
selectedTier.value = tier;
let extraCostMultiplier = 1;
if (job) {
extraCostMultiplier = job.extraTime * job.extraMaterial;
}
// TODO: we really need to extract and re-use this logic...
const f = populateFormulaVars(
legacyFormula(),
tier.offer,
extraCostMultiplier,
);
const result = await calculatePrice(
tier.offer.costsMaterial,
tier.offer.costsTime,
f,
);
selectedTier.value.price = result.finalPrice;
selectedTier.value.priceConverted = result.finalPriceConverted;
const titleItem = tier.contentItems?.find((e: any) =>
e.refId?.includes("_title"),
);
if (titleItem) {
selectedTier.value.title = titleItem.content;
}
const tierName = tier.name?.toLowerCase() || "";
if (tierName.includes("platinum")) {
selectedTier.value.warranty = "2 year limited warranty";
} else if (tierName.includes("gold")) {
selectedTier.value.warranty = "18 month limited warranty";
} else if (tierName.includes("silver")) {
selectedTier.value.warranty = "1 year limited warranty";
} else if (tierName.includes("bronze")) {
selectedTier.value.warranty = "6 month limited warranty";
} else if (tierName.includes("band")) {
selectedTier.value.warranty = "30 day limited warranty";
}
}
}
}
} catch (error) {
console.error("Error loading tier:", error);
}
});
</script>
<style scoped>
.confirmation-container {
padding: 20px;
max-width: 800px;
margin: 0 auto;
}
.content-section {
padding: 0 20px 20px 20px;
}
.tier-card {
background-color: var(--ion-color-dark);
border-radius: 12px;
overflow: hidden;
border: 2px solid transparent;
display: flex;
flex-direction: column;
margin-bottom: 24px;
}
/* Tier-specific border colors */
.tier-platinum {
border-color: var(--menu-color-platinum, #3db4d6);
}
.tier-gold {
border-color: var(--menu-color-gold, #ffd83b);
}
.tier-silver {
border-color: var(--menu-color-silver, #bfbfbf);
}
.tier-bronze {
border-color: var(--menu-color-bronze, #ffad2b);
}
.tier-bandaid {
border-color: var(--menu-color-bandaid, #ff8073);
}
.tier-badge {
padding: 16px 20px;
text-align: center;
font-weight: 700;
font-size: 24px;
text-transform: uppercase;
color: #000;
}
.badge-platinum {
background: linear-gradient(135deg, var(--menu-color-platinum, #3db4d6) 0%, var(--menu-color-platinum-tint, #5ec3dd) 100%);
}
.badge-gold {
background: linear-gradient(135deg, var(--menu-color-gold, #ffd83b) 0%, var(--menu-color-gold-tint, #ffe066) 100%);
}
.badge-silver {
background: linear-gradient(135deg, var(--menu-color-silver, #bfbfbf) 0%, var(--menu-color-silver-tint, #d4d4d4) 100%);
}
.badge-bronze {
background: linear-gradient(135deg, var(--menu-color-bronze, #ffad2b) 0%, var(--menu-color-bronze-tint, #ffc04d) 100%);
}
.badge-bandaid {
background: linear-gradient(135deg, var(--menu-color-bandaid, #ff8073) 0%, var(--menu-color-bandaid-tint, #ff9a8f) 100%);
}
.tier-name {
display: block;
line-height: 1.2;
}
.tier-content {
padding: 24px;
flex: 1;
display: flex;
flex-direction: column;
}
.tier-title {
margin: 0 0 20px 0;
color: #ffffff;
font-size: 20px;
font-weight: 600;
text-align: center;
line-height: 1.3;
}
.features-section {
flex: 1;
margin-bottom: 24px;
}
.feature-item {
margin-bottom: 8px;
}
.feature-text {
display: flex;
align-items: flex-start;
gap: 12px;
margin-bottom: 12px;
color: var(--ion-color-light);
font-size: 15px;
line-height: 1.5;
}
.feature-icon {
color: var(--ion-color-primary);
font-size: 20px;
flex-shrink: 0;
margin-top: 2px;
}
.pricing-section {
text-align: center;
margin-bottom: 20px;
padding-top: 20px;
border-top: 1px solid var(--ion-color-medium);
}
.price-amount {
color: #ffffff;
font-size: 32px;
font-weight: 700;
margin-bottom: 12px;
}
.warranty-text {
color: var(--ion-color-medium);
font-size: 13px;
font-weight: 500;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.actions-section {
display: flex;
flex-direction: column;
gap: 16px;
}
.browse-message {
text-align: center;
margin-top: 8px;
color: var(--ion-color-medium);
font-size: 14px;
}
.browse-message p {
margin: 0;
}
/* Mobile adjustments */
@media (max-width: 767px) {
.confirmation-container {
padding: 16px;
}
.tier-badge {
font-size: 20px;
padding: 12px 16px;
}
.tier-title {
font-size: 18px;
}
.feature-text {
font-size: 14px;
}
.price-amount {
font-size: 28px;
}
}
</style>