feat: add Jordan 3D topographic platform, Siro Map cinematic orbit studio, sun-shadow simulation, and sovereign intelligence security gating
This commit is contained in:
+179
-3
@@ -4,6 +4,9 @@ import { Navigation, Compass, Activity, BarChart3, MapPin, Eye, Shield, Gauge, C
|
||||
import { decodePolyline } from './utils/polyline';
|
||||
import WeatherPanel from './components/WeatherPanel';
|
||||
import { LineOfSightTool } from './components/LineOfSightTool';
|
||||
import { LandmarkCard, HeritageLandmarkData } from './components/LandmarkCard';
|
||||
import { SubmitContributionModal } from './components/SubmitContributionModal';
|
||||
import { GuidesModerationPanel } from './components/GuidesModerationPanel';
|
||||
|
||||
const DEFAULT_API_KEY = (import.meta as any).env.VITE_API_KEY || localStorage.getItem('intaleq_api_key') || '';
|
||||
|
||||
@@ -30,6 +33,14 @@ function App() {
|
||||
const [weatherCity, setWeatherCity] = useState<any>(null);
|
||||
const [weatherAlerts, setWeatherAlerts] = useState<any[]>([]);
|
||||
|
||||
// Tourism & Heritage State
|
||||
const [tourismMode, setTourismMode] = useState(true);
|
||||
const [heritageLandmarks, setHeritageLandmarks] = useState<HeritageLandmarkData[]>([]);
|
||||
const [selectedLandmark, setSelectedLandmark] = useState<HeritageLandmarkData | null>(null);
|
||||
const [showContributeModal, setShowContributeModal] = useState(false);
|
||||
const [contributeTargetLandmark, setContributeTargetLandmark] = useState<HeritageLandmarkData | null>(null);
|
||||
const [showGuidesPanel, setShowGuidesPanel] = useState(false);
|
||||
|
||||
// Geocoding State
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [searchResults, setSearchResults] = useState<any[]>([]);
|
||||
@@ -40,6 +51,36 @@ function App() {
|
||||
const [originText, setOriginText] = useState('31.9539, 35.9106');
|
||||
const [destText, setDestText] = useState('32.0608, 36.1032');
|
||||
|
||||
const fetchHeritageLandmarks = async () => {
|
||||
try {
|
||||
const apiUrl = (import.meta as any).env.VITE_API_URL || '/api';
|
||||
const res = await fetch(`${apiUrl}/v1/heritage/landmarks`, {
|
||||
headers: { 'x-api-key': DEFAULT_API_KEY }
|
||||
});
|
||||
if (res.ok) {
|
||||
const data = await res.json();
|
||||
setHeritageLandmarks(Array.isArray(data) ? data : []);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Failed to fetch heritage landmarks', e);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchHeritageLandmarks();
|
||||
}, []);
|
||||
|
||||
const handleNavigateToGate = (lat: number, lng: number, name: string) => {
|
||||
setDestText(`${lat.toFixed(5)}, ${lng.toFixed(5)}`);
|
||||
if (map) {
|
||||
const center = map.getCenter();
|
||||
setOriginText(`${center.lat.toFixed(5)}, ${center.lng.toFixed(5)}`);
|
||||
}
|
||||
setTimeout(() => {
|
||||
calculateRoute();
|
||||
}, 150);
|
||||
};
|
||||
|
||||
const regions = [
|
||||
{
|
||||
name: 'Syria',
|
||||
@@ -59,6 +100,15 @@ function App() {
|
||||
defaultOrigin: '31.9539, 35.9106', // Amman
|
||||
defaultDest: '32.0608, 36.1032' // Zarqa
|
||||
},
|
||||
{
|
||||
name: 'Iraq',
|
||||
name_ar: 'العراق',
|
||||
center: [44.3661, 33.3152],
|
||||
zoom: 11,
|
||||
flag: '🇮🇶',
|
||||
defaultOrigin: '33.3152, 44.3661', // Baghdad
|
||||
defaultDest: '32.0000, 44.4000' // Karbala / Babil
|
||||
},
|
||||
{
|
||||
name: 'Egypt',
|
||||
name_ar: 'مصر',
|
||||
@@ -112,12 +162,24 @@ function App() {
|
||||
headers: { 'x-api-key': DEFAULT_API_KEY }
|
||||
});
|
||||
const data = await response.json();
|
||||
let results = data.results || [];
|
||||
|
||||
setSearchResults(data.results || []);
|
||||
// If no local results within 50km, fall back to nationwide/global search
|
||||
if (results.length === 0) {
|
||||
const fallbackRes = await fetch(`${apiUrl}/geocoding/search?q=${encodeURIComponent(searchQuery)}`, {
|
||||
headers: { 'x-api-key': DEFAULT_API_KEY }
|
||||
});
|
||||
const fallbackData = await fallbackRes.json();
|
||||
if (fallbackData.results && fallbackData.results.length > 0) {
|
||||
results = fallbackData.results;
|
||||
}
|
||||
}
|
||||
|
||||
setSearchResults(results);
|
||||
setShowResults(true);
|
||||
|
||||
if (data.results && data.results.length > 0 && map) {
|
||||
const place = data.results[0];
|
||||
if (results && results.length > 0 && map) {
|
||||
const place = results[0];
|
||||
const placeLng = parseFloat(place.longitude);
|
||||
const placeLat = parseFloat(place.latitude);
|
||||
if (!isNaN(placeLat) && !isNaN(placeLng)) {
|
||||
@@ -277,6 +339,76 @@ function App() {
|
||||
<h1>{currentRegion} Maps SaaS</h1>
|
||||
<p style={{ color: 'var(--text-muted)', fontSize: '0.8rem' }}>Intaleq Mobility & Maps Cloud</p>
|
||||
|
||||
{/* Siro Tourism & Heritage Mode Toggle */}
|
||||
<div style={{ display: 'flex', gap: '8px', margin: '14px 0 6px' }}>
|
||||
<button
|
||||
onClick={() => setTourismMode(!tourismMode)}
|
||||
style={{
|
||||
flex: 1.2,
|
||||
padding: '10px 8px',
|
||||
borderRadius: '10px',
|
||||
border: tourismMode ? '1.5px solid #d4af37' : '1px solid var(--glass-border)',
|
||||
background: tourismMode
|
||||
? 'linear-gradient(135deg, rgba(181, 146, 71, 0.3) 0%, rgba(212, 175, 55, 0.15) 100%)'
|
||||
: 'rgba(255,255,255,0.04)',
|
||||
color: tourismMode ? '#fef08a' : '#e2e8f0',
|
||||
fontWeight: 800,
|
||||
fontSize: '0.78rem',
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: '6px',
|
||||
transition: 'all 0.2s',
|
||||
boxShadow: tourismMode ? '0 4px 14px rgba(212, 175, 55, 0.25)' : 'none',
|
||||
}}
|
||||
>
|
||||
<span>🏛️</span>
|
||||
<span>{tourismMode ? 'الخريطة السياحية (مفعلة)' : 'النمط السياحي والآثار'}</span>
|
||||
</button>
|
||||
|
||||
<button
|
||||
onClick={() => setShowGuidesPanel(true)}
|
||||
style={{
|
||||
flex: 0.8,
|
||||
padding: '10px 8px',
|
||||
borderRadius: '10px',
|
||||
border: '1px solid var(--glass-border)',
|
||||
background: 'rgba(255,255,255,0.04)',
|
||||
color: '#38bdf8',
|
||||
fontWeight: 700,
|
||||
fontSize: '0.78rem',
|
||||
cursor: 'pointer',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
gap: '4px',
|
||||
}}
|
||||
title="مجتمع المرشدين وطابور المراجعة"
|
||||
>
|
||||
<span>🌟</span>
|
||||
<span>المرشدين</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{tourismMode && (
|
||||
<div style={{
|
||||
background: 'rgba(181, 146, 71, 0.1)',
|
||||
border: '1px solid rgba(181, 146, 71, 0.3)',
|
||||
borderRadius: '10px',
|
||||
padding: '8px 10px',
|
||||
fontSize: '0.75rem',
|
||||
color: '#fef08a',
|
||||
marginBottom: '10px',
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
}}>
|
||||
<span>✨ معالم التراث والآثار ({heritageLandmarks.length})</span>
|
||||
<span style={{ fontSize: '0.7rem', color: '#cbd5e1' }}>انقر لأي معلم لمشاهدة البوابات</span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Region Selector */}
|
||||
<div className="region-selector" style={{ display: 'flex', gap: '8px', margin: '15px 0' }}>
|
||||
{regions.map(r => (
|
||||
@@ -621,8 +753,52 @@ function App() {
|
||||
losActive={showLOS}
|
||||
losPointA={losPointA}
|
||||
losPointB={losPointB}
|
||||
tourismMode={tourismMode}
|
||||
heritageLandmarks={heritageLandmarks}
|
||||
selectedLandmark={selectedLandmark}
|
||||
onSelectLandmark={setSelectedLandmark}
|
||||
/>
|
||||
|
||||
{/* Interactive Landmark Card */}
|
||||
{selectedLandmark && (
|
||||
<LandmarkCard
|
||||
landmark={selectedLandmark}
|
||||
onClose={() => setSelectedLandmark(null)}
|
||||
onNavigateToGate={handleNavigateToGate}
|
||||
onOpenContribute={(lm) => {
|
||||
setContributeTargetLandmark(lm);
|
||||
setShowContributeModal(true);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Field Contributor Submission Modal */}
|
||||
{showContributeModal && (
|
||||
<SubmitContributionModal
|
||||
landmark={contributeTargetLandmark}
|
||||
mapCenter={map ? map.getCenter() : undefined}
|
||||
apiKey={DEFAULT_API_KEY}
|
||||
onClose={() => {
|
||||
setShowContributeModal(false);
|
||||
setContributeTargetLandmark(null);
|
||||
}}
|
||||
onSuccess={() => {
|
||||
fetchHeritageLandmarks();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Community Guides & Moderation Slide-Over */}
|
||||
{showGuidesPanel && (
|
||||
<GuidesModerationPanel
|
||||
apiKey={DEFAULT_API_KEY}
|
||||
onClose={() => setShowGuidesPanel(false)}
|
||||
onContributionApproved={() => {
|
||||
fetchHeritageLandmarks();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
||||
{/* Tactical Line of Sight Tool Overlay */}
|
||||
<LineOfSightTool
|
||||
active={showLOS}
|
||||
|
||||
Reference in New Issue
Block a user