feat: Phase 3 complete — push alerts, feedback scoring, S3 fallback, SPA routing, mobile CSS
- ImpactScoreService: implement getFeedbackBonus() (ST_Distance query on UserFeedback within 20km/6h) - ImpactScoreService: trigger PushNotificationService when score >= 70 - PushNotificationService: send VAPID WebPush to spot subscribers, clean expired subs - PushController: GET vapid-public-key, POST/DELETE subscribe with rate limiting - SentinelHubClient: add optional $collection param, add Sentinel-3 OLCI FAI evalscript (MCI) - IngestionService: add $collection param + HighCloudCoverageException for fallback logic - IngestionService: add ingestWithFallback() — tries S2, falls back to S3 on high cloud - IngestSentinelCommand: --source=auto (default) triggers ingestWithFallback - FeedbackController: rate limiting via apiFeedbackLimiter - Migration: push_subscription table - rate_limiter.yaml: api_read(120/min), api_feedback(10/min), api_push(5/min) - sw.js: service worker handling push events + notificationclick - usePushSubscription hook: subscribe/unsubscribe lifecycle with VAPID - SpotPanel: PushButton component integrated - SpotPanel.css + TimelineSlider.css: mobile responsive (bottom-sheet on small screens) - Caddyfile: SPA served at / with try_files fallback, sw.js served from root scope - vite.config.js: build outDir → backend/public (not /spa) - deploy/post-receive.sh: full deploy script (composer, npm build, migrations, cache, docker up) - docs/roadmap.md: all Phase 3 + transversal items marked done Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -121,3 +121,47 @@
|
||||
padding-top: 14px;
|
||||
border-top: 1px solid #334155;
|
||||
}
|
||||
|
||||
/* Push notifications */
|
||||
.spot-panel__push {
|
||||
border-top: 1px solid #334155;
|
||||
padding-top: 12px;
|
||||
text-align: center;
|
||||
}
|
||||
.spot-panel__push-btn {
|
||||
width: 100%;
|
||||
padding: 9px 16px;
|
||||
border: 1px solid #475569;
|
||||
border-radius: 8px;
|
||||
background: #0f172a;
|
||||
color: #94a3b8;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
transition: background .15s, border-color .15s;
|
||||
}
|
||||
.spot-panel__push-btn:hover:not(:disabled) { background: #1e293b; border-color: #64748b; color: #f1f5f9; }
|
||||
.spot-panel__push-btn--active { border-color: #0ea5e9; color: #38bdf8; }
|
||||
.spot-panel__push-btn:disabled { opacity: .5; cursor: default; }
|
||||
.spot-panel__push-error { color: #f87171; font-size: 12px; margin-top: 6px; }
|
||||
|
||||
/* ── Mobile ── */
|
||||
@media (max-width: 480px) {
|
||||
.spot-panel {
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
transform: none;
|
||||
width: 100%;
|
||||
border-radius: 16px 16px 0 0;
|
||||
max-height: 70vh;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.spot-panel__feedback-btns {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.spot-panel__details {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user