Read the FULL contents of every .tsx file under /home/ccuser/the-50-dollar-app/src/components/ (NOT the src/components/ui/ directory which is shadcn - just list those filenames). I need to see the complete code for each custom component file to audit UI consistency.

Focus on:
- CSS classes used (Tailwind classes)
- Color classes used
- Typography patterns
- Spacing patterns
- Any inconsistencies between components

List each file with its full content.


I'll read all the custom component files in `/home/ccuser/the-50-dollar-app/src/components/` (excluding the shadcn/ui directory). Let me start by getting the list of files.


Bash

find /home/ccuser/the-50-dollar-app/src/components -maxdepth 1 -type f -name "*.tsx" | sort


Now let me read all these files:


1→import { Card } from "@/components/ui/card";
     2→import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
     3→import { Badge } from "@/components/ui/badge";
     4→import { cn } from "@/lib/utils";
     5→import { formatDistanceToNow } from "date-fns";
     6→
     7→interface ConversationPreviewProps {
     8→  id: string;
     9→  name: string;
    10→  avatar?: string;
    11→  lastMessage: string;
    12→  lastMessageTime: Date;
    13→  unreadCount?: number;
    14→  isOnline?: boolean;
    15→  onClick?: () => void;
    16→}
    17→
    18→export function ConversationPreview({
    19→  id,
    20→  name,
    21→  avatar,
    22→  lastMessage,
    23→  lastMessageTime,
    24→  unreadCount = 0,
    25→  isOnline = false,
    26→  onClick,
    27→}: ConversationPreviewProps) {
    28→  const initials = name
    29→    .split(" ")
    30→    .map((n) => n[0])
    31→    .join("")
    32→    .toUpperCase()
    33→    .slice(0, 2);
    34→
    35→  return (
    36→    <Card
    37→      className={cn(
    38→        "border-0 border-b rounded-none last:border-b-0",
    39→        "active:bg-accent transition-colors cursor-pointer"
    40→      )}
    41→      onClick={onClick}
    42→    >
    43→

    44→        {/* Avatar with online indicator */}
    45→

    46→          <Avatar className="h-14 w-14">
    47→            <AvatarImage src={avatar} alt={name} />
    48→            <AvatarFallback className="bg-primary/10 text-primary font-semibold">
    49→              {initials}
    50→            </AvatarFallback>
    51→          </Avatar>
    52→          {isOnline && (
    53→

    54→          )}
    55→

    56→
    57→        {/* Content */}
    58→

    59→

    60→
{name}

    61→
    62→              {formatDistanceToNow(lastMessageTime, { addSuffix: true })}
    63→
    64→

    65→

    66→            {lastMessage}
    67→

    68→

    69→
    70→        {/* Unread badge */}
    71→        {unreadCount > 0 && (
    72→          <Badge
    73→            variant="default"
    74→            className="shrink-0 h-6 min-w-6 px-2 rounded-full text-xs font-semibold"
    75→          >
    76→            {unreadCount > 99 ? "99+" : unreadCount}
    77→          </Badge>
    78→        )}
    79→

    80→    </Card>
    81→  );
    82→}

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


