Merge branch 'feature/workspaces' of github.com:ONLYOFFICE/AppServer into feature/workspaces
This commit is contained in:
commit
7bf2a8e9b5
@ -18,7 +18,6 @@ Headline.propTypes = {
|
||||
};
|
||||
|
||||
Headline.defaultProps = {
|
||||
//color: "#333333",
|
||||
title: null,
|
||||
truncate: false,
|
||||
isInline: false,
|
||||
|
@ -19,7 +19,7 @@ const StyledHeading = styled(Heading)`
|
||||
line-height: 65px;
|
||||
font-size: ${(props) => size[props.headlineType]};
|
||||
font-weight: ${(props) => weight[props.headlineType]};
|
||||
color: ${(props) => props.theme.color};
|
||||
color: ${(props) => (props.color ? props.color : props.theme.color)};
|
||||
`;
|
||||
StyledHeading.defaultProps = { theme: Base };
|
||||
|
||||
|
@ -96,8 +96,9 @@ const HeaderNav = ({
|
||||
iconName={m.iconName}
|
||||
iconUrl={m.iconUrl}
|
||||
badgeNumber={m.notifications}
|
||||
url={m.link}
|
||||
onClick={(e) => {
|
||||
window.open(m.link, "_self");
|
||||
history.push(m.link);
|
||||
e.preventDefault();
|
||||
}}
|
||||
onBadgeClick={(e) => console.log(m.iconName + "Badge Clicked", e)}
|
||||
|
@ -2,7 +2,7 @@ import React from "react";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import PropTypes from "prop-types";
|
||||
import styled from "styled-components";
|
||||
import { Link as LogoLink } from "react-router-dom";
|
||||
import { useLocation, Link as LinkWithoutRedirect } from "react-router-dom";
|
||||
import NavItem from "./nav-item";
|
||||
import Headline from "../../Headline";
|
||||
import Nav from "./nav";
|
||||
@ -67,6 +67,23 @@ const Header = styled.header`
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledLink = styled.div`
|
||||
display: inline;
|
||||
.nav-menu-header_link {
|
||||
color: #7a95b0;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
:hover {
|
||||
color: #7a95b0;
|
||||
-webkit-text-decoration: underline;
|
||||
text-decoration: underline;
|
||||
}
|
||||
`;
|
||||
|
||||
const versionBadgeProps = {
|
||||
color: "#7A95B0",
|
||||
fontWeight: "600",
|
||||
@ -91,6 +108,7 @@ const HeaderComponent = ({
|
||||
...props
|
||||
}) => {
|
||||
const { t } = useTranslation();
|
||||
const { pathname } = useLocation();
|
||||
|
||||
const isNavAvailable = mainModules.length > 0;
|
||||
|
||||
@ -114,7 +132,7 @@ const HeaderComponent = ({
|
||||
const onItemClick = (e) => {
|
||||
if (!e) return;
|
||||
const link = e.currentTarget.dataset.link;
|
||||
window.open(link, "_self");
|
||||
history.push(link);
|
||||
e.preventDefault();
|
||||
};
|
||||
|
||||
@ -147,7 +165,7 @@ const HeaderComponent = ({
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const isMainPage = pathname === "/";
|
||||
return (
|
||||
<>
|
||||
<Header
|
||||
@ -162,7 +180,7 @@ const HeaderComponent = ({
|
||||
noHover={true}
|
||||
/>
|
||||
|
||||
<LogoLink className="header-logo-wrapper" to={defaultPage}>
|
||||
<LinkWithoutRedirect className="header-logo-wrapper" to={defaultPage}>
|
||||
<ReactSVG
|
||||
className="header-logo-icon"
|
||||
loading={() => (
|
||||
@ -177,7 +195,7 @@ const HeaderComponent = ({
|
||||
)}
|
||||
src={props.logoUrl}
|
||||
/>
|
||||
</LogoLink>
|
||||
</LinkWithoutRedirect>
|
||||
<Headline className="header-module-title" type="header" color="#FFF">
|
||||
{currentProductName}
|
||||
</Headline>
|
||||
@ -212,7 +230,7 @@ const HeaderComponent = ({
|
||||
data-id={id}
|
||||
data-link={link}
|
||||
opened={isNavOpened}
|
||||
active={id == currentProductId}
|
||||
active={isMainPage ? false : id == currentProductId}
|
||||
iconName={iconName}
|
||||
iconUrl={iconUrl}
|
||||
badgeNumber={notifications}
|
||||
@ -239,14 +257,11 @@ const HeaderComponent = ({
|
||||
{" "}
|
||||
-{" "}
|
||||
</Text>
|
||||
<Link
|
||||
as="a"
|
||||
onClick={onLinkClick}
|
||||
target="_blank"
|
||||
{...versionBadgeProps}
|
||||
>
|
||||
{t("AboutShort")}
|
||||
</Link>
|
||||
<StyledLink>
|
||||
<LinkWithoutRedirect to="/about" className="nav-menu-header_link">
|
||||
{t("AboutShort")}
|
||||
</LinkWithoutRedirect>
|
||||
</StyledLink>
|
||||
</Box>
|
||||
</Nav>
|
||||
)}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import styled from "styled-components";
|
||||
import Base from "../themes/base";
|
||||
import { Base } from "../themes";
|
||||
|
||||
const StyledButtonsWrapper = styled.div`
|
||||
display: grid;
|
||||
|
@ -22,6 +22,10 @@ const textColor = "#333333",
|
||||
const StyledExpanderDownIcon = styled(ExpanderDownIcon)`
|
||||
${commonIconsStyles}
|
||||
path {
|
||||
color: ${(props) =>
|
||||
props.disabled
|
||||
? props.theme.groupButton.disableColor
|
||||
: props.theme.groupButton.color};
|
||||
fill: ${(props) => props.color};
|
||||
}
|
||||
`;
|
||||
@ -98,7 +102,6 @@ class GroupButton extends React.Component {
|
||||
style,
|
||||
} = this.props;
|
||||
|
||||
const color = disabled ? disabledTextColor : textColor;
|
||||
const itemLabel = !isSelect ? label : this.state.selected;
|
||||
const dropDownMaxHeightProp = dropDownMaxHeight
|
||||
? { maxHeight: dropDownMaxHeight }
|
||||
@ -131,7 +134,7 @@ class GroupButton extends React.Component {
|
||||
>
|
||||
{itemLabel}
|
||||
<Caret isOpen={this.state.isOpen}>
|
||||
<StyledExpanderDownIcon size="scale" color={color} />
|
||||
<StyledExpanderDownIcon size="scale" disabled={disabled} />
|
||||
</Caret>
|
||||
</StyledDropdownToggle>
|
||||
<DropDown
|
||||
|
@ -1,3 +1,4 @@
|
||||
import React from "react";
|
||||
import styled, { css } from "styled-components";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
@ -35,7 +36,7 @@ SimpleLinkWithDropdown.propTypes = {
|
||||
};
|
||||
|
||||
const color = (props) =>
|
||||
isDisabled ? props.theme.linkWithDropdown.disableColor : props.color;
|
||||
props.isDisabled ? props.theme.linkWithDropdown.disableColor : props.color;
|
||||
|
||||
// eslint-disable-next-line react/prop-types, no-unused-vars
|
||||
const ExpanderDownIconWrapper = ({
|
||||
|
@ -10,6 +10,22 @@ import CatalogFolderIcon from "../../../../public/images/catalog.folder.react.sv
|
||||
import CheckboxCheckedIcon from "../../../../public/images/checkbox.checked.react.svg";
|
||||
import CheckboxIndeterminateIcon from "../../../../public/images/checkbox.indeterminate.react.svg";
|
||||
import CheckboxIcon from "../../../../public/images/checkbox.react.svg";
|
||||
import commonIconsStyles from "../../utils/common-icons-style";
|
||||
|
||||
const StyledCheckboxIcon = styled(CheckboxIcon)`
|
||||
${(props) => props.color && `color: ${props.color}`};
|
||||
${commonIconsStyles}
|
||||
`;
|
||||
|
||||
const StyledCheckboxCheckedIcon = styled(CheckboxCheckedIcon)`
|
||||
${(props) => props.color && `color: ${props.color}`};
|
||||
${commonIconsStyles}
|
||||
`;
|
||||
|
||||
const StyledCheckboxIndeterminateIcon = styled(CheckboxIndeterminateIcon)`
|
||||
${(props) => props.color && `color: ${props.color}`};
|
||||
${commonIconsStyles}
|
||||
`;
|
||||
|
||||
var checkboxIcon,
|
||||
checkboxСheckedIcon,
|
||||
@ -24,60 +40,60 @@ var checkboxIcon,
|
||||
treeIcon;
|
||||
|
||||
(function () {
|
||||
checkboxIcon = getCssFromSvg(ReactDOMServer.renderToString(<CheckboxIcon />));
|
||||
checkboxIcon = getCssFromSvg(
|
||||
ReactDOMServer.renderToString(<StyledCheckboxIcon />)
|
||||
);
|
||||
сheckboxDisabledIcon = getCssFromSvg(
|
||||
ReactDOMServer.renderToString(
|
||||
<CheckboxIcon isfill={true} color="#F8F9F9" />
|
||||
)
|
||||
ReactDOMServer.renderToString(<StyledCheckboxIcon color="#F8F9F9" />)
|
||||
);
|
||||
сheckboxHoverIcon = getCssFromSvg(
|
||||
ReactDOMServer.renderToString(<CheckboxIcon isfill={true} color="white" />)
|
||||
ReactDOMServer.renderToString(<StyledCheckboxIcon color="white" />)
|
||||
);
|
||||
|
||||
checkboxСheckedIcon = getCssFromSvg(
|
||||
ReactDOMServer.renderToString(<CheckboxCheckedIcon />)
|
||||
ReactDOMServer.renderToString(<StyledCheckboxCheckedIcon />)
|
||||
);
|
||||
checkboxCheckedDisabledIcon = getCssFromSvg(
|
||||
ReactDOMServer.renderToString(
|
||||
<CheckboxCheckedIcon
|
||||
isfill={true}
|
||||
<StyledCheckboxCheckedIcon
|
||||
//isfill={true}
|
||||
color="#F8F9F9"
|
||||
isStroke={true}
|
||||
stroke="#ECEEF1"
|
||||
//isStroke={true}
|
||||
//stroke="#ECEEF1"
|
||||
/>
|
||||
)
|
||||
);
|
||||
checkboxCheckedHoverIcon = getCssFromSvg(
|
||||
ReactDOMServer.renderToString(
|
||||
<CheckboxCheckedIcon
|
||||
isfill={true}
|
||||
<StyledCheckboxCheckedIcon
|
||||
//isfill={true}
|
||||
color="white"
|
||||
isStroke={true}
|
||||
stroke="#A3A9AE"
|
||||
//isStroke={true}
|
||||
//stroke="#A3A9AE"
|
||||
/>
|
||||
)
|
||||
);
|
||||
|
||||
сheckboxIndeterminateIcon = getCssFromSvg(
|
||||
ReactDOMServer.renderToString(<CheckboxIndeterminateIcon />)
|
||||
ReactDOMServer.renderToString(<StyledCheckboxIndeterminateIcon />)
|
||||
);
|
||||
checkboxIndeterminateDisabledIcon = getCssFromSvg(
|
||||
ReactDOMServer.renderToString(
|
||||
<CheckboxIndeterminateIcon
|
||||
isfill={true}
|
||||
<StyledCheckboxIndeterminateIcon
|
||||
//isfill={true}
|
||||
color="#F8F9F9"
|
||||
isStroke={true}
|
||||
stroke="#ECEEF1"
|
||||
//isStroke={true}
|
||||
//stroke="#ECEEF1"
|
||||
/>
|
||||
)
|
||||
);
|
||||
checkboxIndeterminateHoverIcon = getCssFromSvg(
|
||||
ReactDOMServer.renderToString(
|
||||
<CheckboxIndeterminateIcon
|
||||
isfill={true}
|
||||
<StyledCheckboxIndeterminateIcon
|
||||
//isfill={true}
|
||||
color="white"
|
||||
isStroke={true}
|
||||
stroke="#A3A9AE"
|
||||
//isStroke={true}
|
||||
//stroke="#A3A9AE"
|
||||
/>
|
||||
)
|
||||
);
|
||||
|
@ -102,12 +102,11 @@ class FilesContent extends React.Component {
|
||||
}
|
||||
|
||||
const Files = inject(({ auth, initFilesStore }) => {
|
||||
const homepage = config.homepage; // "/products/files"; //TODO: add homepage to config?
|
||||
return {
|
||||
//isDesktop: auth.settingsStore.isDesktopClient,
|
||||
user: auth.userStore.user,
|
||||
isAuthenticated: auth.isAuthenticated,
|
||||
homepage: auth.settingsStore.homepage || homepage,
|
||||
homepage: auth.settingsStore.homepage || config.homepage,
|
||||
encryptionKeys: auth.settingsStore.encryptionKeys,
|
||||
isEncryption: auth.settingsStore.isEncryptionSupport,
|
||||
isLoaded: auth.isLoaded && initFilesStore.isLoaded,
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React from "react";
|
||||
import styled from "styled-components";
|
||||
import Row from "@appserver/components/row";
|
||||
import LinkWithDropdown from "@appserver/components/link-with-dropdown";
|
||||
import ToggleButton from "@appserver/components/toggle-button";
|
||||
@ -6,6 +7,14 @@ import { StyledLinkRow } from "../StyledPanels";
|
||||
import AccessComboBox from "./AccessComboBox";
|
||||
import { ShareAccessRights } from "@appserver/common/constants";
|
||||
import AccessEditIcon from "../../../../public/images/access.edit.react.svg";
|
||||
import commonIconsStyles from "@appserver/components/utils/common-icons-style";
|
||||
|
||||
const StyledAccessEditIcon = styled(AccessEditIcon)`
|
||||
${commonIconsStyles}
|
||||
path {
|
||||
fill: "#A3A9AE";
|
||||
}
|
||||
`;
|
||||
|
||||
class LinkRow extends React.Component {
|
||||
onToggleButtonChange = () => {
|
||||
@ -48,11 +57,9 @@ class LinkRow extends React.Component {
|
||||
isDisabled={isDisabled}
|
||||
/>
|
||||
) : (
|
||||
<AccessEditIcon
|
||||
<StyledAccessEditIcon
|
||||
size="medium"
|
||||
className="sharing_panel-owner-icon"
|
||||
isfill={true}
|
||||
color="#A3A9AE"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -11,7 +11,6 @@ import { LANGUAGE } from "@appserver/common/constants";
|
||||
// for passing in lng and translations on init
|
||||
|
||||
const languages = ["en", "ru"];
|
||||
const homepage = "/products/files"; //TODO: add homepage to config?
|
||||
|
||||
i18n
|
||||
/*
|
||||
@ -49,7 +48,7 @@ i18n
|
||||
},
|
||||
|
||||
backend: {
|
||||
loadPath: `${homepage}/locales/{{lng}}/{{ns}}.json`,
|
||||
loadPath: `${config.homepage}/locales/{{lng}}/{{ns}}.json`,
|
||||
},
|
||||
|
||||
react: {
|
||||
|
@ -233,8 +233,7 @@ class FilesStore {
|
||||
params.push(`${SORT_ORDER}=${filter.sortOrder}`);
|
||||
|
||||
//console.log("window", window.location);
|
||||
const homepage = "/products/files"; //TODO: add homepage to config?
|
||||
history.push(`${homepage}/filter?${params.join("&")}`);
|
||||
history.push(`${config.homepage}/filter?${params.join("&")}`);
|
||||
};
|
||||
|
||||
fetchFiles = (folderId, filter, clearFilter = true) => {
|
||||
|
@ -97,8 +97,7 @@ class InitFilesStore {
|
||||
setModuleInfo,
|
||||
} = auth.settingsStore;
|
||||
|
||||
const homepage = "/products/files"; //TODO: add homepage to config?
|
||||
setModuleInfo(homepage, "e67be73d-f9ae-4ce1-8fec-1880cb518cb4");
|
||||
setModuleInfo(config.homepage, "e67be73d-f9ae-4ce1-8fec-1880cb518cb4");
|
||||
|
||||
const requests = [];
|
||||
|
||||
|
@ -14,6 +14,6 @@ const ArticleHeaderContent = ({ isLoaded, currentModuleName }) => {
|
||||
export default inject(({ auth }) => {
|
||||
return {
|
||||
isLoaded: auth.isLoaded,
|
||||
currentModuleName: "", //TODO: FIX (auth.isLoaded && auth.product.title) || null,
|
||||
currentModuleName: auth.product.title,
|
||||
};
|
||||
})(observer(ArticleHeaderContent));
|
||||
|
@ -1,4 +1,5 @@
|
||||
import React, { useEffect } from "react";
|
||||
import styled from "styled-components";
|
||||
import { Router, Switch } from "react-router-dom";
|
||||
import { inject, observer } from "mobx-react";
|
||||
import NavMenu from "@appserver/common/components/NavMenu";
|
||||
@ -11,6 +12,7 @@ import Layout from "@appserver/common/components/Layout";
|
||||
import ScrollToTop from "@appserver/common/components/Layout/ScrollToTop";
|
||||
import history from "@appserver/common/history";
|
||||
import toastr from "@appserver/common/components/Toast";
|
||||
import RectangleLoader from "@appserver/common/components/Loaders/RectangleLoader";
|
||||
import { updateTempContent } from "@appserver/common/utils";
|
||||
import { Provider as MobxProvider } from "mobx-react";
|
||||
import ThemeProvider from "@appserver/components/theme-provider";
|
||||
@ -30,15 +32,24 @@ const About = React.lazy(() => import("./components/pages/About"));
|
||||
const Settings = React.lazy(() => import("./components/pages/Settings"));
|
||||
const ComingSoon = React.lazy(() => import("./components/pages/ComingSoon"));
|
||||
|
||||
const LoadingBody = styled.div`
|
||||
padding: 20px;
|
||||
`;
|
||||
const LoadingShell = () => (
|
||||
<LoadingBody>
|
||||
<RectangleLoader height="100%" />
|
||||
</LoadingBody>
|
||||
);
|
||||
|
||||
const SettingsRoute = (props) => (
|
||||
<React.Suspense fallback={null}>
|
||||
<React.Suspense fallback={<LoadingShell />}>
|
||||
<ErrorBoundary>
|
||||
<Settings {...props} />
|
||||
</ErrorBoundary>
|
||||
</React.Suspense>
|
||||
);
|
||||
const PaymentsRoute = (props) => (
|
||||
<React.Suspense fallback={null}>
|
||||
<React.Suspense fallback={<LoadingShell />}>
|
||||
<ErrorBoundary>
|
||||
<Payments {...props} />
|
||||
</ErrorBoundary>
|
||||
@ -46,7 +57,7 @@ const PaymentsRoute = (props) => (
|
||||
);
|
||||
|
||||
const Error404Route = (props) => (
|
||||
<React.Suspense fallback={null}>
|
||||
<React.Suspense fallback={<LoadingShell />}>
|
||||
<ErrorBoundary>
|
||||
<Error404 {...props} />
|
||||
</ErrorBoundary>
|
||||
@ -54,7 +65,7 @@ const Error404Route = (props) => (
|
||||
);
|
||||
|
||||
const HomeRoute = (props) => (
|
||||
<React.Suspense fallback={null}>
|
||||
<React.Suspense fallback={<LoadingShell />}>
|
||||
<ErrorBoundary>
|
||||
<Home {...props} />
|
||||
</ErrorBoundary>
|
||||
@ -62,7 +73,7 @@ const HomeRoute = (props) => (
|
||||
);
|
||||
|
||||
const LoginRoute = (props) => (
|
||||
<React.Suspense fallback={null}>
|
||||
<React.Suspense fallback={<LoadingShell />}>
|
||||
<ErrorBoundary>
|
||||
<Login {...props} />
|
||||
</ErrorBoundary>
|
||||
@ -70,7 +81,7 @@ const LoginRoute = (props) => (
|
||||
);
|
||||
|
||||
const PeopleRoute = (props) => (
|
||||
<React.Suspense fallback={null}>
|
||||
<React.Suspense fallback={<LoadingShell />}>
|
||||
<ErrorBoundary>
|
||||
<People {...props} />
|
||||
</ErrorBoundary>
|
||||
@ -78,7 +89,7 @@ const PeopleRoute = (props) => (
|
||||
);
|
||||
|
||||
const FilesRoute = (props) => (
|
||||
<React.Suspense fallback={null}>
|
||||
<React.Suspense fallback={<LoadingShell />}>
|
||||
<ErrorBoundary>
|
||||
<Files {...props} />
|
||||
</ErrorBoundary>
|
||||
@ -86,7 +97,7 @@ const FilesRoute = (props) => (
|
||||
);
|
||||
|
||||
const AboutRoute = (props) => (
|
||||
<React.Suspense fallback={null}>
|
||||
<React.Suspense fallback={<LoadingShell />}>
|
||||
<ErrorBoundary>
|
||||
<About {...props} />
|
||||
</ErrorBoundary>
|
||||
@ -94,7 +105,7 @@ const AboutRoute = (props) => (
|
||||
);
|
||||
|
||||
const ComingSoonRoute = (props) => (
|
||||
<React.Suspense fallback={null}>
|
||||
<React.Suspense fallback={<LoadingShell />}>
|
||||
<ErrorBoundary>
|
||||
<ComingSoon {...props} />
|
||||
</ErrorBoundary>
|
||||
|
@ -2,7 +2,7 @@ import React, { useCallback } from "react";
|
||||
import PropTypes from "prop-types";
|
||||
import Text from "@appserver/components/text";
|
||||
import StyledModuleTile from "./StyledModuleTile";
|
||||
|
||||
import { Link } from "react-router-dom";
|
||||
const ModuleTile = (props) => {
|
||||
// console.log("ModuleTile render", props);
|
||||
const { title, imageUrl, link, description, isPrimary, onClick } = props;
|
||||
@ -15,7 +15,7 @@ const ModuleTile = (props) => {
|
||||
},
|
||||
[link, onClick]
|
||||
);
|
||||
console.log("imageUrl", imageUrl);
|
||||
|
||||
return (
|
||||
<StyledModuleTile>
|
||||
{isPrimary ? (
|
||||
@ -29,13 +29,15 @@ const ModuleTile = (props) => {
|
||||
</div>
|
||||
|
||||
<div className="title-text-wrapper">
|
||||
<div onClick={handleClick} className="title-text">
|
||||
<Text fontSize="36px" className="title-text-header selectable">
|
||||
{title}
|
||||
</Text>
|
||||
<Text fontSize="12px" className="title-text-description">
|
||||
{description}
|
||||
</Text>
|
||||
<div className="title-text">
|
||||
<Link to={`${link}`}>
|
||||
<Text fontSize="36px" className="title-text-header selectable">
|
||||
{title}
|
||||
</Text>
|
||||
<Text fontSize="12px" className="title-text-description">
|
||||
{description}
|
||||
</Text>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -50,13 +52,11 @@ const ModuleTile = (props) => {
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<Text
|
||||
fontSize="18px"
|
||||
className="sub-title-text"
|
||||
onClick={handleClick}
|
||||
>
|
||||
{title}
|
||||
</Text>
|
||||
<Link to={`${link}`}>
|
||||
<Text fontSize="18px" className="sub-title-text">
|
||||
{title}
|
||||
</Text>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -44,7 +44,11 @@ const StyledModuleTile = styled.div`
|
||||
width: auto;
|
||||
max-width: 50%;
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
|
||||
.title-text {
|
||||
flex: 1 1 auto;
|
||||
padding: 1.25rem;
|
||||
@ -74,6 +78,9 @@ const StyledModuleTile = styled.div`
|
||||
margin: 16px 0 16px 0;
|
||||
text-align: center;
|
||||
}
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user