Refactored side menu to a separate component.
This commit is contained in:
@@ -1,5 +1,128 @@
|
|||||||
describe("SideMenu.cy.tsx", () => {
|
import { useState } from "react";
|
||||||
it("playground", () => {
|
import { LeftMenu, RightMenu } from "../../src/components/SideMenu";
|
||||||
// cy.mount()
|
|
||||||
|
// Test Fixtures
|
||||||
|
function newTestApp(position: "left" | "right") {
|
||||||
|
const Menu = position === "left" ? LeftMenu : RightMenu;
|
||||||
|
|
||||||
|
return function TestApp() {
|
||||||
|
const [isMenuOpen, setIsMenuOpen] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
data-cy="app"
|
||||||
|
className="appContainer"
|
||||||
|
style={{ overflowX: "hidden" }}
|
||||||
|
>
|
||||||
|
<button
|
||||||
|
data-cy="toggle-menu"
|
||||||
|
onClick={() => setIsMenuOpen((prev) => !prev)}
|
||||||
|
>
|
||||||
|
Open Menu
|
||||||
|
</button>
|
||||||
|
<Menu isOpen={isMenuOpen} onClose={() => setIsMenuOpen(false)}>
|
||||||
|
Menu Contents
|
||||||
|
</Menu>
|
||||||
|
<div data-cy="main-content">Main Content</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tests
|
||||||
|
function menuTest(position: "left" | "right") {
|
||||||
|
const isLeft = position === "left";
|
||||||
|
|
||||||
|
// 1. Check initial state
|
||||||
|
cy.dataCy(`${position}-menu`)
|
||||||
|
.as("menu")
|
||||||
|
.should("not.be.visible")
|
||||||
|
|
||||||
|
.contains("Menu Contents")
|
||||||
|
.should("not.be.visible");
|
||||||
|
|
||||||
|
// 2. Open menu
|
||||||
|
cy.dataCy("toggle-menu")
|
||||||
|
.as("toggle")
|
||||||
|
.click()
|
||||||
|
|
||||||
|
.get("@menu")
|
||||||
|
.should("be.visible")
|
||||||
|
|
||||||
|
.contains("Menu Contents")
|
||||||
|
.should("be.visible");
|
||||||
|
|
||||||
|
// 3. Close menu with button
|
||||||
|
cy.dataCy("close-menu")
|
||||||
|
.as("close")
|
||||||
|
.click()
|
||||||
|
|
||||||
|
.get("@menu")
|
||||||
|
.should("not.be.visible")
|
||||||
|
|
||||||
|
.contains("Menu Contents")
|
||||||
|
.should("not.be.visible");
|
||||||
|
|
||||||
|
// 4. Reopen menu, verify interactivity, then close with underlay
|
||||||
|
cy.get("@toggle")
|
||||||
|
.click()
|
||||||
|
|
||||||
|
.get("@menu")
|
||||||
|
.should("be.visible")
|
||||||
|
.click("center")
|
||||||
|
|
||||||
|
.get("@menu")
|
||||||
|
.should("be.visible")
|
||||||
|
|
||||||
|
.dataCy("app")
|
||||||
|
.click(isLeft ? "topRight" : "topLeft")
|
||||||
|
|
||||||
|
.get("@menu")
|
||||||
|
.should("not.be.visible");
|
||||||
|
}
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cy.disableTransitions();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cy.enableTransitions();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("LeftMenu Tests", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const TestApp = newTestApp("left");
|
||||||
|
cy.mount(<TestApp />);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cy.enableTransitions();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("works in portrait", () => {
|
||||||
|
cy.viewport("iphone-6");
|
||||||
|
menuTest("left");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("works in landscape", () => {
|
||||||
|
cy.viewport("iphone-6", "landscape");
|
||||||
|
menuTest("left");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("RightMenu Tests", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
const TestApp = newTestApp("right");
|
||||||
|
cy.mount(<TestApp />);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("works in portrait", () => {
|
||||||
|
cy.viewport("iphone-6");
|
||||||
|
menuTest("right");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("works in landscape", () => {
|
||||||
|
cy.viewport("iphone-6", "landscape");
|
||||||
|
menuTest("right");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+3
-115
@@ -4,53 +4,7 @@
|
|||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.leftMobileMenu {
|
/* Large */
|
||||||
height: 100vh;
|
|
||||||
width: 0;
|
|
||||||
position: fixed;
|
|
||||||
z-index: 30;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
background: white;
|
|
||||||
overflow-x: hidden;
|
|
||||||
transition: 0.5s ease-out;
|
|
||||||
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.leftMenuWrapper {
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leftMobileMenu .topNav {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leftMenuWrapper .closeButton {
|
|
||||||
margin: 10px 10px 10px auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rightMobileMenu {
|
|
||||||
height: 100vh;
|
|
||||||
width: 0;
|
|
||||||
position: fixed;
|
|
||||||
z-index: 20;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
background: white;
|
|
||||||
overflow-x: hidden;
|
|
||||||
transition: 0.5s ease-out;
|
|
||||||
box-shadow: -2px 0 5px rgba(0, 0, 0, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
.rightMenuWrapper {
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Large - Landscape Tablets / Desktops */
|
|
||||||
/* Medium - Portrait Tablets */
|
|
||||||
/* Horizontal layout, vertically scrolling picker and palette content */
|
|
||||||
@media (min-width: 992px),
|
@media (min-width: 992px),
|
||||||
(min-width: 568px) and (max-width: 991px) and (orientation: portrait) {
|
(min-width: 568px) and (max-width: 991px) and (orientation: portrait) {
|
||||||
.appContainer {
|
.appContainer {
|
||||||
@@ -87,8 +41,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Medium - Landscape Phones */
|
/* Landscape Phone */
|
||||||
/* Horizontal layout, side menu, vertical tabbed picker */
|
|
||||||
@media (min-width: 568px) and (max-width: 991px) and (orientation: landscape) {
|
@media (min-width: 568px) and (max-width: 991px) and (orientation: landscape) {
|
||||||
.appContainer {
|
.appContainer {
|
||||||
}
|
}
|
||||||
@@ -116,38 +69,6 @@
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.leftMobileMenu {
|
|
||||||
}
|
|
||||||
|
|
||||||
.leftMobileMenu.open {
|
|
||||||
width: 80vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leftMenuWrapper {
|
|
||||||
width: 80vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leftMobileMenu .topNav {
|
|
||||||
}
|
|
||||||
|
|
||||||
.leftMenuWrapper .closeButton {
|
|
||||||
}
|
|
||||||
|
|
||||||
.rightMobileMenu {
|
|
||||||
}
|
|
||||||
|
|
||||||
.rightMobileMenu.open {
|
|
||||||
width: 80vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rightMenuWrapper {
|
|
||||||
width: 80vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rightMobileMenu .closeButton {
|
|
||||||
margin: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabbedContainer {
|
.tabbedContainer {
|
||||||
max-height: 100vh;
|
max-height: 100vh;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
@@ -172,8 +93,7 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Small - Portrait Phones*/
|
/* Portrait Phone */
|
||||||
/* Vertical layout, side menu, horizontal tabbed picker */
|
|
||||||
@media (max-width: 567px) {
|
@media (max-width: 567px) {
|
||||||
.appContainer {
|
.appContainer {
|
||||||
}
|
}
|
||||||
@@ -205,38 +125,6 @@
|
|||||||
.mobileBetaZone {
|
.mobileBetaZone {
|
||||||
}
|
}
|
||||||
|
|
||||||
.leftMobileMenu {
|
|
||||||
}
|
|
||||||
|
|
||||||
.leftMobileMenu.open {
|
|
||||||
width: 80%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leftMenuWrapper {
|
|
||||||
width: 80vw;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leftMobileMenu .topNav {
|
|
||||||
}
|
|
||||||
|
|
||||||
.leftMenuWrapper .closeButton {
|
|
||||||
}
|
|
||||||
|
|
||||||
.rightMobileMenu {
|
|
||||||
}
|
|
||||||
|
|
||||||
.rightMobileMenu.open {
|
|
||||||
width: 80%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rightMenuWrapper {
|
|
||||||
width: 80vw;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rightMobileMenu .closeButton {
|
|
||||||
margin: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tabbedContainer {
|
.tabbedContainer {
|
||||||
display: flex;
|
display: flex;
|
||||||
overflow-x: scroll;
|
overflow-x: scroll;
|
||||||
|
|||||||
+20
-29
@@ -2,14 +2,15 @@ import { useState } from "react";
|
|||||||
import styles from "./App.module.css";
|
import styles from "./App.module.css";
|
||||||
import ColorPicker from "./components/ColorPicker/ColorPicker";
|
import ColorPicker from "./components/ColorPicker/ColorPicker";
|
||||||
import ColorValues from "./components/ColorValues/ColorValues";
|
import ColorValues from "./components/ColorValues/ColorValues";
|
||||||
|
import { LeftMenu, RightMenu } from "./components/SideMenu";
|
||||||
import clsx from "clsx";
|
import clsx from "clsx";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const [rightMenuOpen, setRightMenuOpen] = useState(false);
|
const [isRightMenuOpen, setIsRightMenuOpen] = useState(false);
|
||||||
const [leftMenuOpen, setLeftMenuOpen] = useState(false);
|
const [isLeftMenuOpen, setIsLeftMenuOpen] = useState(false);
|
||||||
|
|
||||||
const toggleRightMenu = () => setRightMenuOpen(!rightMenuOpen);
|
const toggleRightMenu = () => setIsRightMenuOpen(!isRightMenuOpen);
|
||||||
const toggleLeftMenu = () => setLeftMenuOpen(!leftMenuOpen);
|
const toggleLeftMenu = () => setIsLeftMenuOpen(!isLeftMenuOpen);
|
||||||
|
|
||||||
const RightMenuButton = () => (
|
const RightMenuButton = () => (
|
||||||
<button className={styles.rightMenuButton} onClick={toggleRightMenu}>
|
<button className={styles.rightMenuButton} onClick={toggleRightMenu}>
|
||||||
@@ -29,9 +30,11 @@ function App() {
|
|||||||
<LeftMenuButton />
|
<LeftMenuButton />
|
||||||
<RightMenuButton />
|
<RightMenuButton />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.mobileLeftNav}>
|
<div className={styles.mobileLeftNav}>
|
||||||
<LeftMenuButton />
|
<LeftMenuButton />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.mobileAlphaZone}>
|
<div className={styles.mobileAlphaZone}>
|
||||||
<div className={styles.tabbedContainer}>
|
<div className={styles.tabbedContainer}>
|
||||||
<div className={clsx(styles.tab, styles.colorPickerContainer)}>
|
<div className={clsx(styles.tab, styles.colorPickerContainer)}>
|
||||||
@@ -42,42 +45,28 @@ function App() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.mobileBetaZone}>
|
<div className={styles.mobileBetaZone}>
|
||||||
<div className={styles.paletteEditorContainer}></div>
|
<div className={styles.paletteEditorContainer}></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.mobileRightNav}>
|
<div className={styles.mobileRightNav}>
|
||||||
<RightMenuButton />
|
<RightMenuButton />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
<LeftMenu
|
||||||
className={clsx(styles.leftMobileMenu, {
|
isOpen={isLeftMenuOpen}
|
||||||
[styles.open]: leftMenuOpen,
|
onClose={() => setIsLeftMenuOpen(false)}
|
||||||
})}
|
|
||||||
>
|
>
|
||||||
<div className={styles.leftMenuWrapper}>
|
<div>User Info</div>
|
||||||
<div className={styles.topNav}>
|
</LeftMenu>
|
||||||
<button className={styles.closeButton} onClick={toggleLeftMenu}>
|
|
||||||
×
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className={styles.paletteLibraryContainer}>User Info</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
<RightMenu
|
||||||
className={clsx(styles.rightMobileMenu, {
|
isOpen={isRightMenuOpen}
|
||||||
[styles.open]: rightMenuOpen,
|
onClose={() => setIsRightMenuOpen(false)}
|
||||||
})}
|
|
||||||
>
|
>
|
||||||
<div className={styles.rightMenuWrapper}>
|
|
||||||
<div className={styles.topNav}>
|
|
||||||
<button className={styles.closeButton} onClick={toggleRightMenu}>
|
|
||||||
×
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div className={styles.paletteLibraryContainer}>Palette Library</div>
|
<div className={styles.paletteLibraryContainer}>Palette Library</div>
|
||||||
</div>
|
</RightMenu>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.mainContent}>
|
<div className={styles.mainContent}>
|
||||||
@@ -85,10 +74,12 @@ function App() {
|
|||||||
<div className={styles.colorPickerContainer}>
|
<div className={styles.colorPickerContainer}>
|
||||||
<ColorPicker />
|
<ColorPicker />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.colorValuesContainer}>
|
<div className={styles.colorValuesContainer}>
|
||||||
<ColorValues />
|
<ColorValues />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className={styles.betaZone}>
|
<div className={styles.betaZone}>
|
||||||
<div className={styles.paletteEditorContainer}></div>
|
<div className={styles.paletteEditorContainer}></div>
|
||||||
<div className={styles.paletteLibraryContainer}></div>
|
<div className={styles.paletteLibraryContainer}></div>
|
||||||
|
|||||||
@@ -0,0 +1,88 @@
|
|||||||
|
.sideMenu {
|
||||||
|
height: 100vh;
|
||||||
|
width: 0;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 20;
|
||||||
|
top: 0;
|
||||||
|
background: white;
|
||||||
|
overflow-x: hidden;
|
||||||
|
transition: 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sideMenuUnderlay {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
z-index: 20;
|
||||||
|
background: rgba(0, 0, 0, 0);
|
||||||
|
transition: background-color 0.3s;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sideMenuUnderlay.open {
|
||||||
|
background: rgba(0, 0, 0, 0.2);
|
||||||
|
pointer-events: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left.sideMenu {
|
||||||
|
left: 0;
|
||||||
|
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.right.sideMenu {
|
||||||
|
right: 0;
|
||||||
|
box-shadow: -2px 0 5px rgba(0, 0, 0, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.menuWrapper {
|
||||||
|
position: absolute;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left.menuWrapper {
|
||||||
|
right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right.menuWrapper {
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.topNav {
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right.sideMenu .closeButton {
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.left.sideMenu .closeButton {
|
||||||
|
margin: 10px 10px 10px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Landscape Phone */
|
||||||
|
@media (min-width: 568px) and (max-width: 991px) and (orientation: landscape) {
|
||||||
|
.left.sideMenu.open,
|
||||||
|
.left.menuWrapper {
|
||||||
|
width: 80vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right.sideMenu.open,
|
||||||
|
.right.menuWrapper {
|
||||||
|
width: 80vh;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Portrait Phone */
|
||||||
|
@media (max-width: 567px) {
|
||||||
|
.left.sideMenu.open,
|
||||||
|
.left.menuWrapper {
|
||||||
|
width: 80vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.right.sideMenu.open,
|
||||||
|
.right.menuWrapper {
|
||||||
|
width: 80vw;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,73 @@
|
|||||||
|
import type { ReactNode } from "react";
|
||||||
|
import clsx from "clsx";
|
||||||
|
import styles from "./SideMenu.module.css";
|
||||||
|
|
||||||
|
interface SideMenuProps {
|
||||||
|
isOpen: boolean;
|
||||||
|
onClose: () => void;
|
||||||
|
children: ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface BaseMenuProps extends SideMenuProps {
|
||||||
|
position: "left" | "right";
|
||||||
|
}
|
||||||
|
|
||||||
|
function BaseMenu({ isOpen, onClose, children, position }: BaseMenuProps) {
|
||||||
|
const isLeftMenu = position === "left";
|
||||||
|
|
||||||
|
const handleUnderlayClick = (e: React.MouseEvent<HTMLDivElement>) => {
|
||||||
|
if (e.target === e.currentTarget) {
|
||||||
|
onClose();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={clsx(styles.sideMenuUnderlay, { [styles.open]: isOpen })}
|
||||||
|
onClick={handleUnderlayClick}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
data-cy={`${position}-menu`}
|
||||||
|
className={clsx(
|
||||||
|
styles.sideMenu,
|
||||||
|
isLeftMenu ? styles.left : styles.right,
|
||||||
|
{ [styles.open]: isOpen },
|
||||||
|
)}
|
||||||
|
onClick={(e) => e.stopPropagation()}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
className={clsx(
|
||||||
|
styles.menuWrapper,
|
||||||
|
isLeftMenu ? styles.left : styles.right,
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className={styles.topNav}>
|
||||||
|
<button
|
||||||
|
data-cy="close-menu"
|
||||||
|
className={styles.closeButton}
|
||||||
|
onClick={onClose}
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function newSideMenu(position: "left" | "right") {
|
||||||
|
return function SideMenu({ isOpen, onClose, children }: SideMenuProps) {
|
||||||
|
return (
|
||||||
|
<BaseMenu isOpen={isOpen} onClose={onClose} position={position}>
|
||||||
|
{children}
|
||||||
|
</BaseMenu>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const LeftMenu = newSideMenu("left");
|
||||||
|
const RightMenu = newSideMenu("right");
|
||||||
|
|
||||||
|
export { LeftMenu, RightMenu };
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
import { LeftMenu, RightMenu } from "./SideMenu";
|
||||||
|
export { LeftMenu, RightMenu };
|
||||||
+6
-7
@@ -1,11 +1,10 @@
|
|||||||
import { StrictMode } from 'react'
|
import { StrictMode } from "react";
|
||||||
import { createRoot } from 'react-dom/client'
|
import { createRoot } from "react-dom/client";
|
||||||
import './index.css'
|
import "./index.css";
|
||||||
import App from './App.tsx'
|
import App from "./App.tsx";
|
||||||
|
|
||||||
createRoot(document.getElementById('root')!).render(
|
createRoot(document.getElementById("root")!).render(
|
||||||
<StrictMode>
|
<StrictMode>
|
||||||
<App />
|
<App />
|
||||||
</StrictMode>,
|
</StrictMode>,
|
||||||
)
|
);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user