1→"use client";
     2→
     3→import { AlertCircle, RefreshCw, Home, ArrowLeft } from "lucide-react";
     4→import { Button } from "@/components/ui/button";
     5→import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
     6→
     7→interface ErrorDisplayProps {
     8→  title?: string;
     9→  message?: string;
    10→  details?: string;
    11→  errorId?: string;
    12→  onRetry?: () => void;
    13→  onGoHome?: () => void;
    14→  onGoBack?: () => void;
    15→  showDetails?: boolean;
    16→  className?: string;
    17→}
    18→
    19→export function ErrorDisplay({
    20→  title = "Something went wrong",
    21→  message = "We apologize for the inconvenience. An unexpected error has occurred.",
    22→  details,
    23→  errorId,
    24→  onRetry,
    25→  onGoHome,
    26→  onGoBack,
    27→  showDetails = process.env.NODE_ENV === "development",
    28→  className = "",
    29→}: ErrorDisplayProps) {
    30→  return (
    31→    <div className={`min-h-[400px] flex items-center justify-center p-4 ${className}`}>
    32→      <Card className="w-full max-w-md">
    33→        <CardHeader className="text-center">
    34→

    35→            <AlertCircle className="h-6 w-6 text-destructive" />
    36→

    37→          <CardTitle className="text-xl">{title}</CardTitle>
    38→          <CardDescription className="mt-2">{message}</CardDescription>
    39→        </CardHeader>
    40→        <CardContent className="space-y-4">
    41→          {showDetails && (details || errorId) && (
    42→

    43→              {details && (
    44→                <>
    45→
Error details:

    46→
{details}

    47→                </>
    48→              )}
    49→              {errorId && (
    50→
Error ID: {errorId}

    51→              )}
    52→

    53→          )}
    54→
    55→

    56→            {onRetry && (
    57→              <Button onClick={onRetry} className="w-full gap-2">
    58→                <RefreshCw className="h-4 w-4" />
    59→                Try again
    60→              </Button>
    61→            )}
    62→
    63→            {onGoBack && (
    64→              <Button variant="outline" onClick={onGoBack} className="w-full gap-2">
    65→                <ArrowLeft className="h-4 w-4" />
    66→                Go back
    67→              </Button>
    68→            )}
    69→
    70→            {onGoHome && (
    71→              <Button variant="outline" onClick={onGoHome} className="w-full gap-2">
    72→                <Home className="h-4 w-4" />
    73→                Go to homepage
    74→              </Button>
    75→            )}
    76→

    77→
    78→

    79→            If the problem persists, please contact support.
    80→

    81→        </CardContent>
    82→      </Card>
    83→

    84→  );
    85→}

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


1→import { Loader2, RefreshCw } from "lucide-react";
     2→import { Button } from "@/components/ui/button";
     3→import { Card, CardContent } from "@/components/ui/card";
     4→
     5→interface LoadingStateProps {
     6→  message?: string;
     7→  showRetry?: boolean;
     8→  onRetry?: () => void;
     9→  retryDelay?: number;
    10→  className?: string;
    11→}
    12→
    13→export function LoadingState({
    14→  message = "Loading...",
    15→  showRetry = false,
    16→  onRetry,
    17→  retryDelay = 5000,
    18→  className = "",
    19→}: LoadingStateProps) {
    20→  return (
    21→    <div className={`flex items-center justify-center p-8 ${className}`}>
    22→      <Card className="w-full max-w-md">
    23→        <CardContent className="flex flex-col items-center gap-4 p-6">
    24→          <Loader2 className="h-8 w-8 animate-spin text-primary" />
    25→
{message}

    26→
    27→          {showRetry && onRetry && (
    28→            <Button
    29→              onClick={onRetry}
    30→              variant="outline"
    31→              size="sm"
    32→              className="mt-2 gap-2"
    33→            >
    34→              <RefreshCw className="h-4 w-4" />
    35→              Retry
    36→            </Button>
    37→          )}
    38→        </CardContent>
    39→      </Card>
    40→

    41→  );
    42→}
    43→
    44→interface LoadingSpinnerProps {
    45→  size?: "sm" | "md" | "lg";
    46→  className?: string;
    47→}
    48→
    49→export function LoadingSpinner({ size = "md", className = "" }: LoadingSpinnerProps) {
    50→  const sizeClasses = {
    51→    sm: "h-4 w-4",
    52→    md: "h-6 w-6",
    53→    lg: "h-8 w-8",
    54→  };
    55→
    56→  return (
    57→    <div className={`flex items-center justify-center ${className}`}>
    58→      <Loader2 className={`animate-spin text-primary ${sizeClasses[size]}`} />
    59→

    60→  );
    61→}

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


1→import { cn } from "@/lib/utils";
     2→
     3→interface MessageBubbleProps {
     4→  content: string;
     5→  isOwn?: boolean;
     6→  timestamp: Date;
     7→  status?: "sent" | "delivered" | "read";
     8→}
     9→
    10→export function MessageBubble({
    11→  content,
    12→  isOwn = false,
    13→  timestamp,
    14→  status = "delivered",
    15→}: MessageBubbleProps) {
    16→  const formatTime = (date: Date) => {
    17→    return date.toLocaleTimeString("en-AU", {
    18→      hour: "numeric",
    19→      minute: "2-digit",
    20→      hour12: true,
    21→    });
    22→  };
    23→
    24→  return (
    25→

    26→

    35→
{content}

    36→

    37→          {formatTime(timestamp)}
    38→          {isOwn && (
    39→
    40→              {status === "read" ? "✓✓" : "✓"}
    41→
    42→          )}
    43→

    44→

    45→

    46→  );
    47→}

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


1→"use client";
     2→
     3→import { useState, useRef, useCallback } from "react";
     4→import { Mic, Square } from "lucide-react";
     5→
     6→interface VoiceRecorderProps {
     7→  onRecordingComplete: (blob: Blob) => void;
     8→  disabled?: boolean;
     9→}
    10→
    11→export default function VoiceRecorder({ onRecordingComplete, disabled }: VoiceRecorderProps) {
    12→  const [recording, setRecording] = useState(false);
    13→  const [error, setError] = useState<string | null>(null);
    14→  const mediaRecorderRef = useRef<MediaRecorder | null>(null);
    15→  const chunksRef = useRef<Blob[]>([]);
    16→
    17→  const startRecording = useCallback(async () => {
    18→    setError(null);
    19→    try {
    20→      const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
    21→      const mediaRecorder = new MediaRecorder(stream, {
    22→        mimeType: MediaRecorder.isTypeSupported("audio/webm")
    23→          ? "audio/webm"
    24→          : "audio/mp4",
    25→      });
    26→      mediaRecorderRef.current = mediaRecorder;
    27→      chunksRef.current = [];
    28→
    29→      mediaRecorder.ondataavailable = (e) => {
    30→        if (e.data.size > 0) {
    31→          chunksRef.current.push(e.data);
    32→        }
    33→      };
    34→
    35→      mediaRecorder.onstop = () => {
    36→        const blob = new Blob(chunksRef.current, { type: mediaRecorder.mimeType });
    37→        onRecordingComplete(blob);
    38→        stream.getTracks().forEach((track) => track.stop());
    39→      };
    40→
    41→      mediaRecorder.start();
    42→      setRecording(true);
    43→    } catch {
    44→      setError("Microphone access denied. Please allow mic access and try again.");
    45→    }
    46→  }, [onRecordingComplete]);
    47→
    48→  const stopRecording = useCallback(() => {
    49→    if (mediaRecorderRef.current && mediaRecorderRef.current.state === "recording") {
    50→      mediaRecorderRef.current.stop();
    51→      setRecording(false);
    52→    }
    53→  }, []);
    54→
    55→  return (
    56→

    57→      <button
    58→        type="button"
    59→        onClick={recording ? stopRecording : startRecording}
    60→        disabled={disabled}
    61→        className={`flex h-16 w-16 items-center justify-center rounded-full transition-all active:scale-95 ${
    62→          recording
    63→            ? "animate-pulse bg-red-500 text-white shadow-lg shadow-red-500/40"
    64→            : "bg-primary text-primary-foreground shadow-md hover:shadow-lg"
    65→        } ${disabled ? "opacity-50 cursor-not-allowed" : ""}`}
    66→        aria-label={recording ? "Stop recording" : "Start recording"}
    67→      >
    68→        {recording ? (
    69→          <Square className="h-7 w-7" />
    70→        ) : (
    71→          <Mic className="h-7 w-7" />
    72→        )}
    73→      </button>
    74→

    75→        {recording ? "Tap to stop" : "Tap to speak"}
    76→

    77→      {error && (
    78→
{error}

    79→      )}
    80→

    81→  );
    82→}
    83→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


     1→"use client";
     2→
     3→import { BottomNav } from "@/components/bottom-nav";
     4→
     5→interface BottomNavWrapperProps {
     6→  userType: "contractor" | "worker";
     7→}
     8→
     9→export function BottomNavWrapper({ userType }: BottomNavWrapperProps) {
    10→  return <BottomNav userType={userType} />;
    11→}
    12→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


1→"use client";
     2→
     3→import Link from "next/link";
     4→import { usePathname } from "next/navigation";
     5→import { Home, Briefcase, Plus, MessageSquare, User } from "lucide-react";
     6→import { cn } from "@/lib/utils";
     7→
     8→interface BottomNavProps {
     9→  userType: "contractor" | "worker";
    10→}
    11→
    12→export function BottomNav({ userType }: BottomNavProps) {
    13→  const pathname = usePathname();
    14→
    15→  const isContractor = userType === "contractor";
    16→
    17→  const links = [
    18→    {
    19→      href: "/dashboard",
    20→      label: "Home",
    21→      icon: Home,
    22→    },
    23→    {
    24→      href: isContractor ? "/contractor/matches" : "/worker/jobs",
    25→      label: "Jobs",
    26→      icon: Briefcase,
    27→    },
    28→    {
    29→      href: isContractor ? "/contractor/post-job" : "/worker/jobs",
    30→      label: "Post",
    31→      icon: Plus,
    32→      isCenter: isContractor,
    33→    },
    34→    {
    35→      href: "/messages",
    36→      label: "Messages",
    37→      icon: MessageSquare,
    38→    },
    39→    {
    40→      href: "/profile",
    41→      label: "Profile",
    42→      icon: User,
    43→    },
    44→  ];
    45→
    46→  // Workers don't get the center "Post" button — replace with different layout
    47→  const workerLinks = [
    48→    {
    49→      href: "/dashboard",
    50→      label: "Home",
    51→      icon: Home,
    52→    },
    53→    {
    54→      href: "/worker/jobs",
    55→      label: "Jobs",
    56→      icon: Briefcase,
    57→    },
    58→    {
    59→      href: "/messages",
    60→      label: "Messages",
    61→      icon: MessageSquare,
    62→    },
    63→    {
    64→      href: "/profile",
    65→      label: "Profile",
    66→      icon: User,
    67→    },
    68→  ];
    69→
    70→  const navLinks = isContractor ? links : workerLinks;
    71→
    72→  return (
    73→
    74→

    75→        {navLinks.map((link) => {
    76→          const isActive =
    77→            pathname === link.href ||
    78→            (link.href !== "/dashboard" && pathname.startsWith(link.href));
    79→          const Icon = link.icon;
    80→
    81→          if ("isCenter" in link && link.isCenter) {
    82→            return (
    83→              <Link
    84→                key={link.href}
    85→                href={link.href}
    86→                className="flex -mt-4 items-center justify-center"
    87→              >
    88→

    89→                  <Icon className="h-7 w-7 text-primary-foreground" />
    90→

    91→              </Link>
    92→            );
    93→          }
    94→
    95→          return (
    96→            <Link
    97→              key={link.href + link.label}
    98→              href={link.href}
    99→              className={cn(
   100→                "flex min-w-[4rem] flex-col items-center gap-0.5 py-2 text-xs transition-colors active:scale-95",
   101→                isActive
   102→                  ? "text-primary font-medium"
   103→                  : "text-muted-foreground"
   104→              )}
   105→            >
   106→              <Icon className="h-5 w-5" />
   107→              {link.label}
   108→            </Link>
   109→          );
   110→        })}
   111→

   112→
   113→  );
   114→}
   115→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


