Skip to content

Commit 7f07f65

Browse files
committed
feat: add pricing columns to overview
1 parent ce61f22 commit 7f07f65

8 files changed

Lines changed: 399 additions & 16 deletions

File tree

docs/astro.config.mjs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@ import sitemap from "@astrojs/sitemap";
99

1010
import config from "./src/config/config.json";
1111

12+
const site = import.meta.env.MODE === "production" ? config.site.base_url : "http://localhost:4321";
13+
1214
// https://astro.build/config
1315
export default defineConfig({
14-
site: config.site.base_url,
16+
site,
1517
base: config.site.base_path,
1618
trailingSlash: config.site.trailing_slash ? "always" : "never",
1719
image: { service: sharp() },

docs/src/config/config.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
{
22
"site": {
33
"base_url": "https://rocketsim.app",
4+
"teams_base_url": "https://teams.rocketsim.app",
45
"base_path": "/",
56
"trailing_slash": false,
67
"title": "RocketSim",

docs/src/content.config.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import { glob } from "astro/loaders";
22
import { z, defineCollection } from "astro:content";
33

44
import { pricingCollection } from "./types/pages.collection";
5+
import { pricingSectionCollection } from "./types/sections.collections";
6+
57

68

79
const alignment = z.enum(["left", "right", "full-width"]);
@@ -34,4 +36,4 @@ const feature = defineCollection({
3436
}),
3537
});
3638

37-
export const collections = { feature, pricing: pricingCollection };
39+
export const collections = { feature, pricing: pricingCollection, pricingSectionCollection };
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
---
2+
title: "Choose a pricing plan best suited <em>for you</em>"
3+
4+
plans:
5+
- enable: true
6+
title: Trial
7+
description: Keen to try it out first? Sign up for a free two weeks trial.
8+
price_prefix: ''
9+
color: "#FEECA0" # TODO: change color
10+
features:
11+
- enable: true
12+
included: true
13+
feature: Pro access to RocketSim.
14+
- enable: true
15+
included: true
16+
feature: Team build insights.
17+
- enable: true
18+
included: true
19+
feature: User management.
20+
- enable: true
21+
included: true
22+
feature: License management.
23+
price:
24+
yearly:
25+
amount: FREE
26+
period: ''
27+
cta:
28+
enable: true
29+
label: Sign up for a free trial
30+
site: teams
31+
link: "/signup/trial"
32+
- enable: true
33+
title: Individual
34+
description: Perfect for solo developers.
35+
price_prefix: ""
36+
color: "#C9DEFD" # TODO: change color
37+
features:
38+
- enable: true
39+
included: true
40+
feature: Pro access to RocketSim.
41+
- enable: true
42+
included: false
43+
feature: Team build insights.
44+
- enable: true
45+
included: false
46+
feature: User management.
47+
- enable: true
48+
included: false
49+
feature: License management.
50+
price:
51+
yearly:
52+
amount: 50
53+
period: per year
54+
cta:
55+
enable: true
56+
label: Download now
57+
site: custom
58+
link: "https://apps.apple.com/app/apple-store/id1504940162?pt=117264678&ct=website-header&mt=8"
59+
class: "plausible-event-name=App+Store+Install"
60+
- enable: true
61+
title: Teams
62+
description: Perfect for teams with multiple developers.
63+
price_prefix: ""
64+
color: "#EFEAFE" # TODO: change color
65+
features:
66+
- enable: true
67+
included: true
68+
feature: Pro access to RocketSim.
69+
- enable: true
70+
included: true
71+
feature: Team build insights.
72+
- enable: true
73+
included: true
74+
feature: User management.
75+
- enable: true
76+
included: true
77+
feature: License management.
78+
price:
79+
yearly:
80+
amount: 120
81+
period: 'per year'
82+
cta:
83+
enable: true
84+
label: Subscribe
85+
site: custom
86+
link: "/contact" # TODO: link to Stripe
87+
- enable: false
88+
title: Enterprise
89+
description: For larger enterprises with complex requirements.
90+
price_prefix: ""
91+
color: "#C9DEFD" # TODO: change color
92+
features:
93+
- enable: true
94+
included: true
95+
feature: "Everything in Teams, and:"
96+
- enable: true
97+
included: true
98+
feature: SAML based SSO.
99+
- enable: true
100+
included: true
101+
feature: Distribution outside of the App Store.
102+
- enable: true
103+
included: true
104+
feature: Group based user management.
105+
price:
106+
yearly:
107+
amount: COMING SOON
108+
period: ''
109+
cta:
110+
enable: true
111+
label: Contact sales
112+
site: custom
113+
link: "mailto:ralphduin@rocketsim.app"
114+
---

docs/src/env.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
declare var plausible;
22
declare var AOS;
33

4-
declare module "aos";
4+
declare module "aos";
Lines changed: 231 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,231 @@
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

Comments
 (0)