From 8ef8d1ec648ae01f99f0294a7750c26f883cafa0 Mon Sep 17 00:00:00 2001 From: Fabian Buchenberger <37747351+ffabss@users.noreply.github.com> Date: Tue, 20 Dec 2022 21:51:39 +0000 Subject: [PATCH 01/42] fix broken self-host/docker link in documentation --- apps/docs/pages/guides/self-hosting.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/pages/guides/self-hosting.mdx b/apps/docs/pages/guides/self-hosting.mdx index 1c99b9765d9..950432a3803 100644 --- a/apps/docs/pages/guides/self-hosting.mdx +++ b/apps/docs/pages/guides/self-hosting.mdx @@ -149,7 +149,7 @@ We realize that database migrations are difficult, and this is one of the proble While Supabase officially supports Docker, we have several other deployment strategies managed by the community: -- [supabase-docker](../../guides/hosting/docker) (Official) +- [supabase-docker](./docker) (Official) - [supabase-kubernetes](https://github.com/supabase-community/supabase-kubernetes) (Unofficial) - [supabase-terraform](https://github.com/supabase-community/supabase-terraform) (Unofficial) - [supabase-traefik](https://github.com/supabase-community/supabase-traefik) (Unofficial) From f9d46684c4aee6dd4f0b202e2a52e014cef54ef1 Mon Sep 17 00:00:00 2001 From: dng Date: Tue, 20 Dec 2022 14:07:49 -0800 Subject: [PATCH 02/42] Update apps/docs/pages/guides/self-hosting.mdx --- apps/docs/pages/guides/self-hosting.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/pages/guides/self-hosting.mdx b/apps/docs/pages/guides/self-hosting.mdx index 950432a3803..3092dd9cf45 100644 --- a/apps/docs/pages/guides/self-hosting.mdx +++ b/apps/docs/pages/guides/self-hosting.mdx @@ -149,7 +149,7 @@ We realize that database migrations are difficult, and this is one of the proble While Supabase officially supports Docker, we have several other deployment strategies managed by the community: -- [supabase-docker](./docker) (Official) +- [supabase-docker](/docs/guides/self-hosting/docker) (Official) - [supabase-kubernetes](https://github.com/supabase-community/supabase-kubernetes) (Unofficial) - [supabase-terraform](https://github.com/supabase-community/supabase-terraform) (Unofficial) - [supabase-traefik](https://github.com/supabase-community/supabase-traefik) (Unofficial) From b5b1a58d408f7e0935196c3e32a0d1c04bf5e339 Mon Sep 17 00:00:00 2001 From: dng Date: Tue, 20 Dec 2022 14:13:40 -0800 Subject: [PATCH 03/42] Clean up docs callout --- apps/docs/pages/guides/integrations/weweb.mdx | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/apps/docs/pages/guides/integrations/weweb.mdx b/apps/docs/pages/guides/integrations/weweb.mdx index 93ddbd5f2bc..c65a695400e 100644 --- a/apps/docs/pages/guides/integrations/weweb.mdx +++ b/apps/docs/pages/guides/integrations/weweb.mdx @@ -39,12 +39,7 @@ Once you have added both to WeWeb, you will have the option to enable realtime t **🚨 Warning 🚨** -> Realtime is disabled by default in Supabase for better database performance and security. Learn more about [realtime functionalities](https://supabase.com/docs/guides/database#realtime) in our documentation. -> Member -> @awalias awalias 16 days ago -> might just wanna change these ones to be relative but otherwise looks great 👍 - -@EWMAs Reply... +> Realtime is disabled by default in Supabase for better database performance and security. Learn more about [realtime functionalities](/docs/guides/realtime). ## Step 2: GET Data from Supabase From 10235c32b210f7261ea6009d350505862c0d1f09 Mon Sep 17 00:00:00 2001 From: dannykng Date: Tue, 20 Dec 2022 14:41:45 -0800 Subject: [PATCH 04/42] Add code block language for syntax highlighting --- .../pages/guides/integrations/authsignal.mdx | 340 ++++++++---------- 1 file changed, 154 insertions(+), 186 deletions(-) diff --git a/apps/docs/pages/guides/integrations/authsignal.mdx b/apps/docs/pages/guides/integrations/authsignal.mdx index 250b822b483..65d83b22d01 100644 --- a/apps/docs/pages/guides/integrations/authsignal.mdx +++ b/apps/docs/pages/guides/integrations/authsignal.mdx @@ -61,7 +61,7 @@ cd supabase-authsignal-example Create a `.env.local` file and enter the following values: -``` +```sh NEXT_PUBLIC_SUPABASE_URL=get-from-supabase-dashboard NEXT_PUBLIC_SUPABASE_ANON_KEY=get-from-supabase-dashboard AUTHSIGNAL_SECRET=get-from-authsignal-dashboard @@ -84,19 +84,19 @@ npm run dev Install the Supabase client and Auth helpers for Next.js: -``` +```bash npm install @supabase/supabase-js @supabase/auth-helpers-nextjs ``` Install the Authsignal Node.js client: -``` +```bash npm install @authsignal/node ``` Finally install 2 packages to help encrypt and serialize session data in cookies: -``` +```bash npm install @hapi/iron cookie npm install --save-dev @types/cookie ``` @@ -105,18 +105,18 @@ npm install --save-dev @types/cookie Add the following code to `/lib/authsignal.ts`: -``` -import { Authsignal } from "@authsignal/node"; +```ts +import { Authsignal } from '@authsignal/node' -const secret = process.env.AUTHSIGNAL_SECRET; +const secret = process.env.AUTHSIGNAL_SECRET if (!secret) { - throw new Error("AUTHSIGNAL_SECRET is undefined"); + throw new Error('AUTHSIGNAL_SECRET is undefined') } -const redirectUrl = "http://localhost:3000/api/callback"; +const redirectUrl = 'http://localhost:3000/api/callback' -export const authsignal = new Authsignal({ secret, redirectUrl }); +export const authsignal = new Authsignal({ secret, redirectUrl }) ``` The `redirectUrl` here is a Next.js API route which Authsignal will redirect back to after an MFA challenge. We'll implement this below. @@ -131,83 +131,72 @@ Next we will add some helper functions for managing cookies: Add the following code to `/lib/cookies.ts`: -``` -import Iron from "@hapi/iron"; -import { Session } from "@supabase/supabase-js"; -import { parse, serialize } from "cookie"; -import { NextApiRequest, NextApiResponse } from "next"; +```ts +import Iron from '@hapi/iron' +import { Session } from '@supabase/supabase-js' +import { parse, serialize } from 'cookie' +import { NextApiRequest, NextApiResponse } from 'next' export async function setTempCookie(session: Session, res: NextApiResponse) { - const token = await Iron.seal(session, TEMP_TOKEN_SECRET, Iron.defaults); + const token = await Iron.seal(session, TEMP_TOKEN_SECRET, Iron.defaults) const cookie = serialize(TEMP_COOKIE, token, { maxAge: session.expires_in, httpOnly: true, - secure: process.env.NODE_ENV === "production", - path: "/", - sameSite: "lax", - }); + secure: process.env.NODE_ENV === 'production', + path: '/', + sameSite: 'lax', + }) - res.setHeader("Set-Cookie", cookie); + res.setHeader('Set-Cookie', cookie) } -export async function getSessionFromTempCookie( - req: NextApiRequest -): Promise { - const cookie = req.headers.cookie as string; +export async function getSessionFromTempCookie(req: NextApiRequest): Promise { + const cookie = req.headers.cookie as string - const cookies = parse(cookie ?? ""); + const cookies = parse(cookie ?? '') - const tempCookie = cookies[TEMP_COOKIE]; + const tempCookie = cookies[TEMP_COOKIE] if (!tempCookie) { - return undefined; + return undefined } - const session = await Iron.unseal( - tempCookie, - TEMP_TOKEN_SECRET, - Iron.defaults - ); + const session = await Iron.unseal(tempCookie, TEMP_TOKEN_SECRET, Iron.defaults) - return session; + return session } export function setAuthCookie(session: Session, res: NextApiResponse) { - const { access_token, refresh_token, expires_in } = session; + const { access_token, refresh_token, expires_in } = session const authCookies = [ { name: ACCESS_TOKEN_COOKIE, value: access_token }, - refresh_token - ? { name: REFRESH_TOKEN_COOKIE, value: refresh_token } - : undefined, + refresh_token ? { name: REFRESH_TOKEN_COOKIE, value: refresh_token } : undefined, ] .filter(isDefined) .map(({ name, value }) => serialize(name, value, { maxAge: expires_in, httpOnly: true, - secure: process.env.NODE_ENV === "production", - path: "/", - sameSite: "lax", + secure: process.env.NODE_ENV === 'production', + path: '/', + sameSite: 'lax', }) - ); + ) // Also clear the temp cookie - const updatedCookies = [ - ...authCookies, - serialize(TEMP_COOKIE, "", { maxAge: -1, path: "/" }), - ]; + const updatedCookies = [...authCookies, serialize(TEMP_COOKIE, '', { maxAge: -1, path: '/' })] - res.setHeader("Set-Cookie", updatedCookies); + res.setHeader('Set-Cookie', updatedCookies) } -const isDefined = (value: T | undefined): value is T => !!value; +const isDefined = (value: T | undefined): value is T => !!value -const TEMP_TOKEN_SECRET = process.env.TEMP_TOKEN_SECRET!; -const TEMP_COOKIE = "as-mfa-cookie"; -const ACCESS_TOKEN_COOKIE = "sb-access-token"; -const REFRESH_TOKEN_COOKIE = "sb-refresh-token"; +const TEMP_TOKEN_SECRET = process.env.TEMP_TOKEN_SECRET! +const TEMP_COOKIE = 'as-mfa-cookie' +const ACCESS_TOKEN_COOKIE = 'sb-access-token' +const REFRESH_TOKEN_COOKIE = 'sb-refresh-token' ``` ## Step 7: Building the UI @@ -216,34 +205,34 @@ We will add some form components for signing in and signing up as well as a basi Add the following code to `/pages/sign-up.tsx`: -``` -import Link from "next/link"; -import { useRouter } from "next/router"; +```ts +import Link from 'next/link' +import { useRouter } from 'next/router' export default function SignUpPage() { - const router = useRouter(); + const router = useRouter() return (
{ - e.preventDefault(); + e.preventDefault() const target = e.target as typeof e.target & { - email: { value: string }; - password: { value: string }; - }; + email: { value: string } + password: { value: string } + } - const email = target.email.value; - const password = target.password.value; + const email = target.email.value + const password = target.password.value - await fetch("/api/sign-up", { - method: "POST", - headers: { "Content-Type": "application/json" }, + await fetch('/api/sign-up', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }), - }).then((res) => res.json()); + }).then((res) => res.json()) - router.push("/"); + router.push('/') }} > @@ -253,49 +242,49 @@ export default function SignUpPage() {
- {"Already have an account? "} + {'Already have an account? '} Sign in
- ); + ) } ``` Then add the following code to `/pages/sign-in.tsx`: -``` -import Link from "next/link"; -import { useRouter } from "next/router"; +```ts +import Link from 'next/link' +import { useRouter } from 'next/router' export default function SignInPage() { - const router = useRouter(); + const router = useRouter() return (
{ - e.preventDefault(); + e.preventDefault() const target = e.target as typeof e.target & { - email: { value: string }; - password: { value: string }; - }; + email: { value: string } + password: { value: string } + } - const email = target.email.value; - const password = target.password.value; + const email = target.email.value + const password = target.password.value - const { state, mfaUrl } = await fetch("/api/sign-in", { - method: "POST", - headers: { "Content-Type": "application/json" }, + const { state, mfaUrl } = await fetch('/api/sign-in', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }), - }).then((res) => res.json()); + }).then((res) => res.json()) - if (state === "CHALLENGE_REQUIRED") { - window.location.href = mfaUrl; + if (state === 'CHALLENGE_REQUIRED') { + window.location.href = mfaUrl } else { - router.push("/"); + router.push('/') } }} > @@ -312,38 +301,38 @@ export default function SignInPage() {
- ); + ) } ``` Now we will use Supabase's `withPageAuth` wrapper around `getServerSideProps` to make the home page require authentication via SSR. Replace the existing code in `/pages/index.tsx` with the following: -``` -import { getUser, User, withPageAuth } from "@supabase/auth-helpers-nextjs"; -import { GetServerSideProps } from "next"; -import { useRouter } from "next/router"; -import { authsignal } from "../lib/authsignal"; +```ts +import { getUser, User, withPageAuth } from '@supabase/auth-helpers-nextjs' +import { GetServerSideProps } from 'next' +import { useRouter } from 'next/router' +import { authsignal } from '../lib/authsignal' interface Props { - user: User; - isEnrolled: boolean; + user: User + isEnrolled: boolean } export const getServerSideProps: GetServerSideProps = withPageAuth({ - redirectTo: "/sign-in", + redirectTo: '/sign-in', async getServerSideProps(ctx) { - const { user } = await getUser(ctx); + const { user } = await getUser(ctx) - const { isEnrolled } = await authsignal.getUser({userId: user.id}); + const { isEnrolled } = await authsignal.getUser({ userId: user.id }) return { props: { user, isEnrolled }, - }; + } }, -}); +}) export default function HomePage({ user, isEnrolled }: Props) { - const router = useRouter(); + const router = useRouter() return (
@@ -351,29 +340,29 @@ export default function HomePage({ user, isEnrolled }: Props) {
Signed in as: {user?.email}
- +
- ); + ) } ``` Optional: To make things look a bit nicer, you can add the following to `/styles/globals.css`: -``` +```css main { min-height: 100vh; display: flex; @@ -435,113 +424,95 @@ Now we'll replace the existing api routes in `/pages/api/` with 5 new routes: Add the following code to `/pages/api/sign-in.ts`: -``` -import { supabaseClient } from "@supabase/auth-helpers-nextjs"; -import { NextApiRequest, NextApiResponse } from "next"; -import { authsignal } from "../../lib/authsignal"; -import { setAuthCookie, setTempCookie } from "../../lib/cookies"; +```ts +import { supabaseClient } from '@supabase/auth-helpers-nextjs' +import { NextApiRequest, NextApiResponse } from 'next' +import { authsignal } from '../../lib/authsignal' +import { setAuthCookie, setTempCookie } from '../../lib/cookies' -export default async function signIn( - req: NextApiRequest, - res: NextApiResponse -) { - const { email, password } = req.body; +export default async function signIn(req: NextApiRequest, res: NextApiResponse) { + const { email, password } = req.body - const { data, error } = await supabaseClient.auth.api.signInWithEmail( - email, - password - ); + const { data, error } = await supabaseClient.auth.api.signInWithEmail(email, password) if (error || !data?.user) { - return res.send({ error }); + return res.send({ error }) } const { state, url: mfaUrl } = await authsignal.track({ - action: "signIn", + action: 'signIn', userId: data.user.id, - }); + }) - if (state === "CHALLENGE_REQUIRED") { - await setTempCookie(data, res); + if (state === 'CHALLENGE_REQUIRED') { + await setTempCookie(data, res) } else { - setAuthCookie(data, res); + setAuthCookie(data, res) } - res.send({ state, mfaUrl }); + res.send({ state, mfaUrl }) } ``` Then to handle new sign-ups add the following to `/pages/api/sign-up.ts`: -``` -import { supabaseClient } from "@supabase/auth-helpers-nextjs"; -import { Session } from "@supabase/supabase-js"; -import { NextApiRequest, NextApiResponse } from "next"; -import { setAuthCookie } from "../../lib/cookies"; +```ts +import { supabaseClient } from '@supabase/auth-helpers-nextjs' +import { Session } from '@supabase/supabase-js' +import { NextApiRequest, NextApiResponse } from 'next' +import { setAuthCookie } from '../../lib/cookies' -export default async function signUp( - req: NextApiRequest, - res: NextApiResponse -) { - const { email, password } = req.body; +export default async function signUp(req: NextApiRequest, res: NextApiResponse) { + const { email, password } = req.body - const { data, error } = await supabaseClient.auth.api.signUpWithEmail( - email, - password - ); + const { data, error } = await supabaseClient.auth.api.signUpWithEmail(email, password) if (error || !isSession(data)) { - res.send({ error }); + res.send({ error }) } else { - setAuthCookie(data, res); - res.send({ data }); + setAuthCookie(data, res) + res.send({ data }) } } -const isSession = (data: any): data is Session => !!data?.access_token; +const isSession = (data: any): data is Session => !!data?.access_token ``` To clear the auth cookies on sign-out add the following to `/pages/api/sign-out.ts`: -``` -import { supabaseClient } from "@supabase/auth-helpers-nextjs"; -import { NextApiRequest, NextApiResponse } from "next"; +```ts +import { supabaseClient } from '@supabase/auth-helpers-nextjs' +import { NextApiRequest, NextApiResponse } from 'next' -export default async function signOut( - req: NextApiRequest, - res: NextApiResponse -) { - supabaseClient.auth.api.deleteAuthCookie(req, res, { redirectTo: "/sign-in" }); +export default async function signOut(req: NextApiRequest, res: NextApiResponse) { + supabaseClient.auth.api.deleteAuthCookie(req, res, { redirectTo: '/sign-in' }) } ``` To handle the user's actions to set up MFA or manage their existing MFA settings, add the following to `/pages/api/mfa.ts`: -``` -import { getUser, withApiAuth } from "@supabase/auth-helpers-nextjs"; -import { NextApiRequest, NextApiResponse } from "next"; -import { authsignal } from "../../lib/authsignal"; +```ts +import { getUser, withApiAuth } from '@supabase/auth-helpers-nextjs' +import { NextApiRequest, NextApiResponse } from 'next' +import { authsignal } from '../../lib/authsignal' -export default withApiAuth(async function mfa( - req: NextApiRequest, - res: NextApiResponse -) { - if (req.method !== "POST") { - return res.status(405).send({ message: "Only POST requests allowed" }); +export default withApiAuth(async function mfa(req: NextApiRequest, res: NextApiResponse) { + if (req.method !== 'POST') { + return res.status(405).send({ message: 'Only POST requests allowed' }) } - const { user } = await getUser({ req, res }); + const { user } = await getUser({ req, res }) - const { isEnrolled } = req.body; + const { isEnrolled } = req.body const { url: mfaUrl } = await authsignal.track({ - action: isEnrolled ? "manageSettings" : "enroll", + action: isEnrolled ? 'manageSettings' : 'enroll', userId: user.id, redirectToSettings: isEnrolled, - }); + }) - res.send({ mfaUrl }); -}); + res.send({ mfaUrl }) +}) ``` Because the user should be authenticated with Supabase to set up or manage MFA, we can use Supabase's `withApiAuth` wrapper to protect this route. @@ -550,28 +521,25 @@ The `redirectToSettings` param specifies whether the user should be redirected t Finally we need a route to handle the redirect back from Authsignal after an MFA challenge. Add the following to `/pages/api/callback.ts`: -``` -import { NextApiRequest, NextApiResponse } from "next"; -import { authsignal } from "../../lib/authsignal"; -import { getSessionFromTempCookie, setAuthCookie } from "../../lib/cookies"; +```ts +import { NextApiRequest, NextApiResponse } from 'next' +import { authsignal } from '../../lib/authsignal' +import { getSessionFromTempCookie, setAuthCookie } from '../../lib/cookies' -export default async function callback( - req: NextApiRequest, - res: NextApiResponse -) { - const token = req.query.token as string; +export default async function callback(req: NextApiRequest, res: NextApiResponse) { + const token = req.query.token as string - const { success } = await authsignal.validateChallenge({ token }); + const { success } = await authsignal.validateChallenge({ token }) if (success) { - const session = await getSessionFromTempCookie(req); + const session = await getSessionFromTempCookie(req) if (session) { - setAuthCookie(session, res); + setAuthCookie(session, res) } } - res.redirect("/"); + res.redirect('/') } ``` From efbd726b12ab0fcb89ebe030bc9c50a9b3a87934 Mon Sep 17 00:00:00 2001 From: dannykng Date: Tue, 20 Dec 2022 14:42:08 -0800 Subject: [PATCH 05/42] Allow images from raw.githubusercontent.com --- apps/docs/next.config.mjs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/docs/next.config.mjs b/apps/docs/next.config.mjs index 9762c179622..bf98d6d34fd 100644 --- a/apps/docs/next.config.mjs +++ b/apps/docs/next.config.mjs @@ -48,7 +48,12 @@ const nextConfig = { basePath: '/docs', images: { dangerouslyAllowSVG: true, - domains: ['avatars.githubusercontent.com', 'github.com', 'user-images.githubusercontent.com'], + domains: [ + 'avatars.githubusercontent.com', + 'github.com', + 'user-images.githubusercontent.com', + 'raw.githubusercontent.com', + ], }, experimental: { mdxRs: true, From ec7dc6dd7e5ebe0bf60654324906a33a525f402f Mon Sep 17 00:00:00 2001 From: ethicnology Date: Wed, 21 Dec 2022 00:22:58 +0100 Subject: [PATCH 06/42] fix: deprecated docker-compose in favour of compose v2 --- apps/docs/pages/faq.mdx | 2 +- apps/docs/pages/guides/self-hosting/docker.mdx | 6 +++--- apps/www/_blog/2020-07-10-alpha-launch-postmortem.mdx | 8 ++++---- apps/www/_blog/2021-03-31-supabase-cli.mdx | 2 +- docker/docker-compose.yml | 8 ++++---- package.json | 8 ++++---- studio/README.md | 4 ++-- tests/package.json | 4 ++-- 8 files changed, 21 insertions(+), 21 deletions(-) diff --git a/apps/docs/pages/faq.mdx b/apps/docs/pages/faq.mdx index eced1e89829..66c0c120969 100644 --- a/apps/docs/pages/faq.mdx +++ b/apps/docs/pages/faq.mdx @@ -16,7 +16,7 @@ Self-hosting Supabase is free. If you wish to use our cloud-platform, we provide ### How do I host Supabase? -You can use the docker-compose script [here](https://github.com/supabase/supabase/tree/master/docker), and find detailed instructions [here](/docs/guides/hosting/overview). +You can use the docker compose script [here](https://github.com/supabase/supabase/tree/master/docker), and find detailed instructions [here](/docs/guides/hosting/overview). Supabase is an amalgamation of open source tools. Some of these tools are made by Supabase (like our [Realtime Server](https://github.com/supabase/realtime)), some we support directly (like [PostgREST](http://postgrest.org/en/v7.0.0/)), and some are third-party tools (like [KonSupabase is an amalgamation open sourceg](https://github.com/Kong/kong)). diff --git a/apps/docs/pages/guides/self-hosting/docker.mdx b/apps/docs/pages/guides/self-hosting/docker.mdx index 08da99ae31c..0a32049889b 100644 --- a/apps/docs/pages/guides/self-hosting/docker.mdx +++ b/apps/docs/pages/guides/self-hosting/docker.mdx @@ -11,7 +11,7 @@ Docker is the easiest way to get started with self-hosted Supabase. You need the following installed in your system: -- [Docker](https://docs.docker.com/engine/install/) and [docker-compose](https://docs.docker.com/compose/install/) +- [Docker](https://docs.docker.com/engine/install/) and [docker compose](https://docs.docker.com/compose/install/) - [Git](https://git-scm.com/downloads) ## Quick Start @@ -31,7 +31,7 @@ cd supabase/docker cp .env.example .env # Start -docker-compose up +docker compose up ``` Now visit [http://localhost:3000](http://localhost:3000) to start using Supabase Studio. @@ -97,7 +97,7 @@ Once you have done this, you can safely comment out the `db` section of the `doc ### Setting database's `log_min_messages` -By default, `docker-compose` sets the database's `log_min_messages` configuration to `fatal` to prevent redundant logs generated by Realtime. +By default, `docker compose` sets the database's `log_min_messages` configuration to `fatal` to prevent redundant logs generated by Realtime. However, you might miss important log messages such as database errors. Configure `log_min_messages` based on your needs. ### File storage backend on macOS diff --git a/apps/www/_blog/2020-07-10-alpha-launch-postmortem.mdx b/apps/www/_blog/2020-07-10-alpha-launch-postmortem.mdx index 61bb85d236e..f0186212be6 100644 --- a/apps/www/_blog/2020-07-10-alpha-launch-postmortem.mdx +++ b/apps/www/_blog/2020-07-10-alpha-launch-postmortem.mdx @@ -37,13 +37,13 @@ GitHub stars rocketed for our two main repos, [supabase](https://github.com/supa Here are the things that survived well. -### Middleware: docker-compose up +### Middleware: docker compose up -This was the most surprising survivor. Our middleware was served from a single Ubuntu server with 4 CPUs and 8GB of RAM. This server was running our middleware using `docker-compose up`: +This was the most surprising survivor. Our middleware was served from a single Ubuntu server with 4 CPUs and 8GB of RAM. This server was running our middleware using `docker compose up`: -![This image shows in middleware architecture. We used docker-compose to pull up 5 open source tools: Kong, Realtime, PostgREST, PG-API, and PG-BOSS.](/images/blog/supabase-middleware-docker.png) +![This image shows in middleware architecture. We used docker compose to pull up 5 open source tools: Kong, Realtime, PostgREST, PG-API, and PG-BOSS.](/images/blog/supabase-middleware-docker.png) -In case you're wondering why any sane company would use that in production, it's because we weren't planning to launch - the HackerNews post was created by an early GitHub follower, while we were alpha testing, and it was too scary to migrate the middleware while it was servicing the thundering herd. All Supabase projects use the same middleware stack (sans docker-compose), so I guess this counts as as a successful load test. +In case you're wondering why any sane company would use that in production, it's because we weren't planning to launch - the HackerNews post was created by an early GitHub follower, while we were alpha testing, and it was too scary to migrate the middleware while it was servicing the thundering herd. All Supabase projects use the same middleware stack (without docker compose), so I guess this counts as a successful load test. We have since migrated our middleware to multiple ECS clusters, globally load-balanced using AWS's [Global Accelerator](https://aws.amazon.com/global-accelerator/). diff --git a/apps/www/_blog/2021-03-31-supabase-cli.mdx b/apps/www/_blog/2021-03-31-supabase-cli.mdx index b24682700de..ea8d4c94ed8 100644 --- a/apps/www/_blog/2021-03-31-supabase-cli.mdx +++ b/apps/www/_blog/2021-03-31-supabase-cli.mdx @@ -217,7 +217,7 @@ Also check `kong.yml` inside the `kong` directory where you'll see how all the s Once you're all set, you can start the stack by running: ```bash -docker-compose up +docker compose up ``` Head over to the [Self Hosting Docs](/docs/guides/self-hosting) for a more complete walk through, it also includes several [one-click deploys](/docs/guides/self-hosting#one-click-deploys), so you can easily deploy into your own cloud hosting provider. diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 70da7c553bd..659c501cde3 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -1,8 +1,8 @@ # Usage -# Start: docker-compose up -# With helpers: docker-compose -f docker-compose.yml -f ./dev/docker-compose.dev.yml up -# Stop: docker-compose down -# Destroy: docker-compose -f docker-compose.yml -f ./dev/docker-compose.dev.yml down -v --remove-orphans +# Start: docker compose up +# With helpers: docker compose -f docker-compose.yml -f ./dev/docker-compose.dev.yml up +# Stop: docker compose down +# Destroy: docker compose -f docker-compose.yml -f ./dev/docker-compose.dev.yml down -v --remove-orphans version: "3.8" diff --git a/package.json b/package.json index 4a605d28309..bf893aa1978 100644 --- a/package.json +++ b/package.json @@ -22,10 +22,10 @@ "dev:www": "turbo run dev --filter=www --parallel", "lint": "turbo run lint", "format": "prettier --write \"apps/**/*.{js,jsx,ts,tsx,css,md,json}\"", - "docker:dev": "cd docker && docker-compose -f docker-compose.yml -f ./dev/docker-compose.dev.yml up --renew-anon-volumes", - "docker:up": "cd docker && docker-compose up", - "docker:down": "cd docker && docker-compose -f docker-compose.yml -f ./dev/docker-compose.dev.yml down --remove-orphans", - "docker:remove": "cd docker && docker-compose -f docker-compose.yml -f ./dev/docker-compose.dev.yml rm -vfs", + "docker:dev": "cd docker && docker compose -f docker-compose.yml -f ./dev/docker-compose.dev.yml up --renew-anon-volumes", + "docker:up": "cd docker && docker compose up", + "docker:down": "cd docker && docker compose -f docker-compose.yml -f ./dev/docker-compose.dev.yml down --remove-orphans", + "docker:remove": "cd docker && docker compose -f docker-compose.yml -f ./dev/docker-compose.dev.yml rm -vfs", "test": "jest", "test:studio": "turbo run test --filter=studio", "perf:kong": "ab -t 5 -c 20 -T application/json http://localhost:8000/", diff --git a/studio/README.md b/studio/README.md index 4b9929f4a48..2c4fda0ba60 100644 --- a/studio/README.md +++ b/studio/README.md @@ -19,7 +19,7 @@ As such, the features exposed on Studio for existing deployments are limited to ## Managing Project Settings -Project settings are managed outside of the Dashboard. If you use docker-compose, you should manage the settings in your docker-compose file. If you're deploying Supabase to your own cloud, you should store your secrets and env vars in a vault or secrets manager. +Project settings are managed outside of the Dashboard. If you use docker compose, you should manage the settings in your docker-compose file. If you're deploying Supabase to your own cloud, you should store your secrets and env vars in a vault or secrets manager. ## How to contribute? @@ -51,7 +51,7 @@ Follow the [self-hosting guide](https://supabase.com/docs/guides/hosting/docker) ``` cd .. cd docker -docker-compose -f docker-compose.yml -f ./dev/docker-compose.dev.yml up +docker compose -f docker-compose.yml -f ./dev/docker-compose.dev.yml up ``` Once you've got that set up, update `.env` in the studio folder with the corresponding values. diff --git a/tests/package.json b/tests/package.json index 1633462bdbc..4f6b77c6c84 100644 --- a/tests/package.json +++ b/tests/package.json @@ -3,8 +3,8 @@ "version": "1.0.0", "description": "These tests can be run with Docker.", "scripts": { - "docker:up": "cd ../docker && ENABLE_EMAIL_AUTOCONFIRM=true docker-compose --env-file ./.env -f docker-compose.yml -f dev/docker-compose.dev.yml up -d", - "docker:down": "cd ../docker && docker-compose --env-file ./.env -f docker-compose.yml -f dev/docker-compose.dev.yml down", + "docker:up": "cd ../docker && ENABLE_EMAIL_AUTOCONFIRM=true docker compose --env-file ./.env -f docker-compose.yml -f dev/docker-compose.dev.yml up -d", + "docker:down": "cd ../docker && docker compose --env-file ./.env -f docker-compose.yml -f dev/docker-compose.dev.yml down", "test": "jest", "test:local": "jest --testNamePattern '^((?!\\[skip-local\\]).)*$' --testPathPattern '^((?!realtime).)*$' --detectOpenHandles", "test:stage": "jest --testNamePattern '^((?!\\[skip-stage\\]).)*$' --detectOpenHandles", From d36cc5de2601494cfc4e008bc0a674ee4071709e Mon Sep 17 00:00:00 2001 From: Long Hoang Date: Wed, 21 Dec 2022 10:13:20 +0700 Subject: [PATCH 07/42] Remove currently happening --- apps/www/pages/launch-week/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/www/pages/launch-week/index.tsx b/apps/www/pages/launch-week/index.tsx index 225de69fcb0..d9248069b7c 100644 --- a/apps/www/pages/launch-week/index.tsx +++ b/apps/www/pages/launch-week/index.tsx @@ -38,7 +38,7 @@ export default function launchweek() { const { isDarkMode } = useTheme() const title = 'Launch Week 6' const description = 'Supabase Launch Week 6 | 12-18 Dec 2022' - const liveDay = 'Friday' + const liveDay = null const [supabase] = useState(() => createClient(process.env.NEXT_PUBLIC_SUPABASE_URL!, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!) From eab07390b72e1b79f7747dea7b317ce2d309b0ea Mon Sep 17 00:00:00 2001 From: Long Hoang Date: Wed, 21 Dec 2022 10:26:35 +0700 Subject: [PATCH 08/42] Remove jump to current day on launchweek page --- apps/www/pages/launch-week/index.tsx | 5 ----- 1 file changed, 5 deletions(-) diff --git a/apps/www/pages/launch-week/index.tsx b/apps/www/pages/launch-week/index.tsx index d9248069b7c..7888948b8c7 100644 --- a/apps/www/pages/launch-week/index.tsx +++ b/apps/www/pages/launch-week/index.tsx @@ -219,11 +219,6 @@ export default function launchweek() { )} > - -
- Jump to current day -
-
{process.env.NEXT_PUBLIC_LW_STARTED && ( <> From 13c070e81d17735731f30cfbbeabc80233821b6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Thu, 15 Dec 2022 14:36:17 +0100 Subject: [PATCH 09/42] feat(hCaptcha): captcha for subscription/payment method changes --- .../AddNewPaymentMethodModal.tsx | 49 ++++++++++++++++--- .../interfaces/Billing/Billing.utils.ts | 4 +- .../interfaces/Billing/EnterpriseUpdate.tsx | 31 ++++++++++-- .../Billing/ExitSurvey/ExitSurvey.tsx | 30 ++++++++++-- .../PaymentSummaryPanel.tsx | 9 +++- .../interfaces/Billing/ProUpgrade.tsx | 32 ++++++++++-- 6 files changed, 135 insertions(+), 20 deletions(-) diff --git a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx index 8e8e7e865b6..7808f106114 100644 --- a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx +++ b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx @@ -1,4 +1,4 @@ -import { FC, useEffect, useState } from 'react' +import { FC, useEffect, useRef, useState } from 'react' import { IconLoader, Modal } from 'ui' import { loadStripe } from '@stripe/stripe-js' import { Elements } from '@stripe/react-stripe-js' @@ -7,6 +7,7 @@ import { useStore } from 'hooks' import { post } from 'lib/common/fetch' import { API_URL, STRIPE_PUBLIC_KEY } from 'lib/constants' import AddPaymentMethodForm from './AddPaymentMethodForm' +import HCaptcha from '@hcaptcha/react-hcaptcha' interface Props { visible: boolean @@ -20,18 +21,38 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = const { ui } = useStore() const [intent, setIntent] = useState() - useEffect(() => { - if (visible) { - setupIntent() - } - }, [visible]) + const [captchaLoaded, setCaptchaLoaded] = useState(false) - const setupIntent = async () => { + const [captchaToken, setCaptchaToken] = useState(null) + const captchaRef = useRef(null) + + useEffect(() => { + if (visible && captchaLoaded) { + let token = captchaToken + if (!token) { + captchaRef.current?.execute({ async: true }).then((captchaResponse) => { + return setupIntent(captchaResponse?.response ?? undefined) + }) + } + } + }, [visible, captchaLoaded]) + + const onCaptchaLoaded = () => { + setCaptchaLoaded(true) + } + + const setupIntent = async (hcaptchaToken: string | undefined) => { setIntent(undefined) + const orgSlug = ui.selectedOrganization?.slug ?? '' - const intent = await post(`${API_URL}/organizations/${orgSlug}/payments/setup-intent`, {}) + const intent = await post(`${API_URL}/organizations/${orgSlug}/payments/setup-intent`, { + hcaptchaToken, + }) if (intent.error) { + setCaptchaToken(null) + captchaRef.current?.resetCaptcha() + return ui.setNotification({ category: 'error', message: intent.error.message, @@ -56,6 +77,18 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = onCancel={onCancel} className="PAYMENT" > + { + setCaptchaToken(token) + }} + onExpire={() => { + setCaptchaToken(null) + }} + />
{intent !== undefined ? ( diff --git a/studio/components/interfaces/Billing/Billing.utils.ts b/studio/components/interfaces/Billing/Billing.utils.ts index 84e1e448694..67f98f93e7b 100644 --- a/studio/components/interfaces/Billing/Billing.utils.ts +++ b/studio/components/interfaces/Billing/Billing.utils.ts @@ -52,7 +52,8 @@ export const formSubscriptionUpdatePayload = ( }, nonChangeableAddons: SubscriptionAddon[], selectedPaymentMethod: string, - region: string + region: string, + hcaptchaToken: string | undefined ) => { const { computeSize, pitrDuration, customDomains } = selectedAddons @@ -78,6 +79,7 @@ export const formSubscriptionUpdatePayload = ( addons, proration_date, payment_method: selectedPaymentMethod, + hcaptchaToken } } diff --git a/studio/components/interfaces/Billing/EnterpriseUpdate.tsx b/studio/components/interfaces/Billing/EnterpriseUpdate.tsx index bf98cd7e4aa..430b8dfcc68 100644 --- a/studio/components/interfaces/Billing/EnterpriseUpdate.tsx +++ b/studio/components/interfaces/Billing/EnterpriseUpdate.tsx @@ -1,4 +1,4 @@ -import { FC, useState, useEffect } from 'react' +import { FC, useState, useEffect, useRef } from 'react' import { useRouter } from 'next/router' import { Transition } from '@headlessui/react' @@ -6,6 +6,7 @@ import { useStore, useFlag } from 'hooks' import { getURL } from 'lib/helpers' import { post, patch } from 'lib/common/fetch' import { API_URL, PROJECT_STATUS } from 'lib/constants' +import HCaptcha from '@hcaptcha/react-hcaptcha' import Divider from 'components/ui/Divider' import { SubscriptionAddon } from './AddOns/AddOns.types' @@ -45,6 +46,9 @@ const EnterpriseUpdate: FC = ({ const isCustomDomainsEnabled = useFlag('customDomains') const isPITRSelfServeEnabled = useFlag('pitrSelfServe') + const [captchaToken, setCaptchaToken] = useState(null) + const captchaRef = useRef(null) + const projectId = ui.selectedProject?.id ?? -1 const projectRef = ui.selectedProject?.ref ?? 'default' const projectRegion = ui.selectedProject?.region ?? '' @@ -110,7 +114,8 @@ const EnterpriseUpdate: FC = ({ selectedAddons, nonChangeableAddons, selectedPaymentMethodId, - projectRegion + projectRegion, + undefined ), tier: currentSubscription.tier.price_id, } @@ -129,6 +134,12 @@ const EnterpriseUpdate: FC = ({ // Last todo to support enterprise billing on dashboard + E2E test const onConfirmPayment = async () => { + let token = captchaToken + if (!token) { + const captchaResponse = await captchaRef.current?.execute({ async: true }) + token = captchaResponse?.response ?? null + } + const payload = { ...formSubscriptionUpdatePayload( currentSubscription, @@ -136,7 +147,8 @@ const EnterpriseUpdate: FC = ({ selectedAddons, nonChangeableAddons, selectedPaymentMethodId, - projectRegion + projectRegion, + token ?? undefined ), tier: currentSubscription.tier.price_id, } @@ -273,6 +285,19 @@ const EnterpriseUpdate: FC = ({ setShowAddPaymentMethodModal(true) }} onConfirmPayment={onConfirmPayment} + captcha={ + { + setCaptchaToken(token) + }} + onExpire={() => { + setCaptchaToken(null) + }} + /> + } />
diff --git a/studio/components/interfaces/Billing/ExitSurvey/ExitSurvey.tsx b/studio/components/interfaces/Billing/ExitSurvey/ExitSurvey.tsx index c1838f7c233..7d7a02e26b1 100644 --- a/studio/components/interfaces/Billing/ExitSurvey/ExitSurvey.tsx +++ b/studio/components/interfaces/Billing/ExitSurvey/ExitSurvey.tsx @@ -11,6 +11,7 @@ import { CANCELLATION_REASONS } from '../Billing.constants' import { UpdateSuccess } from '../' import { SubscriptionPreview } from '../Billing.types' import { StripeSubscription } from 'components/interfaces/Billing' +import HCaptcha from '@hcaptcha/react-hcaptcha' interface Props { freeTier: any @@ -24,6 +25,9 @@ const ExitSurvey: FC = ({ freeTier, subscription, onSelectBack }) => { const projectId = ui.selectedProject?.id ?? -1 const projectRef = ui.selectedProject?.ref + const [captchaToken, setCaptchaToken] = useState(null) + const captchaRef = useRef(null) + const formRef = useRef() const initialValues = { message: '' } @@ -99,6 +103,12 @@ const ExitSurvey: FC = ({ freeTier, subscription, onSelectBack }) => { const downgradeProject = async (values?: any) => { const downgradeMessage = values?.message ?? message + let token = captchaToken + if (!token) { + const captchaResponse = await captchaRef.current?.execute({ async: true }) + token = captchaResponse?.response ?? null + } + try { setIsSubmitting(true) @@ -110,6 +120,7 @@ const ExitSurvey: FC = ({ freeTier, subscription, onSelectBack }) => { tier, addons, proration_date, + hcaptchaToken: token ?? undefined }) if (res?.error) { @@ -139,9 +150,9 @@ const ExitSurvey: FC = ({ freeTier, subscription, onSelectBack }) => { } const feedbackRes = await post(`${API_URL}/feedback/downgrade`, { projectRef, - reasons: selectedReasons.reduce((a, b) => `${a}- ${b}\n`, ''), - additionalFeedback: downgradeMessage, - exitAction: 'downgrade', + reasons: selectedReasons.reduce((a, b) => `${a}- ${b}\n`, ''), + additionalFeedback: downgradeMessage, + exitAction: 'downgrade', }) if (feedbackRes.error) throw feedbackRes.error } catch (error: any) { @@ -245,6 +256,19 @@ const ExitSurvey: FC = ({ freeTier, subscription, onSelectBack }) => { credits

+
+ { + setCaptchaToken(token) + }} + onExpire={() => { + setCaptchaToken(null) + }} + /> +
)} diff --git a/studio/components/interfaces/Billing/PaymentSummaryPanel/PaymentSummaryPanel.tsx b/studio/components/interfaces/Billing/PaymentSummaryPanel/PaymentSummaryPanel.tsx index 078d2a067d2..9e0db842011 100644 --- a/studio/components/interfaces/Billing/PaymentSummaryPanel/PaymentSummaryPanel.tsx +++ b/studio/components/interfaces/Billing/PaymentSummaryPanel/PaymentSummaryPanel.tsx @@ -1,4 +1,4 @@ -import { FC, useState } from 'react' +import { FC, useRef, useState } from 'react' import * as Tooltip from '@radix-ui/react-tooltip' import { Listbox, IconLoader, Button, IconPlus, IconAlertCircle, IconCreditCard } from 'ui' import { PermissionAction } from '@supabase/shared-types/out/constants' @@ -44,6 +44,8 @@ interface Props { onSelectAddNewPaymentMethod: () => void onConfirmPayment: () => void isSubmitting: boolean + + captcha: React.ReactNode } // Use case of this panel is actually only for upgrading from Free to Pro @@ -72,6 +74,7 @@ const PaymentSummaryPanel: FC = ({ onSelectAddNewPaymentMethod, onConfirmPayment, isSubmitting, + captcha, }) => { const { ui } = useStore() const projectRegion = ui.selectedProject?.region @@ -120,7 +123,7 @@ const PaymentSummaryPanel: FC = ({ ).toFixed(2) } - const validateOrder = () => { + const validateOrder = async () => { const error = validateSubscriptionUpdatePayload(selectedAddons) if (error) { return ui.setNotification({ @@ -382,6 +385,8 @@ const PaymentSummaryPanel: FC = ({ )} +
{captcha}
+
diff --git a/studio/components/interfaces/Billing/ProUpgrade.tsx b/studio/components/interfaces/Billing/ProUpgrade.tsx index 7fe2e78c578..68ff40f839e 100644 --- a/studio/components/interfaces/Billing/ProUpgrade.tsx +++ b/studio/components/interfaces/Billing/ProUpgrade.tsx @@ -1,4 +1,4 @@ -import { FC, useEffect, useState } from 'react' +import { FC, useEffect, useRef, useState } from 'react' import { Transition } from '@headlessui/react' import { useRouter } from 'next/router' import * as Tooltip from '@radix-ui/react-tooltip' @@ -29,6 +29,7 @@ import { } from './AddOns/AddOns.utils' import BackButton from 'components/ui/BackButton' import SupportPlan from './AddOns/SupportPlan' +import HCaptcha from '@hcaptcha/react-hcaptcha' // Do not allow compute size changes for af-south-1 @@ -52,6 +53,9 @@ const ProUpgrade: FC = ({ const isCustomDomainsEnabled = useFlag('customDomains') const isPITRSelfServeEnabled = useFlag('pitrSelfServe') + const [captchaToken, setCaptchaToken] = useState(null) + const captchaRef = useRef(null) + const { addons } = products const computeSizes = formatComputeSizes(addons) const pitrDurationOptions = formatPITROptions(addons) @@ -133,7 +137,8 @@ const ProUpgrade: FC = ({ selectedAddons, nonChangeableAddons, selectedPaymentMethodId, - projectRegion + projectRegion, + undefined ) setIsRefreshingPreview(true) @@ -149,13 +154,19 @@ const ProUpgrade: FC = ({ } const onConfirmPayment = async () => { + let token = captchaToken + if (!token) { + const captchaResponse = await captchaRef.current?.execute({ async: true }) + token = captchaResponse?.response ?? null + } const payload = formSubscriptionUpdatePayload( currentSubscription, selectedTier, selectedAddons, nonChangeableAddons, selectedPaymentMethodId, - projectRegion + projectRegion, + token ?? undefined, ) setIsSubmitting(true) @@ -165,6 +176,8 @@ const ProUpgrade: FC = ({ category: 'error', message: `Failed to update subscription: ${res?.error?.message}`, }) + setCaptchaToken(null) + captchaRef.current?.resetCaptcha() } else { if (isChangingComputeSize) { app.onProjectStatusUpdated(projectId, PROJECT_STATUS.RESTORING) @@ -320,6 +333,19 @@ const ProUpgrade: FC = ({ }} onConfirmPayment={onConfirmPayment} isSubmitting={isSubmitting} + captcha={ + { + setCaptchaToken(token) + }} + onExpire={() => { + setCaptchaToken(null) + }} + /> + } />
From faf962fd92d42f12301e86be1571271ef0de00cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Thu, 15 Dec 2022 18:28:33 +0100 Subject: [PATCH 10/42] slightly improve handling --- .../AddNewPaymentMethodModal.tsx | 17 +++++++++++------ .../interfaces/Billing/EnterpriseUpdate.tsx | 2 ++ .../Billing/ExitSurvey/ExitSurvey.tsx | 4 +++- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx index 7808f106114..cb355ba8f29 100644 --- a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx +++ b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx @@ -27,14 +27,19 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = const captchaRef = useRef(null) useEffect(() => { - if (visible && captchaLoaded) { - let token = captchaToken - if (!token) { - captchaRef.current?.execute({ async: true }).then((captchaResponse) => { - return setupIntent(captchaResponse?.response ?? undefined) - }) + const loadPaymentForm = async () => { + if (visible && captchaLoaded) { + let token = captchaToken + if (!token) { + const captchaResponse = await captchaRef.current?.execute({ async: true }) + token = captchaResponse?.response ?? null + } + + await setupIntent(token ?? undefined) } } + + loadPaymentForm() }, [visible, captchaLoaded]) const onCaptchaLoaded = () => { diff --git a/studio/components/interfaces/Billing/EnterpriseUpdate.tsx b/studio/components/interfaces/Billing/EnterpriseUpdate.tsx index 430b8dfcc68..8ea5ff543da 100644 --- a/studio/components/interfaces/Billing/EnterpriseUpdate.tsx +++ b/studio/components/interfaces/Billing/EnterpriseUpdate.tsx @@ -156,6 +156,8 @@ const EnterpriseUpdate: FC = ({ setIsSubmitting(true) const res = await patch(`${API_URL}/projects/${projectRef}/subscription`, payload) if (res?.error) { + setCaptchaToken(null) + captchaRef.current?.resetCaptcha() ui.setNotification({ category: 'error', message: `Failed to update subscription: ${res?.error?.message}`, diff --git a/studio/components/interfaces/Billing/ExitSurvey/ExitSurvey.tsx b/studio/components/interfaces/Billing/ExitSurvey/ExitSurvey.tsx index 7d7a02e26b1..7144a13c948 100644 --- a/studio/components/interfaces/Billing/ExitSurvey/ExitSurvey.tsx +++ b/studio/components/interfaces/Billing/ExitSurvey/ExitSurvey.tsx @@ -120,10 +120,12 @@ const ExitSurvey: FC = ({ freeTier, subscription, onSelectBack }) => { tier, addons, proration_date, - hcaptchaToken: token ?? undefined + hcaptchaToken: token ?? undefined, }) if (res?.error) { + setCaptchaToken(null) + captchaRef.current?.resetCaptcha() return ui.setNotification({ category: 'error', message: `Failed to cancel subscription: ${res?.error?.message}`, From ba5fa484484427fea6c6ddb0225c2e46bbc7b728 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Tue, 20 Dec 2022 08:50:41 +0100 Subject: [PATCH 11/42] reset captcha --- .../AddNewPaymentMethodModal.tsx | 12 ++++++++---- .../interfaces/Billing/EnterpriseUpdate.tsx | 9 +++++++-- .../interfaces/Billing/ExitSurvey/ExitSurvey.tsx | 9 +++++++-- studio/components/interfaces/Billing/ProUpgrade.tsx | 12 +++++++++--- 4 files changed, 31 insertions(+), 11 deletions(-) diff --git a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx index cb355ba8f29..edda104deb6 100644 --- a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx +++ b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx @@ -46,6 +46,11 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = setCaptchaLoaded(true) } + const resetCaptcha = () => { + setCaptchaToken(null) + captchaRef.current?.resetCaptcha() + } + const setupIntent = async (hcaptchaToken: string | undefined) => { setIntent(undefined) @@ -54,10 +59,9 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = hcaptchaToken, }) - if (intent.error) { - setCaptchaToken(null) - captchaRef.current?.resetCaptcha() + resetCaptcha() + if (intent.error) { return ui.setNotification({ category: 'error', message: intent.error.message, @@ -65,7 +69,7 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = }) } - setIntent(intent) + captchaRef.current?.resetCaptcha() } const options = { diff --git a/studio/components/interfaces/Billing/EnterpriseUpdate.tsx b/studio/components/interfaces/Billing/EnterpriseUpdate.tsx index 8ea5ff543da..77b10ab0809 100644 --- a/studio/components/interfaces/Billing/EnterpriseUpdate.tsx +++ b/studio/components/interfaces/Billing/EnterpriseUpdate.tsx @@ -132,6 +132,11 @@ const EnterpriseUpdate: FC = ({ setIsRefreshingPreview(false) } + const resetCaptcha = () => { + setCaptchaToken(null) + captchaRef.current?.resetCaptcha() + } + // Last todo to support enterprise billing on dashboard + E2E test const onConfirmPayment = async () => { let token = captchaToken @@ -155,9 +160,9 @@ const EnterpriseUpdate: FC = ({ setIsSubmitting(true) const res = await patch(`${API_URL}/projects/${projectRef}/subscription`, payload) + resetCaptcha() + if (res?.error) { - setCaptchaToken(null) - captchaRef.current?.resetCaptcha() ui.setNotification({ category: 'error', message: `Failed to update subscription: ${res?.error?.message}`, diff --git a/studio/components/interfaces/Billing/ExitSurvey/ExitSurvey.tsx b/studio/components/interfaces/Billing/ExitSurvey/ExitSurvey.tsx index 7144a13c948..52ad8ea889e 100644 --- a/studio/components/interfaces/Billing/ExitSurvey/ExitSurvey.tsx +++ b/studio/components/interfaces/Billing/ExitSurvey/ExitSurvey.tsx @@ -100,6 +100,11 @@ const ExitSurvey: FC = ({ freeTier, subscription, onSelectBack }) => { } } + const resetCaptcha = () => { + setCaptchaToken(null) + captchaRef.current?.resetCaptcha() + } + const downgradeProject = async (values?: any) => { const downgradeMessage = values?.message ?? message @@ -123,9 +128,9 @@ const ExitSurvey: FC = ({ freeTier, subscription, onSelectBack }) => { hcaptchaToken: token ?? undefined, }) + resetCaptcha() + if (res?.error) { - setCaptchaToken(null) - captchaRef.current?.resetCaptcha() return ui.setNotification({ category: 'error', message: `Failed to cancel subscription: ${res?.error?.message}`, diff --git a/studio/components/interfaces/Billing/ProUpgrade.tsx b/studio/components/interfaces/Billing/ProUpgrade.tsx index 68ff40f839e..f299c2308ce 100644 --- a/studio/components/interfaces/Billing/ProUpgrade.tsx +++ b/studio/components/interfaces/Billing/ProUpgrade.tsx @@ -153,6 +153,11 @@ const ProUpgrade: FC = ({ setIsRefreshingPreview(false) } + const resetCaptcha = () => { + setCaptchaToken(null) + captchaRef.current?.resetCaptcha() + } + const onConfirmPayment = async () => { let token = captchaToken if (!token) { @@ -166,18 +171,19 @@ const ProUpgrade: FC = ({ nonChangeableAddons, selectedPaymentMethodId, projectRegion, - token ?? undefined, + token ?? undefined ) setIsSubmitting(true) const res = await patch(`${API_URL}/projects/${projectRef}/subscription`, payload) + + resetCaptcha() + if (res?.error) { ui.setNotification({ category: 'error', message: `Failed to update subscription: ${res?.error?.message}`, }) - setCaptchaToken(null) - captchaRef.current?.resetCaptcha() } else { if (isChangingComputeSize) { app.onProjectStatusUpdated(projectId, PROJECT_STATUS.RESTORING) From 00b94fdc1f9bfea478f3ec808902525451309d0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Tue, 20 Dec 2022 10:03:44 +0100 Subject: [PATCH 12/42] payment method modal --- .../AddNewPaymentMethodModal.tsx | 50 ++++++++++--------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx index edda104deb6..9da33e2f215 100644 --- a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx +++ b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx @@ -36,6 +36,7 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = } await setupIntent(token ?? undefined) + resetCaptcha() } } @@ -59,17 +60,15 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = hcaptchaToken, }) - resetCaptcha() - if (intent.error) { return ui.setNotification({ category: 'error', message: intent.error.message, error: intent.error, }) + } else { + setIntent(intent) } - - captchaRef.current?.resetCaptcha() } const options = { @@ -77,15 +76,9 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = appearance: { theme: 'night', labels: 'floating' }, } as any + // Needs to be re-rendered properly for hCaptcha to work seamlessly return ( - + <> = ({ visible, returnUrl, onCancel }) = setCaptchaToken(null) }} /> -
- {intent !== undefined ? ( - - - - ) : ( -
- -
- )} -
-
+ +
+ {intent !== undefined ? ( + + + + ) : ( +
+ +
+ )} +
+
+ ) } From 5d213e9b53bbcd6205e3136aee5e730e084012f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Tue, 20 Dec 2022 10:21:14 +0100 Subject: [PATCH 13/42] log captcha error for debuggability --- .../AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx index 9da33e2f215..5b23757d3c0 100644 --- a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx +++ b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx @@ -76,7 +76,6 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = appearance: { theme: 'night', labels: 'floating' }, } as any - // Needs to be re-rendered properly for hCaptcha to work seamlessly return ( <> = ({ visible, returnUrl, onCancel }) = sitekey={process.env.NEXT_PUBLIC_HCAPTCHA_SITE_KEY!} size="invisible" onLoad={onCaptchaLoaded} + onError={(err) => console.error(err)} onVerify={(token) => { setCaptchaToken(token) }} From ceb5e9b4f80309aa805e392b384f20f63ce9e52d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Tue, 20 Dec 2022 13:00:30 +0100 Subject: [PATCH 14/42] try to fix hcaptcha in modal --- .../AddNewPaymentMethodModal.tsx | 65 ++++++++++--------- 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx index 5b23757d3c0..e28d65ec9fd 100644 --- a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx +++ b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx @@ -1,4 +1,4 @@ -import { FC, useEffect, useRef, useState } from 'react' +import { FC, useCallback, useEffect, useRef, useState } from 'react' import { IconLoader, Modal } from 'ui' import { loadStripe } from '@stripe/stripe-js' import { Elements } from '@stripe/react-stripe-js' @@ -24,32 +24,41 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = const [captchaLoaded, setCaptchaLoaded] = useState(false) const [captchaToken, setCaptchaToken] = useState(null) - const captchaRef = useRef(null) + const [captchaRef, setCaptchaRef] = useState(null) + + const captchaRefCallback = useCallback((node) => { + setCaptchaRef(node) + }, []) useEffect(() => { const loadPaymentForm = async () => { - if (visible && captchaLoaded) { + if (visible && captchaLoaded && captchaRef) { + console.log('load payment form', { visible, captchaLoaded }) let token = captchaToken if (!token) { - const captchaResponse = await captchaRef.current?.execute({ async: true }) + const captchaResponse = await captchaRef.execute({ async: true }) token = captchaResponse?.response ?? null } + console.log('Setting up intent', { token }) + await setupIntent(token ?? undefined) resetCaptcha() } } loadPaymentForm() - }, [visible, captchaLoaded]) + }, [visible, captchaLoaded, captchaRef]) const onCaptchaLoaded = () => { + console.log('on captcha loaded') setCaptchaLoaded(true) } const resetCaptcha = () => { + console.log('Resetting captcha') setCaptchaToken(null) - captchaRef.current?.resetCaptcha() + captchaRef?.resetCaptcha() } const setupIntent = async (hcaptchaToken: string | undefined) => { @@ -77,9 +86,16 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = } as any return ( - <> + = ({ visible, returnUrl, onCancel }) = setCaptchaToken(null) }} /> - -
- {intent !== undefined ? ( - - - - ) : ( -
- -
- )} -
-
- +
+ {intent !== undefined ? ( + + + + ) : ( +
+ +
+ )} +
+
) } From 5939182bc938026e732fa1cb9a54de7c1a4c106b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Tue, 20 Dec 2022 13:14:59 +0100 Subject: [PATCH 15/42] help me --- .../AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx index e28d65ec9fd..ff7700e1f0e 100644 --- a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx +++ b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx @@ -27,13 +27,14 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = const [captchaRef, setCaptchaRef] = useState(null) const captchaRefCallback = useCallback((node) => { + console.log({ node }) setCaptchaRef(node) }, []) useEffect(() => { const loadPaymentForm = async () => { + console.log('load payment form', { visible, captchaLoaded, captchaRef }) if (visible && captchaLoaded && captchaRef) { - console.log('load payment form', { visible, captchaLoaded }) let token = captchaToken if (!token) { const captchaResponse = await captchaRef.execute({ async: true }) From 861cf7899b6aa62a75707468f6fd913758822669 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Tue, 20 Dec 2022 13:37:15 +0100 Subject: [PATCH 16/42] render only on visible --- .../AddNewPaymentMethodModal.tsx | 32 ++++++++++--------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx index ff7700e1f0e..8fdcc6ef764 100644 --- a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx +++ b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx @@ -34,7 +34,7 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = useEffect(() => { const loadPaymentForm = async () => { console.log('load payment form', { visible, captchaLoaded, captchaRef }) - if (visible && captchaLoaded && captchaRef) { + if (visible && captchaRef && captchaLoaded) { let token = captchaToken if (!token) { const captchaResponse = await captchaRef.execute({ async: true }) @@ -49,7 +49,7 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = } loadPaymentForm() - }, [visible, captchaLoaded, captchaRef]) + }, [visible, captchaRef, captchaLoaded]) const onCaptchaLoaded = () => { console.log('on captcha loaded') @@ -95,19 +95,21 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = onCancel={onCancel} className="PAYMENT" > - console.error(err)} - onVerify={(token) => { - setCaptchaToken(token) - }} - onExpire={() => { - setCaptchaToken(null) - }} - /> + {visible && ( + console.error(err)} + onLoad={() => onCaptchaLoaded()} + onVerify={(token) => { + setCaptchaToken(token) + }} + onExpire={() => { + setCaptchaToken(null) + }} + /> + )}
{intent !== undefined ? ( From 427fb8c56edc39647848d26e4b9a9dd0bc1e47b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Tue, 20 Dec 2022 13:57:28 +0100 Subject: [PATCH 17/42] jesus take the wheel --- .../AddNewPaymentMethodModal.tsx | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx index 8fdcc6ef764..d40e165c652 100644 --- a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx +++ b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx @@ -27,10 +27,14 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = const [captchaRef, setCaptchaRef] = useState(null) const captchaRefCallback = useCallback((node) => { - console.log({ node }) setCaptchaRef(node) }, []) + useEffect(() => { + setCaptchaLoaded(true) + // @ts-ignore + }, [window.hcaptcha]) + useEffect(() => { const loadPaymentForm = async () => { console.log('load payment form', { visible, captchaLoaded, captchaRef }) @@ -51,11 +55,6 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = loadPaymentForm() }, [visible, captchaRef, captchaLoaded]) - const onCaptchaLoaded = () => { - console.log('on captcha loaded') - setCaptchaLoaded(true) - } - const resetCaptcha = () => { console.log('Resetting captcha') setCaptchaToken(null) @@ -86,30 +85,31 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = appearance: { theme: 'night', labels: 'floating' }, } as any + const onLocalCancel = () => { + setIntent(undefined) + return onCancel() + } + return ( - {visible && ( - console.error(err)} - onLoad={() => onCaptchaLoaded()} - onVerify={(token) => { - setCaptchaToken(token) - }} - onExpire={() => { - setCaptchaToken(null) - }} - /> - )} + { + setCaptchaToken(token) + }} + onExpire={() => { + setCaptchaToken(null) + }} + />
{intent !== undefined ? ( From 52548ec10416ab22e5f8c3619427655bd0f79db5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Tue, 20 Dec 2022 14:10:04 +0100 Subject: [PATCH 18/42] pain --- .../AddNewPaymentMethodModal.tsx | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx index d40e165c652..0883d871175 100644 --- a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx +++ b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx @@ -30,6 +30,13 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = setCaptchaRef(node) }, []) + /** + * We want to load the Stripe payment elements only after successfully verifying captcha. + * To execute hCaptcha when displaying the modal, we need to ensure hCaptcha is actually loaded. + * The `onLoad` method from hCaptcha is only executed ONCE for the entire application, so we cannot listen to it. + * One way to find out if hCaptcha has been initialized, is checking `window.hcaptcha`. When checking via window, + * we know if the hCaptcha has been initialized from a different part of the app or through this component. + */ useEffect(() => { setCaptchaLoaded(true) // @ts-ignore @@ -37,7 +44,6 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = useEffect(() => { const loadPaymentForm = async () => { - console.log('load payment form', { visible, captchaLoaded, captchaRef }) if (visible && captchaRef && captchaLoaded) { let token = captchaToken if (!token) { @@ -45,8 +51,6 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = token = captchaResponse?.response ?? null } - console.log('Setting up intent', { token }) - await setupIntent(token ?? undefined) resetCaptcha() } @@ -56,7 +60,6 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = }, [visible, captchaRef, captchaLoaded]) const resetCaptcha = () => { - console.log('Resetting captcha') setCaptchaToken(null) captchaRef?.resetCaptcha() } @@ -113,7 +116,7 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) =
{intent !== undefined ? ( - + ) : (
From 284ce90b1767524dbada3d2fb49f7d0ace7010fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Tue, 20 Dec 2022 14:32:16 +0100 Subject: [PATCH 19/42] move captcha outside of modal --- .../AddNewPaymentMethodModal.tsx | 33 +++++++++---------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx index 0883d871175..065027c7847 100644 --- a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx +++ b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx @@ -94,14 +94,9 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = } return ( - + // We cant display the hCaptcha in the modal, as the modal auto-closes when clicking the captcha + // So we only show the modal if the captcha has been executed successfully (intent loaded) + <> = ({ visible, returnUrl, onCancel }) = setCaptchaToken(null) }} /> -
- {intent !== undefined ? ( + + +
- ) : ( -
- -
- )} -
-
+
+
+ ) } From cd8d8713e67b0cf09e7fb768757b1223774d87c2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Tue, 20 Dec 2022 14:42:54 +0100 Subject: [PATCH 20/42] log open/close --- .../AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx index 065027c7847..59a11d8c36d 100644 --- a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx +++ b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx @@ -104,6 +104,8 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = onVerify={(token) => { setCaptchaToken(token) }} + onClose={() => console.log('onclose')} + onOpen={() => console.log('onopen')} onExpire={() => { setCaptchaToken(null) }} From 5fc41f27a17a03561e0b78bbbcbdae1834153f5c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kevin=20Gr=C3=BCneberg?= Date: Tue, 20 Dec 2022 14:50:17 +0100 Subject: [PATCH 21/42] is this it --- .../AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx index 59a11d8c36d..5f18c9dff3b 100644 --- a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx +++ b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx @@ -104,8 +104,7 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = onVerify={(token) => { setCaptchaToken(token) }} - onClose={() => console.log('onclose')} - onOpen={() => console.log('onopen')} + onClose={onLocalCancel} onExpire={() => { setCaptchaToken(null) }} From 47216df6d0401bc6d47997f20f0aca2f770c2a62 Mon Sep 17 00:00:00 2001 From: Alaister Young Date: Tue, 20 Dec 2022 14:26:42 +0000 Subject: [PATCH 22/42] adds hcaptcha loaded store --- package-lock.json | 61 +++++++++++++++++++ .../AddNewPaymentMethodModal.tsx | 23 ++----- studio/package.json | 1 + studio/pages/_app.tsx | 2 + studio/stores/hcaptcha-loaded-store.tsx | 31 ++++++++++ 5 files changed, 101 insertions(+), 17 deletions(-) create mode 100644 studio/stores/hcaptcha-loaded-store.tsx diff --git a/package-lock.json b/package-lock.json index 5f6dcf98766..938bb21e236 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37607,6 +37607,7 @@ "uniforms-bootstrap4": "^3.6.2", "uniforms-bridge-json-schema": "^3.6.2", "uuid": "^8.3.2", + "valtio": "^1.7.6", "yup": "^0.32.11", "yup-password": "^0.2.2", "zxcvbn": "^4.4.2" @@ -37829,6 +37830,11 @@ "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-9.3.1.tgz", "integrity": "sha512-/Yvsd2D7miYB4HLJ3hOOS0+vnowQpaT75FsHzr/y5M9P4q9bwa7RcbW2YdH6KZBn8ceLbKGnHxMZ1CHliGHUFw==" }, + "studio/node_modules/proxy-compare": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-2.3.0.tgz", + "integrity": "sha512-c3L2CcAi7f7pvlD0D7xsF+2CQIW8C3HaYx2Pfgq8eA4HAl3GAH6/dVYsyBbYF/0XJs2ziGLrzmz5fmzPm6A0pQ==" + }, "studio/node_modules/react-resize-detector": { "version": "6.7.8", "resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-6.7.8.tgz", @@ -37889,6 +37895,46 @@ "node": ">=4.2.0" } }, + "studio/node_modules/valtio": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/valtio/-/valtio-1.7.6.tgz", + "integrity": "sha512-zsGrCCYOIpy8egQxftduFyJusF/BMu3CganhHKUOE/I6t6V6yA1MDfZZkrYoWYCGkC3rSBYcIHEEsoYQM9lV2w==", + "dependencies": { + "proxy-compare": "2.3.0", + "use-sync-external-store": "1.2.0" + }, + "engines": { + "node": ">=12.7.0" + }, + "peerDependencies": { + "@babel/helper-module-imports": ">=7.12", + "@babel/types": ">=7.13", + "aslemammad-vite-plugin-macro": ">=1.0.0-alpha.1", + "babel-plugin-macros": ">=3.0", + "react": ">=16.8", + "vite": ">=2.8.6" + }, + "peerDependenciesMeta": { + "@babel/helper-module-imports": { + "optional": true + }, + "@babel/types": { + "optional": true + }, + "aslemammad-vite-plugin-macro": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + }, + "react": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, "studio/node_modules/webpack-bundle-analyzer": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.3.0.tgz", @@ -63371,6 +63417,7 @@ "uniforms-bootstrap4": "^3.6.2", "uniforms-bridge-json-schema": "^3.6.2", "uuid": "^8.3.2", + "valtio": "*", "yup": "^0.32.11", "yup-password": "^0.2.2", "zxcvbn": "^4.4.2" @@ -63521,6 +63568,11 @@ "resolved": "https://registry.npmjs.org/openapi-types/-/openapi-types-9.3.1.tgz", "integrity": "sha512-/Yvsd2D7miYB4HLJ3hOOS0+vnowQpaT75FsHzr/y5M9P4q9bwa7RcbW2YdH6KZBn8ceLbKGnHxMZ1CHliGHUFw==" }, + "proxy-compare": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-2.3.0.tgz", + "integrity": "sha512-c3L2CcAi7f7pvlD0D7xsF+2CQIW8C3HaYx2Pfgq8eA4HAl3GAH6/dVYsyBbYF/0XJs2ziGLrzmz5fmzPm6A0pQ==" + }, "react-resize-detector": { "version": "6.7.8", "resolved": "https://registry.npmjs.org/react-resize-detector/-/react-resize-detector-6.7.8.tgz", @@ -63564,6 +63616,15 @@ "integrity": "sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==", "dev": true }, + "valtio": { + "version": "1.7.6", + "resolved": "https://registry.npmjs.org/valtio/-/valtio-1.7.6.tgz", + "integrity": "sha512-zsGrCCYOIpy8egQxftduFyJusF/BMu3CganhHKUOE/I6t6V6yA1MDfZZkrYoWYCGkC3rSBYcIHEEsoYQM9lV2w==", + "requires": { + "proxy-compare": "2.3.0", + "use-sync-external-store": "1.2.0" + } + }, "webpack-bundle-analyzer": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.3.0.tgz", diff --git a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx index 5f18c9dff3b..0f553810bc3 100644 --- a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx +++ b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx @@ -1,13 +1,14 @@ -import { FC, useCallback, useEffect, useRef, useState } from 'react' -import { IconLoader, Modal } from 'ui' +import HCaptcha from '@hcaptcha/react-hcaptcha' import { loadStripe } from '@stripe/stripe-js' import { Elements } from '@stripe/react-stripe-js' +import { FC, useCallback, useEffect, useState } from 'react' +import { Modal } from 'ui' import { useStore } from 'hooks' +import { useIsHCaptchaLoaded } from 'stores/hcaptcha-loaded-store' import { post } from 'lib/common/fetch' import { API_URL, STRIPE_PUBLIC_KEY } from 'lib/constants' import AddPaymentMethodForm from './AddPaymentMethodForm' -import HCaptcha from '@hcaptcha/react-hcaptcha' interface Props { visible: boolean @@ -21,7 +22,7 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = const { ui } = useStore() const [intent, setIntent] = useState() - const [captchaLoaded, setCaptchaLoaded] = useState(false) + const captchaLoaded = useIsHCaptchaLoaded() const [captchaToken, setCaptchaToken] = useState(null) const [captchaRef, setCaptchaRef] = useState(null) @@ -30,18 +31,6 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = setCaptchaRef(node) }, []) - /** - * We want to load the Stripe payment elements only after successfully verifying captcha. - * To execute hCaptcha when displaying the modal, we need to ensure hCaptcha is actually loaded. - * The `onLoad` method from hCaptcha is only executed ONCE for the entire application, so we cannot listen to it. - * One way to find out if hCaptcha has been initialized, is checking `window.hcaptcha`. When checking via window, - * we know if the hCaptcha has been initialized from a different part of the app or through this component. - */ - useEffect(() => { - setCaptchaLoaded(true) - // @ts-ignore - }, [window.hcaptcha]) - useEffect(() => { const loadPaymentForm = async () => { if (visible && captchaRef && captchaLoaded) { @@ -118,7 +107,7 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = onCancel={onLocalCancel} className="PAYMENT" > -
+
diff --git a/studio/package.json b/studio/package.json index bf108ae4eda..7a39cefd0d6 100644 --- a/studio/package.json +++ b/studio/package.json @@ -89,6 +89,7 @@ "uniforms-bootstrap4": "^3.6.2", "uniforms-bridge-json-schema": "^3.6.2", "uuid": "^8.3.2", + "valtio": "^1.7.6", "yup": "^0.32.11", "yup-password": "^0.2.2", "zxcvbn": "^4.4.2" diff --git a/studio/pages/_app.tsx b/studio/pages/_app.tsx index cd16894f14d..e77baa71827 100644 --- a/studio/pages/_app.tsx +++ b/studio/pages/_app.tsx @@ -27,6 +27,7 @@ import { useEffect, useState } from 'react' import { Hydrate, QueryClientProvider } from '@tanstack/react-query' import { ReactQueryDevtools } from '@tanstack/react-query-devtools' import { RootStore } from 'stores' +import HCaptchaLoadedStore from 'stores/hcaptcha-loaded-store' import { StoreProvider } from 'hooks' import { GOTRUE_ERRORS } from 'lib/constants' import { auth } from 'lib/gotrue' @@ -115,6 +116,7 @@ function MyApp({ Component, pageProps }: AppPropsWithLayout) { + diff --git a/studio/stores/hcaptcha-loaded-store.tsx b/studio/stores/hcaptcha-loaded-store.tsx new file mode 100644 index 00000000000..008053d20b0 --- /dev/null +++ b/studio/stores/hcaptcha-loaded-store.tsx @@ -0,0 +1,31 @@ +import HCaptcha from '@hcaptcha/react-hcaptcha' +import { proxy, useSnapshot } from 'valtio' + +const hCaptchaLoadedStoreState = proxy({ + loaded: false, + setLoaded() { + hCaptchaLoadedStoreState.loaded = true + }, +}) + +export const useIsHCaptchaLoaded = () => { + const snap = useSnapshot(hCaptchaLoadedStoreState) + + return snap.loaded +} + +const HCaptchaLoadedStore = () => { + const onLoad = () => { + hCaptchaLoadedStoreState.setLoaded() + } + + return ( + + ) +} + +export default HCaptchaLoadedStore From 773403b4ab6d6011f91ac1d228045504f1d5ee95 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 21 Dec 2022 11:39:39 +0700 Subject: [PATCH 23/42] Change loading logic for when hcaptcha is involved in payment summary panel --- package-lock.json | 2 +- studio/components/interfaces/Billing/ProUpgrade.tsx | 13 ++++++++----- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 938bb21e236..d002b6210bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -63417,7 +63417,7 @@ "uniforms-bootstrap4": "^3.6.2", "uniforms-bridge-json-schema": "^3.6.2", "uuid": "^8.3.2", - "valtio": "*", + "valtio": "^1.7.6", "yup": "^0.32.11", "yup-password": "^0.2.2", "zxcvbn": "^4.4.2" diff --git a/studio/components/interfaces/Billing/ProUpgrade.tsx b/studio/components/interfaces/Billing/ProUpgrade.tsx index f299c2308ce..2b3357df770 100644 --- a/studio/components/interfaces/Billing/ProUpgrade.tsx +++ b/studio/components/interfaces/Billing/ProUpgrade.tsx @@ -159,11 +159,16 @@ const ProUpgrade: FC = ({ } const onConfirmPayment = async () => { - let token = captchaToken + setIsSubmitting(true) + + const token = + captchaToken ?? (await captchaRef.current?.execute({ async: true }))?.response ?? undefined + if (!token) { - const captchaResponse = await captchaRef.current?.execute({ async: true }) - token = captchaResponse?.response ?? null + setIsSubmitting(false) + return } + const payload = formSubscriptionUpdatePayload( currentSubscription, selectedTier, @@ -173,8 +178,6 @@ const ProUpgrade: FC = ({ projectRegion, token ?? undefined ) - - setIsSubmitting(true) const res = await patch(`${API_URL}/projects/${projectRef}/subscription`, payload) resetCaptcha() From ebed18fcfe8f0a537a7f3bd6f091990d57322282 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 21 Dec 2022 11:40:35 +0700 Subject: [PATCH 24/42] Change loading logic for when hcaptcha is involved in payment summary panel enterprise --- .../interfaces/Billing/EnterpriseUpdate.tsx | 12 +++++++----- studio/components/interfaces/Billing/ProUpgrade.tsx | 1 - 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/studio/components/interfaces/Billing/EnterpriseUpdate.tsx b/studio/components/interfaces/Billing/EnterpriseUpdate.tsx index 77b10ab0809..ed599f91aa2 100644 --- a/studio/components/interfaces/Billing/EnterpriseUpdate.tsx +++ b/studio/components/interfaces/Billing/EnterpriseUpdate.tsx @@ -139,10 +139,14 @@ const EnterpriseUpdate: FC = ({ // Last todo to support enterprise billing on dashboard + E2E test const onConfirmPayment = async () => { - let token = captchaToken + setIsSubmitting(true) + + const token = + captchaToken ?? (await captchaRef.current?.execute({ async: true }))?.response ?? undefined + if (!token) { - const captchaResponse = await captchaRef.current?.execute({ async: true }) - token = captchaResponse?.response ?? null + setIsSubmitting(false) + return } const payload = { @@ -157,8 +161,6 @@ const EnterpriseUpdate: FC = ({ ), tier: currentSubscription.tier.price_id, } - - setIsSubmitting(true) const res = await patch(`${API_URL}/projects/${projectRef}/subscription`, payload) resetCaptcha() diff --git a/studio/components/interfaces/Billing/ProUpgrade.tsx b/studio/components/interfaces/Billing/ProUpgrade.tsx index 2b3357df770..a1a75ddc465 100644 --- a/studio/components/interfaces/Billing/ProUpgrade.tsx +++ b/studio/components/interfaces/Billing/ProUpgrade.tsx @@ -179,7 +179,6 @@ const ProUpgrade: FC = ({ token ?? undefined ) const res = await patch(`${API_URL}/projects/${projectRef}/subscription`, payload) - resetCaptcha() if (res?.error) { From a436163c96e51e17b810573b8e2e5df76f09da06 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 21 Dec 2022 11:59:28 +0700 Subject: [PATCH 25/42] Add console log --- studio/components/interfaces/Billing/ProUpgrade.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/studio/components/interfaces/Billing/ProUpgrade.tsx b/studio/components/interfaces/Billing/ProUpgrade.tsx index a1a75ddc465..e4eae47af07 100644 --- a/studio/components/interfaces/Billing/ProUpgrade.tsx +++ b/studio/components/interfaces/Billing/ProUpgrade.tsx @@ -164,6 +164,8 @@ const ProUpgrade: FC = ({ const token = captchaToken ?? (await captchaRef.current?.execute({ async: true }))?.response ?? undefined + console.log('confirm', { token }) + if (!token) { setIsSubmitting(false) return From b57fc12da86da9385b05b823a1b3e3cf73199961 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 21 Dec 2022 12:09:00 +0700 Subject: [PATCH 26/42] Add more logs --- .../components/interfaces/Billing/ProUpgrade.tsx | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/studio/components/interfaces/Billing/ProUpgrade.tsx b/studio/components/interfaces/Billing/ProUpgrade.tsx index e4eae47af07..d30584ef87c 100644 --- a/studio/components/interfaces/Billing/ProUpgrade.tsx +++ b/studio/components/interfaces/Billing/ProUpgrade.tsx @@ -160,17 +160,21 @@ const ProUpgrade: FC = ({ const onConfirmPayment = async () => { setIsSubmitting(true) + let token = captchaToken - const token = - captchaToken ?? (await captchaRef.current?.execute({ async: true }))?.response ?? undefined - - console.log('confirm', { token }) - - if (!token) { + try { + if (!token) { + const captchaResponse = await captchaRef.current?.execute({ async: true }) + token = captchaResponse?.response ?? null + } + } catch (error) { + console.log('ERRORR', error) setIsSubmitting(false) return } + console.log('TOKEN', { token }) + const payload = formSubscriptionUpdatePayload( currentSubscription, selectedTier, From 0d5c1556a61d0a9a250e70113c2adda3be69113e Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 21 Dec 2022 12:42:34 +0700 Subject: [PATCH 27/42] Fix hcaptcha loading for proupgrade and enterprise update --- .../interfaces/Billing/EnterpriseUpdate.tsx | 11 +++++++---- studio/components/interfaces/Billing/ProUpgrade.tsx | 3 --- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/studio/components/interfaces/Billing/EnterpriseUpdate.tsx b/studio/components/interfaces/Billing/EnterpriseUpdate.tsx index ed599f91aa2..078cd190f42 100644 --- a/studio/components/interfaces/Billing/EnterpriseUpdate.tsx +++ b/studio/components/interfaces/Billing/EnterpriseUpdate.tsx @@ -140,11 +140,14 @@ const EnterpriseUpdate: FC = ({ // Last todo to support enterprise billing on dashboard + E2E test const onConfirmPayment = async () => { setIsSubmitting(true) + let token = captchaToken - const token = - captchaToken ?? (await captchaRef.current?.execute({ async: true }))?.response ?? undefined - - if (!token) { + try { + if (!token) { + const captchaResponse = await captchaRef.current?.execute({ async: true }) + token = captchaResponse?.response ?? null + } + } catch (error) { setIsSubmitting(false) return } diff --git a/studio/components/interfaces/Billing/ProUpgrade.tsx b/studio/components/interfaces/Billing/ProUpgrade.tsx index d30584ef87c..e7f246d8b27 100644 --- a/studio/components/interfaces/Billing/ProUpgrade.tsx +++ b/studio/components/interfaces/Billing/ProUpgrade.tsx @@ -168,13 +168,10 @@ const ProUpgrade: FC = ({ token = captchaResponse?.response ?? null } } catch (error) { - console.log('ERRORR', error) setIsSubmitting(false) return } - console.log('TOKEN', { token }) - const payload = formSubscriptionUpdatePayload( currentSubscription, selectedTier, From cdbc41440568560cbbc8ee80bbb16f5e491b5df3 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 21 Dec 2022 13:01:39 +0700 Subject: [PATCH 28/42] Update ext survey hcaptcha --- .../AddNewPaymentMethodModal.tsx | 11 +++++-- .../Billing/ExitSurvey/ExitSurvey.tsx | 29 ++++++++++++------- .../[ref]/settings/billing/update/free.tsx | 2 +- 3 files changed, 27 insertions(+), 15 deletions(-) diff --git a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx index 0f553810bc3..f8c52edd2eb 100644 --- a/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx +++ b/studio/components/interfaces/Billing/AddNewPaymentMethodModal/AddNewPaymentMethodModal.tsx @@ -35,9 +35,14 @@ const AddNewPaymentMethodModal: FC = ({ visible, returnUrl, onCancel }) = const loadPaymentForm = async () => { if (visible && captchaRef && captchaLoaded) { let token = captchaToken - if (!token) { - const captchaResponse = await captchaRef.execute({ async: true }) - token = captchaResponse?.response ?? null + + try { + if (!token) { + const captchaResponse = await captchaRef.execute({ async: true }) + token = captchaResponse?.response ?? null + } + } catch (error) { + return } await setupIntent(token ?? undefined) diff --git a/studio/components/interfaces/Billing/ExitSurvey/ExitSurvey.tsx b/studio/components/interfaces/Billing/ExitSurvey/ExitSurvey.tsx index 52ad8ea889e..35232ef2b7f 100644 --- a/studio/components/interfaces/Billing/ExitSurvey/ExitSurvey.tsx +++ b/studio/components/interfaces/Billing/ExitSurvey/ExitSurvey.tsx @@ -25,8 +25,8 @@ const ExitSurvey: FC = ({ freeTier, subscription, onSelectBack }) => { const projectId = ui.selectedProject?.id ?? -1 const projectRef = ui.selectedProject?.ref - const [captchaToken, setCaptchaToken] = useState(null) const captchaRef = useRef(null) + const [captchaToken, setCaptchaToken] = useState(null) const formRef = useRef() @@ -92,11 +92,24 @@ const ExitSurvey: FC = ({ freeTier, subscription, onSelectBack }) => { }) } + setIsSubmitting(true) + let token = captchaToken + + try { + if (!token) { + const captchaResponse = await captchaRef.current?.execute({ async: true }) + token = captchaResponse?.response ?? null + } + } catch (error) { + setIsSubmitting(false) + return + } + if (willChangeComputeSize) { setMessage(values.message) return setShowConfirmModal(true) } else { - downgradeProject(values) + downgradeProject(values, token as string) } } @@ -105,15 +118,9 @@ const ExitSurvey: FC = ({ freeTier, subscription, onSelectBack }) => { captchaRef.current?.resetCaptcha() } - const downgradeProject = async (values?: any) => { + const downgradeProject = async (values?: any, hcaptchaToken?: string) => { const downgradeMessage = values?.message ?? message - let token = captchaToken - if (!token) { - const captchaResponse = await captchaRef.current?.execute({ async: true }) - token = captchaResponse?.response ?? null - } - try { setIsSubmitting(true) @@ -125,7 +132,7 @@ const ExitSurvey: FC = ({ freeTier, subscription, onSelectBack }) => { tier, addons, proration_date, - hcaptchaToken: token ?? undefined, + hcaptchaToken: captchaToken ?? hcaptchaToken, }) resetCaptcha() @@ -191,7 +198,7 @@ const ExitSurvey: FC = ({ freeTier, subscription, onSelectBack }) => { enterFrom="transform opacity-0 translate-x-10" enterTo="transform opacity-100 translate-x-0" > -
+
- -
-
-
- - ) -} From 6ed0299844d8b3d3caad7e60dfd18c8eba0a5c55 Mon Sep 17 00:00:00 2001 From: Terry Sutton Date: Tue, 20 Dec 2022 17:28:14 -0330 Subject: [PATCH 31/42] Fixes unique keys warning --- apps/docs/components/Navigation/Footer.tsx | 4 ++-- .../docs/components/Navigation/NavigationMenu/HomeMenu.tsx | 4 ++-- .../Navigation/NavigationMenu/NavigationMenu.tsx | 4 ++-- spec/common-self-hosting-storage-sections.json | 7 ------- 4 files changed, 6 insertions(+), 13 deletions(-) diff --git a/apps/docs/components/Navigation/Footer.tsx b/apps/docs/components/Navigation/Footer.tsx index 3d85d66b3cb..cbbb215e7a6 100644 --- a/apps/docs/components/Navigation/Footer.tsx +++ b/apps/docs/components/Navigation/Footer.tsx @@ -14,7 +14,7 @@ const Footer = () => { Supabase 2022 — {footerData.map((item) => ( - + {item.title} ))} @@ -41,7 +41,7 @@ const Footer = () => { diff --git a/apps/docs/components/Navigation/NavigationMenu/HomeMenu.tsx b/apps/docs/components/Navigation/NavigationMenu/HomeMenu.tsx index e6f5e8ea4be..d4167720768 100644 --- a/apps/docs/components/Navigation/NavigationMenu/HomeMenu.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/HomeMenu.tsx @@ -129,7 +129,7 @@ const NavigationMenuHome = () => {
    {home.map((section, sectionIndex) => { return ( - <> +
    {sectionIndex !== 0 && (
    { })}
    - +
) })} diff --git a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx index b8ea7efeab4..96bb2416bcf 100644 --- a/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx +++ b/apps/docs/components/Navigation/NavigationMenu/NavigationMenu.tsx @@ -173,7 +173,7 @@ const SideNav = () => { {/* // Client Libs */} { allowedClientKeys={generateAllowedClientLibKeys(libCommonSections, spec_dart_v0)} /> Date: Tue, 20 Dec 2022 17:28:30 -0330 Subject: [PATCH 32/42] Exclude Edit this Page button --- apps/docs/layouts/guides/index.tsx | 30 ++++++++++++++++++------------ apps/docs/pages/index.mdx | 2 +- 2 files changed, 19 insertions(+), 13 deletions(-) diff --git a/apps/docs/layouts/guides/index.tsx b/apps/docs/layouts/guides/index.tsx index 25b26b3fec7..e3b64828a60 100644 --- a/apps/docs/layouts/guides/index.tsx +++ b/apps/docs/layouts/guides/index.tsx @@ -31,10 +31,11 @@ const Layout: FC = (props) => { const [tocList, setTocList] = useState([]) const { asPath } = useRouter() - const page = getPageType(asPath) const router = useRouter() + const EDIT_BUTTON_EXCLUDE_LIST = ['/404'] + useEffect(() => { if (hash && tocList.length > 0) { highlightSelectedTocItem(hash as string) @@ -100,18 +101,23 @@ const Layout: FC = (props) => { )}
{props.children} -
-
- - - Edit this page on GitHub - - + + {EDIT_BUTTON_EXCLUDE_LIST.includes(router.route) ? ( + <> + ) : ( + -
+ )}
{!props.hideToc && hasTableOfContents && !props.meta?.hide_table_of_contents && ( diff --git a/apps/docs/pages/index.mdx b/apps/docs/pages/index.mdx index 5a7e2f17b28..9921286f50c 100644 --- a/apps/docs/pages/index.mdx +++ b/apps/docs/pages/index.mdx @@ -30,7 +30,7 @@ export const meta = { ">
-
+

Getting Started

Discover how to set up a database to an app making queries in just a few minutes. From 728cb7b86aa4baeac47e59f81602095cac7c15d4 Mon Sep 17 00:00:00 2001 From: Long Hoang Date: Wed, 21 Dec 2022 11:48:26 +0700 Subject: [PATCH 33/42] Update link to new careers page --- apps/docs/public/humans.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/docs/public/humans.txt b/apps/docs/public/humans.txt index 95817e22f6d..a403c683c7c 100644 --- a/apps/docs/public/humans.txt +++ b/apps/docs/public/humans.txt @@ -60,7 +60,7 @@ ____________ JOBS -If you are interested in working with us check out the open roles here: https://about.supabase.com/careers +If you are interested in working with us check out the open roles here: https://supabase.com/careers ___________ From a5632d5d7317df86feefeb9fd54ec7dcd9e3e1b1 Mon Sep 17 00:00:00 2001 From: Long Hoang Date: Wed, 21 Dec 2022 11:48:43 +0700 Subject: [PATCH 34/42] Add Long to humans.txt --- apps/docs/public/humans.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/docs/public/humans.txt b/apps/docs/public/humans.txt index a403c683c7c..2231880862b 100644 --- a/apps/docs/public/humans.txt +++ b/apps/docs/public/humans.txt @@ -34,6 +34,7 @@ Kevin Grüneberg Lakshmipathi G Laura C Leo T +Long Hoang Margarita Sandomirskaia Marijana Šimag Mark Burggraf From 70aac81cec8c3c97861b64c0e5edfc676c2e7663 Mon Sep 17 00:00:00 2001 From: dannykng Date: Tue, 20 Dec 2022 16:55:35 -0800 Subject: [PATCH 35/42] Update Developers links --- apps/www/data/Developers.json | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/apps/www/data/Developers.json b/apps/www/data/Developers.json index a29460f0c5b..df449e784ab 100644 --- a/apps/www/data/Developers.json +++ b/apps/www/data/Developers.json @@ -1,26 +1,20 @@ [ { "text": "Documentation", - "description": "Everything you need that makes it simple to get started.", + "description": "Everything you need to start building with Supabase.", "url": "/docs", "icon": "M12 6.253v13m0-13C10.832 5.477 9.246 5 7.5 5S4.168 5.477 3 6.253v13C4.168 18.477 5.754 18 7.5 18s3.332.477 4.5 1.253m0-13C13.168 5.477 14.754 5 16.5 5c1.747 0 3.332.477 4.5 1.253v13C19.832 18.477 18.247 18 16.5 18c-1.746 0-3.332.477-4.5 1.253" }, { - "text": "API reference", - "description": "Examples and references for using API libraries.", - "url": "/docs/client/supabase-client", - "icon": "M10 20l4-16m4 4l4 4-4 4M6 16l-4-4 4-4" - }, - { - "text": "Guides", - "description": "Examples and references for using Supabase.", - "url": "/docs/", + "text": "Changelog", + "description": "See the latest updates and product improvements.", + "url": "/changelog", "icon": "M19.428 15.428a2 2 0 00-1.022-.547l-2.387-.477a6 6 0 00-3.86.517l-.318.158a6 6 0 01-3.86.517L6.05 15.21a2 2 0 00-1.806.547M8 4h8l-1 1v5.172a2 2 0 00.586 1.414l5 5c1.26 1.26.367 3.414-1.415 3.414H4.828c-1.782 0-2.674-2.154-1.414-3.414l5-5A2 2 0 009 10.172V5L8 4z" }, { "text": "Careers", "description": "Join the Supabase team and get involved.", - "url": "https://supabase.com/careers", + "url": "/careers", "icon": "M21 13.255A23.931 23.931 0 0112 15c-3.183 0-6.22-.62-9-1.745M16 6V4a2 2 0 00-2-2h-4a2 2 0 00-2 2v2m4 6h.01M5 20h14a2 2 0 002-2V8a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z" } ] From 70b66c916f177090976f9b0677c64ea02b60ba15 Mon Sep 17 00:00:00 2001 From: dng Date: Tue, 20 Dec 2022 16:29:56 -0800 Subject: [PATCH 36/42] Remove redirect --- apps/www/lib/redirects.js | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/apps/www/lib/redirects.js b/apps/www/lib/redirects.js index 09317c8a31f..ff18f604298 100644 --- a/apps/www/lib/redirects.js +++ b/apps/www/lib/redirects.js @@ -1787,16 +1787,6 @@ module.exports = [ source: '/docs/guides/with-ionic-angular', destination: '/docs/guides/getting-started/tutorials/with-ionic-angular', }, - { - permanent: true, - source: '/docs/guides/examples', - destination: '/docs/guides/resources/examples', - }, - { - permanent: true, - source: '/docs/guides/examples', - destination: '/docs/guides/resources/examples', - }, { permanent: true, source: '/docs/guides/hosting/overview', From 32a3ec5f7e856e7dd190debdd348af975c00dfc3 Mon Sep 17 00:00:00 2001 From: Ollie Halliday <22655069+Hallidayo@users.noreply.github.com> Date: Tue, 20 Dec 2022 16:39:27 +0000 Subject: [PATCH 37/42] added on delete cascade --- studio/components/interfaces/SQLEditor/SQLEditor.constants.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/studio/components/interfaces/SQLEditor/SQLEditor.constants.ts b/studio/components/interfaces/SQLEditor/SQLEditor.constants.ts index 63ec9612ed2..de1990ca96b 100644 --- a/studio/components/interfaces/SQLEditor/SQLEditor.constants.ts +++ b/studio/components/interfaces/SQLEditor/SQLEditor.constants.ts @@ -812,7 +812,7 @@ create publication supabase_realtime sql: ` -- Create a table for public profiles create table profiles ( - id uuid references auth.users not null primary key, + id uuid references auth.users on delete cascade not null primary key, updated_at timestamp with time zone, username text unique, full_name text, From eb6cffb09aa7e3ba1dc3ebcb112a2300644cd48f Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 21 Dec 2022 16:35:42 +0700 Subject: [PATCH 38/42] Hide schemas with pg_ prefix --- studio/stores/pgmeta/MetaStore.ts | 1 - studio/stores/pgmeta/SchemaStore.ts | 8 ++++++++ studio/stores/pgmeta/TriggersStore.ts | 4 ++-- 3 files changed, 10 insertions(+), 3 deletions(-) diff --git a/studio/stores/pgmeta/MetaStore.ts b/studio/stores/pgmeta/MetaStore.ts index a8ed860a318..eef317f1397 100644 --- a/studio/stores/pgmeta/MetaStore.ts +++ b/studio/stores/pgmeta/MetaStore.ts @@ -154,7 +154,6 @@ export default class MetaStore implements IMetaStore { 'extensions', 'information_schema', 'net', - 'pg_catalog', 'pgsodium', 'pgsodium_masks', 'pgbouncer', diff --git a/studio/stores/pgmeta/SchemaStore.ts b/studio/stores/pgmeta/SchemaStore.ts index 7a0082db225..1fd1ff89ac5 100644 --- a/studio/stores/pgmeta/SchemaStore.ts +++ b/studio/stores/pgmeta/SchemaStore.ts @@ -27,6 +27,14 @@ export default class SchemaStore extends PostgresMetaInterface { this.views = [] } + // Dashboard to hide schemas with pg_ prefix + list(filter: any) { + const schemasFilter = (schema: PostgresSchema) => + !schema.name.startsWith('pg_') && (typeof filter === 'function' ? filter(schema) : true) + + return super.list(schemasFilter) + } + async loadViews(schema: string) { const query = ` select schemaname as schema, viewname as name from pg_catalog.pg_views diff --git a/studio/stores/pgmeta/TriggersStore.ts b/studio/stores/pgmeta/TriggersStore.ts index 4d83608c885..91631e94f7f 100644 --- a/studio/stores/pgmeta/TriggersStore.ts +++ b/studio/stores/pgmeta/TriggersStore.ts @@ -20,8 +20,8 @@ export default class TriggersStore extends PostgresMetaInterface { */ list(filter: any) { const triggersFilter = (h: any) => - h.function_schema != 'supabase_functions' && - h.schema != 'net' && + h.function_schema !== 'supabase_functions' && + h.schema !== 'net' && (typeof filter === 'function' ? filter(h) : true) return super.list(triggersFilter) From d8bc12f9d6edb67f798c04fdc97db402a4229401 Mon Sep 17 00:00:00 2001 From: Alaister Young Date: Wed, 21 Dec 2022 11:50:14 +0000 Subject: [PATCH 39/42] fix: pro upgrade show captcha before confirm modal --- .../PaymentSummaryPanel.tsx | 16 ++++++++----- .../interfaces/Billing/ProUpgrade.tsx | 24 +++++++++++-------- 2 files changed, 24 insertions(+), 16 deletions(-) diff --git a/studio/components/interfaces/Billing/PaymentSummaryPanel/PaymentSummaryPanel.tsx b/studio/components/interfaces/Billing/PaymentSummaryPanel/PaymentSummaryPanel.tsx index 9e0db842011..8f648296f80 100644 --- a/studio/components/interfaces/Billing/PaymentSummaryPanel/PaymentSummaryPanel.tsx +++ b/studio/components/interfaces/Billing/PaymentSummaryPanel/PaymentSummaryPanel.tsx @@ -42,6 +42,7 @@ interface Props { isLoadingPaymentMethods: boolean onSelectPaymentMethod: (method: any) => void onSelectAddNewPaymentMethod: () => void + beforeConfirmPayment?: () => Promise onConfirmPayment: () => void isSubmitting: boolean @@ -72,6 +73,7 @@ const PaymentSummaryPanel: FC = ({ isLoadingPaymentMethods, onSelectPaymentMethod, onSelectAddNewPaymentMethod, + beforeConfirmPayment, onConfirmPayment, isSubmitting, captcha, @@ -132,6 +134,8 @@ const PaymentSummaryPanel: FC = ({ message: error, }) } else { + await beforeConfirmPayment?.() + isChangingComputeSize || (isChangingPITRDuration && selectedPITRDays === 0) ? setShowConfirmModal(true) : onConfirmPayment() @@ -141,7 +145,7 @@ const PaymentSummaryPanel: FC = ({ return ( <>

Payment Summary

@@ -296,7 +300,7 @@ const PaymentSummaryPanel: FC = ({
)} -
+
= ({

Select payment method

{isLoadingPaymentMethods ? ( -
+

Retrieving payment methods

) : paymentMethods.length === 0 ? ( -
+

No saved payment methods

@@ -373,11 +377,11 @@ const PaymentSummaryPanel: FC = ({ ) })}
-

+

Add new payment method

diff --git a/studio/components/interfaces/Billing/ProUpgrade.tsx b/studio/components/interfaces/Billing/ProUpgrade.tsx index e7f246d8b27..9f3ef740bcc 100644 --- a/studio/components/interfaces/Billing/ProUpgrade.tsx +++ b/studio/components/interfaces/Billing/ProUpgrade.tsx @@ -158,7 +158,7 @@ const ProUpgrade: FC = ({ captchaRef.current?.resetCaptcha() } - const onConfirmPayment = async () => { + const beforeConfirmPayment = async () => { setIsSubmitting(true) let token = captchaToken @@ -166,12 +166,15 @@ const ProUpgrade: FC = ({ if (!token) { const captchaResponse = await captchaRef.current?.execute({ async: true }) token = captchaResponse?.response ?? null + setCaptchaToken(token) } } catch (error) { setIsSubmitting(false) return } + } + const onConfirmPayment = async () => { const payload = formSubscriptionUpdatePayload( currentSubscription, selectedTier, @@ -179,7 +182,7 @@ const ProUpgrade: FC = ({ nonChangeableAddons, selectedPaymentMethodId, projectRegion, - token ?? undefined + captchaToken ?? undefined ) const res = await patch(`${API_URL}/projects/${projectRef}/subscription`, payload) resetCaptcha() @@ -223,17 +226,17 @@ const ProUpgrade: FC = ({ enter="transition ease-out duration-300" enterFrom="transform opacity-0 translate-x-10" enterTo="transform opacity-100 translate-x-0" - className="flex w-full items-start justify-between" + className="flex items-start justify-between w-full" >
-
+
onSelectBack()} />

Change your project's subscription

@@ -257,14 +260,14 @@ const ProUpgrade: FC = ({ )}
-
+

Enable spend cap

setShowSpendCapHelperModal(true)} />
@@ -342,6 +345,7 @@ const ProUpgrade: FC = ({ onSelectAddNewPaymentMethod={() => { setShowAddPaymentMethodModal(true) }} + beforeConfirmPayment={beforeConfirmPayment} onConfirmPayment={onConfirmPayment} isSubmitting={isSubmitting} captcha={ @@ -375,7 +379,7 @@ const ProUpgrade: FC = ({ header="Enabling spend cap" onCancel={() => setShowSpendCapHelperModal(false)} > -
+

@@ -390,7 +394,7 @@ const ProUpgrade: FC = ({

{/* Maybe instead of a table, show something more interactive like a spend cap playground */} {/* Maybe ideate this in Figma first but this is good enough for now */} -
+

Item

Limit

@@ -417,7 +421,7 @@ const ProUpgrade: FC = ({ From 4b765fd1250379ac1cf47def855b8293fe4633af Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 21 Dec 2022 21:30:18 +0700 Subject: [PATCH 40/42] Fix hcaptcha for upgrading instance --- .../interfaces/Billing/EnterpriseUpdate.tsx | 19 +++++++++++++++++++ .../PaymentSummaryPanel.tsx | 17 +++++++++++------ .../interfaces/Billing/ProUpgrade.tsx | 6 ++++-- 3 files changed, 34 insertions(+), 8 deletions(-) diff --git a/studio/components/interfaces/Billing/EnterpriseUpdate.tsx b/studio/components/interfaces/Billing/EnterpriseUpdate.tsx index 078cd190f42..697060a6340 100644 --- a/studio/components/interfaces/Billing/EnterpriseUpdate.tsx +++ b/studio/components/interfaces/Billing/EnterpriseUpdate.tsx @@ -137,6 +137,24 @@ const EnterpriseUpdate: FC = ({ captchaRef.current?.resetCaptcha() } + const beforeConfirmPayment = async (): Promise => { + setIsSubmitting(true) + let token = captchaToken + + try { + if (!token) { + const captchaResponse = await captchaRef.current?.execute({ async: true }) + token = captchaResponse?.response ?? null + setCaptchaToken(token) + } + } catch (error) { + setIsSubmitting(false) + return false + } + + return true + } + // Last todo to support enterprise billing on dashboard + E2E test const onConfirmPayment = async () => { setIsSubmitting(true) @@ -296,6 +314,7 @@ const EnterpriseUpdate: FC = ({ onSelectAddNewPaymentMethod={() => { setShowAddPaymentMethodModal(true) }} + beforeConfirmPayment={beforeConfirmPayment} onConfirmPayment={onConfirmPayment} captcha={ void onSelectAddNewPaymentMethod: () => void - beforeConfirmPayment?: () => Promise + beforeConfirmPayment: () => Promise onConfirmPayment: () => void isSubmitting: boolean @@ -134,11 +134,16 @@ const PaymentSummaryPanel: FC = ({ message: error, }) } else { - await beforeConfirmPayment?.() - - isChangingComputeSize || (isChangingPITRDuration && selectedPITRDays === 0) - ? setShowConfirmModal(true) - : onConfirmPayment() + // [Joshen] We're validating captcha before subsequent actions as there's an issue + // with the hcaptcha overlay and the modal component, such that clicking on the hcaptcha closes + // both the hcaptcha overlay and the modal. Need to figure that out first, then we can validate + // the hcaptcha within onConfirmPayment() + const hasValidCaptcha = await beforeConfirmPayment() + if (hasValidCaptcha) { + isChangingComputeSize || (isChangingPITRDuration && selectedPITRDays === 0) + ? setShowConfirmModal(true) + : onConfirmPayment() + } } } diff --git a/studio/components/interfaces/Billing/ProUpgrade.tsx b/studio/components/interfaces/Billing/ProUpgrade.tsx index 9f3ef740bcc..58434629eb8 100644 --- a/studio/components/interfaces/Billing/ProUpgrade.tsx +++ b/studio/components/interfaces/Billing/ProUpgrade.tsx @@ -158,7 +158,7 @@ const ProUpgrade: FC = ({ captchaRef.current?.resetCaptcha() } - const beforeConfirmPayment = async () => { + const beforeConfirmPayment = async (): Promise => { setIsSubmitting(true) let token = captchaToken @@ -170,8 +170,10 @@ const ProUpgrade: FC = ({ } } catch (error) { setIsSubmitting(false) - return + return false } + + return true } const onConfirmPayment = async () => { From 4b391ae42691348c344781da96902815f5da631f Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 21 Dec 2022 21:44:01 +0700 Subject: [PATCH 41/42] Fix submitting state --- studio/components/interfaces/Billing/EnterpriseUpdate.tsx | 1 + studio/components/interfaces/Billing/ProUpgrade.tsx | 1 + 2 files changed, 2 insertions(+) diff --git a/studio/components/interfaces/Billing/EnterpriseUpdate.tsx b/studio/components/interfaces/Billing/EnterpriseUpdate.tsx index 697060a6340..254d83275c7 100644 --- a/studio/components/interfaces/Billing/EnterpriseUpdate.tsx +++ b/studio/components/interfaces/Billing/EnterpriseUpdate.tsx @@ -152,6 +152,7 @@ const EnterpriseUpdate: FC = ({ return false } + setIsSubmitting(false) return true } diff --git a/studio/components/interfaces/Billing/ProUpgrade.tsx b/studio/components/interfaces/Billing/ProUpgrade.tsx index 58434629eb8..2f05f9a8844 100644 --- a/studio/components/interfaces/Billing/ProUpgrade.tsx +++ b/studio/components/interfaces/Billing/ProUpgrade.tsx @@ -173,6 +173,7 @@ const ProUpgrade: FC = ({ return false } + setIsSubmitting(false) return true } From b40e6216aba6ec03574318b23a45b7648333e529 Mon Sep 17 00:00:00 2001 From: Joshen Lim Date: Wed, 21 Dec 2022 16:49:34 +0700 Subject: [PATCH 42/42] Fix storage side bar text overflow --- studio/components/layouts/StorageLayout/StorageMenu.tsx | 6 ++++-- .../Storage/StorageExplorer/FileExplorerHeader.js | 3 ++- studio/components/ui/ProductMenu/ProductMenuItem.tsx | 8 ++++---- 3 files changed, 10 insertions(+), 7 deletions(-) diff --git a/studio/components/layouts/StorageLayout/StorageMenu.tsx b/studio/components/layouts/StorageLayout/StorageMenu.tsx index dc41cdacb64..2889292f42f 100644 --- a/studio/components/layouts/StorageLayout/StorageMenu.tsx +++ b/studio/components/layouts/StorageLayout/StorageMenu.tsx @@ -122,8 +122,10 @@ const BucketRow = ({ -

{bucket.name}

+
+

+ {bucket.name} +

{bucket.public && Public}
} diff --git a/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerHeader.js b/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerHeader.js index 139a0be29cb..ad57ec629c1 100644 --- a/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerHeader.js +++ b/studio/components/to-be-cleaned/Storage/StorageExplorer/FileExplorerHeader.js @@ -34,7 +34,7 @@ const HeaderPathEdit = ({ loading, breadcrumbs, togglePathEdit }) => {
) : (
-

{breadcrumbs[breadcrumbs.length - 1] || ''}

+

{breadcrumbs[breadcrumbs.length - 1] || ''}