Bug 1941807 - Add ability to toggle AboutWelcome screen content by clicking a configurable header r=omc-reviewers,jprickett
Support a second variant of the preonboarding modal design by adding the ability to configure a header that toggles visiblity of content tiles. - See design for [[ https://docs.google.com/presentation/d/1dDC9M9y-W3q90mkVRp4gq3p-x3u7F6qgy0jVeE4MwUw/edit#slide=id.g325219d6753_0_95 | modal Variant B ]] Differential Revision: https://phabricator.services.mozilla.com/D235325
This commit is contained in:
@@ -1963,7 +1963,68 @@ html {
|
||||
}
|
||||
}
|
||||
|
||||
.content-tiles-container {
|
||||
.onboardingContainer .main-content.no-steps:has(button.content-tiles-header[aria-expanded='false']) {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
#content-tiles-container button.tile-header,
|
||||
button.content-tiles-header {
|
||||
border: 1px solid var(--in-content-border-color);
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 12px 16px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
border-radius: 0;
|
||||
background-color: var(--in-content-page-background);
|
||||
cursor: pointer;
|
||||
// ensures focus ring is fully visible
|
||||
outline-offset: -12px;
|
||||
|
||||
.arrow-icon {
|
||||
width: 1em;
|
||||
height: 1.5em;
|
||||
-moz-context-properties: fill;
|
||||
fill: currentColor;
|
||||
background: url('chrome://global/skin/icons/arrow-down.svg') center center / 100% no-repeat;
|
||||
}
|
||||
|
||||
&[aria-expanded='true'] {
|
||||
border-end-start-radius: 0;
|
||||
border-end-end-radius: 0;
|
||||
|
||||
.arrow-icon {
|
||||
background: url('chrome://global/skin/icons/arrow-up.svg') center center / 100% no-repeat;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
button.content-tiles-header {
|
||||
margin: 0.5em 0 0;
|
||||
font-size: 11px;
|
||||
font-weight: 400;
|
||||
justify-content: center;
|
||||
border-width: 1px 0;
|
||||
|
||||
@media (prefers-contrast: no-preference) {
|
||||
color: #5B5B66;
|
||||
}
|
||||
|
||||
@media (prefers-contrast: no-preference) and (prefers-color-scheme: dark) {
|
||||
color: #CFCFD8;
|
||||
}
|
||||
|
||||
@media (prefers-contrast) {
|
||||
color: var(--in-content-page-color);
|
||||
}
|
||||
|
||||
.arrow-icon {
|
||||
margin-inline: 1em
|
||||
}
|
||||
}
|
||||
|
||||
#content-tiles-container {
|
||||
--tiles-container-border-radius: 8px;
|
||||
|
||||
margin: 24px 48px;
|
||||
@@ -2005,18 +2066,8 @@ html {
|
||||
}
|
||||
|
||||
button.tile-header {
|
||||
border: 1px solid var(--in-content-border-color);
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-radius: 0;
|
||||
font-size: 13px;
|
||||
padding: 12px 16px;
|
||||
background-color: var(--in-content-page-background);
|
||||
cursor: pointer;
|
||||
justify-content: space-between;
|
||||
|
||||
& + .tile-content {
|
||||
border-inline-start: 1px solid var(--in-content-border-color);
|
||||
@@ -2037,25 +2088,6 @@ html {
|
||||
line-height: 1.5;
|
||||
}
|
||||
}
|
||||
|
||||
.arrow-icon {
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
width: 1em;
|
||||
height: 1.5em;
|
||||
-moz-context-properties: fill;
|
||||
fill: currentColor;
|
||||
background-image: url('chrome://global/skin/icons/arrow-down.svg');
|
||||
}
|
||||
|
||||
&[aria-expanded='true'] {
|
||||
border-end-start-radius: 0;
|
||||
border-end-end-radius: 0;
|
||||
|
||||
.arrow-icon {
|
||||
background-image: url('chrome://global/skin/icons/arrow-up.svg');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.multi-select-container {
|
||||
|
||||
@@ -32,6 +32,8 @@ const TILE_STYLES = [
|
||||
export const ContentTiles = props => {
|
||||
const { content } = props;
|
||||
const [expandedTileIndex, setExpandedTileIndex] = useState(null);
|
||||
// State for header that toggles showing and hiding all tiles, if applicable
|
||||
const [tilesHeaderExpanded, setTilesHeaderExpanded] = useState(false);
|
||||
const { tiles } = content;
|
||||
if (!tiles) {
|
||||
return null;
|
||||
@@ -43,6 +45,14 @@ export const ContentTiles = props => {
|
||||
AboutWelcomeUtils.sendActionTelemetry(props.messageId, tileId);
|
||||
};
|
||||
|
||||
const toggleTiles = () => {
|
||||
setTilesHeaderExpanded(prev => !prev);
|
||||
AboutWelcomeUtils.sendActionTelemetry(
|
||||
props.messageId,
|
||||
"content_tiles_header"
|
||||
);
|
||||
};
|
||||
|
||||
const renderContentTile = (tile, index = 0) => {
|
||||
const isExpanded = expandedTileIndex === index;
|
||||
const { header } = tile;
|
||||
@@ -126,13 +136,35 @@ export const ContentTiles = props => {
|
||||
);
|
||||
};
|
||||
|
||||
if (Array.isArray(content.tiles)) {
|
||||
const renderContentTiles = () => {
|
||||
if (Array.isArray(tiles)) {
|
||||
return (
|
||||
<div id="content-tiles-container">
|
||||
{tiles.map((tile, index) => renderContentTile(tile, index))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// If tiles is not an array render the tile alone without a container
|
||||
return renderContentTile(tiles, 0);
|
||||
};
|
||||
|
||||
if (content.tiles_header) {
|
||||
return (
|
||||
<div className="content-tiles-container">
|
||||
{content.tiles.map((tile, index) => renderContentTile(tile, index))}
|
||||
</div>
|
||||
<React.Fragment>
|
||||
<button
|
||||
className="content-tiles-header"
|
||||
onClick={toggleTiles}
|
||||
aria-expanded={tilesHeaderExpanded}
|
||||
aria-controls={`content-tiles-container`}
|
||||
>
|
||||
<Localized text={content.tiles_header.title}>
|
||||
<span className="header-title" />
|
||||
</Localized>
|
||||
<div className="arrow-icon"></div>
|
||||
</button>
|
||||
{tilesHeaderExpanded && renderContentTiles()}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
// If tiles is not an array render the tile alone without a container
|
||||
return renderContentTile(tiles, 0);
|
||||
return renderContentTiles(tiles);
|
||||
};
|
||||
|
||||
@@ -2026,6 +2026,8 @@ const ContentTiles = props => {
|
||||
content
|
||||
} = props;
|
||||
const [expandedTileIndex, setExpandedTileIndex] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(null);
|
||||
// State for header that toggles showing and hiding all tiles, if applicable
|
||||
const [tilesHeaderExpanded, setTilesHeaderExpanded] = (0,react__WEBPACK_IMPORTED_MODULE_0__.useState)(false);
|
||||
const {
|
||||
tiles
|
||||
} = content;
|
||||
@@ -2037,6 +2039,10 @@ const ContentTiles = props => {
|
||||
setExpandedTileIndex(prevIndex => prevIndex === index ? null : index);
|
||||
_lib_aboutwelcome_utils_mjs__WEBPACK_IMPORTED_MODULE_9__.AboutWelcomeUtils.sendActionTelemetry(props.messageId, tileId);
|
||||
};
|
||||
const toggleTiles = () => {
|
||||
setTilesHeaderExpanded(prev => !prev);
|
||||
_lib_aboutwelcome_utils_mjs__WEBPACK_IMPORTED_MODULE_9__.AboutWelcomeUtils.sendActionTelemetry(props.messageId, "content_tiles_header");
|
||||
};
|
||||
const renderContentTile = (tile, index = 0) => {
|
||||
const isExpanded = expandedTileIndex === index;
|
||||
const {
|
||||
@@ -2106,13 +2112,30 @@ const ContentTiles = props => {
|
||||
style: tile.data.style
|
||||
})) : null);
|
||||
};
|
||||
if (Array.isArray(content.tiles)) {
|
||||
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
||||
className: "content-tiles-container"
|
||||
}, content.tiles.map((tile, index) => renderContentTile(tile, index)));
|
||||
const renderContentTiles = () => {
|
||||
if (Array.isArray(tiles)) {
|
||||
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
||||
id: "content-tiles-container"
|
||||
}, tiles.map((tile, index) => renderContentTile(tile, index)));
|
||||
}
|
||||
// If tiles is not an array render the tile alone without a container
|
||||
return renderContentTile(tiles, 0);
|
||||
};
|
||||
if (content.tiles_header) {
|
||||
return /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement((react__WEBPACK_IMPORTED_MODULE_0___default().Fragment), null, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("button", {
|
||||
className: "content-tiles-header",
|
||||
onClick: toggleTiles,
|
||||
"aria-expanded": tilesHeaderExpanded,
|
||||
"aria-controls": `content-tiles-container`
|
||||
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement(_MSLocalized__WEBPACK_IMPORTED_MODULE_1__.Localized, {
|
||||
text: content.tiles_header.title
|
||||
}, /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("span", {
|
||||
className: "header-title"
|
||||
})), /*#__PURE__*/react__WEBPACK_IMPORTED_MODULE_0___default().createElement("div", {
|
||||
className: "arrow-icon"
|
||||
})), tilesHeaderExpanded && renderContentTiles());
|
||||
}
|
||||
// If tiles is not an array render the tile alone without a container
|
||||
return renderContentTile(tiles, 0);
|
||||
return renderContentTiles(tiles);
|
||||
};
|
||||
|
||||
/***/ }),
|
||||
|
||||
@@ -2925,92 +2925,125 @@ html {
|
||||
.onboardingContainer .mobile-download-buttons li:not(:first-child) {
|
||||
margin-inline: 5px 0;
|
||||
}
|
||||
.onboardingContainer .content-tiles-container {
|
||||
--tiles-container-border-radius: 8px;
|
||||
margin: 24px 48px;
|
||||
.onboardingContainer .onboardingContainer .main-content.no-steps:has(button.content-tiles-header[aria-expanded=false]) {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.onboardingContainer .content-tiles-container .content-tile {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.onboardingContainer .content-tiles-container .content-tile.has-header:first-of-type button.tile-header, .onboardingContainer .content-tiles-container .content-tile:not(.has-header) + .content-tile.has-header button.tile-header {
|
||||
border-start-start-radius: var(--tiles-container-border-radius);
|
||||
border-start-end-radius: var(--tiles-container-border-radius);
|
||||
}
|
||||
.onboardingContainer .content-tiles-container .content-tile.has-header:not(:has(+ .content-tile.has-header)) button.tile-header {
|
||||
border-end-start-radius: var(--tiles-container-border-radius);
|
||||
border-end-end-radius: var(--tiles-container-border-radius);
|
||||
}
|
||||
.onboardingContainer .content-tiles-container .content-tile.has-header:not(:has(+ .content-tile.has-header)) button.tile-header[aria-expanded=true] {
|
||||
border-end-start-radius: 0;
|
||||
border-end-end-radius: 0;
|
||||
}
|
||||
.onboardingContainer .content-tiles-container .content-tile.has-header:not(:has(+ .content-tile.has-header)) button.tile-header[aria-expanded=true] + .tile-content {
|
||||
border: 1px solid var(--in-content-border-color);
|
||||
border-top: none;
|
||||
border-radius: 0 0 var(--tiles-container-border-radius) var(--tiles-container-border-radius);
|
||||
}
|
||||
.onboardingContainer .content-tiles-container .content-tile.has-header:has(+ .content-tile.has-header) button.tile-header[aria-expanded=false] {
|
||||
border-bottom: none;
|
||||
}
|
||||
.onboardingContainer .content-tiles-container .content-tile button.tile-header {
|
||||
.onboardingContainer #content-tiles-container button.tile-header,
|
||||
.onboardingContainer button.content-tiles-header {
|
||||
border: 1px solid var(--in-content-border-color);
|
||||
width: 100%;
|
||||
margin: 0;
|
||||
padding: 12px 16px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-radius: 0;
|
||||
font-size: 13px;
|
||||
padding: 12px 16px;
|
||||
background-color: var(--in-content-page-background);
|
||||
cursor: pointer;
|
||||
outline-offset: -12px;
|
||||
}
|
||||
.onboardingContainer .content-tiles-container .content-tile button.tile-header + .tile-content {
|
||||
border-inline-start: 1px solid var(--in-content-border-color);
|
||||
border-inline-end: 1px solid var(--in-content-border-color);
|
||||
}
|
||||
.onboardingContainer .content-tiles-container .content-tile button.tile-header .header-text-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.onboardingContainer .content-tiles-container .content-tile button.tile-header .header-text-container .header-title {
|
||||
font-weight: 590;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.onboardingContainer .content-tiles-container .content-tile button.tile-header .header-text-container .header-subtitle {
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.onboardingContainer .content-tiles-container .content-tile button.tile-header .arrow-icon {
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
.onboardingContainer #content-tiles-container button.tile-header .arrow-icon,
|
||||
.onboardingContainer button.content-tiles-header .arrow-icon {
|
||||
width: 1em;
|
||||
height: 1.5em;
|
||||
-moz-context-properties: fill;
|
||||
fill: currentColor;
|
||||
background-image: url("chrome://global/skin/icons/arrow-down.svg");
|
||||
background: url("chrome://global/skin/icons/arrow-down.svg") center center/100% no-repeat;
|
||||
}
|
||||
.onboardingContainer .content-tiles-container .content-tile button.tile-header[aria-expanded=true] {
|
||||
.onboardingContainer #content-tiles-container button.tile-header[aria-expanded=true],
|
||||
.onboardingContainer button.content-tiles-header[aria-expanded=true] {
|
||||
border-end-start-radius: 0;
|
||||
border-end-end-radius: 0;
|
||||
}
|
||||
.onboardingContainer .content-tiles-container .content-tile button.tile-header[aria-expanded=true] .arrow-icon {
|
||||
background-image: url("chrome://global/skin/icons/arrow-up.svg");
|
||||
.onboardingContainer #content-tiles-container button.tile-header[aria-expanded=true] .arrow-icon,
|
||||
.onboardingContainer button.content-tiles-header[aria-expanded=true] .arrow-icon {
|
||||
background: url("chrome://global/skin/icons/arrow-up.svg") center center/100% no-repeat;
|
||||
}
|
||||
.onboardingContainer .content-tiles-container .content-tile .multi-select-container {
|
||||
.onboardingContainer button.content-tiles-header {
|
||||
margin: 0.5em 0 0;
|
||||
font-size: 11px;
|
||||
font-weight: 400;
|
||||
justify-content: center;
|
||||
border-width: 1px 0;
|
||||
}
|
||||
@media (prefers-contrast: no-preference) {
|
||||
.onboardingContainer button.content-tiles-header {
|
||||
color: #5B5B66;
|
||||
}
|
||||
}
|
||||
@media (prefers-contrast: no-preference) and (prefers-color-scheme: dark) {
|
||||
.onboardingContainer button.content-tiles-header {
|
||||
color: #CFCFD8;
|
||||
}
|
||||
}
|
||||
@media (prefers-contrast) {
|
||||
.onboardingContainer button.content-tiles-header {
|
||||
color: var(--in-content-page-color);
|
||||
}
|
||||
}
|
||||
.onboardingContainer button.content-tiles-header .arrow-icon {
|
||||
margin-inline: 1em;
|
||||
}
|
||||
.onboardingContainer #content-tiles-container {
|
||||
--tiles-container-border-radius: 8px;
|
||||
margin: 24px 48px;
|
||||
}
|
||||
.onboardingContainer #content-tiles-container .content-tile {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.onboardingContainer #content-tiles-container .content-tile.has-header:first-of-type button.tile-header, .onboardingContainer #content-tiles-container .content-tile:not(.has-header) + .content-tile.has-header button.tile-header {
|
||||
border-start-start-radius: var(--tiles-container-border-radius);
|
||||
border-start-end-radius: var(--tiles-container-border-radius);
|
||||
}
|
||||
.onboardingContainer #content-tiles-container .content-tile.has-header:not(:has(+ .content-tile.has-header)) button.tile-header {
|
||||
border-end-start-radius: var(--tiles-container-border-radius);
|
||||
border-end-end-radius: var(--tiles-container-border-radius);
|
||||
}
|
||||
.onboardingContainer #content-tiles-container .content-tile.has-header:not(:has(+ .content-tile.has-header)) button.tile-header[aria-expanded=true] {
|
||||
border-end-start-radius: 0;
|
||||
border-end-end-radius: 0;
|
||||
}
|
||||
.onboardingContainer #content-tiles-container .content-tile.has-header:not(:has(+ .content-tile.has-header)) button.tile-header[aria-expanded=true] + .tile-content {
|
||||
border: 1px solid var(--in-content-border-color);
|
||||
border-top: none;
|
||||
border-radius: 0 0 var(--tiles-container-border-radius) var(--tiles-container-border-radius);
|
||||
}
|
||||
.onboardingContainer #content-tiles-container .content-tile.has-header:has(+ .content-tile.has-header) button.tile-header[aria-expanded=false] {
|
||||
border-bottom: none;
|
||||
}
|
||||
.onboardingContainer #content-tiles-container .content-tile button.tile-header {
|
||||
font-size: 13px;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.onboardingContainer #content-tiles-container .content-tile button.tile-header + .tile-content {
|
||||
border-inline-start: 1px solid var(--in-content-border-color);
|
||||
border-inline-end: 1px solid var(--in-content-border-color);
|
||||
}
|
||||
.onboardingContainer #content-tiles-container .content-tile button.tile-header .header-text-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
.onboardingContainer #content-tiles-container .content-tile button.tile-header .header-text-container .header-title {
|
||||
font-weight: 590;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.onboardingContainer #content-tiles-container .content-tile button.tile-header .header-text-container .header-subtitle {
|
||||
font-weight: 400;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.onboardingContainer #content-tiles-container .content-tile .multi-select-container {
|
||||
padding: 24px;
|
||||
margin: 0;
|
||||
}
|
||||
.onboardingContainer .content-tiles-container .content-tile .multi-select-container .checkbox-container {
|
||||
.onboardingContainer #content-tiles-container .content-tile .multi-select-container .checkbox-container {
|
||||
display: grid;
|
||||
}
|
||||
.onboardingContainer .content-tiles-container .content-tile .multi-select-container .checkbox-container label,
|
||||
.onboardingContainer .content-tiles-container .content-tile .multi-select-container .checkbox-container p {
|
||||
.onboardingContainer #content-tiles-container .content-tile .multi-select-container .checkbox-container label,
|
||||
.onboardingContainer #content-tiles-container .content-tile .multi-select-container .checkbox-container p {
|
||||
grid-column: 2;
|
||||
}
|
||||
.onboardingContainer .content-tiles-container .content-tile .multi-select-container .checkbox-container p {
|
||||
.onboardingContainer #content-tiles-container .content-tile .multi-select-container .checkbox-container p {
|
||||
margin-block: 0.5em 0;
|
||||
}
|
||||
.onboardingContainer .dismiss-button {
|
||||
|
||||
@@ -96,7 +96,7 @@ describe("ContentTiles component", () => {
|
||||
assert.equal(telemetrySpy.firstCall.args[1], tileId);
|
||||
});
|
||||
|
||||
it("should only expand on tiles at a time", () => {
|
||||
it("should only expand one tile at a time", () => {
|
||||
const firstTileButton = wrapper.find(".tile-header").at(0);
|
||||
firstTileButton.simulate("click");
|
||||
const secondTileButton = wrapper.find(".tile-header").at(1);
|
||||
@@ -109,6 +109,44 @@ describe("ContentTiles component", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("should toggle all tiles and send telemetry when the tiles header is clicked", () => {
|
||||
const TEST_CONTENT_HEADER = {
|
||||
tiles: [CHECKLIST_TILE, MOBILE_TILE],
|
||||
tiles_header: {
|
||||
title: "Toggle Tiles Header",
|
||||
},
|
||||
};
|
||||
|
||||
wrapper = mount(
|
||||
<ContentTiles content={TEST_CONTENT_HEADER} handleAction={handleAction} />
|
||||
);
|
||||
|
||||
let telemetrySpy = sandbox.spy(AboutWelcomeUtils, "sendActionTelemetry");
|
||||
const tilesHeaderButton = wrapper.find(".content-tiles-header");
|
||||
|
||||
assert.ok(tilesHeaderButton.exists(), "Tiles header button should exist");
|
||||
tilesHeaderButton.simulate("click");
|
||||
|
||||
assert.equal(
|
||||
wrapper.find("#content-tiles-container").exists(),
|
||||
true,
|
||||
"Content tiles container should be visible after toggle"
|
||||
);
|
||||
assert.calledOnce(telemetrySpy);
|
||||
assert.equal(
|
||||
telemetrySpy.firstCall.args[1],
|
||||
"content_tiles_header",
|
||||
"Telemetry should be sent for tiles header toggle"
|
||||
);
|
||||
|
||||
tilesHeaderButton.simulate("click");
|
||||
assert.equal(
|
||||
wrapper.find("#content-tiles-container").exists(),
|
||||
false,
|
||||
"Content tiles container should not be visible after second toggle"
|
||||
);
|
||||
});
|
||||
|
||||
it("should apply configured styles to the header buttons", () => {
|
||||
const mountedWrapper = mount(
|
||||
<ContentTiles content={TEST_CONTENT} handleAction={() => {}} />
|
||||
|
||||
@@ -88,6 +88,7 @@ const MESSAGES = () => [
|
||||
display: "block",
|
||||
padding: "20px 0 0 0",
|
||||
width: "560px",
|
||||
overflow: "auto",
|
||||
},
|
||||
logo: {
|
||||
height: "40px",
|
||||
@@ -100,6 +101,7 @@ const MESSAGES = () => [
|
||||
raw: "Review the content below before continuing.",
|
||||
fontSize: "15px",
|
||||
},
|
||||
tiles_header: { title: "Click to toggle content tiles." },
|
||||
tiles: [
|
||||
{
|
||||
type: "embedded_browser",
|
||||
@@ -130,6 +132,7 @@ const MESSAGES = () => [
|
||||
},
|
||||
{
|
||||
type: "multiselect",
|
||||
style: { marginBlock: "18px" },
|
||||
data: [
|
||||
{
|
||||
id: "checkbox-test-1",
|
||||
|
||||
Reference in New Issue
Block a user