rejuvallife/resources/js/pages/checkout.vue
2024-10-25 02:59:37 +05:00

731 lines
30 KiB
Vue

<script setup>
import StartOverPupup from '@/views/pages/home/StartOverPupup.vue';
import axios from '@axios';
import {
cardNumberValidator,
cvvValidator,
expiryValidator,
requiredValidator,
} from '@validators';
import { onBeforeMount, onMounted } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useStore } from 'vuex';
import Cart from '../layouts/components/cart.vue';
import CustomNav from '../layouts/components/navbar-custom.vue';
const store = useStore()
const router = useRouter()
const route = useRoute()
const isMobile = ref(window.innerWidth <= 768); // Assuming mobile width is less than or equal to 768px
const patient_id = localStorage.getItem('patient_id')
const access_token = localStorage.getItem('access_token');
const isTonalSnackbarVisible = ref(false)
const patientResponse = ref(false)
const isLoadingVisible = ref(false)
const paymentForm = ref()
const cardNumber = ref('')
const expiry = ref('')
const cvv = ref('')
const firstName = ref('')
const lastName = ref('')
const email = ref('')
const phone = ref('')
const address = ref('')
const apt = ref('')
const city = ref('')
const state = ref('')
const zipcode = ref('')
const billingaddress = ref('')
const billingapt = ref('')
const billingcity = ref('')
const billingstate = ref('')
const billingzipcode = ref('')
const termAndCondtiton = ref(true)
const billingSection = ref(false)
const schedule_consultant = ref(true)
const order_complete = ref(false);
const planName = ref(null)
const planAmount = ref(null)
const list_one_title = ref(null)
const list_sub_title = ref(null)
const list_two_title = ref(null)
const prescription_required = ref(null)
const shipping_price = ref(null)
const confirmPopup = ref(false)
const verifiedAddress = ref(null);
const suggestedStreet = ref(null);
const suggestedCity = ref(null);
const suggestedState = ref(null);
const suggestedZip = ref(null);
const suggestedAddress = ref(null);
const selectedAddress = ref('notsuggested')
const typedAddress = ref(null)
const seetingPlanLogo = ref();
const products = JSON.parse(localStorage.getItem('cart_products'));
const labKits = JSON.parse(localStorage.getItem('labkits'));
const totalShipping = ref(0)
const totalAmount = ref(0)
const grandTotal = ref(0)
const prescreptionRequired = ref(false)
const paymentPopup = ref(false)
const paymentPopupText = "Your payment for $"+labKits[0].amount+".00 was successful"
const states = ref([
{ name: 'Alabama', abbreviation: 'AL' },
{ name: 'Alaska', abbreviation: 'AK' },
{ name: 'Arizona', abbreviation: 'AZ' },
{ name: 'Arkansas', abbreviation: 'AR' },
{ name: 'Howland Island', abbreviation: 'UM-84' },
{ name: 'Delaware', abbreviation: 'DE' },
{ name: 'Maryland', abbreviation: 'MD' },
{ name: 'Baker Island', abbreviation: 'UM-81' },
{ name: 'Kingman Reef', abbreviation: 'UM-89' },
{ name: 'New Hampshire', abbreviation: 'NH' },
{ name: 'Wake Island', abbreviation: 'UM-79' },
{ name: 'Kansas', abbreviation: 'KS' },
{ name: 'Texas', abbreviation: 'TX' },
{ name: 'Nebraska', abbreviation: 'NE' },
{ name: 'Vermont', abbreviation: 'VT' },
{ name: 'Jarvis Island', abbreviation: 'UM-86' },
{ name: 'Hawaii', abbreviation: 'HI' },
{ name: 'Guam', abbreviation: 'GU' },
{ name: 'United States Virgin Islands', abbreviation: 'VI' },
{ name: 'Utah', abbreviation: 'UT' },
{ name: 'Oregon', abbreviation: 'OR' },
{ name: 'California', abbreviation: 'CA' },
{ name: 'New Jersey', abbreviation: 'NJ' },
{ name: 'North Dakota', abbreviation: 'ND' },
{ name: 'Kentucky', abbreviation: 'KY' },
{ name: 'Minnesota', abbreviation: 'MN' },
{ name: 'Oklahoma', abbreviation: 'OK' },
{ name: 'Pennsylvania', abbreviation: 'PA' },
{ name: 'New Mexico', abbreviation: 'NM' },
{ name: 'American Samoa', abbreviation: 'AS' },
{ name: 'Illinois', abbreviation: 'IL' },
{ name: 'Michigan', abbreviation: 'MI' },
{ name: 'Virginia', abbreviation: 'VA' },
{ name: 'Johnston Atoll', abbreviation: 'UM-67' },
{ name: 'West Virginia', abbreviation: 'WV' },
{ name: 'Mississippi', abbreviation: 'MS' },
{ name: 'Northern Mariana Islands', abbreviation: 'MP' },
{ name: 'United States Minor Outlying Islands', abbreviation: 'UM' },
{ name: 'Massachusetts', abbreviation: 'MA' },
{ name: 'Connecticut', abbreviation: 'CT' },
{ name: 'Florida', abbreviation: 'FL' },
{ name: 'District of Columbia', abbreviation: 'DC' },
{ name: 'Midway Atoll', abbreviation: 'UM-71' },
{ name: 'Navassa Island', abbreviation: 'UM-76' },
{ name: 'Indiana', abbreviation: 'IN' },
{ name: 'Wisconsin', abbreviation: 'WI' },
{ name: 'Wyoming', abbreviation: 'WY' },
{ name: 'South Carolina', abbreviation: 'SC' },
{ name: 'Arkansas', abbreviation: 'AR' },
{ name: 'South Dakota', abbreviation: 'SD' },
{ name: 'Montana', abbreviation: 'MT' },
{ name: 'North Carolina', abbreviation: 'NC' },
{ name: 'Palmyra Atoll', abbreviation: 'UM-95' },
{ name: 'Puerto Rico', abbreviation: 'PR' },
{ name: 'Colorado', abbreviation: 'CO' },
{ name: 'Missouri', abbreviation: 'MO' },
{ name: 'New York', abbreviation: 'NY' },
{ name: 'Maine', abbreviation: 'ME' },
{ name: 'Tennessee', abbreviation: 'TN' },
{ name: 'Georgia', abbreviation: 'GA' },
{ name: 'Louisiana', abbreviation: 'LA' },
{ name: 'Nevada', abbreviation: 'NV' },
{ name: 'Iowa', abbreviation: 'IA' },
{ name: 'Idaho', abbreviation: 'ID' },
{ name: 'Rhode Island', abbreviation: 'RI' },
{ name: 'Washington', abbreviation: 'WA' },
{ name: 'Ohio', abbreviation: 'OH' },
// ... (add the rest of the states)
]);
const sortedStates = computed(() => {
return states.value.slice().sort((a, b) => {
return a.name.localeCompare(b.name);
});
});
const errors = ref({
address: undefined,
city: undefined,
state: undefined,
zipcode: undefined,
country: undefined,
})
onBeforeMount(async () => {
products.forEach(product => {
var price = 0
if (product.is_prescription_required == 1) {
prescreptionRequired.value = true
if (labKits[0].amount)
price = labKits[0].amount
product.title = 'LabKit For ' + product.title
product.price = productTotalPreReq(price)
totalAmount.value += price;
console.log('Prescreption Req Price', price)
} else {
prescreptionRequired.value = false
price = parseFloat(product.price);
product.price = productTotal(product)
totalAmount.value += product.qty * price;
console.log('Not Req Price', price)
}
const shippingPrice = parseFloat(product.shipping_cost);
totalShipping.value += product.qty * shippingPrice;
});
let options = { style: 'currency', currency: 'USD', minimumFractionDigits: 2, maximumFractionDigits: 2 };
grandTotal.value = new Intl.NumberFormat('en-US', options).format(parseFloat(totalAmount.value) + parseFloat(totalShipping.value));
totalAmount.value = new Intl.NumberFormat('en-US', options).format(totalAmount.value);
totalShipping.value = new Intl.NumberFormat('en-US', options).format(totalShipping.value);
// grandTotal.value = parseFloat(totalAmount.value) + parseFloat(totalShipping.value)
store.dispatch('updateIsLoading', true)
store.dispatch('updateCurrentPage', 'checkout')
localStorage.setItem('currentPage', 'checkout')
await store.dispatch('getPatientInfo')
await store.dispatch('getPlanInfo')
// await store.dispatch('getPatientAppointment')
await store.dispatch('getAdditionalInformation')
//Plan Information
planName.value = store.getters.getPatientPlan.plan_name
planAmount.value = store.getters.getPatientPlan.plan_amount
list_one_title.value = store.getters.getPatientPlan.list_one_title
list_sub_title.value = store.getters.getPatientPlan.list_sub_title
list_two_title.value = store.getters.getPatientPlan.list_two_title
prescription_required.value = store.getters.getPatientPlan.prescription_required
shipping_price.value = store.getters.getPatientPlan.shipping_price
firstName.value = store.getters.getPatient.first_name
lastName.value = store.getters.getPatient.last_name
email.value = store.getters.getPatient.email
phone.value = store.getters.getPatient.phone_no
address.value = store.getters.getShippingInformation.address
apt.value = store.getters.getShippingInformation.shipping_address2
city.value = store.getters.getShippingInformation.shipping_city
state.value = store.getters.getShippingInformation.shipping_state
zipcode.value = store.getters.getShippingInformation.shipping_zipcode
billingaddress.value = store.getters.getShippingInformation.billing_address1
billingapt.value = store.getters.getShippingInformation.billing_address2
billingcity.value = store.getters.getShippingInformation.billing_city
billingstate.value = store.getters.getShippingInformation.billing_state
billingzipcode.value = store.getters.getShippingInformation.billing_zipcode
store.dispatch('updateIsLoading', false)
})
onMounted(async () => {
window.addEventListener('resize', checkIfMobile);
let setting = await axios.post('/api/settings', {})
console.log(setting.data)
seetingPlanLogo.value = '/assets/logo/' + setting.data.logo
})
// Detach event listener on component unmount
onUnmounted(() => {
window.removeEventListener('resize', checkIfMobile);
});
const productTotalPreReq = (price) => {
let options = { style: 'currency', currency: 'USD', minimumFractionDigits: 2, maximumFractionDigits: 2 };
return new Intl.NumberFormat('en-US', options).format(parseFloat(price));
};
const productTotal = (product) => {
let options = { style: 'currency', currency: 'USD', minimumFractionDigits: 2, maximumFractionDigits: 2 };
return new Intl.NumberFormat('en-US', options).format(product.qty * parseFloat(product.price));
};
const checkIfMobile = () => {
isMobile.value = window.innerWidth <= 768;
};
const billingAddressShow = () => {
if (billingSection.value == false) {
billingSection.value = true;
} else {
billingSection.value = false;
}
}
const corfirmFun = async () => {
confirmPopup.value = true;
if (address.value && city.value && state.value && zipcode.value)
typedAddress.value = address.value + ' ' + city.value + ' ' + state.value + ' ' + zipcode.value
if (address.value)
await verfiFyAddress(address.value);
}
const validatePayment = async () => {
const { valid: isValid } = await paymentForm.value?.validate();
console.log('isValid ', isValid);
if (isValid) {
await saveOrderInfo()
await processPayment()
// await updatePatientAddress()
// if (prescreptionRequired.value)
if (!store.getters.getErrorMessage) {
if (store.getters.getPaymentProcessed) {
paymentPopup.value = store.getters.getPaymentProcessed
setTimeout(() => {
router.replace(route.query.to && route.query.to !== '/checkout' ? String(route.query.to) : '/book-appointment')
}, 5000)
}
}
// else
// router.replace(route.query.to && route.query.to != '/checkout' ? String(route.query.to) : '/thankyou')
}
};
// const processPayment = async () => {
// isLoadingVisible.value = true;
// await axios.post('/api/process-payment')
// .then(response => {
// console.log(response.data)
// isLoadingVisible.value = false;
// })
// .catch(error => {
// isLoadingVisible.value = false;
// console.error(error);
// });
// }
const updatePatientAddress = async () => {
console.log('updatePatientAddress');
await store.dispatch('updatePatientAddress', {
address: address.value,
city: city.value,
state: state.value,
zip_code: zipcode.value,
country: 'United States',
})
}
const saveOrderInfo = async () => {
const productIds = products.map(product => ({ plans_id: product.id, quantity: product.qty, subscription: product.subscription, onetime: product.onetime }));
console.log('saveOrderInfo');
isLoadingVisible.value = true;
let addressVal = null
let cityVal = null
let stateVal = null
let zipcodeVal = null
if (selectedAddress.value == 'suggested') {
addressVal = suggestedAddress.value
cityVal = suggestedCity.value
stateVal = suggestedState.value
zipcodeVal = suggestedZip.value
} else {
addressVal = address.value
cityVal = city.value
stateVal = state.value
zipcodeVal = zipcode.value
}
await store.dispatch('saveShippingInformation', {
first_name: firstName.value,
last_name: lastName.value,
email: email.value,
phone: phone.value,
patient_id: patient_id,
shipping_address1: addressVal,
shipping_address2: apt.value,
shipping_city: cityVal,
shipping_state: stateVal,
shipping_zipcode: zipcodeVal,
shipping_country: 'United States',
billing_address1: billingaddress.value,
billing_address2: billingapt.value,
billing_city: billingcity.value,
billing_state: billingstate.value,
billing_zipcode: billingzipcode.value,
billing_country: "",
shipping_amount: parseFloat(totalShipping.value.replace(/[^0-9.-]+/g, "")),
total_amount: parseFloat(totalAmount.value.replace(/[^0-9.-]+/g, "")),
items: productIds
})
}
const verfiFyAddress = async (address) => {
isLoadingVisible.value = true;
suggestedAddress.value = null
let addressT = address
// let addressT = '11 pinewood Pi'
let cityt = 'BOWLING GREEN'
let statet = 'KY'
let zipt = '42101'
const geocoder = new google.maps.Geocoder();
geocoder.geocode({ address: addressT }, (results, status) => {
if (status === 'OK' && results.length > 0) {
verifiedAddress.value = results[0];
} else {
console.error('Geocode was not successful for the following reason:', status);
verifiedAddress.value = null;
}
console.log(verifiedAddress.value)
suggestedStreet.value = getStreetAddress()
suggestedCity.value = getAddressComponent('locality')
suggestedState.value = getAddressComponent('administrative_area_level_1', true)
suggestedZip.value = getAddressComponent('postal_code')
if (suggestedStreet.value && suggestedCity.value && suggestedState.value && suggestedZip.value)
suggestedAddress.value = `${suggestedStreet.value}, ${suggestedCity.value}, ${suggestedState.value} ${suggestedZip.value}`;
console.log(suggestedAddress.value)
console.log('street : ', getStreetAddress())
console.log('city : ', getAddressComponent('locality'))
console.log('state : ', getAddressComponent('administrative_area_level_1'))
console.log('zip : ', getAddressComponent('postal_code'))
// const response = await axios.get('http://production.shippingapis.com/ShippingAPI.dll', {
// params: {
// API: 'Verify',
// XML: `<AddressValidateRequest USERID="201CBSUR1218"><Address>${addressT}</Address><City>${cityt}</City><State>${statet}</State><Zip5>${zipt}</Zip5></AddressValidateRequest>`
// }
// });
isLoadingVisible.value = false;
})
}
const getStreetAddress = () => {
if (!verifiedAddress.value) return '';
return verifiedAddress.value.formatted_address.split(',')[0];
};
const getAddressComponent = (type, shortname) => {
if (!verifiedAddress.value) return '';
for (const component of verifiedAddress.value.address_components) {
if (component.types.includes(type)) {
if (shortname) {
return component.short_name
} else {
return component.long_name;
}
}
}
return '';
};
const processPayment = async () => {
// Split the string by "/"
let [month, year] = expiry.value.split("/");
year = year.length === 2 ? (year >= "50" ? "19" + year : "20" + year) : year;
console.log('year',expiry.value,year)
await store.dispatch('processPayment',{
card_number: cardNumber.value,
cvv: cvv.value,
expiration_month: month,
expiration_year: year
})
}
const cardNumberFormat = () => {
cardNumber.value = cardNumber.value.replace(/\D/g, '').substring(0, 16);
};
const formatExpiry = () => {
// Automatically format the input to MM/YY format
expiry.value = expiry.value.replace(/\D/g, '').slice(0, 4).replace(/(\d{2})(\d{2})/, '$1/$2');
};
const handleCVVInput = () => {
// Remove non-digit characters from input
cvv.value = cvv.value.replace(/\D/g, '');
};
watch(selectedAddress, (newValue) => {
console.log('selectedAddress.value',selectedAddress.value)
if (newValue === 'suggested') {
address.value = suggestedStreet.value;
apt.value = suggestedStreet.value;
city.value = suggestedCity.value;
state.value = suggestedState.value;
zipcode.value = suggestedZip.value;
} else if (newValue === 'notsuggested') {
// Split the typedAddress into its components
const addressParts = typedAddress.value.split(' ');
address.value = addressParts.slice(0, -3).join(' ');
apt.value = addressParts.slice(0, -3).join(' ');
city.value = addressParts[addressParts.length - 3];
state.value = addressParts[addressParts.length - 2];
zipcode.value = addressParts[addressParts.length - 1];
}
});
const backFun = () => {
store.dispatch('updateIsLoading', true)
router.replace(route.query.to && route.query.to != '/checkout' ? String(route.query.to) : '/additional-information')
}
</script>
<template>
<StartOverPupup :showPopup="store.getters.getShowStartOverPupup"></StartOverPupup>
<VDialog v-model="store.getters.getIsLoading" width="110" height="150" color="yellow-theme-button">
<VCardText class="" style="color: white !important;">
<div class="demo-space-x">
<VProgressCircular :size="40" color="yellow-theme-button" indeterminate />
</div>
</VCardText>
</VDialog>
<VSnackbar
v-model="store.getters.getIsTonalSnackbarVisible"
:timeout="5000"
location="top end"
variant="flat"
color="red"
>
{{ store.getters.getErrorMessage }}
</VSnackbar>
<v-dialog
v-model="paymentPopup"
width="auto"
>
<v-card
max-width="400"
prepend-icon="mdi-checkbox-marked-circle"
:text=paymentPopupText
title="Payment Success"
>
</v-card>
</v-dialog>
<VRow><CustomNav :logo='seetingPlanLogo'></CustomNav></VRow>
<VRow
style="min-height: 100dvh; margin: 0px;"
:style="isMobile ? { marginTop: '90px' } : { marginTop: '80px' }"
>
<VCol cols="12" md="6" class="bg-custom col-order-1"
:class="isMobile ? '' : ' d-flex align-center justify-center pa-4'">
<Cart></Cart>
</VCol>
<VCol cols="12" md="6" class="bg-custom-color col-order-2"
:class="isMobile ? '' : 'auth-wrapper d-flex align-center justify-center pa-4'">
<VCard class="auth-card pa-2 rounded-5" style="" :class="isMobile ? '' : 'card-wid'">
<VRow class=" mx-0 gy-3">
<!-- <VCol cols="12" lg="12" md="12">
<router-link to="/" class="text-center mb-2 mt-2"
style="width: 100%;position: relative;display: block;padding-top: 20px;">
<span class="text-center">
<VImg :src="seetingPlanLogo" width="250" height="50" class="logo-img" />
</span>
</router-link>
</VCol> -->
<VCol cols="12" md="12">
<v-card class="px-0" flat>
<VForm ref="paymentForm" @submit.prevent="() => { }">
<!-- <v-card class="" flat> -->
<div class="mb-3">
<h5 class="text-h5 mb-1 text-left mb-4">Shipping Information</h5>
<small>Please provide your shipping details below so that we can
promptly send you
the
product:</small>
</div>
<VRow>
<VCol cols="12" md="12">
<VTextField v-model="address" label="Address" :rules="[requiredValidator]"
density="comfortable" />
</VCol>
<VCol cols="12" md="12">
<VTextField v-model="apt" label="APT/Suite #" density="comfortable" />
</VCol>
</VRow>
<VRow>
<VCol cols="12" md="4">
<VTextField v-model="city" label="City" :rules="[requiredValidator]"
density="comfortable" />
</VCol>
<VCol cols="12" md="5">
<v-autocomplete clearable v-model="state" label="Select State"
:items="sortedStates" item-title="name" item-value="abbreviation"
:rules="[requiredValidator]" :error-messages="errors.state"
density="comfortable">
</v-autocomplete>
</VCol>
<VCol cols="12" md="3">
<VTextField type="number" v-model="zipcode" :rules="[requiredValidator]"
label="ZipCode" density="comfortable" />
</VCol>
</VRow>
<div class="mb-3">
<h4 class="mb-2 mt-4">
Card Information &nbsp;<VIcon>mdi-credit-card</VIcon>
</h4>
</div>
<VRow>
<VCol cols="12" lg="12" md="12">
<VTextField v-model="cardNumber" label="Credit Card Number*"
:rules="[requiredValidator, cardNumberValidator]"
placeholder="xxxxxxxxxxxxxxxx" @input="cardNumberFormat"
density="comfortable" />
</VCol>
<!-- <VCol cols="12" lg="4" md="4">
<VTextField v-model="zipcode" label="Zipcode*" type="number"
:rules="[requiredValidator]" placeholder="zipcode" density="comfortable"/>
</VCol> -->
<VCol cols="12" lg="6" md="6">
<VTextField v-model="expiry" label="Expiration Date*"
:rules="[requiredValidator, expiryValidator]" placeholder="MM/YY"
@input="formatExpiry" density="comfortable" />
</VCol>
<VCol cols="12" lg="6" md="6">
<VTextField v-model="cvv" :rules="[requiredValidator, cvvValidator]"
label="CVV*" maxlength="3" @input="handleCVVInput" density="comfortable" />
</VCol>
</VRow>
<!-- <VRow>
<VCol cols="12" md="12" class="px-4 mt-3">
<VCheckbox v-model="termAndCondtiton" @click=billingAddressShow
label="Billing Address same as shipping." />
</VCol>
</VRow> -->
<!-- </v-card> -->
<v-card class="px-2 mt-2 mb-2" flat v-if="billingSection">
<h3 class="mb-3">Billing Information</h3>
<VRow>
<VCol cols="12" md="6">
<VTextField v-model="billingaddress" label="Address"
:rules="[requiredValidator]" density="comfortable" />
</VCol>
<VCol cols="12" md="6">
<VTextField v-model="billingapt" label="APT/Suite #"
density="comfortable" />
</VCol>
</VRow>
<VRow>
<VCol cols="12" md="6">
<VTextField v-model="billingcity" label="City" :rules="[requiredValidator]"
density="comfortable" />
</VCol>
</VRow>
</v-card>
<div class="text-center mb-2 mt-4">
<VBtn class="px-4 mb-2" color="primary" variant="flat" block @click="corfirmFun"
style="background-color: rgb(var(--v-theme-yellow-theme-button)) !important;">
Checkout</VBtn>
<!-- <VBtn class="px-4" color="grey" variant="flat" @click="backFun"
:class="isMobile ? '' : 'mr-2'" block>
Back</VBtn> -->
</div>
<VDialog v-model="confirmPopup" refs="myDialog" persistent width="500">
<!-- <template v-slot:default="{ isActive }"> -->
<v-card>
<v-card-text>
<div class="mt-2 mb-2">
<h4>We've Found a Match from your Address.</h4>
<small>Select the correct address that match your current
address</small>
</div>
<v-radio-group v-model="selectedAddress" :rules="[requiredValidator]"
v-if="typedAddress || suggestedAddress">
<v-radio :label="suggestedAddress" v-if="suggestedAddress"
value="suggested"></v-radio>
<v-radio :label="typedAddress" value="notsuggested"
v-if="typedAddress"></v-radio>
</v-radio-group>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn type="submit" text="Confirm" @click="validatePayment"></v-btn>
<v-btn text="Close" @click="confirmPopup = false"></v-btn>
</v-card-actions>
</v-card>
<!-- </template> -->
</VDialog>
</VForm>
</v-card>
</VCol>
</VRow>
</VCard>
</VCol>
</VRow>
</template>
<style scoped>
@media only screen and (max-width: 768px) {
.card-wid {
max-width: 600px !important;
min-width: auto !important;
}
.col-order-1 {
order: 2;
}
.col-order-2 {
order: 1;
}
}
@media only screen and (min-width: 769px) {
.col-order-1 {
order: 1;
}
.col-order-2 {
order: 2;
}
}
.total-font {
font-size: 20px;
margin-bottom: 5px;
}
.bg-custom {
background: #fffeee;
}
.bg-custom-color {
background: #f1e7e4;
}
.bg-white bg-change-bk .current-plan {
border: 2px solid rgb(var(--v-theme-primary));
}
.cut-text {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
text-decoration: line-through;
text-decoration-color: red;
text-decoration-thickness: 1px;
}
.plan-card {
margin: 0rem;
margin-bottom: 0;
}
.card-wid {
max-width: 600px;
}
.layout-wrapper {
justify-content: center;
}
.error-message {
color: #ff2f2f;
font-size: 15px;
}
</style>
<style lang="scss">
.bg-custom {
background: #fffeee;
}
.bg-custom-color {
background: #f1e7e4;
}
.total-font {
font-size: 20px;
margin-bottom: 5px;
}
.card-title {
font-family: "Public Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
}
.logo-img {
display: block;
position: relative;
margin: 0 auto;
}
</style>