Skip to content

Commit

Permalink
Allow switching between Hobby and Pro plans; update eslint rules
Browse files Browse the repository at this point in the history
  • Loading branch information
mlejva committed Feb 15, 2024
1 parent cd50d42 commit c37d184
Show file tree
Hide file tree
Showing 13 changed files with 256 additions and 91 deletions.
28 changes: 16 additions & 12 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,23 @@ module.exports = {
browser: true,
es6: true,
},
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
parser: "@typescript-eslint/parser",
extends: ['eslint:recommended', 'plugin:@typescript-eslint/recommended'],
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaVersion: "latest",
sourceType: "module",
ecmaVersion: 'latest',
sourceType: 'module',
},
ignorePatterns: ["dist/", "node_modules/", "*.gen.ts"],
plugins: ["@typescript-eslint", "unused-imports"],
ignorePatterns: ['dist/', 'node_modules/', '*.gen.ts'],
plugins: ['@typescript-eslint', 'unused-imports', '@stylistic/ts'],
rules: {
"@typescript-eslint/member-ordering": "error",
"@typescript-eslint/ban-ts-comment": "off", // "move fast" mode
"@typescript-eslint/no-explicit-any": "off", // "move fast" mode
"linebreak-style": ["error", "unix"],
"unused-imports/no-unused-imports": "error",
'@typescript-eslint/member-ordering': 'error',
'@typescript-eslint/ban-ts-comment': 'off', // "move fast" mode
'@typescript-eslint/no-explicit-any': 'off', // "move fast" mode
'linebreak-style': ['error', 'unix'],
'unused-imports/no-unused-imports': 'error',
// No double quotes
quotes: ['error', 'single', { avoidEscape: true }],
// No extra semicolon
'@stylistic/ts/semi': ['error', 'never'],
},
};
}
1 change: 1 addition & 0 deletions apps/docs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
},
"devDependencies": {
"@nodelib/fs.walk": "^2.0.0",
"@stylistic/eslint-plugin-ts": "^1.6.2",
"@types/mdx": "^2.0.8",
"eslint-config-next": "13.4.19",
"knip": "^2.25.2",
Expand Down
42 changes: 21 additions & 21 deletions apps/docs/src/app/getting-started/api-key/APIKey.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
"use client";
'use client'

import clsx from "clsx";
import { useAccessToken, useApiKey, useUser } from "@/utils/useUser";
import { usePostHog } from "posthog-js/react";
import { useSignIn } from "@/utils/useSignIn";
import { obfuscateSecret } from "@/utils/obfuscate";
import { Button } from "@/components/Button";
import { CopyButton } from "@/components/CopyButton";
import clsx from 'clsx'
import { useAccessToken, useApiKey, useUser } from '@/utils/useUser'
import { usePostHog } from 'posthog-js/react'
import { useSignIn } from '@/utils/useSignIn'
import { obfuscateSecret } from '@/utils/obfuscate'
import { Button } from '@/components/Button'
import { CopyButton } from '@/components/CopyButton'
import { Note } from '@/components/mdx'

export function CopyableSecret({
Expand All @@ -30,14 +30,14 @@ export function CopyableSecret({
code={secret}
onAfterCopy={onAfterCopy}
customPositionClassNames={clsx(
"top-[-2px] bottom-[2px]" /* nudge 2px up*/,
"left-[-8px] right-[-8px]" /* widen a little to fit nicely */,
"min-h-[28px]"
'top-[-2px] bottom-[2px]' /* nudge 2px up*/,
'left-[-8px] right-[-8px]' /* widen a little to fit nicely */,
'min-h-[28px]'
)}
/>
</span>
</div>
);
)
}

function SecretBlock({ name, description, secret, posthog, tip }) {
Expand All @@ -48,7 +48,7 @@ function SecretBlock({ name, description, secret, posthog, tip }) {
<div className="group text-sm ml-4 border-2 mt-1 outline-2 outline-offset-2 outline-zinc-700 rounded-full px-[8px]">
<CopyableSecret
secret={secret}
onAfterCopy={() => posthog?.capture("copied API key")}
onAfterCopy={() => posthog?.capture('copied API key')}
obfuscateStart={12}
obfuscateEnd={5}
/>
Expand All @@ -57,15 +57,15 @@ function SecretBlock({ name, description, secret, posthog, tip }) {
<span>{description}</span>
<span>{tip}</span>
</div>
);
)
}

function APIKey() {
const signIn = useSignIn();
const { user } = useUser();
const apiKey = useApiKey();
const posthog = usePostHog();
const accessToken = useAccessToken();
const signIn = useSignIn()
const { user } = useUser()
const apiKey = useApiKey()
const posthog = usePostHog()
const accessToken = useAccessToken()

return (
<div className="flex flex-col items-start justify-start -mb-6">
Expand Down Expand Up @@ -119,7 +119,7 @@ function APIKey() {
</div>
)}
</div>
);
)
}