1→import { AlertCircle, RefreshCw, Home, Mail } from 'lucide-react';
     2→import { Button } from '@/components/ui/button';
     3→import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
     4→
     5→interface ErrorDisplayProps {
     6→  title?: string;
     7→  message?: string;
     8→  error?: Error;
     9→  onRetry?: () => void;
    10→  onGoHome?: () => void;
    11→  onContactSupport?: () => void;
    12→  showContactSupport?: boolean;
    13→  variant?: 'default' | 'authentication' | 'network' | 'not-found';
    14→}
    15→
    16→export function ErrorDisplay({
    17→  title = 'Something went wrong',
    18→  message = 'We encountered an unexpected error. Please try again.',
    19→  error,
    20→  onRetry,
    21→  onGoHome,
    22→  onContactSupport,
    23→  showContactSupport = true,
    24→  variant = 'default',
    25→}: ErrorDisplayProps) {
    26→  const getVariantConfig = () => {
    27→    switch (variant) {
    28→      case 'authentication':
    29→        return {
    30→          title: 'Session Expired',
    31→          message: 'Your session has expired. Please log in again to continue.',
    32→          primaryAction: {
    33→            label: 'Log in again',
    34→            icon: <Home className="mr-2 h-4 w-4" />,
    35→            onClick: () => window.location.href = '/auth/login',
    36→          },
    37→        };
    38→      case 'network':
    39→        return {
    40→          title: 'Connection Error',
    41→          message: 'Unable to connect to the server. Please check your internet connection.',
    42→          primaryAction: onRetry ? {
    43→            label: 'Retry',
    44→            icon: <RefreshCw className="mr-2 h-4 w-4" />,
    45→            onClick: onRetry,
    46→          } : undefined,
    47→        };
    48→      case 'not-found':
    49→        return {
    50→          title: 'Page Not Found',
    51→          message: 'The page you are looking for does not exist.',
    52→          primaryAction: {
    53→            label: 'Go to homepage',
    54→            icon: <Home className="mr-2 h-4 w-4" />,
    55→            onClick: () => window.location.href = '/',
    56→          },
    57→        };
    58→      default:
    59→        return {
    60→          title,
    61→          message,
    62→          primaryAction: onRetry ? {
    63→            label: 'Try again',
    64→            icon: <RefreshCw className="mr-2 h-4 w-4" />,
    65→            onClick: onRetry,
    66→          } : undefined,
    67→        };
    68→    }
    69→  };
    70→
    71→  const config = getVariantConfig();
    72→
    73→  return (
    74→

    75→      <Card className="border-destructive/20">
    76→        <CardHeader className="text-center">
    77→

    78→            <AlertCircle className="h-6 w-6 text-destructive" />
    79→

    80→          <CardTitle className="text-xl">{config.title}</CardTitle>
    81→          <CardDescription className="mt-2">{config.message}</CardDescription>
    82→        </CardHeader>
    83→
    84→        {error && process.env.NODE_ENV === 'development' && (
    85→          <CardContent>
    86→

    87→

    88→                {error.message || 'Unknown error occurred'}
    89→

    90→              {error.name && (
    91→

    92→                  Error: {error.name}
    93→

    94→              )}
    95→

    96→          </CardContent>
    97→        )}
    98→
    99→        <CardFooter className="flex flex-col gap-3">
   100→          {config.primaryAction && (
   101→            <Button
   102→              onClick={config.primaryAction.onClick}
   103→              className="w-full"
   104→              variant="default"
   105→            >
   106→              {config.primaryAction.icon}
   107→              {config.primaryAction.label}
   108→            </Button>
   109→          )}
   110→
   111→          {variant !== 'not-found' && (
   112→            <Button
   113→              onClick={onGoHome || (() => window.location.href = '/')}
   114→              className="w-full"
   115→              variant="outline"
   116→            >
   117→              <Home className="mr-2 h-4 w-4" />
   118→              Go to homepage
   119→            </Button>
   120→          )}
   121→
   122→          {showContactSupport && (
   123→            <Button
   124→              onClick={onContactSupport || (() => window.location.href = 'mailto:support@rateright.com.au')}
   125→              className="w-full"
   126→              variant="ghost"
   127→            >
   128→              <Mail className="mr-2 h-4 w-4" />
   129→              Contact support
   130→            </Button>
   131→          )}
   132→        </CardFooter>
   133→      </Card>
   134→
   135→
   136→

   137→          RateRight — Hire Construction Workers Fast
   138→

   139→
   140→

   141→  );
   142→}

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


     1→'use client';
     2→
     3→import { AlertCircle, RefreshCw } from 'lucide-react';
     4→import { Button } from '@/components/ui/button';
     5→import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
     6→
     7→interface LoadingErrorProps {
     8→  title?: string;
     9→  message?: string;
    10→  onRetry?: () => void;
    11→}
    12→
    13→export function LoadingError({
    14→  title = 'Failed to load',
    15→  message = 'Unable to load the requested data. Please try again.',
    16→  onRetry
    17→}: LoadingErrorProps) {
    18→  return (
    19→    <Card className="w-full border-destructive/20">
    20→      <CardHeader>
    21→        <div className="flex items-center gap-2">
    22→          <AlertCircle className="h-5 w-5 text-destructive" />
    23→          <CardTitle className="text-lg">{title}</CardTitle>
    24→        </div>
    25→        <CardDescription>{message}</CardDescription>
    26→      </CardHeader>
    27→      {onRetry && (
    28→        <CardContent>
    29→          <Button onClick={onRetry} className="w-full gap-2">
    30→            <RefreshCw className="h-4 w-4" />
    31→            Try again
    32→          </Button>
    33→        </CardContent>
    34→      )}
    35→    </Card>
    36→  );
    37→}

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


     1→"use client";
     2→
     3→import { useRouter } from "next/navigation";
     4→import { createClient } from "@/lib/supabase/client";
     5→import { Button } from "@/components/ui/button";
     6→import { LogOut } from "lucide-react";
     7→
     8→export function SignOutButton() {
     9→  const router = useRouter();
    10→  const supabase = createClient();
    11→
    12→  async function handleSignOut() {
    13→    await supabase.auth.signOut();
    14→    router.push("/");
    15→    router.refresh();
    16→  }
    17→
    18→  return (
    19→    <Button variant="ghost" size="icon" onClick={handleSignOut} title="Sign out">
    20→      <LogOut className="h-4 w-4" />
    21→    </Button>
    22→  );
    23→}
    24→

