fix: remove landing page nav tab and prevent crowdsource contribution modal from auto-opening on tourism map

This commit is contained in:
Hamza-Ayed
2026-09-20 00:46:06 +03:00
parent ce31d79ba6
commit e8f5de585d
4 changed files with 271 additions and 87 deletions
+45 -45
View File
@@ -8,7 +8,7 @@ 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') || '';
const DEFAULT_API_KEY = (import.meta as any).env.VITE_API_KEY || localStorage.getItem('intaleq_api_key') || localStorage.getItem('map_admin_key');
function App() {
const [map, setMap] = useState<any>(null);
@@ -19,7 +19,7 @@ function App() {
const [loading, setLoading] = useState(false);
const [routeLoading, setRouteLoading] = useState(false);
const [routeError, setRouteError] = useState('');
// Layer Toggles
const [show3D, setShow3D] = useState(false);
const [showPOIs, setShowPOIs] = useState(true);
@@ -87,38 +87,38 @@ function App() {
};
const regions = [
{
name: 'Syria',
name_ar: 'سوريا',
center: [36.29, 33.51],
zoom: 11,
{
name: 'Syria',
name_ar: 'سوريا',
center: [36.29, 33.51],
zoom: 11,
flag: '🇸🇾',
defaultOrigin: '33.5138, 36.2765', // Damascus
defaultDest: '36.2021, 37.1343' // Aleppo
},
{
name: 'Jordan',
name_ar: 'الأردن',
center: [35.91, 31.95],
zoom: 11,
{
name: 'Jordan',
name_ar: 'الأردن',
center: [35.91, 31.95],
zoom: 11,
flag: '🇯🇴',
defaultOrigin: '31.9539, 35.9106', // Amman
defaultDest: '32.0608, 36.1032' // Zarqa
},
{
name: 'Iraq',
name_ar: 'العراق',
center: [44.3661, 33.3152],
zoom: 11,
{
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: 'مصر',
center: [31.23, 30.04],
zoom: 10,
{
name: 'Egypt',
name_ar: 'مصر',
center: [31.23, 30.04],
zoom: 10,
flag: '🇪🇬',
defaultOrigin: '30.0444, 31.2357', // Cairo
defaultDest: '31.2001, 29.9187' // Alexandria
@@ -447,12 +447,12 @@ function App() {
<div className="input-group">
<label><MapPin size={14} style={{ marginRight: 5 }} /> Search Places / البحث في الأماكن</label>
<div style={{ display: 'flex', gap: '5px' }}>
<input
type="text"
placeholder="Search city, street, cafe..."
value={searchQuery}
onChange={e => setSearchQuery(e.target.value)}
onKeyDown={e => e.key === 'Enter' && handleSearch()}
<input
type="text"
placeholder="Search city, street, cafe..."
value={searchQuery}
onChange={e => setSearchQuery(e.target.value)}
onKeyDown={e => e.key === 'Enter' && handleSearch()}
/>
<button className="btn" style={{ width: 'auto', marginTop: 0, padding: '10px 15px' }} onClick={handleSearch} disabled={loading}>
{loading ? '...' : 'Go'}
@@ -464,9 +464,9 @@ function App() {
{searchResults.map((res) => (
<div
key={res.id || Math.random()}
onClick={() => {
const rLng = parseFloat(res.longitude);
const rLat = parseFloat(res.latitude);
onClick={() => {
const rLng = parseFloat(res.longitude);
const rLat = parseFloat(res.latitude);
if (!isNaN(rLat) && !isNaN(rLng)) {
map?.flyTo({ center: [rLng, rLat], zoom: 16, duration: 1500 });
}
@@ -498,21 +498,21 @@ function App() {
{/* Route Calculation */}
<div className="input-group">
<label><Navigation size={14} style={{ marginRight: 5 }} /> Origin / نقطة الانطلاق</label>
<input
type="text"
placeholder="lat, lng"
value={originText}
onChange={e => setOriginText(e.target.value)}
<input
type="text"
placeholder="lat, lng"
value={originText}
onChange={e => setOriginText(e.target.value)}
/>
</div>
<div className="input-group">
<label><Compass size={14} style={{ marginRight: 5 }} /> Destination / الوجهة</label>
<input
type="text"
placeholder="lat, lng"
value={destText}
onChange={e => setDestText(e.target.value)}
<input
type="text"
placeholder="lat, lng"
value={destText}
onChange={e => setDestText(e.target.value)}
/>
</div>
@@ -705,16 +705,16 @@ function App() {
border: showLOS ? '1px solid rgba(99, 102, 241, 0.4)' : '1px solid transparent'
}}>
<label style={{ cursor: 'pointer', display: 'flex', alignItems: 'center', gap: '8px', fontWeight: showLOS ? 700 : 400 }}>
<input
type="checkbox"
checked={showLOS}
<input
type="checkbox"
checked={showLOS}
onChange={(e) => {
setShowLOS(e.target.checked);
if (!e.target.checked) {
setLosPointA(null);
setLosPointB(null);
}
}}
}}
/>
🎯 Tactical LOS / تبادل الرؤية العسكري
</label>
@@ -3,7 +3,9 @@ import { X, Send, MapPin, CheckCircle2, AlertCircle, Sparkles, Shield } from 'lu
import { HeritageLandmarkData } from './LandmarkCard';
interface SubmitContributionModalProps {
isOpen?: boolean;
landmark?: HeritageLandmarkData | null;
initialLandmark?: HeritageLandmarkData | null;
mapCenter?: { lat: number; lng: number };
apiKey?: string;
onClose: () => void;
@@ -11,21 +13,46 @@ interface SubmitContributionModalProps {
}
export const SubmitContributionModal: React.FC<SubmitContributionModalProps> = ({
isOpen = false,
landmark,
initialLandmark,
mapCenter,
apiKey = '',
onClose,
onSuccess,
}) => {
if (!isOpen) return null;
const targetLandmark = landmark || initialLandmark;
const [displayName, setDisplayName] = useState('المرشد الميداني (حمزة)');
const [contributionType, setContributionType] = useState('CONFIRM_GATE');
const [lat, setLat] = useState(landmark?.coordinates.access_gate?.lat ?? mapCenter?.lat ?? 31.9539);
const [lng, setLng] = useState(landmark?.coordinates.access_gate?.lng ?? mapCenter?.lng ?? 35.9106);
const [lat, setLat] = useState(targetLandmark?.coordinates.access_gate?.lat ?? mapCenter?.lat ?? 31.9539);
const [lng, setLng] = useState(targetLandmark?.coordinates.access_gate?.lng ?? mapCenter?.lng ?? 35.9106);
const [notes, setNotes] = useState('');
const [loading, setLoading] = useState(false);
const [submitted, setSubmitted] = useState(false);
const [error, setError] = useState('');
React.useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
onClose();
}
};
window.addEventListener('keydown', handleKeyDown);
return () => window.removeEventListener('keydown', handleKeyDown);
}, [onClose]);
React.useEffect(() => {
if (targetLandmark?.coordinates.access_gate) {
setLat(targetLandmark.coordinates.access_gate.lat);
setLng(targetLandmark.coordinates.access_gate.lng);
} else if (mapCenter) {
setLat(mapCenter.lat);
setLng(mapCenter.lng);
}
}, [targetLandmark, mapCenter]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setLoading(true);
@@ -38,8 +65,8 @@ export const SubmitContributionModal: React.FC<SubmitContributionModalProps> = (
displayName: displayName.trim(),
contributionType,
targetTable: 'heritage.landmarks',
targetId: landmark?.id ?? null,
placeName: landmark?.name_ar || 'معلم أثري ميداني',
targetId: targetLandmark?.id ?? null,
placeName: targetLandmark?.name_ar || 'معلم أثري ميداني',
lat: parseFloat(lat as any),
lng: parseFloat(lng as any),
suggestedData: {
@@ -75,6 +102,7 @@ export const SubmitContributionModal: React.FC<SubmitContributionModalProps> = (
return (
<div
dir="rtl"
onClick={onClose}
style={{
position: 'fixed',
inset: 0,
@@ -89,6 +117,7 @@ export const SubmitContributionModal: React.FC<SubmitContributionModalProps> = (
}}
>
<div
onClick={(e) => e.stopPropagation()}
style={{
width: '100%',
maxWidth: '520px',
@@ -134,7 +163,7 @@ export const SubmitContributionModal: React.FC<SubmitContributionModalProps> = (
<Sparkles size={16} /> شبكة المساهمين والمرشدين المحليين
</div>
<h2 style={{ margin: 0, fontSize: '20px', fontWeight: 800 }}>
{landmark ? `اقتراح وتوثيق: ${landmark.name_ar}` : 'إضافة وتوثيق بوابة سياحية جديدة'}
{targetLandmark ? `اقتراح وتوثيق: ${targetLandmark.name_ar}` : 'إضافة وتوثيق بوابة سياحية جديدة'}
</h2>
<p style={{ margin: '6px 0 0', fontSize: '13px', color: '#94a3b8' }}>
تخضع المساهمات لطابور المراجعة المعتمد قبل نشرها للجمهور وتمنحك نقاط المرشد الموثوق.
@@ -315,29 +344,48 @@ export const SubmitContributionModal: React.FC<SubmitContributionModalProps> = (
/>
</div>
{/* Submit Button */}
<button
type="submit"
disabled={loading}
style={{
width: '100%',
background: 'linear-gradient(135deg, #0b2547 0%, #153e75 100%)',
color: '#ffffff',
border: 'none',
borderRadius: '12px',
padding: '14px',
fontSize: '15px',
fontWeight: 800,
cursor: loading ? 'wait' : 'pointer',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: '8px',
boxShadow: '0 4px 14px rgba(11, 37, 71, 0.25)',
}}
>
{loading ? 'جارٍ الإرسال والتسجيل...' : <><Send size={16} /> إرسال المساهمة لطابور الاعتماد</>}
</button>
{/* Submit & Cancel Buttons */}
<div style={{ display: 'flex', gap: '10px', marginTop: '10px' }}>
<button
type="button"
onClick={onClose}
style={{
flex: 1,
background: 'rgba(241, 245, 249, 0.9)',
color: '#475569',
border: '1px solid #cbd5e1',
borderRadius: '12px',
padding: '13px',
fontSize: '14px',
fontWeight: 700,
cursor: 'pointer',
}}
>
إلغاء
</button>
<button
type="submit"
disabled={loading}
style={{
flex: 2,
background: 'linear-gradient(135deg, #0b2547 0%, #153e75 100%)',
color: '#ffffff',
border: 'none',
borderRadius: '12px',
padding: '13px',
fontSize: '14px',
fontWeight: 800,
cursor: loading ? 'wait' : 'pointer',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
gap: '8px',
boxShadow: '0 4px 14px rgba(11, 37, 71, 0.25)',
}}
>
{loading ? 'جارٍ الإرسال والتسجيل...' : <><Send size={16} /> إرسال المساهمة لطابور الاعتماد</>}
</button>
</div>
</form>
)}
</div>
+137 -7
View File
@@ -25,7 +25,6 @@ try {
// Lightweight hash router with Sovereign Executive Styling
const NAV = [
{ hash: '#landing', label: 'Landing Page (صفحة الهبوط)', icon: '🚀' },
{ hash: '#map', label: 'الخريطة والملاحة الشاملة', icon: '🗺️' },
{ hash: '#terrain3d', label: 'الخريطة ثلاثية الأبعاد والتضاريس', icon: '🏔️' },
{ hash: '#tourism', label: 'الخريطة السياحية والتراثية', icon: '🏛️' },
@@ -37,13 +36,33 @@ const NAV = [
function MasterHeader({ hash }: { hash: string }) {
const currentHash = hash || '#map'
const [isFullscreen, setIsFullscreen] = useState(false);
const [showAdminModal, setShowAdminModal] = useState(false);
const [adminKey, setAdminKey] = useState(() => {
return (import.meta as any).env.VITE_ADMIN_API_KEY || (import.meta as any).env.VITE_API_KEY || localStorage.getItem('map_admin_key') || localStorage.getItem('intaleq_api_key') || '';
});
const [keyInput, setKeyInput] = useState(adminKey);
const [savedSuccess, setSavedSuccess] = useState(false);
const saveAdminKey = (newKey: string) => {
const trimmed = newKey.trim();
localStorage.setItem('map_admin_key', trimmed);
localStorage.setItem('intaleq_api_key', trimmed);
sessionStorage.setItem('tactical_api_key', trimmed);
setAdminKey(trimmed);
setSavedSuccess(true);
setTimeout(() => {
setSavedSuccess(false);
setShowAdminModal(false);
window.location.reload();
}, 800);
};
const toggleFullscreen = () => {
if (!document.fullscreenElement) {
document.documentElement.requestFullscreen().catch(() => {});
document.documentElement.requestFullscreen().catch(() => { });
setIsFullscreen(true);
} else {
document.exitFullscreen().catch(() => {});
document.exitFullscreen().catch(() => { });
setIsFullscreen(false);
}
};
@@ -149,8 +168,29 @@ function MasterHeader({ hash }: { hash: string }) {
})}
</nav>
{/* Left Section: Live Sovereign Telemetry & Fullscreen */}
{/* Left Section: Admin Key Clearance & Telemetry & Fullscreen */}
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
<button
onClick={() => setShowAdminModal(true)}
title="تصريح المشرف الرسمي (Admin Clearance)"
style={{
display: 'flex',
alignItems: 'center',
gap: 6,
background: 'rgba(234, 179, 8, 0.12)',
border: '1px solid rgba(234, 179, 8, 0.35)',
color: '#facc15',
padding: '4px 10px',
borderRadius: 8,
fontSize: '0.72rem',
fontWeight: 700,
cursor: 'pointer'
}}
>
<span>🛡️</span>
<span>تصريح المشرف (Admin)</span>
</button>
<div style={{
display: 'flex',
alignItems: 'center',
@@ -194,6 +234,99 @@ function MasterHeader({ hash }: { hash: string }) {
<span>{isFullscreen ? '🗗' : '🗖'}</span>
</button>
</div>
{/* Admin Key Clearance Modal */}
{showAdminModal && (
<div style={{
position: 'fixed',
inset: 0,
background: 'rgba(0, 0, 0, 0.75)',
backdropFilter: 'blur(10px)',
zIndex: 100000,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
direction: 'rtl'
}}>
<div style={{
background: '#0d1527',
border: '1px solid rgba(234, 179, 8, 0.4)',
borderRadius: 16,
padding: '24px 28px',
width: 480,
maxWidth: '90vw',
boxShadow: '0 20px 50px rgba(0,0,0,0.6)',
color: '#fff'
}}>
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 12 }}>
<span style={{ fontSize: 24 }}>🛡️</span>
<h3 style={{ margin: 0, fontSize: '1.1rem', fontWeight: 800 }}>تصريح المشرف الرسمي (Admin Clearance)</h3>
</div>
<p style={{ fontSize: '0.85rem', color: '#94a3b8', margin: '0 0 16px 0', lineHeight: 1.6 }}>
يُستخدم هذا المفتاح للوصول إلى المنظومة التكتيكية وتدقيق الطرق الذكي وكافة ميزات المنصة السيادية بصلاحيات المشرف الكاملة.
</p>
<div style={{ marginBottom: 16 }}>
<label style={{ display: 'block', fontSize: '0.78rem', color: '#cbd5e1', marginBottom: 6, fontWeight: 700 }}>
مفتاح الـ API الرسمي للمشرف (Admin API Key):
</label>
<input
type="text"
value={keyInput}
onChange={(e) => setKeyInput(e.target.value)}
placeholder="أدخل مفتاح الـ Admin هنا..."
style={{
width: '100%',
padding: '10px 14px',
borderRadius: 8,
border: '1px solid rgba(255, 255, 255, 0.15)',
background: 'rgba(255, 255, 255, 0.05)',
color: '#facc15',
fontFamily: 'monospace',
fontSize: '0.9rem',
outline: 'none',
boxSizing: 'border-box'
}}
/>
</div>
{savedSuccess && (
<div style={{ color: '#4ade80', fontSize: '0.82rem', marginBottom: 12, fontWeight: 700 }}>
✅ تم حفظ وتفعيل مفتاح المشرف بنجاح! جاري التحديث...
</div>
)}
<div style={{ display: 'flex', justifyContent: 'flex-end', gap: 10 }}>
<button
onClick={() => setShowAdminModal(false)}
style={{
padding: '8px 16px',
borderRadius: 8,
border: '1px solid rgba(255, 255, 255, 0.15)',
background: 'transparent',
color: '#94a3b8',
cursor: 'pointer',
fontWeight: 600
}}
>
إلغاء
</button>
<button
onClick={() => saveAdminKey(keyInput)}
style={{
padding: '8px 20px',
borderRadius: 8,
border: 'none',
background: 'linear-gradient(135deg, #eab308, #ca8a04)',
color: '#000',
fontWeight: 800,
cursor: 'pointer',
boxShadow: '0 2px 10px rgba(234, 179, 8, 0.4)'
}}
>
تفعيل وحفظ المفتاح
</button>
</div>
</div>
</div>
)}
</header>
)
}
@@ -261,9 +394,6 @@ function Root() {
return window.location.hash;
}
const p = window.location.pathname.toLowerCase();
if (p.includes('landing')) {
return '#landing';
}
if (p.includes('tourism') || p.includes('heritage') || p.includes('tourist') || window.location.href.includes('سياح')) {
return '#tourism';
}
+13 -7
View File
@@ -538,7 +538,7 @@ export const TourismMapView: React.FC = () => {
startLat = fallbackHub[1];
}
const SOVEREIGN_MAP_API_KEY = 'zP9vL5mK2nQ8xR7jT4wS1yB6hG3fV0cX';
const SOVEREIGN_MAP_API_KEY = (import.meta as any).env.VITE_API_KEY || (import.meta as any).env.VITE_ADMIN_API_KEY || localStorage.getItem('map_admin_key') || localStorage.getItem('intaleq_api_key') || '';
const queryParams = `fromLat=${startLat}&fromLng=${startLng}&toLat=${lat}&toLng=${lng}&profile=car&steps=true&locale=ar&key=${SOVEREIGN_MAP_API_KEY}`;
const isServerEnv = window.location.hostname.includes('intaleqapp.com') || window.location.port === '3201' || window.location.port === '3204';
@@ -1000,12 +1000,18 @@ export const TourismMapView: React.FC = () => {
/>
)}
{/* 7. Crowdsourcing Contribution Modal */}
<SubmitContributionModal
isOpen={isContributeOpen}
onClose={() => setIsContributeOpen(false)}
initialLandmark={contributeTarget}
/>
{/* 7. Crowdsourcing Contribution Modal (Only open on user action) */}
{isContributeOpen && (
<SubmitContributionModal
isOpen={isContributeOpen}
onClose={() => {
setIsContributeOpen(false);
setContributeTarget(undefined);
}}
initialLandmark={contributeTarget}
mapCenter={mapInstance ? { lat: mapInstance.getCenter().lat, lng: mapInstance.getCenter().lng } : undefined}
/>
)}
{/* 8. Sovereign In-App Navigation HUD */}
{activeNav && (