Add transactional emails (SMTP) and planner features (no-go areas, notes, crash recovery)
Transactional emails: - Add nodemailer SMTP email module with dev-mode console logging - Magic link template and welcome template with HTML + plain text - Wire sendMagicLink into login flow, sendWelcome into registration - Update privacy page and deploy docs for SMTP configuration Planner features: - No-go areas: draw polygons on map (leaflet-geoman), synced via Yjs, passed to BRouter as nogos parameter, route recomputes on change - Session notes: collaborative Y.Text textarea in sidebar tab - Crash recovery: periodic localStorage save of Yjs state, restore on reconnect - Rate limit session creation (10/IP/hour) in /new route Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
05b5f6febf
commit
0a8dd0b766
27 changed files with 1030 additions and 69 deletions
|
|
@ -4,6 +4,7 @@ import L from "leaflet";
|
|||
import * as Y from "yjs";
|
||||
import type { YjsState } from "~/lib/use-yjs";
|
||||
import { baseLayers } from "@trails-cool/map";
|
||||
import { NoGoAreaLayer } from "./NoGoAreaLayer";
|
||||
import "leaflet/dist/leaflet.css";
|
||||
|
||||
function waypointIcon(index: number): L.DivIcon {
|
||||
|
|
@ -126,9 +127,38 @@ function CursorTracker({ awareness }: { awareness: YjsState["awareness"] }) {
|
|||
);
|
||||
}
|
||||
|
||||
function NoGoAreaButton({ active, onClick }: { active: boolean; onClick: () => void }) {
|
||||
return (
|
||||
<div className="leaflet-top leaflet-left" style={{ marginTop: 80 }}>
|
||||
<div className="leaflet-control leaflet-bar">
|
||||
<a
|
||||
href="#"
|
||||
role="button"
|
||||
title={active ? "Cancel no-go area" : "Draw no-go area"}
|
||||
onClick={(e) => { e.preventDefault(); onClick(); }}
|
||||
style={{
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
width: 30,
|
||||
height: 30,
|
||||
fontSize: 16,
|
||||
background: active ? "#fecaca" : "white",
|
||||
color: active ? "#dc2626" : "#333",
|
||||
}}
|
||||
>
|
||||
⊘
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function PlannerMap({ yjs, onRouteRequest, highlightPosition }: PlannerMapProps) {
|
||||
const [waypoints, setWaypoints] = useState<WaypointData[]>([]);
|
||||
const [routeGeoJson, setRouteGeoJson] = useState<L.LatLngExpression[] | null>(null);
|
||||
const [noGoDrawing, setNoGoDrawing] = useState(false);
|
||||
const toggleNoGoDraw = useCallback(() => setNoGoDrawing((v) => !v), []);
|
||||
|
||||
// Sync waypoints from Yjs
|
||||
useEffect(() => {
|
||||
|
|
@ -215,8 +245,10 @@ export function PlannerMap({ yjs, onRouteRequest, highlightPosition }: PlannerMa
|
|||
))}
|
||||
</LayersControl>
|
||||
|
||||
<MapClickHandler onAdd={addWaypoint} />
|
||||
<MapClickHandler onAdd={noGoDrawing ? () => {} : addWaypoint} />
|
||||
<CursorTracker awareness={yjs.awareness} />
|
||||
<NoGoAreaLayer noGoAreas={yjs.noGoAreas} doc={yjs.doc} enabled={noGoDrawing} onToggle={toggleNoGoDraw} />
|
||||
<NoGoAreaButton active={noGoDrawing} onClick={toggleNoGoDraw} />
|
||||
|
||||
{waypoints.map((wp, i) => (
|
||||
<Marker
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue