added ui fro and iintegerated ui for decal modification
This commit is contained in:
@@ -1,23 +1,29 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
|
||||||
interface RotationInputProps {
|
interface RotationInputProps {
|
||||||
heading?: string; // Optional label for the input
|
heading?: string;
|
||||||
label?: string; // Optional label for the input
|
label?: string;
|
||||||
onChange: (value: string) => void; // Callback for value change
|
onChange: (value: string) => void;
|
||||||
placeholder?: string; // Optional placeholder
|
placeholder?: string;
|
||||||
type?: string; // Input type (e.g., text, number, email)
|
type?: string;
|
||||||
value?: number;
|
value?: number;
|
||||||
disabled?: boolean; // Disable the input if true
|
disabled?: boolean;
|
||||||
|
min?: number;
|
||||||
|
max?: number;
|
||||||
|
step?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
const RotationInput: React.FC<RotationInputProps> = ({
|
const RotationInput: React.FC<RotationInputProps> = ({
|
||||||
label = "Rotate :", // Default label
|
label = "Rotate :",
|
||||||
heading = "Rotation", // Default heading
|
heading = "Rotation",
|
||||||
onChange,
|
onChange,
|
||||||
placeholder = "Enter value", // Default placeholder
|
placeholder = "Enter value",
|
||||||
type = "number", // Default type
|
type = "number",
|
||||||
value = "number",
|
value,
|
||||||
disabled = false,
|
disabled = false,
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
step,
|
||||||
}) => {
|
}) => {
|
||||||
return (
|
return (
|
||||||
<div className="custom-input-container">
|
<div className="custom-input-container">
|
||||||
@@ -32,6 +38,9 @@ const RotationInput: React.FC<RotationInputProps> = ({
|
|||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
value={value}
|
value={value}
|
||||||
disabled={disabled}
|
disabled={disabled}
|
||||||
|
min={min}
|
||||||
|
max={max}
|
||||||
|
step={step}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,42 +1,178 @@
|
|||||||
|
import { useParams } from "react-router-dom";
|
||||||
|
import { useVersionContext } from "../../../../modules/builder/version/versionContext";
|
||||||
|
import { useSceneContext } from "../../../../modules/scene/sceneContext";
|
||||||
|
import { useBuilderStore } from "../../../../store/builder/useBuilderStore";
|
||||||
import { LayeringBottomIcon, LayeringTopIcon } from "../../../icons/ExportCommonIcons";
|
import { LayeringBottomIcon, LayeringTopIcon } from "../../../icons/ExportCommonIcons";
|
||||||
|
import { useSocketStore } from "../../../../store/builder/store";
|
||||||
import InputRange from "../../../ui/inputs/InputRange";
|
import InputRange from "../../../ui/inputs/InputRange";
|
||||||
import RotationInput from "../customInput/RotationInput";
|
import RotationInput from "../customInput/RotationInput";
|
||||||
|
|
||||||
|
import { getUserData } from "../../../../functions/getUserData";
|
||||||
|
// import { upsertWallApi } from "../../../../services/factoryBuilder/wall/upsertWallApi";
|
||||||
|
// import { upsertFloorApi } from "../../../../services/factoryBuilder/floor/upsertFloorApi";
|
||||||
|
|
||||||
const SelectedDecalProperties = () => {
|
const SelectedDecalProperties = () => {
|
||||||
|
const { selectedDecal, setSelectedDecal } = useBuilderStore();
|
||||||
|
const { wallStore, floorStore } = useSceneContext();
|
||||||
|
const { updateDecal: updateDecalFromWall } = wallStore();
|
||||||
|
const { updateDecal: updateDecalFromFloor } = floorStore();
|
||||||
|
const { userId, organization } = getUserData();
|
||||||
|
const { selectedVersionStore } = useVersionContext();
|
||||||
|
const { selectedVersion } = selectedVersionStore();
|
||||||
|
const { projectId } = useParams();
|
||||||
|
const { socket } = useSocketStore();
|
||||||
|
|
||||||
|
const updateBackend = (updatedData: Wall | Floor) => {
|
||||||
|
if ('wallUuid' in updatedData) {
|
||||||
|
if (projectId && updatedData) {
|
||||||
|
// API
|
||||||
|
|
||||||
|
// upsertWallApi(projectId, selectedVersion?.versionId || '', updatedData);
|
||||||
|
|
||||||
|
// SOCKET
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
wallData: updatedData,
|
||||||
|
projectId: projectId,
|
||||||
|
versionId: selectedVersion?.versionId || '',
|
||||||
|
userId: userId,
|
||||||
|
organization: organization
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.emit('v1:model-Wall:add', data);
|
||||||
|
}
|
||||||
|
} else if ('floorUuid' in updatedData) {
|
||||||
|
if (projectId && updatedData) {
|
||||||
|
// API
|
||||||
|
|
||||||
|
// upsertFloorApi(projectId, selectedVersion?.versionId || '', updatedData);
|
||||||
|
|
||||||
|
// SOCKET
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
floorData: updatedData,
|
||||||
|
projectId: projectId,
|
||||||
|
versionId: selectedVersion?.versionId || '',
|
||||||
|
userId: userId,
|
||||||
|
organization: organization
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.emit('v1:model-Floor:add', data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleRotationChange = (value: number) => {
|
||||||
|
if (!selectedDecal) return;
|
||||||
|
const updatedDecal = { ...selectedDecal.decalData, decalRotation: value };
|
||||||
|
setSelectedDecal({ ...selectedDecal, decalData: updatedDecal });
|
||||||
|
|
||||||
|
if ('wallUuid' in selectedDecal.decalData.decalType) {
|
||||||
|
const updatedWall = updateDecalFromWall(updatedDecal.decalUuid, updatedDecal);
|
||||||
|
if (updatedWall) updateBackend(updatedWall);
|
||||||
|
} else if ('floorUuid' in selectedDecal.decalData.decalType) {
|
||||||
|
const updatedFloor = updateDecalFromFloor(updatedDecal.decalUuid, updatedDecal);
|
||||||
|
if (updatedFloor) updateBackend(updatedFloor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleScaleChange = (value: number) => {
|
||||||
|
if (!selectedDecal) return;
|
||||||
|
const updatedDecal = { ...selectedDecal.decalData, decalScale: value };
|
||||||
|
setSelectedDecal({ ...selectedDecal, decalData: updatedDecal });
|
||||||
|
|
||||||
|
if ('wallUuid' in selectedDecal.decalData.decalType) {
|
||||||
|
const updatedWall = updateDecalFromWall(updatedDecal.decalUuid, updatedDecal);
|
||||||
|
if (updatedWall) updateBackend(updatedWall);
|
||||||
|
} else if ('floorUuid' in selectedDecal.decalData.decalType) {
|
||||||
|
const updatedFloor = updateDecalFromFloor(updatedDecal.decalUuid, updatedDecal);
|
||||||
|
if (updatedFloor) updateBackend(updatedFloor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleOpacityChange = (value: number) => {
|
||||||
|
if (!selectedDecal) return;
|
||||||
|
const updatedDecal = { ...selectedDecal.decalData, decalOpacity: value };
|
||||||
|
setSelectedDecal({ ...selectedDecal, decalData: updatedDecal });
|
||||||
|
|
||||||
|
if ('wallUuid' in selectedDecal.decalData.decalType) {
|
||||||
|
const updatedWall = updateDecalFromWall(updatedDecal.decalUuid, updatedDecal);
|
||||||
|
if (updatedWall) updateBackend(updatedWall);
|
||||||
|
} else if ('floorUuid' in selectedDecal.decalData.decalType) {
|
||||||
|
const updatedFloor = updateDecalFromFloor(updatedDecal.decalUuid, updatedDecal);
|
||||||
|
if (updatedFloor) updateBackend(updatedFloor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleLayerChange = (direction: "up" | "down") => {
|
||||||
|
if (!selectedDecal) return;
|
||||||
|
|
||||||
|
const position: [number, number, number] = [...(selectedDecal.decalData.decalPosition || [0, 0, 0]),];
|
||||||
|
|
||||||
|
if (direction === "up") {
|
||||||
|
position[2] = Math.abs(position[2]);
|
||||||
|
} else {
|
||||||
|
position[2] = -Math.abs(position[2]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const updatedDecal: Decal = { ...selectedDecal.decalData, decalPosition: position, };
|
||||||
|
|
||||||
|
setSelectedDecal({ ...selectedDecal, decalData: updatedDecal });
|
||||||
|
|
||||||
|
if ("wallUuid" in selectedDecal.decalData.decalType) {
|
||||||
|
const updatedWall = updateDecalFromWall(updatedDecal.decalUuid, updatedDecal);
|
||||||
|
if (updatedWall) updateBackend(updatedWall);
|
||||||
|
} else if ("floorUuid" in selectedDecal.decalData.decalType) {
|
||||||
|
const updatedFloor = updateDecalFromFloor(updatedDecal.decalUuid, updatedDecal);
|
||||||
|
if (updatedFloor) updateBackend(updatedFloor);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!selectedDecal) return null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="decal-transformation-container">
|
<div className="decal-transformation-container">
|
||||||
<div className="header">Decal Properties</div>
|
<div className="header">Decal Properties</div>
|
||||||
<section>
|
<section>
|
||||||
<RotationInput
|
<RotationInput
|
||||||
onChange={() => { }}
|
onChange={(e) => { handleRotationChange(parseFloat(e)) }}
|
||||||
value={10}
|
value={selectedDecal.decalData.decalRotation || 0}
|
||||||
/>
|
/>
|
||||||
<RotationInput
|
<RotationInput
|
||||||
|
min={0.1}
|
||||||
|
max={10}
|
||||||
|
step={0.1}
|
||||||
heading="Scaling"
|
heading="Scaling"
|
||||||
label="Scale :"
|
label="Scale :"
|
||||||
onChange={() => { }}
|
onChange={(e) => { handleScaleChange(parseFloat(e)) }}
|
||||||
value={10}
|
value={selectedDecal.decalData.decalScale || 1}
|
||||||
/>
|
/>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<InputRange
|
<InputRange
|
||||||
label="Opacity"
|
label="Opacity"
|
||||||
value={1}
|
value={selectedDecal.decalData.decalOpacity || 1}
|
||||||
min={0}
|
min={0.1}
|
||||||
step={0.1}
|
step={0.1}
|
||||||
max={1}
|
max={1}
|
||||||
onChange={(value: number) => console.log(value)}
|
onChange={(value: number) => handleOpacityChange(value)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div className="transformation-wrapper opacity">
|
<div className="transformation-wrapper opacity">
|
||||||
<div className="transformation-header">Layering</div>
|
<div className="transformation-header">Layering</div>
|
||||||
|
|
||||||
<div className="layers-list">
|
<div className="layers-list">
|
||||||
<button className="layer-move-btn">
|
<button
|
||||||
|
className="layer-move-btn"
|
||||||
|
onClick={() => handleLayerChange("down")}
|
||||||
|
>
|
||||||
<LayeringBottomIcon />
|
<LayeringBottomIcon />
|
||||||
</button>
|
</button>
|
||||||
<button className="layer-move-btn">
|
<button
|
||||||
|
className="layer-move-btn"
|
||||||
|
onClick={() => handleLayerChange("up")}
|
||||||
|
>
|
||||||
<LayeringTopIcon />
|
<LayeringTopIcon />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ function DecalInstance({ parent, visible = true, decal, zPosition = decal.decalP
|
|||||||
// debug
|
// debug
|
||||||
visible={visible}
|
visible={visible}
|
||||||
position={[decal.decalPosition[0], decal.decalPosition[1], zPosition]}
|
position={[decal.decalPosition[0], decal.decalPosition[1], zPosition]}
|
||||||
rotation={[0, 0, 0]}
|
rotation={[0, 0, decal.decalRotation * (Math.PI / 180)]}
|
||||||
scale={[decal.decalScale, decal.decalScale, 0.01]}
|
scale={[decal.decalScale, decal.decalScale, 0.01]}
|
||||||
userData={decal}
|
userData={decal}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
@@ -101,7 +101,7 @@ function DecalInstance({ parent, visible = true, decal, zPosition = decal.decalP
|
|||||||
if (e.object.userData.decalUuid) {
|
if (e.object.userData.decalUuid) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (toolMode === 'cursor') {
|
if (toolMode === 'cursor') {
|
||||||
setSelectedDecal(e.object);
|
setSelectedDecal({ decalMesh: e.object, decalData: decal });
|
||||||
setSelectedWall(null);
|
setSelectedWall(null);
|
||||||
setSelectedFloor(null);
|
setSelectedFloor(null);
|
||||||
} else if (toolMode === '3D-Delete') {
|
} else if (toolMode === '3D-Delete') {
|
||||||
@@ -131,7 +131,7 @@ function DecalInstance({ parent, visible = true, decal, zPosition = decal.decalP
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
onPointerMissed={() => {
|
onPointerMissed={() => {
|
||||||
if (selectedDecal && selectedDecal.userData.decalUuid === decal.decalUuid) {
|
if (selectedDecal && selectedDecal.decalMesh.userData.decalUuid === decal.decalUuid) {
|
||||||
setSelectedDecal(null);
|
setSelectedDecal(null);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
@@ -141,6 +141,8 @@ function DecalInstance({ parent, visible = true, decal, zPosition = decal.decalP
|
|||||||
side={THREE.DoubleSide}
|
side={THREE.DoubleSide}
|
||||||
polygonOffset
|
polygonOffset
|
||||||
polygonOffsetFactor={-1}
|
polygonOffsetFactor={-1}
|
||||||
|
transparent
|
||||||
|
opacity={decal.decalOpacity}
|
||||||
/>
|
/>
|
||||||
</Decal>
|
</Decal>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -167,6 +167,7 @@ function Wall({ wall }: { readonly wall: Wall }) {
|
|||||||
decalId: 'Default Decal',
|
decalId: 'Default Decal',
|
||||||
decalPosition: [0, 0, wall.wallThickness / 2 + 0.001],
|
decalPosition: [0, 0, wall.wallThickness / 2 + 0.001],
|
||||||
decalRotation: 0,
|
decalRotation: 0,
|
||||||
|
decalOpacity: 1,
|
||||||
decalScale: 1,
|
decalScale: 1,
|
||||||
decalType: { type: 'Wall', wallUuid: wall.wallUuid }
|
decalType: { type: 'Wall', wallUuid: wall.wallUuid }
|
||||||
}
|
}
|
||||||
@@ -183,7 +184,7 @@ function Wall({ wall }: { readonly wall: Wall }) {
|
|||||||
<MeshDiscardMaterial />
|
<MeshDiscardMaterial />
|
||||||
|
|
||||||
{wall.decals.map((decal) => (
|
{wall.decals.map((decal) => (
|
||||||
<DecalInstance parent={wall} zPosition={wall.wallThickness / 2 + 0.001} visible={visible} key={decal.decalUuid} decal={decal} />
|
<DecalInstance parent={wall} visible={visible} key={decal.decalUuid} decal={decal} />
|
||||||
))}
|
))}
|
||||||
</mesh>
|
</mesh>
|
||||||
</mesh>
|
</mesh>
|
||||||
|
|||||||
@@ -165,7 +165,7 @@ export default function PostProcessing() {
|
|||||||
)}
|
)}
|
||||||
{selectedDecal && (
|
{selectedDecal && (
|
||||||
<Outline
|
<Outline
|
||||||
selection={selectedDecal}
|
selection={selectedDecal.decalMesh}
|
||||||
selectionLayer={10}
|
selectionLayer={10}
|
||||||
width={2000}
|
width={2000}
|
||||||
blendFunction={BlendFunction.ALPHA}
|
blendFunction={BlendFunction.ALPHA}
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ interface BuilderState {
|
|||||||
zoneColor: string;
|
zoneColor: string;
|
||||||
|
|
||||||
// Decal Settings
|
// Decal Settings
|
||||||
selectedDecal: Object3D | null;
|
selectedDecal: { decalMesh: Object3D, decalData: Decal } | null;
|
||||||
deletableDecal: Object3D | null;
|
deletableDecal: Object3D | null;
|
||||||
|
|
||||||
// Aisle General
|
// Aisle General
|
||||||
@@ -87,7 +87,7 @@ interface BuilderState {
|
|||||||
setZoneColor: (color: string) => void;
|
setZoneColor: (color: string) => void;
|
||||||
|
|
||||||
// Setters - Decal
|
// Setters - Decal
|
||||||
setSelectedDecal: (decal: Object3D | null) => void;
|
setSelectedDecal: (decal: { decalMesh: Object3D, decalData: Decal } | null) => void;
|
||||||
setDeletableDecal: (decal: Object3D | null) => void;
|
setDeletableDecal: (decal: Object3D | null) => void;
|
||||||
|
|
||||||
// Setters - Aisle General
|
// Setters - Aisle General
|
||||||
@@ -290,7 +290,7 @@ export const useBuilderStore = create<BuilderState>()(
|
|||||||
|
|
||||||
// === Setters: Decal ===
|
// === Setters: Decal ===
|
||||||
|
|
||||||
setSelectedDecal: (decal: Object3D | null) => {
|
setSelectedDecal: (decal: { decalMesh: Object3D, decalData: Decal } | null) => {
|
||||||
set((state) => {
|
set((state) => {
|
||||||
state.selectedDecal = decal;
|
state.selectedDecal = decal;
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ interface FloorStore {
|
|||||||
setDepth: (uuid: string, depth: number) => void;
|
setDepth: (uuid: string, depth: number) => void;
|
||||||
setMaterial: (uuid: string, sideMaterial: string, topMaterial: string) => void;
|
setMaterial: (uuid: string, sideMaterial: string, topMaterial: string) => void;
|
||||||
addDecal: (floors: string, decal: Decal) => void;
|
addDecal: (floors: string, decal: Decal) => void;
|
||||||
updateDecal: (decalUuid: string, decal: Decal) => void;
|
updateDecal: (decalUuid: string, decal: Decal) => Floor | undefined;
|
||||||
removeDecal: (decalUuid: string) => Floor | undefined;
|
removeDecal: (decalUuid: string) => Floor | undefined;
|
||||||
updateDecalPosition: (decalUuid: string, position: [number, number, number]) => void;
|
updateDecalPosition: (decalUuid: string, position: [number, number, number]) => void;
|
||||||
updateDecalRotation: (decalUuid: string, rotation: number) => void;
|
updateDecalRotation: (decalUuid: string, rotation: number) => void;
|
||||||
@@ -203,15 +203,20 @@ export const createFloorStore = () => {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
updateDecal: (decalUuid, updatedDecal) => set(state => {
|
updateDecal: (decalUuid, updatedDecal) => {
|
||||||
for (const floor of state.floors) {
|
let affectedFloor: Floor | undefined;
|
||||||
const index = floor.decals.findIndex(d => d.decalUuid === decalUuid);
|
set(state => {
|
||||||
if (index !== -1) {
|
for (const floor of state.floors) {
|
||||||
floor.decals[index] = updatedDecal;
|
const index = floor.decals.findIndex(d => d.decalUuid === decalUuid);
|
||||||
break;
|
if (index !== -1) {
|
||||||
|
floor.decals[index] = updatedDecal;
|
||||||
|
affectedFloor = JSON.parse(JSON.stringify(floor));
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}),
|
return affectedFloor;
|
||||||
|
},
|
||||||
|
|
||||||
removeDecal: (decalUuid) => {
|
removeDecal: (decalUuid) => {
|
||||||
let affectedFloor: Floor | undefined;
|
let affectedFloor: Floor | undefined;
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ interface WallStore {
|
|||||||
clearWalls: () => void;
|
clearWalls: () => void;
|
||||||
removeWallByPoints: (Points: [Point, Point]) => Wall | undefined;
|
removeWallByPoints: (Points: [Point, Point]) => Wall | undefined;
|
||||||
addDecal: (wallUuid: string, decal: Decal) => void;
|
addDecal: (wallUuid: string, decal: Decal) => void;
|
||||||
updateDecal: (decalUuid: string, decal: Decal) => void;
|
updateDecal: (decalUuid: string, decal: Decal) => Wall | undefined;
|
||||||
removeDecal: (decalUuid: string) => Wall | undefined;
|
removeDecal: (decalUuid: string) => Wall | undefined;
|
||||||
updateDecalPosition: (decalUuid: string, position: [number, number, number]) => void;
|
updateDecalPosition: (decalUuid: string, position: [number, number, number]) => void;
|
||||||
updateDecalRotation: (decalUuid: string, rotation: number) => void;
|
updateDecalRotation: (decalUuid: string, rotation: number) => void;
|
||||||
@@ -90,14 +90,19 @@ export const createWallStore = () => {
|
|||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
|
||||||
updateDecal: (decalUuid, decal) => set((state) => {
|
updateDecal: (decalUuid, decal) => {
|
||||||
for (const wall of state.walls) {
|
let affectedWall: Wall | undefined;
|
||||||
const decalToUpdate = wall.decals.find(d => d.decalUuid === decalUuid);
|
set((state) => {
|
||||||
if (decalToUpdate) {
|
for (const wall of state.walls) {
|
||||||
Object.assign(decalToUpdate, decal);
|
const decalToUpdate = wall.decals.find(d => d.decalUuid === decalUuid);
|
||||||
|
if (decalToUpdate) {
|
||||||
|
Object.assign(decalToUpdate, decal);
|
||||||
|
affectedWall = JSON.parse(JSON.stringify(wall));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
}),
|
return affectedWall;
|
||||||
|
},
|
||||||
|
|
||||||
removeDecal: (decalUuid) => {
|
removeDecal: (decalUuid) => {
|
||||||
let affectedWall: Wall | undefined;
|
let affectedWall: Wall | undefined;
|
||||||
|
|||||||
1
app/src/types/builderTypes.d.ts
vendored
1
app/src/types/builderTypes.d.ts
vendored
@@ -88,6 +88,7 @@ interface Decal {
|
|||||||
decalType: WallDecal | FloorDecal;
|
decalType: WallDecal | FloorDecal;
|
||||||
decalPosition: [number, number, number];
|
decalPosition: [number, number, number];
|
||||||
decalRotation: number;
|
decalRotation: number;
|
||||||
|
decalOpacity: number;
|
||||||
decalScale: number;
|
decalScale: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user