Skip to content

Commit 4a1cb6d

Browse files
authored
fix(frontend): performance and layout issues (#11036)
## Changes πŸ—οΈ ### Performance (Onboarding) 🐎 - Moved non-UI logic into `providers/onboarding/helpers.ts` to reduce provider complexity. - Memoized provider value and narrowed state updates to cut unnecessary re-renders. - Deferred non-critical effects until after mount to lower initial JS work. **Result:** faster initial render and smoother onboarding flows under load. ### Layout and overflow fixes πŸ“ - Replaced `w-screen` with `w-full` in platform/admin/profile layouts and marketplace wrappers to avoid 100vw scrollbar overflow. - Adjusted mobile navbar position (`right-0` instead of `-right-4`) to prevent off-viewport elements. **Result:** removed horizontal scrolling on Marketplace, Library, and Settings pages; Build remains unaffected. ### New Generic Error pages - Standardized global error handling in `app/global-error.tsx` for consistent display and user feedback. - Added platform-scoped error page(s) under `app/(platform)/error` for route-level failures with a consistent layout. - Improved retry affordances using existing `ErrorCard`. ## 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] Verify onboarding flows render faster and re-render less (DevTools flamegraph) - [x] Confirm no horizontal scrolling on Marketplace, Library, Settings at common widths - [x] Validate mobile navbar stays within viewport - [x] Trigger errors to confirm global and platform error pages render consistently ### For configuration changes: None
1 parent 7c9db74 commit 4a1cb6d

File tree

24 files changed

+514
-201
lines changed

24 files changed

+514
-201
lines changed

β€Žautogpt_platform/frontend/src/app/(platform)/admin/layout.tsxβ€Ž

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export default function AdminLayout({
3131
children: React.ReactNode;
3232
}) {
3333
return (
34-
<div className="flex min-h-screen w-screen flex-col lg:flex-row">
34+
<div className="flex min-h-screen w-full flex-col lg:flex-row">
3535
<Sidebar linkGroups={sidebarLinkGroups} />
3636
<div className="flex-1 pl-4">{children}</div>
3737
</div>

β€Žautogpt_platform/frontend/src/app/(platform)/auth/callback/route.tsβ€Ž

Lines changed: 41 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -11,56 +11,12 @@ async function shouldShowOnboarding() {
1111
);
1212
}
1313

14-
// Default redirect path - matches the home page redirect destination
15-
const DEFAULT_REDIRECT_PATH = "/marketplace";
16-
17-
// Validate redirect URL to prevent open redirect attacks and malformed URLs
18-
function validateRedirectUrl(url: string): string {
19-
try {
20-
const cleanUrl = url.trim();
21-
22-
// Check for completely invalid patterns that suggest URL corruption
23-
if (
24-
cleanUrl.includes(",") || // Any comma suggests concatenated URLs
25-
cleanUrl.includes(" ") // Spaces in URLs are problematic
26-
) {
27-
console.warn(
28-
"Detected corrupted redirect URL (likely race condition):",
29-
cleanUrl,
30-
);
31-
return DEFAULT_REDIRECT_PATH;
32-
}
33-
34-
// Only allow relative URLs that start with /
35-
if (!cleanUrl.startsWith("/") || cleanUrl.startsWith("//")) {
36-
console.warn("Invalid redirect URL format:", cleanUrl);
37-
return DEFAULT_REDIRECT_PATH;
38-
}
39-
40-
// Additional safety checks
41-
if (cleanUrl.split("/").length > 5) {
42-
// Reasonable path depth limit
43-
console.warn("Suspiciously deep redirect URL:", cleanUrl);
44-
return DEFAULT_REDIRECT_PATH;
45-
}
46-
47-
// For now, allow any valid relative path (can be restricted later if needed)
48-
return cleanUrl;
49-
} catch (error) {
50-
console.error("Error validating redirect URL:", error);
51-
return DEFAULT_REDIRECT_PATH;
52-
}
53-
}
54-
5514
// Handle the callback to complete the user session login
5615
export async function GET(request: Request) {
5716
const { searchParams, origin } = new URL(request.url);
5817
const code = searchParams.get("code");
5918

60-
// if "next" is in param, use it as the redirect URL
61-
const nextParam = searchParams.get("next") ?? "/";
62-
// Validate redirect URL to prevent open redirect attacks
63-
let next = validateRedirectUrl(nextParam);
19+
let next = "/marketplace";
6420

6521
if (code) {
6622
const supabase = await getServerSupabase();
@@ -70,7 +26,7 @@ export async function GET(request: Request) {
7026
}
7127

7228
const { error } = await supabase.auth.exchangeCodeForSession(code);
73-
// data.session?.refresh_token is available if you need to store it for later use
29+
7430
if (!error) {
7531
try {
7632
const api = new BackendAPI();
@@ -84,7 +40,45 @@ export async function GET(request: Request) {
8440
}
8541
} catch (createUserError) {
8642
console.error("Error creating user:", createUserError);
87-
// Continue with redirect even if createUser fails
43+
44+
// Handle ApiError from the backend API client
45+
if (
46+
createUserError &&
47+
typeof createUserError === "object" &&
48+
"status" in createUserError
49+
) {
50+
const apiError = createUserError as any;
51+
52+
if (apiError.status === 401) {
53+
// Authentication issues - token missing/invalid
54+
return NextResponse.redirect(
55+
`${origin}/error?message=auth-token-invalid`,
56+
);
57+
} else if (apiError.status >= 500) {
58+
// Server/database errors
59+
return NextResponse.redirect(
60+
`${origin}/error?message=server-error`,
61+
);
62+
} else if (apiError.status === 429) {
63+
// Rate limiting
64+
return NextResponse.redirect(
65+
`${origin}/error?message=rate-limited`,
66+
);
67+
}
68+
}
69+
70+
// Handle network/fetch errors
71+
if (
72+
createUserError instanceof TypeError &&
73+
createUserError.message.includes("fetch")
74+
) {
75+
return NextResponse.redirect(`${origin}/error?message=network-error`);
76+
}
77+
78+
// Generic user creation failure
79+
return NextResponse.redirect(
80+
`${origin}/error?message=user-creation-failed`,
81+
);
8882
}
8983

9084
const forwardedHost = request.headers.get("x-forwarded-host"); // original origin before load balancer
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
"use client";
2+
3+
import { ErrorCard } from "@/components/molecules/ErrorCard/ErrorCard";
4+
import { getErrorDetails } from "./error/helpers";
5+
import { useSearchParams } from "next/navigation";
6+
import { Suspense } from "react";
7+
8+
function ErrorPageContent() {
9+
const searchParams = useSearchParams();
10+
const errorMessage = searchParams.get("message");
11+
12+
const errorDetails = getErrorDetails(errorMessage);
13+
14+
function handleRetry() {
15+
if (
16+
errorMessage === "user-creation-failed" ||
17+
errorMessage === "auth-failed"
18+
) {
19+
window.location.href = "/login";
20+
} else {
21+
window.location.href = "/marketplace";
22+
}
23+
}
24+
25+
return (
26+
<div className="flex min-h-screen items-center justify-center bg-gray-50 px-4 py-12 sm:px-6 lg:px-8">
27+
<div className="w-full max-w-md">
28+
<ErrorCard
29+
responseError={errorDetails.responseError}
30+
context={errorDetails.context}
31+
onRetry={handleRetry}
32+
/>
33+
</div>
34+
</div>
35+
);
36+
}
37+
38+
export default function ErrorPage() {
39+
return (
40+
<Suspense
41+
fallback={
42+
<div className="flex min-h-screen items-center justify-center bg-gray-50 px-4 py-12 sm:px-6 lg:px-8">
43+
<div className="w-full max-w-md">
44+
<ErrorCard
45+
responseError={{ message: "Loading..." }}
46+
context="application"
47+
/>
48+
</div>
49+
</div>
50+
}
51+
>
52+
<ErrorPageContent />
53+
</Suspense>
54+
);
55+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
export function getErrorDetails(errorType: string | null) {
2+
switch (errorType) {
3+
case "user-creation-failed":
4+
return {
5+
responseError: {
6+
message:
7+
"Failed to create your user account in our system. This could be due to a temporary server issue or a problem with your account setup.",
8+
},
9+
context: "user account creation",
10+
};
11+
case "auth-token-invalid":
12+
return {
13+
responseError: {
14+
message:
15+
"Your authentication token is missing or invalid. Please try signing in again.",
16+
},
17+
context: "authentication token",
18+
};
19+
case "server-error":
20+
return {
21+
responseError: {
22+
message:
23+
"Our servers are experiencing issues. Please try again in a few minutes, or contact support if the problem persists.",
24+
},
25+
context: "server error",
26+
};
27+
case "rate-limited":
28+
return {
29+
responseError: {
30+
message:
31+
"Too many requests have been made. Please wait a moment before trying again.",
32+
},
33+
context: "rate limiting",
34+
};
35+
case "network-error":
36+
return {
37+
responseError: {
38+
message:
39+
"Unable to connect to our servers. Please check your internet connection and try again.",
40+
},
41+
context: "network connectivity",
42+
};
43+
case "auth-failed":
44+
return {
45+
responseError: {
46+
message: "Authentication failed. Please try signing in again.",
47+
},
48+
context: "authentication",
49+
};
50+
case "session-expired":
51+
return {
52+
responseError: {
53+
message:
54+
"Your session has expired. Please sign in again to continue.",
55+
},
56+
context: "session",
57+
};
58+
default:
59+
return {
60+
responseError: {
61+
message:
62+
"An unexpected error occurred. Please try again or contact support if the problem persists.",
63+
},
64+
context: "application",
65+
};
66+
}
67+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
"use client";
2+
3+
import { ErrorCard } from "@/components/molecules/ErrorCard/ErrorCard";
4+
import { useSearchParams } from "next/navigation";
5+
import { Suspense } from "react";
6+
import { getErrorDetails } from "./helpers";
7+
8+
function ErrorPageContent() {
9+
const searchParams = useSearchParams();
10+
const errorMessage = searchParams.get("message");
11+
const errorDetails = getErrorDetails(errorMessage);
12+
13+
function handleRetry() {
14+
// Auth-related errors should redirect to login
15+
if (
16+
errorMessage === "user-creation-failed" ||
17+
errorMessage === "auth-failed" ||
18+
errorMessage === "auth-token-invalid" ||
19+
errorMessage === "session-expired"
20+
) {
21+
window.location.href = "/login";
22+
} else if (errorMessage === "rate-limited") {
23+
// For rate limiting, wait a moment then try again
24+
setTimeout(() => {
25+
window.location.reload();
26+
}, 2000);
27+
} else {
28+
// For server/network errors, go to marketplace
29+
window.location.href = "/marketplace";
30+
}
31+
}
32+
33+
return (
34+
<div className="flex min-h-screen items-center justify-center bg-gray-50 px-4 py-12 sm:px-6 lg:px-8">
35+
<div className="relative w-full max-w-xl lg:bottom-[4rem]">
36+
<ErrorCard
37+
responseError={errorDetails.responseError}
38+
context={errorDetails.context}
39+
onRetry={handleRetry}
40+
/>
41+
</div>
42+
</div>
43+
);
44+
}
45+
46+
export default function ErrorPage() {
47+
return (
48+
<Suspense
49+
fallback={
50+
<div className="flex min-h-screen items-center justify-center bg-gray-50 px-4 py-12 sm:px-6 lg:px-8">
51+
<div className="relative w-full max-w-xl lg:-top-[4rem]">
52+
<ErrorCard
53+
responseError={{ message: "Loading..." }}
54+
context="application"
55+
/>
56+
</div>
57+
</div>
58+
}
59+
>
60+
<ErrorPageContent />
61+
</Suspense>
62+
);
63+
}

β€Žautogpt_platform/frontend/src/app/(platform)/layout.tsxβ€Ž

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { ReactNode } from "react";
33

44
export default function PlatformLayout({ children }: { children: ReactNode }) {
55
return (
6-
<main className="flex h-screen w-screen flex-col">
6+
<main className="flex h-screen w-full flex-col">
77
<Navbar />
88
<section className="flex-1">{children}</section>
99
</main>

β€Žautogpt_platform/frontend/src/app/(platform)/login/page.tsxβ€Ž

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ export default function LoginPage() {
8585
/>
8686

8787
{/* Turnstile CAPTCHA Component */}
88-
{isCloudEnv && !turnstile.verified ? (
88+
{turnstile.shouldRender ? (
8989
<Turnstile
9090
key={captchaKey}
9191
siteKey={turnstile.siteKey}

β€Žautogpt_platform/frontend/src/app/(platform)/marketplace/components/AgentPageLoading.tsxβ€Ž

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Skeleton } from "@/components/__legacy__/ui/skeleton";
22

33
export const AgentPageLoading = () => {
44
return (
5-
<div className="mx-auto w-screen max-w-[1360px]">
5+
<div className="mx-auto w-full max-w-[1360px]">
66
<main className="mt-5 px-4">
77
<div className="flex items-center space-x-2">
88
<Skeleton className="h-4 w-24" />

β€Žautogpt_platform/frontend/src/app/(platform)/marketplace/components/CreatorPageLoading.tsxβ€Ž

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Skeleton } from "@/components/__legacy__/ui/skeleton";
22

33
export const CreatorPageLoading = () => {
44
return (
5-
<div className="mx-auto w-screen max-w-[1360px]">
5+
<div className="mx-auto w-full max-w-[1360px]">
66
<main className="mt-5 px-4">
77
<Skeleton className="mb-4 h-6 w-40" />
88

β€Žautogpt_platform/frontend/src/app/(platform)/marketplace/components/MainAgentPage/MainAgentPage.tsxβ€Ž

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ export const MainAgentPage = ({ params }: MainAgentPageProps) => {
3030
}
3131
if (hasError) {
3232
return (
33-
<div className="mx-auto w-screen max-w-[1360px]">
33+
<div className="mx-auto w-full max-w-[1360px]">
3434
<main className="px-4">
3535
<div className="flex min-h-[400px] items-center justify-center">
3636
<ErrorCard
@@ -48,7 +48,7 @@ export const MainAgentPage = ({ params }: MainAgentPageProps) => {
4848

4949
if (!agent) {
5050
return (
51-
<div className="mx-auto w-screen max-w-[1360px]">
51+
<div className="mx-auto w-full max-w-[1360px]">
5252
<main className="px-4">
5353
<div className="flex min-h-[400px] items-center justify-center">
5454
<ErrorCard
@@ -74,7 +74,7 @@ export const MainAgentPage = ({ params }: MainAgentPageProps) => {
7474
];
7575

7676
return (
77-
<div className="mx-auto w-screen max-w-[1360px]">
77+
<div className="mx-auto w-full max-w-[1360px]">
7878
<main className="mt-5 px-4">
7979
<Breadcrumbs items={breadcrumbs} />
8080

0 commit comments

Comments
Β (0)