export default APIKey;
export default APIKey
26 changes: 14 additions & 12 deletions apps/docs/src/app/pricing/ManageBilling.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@
'use client'

import { useEffect, useState } from 'react'
import { Button } from '@/components/Button'
import { useUser } from '@/utils/useUser'
import { useRouter } from 'next/navigation'


const manageBillingURL = process.env.NEXT_PUBLIC_STRIPE_BILLING_URL

function ManageBilling() {
const { user } = useUser()
const router = useRouter()
const [url, setURL] = useState('')

useEffect(function getBillingURL() {
if (!user) return
const u = `${process.env.NEXT_PUBLIC_STRIPE_BILLING_URL}?prefilled_email=${user.teams[0].email}`
setURL(u)
}, [user])

if (!user || !manageBillingURL) {
if (!user || !url) {
return
}

return (
<div className="flex flex-col items-start justify-start gap-4 mb-8">
<div className="flex flex-col items-start justify-start">
</div>
<Button onClick={() => router.push(manageBillingURL)}>
Manage Billing
</Button>
<div className="mb-8 flex flex-col items-start justify-start gap-4">
<div className="flex flex-col items-start justify-start"></div>
<a href={url} target="_blank" rel="noreferrer">
<Button>Manage Billing</Button>
</a>
</div>
)
}
Expand Down
45 changes: 45 additions & 0 deletions apps/docs/src/app/pricing/SwitchToHobbyButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
'use client'

import { useEffect, useState } from 'react'
import { Button } from '@/components/Button'
import { useUser } from '@/utils/useUser'

import { TierActiveTag } from './TierActiveTag'


const tierID = 'base_v1'

function SwitchToHobbyButton() {
const { user } = useUser()
const [url, setURL] = useState('')

useEffect(function getBillingURL() {
if (!user) return
const u = `${process.env.NEXT_PUBLIC_STRIPE_BILLING_URL}?prefilled_email=${user.teams[0].email}`
setURL(u)
}, [user])

// Only show the button if the user is on the base_v1 tier.
// Teams can have custom tiers. We only want the button to users on the free tier.
if (!user) {
return
}

return (
<div className="flex flex-col items-start justify-start gap-1 my-4">
<div className="flex items-center justify-start gap-2">
{user.pricingTier.id === tierID && (
<TierActiveTag />
)}

{user.pricingTier.id !== tierID && (
<a href={url} target="_blank" rel="noreferrer noopener">
<Button>Switch to Hobby</Button>
</a>
)}
</div>
</div>
)
}

export default SwitchToHobbyButton
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@ import { useUser } from '@/utils/useUser'
import { useState } from 'react'
import { useRouter } from 'next/navigation'

export interface Props {
tierID: string
}
import { TierActiveTag } from './TierActiveTag'

const billingApiURL = process.env.NEXT_PUBLIC_BILLING_API_URL
function createCheckout(tierID: string, teamID: string) {
Expand All @@ -23,17 +21,10 @@ function createCheckout(tierID: string, teamID: string) {
})
}

function tierDisplayName(tierID: string) {
if (tierID === 'pro_v1') {
return 'Pro'
}
throw new Error(`Unknown tierID: ${tierID}`)
}

const tierID = 'pro_v1'

