From 99fa5af914012d91d7ef192f47e0be88200fa7f5 Mon Sep 17 00:00:00 2001 From: Vasek Mlejnsky Date: Tue, 17 Dec 2024 11:44:12 -0800 Subject: [PATCH 01/10] docs: update code snippets for persistence --- .../(docs)/docs/sandbox/persistence/page.mdx | 56 +++++++++++++------ 1 file changed, 39 insertions(+), 17 deletions(-) diff --git a/apps/web/src/app/(docs)/docs/sandbox/persistence/page.mdx b/apps/web/src/app/(docs)/docs/sandbox/persistence/page.mdx index a4dff35d4..1e35d43b4 100644 --- a/apps/web/src/app/(docs)/docs/sandbox/persistence/page.mdx +++ b/apps/web/src/app/(docs)/docs/sandbox/persistence/page.mdx @@ -2,8 +2,8 @@ Sandbox persistence is currently in beta: -1. [Reach out to us](/docs/support) to get access to the beta. -1. You need to install the [beta version of the SDKs](#installing-the-beta-version-of-the-sdks). +1. [Reach out to us](/docs/support) with your use caseto get access to the beta. +1. You'll need to install the [beta version of the SDKs](#installing-the-beta-version-of-the-sdks). 1. Consider [some limitations](#limitations-while-in-beta). 1. The persistence is free for all users during the beta. @@ -17,13 +17,25 @@ To use the sandbox persistence, you need to install the beta version of the SDKs ```bash {{ language: 'js' }} -npm i e2b@beta +npm i @e2b/code-interpreter@beta +# +# or use Core: https://github.com/e2b-dev/e2b +# npm i e2b@beta +# +# or use Desktop: https://github.com/e2b-dev/desktop +# npm i @e2b/desktop@beta ``` ```bash {{ language: 'python' }} # Install the latest beta version of the SDK on PyPi # https://pypi.org/project/e2b/#history -pip install e2b-code-interpreter==1.1.0.b17 +pip install e2b-code-interpreter==1.0.4b0 +# +# or use Core: https://github.com/e2b-dev/e2b +# pip install e2b==1.1.0.b17 +# +# or use Desktop: https://github.com/e2b-dev/desktop +# pip install e2b-desktop==1.1.0.b0 ``` @@ -33,9 +45,9 @@ When you pause a sandbox, both the sandbox's filesystem and memory state will be ```js -import { Sandbox } from 'e2b' -// or use Code Interpreter: https://github.com/e2b-dev/code-interpreter -// import { Sandbox } from '@e2b/code-interpreter' +import { Sandbox } from '@e2b/code-interpreter' +// or use Core: https://github.com/e2b-dev/e2b +// import { Sandbox } from 'e2b' // // or use Desktop: https://github.com/e2b-dev/desktop // import { Sandbox } from '@e2b/desktop' @@ -50,9 +62,9 @@ const sandboxId = await sbx.pause() // $HighlightLine console.log('Sandbox paused', sandboxId) // $HighlightLine ``` ```python -from e2b import Sandbox -# or use Code Interpreter: https://github.com/e2b-dev/code-interpreter -# from e2b_code_interpreter import Sandbox +from e2b_code_interpreter import Sandbox +# or use Core: https://github.com/e2b-dev/e2b +# from e2b import Sandbox # # or use Desktop: https://github.com/e2b-dev/desktop # from e2b_desktop import Sandbox @@ -75,9 +87,9 @@ This means that all the files in the sandbox's filesystem will be restored and a ```js -import { Sandbox } from 'e2b' -// or use Code Interpreter: https://github.com/e2b-dev/code-interpreter -// import { Sandbox } from '@e2b/code-interpreter' +import { Sandbox } from '@e2b/code-interpreter' +// or use Core: https://github.com/e2b-dev/e2b +// import { Sandbox } from 'e2b' // // or use Desktop: https://github.com/e2b-dev/desktop // import { Sandbox } from '@e2b/desktop' @@ -97,8 +109,8 @@ console.log('Sandbox resumed', sameSbx.sandboxId) // $HighlightLine ``` ```python from e2b import Sandbox -# or use Code Interpreter: https://github.com/e2b-dev/code-interpreter -# from e2b_code_interpreter import Sandbox +# or use Core: https://github.com/e2b-dev/e2b +# from e2b import Sandbox # # or use Desktop: https://github.com/e2b-dev/desktop # from e2b_desktop import Sandbox @@ -126,12 +138,22 @@ You can pass a custom timeout to the `Sandbox.resume()` method like this: ```js -import { Sandbox } from 'e2b' +import { Sandbox } from '@e2b/code-interpreter' +// or use Core: https://github.com/e2b-dev/e2b +// import { Sandbox } from 'e2b' +// +// or use Desktop: https://github.com/e2b-dev/desktop +// import { Sandbox } from '@e2b/desktop' const sbx = await Sandbox.resume(sandboxId, { timeoutMs: 60 * 1000 }) // 60 seconds ``` ```python -from e2b import Sandbox +from e2b_code_interpreter import Sandbox +# or use Core: https://github.com/e2b-dev/e2b +# from e2b import Sandbox +# +# or use Desktop: https://github.com/e2b-dev/desktop +# from e2b_desktop import Sandbox sbx = Sandbox.resume(sandbox_id, timeout=60) # 60 seconds ``` From 7f79ebfa7f9689457cc08b1b4d38eb1f3115cac7 Mon Sep 17 00:00:00 2001 From: Vasek Mlejnsky Date: Tue, 17 Dec 2024 11:48:46 -0800 Subject: [PATCH 02/10] docs: update installation instructions to install latest beta in python --- .../app/(docs)/docs/sandbox/persistence/page.mdx | 15 +++------------ 1 file changed, 3 insertions(+), 12 deletions(-) diff --git a/apps/web/src/app/(docs)/docs/sandbox/persistence/page.mdx b/apps/web/src/app/(docs)/docs/sandbox/persistence/page.mdx index 70fc95a0d..60b3dff20 100644 --- a/apps/web/src/app/(docs)/docs/sandbox/persistence/page.mdx +++ b/apps/web/src/app/(docs)/docs/sandbox/persistence/page.mdx @@ -18,31 +18,22 @@ To use the sandbox persistence, you need to install the beta version of the SDKs ```bash {{ language: 'js' }} npm i @e2b/code-interpreter@beta -<<<<<<< HEAD # # or use Core: https://github.com/e2b-dev/e2b # npm i e2b@beta # # or use Desktop: https://github.com/e2b-dev/desktop # npm i @e2b/desktop@beta -======= ->>>>>>> e81a6dc06d59e775bdd9579d9c3226cee35924cd ``` ```bash {{ language: 'python' }} -# Install the latest beta version of the SDK on PyPi -# https://pypi.org/project/e2b/#history -<<<<<<< HEAD -pip install e2b-code-interpreter==1.0.4b0 +pip install e2b-code-interpreter==1.0.4b # # or use Core: https://github.com/e2b-dev/e2b -# pip install e2b==1.1.0.b17 +# pip install e2b==1.1.0.b # # or use Desktop: https://github.com/e2b-dev/desktop -# pip install e2b-desktop==1.1.0.b0 -======= -pip install e2b-code-interpreter==1.0.4b ->>>>>>> e81a6dc06d59e775bdd9579d9c3226cee35924cd +# pip install e2b-desktop==1.1.0.b ``` From d3f19614cd0a41c39337635223e69ed38acf1b85 Mon Sep 17 00:00:00 2001 From: Vasek Mlejnsky Date: Tue, 17 Dec 2024 12:54:50 -0800 Subject: [PATCH 03/10] Update apps/web/src/app/(docs)/docs/sandbox/persistence/page.mdx Co-authored-by: 0div <98087403+0div@users.noreply.github.com> --- apps/web/src/app/(docs)/docs/sandbox/persistence/page.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/app/(docs)/docs/sandbox/persistence/page.mdx b/apps/web/src/app/(docs)/docs/sandbox/persistence/page.mdx index 60b3dff20..2c1d112ed 100644 --- a/apps/web/src/app/(docs)/docs/sandbox/persistence/page.mdx +++ b/apps/web/src/app/(docs)/docs/sandbox/persistence/page.mdx @@ -2,7 +2,7 @@ Sandbox persistence is currently in beta: -1. [Reach out to us](/docs/support) with your use caseto get access to the beta. +1. [Reach out to us](/docs/support) with your use case to get access to the beta. 1. You'll need to install the [beta version of the SDKs](#installing-the-beta-version-of-the-sdks). 1. Consider [some limitations](#limitations-while-in-beta). 1. The persistence is free for all users during the beta. From 49849f38bdd7167a9b89ac13db7a4eef5fe29a87 Mon Sep 17 00:00:00 2001 From: Vasek Mlejnsky Date: Wed, 18 Dec 2024 14:39:51 -0800 Subject: [PATCH 04/10] Correctly parse the API URL in dashboard --- apps/web/src/components/Dashboard/Developer.tsx | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/apps/web/src/components/Dashboard/Developer.tsx b/apps/web/src/components/Dashboard/Developer.tsx index b3bd86b59..f2c3eda74 100644 --- a/apps/web/src/components/Dashboard/Developer.tsx +++ b/apps/web/src/components/Dashboard/Developer.tsx @@ -20,6 +20,14 @@ export const DeveloperContent = ({ } } + function removeApiSubdomain(url: URL): string { + const hostParts = url.host.split('.') + if (hostParts.length > 2 && hostParts[0] === 'api') { + return hostParts.slice(1).join('.') + } + return url.host + } + return (
@@ -45,7 +53,7 @@ export const DeveloperContent = ({
                 E2B_DOMAIN={
                   isUrl(apiUrl)
-                    ? `"${new URL(apiUrl).host}"`
+                    ? `"${removeApiSubdomain(new URL(apiUrl))}"`
                     : 'Invalid URL'
                 }
               
From 0a70f3bc19cf07471f369b0ba59804d3a7d26255 Mon Sep 17 00:00:00 2001 From: Ben Fornefeld Date: Wed, 18 Dec 2024 23:49:47 +0100 Subject: [PATCH 05/10] fix: dashboard reset password href --- apps/web/src/components/Dashboard/Personal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/web/src/components/Dashboard/Personal.tsx b/apps/web/src/components/Dashboard/Personal.tsx index bce65ab06..419163685 100644 --- a/apps/web/src/components/Dashboard/Personal.tsx +++ b/apps/web/src/components/Dashboard/Personal.tsx @@ -124,7 +124,7 @@ export const PersonalContent = ({
- +
From f7d51d8b2f799a95ebe8b1f51dea99536ca769e9 Mon Sep 17 00:00:00 2001 From: Jakub Novak Date: Thu, 19 Dec 2024 13:50:45 +0100 Subject: [PATCH 06/10] Use billing subdomain --- .../src/app/(dashboard)/dashboard/page.tsx | 37 ++++------- apps/web/src/components/Dashboard/Billing.tsx | 32 ++++++---- .../src/components/Dashboard/Developer.tsx | 61 +++++++++++-------- apps/web/src/components/Dashboard/Keys.tsx | 36 ++++++----- .../web/src/components/Dashboard/Personal.tsx | 6 +- apps/web/src/components/Dashboard/Team.tsx | 59 ++++++++++-------- apps/web/src/components/Dashboard/Usage.tsx | 17 +++--- .../components/Pricing/SwitchToProButton.tsx | 19 ++---- 8 files changed, 140 insertions(+), 127 deletions(-) diff --git a/apps/web/src/app/(dashboard)/dashboard/page.tsx b/apps/web/src/app/(dashboard)/dashboard/page.tsx index 872b149c5..ac0ba0ffa 100644 --- a/apps/web/src/app/(dashboard)/dashboard/page.tsx +++ b/apps/web/src/app/(dashboard)/dashboard/page.tsx @@ -91,10 +91,6 @@ const Dashboard = ({ user }) => { 'apiUrl', process.env.NEXT_PUBLIC_API_URL || '' ) - const billingUrlState = useLocalStorage( - 'billingUrl', - process.env.NEXT_PUBLIC_BILLING_API_URL || '' - ) const initialTab = tab && menuLabels.includes(tab as MenuLabel) @@ -167,7 +163,6 @@ const Dashboard = ({ user }) => { setTeams={setTeams} setCurrentTeam={setCurrentTeam} apiUrlState={apiUrlState} - billingUrlState={billingUrlState} />
@@ -230,16 +225,18 @@ const MenuItem = ({ onClick: () => void }) => (

{label[0].toUpperCase() + label.slice(1)}

@@ -254,7 +251,6 @@ function MainContent({ setTeams, setCurrentTeam, apiUrlState, - billingUrlState, }: { selectedItem: MenuLabel user: E2BUser @@ -263,18 +259,13 @@ function MainContent({ setTeams: (teams: Team[]) => void setCurrentTeam: (team: Team) => void apiUrlState: [string, (value: string) => void] - billingUrlState: [string, (value: string) => void] }) { switch (selectedItem) { case 'personal': - return + return case 'keys': return ( - + ) case 'sandboxes': return @@ -287,9 +278,9 @@ function MainContent({ /> ) case 'usage': - return + return case 'billing': - return + return case 'team': return ( ) case 'developer': - return ( - - ) + return default: return } diff --git a/apps/web/src/components/Dashboard/Billing.tsx b/apps/web/src/components/Dashboard/Billing.tsx index 8c6a01d84..97e7c3828 100644 --- a/apps/web/src/components/Dashboard/Billing.tsx +++ b/apps/web/src/components/Dashboard/Billing.tsx @@ -29,10 +29,10 @@ interface Invoice { export const BillingContent = ({ team, - billingUrl, + apiUrl, }: { team: Team - billingUrl: string + apiUrl: string }) => { const [invoices, setInvoices] = useState([]) const [credits, setCredits] = useState(null) @@ -40,11 +40,14 @@ export const BillingContent = ({ useEffect(() => { const getInvoices = async function getInvoices() { setInvoices([]) - const res = await fetch(`${billingUrl}/teams/${team.id}/invoices`, { - headers: { - 'X-Team-API-Key': team.apiKeys[0], - }, - }) + const res = await fetch( + `https://billing.${apiUrl}/teams/${team.id}/invoices`, + { + headers: { + 'X-Team-API-Key': team.apiKeys[0], + }, + } + ) if (!res.ok) { // TODO: add sentry error console.log(res) @@ -55,11 +58,14 @@ export const BillingContent = ({ setInvoices(invoices) setCredits(null) - const creditsRes = await fetch(`${billingUrl}/teams/${team.id}/usage`, { - headers: { - 'X-Team-API-Key': team.apiKeys[0], - }, - }) + const creditsRes = await fetch( + `https://billing.${apiUrl}/teams/${team.id}/usage`, + { + headers: { + 'X-Team-API-Key': team.apiKeys[0], + }, + } + ) const credits = await creditsRes.json() setCredits(credits.credits) } @@ -108,7 +114,7 @@ export const BillingContent = ({

Pro tier

- +
  • One-time $100 credits
  • diff --git a/apps/web/src/components/Dashboard/Developer.tsx b/apps/web/src/components/Dashboard/Developer.tsx index f2c3eda74..7896e8795 100644 --- a/apps/web/src/components/Dashboard/Developer.tsx +++ b/apps/web/src/components/Dashboard/Developer.tsx @@ -1,15 +1,18 @@ 'use client' import { Button } from '../Button' +import { useState } from 'react' -const defaultAPIUrl = process.env.NEXT_PUBLIC_API_URL || '' +const DEFAULT_DOMAIN = 'e2b.dev' +const DOMAIN = process.env.NEXT_PUBLIC_DOMAIN || DEFAULT_DOMAIN export const DeveloperContent = ({ - apiUrlState, + domainState, }: { - apiUrlState: [string, (value: string) => void] + domainState: [string, (value: string) => void] }) => { - const [apiUrl, setApiUrl] = apiUrlState + const [domain, setDomain] = domainState + const [url, setUrl] = useState(domain) function isUrl(url: string) { try { @@ -20,12 +23,26 @@ export const DeveloperContent = ({ } } - function removeApiSubdomain(url: URL): string { - const hostParts = url.host.split('.') - if (hostParts.length > 2 && hostParts[0] === 'api') { - return hostParts.slice(1).join('.') + function changeUrl(url: string) { + setUrl(url) + + // Add protocol if missing + let urlWithProtocol: string = url + if (!url.startsWith('http://') && !url.startsWith('https://')) { + urlWithProtocol = `https://${url}` + } + + if (isUrl(urlWithProtocol)) { + const domain = new URL(urlWithProtocol).host + let hostParts = domain.split('.') + if (hostParts.length > 2 && hostParts[0] === 'api') { + hostParts = hostParts.slice(1) + } + setDomain(hostParts.join('.')) + } else { + console.log('Invalid URL') + setDomain('Invalid URL') } - return url.host } return ( @@ -35,10 +52,12 @@ export const DeveloperContent = ({ API URL - Set API URL so the dashboard can connect to your E2B Cluster and correctly display running sandboxes and templates. + Set API URL so the dashboard can connect to your E2B Cluster and + correctly display running sandboxes and templates. - {apiUrl !== defaultAPIUrl && ( + {/* Env var has to be set if the domain is not equal to the default */} + {domain !== DEFAULT_DOMAIN && (
    - In your environment variables, set the E2B_DOMAIN variable to your - custom domain: + In your environment variables, set the E2B_DOMAIN{' '} + variable to your custom domain:
    -                E2B_DOMAIN={
    -                  isUrl(apiUrl)
    -                    ? `"${removeApiSubdomain(new URL(apiUrl))}"`
    -                    : 'Invalid URL'
    -                }
    +                E2B_DOMAIN=
    +                {domain ?? 'Invalid URL'}
                   
    @@ -65,13 +81,10 @@ export const DeveloperContent = ({ setApiUrl(e.target.value)} + value={url} + onChange={(e) => changeUrl(e.target.value)} /> -
diff --git a/apps/web/src/components/Dashboard/Keys.tsx b/apps/web/src/components/Dashboard/Keys.tsx index 8790d7cc7..e17b36549 100644 --- a/apps/web/src/components/Dashboard/Keys.tsx +++ b/apps/web/src/components/Dashboard/Keys.tsx @@ -46,11 +46,11 @@ type TeamApiKey = { export const KeysContent = ({ currentTeam, user, - billingUrl, + apiUrl, }: { currentTeam: Team user: E2BUser - billingUrl: string + apiUrl: string }) => { const { toast } = useToast() const [isKeyDialogOpen, setIsKeyDialogOpen] = useState(false) @@ -60,10 +60,11 @@ export const KeysContent = ({ const [newApiKeyInput, setNewApiKeyInput] = useState('') const [apiKeys, setApiKeys] = useState([]) + console.log(apiUrl) useEffect(() => { async function fetchApiKeys() { const res = await fetch( - `${billingUrl}/teams/${currentTeam.id}/api-keys`, + `https://billing.${apiUrl}/teams/${currentTeam.id}/api-keys`, { headers: { 'X-USER-ACCESS-TOKEN': user.accessToken, @@ -85,7 +86,7 @@ export const KeysContent = ({ } fetchApiKeys() - }, [currentTeam]) + }, [apiUrl, currentTeam]) async function deleteApiKey() { if (apiKeys.length === 1) { @@ -97,7 +98,7 @@ export const KeysContent = ({ } const res = await fetch( - `${billingUrl}/teams/${currentTeam.id}/api-keys/${currentKey?.id}`, + `https://billing.${apiUrl}/teams/${currentTeam.id}/api-keys/${currentKey?.id}`, { method: 'DELETE', headers: { @@ -119,16 +120,19 @@ export const KeysContent = ({ } async function createApiKey() { - const res = await fetch(`${billingUrl}/teams/${currentTeam.id}/api-keys`, { - method: 'POST', - headers: { - 'X-USER-ACCESS-TOKEN': user.accessToken, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - name: newApiKeyInput, - }), - }) + const res = await fetch( + `https://billing.${apiUrl}/teams/${currentTeam.id}/api-keys`, + { + method: 'POST', + headers: { + 'X-USER-ACCESS-TOKEN': user.accessToken, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + name: newApiKeyInput, + }), + } + ) if (!res.ok) { toast({ @@ -149,7 +153,7 @@ export const KeysContent = ({ async function updateApiKey() { const res = await fetch( - `${billingUrl}/teams/${currentTeam.id}/api-keys/${currentKey?.id}`, + `https://billing.${apiUrl}/teams/${currentTeam.id}/api-keys/${currentKey?.id}`, { method: 'PATCH', headers: { diff --git a/apps/web/src/components/Dashboard/Personal.tsx b/apps/web/src/components/Dashboard/Personal.tsx index 419163685..10937ae31 100644 --- a/apps/web/src/components/Dashboard/Personal.tsx +++ b/apps/web/src/components/Dashboard/Personal.tsx @@ -9,10 +9,10 @@ import { E2BUser } from '@/utils/useUser' export const PersonalContent = ({ user, - billingUrl, + apiUrl, }: { user: E2BUser - billingUrl: string + apiUrl: string }) => { const { toast } = useToast() const [hovered, setHovered] = useState(false) @@ -33,7 +33,7 @@ export const PersonalContent = ({ } const updateUserEmail = async () => { - const res = await fetch(`${billingUrl}/users`, { + const res = await fetch(`https://billing.${apiUrl}/users`, { method: 'PATCH', headers: { 'Content-Type': 'application/json', diff --git a/apps/web/src/components/Dashboard/Team.tsx b/apps/web/src/components/Dashboard/Team.tsx index ff580dbed..574d55085 100644 --- a/apps/web/src/components/Dashboard/Team.tsx +++ b/apps/web/src/components/Dashboard/Team.tsx @@ -45,14 +45,14 @@ export const TeamContent = ({ teams, setTeams, setCurrentTeam, - billingUrl, + apiUrl, }: { team: Team user: E2BUser teams: Team[] setTeams: (teams: Team[]) => void setCurrentTeam: (team: Team) => void - billingUrl: string + apiUrl: string }) => { const [isDialogOpen, setIsDialogOpen] = useState(false) const [currentMemberId, setCurrentMemberId] = useState(null) @@ -64,12 +64,15 @@ export const TeamContent = ({ useEffect(() => { const getTeamMembers = async () => { - const res = await fetch(`${billingUrl}/teams/${team.id}/users`, { - headers: { - 'X-User-Access-Token': user.accessToken, - 'X-Team-API-Key': team.apiKeys[0], - }, - }) + const res = await fetch( + `https://billing.${apiUrl}/teams/${team.id}/users`, + { + headers: { + 'X-User-Access-Token': user.accessToken, + 'X-Team-API-Key': team.apiKeys[0], + }, + } + ) if (!res.ok) { toast({ @@ -101,14 +104,17 @@ export const TeamContent = ({ } const deleteUserFromTeam = async () => { - const res = await fetch(`${billingUrl}/teams/${team.id}/users`, { - method: 'DELETE', - headers: { - 'X-User-Access-Token': user.accessToken, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ user_id: currentMemberId }), - }) + const res = await fetch( + `https://billing.${apiUrl}/teams/${team.id}/users`, + { + method: 'DELETE', + headers: { + 'X-User-Access-Token': user.accessToken, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ user_id: currentMemberId }), + } + ) if (!res.ok) { toast({ @@ -124,7 +130,7 @@ export const TeamContent = ({ } const changeTeamName = async () => { - const res = await fetch(`${billingUrl}/teams/${team.id}`, { + const res = await fetch(`https://billing.${apiUrl}/teams/${team.id}`, { headers: { 'X-Team-API-Key': team.apiKeys[0], 'Content-Type': 'application/json', @@ -161,14 +167,17 @@ export const TeamContent = ({ return } - const res = await fetch(`${billingUrl}/teams/${team.id}/users`, { - headers: { - 'X-User-Access-Token': user.accessToken, - 'Content-Type': 'application/json', - }, - method: 'POST', - body: JSON.stringify({ user_email: userToAdd.trim() }), - }) + const res = await fetch( + `https://billing.${apiUrl}/teams/${team.id}/users`, + { + headers: { + 'X-User-Access-Token': user.accessToken, + 'Content-Type': 'application/json', + }, + method: 'POST', + body: JSON.stringify({ user_email: userToAdd.trim() }), + } + ) if (!res.ok) { toast({ diff --git a/apps/web/src/components/Dashboard/Usage.tsx b/apps/web/src/components/Dashboard/Usage.tsx index f195fe8a0..74106513e 100644 --- a/apps/web/src/components/Dashboard/Usage.tsx +++ b/apps/web/src/components/Dashboard/Usage.tsx @@ -23,10 +23,10 @@ type Series = { export const UsageContent = ({ team, - billingUrl, + apiUrl, }: { team: Team - billingUrl: string + apiUrl: string }) => { const [vcpuData, setVcpuData] = useState([]) const [vcpuHoursThisMonth, setVcpuHoursThisMonth] = useState< @@ -45,11 +45,14 @@ export const UsageContent = ({ setRamData([]) setCostUsage([]) - const response = await fetch(`${billingUrl}/teams/${team.id}/usage`, { - headers: { - 'X-Team-API-Key': team.apiKeys[0], - }, - }) + const response = await fetch( + `https://billing.${apiUrl}/teams/${team.id}/usage`, + { + headers: { + 'X-Team-API-Key': team.apiKeys[0], + }, + } + ) if (!response.ok) { // TODO: Add sentry event here toast({ diff --git a/apps/web/src/components/Pricing/SwitchToProButton.tsx b/apps/web/src/components/Pricing/SwitchToProButton.tsx index b0d0aee03..98558a8cf 100644 --- a/apps/web/src/components/Pricing/SwitchToProButton.tsx +++ b/apps/web/src/components/Pricing/SwitchToProButton.tsx @@ -9,8 +9,8 @@ import Spinner from '@/components/Spinner' import { TierActiveTag } from './TierActiveTag' -function createCheckout(billingApiURL: string, tierID: string, teamID: string) { - return fetch(`${billingApiURL}/checkouts`, { +function createCheckout(apiUrl: string, tierID: string, teamID: string) { + return fetch(`https://billing.${apiUrl}/checkouts`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -22,13 +22,7 @@ function createCheckout(billingApiURL: string, tierID: string, teamID: string) { }) } -function SwitchTierButton({ - team, - billingApiURL, -}: { - team: Team - billingApiURL: string -}) { +function SwitchTierButton({ team, apiUrl }: { team: Team; apiUrl: string }) { const { user, isLoading } = useUser() const [error, setError] = useState('') @@ -40,7 +34,7 @@ function SwitchTierButton({ } const response = await createCheckout( - billingApiURL, + apiUrl, tiers.pro.id, user.teams[0].id ) @@ -67,10 +61,7 @@ function SwitchTierButton({ // 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 ( - !billingApiURL || - (team.tier !== tiers.hobby.id && team.tier !== tiers.pro.id) - ) { + if (!apiUrl || (team.tier !== tiers.hobby.id && team.tier !== tiers.pro.id)) { return } From 713c92056449f2e2e2d9ab0714604942494634f4 Mon Sep 17 00:00:00 2001 From: Jakub Novak Date: Thu, 19 Dec 2024 14:29:09 +0100 Subject: [PATCH 07/10] Improve naming --- .../src/app/(dashboard)/dashboard/page.tsx | 28 +++++------ .../src/app/(dashboard)/dashboard/utils.ts | 27 ++++++++++ apps/web/src/components/Dashboard/Billing.tsx | 13 ++--- apps/web/src/components/Dashboard/Keys.tsx | 20 +++++--- .../web/src/components/Dashboard/Personal.tsx | 7 +-- .../src/components/Dashboard/Sandboxes.tsx | 11 +++-- apps/web/src/components/Dashboard/Team.tsx | 49 +++++++++---------- .../src/components/Dashboard/Templates.tsx | 25 +++++----- apps/web/src/components/Dashboard/Usage.tsx | 9 ++-- .../components/Pricing/SwitchToProButton.tsx | 11 +++-- 10 files changed, 116 insertions(+), 84 deletions(-) create mode 100644 apps/web/src/app/(dashboard)/dashboard/utils.ts diff --git a/apps/web/src/app/(dashboard)/dashboard/page.tsx b/apps/web/src/app/(dashboard)/dashboard/page.tsx index ac0ba0ffa..4224bf658 100644 --- a/apps/web/src/app/(dashboard)/dashboard/page.tsx +++ b/apps/web/src/app/(dashboard)/dashboard/page.tsx @@ -87,9 +87,9 @@ const Dashboard = ({ user }) => { const [teams, setTeams] = useState([]) const [currentTeam, setCurrentTeam] = useState(null) - const apiUrlState = useLocalStorage( - 'apiUrl', - process.env.NEXT_PUBLIC_API_URL || '' + const domainState = useLocalStorage( + 'e2bDomain', + process.env.NEXT_PUBLIC_DOMAIN || '' ) const initialTab = @@ -162,7 +162,7 @@ const Dashboard = ({ user }) => { teams={teams} setTeams={setTeams} setCurrentTeam={setCurrentTeam} - apiUrlState={apiUrlState} + domainState={domainState} />
@@ -250,7 +250,7 @@ function MainContent({ teams, setTeams, setCurrentTeam, - apiUrlState, + domainState, }: { selectedItem: MenuLabel user: E2BUser @@ -258,29 +258,29 @@ function MainContent({ teams: Team[] setTeams: (teams: Team[]) => void setCurrentTeam: (team: Team) => void - apiUrlState: [string, (value: string) => void] + domainState: [string, (value: string) => void] }) { switch (selectedItem) { case 'personal': - return + return case 'keys': return ( - + ) case 'sandboxes': - return + return case 'templates': return ( ) case 'usage': - return + return case 'billing': - return + return case 'team': return ( ) case 'developer': - return + return default: return } diff --git a/apps/web/src/app/(dashboard)/dashboard/utils.ts b/apps/web/src/app/(dashboard)/dashboard/utils.ts new file mode 100644 index 000000000..0b8e81e95 --- /dev/null +++ b/apps/web/src/app/(dashboard)/dashboard/utils.ts @@ -0,0 +1,27 @@ +export function getBaseUrl(domain: string) { + let url = domain + if (!domain.startsWith('http')) { + const local = domain === 'localhost' || domain.startsWith('127.0.0.') + url = `http${local ? '' : 's'}://${domain}` + } + + const parsedUrl = new URL(url) + + return parsedUrl.toString() +} + +export function getBillingUrl(domain: string) { + let url = domain + const local = domain === 'localhost' || domain.startsWith('127.0.0.') + + if (!domain.startsWith('http')) { + url = `http${local ? '' : 's'}://${domain}` + } + + const parsedUrl = new URL(url) + if (!local) { + parsedUrl.hostname = `billing.${parsedUrl.hostname}` + } + + return parsedUrl.toString() +} diff --git a/apps/web/src/components/Dashboard/Billing.tsx b/apps/web/src/components/Dashboard/Billing.tsx index 97e7c3828..8054794fa 100644 --- a/apps/web/src/components/Dashboard/Billing.tsx +++ b/apps/web/src/components/Dashboard/Billing.tsx @@ -12,6 +12,7 @@ import { } from '../ui/table' import SwitchToHobbyButton from '@/components/Pricing/SwitchToHobbyButton' import SwitchToProButton from '@/components/Pricing/SwitchToProButton' +import { getBillingUrl } from '@/app/(dashboard)/dashboard/utils' function formatCurrency(value: number) { return value.toLocaleString('en-US', { @@ -29,10 +30,10 @@ interface Invoice { export const BillingContent = ({ team, - apiUrl, + domain, }: { team: Team - apiUrl: string + domain: string }) => { const [invoices, setInvoices] = useState([]) const [credits, setCredits] = useState(null) @@ -41,7 +42,7 @@ export const BillingContent = ({ const getInvoices = async function getInvoices() { setInvoices([]) const res = await fetch( - `https://billing.${apiUrl}/teams/${team.id}/invoices`, + `https://billing.${domain}/teams/${team.id}/invoices`, { headers: { 'X-Team-API-Key': team.apiKeys[0], @@ -59,7 +60,7 @@ export const BillingContent = ({ setCredits(null) const creditsRes = await fetch( - `https://billing.${apiUrl}/teams/${team.id}/usage`, + `${getBillingUrl(domain)}/teams/${team.id}/usage`, { headers: { 'X-Team-API-Key': team.apiKeys[0], @@ -71,7 +72,7 @@ export const BillingContent = ({ } getInvoices() - }, [team]) + }, [domain, team]) return (
@@ -114,7 +115,7 @@ export const BillingContent = ({

Pro tier

- +
  • One-time $100 credits
  • diff --git a/apps/web/src/components/Dashboard/Keys.tsx b/apps/web/src/components/Dashboard/Keys.tsx index e17b36549..b4fa43ce7 100644 --- a/apps/web/src/components/Dashboard/Keys.tsx +++ b/apps/web/src/components/Dashboard/Keys.tsx @@ -28,6 +28,7 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from '../ui/dropdown-menu' +import { getBillingUrl } from '@/app/(dashboard)/dashboard/utils' type TeamApiKey = { id: string @@ -46,11 +47,11 @@ type TeamApiKey = { export const KeysContent = ({ currentTeam, user, - apiUrl, + domain, }: { currentTeam: Team user: E2BUser - apiUrl: string + domain: string }) => { const { toast } = useToast() const [isKeyDialogOpen, setIsKeyDialogOpen] = useState(false) @@ -60,11 +61,10 @@ export const KeysContent = ({ const [newApiKeyInput, setNewApiKeyInput] = useState('') const [apiKeys, setApiKeys] = useState([]) - console.log(apiUrl) useEffect(() => { async function fetchApiKeys() { const res = await fetch( - `https://billing.${apiUrl}/teams/${currentTeam.id}/api-keys`, + `${getBillingUrl(domain)}/teams/${currentTeam.id}/api-keys`, { headers: { 'X-USER-ACCESS-TOKEN': user.accessToken, @@ -86,7 +86,7 @@ export const KeysContent = ({ } fetchApiKeys() - }, [apiUrl, currentTeam]) + }, [domain, currentTeam, user.accessToken]) async function deleteApiKey() { if (apiKeys.length === 1) { @@ -98,7 +98,9 @@ export const KeysContent = ({ } const res = await fetch( - `https://billing.${apiUrl}/teams/${currentTeam.id}/api-keys/${currentKey?.id}`, + `${getBillingUrl(domain)}/teams/${currentTeam.id}/api-keys/${ + currentKey?.id + }`, { method: 'DELETE', headers: { @@ -121,7 +123,7 @@ export const KeysContent = ({ async function createApiKey() { const res = await fetch( - `https://billing.${apiUrl}/teams/${currentTeam.id}/api-keys`, + `${getBillingUrl(domain)}/teams/${currentTeam.id}/api-keys`, { method: 'POST', headers: { @@ -153,7 +155,9 @@ export const KeysContent = ({ async function updateApiKey() { const res = await fetch( - `https://billing.${apiUrl}/teams/${currentTeam.id}/api-keys/${currentKey?.id}`, + `${getBillingUrl(domain)}/teams/${currentTeam.id}/api-keys/${ + currentKey?.id + }`, { method: 'PATCH', headers: { diff --git a/apps/web/src/components/Dashboard/Personal.tsx b/apps/web/src/components/Dashboard/Personal.tsx index 10937ae31..85b4c5b66 100644 --- a/apps/web/src/components/Dashboard/Personal.tsx +++ b/apps/web/src/components/Dashboard/Personal.tsx @@ -6,13 +6,14 @@ import Link from 'next/link' import { useState } from 'react' import { Copy } from 'lucide-react' import { E2BUser } from '@/utils/useUser' +import { getBillingUrl } from '@/app/(dashboard)/dashboard/utils' export const PersonalContent = ({ user, - apiUrl, + domain, }: { user: E2BUser - apiUrl: string + domain: string }) => { const { toast } = useToast() const [hovered, setHovered] = useState(false) @@ -33,7 +34,7 @@ export const PersonalContent = ({ } const updateUserEmail = async () => { - const res = await fetch(`https://billing.${apiUrl}/users`, { + const res = await fetch(`${getBillingUrl(domain)}/users`, { method: 'PATCH', headers: { 'Content-Type': 'application/json', diff --git a/apps/web/src/components/Dashboard/Sandboxes.tsx b/apps/web/src/components/Dashboard/Sandboxes.tsx index 0a5e7b90e..50569fc84 100644 --- a/apps/web/src/components/Dashboard/Sandboxes.tsx +++ b/apps/web/src/components/Dashboard/Sandboxes.tsx @@ -10,6 +10,7 @@ import { import { useState } from 'react' import { useEffect } from 'react' import { Team } from '@/utils/useUser' +import { getBaseUrl } from '@/app/(dashboard)/dashboard/utils' interface Sandbox { alias: string @@ -25,10 +26,10 @@ interface Sandbox { export function SandboxesContent({ team, - apiUrl, + domain, }: { team: Team - apiUrl: string + domain: string }) { const [runningSandboxes, setRunningSandboxes] = useState([]) @@ -36,7 +37,7 @@ export function SandboxesContent({ function f() { const apiKey = team.apiKeys[0] if (apiKey) { - fetchSandboxes(apiUrl, apiKey).then((newSandboxes) => { + fetchSandboxes(domain, apiKey).then((newSandboxes) => { if (newSandboxes) { setRunningSandboxes(newSandboxes) } @@ -101,10 +102,10 @@ export function SandboxesContent({ } async function fetchSandboxes( - apiUrl: string, + domain: string, apiKey: string ): Promise { - const res = await fetch(`${apiUrl}/sandboxes`, { + const res = await fetch(`${getBaseUrl(domain)}/sandboxes`, { method: 'GET', headers: { 'X-API-KEY': apiKey, diff --git a/apps/web/src/components/Dashboard/Team.tsx b/apps/web/src/components/Dashboard/Team.tsx index 574d55085..0171c72da 100644 --- a/apps/web/src/components/Dashboard/Team.tsx +++ b/apps/web/src/components/Dashboard/Team.tsx @@ -24,6 +24,7 @@ import { AlertDialogTitle, } from '../ui/alert-dialog' import Spinner from '@/components/Spinner' +import { getBillingUrl } from '@/app/(dashboard)/dashboard/utils' interface TeamMember { id: string @@ -45,14 +46,14 @@ export const TeamContent = ({ teams, setTeams, setCurrentTeam, - apiUrl, + domain, }: { team: Team user: E2BUser teams: Team[] setTeams: (teams: Team[]) => void setCurrentTeam: (team: Team) => void - apiUrl: string + domain: string }) => { const [isDialogOpen, setIsDialogOpen] = useState(false) const [currentMemberId, setCurrentMemberId] = useState(null) @@ -65,7 +66,7 @@ export const TeamContent = ({ useEffect(() => { const getTeamMembers = async () => { const res = await fetch( - `https://billing.${apiUrl}/teams/${team.id}/users`, + `${getBillingUrl(domain)}/teams/${team.id}/users`, { headers: { 'X-User-Access-Token': user.accessToken, @@ -91,7 +92,7 @@ export const TeamContent = ({ } getTeamMembers() - }, [user, userAdded, team]) + }, [user, userAdded, team, domain]) useEffect(() => { setTeamName(team.name) @@ -104,17 +105,14 @@ export const TeamContent = ({ } const deleteUserFromTeam = async () => { - const res = await fetch( - `https://billing.${apiUrl}/teams/${team.id}/users`, - { - method: 'DELETE', - headers: { - 'X-User-Access-Token': user.accessToken, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ user_id: currentMemberId }), - } - ) + const res = await fetch(`${getBillingUrl(domain)}/teams/${team.id}/users`, { + method: 'DELETE', + headers: { + 'X-User-Access-Token': user.accessToken, + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ user_id: currentMemberId }), + }) if (!res.ok) { toast({ @@ -130,7 +128,7 @@ export const TeamContent = ({ } const changeTeamName = async () => { - const res = await fetch(`https://billing.${apiUrl}/teams/${team.id}`, { + const res = await fetch(`${getBillingUrl(domain)}/teams/${team.id}`, { headers: { 'X-Team-API-Key': team.apiKeys[0], 'Content-Type': 'application/json', @@ -167,17 +165,14 @@ export const TeamContent = ({ return } - const res = await fetch( - `https://billing.${apiUrl}/teams/${team.id}/users`, - { - headers: { - 'X-User-Access-Token': user.accessToken, - 'Content-Type': 'application/json', - }, - method: 'POST', - body: JSON.stringify({ user_email: userToAdd.trim() }), - } - ) + const res = await fetch(`${getBillingUrl(domain)}/teams/${team.id}/users`, { + headers: { + 'X-User-Access-Token': user.accessToken, + 'Content-Type': 'application/json', + }, + method: 'POST', + body: JSON.stringify({ user_email: userToAdd.trim() }), + }) if (!res.ok) { toast({ diff --git a/apps/web/src/components/Dashboard/Templates.tsx b/apps/web/src/components/Dashboard/Templates.tsx index 939df885c..a1bcdd573 100644 --- a/apps/web/src/components/Dashboard/Templates.tsx +++ b/apps/web/src/components/Dashboard/Templates.tsx @@ -28,6 +28,7 @@ import { AlertDialogAction, } from '../ui/alert-dialog' import { toast } from '../ui/use-toast' +import { getBaseUrl } from '@/app/(dashboard)/dashboard/utils' interface Template { aliases: string[] @@ -47,11 +48,11 @@ interface Template { export function TemplatesContent({ user, teamId, - apiUrl, + domain, }: { user: E2BUser teamId: string - apiUrl: string + domain: string }) { const [templates, setTemplates] = useState([]) const [currentTemplate, setCurrentTemplate] = useState