PART 9 OF 14

Trust & Verification - Building User Confidence

December 1, 2025
10 min read

The Trust Problem

Food security is personal. When someone is hungry, they need confidence that:

  • The food bank exists and is open
  • The hours shown are current
  • The address is correct
  • The services listed are actually available

Bad data doesn't just inconvenience users - it can cause real harm (wasted trips, missed meals).

Phase 3 focused on building user trust through transparency and verification.

Verification Badges

Not all data sources are equal. I created a visual hierarchy:

// src/components/foodshare/verification-badge.tsx
export function VerificationBadge({ status, lastVerified }: Props) {
  const badges = {
    official: {
      label: "Official",
      icon: <ShieldCheck className="h-3 w-3" />,
      color: "text-green-600 bg-green-50 border-green-200",
      description: "Verified by organization staff",
    },
    community_verified: {
      label: "Community Verified",
      icon: <Users className="h-3 w-3" />,
      color: "text-blue-600 bg-blue-50 border-blue-200",
      description: "Confirmed by multiple community members",
    },
    ai_discovered: {
      label: "AI Discovered",
      icon: <Sparkles className="h-3 w-3" />,
      color: "text-purple-600 bg-purple-50 border-purple-200",
      description: "Found by automated discovery, pending verification",
    },
    pending: {
      label: "Pending",
      icon: <Clock className="h-3 w-3" />,
      color: "text-gray-600 bg-gray-50 border-gray-200",
      description: "Awaiting verification",
    },
  };

  const badge = badges[status] || badges.pending;

  return (
    <TooltipProvider>
      <Tooltip>
        <TooltipTrigger>
          <Badge variant="outline" className={badge.color}>
            {badge.icon}
            <span className="ml-1">{badge.label}</span>
          </Badge>
        </TooltipTrigger>
        <TooltipContent>
          <p>{badge.description}</p>
          {lastVerified && (
            <p className="text-xs mt-1">
              Last verified {formatDistanceToNow(lastVerified)} ago
            </p>
          )}
        </TooltipContent>
      </Tooltip>
    </TooltipProvider>
  );
}

Users could see at a glance the data provenance.

Source Attribution

Transparency required showing where data came from:

-- Schema update
ALTER TABLE food_banks
ADD COLUMN sources JSONB,
ADD COLUMN discovery_method TEXT,
ADD COLUMN discovered_by TEXT,
ADD COLUMN admin_verified_at TIMESTAMP;
// src/components/foodshare/sources-section.tsx
export function SourcesSection({ resource }: Props) {
  return (
    <Accordion type="single" collapsible>
      <AccordionItem value="sources">
        <AccordionTrigger>
          <div className="flex items-center gap-2">
            <ExternalLink className="h-4 w-4" />
            Data Sources
          </div>
        </AccordionTrigger>
        <AccordionContent>
          <div className="space-y-2">
            {resource.sources?.map((source, idx) => (
              <div key={idx} className="flex items-center justify-between text-sm">
                <span className="text-muted-foreground">
                  {getFriendlySourceName(source)}
                </span>
                <Button asChild variant="ghost" size="sm">
                  <a href={source} target="_blank" rel="noopener noreferrer">
                    View
                    <ExternalLink className="ml-1 h-3 w-3" />
                  </a>
                </Button>
              </div>
            ))}

            <Separator className="my-2" />

            <p className="text-xs text-muted-foreground">
              Discovery: {resource.discoveryMethod || "Manual"}
            </p>
            {resource.adminVerifiedAt && (
              <p className="text-xs text-muted-foreground">
                Verified {formatDistanceToNow(resource.adminVerifiedAt)} ago
              </p>
            )}
          </div>
        </AccordionContent>
      </AccordionItem>
    </Accordion>
  );
}

This section showed:

  • Original source URLs (Feeding America, 211, etc.)
  • How the resource was discovered (Tavily scan, manual entry, user submission)
  • When it was last verified

Data Completeness Indicator

Users needed to know if a resource had complete information:

// src/components/foodshare/completeness-indicator.tsx
export function CompletenessIndicator({ resource }: Props) {
  const fields = [
    { label: "Phone", value: resource.phone },
    { label: "Website", value: resource.website },
    { label: "Hours", value: resource.hours },
    { label: "Description", value: resource.description },
    { label: "Services", value: resource.services?.length },
  ];

  const filled = fields.filter((f) => f.value != null).length;
  const percentage = Math.round((filled / fields.length) * 100);

  const color =
    percentage >= 80
      ? "bg-green-500"
      : percentage >= 50
      ? "bg-yellow-500"
      : "bg-gray-300";

  return (
    <div className="space-y-2">
      <div className="flex items-center justify-between text-sm">
        <span className="text-muted-foreground">Data Completeness</span>
        <span className="font-medium">{percentage}%</span>
      </div>

      <div className="h-2 bg-gray-100 rounded-full overflow-hidden">
        <div
          className={`h-full ${color} transition-all duration-300`}
          style={{ width: `${percentage}%` }}
        />
      </div>

      <Accordion type="single" collapsible>
        <AccordionItem value="details" className="border-none">
          <AccordionTrigger className="text-xs text-muted-foreground py-2">
            View Details
          </AccordionTrigger>
          <AccordionContent>
            <div className="space-y-1">
              {fields.map((field) => (
                <div key={field.label} className="flex items-center gap-2 text-xs">
                  {field.value ? (
                    <Check className="h-3 w-3 text-green-600" />
                  ) : (
                    <X className="h-3 w-3 text-gray-400" />
                  )}
                  <span>{field.label}</span>
                </div>
              ))}
            </div>
          </AccordionContent>
        </AccordionItem>
      </Accordion>
    </div>
  );
}

