Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 2 additions & 7 deletions src/components/map/constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { VALHALLA_OSM_URL } from '@/utils/valhalla';

const centerCoords = import.meta.env.VITE_CENTER_COORDS?.split(',') || [];

export const DEFAULT_CENTER: [number, number] = [
Expand All @@ -13,11 +11,8 @@ export const maxBounds: [[number, number], [number, number]] | undefined =
undefined;

export const routeObjects = {
[VALHALLA_OSM_URL!]: {
color: '#0066ff',
alternativeColor: '#66a3ff',
name: 'OSM',
},
color: '#0066ff',
alternativeColor: '#66a3ff',
};

export const MAP_STYLE_STORAGE_KEY = 'selectedMapStyle';
Expand Down
8 changes: 4 additions & 4 deletions src/components/map/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import 'maplibre-gl/dist/maplibre-gl.css';
import axios from 'axios';
import { throttle } from 'throttle-debounce';
import {
VALHALLA_OSM_URL,
getValhallaUrl,
buildHeightRequest,
buildLocateRequest,
} from '@/utils/valhalla';
Expand Down Expand Up @@ -262,7 +262,7 @@ export const MapComponent = () => {
const getHeight = useCallback((lng: number, lat: number) => {
setIsHeightLoading(true);
axios
.post(VALHALLA_OSM_URL + '/height', buildHeightRequest([[lat, lng]]), {
.post(getValhallaUrl() + '/height', buildHeightRequest([[lat, lng]]), {
headers: {
'Content-Type': 'application/json',
},
Expand All @@ -285,7 +285,7 @@ export const MapComponent = () => {
setIsLocateLoading(true);
axios
.post(
VALHALLA_OSM_URL + '/locate',
getValhallaUrl() + '/locate',
buildLocateRequest({ lng, lat }, profile || 'bicycle'),
{
headers: {
Expand Down Expand Up @@ -336,7 +336,7 @@ export const MapComponent = () => {
setIsHeightLoading(true);
setHeightPayload(heightPayloadNew);
axios
.post(VALHALLA_OSM_URL + '/height', heightPayloadNew, {
.post(getValhallaUrl() + '/height', heightPayloadNew, {
headers: {
'Content-Type': 'application/json',
},
Expand Down
13 changes: 0 additions & 13 deletions src/components/map/parts/route-lines.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,19 +23,6 @@ vi.mock('@/stores/directions-store', () => ({
mockUseDirectionsStore(selector),
}));

vi.mock('@/utils/valhalla', () => ({
VALHALLA_OSM_URL: 'https://valhalla.example.com',
}));

vi.mock('../constants', () => ({
routeObjects: {
'https://valhalla.example.com': {
color: '#0066ff',
alternativeColor: '#999999',
},
},
}));

const createMockState = (overrides = {}) => ({
results: {
data: {
Expand Down
5 changes: 2 additions & 3 deletions src/components/map/parts/route-lines.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { useMemo } from 'react';
import { Source, Layer } from 'react-map-gl/maplibre';
import { useDirectionsStore } from '@/stores/directions-store';
import { VALHALLA_OSM_URL } from '@/utils/valhalla';
import { routeObjects } from '../constants';
import type { Feature, FeatureCollection, LineString } from 'geojson';
import type { ParsedDirectionsGeometry } from '@/components/types';
Expand Down Expand Up @@ -34,7 +33,7 @@ export function RouteLines() {
coordinates: coords.map((c) => [c[1] ?? 0, c[0] ?? 0]),
},
properties: {
color: routeObjects[VALHALLA_OSM_URL!]!.alternativeColor,
color: routeObjects.alternativeColor,
type: 'alternate',
summary,
},
Expand All @@ -53,7 +52,7 @@ export function RouteLines() {
coordinates: coords.map((c) => [c[1] ?? 0, c[0] ?? 0]),
},
properties: {
color: routeObjects[VALHALLA_OSM_URL!]!.color,
color: routeObjects.color,
type: 'main',
summary,
},
Expand Down
4 changes: 2 additions & 2 deletions src/components/route-planner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { format } from 'date-fns';
import { DirectionsControl } from './directions/directions';
import { IsochronesControl } from './isochrones/isochrones';
import { useCommonStore } from '@/stores/common-store';
import { VALHALLA_OSM_URL } from '@/utils/valhalla';
import { getValhallaUrl } from '@/utils/valhalla';
import {
Sheet,
SheetContent,
Expand Down Expand Up @@ -40,7 +40,7 @@ export const RoutePlanner = () => {
} = useQuery({
queryKey: ['lastUpdate'],
queryFn: async () => {
const response = await fetch(`${VALHALLA_OSM_URL}/status`);
const response = await fetch(`${getValhallaUrl()}/status`);
const data = await response.json();
return new Date(data.tileset_last_modified * 1000);
},
Expand Down
132 changes: 132 additions & 0 deletions src/components/settings-panel/server-settings.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import { useState } from 'react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import {
getBaseUrl,
setBaseUrl,
getDefaultBaseUrl,
validateBaseUrl,
normalizeBaseUrl,
testConnection,
} from '@/utils/base-url';
import { CollapsibleSection } from '@/components/ui/collapsible-section';
import { Server, RotateCcw, Loader2 } from 'lucide-react';
import {
Field,
FieldLabel,
FieldDescription,
FieldError,
} from '@/components/ui/field';

export const ServerSettings = () => {
const [baseUrl, setBaseUrlState] = useState<string>(() => getBaseUrl());
const [baseUrlError, setBaseUrlError] = useState<string | null>(null);
const [isTestingConnection, setIsTestingConnection] = useState(false);
const [isOpen, setIsOpen] = useState(false);

const handleBaseUrlChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setBaseUrlState(e.target.value);
setBaseUrlError(null);
};

const handleBaseUrlBlur = async () => {
const currentStoredUrl = getBaseUrl();
const trimmedUrl = baseUrl.trim();

if (trimmedUrl === currentStoredUrl) {
return;
}

if (trimmedUrl === '' || trimmedUrl === getDefaultBaseUrl()) {
setBaseUrl(trimmedUrl);
setBaseUrlState(trimmedUrl || getDefaultBaseUrl());
setBaseUrlError(null);
return;
}

const validation = validateBaseUrl(trimmedUrl);
if (!validation.valid) {
setBaseUrlError(validation.error || 'Invalid URL');
return;
}

setIsTestingConnection(true);
setBaseUrlError(null);

const result = await testConnection(trimmedUrl);

setIsTestingConnection(false);

if (result.reachable) {
const normalizedUrl = normalizeBaseUrl(trimmedUrl);
setBaseUrl(normalizedUrl);
setBaseUrlState(normalizedUrl);
setBaseUrlError(null);
} else {
setBaseUrlError(result.error || 'Server unreachable');
}
};

const handleResetBaseUrl = () => {
const defaultUrl = getDefaultBaseUrl();
setBaseUrlState(defaultUrl);
setBaseUrl(defaultUrl);
setBaseUrlError(null);
};

return (
<CollapsibleSection
title="Server Settings"
icon={Server}
open={isOpen}
onOpenChange={setIsOpen}
>
<div className="space-y-2">
<Field data-invalid={!!baseUrlError}>
<FieldLabel htmlFor="base-url-input">Base URL</FieldLabel>
<FieldDescription>
The Valhalla server URL for routing and isochrone requests
</FieldDescription>
<div className="flex gap-2">
<div className="flex-1 relative">
<Input
id="base-url-input"
type="url"
placeholder="https://valhalla.example.com"
value={baseUrl}
onChange={handleBaseUrlChange}
onBlur={handleBaseUrlBlur}
disabled={isTestingConnection}
aria-invalid={!!baseUrlError}
className={
baseUrlError
? 'border-destructive focus-visible:ring-destructive/50'
: ''
}
/>
{isTestingConnection && (
<div className="absolute right-3 top-1/2 -translate-y-1/2">
<Loader2 className="size-4 animate-spin text-muted-foreground" />
</div>
)}
</div>
</div>
<FieldError>{baseUrlError}</FieldError>
</Field>
<Button
variant="outline"
size="sm"
onClick={handleResetBaseUrl}
disabled={
isTestingConnection ||
normalizeBaseUrl(baseUrl) === getDefaultBaseUrl()
}
className="w-full"
>
<RotateCcw className="size-3.5" />
Reset Base URL
</Button>
</div>
</CollapsibleSection>
);
};
Loading