Skip to content

Latest commit

 

History

History
527 lines (372 loc) · 14.7 KB

File metadata and controls

527 lines (372 loc) · 14.7 KB

UI States Design Guide

Overview

This guide defines the empty states, error states, and loading patterns for the Credence Frontend application. These states ensure a consistent, helpful user experience across all views.


See also: Copy Tone Guide for dos and don'ts when phrasing success, error, empty, and loading copy.

Empty States

Empty states appear when there's no data to display. They should be encouraging and guide users toward their next action.

Design Principles

  • Clear & Concise: Use simple language that explains why the state is empty
  • Actionable: Provide a clear next step when possible
  • Visual: Include relevant icons or illustrations
  • Tone: Friendly, encouraging, never blaming the user

Core Empty States

1. No Bond Yet

When: User has not created any bond Location: Bond page, Home highlights

<EmptyState
  illustration="bond"
  title="No bond created yet"
  description="Lock USDC into Credence to build your economic reputation and unlock trust-based opportunities."
  action={{
    label: 'Create your first bond',
    onClick: () => navigate('/bond'),
  }}
/>

Microcopy Guidelines:

  • Title: 4-6 words, state the fact
  • Description: 1-2 sentences, explain the benefit
  • CTA: Action-oriented verb + outcome

Bond page onboarding

This section defines the first-run help pattern for the Bond page. The goal is to guide new users through their first bond creation without interrupting expert users.

Pattern

  • Show a dismissible coach mark on first Bond page visit when the user has no existing bond.
  • Anchor the coach mark to the bond form area and primary CTA, not as a modal overlay.
  • Provide a complementary inline helper text in the form header or description so the experience is accessible and lightweight.
  • Avoid blocking expert users: the coach mark is optional and can be closed immediately.

Copy and structure

  • Title: "Create your first bond"
  • Description: "Lock USDC into Credence to build your economic reputation. Start with a small amount and your bond will show up in your dashboard."
  • Primary action: "Got it" or "Start bond"
  • Secondary action: "Not now" or close icon
  • Optional inline helper: "Bonds are locked for a minimum of 30 days; early withdrawal may incur a penalty."

Accessibility requirements

  • The coach mark must be reachable by keyboard and screen readers.
  • It must not trap focus. Focus should remain in the main form flow or move naturally to the close button.
  • Use an accessible label and descriptive text, e.g. aria-describedby="bond-onboarding-desc".
  • Support dismissal via keyboard (Tab, Enter, Space, Escape).
  • If the coach mark is visible, the page remains fully navigable and the user can interact with the form behind it.

Dismissal and persistence

  • Allow users to dismiss the coach mark with a close button or primary acknowledgment action.
  • Persist dismissal state only after legal/privacy review. For design purposes, the recommended implementation is localStorage with a namespaced key such as credence.bondOnboardingDismissed.
  • Do not show the coach mark again after dismissal, unless the user explicitly resets onboarding in settings or clears site data.
  • Do not persist dismissal for authenticated preferences unless privacy requirements allow it.

When to show it

  • First time a wallet user lands on /bond and has no active bonds recorded.
  • Do not show on subsequent visits after dismissal.
  • Do not show after the user has already created a bond.
  • On page refresh, if the coach mark was previously dismissed, keep it hidden.

Developer guidance (design-only)

  • Prefer a non-modal toast-like coach mark positioned near the bond amount field and create button.
  • If a static prototype is desired, use a simple page overlay callout on the Bond page in the design doc rather than implementing runtime behavior.
  • Keep the bond form usable while the coach mark is visible.

2. No Trust Score Yet

When: Address has no trust score data Location: Trust Score page

<EmptyState
  illustration="trust"
  title="No trust score found"
  description="This address hasn't established a trust score yet. Create a bond and gather attestations to build reputation."
  action={{
    label: 'Learn how trust scores work',
    onClick: () => window.open('/docs/trust-score', '_blank'),
    variant: 'secondary',
  }}
/>

3. No Disputes

When: No disputes exist for the user Location: Activity/Governance page

<EmptyState
  illustration="dispute"
  title="No disputes"
  description="You have no active or past disputes. Your reputation remains intact."
/>

Note: No action needed - this is a positive state


4. No Attestations

When: User has no attestations Location: Trust Score details, Profile

