Skip to content

UI #21 — Build search history with localStorage persistence #169

@Tinna23

Description

@Tinna23

Description

Every transaction hash or account address a user successfully looks up should be saved to a browsable history. This makes the app feel personal and lets users return to past lookups without needing to remember long hashes.

This issue also introduces AppShell and AppShellContext — the shared wrapper that all app pages use. Previous issues used a stub version of AppShell. This is where the real implementation lands.

What To Build

Part 1 — AppShell (replaces stub from UI #5)

**`src/components/AppShellContext.ts`**

- Defines the `AppShellContextValue` interface and exports the `useAppShell()` hook
- Context shape includes: history state, address book state (stubs for now), copy utility, and personal mode (stubs for now)
- Any component inside `AppShell` can call `useAppShell()` to access shared state

**`src/components/AppShell.tsx`** — replaces the stub created in UI #5
- Wraps all app pages (`/app`, `/tx/[hash]`, `/account/[address]`)
- Provides the dark background + grid overlay layers
- Renders the persistent app header: logo (links to `/app`), `HistoryButton` trigger, panel buttons area
- Wraps children with `AppShellContext.Provider`
- Renders `HistoryPanel` and `Toast` at root level (outside normal flow)
- Body scroll locked when any panel is open

**Inner component pattern** — each result page must follow this structure to avoid hooks-in-callbacks errors:
```tsx
function TxPageInner() {
  const { addEntry } = useAppShell(); // ✅ hook at component top level
  ...
}
export default function TxPage() {
  return <AppShell><TxPageInner /></AppShell>;
}

Part 2 — Search History

src/hooks/useSearchHistory.ts

  • Manages history in localStorage under key stellar-explain-history
  • Stores up to 20 entries: { id, type: 'transaction' | 'account', identifier, searchedAt: ISO string, summary?: string }
  • summary populated from the backend response after a successful fetch (truncated to 80 chars)
  • Only reads localStorage after mount to avoid SSR hydration mismatch
  • Exports: addEntry(), removeEntry(), clearAll(), entries, count

src/components/history/HistoryButton.tsx

  • Trigger button with a clock icon and a live count badge
  • Props: count: number, onClick: () => void
  • Purple/sky-blue pill style consistent with the app header

src/components/history/HistoryItem.tsx

  • Single history entry row — pure display component
  • Shows: type icon pill (tx = sky blue, account = purple), truncated identifier (first 8…last 8), relative time ("2 hours ago"), summary snippet
  • Individual delete (×) button
  • Clicking the row calls onSelect(entry)

src/components/history/HistoryPanel.tsx

  • Glassy slide-out panel from the right with blur overlay
  • Width: min(380px, 100vw) — full width on mobile
  • Animation: translateX(100%)translateX(0) with cubic-bezier(0.32, 0.72, 0, 1)
  • Blur overlay: backdrop-filter: blur(4px) with dark tint — clicking it closes the panel
  • Entries grouped by Transactions and Accounts sections
  • Empty state with clock illustration when no history
  • Clear all history button in the footer (only shown when entries exist)
  • Escape key + backdrop click both close the panel
  • Custom scrollbar via .history-panel-scroll CSS class

Key Files

New files to create:

  • src/components/AppShellContext.ts
  • src/components/AppShell.tsx ← replaces stub
  • src/hooks/useSearchHistory.ts
  • src/components/history/HistoryButton.tsx
  • src/components/history/HistoryItem.tsx
  • src/components/history/HistoryPanel.tsx

Files to update:

  • src/app/app/page.tsx — wrap with <AppShell>, wire history
  • src/app/tx/[hash]/page.tsx — wrap with <AppShell>, call addEntry on success
  • src/app/account/[address]/page.tsx — wrap with <AppShell>, call addEntry on success

Acceptance Criteria

  • Stub AppShell from UI 5 is fully replaced by the real implementation
  • useAppShell() hook is accessible from any page wrapped in AppShell
  • History button appears in the app header with a live count badge
  • Successfully resolved searches are saved to history automatically
  • History panel slides in from the right with blur overlay on backdrop
  • Entries are grouped by Transactions and Accounts
  • Individual entries can be deleted; all entries can be cleared
  • Clicking a history entry navigates to the correct result page
  • History persists across page reloads
  • Panel closes on Escape key and backdrop click
  • Body scroll is locked while panel is open
  • No hydration errors in Next.js
  • Graceful degradation in private browsing / SSR

Depends on: UI #5, UI #6, UI #7

Metadata

Metadata

Assignees

No one assigned

    Labels

    frontendcreate high-quality web applications with the power of React components.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions