improved tooltip in map, improved user-settings handling

This commit is contained in:
orangecoding
2026-01-26 11:20:02 +01:00
parent 28f7760120
commit 59226491f2
6 changed files with 110 additions and 16 deletions

View File

@@ -12,6 +12,7 @@ import { calculateDistanceForUser } from '../../services/geocoding/distanceServi
import { fromJson } from '../../utils.js';
import { trackFeature } from '../../services/tracking/Tracker.js';
import { FEATURES } from '../../features.js';
import logger from '../../services/logger.js';
const service = restana();
const userSettingsRouter = service.newRouter();
@@ -34,11 +35,12 @@ userSettingsRouter.get('/autocomplete', async (req, res) => {
res.body = results;
res.send();
} catch (error) {
res.status(500).send({ error: error.message });
res.statusCode = 500;
res.send({ error: error.message });
}
});
userSettingsRouter.post('/', async (req, res) => {
userSettingsRouter.post('/home-address', async (req, res) => {
const userId = req.session.currentUser;
const { home_address } = req.body;
@@ -51,15 +53,17 @@ userSettingsRouter.post('/', async (req, res) => {
calculateDistanceForUser(userId);
res.send({ success: true, coords });
} else {
res.status(400).send({ error: 'Could not geocode address' });
res.statusCode = 400;
res.send({ error: 'Could not geocode address' });
}
} else {
// If address is empty, maybe clear it?
upsertSettings({ home_address: null }, userId);
res.send({ success: true });
}
} catch (error) {
res.status(500).send({ error: error.message });
logger.error('Error updating home address settings', error);
res.statusCode = 500;
res.send({ error: error.message });
}
});

View File

@@ -92,7 +92,7 @@ export function upsertSettings(settingsMapOrEntry, userId = null) {
for (const [name, rawValue] of entries) {
const id = nanoid();
const create_date = Date.now();
const json = toJson(rawValue);
const json = toJson(rawValue === null ? 'null' : rawValue);
SqliteConnection.execute(
`INSERT INTO settings (id, create_date, name, value, user_id)
VALUES (@id, @create_date, @name, @value, @userId)

View File

@@ -40,6 +40,8 @@ a:active {
text-decoration: underline;
}
a {outline : none;}
.semi-icon:not(.semi-tabs-bar .semi-tabs-tab .semi-icon) {
vertical-align: middle;
}

View File

@@ -4,16 +4,20 @@
*/
import React, { useEffect, useRef, useState } from 'react';
import { renderToString } from 'react-dom/server';
import maplibregl from 'maplibre-gl';
import 'maplibre-gl/dist/maplibre-gl.css';
import { useSelector, useActions } from '../../services/state/store.js';
import { distanceMeters, generateCircleCoords, getBoundsFromCenter } from './mapUtils.js';
import { Select, Space, Typography, Button, Popover, Divider, Switch, Banner } from '@douyinfe/semi-ui-19';
import { IconFilter } from '@douyinfe/semi-icons';
import { Select, Space, Typography, Button, Popover, Divider, Switch, Banner, Toast } from '@douyinfe/semi-ui-19';
import { IconFilter, IconLink } from '@douyinfe/semi-icons';
import { IconDelete } from '@douyinfe/semi-icons';
import no_image from '../../assets/no_image.jpg';
import RangeSlider from 'react-range-slider-input';
import 'react-range-slider-input/dist/style.css';
import './Map.less';
import { xhrDelete } from '../../services/xhr.js';
const { Text } = Typography;
@@ -97,6 +101,22 @@ export default function MapView() {
return listings.filter((listing) => listing.price && listing.price >= min && listing.price <= max);
};
useEffect(() => {
window.deleteListing = async (id) => {
try {
await xhrDelete('/api/listings/', { ids: [id] });
Toast.success('Listing successfully removed');
fetchListings();
} catch (error) {
Toast.error(error.message || 'Error deleting listing');
}
};
return () => {
delete window.deleteListing;
};
}, []);
useEffect(() => {
if (map.current) return;
@@ -325,8 +345,8 @@ export default function MapView() {
? listing.provider.charAt(0).toUpperCase() + listing.provider.slice(1)
: 'N/A';
const popup = new maplibregl.Popup({ offset: 25 }).setHTML(
`<div class="map-popup-content">
const popupContent = `
<div class="map-popup-content">
<img src="${listing.image_url || no_image}" alt="${listing.title}" />
<h4>${listing.title}</h4>
<div class="info">
@@ -334,10 +354,25 @@ export default function MapView() {
<span><strong>Address:</strong> ${listing.address || 'N/A'}</span>
<span><strong>Job:</strong> ${listing.job_name || 'N/A'}</span>
<span><strong>Provider:</strong> ${capitalizedProvider}</span>
<a href="${listing.link}" target="_blank" rel="noopener noreferrer">View Listing</a>
<span><strong>Size:</strong> ${listing.size != null ? `${listing.size}` : 'N/A'}</span>
<div style="display: flex; gap: 8px; margin-top: 8px; justify-content: space-between;">
<div class="map-popup-content__linkButton">
<a href="${listing.link}" target="_blank" rel="noopener noreferrer">
${renderToString(<IconLink />)}
</a>
</div>
<button
class="map-popup-content__deleteButton"
title="Remove"
onclick="deleteListing('${listing.id}')"
>
${renderToString(<IconDelete />)}
</button>
</div>
</div>
</div>`,
);
</div>`;
const popup = new maplibregl.Popup({ offset: 25 }).setHTML(popupContent);
let color = '#3FB1CE'; // Default blue-ish
if (distanceFilter > 0 && homeAddress?.coords) {

View File

@@ -43,10 +43,62 @@
}
.info {
font-size: 0.9rem;
font-size: 0.8rem;
display: flex;
flex-direction: column;
gap: 0.2rem;
gap: 0.1rem;
}
&__linkButton {
background: var(--semi-color-primary);
color: white;
font-size: 14px;
line-height: 20px;
font-weight: 600;
height: 32px;
width: 32px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 6px;
a {
color: white;
display: flex;
align-items: center;
justify-content: center;
text-decoration: none;
width: 100%;
height: 100%;
}
&:hover {
background: var(--semi-color-primary-hover);
cursor: pointer;
}
}
&__deleteButton {
background: var(--semi-color-danger);
color: white;
border: none;
font-size: 14px;
height: 32px;
width: 32px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 6px;
cursor: pointer;
padding: 0;
&:hover {
background: var(--semi-color-danger-hover);
}
svg {
fill: currentColor;
}
}
}

View File

@@ -29,7 +29,7 @@ const UserSettings = () => {
const handleSave = async () => {
setSaving(true);
try {
const response = await xhrPost('/api/user/settings', { home_address: address });
const response = await xhrPost('/api/user/settings/home-address', { home_address: address });
if (response.status === 200) {
setCoords(response.json.coords);
await actions.userSettings.getUserSettings();
@@ -81,6 +81,7 @@ const UserSettings = () => {
<AutoComplete
data={dataSource}
value={address}
showClear
onChange={(v) => setAddress(v)}
onSearch={searchAddress}
placeholder="Enter your home address"