fix: remove landing page nav tab and prevent crowdsource contribution modal from auto-opening on tourism map
This commit is contained in:
+45
-45
@@ -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);
|
||||||
@@ -19,7 +19,7 @@ function App() {
|
|||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [routeLoading, setRouteLoading] = useState(false);
|
const [routeLoading, setRouteLoading] = useState(false);
|
||||||
const [routeError, setRouteError] = useState('');
|
const [routeError, setRouteError] = useState('');
|
||||||
|
|
||||||
// Layer Toggles
|
// Layer Toggles
|
||||||
const [show3D, setShow3D] = useState(false);
|
const [show3D, setShow3D] = useState(false);
|
||||||
const [showPOIs, setShowPOIs] = useState(true);
|
const [showPOIs, setShowPOIs] = useState(true);
|
||||||
@@ -87,38 +87,38 @@ function App() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const regions = [
|
const regions = [
|
||||||
{
|
{
|
||||||
name: 'Syria',
|
name: 'Syria',
|
||||||
name_ar: 'سوريا',
|
name_ar: 'سوريا',
|
||||||
center: [36.29, 33.51],
|
center: [36.29, 33.51],
|
||||||
zoom: 11,
|
zoom: 11,
|
||||||
flag: '🇸🇾',
|
flag: '🇸🇾',
|
||||||
defaultOrigin: '33.5138, 36.2765', // Damascus
|
defaultOrigin: '33.5138, 36.2765', // Damascus
|
||||||
defaultDest: '36.2021, 37.1343' // Aleppo
|
defaultDest: '36.2021, 37.1343' // Aleppo
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Jordan',
|
name: 'Jordan',
|
||||||
name_ar: 'الأردن',
|
name_ar: 'الأردن',
|
||||||
center: [35.91, 31.95],
|
center: [35.91, 31.95],
|
||||||
zoom: 11,
|
zoom: 11,
|
||||||
flag: '🇯🇴',
|
flag: '🇯🇴',
|
||||||
defaultOrigin: '31.9539, 35.9106', // Amman
|
defaultOrigin: '31.9539, 35.9106', // Amman
|
||||||
defaultDest: '32.0608, 36.1032' // Zarqa
|
defaultDest: '32.0608, 36.1032' // Zarqa
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Iraq',
|
name: 'Iraq',
|
||||||
name_ar: 'العراق',
|
name_ar: 'العراق',
|
||||||
center: [44.3661, 33.3152],
|
center: [44.3661, 33.3152],
|
||||||
zoom: 11,
|
zoom: 11,
|
||||||
flag: '🇮🇶',
|
flag: '🇮🇶',
|
||||||
defaultOrigin: '33.3152, 44.3661', // Baghdad
|
defaultOrigin: '33.3152, 44.3661', // Baghdad
|
||||||
defaultDest: '32.0000, 44.4000' // Karbala / Babil
|
defaultDest: '32.0000, 44.4000' // Karbala / Babil
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: 'Egypt',
|
name: 'Egypt',
|
||||||
name_ar: 'مصر',
|
name_ar: 'مصر',
|
||||||
center: [31.23, 30.04],
|
center: [31.23, 30.04],
|
||||||
zoom: 10,
|
zoom: 10,
|
||||||
flag: '🇪🇬',
|
flag: '🇪🇬',
|
||||||
defaultOrigin: '30.0444, 31.2357', // Cairo
|
defaultOrigin: '30.0444, 31.2357', // Cairo
|
||||||
defaultDest: '31.2001, 29.9187' // Alexandria
|
defaultDest: '31.2001, 29.9187' // Alexandria
|
||||||
@@ -447,12 +447,12 @@ function App() {
|
|||||||
<div className="input-group">
|
<div className="input-group">
|
||||||
<label><MapPin size={14} style={{ marginRight: 5 }} /> Search Places / البحث في الأماكن</label>
|
<label><MapPin size={14} style={{ marginRight: 5 }} /> Search Places / البحث في الأماكن</label>
|
||||||
<div style={{ display: 'flex', gap: '5px' }}>
|
<div style={{ display: 'flex', gap: '5px' }}>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="Search city, street, cafe..."
|
placeholder="Search city, street, cafe..."
|
||||||
value={searchQuery}
|
value={searchQuery}
|
||||||
onChange={e => setSearchQuery(e.target.value)}
|
onChange={e => setSearchQuery(e.target.value)}
|
||||||
onKeyDown={e => e.key === 'Enter' && handleSearch()}
|
onKeyDown={e => e.key === 'Enter' && handleSearch()}
|
||||||
/>
|
/>
|
||||||
<button className="btn" style={{ width: 'auto', marginTop: 0, padding: '10px 15px' }} onClick={handleSearch} disabled={loading}>
|
<button className="btn" style={{ width: 'auto', marginTop: 0, padding: '10px 15px' }} onClick={handleSearch} disabled={loading}>
|
||||||
{loading ? '...' : 'Go'}
|
{loading ? '...' : 'Go'}
|
||||||
@@ -464,9 +464,9 @@ function App() {
|
|||||||
{searchResults.map((res) => (
|
{searchResults.map((res) => (
|
||||||
<div
|
<div
|
||||||
key={res.id || Math.random()}
|
key={res.id || Math.random()}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
const rLng = parseFloat(res.longitude);
|
const rLng = parseFloat(res.longitude);
|
||||||
const rLat = parseFloat(res.latitude);
|
const rLat = parseFloat(res.latitude);
|
||||||
if (!isNaN(rLat) && !isNaN(rLng)) {
|
if (!isNaN(rLat) && !isNaN(rLng)) {
|
||||||
map?.flyTo({ center: [rLng, rLat], zoom: 16, duration: 1500 });
|
map?.flyTo({ center: [rLng, rLat], zoom: 16, duration: 1500 });
|
||||||
}
|
}
|
||||||
@@ -498,21 +498,21 @@ function App() {
|
|||||||
{/* Route Calculation */}
|
{/* Route Calculation */}
|
||||||
<div className="input-group">
|
<div className="input-group">
|
||||||
<label><Navigation size={14} style={{ marginRight: 5 }} /> Origin / نقطة الانطلاق</label>
|
<label><Navigation size={14} style={{ marginRight: 5 }} /> Origin / نقطة الانطلاق</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="lat, lng"
|
placeholder="lat, lng"
|
||||||
value={originText}
|
value={originText}
|
||||||
onChange={e => setOriginText(e.target.value)}
|
onChange={e => setOriginText(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="input-group">
|
<div className="input-group">
|
||||||
<label><Compass size={14} style={{ marginRight: 5 }} /> Destination / الوجهة</label>
|
<label><Compass size={14} style={{ marginRight: 5 }} /> Destination / الوجهة</label>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
placeholder="lat, lng"
|
placeholder="lat, lng"
|
||||||
value={destText}
|
value={destText}
|
||||||
onChange={e => setDestText(e.target.value)}
|
onChange={e => setDestText(e.target.value)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -705,16 +705,16 @@ function App() {
|
|||||||
border: showLOS ? '1px solid rgba(99, 102, 241, 0.4)' : '1px solid transparent'
|
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 }}>
|
<label style={{ cursor: 'pointer', display: 'flex', alignItems: 'center', gap: '8px', fontWeight: showLOS ? 700 : 400 }}>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
checked={showLOS}
|
checked={showLOS}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setShowLOS(e.target.checked);
|
setShowLOS(e.target.checked);
|
||||||
if (!e.target.checked) {
|
if (!e.target.checked) {
|
||||||
setLosPointA(null);
|
setLosPointA(null);
|
||||||
setLosPointB(null);
|
setLosPointB(null);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
🎯 Tactical LOS / تبادل الرؤية العسكري
|
🎯 Tactical LOS / تبادل الرؤية العسكري
|
||||||
</label>
|
</label>
|
||||||
|
|||||||
@@ -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
@@ -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';
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 && (
|
||||||
|
|||||||
Reference in New Issue
Block a user