Merge branch 'ts-final' of github.com:trouble/payload into ts-final
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Props } from './types';
|
||||
import { Props, RenderedTypeProps } from './types';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
@@ -25,7 +25,7 @@ const Banner: React.FC<Props> = ({
|
||||
icon && `${baseClass}--align-icon-${alignIcon}`,
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
let RenderedType = 'div';
|
||||
let RenderedType: string | React.ComponentType<RenderedTypeProps> = 'div';
|
||||
|
||||
if (onClick && !to) RenderedType = 'button';
|
||||
if (to) RenderedType = Link;
|
||||
@@ -34,7 +34,6 @@ const Banner: React.FC<Props> = ({
|
||||
<RenderedType
|
||||
className={classes}
|
||||
onClick={onClick}
|
||||
type={RenderedType === 'button' ? 'button' : undefined}
|
||||
to={to || undefined}
|
||||
>
|
||||
{(icon && alignIcon === 'left') && (
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
import { MouseEvent } from 'react';
|
||||
|
||||
type onClick = (event: MouseEvent) => void
|
||||
|
||||
export type Props = {
|
||||
children?: React.ReactNode,
|
||||
className?: string,
|
||||
icon?: React.ReactNode,
|
||||
alignIcon?: 'left' | 'right',
|
||||
onClick?: (event: MouseEvent) => void,
|
||||
onClick?: onClick
|
||||
to?: string,
|
||||
type?: 'error' | 'success' | 'info' | 'default',
|
||||
}
|
||||
|
||||
export type RenderedTypeProps = {
|
||||
className?: string
|
||||
onClick?: onClick
|
||||
to: string
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
const getInitialColumnState = (fields, useAsTitle, defaultColumns) => {
|
||||
import { Field, fieldHasSubFields } from '../../../../fields/config/types';
|
||||
|
||||
const getInitialColumnState = (fields: Field[], useAsTitle: string, defaultColumns: string[]): { columns: string[] } => {
|
||||
let initialColumns = [];
|
||||
|
||||
if (Array.isArray(defaultColumns) && defaultColumns.length >= 1) {
|
||||
@@ -17,7 +19,7 @@ const getInitialColumnState = (fields, useAsTitle, defaultColumns) => {
|
||||
return remaining;
|
||||
}
|
||||
|
||||
if (!field.name && Array.isArray(field.fields)) {
|
||||
if (!field.name && fieldHasSubFields(field)) {
|
||||
return [
|
||||
...remaining,
|
||||
...field.fields.map((subField) => subField.name),
|
||||
@@ -39,4 +41,4 @@ const getInitialColumnState = (fields, useAsTitle, defaultColumns) => {
|
||||
};
|
||||
|
||||
|
||||
module.exports = getInitialColumnState;
|
||||
export default getInitialColumnState;
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Collection } from '../../../../collections/config/types';
|
||||
import { CollectionConfig } from '../../../../collections/config/types';
|
||||
|
||||
export type Props = {
|
||||
collection: Collection,
|
||||
collection: CollectionConfig,
|
||||
handleChange: (columns) => void,
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ import Loading from '../Loading';
|
||||
|
||||
const DatePicker = lazy(() => import('./DatePicker'));
|
||||
|
||||
export default (props) => (
|
||||
export default (props: unknown): React.ReactNode => (
|
||||
<Suspense fallback={<Loading />}>
|
||||
<DatePicker {...props} />
|
||||
</Suspense>
|
||||
|
||||
@@ -83,7 +83,6 @@ const DeleteDocument: React.FC<Props> = (props) => {
|
||||
<React.Fragment>
|
||||
<button
|
||||
type="button"
|
||||
slug={modalSlug}
|
||||
className={`${baseClass}__toggle`}
|
||||
onClick={(e) => {
|
||||
e.preventDefault();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Collection } from '../../../../collections/config/types';
|
||||
import { CollectionConfig } from '../../../../collections/config/types';
|
||||
|
||||
export type Props = {
|
||||
collection?: Collection,
|
||||
collection?: CollectionConfig,
|
||||
id?: string,
|
||||
title?: string,
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ const GenerateConfirmation: React.FC<Props> = (props) => {
|
||||
<Button
|
||||
size="small"
|
||||
buttonStyle="secondary"
|
||||
slug={modalSlug}
|
||||
onClick={() => {
|
||||
toggle(modalSlug);
|
||||
}}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
import { createTrue } from 'typescript';
|
||||
|
||||
export type Props = {
|
||||
setKey: () => void,
|
||||
highlightField: (Boolean) => void,
|
||||
|
||||
@@ -22,7 +22,6 @@ const ListControls: React.FC<Props> = (props) => {
|
||||
fields,
|
||||
admin: {
|
||||
useAsTitle,
|
||||
defaultColumns,
|
||||
},
|
||||
},
|
||||
} = props;
|
||||
@@ -31,7 +30,7 @@ const ListControls: React.FC<Props> = (props) => {
|
||||
const [search, setSearch] = useState('');
|
||||
const [columns, setColumns] = useState([]);
|
||||
const [where, setWhere] = useState({});
|
||||
const [visibleDrawer, setVisibleDrawer] = useState(false);
|
||||
const [visibleDrawer, setVisibleDrawer] = useState<'where' | 'sort' | 'columns'>();
|
||||
|
||||
useEffect(() => {
|
||||
if (useAsTitle) {
|
||||
@@ -44,7 +43,7 @@ const ListControls: React.FC<Props> = (props) => {
|
||||
}, [useAsTitle, fields]);
|
||||
|
||||
useEffect(() => {
|
||||
const newState = {
|
||||
const newState: any = {
|
||||
columns,
|
||||
};
|
||||
|
||||
@@ -83,7 +82,7 @@ const ListControls: React.FC<Props> = (props) => {
|
||||
<Button
|
||||
className={`${baseClass}__toggle-columns`}
|
||||
buttonStyle={visibleDrawer === 'columns' ? undefined : 'secondary'}
|
||||
onClick={() => setVisibleDrawer(visibleDrawer !== 'columns' ? 'columns' : false)}
|
||||
onClick={() => setVisibleDrawer(visibleDrawer !== 'columns' ? 'columns' : undefined)}
|
||||
icon="chevron"
|
||||
iconStyle="none"
|
||||
>
|
||||
@@ -93,7 +92,7 @@ const ListControls: React.FC<Props> = (props) => {
|
||||
<Button
|
||||
className={`${baseClass}__toggle-where`}
|
||||
buttonStyle={visibleDrawer === 'where' ? undefined : 'secondary'}
|
||||
onClick={() => setVisibleDrawer(visibleDrawer !== 'where' ? 'where' : false)}
|
||||
onClick={() => setVisibleDrawer(visibleDrawer !== 'where' ? 'where' : undefined)}
|
||||
icon="chevron"
|
||||
iconStyle="none"
|
||||
>
|
||||
@@ -103,7 +102,7 @@ const ListControls: React.FC<Props> = (props) => {
|
||||
<Button
|
||||
className={`${baseClass}__toggle-sort`}
|
||||
buttonStyle={visibleDrawer === 'sort' ? undefined : 'secondary'}
|
||||
onClick={() => setVisibleDrawer(visibleDrawer !== 'sort' ? 'sort' : false)}
|
||||
onClick={() => setVisibleDrawer(visibleDrawer !== 'sort' ? 'sort' : undefined)}
|
||||
icon="chevron"
|
||||
iconStyle="none"
|
||||
>
|
||||
@@ -120,7 +119,6 @@ const ListControls: React.FC<Props> = (props) => {
|
||||
>
|
||||
<ColumnSelector
|
||||
collection={collection}
|
||||
defaultColumns={defaultColumns}
|
||||
handleChange={setColumns}
|
||||
/>
|
||||
</AnimateHeight>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { Collection } from '../../../../collections/config/types';
|
||||
import { CollectionConfig } from '../../../../collections/config/types';
|
||||
|
||||
export type Props = {
|
||||
enableColumns?: boolean,
|
||||
enableSort?: boolean,
|
||||
setSort: () => void,
|
||||
collection: Collection,
|
||||
collection: CollectionConfig,
|
||||
handleChange: (newState) => void,
|
||||
}
|
||||
|
||||
@@ -47,7 +47,7 @@ const Localizer: React.FC<Props> = () => {
|
||||
return (
|
||||
<li
|
||||
key={localeOption}
|
||||
className={localeClasses}
|
||||
className={localeClasses.join(' ')}
|
||||
>
|
||||
<Link
|
||||
to={{ search }}
|
||||
|
||||
@@ -127,7 +127,7 @@ const DefaultNav = () => {
|
||||
);
|
||||
};
|
||||
|
||||
const Nav = () => {
|
||||
const Nav: React.FC = () => {
|
||||
const {
|
||||
admin: {
|
||||
components: {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React from 'react';
|
||||
import { useHistory, useLocation } from 'react-router-dom';
|
||||
import queryString from 'qs';
|
||||
import { Props } from './types';
|
||||
import { Props, Node } from './types';
|
||||
|
||||
import Page from './Page';
|
||||
import Separator from './Separator';
|
||||
@@ -58,7 +58,7 @@ const Pagination: React.FC<Props> = (props) => {
|
||||
const rangeEndIndex = (currentPage - 1) + numberOfNeighbors + 1;
|
||||
|
||||
// Slice out the range of pages that we want to render
|
||||
const nodes = pages.slice(rangeStartIndex, rangeEndIndex);
|
||||
const nodes: Node[] = pages.slice(rangeStartIndex, rangeEndIndex);
|
||||
|
||||
// Add prev separator if necessary
|
||||
if (currentPage - numberOfNeighbors - 1 >= 2) nodes.unshift({ type: 'Separator' });
|
||||
|
||||
@@ -8,5 +8,17 @@ export type Props = {
|
||||
nextPage?: number,
|
||||
numberOfNeighbors?: number,
|
||||
disableHistoryChange?: boolean,
|
||||
onChange?: (page) => void,
|
||||
onChange?: (page: number) => void,
|
||||
}
|
||||
|
||||
export type Node = {
|
||||
type: 'Page' | 'Separator' | 'ClickableArrow'
|
||||
props?: {
|
||||
page?: number
|
||||
updatePage: (page?: number) => void
|
||||
isFirstPage?: boolean
|
||||
isLastPage?: boolean
|
||||
isDisabled?: boolean
|
||||
direction?: 'right' | 'left'
|
||||
}
|
||||
} | number
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { Props } from './types';
|
||||
import { Props, RenderedTypeProps } from './types';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
@@ -25,7 +25,7 @@ const Pill: React.FC<Props> = ({
|
||||
icon && `${baseClass}--align-icon-${alignIcon}`,
|
||||
].filter(Boolean).join(' ');
|
||||
|
||||
let RenderedType = 'div';
|
||||
let RenderedType: string | React.FC<RenderedTypeProps> = 'div';
|
||||
|
||||
if (onClick && !to) RenderedType = 'button';
|
||||
if (to) RenderedType = Link;
|
||||
|
||||
@@ -1,9 +1,15 @@
|
||||
export type Props = {
|
||||
children?: React.ReactNode,
|
||||
className?: string,
|
||||
to?: string,
|
||||
icon?: React.ReactNode,
|
||||
alignIcon?: 'left' | 'right',
|
||||
onClick?: (onClick) => void,
|
||||
onClick?: () => void,
|
||||
pillStyle?: 'light' | 'dark' | 'light-gray',
|
||||
}
|
||||
|
||||
export type RenderedTypeProps = {
|
||||
className?: string,
|
||||
to: string,
|
||||
onClick?: () => void,
|
||||
type?: 'button'
|
||||
}
|
||||
|
||||
@@ -28,7 +28,7 @@ const PopupButton: React.FC<Props> = (props) => {
|
||||
return (
|
||||
<div
|
||||
role="button"
|
||||
tabIndex="0"
|
||||
tabIndex={0}
|
||||
onKeyDown={(e) => { if (e.key === 'Enter') handleClick(); }}
|
||||
onClick={handleClick}
|
||||
className={classes}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
export type Props = {
|
||||
buttonType: 'custom' | 'default',
|
||||
button: React.ReactNode,
|
||||
setActive: () => void,
|
||||
setActive: (active: boolean) => void,
|
||||
active: boolean,
|
||||
onToggleOpen: () => void,
|
||||
onToggleOpen: (active: boolean) => void,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import { useForm } from '../../forms/Form/context';
|
||||
import { useAuth } from '@payloadcms/config-provider';
|
||||
import { useForm } from '../../forms/Form/context';
|
||||
import Button from '../Button';
|
||||
import { Props } from './types';
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import React from 'react';
|
||||
import Select from 'react-select';
|
||||
import { Props } from './types';
|
||||
import Select, { ValueType, OptionsType } from 'react-select';
|
||||
import { Props, Value } from './types';
|
||||
import Chevron from '../../icons/Chevron';
|
||||
|
||||
import './index.scss';
|
||||
@@ -9,7 +9,6 @@ const ReactSelect: React.FC<Props> = (props) => {
|
||||
const {
|
||||
showError = false,
|
||||
options,
|
||||
isMulti = false,
|
||||
onChange,
|
||||
value,
|
||||
disabled = false,
|
||||
@@ -25,13 +24,13 @@ const ReactSelect: React.FC<Props> = (props) => {
|
||||
<Select
|
||||
{...props}
|
||||
value={value}
|
||||
onChange={(selected) => {
|
||||
onChange={(selected: ValueType<Value>) => {
|
||||
if (formatValue) {
|
||||
onChange(formatValue(selected));
|
||||
} else {
|
||||
let valueToChange;
|
||||
let valueToChange: string | string[];
|
||||
|
||||
if (isMulti) {
|
||||
if (Array.isArray(selected)) {
|
||||
if (selected) {
|
||||
valueToChange = selected.map((selectedOption) => {
|
||||
if (typeof selectedOption === 'string') {
|
||||
@@ -45,7 +44,7 @@ const ReactSelect: React.FC<Props> = (props) => {
|
||||
});
|
||||
}
|
||||
} else if (selected) {
|
||||
valueToChange = selected.value;
|
||||
valueToChange = (selected as Value).value;
|
||||
}
|
||||
onChange(valueToChange);
|
||||
}
|
||||
@@ -54,20 +53,19 @@ const ReactSelect: React.FC<Props> = (props) => {
|
||||
components={{ DropdownIndicator: Chevron }}
|
||||
className={classes}
|
||||
classNamePrefix="rs"
|
||||
options={options.map((option) => {
|
||||
const formattedOption = {
|
||||
value: option,
|
||||
label: option,
|
||||
};
|
||||
|
||||
if (typeof option === 'object') {
|
||||
formattedOption.value = option.value;
|
||||
formattedOption.label = option.label;
|
||||
|
||||
if (option.options) formattedOption.options = option.options;
|
||||
options={(options as OptionsType<Value>).map((option) => {
|
||||
if (typeof option === 'string') {
|
||||
return {
|
||||
value: option,
|
||||
label: option,
|
||||
};
|
||||
}
|
||||
|
||||
return formattedOption;
|
||||
return {
|
||||
value: option.value,
|
||||
label: option.label,
|
||||
options: option?.options,
|
||||
};
|
||||
})}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -1,9 +1,18 @@
|
||||
import { ValueType, OptionsType, GroupedOptionsType } from 'react-select';
|
||||
|
||||
export type Value = {
|
||||
label: string
|
||||
value: string
|
||||
options?: Value[]
|
||||
}
|
||||
|
||||
export type Props = {
|
||||
value?: string | [],
|
||||
onChange?: (formatValue) => void,
|
||||
value?: ValueType<Value>,
|
||||
onChange?: (value: any) => void,
|
||||
disabled?: boolean,
|
||||
showError?: boolean,
|
||||
formatValue?: () => void,
|
||||
options?: string[] | { value: string, label: string }[],
|
||||
formatValue?: (value: ValueType<Value>) => string[] | string,
|
||||
options: OptionsType<Value> | GroupedOptionsType<Value>
|
||||
isMulti?: boolean,
|
||||
isDisabled?: boolean
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Collection } from '../../../../collections/config/types';
|
||||
import { CollectionConfig } from '../../../../collections/config/types';
|
||||
|
||||
export type Props = {
|
||||
handleChange: (any) => void,
|
||||
collection: Collection,
|
||||
handleChange: (controls: any) => void,
|
||||
collection: CollectionConfig,
|
||||
}
|
||||
|
||||
@@ -12,7 +12,6 @@ const Table: React.FC<Props> = ({ columns, data }) => {
|
||||
<table
|
||||
cellPadding="0"
|
||||
cellSpacing="0"
|
||||
border="0"
|
||||
>
|
||||
<thead>
|
||||
<tr>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import React from 'react';
|
||||
|
||||
export type Props = {
|
||||
columns: {
|
||||
accessor: string,
|
||||
components: {
|
||||
Heading: React.ReactNode,
|
||||
renderCell: () => void,
|
||||
renderCell: (row: any, data: any) => React.ReactNode,
|
||||
},
|
||||
}[],
|
||||
data: []
|
||||
|
||||
@@ -13,7 +13,7 @@ const Thumbnail: React.FC<Props> = (props) => {
|
||||
filename,
|
||||
mimeType,
|
||||
staticURL,
|
||||
sizes = 'medium',
|
||||
sizes,
|
||||
adminThumbnail,
|
||||
size,
|
||||
} = props;
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { FileSizes } from '../../../../uploads/types';
|
||||
|
||||
export type Props = {
|
||||
filename: string,
|
||||
sizes?: unknown,
|
||||
sizes?: FileSizes
|
||||
adminThumbnail?: string,
|
||||
mimeType?: string,
|
||||
staticURL: string,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable jsx-a11y/click-events-have-key-events */
|
||||
import React from 'react';
|
||||
import { Props } from './types';
|
||||
import Thumbnail from '../Thumbnail';
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import { Collection } from '../../../../collections/config/types';
|
||||
import { CollectionConfig } from '../../../../collections/config/types';
|
||||
import { FileSizes } from '../../../../uploads/types';
|
||||
|
||||
export type Props = {
|
||||
collection: Collection,
|
||||
collection: CollectionConfig,
|
||||
id: string,
|
||||
filename: string,
|
||||
mimeType: string,
|
||||
sizes?: unknown,
|
||||
sizes?: FileSizes,
|
||||
onClick?: () => void,
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Collection } from '../../../../collections/config/types';
|
||||
import { CollectionConfig } from '../../../../collections/config/types';
|
||||
|
||||
export type Props = {
|
||||
handleChange: () => void,
|
||||
collection: Collection,
|
||||
handleChange: (controls: any) => void,
|
||||
collection: CollectionConfig,
|
||||
}
|
||||
|
||||
@@ -4,11 +4,13 @@ import { Fields, Field, Data } from './types';
|
||||
const buildValidationPromise = async (fieldState: Field, field: FieldSchema) => {
|
||||
const validatedFieldState = fieldState;
|
||||
|
||||
validatedFieldState.valid = typeof field.validate === 'function' ? await field.validate(fieldState.value, field) : true;
|
||||
const validationResult = typeof field.validate === 'function' ? await field.validate(fieldState.value, field) : true;
|
||||
|
||||
if (typeof validatedFieldState.valid === 'string') {
|
||||
validatedFieldState.errorMessage = validatedFieldState.valid;
|
||||
if (typeof validationResult === 'string') {
|
||||
validatedFieldState.errorMessage = validationResult;
|
||||
validatedFieldState.valid = false;
|
||||
} else {
|
||||
validatedFieldState.valid = true;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -86,7 +88,7 @@ const buildStateFromSchema = async (fieldSchema: FieldSchema[], fullData: Data =
|
||||
}
|
||||
|
||||
// Handle non-array-based nested fields (group, etc)
|
||||
if (field.type === 'row' || field.type === 'group') {
|
||||
if (field.type === 'group') {
|
||||
const subFieldData = initialData?.[field.name] as Data;
|
||||
|
||||
return {
|
||||
@@ -102,7 +104,7 @@ const buildStateFromSchema = async (fieldSchema: FieldSchema[], fullData: Data =
|
||||
}
|
||||
|
||||
// Handle field types that do not use names (row, etc)
|
||||
if (field.type === 'row' || field.type === 'group') {
|
||||
if (field.type === 'row') {
|
||||
return {
|
||||
...state,
|
||||
...iterateFields(field.fields, data, path),
|
||||
|
||||
@@ -6,6 +6,7 @@ import useFieldType from '../../useFieldType';
|
||||
import Label from '../../Label';
|
||||
import Error from '../../Error';
|
||||
import { select } from '../../../../../fields/validations';
|
||||
import { Props } from './types';
|
||||
|
||||
import './index.scss';
|
||||
|
||||
@@ -62,7 +63,7 @@ const formatRenderValue = (value, options) => {
|
||||
return value;
|
||||
};
|
||||
|
||||
const Select = (props) => {
|
||||
const Select: React.FC<Props> = (props) => {
|
||||
const {
|
||||
path: pathFromProps,
|
||||
name,
|
||||
|
||||
17
src/admin/components/forms/field-types/Select/types.ts
Normal file
17
src/admin/components/forms/field-types/Select/types.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import React from 'react';
|
||||
import { Validate } from '../../../../../fields/config/types';
|
||||
|
||||
export type Props = {
|
||||
path?: string
|
||||
name: string
|
||||
required?: boolean
|
||||
validate?: Validate
|
||||
label?: string
|
||||
options?: string[] | { label: string, value: string }[]
|
||||
hasMany?: boolean
|
||||
admin?: {
|
||||
readOnly?: boolean
|
||||
width?: string
|
||||
style?: React.CSSProperties
|
||||
}
|
||||
}
|
||||
@@ -195,3 +195,12 @@ export type Field =
|
||||
export type FieldWithPath = Field & {
|
||||
path?: string
|
||||
}
|
||||
|
||||
export type FieldWithSubFields =
|
||||
GroupField
|
||||
| ArrayField
|
||||
| RowField;
|
||||
|
||||
export function fieldHasSubFields(field: Field): field is FieldWithSubFields {
|
||||
return (field.type === 'group' || field.type === 'array' || field.type === 'row');
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import isImage from './isImage';
|
||||
import { FileData } from './types';
|
||||
import { FileSizes } from './types';
|
||||
|
||||
const getThumbnail = (mimeType: string, staticURL: string, filename: string, sizes: FileData[], adminThumbnail: string): string | boolean => {
|
||||
const getThumbnail = (mimeType: string, staticURL: string, filename: string, sizes: FileSizes, adminThumbnail: string): string | boolean => {
|
||||
if (isImage(mimeType)) {
|
||||
if (sizes?.[adminThumbnail]?.filename) {
|
||||
return `${staticURL}/${sizes[adminThumbnail].filename}`;
|
||||
|
||||
Reference in New Issue
Block a user