This visual indicator helped users choose between multiple nearby resources - preferring those with complete data.

Map Deep Linking

Trust required verifiability. Users needed to see resources on a map:

// Community widget linking to map
<Button asChild variant="outline">
  <Link href={`/map?resource=${resource.id}`}>
    <MapPin className="h-4 w-4 mr-2" />
    View on Map
  </Link>
</Button>

The map page supported deep linking:

// src/app/map/pageClient.tsx
export default function MapPageClient({ initialResources }: Props) {
  const searchParams = useSearchParams();
  const resourceId = searchParams.get('resource');

  useEffect(() => {
    if (resourceId) {
      const resource = initialResources.find((r) => r.id === resourceId);
      if (resource) {
        // Auto-select and center on resource
        setSelectedResource(resource.id);
        setCenter([resource.longitude, resource.latitude]);
        setZoom(15); // Zoom in to street level
      }
    }
  }, [resourceId]);

  // ...
}

Clicking "View on Map" auto-centered and selected the resource - instant context.

Community Resource Widget

The community page showed nearby resources:

// src/app/community/components/sidebar/resources-near-you.tsx
export async function ResourcesNearYou() {
  const coords = await getUserLocation(); // From session or IP

  const nearby = await searchFoodBanks({
    latitude: coords.lat,
    longitude: coords.lng,
    radiusMiles: 5,
    limit: 5,
  });

  return (
    <Card>
      <CardHeader>
        <CardTitle className="text-lg">Resources Near You</CardTitle>
      </CardHeader>
      <CardContent className="space-y-3">
        {nearby.map((resource) => (
          <div
            key={resource.id}
            className="flex items-start gap-3 p-2 rounded hover:bg-muted/50 transition"
          >
            <MapPin className="h-4 w-4 mt-0.5 text-muted-foreground" />
            <div className="flex-1 min-w-0">
              <p className="font-medium text-sm truncate">{resource.name}</p>
              <p className="text-xs text-muted-foreground">
                {calculateDistance(coords.lat, coords.lng, resource.latitude, resource.longitude).toFixed(1)} miles
              </p>
            </div>
            <VerificationBadge status={resource.verificationStatus} />
          </div>
        ))}

        <Button asChild variant="outline" className="w-full">
          <Link href="/map">
            View All on Map
          </Link>
        </Button>
      </CardContent>
    </Card>
  );
}

This widget surfaced trusted, nearby resources without leaving the community page.

What Went Right

  1. Verification Badges: Clear visual hierarchy built trust instantly

  2. Source Attribution: Transparency about data origin reduced skepticism

  3. Completeness Indicators: Helped users choose between resources

  4. Deep Linking: Made resources verifiable across pages

  5. Community Integration: Resources widget connected discovery and community

What I'd Do Differently

Mistake 1: No User Contribution Flow

Users couldn't suggest updates or corrections. This required admin intervention for every error.

Mistake 2: Static Verification Status

Verification didn't expire. A resource verified 6 months ago might be outdated.

Mistake 3: No "Report Issue" Flow

Users who found incorrect data had no way to flag it easily.

What I Learned

  1. Trust Is Earned Visually: Badges and indicators matter more than disclaimers

  2. Transparency Reduces Skepticism: Showing sources built credibility

  3. Completeness Matters: Users prefer detailed resources over numerous sparse ones

  4. Deep Linking Enables Verification: Let users confirm data themselves

  5. Integration Beats Isolation: Resources everywhere (map, community, chat) increased usage

Up Next

In Part 10, I'll cover production deployment to Vercel - database configuration, authentication fixes, and going live.


Key Commits: Phase 3 core features

Related Files:

  • src/components/foodshare/verification-badge.tsx - Verification badge
  • src/components/foodshare/sources-section.tsx - Source attribution
  • src/components/foodshare/completeness-indicator.tsx - Completeness UI

JH

Jordan Hindo

Full-stack Developer & AI Engineer building in public. Exploring the future of agentic coding and AI-generated assets.

Get in touch