mirror of
https://github.com/ansible/awx.git
synced 2026-01-12 02:19:58 -03:30
moves and renames new field component. adresses console errors, removes unneeded props adds back isVaid prop on formfield
This commit is contained in:
parent
dbc235cfb6
commit
783a0963ff
@ -29,6 +29,7 @@ function FormField(props) {
|
||||
helperText={helperText}
|
||||
helperTextInvalid={meta.error}
|
||||
isRequired={isRequired}
|
||||
validated={isValid ? 'default' : 'error'}
|
||||
label={label}
|
||||
labelIcon={<Popover content={tooltip} maxWidth={tooltipMaxWidth} />}
|
||||
>
|
||||
|
||||
@ -4,4 +4,3 @@ export { default as PasswordField } from './PasswordField';
|
||||
export { default as PasswordInput } from './PasswordInput';
|
||||
export { default as FormSubmitError } from './FormSubmitError';
|
||||
export { default as ArrayTextField } from './ArrayTextField';
|
||||
export { default as TextAndCheckboxField } from './TextAndCheckboxField';
|
||||
|
||||
@ -9,16 +9,16 @@ import {
|
||||
} from '@patternfly/react-core';
|
||||
import PFCheckIcon from '@patternfly/react-icons/dist/js/icons/check-icon';
|
||||
import styled from 'styled-components';
|
||||
import Popover from '../Popover';
|
||||
import Popover from '../../../components/Popover';
|
||||
|
||||
const InputGroup = styled(PFInputGroup)`
|
||||
padding-bottom: 5px;
|
||||
`;
|
||||
|
||||
const CheckIcon = styled(PFCheckIcon)`
|
||||
color: var(--pf-c-button--m-plain--disabled--Color);
|
||||
${props =>
|
||||
props.isSelected &&
|
||||
`color: var(--pf-c-button--m-secondary--active--Color)`};
|
||||
props.selected && `color: var(--pf-c-button--m-secondary--active--Color)`};
|
||||
`;
|
||||
|
||||
const validate = () => {
|
||||
@ -33,7 +33,7 @@ const validate = () => {
|
||||
return message;
|
||||
};
|
||||
};
|
||||
function TextAndCheckboxField({ label, tooltip }) {
|
||||
function MultipleChoiceField({ label, tooltip }) {
|
||||
const [
|
||||
formattedChoicesField,
|
||||
formattedChoicesMeta,
|
||||
@ -50,6 +50,8 @@ function TextAndCheckboxField({ label, tooltip }) {
|
||||
<FormGroup
|
||||
label={label}
|
||||
isRequired
|
||||
name="formattedChoices"
|
||||
id="formattedChoices"
|
||||
helperText={
|
||||
!formattedChoicesField.value[0].choice.trim().length
|
||||
? t`Type answer then click checkbox on right to select answer as default.`
|
||||
@ -64,10 +66,10 @@ function TextAndCheckboxField({ label, tooltip }) {
|
||||
validated={isValid ? 'default' : 'error'}
|
||||
labelIcon={<Popover content={tooltip} />}
|
||||
>
|
||||
{formattedChoicesField.value.map(({ choice, isDefault }, i) => (
|
||||
<InputGroup>
|
||||
{formattedChoicesField.value.map(({ choice, isDefault, id }, i) => (
|
||||
<InputGroup key={id}>
|
||||
<TextInput
|
||||
aria-label={choice}
|
||||
aria-label={choice || t`new choice`}
|
||||
onKeyUp={e => {
|
||||
if (
|
||||
e.key === 'Enter' &&
|
||||
@ -78,6 +80,7 @@ function TextAndCheckboxField({ label, tooltip }) {
|
||||
formattedChoicesField.value.concat({
|
||||
choice: '',
|
||||
isDefault: false,
|
||||
id: i + 1,
|
||||
})
|
||||
);
|
||||
}
|
||||
@ -96,36 +99,51 @@ function TextAndCheckboxField({ label, tooltip }) {
|
||||
}}
|
||||
value={choice}
|
||||
onChange={value => {
|
||||
const newValues = formattedChoicesField.value.map((cfv, index) =>
|
||||
i === index ? { choice: value, isDefault: false } : cfv
|
||||
const newValues = formattedChoicesField.value.map(
|
||||
(choiceField, index) =>
|
||||
i === index
|
||||
? { choice: value, isDefault: false, id: choiceField.id }
|
||||
: choiceField
|
||||
);
|
||||
formattedChoicesHelpers.setValue(newValues);
|
||||
}}
|
||||
/>
|
||||
|
||||
<Button
|
||||
variant="control"
|
||||
aria-label={t`Click to toggle default value`}
|
||||
ouiaId={choice}
|
||||
isDisabled={!choice.trim()}
|
||||
onClick={() => {
|
||||
const newValues = formattedChoicesField.value.map((cfv, index) =>
|
||||
i === index
|
||||
? { choice: cfv.choice, isDefault: !cfv.isDefault }
|
||||
: cfv
|
||||
const newValues = formattedChoicesField.value.map(
|
||||
(choiceField, index) =>
|
||||
i === index
|
||||
? {
|
||||
choice: choiceField.choice,
|
||||
isDefault: !choiceField.isDefault,
|
||||
id: choiceField.id,
|
||||
}
|
||||
: choiceField
|
||||
);
|
||||
const singleSelectValues = formattedChoicesField.value.map(
|
||||
(cfv, index) =>
|
||||
(choiceField, index) =>
|
||||
i === index
|
||||
? { choice: cfv.choice, isDefault: !cfv.isDefault }
|
||||
: { choice: cfv.choice, isDefault: false }
|
||||
? {
|
||||
choice: choiceField.choice,
|
||||
isDefault: !choiceField.isDefault,
|
||||
id: choiceField.id,
|
||||
}
|
||||
: {
|
||||
choice: choiceField.choice,
|
||||
isDefault: false,
|
||||
id: choiceField.id,
|
||||
}
|
||||
);
|
||||
return typeField.value === 'multiplechoice'
|
||||
? formattedChoicesHelpers.setValue(singleSelectValues)
|
||||
: formattedChoicesHelpers.setValue(newValues);
|
||||
}}
|
||||
>
|
||||
<CheckIcon isSelected={isDefault} />
|
||||
<CheckIcon selected={isDefault} />
|
||||
</Button>
|
||||
</InputGroup>
|
||||
))}
|
||||
@ -133,4 +151,4 @@ function TextAndCheckboxField({ label, tooltip }) {
|
||||
);
|
||||
}
|
||||
|
||||
export default TextAndCheckboxField;
|
||||
export default MultipleChoiceField;
|
||||
@ -1,10 +1,10 @@
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { Formik } from 'formik';
|
||||
import { mountWithContexts } from '../../../testUtils/enzymeHelpers';
|
||||
import TextAndCheckboxField from './TextAndCheckboxField';
|
||||
import { mountWithContexts } from '../../../../testUtils/enzymeHelpers';
|
||||
import MultipleChoiceField from './MultipleChoiceField';
|
||||
|
||||
describe('<TextAndCheckboxField/>', () => {
|
||||
describe('<MultipleChoiceField/>', () => {
|
||||
test('should activate default values, multiselect', async () => {
|
||||
let wrapper;
|
||||
|
||||
@ -20,7 +20,7 @@ describe('<TextAndCheckboxField/>', () => {
|
||||
type: 'multiselect',
|
||||
}}
|
||||
>
|
||||
<TextAndCheckboxField id="question-options" name="choices" />
|
||||
<MultipleChoiceField id="question-options" name="choices" />
|
||||
</Formik>
|
||||
);
|
||||
});
|
||||
@ -29,7 +29,7 @@ describe('<TextAndCheckboxField/>', () => {
|
||||
wrapper
|
||||
.find('Button[ouiaId="alex"]')
|
||||
.find('CheckIcon')
|
||||
.prop('isSelected')
|
||||
.prop('selected')
|
||||
).toBe(true);
|
||||
await act(() => wrapper.find('Button[ouiaId="alex"]').prop('onClick')());
|
||||
wrapper.update();
|
||||
@ -37,22 +37,22 @@ describe('<TextAndCheckboxField/>', () => {
|
||||
wrapper
|
||||
.find('Button[ouiaId="alex"]')
|
||||
.find('CheckIcon')
|
||||
.prop('isSelected')
|
||||
.prop('selected')
|
||||
).toBe(false);
|
||||
await act(async () =>
|
||||
wrapper
|
||||
.find('TextAndCheckboxField')
|
||||
.find('MultipleChoiceField')
|
||||
.find('TextInput')
|
||||
.at(0)
|
||||
.prop('onKeyUp')({ key: 'Enter' })
|
||||
);
|
||||
wrapper.update();
|
||||
expect(wrapper.find('TextAndCheckboxField').find('InputGroup').length).toBe(
|
||||
expect(wrapper.find('MultipleChoiceField').find('InputGroup').length).toBe(
|
||||
3
|
||||
);
|
||||
await act(async () =>
|
||||
wrapper
|
||||
.find('TextAndCheckboxField')
|
||||
.find('MultipleChoiceField')
|
||||
.find('TextInput')
|
||||
.at(2)
|
||||
.prop('onChange')('spencer')
|
||||
@ -65,7 +65,7 @@ describe('<TextAndCheckboxField/>', () => {
|
||||
wrapper
|
||||
.find('Button[ouiaId="spencer"]')
|
||||
.find('CheckIcon')
|
||||
.prop('isSelected')
|
||||
.prop('selected')
|
||||
).toBe(true);
|
||||
await act(() => wrapper.find('Button[ouiaId="alex"]').prop('onClick')());
|
||||
wrapper.update();
|
||||
@ -73,7 +73,7 @@ describe('<TextAndCheckboxField/>', () => {
|
||||
wrapper
|
||||
.find('Button[ouiaId="alex"]')
|
||||
.find('CheckIcon')
|
||||
.prop('isSelected')
|
||||
.prop('selected')
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
@ -85,14 +85,14 @@ describe('<TextAndCheckboxField/>', () => {
|
||||
<Formik
|
||||
initialValues={{
|
||||
formattedChoices: [
|
||||
{ choice: 'alex', isDefault: true },
|
||||
{ choice: 'apollo', isDefault: false },
|
||||
{ choice: 'athena', isDefault: false },
|
||||
{ choice: 'alex', isDefault: true, id: 1 },
|
||||
{ choice: 'apollo', isDefault: false, id: 2 },
|
||||
{ choice: 'athena', isDefault: false, id: 3 },
|
||||
],
|
||||
type: 'multiplechoice',
|
||||
}}
|
||||
>
|
||||
<TextAndCheckboxField id="question-options" name="choices" />
|
||||
<MultipleChoiceField id="question-options" name="choices" />
|
||||
</Formik>
|
||||
);
|
||||
});
|
||||
@ -101,7 +101,7 @@ describe('<TextAndCheckboxField/>', () => {
|
||||
wrapper
|
||||
.find('Button[ouiaId="alex"]')
|
||||
.find('CheckIcon')
|
||||
.prop('isSelected')
|
||||
.prop('selected')
|
||||
).toBe(true);
|
||||
await act(() => wrapper.find('Button[ouiaId="alex"]').prop('onClick')());
|
||||
wrapper.update();
|
||||
@ -109,14 +109,14 @@ describe('<TextAndCheckboxField/>', () => {
|
||||
wrapper
|
||||
.find('Button[ouiaId="alex"]')
|
||||
.find('CheckIcon')
|
||||
.prop('isSelected')
|
||||
.prop('selected')
|
||||
).toBe(false);
|
||||
expect(wrapper.find('TextAndCheckboxField').find('InputGroup').length).toBe(
|
||||
expect(wrapper.find('MultipleChoiceField').find('InputGroup').length).toBe(
|
||||
3
|
||||
);
|
||||
await act(async () =>
|
||||
wrapper
|
||||
.find('TextAndCheckboxField')
|
||||
.find('MultipleChoiceField')
|
||||
.find('TextInput')
|
||||
.at(0)
|
||||
.prop('onKeyUp')({ key: 'Enter' })
|
||||
@ -124,7 +124,7 @@ describe('<TextAndCheckboxField/>', () => {
|
||||
wrapper.update();
|
||||
await act(async () =>
|
||||
wrapper
|
||||
.find('TextAndCheckboxField')
|
||||
.find('MultipleChoiceField')
|
||||
.find('TextInput')
|
||||
.at(2)
|
||||
.prop('onChange')('spencer')
|
||||
@ -138,13 +138,13 @@ describe('<TextAndCheckboxField/>', () => {
|
||||
wrapper
|
||||
.find('Button[ouiaId="spencer"]')
|
||||
.find('CheckIcon')
|
||||
.prop('isSelected')
|
||||
.prop('selected')
|
||||
).toBe(true);
|
||||
expect(
|
||||
wrapper
|
||||
.find('Button[ouiaId="alex"]')
|
||||
.find('CheckIcon')
|
||||
.prop('isSelected')
|
||||
.prop('selected')
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
@ -37,18 +37,11 @@ export default function SurveyQuestionAdd({ survey, updateSurvey }) {
|
||||
: defaultAnswers.concat(`${choice}\n`);
|
||||
}
|
||||
});
|
||||
formData.default = defaultAnswers;
|
||||
formData.choices = choices;
|
||||
formData.default = defaultAnswers.trim();
|
||||
formData.choices = choices.trim();
|
||||
}
|
||||
delete formData.formattedChoices;
|
||||
|
||||
if (formData.type === 'multiselect') {
|
||||
formData.default = formData.default
|
||||
.split('\n')
|
||||
.filter(v => v !== '' || '\n')
|
||||
.map(v => v.trim())
|
||||
.join('\n');
|
||||
}
|
||||
const newSpec = survey.spec ? survey.spec.concat(formData) : [formData];
|
||||
|
||||
await updateSurvey(newSpec);
|
||||
|
||||
@ -74,8 +74,8 @@ export default function SurveyQuestionEdit({ survey, updateSurvey }) {
|
||||
: defaultAnswers.concat(`${choice}\n`);
|
||||
}
|
||||
});
|
||||
submittedData.default = defaultAnswers;
|
||||
submittedData.choices = choices;
|
||||
submittedData.default = defaultAnswers.trim();
|
||||
submittedData.choices = choices.trim();
|
||||
}
|
||||
|
||||
await updateSurvey([
|
||||
|
||||
@ -9,7 +9,6 @@ import FormField, {
|
||||
CheckboxField,
|
||||
PasswordField,
|
||||
FormSubmitError,
|
||||
TextAndCheckboxField,
|
||||
} from '../../../components/FormField';
|
||||
import { useConfig } from '../../../contexts/Config';
|
||||
import getDocsBaseUrl from '../../../util/getDocsBaseUrl';
|
||||
@ -23,6 +22,7 @@ import {
|
||||
integer,
|
||||
number as numberValidator,
|
||||
} from '../../../util/validators';
|
||||
import MultipleChoiceField from './MultipleChoiceField';
|
||||
|
||||
function AnswerTypeField() {
|
||||
const [field] = useField({
|
||||
@ -86,16 +86,16 @@ function SurveyQuestionForm({
|
||||
max: question?.max || 1024,
|
||||
default: question?.default || '',
|
||||
choices: question?.choices || '',
|
||||
formattedChoices: [{ choice: '', isDefault: false }],
|
||||
formattedChoices: [{ choice: '', isDefault: false, id: 0 }],
|
||||
new_question: !question,
|
||||
};
|
||||
if (question?.type === 'multiselect' || question?.type === 'multiplechoice') {
|
||||
const newQuestions = question.choices.split('\n').map(c => {
|
||||
const newQuestions = question.choices.split('\n').map((c, i) => {
|
||||
if (question.default.split('\n').includes(c)) {
|
||||
return { choice: c, isDefault: true };
|
||||
return { choice: c, isDefault: true, id: i };
|
||||
}
|
||||
|
||||
return { choice: c, isDefault: false };
|
||||
return { choice: c, isDefault: false, id: i };
|
||||
});
|
||||
|
||||
initialValues = {
|
||||
@ -218,11 +218,8 @@ function SurveyQuestionForm({
|
||||
/>
|
||||
)}
|
||||
{['multiplechoice', 'multiselect'].includes(formik.values.type) && (
|
||||
<TextAndCheckboxField
|
||||
id="question-options"
|
||||
name="choices"
|
||||
<MultipleChoiceField
|
||||
label={t`Multiple Choice Options`}
|
||||
validate={required()}
|
||||
tooltip={
|
||||
<>
|
||||
<span>{t`Refer to the`} </span>
|
||||
@ -236,7 +233,6 @@ function SurveyQuestionForm({
|
||||
{t`for more information.`}
|
||||
</>
|
||||
}
|
||||
isRequired
|
||||
/>
|
||||
)}
|
||||
</FormColumnLayout>
|
||||
|
||||
@ -149,13 +149,13 @@ describe('<SurveyQuestionForm />', () => {
|
||||
});
|
||||
await selectType(wrapper, 'multiplechoice');
|
||||
|
||||
expect(wrapper.find('TextAndCheckboxField').length).toBe(1);
|
||||
expect(wrapper.find('TextAndCheckboxField').find('TextInput').length).toBe(
|
||||
expect(wrapper.find('MultipleChoiceField').length).toBe(1);
|
||||
expect(wrapper.find('MultipleChoiceField').find('TextInput').length).toBe(
|
||||
1
|
||||
);
|
||||
expect(
|
||||
wrapper
|
||||
.find('TextAndCheckboxField')
|
||||
.find('MultipleChoiceField')
|
||||
.find('Button[aria-label="Click to toggle default value"]').length
|
||||
).toBe(1);
|
||||
});
|
||||
@ -174,13 +174,13 @@ describe('<SurveyQuestionForm />', () => {
|
||||
});
|
||||
await selectType(wrapper, 'multiselect');
|
||||
|
||||
expect(wrapper.find('TextAndCheckboxField').length).toBe(1);
|
||||
expect(wrapper.find('TextAndCheckboxField').find('TextInput').length).toBe(
|
||||
expect(wrapper.find('MultipleChoiceField').length).toBe(1);
|
||||
expect(wrapper.find('MultipleChoiceField').find('TextInput').length).toBe(
|
||||
1
|
||||
);
|
||||
expect(
|
||||
wrapper
|
||||
.find('TextAndCheckboxField')
|
||||
.find('MultipleChoiceField')
|
||||
.find('Button[aria-label="Click to toggle default value"]').length
|
||||
).toBe(1);
|
||||
});
|
||||
@ -249,8 +249,8 @@ describe('<SurveyQuestionForm />', () => {
|
||||
await selectType(wrapper, 'multiselect');
|
||||
await act(async () =>
|
||||
wrapper
|
||||
.find('TextAndCheckboxField')
|
||||
.find('TextAndCheckboxField')
|
||||
.find('MultipleChoiceField')
|
||||
.find('MultipleChoiceField')
|
||||
.find('TextInput')
|
||||
.at(0)
|
||||
.prop('onChange')('alex')
|
||||
@ -260,7 +260,7 @@ describe('<SurveyQuestionForm />', () => {
|
||||
wrapper
|
||||
.find('Button[ouiaId="alex"]')
|
||||
.find('CheckIcon')
|
||||
.prop('isSelected')
|
||||
.prop('selected')
|
||||
).toBe(false);
|
||||
await act(() => wrapper.find('Button[ouiaId="alex"]').prop('onClick')());
|
||||
wrapper.update();
|
||||
@ -268,28 +268,28 @@ describe('<SurveyQuestionForm />', () => {
|
||||
wrapper
|
||||
.find('Button[ouiaId="alex"]')
|
||||
.find('CheckIcon')
|
||||
.prop('isSelected')
|
||||
.prop('selected')
|
||||
).toBe(true);
|
||||
await act(async () =>
|
||||
wrapper
|
||||
.find('TextAndCheckboxField')
|
||||
.find('MultipleChoiceField')
|
||||
.find('TextInput')
|
||||
.at(0)
|
||||
.prop('onKeyUp')({ key: 'Enter' })
|
||||
);
|
||||
wrapper.update();
|
||||
expect(wrapper.find('TextAndCheckboxField').find('InputGroup').length).toBe(
|
||||
expect(wrapper.find('MultipleChoiceField').find('InputGroup').length).toBe(
|
||||
2
|
||||
);
|
||||
await act(async () =>
|
||||
wrapper
|
||||
.find('TextAndCheckboxField')
|
||||
.find('MultipleChoiceField')
|
||||
.find('TextInput')
|
||||
.at(1)
|
||||
.prop('onChange')('spencer')
|
||||
);
|
||||
wrapper.update();
|
||||
expect(wrapper.find('TextAndCheckboxField').find('InputGroup').length).toBe(
|
||||
expect(wrapper.find('MultipleChoiceField').find('InputGroup').length).toBe(
|
||||
2
|
||||
);
|
||||
await act(() => wrapper.find('Button[ouiaId="spencer"]').prop('onClick')());
|
||||
@ -298,7 +298,7 @@ describe('<SurveyQuestionForm />', () => {
|
||||
wrapper
|
||||
.find('Button[ouiaId="spencer"]')
|
||||
.find('CheckIcon')
|
||||
.prop('isSelected')
|
||||
.prop('selected')
|
||||
).toBe(true);
|
||||
await act(() => wrapper.find('Button[ouiaId="alex"]').prop('onClick')());
|
||||
wrapper.update();
|
||||
@ -306,7 +306,7 @@ describe('<SurveyQuestionForm />', () => {
|
||||
wrapper
|
||||
.find('Button[ouiaId="alex"]')
|
||||
.find('CheckIcon')
|
||||
.prop('isSelected')
|
||||
.prop('selected')
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
@ -325,7 +325,7 @@ describe('<SurveyQuestionForm />', () => {
|
||||
await selectType(wrapper, 'multiplechoice');
|
||||
await act(async () =>
|
||||
wrapper
|
||||
.find('TextAndCheckboxField')
|
||||
.find('MultipleChoiceField')
|
||||
.find('TextInput')
|
||||
.at(0)
|
||||
.prop('onChange')('alex')
|
||||
@ -335,7 +335,7 @@ describe('<SurveyQuestionForm />', () => {
|
||||
wrapper
|
||||
.find('Button[ouiaId="alex"]')
|
||||
.find('CheckIcon')
|
||||
.prop('isSelected')
|
||||
.prop('selected')
|
||||
).toBe(false);
|
||||
await act(() => wrapper.find('Button[ouiaId="alex"]').prop('onClick')());
|
||||
wrapper.update();
|
||||
@ -343,14 +343,14 @@ describe('<SurveyQuestionForm />', () => {
|
||||
wrapper
|
||||
.find('Button[ouiaId="alex"]')
|
||||
.find('CheckIcon')
|
||||
.prop('isSelected')
|
||||
.prop('selected')
|
||||
).toBe(true);
|
||||
expect(wrapper.find('TextAndCheckboxField').find('InputGroup').length).toBe(
|
||||
expect(wrapper.find('MultipleChoiceField').find('InputGroup').length).toBe(
|
||||
1
|
||||
);
|
||||
await act(async () =>
|
||||
wrapper
|
||||
.find('TextAndCheckboxField')
|
||||
.find('MultipleChoiceField')
|
||||
.find('TextInput')
|
||||
.at(0)
|
||||
.prop('onKeyUp')({ key: 'Enter' })
|
||||
@ -358,13 +358,13 @@ describe('<SurveyQuestionForm />', () => {
|
||||
wrapper.update();
|
||||
await act(async () =>
|
||||
wrapper
|
||||
.find('TextAndCheckboxField')
|
||||
.find('MultipleChoiceField')
|
||||
.find('TextInput')
|
||||
.at(1)
|
||||
.prop('onChange')('spencer')
|
||||
);
|
||||
wrapper.update();
|
||||
expect(wrapper.find('TextAndCheckboxField').find('InputGroup').length).toBe(
|
||||
expect(wrapper.find('MultipleChoiceField').find('InputGroup').length).toBe(
|
||||
2
|
||||
);
|
||||
await act(() => wrapper.find('Button[ouiaId="spencer"]').prop('onClick')());
|
||||
@ -374,13 +374,13 @@ describe('<SurveyQuestionForm />', () => {
|
||||
wrapper
|
||||
.find('Button[ouiaId="spencer"]')
|
||||
.find('CheckIcon')
|
||||
.prop('isSelected')
|
||||
.prop('selected')
|
||||
).toBe(true);
|
||||
expect(
|
||||
wrapper
|
||||
.find('Button[ouiaId="alex"]')
|
||||
.find('CheckIcon')
|
||||
.prop('isSelected')
|
||||
.prop('selected')
|
||||
).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user