Files
remark42/frontend/app/components/auth-panel/auth-panel.tsx
T

344 lines
10 KiB
TypeScript

/** @jsx createElement */
import { createElement, Component, Fragment } from 'preact';
import { useSelector } from 'react-redux';
import { FormattedMessage, defineMessages, IntlShape, useIntl } from 'react-intl';
import b from 'bem-react-helper';
import { User, AuthProvider, Sorting, Theme, PostInfo } from '@app/common/types';
import { IS_STORAGE_AVAILABLE, IS_THIRD_PARTY } from '@app/common/constants';
import { requestDeletion } from '@app/utils/email';
import postMessage from '@app/utils/postMessage';
import { getHandleClickProps } from '@app/common/accessibility';
import { StoreState } from '@app/store';
import { ProviderState } from '@app/store/provider/reducers';
import { Dropdown, DropdownItem } from '@app/components/dropdown';
import { Button } from '@app/components/button';
import Auth from '@app/components/auth';
import useTheme from '@app/hooks/useTheme';
import { StaticStore } from '@app/common/static_store';
export interface OwnProps {
user: User | null;
hiddenUsers: StoreState['hiddenUsers'];
isCommentsDisabled: boolean;
postInfo: PostInfo;
onSortChange(s: Sorting): Promise<void>;
onSignIn(p: AuthProvider): Promise<User | null>;
onSignOut(): Promise<void>;
onCommentsChangeReadOnlyMode(readOnly: boolean): Promise<void>;
onBlockedUsersShow(): void;
onBlockedUsersHide(): void;
}
export interface Props extends OwnProps {
intl: IntlShape;
theme: Theme;
providers: AuthProvider['name'][];
provider: ProviderState;
sort: Sorting;
}
interface State {
isBlockedVisible: boolean;
anonymousUsernameInputValue: string;
sortSelectFocused: boolean;
}
export class AuthPanel extends Component<Props, State> {
state = {
isBlockedVisible: false,
anonymousUsernameInputValue: 'anon',
sortSelectFocused: false,
};
onSortChange = (e: Event) => {
const { value } = e.target as HTMLOptionElement;
this.props.onSortChange(value as Sorting);
};
onSortFocus = () => {
this.setState({ sortSelectFocused: true });
};
onSortBlur = (e: Event) => {
this.setState({ sortSelectFocused: false });
this.onSortChange(e);
};
toggleBlockedVisibility = () => {
if (!this.state.isBlockedVisible) {
if (this.props.onBlockedUsersShow) this.props.onBlockedUsersShow();
} else if (this.props.onBlockedUsersHide) this.props.onBlockedUsersHide();
this.setState({ isBlockedVisible: !this.state.isBlockedVisible });
};
toggleCommentsAvailability = () => {
this.props.onCommentsChangeReadOnlyMode(!this.props.isCommentsDisabled);
};
toggleUserInfoVisibility = () => {
const { user } = this.props;
if (window.parent && user) {
postMessage({ isUserInfoShown: true, user });
}
};
renderAuthorized = (user: User) => {
const { onSignOut, theme } = this.props;
const isUserAnonymous = user && user.id.substr(0, 10) === 'anonymous_';
return (
<Fragment>
<FormattedMessage id="authPanel.logged-as" defaultMessage="You logged in as" />{' '}
<Dropdown title={user.name} titleClass="auth-panel__user-dropdown-title" theme={theme}>
<DropdownItem separator={!isUserAnonymous}>
<div
id={user.id}
className={b('auth-panel__user-id', {}, { theme })}
{...getHandleClickProps(this.toggleUserInfoVisibility)}
>
{user.id}
</div>
</DropdownItem>
{!isUserAnonymous && (
<DropdownItem>
<Button theme={theme} onClick={() => requestDeletion().then(onSignOut)}>
<FormattedMessage id="authPanel.request-to-delete-data" defaultMessage="Request my data removal" />
</Button>
</DropdownItem>
)}
</Dropdown>{' '}
<Button kind="link" theme={theme} onClick={onSignOut}>
<FormattedMessage id="authPanel.logout" defaultMessage="Logout?" />
</Button>
</Fragment>
);
};
renderThirdPartyWarning = () => {
if (IS_STORAGE_AVAILABLE || !IS_THIRD_PARTY) return null;
return (
<div className="auth-panel__column">
<FormattedMessage
id="authPanel.disabled-cookies"
defaultMessage="Disable third-party cookies blocking to login or open comments in"
/>{' '}
<a
className="auth-panel__pseudo-link"
href={`${window.location.origin}/web/comments.html${window.location.search}`}
target="_blank"
>
new page
</a>
</div>
);
};
renderCookiesWarning = () => {
if (IS_STORAGE_AVAILABLE || IS_THIRD_PARTY) return null;
return (
<div className="auth-panel__column">
<FormattedMessage id="authPanel.enable-cookies" defaultMessage="Allow cookies to login and comment" />
</div>
);
};
renderSettingsLabel = () => {
return (
<Button
kind="link"
mix="auth-panel__admin-action"
{...getHandleClickProps(this.toggleBlockedVisibility)}
role="link"
>
{this.state.isBlockedVisible ? (
<FormattedMessage id="authPanel.hide-settings" defaultMessage="Hide settings" />
) : (
<FormattedMessage id="authPanel.show-settings" defaultMessage="Show settings" />
)}
</Button>
);
};
renderReadOnlySwitch = () => {
const { isCommentsDisabled } = this.props;
return (
<Button
kind="link"
mix="auth-panel__admin-action"
{...getHandleClickProps(this.toggleCommentsAvailability)}
role="link"
>
{isCommentsDisabled ? (
<FormattedMessage id="authPanel.enable-comments" defaultMessage="Enable comments" />
) : (
<FormattedMessage id="authPanel.disable-comments" defaultMessage="Disable comments" />
)}
</Button>
);
};
renderSort = () => {
const { sort } = this.props;
const { sortSelectFocused } = this.state;
const sortArray = getSortArray(sort, this.props.intl);
return (
<span className="auth-panel__sort">
<FormattedMessage id="commentSort.sort-by" defaultMessage="Sort by" />{' '}
<span className="auth-panel__select-label">
<span className={b('auth-panel__select-label-value', {}, { focused: sortSelectFocused })}>
{sortArray.find(x => 'selected' in x && x.selected!)!.label}
</span>
<select
className="auth-panel__select"
onChange={this.onSortChange}
onFocus={this.onSortFocus}
onBlur={this.onSortBlur}
>
{sortArray.map(sort => (
<option value={sort.value} selected={sort.selected}>
{sort.label}
</option>
))}
</select>
</span>
</span>
);
};
render({ user, postInfo, theme }: Props, { isBlockedVisible }: State) {
const { read_only } = postInfo;
const isAdmin = user && user.admin;
const isSettingsLabelVisible = Object.keys(this.props.hiddenUsers).length > 0 || isAdmin || isBlockedVisible;
return (
<div className={b('auth-panel', {}, { theme, loggedIn: !!user })}>
<div className="auth-panel__column">{user ? this.renderAuthorized(user) : read_only && <Auth />}</div>
{this.renderThirdPartyWarning()}
{this.renderCookiesWarning()}
<div className="auth-panel__column">
{isSettingsLabelVisible && this.renderSettingsLabel()}
{isSettingsLabelVisible && ' • '}
{isAdmin && this.renderReadOnlySwitch()}
{isAdmin && ' • '}
{!isAdmin && read_only && (
<span className="auth-panel__readonly-label">
<FormattedMessage id="authPanel.read-only" defaultMessage="Read-only" />
</span>
)}
{this.renderSort()}
</div>
</div>
);
}
}
const sortMessages = defineMessages({
best: {
id: 'commentsSort.best',
defaultMessage: 'Best',
},
worst: {
id: 'commentsSort.worst',
defaultMessage: 'Worst',
},
newest: {
id: 'commentsSort.newest',
defaultMessage: 'Newest',
},
oldest: {
id: 'commentsSort.oldest',
defaultMessage: 'Oldest',
},
recentlyUpdated: {
id: 'commentsSort.recently-updated',
defaultMessage: 'Recently updated',
},
leastRecentlyUpdated: {
id: 'commentsSort.least-recently-updated',
defaultMessage: 'Least recently updated',
},
mostControversial: {
id: 'commentsSort.most-controversial',
defaultMessage: 'Most controversial',
},
leastControversial: {
id: 'commentsSort.least-controversial',
defaultMessage: 'Least controversial',
},
});
function getSortArray(currentSort: Sorting, intl: IntlShape) {
const sortArray: {
value: Sorting;
label: string;
selected?: boolean;
}[] = [
{
value: '-score',
label: intl.formatMessage(sortMessages.best),
},
{
value: '+score',
label: intl.formatMessage(sortMessages.worst),
},
{
value: '-time',
label: intl.formatMessage(sortMessages.newest),
},
{
value: '+time',
label: intl.formatMessage(sortMessages.oldest),
},
{
value: '-active',
label: intl.formatMessage(sortMessages.recentlyUpdated),
},
{
value: '+active',
label: intl.formatMessage(sortMessages.leastRecentlyUpdated),
},
{
value: '-controversy',
label: intl.formatMessage(sortMessages.mostControversial),
},
{
value: '+controversy',
label: intl.formatMessage(sortMessages.leastControversial),
},
];
return sortArray.map(sort => {
if (sort.value === currentSort) {
sort.selected = true;
}
return sort;
});
}
export default function AuthPanelConnected(props: OwnProps) {
const intl = useIntl();
const theme = useTheme();
const provider = useSelector<StoreState, ProviderState>(state => state.provider);
const sort = useSelector<StoreState, Sorting>(state => state.comments.sort);
return (
<AuthPanel
intl={intl}
theme={theme}
providers={StaticStore.config.auth_providers}
provider={provider}
sort={sort}
{...props}
/>
);
}