fix(settings): wrap useSearchParams pages in Suspense so nav works in prod

The settings page derived its active panel from `useSearchParams()` but
wasn't wrapped in a Suspense boundary. In Next 16 that fails the
production build ("missing Suspense with CSR bailout") and forces the
page to bail to full client-side rendering — shipping a settings screen
whose rail click handlers never wire up. Because the account-menu
Settings link targets `?tab=whatsapp`, users land on the WhatsApp panel
and can't navigate to any other section ("locked on WhatsApp").

Wrap the `useSearchParams` consumer in `<Suspense>` on the three
affected dashboard pages — settings, inbox, automations/new — mirroring
the split login/signup already use. Also add the missing
`Settings.sections.quick-replies` label, which was rendering as a raw
translation key in the settings rail.

Verified: production build now succeeds (previously errored out during
static generation); every settings rail section navigates correctly in a
production build.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Arnas Donauskas 2026-07-17 15:08:25 +03:00
parent 8bbda771ce
commit 8bbbe0a471
No known key found for this signature in database
4 changed files with 42 additions and 3 deletions

View File

@ -1501,6 +1501,7 @@
"appearance": "Appearance",
"whatsapp": "WhatsApp",
"templates": "Templates",
"quick-replies": "Quick replies",
"fields": "Fields & tags",
"deals": "Deals & currency",
"members": "Team members",

View File

@ -1,6 +1,6 @@
"use client"
import { useMemo } from "react"
import { Suspense, useMemo } from "react"
import { useSearchParams } from "next/navigation"
import {
@ -11,7 +11,18 @@ import {
import { AUTOMATION_TEMPLATES, type TemplateSlug } from "@/lib/automations/templates"
import type { AutomationStepType, AutomationTriggerType } from "@/types"
// `useSearchParams` requires a Suspense boundary or the production build
// bails to CSR and errors out. Thin wrapper supplies it; the inner
// component reads the `?template=` query string.
export default function NewAutomationPage() {
return (
<Suspense fallback={null}>
<NewAutomationPageInner />
</Suspense>
)
}
function NewAutomationPageInner() {
const params = useSearchParams()
const template = params.get("template") as TemplateSlug | null

View File

@ -1,6 +1,6 @@
"use client";
import { useState, useCallback, useEffect, useRef } from "react";
import { Suspense, useState, useCallback, useEffect, useRef } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { useTranslations } from "next-intl";
import { createClient } from "@/lib/supabase/client";
@ -21,7 +21,18 @@ import { cn } from "@/lib/utils";
// across reloads and sessions (device-scoped, like the theme prefs).
const CONTACT_PANEL_STORAGE_KEY = "wacrm:inbox:contact-panel-open";
// `useSearchParams` (the `?c=<id>` deep link below) requires a Suspense
// boundary or the production build bails to CSR and errors out. Thin
// wrapper supplies it; the inner component holds all the inbox state.
export default function InboxPage() {
return (
<Suspense fallback={null}>
<InboxPageInner />
</Suspense>
);
}
function InboxPageInner() {
const t = useTranslations("Inbox.page");
const router = useRouter();
const searchParams = useSearchParams();

View File

@ -1,6 +1,6 @@
'use client';
import { useMemo, type ReactNode } from 'react';
import { Suspense, useMemo, type ReactNode } from 'react';
import { useRouter, useSearchParams } from 'next/navigation';
import { useTranslations } from 'next-intl';
@ -23,7 +23,23 @@ import {
type SettingsSection,
} from '@/components/settings/settings-sections';
// `useSearchParams` opts this page out of static prerendering unless it
// sits under a Suspense boundary. Without one, the production build hits
// the "missing Suspense with CSR bailout" error and the whole page bails
// to client-side rendering — shipping a settings screen whose rail never
// wires up its click handlers. You land on the section the URL carried
// (the account-menu Settings link points at `?tab=whatsapp`) and can't
// navigate away. Mirror the login/signup split: a thin wrapper supplies
// the boundary; the inner component reads the query string.
export default function SettingsPage() {
return (
<Suspense fallback={null}>
<SettingsPageInner />
</Suspense>
);
}
function SettingsPageInner() {
const router = useRouter();
const searchParams = useSearchParams();
const { defaultCurrency } = useAuth();