|
| 1 | +--- |
| 2 | +import { markdownify } from "@/lib/utils/textConverter"; |
| 3 | +import type { CollectionEntry } from "astro:content"; |
| 4 | +import { getEntry } from "astro:content"; |
| 5 | +
|
| 6 | +import config from "src/config/config.json"; |
| 7 | +
|
| 8 | +const { isPageHeader } = Astro.props; |
| 9 | +const sectionData = (await getEntry( |
| 10 | + "pricingSectionCollection", |
| 11 | + "pricing" |
| 12 | +)) as CollectionEntry<"pricingSectionCollection">; |
| 13 | +const { title, plans } = sectionData.data; |
| 14 | +
|
| 15 | +const teamsUrl = import.meta.env.MODE === "production" ? config.site.teams_base_url : 'http://localhost:5173'; |
| 16 | +--- |
| 17 | + |
| 18 | +<section class={isPageHeader ? "" : "section"}> |
| 19 | + <div class="container"> |
| 20 | + <div class="section-container"> |
| 21 | + <div class="section-intro centralize"> |
| 22 | + <!-- Header Area --> |
| 23 | + { |
| 24 | + !isPageHeader && ( |
| 25 | + <h2 |
| 26 | + class="title hasEmphasize" |
| 27 | + data-aos="" |
| 28 | + set:html={markdownify(title)} |
| 29 | + /> |
| 30 | + ) |
| 31 | + } |
| 32 | + </div> |
| 33 | + <div class="section-content"> |
| 34 | + <div class="grid md:grid-cols-2 xl:grid-cols-3 justify-center gap-6"> |
| 35 | + { |
| 36 | + plans && |
| 37 | + plans.length && |
| 38 | + plans.map((plan, index) => { |
| 39 | + const isEvenIndex = index % 2 !== 0; |
| 40 | + const cardColor = plan.color || "bg-secondary/80"; |
| 41 | + |
| 42 | + return ( |
| 43 | + <div data-aos="fade-up-sm" data-aos-delay={index * 100}> |
| 44 | + <div |
| 45 | + class:list={[ |
| 46 | + "p-8 rounded-[1.9rem] flex flex-col justify-between relative transition-shadow duration-300 hover:shadow-xl", |
| 47 | + { "bg-light text-text-dark ": isEvenIndex }, |
| 48 | + { "bg-white text-text-dark": !isEvenIndex }, |
| 49 | + ]} |
| 50 | + style={`background-color: ${cardColor}`} |
| 51 | + > |
| 52 | + <div> |
| 53 | + <span |
| 54 | + class="h6 mb-7 inline-block" |
| 55 | + set:html={markdownify(plan.title)} |
| 56 | + /> |
| 57 | + {/* PRICE NUMBERS */} |
| 58 | + <div class="flex items-baseline gap-2 mb-5"> |
| 59 | + <h3 class="flex items-center text-text-dark text-h2"> |
| 60 | + <span>{plan.price_prefix}</span> |
| 61 | + <span |
| 62 | + class="data-count" |
| 63 | + data-count-yearly={plan.price.yearly.amount} |
| 64 | + set:html={plan.price.yearly.amount} |
| 65 | + /> |
| 66 | + </h3> |
| 67 | + <div class="flex flex-col gap-1 text-text"> |
| 68 | + <span class="text-yearly capitalize"> |
| 69 | + {plan.price.yearly.period} |
| 70 | + </span> |
| 71 | + </div> |
| 72 | + </div> |
| 73 | + <p |
| 74 | + class="text-text " |
| 75 | + set:html={markdownify(plan.description)} |
| 76 | + /> |
| 77 | + |
| 78 | + <hr class:list={["my-14 border-text-dark/20"]} /> |
| 79 | + <ul class="flex flex-col gap-5"> |
| 80 | + {plan.features && |
| 81 | + plan.features.length && |
| 82 | + plan.features |
| 83 | + .filter((filter) => filter.enable) |
| 84 | + .map((feature) => { |
| 85 | + const isIncluded = feature.included; |
| 86 | + return ( |
| 87 | + <li class="flex items-center gap-x-5 text-text-dark"> |
| 88 | + {!isIncluded ? ( |
| 89 | + <svg |
| 90 | + width="24" |
| 91 | + height="24" |
| 92 | + viewBox="0 0 24 24" |
| 93 | + fill="none" |
| 94 | + class="text-primary w-full max-w-6 aspect-square " |
| 95 | + xmlns="http://www.w3.org/2000/svg" |
| 96 | + > |
| 97 | + <g opacity="0.4"> |
| 98 | + <path |
| 99 | + fill-rule="evenodd" |
| 100 | + clip-rule="evenodd" |
| 101 | + d="M1.5 12C1.5 6.20101 6.20101 1.5 12 1.5C17.799 1.5 22.5 6.20101 22.5 12C22.5 17.799 17.799 22.5 12 22.5C6.20101 22.5 1.5 17.799 1.5 12ZM9.87896 8.81803C9.58607 8.52513 9.1112 8.52513 8.8183 8.81803C8.52541 9.11092 8.52541 9.5858 8.8183 9.87869L10.9396 12L8.81831 14.1213C8.52542 14.4142 8.52542 14.8891 8.81831 15.182C9.1112 15.4749 9.58608 15.4749 9.87897 15.182L12.0003 13.0607L14.1216 15.182C14.4145 15.4749 14.8894 15.4749 15.1823 15.182C15.4752 14.8891 15.4752 14.4142 15.1823 14.1213L13.0609 12L15.1823 9.87869C15.4752 9.5858 15.4752 9.11092 15.1823 8.81803C14.8894 8.52513 14.4145 8.52513 14.1216 8.81803L12.0003 10.9394L9.87896 8.81803Z" |
| 102 | + fill="currentColor" |
| 103 | + /> |
| 104 | + </g> |
| 105 | + </svg> |
| 106 | + ) : ( |
| 107 | + <svg |
| 108 | + width="24" |
| 109 | + height="24" |
| 110 | + viewBox="0 0 24 24" |
| 111 | + fill="none" |
| 112 | + class="text-primary w-full max-w-6 aspect-square" |
| 113 | + xmlns="http://www.w3.org/2000/svg" |
| 114 | + > |
| 115 | + <path |
| 116 | + fill-rule="evenodd" |
| 117 | + clip-rule="evenodd" |
| 118 | + d="M1.5 12C1.5 6.20101 6.20101 1.5 12 1.5C17.799 1.5 22.5 6.20101 22.5 12C22.5 17.799 17.799 22.5 12 22.5C6.20101 22.5 1.5 17.799 1.5 12ZM15.7127 10.7197C16.0055 10.4268 16.0055 9.95192 15.7127 9.65903C15.4198 9.36614 14.9449 9.36614 14.652 9.65903L10.9397 13.3713L9.34869 11.7804C9.0558 11.4875 8.58092 11.4875 8.28803 11.7804C7.99514 12.0732 7.99514 12.5481 8.28803 12.841L10.4093 14.9623C10.7022 15.2552 11.1771 15.2552 11.47 14.9623L15.7127 10.7197Z" |
| 119 | + fill="currentColor" |
| 120 | + /> |
| 121 | + </svg> |
| 122 | + )} |
| 123 | + |
| 124 | + <span |
| 125 | + class={`${isIncluded ? "text-text-dark" : "text-text-light"}`} |
| 126 | + > |
| 127 | + {feature.feature} |
| 128 | + </span> |
| 129 | + </li> |
| 130 | + ); |
| 131 | + })} |
| 132 | + </ul> |
| 133 | + </div> |
| 134 | + |
| 135 | + {/* CTA BUTTON */} |
| 136 | + {plan.cta.enable && ( |
| 137 | + <a |
| 138 | + href={plan.cta.site === 'custom' ? plan.cta.link : `${plan.cta.site === 'teams' ? teamsUrl : ''}${plan.cta.link}`} |
| 139 | + class:list={[ |
| 140 | + "btn inline-block self-start mt-14 text-center rounded-full", |
| 141 | + { |
| 142 | + "btn btn-primary bg-text-dark border-text-dark": |
| 143 | + isEvenIndex, |
| 144 | + }, |
| 145 | + { "btn btn-outline-secondary": !isEvenIndex }, |
| 146 | + plan.cta.class |
| 147 | + ]} |
| 148 | + > |
| 149 | + <span>{plan.cta.label}</span> |
| 150 | + </a> |
| 151 | + )} |
| 152 | + </div> |
| 153 | + </div> |
| 154 | + ); |
| 155 | + }) |
| 156 | + } |
| 157 | + </div> |
| 158 | + </div> |
| 159 | + </div> |
| 160 | + </div> |
| 161 | +</section> |
| 162 | + |
| 163 | +<script> |
| 164 | + function pricingInit() { |
| 165 | + // Select the toggle switch element |
| 166 | + const toggleSwitch = |
| 167 | + document.querySelector<HTMLInputElement>(".pricing-check"); |
| 168 | + const numbers = document.querySelectorAll<HTMLDivElement>(".data-count"); |
| 169 | + toggleSwitch && |
| 170 | + toggleSwitch.addEventListener("change", function () { |
| 171 | + if (toggleSwitch.checked) { |
| 172 | + numbers.forEach(function (number) { |
| 173 | + const yearlyCount = number.getAttribute("data-count-yearly"); |
| 174 | + if (yearlyCount && !isNaN(parseInt(yearlyCount, 10))) { |
| 175 | + number.innerHTML = yearlyCount; |
| 176 | + animateCounter(number, parseInt(yearlyCount, 10)); |
| 177 | + } else { |
| 178 | + number.innerHTML = yearlyCount as string; |
| 179 | + } |
| 180 | + }); |
| 181 | + toggleVisibility(".text-yearly", "d-block", "hidden"); |
| 182 | + toggleVisibility(".text-monthly", "hidden", "d-block"); |
| 183 | + } else { |
| 184 | + numbers.forEach(function (number) { |
| 185 | + const monthlyCount = number.getAttribute("data-count-monthly"); |
| 186 | + if (monthlyCount && !isNaN(parseInt(monthlyCount, 10))) { |
| 187 | + number.innerHTML = monthlyCount; |
| 188 | + animateCounter(number, parseInt(monthlyCount, 10)); |
| 189 | + } else { |
| 190 | + number.innerHTML = monthlyCount as string; |
| 191 | + } |
| 192 | + }); |
| 193 | + toggleVisibility(".text-monthly", "block", "hidden"); |
| 194 | + toggleVisibility(".text-yearly", "hidden", "d-block"); |
| 195 | + } |
| 196 | + }); |
| 197 | + |
| 198 | + function animateCounter(element: HTMLElement, endValue: number) { |
| 199 | + let startValue = 0; |
| 200 | + const duration = 300; |
| 201 | + let startTime: number | null = null; |
| 202 | + |
| 203 | + function step(currentTime: number) { |
| 204 | + if (!startTime) startTime = currentTime; |
| 205 | + const progress = currentTime - startTime; |
| 206 | + const value = |
| 207 | + Math.min(progress / duration, 1) * (endValue - startValue) + |
| 208 | + startValue; |
| 209 | + element.innerHTML = Math.ceil(value).toString(); |
| 210 | + if (progress < duration) { |
| 211 | + requestAnimationFrame(step); |
| 212 | + } |
| 213 | + } |
| 214 | + |
| 215 | + requestAnimationFrame(step); |
| 216 | + } |
| 217 | + |
| 218 | + function toggleVisibility( |
| 219 | + selector: string, |
| 220 | + addClass: string, |
| 221 | + removeClass: string |
| 222 | + ) { |
| 223 | + const elements = document.querySelectorAll<HTMLElement>(selector); |
| 224 | + elements.forEach(function (element) { |
| 225 | + element.classList.add(addClass); |
| 226 | + element.classList.remove(removeClass); |
| 227 | + }); |
| 228 | + } |
| 229 | + } |
| 230 | + document.addEventListener("astro:page-load", pricingInit); |
| 231 | +</script> |
0 commit comments