Hello from MCP server
<template>
<ion-card
:class="[tierClass, { 'tier-selected': selected }]"
:button="clickable"
@click="handleClick"
>
<ion-card-header>
<ion-card-title class="main-title">
{{ tierName?.toUpperCase() }}
</ion-card-title>
<ion-card-subtitle class="subtitle">
{{ tierTitle }}
</ion-card-subtitle>
</ion-card-header>
<ion-card-content>
<ion-grid>
<ion-row>
<ion-col size="12" size-sm="8" size-md="9" size-lg="10">
<ion-list>
<template v-if="menuCopy">
<template v-for="copy in menuCopy" :key="copy.id">
<ion-item
v-for="item in copy.contentItems"
:key="item.id"
class="service-item"
>
{{ item.content }}
</ion-item>
</template>
</template>
<template v-else-if="contentItems">
<ion-item
v-for="item in contentItems"
:key="item.id"
v-show="!item.refId?.includes('_title')"
class="service-item"
>
{{ item.content }}
</ion-item>
</template>
</ion-list>
</ion-col>
<ion-col
size="12"
size-sm="4"
size-md="3"
size-lg="2"
class="price-col"
>
<div class="price-container">
<!-- Payment plan view -->
<template v-if="showPlan">
<div v-if="showDiscount && discountPriceConverted" class="discount-price">
{{ discountMonthlyPaymentConverted }}/mo
</div>
<div class="price-amount" :class="{ 'price-crossed': showDiscount && discountPriceConverted }">
{{ monthlyPaymentConverted }}/mo
</div>
</template>
<!-- Regular price view -->
<template v-else>
<div v-if="showDiscount && discountPriceConverted" class="discount-price">
{{ stripCents(discountPriceConverted) }}
</div>
<div class="price-amount" :class="{ 'price-crossed': showDiscount && discountPriceConverted }">
{{ stripCents(priceConverted) }}
</div>
</template>
</div>
<div class="warranty-text" v-if="warranty">
{{ warranty }}
</div>
</ion-col>
</ion-row>
</ion-grid>
</ion-card-content>
</ion-card>
</template>
<script setup lang="ts">
import { computed } from 'vue';
import {
IonCard,
IonCardHeader,
IonCardTitle,
IonCardSubtitle,
IonCardContent,
IonGrid,
IonRow,
IonCol,
IonList,
IonItem,
} from '@ionic/vue';
import { useTnfrStore } from '@/stores/tnfr';
interface ContentItem {
id: string;
content: string;
refId?: string;
}
interface MenuCopy {
id: string;
contentItems: ContentItem[];
}
const tnfrStore = useTnfrStore();
const props = withDefaults(defineProps<{
tierName: string;
tierTitle?: string;
tierClass: string;
menuCopy?: MenuCopy[];
contentItems?: ContentItem[];
price: string | number;
priceConverted?: string;
discountPrice?: string | number;
discountPriceConverted?: string;
warranty?: string;
showDiscount?: boolean;
showPlan?: boolean;
selected?: boolean;
clickable?: boolean;
}>(), {
tierTitle: '',
priceConverted: '',
discountPriceConverted: '',
warranty: '',
showDiscount: false,
showPlan: false,
selected: false,
clickable: true,
});
const emit = defineEmits<{
click: [];
}>();
// Extract numeric value from formatted price string (e.g., "$1,234" -> 1234)
function parseFormattedPrice(formatted: string | undefined): number {
if (!formatted) return 0;
// Remove currency symbol and commas, then parse
const numericStr = formatted.replace(/[^0-9.]/g, '');
return parseFloat(numericStr) || 0;
}
// Monthly payment for regular price (converted)
const monthlyPaymentConverted = computed(() => {
// Use converted price value for calculation
const principal = parseFormattedPrice(props.priceConverted);
if (principal <= 0) return '';
const monthly = tnfrStore.calculateMonthlyPayment(principal);
// Extract symbol from priceConverted (e.g., "$123" -> "$")
const symbol = props.priceConverted?.match(/^[^0-9]*/)?.[0] || '$';
return `${symbol}${Math.ceil(monthly).toLocaleString()}`;
});
// Monthly payment for discount price (converted)
const discountMonthlyPaymentConverted = computed(() => {
if (!props.discountPriceConverted) return '';
// Use converted discount price value for calculation
const principal = parseFormattedPrice(props.discountPriceConverted);
if (principal <= 0) return '';
const monthly = tnfrStore.calculateMonthlyPayment(principal);
const symbol = props.priceConverted?.match(/^[^0-9]*/)?.[0] || '$';
return `${symbol}${Math.ceil(monthly).toLocaleString()}`;
});
// Strip decimal portion from price string (e.g., "$123.45" -> "$123")
function stripCents(price: string | undefined): string {
if (!price) return '';
return price.split('.')[0];
}
const handleClick = () => {
if (props.clickable) {
emit('click');
}
};
</script>
<style scoped>
.tier-platinum {
--background: var(--menu-color-platinum-tint, #d0f5fe);
border-left: 30px solid var(--menu-color-platinum, #3db4d6);
}
.tier-gold {
--background: var(--menu-color-gold-tint, #fde791);
border-left: 30px solid var(--menu-color-gold, #ffd83b);
}
.tier-silver {
--background: var(--menu-color-silver-tint, #f0f0f0);
border-left: 30px solid var(--menu-color-silver, #bfbfbf);
}
.tier-bronze {
--background: var(--menu-color-bronze-tint, #fcc987);
border-left: 30px solid var(--menu-color-bronze, #ffad2b);
}
.tier-bandaid {
--background: var(--menu-color-bandaid-tint, #ffd1d1);
border-left: 30px solid var(--menu-color-bandaid, #ff8073);
}
ion-card {
position: relative;
border-radius: 0;
cursor: pointer;
transition: transform 0.2s ease-in-out;
margin: 0;
}
ion-card:hover {
transform: translateY(-2px);
}
.tier-selected {
border: 4px solid var(--ion-color-primary) !important;
box-shadow: 0 0 12px rgba(var(--ion-color-primary-rgb), 0.5);
}
ion-card-header {
padding: 6px 10px 0px 10px;
margin-bottom: -8px;
}
ion-card-content {
padding: 6px 10px 8px 10px !important;
}
.card-content-ios {
padding-inline-start: 5px;
padding-bottom: 5px;
}
.subtitle {
font-size: 16px;
font-weight: 700;
margin: 4px 0px 0px 0px;
color: #000;
font-variant: small-caps;
text-transform: lowercase;
letter-spacing: 0;
line-height: 1.1;
}
.main-title {
font-size: 20px;
font-weight: 700;
line-height: 1;
margin: 2px 0 0;
color: #000;
text-transform: uppercase;
}
ion-list {
background: transparent;
padding: 0;
}
.service-item {
--background: transparent;
--border-style: none;
--inner-border-width: 0;
--padding-start: 12px;
--padding-end: 0;
--padding-top: 1px;
--padding-bottom: 1px;
font-size: 14px;
line-height: 1.2;
color: #000;
--min-height: 18px;
position: relative;
}
.service-item::before {
content: "•";
position: absolute;
left: 0;
color: #000;
font-weight: bold;
}
.price-col {
display: flex;
flex-direction: column;
justify-content: center;
align-items: flex-end;
}
.price-container {
display: flex;
flex-direction: column;
align-items: flex-end;
margin-bottom: 4px;
}
.price-amount {
font-size: 28px;
font-weight: 700;
color: #000;
line-height: 1;
}
.price-crossed {
text-decoration: line-through;
opacity: 0.6;
font-size: 20px;
}
.discount-price {
font-size: 28px;
font-weight: 700;
color: #000;
line-height: 1;
margin-bottom: 2px;
}
.warranty-text {
font-size: 12px;
color: #000;
text-align: right;
margin-top: 2px;
font-weight: 600;
}
/* Mobile adjustments */
@media (max-width: 767px) {
.price-col {
align-items: flex-start;
margin-top: 10px;
}
.price-container {
align-items: flex-start;
}
.warranty-text {
text-align: left;
}
}
</style>