Skip to content

Commit 4e335b6

Browse files
authored
Pro & nonprofit no cc (windmill-labs#855)
1 parent 0b4b043 commit 4e335b6

File tree

3 files changed

+74
-53
lines changed

3 files changed

+74
-53
lines changed

src/components/Pricing.js

Lines changed: 51 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ const pricing = {
4949
{
5050
name: 'Enterprise',
5151
name_nonprofit: 'Enterprise - Nonprofit',
52-
name_smb: 'Pro',
52+
name_pro: 'Pro',
5353
id: 'tier-enterprise-selfhost',
5454
href: 'mailto:contact@windmill.dev',
5555
price: {
@@ -94,7 +94,7 @@ const pricing = {
9494
max: 1000
9595
}
9696
},
97-
price_smb: {
97+
price_pro: {
9898
worker: {
9999
default: workerDefaults.workers,
100100
min: workerDefaults.minWorkers,
@@ -116,11 +116,11 @@ const pricing = {
116116
}
117117
},
118118
minPrice: 120,
119-
minPrice_smb: 48,
119+
minPrice_pro: 48,
120120
minPrice_nonprofit: 48,
121121
description: 'For advanced needs in observability, security and performance.',
122122
description_nonprofit: '60% discount for nonprofits & universities, no limits.',
123-
description_smb:
123+
description_pro:
124124
'For individuals and small businesses.',
125125
enterprise_edition: true,
126126
features: [
@@ -153,7 +153,7 @@ const pricing = {
153153
text: <span>Design partners for roadmap</span>
154154
}
155155
],
156-
features_smb: [
156+
features_pro: [
157157
{
158158
text: (
159159
<span>
@@ -570,7 +570,7 @@ export default function Pricing() {
570570
tier.id === 'tier-enterprise-selfhost' && selectedOption === 'Nonprofit'
571571
? tier.name_nonprofit // Display name for Nonprofit
572572
: tier.id === 'tier-enterprise-selfhost' && selectedOption === 'Pro'
573-
? tier.name_smb // Display name for Pro
573+
? tier.name_pro // Display name for Pro
574574
: tier.name // Default name
575575
}
576576
</h3>
@@ -601,13 +601,13 @@ export default function Pricing() {
601601
{period.value === 'annually'
602602
? tier.id === 'tier-team'
603603
? (tier.minPrice * 12).toLocaleString('en-US') // Team tier calculation
604-
: selectedOption === 'Pro' && tier.minPrice_smb !== undefined
605-
? (tier.minPrice_smb * 10).toLocaleString('en-US') // Annual price for Pro
604+
: selectedOption === 'Pro' && tier.minPrice_pro !== undefined
605+
? (tier.minPrice_pro * 10).toLocaleString('en-US') // Annual price for Pro
606606
: selectedOption === 'Nonprofit' && tier.minPrice_nonprofit !== undefined
607607
? (tier.minPrice_nonprofit * 10).toLocaleString('en-US') // Annual price for Nonprofit
608608
: (tier.minPrice * 10).toLocaleString('en-US') // Annual price for others
609-
: selectedOption === 'Pro' && tier.minPrice_smb !== undefined
610-
? tier.minPrice_smb.toLocaleString('en-US') // Monthly price for Pro
609+
: selectedOption === 'Pro' && tier.minPrice_pro !== undefined
610+
? tier.minPrice_pro.toLocaleString('en-US') // Monthly price for Pro
611611
: selectedOption === 'Nonprofit' && tier.minPrice_nonprofit !== undefined
612612
? tier.minPrice_nonprofit.toLocaleString('en-US') // Monthly price for Nonprofit
613613
: tier.minPrice.toLocaleString('en-US')}
@@ -661,7 +661,7 @@ export default function Pricing() {
661661
tier.id === 'tier-enterprise-selfhost' && selectedOption === 'Nonprofit'
662662
? tier.description_nonprofit // Display the nonprofit description with links
663663
: tier.id === 'tier-enterprise-selfhost' && selectedOption === 'Pro'
664-
? tier.description_smb // Display the Pro description with links
664+
? tier.description_pro // Display the Pro description with links
665665
: tier.description // Default description with links
666666
}}
667667
/>
@@ -699,7 +699,7 @@ export default function Pricing() {
699699
style={{ marginBottom: '4rem' }}
700700
>
701701
{tier.id === 'tier-enterprise-selfhost' && selectedOption === 'Pro' ? (
702-
<FeatureList features={tier.features_smb} level={1} id={tier.id} />
702+
<FeatureList features={tier.features_pro} level={1} id={tier.id} />
703703
) : (
704704
<FeatureList features={tier.features} level={1} id={tier.id} />
705705
)}
@@ -710,7 +710,6 @@ export default function Pricing() {
710710
tier={tier}
711711
period={period}
712712
selectedOption={selectedOption}
713-
workerDefaults={workerDefaults}
714713
/>
715714
) : null}
716715

@@ -741,25 +740,47 @@ export default function Pricing() {
741740
</a>
742741
</div>
743742
) : (
744-
<button
745-
onClick={(e) => {
746-
e.preventDefault();
747-
handlePlanClick(
743+
<div className="try-it-button">
744+
<a
745+
href={
748746
selectedOption === 'Pro'
749747
? 'https://billing.windmill.dev/b/28o3dq51Y6ZJ9jy7sM'
750-
: 'https://billing.windmill.dev/b/4gw4hu51YbfZ0N200j?prefilled_promo_code=nonprofit',
751-
selectedOption
752-
);
753-
}}
754-
className={classNames(
755-
selectedOption === 'Pro'
756-
? 'text-sm bg-blue-600 !text-white shadow-sm hover:bg-blue-700 focus-visible:outline-blue-600'
757-
: 'text-sm bg-teal-600 !text-white shadow-sm hover:bg-teal-700 focus-visible:outline-teal-600',
758-
'!no-underline text-center mt-6 block rounded-md py-2 px-3 font-semibold leading-6 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2'
759-
)}
760-
>
761-
Try it for a month
762-
</button>
748+
: 'https://billing.windmill.dev/b/4gw4hu51YbfZ0N200j' +
749+
(selectedOption === 'Nonprofit' ? '?prefilled_promo_code=nonprofit' : '')
750+
}
751+
onClick={(e) => {
752+
e.preventDefault();
753+
e.stopPropagation();
754+
handlePlanClick(
755+
selectedOption === 'Pro'
756+
? 'https://billing.windmill.dev/b/28o3dq51Y6ZJ9jy7sM'
757+
: 'https://billing.windmill.dev/b/4gw4hu51YbfZ0N200j' +
758+
(selectedOption === 'Nonprofit' ? '?prefilled_promo_code=nonprofit' : ''),
759+
selectedOption
760+
);
761+
}}
762+
className={classNames(
763+
selectedOption === 'Pro'
764+
? 'text-sm bg-blue-600 !text-white shadow-sm hover:bg-blue-700 focus-visible:outline-blue-600'
765+
: 'text-sm bg-teal-600 !text-white shadow-sm hover:bg-teal-700 focus-visible:outline-teal-600',
766+
'main-section !no-underline text-center font-semibold leading-6 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2'
767+
)}
768+
>
769+
Try it for a month
770+
</a>
771+
<a
772+
href={`https://app.windmill.dev/public/windmill-labs/cae76591e9a0c229a338636c50e67066${
773+
selectedOption !== 'Enterprise' ? `?plan=${selectedOption.toLowerCase()}` : ''
774+
}`}
775+
className="hover-section"
776+
target="_blank"
777+
onClick={(e) => {
778+
e.stopPropagation();
779+
}}
780+
>
781+
No credit card?
782+
</a>
783+
</div>
763784
))}
764785
</div>
765786
</div>

src/components/QuoteForm.tsx

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export function QuoteForm({
7777
// Add these helper functions
7878
const getPlanDisplay = () => {
7979
if (plan === 'cloud_ee') return 'Cloud EE';
80-
if (selectedOption === 'SMB' && plan === 'selfhosted_ee') return 'Pro';
80+
if (selectedOption === 'Pro' && plan === 'selfhosted_ee') return 'Pro';
8181
if (selectedOption === 'Nonprofit' && plan === 'selfhosted_ee') return 'Enterprise - Nonprofit';
8282
return 'Self-Hosted EE';
8383
};
@@ -93,7 +93,7 @@ export function QuoteForm({
9393
// Modify plan if conditions are met
9494
let apiPlan = plan;
9595

96-
if (selectedOption === 'SMB' && plan === 'selfhosted_ee') {
96+
if (selectedOption === 'Pro' && plan === 'selfhosted_ee') {
9797
apiPlan = 'selfhosted_pro';
9898
} else if (selectedOption === 'Nonprofit' && plan === 'selfhosted_ee') {
9999
apiPlan = 'nonprofit_ee';
@@ -126,7 +126,7 @@ export function QuoteForm({
126126
compute_units: computeUnits,
127127
email,
128128
company_name: companyName,
129-
reason_plan: (selectedOption === 'Nonprofit' || selectedOption === 'SMB') ? qualification : '',
129+
reason_plan: (selectedOption === 'Nonprofit' || selectedOption === 'Pro') ? qualification : '',
130130
})
131131
}
132132
);
@@ -163,7 +163,7 @@ export function QuoteForm({
163163
(2 * (workers as { small: number; standard: number; large: number }).large);
164164

165165
const computeUnits = Math.max(2, rawComputeUnits);
166-
const isSmbWithTooManyUnits = selectedOption === 'SMB' && computeUnits > 10;
166+
const isProWithTooManyUnits = selectedOption === 'Pro' && computeUnits > 10;
167167

168168
return (
169169
<Dialog open={open} onClose={() => setOpen(false)}>
@@ -283,7 +283,7 @@ export function QuoteForm({
283283
/>
284284
)}
285285

286-
{plan === 'selfhosted_ee' && (selectedOption === 'Nonprofit' || selectedOption === 'SMB') && (
286+
{plan === 'selfhosted_ee' && (selectedOption === 'Nonprofit' || selectedOption === 'Pro') && (
287287
<label className="flex flex-col">
288288
<span className="font-medium text-gray-800 dark:text-gray-200 text-sm">
289289
Qualification for {selectedOption === 'Nonprofit' ? 'nonprofit' : 'Pro plan'} (<a href="#pro-plan" className="font-normal text-blue-600 hover:text-blue-800">see rules</a>)
@@ -298,7 +298,7 @@ export function QuoteForm({
298298
</label>
299299
)}
300300

301-
{isSmbWithTooManyUnits && (
301+
{isProWithTooManyUnits && (
302302
<div className="text-red-500 text-sm">
303303
Pro plan is limited to 10 compute units maximum
304304
</div>
@@ -314,8 +314,8 @@ export function QuoteForm({
314314
!email ||
315315
!isValidEmail(email) ||
316316
loading ||
317-
isSmbWithTooManyUnits ||
318-
((plan === 'selfhosted_ee' && (selectedOption === 'Nonprofit' || selectedOption === 'SMB')) && qualification.length < 3)
317+
isProWithTooManyUnits ||
318+
((plan === 'selfhosted_ee' && (selectedOption === 'Nonprofit' || selectedOption === 'Pro')) && qualification.length < 3)
319319
}
320320
>
321321
Generate quote

src/components/pricing/PriceCalculator.js

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ function calculateWorkerPrice(memoryGB, tierId, selectedOption) {
5050
let basePrice = 25 + (effectiveMemory - 1) * 25;
5151

5252
// Apply discounts based on selectedOption
53-
if (selectedOption === 'SMB' || selectedOption === 'Nonprofit') {
53+
if (selectedOption === 'Pro' || selectedOption === 'Nonprofit') {
5454
basePrice = basePrice * 0.4; // 60% discount
5555
}
5656
return basePrice;
@@ -117,9 +117,9 @@ const ComputeUnitsSummary = ({ workerGroups, nativeWorkers, selectedOption, tier
117117
<div className="flex flex-col gap-1 text-sm text-gray-600 dark:text-gray-200 min-h-[6.5rem]">
118118
<span className="text-gray-900 dark:text-white">
119119
Total <a href="#compute-units" className="custom-link text-gray-900 hover:text-gray-600 dark:text-white dark:hover:text-gray-200">compute units</a> (CU):
120-
{' '}<span className={selectedOption === 'SMB' && totalComputeUnits > 10 ? "text-rose-700 dark:text-red-400" : ""}>
120+
{' '}<span className={selectedOption === 'Pro' && totalComputeUnits > 10 ? "text-rose-700 dark:text-red-400" : ""}>
121121
{Math.round(totalComputeUnits)}
122-
{selectedOption === 'SMB' && totalComputeUnits > 10 ? ' (max 10 CU on Pro plan)' : ''}
122+
{selectedOption === 'Pro' && totalComputeUnits > 10 ? ' (max 10 CU on Pro plan)' : ''}
123123
</span>
124124
</span>
125125
{Object.entries(groupedWorkers)
@@ -188,8 +188,8 @@ export default function PriceCalculator({ period, tier, selectedOption }) {
188188
// Get the appropriate pricing based on selectedOption and tier.id
189189
function getPriceByOption() {
190190
if (tier.id === 'tier-enterprise-selfhost') {
191-
if (selectedOption === 'SMB' && tier.price_smb) {
192-
return tier.price_smb;
191+
if (selectedOption === 'Pro' && tier.price_pro) {
192+
return tier.price_pro;
193193
} else if (selectedOption === 'Nonprofit' && tier.price_nonprofit) {
194194
return tier.price_nonprofit;
195195
}
@@ -242,7 +242,7 @@ export default function PriceCalculator({ period, tier, selectedOption }) {
242242

243243
// Apply minimum worker group pricing
244244
let minimumWorkerPrice = tier.id === 'tier-enterprise-cloud' ? 200 : 100;
245-
if (tier.id === 'tier-enterprise-selfhost' && (selectedOption === 'SMB' || selectedOption === 'Nonprofit')) {
245+
if (tier.id === 'tier-enterprise-selfhost' && (selectedOption === 'Pro' || selectedOption === 'Nonprofit')) {
246246
minimumWorkerPrice = 40;
247247
}
248248

@@ -341,19 +341,19 @@ export default function PriceCalculator({ period, tier, selectedOption }) {
341341
return sum + (pricePerWorker * group.workers);
342342
}, 0) + (pricing.worker?.native * nativeWorkers / 8)) <
343343
(tier.id === 'tier-enterprise-cloud' ? 200 :
344-
(selectedOption === 'SMB' || selectedOption === 'Nonprofit' ? 40 : 100)) && (
344+
(selectedOption === 'Pro' || selectedOption === 'Nonprofit' ? 40 : 100)) && (
345345
<span className="text-sm text-rose-700 dark:text-red-400">
346346
Price for workers can't be below ${tier.id === 'tier-enterprise-cloud'
347347
? (period.value === 'annually' ? '2,000' : '200')
348-
: (selectedOption === 'SMB' || selectedOption === 'Nonprofit'
348+
: (selectedOption === 'Pro' || selectedOption === 'Nonprofit'
349349
? (period.value === 'annually' ? '400' : '40')
350350
: (period.value === 'annually' ? '1,000' : '100'))}
351351
/{period.value === 'annually' ? 'yr' : 'mo'}
352352
</span>
353353
)}
354354

355-
{/* New CU limit warning for SMB */}
356-
{selectedOption === 'SMB' && (
355+
{/* New CU limit warning for Pro */}
356+
{selectedOption === 'Pro' && (
357357
(() => {
358358
const counts = getWorkerCounts(workerGroups);
359359
const totalComputeUnits = (counts.small / 2 || 0) +
@@ -380,7 +380,7 @@ export default function PriceCalculator({ period, tier, selectedOption }) {
380380
(counts.standard || 0) +
381381
((1/8) * nativeWorkers) +
382382
(2 * (counts.large || 0));
383-
const isOverLimit = selectedOption === 'SMB' && totalComputeUnits > 10;
383+
const isOverLimit = selectedOption === 'Pro' && totalComputeUnits > 10;
384384
const textColor = isOverLimit ? "text-rose-700 dark:text-red-400" : "text-gray-900 dark:text-white";
385385
const subTextColor = isOverLimit ? "text-rose-700 dark:text-red-400" : "text-gray-500";
386386

@@ -553,7 +553,7 @@ export default function PriceCalculator({ period, tier, selectedOption }) {
553553
</div>
554554
<Slider
555555
min={1}
556-
max={selectedOption === 'SMB' ? 10 : 1000}
556+
max={selectedOption === 'Pro' ? 10 : 1000}
557557
step={1}
558558
defaultValue={group.workers}
559559
onChange={(value) => updateWorkerGroup(index, 'workers', value)}
@@ -613,7 +613,7 @@ export default function PriceCalculator({ period, tier, selectedOption }) {
613613
</div>
614614
<Slider
615615
min={0}
616-
max={selectedOption === 'SMB' ? 32 : 200}
616+
max={selectedOption === 'Pro' ? 32 : 200}
617617
step={8}
618618
defaultValue={8}
619619
onChange={(value) => setNativeWorkers(value)}
@@ -743,7 +743,7 @@ export default function PriceCalculator({ period, tier, selectedOption }) {
743743
amount={computeTotalPrice()}
744744
period={period.value}
745745
className={classNames(
746-
selectedOption === 'SMB' && totalComputeUnits > 10
746+
selectedOption === 'Pro' && totalComputeUnits > 10
747747
? "text-rose-700 dark:text-red-400"
748748
: "text-gray-900 dark:text-white"
749749
)}
@@ -763,7 +763,7 @@ export default function PriceCalculator({ period, tier, selectedOption }) {
763763
onClick={() => setShowQuoteForm(true)}
764764
className={classNames(
765765
'text-sm cursor-pointer border shadow-sm !no-underline mt-6 block rounded-md py-2 px-3 text-center font-semibold leading-6 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2',
766-
tier.id === 'tier-enterprise-cloud' || selectedOption !== 'SMB'
766+
tier.id === 'tier-enterprise-cloud' || selectedOption !== 'Pro'
767767
? 'border-teal-600 text-teal-600 hover:text-teal-700 dark:hover:text-teal-500 hover:bg-teal-50 dark:hover:bg-teal-950 focus-visible:outline-teal-600'
768768
: 'border-blue-600 text-blue-600 hover:text-blue-700 dark:hover:text-blue-500 hover:bg-blue-50 dark:hover:bg-blue-950 focus-visible:outline-blue-600'
769769
)}

0 commit comments

Comments
 (0)