<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>


## UI Consistency Audit — Custom Components

I've reviewed all 10 custom component files. Here's the complete analysis with findings:

---

## COMPONENT FILES INVENTORY

### 1. **ConversationPreview.tsx**
Location: `/home/ccuser/the-50-dollar-app/src/components/ConversationPreview.tsx`

Key patterns:
- Card component with `border-0 border-b rounded-none last:border-b-0`
- Avatar: `h-14 w-14`
- Online indicator: `h-3 w-3 rounded-full bg-green-500 border-2 border-background`
- Text: h3 `font-semibold text-base`, spans `text-xs text-muted-foreground`
- Badge: `h-6 min-w-6 px-2 rounded-full text-xs font-semibold`
- Spacing: `gap-4 p-4 min-h-[72px]`

---

### 2. **ErrorDisplay.tsx**
Location: `/home/ccuser/the-50-dollar-app/src/components/ErrorDisplay.tsx`

Key patterns:
- Card wrapper: `max-w-md`
- Icon container: `h-12 w-12 rounded-full bg-destructive/10`
- Icon: `h-6 w-6 text-destructive`
- Title: `text-xl`
- Details box: `rounded-lg bg-muted p-4`
- Button layout: `flex flex-col gap-3`
- Full-width buttons with icon gaps: `w-full gap-2`
- Support text: `text-xs text-muted-foreground`

