Gift Modal: Fix UI (#6642)
This commit is contained in:
parent
49013cdf7b
commit
b40fe83338
@ -133,7 +133,6 @@
|
||||
|
||||
.resaleHeaderContentContainer {
|
||||
align-items: center;
|
||||
justify-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
@ -166,6 +165,12 @@
|
||||
.resaleHeaderText {
|
||||
margin: 0;
|
||||
margin-bottom: 0.0625rem !important;
|
||||
font-size: 1rem;
|
||||
line-height: 1.3125rem;
|
||||
}
|
||||
|
||||
.resaleHeaderTextBlock {
|
||||
margin-left: 4.5rem;
|
||||
}
|
||||
|
||||
.resaleHeaderDescription {
|
||||
|
||||
@ -40,7 +40,6 @@ import Button from '../../ui/Button';
|
||||
import InfiniteScroll from '../../ui/InfiniteScroll';
|
||||
import Modal from '../../ui/Modal';
|
||||
import Transition from '../../ui/Transition';
|
||||
import BalanceBlock from '../stars/BalanceBlock';
|
||||
import GiftComposer from './GiftComposer';
|
||||
import GiftItemPremium from './GiftItemPremium';
|
||||
import GiftItemStar from './GiftItemStar';
|
||||
@ -518,23 +517,25 @@ const GiftModal: FC<OwnProps & StateProps> = ({
|
||||
const isFirstLoading = areResaleGiftsLoading && !resaleGiftsCount;
|
||||
return (
|
||||
<div className={styles.resaleHeaderContentContainer}>
|
||||
<h2 className={styles.resaleHeaderText}>
|
||||
{selectedResaleGift.title}
|
||||
</h2>
|
||||
{isFirstLoading
|
||||
&& (
|
||||
<div className={styles.resaleHeaderDescription}>
|
||||
{lang('Loading')}
|
||||
</div>
|
||||
)}
|
||||
{!isFirstLoading && resaleGiftsCount !== undefined
|
||||
&& (
|
||||
<div className={styles.resaleHeaderDescription}>
|
||||
{lang('HeaderDescriptionResaleGifts', {
|
||||
count: resaleGiftsCount,
|
||||
}, { withNodes: true, withMarkdown: true, pluralValue: resaleGiftsCount })}
|
||||
</div>
|
||||
)}
|
||||
<div className={styles.resaleHeaderTextBlock}>
|
||||
<h2 className={styles.resaleHeaderText}>
|
||||
{selectedResaleGift.title}
|
||||
</h2>
|
||||
{isFirstLoading
|
||||
&& (
|
||||
<div className={styles.resaleHeaderDescription}>
|
||||
{lang('Loading')}
|
||||
</div>
|
||||
)}
|
||||
{!isFirstLoading && resaleGiftsCount !== undefined
|
||||
&& (
|
||||
<div className={styles.resaleHeaderDescription}>
|
||||
{lang('HeaderDescriptionResaleGifts', {
|
||||
count: resaleGiftsCount,
|
||||
}, { withNodes: true, withMarkdown: true, pluralValue: resaleGiftsCount })}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
<GiftResaleFilters dialogRef={dialogRef} />
|
||||
</div>
|
||||
);
|
||||
@ -555,6 +556,7 @@ const GiftModal: FC<OwnProps & StateProps> = ({
|
||||
contentClassName={styles.content}
|
||||
className={buildClassName(styles.modalDialog, styles.root)}
|
||||
isLowStackPriority
|
||||
withBalanceBar
|
||||
>
|
||||
<Button
|
||||
className={styles.closeButton}
|
||||
@ -566,12 +568,11 @@ const GiftModal: FC<OwnProps & StateProps> = ({
|
||||
>
|
||||
<div className={buttonClassName} />
|
||||
</Button>
|
||||
<BalanceBlock className={styles.balance} balance={starBalance} withAddButton />
|
||||
<div className={buildClassName(
|
||||
styles.header,
|
||||
isResaleScreen && styles.resaleHeader,
|
||||
!shouldShowHeader && styles.hiddenHeader,
|
||||
isCategoryListPinned && styles.noBorder)}
|
||||
isCategoryListPinned && !isResaleScreen && styles.noBorder)}
|
||||
>
|
||||
<Transition
|
||||
name="slideVerticalFade"
|
||||
|
||||
@ -15,14 +15,14 @@
|
||||
overflow-y: hidden;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding-right: 0.5rem;
|
||||
padding-left: 0.5rem;
|
||||
|
||||
white-space: nowrap;
|
||||
|
||||
@include mixins.gradient-border-horizontal(0.5rem, 0.5rem);
|
||||
@include mixins.gradient-border-horizontal(0.25rem, 0.25rem);
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
height: 0;
|
||||
@ -33,10 +33,69 @@
|
||||
}
|
||||
}
|
||||
|
||||
.itemIcon {
|
||||
.dropdownArrows {
|
||||
position: relative;
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
width: 0.625rem;
|
||||
height: 1rem;
|
||||
margin-inline-start: 0.25rem;
|
||||
}
|
||||
|
||||
.arrowLine {
|
||||
/* stylelint-disable-next-line plugin/whole-pixel */
|
||||
--x: 1.5px;
|
||||
--y: 0.25rem;
|
||||
--a: 45deg;
|
||||
|
||||
position: absolute;
|
||||
|
||||
/* stylelint-disable-next-line plugin/whole-pixel */
|
||||
width: 1.5px;
|
||||
height: 0.3125rem;
|
||||
border-radius: 1px;
|
||||
|
||||
background-color: currentcolor;
|
||||
|
||||
transition: transform 0.2s ease-in-out;
|
||||
|
||||
&.topLeft {
|
||||
transform: translate(calc(-1 * var(--x)), calc(-1 * var(--y))) rotate(var(--a));
|
||||
}
|
||||
|
||||
&.topRight {
|
||||
transform: translate(var(--x), calc(-1 * var(--y))) rotate(calc(-1 * var(--a)));
|
||||
}
|
||||
|
||||
&.bottomLeft {
|
||||
transform: translate(calc(-1 * var(--x)), var(--y)) rotate(calc(-1 * var(--a)));
|
||||
}
|
||||
|
||||
&.bottomRight {
|
||||
transform: translate(var(--x), var(--y)) rotate(var(--a));
|
||||
}
|
||||
|
||||
&.open.topLeft {
|
||||
transform: translate(calc(-1 * var(--x)), calc(-1 * var(--y))) rotate(calc(-1 * var(--a)));
|
||||
}
|
||||
|
||||
&.open.topRight {
|
||||
transform: translate(var(--x), calc(-1 * var(--y))) rotate(var(--a));
|
||||
}
|
||||
|
||||
&.open.bottomLeft {
|
||||
transform: translate(calc(-1 * var(--x)), var(--y)) rotate(var(--a));
|
||||
}
|
||||
|
||||
&.open.bottomRight {
|
||||
transform: translate(var(--x), var(--y)) rotate(calc(-1 * var(--a)));
|
||||
}
|
||||
}
|
||||
|
||||
.sticker {
|
||||
margin-inline-start: 0.375rem;
|
||||
}
|
||||
@ -55,6 +114,11 @@
|
||||
margin-inline-start: 3rem;
|
||||
}
|
||||
|
||||
.menuItemCount {
|
||||
margin-inline-start: 0.5rem;
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.backdrop {
|
||||
position: absolute;
|
||||
left: 0.625rem;
|
||||
@ -69,6 +133,11 @@
|
||||
margin-inline-start: 0.5 !important;
|
||||
}
|
||||
|
||||
.itemIcon {
|
||||
margin-inline-end: 0.125rem;
|
||||
font-size: 1.125rem !important;
|
||||
}
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@ -76,7 +145,7 @@
|
||||
|
||||
width: auto;
|
||||
margin-inline: 0.25rem;
|
||||
padding: 0.125rem 0.75rem;
|
||||
padding: 0.25rem 0.75rem;
|
||||
border-radius: 1rem;
|
||||
|
||||
font-size: 0.875rem;
|
||||
|
||||
@ -126,6 +126,27 @@ const GiftResaleFilters: FC<StateProps & OwnProps> = ({
|
||||
};
|
||||
}, [filteredAttributes, searchModelQuery, searchBackdropQuery, searchPatternQuery]);
|
||||
|
||||
const countersMap = useMemo(() => {
|
||||
const map = {
|
||||
model: new Map<string, number>(),
|
||||
pattern: new Map<string, number>(),
|
||||
backdrop: new Map<number, number>(),
|
||||
};
|
||||
|
||||
for (const counter of counters ?? []) {
|
||||
const { attribute, count } = counter;
|
||||
if (attribute.type === 'model') {
|
||||
map.model.set(attribute.documentId, count);
|
||||
} else if (attribute.type === 'pattern') {
|
||||
map.pattern.set(attribute.documentId, count);
|
||||
} else if (attribute.type === 'backdrop') {
|
||||
map.backdrop.set(attribute.backdropId, count);
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}, [counters]);
|
||||
|
||||
// Sort Menu
|
||||
const sortMenuRef = useRef<HTMLDivElement>();
|
||||
const {
|
||||
@ -175,18 +196,21 @@ const GiftResaleFilters: FC<StateProps & OwnProps> = ({
|
||||
const SortMenuButton: FC<{ onTrigger: (e: ReactMouseEvent<HTMLDivElement, MouseEvent>) => void; isOpen?: boolean }>
|
||||
= useMemo(() => {
|
||||
const sortType = filter.sortType;
|
||||
const iconName = sortType === 'byDate' ? 'sort-by-date'
|
||||
: sortType === 'byNumber' ? 'sort-by-number'
|
||||
: 'sort-by-price';
|
||||
return ({ onTrigger, isOpen: isMenuOpen }) => (
|
||||
<div
|
||||
className={styles.item}
|
||||
onClick={onTrigger}
|
||||
>
|
||||
<Icon
|
||||
name={iconName}
|
||||
className={styles.itemIcon}
|
||||
/>
|
||||
{sortType === 'byDate' && lang('ValueGiftSortByDate')}
|
||||
{sortType === 'byNumber' && lang('ValueGiftSortByNumber')}
|
||||
{sortType === 'byPrice' && lang('ValueGiftSortByPrice')}
|
||||
<Icon
|
||||
name="dropdown-arrows"
|
||||
className={styles.itemIcon}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}, [lang, filter]);
|
||||
@ -203,10 +227,7 @@ const GiftResaleFilters: FC<StateProps & OwnProps> = ({
|
||||
{attributesCount === 0 && lang('GiftAttributeModel')}
|
||||
{attributesCount > 0
|
||||
&& lang('GiftAttributeModelPlural', { count: attributesCount }, { pluralValue: attributesCount })}
|
||||
<Icon
|
||||
name="dropdown-arrows"
|
||||
className={styles.itemIcon}
|
||||
/>
|
||||
{renderDropdownArrows(isMenuOpen)}
|
||||
</div>
|
||||
);
|
||||
}, [lang, filter]);
|
||||
@ -222,10 +243,7 @@ const GiftResaleFilters: FC<StateProps & OwnProps> = ({
|
||||
{attributesCount === 0 && lang('GiftAttributeBackdrop')}
|
||||
{attributesCount > 0
|
||||
&& lang('GiftAttributeBackdropPlural', { count: attributesCount }, { pluralValue: attributesCount })}
|
||||
<Icon
|
||||
name="dropdown-arrows"
|
||||
className={styles.itemIcon}
|
||||
/>
|
||||
{renderDropdownArrows(isMenuOpen)}
|
||||
</div>
|
||||
);
|
||||
}, [lang, filter]);
|
||||
@ -240,10 +258,7 @@ const GiftResaleFilters: FC<StateProps & OwnProps> = ({
|
||||
{attributesCount === 0 && lang('GiftAttributeSymbol')}
|
||||
{attributesCount > 0
|
||||
&& lang('GiftAttributeSymbolPlural', { count: attributesCount }, { pluralValue: attributesCount })}
|
||||
<Icon
|
||||
name="dropdown-arrows"
|
||||
className={styles.itemIcon}
|
||||
/>
|
||||
{renderDropdownArrows(isMenuOpen)}
|
||||
</div>
|
||||
);
|
||||
}, [lang, filter]);
|
||||
@ -334,6 +349,17 @@ const GiftResaleFilters: FC<StateProps & OwnProps> = ({
|
||||
} });
|
||||
});
|
||||
|
||||
function renderDropdownArrows(isOpen?: boolean) {
|
||||
return (
|
||||
<div className={styles.dropdownArrows}>
|
||||
<div className={buildClassName(styles.arrowLine, styles.topLeft, isOpen && styles.open)} />
|
||||
<div className={buildClassName(styles.arrowLine, styles.topRight, isOpen && styles.open)} />
|
||||
<div className={buildClassName(styles.arrowLine, styles.bottomLeft, isOpen && styles.open)} />
|
||||
<div className={buildClassName(styles.arrowLine, styles.bottomRight, isOpen && styles.open)} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function renderSortMenuItems() {
|
||||
return (
|
||||
<>
|
||||
@ -425,7 +451,7 @@ const GiftResaleFilters: FC<StateProps & OwnProps> = ({
|
||||
onReset={handleSearchModelInputReset}
|
||||
placeholder={lang('Search')}
|
||||
/>
|
||||
<MenuItem icon="select" onClick={handleSelectedAllModelsClick} disabled={isSelectedAll}>
|
||||
<MenuItem icon="select" onClick={handleSelectedAllModelsClick}>
|
||||
{lang('ContextMenuItemSelectAll')}
|
||||
</MenuItem>
|
||||
{models.map((model) => {
|
||||
@ -447,6 +473,9 @@ const GiftResaleFilters: FC<StateProps & OwnProps> = ({
|
||||
/>
|
||||
<div className={styles.menuItemStickerText}>
|
||||
{model.name}
|
||||
<span className={styles.menuItemCount}>
|
||||
{lang.number(countersMap.model.get(model.sticker.id) || 0)}
|
||||
</span>
|
||||
</div>
|
||||
<Icon
|
||||
className={styles.menuItemIcon}
|
||||
@ -495,7 +524,7 @@ const GiftResaleFilters: FC<StateProps & OwnProps> = ({
|
||||
onReset={handleSearchBackdropInputReset}
|
||||
placeholder={lang('Search')}
|
||||
/>
|
||||
<MenuItem icon="select" onClick={handleSelectedAllBackdropsClick} disabled={isSelectedAll}>
|
||||
<MenuItem icon="select" onClick={handleSelectedAllBackdropsClick}>
|
||||
{lang('ContextMenuItemSelectAll')}
|
||||
</MenuItem>
|
||||
{backdrops.map((backdrop) => {
|
||||
@ -515,6 +544,9 @@ const GiftResaleFilters: FC<StateProps & OwnProps> = ({
|
||||
/>
|
||||
<div className={styles.backdropAttributeMenuItemText}>
|
||||
{backdrop.name}
|
||||
<span className={styles.menuItemCount}>
|
||||
{lang.number(countersMap.backdrop.get(backdrop.backdropId) || 0)}
|
||||
</span>
|
||||
</div>
|
||||
<Icon
|
||||
className={styles.menuItemIcon}
|
||||
@ -560,7 +592,7 @@ const GiftResaleFilters: FC<StateProps & OwnProps> = ({
|
||||
onReset={handleSearchPatternInputReset}
|
||||
placeholder={lang('Search')}
|
||||
/>
|
||||
<MenuItem icon="select" onClick={handleSelectedAllPatternsClick} disabled={isSelectedAll}>
|
||||
<MenuItem icon="select" onClick={handleSelectedAllPatternsClick}>
|
||||
{lang('ContextMenuItemSelectAll')}
|
||||
</MenuItem>
|
||||
{patterns.map((pattern) => {
|
||||
@ -583,6 +615,9 @@ const GiftResaleFilters: FC<StateProps & OwnProps> = ({
|
||||
|
||||
<div className={styles.menuItemStickerText}>
|
||||
{pattern.name}
|
||||
<span className={styles.menuItemCount}>
|
||||
{lang.number(countersMap.pattern.get(pattern.sticker.id) || 0)}
|
||||
</span>
|
||||
</div>
|
||||
<Icon
|
||||
className={styles.menuItemIcon}
|
||||
|
||||
@ -58,10 +58,10 @@
|
||||
|
||||
&.with-balance-bar {
|
||||
.modal-container {
|
||||
top: 5.5rem;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.modal-dialog {
|
||||
max-height: calc(100vh - 7.5rem);
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -208,14 +208,14 @@ const Modal: FC<OwnProps> = ({
|
||||
tabIndex={-1}
|
||||
role="dialog"
|
||||
>
|
||||
{withBalanceBar && (
|
||||
<ModalStarBalanceBar
|
||||
isModalOpen={isOpen}
|
||||
currency={currencyInBalanceBar}
|
||||
/>
|
||||
)}
|
||||
<div className="modal-container">
|
||||
<div className="modal-backdrop" onClick={!noBackdropClose ? onClose : undefined} />
|
||||
{withBalanceBar && (
|
||||
<ModalStarBalanceBar
|
||||
isModalOpen={isOpen}
|
||||
currency={currencyInBalanceBar}
|
||||
/>
|
||||
)}
|
||||
<div className={modalDialogClassName} ref={actualDialogRef} style={dialogStyle}>
|
||||
{renderHeader()}
|
||||
{Boolean(moreMenuItems) && (
|
||||
|
||||
@ -1,14 +1,12 @@
|
||||
.root {
|
||||
position: absolute;
|
||||
z-index: var(--z-modal);
|
||||
top: 0rem;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -1rem);
|
||||
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
padding: 0.5rem 1.25rem;
|
||||
border-radius: 2rem;
|
||||
|
||||
@ -20,6 +18,12 @@
|
||||
|
||||
transition: transform 0.2s ease, opacity 0.2s ease;
|
||||
|
||||
&.hidden {
|
||||
pointer-events: none;
|
||||
transform: scale(0);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
:global(.confirm) & {
|
||||
z-index: var(--z-modal-confirm);
|
||||
}
|
||||
@ -32,14 +36,6 @@
|
||||
transform: none !important;
|
||||
transition: none;
|
||||
}
|
||||
|
||||
&:not(:global(.open)) {
|
||||
transform: translate(-50%, 0);
|
||||
}
|
||||
|
||||
&:not(:global(.closing)) {
|
||||
transform: translate(-50%, 1rem);
|
||||
}
|
||||
}
|
||||
|
||||
.starIcon {
|
||||
@ -51,3 +47,8 @@
|
||||
.tonInUsdDescription {
|
||||
color: var(--color-text-secondary);
|
||||
}
|
||||
|
||||
.tonInUsdDescription,
|
||||
.getMoreStarsLink {
|
||||
font-weight: var(--font-weight-semibold);
|
||||
}
|
||||
|
||||
@ -1,6 +1,4 @@
|
||||
import {
|
||||
memo,
|
||||
} from '../../lib/teact/teact';
|
||||
import { memo } from '../../lib/teact/teact';
|
||||
import { getActions, withGlobal } from '../../global';
|
||||
|
||||
import type { ApiStarsAmount, ApiTonAmount } from '../../api/types';
|
||||
@ -10,6 +8,7 @@ import buildClassName from '../../util/buildClassName';
|
||||
import { convertTonFromNanos, convertTonToUsd, formatCurrencyAsString } from '../../util/formatCurrency';
|
||||
import { formatStarsAsIcon, formatTonAsIcon } from '../../util/localization/format';
|
||||
|
||||
import useIsTopmostBalanceBarModal from '../../hooks/element/useIsTopmostBalanceBarModal';
|
||||
import useLang from '../../hooks/useLang';
|
||||
import useLastCallback from '../../hooks/useLastCallback';
|
||||
import useShowTransition from '../../hooks/useShowTransition';
|
||||
@ -56,6 +55,8 @@ function ModalStarBalanceBar({
|
||||
withShouldRender: true,
|
||||
});
|
||||
|
||||
const isTopmost = useIsTopmostBalanceBarModal(ref, Boolean(shouldRender && currentBalance));
|
||||
|
||||
const handleGetMoreStars = useLastCallback(() => {
|
||||
openStarsBalanceModal(isTonMode ? { currency: 'TON' } : {});
|
||||
});
|
||||
@ -66,7 +67,7 @@ function ModalStarBalanceBar({
|
||||
|
||||
return (
|
||||
<div
|
||||
className={buildClassName(styles.root)}
|
||||
className={buildClassName(styles.root, !isTopmost && styles.hidden)}
|
||||
ref={ref}
|
||||
>
|
||||
<div>
|
||||
@ -101,7 +102,7 @@ function ModalStarBalanceBar({
|
||||
</div>
|
||||
)}
|
||||
{!isTonMode && (
|
||||
<Link isPrimary onClick={handleGetMoreStars}>
|
||||
<Link className={styles.getMoreStarsLink} isPrimary onClick={handleGetMoreStars}>
|
||||
{lang('GetMoreStarsLinkText')}
|
||||
</Link>
|
||||
)}
|
||||
|
||||
59
src/hooks/element/useIsTopmostBalanceBarModal.ts
Normal file
59
src/hooks/element/useIsTopmostBalanceBarModal.ts
Normal file
@ -0,0 +1,59 @@
|
||||
import { useEffect, useState } from '../../lib/teact/teact';
|
||||
|
||||
import { createCallbackManager } from '../../util/callbacks';
|
||||
|
||||
const BALANCE_BAR_MODAL_SELECTOR = '.Modal.with-balance-bar';
|
||||
|
||||
const balanceBarCallbacks = createCallbackManager();
|
||||
|
||||
export default function useIsTopmostBalanceBarModal(
|
||||
ref: { current?: HTMLElement },
|
||||
isActive?: boolean,
|
||||
) {
|
||||
const [isTopmost, setIsTopmost] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isActive) return undefined;
|
||||
|
||||
const updateIsTopmost = () => {
|
||||
setIsTopmost(checkIsTopmostBalanceBarModal(ref.current));
|
||||
};
|
||||
|
||||
updateIsTopmost();
|
||||
|
||||
const unsubscribe = balanceBarCallbacks.addCallback(updateIsTopmost);
|
||||
balanceBarCallbacks.runCallbacks();
|
||||
|
||||
return () => {
|
||||
unsubscribe();
|
||||
balanceBarCallbacks.runCallbacks();
|
||||
};
|
||||
}, [isActive, ref]);
|
||||
|
||||
return isTopmost;
|
||||
}
|
||||
|
||||
function checkIsTopmostBalanceBarModal(element?: HTMLElement) {
|
||||
if (!element) return true;
|
||||
|
||||
const parentModal = element.closest(BALANCE_BAR_MODAL_SELECTOR);
|
||||
if (!parentModal) return true;
|
||||
|
||||
const allBalanceBarModals = document.querySelectorAll(BALANCE_BAR_MODAL_SELECTOR);
|
||||
if (allBalanceBarModals.length <= 1) {
|
||||
return true;
|
||||
}
|
||||
|
||||
let topmostModal: Element | undefined;
|
||||
let highestZIndex = -Infinity;
|
||||
|
||||
allBalanceBarModals.forEach((modal) => {
|
||||
const zIndex = parseInt(getComputedStyle(modal).zIndex, 10) || 0;
|
||||
if (zIndex >= highestZIndex) {
|
||||
highestZIndex = zIndex;
|
||||
topmostModal = modal;
|
||||
}
|
||||
});
|
||||
|
||||
return parentModal === topmostModal;
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user