Compare commits

..

19 Commits

Author SHA1 Message Date
Nicholas Tindle
d89b84ba2b remove logs 2025-10-18 03:09:26 -05:00
Nicholas Tindle
4619b07945 try this 2025-10-18 02:53:34 -05:00
Nicholas Tindle
d43535e491 Update route.ts 2025-10-18 02:39:28 -05:00
Nicholas Tindle
a35914889a feat: turn off captcha 2025-10-18 02:36:26 -05:00
Nicholas Tindle
7c248f2d6e test: discable turnstile 2025-10-18 02:22:53 -05:00
Nicholas Tindle
d4a7ce3846 feat: aggressive logging 2025-10-18 02:16:46 -05:00
Nicholas Tindle
605a198c09 feat: add log? 2025-10-18 01:44:21 -05:00
Nicholas Tindle
a3389485a7 Merge branch 'hotfix/waitlist-error-display' of https://github.com/Significant-Gravitas/AutoGPT into hotfix/waitlist-error-display 2025-10-17 23:46:48 -05:00
Nicholas Tindle
cd439e912a fix: same thing 2025-10-17 23:37:56 -05:00
Nicholas Tindle
7b32290582 Merge branch 'dev' into hotfix/waitlist-error-display 2025-10-17 23:35:02 -05:00
Nicholas Tindle
e3137382c3 feat: add error code check 2025-10-17 23:33:31 -05:00
Nicholas Tindle
097a19141d fix(frontend): improve waitlist error display for users not on allowlist (#11196)
## Summary

This PR improves the user experience for users who are not on the
waitlist during sign-up. When a user attempts to sign up or log in with
an email that's not on the allowlist, they now see a clear, helpful
modal with a direct call-to-action to join the waitlist.

Fixes
[OPEN-2794](https://linear.app/autogpt/issue/OPEN-2794/display-waitlist-error-for-users-not-on-waitlist-during-sign-up)

## Changes

-  Updated `EmailNotAllowedModal` with improved messaging and a "Join
Waitlist" button
- 🔧 Fixed OAuth provider signup/login to properly display the waitlist
modal
- 📝 Enhanced auth-code-error page to detect and display
waitlist-specific errors
- 💬 Added helpful guidance about checking email address and Discord
support link
- 🎯 Consistent waitlist error handling across all auth flows (regular
signup, OAuth, error pages)

## Test Plan

Tested locally by:
1. Attempting signup with non-allowlisted email - modal appears 
2. Attempting Google SSO with non-allowlisted account - modal appears 
3. Modal shows "Join Waitlist" button that opens
https://agpt.co/waitlist 
4. Help text about checking email and Discord support is visible 

## Screenshots

The new waitlist modal includes:
- Clear "Join the Waitlist" title
- Explanation that platform is in closed beta
- "Join Waitlist" button (opens in new tab)
- Help text about checking email address
- Discord support link for users who need help

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Reinier van der Leer <pwuts@agpt.co>
2025-10-18 03:37:31 +00:00
Nicholas Tindle
65f2c04ef1 fix: lint 2025-10-17 14:02:23 -05:00
Nicholas Tindle
865abdb9e0 fix(frontend): correct waitlist error detection in auth-code-error page
- Removed incorrect 403 error code check (Supabase doesn't send HTTP codes in OAuth redirects)
- Added isWaitlistErrorFromParams() utility for OAuth callback errors
- Now properly detects P0001 and waitlist errors from error_description parameter
- Consistent error detection across all auth flows

The auth-code-error page receives errors via URL hash parameters from
Supabase OAuth redirects, not HTTP status codes. This fix ensures we
check the error description content rather than expecting a 403 code
that would never be sent.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-17 13:46:20 -05:00
Nicholas Tindle
b59b200bd6 formatting 2025-10-17 13:40:49 -05:00
Nicholas Tindle
e7fb4cce5a fix: resolve merge conflicts 2025-10-17 13:23:58 -05:00
Nicholas Tindle
85e2aef6ad refactor(frontend): improve waitlist error detection with centralized utilities
- Created utility functions for robust waitlist error detection
- Added multiple fallback checks: P0001 error code, message text, and table reference
- Centralized logic in utils.ts to avoid duplication
- Added privacy-conscious logging that sanitizes email addresses
- More resilient detection that handles various Supabase error formats

The error detection now checks for:
1. PostgreSQL P0001 error code (primary indicator)
2. "not allowed to register" message from trigger
3. Reference to "allowed_users" table

This makes the waitlist check more reliable even if Supabase changes
how it formats PostgreSQL trigger errors.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-17 13:20:26 -05:00
Nicholas Tindle
85a8fb598e Apply suggestion from @Pwuts
Co-authored-by: Reinier van der Leer <pwuts@agpt.co>
2025-10-17 13:06:35 -05:00
Nicholas Tindle
ae20da8aaa fix(frontend): improve waitlist error display for users not on allowlist
- Updated EmailNotAllowedModal with clear waitlist CTA and helpful messaging
- Added "Join Waitlist" button that opens https://agpt.co/waitlist
- Fixed OAuth provider signup/login to properly display waitlist modal
- Enhanced auth-code-error page to detect and display waitlist errors
- Added helpful guidance about checking email and Discord support link
- Consistent waitlist error handling across all auth flows

Fixes OPEN-2794

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-17 12:37:03 -05:00
9 changed files with 203 additions and 25 deletions

View File

@@ -2,11 +2,18 @@
import { isServerSide } from "@/lib/utils/is-server-side";
import { useEffect, useState } from "react";
import { Button } from "@/components/atoms/Button/Button";
import { Text } from "@/components/atoms/Text/Text";
import { Card } from "@/components/atoms/Card/Card";
import { WaitlistErrorContent } from "@/components/auth/WaitlistErrorContent";
import { isWaitlistError } from "@/app/api/auth/utils";
import { useRouter } from "next/navigation";
export default function AuthErrorPage() {
const [errorType, setErrorType] = useState<string | null>(null);
const [errorCode, setErrorCode] = useState<string | null>(null);
const [errorDescription, setErrorDescription] = useState<string | null>(null);
const router = useRouter();
useEffect(() => {
// This code only runs on the client side
@@ -23,15 +30,57 @@ export default function AuthErrorPage() {
}, []);
if (!errorType && !errorCode && !errorDescription) {
return <div>Loading...</div>;
return (
<div className="flex h-screen items-center justify-center">
<Text variant="body">Loading...</Text>
</div>
);
}
// Check if this is a waitlist/not allowed error
const isWaitlistErr = isWaitlistError(errorCode, errorDescription);
if (isWaitlistErr) {
return (
<div className="flex h-screen items-center justify-center">
<Card className="w-full max-w-md p-8">
<WaitlistErrorContent
onClose={() => router.push("/login")}
closeButtonText="Back to Login"
/>
</Card>
</div>
);
}
// Default error display for other types of errors
return (
<div>
<h1>Authentication Error</h1>
{errorType && <p>Error Type: {errorType}</p>}
{errorCode && <p>Error Code: {errorCode}</p>}
{errorDescription && <p>Error Description: {errorDescription}</p>}
<div className="flex h-screen items-center justify-center">
<Card className="w-full max-w-md p-8">
<div className="flex flex-col items-center gap-6">
<Text variant="h3">Authentication Error</Text>
<div className="flex flex-col gap-2 text-center">
{errorType && (
<Text variant="body">
<strong>Error Type:</strong> {errorType}
</Text>
)}
{errorCode && (
<Text variant="body">
<strong>Error Code:</strong> {errorCode}
</Text>
)}
{errorDescription && (
<Text variant="body">
<strong>Description:</strong> {errorDescription}
</Text>
)}
</div>
<Button variant="primary" onClick={() => router.push("/login")}>
Back to Login
</Button>
</div>
</Card>
</div>
);
}

View File

@@ -20,6 +20,11 @@ export async function GET(request: Request) {
const { error } = await supabase.auth.exchangeCodeForSession(code);
// Keep minimal error logging for OAuth debugging if needed
if (error) {
console.error("OAuth code exchange failed:", error.message);
}
if (!error) {
try {
const api = new BackendAPI();

View File

@@ -67,7 +67,7 @@ export function useLoginPage() {
if (!response.ok) {
const { error } = await response.json();
if (typeof error === "string" && error.includes("not_allowed")) {
if (error === "not_allowed") {
setShowNotAllowedModal(true);
} else {
setFeedback(error || "Failed to start OAuth flow");

View File

@@ -59,6 +59,7 @@ export function useSignupPage() {
resetCaptcha();
return;
}
try {
const response = await fetch("/api/auth/provider", {
method: "POST",
@@ -70,6 +71,13 @@ export function useSignupPage() {
const { error } = await response.json();
setIsGoogleLoading(false);
resetCaptcha();
// Check for waitlist error
if (error === "not_allowed") {
setShowNotAllowedModal(true);
return;
}
toast({
title: error || "Failed to start OAuth flow",
variant: "destructive",
@@ -142,6 +150,7 @@ export function useSignupPage() {
setShowNotAllowedModal(true);
return;
}
toast({
title: result?.error || "Signup failed",
variant: "destructive",

View File

@@ -1,6 +1,7 @@
import { getServerSupabase } from "@/lib/supabase/server/getServerSupabase";
import { NextResponse } from "next/server";
import { LoginProvider } from "@/types/auth";
import { isWaitlistError, logWaitlistError } from "../utils";
export async function POST(request: Request) {
try {
@@ -31,8 +32,9 @@ export async function POST(request: Request) {
});
if (error) {
// FIXME: supabase doesn't return the correct error message for this case
if (error.message.includes("P0001")) {
// Check for waitlist/allowlist error
if (isWaitlistError(error?.code, error?.message)) {
logWaitlistError("OAuth Provider", error.message);
return NextResponse.json({ error: "not_allowed" }, { status: 403 });
}

View File

@@ -4,6 +4,7 @@ import { getServerSupabase } from "@/lib/supabase/server/getServerSupabase";
import { verifyTurnstileToken } from "@/lib/turnstile";
import { signupFormSchema } from "@/types/auth";
import { shouldShowOnboarding } from "../../helpers";
import { isWaitlistError, logWaitlistError } from "../utils";
export async function POST(request: Request) {
try {
@@ -29,6 +30,7 @@ export async function POST(request: Request) {
turnstileToken ?? "",
"signup",
);
if (!captchaOk) {
return NextResponse.json(
{ error: "CAPTCHA verification failed. Please try again." },
@@ -47,16 +49,19 @@ export async function POST(request: Request) {
const { data, error } = await supabase.auth.signUp(parsed.data);
if (error) {
// FIXME: supabase doesn't return the correct error message for this case
if (error.message.includes("P0001")) {
// Check for waitlist/allowlist error
if (isWaitlistError(error?.code, error?.message)) {
logWaitlistError("Signup", error.message);
return NextResponse.json({ error: "not_allowed" }, { status: 403 });
}
if ((error as any).code === "user_already_exists") {
return NextResponse.json(
{ error: "user_already_exists" },
{ status: 409 },
);
}
return NextResponse.json({ error: error.message }, { status: 400 });
}

View File

@@ -0,0 +1,60 @@
/**
* Checks if an error is related to the waitlist/allowlist
*
* Can be used with either:
* - Error objects from Supabase auth operations: `isWaitlistError(error?.code, error?.message)`
* - URL parameters from OAuth callbacks: `isWaitlistError(errorCode, errorDescription)`
*
* The PostgreSQL trigger raises P0001 with message format:
* "The email address "email" is not allowed to register. Please contact support for assistance."
*
* @param code - Error code (e.g., "P0001", "unexpected_failure") or null
* @param message - Error message/description or null
* @returns true if this appears to be a waitlist/allowlist error
*/
export function isWaitlistError(
code?: string | null,
message?: string | null,
): boolean {
// Check for explicit PostgreSQL trigger error code
if (code === "P0001") return true;
if (!message) return false;
const lowerMessage = message.toLowerCase();
// Check for the generic database error that occurs during waitlist check
// This happens when Supabase wraps the PostgreSQL trigger error
if (
code === "unexpected_failure" &&
message === "Database error saving new user"
) {
return true;
}
// Check for various waitlist-related patterns in the message
return (
lowerMessage.includes("p0001") || // PostgreSQL error code in message
lowerMessage.includes("not allowed") || // Common waitlist message
lowerMessage.includes("waitlist") || // Explicit waitlist mention
lowerMessage.includes("allowlist") || // Explicit allowlist mention
lowerMessage.includes("allowed_users") || // Database table reference
lowerMessage.includes("not allowed to register") // Full trigger message
);
}
/**
* Logs a waitlist error for debugging purposes
* Does not expose user email in logs for privacy
*
* @param context - Where the error occurred (e.g., "Signup", "OAuth Provider")
* @param errorMessage - The full error message
*/
export function logWaitlistError(context: string, errorMessage: string): void {
// Only log the error code and general message, not the email
const sanitizedMessage = errorMessage.replace(
/"[^"]+@[^"]+"/g, // Matches email addresses in quotes
'"[email]"',
);
console.log(`[${context}] Waitlist check failed:`, sanitizedMessage);
}

View File

@@ -1,6 +1,5 @@
import { Button } from "../atoms/Button/Button";
import { Text } from "../atoms/Text/Text";
import { Dialog } from "../molecules/Dialog/Dialog";
import { WaitlistErrorContent } from "./WaitlistErrorContent";
interface Props {
isOpen: boolean;
@@ -14,18 +13,8 @@ export function EmailNotAllowedModal({ isOpen, onClose }: Props) {
styling={{ maxWidth: "35rem" }}
>
<Dialog.Content>
<div className="flex flex-col items-center gap-8 py-4">
<Text variant="h3">Access Restricted</Text>
<Text variant="large-medium" className="text-center">
We&apos;re currently in a limited access phase. Your email address
isn&apos;t on our current allowlist for early access. If you believe
this is an error or would like to request access, please contact us.
</Text>
<div className="flex justify-end pt-4">
<Button variant="primary" onClick={onClose}>
I understand
</Button>
</div>
<div className="py-4">
<WaitlistErrorContent onClose={onClose} />
</div>
</Dialog.Content>
</Dialog>

View File

@@ -0,0 +1,59 @@
import { Button } from "../atoms/Button/Button";
import { Text } from "../atoms/Text/Text";
interface WaitlistErrorContentProps {
onClose: () => void;
closeButtonText?: string;
closeButtonVariant?: "primary" | "secondary";
}
export function WaitlistErrorContent({
onClose,
closeButtonText = "Close",
closeButtonVariant = "primary",
}: WaitlistErrorContentProps) {
return (
<div className="flex flex-col items-center gap-6">
<Text variant="h3">Join the Waitlist</Text>
<div className="flex flex-col gap-4 text-center">
<Text variant="large-medium" className="text-center">
The AutoGPT Platform is currently in closed beta. Your email address
isn&apos;t on our current allowlist for early access.
</Text>
<Text variant="body" className="text-center">
Join our waitlist to get notified when we open up access!
</Text>
</div>
<div className="flex gap-3">
<Button
variant="secondary"
onClick={() => {
window.open("https://agpt.co/waitlist", "_blank");
}}
>
Join Waitlist
</Button>
<Button variant={closeButtonVariant} onClick={onClose}>
{closeButtonText}
</Button>
</div>
<div className="flex flex-col gap-2">
<Text variant="small" className="text-center text-muted-foreground">
Already signed up for the waitlist? Make sure you&apos;re using the
exact same email address you used when signing up.
</Text>
<Text variant="small" className="text-center text-muted-foreground">
If you&apos;re not sure which email you used or need help,{" "}
<a
href="https://discord.gg/autogpt"
target="_blank"
rel="noopener noreferrer"
className="underline hover:text-foreground"
>
reach out on Discord
</a>
</Text>
</div>
</div>
);
}