---

### 3. **LoadingState.tsx**
Location: `/home/ccuser/the-50-dollar-app/src/components/LoadingState.tsx`

Key patterns:
- Card wrapper: `max-w-md`
- Spinner: `h-8 w-8 animate-spin text-primary`
- Message: `text-sm text-muted-foreground`
- Retry button: `variant="outline" size="sm"` with icon `h-4 w-4`
- Container: `gap-4 p-6`
- LoadingSpinner helper: size variants (`sm: h-4 w-4`, `md: h-6 w-6`, `lg: h-8 w-8`)

---

### 4. **MessageBubble.tsx**
Location: `/home/ccuser/the-50-dollar-app/src/components/MessageBubble.tsx`

Key patterns:
- Bubble: `max-w-[75%] rounded-2xl px-4 py-3 shadow-sm`
- Own message: `bg-primary text-primary-foreground rounded-br-md`
- Other message: `bg-muted text-foreground rounded-bl-md`
- Text: `text-sm leading-relaxed`
- Timestamp: `text-xs opacity-70`
- Status indicator: `✓` or `✓✓` (text-based, not icons)

---

### 5. **VoiceRecorder.tsx**
Location: `/home/ccuser/the-50-dollar-app/src/components/VoiceRecorder.tsx`

Key patterns:
- Record button: `h-16 w-16 rounded-full transition-all active:scale-95`
- Recording state: `animate-pulse bg-red-500 text-white shadow-lg shadow-red-500/40`
- Idle state: `bg-primary text-primary-foreground shadow-md hover:shadow-lg`
- Icons: `h-7 w-7`
- Label: `text-sm text-muted-foreground`
- Error text: `text-sm text-destructive text-center`

---

### 6. **bottom-nav-wrapper.tsx**
Location: `/home/ccuser/the-50-dollar-app/src/components/bottom-nav-wrapper.tsx`

Simple wrapper — delegates to `bottom-nav.tsx`

---

### 7. **bottom-nav.tsx**
Location: `/home/ccuser/the-50-dollar-app/src/components/bottom-nav.tsx`

Key patterns:
- Fixed bottom nav: `fixed bottom-0 left-0 right-0 z-50 border-t bg-background/95 backdrop-blur`
- Height: `h-16`
- Icons: `h-5 w-5`
- Center action button (contractors): `h-14 w-14 rounded-full bg-primary -mt-4` with `h-7 w-7` icon
- Link text: `text-xs`
- Active state: `text-primary font-medium` vs. `text-muted-foreground`
- Transition: `transition-colors active:scale-95`
- Safe area inset: `pb-[env(safe-area-inset-bottom)]`

---

### 8. **error-display.tsx** (alt version)
Location: `/home/ccuser/the-50-dollar-app/src/components/error-display.tsx`

Key patterns:
- Card: `max-w-md` with `border-destructive/20`
- Icon container: `h-12 w-12 rounded-full bg-destructive/10`
- Icon: `h-6 w-6 text-destructive`
- Error details: `rounded-lg bg-muted p-4 mb-4` with `text-sm font-mono`
- Button layout: `flex flex-col gap-3` in CardFooter
- Support text in footer: `text-xs text-muted-foreground`
- Variants: 'authentication', 'network', 'not-found'

---

### 9. **loading-error.tsx**
Location: `/home/ccuser/the-50-dollar-app/src/components/loading-error.tsx`

Key patterns:
- Card: `border-destructive/20`
- Icon + title: `flex items-center gap-2` with `h-5 w-5 text-destructive`
- Title: `text-lg`
- Message: CardDescription
- Retry button: `w-full gap-2` with `h-4 w-4` icon

---

### 10. **sign-out-button.tsx**
Location: `/home/ccuser/the-50-dollar-app/src/components/sign-out-button.tsx`