<EmptyState
  illustration="attestation"
  title="No attestations yet"
  description="Attestations from trusted parties strengthen your reputation. Complete transactions and request attestations to build trust."
  action={{
    label: 'Request attestation',
    onClick: () => navigate('/attestations/request'),
  }}
/>

5. No Activity

When: No governance or activity history Location: Activity/Governance page

<EmptyState
  illustration="activity"
  title="No activity yet"
  description="Your governance participation and transaction history will appear here."
/>

Error States

Error states inform users when something goes wrong and help them recover.

Design Principles

  • Honest: Clearly state what went wrong
  • Helpful: Explain what the user can do
  • Non-technical: Avoid jargon and error codes in primary message
  • Tone: Apologetic but solution-focused

Core Error States

1. Network Failure

When: Unable to connect to Stellar network or backend Trigger: Network timeout, offline, DNS failure

<ErrorState
  type="network"
  action={{
    label: 'Try again',
    onClick: () => refetch(),
  }}
/>

Default Message: "Unable to connect to the network. Check your internet connection and try again."


2. Backend Error

When: API returns 500, service unavailable Trigger: Server error, maintenance

<ErrorState
  type="backend"
  title="Service temporarily unavailable"
  message="We're experiencing technical difficulties. Please try again in a few moments."
  action={{
    label: 'Retry',
    onClick: () => refetch(),
  }}
/>

3. Invalid Address

When: User enters malformed Stellar address Trigger: Validation failure on trust score lookup

<ErrorState
  type="validation"
  title="Invalid wallet address"
  message="Please enter a valid Stellar address starting with 'G' and containing 56 characters."
  action={{
    label: 'Go back',
    onClick: () => reset(),
  }}
/>

4. Transaction Failed

When: Blockchain transaction fails Trigger: Insufficient funds, rejected transaction

<ErrorState
  type="generic"
  title="Transaction failed"
  message="Your transaction could not be completed. Please check your wallet balance and try again."
  action={{
    label: 'Try again',
    onClick: () => retryTransaction(),
  }}
/>

Loading States

Loading states provide feedback during asynchronous operations.

Design Principles

  • Immediate: Show loading state instantly
  • Contextual: Match the content being loaded
  • Smooth: Use subtle animations
  • Consistent: Same patterns across the app

Loading Patterns

1. Form Loading

When: Submitting bond creation, attestation request Usage:

{
  isLoading ? <LoadingSkeleton variant="form" rows={3} /> : <BondForm />
}

2. Card/Dashboard Loading

When: Loading home highlights, stats cards Usage:

{
  isLoading ? <LoadingSkeleton variant="dashboard" rows={3} /> : <DashboardCards />
}

3. Table Loading

When: Loading activity history, dispute list Usage:

{
  isLoading ? <LoadingSkeleton variant="table" rows={5} /> : <ActivityTable />
}

4. Text/Content Loading

When: Loading descriptions, details Usage:

{
  isLoading ? <LoadingSkeleton variant="text" rows={3} /> : <TrustScoreDetails />
}

Implementation Guidelines

For Developers

  1. State Priority: Check states in this order:

    • Loading → Error → Empty → Content
  2. Composition Pattern:

function MyComponent() {
  const { data, isLoading, error } = useQuery()

  if (isLoading) return <LoadingSkeleton variant="card" />
  if (error) return <ErrorState type="network" />
  if (!data || data.length === 0) return <EmptyState {...emptyConfig} />

  return <Content data={data} />
}
  1. Accessibility:

    • Loading states should have role="status" and aria-live="polite"
    • Error states should have role="alert"
    • Empty states should be keyboard navigable if they have actions
  2. Testing:

    • Test each state independently
    • Verify transitions between states
    • Check responsive behavior

Microcopy Guidelines

Tone & Voice

  • Friendly: Use conversational language
  • Clear: Avoid ambiguity
  • Concise: Respect user's time
  • Helpful: Always suggest next steps

Length Guidelines

  • Titles: 3-6 words
  • Descriptions: 1-2 sentences (max 140 characters)
  • CTAs: 2-4 words, action verb + object

Examples

✅ Good:

  • "No bonds yet" / "Create your first bond"
  • "Connection lost" / "Check your network and retry"

❌ Avoid:

  • "You haven't created any bonds in the system yet" (too wordy)
  • "Error 500" (too technical)
  • "Oops! Something went wrong!" (overused, not helpful)

Figma Design Specs

Color Palette

