import React, { useState, useEffect } from 'react';
import {
ShieldAlert, Radio, AlertTriangle, CheckCircle, MapPin, Eye,
Activity, ClipboardCheck, History, Search, Zap, OctagonX,
Check as CheckIcon, Image as ImageIcon, X, Sparkles, Coins, Trash2, Clock, QrCode, Download, Share2, ShieldCheck, Printer, Camera, Upload, ArrowUpRight, Loader2
} from 'lucide-react';
import { useLanguage } from '../contexts/LanguageContext';
import { Button } from '../components/ui/Button';
import { matchEmergencyReports } from '../services/geminiService';
import { StorageService } from '../services/storageService';
import { FTLStorageService } from '../services/ftl/storage';
import { FTLMission, Sighting, UserProfile } from '../types';
import confetti from 'canvas-confetti';
const FTLLogo = () => (
);
const QRGenerator = ({ onClose }: { onClose: () => void }) => {
const [qrData, setQrData] = useState({ name: '', contact: '', info: '', photo: '' });
const [step, setStep] = useState(1);
const [isGenerating, setIsGenerating] = useState(false);
// Using a public API for QR generation to ensure we have a valid image source for canvas
const getQRUrl = (data: string) => `https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=${encodeURIComponent(data)}&bgcolor=ffffff`;
const handlePhoto = (e: React.ChangeEvent) => {
const file = e.target.files?.[0];
if (file) {
const reader = new FileReader();
reader.onload = (ev) => setQrData(p => ({ ...p, photo: ev.target?.result as string }));
reader.readAsDataURL(file);
}
};
const generateSmartTag = async () => {
setIsGenerating(true);
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
if (!ctx) return;
// Card Dimensions
const width = 600;
const height = 900;
canvas.width = width;
canvas.height = height;
// 1. Background
const grd = ctx.createLinearGradient(0, 0, 0, height);
grd.addColorStop(0, '#ffffff');
grd.addColorStop(1, '#f8fafc');
ctx.fillStyle = grd;
ctx.fillRect(0, 0, width, height);
// 2. Header Color (Red)
ctx.fillStyle = '#dc2626';
ctx.fillRect(0, 0, width, 20);
// 3. User Photo (Circular)
if (qrData.photo) {
const img = new Image();
img.src = qrData.photo;
await new Promise((resolve) => { img.onload = resolve; });
ctx.save();
ctx.beginPath();
ctx.arc(width/2, 200, 120, 0, Math.PI * 2, true);
ctx.closePath();
ctx.clip();
ctx.drawImage(img, width/2 - 120, 80, 240, 240);
// Border
ctx.lineWidth = 10;
ctx.strokeStyle = '#dc2626';
ctx.stroke();
ctx.restore();
}
// 4. Text Info
ctx.textAlign = 'center';
// Name
ctx.fillStyle = '#0f172a';
ctx.font = 'bold 50px Inter, sans-serif';
ctx.fillText(qrData.name.toUpperCase(), width/2, 380);
// Label
ctx.fillStyle = '#dc2626';
ctx.font = 'bold 24px Inter, sans-serif';
ctx.fillText('EMERGENCY RESCUE TAG', width/2, 420);
// Info Box
ctx.fillStyle = '#f1f5f9';
ctx.beginPath();
ctx.roundRect(50, 450, 500, 100, 20);
ctx.fill();
ctx.fillStyle = '#475569';
ctx.font = 'italic 24px Inter, sans-serif';
// Simple text wrapping for info (first line only for simplicity in this demo)
const infoText = qrData.info.length > 35 ? qrData.info.substring(0, 35) + '...' : qrData.info;
ctx.fillText(`"${infoText}"`, width/2, 510);
// 5. QR Code
const qrJson = JSON.stringify({
n: qrData.name,
c: qrData.contact,
i: qrData.info,
app: 'Rudraksha'
});
const qrImg = new Image();
qrImg.crossOrigin = "Anonymous"; // Crucial for external images on canvas
qrImg.src = getQRUrl(qrJson);
await new Promise((resolve, reject) => {
qrImg.onload = resolve;
qrImg.onerror = resolve; // Continue even if QR fails (though it shouldn't)
});
ctx.drawImage(qrImg, width/2 - 100, 600, 200, 200);
// 6. Footer
ctx.fillStyle = '#0f172a';
ctx.font = 'bold 30px Inter, sans-serif';
ctx.fillText(qrData.contact, width/2, 850);
// Convert to Download
const link = document.createElement('a');
link.download = `Rudraksha-Tag-${qrData.name}.png`;
link.href = canvas.toDataURL('image/png');
link.click();
setIsGenerating(false);
confetti({ particleCount: 100, spread: 70, origin: { y: 0.6 } });
};
return (
Rescue Tag Studio
Protocol: QR Identification Generator
{step === 1 ? (
{qrData.photo ? (
<>
setQrData(p => ({...p, photo: ''}))} className="absolute top-4 right-4 p-2 bg-black/60 text-white rounded-full">
>
) : (
Upload Subject Photo
)}
Critical Info / Medical
setStep(2)} disabled={!qrData.name || !qrData.photo} className="w-full h-18 bg-red-600 hover:bg-red-700 text-lg font-black uppercase italic shadow-2xl shadow-red-600/30">
Generate Digital Tag
) : (
{qrData.name}
Secure Rescue ID
SCAN VIA RUDRAKSHA
{isGenerating ? : }
Download Tag
setStep(1)} className="text-[10px] font-black uppercase tracking-widest text-gray-500 hover:text-red-500 transition-colors">Modify Identification
)}
);
};
const Safety: React.FC = () => {
const { t } = useLanguage();
const [activeTab, setActiveTab] = useState<'feed' | 'my-logs' | 'vault'>('feed');
const [showFTLModal, setShowFTLModal] = useState(false);
const [showQRModal, setShowQRModal] = useState(false);
const [missions, setMissions] = useState([]);
const [selectedMission, setSelectedMission] = useState(null);
const [profile, setProfile] = useState(null);
const [reportMode, setReportMode] = useState<'lost' | 'found' | 'sighting'>('lost');
const [reportType, setReportType] = useState<'pet' | 'person' | 'object' | 'ai' | null>(null);
const [targetMissionId, setTargetMissionId] = useState(null);
const [isBroadcasting, setIsBroadcasting] = useState(false);
const [isSubmitted, setIsSubmitted] = useState(false);
const [selectedPhoto, setSelectedPhoto] = useState(null);
const [formData, setFormData] = useState({ name: '', description: '', location: '', contact: '' });
const [bountyAmount, setBountyAmount] = useState('');
const [potentialMatch, setPotentialMatch] = useState<{ mission: FTLMission, reasoning: string } | null>(null);
const [isCheckingMatch, setIsCheckingMatch] = useState(false);
useEffect(() => {
loadData();
const handleUpdate = () => loadData();
const handleDraft = (e: any) => {
const { status, category, description, location } = e.detail;
setReportMode(status);
setReportType(category);
setFormData(prev => ({
...prev,
description: description || '',
location: location || '',
name: description ? description.split(' ').slice(0, 2).join(' ') : ''
}));
setShowFTLModal(true);
};
window.addEventListener('rudraksha-ftl-update', handleUpdate);
window.addEventListener('rudraksha-ftl-draft', handleDraft);
return () => {
window.removeEventListener('rudraksha-ftl-update', handleUpdate);
window.removeEventListener('rudraksha-ftl-draft', handleDraft);
};
}, []);
const loadData = async () => {
const m = await FTLStorageService.getMissions();
const p = await StorageService.getProfile();
setMissions(m.sort((a, b) => b.timestamp - a.timestamp));
setProfile(p);
};
const handleFTLClick = (mode: 'lost' | 'found' | 'sighting' = 'lost', missionId?: string) => {
setReportMode(mode);
setReportType(null);
setTargetMissionId(missionId || null);
setIsSubmitted(false);
setIsBroadcasting(false);
setSelectedPhoto(null);
setPotentialMatch(null);
setFormData({ name: '', description: '', location: '', contact: '' });
setBountyAmount('');
setShowFTLModal(true);
};
const handleReportSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!profile) return;
const numericBounty = parseInt(bountyAmount) || 0;
if (reportMode === 'lost' && numericBounty > profile.points) {
alert("Insufficient Karma points for this reward.");
return;
}
setIsBroadcasting(true);
setTimeout(async () => {
if (reportMode === 'found') {
setIsCheckingMatch(true);
const matchResult = await matchEmergencyReports(formData.description, missions);
if (matchResult.matchId && matchResult.confidence > 60) {
const matched = missions.find(m => m.id === matchResult.matchId);
if (matched) setPotentialMatch({ mission: matched, reasoning: matchResult.reasoning });
}
setIsCheckingMatch(false);
}
if (reportMode === 'sighting' && targetMissionId) {
const sighting: Sighting = {
id: 's' + Date.now(),
userId: profile.id,
userName: profile.name,
time: 'Just now',
location: formData.location,
info: formData.description,
image: selectedPhoto || undefined,
timestamp: Date.now()
};
await FTLStorageService.addSighting(targetMissionId, sighting);
await StorageService.addPoints(50, 250, 'reward', `Valid FTL Sighting Logged`);
confetti({ particleCount: 80, origin: { y: 0.7 }, colors: ['#3b82f6'] });
} else {
const newMission: FTLMission = {
id: 'm' + Date.now(),
type: reportType === 'ai' ? 'person' : (reportType || 'pet'),
title: `${reportMode === 'lost' ? 'Lost' : 'Found'}: ${formData.name}`,
location: formData.location,
time: 'Just now',
status: 'active',
bounty: reportMode === 'lost' ? numericBounty : 0,
description: formData.description,
sightings: [],
image: selectedPhoto || 'https://images.unsplash.com/photo-1541339907198-e08756eaa539?q=80&w=400&auto=format&fit=crop',
userId: profile.id,
isLost: reportMode === 'lost',
timestamp: Date.now()
};
await FTLStorageService.saveMission(newMission);
if (newMission.bounty > 0) {
await StorageService.updateProfile({ points: profile.points - newMission.bounty });
}
if (reportMode === 'found') {
await StorageService.addPoints(100, 500, 'reward', `Reported Found Item: ${formData.name}`);
confetti({ particleCount: 150, origin: { y: 0.7 }, colors: ['#10b981', '#facc15'] });
}
}
await loadData();
setIsBroadcasting(false);
setIsSubmitted(true);
}, 2000);
};
const handleVerifySighting = async (missionId: string, sightingId: string, verified: boolean) => {
if (verified && selectedMission?.bounty && selectedMission.bounty > 0) {
if (confirm(`Confirm ${selectedMission.bounty} Karma Bounty to this finder? Action is irreversible.`)) {
const sighterId = selectedMission.sightings.find(s => s.id === sightingId)?.userId;
await FTLStorageService.verifySighting(missionId, sightingId, true);
await FTLStorageService.resolveMission(missionId);
if (sighterId) {
await StorageService.rewardUser(sighterId, selectedMission.bounty);
}
confetti({ particleCount: 150, spread: 100, colors: ['#fbbf24'] });
window.dispatchEvent(new Event('rudraksha-profile-update'));
alert("Reward Issued! Neural Ledger Updated.");
setSelectedMission(null);
await loadData();
return;
}
} else {
await FTLStorageService.verifySighting(missionId, sightingId, verified);
}
await loadData();
if (verified) {
confetti({ particleCount: 40, colors: ['#10b981'] });
setTimeout(async () => {
const reloaded = await FTLStorageService.getMissions();
const fresh = reloaded.find(m => m.id === missionId);
if (fresh) setSelectedMission({ ...fresh });
}, 200);
}
};
const handleResolveMission = async (missionId: string) => {
if (confirm("Confirm recovery? This mission will be marked as resolved.")) {
await FTLStorageService.resolveMission(missionId);
confetti({ particleCount: 150, origin: { y: 0.6 }, colors: ['#fbbf24', '#ef4444'] });
setSelectedMission(null);
await loadData();
}
};
const handleDeleteMission = async (mission: FTLMission) => {
if (confirm("Delete this listing? If you set a bounty, it will be refunded.")) {
await FTLStorageService.deleteMission(mission.id);
setSelectedMission(null);
await loadData();
}
};
const handlePhotoChange = (e: React.ChangeEvent) => {
const file = e.target.files?.[0];
if (file) {
const reader = new FileReader();
reader.onloadend = () => setSelectedPhoto(reader.result as string);
reader.readAsDataURL(file);
}
};
return (
Emergency Network Active
FIND THE LOST
{t("Rudraksha's specialized neighborhood response protocol. Support your community, report sightings, and earn Karma rewards for every verified recovery.", "Rudraksha's specialized neighborhood response protocol. Support your community, report sightings, and earn Karma rewards for every verified recovery.")}
handleFTLClick('lost')} className="bg-red-600 hover:bg-red-700 h-16 px-10 rounded-2xl text-xl font-black shadow-2xl shadow-red-600/40">
{t("Report Loss", "Report Loss")}
setShowQRModal(true)} className="h-16 px-10 rounded-2xl border-2 border-indigo-600/30 bg-indigo-50/5 hover:bg-indigo-500/10 text-indigo-400 font-black text-lg transition-all flex items-center gap-3 group">
{t("Generate Rescue Tag", "Generate Rescue Tag")}
handleFTLClick('sighting')}>
Tap Radar to Log Sighting
{[
{ id: 'feed', label: 'Active Feed', icon: Activity, color: 'text-red-500' },
{ id: 'my-logs', label: 'My Rescue Logs', icon: ClipboardCheck, color: 'text-indigo-500' },
{ id: 'vault', label: 'Community Vault', icon: History, color: 'text-amber-500' }
].map(tab => (
setActiveTab(tab.id as any)}
className={`flex items-center gap-3 px-6 py-3 rounded-full text-sm font-black transition-all ${activeTab === tab.id ? 'bg-white dark:bg-gray-700 shadow-xl text-gray-900 dark:text-white' : 'text-gray-500 hover:text-gray-700 dark:hover:text-gray-300'}`}
>
{t(tab.label, tab.label)}
))}
{activeTab === 'feed' && (
{missions.filter(m => m.status === 'active').map(mission => (
setSelectedMission(mission)} className="w-full h-64 rounded-[2rem] overflow-hidden shrink-0 shadow-lg relative cursor-pointer">
Live Mission
{mission.bounty > 0 && (
Bounty: {mission.bounty}
)}
{mission.type}
{mission.time}
handleFTLClick('sighting', mission.id)} className="bg-blue-600 hover:bg-blue-700 text-white p-2.5 rounded-2xl shadow-lg transition-transform active:scale-95 flex items-center gap-2 px-4">
Log Sighting
setSelectedMission(mission)}>{mission.title}
{mission.description}
))}
)}
{activeTab === 'my-logs' && (
My Broadcast History
{missions.filter(m => m.userId === profile?.id).length} Entries
{missions.filter(m => m.userId === profile?.id).length === 0 ? (
No loss reports broadcasted by you.
) : (
missions.filter(m => m.userId === profile?.id).map(mission => (
setSelectedMission(mission)}>
{mission.title}
{mission.status} • {mission.sightings.length} Sightings
))
)}
)}
{selectedMission && (
setSelectedMission(null)}>
{selectedMission.bounty > 0 && (
Reward: {selectedMission.bounty} Karma
)}
{selectedMission.title}
{selectedMission.location}
Mission Status
{selectedMission.status}
setSelectedMission(null)} className="p-3 bg-gray-100 dark:bg-gray-800 rounded-full hover:bg-red-500 hover:text-white transition-all">
Detailed Protocol
"{selectedMission.description}"
Community Sightings
handleFTLClick('sighting', selectedMission.id)} className="bg-blue-600 text-white px-3 py-1 rounded-lg text-[10px] font-black uppercase hover:bg-blue-700 transition-colors">
+ Add Info
{selectedMission.sightings.length}
{selectedMission.sightings.map(s => (
{s.location}
By {s.userName || 'Anonymous'}
{(s as any).isVerified &&
Verified }
{s.info}
{s.time}
{selectedMission.userId === profile?.id && !(s as any).isVerified && (
handleVerifySighting(selectedMission.id, s.id, true)} className="flex items-center gap-1 text-green-500 hover:text-green-600 font-black uppercase tracking-widest bg-green-50 dark:bg-green-950 p-2 rounded-xl border border-green-200 transition-all active:scale-95">
{selectedMission.bounty > 0 ? "Confirm & Reward" : "Confirm"}
)}
))}
{selectedMission.userId === profile?.id && (
{selectedMission.status === 'active' && (
handleResolveMission(selectedMission.id)} className="flex-1 h-14 bg-green-600 hover:bg-green-700 text-lg font-black uppercase italic shadow-2xl shadow-green-600/30">
Mark Resolved
)}
handleDeleteMission(selectedMission)} variant="ghost" className="h-14 w-14 rounded-2xl bg-red-50 text-red-500 hover:bg-red-100 hover:text-red-600">
)}
)}
{showFTLModal && (
setShowFTLModal(false)}>
{reportMode === 'lost' ?
: reportMode === 'found' ?
: }
MISSION COMMAND
Protocol: {reportMode} entry
setShowFTLModal(false)} className="p-3 hover:bg-red-500/20 text-gray-500 hover:text-red-500 rounded-full transition-all active:scale-95">
{!reportType && reportMode !== 'sighting' ? (
{['person', 'pet', 'object', 'ai'].map(type => (
setReportType(type as any)} className="bg-[#1a1a1a] p-10 rounded-[2.5rem] border-2 border-gray-800 hover:border-blue-600 transition-all text-left">
{type}
Report {reportMode} {type}.
))}
) : isBroadcasting ? (
) : isCheckingMatch ? (
AI GUARDIAN SCANNING...
Comparing your description with existing lost reports.
) : isSubmitted ? (
{potentialMatch ? (
Match Detected!
Your Found Item
"{formData.description}"
Matched Lost Report
"{potentialMatch.mission.title}"
"{potentialMatch.mission.description}"
"AI Logic: {potentialMatch.reasoning}"
setSelectedMission(potentialMatch.mission)}>Contact Owner Immediately
) : (
LOGGED
setShowFTLModal(false)} className="w-full h-20 text-2xl font-black">RETURN
)}
) : (
)}
)}
{showQRModal &&
setShowQRModal(false)} />}
);
};
export default Safety;