Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 50 additions & 2 deletions app/(dashboard)/_components/board-list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export const BoardList = ({ orgId }: BoardListProps) => {
const searchParams = useSearchParams();
const { organization } = useOrganization();
const [isOrgSwitching, setIsOrgSwitching] = useState(false);
const [hasConvexError, setHasConvexError] = useState(false);

// Inject keyframes dynamically since no global CSS is used
useEffect(() => {
Expand All @@ -41,14 +42,17 @@ export const BoardList = ({ orgId }: BoardListProps) => {
};
}, []);

// Handle organization switching
// Handle organization switching with improved error handling
useEffect(() => {
if (organization?.id !== orgId) {
setIsOrgSwitching(true);
const timer = setTimeout(() => {
setIsOrgSwitching(false);
}, 1000);
}, 2000); // Increased timeout to handle RSC delays
return () => clearTimeout(timer);
} else {
// Clear switching state when organization matches
setIsOrgSwitching(false);
}
}, [organization?.id, orgId]);

Expand All @@ -63,9 +67,53 @@ export const BoardList = ({ orgId }: BoardListProps) => {

const data = useQuery(api.boards.get, { orgId, search, favorites });

// Handle Convex connection errors with improved timeout
useEffect(() => {
const timeout = setTimeout(() => {
if (data === undefined && !hasConvexError && !isOrgSwitching) {
setHasConvexError(true);
}
}, 8000); // Increased timeout to handle organization switching

if (data !== undefined) {
setHasConvexError(false);
}

return () => clearTimeout(timeout);
}, [data, hasConvexError, isOrgSwitching]);

// Debug logging
// console.log("BoardList - Search:", search, "Favorites:", favorites, "Data:", data);

// Show error state when Convex backend is unavailable
if (hasConvexError) {
return (
<div className="relative h-full flex items-center justify-center">
<div className="text-center space-y-4">
<div className="text-6xl">🔌</div>
<h2 className="text-2xl font-semibold">Connection Error</h2>
<p className="text-muted-foreground max-w-md">
Unable to connect to the backend. Please make sure the Convex development server is running.
</p>
<div className="space-y-2">
<p className="text-sm text-muted-foreground">
Run this command in your terminal:
</p>
<code className="bg-muted p-2 rounded text-sm block">
npx convex dev
</code>
</div>
<button
onClick={() => window.location.reload()}
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
>
Retry Connection
</button>
</div>
</div>
);
}

// Show loading state when organization is switching
if (isOrgSwitching || data === undefined) {
return (
Expand Down
5 changes: 4 additions & 1 deletion app/(dashboard)/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { ConvexClientProvider } from "@/providers/convex-client-provider";
import { Toaster } from "@/components/ui/sonner";
import { ModalProvider } from "@/providers/modal-provider";
import { useEffect } from "react";
import ErrorBoundary from "@/components/error-boundary";

interface DashboardLayoutProps {
children: React.ReactNode;
Expand Down Expand Up @@ -81,7 +82,9 @@ const DashboardLayout = ({ children }: DashboardLayoutProps) => {
<Navbar />
<div className="flex-1 p-3 md:p-6 overflow-hidden">
<div className="h-full bg-white/70 dark:bg-slate-800/70 backdrop-blur-sm rounded-xl md:rounded-2xl shadow-xl border border-white/20 dark:border-slate-700/20 overflow-hidden flex flex-col">
{children}
<ErrorBoundary>
{children}
</ErrorBoundary>
</div>
</div>
</div>
Expand Down
49 changes: 48 additions & 1 deletion app/(dashboard)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,56 @@ import { useOrganization } from "@clerk/nextjs";
import { EmptyOrg } from "./_components/empty-org";
import { BoardList } from "./_components/board-list";
import { MobileOptimization } from "@/components/mobile-optimization";
import { useEffect, useState } from "react";

const DashboardPage = () => {
const { organization } = useOrganization();
const { organization, isLoaded } = useOrganization();
const [orgError, setOrgError] = useState(false);

// Handle organization loading errors with improved timeout
useEffect(() => {
const timeout = setTimeout(() => {
if (!isLoaded && !orgError) {
setOrgError(true);
}
}, 5000); // Increased timeout to handle RSC delays

if (isLoaded) {
setOrgError(false);
}

return () => clearTimeout(timeout);
}, [isLoaded, orgError]);

// Show error state if organization fails to load
if (orgError) {
return (
<div className="relative h-full flex items-center justify-center">
<div className="text-center space-y-4">
<div className="text-6xl">⚠️</div>
<h2 className="text-2xl font-semibold">Authentication Error</h2>
<p className="text-muted-foreground max-w-md">
Unable to load organization information. Please check your authentication setup.
</p>
<button
onClick={() => window.location.reload()}
className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition-colors"
>
Retry
</button>
</div>
</div>
);
}

// Show loading state while organization is loading
if (!isLoaded) {
return (
<div className="relative h-full flex items-center justify-center">
<div className="animate-spin h-8 w-8 border-2 border-blue-500 border-t-transparent rounded-full"></div>
</div>
);
}

return (
<>
Expand Down
78 changes: 78 additions & 0 deletions components/error-boundary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
"use client";

import React from "react";
import { Button } from "@/components/ui/button";

interface ErrorBoundaryState {
hasError: boolean;
error?: Error;
}

interface ErrorBoundaryProps {
children: React.ReactNode;
fallback?: React.ComponentType<{ error?: Error; reset: () => void }>;
}

export class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = { hasError: false };
}

static getDerivedStateFromError(error: Error): ErrorBoundaryState {
return { hasError: true, error };
}

componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
console.error("ErrorBoundary caught an error:", error, errorInfo);
}

reset = () => {
this.setState({ hasError: false, error: undefined });
};

render() {
if (this.state.hasError) {
const FallbackComponent = this.props.fallback || DefaultErrorFallback;
return <FallbackComponent error={this.state.error} reset={this.reset} />;
}

return this.props.children;
}
}

function DefaultErrorFallback({ error, reset }: { error?: Error; reset: () => void }) {
return (
<div className="relative h-full flex items-center justify-center p-6">
<div className="text-center space-y-4 max-w-md">
<div className="text-6xl">⚠️</div>
<h2 className="text-2xl font-semibold">Something went wrong</h2>
<p className="text-muted-foreground">
{error?.message || "An unexpected error occurred while loading this page."}
</p>
<div className="space-y-2">
<Button onClick={reset} className="w-full">
Try Again
</Button>
<Button
variant="outline"
onClick={() => window.location.reload()}
className="w-full"
>
Reload Page
</Button>
</div>
{process.env.NODE_ENV === "development" && error && (
<details className="text-left text-xs text-muted-foreground mt-4">
<summary>Error Details</summary>
<pre className="mt-2 p-2 bg-muted rounded overflow-auto">
{error.stack}
</pre>
</details>
)}
</div>
</div>
);
}

export default ErrorBoundary;
8 changes: 6 additions & 2 deletions middleware.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { clerkMiddleware } from "@clerk/nextjs/server";
import { clerkMiddleware, createRouteMatcher } from "@clerk/nextjs/server";

const isPublicRoute = createRouteMatcher(["/", "/sign-in(.*)", "/sign-up(.*)"]);

export default clerkMiddleware(async (auth, req) => {
const { userId, redirectToSignIn } = await auth();
if (!userId) {

// Protect routes that aren't public
if (!userId && !isPublicRoute(req)) {
redirectToSignIn();
}
});
Expand Down
Loading