--- /dev/null
+import React from 'react';
+import PropTypes from 'prop-types';
+import api from 'flavours/glitch/util/api';
+import { injectIntl, defineMessages } from 'react-intl';
+import classNames from 'classnames';
+
+const messages = defineMessages({
+ other: { id: 'report.categories.other', defaultMessage: 'Other' },
+ spam: { id: 'report.categories.spam', defaultMessage: 'Spam' },
+ violation: { id: 'report.categories.violation', defaultMessage: 'Content violates one or more server rules' },
+});
+
+class Category extends React.PureComponent {
+
+ static propTypes = {
+ id: PropTypes.string.isRequired,
+ text: PropTypes.string.isRequired,
+ selected: PropTypes.bool,
+ disabled: PropTypes.bool,
+ onSelect: PropTypes.func,
+ children: PropTypes.node,
+ };
+
+ handleClick = () => {
+ const { id, disabled, onSelect } = this.props;
+
+ if (!disabled) {
+ onSelect(id);
+ }
+ };
+
+ render () {
+ const { id, text, disabled, selected, children } = this.props;
+
+ return (
+ <div tabIndex='0' role='button' className={classNames('report-reason-selector__category', { selected, disabled })} onClick={this.handleClick}>
+ {selected && <input type='hidden' name='report[category]' value={id} />}
+
+ <div className='report-reason-selector__category__label'>
+ <span className={classNames('poll__input', { active: selected, disabled })} />
+ {text}
+ </div>
+
+ {(selected && children) && (
+ <div className='report-reason-selector__category__rules'>
+ {children}
+ </div>
+ )}
+ </div>
+ );
+ }
+
+}
+
+class Rule extends React.PureComponent {
+
+ static propTypes = {
+ id: PropTypes.string.isRequired,
+ text: PropTypes.string.isRequired,
+ selected: PropTypes.bool,
+ disabled: PropTypes.bool,
+ onToggle: PropTypes.func,
+ };
+
+ handleClick = () => {
+ const { id, disabled, onToggle } = this.props;
+
+ if (!disabled) {
+ onToggle(id);
+ }
+ };
+
+ render () {
+ const { id, text, disabled, selected } = this.props;
+
+ return (
+ <div tabIndex='0' role='button' className={classNames('report-reason-selector__rule', { selected, disabled })} onClick={this.handleClick}>
+ <span className={classNames('poll__input', { checkbox: true, active: selected, disabled })} />
+ {selected && <input type='hidden' name='report[rule_ids][]' value={id} />}
+ {text}
+ </div>
+ );
+ }
+
+}
+
+export default @injectIntl
+class ReportReasonSelector extends React.PureComponent {
+
+ static propTypes = {
+ id: PropTypes.string.isRequired,
+ category: PropTypes.string.isRequired,
+ rule_ids: PropTypes.arrayOf(PropTypes.string),
+ disabled: PropTypes.bool,
+ intl: PropTypes.object.isRequired,
+ };
+
+ state = {
+ category: this.props.category,
+ rule_ids: this.props.rule_ids || [],
+ rules: [],
+ };
+
+ componentDidMount() {
+ api().get('/api/v1/instance').then(res => {
+ this.setState({
+ rules: res.data.rules,
+ });
+ }).catch(err => {
+ console.error(err);
+ });
+ }
+
+ _save = () => {
+ const { id, disabled } = this.props;
+ const { category, rule_ids } = this.state;
+
+ if (disabled) {
+ return;
+ }
+
+ api().put(`/api/v1/admin/reports/${id}`, {
+ category,
+ rule_ids,
+ }).catch(err => {
+ console.error(err);
+ });
+ };
+
+ handleSelect = id => {
+ this.setState({ category: id }, () => this._save());
+ };
+
+ handleToggle = id => {
+ const { rule_ids } = this.state;
+
+ if (rule_ids.includes(id)) {
+ this.setState({ rule_ids: rule_ids.filter(x => x !== id ) }, () => this._save());
+ } else {
+ this.setState({ rule_ids: [...rule_ids, id] }, () => this._save());
+ }
+ };
+
+ render () {
+ const { disabled, intl } = this.props;
+ const { rules, category, rule_ids } = this.state;
+
+ return (
+ <div className='report-reason-selector'>
+ <Category id='other' text={intl.formatMessage(messages.other)} selected={category === 'other'} onSelect={this.handleSelect} disabled={disabled} />
+ <Category id='spam' text={intl.formatMessage(messages.spam)} selected={category === 'spam'} onSelect={this.handleSelect} disabled={disabled} />
+ <Category id='violation' text={intl.formatMessage(messages.violation)} selected={category === 'violation'} onSelect={this.handleSelect} disabled={disabled}>
+ {rules.map(rule => <Rule key={rule.id} id={rule.id} text={rule.text} selected={rule_ids.includes(rule.id)} onToggle={this.handleToggle} disabled={disabled} />)}
+ </Category>
+ </div>
+ );
+ }
+
+}
.log-entry {
line-height: 20px;
- padding: 15px 0;
+ padding: 15px;
+ padding-left: 15px * 2 + 40px;
background: $ui-base-color;
- border-bottom: 1px solid lighten($ui-base-color, 4%);
+ border-bottom: 1px solid darken($ui-base-color, 8%);
+ position: relative;
+
+ &:first-child {
+ border-top-left-radius: 4px;
+ border-top-right-radius: 4px;
+ }
&:last-child {
+ border-bottom-left-radius: 4px;
+ border-bottom-right-radius: 4px;
border-bottom: 0;
}
+ &:hover {
+ background: lighten($ui-base-color, 4%);
+ }
+
&__header {
- display: flex;
- justify-content: flex-start;
- align-items: center;
color: $darker-text-color;
font-size: 14px;
- padding: 0 10px;
}
&__avatar {
- margin-right: 10px;
+ position: absolute;
+ left: 15px;
+ top: 15px;
.avatar {
- display: block;
- margin: 0;
- border-radius: 50%;
+ border-radius: 4px;
width: 40px;
height: 40px;
}
}
- &__content {
- max-width: calc(100% - 90px);
- }
-
&__title {
word-wrap: break-word;
}
text-decoration: none;
font-weight: 500;
}
+
+ a {
+ &:hover,
+ &:focus,
+ &:active {
+ text-decoration: underline;
+ }
+ }
}
a.name-tag,
a.name-tag,
.name-tag {
- display: flex;
+ display: inline-flex;
align-items: center;
+ vertical-align: top;
.avatar {
display: block;
}
}
}
+
+.report-reason-selector {
+ border-radius: 4px;
+ background: $ui-base-color;
+ margin-bottom: 20px;
+
+ &__category {
+ cursor: pointer;
+ border-bottom: 1px solid darken($ui-base-color, 8%);
+
+ &:last-child {
+ border-bottom: 0;
+ }
+
+ &__label {
+ padding: 15px;
+ }
+
+ &__rules {
+ margin-left: 30px;
+ }
+ }
+
+ &__rule {
+ cursor: pointer;
+ padding: 15px;
+ }
+}
+
+.report-header {
+ display: grid;
+ grid-gap: 15px;
+ grid-template-columns: minmax(0, 1fr) 300px;
+
+ &__details {
+ &__item {
+ border-bottom: 1px solid lighten($ui-base-color, 8%);
+ padding: 15px 0;
+
+ &:last-child {
+ border-bottom: 0;
+ }
+
+ &__header {
+ font-weight: 600;
+ padding: 4px 0;
+ }
+ }
+
+ &--horizontal {
+ display: grid;
+ grid-auto-columns: minmax(0, 1fr);
+ grid-auto-flow: column;
+
+ .report-header__details__item {
+ border-bottom: 0;
+ }
+ }
+ }
+}
+
+.account-card {
+ background: $ui-base-color;
+ border-radius: 4px;
+
+ &__header {
+ padding: 4px;
+ border-radius: 4px;
+ height: 128px;
+
+ img {
+ display: block;
+ margin: 0;
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ background: darken($ui-base-color, 8%);
+ }
+ }
+
+ &__title {
+ margin-top: -25px;
+ display: flex;
+ align-items: flex-end;
+
+ &__avatar {
+ padding: 15px;
+
+ img {
+ display: block;
+ margin: 0;
+ width: 56px;
+ height: 56px;
+ background: darken($ui-base-color, 8%);
+ border-radius: 8px;
+ }
+ }
+
+ .display-name {
+ color: $darker-text-color;
+ padding-bottom: 15px;
+ font-size: 15px;
+
+ bdi {
+ display: block;
+ color: $primary-text-color;
+ font-weight: 500;
+ }
+ }
+ }
+
+ &__bio {
+ padding: 0 15px;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ word-wrap: break-word;
+ max-height: 18px * 2;
+ position: relative;
+
+ &::after {
+ display: block;
+ content: "";
+ width: 50px;
+ height: 18px;
+ position: absolute;
+ bottom: 0;
+ right: 15px;
+ background: linear-gradient(to left, $ui-base-color, transparent);
+ pointer-events: none;
+ }
+ }
+
+ &__actions {
+ display: flex;
+ align-items: center;
+ padding-top: 10px;
+
+ &__button {
+ flex: 0 0 auto;
+ padding: 0 15px;
+ }
+ }
+
+ &__counters {
+ flex: 1 1 auto;
+ display: grid;
+ grid-auto-columns: minmax(0, 1fr);
+ grid-auto-flow: column;
+
+ &__item {
+ padding: 15px;
+ text-align: center;
+ color: $primary-text-color;
+ font-weight: 600;
+ font-size: 15px;
+
+ small {
+ display: block;
+ color: $darker-text-color;
+ font-weight: 400;
+ font-size: 13px;
+ }
+ }
+ }
+}
+
+.report-notes {
+ margin-bottom: 20px;
+
+ &__item {
+ background: $ui-base-color;
+ position: relative;
+ padding: 15px;
+ padding-left: 15px * 2 + 40px;
+ border-bottom: 1px solid darken($ui-base-color, 8%);
+
+ &:first-child {
+ border-top-left-radius: 4px;
+ border-top-right-radius: 4px;
+ }
+
+ &:last-child {
+ border-bottom-left-radius: 4px;
+ border-bottom-right-radius: 4px;
+ border-bottom: 0;
+ }
+
+ &:hover {
+ background-color: lighten($ui-base-color, 4%);
+ }
+
+ &__avatar {
+ position: absolute;
+ left: 15px;
+ top: 15px;
+ border-radius: 4px;
+ width: 40px;
+ height: 40px;
+ }
+
+ &__header {
+ color: $darker-text-color;
+ font-size: 15px;
+ line-height: 20px;
+ margin-bottom: 4px;
+
+ .username a {
+ color: $primary-text-color;
+ font-weight: 500;
+ text-decoration: none;
+ margin-right: 5px;
+
+ &:hover,
+ &:focus,
+ &:active {
+ text-decoration: underline;
+ }
+ }
+
+ time {
+ margin-left: 5px;
+ vertical-align: baseline;
+ }
+ }
+
+ &__content {
+ font-size: 15px;
+ line-height: 20px;
+ word-wrap: break-word;
+ font-weight: 400;
+ color: $primary-text-color;
+
+ p {
+ margin-bottom: 20px;
+ white-space: pre-wrap;
+ unicode-bidi: plaintext;
+
+ &:last-child {
+ margin-bottom: 0;
+ }
+ }
+ }
+
+ &__actions {
+ position: absolute;
+ top: 15px;
+ right: 15px;
+ text-align: right;
+ }
+ }
+}
+
+.report-actions {
+ border: 1px solid darken($ui-base-color, 8%);
+
+ &__item {
+ display: flex;
+ align-items: center;
+ line-height: 18px;
+ border-bottom: 1px solid darken($ui-base-color, 8%);
+
+ &:last-child {
+ border-bottom: 0;
+ }
+
+ &__button {
+ flex: 0 0 auto;
+ width: 100px;
+ padding: 15px;
+ padding-right: 0;
+
+ .button {
+ display: block;
+ width: 100%;
+ }
+ }
+
+ &__description {
+ padding: 15px;
+ font-size: 14px;
+ color: $dark-text-color;
+ }
+ }
+}