diff --git a/app/(dashboard)/_components/board-list.tsx b/app/(dashboard)/_components/board-list.tsx
index ab25f0f..47902ff 100644
--- a/app/(dashboard)/_components/board-list.tsx
+++ b/app/(dashboard)/_components/board-list.tsx
@@ -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(() => {
@@ -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]);
@@ -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 (
+
+
+
🔌
+
Connection Error
+
+ Unable to connect to the backend. Please make sure the Convex development server is running.
+
+
+
+ Run this command in your terminal:
+
+
+ npx convex dev
+
+
+
+
+
+ );
+ }
+
// Show loading state when organization is switching
if (isOrgSwitching || data === undefined) {
return (
diff --git a/app/(dashboard)/layout.tsx b/app/(dashboard)/layout.tsx
index 10a7e3a..ad6bbbe 100644
--- a/app/(dashboard)/layout.tsx
+++ b/app/(dashboard)/layout.tsx
@@ -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;
@@ -81,7 +82,9 @@ const DashboardLayout = ({ children }: DashboardLayoutProps) => {
- {children}
+
+ {children}
+
diff --git a/app/(dashboard)/page.tsx b/app/(dashboard)/page.tsx
index 0be87c2..06d6648 100644
--- a/app/(dashboard)/page.tsx
+++ b/app/(dashboard)/page.tsx
@@ -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 (
+
+
+
⚠️
+
Authentication Error
+
+ Unable to load organization information. Please check your authentication setup.
+
+
+
+
+ );
+ }
+
+ // Show loading state while organization is loading
+ if (!isLoaded) {
+ return (
+
+ );
+ }
return (
<>
diff --git a/components/error-boundary.tsx b/components/error-boundary.tsx
new file mode 100644
index 0000000..1231b81
--- /dev/null
+++ b/components/error-boundary.tsx
@@ -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 {
+ 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 ;
+ }
+
+ return this.props.children;
+ }
+}
+
+function DefaultErrorFallback({ error, reset }: { error?: Error; reset: () => void }) {
+ return (
+
+
+
⚠️
+
Something went wrong
+
+ {error?.message || "An unexpected error occurred while loading this page."}
+
+
+
+
+
+ {process.env.NODE_ENV === "development" && error && (
+
+ Error Details
+
+ {error.stack}
+
+
+ )}
+
+
+ );
+}
+
+export default ErrorBoundary;
diff --git a/middleware.ts b/middleware.ts
index d2b5c0a..569477d 100644
--- a/middleware.ts
+++ b/middleware.ts
@@ -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();
}
});