feat(frontend): PR preview banner (#11412)

## Changes 🏗️

<img width="900" height="757" alt="Screenshot 2025-11-19 at 12 18 38"
src="https://github.com/user-attachments/assets/e2c2a4cf-a05e-431e-853d-fb0a68729e54"
/>

When the dev environment is used for a PR preview, show a banner at the
top of the page to indicate this.

### Checklist 📋

#### For code changes:
- [x] I have clearly listed my changes in the PR description
- [x] I have made a test plan
- [x] I have tested my changes according to the test plan:
  - [x] Create a PR preview against Dev
  - [x] Check it shows the banner once this is merged
  - [x] Or try locally with the env var set

### For configuration changes:

`NEXT_PUBLIC_PREVIEW_STEALING_DEV` is set programmatically via our Infra
CI.
This commit is contained in:
Ubbe
2025-11-20 23:08:32 +07:00
committed by GitHub
parent 06d20e7e4c
commit 80e573f33b
5 changed files with 67 additions and 22 deletions

View File

@@ -1,22 +1,32 @@
NEXT_PUBLIC_SUPABASE_URL=http://localhost:8000
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE
# Supabase
NEXT_PUBLIC_SUPABASE_URL=http://localhost:8000
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE
NEXT_PUBLIC_AGPT_SERVER_URL=http://localhost:8006/api
NEXT_PUBLIC_AGPT_WS_SERVER_URL=ws://localhost:8001/ws
NEXT_PUBLIC_FRONTEND_BASE_URL=http://localhost:3000
# Back-end services
NEXT_PUBLIC_AGPT_SERVER_URL=http://localhost:8006/api
NEXT_PUBLIC_AGPT_WS_SERVER_URL=ws://localhost:8001/ws
NEXT_PUBLIC_FRONTEND_BASE_URL=http://localhost:3000
NEXT_PUBLIC_APP_ENV=local
NEXT_PUBLIC_BEHAVE_AS=LOCAL
# Env config
NEXT_PUBLIC_APP_ENV=local
NEXT_PUBLIC_BEHAVE_AS=LOCAL
NEXT_PUBLIC_LAUNCHDARKLY_ENABLED=false
NEXT_PUBLIC_LAUNCHDARKLY_CLIENT_ID=687ab1372f497809b131e06e
# Feature flags
NEXT_PUBLIC_LAUNCHDARKLY_ENABLED=false
NEXT_PUBLIC_LAUNCHDARKLY_CLIENT_ID=687ab1372f497809b131e06e
NEXT_PUBLIC_REACT_QUERY_DEVTOOL=true
NEXT_PUBLIC_GA_MEASUREMENT_ID=G-FH2XK2W4GN
# Debugging
NEXT_PUBLIC_REACT_QUERY_DEVTOOL=true
NEXT_PUBLIC_GA_MEASUREMENT_ID=G-FH2XK2W4GN
# Google Drive Picker
NEXT_PUBLIC_GOOGLE_CLIENT_ID=
NEXT_PUBLIC_GOOGLE_API_KEY=
NEXT_PUBLIC_GOOGLE_APP_ID=
# Cloudflare CAPTCHA
NEXT_PUBLIC_CLOUDFLARE_TURNSTILE_SITE_KEY=
NEXT_PUBLIC_TURNSTILE=disabled
# PR previews
NEXT_PUBLIC_PREVIEW_STEALING_DEV=

View File

@@ -8,9 +8,7 @@ import dotenv from "dotenv";
import path from "path";
dotenv.config({ path: path.resolve(__dirname, ".env") });
dotenv.config({ path: path.resolve(__dirname, "../backend/.env") });
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: "./src/tests",
/* Global setup file that runs before all tests */
@@ -62,7 +60,7 @@ export default defineConfig({
/* Maximum time one test can run for */
timeout: 25000,
/* Configure web server to start automatically */
/* Configure web server to start automatically (local dev only) */
webServer: {
command: "pnpm start",
url: "http://localhost:3000",

View File

@@ -6,12 +6,14 @@ import "./globals.css";
import { Providers } from "@/app/providers";
import { CookieConsentBanner } from "@/components/molecules/CookieConsentBanner/CookieConsentBanner";
import { PreviewBanner } from "@/components/molecules/PreviewBanner/PreviewBanner";
import TallyPopupSimple from "@/components/molecules/TallyPoup/TallyPopup";
import { Toaster } from "@/components/molecules/Toast/toaster";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { headers } from "next/headers";
import { SetupAnalytics } from "@/services/analytics";
import { VercelAnalyticsWrapper } from "@/services/analytics/VercelAnalyticsWrapper";
import { environment } from "@/services/environment";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import { headers } from "next/headers";
export const metadata: Metadata = {
title: "AutoGPT Platform",
@@ -25,6 +27,7 @@ export default async function RootLayout({
}>) {
const headersList = await headers();
const host = headersList.get("host") || "";
const previewStealingDev = environment.getPreviewStealingDev();
return (
<html
@@ -49,6 +52,9 @@ export default async function RootLayout({
disableTransitionOnChange
>
<div className="flex min-h-screen flex-col items-stretch justify-items-stretch">
{previewStealingDev && environment.isDev() ? (
<PreviewBanner branchName={previewStealingDev} />
) : null}
{children}
<TallyPopupSimple />
<VercelAnalyticsWrapper />

View File

@@ -0,0 +1,16 @@
type Props = {
branchName: string;
};
export function PreviewBanner({ branchName }: Props) {
if (!branchName) {
return null;
}
return (
<div className="sticky top-0 z-50 w-full bg-green-500 px-4 py-2 text-center text-sm font-medium text-white">
This is a Preview build for the branch:{" "}
<span className="font-mono font-semibold">{branchName}</span>
</div>
);
}

View File

@@ -60,14 +60,26 @@ function getEnvironmentStr() {
return `app:${getAppEnv().toLowerCase()}-behave:${getBehaveAs().toLowerCase()}`;
}
function isProd() {
function getPreviewStealingDev() {
return process.env.NEXT_PUBLIC_PREVIEW_STEALING_DEV || "";
}
function isProductionBuild() {
return process.env.NODE_ENV === "production";
}
function isDev() {
function isDevelopmentBuild() {
return process.env.NODE_ENV === "development";
}
function isDev() {
return isCloud() && getAppEnv() === AppEnv.DEV;
}
function isProd() {
return isCloud() && getAppEnv() === AppEnv.PROD;
}
function isCloud() {
return getBehaveAs() === BehaveAs.CLOUD;
}
@@ -103,11 +115,14 @@ export const environment = {
getAGPTWsServerUrl,
getSupabaseUrl,
getSupabaseAnonKey,
getPreviewStealingDev,
// Assertions
isServerSide,
isClientSide,
isProd,
isProductionBuild,
isDevelopmentBuild,
isDev,
isProd,
isCloud,
isLocal,
isVercelPreview,