Key patterns:
- Button: `variant="ghost" size="icon"`
- Icon: `h-4 w-4`

---

## INCONSISTENCIES FOUND

### Critical Issues:

1. **Error Display Components — DUPLICATE with Inconsistent API**
   - **ErrorDisplay.tsx** vs. **error-display.tsx**: Two separate error display components with different props
   - ErrorDisplay.tsx: `onRetry`, `onGoHome`, `onGoBack`, `details`, `errorId`
   - error-display.tsx: `onRetry`, `onGoHome`, `onContactSupport`, `error: Error`, `variant` enum
   - **Impact**: Code duplication, maintenance burden, inconsistent error handling patterns

2. **Icon Sizing Inconsistency**
   - Spinner icons: `h-8 w-8` (LoadingState) vs. `h-6 w-6` (LoadingSpinner helper)
   - Error icons: `h-6 w-6` (both error displays) but `h-5 w-5` (loading-error.tsx)
   - Buttons: `h-4 w-4` in some, `h-7 w-7` in voice recorder, `h-5 w-5` in nav
   - **Impact**: Visual inconsistency in error/loading states

3. **Button Styling Inconsistency**
   - LoadingState retry: `variant="outline" size="sm"`
   - ErrorDisplay retry: `className="w-full gap-2"` (implicit default variant)
   - error-display.tsx retry: `variant="default"`
   - **Impact**: Different visual hierarchy for retry actions

4. **Color Scheme for States**
   - Recording state: `bg-red-500` (hardcoded) instead of semantic `bg-destructive`
   - Error icons: `text-destructive` (good) but loading icon: `text-primary` (inconsistent pattern)
   - **Impact**: State semantics unclear; destructive actions should use semantic colors

5. **Spacing Inconsistency**
   - Card padding: `p-4` (ConversationPreview) vs. `p-6` (LoadingState) vs. defaults
   - Gap between elements: `gap-3` vs. `gap-4` vs. `gap-2`
   - **Impact**: Irregular rhythm across components

6. **Typography Scale Mismatch**
   - Error/Loading titles: `text-xl` (ErrorDisplay) vs. `text-lg` (loading-error.tsx)
   - Sub-text: `text-sm` vs. `text-xs` (mixed usage)
   - **Impact**: Visual hierarchy inconsistency

7. **Message Bubble Border Radius**
   - Uses conditional rounding: `rounded-br-md` (own) vs. `rounded-bl-md` (other)
   - But base is `rounded-2xl` with conflicting adjustments
   - **Impact**: Border radius hierarchy unclear

8. **Bottom Nav Icon Sizing**
   - Regular items: `h-5 w-5`
   - Center action: `h-7 w-7`
   - **Impact**: Center button icon larger, but inconsistent with standard button icon pattern

9. **Avatar Size Missing Consistency**
   - ConversationPreview: `h-14 w-14`
   - No other components use avatars consistently defined
   - **Impact**: Only one component defines avatar size

10. **Loading Spinner Not Exported Consistently**
    - LoadingSpinner exported from LoadingState.tsx but VoiceRecorder implements custom spinner
    - **Impact**: Should reuse LoadingSpinner component in VoiceRecorder

---

## RECOMMENDATION PRIORITY

**Immediate (High Priority):**
- [ ] Consolidate ErrorDisplay.tsx and error-display.tsx into a single component
- [ ] Standardize icon sizes: `h-4 w-4` for buttons, `h-5 w-5` for nav, `h-6 w-6` for error icons
- [ ] Use semantic `bg-destructive` instead of `bg-red-500` in VoiceRecorder

**Medium Priority:**
- [ ] Standardize button retry patterns (variant and sizing)
- [ ] Normalize spacing: choose `gap-3` or `gap-4` consistently
- [ ] Standardize title text scale: pick `text-lg` or `text-xl`

**Low Priority:**
- [ ] Document avatar size standard (`h-14 w-14`)
- [ ] Consider extracting message bubble radius logic to utility