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
+1 -1
View File
@@ -8,7 +8,7 @@ import { LandmarkCard, HeritageLandmarkData } from './components/LandmarkCard';
import { SubmitContributionModal } from './components/SubmitContributionModal'; import { SubmitContributionModal } from './components/SubmitContributionModal';
import { GuidesModerationPanel } from './components/GuidesModerationPanel'; 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() { function App() {
const [map, setMap] = useState<any>(null); const [map, setMap] = useState<any>(null);
@@ -3,7 +3,9 @@ import { X, Send, MapPin, CheckCircle2, AlertCircle, Sparkles, Shield } from 'lu
import { HeritageLandmarkData } from './LandmarkCard'; import { HeritageLandmarkData } from './LandmarkCard';
interface SubmitContributionModalProps { interface SubmitContributionModalProps {
isOpen?: boolean;
landmark?: HeritageLandmarkData | null; landmark?: HeritageLandmarkData | null;
initialLandmark?: HeritageLandmarkData | null;
mapCenter?: { lat: number; lng: number }; mapCenter?: { lat: number; lng: number };
apiKey?: string; apiKey?: string;
onClose: () => void; onClose: () => void;
@@ -11,21 +13,46 @@ interface SubmitContributionModalProps {
} }
export const SubmitContributionModal: React.FC<SubmitContributionModalProps> = ({ export const SubmitContributionModal: React.FC<SubmitContributionModalProps> = ({
isOpen = false,
landmark, landmark,
initialLandmark,
mapCenter, mapCenter,
apiKey = '', apiKey = '',
onClose, onClose,
onSuccess, onSuccess,
}) => { }) => {
if (!isOpen) return null;
const targetLandmark = landmark || initialLandmark;
const [displayName, setDisplayName] = useState('المرشد الميداني (حمزة)'); const [displayName, setDisplayName] = useState('المرشد الميداني (حمزة)');
const [contributionType, setContributionType] = useState('CONFIRM_GATE'); const [contributionType, setContributionType] = useState('CONFIRM_GATE');
const [lat, setLat] = useState(landmark?.coordinates.access_gate?.lat ?? mapCenter?.lat ?? 31.9539); const [lat, setLat] = useState(targetLandmark?.coordinates.access_gate?.lat ?? mapCenter?.lat ?? 31.9539);
const [lng, setLng] = useState(landmark?.coordinates.access_gate?.lng ?? mapCenter?.lng ?? 35.9106); const [lng, setLng] = useState(targetLandmark?.coordinates.access_gate?.lng ?? mapCenter?.lng ?? 35.9106);
const [notes, setNotes] = useState(''); const [notes, setNotes] = useState('');
const [loading, setLoading] = useState(false); const [loading, setLoading] = useState(false);
const [submitted, setSubmitted] = useState(false); const [submitted, setSubmitted] = useState(false);
const [error, setError] = useState(''); 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) => { const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault(); e.preventDefault();
setLoading(true); setLoading(true);
@@ -38,8 +65,8 @@ export const SubmitContributionModal: React.FC<SubmitContributionModalProps> = (
displayName: displayName.trim(), displayName: displayName.trim(),
contributionType, contributionType,
targetTable: 'heritage.landmarks', targetTable: 'heritage.landmarks',
targetId: landmark?.id ?? null, targetId: targetLandmark?.id ?? null,
placeName: landmark?.name_ar || 'معلم أثري ميداني', placeName: targetLandmark?.name_ar || 'معلم أثري ميداني',
lat: parseFloat(lat as any), lat: parseFloat(lat as any),
lng: parseFloat(lng as any), lng: parseFloat(lng as any),
suggestedData: { suggestedData: {
@@ -75,6 +102,7 @@ export const SubmitContributionModal: React.FC<SubmitContributionModalProps> = (
return ( return (
<div <div
dir="rtl" dir="rtl"
onClick={onClose}
style={{ style={{
position: 'fixed', position: 'fixed',
inset: 0, inset: 0,
@@ -89,6 +117,7 @@ export const SubmitContributionModal: React.FC<SubmitContributionModalProps> = (
}} }}
> >
<div <div
onClick={(e) => e.stopPropagation()}
style={{ style={{
width: '100%', width: '100%',
maxWidth: '520px', maxWidth: '520px',
@@ -134,7 +163,7 @@ export const SubmitContributionModal: React.FC<SubmitContributionModalProps> = (
<Sparkles size={16} /> شبكة المساهمين والمرشدين المحليين <Sparkles size={16} /> شبكة المساهمين والمرشدين المحليين
</div> </div>
<h2 style={{ margin: 0, fontSize: '20px', fontWeight: 800 }}> <h2 style={{ margin: 0, fontSize: '20px', fontWeight: 800 }}>
{landmark ? `اقتراح وتوثيق: ${landmark.name_ar}` : 'إضافة وتوثيق بوابة سياحية جديدة'} {targetLandmark ? `اقتراح وتوثيق: ${targetLandmark.name_ar}` : 'إضافة وتوثيق بوابة سياحية جديدة'}
</h2> </h2>
<p style={{ margin: '6px 0 0', fontSize: '13px', color: '#94a3b8' }}> <p style={{ margin: '6px 0 0', fontSize: '13px', color: '#94a3b8' }}>
تخضع المساهمات لطابور المراجعة المعتمد قبل نشرها للجمهور وتمنحك نقاط المرشد الموثوق. تخضع المساهمات لطابور المراجعة المعتمد قبل نشرها للجمهور وتمنحك نقاط المرشد الموثوق.
@@ -315,29 +344,48 @@ export const SubmitContributionModal: React.FC<SubmitContributionModalProps> = (
/> />
</div> </div>
{/* Submit Button */} {/* Submit & Cancel Buttons */}
<button <div style={{ display: 'flex', gap: '10px', marginTop: '10px' }}>
type="submit" <button
disabled={loading} type="button"
style={{ onClick={onClose}
width: '100%', style={{
background: 'linear-gradient(135deg, #0b2547 0%, #153e75 100%)', flex: 1,
color: '#ffffff', background: 'rgba(241, 245, 249, 0.9)',
border: 'none', color: '#475569',
borderRadius: '12px', border: '1px solid #cbd5e1',
padding: '14px', borderRadius: '12px',
fontSize: '15px', padding: '13px',
fontWeight: 800, fontSize: '14px',
cursor: loading ? 'wait' : 'pointer', fontWeight: 700,
display: 'flex', cursor: 'pointer',
alignItems: 'center', }}
justifyContent: 'center', >
gap: '8px', إلغاء
boxShadow: '0 4px 14px rgba(11, 37, 71, 0.25)', </button>
}} <button
> type="submit"
{loading ? 'جارٍ الإرسال والتسجيل...' : <><Send size={16} /> إرسال المساهمة لطابور الاعتماد</>} disabled={loading}
</button> 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> </form>
)} )}
</div> </div>
+137 -7
View File
@@ -25,7 +25,6 @@ try {
// Lightweight hash router with Sovereign Executive Styling // Lightweight hash router with Sovereign Executive Styling
const NAV = [ const NAV = [
{ hash: '#landing', label: 'Landing Page (صفحة الهبوط)', icon: '🚀' },
{ hash: '#map', label: 'الخريطة والملاحة الشاملة', icon: '🗺️' }, { hash: '#map', label: 'الخريطة والملاحة الشاملة', icon: '🗺️' },
{ hash: '#terrain3d', label: 'الخريطة ثلاثية الأبعاد والتضاريس', icon: '🏔️' }, { hash: '#terrain3d', label: 'الخريطة ثلاثية الأبعاد والتضاريس', icon: '🏔️' },
{ hash: '#tourism', label: 'الخريطة السياحية والتراثية', icon: '🏛️' }, { hash: '#tourism', label: 'الخريطة السياحية والتراثية', icon: '🏛️' },
@@ -37,13 +36,33 @@ const NAV = [
function MasterHeader({ hash }: { hash: string }) { function MasterHeader({ hash }: { hash: string }) {
const currentHash = hash || '#map' const currentHash = hash || '#map'
const [isFullscreen, setIsFullscreen] = useState(false); 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 = () => { const toggleFullscreen = () => {
if (!document.fullscreenElement) { if (!document.fullscreenElement) {
document.documentElement.requestFullscreen().catch(() => {}); document.documentElement.requestFullscreen().catch(() => { });
setIsFullscreen(true); setIsFullscreen(true);
} else { } else {
document.exitFullscreen().catch(() => {}); document.exitFullscreen().catch(() => { });
setIsFullscreen(false); setIsFullscreen(false);
} }
}; };
@@ -149,8 +168,29 @@ function MasterHeader({ hash }: { hash: string }) {
})} })}
</nav> </nav>
{/* Left Section: Live Sovereign Telemetry & Fullscreen */} {/* Left Section: Admin Key Clearance & Telemetry & Fullscreen */}
<div style={{ display: 'flex', alignItems: 'center', gap: 10 }}> <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={{ <div style={{
display: 'flex', display: 'flex',
alignItems: 'center', alignItems: 'center',
@@ -194,6 +234,99 @@ function MasterHeader({ hash }: { hash: string }) {
<span>{isFullscreen ? '🗗' : '🗖'}</span> <span>{isFullscreen ? '🗗' : '🗖'}</span>
</button> </button>
</div> </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> </header>
) )
} }
@@ -261,9 +394,6 @@ function Root() {
return window.location.hash; return window.location.hash;
} }
const p = window.location.pathname.toLowerCase(); 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('سياح')) { if (p.includes('tourism') || p.includes('heritage') || p.includes('tourist') || window.location.href.includes('سياح')) {
return '#tourism'; return '#tourism';
} }
+13 -7
View File
@@ -538,7 +538,7 @@ export const TourismMapView: React.FC = () => {
startLat = fallbackHub[1]; 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 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'; 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 */} {/* 7. Crowdsourcing Contribution Modal (Only open on user action) */}
<SubmitContributionModal {isContributeOpen && (
isOpen={isContributeOpen} <SubmitContributionModal
onClose={() => setIsContributeOpen(false)} isOpen={isContributeOpen}
initialLandmark={contributeTarget} onClose={() => {
/> setIsContributeOpen(false);
setContributeTarget(undefined);
}}
initialLandmark={contributeTarget}
mapCenter={mapInstance ? { lat: mapInstance.getCenter().lat, lng: mapInstance.getCenter().lng } : undefined}
/>
)}
{/* 8. Sovereign In-App Navigation HUD */} {/* 8. Sovereign In-App Navigation HUD */}
{activeNav && ( {activeNav && (