diff --git a/frontend/src/utils/helpers/apolloClient.ts b/frontend/src/utils/helpers/apolloClient.ts index 42fdbeca87..0aa052746b 100644 --- a/frontend/src/utils/helpers/apolloClient.ts +++ b/frontend/src/utils/helpers/apolloClient.ts @@ -1,12 +1,12 @@ import { ApolloClient, InMemoryCache, HttpLink } from '@apollo/client' import { setContext } from '@apollo/client/link/context' -import { AppError, handleAppError } from 'app/global-error' + import { GRAPHQL_URL } from 'utils/env.client' import { getCsrfToken } from 'utils/utility' + const createApolloClient = () => { if (!GRAPHQL_URL) { - const error = new AppError(500, 'Missing GraphQL URL') - handleAppError(error) + // Only create the error for logging or throwing, do not emit a toast here return null } diff --git a/frontend/src/wrappers/provider.tsx b/frontend/src/wrappers/provider.tsx index 974bdb304d..ce1ebfc224 100644 --- a/frontend/src/wrappers/provider.tsx +++ b/frontend/src/wrappers/provider.tsx @@ -1,11 +1,14 @@ 'use client' import { ApolloProvider } from '@apollo/client/react' import { HeroUIProvider, ToastProvider } from '@heroui/react' +import { addToast } from '@heroui/toast' import { useDjangoSession } from 'hooks/useDjangoSession' import { SessionProvider } from 'next-auth/react' import { ThemeProvider as NextThemesProvider } from 'next-themes' import React, { Suspense } from 'react' +import { ENVIRONMENT, GRAPHQL_URL } from 'utils/env.client' import apolloClient from 'utils/helpers/apolloClient' +import { getCsrfToken } from 'utils/utility' // is a component that initializes the Django session. // It ensures the session is synced with Django when the app starts. @@ -22,24 +25,109 @@ export function Providers({ }: Readonly<{ children: React.ReactNode }>) { + const isProduction = ENVIRONMENT === 'production' + const [graphQLReachability, setGraphQLReachability] = React.useState< + 'pending' | 'reachable' | 'unreachable' + >('pending') + const lastToastMessageRef = React.useRef(null) + + React.useEffect(() => { + if (!apolloClient || !GRAPHQL_URL) { + setGraphQLReachability('unreachable') + return + } + const graphqlUrl = GRAPHQL_URL + const abortController = new AbortController() + + const verifyGraphQLEndpoint = async () => { + try { + const csrfToken = await getCsrfToken() + const response = await fetch(graphqlUrl, { + body: JSON.stringify({ query: 'query { __typename }' }), + credentials: 'include', + headers: { + 'Content-Type': 'application/json', + 'X-CSRFToken': csrfToken, + }, + method: 'POST', + signal: abortController.signal, + }) + + if (!response.ok) { + setGraphQLReachability('unreachable') + return + } + + setGraphQLReachability('reachable') + } catch { + if (!abortController.signal.aborted) { + setGraphQLReachability('unreachable') + } + } + } + + setGraphQLReachability('pending') + void verifyGraphQLEndpoint() + + return () => { + abortController.abort() + } + }, []) + + let graphQLErrorMessage: string | null = null if (!apolloClient) { - return ( -
- Configuration Error: GraphQL Client failed to initialize -
- ) + if (isProduction) { + graphQLErrorMessage = 'Something went wrong' + } else { + graphQLErrorMessage = + 'GraphQL client setup required. Ensure backend is running and GraphQL environment variables are configured.' + } + } else if (graphQLReachability === 'unreachable') { + if (isProduction) { + graphQLErrorMessage = 'Something went wrong' + } else { + graphQLErrorMessage = + 'GraphQL endpoint is unreachable. Ensure the backend service is running and NEXT_PUBLIC_GRAPHQL_URL is correct.' + } } + React.useEffect(() => { + if (!graphQLErrorMessage || lastToastMessageRef.current === graphQLErrorMessage) { + return + } + + addToast({ + color: 'danger', + description: graphQLErrorMessage, + shouldShowTimeoutProgress: true, + timeout: 5000, + title: 'Configuration Error', + variant: 'solid', + }) + + lastToastMessageRef.current = graphQLErrorMessage + }, [graphQLErrorMessage]) + return ( - - - {children} - + {graphQLReachability === 'pending' && ( +
Checking GraphQL endpoint…
+ )} + {graphQLReachability === 'unreachable' && ( +
+ GraphQL endpoint is unreachable. +
+ )} + {apolloClient && graphQLReachability === 'reachable' ? ( + + + {children} + + ) : null}