101 lines
13 KiB
Markdown
101 lines
13 KiB
Markdown
# 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.
|
||
|
||
1. `app/(protected)/upsell-evaluator/_flow/UpsellEvaluator.tsx` (lines 183–218) — the core two-column flow shell, currently **inline `style={{}}`**, zero responsive behavior: outer wrapper `maxWidth: 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.
|
||
2. `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).
|
||
3. `app/(protected)/upsell-evaluator/_history/TicketDetailView.tsx:44` (`grid-cols-2`) and `SessionHistoryView.tsx:119` (`grid-cols-3`) — confirmed (via `grep`) these render under `/my-metrics` (`app/(protected)/my-metrics/page.tsx` and `[id]/page.tsx`), reached from the "My Usage" NavBar link right next to Upsell Evaluator.
|
||
4. Two dashboard tables missing `overflow-x-auto` (have `overflow-y-auto` only, forcing page-level horizontal overflow instead of a contained scroll): `app/(protected)/dashboard/page.tsx:607-608` (`min-w-[760px]`) and `components/dashboard/AllTeamsCard.tsx:321-322` (`min-w-[700px]`). One-line fix each, matching the already-correct sibling `components/dashboard/ValidationsCard.tsx:123-124` (`overflow-x-auto` + `min-w-[600px]`).
|
||
5. `components/ranking/LeaguesLadder.tsx:96` (`grid-cols-4`) and `components/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:
|
||
|
||
```tsx
|
||
<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 `nav` prop from a structured `{id,label,href,active}[]` array to `ReactNode` (a slot, same shape as `actions` already 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* — so `NavBar.tsx` keeps building its role-gated links with `next/link`'s `Link` (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"` + internal `open` state (mobile menu). Reuse the sibling `Drawer` component from the same package (`import { Drawer } from '../Drawer'` or via the barrel) — no new slide-over logic needed, `Drawer` already has focus-trap, Tab-trap, Escape, backdrop-close, and focus-restore.
|
||
- `style.module.scss`: add a `≥768px` media query (matching Tailwind's `md`) that shows the existing full-width `.nav`/`.actions` row and hides a new hamburger `.trigger` button; below `768px`, hide `.nav`/`.actions` from the row and show `.trigger`, which opens the `Drawer` containing the same `nav` and `actions` nodes.
|
||
- 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`, building `Link` elements, composing `StreakBell`/`AttendanceBell`/`ThemeToggle`/`LogoutButton`/`TourButton`) are unchanged. **Confirmed division of responsibility:** `NavBar.tsx` decides *what* to show (which links per role, which icons); `AppHeader` only decides *how* to arrange it responsively. `AppHeader` never sees `Profile`/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, no `next/link` support) and building a brand-new, separate DS component instead of enhancing `AppHeader` (would leave `AppHeader` as 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
|
||
|
||
```diff
|
||
- <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 the `nav`/`actions` props); 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 that `nav`/`actions` content 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. Update `AppHeader.stories.tsx` with 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:
|
||
1. 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.
|
||
2. 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.
|
||
3. `/my-metrics` and `/my-metrics/[id]`: grids stack to 1 column instead of squeezing.
|
||
4. `/dashboard`: the two fixed tables scroll horizontally within their own box instead of blowing out page width.
|
||
5. `/ranking` and admin config panels: grids reflow instead of squeezing.
|
||
6. Verify-only items: `CopilotWidget` panel and `StreakBell` popover (now inside the Drawer) don't clip at 320–340px.
|
||
7. 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`):
|
||
1. `UpsellEvaluator.tsx` shell → Tailwind + stacking.
|
||
2. `packages/design-system/src/components/AppHeader/` → responsive rework (`nav`/`actions` widened to `ReactNode`, `"use client"`, internal Drawer-based mobile menu, updated story/Jest/Cypress).
|
||
3. `components/NavBar.tsx` → adopt `AppHeader` from `@gfiber/design-system`, passing existing role-gated `Link` elements and icon cluster as `nav`/`actions` props (server-component responsibilities unchanged).
|
||
4. `TicketDetailView.tsx` + `SessionHistoryView.tsx` → responsive grids.
|
||
5. `dashboard/page.tsx` + `AllTeamsCard.tsx` → `overflow-x-auto`.
|
||
6. `ranking/LeaguesLadder.tsx` + `ranking/RankingConfigPanel.tsx` → responsive `grid-cols-N`.
|
||
7. DS Jest/Cypress/Storybook updates for `AppHeader`; app Cypress narrow-viewport passes (including mobile-menu navigation via the new header).
|
||
8. qa-validator pass at 340/480/640px across Upsell Evaluator, My Usage, Dashboard, and Ranking/Admin pages; screenshots attached to PR description.
|