13 KiB
GFIBER-603 — Make the UI fully responsive
Context
Jira GFIBER-603 (High priority, "Tarea", assigned to a.barrientos, In Development):
The team has observed during shadowing that agents normally have multiple windows open on the same screen. Upsell Evaluator would usually occupy between 1/4 and 1/3 of the total width. The app should be adaptable so that it's perfectly usable in these sizes.
Acceptance Criteria:
- The app is fully responsive
- All input fields are easily accessible and readable from 1/3 and 1/4 of the screen width
This is not mobile-phone responsiveness — it's a desktop browser window resized narrow (≈320–640px: 1/4 of a 1366px laptop ≈340px, 1/3 of a 1920px monitor ≈640px), mouse-driven, used by call-center agents running the Upsell Evaluator alongside CRM/phone software. That range sits entirely below Tailwind's default sm breakpoint (640px), so a mobile-first stacking approach (unstyled/base = narrow, sm:+ = normal desktop) fits naturally with zero custom breakpoints needed — the project has no tailwind.config.js (Tailwind v4, CSS-based config, defaults sm:640 md:768 lg:1024 xl:1280 untouched).
The repo has an established design system (packages/design-system/, 51 components, consumed via transpilePackages from source, DS-first convention: new/changed UI goes in the DS with story+Jest+Cypress). Investigation (Explore agent + manual verification) found the actual defects are app-level compositions (inline styles / missing responsive classes in page/NavBar code), not DS component bugs — FlowStepper, TextField, Select, and Textarea were all checked and are already width-agnostic (no fixed-px widths). So no new DS components are required; fixes are Tailwind-class changes in existing app files, consistent with the DS-first convention (these are app-level compositions with no 1:1 DS component, same category as UpsellEvaluator.tsx's existing upsell.css).
Scope
Per user decision, this covers the full AC-literal reading ("the app is fully responsive") — Upsell Evaluator/My Usage and ranking/admin grids, not just the narrow-window tool.
app/(protected)/upsell-evaluator/_flow/UpsellEvaluator.tsx(lines 183–218) — the core two-column flow shell, currently inlinestyle={{}}, zero responsive behavior: outer wrappermaxWidth: 1080,<aside>width: 220, flexShrink: 0, position: sticky,<main>flex: 1. This is the highest-priority fix — it's the actual tool agents run in the narrow window.components/NavBar.tsx— present on every page including Upsell Evaluator. Single always-visible flex row (brand + 2–4 links + up to 5 right-side icons: StreakBell, AttendanceBell, ThemeToggle, LogoutButton, TourButton), only one responsive class in the whole file. Will overflow/clip at 320–480px. Fixed via a collapsible hamburger menu (see below).app/(protected)/upsell-evaluator/_history/TicketDetailView.tsx:44(grid-cols-2) andSessionHistoryView.tsx:119(grid-cols-3) — confirmed (viagrep) these render under/my-metrics(app/(protected)/my-metrics/page.tsxand[id]/page.tsx), reached from the "My Usage" NavBar link right next to Upsell Evaluator.- Two dashboard tables missing
overflow-x-auto(haveoverflow-y-autoonly, forcing page-level horizontal overflow instead of a contained scroll):app/(protected)/dashboard/page.tsx:607-608(min-w-[760px]) andcomponents/dashboard/AllTeamsCard.tsx:321-322(min-w-[700px]). One-line fix each, matching the already-correct siblingcomponents/dashboard/ValidationsCard.tsx:123-124(overflow-x-auto+min-w-[600px]). components/ranking/LeaguesLadder.tsx:96(grid-cols-4) andcomponents/ranking/RankingConfigPanel.tsx:169,202,241(grid-cols-2×3) — add responsive variants (e.g.grid-cols-2 sm:grid-cols-4/grid-cols-1 sm:grid-cols-2) so these admin/ranking panels also reflow instead of squeezing at narrow widths.
Verify-only, no code change expected (check visually during QA, fix only if actually broken): components/upsell/CopilotWidget.tsx (w-[380px] max-w-[calc(100vw-2rem)] — already viewport-clamped) and components/NavBar/StreakBell.tsx:107 (w-64 popover anchored right-0 inside the icon cluster).
Implementation approach
1. UpsellEvaluator.tsx — convert inline styles to Tailwind, stack below sm
Replace the inline-styled shell (lines 183–193) with:
<div className="upsell-flow" style={{ minHeight: "calc(100vh - 57px)" }}>
<div className="mx-auto flex max-w-[1080px] flex-col items-stretch gap-4 p-4 sm:flex-row sm:items-start sm:gap-6 sm:p-6">
<aside aria-label="Flow navigation" className="w-full flex-shrink-0 sm:sticky sm:top-4 sm:w-[220px]">
<FlowStepper .../>
<div className="mt-3"><Button variant="ghost" onClick={newCall}>...</Button></div>
{blocked && <div className="mt-3"><Badge variant="danger">Hard stop active</Badge></div>}
</aside>
<main className="min-w-0 flex-1">...</main>
</div>
Below sm (640px): aside goes full-width and stacks above main content. At sm: and up: reverts to today's sticky 220px sidebar + flex-1 main — no visual change for normal desktop use. FlowStepper needs no changes (already flex-direction: column; width: 100%, no fixed-px widths); the narrowness problem was entirely the parent <aside>'s hardcoded width. Leave the saveToast inline style block (lines 160–181) as-is unless touching it is free — not a responsiveness bug (centered fixed toast is width-agnostic).
2. NavBar.tsx — make the existing (orphaned) DS AppHeader component responsive and adopt it
Per user decision, the responsive collapse behavior itself belongs in the design system as a real component, not composed ad hoc in the app. packages/design-system/src/components/AppHeader/index.tsx already exists with almost exactly the right shape (brand?: ReactNode, nav?: ..., actions?: ReactNode) but is presentational, non-responsive (fixed height: 64px row, no collapse), and unused anywhere in the app (grep confirms zero imports outside its own Storybook/tests) — so there's no adoption-migration risk, and enhancing it in place is strictly additive.
DS-side changes (packages/design-system/src/components/AppHeader/):
- Widen the
navprop from a structured{id,label,href,active}[]array toReactNode(a slot, same shape asactionsalready is). This is the key change the user is asking for: the DS component stops knowing about hrefs/active-state/routing and just arranges whatever nav content the app hands it — soNavBar.tsxkeeps building its role-gated links withnext/link'sLink(prefetch, client-side nav) exactly as it does today, and simply passes that finished JSX in as a prop instead of AppHeader re-rendering plain<a>tags. - Add
"use client"+ internalopenstate (mobile menu). Reuse the siblingDrawercomponent from the same package (import { Drawer } from '../Drawer'or via the barrel) — no new slide-over logic needed,Draweralready has focus-trap, Tab-trap, Escape, backdrop-close, and focus-restore. style.module.scss: add a≥768pxmedia query (matching Tailwind'smd) that shows the existing full-width.nav/.actionsrow and hides a new hamburger.triggerbutton; below768px, hide.nav/.actionsfrom the row and show.trigger, which opens theDrawercontaining the samenavandactionsnodes.- Add the standard DS file set for this change: update
AppHeader.stories.tsx(add a narrow-viewport/mobile story),AppHeader.test.tsx(Jest: trigger opens Drawer, Escape/backdrop closes it, content renders in both desktop and mobile paths),AppHeader.test.cy.tsx(Cypress: real viewport resize + click-through + focus-trap), per the repo's DS-first convention (every component ships with story + Jest + Cypress).
App-side changes (components/NavBar.tsx):
- Stays a Server Component — all existing responsibilities (role gating via
profile, buildingLinkelements, composingStreakBell/AttendanceBell/ThemeToggle/LogoutButton/TourButton) are unchanged. Confirmed division of responsibility:NavBar.tsxdecides what to show (which links per role, which icons);AppHeaderonly decides how to arrange it responsively.AppHeadernever seesProfile/Supabase types — this preserves the DS = UI / app = business-logic boundary already established in GFIBER-585 (Paso 3 — "DS = estilos/UI; app = lógica de negocio"). - Replace the current hand-rolled
<nav>/<div>markup with<AppHeader brand={...} nav={<>...existing Link elements...</>} actions={<>...existing icon cluster...</>} />imported from@gfiber/design-system. Passing server-built JSX as props into a client component (AppHeader) is a standard, supported RSC pattern (the children/props aren't re-executed on the client) — no NavBar behavior changes. - Considered and rejected: reusing
KebabMenu(wrong semantics/shape for primary nav — actions-menu, not nav, nonext/linksupport) and building a brand-new, separate DS component instead of enhancingAppHeader(would leaveAppHeaderas dead, non-representative Penpot-sourced code and create two overlapping "app header" components in the DS).
3. TicketDetailView.tsx / SessionHistoryView.tsx grids
grid-cols-2 → grid-cols-1 sm:grid-cols-2 (TicketDetailView:44); grid-cols-3 → grid-cols-1 sm:grid-cols-3 (SessionHistoryView:119) — adjust to sm:grid-cols-2 lg:grid-cols-3 instead if 3-up reads cramped exactly at 640px during QA.
4. Dashboard tables
- <div className="max-h-[460px] overflow-y-auto">
+ <div className="max-h-[460px] overflow-x-auto overflow-y-auto">
Apply to both app/(protected)/dashboard/page.tsx:607-608 and components/dashboard/AllTeamsCard.tsx:321-322.
Verification
- Vitest + Testing Library (existing suites for
UpsellEvaluator.tsx,NavBar.tsx,TicketDetailView/SessionHistoryView): keep existing behavioral tests green (stage transitions, role-gated nav links render correctly into thenav/actionsprops); don't add tests whose only assertion is "this className string exists" — low value, jsdom has no real layout engine. - Design-system (Jest + Testing Library + Cypress) for the enhanced
AppHeader: Jest test for open/close/focus-trap/Escape/backdrop-close and thatnav/actionscontent renders identically in both the desktop row and the mobile Drawer; Cypress test with real viewport resize (e.g. 340/480/768px) verifying the trigger appears/disappears at the right breakpoint and the drawer is fully interactive. UpdateAppHeader.stories.tsxwith a mobile/narrow story so it's visually reviewable in Storybook. - Cypress E2E (app-level, existing Upsell Evaluator flow spec): add
cy.viewport(340, 720)/cy.viewport(480, 720)passes, re-running the same GAID lookup → stages → close interactions to catch real click-target/overlap regressions, including opening the header's mobile menu and navigating via it. - qa-validator (real browser) — the concrete check for the AC. Resize to 340px, 480px, and 640px (not just one width) and confirm:
- Upsell Evaluator: stepper stacks above stage content, no squeeze; every input/select/textarea fully visible, not clipped; all buttons (New Call, Continue/Back, ObjectionDrawer trigger) reachable without horizontal scroll.
- NavBar: below
md:, only brand + hamburger trigger show; opening the menu reveals all links + icon cluster in the Drawer with working focus-trap/Escape/backdrop-close; nothing clipped or unreachable. /my-metricsand/my-metrics/[id]: grids stack to 1 column instead of squeezing./dashboard: the two fixed tables scroll horizontally within their own box instead of blowing out page width./rankingand admin config panels: grids reflow instead of squeezing.- Verify-only items:
CopilotWidgetpanel andStreakBellpopover (now inside the Drawer) don't clip at 320–340px. - Screenshots at each width for the PR description, as evidence against the AC.
Suggested commit/PR breakdown
Single PR (feat/gfiber-603-responsive-upsell-evaluator):
UpsellEvaluator.tsxshell → Tailwind + stacking.packages/design-system/src/components/AppHeader/→ responsive rework (nav/actionswidened toReactNode,"use client", internal Drawer-based mobile menu, updated story/Jest/Cypress).components/NavBar.tsx→ adoptAppHeaderfrom@gfiber/design-system, passing existing role-gatedLinkelements and icon cluster asnav/actionsprops (server-component responsibilities unchanged).TicketDetailView.tsx+SessionHistoryView.tsx→ responsive grids.dashboard/page.tsx+AllTeamsCard.tsx→overflow-x-auto.ranking/LeaguesLadder.tsx+ranking/RankingConfigPanel.tsx→ responsivegrid-cols-N.- DS Jest/Cypress/Storybook updates for
AppHeader; app Cypress narrow-viewport passes (including mobile-menu navigation via the new header). - qa-validator pass at 340/480/640px across Upsell Evaluator, My Usage, Dashboard, and Ranking/Admin pages; screenshots attached to PR description.