feat: Plus und Minus Icons
This commit is contained in:
@@ -23,4 +23,4 @@ NEXT_PUBLIC_USE_MOCKS=true
|
|||||||
# z.B. http://10.10.0.13/xyz/index.aspx -> basePath in config.json auf /xyz setzen
|
# z.B. http://10.10.0.13/xyz/index.aspx -> basePath in config.json auf /xyz setzen
|
||||||
# basePath wird jetzt in public/config.json gepflegt
|
# basePath wird jetzt in public/config.json gepflegt
|
||||||
# App-Versionsnummer
|
# App-Versionsnummer
|
||||||
NEXT_PUBLIC_APP_VERSION=1.1.375
|
NEXT_PUBLIC_APP_VERSION=1.1.376
|
||||||
|
|||||||
@@ -24,4 +24,4 @@ NEXT_PUBLIC_USE_MOCKS=false
|
|||||||
# basePath wird jetzt in public/config.json gepflegt
|
# basePath wird jetzt in public/config.json gepflegt
|
||||||
|
|
||||||
# App-Versionsnummer
|
# App-Versionsnummer
|
||||||
NEXT_PUBLIC_APP_VERSION=1.1.375
|
NEXT_PUBLIC_APP_VERSION=1.1.376
|
||||||
|
|||||||
14
components/icons/material-symbols/MinusIcon.js
Normal file
14
components/icons/material-symbols/MinusIcon.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
const MinusIcon = ({ className = "h-8 w-8" }) => (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
className={className}
|
||||||
|
>
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M5 12h14" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default MinusIcon;
|
||||||
14
components/icons/material-symbols/PlusIcon.js
Normal file
14
components/icons/material-symbols/PlusIcon.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
const PlusIcon = ({ className = "h-8 w-8" }) => (
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
fill="none"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
stroke="currentColor"
|
||||||
|
className={className}
|
||||||
|
>
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" d="M12 5v14M5 12h14" />
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
|
||||||
|
export default PlusIcon;
|
||||||
@@ -15,6 +15,8 @@ import InfoIcon from "@/components/icons/material-symbols/InfoIcon";
|
|||||||
import AlarmIcon from "@/components/icons/material-symbols/AlarmIcon";
|
import AlarmIcon from "@/components/icons/material-symbols/AlarmIcon";
|
||||||
import MapMarkerIcon from "@/components/icons/material-symbols/MapMarkerIcon";
|
import MapMarkerIcon from "@/components/icons/material-symbols/MapMarkerIcon";
|
||||||
import ExpandIcon from "@/components/icons/material-symbols/ExpandIcon";
|
import ExpandIcon from "@/components/icons/material-symbols/ExpandIcon";
|
||||||
|
import PlusIcon from "@/components/icons/material-symbols/PlusIcon";
|
||||||
|
import MinusIcon from "@/components/icons/material-symbols/MinusIcon";
|
||||||
import PoiUpdateModal from "@/components/pois/poiUpdateModal/PoiUpdateModal.js";
|
import PoiUpdateModal from "@/components/pois/poiUpdateModal/PoiUpdateModal.js";
|
||||||
import { ToastContainer, toast } from "react-toastify";
|
import { ToastContainer, toast } from "react-toastify";
|
||||||
import plusRoundIcon from "../icons/devices/overlapping/PlusRoundIcon.js";
|
import plusRoundIcon from "../icons/devices/overlapping/PlusRoundIcon.js";
|
||||||
@@ -1266,6 +1268,27 @@ const MapComponent = ({ locations, onLocationUpdate, lineCoordinates }) => {
|
|||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
{/* Custom Zoom Controls bottom-right, styled in littwin-blue to match app icons */}
|
||||||
|
<div className="absolute bottom-8 right-3 z-50 flex flex-col gap-1">
|
||||||
|
<button
|
||||||
|
data-testid="zoom-in"
|
||||||
|
onClick={() => map?.zoomIn?.()}
|
||||||
|
aria-label="Zoom in"
|
||||||
|
className="rounded-md bg-white/90 hover:bg-white shadow-sm p-1"
|
||||||
|
title="Zoom in"
|
||||||
|
>
|
||||||
|
<PlusIcon className="h-5 w-5 text-littwin-blue" />
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
data-testid="zoom-out"
|
||||||
|
onClick={() => map?.zoomOut?.()}
|
||||||
|
aria-label="Zoom out"
|
||||||
|
className="rounded-md bg-white/90 hover:bg-white shadow-sm p-1"
|
||||||
|
title="Zoom out"
|
||||||
|
>
|
||||||
|
<MinusIcon className="h-5 w-5 text-littwin-blue" />
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
{/* Marker/AreaDropdown Panel außerhalb der Button-Leiste platzieren, damit die Position mit den anderen Panels identisch ist */}
|
{/* Marker/AreaDropdown Panel außerhalb der Button-Leiste platzieren, damit die Position mit den anderen Panels identisch ist */}
|
||||||
{overlay === "area" && <AreaDropdown onClose={() => setOverlay(null)} />}
|
{overlay === "area" && <AreaDropdown onClose={() => setOverlay(null)} />}
|
||||||
{/* BaseMapPanel entfernt */}
|
{/* BaseMapPanel entfernt */}
|
||||||
|
|||||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "nodemap",
|
"name": "nodemap",
|
||||||
"version": "1.1.375",
|
"version": "1.1.376",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "nodemap",
|
"name": "nodemap",
|
||||||
"version": "1.1.375",
|
"version": "1.1.376",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/react": "^11.13.3",
|
"@emotion/react": "^11.13.3",
|
||||||
"@emotion/styled": "^11.13.0",
|
"@emotion/styled": "^11.13.0",
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "nodemap",
|
"name": "nodemap",
|
||||||
"version": "1.1.375",
|
"version": "1.1.376",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@emotion/react": "^11.13.3",
|
"@emotion/react": "^11.13.3",
|
||||||
"@emotion/styled": "^11.13.0",
|
"@emotion/styled": "^11.13.0",
|
||||||
|
|||||||
@@ -282,10 +282,10 @@ test("zoom control buttons update mapZoom", async ({ page }) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
await page.goto("http://localhost:3000/?m=12&u=484");
|
await page.goto("http://localhost:3000/?m=12&u=484");
|
||||||
// Wait for Leaflet map and zoom controls
|
// Wait for Leaflet map and custom zoom controls
|
||||||
await page.locator("#map.leaflet-container").waitFor({ state: "visible", timeout: 20_000 });
|
await page.locator("#map.leaflet-container").waitFor({ state: "visible", timeout: 20_000 });
|
||||||
const zoomInBtn = page.locator(".leaflet-control-zoom-in");
|
const zoomInBtn = page.getByTestId("zoom-in");
|
||||||
const zoomOutBtn = page.locator(".leaflet-control-zoom-out");
|
const zoomOutBtn = page.getByTestId("zoom-out");
|
||||||
await expect(zoomInBtn).toBeVisible();
|
await expect(zoomInBtn).toBeVisible();
|
||||||
await expect(zoomOutBtn).toBeVisible();
|
await expect(zoomOutBtn).toBeVisible();
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<testsuites id="" name="" tests="3" failures="3" skipped="0" errors="0" time="14.65598">
|
<testsuites id="" name="" tests="3" failures="3" skipped="0" errors="0" time="14.492743999999998">
|
||||||
<testsuite name="mapcomponent.spec.js" timestamp="2025-09-16T10:21:16.870Z" hostname="chromium" tests="3" failures="3" skipped="0" time="38.656" errors="0">
|
<testsuite name="mapcomponent.spec.js" timestamp="2025-09-16T10:34:03.733Z" hostname="chromium" tests="3" failures="3" skipped="0" time="38.186" errors="0">
|
||||||
<testcase name="MapComponent" classname="mapcomponent.spec.js" time="13.578">
|
<testcase name="MapComponent" classname="mapcomponent.spec.js" time="13.365">
|
||||||
<failure message="mapcomponent.spec.js:162:5 MapComponent" type="FAILURE">
|
<failure message="mapcomponent.spec.js:162:5 MapComponent" type="FAILURE">
|
||||||
<![CDATA[ [chromium] › mapcomponent.spec.js:162:5 › MapComponent ───────────────────────────────────────────
|
<![CDATA[ [chromium] › mapcomponent.spec.js:162:5 › MapComponent ───────────────────────────────────────────
|
||||||
|
|
||||||
@@ -56,7 +56,7 @@
|
|||||||
]]>
|
]]>
|
||||||
</system-out>
|
</system-out>
|
||||||
</testcase>
|
</testcase>
|
||||||
<testcase name="mouse wheel zoom updates mapZoom" classname="mapcomponent.spec.js" time="12.558">
|
<testcase name="mouse wheel zoom updates mapZoom" classname="mapcomponent.spec.js" time="12.426">
|
||||||
<failure message="mapcomponent.spec.js:239:5 mouse wheel zoom updates mapZoom" type="FAILURE">
|
<failure message="mapcomponent.spec.js:239:5 mouse wheel zoom updates mapZoom" type="FAILURE">
|
||||||
<![CDATA[ [chromium] › mapcomponent.spec.js:239:5 › mouse wheel zoom updates mapZoom ───────────────────────
|
<![CDATA[ [chromium] › mapcomponent.spec.js:239:5 › mouse wheel zoom updates mapZoom ───────────────────────
|
||||||
|
|
||||||
@@ -103,7 +103,7 @@
|
|||||||
]]>
|
]]>
|
||||||
</system-out>
|
</system-out>
|
||||||
</testcase>
|
</testcase>
|
||||||
<testcase name="zoom control buttons update mapZoom" classname="mapcomponent.spec.js" time="12.52">
|
<testcase name="zoom control buttons update mapZoom" classname="mapcomponent.spec.js" time="12.395">
|
||||||
<failure message="mapcomponent.spec.js:272:5 zoom control buttons update mapZoom" type="FAILURE">
|
<failure message="mapcomponent.spec.js:272:5 zoom control buttons update mapZoom" type="FAILURE">
|
||||||
<![CDATA[ [chromium] › mapcomponent.spec.js:272:5 › zoom control buttons update mapZoom ────────────────────
|
<![CDATA[ [chromium] › mapcomponent.spec.js:272:5 › zoom control buttons update mapZoom ────────────────────
|
||||||
|
|
||||||
|
|||||||
@@ -6,61 +6,30 @@
|
|||||||
- generic [ref=e4]:
|
- generic [ref=e4]:
|
||||||
- generic:
|
- generic:
|
||||||
- generic:
|
- generic:
|
||||||
- button "Marker" [ref=e5] [cursor=pointer]
|
- generic [ref=e5]:
|
||||||
- button "Marker" [ref=e6] [cursor=pointer]
|
- button "Zoom in" [ref=e6] [cursor=pointer]:
|
||||||
- button "Marker" [ref=e7] [cursor=pointer]
|
- generic [ref=e7] [cursor=pointer]: +
|
||||||
- button "Marker" [ref=e8] [cursor=pointer]
|
- button "Zoom out" [ref=e8] [cursor=pointer]:
|
||||||
- button "Marker" [ref=e9] [cursor=pointer]
|
- generic [ref=e9] [cursor=pointer]: −
|
||||||
- button "Marker" [ref=e10] [cursor=pointer]
|
- generic [ref=e10]:
|
||||||
- button "Marker" [ref=e11] [cursor=pointer]
|
- link "Leaflet" [ref=e11] [cursor=pointer]:
|
||||||
- button "Marker" [ref=e12] [cursor=pointer]
|
|
||||||
- button "Marker" [ref=e13] [cursor=pointer]
|
|
||||||
- button "Marker" [ref=e14] [cursor=pointer]
|
|
||||||
- button "Marker" [ref=e15] [cursor=pointer]
|
|
||||||
- button "Marker" [ref=e16] [cursor=pointer]
|
|
||||||
- button "Marker" [ref=e17] [cursor=pointer]
|
|
||||||
- button "Marker" [ref=e18] [cursor=pointer]
|
|
||||||
- button "Marker" [ref=e19] [cursor=pointer]
|
|
||||||
- button "Marker" [ref=e20] [cursor=pointer]
|
|
||||||
- button "Marker" [ref=e21] [cursor=pointer]
|
|
||||||
- button "Marker" [ref=e22] [cursor=pointer]
|
|
||||||
- button "Marker" [ref=e23] [cursor=pointer]
|
|
||||||
- button "Marker" [ref=e24] [cursor=pointer]
|
|
||||||
- button "Marker" [ref=e25] [cursor=pointer]
|
|
||||||
- button "Marker" [ref=e26] [cursor=pointer]
|
|
||||||
- button "Marker" [ref=e27] [cursor=pointer]
|
|
||||||
- button "Marker" [ref=e28] [cursor=pointer]
|
|
||||||
- button "Marker" [ref=e29] [cursor=pointer]
|
|
||||||
- button "Marker" [ref=e30] [cursor=pointer]
|
|
||||||
- button "Marker" [ref=e31] [cursor=pointer]
|
|
||||||
- button "Marker" [ref=e32] [cursor=pointer]
|
|
||||||
- button "Marker" [ref=e33] [cursor=pointer]
|
|
||||||
- generic:
|
|
||||||
- generic:
|
|
||||||
- generic [ref=e34]:
|
|
||||||
- button "Zoom in" [ref=e35] [cursor=pointer]:
|
|
||||||
- generic [ref=e36] [cursor=pointer]: +
|
|
||||||
- button "Zoom out" [ref=e37] [cursor=pointer]:
|
|
||||||
- generic [ref=e38] [cursor=pointer]: −
|
|
||||||
- generic [ref=e39]:
|
|
||||||
- link "Leaflet" [ref=e40] [cursor=pointer]:
|
|
||||||
- /url: https://leafletjs.com
|
- /url: https://leafletjs.com
|
||||||
- img [ref=e41] [cursor=pointer]
|
- img [ref=e12] [cursor=pointer]
|
||||||
- text: Leaflet
|
- text: Leaflet
|
||||||
- generic [ref=e45]: "|"
|
- generic [ref=e16]: "|"
|
||||||
- text: © OpenStreetMap contributors
|
- text: © OpenStreetMap contributors
|
||||||
- generic [ref=e46]:
|
- generic [ref=e17]:
|
||||||
- button "Marker" [ref=e47] [cursor=pointer]:
|
- button "Marker" [ref=e18] [cursor=pointer]:
|
||||||
- img [ref=e48] [cursor=pointer]
|
- img [ref=e19] [cursor=pointer]
|
||||||
- button "Koordinatensuche einblenden" [ref=e51] [cursor=pointer]:
|
- button "Koordinatensuche einblenden" [ref=e22] [cursor=pointer]:
|
||||||
- img [ref=e52] [cursor=pointer]
|
- img [ref=e23] [cursor=pointer]
|
||||||
- button "Bearbeitungsmodus aktivieren" [ref=e54] [cursor=pointer]:
|
- button "Bearbeitungsmodus aktivieren" [ref=e25] [cursor=pointer]:
|
||||||
- img [ref=e55] [cursor=pointer]
|
- img [ref=e26] [cursor=pointer]
|
||||||
- button "Karte auf Standardansicht" [ref=e57] [cursor=pointer]:
|
- button "Karte auf Standardansicht" [ref=e28] [cursor=pointer]:
|
||||||
- img [ref=e58] [cursor=pointer]
|
- img [ref=e29] [cursor=pointer]
|
||||||
- button "Layer-Panel einblenden" [active] [ref=e60] [cursor=pointer]:
|
- button "Layer-Panel einblenden" [active] [ref=e31] [cursor=pointer]:
|
||||||
- img [ref=e61] [cursor=pointer]
|
- img [ref=e32] [cursor=pointer]
|
||||||
- button "Info einblenden" [ref=e63] [cursor=pointer]:
|
- button "Info einblenden" [ref=e34] [cursor=pointer]:
|
||||||
- img [ref=e64] [cursor=pointer]
|
- img [ref=e35] [cursor=pointer]
|
||||||
- alert [ref=e66]
|
- alert [ref=e37]
|
||||||
```
|
```
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.6 MiB After Width: | Height: | Size: 1.6 MiB |
Binary file not shown.
@@ -6,61 +6,30 @@
|
|||||||
- generic [ref=e4]:
|
- generic [ref=e4]:
|
||||||
- generic:
|
- generic:
|
||||||
- generic:
|
- generic:
|
||||||
- button "Marker" [ref=e5] [cursor=pointer]
|
- generic [ref=e5]:
|
||||||
- button "Marker" [ref=e6] [cursor=pointer]
|
- button "Zoom in" [ref=e6] [cursor=pointer]:
|
||||||
- button "Marker" [ref=e7] [cursor=pointer]
|
- generic [ref=e7] [cursor=pointer]: +
|
||||||
- button "Marker" [ref=e8] [cursor=pointer]
|
- button "Zoom out" [ref=e8] [cursor=pointer]:
|
||||||
- button "Marker" [ref=e9] [cursor=pointer]
|
- generic [ref=e9] [cursor=pointer]: −
|
||||||
- button "Marker" [ref=e10] [cursor=pointer]
|
- generic [ref=e10]:
|
||||||
- button "Marker" [ref=e11] [cursor=pointer]
|
- link "Leaflet" [ref=e11] [cursor=pointer]:
|
||||||
- button "Marker" [ref=e12] [cursor=pointer]
|
|
||||||
- button "Marker" [ref=e13] [cursor=pointer]
|
|
||||||
- button "Marker" [ref=e14] [cursor=pointer]
|
|
||||||
- button "Marker" [ref=e15] [cursor=pointer]
|
|
||||||
- button "Marker" [ref=e16] [cursor=pointer]
|
|
||||||
- button "Marker" [ref=e17] [cursor=pointer]
|
|
||||||
- button "Marker" [ref=e18] [cursor=pointer]
|
|
||||||
- button "Marker" [ref=e19] [cursor=pointer]
|
|
||||||
- button "Marker" [ref=e20] [cursor=pointer]
|
|
||||||
- button "Marker" [ref=e21] [cursor=pointer]
|
|
||||||
- button "Marker" [ref=e22] [cursor=pointer]
|
|
||||||
- button "Marker" [ref=e23] [cursor=pointer]
|
|
||||||
- button "Marker" [ref=e24] [cursor=pointer]
|
|
||||||
- button "Marker" [ref=e25] [cursor=pointer]
|
|
||||||
- button "Marker" [ref=e26] [cursor=pointer]
|
|
||||||
- button "Marker" [ref=e27] [cursor=pointer]
|
|
||||||
- button "Marker" [ref=e28] [cursor=pointer]
|
|
||||||
- button "Marker" [ref=e29] [cursor=pointer]
|
|
||||||
- button "Marker" [ref=e30] [cursor=pointer]
|
|
||||||
- button "Marker" [ref=e31] [cursor=pointer]
|
|
||||||
- button "Marker" [ref=e32] [cursor=pointer]
|
|
||||||
- button "Marker" [ref=e33] [cursor=pointer]
|
|
||||||
- generic:
|
|
||||||
- generic:
|
|
||||||
- generic [ref=e34]:
|
|
||||||
- button "Zoom in" [ref=e35] [cursor=pointer]:
|
|
||||||
- generic [ref=e36] [cursor=pointer]: +
|
|
||||||
- button "Zoom out" [ref=e37] [cursor=pointer]:
|
|
||||||
- generic [ref=e38] [cursor=pointer]: −
|
|
||||||
- generic [ref=e39]:
|
|
||||||
- link "Leaflet" [ref=e40] [cursor=pointer]:
|
|
||||||
- /url: https://leafletjs.com
|
- /url: https://leafletjs.com
|
||||||
- img [ref=e41] [cursor=pointer]
|
- img [ref=e12] [cursor=pointer]
|
||||||
- text: Leaflet
|
- text: Leaflet
|
||||||
- generic [ref=e45]: "|"
|
- generic [ref=e16]: "|"
|
||||||
- text: © OpenStreetMap contributors
|
- text: © OpenStreetMap contributors
|
||||||
- generic [ref=e46]:
|
- generic [ref=e17]:
|
||||||
- button "Marker" [ref=e47] [cursor=pointer]:
|
- button "Marker" [ref=e18] [cursor=pointer]:
|
||||||
- img [ref=e48] [cursor=pointer]
|
- img [ref=e19] [cursor=pointer]
|
||||||
- button "Koordinatensuche einblenden" [ref=e51] [cursor=pointer]:
|
- button "Koordinatensuche einblenden" [ref=e22] [cursor=pointer]:
|
||||||
- img [ref=e52] [cursor=pointer]
|
- img [ref=e23] [cursor=pointer]
|
||||||
- button "Bearbeitungsmodus aktivieren" [ref=e54] [cursor=pointer]:
|
- button "Bearbeitungsmodus aktivieren" [ref=e25] [cursor=pointer]:
|
||||||
- img [ref=e55] [cursor=pointer]
|
- img [ref=e26] [cursor=pointer]
|
||||||
- button "Karte auf Standardansicht" [ref=e57] [cursor=pointer]:
|
- button "Karte auf Standardansicht" [ref=e28] [cursor=pointer]:
|
||||||
- img [ref=e58] [cursor=pointer]
|
- img [ref=e29] [cursor=pointer]
|
||||||
- button "Layer-Panel einblenden" [ref=e60] [cursor=pointer]:
|
- button "Layer-Panel einblenden" [ref=e31] [cursor=pointer]:
|
||||||
- img [ref=e61] [cursor=pointer]
|
- img [ref=e32] [cursor=pointer]
|
||||||
- button "Info einblenden" [ref=e63] [cursor=pointer]:
|
- button "Info einblenden" [ref=e34] [cursor=pointer]:
|
||||||
- img [ref=e64] [cursor=pointer]
|
- img [ref=e35] [cursor=pointer]
|
||||||
- alert [ref=e66]
|
- alert [ref=e37]
|
||||||
```
|
```
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 1.5 MiB After Width: | Height: | Size: 1.5 MiB |
Binary file not shown.
@@ -143,10 +143,7 @@ export const initializeMap = (
|
|||||||
|
|
||||||
initMap.dragging.enable();
|
initMap.dragging.enable();
|
||||||
|
|
||||||
// Add visible +/- zoom control bottom-right (playwright-friendly)
|
// Do not add the default Leaflet zoom control; we'll render custom React controls in MapComponent
|
||||||
try {
|
|
||||||
L.control.zoom({ position: "bottomright" }).addTo(initMap);
|
|
||||||
} catch (_) {}
|
|
||||||
|
|
||||||
L.tileLayer(tileLayerUrl, {
|
L.tileLayer(tileLayerUrl, {
|
||||||
attribution: "© OpenStreetMap contributors",
|
attribution: "© OpenStreetMap contributors",
|
||||||
|
|||||||
Reference in New Issue
Block a user