Empty States:

  • Background: Contextual (blue for bond, purple for trust, etc.)
  • Text: #0f172a (title), #64748b (description)

Error States:

  • Background: #fef2f2
  • Border: #fee2e2
  • Text: #991b1b (title), #7f1d1d (description)
  • Button: #dc2626

Loading States:

  • Base: #f1f5f9
  • Shimmer: #e2e8f0

Spacing

  • Icon/Illustration: 64px diameter
  • Icon margin-bottom: 16px
  • Title margin-bottom: 8px
  • Description margin-bottom: 24px (if action present)
  • Padding: 48px 24px

Typography

  • Title: 18px, font-weight 600
  • Description: 14px, line-height 1.5
  • Button: 14px, font-weight 600

Validation Checklist

Before shipping, validate:

  • All empty states have clear CTAs (where appropriate)
  • Error messages are user-friendly (no technical jargon)
  • Loading skeletons match content layout
  • All states are responsive (mobile, tablet, desktop)
  • Microcopy follows tone guidelines
  • Accessibility attributes are present
  • States transition smoothly
  • Product team has approved CTAs and messaging


Error Boundary Strategy

Architecture

Two boundaries protect the app from white-screen crashes:

Layer Component Location Catches
Root (outermost) ErrorBoundary main.tsx wrapping <App /> Provider bootstrap errors
Route tree ErrorBoundary App.tsx wrapping <Suspense> Lazy-chunk failures, route render errors
Router-level RouteErrorPage errorElement on root <Route> Loader errors (data-router)

Class ErrorBoundary (src/components/ErrorBoundary.tsx)

import ErrorBoundary from './components/ErrorBoundary'

// Default fallback (ErrorState + retry + home link)
<ErrorBoundary>
  <SomeSubtree />
</ErrorBoundary>

// Custom fallback via render prop
<ErrorBoundary fallback={(error, reset) => (
  <div>
    <p>{error.message}</p>
    <button onClick={reset}>Retry</button>
  </div>
)}>
  <SomeSubtree />
</ErrorBoundary>

Retry behaviour: clicking "Try again" calls setState({ hasError: false, error: null }). React re-renders the children without a hard reload. If they throw again the boundary catches once more.

Telemetry hook: componentDidCatch currently logs to console.error. Replace the console.error call with your error monitoring SDK (Sentry, Datadog, etc.) before shipping.

RouteErrorPage (src/pages/RouteErrorPage.tsx)

Registered as errorElement on the root route. In the current BrowserRouter setup this serves as forward-compatible scaffolding; it becomes fully active if the app is migrated to createBrowserRouter. Handles 404 (route miss) and generic router errors with the same ErrorState visual.

Accessibility

  • The fallback renders an <h3> heading from ErrorState — screen readers announce the error.
  • A <a href="/">Go to home page</a> link ensures keyboard-only users have a navigation escape hatch even if JavaScript is broken.
  • The retry <button> is focus-visible styled via the shared class.

Bond Row Inline Slash-Exposure Disclosure

Each bond row in the Active Bonds list surfaces penalty exposure inline — before the user commits to withdrawing — via an accessible disclosure.

States per row

Bond status Disclosure control Panel content
locked "Show penalty" button (aria-expanded) Bond amount, 20% penalty amount, resulting balance
grace-period "Show penalty" button (aria-expanded) Bond amount, 10% penalty amount, resulting balance
active None (no expander) Static "No early-withdrawal penalty" message

Behaviour

  • The toggle button carries aria-expanded="false" when collapsed and aria-expanded="true" when open, pointing to the panel via aria-controls.
  • The panel uses role="region" with an aria-label identifying the bond.
  • The breakdown values (bond amount, penalty %, penalty USDC, resulting balance) are computed by computeWithdrawBreakdown — the same function used in the ConfirmDialog — so numbers are identical at both stages.
  • Active bonds show a "No early-withdrawal penalty" message in place of the expander to make the safe state explicit.
  • The row layout uses flexWrap: wrap so it reflows cleanly on narrow viewports.

Disclaimer placement

The existing page-level slash-exposure Banner remains above the bond list as a summary callout. The per-row disclosure is the detailed, bond-specific complement to that banner — not a replacement.


Future Enhancements

  • Custom illustrations for each empty state
  • Animated loading states
  • Progressive loading (show partial content)
  • Contextual help links in error states
  • A/B test different microcopy variations