Skip to content

Commit 3bc5feb

Browse files
committed
pricing
1 parent 2293476 commit 3bc5feb

File tree

7 files changed

+350
-208
lines changed

7 files changed

+350
-208
lines changed

app/components/Pricing.vue

Lines changed: 0 additions & 173 deletions
This file was deleted.
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
<script setup lang="ts">
2+
import { computed, ref } from 'vue'
3+
4+
const FREE_TOKENS = 1000
5+
const MAX_PRICE = 1500
6+
7+
const userCountSteps: Record<number, number> = {
8+
0: 30,
9+
1: 250,
10+
2: 500,
11+
3: 1000,
12+
4: 2500,
13+
5: 5000,
14+
6: 7500,
15+
7: 10_000,
16+
8: 20_000,
17+
9: 50_000,
18+
10: 100_000,
19+
11: 250_000,
20+
}
21+
const currentStep = ref(0)
22+
const userCount = computed(() => {
23+
return userCountSteps[currentStep.value]!
24+
})
25+
26+
const loginsFrequency: Record<number, { name: string, amount: number }> = {
27+
0: { name: 'Month', amount: 1 },
28+
1: { name: 'Week', amount: 4 },
29+
2: { name: 'Workday', amount: 20 },
30+
3: { name: 'Day', amount: 30 },
31+
}
32+
const currentLoginsPerMonth = ref(0)
33+
const loginsPerMonth = computed(() => loginsFrequency[currentLoginsPerMonth.value]!)
34+
35+
const tokensPerMonth = computed(() => loginsPerMonth.value.amount * userCount.value)
36+
const price = computed(() => (tokensPerMonth.value <= FREE_TOKENS ? 0 : Math.ceil(tokensPerMonth.value / 10000) * 10))
37+
const formattedPrice = computed(() => {
38+
if (price.value >= MAX_PRICE) {
39+
return `${MAX_PRICE}+`
40+
}
41+
return price.value
42+
})
43+
const mauPrice = computed(() => (price.value / userCount.value).toFixed(3))
44+
45+
// tokensPerMonth formatted as 1k, 1m, 1b
46+
const formattedTokensPerMonth = computed(() => {
47+
const tokens = tokensPerMonth.value
48+
if (tokens < 1_000) {
49+
return `${tokens}`
50+
}
51+
if (tokens < 1_000_000) {
52+
return `${tokens / 1_000}k`
53+
}
54+
if (tokens < 1_000_000_000) {
55+
return `${tokens / 1_000_000}m`
56+
}
57+
return `${tokens / 1_000_000_000}b`
58+
})
59+
</script>
60+
61+
<template>
62+
<div class="p-8 sm:p-12 rounded-xl border border-solid border-white flex flex-col gap-10 sm:mx-4 bg-gradient-to-t from-base-100 to-base-200">
63+
<Flex col class="gap-2">
64+
<h2 class="text-4xl! !m-0 !p-0 border-none!">
65+
Pricing Calculator
66+
</h2>
67+
<div class="text-base">
68+
Estimate your monthly cost based on how often users use your app.
69+
A token is valid for 1 day, and you only pay for actual tokens used.
70+
</div>
71+
</Flex>
72+
73+
<Flex col class="rounded-xl border border-solid border-white/10 p-4 gap-1">
74+
<Flex class="md:flex-row items-center gap-2">
75+
<label class="text-lg font-semibold">Number of Users</label>
76+
<span class="text-lg font-semibold text-right border border-solid border-white/30 px-4 rounded-lg">{{ userCount
77+
}}</span>
78+
</Flex>
79+
<Range v-model="currentStep" min="0" :max="Object.keys(userCountSteps).length - 1" step="1" class="w-full" />
80+
</Flex>
81+
82+
<Flex col class="rounded-xl border border-solid border-white/10 p-4 gap-1">
83+
<Flex col class="items-center sm:flex-row gap-2">
84+
<Text is="label" lg semibold>Users visit once every</Text>
85+
<Text lg semibold right center class="border border-solid border-white/30 px-4 rounded-lg">
86+
{{ loginsPerMonth.name }}
87+
</Text>
88+
</Flex>
89+
<Range v-model="currentLoginsPerMonth" min="0" max="3" step="1" class="w-full" />
90+
</Flex>
91+
92+
<Flex col items-center class="gap-8">
93+
<Flex col items-center>
94+
<Text size="2xl" medium>
95+
{{ formattedTokensPerMonth }}
96+
</Text>
97+
<Text center sm class="opacity-75">
98+
tokens
99+
</Text>
100+
</Flex>
101+
102+
<Flex row wrap items-center class="gap-8">
103+
<Flex col items-center>
104+
<div class="text-white text-5xl font-medium">
105+
${{ formattedPrice }}
106+
</div>
107+
<div class="text-sm/5 text-white/75 text-center">
108+
<div>USD per month</div>
109+
<transition>
110+
<div v-if="userCount > 1000 && price < MAX_PRICE">
111+
${{ mauPrice }} max per monthly active user
112+
</div>
113+
</transition>
114+
</div>
115+
</Flex>
116+
</Flex>
117+
118+
<div v-if="price >= MAX_PRICE" class="text-secondary">
119+
<a href="mailto:hello@feathers.dev">Contact Us</a>&nbsp;
120+
<span>for Volume Discounts and Self-hosted options</span>
121+
</div>
122+
123+
<NuxtLink class="btn btn-xl btn-primary" to="https://app.feathers.dev/">
124+
Get started for free
125+
</NuxtLink>
126+
</Flex>
127+
</div>
128+
</template>

app/components/PricingCollapse.vue

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<script setup lang="ts">
2+
defineProps<{ value: string }>()
3+
const pane = defineModel('currentPane')
4+
</script>
5+
6+
<template>
7+
<Collapse
8+
:value="value"
9+
:class="{
10+
'border border-transparent': pane !== value,
11+
'border border-primary': pane === value,
12+
}"
13+
>
14+
<CollapseTitle class="flex items-center justify-center">
15+
<slot name="title" />
16+
</CollapseTitle>
17+
<CollapseContent class="flex items-center justify-center">
18+
<slot name="content" />
19+
</CollapseContent>
20+
</Collapse>
21+
</template>

0 commit comments

Comments
 (0)