mirror of
https://github.com/hydralauncher/hydra.git
synced 2026-01-11 22:06:17 +00:00
deleted duplicate file, fixed assertions
This commit is contained in:
@@ -110,6 +110,7 @@ app.whenReady().then(async () => {
|
||||
x2 = "0%";
|
||||
y2 = "0%";
|
||||
}
|
||||
// Note: "135deg" case removed as it uses all default values
|
||||
|
||||
const svgContent = `
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="400" height="300" viewBox="0 0 400 300">
|
||||
|
||||
@@ -17,10 +17,10 @@ export interface SidebarAddingCustomGameModalProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
export function SidebarAddingCustomGameModal ({
|
||||
export function SidebarAddingCustomGameModal({
|
||||
visible,
|
||||
onClose,
|
||||
}: Readonly<SidebarAddingCustomGameModalProps>) {
|
||||
}: Readonly<SidebarAddingCustomGameModalProps>) {
|
||||
const { t } = useTranslation("sidebar");
|
||||
const { updateLibrary } = useLibrary();
|
||||
const { showSuccessToast, showErrorToast } = useToast();
|
||||
|
||||
@@ -7,7 +7,7 @@ import { HeroPanel } from "./hero";
|
||||
import { DescriptionHeader } from "./description-header/description-header";
|
||||
import { GallerySlider } from "./gallery-slider/gallery-slider";
|
||||
import { Sidebar } from "./sidebar/sidebar";
|
||||
import { EditCustomGameModal, EditGameModal } from "./modals";
|
||||
import { EditGameModal } from "./modals";
|
||||
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { cloudSyncContext, gameDetailsContext } from "@renderer/context";
|
||||
@@ -65,7 +65,6 @@ export function GameDetailsContent() {
|
||||
}, [shopDetails, t, game?.shop]);
|
||||
|
||||
const [backdropOpacity, setBackdropOpacity] = useState(1);
|
||||
const [showEditCustomGameModal, setShowEditCustomGameModal] = useState(false);
|
||||
const [showEditGameModal, setShowEditGameModal] = useState(false);
|
||||
|
||||
const handleHeroLoad = async () => {
|
||||
@@ -102,10 +101,6 @@ export function GameDetailsContent() {
|
||||
setShowCloudSyncModal(true);
|
||||
};
|
||||
|
||||
const handleEditCustomGameClick = () => {
|
||||
setShowEditCustomGameModal(true);
|
||||
};
|
||||
|
||||
const handleEditGameClick = () => {
|
||||
setShowEditGameModal(true);
|
||||
};
|
||||
@@ -181,11 +176,7 @@ export function GameDetailsContent() {
|
||||
<button
|
||||
type="button"
|
||||
className="game-details__edit-custom-game-button"
|
||||
onClick={
|
||||
game?.shop === "custom"
|
||||
? handleEditCustomGameClick
|
||||
: handleEditGameClick
|
||||
}
|
||||
onClick={handleEditGameClick}
|
||||
title={t("edit_custom_game")}
|
||||
>
|
||||
<PencilIcon size={16} />
|
||||
@@ -231,23 +222,12 @@ export function GameDetailsContent() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{game?.shop === "custom" && (
|
||||
<EditCustomGameModal
|
||||
visible={showEditCustomGameModal}
|
||||
onClose={() => setShowEditCustomGameModal(false)}
|
||||
game={game}
|
||||
onGameUpdated={handleGameUpdated}
|
||||
/>
|
||||
)}
|
||||
|
||||
{game?.shop !== "custom" && (
|
||||
<EditGameModal
|
||||
visible={showEditGameModal}
|
||||
onClose={() => setShowEditGameModal(false)}
|
||||
game={game}
|
||||
onGameUpdated={handleGameUpdated}
|
||||
/>
|
||||
)}
|
||||
<EditGameModal
|
||||
visible={showEditGameModal}
|
||||
onClose={() => setShowEditGameModal(false)}
|
||||
game={game}
|
||||
onGameUpdated={handleGameUpdated}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
@use "../../../scss/globals.scss";
|
||||
|
||||
.edit-custom-game-modal {
|
||||
&__container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: calc(globals.$spacing-unit * 2);
|
||||
}
|
||||
|
||||
&__form {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: calc(globals.$spacing-unit * 2);
|
||||
}
|
||||
|
||||
&__image-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: globals.$spacing-unit;
|
||||
}
|
||||
|
||||
&__image-preview {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: globals.$spacing-unit;
|
||||
border: 1px dashed globals.$border-color;
|
||||
border-radius: 8px;
|
||||
background-color: rgba(255, 255, 255, 0.05);
|
||||
}
|
||||
|
||||
&__preview-image {
|
||||
max-width: 120px;
|
||||
max-height: 80px;
|
||||
border-radius: 8px;
|
||||
object-fit: cover;
|
||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
&__actions {
|
||||
display: flex;
|
||||
gap: globals.$spacing-unit;
|
||||
justify-content: flex-end;
|
||||
margin-top: globals.$spacing-unit;
|
||||
}
|
||||
}
|
||||
@@ -1,315 +0,0 @@
|
||||
import { useState, useEffect } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { ImageIcon } from "@primer/octicons-react";
|
||||
|
||||
import { Modal, TextField, Button } from "@renderer/components";
|
||||
import { useToast } from "@renderer/hooks";
|
||||
import type { Game } from "@types";
|
||||
|
||||
import "./edit-custom-game-modal.scss";
|
||||
|
||||
export interface EditCustomGameModalProps {
|
||||
visible: boolean;
|
||||
onClose: () => void;
|
||||
game: Game;
|
||||
onGameUpdated: (updatedGame: Game) => void;
|
||||
}
|
||||
|
||||
export function EditCustomGameModal({
|
||||
visible,
|
||||
onClose,
|
||||
game,
|
||||
onGameUpdated,
|
||||
}: Readonly<EditCustomGameModalProps>) {
|
||||
const { t } = useTranslation("sidebar");
|
||||
const { showSuccessToast, showErrorToast } = useToast();
|
||||
|
||||
const [gameName, setGameName] = useState("");
|
||||
const [iconPath, setIconPath] = useState("");
|
||||
const [logoPath, setLogoPath] = useState("");
|
||||
const [heroPath, setHeroPath] = useState("");
|
||||
const [isUpdating, setIsUpdating] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (game && visible) {
|
||||
setGameName(game.title || "");
|
||||
|
||||
const currentIconPath = game.iconUrl?.startsWith("local:")
|
||||
? game.iconUrl.replace("local:", "")
|
||||
: "";
|
||||
const currentLogoPath = game.logoImageUrl?.startsWith("local:")
|
||||
? game.logoImageUrl.replace("local:", "")
|
||||
: "";
|
||||
const currentHeroPath = game.libraryHeroImageUrl?.startsWith("local:")
|
||||
? game.libraryHeroImageUrl.replace("local:", "")
|
||||
: "";
|
||||
|
||||
setIconPath(currentIconPath);
|
||||
setLogoPath(currentLogoPath);
|
||||
setHeroPath(currentHeroPath);
|
||||
}
|
||||
}, [game, visible]);
|
||||
|
||||
const handleGameNameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||
setGameName(event.target.value);
|
||||
};
|
||||
|
||||
const handleSelectIcon = async () => {
|
||||
const { filePaths } = await window.electron.showOpenDialog({
|
||||
properties: ["openFile"],
|
||||
filters: [
|
||||
{
|
||||
name: t("edit_custom_game_modal_image_filter"),
|
||||
extensions: ["jpg", "jpeg", "png", "gif", "webp"],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
if (filePaths && filePaths.length > 0) {
|
||||
setIconPath(filePaths[0]);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelectLogo = async () => {
|
||||
const { filePaths } = await window.electron.showOpenDialog({
|
||||
properties: ["openFile"],
|
||||
filters: [
|
||||
{
|
||||
name: t("edit_custom_game_modal_image_filter"),
|
||||
extensions: ["jpg", "jpeg", "png", "gif", "webp"],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
if (filePaths && filePaths.length > 0) {
|
||||
setLogoPath(filePaths[0]);
|
||||
}
|
||||
};
|
||||
|
||||
const handleSelectHero = async () => {
|
||||
const { filePaths } = await window.electron.showOpenDialog({
|
||||
properties: ["openFile"],
|
||||
filters: [
|
||||
{
|
||||
name: t("edit_custom_game_modal_image_filter"),
|
||||
extensions: ["jpg", "jpeg", "png", "gif", "webp"],
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
if (filePaths && filePaths.length > 0) {
|
||||
setHeroPath(filePaths[0]);
|
||||
}
|
||||
};
|
||||
|
||||
const handleUpdateGame = async () => {
|
||||
if (!gameName.trim()) {
|
||||
showErrorToast(t("edit_custom_game_modal_fill_required"));
|
||||
return;
|
||||
}
|
||||
|
||||
setIsUpdating(true);
|
||||
|
||||
try {
|
||||
// Preserve existing image URLs if not changed
|
||||
const iconUrl = iconPath ? `local:${iconPath}` : game.iconUrl;
|
||||
const logoImageUrl = logoPath ? `local:${logoPath}` : game.logoImageUrl;
|
||||
const libraryHeroImageUrl = heroPath
|
||||
? `local:${heroPath}`
|
||||
: game.libraryHeroImageUrl;
|
||||
|
||||
const updatedGame = await window.electron.updateCustomGame(
|
||||
game.shop,
|
||||
game.objectId,
|
||||
gameName.trim(),
|
||||
iconUrl || undefined,
|
||||
logoImageUrl || undefined,
|
||||
libraryHeroImageUrl || undefined
|
||||
);
|
||||
|
||||
showSuccessToast(t("edit_custom_game_modal_success"));
|
||||
onGameUpdated(updatedGame);
|
||||
onClose();
|
||||
} catch (error) {
|
||||
console.error("Failed to update custom game:", error);
|
||||
showErrorToast(
|
||||
error instanceof Error
|
||||
? error.message
|
||||
: t("edit_custom_game_modal_failed")
|
||||
);
|
||||
} finally {
|
||||
setIsUpdating(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
if (!isUpdating) {
|
||||
setGameName(game?.title || "");
|
||||
|
||||
const currentIconPath = game?.iconUrl?.startsWith("local:")
|
||||
? game.iconUrl.replace("local:", "")
|
||||
: "";
|
||||
const currentLogoPath = game?.logoImageUrl?.startsWith("local:")
|
||||
? game.logoImageUrl.replace("local:", "")
|
||||
: "";
|
||||
const currentHeroPath = game?.libraryHeroImageUrl?.startsWith("local:")
|
||||
? game.libraryHeroImageUrl.replace("local:", "")
|
||||
: "";
|
||||
|
||||
setIconPath(currentIconPath);
|
||||
setLogoPath(currentLogoPath);
|
||||
setHeroPath(currentHeroPath);
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
const isFormValid = gameName.trim();
|
||||
|
||||
const getIconPreviewUrl = () => {
|
||||
return iconPath ? `local:${iconPath}` : null;
|
||||
};
|
||||
|
||||
const getLogoPreviewUrl = () => {
|
||||
return logoPath ? `local:${logoPath}` : null;
|
||||
};
|
||||
|
||||
const getHeroPreviewUrl = () => {
|
||||
return heroPath ? `local:${heroPath}` : null;
|
||||
};
|
||||
|
||||
return (
|
||||
<Modal
|
||||
visible={visible}
|
||||
title={t("edit_custom_game_modal")}
|
||||
description={t("edit_custom_game_modal_description")}
|
||||
onClose={handleClose}
|
||||
>
|
||||
<div className="edit-custom-game-modal__container">
|
||||
<div className="edit-custom-game-modal__form">
|
||||
<TextField
|
||||
label={t("edit_custom_game_modal_game_name")}
|
||||
placeholder={t("edit_custom_game_modal_enter_name")}
|
||||
value={gameName}
|
||||
onChange={handleGameNameChange}
|
||||
theme="dark"
|
||||
disabled={isUpdating}
|
||||
/>
|
||||
|
||||
<div className="edit-custom-game-modal__image-section">
|
||||
<TextField
|
||||
label={t("edit_custom_game_modal_icon")}
|
||||
placeholder={t("edit_custom_game_modal_select_icon")}
|
||||
value={iconPath}
|
||||
readOnly
|
||||
theme="dark"
|
||||
rightContent={
|
||||
<Button
|
||||
type="button"
|
||||
theme="outline"
|
||||
onClick={handleSelectIcon}
|
||||
disabled={isUpdating}
|
||||
>
|
||||
<ImageIcon />
|
||||
{t("edit_custom_game_modal_browse")}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
||||
{iconPath && (
|
||||
<div className="edit-custom-game-modal__image-preview">
|
||||
<img
|
||||
src={getIconPreviewUrl()!}
|
||||
alt={t("edit_custom_game_modal_icon_preview")}
|
||||
className="edit-custom-game-modal__preview-image"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="edit-custom-game-modal__image-section">
|
||||
<TextField
|
||||
label={t("edit_custom_game_modal_logo")}
|
||||
placeholder={t("edit_custom_game_modal_select_logo")}
|
||||
value={logoPath}
|
||||
readOnly
|
||||
theme="dark"
|
||||
rightContent={
|
||||
<Button
|
||||
type="button"
|
||||
theme="outline"
|
||||
onClick={handleSelectLogo}
|
||||
disabled={isUpdating}
|
||||
>
|
||||
<ImageIcon />
|
||||
{t("edit_custom_game_modal_browse")}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
||||
{logoPath && (
|
||||
<div className="edit-custom-game-modal__image-preview">
|
||||
<img
|
||||
src={getLogoPreviewUrl()!}
|
||||
alt={t("edit_custom_game_modal_logo_preview")}
|
||||
className="edit-custom-game-modal__preview-image"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="edit-custom-game-modal__image-section">
|
||||
<TextField
|
||||
label={t("edit_custom_game_modal_hero")}
|
||||
placeholder={t("edit_custom_game_modal_select_hero")}
|
||||
value={heroPath}
|
||||
readOnly
|
||||
theme="dark"
|
||||
rightContent={
|
||||
<Button
|
||||
type="button"
|
||||
theme="outline"
|
||||
onClick={handleSelectHero}
|
||||
disabled={isUpdating}
|
||||
>
|
||||
<ImageIcon />
|
||||
{t("edit_custom_game_modal_browse")}
|
||||
</Button>
|
||||
}
|
||||
/>
|
||||
|
||||
{heroPath && (
|
||||
<div className="edit-custom-game-modal__image-preview">
|
||||
<img
|
||||
src={getHeroPreviewUrl()!}
|
||||
alt={t("edit_custom_game_modal_hero_preview")}
|
||||
className="edit-custom-game-modal__preview-image"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="edit-custom-game-modal__actions">
|
||||
<Button
|
||||
type="button"
|
||||
theme="outline"
|
||||
onClick={handleClose}
|
||||
disabled={isUpdating}
|
||||
>
|
||||
{t("edit_custom_game_modal_cancel")}
|
||||
</Button>
|
||||
<Button
|
||||
type="button"
|
||||
theme="primary"
|
||||
onClick={handleUpdateGame}
|
||||
disabled={!isFormValid || isUpdating}
|
||||
>
|
||||
{isUpdating
|
||||
? t("edit_custom_game_modal_updating")
|
||||
: t("edit_custom_game_modal_update")}
|
||||
</Button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
);
|
||||
}
|
||||
@@ -4,14 +4,14 @@ import { ImageIcon } from "@primer/octicons-react";
|
||||
|
||||
import { Modal, TextField, Button } from "@renderer/components";
|
||||
import { useToast } from "@renderer/hooks";
|
||||
import type { LibraryGame } from "@types";
|
||||
import type { LibraryGame, Game } from "@types";
|
||||
|
||||
import "./edit-game-modal.scss";
|
||||
|
||||
export interface EditGameModalProps {
|
||||
visible: boolean;
|
||||
onClose: () => void;
|
||||
game: LibraryGame | null;
|
||||
game: LibraryGame | Game | null;
|
||||
onGameUpdated: (updatedGame: any) => void;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ export function EditGameModal({
|
||||
onClose,
|
||||
game,
|
||||
onGameUpdated,
|
||||
}: EditGameModalProps) {
|
||||
}: Readonly<EditGameModalProps>) {
|
||||
const { t } = useTranslation("sidebar");
|
||||
const { showSuccessToast, showErrorToast } = useToast();
|
||||
|
||||
@@ -30,13 +30,18 @@ export function EditGameModal({
|
||||
const [heroPath, setHeroPath] = useState("");
|
||||
const [isUpdating, setIsUpdating] = useState(false);
|
||||
|
||||
// Helper function to check if game is a custom game
|
||||
const isCustomGame = (game: LibraryGame | Game): boolean => {
|
||||
return game.shop === "custom";
|
||||
};
|
||||
|
||||
// Helper function to extract local path from URL
|
||||
const extractLocalPath = (url: string | null | undefined): string => {
|
||||
return url?.startsWith("local:") ? url.replace("local:", "") : "";
|
||||
};
|
||||
|
||||
// Helper function to set asset paths for custom games
|
||||
const setCustomGameAssets = (game: LibraryGame) => {
|
||||
const setCustomGameAssets = (game: LibraryGame | Game) => {
|
||||
setIconPath(extractLocalPath(game.iconUrl));
|
||||
setLogoPath(extractLocalPath(game.logoImageUrl));
|
||||
setHeroPath(extractLocalPath(game.libraryHeroImageUrl));
|
||||
@@ -53,10 +58,10 @@ export function EditGameModal({
|
||||
if (game && visible) {
|
||||
setGameName(game.title || "");
|
||||
|
||||
if (game.shop === "custom") {
|
||||
if (isCustomGame(game)) {
|
||||
setCustomGameAssets(game);
|
||||
} else {
|
||||
setNonCustomGameAssets(game);
|
||||
setNonCustomGameAssets(game as LibraryGame);
|
||||
}
|
||||
}
|
||||
}, [game, visible]);
|
||||
@@ -114,7 +119,7 @@ export function EditGameModal({
|
||||
};
|
||||
|
||||
// Helper function to prepare custom game assets
|
||||
const prepareCustomGameAssets = (game: LibraryGame) => {
|
||||
const prepareCustomGameAssets = (game: LibraryGame | Game) => {
|
||||
const iconUrl = iconPath ? `local:${iconPath}` : game.iconUrl;
|
||||
const logoImageUrl = logoPath ? `local:${logoPath}` : game.logoImageUrl;
|
||||
const libraryHeroImageUrl = heroPath
|
||||
@@ -134,7 +139,7 @@ export function EditGameModal({
|
||||
};
|
||||
|
||||
// Helper function to update custom game
|
||||
const updateCustomGame = async (game: LibraryGame) => {
|
||||
const updateCustomGame = async (game: LibraryGame | Game) => {
|
||||
const { iconUrl, logoImageUrl, libraryHeroImageUrl } =
|
||||
prepareCustomGameAssets(game);
|
||||
|
||||
@@ -173,9 +178,9 @@ export function EditGameModal({
|
||||
|
||||
try {
|
||||
const updatedGame =
|
||||
game.shop === "custom"
|
||||
isCustomGame(game)
|
||||
? await updateCustomGame(game)
|
||||
: await updateNonCustomGame(game);
|
||||
: await updateNonCustomGame(game as LibraryGame);
|
||||
|
||||
showSuccessToast(t("edit_custom_game_modal_success"));
|
||||
onGameUpdated(updatedGame);
|
||||
@@ -193,13 +198,13 @@ export function EditGameModal({
|
||||
};
|
||||
|
||||
// Helper function to reset form to initial state
|
||||
const resetFormToInitialState = (game: LibraryGame) => {
|
||||
const resetFormToInitialState = (game: LibraryGame | Game) => {
|
||||
setGameName(game.title || "");
|
||||
|
||||
if (game.shop === "custom") {
|
||||
if (isCustomGame(game)) {
|
||||
setCustomGameAssets(game);
|
||||
} else {
|
||||
setNonCustomGameAssets(game);
|
||||
setNonCustomGameAssets(game as LibraryGame);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -212,16 +217,16 @@ export function EditGameModal({
|
||||
|
||||
const isFormValid = gameName.trim();
|
||||
|
||||
const getIconPreviewUrl = () => {
|
||||
return iconPath ? `local:${iconPath}` : null;
|
||||
const getIconPreviewUrl = (): string | undefined => {
|
||||
return iconPath ? `local:${iconPath}` : undefined;
|
||||
};
|
||||
|
||||
const getLogoPreviewUrl = () => {
|
||||
return logoPath ? `local:${logoPath}` : null;
|
||||
const getLogoPreviewUrl = (): string | undefined => {
|
||||
return logoPath ? `local:${logoPath}` : undefined;
|
||||
};
|
||||
|
||||
const getHeroPreviewUrl = () => {
|
||||
return heroPath ? `local:${heroPath}` : null;
|
||||
const getHeroPreviewUrl = (): string | undefined => {
|
||||
return heroPath ? `local:${heroPath}` : undefined;
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -265,7 +270,7 @@ export function EditGameModal({
|
||||
{iconPath && (
|
||||
<div className="edit-game-modal__image-preview">
|
||||
<img
|
||||
src={getIconPreviewUrl()!}
|
||||
src={getIconPreviewUrl()}
|
||||
alt={t("edit_custom_game_modal_icon_preview")}
|
||||
className="edit-game-modal__preview-image"
|
||||
/>
|
||||
@@ -296,7 +301,7 @@ export function EditGameModal({
|
||||
{logoPath && (
|
||||
<div className="edit-game-modal__image-preview">
|
||||
<img
|
||||
src={getLogoPreviewUrl()!}
|
||||
src={getLogoPreviewUrl()}
|
||||
alt={t("edit_custom_game_modal_logo_preview")}
|
||||
className="edit-game-modal__preview-image"
|
||||
/>
|
||||
@@ -327,7 +332,7 @@ export function EditGameModal({
|
||||
{heroPath && (
|
||||
<div className="edit-game-modal__image-preview">
|
||||
<img
|
||||
src={getHeroPreviewUrl()!}
|
||||
src={getHeroPreviewUrl()}
|
||||
alt={t("edit_custom_game_modal_hero_preview")}
|
||||
className="edit-game-modal__preview-image"
|
||||
/>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
export * from "./repacks-modal";
|
||||
export * from "./download-settings-modal";
|
||||
export * from "./game-options-modal";
|
||||
export * from "./edit-custom-game-modal";
|
||||
export * from "./edit-game-modal";
|
||||
|
||||
Reference in New Issue
Block a user