SkateSpots/src/components/ShopBountyCard.tsx
shakespeare.diy e29d35f1e4 fix: restore full skate-spots source tree from misplaced src/src/ copy
The entire skate-spots codebase was accidentally copied into src/src/ and
public/public/ instead of src/ and public/ directly. This left the real
src/ directory as blank MKStack template stubs, explaining why the landing
page showed the default template instead of SkateSpots.

Changes made:
- Copied all files from src/src/ up to src/ (App.tsx, AppRouter.tsx, main.tsx,
  all pages, components, hooks, contexts, lib, i18n, test)
- Copied all files from public/public/ up to public/ (images, manifest, etc.)
- Deleted src/src/ and public/public/ nested directories
- Removed HelloWorld.tsx template stub from pages/
- Retained the previously fixed index.css (Tailwind v3 directives)

Full skate-spots feature set is now active:
- All 30+ routes (map, events, spots, coaches, marketplace, wallet, etc.)
- NWCProvider, LanguageProvider, PWAInstallPrompt, AmberCallbackHandler
- Skate community relays (goskatespots.nostr1.com, wotr.relatr.xyz, etc.)
- Outfit Variable font, Amber pre-check in main.tsx
- All hooks (useSkateSpots, useCoaches, useZaps, etc.)
- i18n support (en/es)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-15 22:18:28 +07:00

192 lines
5.1 KiB
TypeScript

import { Card, CardContent } from '@/components/ui/card';
import { Progress } from '@/components/ui/progress';
import {
Zap,
Check,
Circle,
Camera,
FileText,
MapPin,
Phone,
Clock,
} from 'lucide-react';
import { cn } from '@/lib/utils';
interface ShopBountyCardProps {
// Current form state to calculate potential reward
hasName: boolean;
hasAddress: boolean;
hasCoordinates: boolean;
hasDescription: boolean;
hasPhone: boolean;
hasWebsite: boolean;
hasHours: boolean;
photoCount: number;
}
interface QualityCheckItem {
id: string;
label: string;
points: number;
completed: boolean;
icon: React.ComponentType<{ className?: string }>;
}
export function ShopBountyCard({
hasName,
hasAddress,
hasCoordinates,
hasDescription,
hasPhone,
hasWebsite,
hasHours,
photoCount,
}: ShopBountyCardProps) {
const checks: QualityCheckItem[] = [
{
id: 'name',
label: 'Shop name',
points: 15,
completed: hasName,
icon: MapPin,
},
{
id: 'location',
label: 'Address & coordinates',
points: 20,
completed: hasAddress && hasCoordinates,
icon: MapPin,
},
{
id: 'photo1',
label: 'At least 1 photo',
points: 15,
completed: photoCount >= 1,
icon: Camera,
},
{
id: 'photo3',
label: '3+ photos',
points: 10,
completed: photoCount >= 3,
icon: Camera,
},
{
id: 'description',
label: 'Description',
points: 15,
completed: hasDescription,
icon: FileText,
},
{
id: 'contact',
label: 'Contact info',
points: 10,
completed: hasPhone || hasWebsite,
icon: Phone,
},
{
id: 'hours',
label: 'Opening hours',
points: 15,
completed: hasHours,
icon: Clock,
},
];
const completedChecks = checks.filter(c => c.completed);
const score = completedChecks.reduce((sum, c) => sum + c.points, 0);
const maxScore = checks.reduce((sum, c) => sum + c.points, 0);
const percentage = Math.round((score / maxScore) * 100);
const getScoreColor = () => {
if (percentage >= 80) return 'text-green-400';
if (percentage >= 50) return 'text-[#ccff00]';
if (percentage >= 30) return 'text-yellow-400';
return 'text-orange-400';
};
const getProgressColor = () => {
if (percentage >= 80) return 'bg-green-500';
if (percentage >= 50) return 'bg-[#ccff00]';
if (percentage >= 30) return 'bg-yellow-500';
return 'bg-orange-500';
};
const getScoreLabel = () => {
if (percentage >= 80) return 'Excellent!';
if (percentage >= 50) return 'Good';
if (percentage >= 30) return 'Basic';
return 'Getting started';
};
return (
<Card className="bg-gradient-to-br from-[#ccff00]/5 to-transparent border-[#ccff00]/20">
<CardContent className="p-4">
<div className="flex items-center justify-between mb-3">
<div className="flex items-center gap-2">
<Zap className="h-4 w-4 text-[#ccff00]" />
<span className="text-sm font-bold uppercase text-gray-300">Earning Potential</span>
</div>
<div className="flex items-center gap-2">
<span className={cn('text-sm font-bold', getScoreColor())}>
{getScoreLabel()}
</span>
<span className="text-xs text-gray-500">
{percentage}%
</span>
</div>
</div>
{/* Progress Bar */}
<div className="relative h-3 bg-gray-800 rounded-full overflow-hidden mb-4">
<div
className={cn('h-full transition-all duration-500 ease-out rounded-full', getProgressColor())}
style={{ width: `${percentage}%` }}
/>
</div>
{/* Checklist */}
<div className="space-y-2">
{checks.map((check) => {
const Icon = check.icon;
return (
<div
key={check.id}
className={cn(
'flex items-center gap-2 text-xs transition-all',
check.completed ? 'text-green-400' : 'text-gray-500'
)}
>
<div className="flex-shrink-0">
{check.completed ? (
<Check className="h-3.5 w-3.5" />
) : (
<Circle className="h-3.5 w-3.5" />
)}
</div>
<Icon className="h-3 w-3 flex-shrink-0" />
<span className={check.completed ? 'line-through opacity-70' : ''}>
{check.label}
</span>
<span className={cn(
'ml-auto text-[10px] font-bold',
check.completed ? 'text-green-400' : 'text-gray-600'
)}>
+{check.points}%
</span>
</div>
);
})}
</div>
{/* Tip */}
<div className="mt-4 pt-3 border-t border-gray-800">
<p className="text-[10px] text-gray-500">
💡 Higher quality = More zaps from the community!
</p>
</div>
</CardContent>
</Card>
);
}