Hello from MCP server

List Files | Just Commands | Repo | Logs

← back |
<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>