function SwitchTearButton({
tierID,
}: Props) {
function SwitchTierButton() {
const { user } = useUser()
const [error, setError] = useState('')
const router = useRouter()
Expand All @@ -53,26 +44,32 @@ function SwitchTearButton({

// Only show the button if the user is on the base_v1 tier.
// Teams can have custom tiers. We only want the button to users on the free tier.
if (!user || !billingApiURL || user.teams[0].tier !== 'base_v1') {
if (!user || !billingApiURL) {
return
}

return (
<div className="flex flex-col items-start justify-start gap-1 my-4">
<div className="flex items-center justify-start gap-2">
<div className="flex flex-col items-start justify-start">
</div>
<Button
onClick={createCheckoutSession}
>
Switch to {tierDisplayName(tierID)} tier
</Button>
{user.pricingTier.id === tierID && (
<TierActiveTag />
)}


{user.pricingTier.id !== tierID && (
<Button
onClick={createCheckoutSession}
>
Switch to Pro
</Button>
)}
</div>

{error && (
<span className="text-red-500 font-medium">{error}</span>
)}
</div>
)
}

export default SwitchTearButton
export default SwitchTierButton
9 changes: 9 additions & 0 deletions apps/docs/src/app/pricing/TierActiveTag.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@

export function TierActiveTag() {
return (
<span className="text-sm font-bold text-green-500">
ACTIVE
</span>
)
}

16 changes: 8 additions & 8 deletions apps/docs/src/app/pricing/page.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import Promo from './Promo'
import SwitchTearButton from './SwitchTearButton'
import SwitchToHobbyButton from './SwitchToHobbyButton'
import SwitchToProButton from './SwitchToProButton'
import ManageBilling from './ManageBilling'

# Pricing
Expand All @@ -9,7 +10,6 @@ import ManageBilling from './ManageBilling'

We charge you based on your real sandbox usage. All new users get one-time free $100 worth of usage in credits. {{ className: 'lead' }}


## Resource pricing
- vCPU: $0.000014 per second (~$0.05 per hour)
- GB RAM: $0.0000045 per second (~$0.018 per hour)
Expand All @@ -18,7 +18,8 @@ We charge you based on your real sandbox usage. All new users get one-time free
## Plans
Additionally to the usage costs, you can also select a tier that includes dedicated support, prioritized features, and customizable sandbox machine specs.

### Hobby tier (usage costs)
### Hobby tier ($0/month + usage costs)
<SwitchToHobbyButton/>
- One-time $100 of usage in credits
- Community support
- 1 hours max sandbox session length
Expand All @@ -28,13 +29,12 @@ Additionally to the usage costs, you can also select a tier that includes dedica
- 512 MB RAM
- 1 GB free disk storage

### Pro tier ($150 per month + usage costs)
<SwitchTearButton tierID={'pro_v1'} />

### Pro tier ($150/month + usage costs)
<SwitchToProButton/>
- One-time $100 of usage in credits
- Dedicated Slack channel with live Pro support from our team
- Prioritized features
- Customize your sandbox machine specs based on your needs
- Customize your sandbox machine specs based on your needs ([let us know](/getting-help))
- 24 hours max sandbox session length
- Up to 100 concurrently running sandboxes
- Pro sandbox machine specs:
Expand All @@ -43,4 +43,4 @@ Additionally to the usage costs, you can also select a tier that includes dedica
- 5 GB free disk storage


If you need more resources, please [reach out to us](/getting-help) with your use case.
If you need any additional features or resources, please [reach out to us](/getting-help) with your use case.
10 changes: 5 additions & 5 deletions apps/docs/src/app/sandbox/overview/features.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
/* eslint-disable react/jsx-key */
import Link from 'next/link';
import Link from 'next/link'

export const features = [
<span>
<Link href="https://github.com/e2b-dev/e2b" target="_blank" rel="noreferrer noopener">Open-source</Link> sandbox for LLM
</span>,
"A full VM environment",
"No need for orchestration or infrastructure management",
"Ability to give each user of your AI app their own isolated environment",
'A full VM environment',
'No need for orchestration or infrastructure management',
'Ability to give each user of your AI app their own isolated environment',
<span>
<Link href="/getting-started/installation">
Python & Node.js SDK
Expand All @@ -22,7 +22,7 @@ export const features = [
</Link>
, and more.
</span>,
"Support for up to 24h long-running sandbox sessions",
'Support for up to 24h long-running sandbox sessions',
<span>
Ability to <Link href="/sandbox/api/upload">upload files</Link> to the sandbox and <Link href="/sandbox/api/download">download files </Link>from the sandbox,
</span>,
Expand Down
8 changes: 4 additions & 4 deletions apps/docs/src/components/Banner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,21 @@ interface Promo {
validTo: string
}

const fetcher = (url: string) => fetch(url).then((res) => res.json());
const fetcher = (url: string) => fetch(url).then((res) => res.json())

export function Banner() {
const { user } = useUser()

const { data, error, isLoading } = useSWR<Promo>(`/docs/pricing/promo?${new URLSearchParams({ id: user?.pricingTier.id })}`, fetcher)

if (isLoading) return null;
if (isLoading) return null

if (error) {
console.error('Error fetching promo:', error)
return null;
return null
}

if (!user?.pricingTier.isPromo) return null;
if (!user?.pricingTier.isPromo) return null

return (
<div className="py-2 px-8 border-y border-zinc-700 flex flex-col md:flex-row gap-1 fixed inset-x-0 top-[55px] bg-zinc-800 z-40">
Expand Down
Loading

0 comments on commit c37d184

Please sign in to comment.