feat: add place gate population scripts, map style assets, automated cron sync tasks, and expand dashboard and landing applications

This commit is contained in:
Hamza-Ayed
2026-09-19 21:22:05 +03:00
parent 82b1045f9c
commit ce31d79ba6
76 changed files with 118969 additions and 115428 deletions
+8 -3
View File
@@ -34,7 +34,7 @@ function App() {
const [weatherAlerts, setWeatherAlerts] = useState<any[]>([]);
// Tourism & Heritage State
const [tourismMode, setTourismMode] = useState(true);
const [tourismMode, setTourismMode] = useState(false);
const [heritageLandmarks, setHeritageLandmarks] = useState<HeritageLandmarkData[]>([]);
const [selectedLandmark, setSelectedLandmark] = useState<HeritageLandmarkData | null>(null);
const [showContributeModal, setShowContributeModal] = useState(false);
@@ -67,8 +67,13 @@ function App() {
};
useEffect(() => {
fetchHeritageLandmarks();
}, []);
if (tourismMode) {
fetchHeritageLandmarks();
} else {
setHeritageLandmarks([]);
setSelectedLandmark(null);
}
}, [tourismMode]);
const handleNavigateToGate = (lat: number, lng: number, name: string) => {
setDestText(`${lat.toFixed(5)}, ${lng.toFixed(5)}`);
File diff suppressed because one or more lines are too long
+47
View File
@@ -0,0 +1,47 @@
/** Shared behavior for the React view and the generated standalone document. */
export function initializeLanding(root) {
const controller = new AbortController();
const { signal } = controller;
const reduced = window.matchMedia('(prefers-reduced-motion: reduce)');
root.addEventListener('click', async (event) => {
const target = event.target.closest('a, button');
if (!target || !root.contains(target)) return;
if (target.dataset.scroll) {
event.preventDefault();
const section = root.querySelector(`#${target.dataset.scroll}`);
section?.scrollIntoView({ behavior: reduced.matches ? 'instant' : 'smooth', block: 'start' });
section?.setAttribute('tabindex', '-1');
section?.focus({ preventScroll: true });
}
if (target.dataset.mode) {
const mode = target.dataset.mode;
root.querySelectorAll('[data-mode]').forEach(button => button.setAttribute('aria-pressed', String(button === target)));
root.querySelector('[data-scene]').dataset.scene = mode;
const modes = {
routes: ['ROUTE LAYER', 'اربط نقطة الانطلاق بالوجهة داخل تجربة تطبيقك.', '/#map'],
terrain: ['TERRAIN LAYER', 'أضف الارتفاعات وسياق التضاريس إلى المشهد المكاني.', '/#terrain3d'],
data: ['DATA LAYER', 'ضع مواقعك وبياناتك فوق الخريطة لفهم توزيعها.', '/#map']
};
const [label, description, href] = modes[mode];
root.querySelector('[data-mode-label]').textContent = label;
root.querySelector('.sl-mode-description').textContent = description;
root.querySelector('[data-mode-link]').href = href;
}
if (target.classList.contains('sl-motion')) {
const paused = root.classList.toggle('sl-paused');
target.setAttribute('aria-pressed', String(paused));
target.setAttribute('aria-label', paused ? 'تشغيل الحركة' : 'إيقاف الحركة');
target.textContent = paused ? '▷' : 'Ⅱ';
}
if (target.classList.contains('sl-copy')) {
const status = root.querySelector('.sl-copy-status');
try {
await navigator.clipboard.writeText(root.querySelector('.sl-code code').textContent);
if (!signal.aborted) status.textContent = 'تم نسخ الكود';
} catch {
if (!signal.aborted) status.textContent = 'تعذّر النسخ التلقائي. يمكنك تحديد الكود ونسخه يدويًا.';
}
}
}, { signal });
return () => controller.abort();
}
File diff suppressed because one or more lines are too long
+8 -5
View File
@@ -257,6 +257,9 @@ class ErrorBoundary extends React.Component<{ children: React.ReactNode }, { has
function Root() {
const getInitialHash = () => {
if (window.location.hash) {
return window.location.hash;
}
const p = window.location.pathname.toLowerCase();
if (p.includes('landing')) {
return '#landing';
@@ -267,7 +270,7 @@ function Root() {
if (p.includes('tactical') || p.includes('military')) {
return '#tactical';
}
return window.location.hash || '#map';
return '#map';
};
const [hash, setHash] = useState(getInitialHash);
@@ -284,7 +287,7 @@ function Root() {
return () => window.removeEventListener('hashchange', on);
}, []);
const isLanding = hash === '#landing' || hash === '#portal' || window.location.pathname.toLowerCase().includes('landing');
const isLanding = hash === '#landing' || hash === '#portal';
const isTourism = hash === '#tourism' || hash === '#heritage' || hash === '#tourist';
const isTactical = hash === '#tactical' || hash === '#military' || hash === '#siro';
const isTerrain3D = hash === '#terrain3d' || hash === '#3d' || hash === '#terrain';
@@ -295,16 +298,16 @@ function Root() {
return (
<ErrorBoundary>
<MasterHeader hash={hash} />
{!isLanding && <MasterHeader hash={hash} />}
<main style={{
position: 'absolute',
top: 52,
top: isLanding ? 0 : 52,
bottom: 0,
left: 0,
right: 0,
width: '100vw',
height: 'calc(100vh - 52px)',
height: isLanding ? '100vh' : 'calc(100vh - 52px)',
overflow: 'hidden'
}}>
{isLanding && (
File diff suppressed because it is too large Load Diff