mirror of
https://github.com/pockethost/pockethost.git
synced 2025-03-30 15:08:30 +00:00
Merge branch 'main' of github.com:pockethost/pockethost
This commit is contained in:
commit
2b0740944d
11
package.json
11
package.json
@ -16,18 +16,17 @@
|
||||
},
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"@changesets/cli": "^2.27.9",
|
||||
"prettier": "^3.0.3",
|
||||
"prettier-plugin-jsdoc": "^1.3.0",
|
||||
"@changesets/cli": "^2.27.11",
|
||||
"prettier": "^3.4.2",
|
||||
"prettier-plugin-jsdoc": "^1.3.2",
|
||||
"prettier-plugin-organize-imports": "^4.1.0",
|
||||
"prettier-plugin-svelte": "^3.2.8",
|
||||
"prettier-plugin-svelte": "^3.3.2",
|
||||
"tslib": "^2.8.1",
|
||||
"tsx": "^4.19.2",
|
||||
"typescript": "^5.6.3"
|
||||
"typescript": "^5.7.3"
|
||||
},
|
||||
"pnpm": {
|
||||
"patchedDependencies": {
|
||||
"eventsource@2.0.2": "patches/eventsource@2.0.2.patch",
|
||||
"@sveltejs/enhanced-img": "patches/@sveltejs__enhanced-img.patch",
|
||||
"tail": "patches/tail.patch",
|
||||
"@types/tail": "patches/@types__tail.patch"
|
||||
|
@ -1,16 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { PLAN_NAMES, SubscriptionType } from 'pockethost/common'
|
||||
import { stats, userStore } from '$util/stores'
|
||||
import { stats, userStore, isUserLoggedIn } from '$util/stores'
|
||||
import PricingCard from '$components/PricingCard.svelte'
|
||||
|
||||
const TOTAL_QTY = 150
|
||||
export let priceMonthly: [number, string?, number?] = [
|
||||
359,
|
||||
'once, use forever',
|
||||
]
|
||||
export let priceAnnually: [number, string?, number?] = [
|
||||
159,
|
||||
'year (save 55%)',
|
||||
]
|
||||
export let startDate: Date | null = null
|
||||
export let endDate: Date | null = null
|
||||
|
||||
@ -22,23 +19,22 @@
|
||||
|
||||
<PricingCard
|
||||
name={`${PLAN_NAMES[SubscriptionType.Flounder]}`}
|
||||
qtyRemaining={1000 - $stats.total_flounder_subscribers}
|
||||
qtyMax={1000}
|
||||
qtyRemaining={TOTAL_QTY - $stats.total_flounder_subscribers}
|
||||
qtyMax={TOTAL_QTY}
|
||||
description="Epic elite! The Flounder's Edition is almost as good as the Founder's edition, and you'll help PocketHost go global."
|
||||
{priceMonthly}
|
||||
{priceAnnually}
|
||||
requireAuthenticatedUser
|
||||
checkoutMonthURL="https://store.pockethost.io/buy/9ff8775b-6b9e-4aa8-a0ab-dc5e58e25408?checkout[custom][user_id]={$userStore?.id}&checkout[email]={$userStore?.email}"
|
||||
checkoutYearURL="https://store.pockethost.io/buy/82d79f7c-64f6-4c2b-9f58-dcc8951f1cdd?checkout[custom][user_id]={$userStore?.id}&checkout[email]={$userStore?.email}"
|
||||
checkoutMonthURL={$isUserLoggedIn
|
||||
? 'https://store.pockethost.io/buy/d4b2d062-429c-49b4-9cdc-853aaeb17e20?checkout[custom][user_id]=${$userStore?.id}&checkout[email]=${$userStore?.email}'
|
||||
: `/get-started`}
|
||||
features={[
|
||||
`Everything in the ${PLAN_NAMES[SubscriptionType.Premium]} tier`,
|
||||
'Unlimited instances',
|
||||
'Unlimited bandwidth',
|
||||
'Unlimited storage & files',
|
||||
`Private #onlyflounders Discord channel`,
|
||||
`Priority support`,
|
||||
`Commemorative Flounder's badge`,
|
||||
`PocketHost t-shirt`,
|
||||
`#onlyflounders private discord channel`,
|
||||
`-Girlfriend`,
|
||||
]}
|
||||
fundingGoals={[
|
||||
`Global regions (approx 40)`,
|
||||
`Global low latency from anywhere`,
|
||||
]}
|
||||
/>
|
||||
|
@ -14,7 +14,6 @@
|
||||
checkoutMonthURL="https://store.pockethost.io/checkout/buy/e71cbfb5-cec3-4745-97a7-d877f6776503?checkout[custom][user_id]={$userStore?.id}"
|
||||
checkoutYearURL="https://store.pockethost.io/checkout/buy/e5660329-5b99-4ed6-8f36-0d387803e1d6?checkout[custom][user_id]={$userStore?.id}"
|
||||
features={[
|
||||
`Everything in the ${PLAN_NAMES[SubscriptionType.Premium]} tier`,
|
||||
'Unlimited instances',
|
||||
'Unlimited bandwidth',
|
||||
'Unlimited storage & files',
|
||||
|
@ -80,7 +80,7 @@
|
||||
</script>
|
||||
|
||||
<!-- It's a card, so use card -->
|
||||
<div class="card bg-base-100 w-96 shadow-xl">
|
||||
<div class="card w-96 shadow-xl bg-neutral text-neutral-content">
|
||||
<div class="card-body items-center text-center">
|
||||
<div class="card-title">
|
||||
<h2 id="tier-startup" class="text-2xl font-semibold leading-8 text-white">
|
||||
|
@ -7,12 +7,12 @@
|
||||
name: 'Riddge Mussington',
|
||||
title: '',
|
||||
quote:
|
||||
"I use pockethost for a couple projects and I think you would be hard pressed to find a better hosted provider for pocketbase projects. It is very easy to use and setup and secure by default because it uses pocketbase under the hood, it's also very fast at shipping pocketbase updates and maintaining uptime and fixing issues, it's truly a god chosen database especially with the very generous free tier that I wish will never change, I'm currently building my next side hustle with it so I can't wait for it to grow larger",
|
||||
"I use pockethost for a couple projects and I think you would be hard pressed to find a better hosted provider for pocketbase projects. It is very easy to use and setup and secure by default because it uses pocketbase under the hood, it's also very fast at shipping pocketbase updates and maintaining uptime and fixing issues, it's truly a god chosen database, I'm currently building my next side hustle with it so I can't wait for it to grow larger",
|
||||
},
|
||||
{
|
||||
name: 'H.Mohamed',
|
||||
title: '',
|
||||
quote: 'Great BaaS option for simpler apps with a generous free plan.',
|
||||
quote: 'Great BaaS option.',
|
||||
},
|
||||
{
|
||||
name: 'Damian Kennedy',
|
||||
@ -48,27 +48,32 @@
|
||||
]
|
||||
</script>
|
||||
|
||||
<div class="flex flex-wrap justify-center gap-4">
|
||||
{#each testimonials as testimonial}
|
||||
<div class="card bg-neutral shadow-xl w-96">
|
||||
<div class="card-body">
|
||||
<div class="flex mb-2">
|
||||
<Fa icon={faStar} class="text-warning" />
|
||||
<Fa icon={faStar} class="text-warning" />
|
||||
<Fa icon={faStar} class="text-warning" />
|
||||
<Fa icon={faStar} class="text-warning" />
|
||||
<Fa icon={faStar} class="text-warning" />
|
||||
</div>
|
||||
<p class="mb-4">{testimonial.quote}</p>
|
||||
<div class="card-actions">
|
||||
<div>
|
||||
<p class="font-bold">{testimonial.name}</p>
|
||||
{#if testimonial.title}
|
||||
<p class="text-sm opacity-75">{testimonial.title}</p>
|
||||
{/if}
|
||||
<div class="flex-1 p-5 flex flex-col">
|
||||
<div class="text-3xl text-center pb-5">Testimonials</div>
|
||||
<div class="flex flex-wrap gap-6 justify-center">
|
||||
<div class="flex flex-wrap justify-center gap-4">
|
||||
{#each testimonials as testimonial}
|
||||
<div class="card bg-neutral shadow-xl w-96">
|
||||
<div class="card-body">
|
||||
<div class="flex mb-2">
|
||||
<Fa icon={faStar} class="text-warning" />
|
||||
<Fa icon={faStar} class="text-warning" />
|
||||
<Fa icon={faStar} class="text-warning" />
|
||||
<Fa icon={faStar} class="text-warning" />
|
||||
<Fa icon={faStar} class="text-warning" />
|
||||
</div>
|
||||
<p class="mb-4">{testimonial.quote}</p>
|
||||
<div class="card-actions">
|
||||
<div>
|
||||
<p class="font-bold">{testimonial.name}</p>
|
||||
{#if testimonial.title}
|
||||
<p class="text-sm opacity-75">{testimonial.title}</p>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,10 +1,25 @@
|
||||
<script>
|
||||
import UserLoggedIn from '$components/guards/UserLoggedIn.svelte'
|
||||
import UserLoggedOut from '$components/guards/UserLoggedOut.svelte'
|
||||
import { userStore } from '$util/stores'
|
||||
|
||||
$: maxInstances = $userStore?.subscription_quantity
|
||||
</script>
|
||||
|
||||
<div class="m-4">
|
||||
<UserLoggedIn>
|
||||
{#if maxInstances === 0}
|
||||
<div class="flex flex-col space-x-4 items-center justify-center">
|
||||
<div class="prose">
|
||||
<p class="text-2xl text-center text-warning">
|
||||
Instances will not run until you <a
|
||||
href="/access"
|
||||
class="link text-primary">upgrade</a
|
||||
>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
<slot />
|
||||
</UserLoggedIn>
|
||||
<UserLoggedOut>
|
||||
|
5
packages/dashboard/src/routes/(app)/access/+page.svelte
Normal file
5
packages/dashboard/src/routes/(app)/access/+page.svelte
Normal file
@ -0,0 +1,5 @@
|
||||
<script lang="ts">
|
||||
import Paywall from '../../(static)/pricing/Paywall.svelte'
|
||||
</script>
|
||||
|
||||
<Paywall />
|
@ -28,27 +28,38 @@
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-row space-x-4 items-center justify-center">
|
||||
<div>Instances</div>
|
||||
<progress
|
||||
class="progress progress-primary w-48 md:w-80"
|
||||
value={instanceCount}
|
||||
max={maxInstances}
|
||||
></progress>
|
||||
<div>
|
||||
{#if $userSubscriptionType === SubscriptionType.Founder}
|
||||
{instanceCount}/<a
|
||||
href="https://discord.com/channels/1128192380500193370/1128192380500193373/1296340516044017718"
|
||||
class="link"
|
||||
target="_blank">{maxInstances}</a
|
||||
>
|
||||
{:else}
|
||||
{instanceCount}/{maxInstances}
|
||||
<div class="flex flex-col space-x-4 items-center justify-center">
|
||||
{#if maxInstances > 0}
|
||||
{#if instanceCount > maxInstances}
|
||||
<p class="text-center text-error">
|
||||
You have exceeded your instance limit.
|
||||
</p>
|
||||
{/if}
|
||||
{#if $userSubscriptionType === SubscriptionType.Free}
|
||||
<a href="/pricing" class="link text-xs text-success">Upgrade</a>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="flex flex-row space-x-4 items-center justify-center">
|
||||
<div>Instances</div>
|
||||
<progress
|
||||
class="progress {instanceCount > maxInstances
|
||||
? 'progress-error'
|
||||
: 'progress-primary'} w-48 md:w-80"
|
||||
value={instanceCount}
|
||||
max={maxInstances}
|
||||
></progress>
|
||||
<div>
|
||||
{#if $userSubscriptionType === SubscriptionType.Founder}
|
||||
{instanceCount}/<a
|
||||
href="https://discord.com/channels/1128192380500193370/1128192380500193373/1296340516044017718"
|
||||
class="link"
|
||||
target="_blank">{maxInstances}</a
|
||||
>
|
||||
{:else}
|
||||
{instanceCount}/{maxInstances}
|
||||
{/if}
|
||||
{#if instanceCount >= maxInstances}
|
||||
<a href="/support" class="link text-xs text-success">Upgrade</a>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap gap-2 items-center justify-center">
|
||||
|
@ -8,18 +8,10 @@
|
||||
<div class="card max-w-sm bg-base-100 shadow-xl">
|
||||
<div class="card-body">
|
||||
<h2 class="card-title">Paywall!</h2>
|
||||
<p>Oof. You hit a paywall.</p>
|
||||
<p class="text-error">
|
||||
You're only allowed {maxInstances} projects on the {PLAN_NAMES[
|
||||
$userSubscriptionType
|
||||
]} plan.
|
||||
</p>
|
||||
<p>
|
||||
But that's okay, we know you want to support PocketHost if you love it
|
||||
this much!
|
||||
</p>
|
||||
<p>Oof. You hit a paywall because you are out of instances.</p>
|
||||
|
||||
<div class="card-actions justify-end">
|
||||
<a href="/pricing" class="btn btn-primary">Unlock More Projects</a>
|
||||
<a href="/access" class="btn btn-primary">Unlock</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -0,0 +1,11 @@
|
||||
## Hard paywall is live
|
||||
|
||||
_[@cap'n](https://discord.gg/nVTxCMEcGT) Jan 10, 2025_
|
||||
|
||||
As [announced previously](/blog/hard-paywall), I've moved to a hard paywall. This means that every new user will have to pay for their PocketHost account before they can use it. There will be a 7 day free trial, but after that, you'll have to pay, and you will know that going in.
|
||||
|
||||
Freemium is broken, especially for small businesses. It's a great way to get people to try your product, but it's not a great way to grow independently.
|
||||
|
||||
I encourage everyone to consider subscribing to PocketHost. It's a great way to support the PocketBase community and ensure the project's long term viability.
|
||||
|
||||
Existing users will be grandfathered in.
|
@ -1,4 +1,8 @@
|
||||
export const toc = [
|
||||
{
|
||||
title: 'Hard paywall is live',
|
||||
path: '/blog/hard-paywall-is-live',
|
||||
},
|
||||
{
|
||||
title: 'YouTube Dev Channel is Live',
|
||||
path: '/blog/announcing-dev-channel',
|
||||
|
@ -1,42 +1,16 @@
|
||||
# Pricing Ethos
|
||||
|
||||
At PocketHost, we believe in keeping things simple and transparent. Our pricing is designed to support indie hackers, makers, and small businesses. We offer flexible plans to suit your needs, including a Free Tier, a Pro Tier, and occasionally, limited Lifetime Offers during our bootstrapping phase.
|
||||
At PocketHost, we believe in keeping things simple and transparent. Our pricing is designed to support indie hackers, makers, and small businesses. We offer flexible plans to suit your needs, based on the number of instances you want to run. We also offer limited Lifetime Offers during our bootstrapping phase.
|
||||
|
||||
## Free Tier
|
||||
## Instances
|
||||
|
||||
**Free Forever**
|
||||
You can get started easily on PocketHost by purchasing an instance.
|
||||
|
||||
Our Free Tier is perfect for getting started with PocketHost without any upfront costs. It includes:
|
||||
|
||||
- **Multiple Projects**: Create up to 25 projects as you need.
|
||||
- **Generous Resources**: Fair use of storage, bandwidth, and CPU.
|
||||
- **Essential Features**: Access to the core functionalities of PocketHost.
|
||||
|
||||
The Free Tier is ideal for prototypes, personal projects, or exploring what PocketHost has to offer.
|
||||
|
||||
## Pro Tier
|
||||
|
||||
**Enhanced Capacity and Access**
|
||||
|
||||
For those who need more, our Pro Tier offers expanded capabilities:
|
||||
|
||||
- **All Free Tier Benefits**: Plus additional resources.
|
||||
- **Priority Support**: Get help when you need it.
|
||||
- **Advanced Features**: Unlock premium functionalities.
|
||||
|
||||
**Subscription Options**:
|
||||
|
||||
- **Monthly Plan**: Flexibility to pay as you go.
|
||||
- **Annual Plan**: Save more with yearly billing.
|
||||
|
||||
Our Pro Tier remains a great deal for users who require extra power and support for their growing projects.
|
||||
|
||||
## Lifetime Offers
|
||||
|
||||
**Limited Supply During Bootstrapping**
|
||||
|
||||
Occasionally, we offer Lifetime Plans as part of our bootstrapping efforts:
|
||||
|
||||
- **One-Time Payment**: Enjoy Pro Tier benefits without recurring fees.
|
||||
- **Limited Availability**: Offered in limited quantities and sold on a first-come, first-served basis.
|
||||
- **Non-Transferable**: Cannot be pro-rated, transferred, used retroactively, reserved, or purchased in advance.
|
||||
@ -46,16 +20,14 @@ Occasionally, we offer Lifetime Plans as part of our bootstrapping efforts:
|
||||
- Once sold out, Lifetime Offers are gone forever.
|
||||
- Timing is everything—if you can wait and catch one of these deals, it's an incredible opportunity.
|
||||
|
||||
Many users opt for our standard Pro Tier, which continues to provide excellent value even when Lifetime Offers are unavailable.
|
||||
|
||||
## Fair Use Policy
|
||||
|
||||
**Unlimited Doesn't Mean Infinite**
|
||||
|
||||
While we offer ample limits for projects, storage, bandwidth, and CPU, all usage is subject to our [Fair Use Policy](/terms):
|
||||
While we offer ample limits for projects, storage, bandwidth, and CPU, all usage is subject to Fair Use:
|
||||
|
||||
- **Fair Use Basis**: Use resources similarly to the average active app on our platform.
|
||||
- **Resource Management**: Your app scales up or down based on its needs.
|
||||
- **Good Citizenship**: Be mindful of resource consumption to ensure a positive experience for all users.
|
||||
|
||||
For more details, please refer to our Fair Use Policy in the [Terms of Service](/terms).
|
||||
For more details, please refer to our complete [Fair Use Policy](/terms).
|
||||
|
@ -1,29 +1,5 @@
|
||||
<script lang="ts">
|
||||
import PricingTable from './PricingTable.svelte'
|
||||
import FounderCard from '$components/FounderCard.svelte'
|
||||
import FlounderCard from '$components/FlounderCard.svelte'
|
||||
import Testimonials from '$components/Testimonials.svelte'
|
||||
import Paywall from './Paywall.svelte'
|
||||
</script>
|
||||
|
||||
<!-- Flex them up -->
|
||||
<div class="flex flex-col mt-2">
|
||||
<div class="flex-1 text-center font-bold text-primary text-4xl mb-6">
|
||||
Pricing
|
||||
</div>
|
||||
|
||||
<div class="flex-1 bg-neutral text-neutral-content p-5 flex flex-col">
|
||||
<div class="text-xl text-center pb-5">Rare deals</div>
|
||||
<div class="flex flex-wrap gap-6 justify-center">
|
||||
<div class="w-96">
|
||||
<FounderCard />
|
||||
</div>
|
||||
|
||||
<div class="w-96">
|
||||
<FlounderCard endDate={new Date(2024, 11, 3)} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<PricingTable />
|
||||
<Testimonials />
|
||||
</div>
|
||||
<Paywall />
|
||||
|
@ -0,0 +1,26 @@
|
||||
<script lang="ts">
|
||||
import { userStore } from '$util/stores'
|
||||
import UserLoggedIn from '$components/guards/UserLoggedIn.svelte'
|
||||
import UserLoggedOut from '$components/guards/UserLoggedOut.svelte'
|
||||
|
||||
export let fixed = false
|
||||
</script>
|
||||
|
||||
<UserLoggedIn>
|
||||
<a
|
||||
class="btn btn-warning btn-lg {fixed
|
||||
? ' btn-block rounded-none fixed bottom-0'
|
||||
: ''}"
|
||||
style="z-index: 1000"
|
||||
href={`https://store.pockethost.io/buy/d4b2d062-429c-49b4-9cdc-853aaeb17e20?checkout[custom][user_id]=${$userStore?.id}&checkout[email]=${$userStore?.email}`}
|
||||
>Unlock Access Now</a
|
||||
>
|
||||
</UserLoggedIn>
|
||||
<UserLoggedOut>
|
||||
<a
|
||||
class="btn btn-warning btn-lg {fixed
|
||||
? ' btn-block rounded-none fixed bottom-0'
|
||||
: ''}"
|
||||
href="/get-started">Get Started</a
|
||||
>
|
||||
</UserLoggedOut>
|
23
packages/dashboard/src/routes/(static)/pricing/Card.svelte
Normal file
23
packages/dashboard/src/routes/(static)/pricing/Card.svelte
Normal file
@ -0,0 +1,23 @@
|
||||
<script lang="ts">
|
||||
import { faCheck } from '@fortawesome/free-solid-svg-icons'
|
||||
import Fa from 'svelte-fa'
|
||||
|
||||
export let feature: any
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="flex flex-col items-center prose bg-neutral text-neutral-content rounded-lg relative w-full"
|
||||
>
|
||||
<div class="text-2xl text-white pt-2">
|
||||
{feature.title}
|
||||
</div>
|
||||
{#if feature.img}
|
||||
<enhanced:img src={feature.img} class="pl-5 pr-5" />
|
||||
{/if}
|
||||
<div class="text-xl p-5">{@html feature.description}</div>
|
||||
<div
|
||||
class="absolute top-0 left-0 text-primary-content text-2xl bg-primary p-2 rounded-br-lg rounded-tl-lg"
|
||||
>
|
||||
<Fa icon={faCheck} class="" />
|
||||
</div>
|
||||
</div>
|
13
packages/dashboard/src/routes/(static)/pricing/Deals.svelte
Normal file
13
packages/dashboard/src/routes/(static)/pricing/Deals.svelte
Normal file
@ -0,0 +1,13 @@
|
||||
<script lang="ts">
|
||||
import FounderCard from '$components/FounderCard.svelte'
|
||||
import FlounderCard from '$components/FlounderCard.svelte'
|
||||
</script>
|
||||
|
||||
<div class="flex-1 p-5 flex flex-col">
|
||||
<div class="text-3xl text-center pb-5">Rare deals</div>
|
||||
<div class="flex flex-wrap gap-6 justify-center">
|
||||
<FounderCard />
|
||||
|
||||
<FlounderCard />
|
||||
</div>
|
||||
</div>
|
@ -7,12 +7,14 @@
|
||||
|
||||
<td class=" px-4 py-0 text-center">
|
||||
<div class="relative h-full w-full py-3 flex justify-center">
|
||||
{#if item === 'YesBlock'}
|
||||
<Fa icon={faCheck} class="text-primary" />
|
||||
{:else if item === 'NoBlock'}
|
||||
<Fa icon={faX} class="text-error" />
|
||||
{:else}
|
||||
{@html item}
|
||||
{/if}
|
||||
<span>
|
||||
{#if item === 'YesBlock'}
|
||||
<Fa icon={faCheck} class="text-primary" />
|
||||
{:else if item === 'NoBlock'}
|
||||
<Fa icon={faX} class="text-error" />
|
||||
{:else}
|
||||
{@html item}
|
||||
{/if}
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
|
@ -0,0 +1,8 @@
|
||||
<script lang="ts">
|
||||
import Card from './Card.svelte'
|
||||
import { features } from './features'
|
||||
</script>
|
||||
|
||||
{#each features as feature}
|
||||
<Card {feature} />
|
||||
{/each}
|
@ -1,15 +0,0 @@
|
||||
<script lang="ts">
|
||||
import FeatureSupportBlock from './FeatureSupportBlock.svelte'
|
||||
|
||||
export let feature: string
|
||||
export let item: string
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="flex items-center justify-between px-4 py-3 sm:grid sm:grid-cols-2 sm:px-0"
|
||||
>
|
||||
<dt class="pr-4">{feature}</dt>
|
||||
<dd class="flex items-center justify-end sm:justify-center sm:px-4">
|
||||
<FeatureSupportBlock {item} />
|
||||
</dd>
|
||||
</div>
|
@ -0,0 +1,43 @@
|
||||
<script lang="ts">
|
||||
import Card from './Card.svelte'
|
||||
import Testimonials from '$src/components/Testimonials.svelte'
|
||||
import CtaButton from './CTAButton.svelte'
|
||||
import { features } from './features'
|
||||
import Deals from './Deals.svelte'
|
||||
import Features from './Features.svelte'
|
||||
</script>
|
||||
|
||||
<div class="prose ml-auto mr-auto">
|
||||
<p class="text-3xl text-center">PocketHost Access</p>
|
||||
<p class="text-2xl text-center">
|
||||
<span class="text-primary">$5/instance</span> or
|
||||
<span class="text-primary">$25/unlimited</span>
|
||||
</p>
|
||||
<p class="text-xl text-accent text-center">7 Day Free Trial</p>
|
||||
<div class="flex justify-center mt-10 mb-10">
|
||||
<CtaButton />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-center mb-10">
|
||||
<iframe
|
||||
width="560"
|
||||
height="315"
|
||||
src="https://www.youtube.com/embed/Xe0FrGzlcVM?si=XTJJ7pmp9cwjBOMY"
|
||||
title="YouTube video player"
|
||||
frameborder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
referrerpolicy="strict-origin-when-cross-origin"
|
||||
allowfullscreen
|
||||
></iframe>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-10 items-center">
|
||||
<Features />
|
||||
</div>
|
||||
|
||||
<Deals />
|
||||
|
||||
<Testimonials />
|
||||
|
||||
<CtaButton fixed />
|
@ -1,198 +0,0 @@
|
||||
<script lang="ts">
|
||||
import FeatureName from '$src/routes/(static)/pricing/FeatureName.svelte'
|
||||
import FeatureSupportBlock from './FeatureSupportBlock.svelte'
|
||||
import { PLAN_NAMES, SubscriptionType } from 'pockethost/common'
|
||||
import { isUserVerified, userStore } from '$src/util/stores'
|
||||
|
||||
interface Item {
|
||||
name: string
|
||||
items: string[]
|
||||
isNew?: boolean
|
||||
info?: string
|
||||
}
|
||||
|
||||
const plans = [
|
||||
{
|
||||
name: PLAN_NAMES[SubscriptionType.Free],
|
||||
price: null,
|
||||
},
|
||||
{
|
||||
name: PLAN_NAMES[SubscriptionType.Premium],
|
||||
price: [
|
||||
{
|
||||
text: '$20/mo',
|
||||
link: `https://store.pockethost.io/checkout/buy/8e7cfb35-846a-4fd6-adcb-c2db5589275d?checkout[custom][user_id]=${$userStore?.id}&checkout[email]=${$userStore?.email}`,
|
||||
},
|
||||
{
|
||||
text: '$199/yr (save 20%)',
|
||||
link: `https://store.pockethost.io/checkout/buy/96e4ab4b-f646-4fb2-b830-5584db983e73?checkout[custom][user_id]=${$userStore?.id}&checkout[email]=${$userStore?.email}`,
|
||||
},
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
const items: Item[] = [
|
||||
{
|
||||
name: 'Number of Instances',
|
||||
items: ['25', '250'],
|
||||
info: `Each instance can have its own domain, database, and files.`,
|
||||
},
|
||||
{
|
||||
name: 'Premium Bandwidth',
|
||||
items: ['10GB', '100GB'],
|
||||
info: `Premium Bandwidth is pooled per account and refreshes monthly. Any data data transferred in or out of any PocketHost instance you own counts against this pool. This includes all HTTP traffic, file downloads, and admin panel usage.`,
|
||||
},
|
||||
{
|
||||
name: 'Standard Bandwidth',
|
||||
items: ['Unlimited', 'Unlimited'],
|
||||
info: `Standard Bandwidth may be slower than Premium. It is provided so your app doesn't just stop working when you exceed your Premium quota.`,
|
||||
},
|
||||
{
|
||||
name: 'Storage',
|
||||
items: [
|
||||
'10GB',
|
||||
'<div class="flex flex-col"><div>100GB</div><div class="text-xs text-neutral-content">$5 per 100GB thereafter</div></div>',
|
||||
],
|
||||
info: `Storage is pooled per account. The first 100GB is included. After that, you are billed $5 for each subsequent 100GB block or partial block of 100GB used.`,
|
||||
},
|
||||
{
|
||||
name: 'Max inodes',
|
||||
items: ['1k', '100k'],
|
||||
info: `inodes are pooled per account. An inode is typically a file or directory. The inode limit counts toward any file you or your users upload.`,
|
||||
},
|
||||
{
|
||||
name: 'CPU',
|
||||
items: ['Unlimited', 'Unlimited'],
|
||||
info: `Your PocketBase instance runs in a Docker container. While there are practical limits, we do not meter or throttle CPU usage.`,
|
||||
},
|
||||
{
|
||||
name: 'FTP access',
|
||||
items: ['YesBlock', 'YesBlock'],
|
||||
},
|
||||
{
|
||||
name: 'Run every version of PocketBase',
|
||||
items: ['YesBlock', 'YesBlock'],
|
||||
info: `We support the latest patch of every minor release of PocketBase.`,
|
||||
},
|
||||
{
|
||||
name: 'Secure infrastructure',
|
||||
items: ['YesBlock', 'YesBlock'],
|
||||
},
|
||||
|
||||
{
|
||||
name: 'Community Discord',
|
||||
items: ['YesBlock', 'YesBlock'],
|
||||
},
|
||||
{
|
||||
name: 'Custom Domains',
|
||||
items: ['NoBlock', 'YesBlock'],
|
||||
},
|
||||
{ name: 'Pro Discord', items: ['NoBlock', 'YesBlock'] },
|
||||
]
|
||||
</script>
|
||||
|
||||
<div class="bg-info text-info-content p-4 text-center text-lg">
|
||||
Learn more about our <a href="/docs/pricing-ethos" class="link"
|
||||
>pricing ethos</a
|
||||
>
|
||||
and <a href="/terms" class="link">Fair Use</a>.
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="flex justify-center items-center flex-col space-y-10 p-10 bg-emerald-950"
|
||||
>
|
||||
<div class="text-2xl text-center">Feature comparison</div>
|
||||
|
||||
<table class="hidden md:table border-spacing-x-8 max-w-[354px] md:max-w-md">
|
||||
<thead class="table-header-group">
|
||||
<tr class=" text-left">
|
||||
<th class=" md:min-w-48"> </th>
|
||||
{#each plans as plan}
|
||||
<th class="text-center text-lg min-w-48">
|
||||
{plan.name}
|
||||
</th>
|
||||
{/each}
|
||||
</tr>
|
||||
<tr class="">
|
||||
<th class=""></th>
|
||||
{#each plans as plan}
|
||||
{#if !plan.price}
|
||||
<th class="text-center">Free Forever</th>
|
||||
{:else}
|
||||
<th>
|
||||
<div class="flex flex-col justify-center py-4">
|
||||
{#each plan.price as pPrice}
|
||||
<a
|
||||
href={$userStore && $isUserVerified
|
||||
? pPrice.link
|
||||
: `/login`}
|
||||
class="btn btn-sm btn-secondary text-base mb-2"
|
||||
>
|
||||
{pPrice.text}
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
</th>
|
||||
{/if}
|
||||
{/each}
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{#each items as item}
|
||||
<tr class="table-row text-base border-b border-gray-700">
|
||||
<FeatureName {item} enlarge />
|
||||
{#each item.items as itemVal, idx}
|
||||
<FeatureSupportBlock item={itemVal ?? ''} />
|
||||
{/each}
|
||||
</tr>
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<table class="md:hidden border-spacing-x-8 max-w-[354px] md:max-w-md">
|
||||
<thead class="table-header-group">
|
||||
{#each plans as plan}
|
||||
<tr class="text-left">
|
||||
<th class=" text-lg min-w-48">
|
||||
{plan.name}
|
||||
</th>
|
||||
{#if !plan.price}
|
||||
<th>Free Forever</th>
|
||||
{:else}
|
||||
<th>
|
||||
<div class="flex flex-col justify-center py-4">
|
||||
{#each plan.price as pPrice}
|
||||
<a
|
||||
href={$userStore && $isUserVerified
|
||||
? pPrice.link
|
||||
: `/login`}
|
||||
class="btn btn-xs w-40 btn-primary mb-2"
|
||||
>
|
||||
{pPrice.text}
|
||||
</a>
|
||||
{/each}
|
||||
</div>
|
||||
</th>
|
||||
{/if}
|
||||
</tr>
|
||||
{/each}
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{#each items as item}
|
||||
<tr class="table-row text-base border-b border-gray-700 col-span-2">
|
||||
<FeatureName {item} enlarge />
|
||||
</tr>
|
||||
{#each item.items as itemVal, idx}
|
||||
<tr class="table-row border-b even:border-0 border-gray-800">
|
||||
<td class="text-left">
|
||||
{plans[idx]?.name}
|
||||
</td>
|
||||
<FeatureSupportBlock item={itemVal ?? ''} />
|
||||
</tr>
|
||||
{/each}
|
||||
{/each}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
83
packages/dashboard/src/routes/(static)/pricing/features.ts
Normal file
83
packages/dashboard/src/routes/(static)/pricing/features.ts
Normal file
@ -0,0 +1,83 @@
|
||||
import regions from './regions.png?enhanced'
|
||||
|
||||
export const features = [
|
||||
{
|
||||
title: 'Risk-free Trial',
|
||||
description: `Test-drive PocketHost for 7 days. Credit card required.`,
|
||||
},
|
||||
|
||||
{
|
||||
title: 'Global Infrastructure',
|
||||
description:
|
||||
'PocketHost operates across 40+ regional edge locations. Users connect through our distributed network of edge servers, while our enterprise-grade internal VPN ensures optimal routing to your data. Experience consistent 10-30ms edge latency for superior application performance.',
|
||||
img: regions,
|
||||
},
|
||||
{
|
||||
title: 'Developer-Friendly Pricing',
|
||||
description: `Get started for just $5 per instance. After 5 instances, it's free.`,
|
||||
},
|
||||
{
|
||||
title: 'Unlimited Bandwidth, Storage, and CPU',
|
||||
description:
|
||||
'Access to unlimited bandwidth, storage, and computational resources under our <a href="/docs/pricing-ethos" class="link">fair use policy</a>.',
|
||||
},
|
||||
|
||||
{
|
||||
title: 'FTP access',
|
||||
description: 'Easily access your data from any FTP client.',
|
||||
},
|
||||
{
|
||||
title: 'Run every version of PocketBase',
|
||||
description: `We support the latest patch of every minor release of PocketBase.`,
|
||||
},
|
||||
{
|
||||
title: 'Enterprise-Grade Security',
|
||||
description:
|
||||
'Infrastructure secured with RSA-2048 encryption and industry-standard security protocols.',
|
||||
},
|
||||
|
||||
{
|
||||
title: 'Community Support',
|
||||
description:
|
||||
'Access to our community Discord platform with over 1,000 developers and technical support resources.',
|
||||
},
|
||||
{
|
||||
title: 'Priority Support Channel',
|
||||
description:
|
||||
'Subscribers receive access to dedicated support channels via Discord.',
|
||||
},
|
||||
{
|
||||
title: 'Domain Customization',
|
||||
description:
|
||||
'Seamless custom domain integration for your PocketHost instances.',
|
||||
},
|
||||
{
|
||||
title: 'Lifetime Access Option',
|
||||
description:
|
||||
'Limited-time opportunity for lifetime Pro tier access (up to 250 instances) with a single payment.',
|
||||
},
|
||||
{
|
||||
title: 'Early Access to New Tech',
|
||||
description: `Get your hands on our experimental <a href="/blog/announcing-pocker" class="link">Pocker</a> tech before anyone else. Help us push the boundaries of what's possible.`,
|
||||
},
|
||||
{
|
||||
title: 'Enterprise Reliability',
|
||||
description:
|
||||
'Industry-leading 99.95% uptime guarantee ensures consistent application availability.',
|
||||
},
|
||||
{
|
||||
title: 'Managed Infrastructure',
|
||||
description:
|
||||
'Comprehensive infrastructure management including scaling, backups, and maintenance operations.',
|
||||
},
|
||||
{
|
||||
title: 'Hack More, Pay Less',
|
||||
description:
|
||||
'Why waste time managing servers? Put your resources into building the next big thing. Your wallet (and your future app) will thank you.',
|
||||
},
|
||||
{
|
||||
title: 'Open Source Support',
|
||||
description:
|
||||
'Your subscription directly supports the development of open-source PocketBase and PocketHost projects.',
|
||||
},
|
||||
]
|
BIN
packages/dashboard/src/routes/(static)/pricing/regions.png
Normal file
BIN
packages/dashboard/src/routes/(static)/pricing/regions.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 286 KiB |
@ -42,6 +42,7 @@
|
||||
<GdprBanner
|
||||
cookieName="pockethost_gpdr"
|
||||
description="PocketHost uses cookies to ensure you get the best experience."
|
||||
showEditIcon={false}
|
||||
/>
|
||||
<div>
|
||||
<!-- Google tag (gtag.js) -->
|
||||
|
@ -25,24 +25,24 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@s-libs/micro-dash": "^18.0.0",
|
||||
"ajv": "^8.12.0",
|
||||
"better-sqlite3": "^11.5.0",
|
||||
"ajv": "^8.17.1",
|
||||
"better-sqlite3": "^11.7.2",
|
||||
"bottleneck": "^2.19.5",
|
||||
"commander": "^12.1.0",
|
||||
"commander": "^13.0.0",
|
||||
"cors": "^2.8.5",
|
||||
"devcert": "^1.2.2",
|
||||
"dockerode": "^4.0.2",
|
||||
"dotenv": "^16.4.5",
|
||||
"dockerode": "^4.0.3",
|
||||
"dotenv": "^16.4.7",
|
||||
"env-paths": "^3.0.0",
|
||||
"env-var": "^7.5.0",
|
||||
"eventsource": "^2.0.2",
|
||||
"eventsource": "^3.0.2",
|
||||
"exit-hook": "^4.0.0",
|
||||
"express": "^4.18.2",
|
||||
"express": "^4.21.2",
|
||||
"express-async-errors": "^3.1.1",
|
||||
"express-sslify": "^1.2.0",
|
||||
"fs-extra": "^11.2.0",
|
||||
"ftp-srv": "github:pockethost/ftp-srv#0fc708bae0d5d7a55ce948767f082d6fcfb2af59",
|
||||
"glob": "^11.0.0",
|
||||
"glob": "^11.0.1",
|
||||
"gobot": "1.0.0-alpha.41",
|
||||
"http-proxy": "^1.18.1",
|
||||
"http-proxy-middleware": "^3.0.3",
|
||||
@ -52,33 +52,33 @@
|
||||
"node-fetch": "^3.3.2",
|
||||
"node-os-utils": "^1.3.7",
|
||||
"pocketbase": "^0.21.3",
|
||||
"semver": "^7.5.4",
|
||||
"semver": "^7.6.3",
|
||||
"tail": "^2.2.6",
|
||||
"type-fest": "^4.6.0",
|
||||
"type-fest": "^4.32.0",
|
||||
"vhost": "^3.0.2",
|
||||
"winston": "^3.17.0",
|
||||
"winston-transport": "^4.9.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/better-sqlite3": "^7.6.11",
|
||||
"@types/better-sqlite3": "^7.6.12",
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/d3-scale": "^4.0.6",
|
||||
"@types/d3-scale-chromatic": "^3.0.1",
|
||||
"@types/decompress": "^4.2.6",
|
||||
"@types/dockerode": "^3.3.31",
|
||||
"@types/eventsource": "^1.1.14",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/d3-scale": "^4.0.8",
|
||||
"@types/d3-scale-chromatic": "^3.1.0",
|
||||
"@types/decompress": "^4.2.7",
|
||||
"@types/dockerode": "^3.3.34",
|
||||
"@types/eventsource": "^1.1.15",
|
||||
"@types/express": "^5.0.0",
|
||||
"@types/express-sslify": "^1.2.5",
|
||||
"@types/fs-extra": "^11.0.4",
|
||||
"@types/http-proxy": "^1.17.13",
|
||||
"@types/js-cookie": "^3.0.5",
|
||||
"@types/json-stringify-safe": "^5.0.2",
|
||||
"@types/memorystream": "^0.3.3",
|
||||
"@types/node": "^20.8.10",
|
||||
"@types/http-proxy": "^1.17.15",
|
||||
"@types/js-cookie": "^3.0.6",
|
||||
"@types/json-stringify-safe": "^5.0.3",
|
||||
"@types/memorystream": "^0.3.4",
|
||||
"@types/node": "^22.10.5",
|
||||
"@types/node-os-utils": "^1.3.4",
|
||||
"@types/semver": "^7.5.4",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@types/tail": "^2.2.3",
|
||||
"@types/unzipper": "^0.10.8",
|
||||
"@types/unzipper": "^0.10.10",
|
||||
"@types/vhost": "^3.0.9"
|
||||
},
|
||||
"files": [
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
import { LogLevelName, gracefulExit, logger } from '@'
|
||||
import { program } from 'commander'
|
||||
import EventSource from 'eventsource'
|
||||
import { EventSource } from 'eventsource'
|
||||
import { version } from '../../package.json'
|
||||
import { EdgeCommand } from './commands/EdgeCommand'
|
||||
import { FirewallCommand } from './commands/FirewallCommand'
|
||||
|
@ -1,22 +0,0 @@
|
||||
diff --git a/lib/eventsource.js b/lib/eventsource.js
|
||||
index bd401a106c16ca1f5aa6a965db51d9a38f10a1c6..a271b2d391df5a62ccf5d4e69e81afe1fa36c704 100644
|
||||
--- a/lib/eventsource.js
|
||||
+++ b/lib/eventsource.js
|
||||
@@ -87,7 +87,7 @@ function EventSource (url, eventSourceInitDict) {
|
||||
var reconnectUrl = null
|
||||
|
||||
function connect () {
|
||||
- var options = parse(url)
|
||||
+ var options = new URL(url)
|
||||
var isSecure = options.protocol === 'https:'
|
||||
options.headers = { 'Cache-Control': 'no-cache', 'Accept': 'text/event-stream' }
|
||||
if (lastEventId) options.headers['Last-Event-ID'] = lastEventId
|
||||
@@ -113,7 +113,7 @@ function EventSource (url, eventSourceInitDict) {
|
||||
// and include the original url in path and Host headers
|
||||
var useProxy = eventSourceInitDict && eventSourceInitDict.proxy
|
||||
if (useProxy) {
|
||||
- var proxy = parse(eventSourceInitDict.proxy)
|
||||
+ var proxy = new URL(eventSourceInitDict.proxy)
|
||||
isSecure = proxy.protocol === 'https:'
|
||||
|
||||
options.protocol = isSecure ? 'https:' : 'http:'
|
3610
pnpm-lock.yaml
generated
3610
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user