fix(layout): mobile UX cleanup + interest-stage legend popover

Mobile UX:
- Hide ColumnPicker on `< sm` viewports (cards, no columns to toggle).
- Hide kanban toggle in interest list on mobile and snap viewMode back
  to 'table' if the persisted choice was 'board'.
- Drop dead "Inbox" link from the More-sheet (email/IMAP feature is
  deferred per sidebar.tsx note).
- Repoint Notifications nav from `/notifications` (no page.tsx — 404)
  to `/notifications/preferences` and re-label as "Notification
  preferences" (the bell stays the surface for actual notifications).
- Hide Website Analytics on both desktop sidebar and mobile More-sheet
  when Umami isn't configured for the port (`useUmamiActive()`).

Interests:
- New `<StageLegend>` popover button in the filter row decodes the
  card stripe colours to pipeline stage names, kept in sync with
  `STAGE_DOT` automatically.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-09 04:11:01 +02:00
parent 82fd75081a
commit 19622985b5
5 changed files with 99 additions and 9 deletions

View File

@@ -1,6 +1,6 @@
'use client';
import { useState } from 'react';
import { useEffect, useState } from 'react';
import { useParams } from 'next/navigation';
import {
Plus,
@@ -35,6 +35,7 @@ import { ColumnPicker } from '@/components/shared/column-picker';
import { SaveViewDialog } from '@/components/shared/save-view-dialog';
import { useTablePreferences } from '@/hooks/use-table-preferences';
import { InterestCard } from '@/components/interests/interest-card';
import { StageLegend } from '@/components/interests/stage-legend';
import { TagPicker } from '@/components/shared/tag-picker';
import {
Dialog,
@@ -63,6 +64,13 @@ export function InterestList() {
const queryClient = useQueryClient();
const { viewMode, setViewMode } = usePipelineStore();
// Force the list view at mobile widths even when the user previously
// toggled the kanban from desktop — the board is desktop-only.
useEffect(() => {
if (typeof window === 'undefined') return;
if (viewMode === 'board' && window.innerWidth < 640) setViewMode('table');
}, [viewMode, setViewMode]);
const [createOpen, setCreateOpen] = useState(false);
const [editInterest, setEditInterest] = useState<InterestRow | null>(null);
const [archiveInterest, setArchiveInterest] = useState<InterestRow | null>(null);
@@ -157,7 +165,10 @@ export function InterestList() {
variant="gradient"
actions={
<div className="flex items-center gap-2">
<div className="flex items-center border rounded-md overflow-hidden">
{/* Kanban view is desktop-only — mobile drops the toggle and
falls back to the list/cards view (the board's column
horizontal-scroll model is unusable at phone widths). */}
<div className="hidden sm:flex items-center border rounded-md overflow-hidden">
<Button
size="sm"
variant={viewMode === 'table' ? 'default' : 'ghost'}
@@ -234,6 +245,7 @@ export function InterestList() {
/>
</>
) : null}
<StageLegend />
</div>
<SaveViewDialog

View File

@@ -0,0 +1,52 @@
'use client';
import { Info } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Popover, PopoverContent, PopoverTrigger } from '@/components/ui/popover';
import {
PIPELINE_STAGES,
STAGE_LABELS,
stageDotClass,
type PipelineStage,
} from '@/lib/constants';
/**
* Small popover that decodes the colored stripe on each interest card to
* the pipeline stage it represents. The stripe colors come from
* `STAGE_DOT` in `lib/constants.ts`; if that map changes, this legend
* stays in sync because both read from the same source.
*/
export function StageLegend() {
return (
<Popover>
<PopoverTrigger asChild>
<Button
variant="ghost"
size="sm"
className="h-8 gap-1.5 text-muted-foreground"
aria-label="What do the colors mean?"
>
<Info className="h-3.5 w-3.5" />
<span className="hidden sm:inline">Legend</span>
</Button>
</PopoverTrigger>
<PopoverContent align="start" className="w-64 p-3">
<p className="mb-2 text-xs font-semibold uppercase tracking-wide text-muted-foreground">
Pipeline stage colors
</p>
<ul className="space-y-1.5">
{PIPELINE_STAGES.map((stage: PipelineStage) => (
<li key={stage} className="flex items-center gap-2 text-sm">
<span
aria-hidden
className={`inline-block h-2.5 w-2.5 shrink-0 rounded-full ${stageDotClass(stage)}`}
/>
<span>{STAGE_LABELS[stage]}</span>
</li>
))}
</ul>
</PopoverContent>
</Popover>
);
}