11import { useState , useRef , useEffect , useMemo } from "react" ;
2+ import { toast } from "sonner" ;
23import {
34 Download ,
45 Upload ,
@@ -175,7 +176,7 @@ export default function App() {
175176
176177 const handleExport = async ( ) => {
177178 if ( waypoints . length < 2 ) {
178- alert ( "Need at least 2 waypoints to export" ) ;
179+ toast . warning ( "Need at least 2 waypoints to export" ) ;
179180 return ;
180181 }
181182
@@ -195,7 +196,7 @@ export default function App() {
195196 a . click ( ) ;
196197 URL . revokeObjectURL ( url ) ;
197198 } catch ( err : any ) {
198- alert ( `Export failed: ${ err . message } ` ) ;
199+ toast . error ( `Export failed: ${ err . message } ` ) ;
199200 } finally {
200201 setExporting ( false ) ;
201202 }
@@ -206,6 +207,10 @@ export default function App() {
206207 setShowAuthModal ( true ) ;
207208 return ;
208209 }
210+ if ( ! missionName . trim ( ) ) {
211+ toast . warning ( "Please enter a mission name before saving" ) ;
212+ return ;
213+ }
209214 setSaving ( true ) ;
210215 try {
211216 if ( missionId ) {
@@ -228,7 +233,7 @@ export default function App() {
228233 }
229234 setDirty ( false ) ;
230235 } catch ( err : any ) {
231- alert ( `Save failed: ${ err . message } ` ) ;
236+ toast . error ( `Save failed: ${ err . message } ` ) ;
232237 } finally {
233238 setSaving ( false ) ;
234239 }
@@ -254,7 +259,7 @@ export default function App() {
254259 pois : result . pois ,
255260 } ) ;
256261 } catch ( err : any ) {
257- alert ( `Import failed: ${ err . message } ` ) ;
262+ toast . error ( `Import failed: ${ err . message } ` ) ;
258263 }
259264
260265 // Reset file input
@@ -383,7 +388,7 @@ export default function App() {
383388 }
384389
385390 return (
386- < div className = "flex h-screen w-screen overflow-hidden bg-background" >
391+ < div className = "flex h-dvh w-screen overflow-hidden bg-background" >
387392 { /* Sidebar */ }
388393 < div className = "w-88 flex flex-col border-r border-border bg-card shrink-0 tabular-nums" >
389394 { /* Header */ }
@@ -428,8 +433,9 @@ export default function App() {
428433 < Input
429434 value = { missionName }
430435 onChange = { ( e ) => setMissionName ( e . target . value ) }
431- className = "h-8 text-xs font-medium"
436+ className = "h-8 text-xs font-medium border-blue-500/30 bg-blue-500/5 focus-visible:ring-blue-500/40 "
432437 placeholder = "Mission name"
438+ title = "Name your mission for easy identification"
433439 />
434440 </ div >
435441
@@ -440,7 +446,7 @@ export default function App() {
440446 size = "sm"
441447 onClick = { handleSave }
442448 disabled = { saving }
443- className = "flex-1 text-xs h-7"
449+ className = "flex-1 text-xs h-7 border-blue-500/30 bg-blue-500/5 hover:bg-blue-500/15 hover:text-blue-300 "
444450 title = "Save mission to your account"
445451 >
446452 < Save className = "h-3 w-3" />
@@ -451,8 +457,12 @@ export default function App() {
451457 size = "sm"
452458 onClick = { handleExport }
453459 disabled = { exporting || waypoints . length < 2 }
454- className = "flex-1 text-xs h-7"
455- title = "Export mission as DJI KMZ file"
460+ className = "flex-1 text-xs h-7 border-blue-500/30 bg-blue-500/5 hover:bg-blue-500/15 hover:text-blue-300"
461+ title = {
462+ waypoints . length < 2
463+ ? "Add at least 2 waypoints to export"
464+ : "Export mission as DJI KMZ file"
465+ }
456466 >
457467 < Download className = "h-3 w-3" />
458468 { exporting ? "..." : "Export KMZ" }
@@ -461,7 +471,7 @@ export default function App() {
461471 variant = "outline"
462472 size = "sm"
463473 onClick = { ( ) => fileInputRef . current ?. click ( ) }
464- className = "flex-1 text-xs h-7"
474+ className = "flex-1 text-xs h-7 border-blue-500/30 bg-blue-500/5 hover:bg-blue-500/15 hover:text-blue-300 "
465475 title = "Import a DJI KMZ file"
466476 >
467477 < Upload className = "h-3 w-3" />
@@ -481,8 +491,9 @@ export default function App() {
481491 { /* Waypoints section — BLUE accent */ }
482492 < div className = "border-l-2 border-blue-500/70 bg-blue-500/[0.03]" >
483493 < button
484- className = "flex items-center gap-2 w-full px-3 py-2 text-xs font-semibold uppercase tracking-wider hover:bg-blue-500/10 text-blue-400"
494+ className = "flex items-center gap-2 w-full px-3 py-2 text-xs font-semibold uppercase tracking-wider bg-blue-500/10 hover:bg-blue-500/15 text-blue-400"
485495 onClick = { ( ) => toggleSection ( "waypoints" ) }
496+ title = "Flight path coordinates — click on the map to add"
486497 >
487498 { expandedSections . waypoints ? (
488499 < ChevronDown className = "h-3 w-3" />
@@ -493,7 +504,7 @@ export default function App() {
493504 Waypoints ({ waypoints . length } )
494505 </ button >
495506 { expandedSections . waypoints && (
496- < div className = "max-h-[40vh] overflow-y-auto" >
507+ < div className = "max-h-[40vh] overflow-y-auto section-expand " >
497508 < WaypointList />
498509 </ div >
499510 ) }
@@ -502,8 +513,9 @@ export default function App() {
502513 { /* POIs section — AMBER/ORANGE accent */ }
503514 < div className = "border-l-2 border-amber-500/70 bg-amber-500/[0.03]" >
504515 < button
505- className = "flex items-center gap-2 w-full px-3 py-2 text-xs font-semibold uppercase tracking-wider hover:bg-amber-500/10 text-amber-400"
516+ className = "flex items-center gap-2 w-full px-3 py-2 text-xs font-semibold uppercase tracking-wider bg-amber-500/10 hover:bg-amber-500/15 text-amber-400"
506517 onClick = { ( ) => toggleSection ( "pois" ) }
518+ title = "Targets the drone can point its camera at"
507519 >
508520 { expandedSections . pois ? (
509521 < ChevronDown className = "h-3 w-3" />
@@ -514,7 +526,7 @@ export default function App() {
514526 Points of interest ({ pois . length } )
515527 </ button >
516528 { expandedSections . pois && (
517- < div className = "max-h-[30vh] overflow-y-auto" >
529+ < div className = "max-h-[30vh] overflow-y-auto section-expand " >
518530 < PoiList />
519531 </ div >
520532 ) }
@@ -523,8 +535,9 @@ export default function App() {
523535 { /* Obstacles section — RED accent */ }
524536 < div className = "border-l-2 border-red-500/70 bg-red-500/[0.03]" >
525537 < button
526- className = "flex items-center gap-2 w-full px-3 py-2 text-xs font-semibold uppercase tracking-wider hover:bg-red-500/10 text-red-400"
538+ className = "flex items-center gap-2 w-full px-3 py-2 text-xs font-semibold uppercase tracking-wider bg-red-500/10 hover:bg-red-500/15 text-red-400"
527539 onClick = { ( ) => toggleSection ( "obstacles" ) }
540+ title = "No-fly zones to avoid during the mission"
528541 >
529542 { expandedSections . obstacles ? (
530543 < ChevronDown className = "h-3 w-3" />
@@ -535,7 +548,7 @@ export default function App() {
535548 Obstacles ({ obstacles . length } )
536549 </ button >
537550 { expandedSections . obstacles && (
538- < div className = "max-h-[30vh] overflow-y-auto" >
551+ < div className = "max-h-[30vh] overflow-y-auto section-expand " >
539552 < ObstacleList />
540553 </ div >
541554 ) }
@@ -544,8 +557,9 @@ export default function App() {
544557 { /* Mission Settings section — PURPLE accent */ }
545558 < div className = "border-l-2 border-purple-500/70 bg-purple-500/[0.03]" >
546559 < button
547- className = "flex items-center gap-2 w-full px-3 py-2 text-xs font-semibold uppercase tracking-wider hover:bg-purple-500/10 text-purple-400"
560+ className = "flex items-center gap-2 w-full px-3 py-2 text-xs font-semibold uppercase tracking-wider bg-purple-500/10 hover:bg-purple-500/15 text-purple-400"
548561 onClick = { ( ) => toggleSection ( "config" ) }
562+ title = "Drone model, speed, altitude and flight behavior"
549563 >
550564 { expandedSections . config ? (
551565 < ChevronDown className = "h-3 w-3" />
@@ -556,7 +570,7 @@ export default function App() {
556570 Mission settings
557571 </ button >
558572 { expandedSections . config && (
559- < div className = "max-h-[40vh] overflow-y-auto" >
573+ < div className = "max-h-[40vh] overflow-y-auto section-expand " >
560574 < MissionConfig />
561575 </ div >
562576 ) }
@@ -612,64 +626,60 @@ export default function App() {
612626 } ) ( ) }
613627 </ div >
614628 < div className = "flex items-center gap-3" >
615- { waypoints . length >= 2 && flightStats ? (
616- ( ( ) => {
617- const { distance, time } = flightStats ;
618- const elevGain = waypoints . reduce ( ( sum , wp , i ) => {
619- if ( i === 0 ) return 0 ;
620- const diff = wp . height - waypoints [ i - 1 ] . height ;
621- return sum + ( diff > 0 ? diff : 0 ) ;
622- } , 0 ) ;
623- const exceedsBattery = time > config . maxBatteryMinutes * 60 ;
624- return (
625- < >
626- { elevGain > 0 && (
629+ { waypoints . length >= 2 && flightStats
630+ ? ( ( ) => {
631+ const { distance, time } = flightStats ;
632+ const elevGain = waypoints . reduce ( ( sum , wp , i ) => {
633+ if ( i === 0 ) return 0 ;
634+ const diff = wp . height - waypoints [ i - 1 ] . height ;
635+ return sum + ( diff > 0 ? diff : 0 ) ;
636+ } , 0 ) ;
637+ const exceedsBattery = time > config . maxBatteryMinutes * 60 ;
638+ return (
639+ < >
640+ { elevGain > 0 && (
641+ < span
642+ className = "flex items-center gap-1 text-[11px]"
643+ title = "Elevation gain"
644+ >
645+ < TrendingUp className = "h-3 w-3 text-orange-400" />
646+ < span className = "text-orange-300 font-medium" >
647+ { elevGain } m
648+ </ span >
649+ </ span >
650+ ) }
627651 < span
628652 className = "flex items-center gap-1 text-[11px]"
629- title = "Elevation gain "
653+ title = "Total distance "
630654 >
631- < TrendingUp className = "h-3 w-3 text-orange-400" />
632- < span className = "text-orange-300 font-medium" >
633- { elevGain } m
655+ < Route className = "h-3 w-3 text-emerald-400" />
656+ < span className = "text-emerald-300 font-medium" >
657+ { distance >= 1000
658+ ? `${ ( distance / 1000 ) . toFixed ( 1 ) } km`
659+ : `~${ distance . toFixed ( 0 ) } m` }
634660 </ span >
635661 </ span >
636- ) }
637- < span
638- className = "flex items-center gap-1 text-[11px]"
639- title = "Total distance"
640- >
641- < Route className = "h-3 w-3 text-emerald-400" />
642- < span className = "text-emerald-300 font-medium" >
643- { distance >= 1000
644- ? `${ ( distance / 1000 ) . toFixed ( 1 ) } km`
645- : `~${ distance . toFixed ( 0 ) } m` }
646- </ span >
647- </ span >
648- < span
649- className = "flex items-center gap-1 text-[11px]"
650- title = {
651- exceedsBattery
652- ? `Exceeds max battery (${ config . maxBatteryMinutes } min)`
653- : "Estimated flight time"
654- }
655- >
656- < Clock
657- className = { `h-3 w-3 ${ exceedsBattery ? "text-orange-400" : "text-yellow-400" } ` }
658- />
659662 < span
660- className = { `font-medium ${ exceedsBattery ? "text-orange-300" : "text-yellow-300" } ` }
663+ className = "flex items-center gap-1 text-[11px]"
664+ title = {
665+ exceedsBattery
666+ ? `Exceeds max battery (${ config . maxBatteryMinutes } min)`
667+ : "Estimated flight time"
668+ }
661669 >
662- { formatDuration ( time ) }
670+ < Clock
671+ className = { `h-3 w-3 ${ exceedsBattery ? "text-orange-400" : "text-yellow-400" } ` }
672+ />
673+ < span
674+ className = { `font-medium ${ exceedsBattery ? "text-orange-300" : "text-yellow-300" } ` }
675+ >
676+ { formatDuration ( time ) }
677+ </ span >
663678 </ span >
664- </ span >
665- </ >
666- ) ;
667- } ) ( )
668- ) : (
669- < span className = "text-[10px] text-muted-foreground" >
670- Add 2+ waypoints
671- </ span >
672- ) }
679+ </ >
680+ ) ;
681+ } ) ( )
682+ : null }
673683 </ div >
674684 </ div >
675685
@@ -727,13 +737,11 @@ export default function App() {
727737 < div className = "h-6 w-6 rounded-full bg-muted flex items-center justify-center shrink-0" >
728738 < User className = "h-3 w-3 text-muted-foreground" />
729739 </ div >
730- Sign in to save missions
740+ Guest mode. Sign in to save missions
731741 </ Button >
732742 ) }
733743 </ div >
734744 </ div >
735-
736- { /* Map */ }
737745 < div className = "flex-1 relative" >
738746 < MapView />
739747 < BulkActionToolbar />
0 commit comments