Merge branch 'master' into vote

This commit is contained in:
Umputun
2019-04-09 14:21:05 -05:00
28 changed files with 870 additions and 292 deletions
+1
View File
@@ -38,5 +38,6 @@ services:
- ADMIN_SHARED_ID=dev_user # set admin flag for default user on local ouath2
- POSITIVE_SCORE=false # restricts comment's score to be only positive
- EDIT_TIME=5m # edit window
- AUTH_ANON=true
volumes:
- ./var:/srv/var
+2 -1
View File
@@ -6,7 +6,8 @@
"targets": {
"browsers": ["> 1%", "android >= 4.4.4", "ios >= 9", "IE >= 11"]
},
"useBuiltIns": "usage"
"useBuiltIns": "usage",
"corejs": 3
}
],
[
+3 -3
View File
@@ -1,11 +1,11 @@
const handleBtnKeyPress = (event: KeyboardEvent, handler?: () => void) => {
const handleBtnKeyPress = (event: KeyboardEvent, handler?: (e: KeyboardEvent | MouseEvent) => void) => {
if (event.key === ' ' || event.key === 'Enter') {
event.preventDefault();
handler && handler();
handler && handler(event);
}
};
export const getHandleClickProps = (handler?: () => void) => ({
export const getHandleClickProps = (handler?: (e: KeyboardEvent | MouseEvent) => void) => ({
role: 'button',
onClick: handler,
onKeyPress: (event: KeyboardEvent) => handleBtnKeyPress(event, handler),
+16 -7
View File
@@ -1,17 +1,26 @@
import { siteId, url } from './settings';
import { BASE_URL } from './constants';
import { Config, Comment, Tree, User, BlockedUser, Sorting, Provider, BlockTTL } from './types';
import { Config, Comment, Tree, User, BlockedUser, Sorting, AuthProvider, BlockTTL } from './types';
import fetcher from './fetcher';
/* common */
export const logIn = (provider: Provider) => {
const __loginAnonymously = (username: string): Promise<User | null> => {
const url = `/auth/anonymous/login?user=${encodeURIComponent(username)}&aud=${siteId}?from=${encodeURIComponent(
location.origin + location.pathname + '?selfClose'
)}`;
return fetcher.get<User>({ url, withCredentials: true, overriddenApiBase: '' });
};
export const logIn = (provider: AuthProvider): Promise<User | null> => {
if (provider.name === 'anonymous') return __loginAnonymously(provider.username);
return new Promise<User | null>((resolve, reject) => {
const newWindow = window.open(
`${BASE_URL}/auth/${provider}/login?from=${encodeURIComponent(
location.origin + location.pathname + '?selfClose'
)}&site=${siteId}`
);
const url = `${BASE_URL}/auth/${provider.name}/login?from=${encodeURIComponent(
location.origin + location.pathname + '?selfClose'
)}&site=${siteId}`;
const newWindow = window.open(url);
let secondsPass = 0;
const checkMsDelay = 300;
+4 -3
View File
@@ -1,4 +1,4 @@
import { Sorting, Provider, BlockingDuration, Theme } from './types';
import { Sorting, AuthProvider, BlockingDuration, Theme } from './types';
export const BASE_URL: string = process.env.REMARK_URL!;
export const API_BASE = '/api/v1';
@@ -12,13 +12,14 @@ export const MAX_SHOWN_ROOT_COMMENTS = 10;
export const DEFAULT_SORT: Sorting = '-active';
/* object of supported providers */
export const PROVIDER_NAMES: { [P in Provider]: string } = {
/* matches auth providers to UI label */
export const PROVIDER_NAMES: { [P in AuthProvider['name']]: string } = {
google: 'Google',
facebook: 'Facebook',
github: 'GitHub',
yandex: 'Yandex',
dev: 'Dev',
anonymous: 'Anonymous',
};
/** locastorage key for collapsed comments */
+1 -1
View File
@@ -1,4 +1,4 @@
import 'core-js/es7/promise';
import 'core-js/es/promise';
import 'focus-visible';
export default async function loadPolyfills() {
+8 -2
View File
@@ -92,7 +92,7 @@ export interface Config {
max_comment_size: number;
admins: string[];
admin_email: string;
auth_providers: Provider[];
auth_providers: (AuthProvider['name'])[];
low_score: number;
critical_score: number;
positive_score: boolean;
@@ -108,7 +108,13 @@ export interface RemarkConfig {
export type Sorting = '-time' | '+time' | '-active' | '+active' | '-score' | '+score' | '-controversy' | '+controversy';
export type Provider = 'google' | 'facebook' | 'github' | 'yandex' | 'dev';
export type AuthProvider =
| { name: 'google' }
| { name: 'facebook' }
| { name: 'github' }
| { name: 'yandex' }
| { name: 'dev' }
| { name: 'anonymous'; username: string };
export type BlockTTL = 'permanently' | '43200m' | '10080m' | '1440m';
@@ -0,0 +1,29 @@
.auth-panel-anonymous-login-form {
padding: 0.5em 0.7em;
display: flex;
flex-direction: row;
flex-wrap: nowrap;
}
.auth-panel-anonymous-login-form__input {
width: 15em;
}
.auth-panel-anonymous-login-form__remember-me {
display: none;
}
.auth-panel-anonymous-login-form__submit {
background: none;
border: none;
padding: 0;
margin: 0 0.5em;
color: currentColor;
font: inherit;
cursor: pointer;
}
.auth-panel-anonymous-login-form__submit:disabled {
opacity: 0.4;
cursor: not-allowed;
}
@@ -0,0 +1,105 @@
/** @jsx h */
import { h, Component, RenderableProps } from 'preact';
import b from 'bem-react-helper';
import { Theme } from '@app/common/types';
interface Props {
onSubmit(username: string): Promise<void>;
theme: Theme;
className?: string;
}
interface State {
inputValue: string;
honeyPotValue: boolean;
}
export class AnonymousLoginForm extends Component<Props, State> {
static usernameRegex = /^[a-zA-Z][\w ]+$/;
inputRef?: HTMLInputElement;
constructor(props: Props) {
super(props);
this.state = {
inputValue: '',
honeyPotValue: false,
};
this.onSubmit = this.onSubmit.bind(this);
this.onChange = this.onChange.bind(this);
this.onCheckedChange = this.onCheckedChange.bind(this);
}
onSubmit(e: Event) {
e.preventDefault();
if (this.state.honeyPotValue) {
// what should i do if bot uncovered?
window.location.reload();
return;
}
this.props.onSubmit(this.state.inputValue);
}
onChange(e: Event) {
this.setState({ inputValue: (e.target as HTMLInputElement).value });
}
getUsernameInvalidReason(): string | null {
const value = this.state.inputValue;
if (value.length < 3) return 'Username must be at least 3 characters long';
if (!AnonymousLoginForm.usernameRegex.test(value))
return 'Username must start from the letter and contain only latin letters, numbers, underscores, and spaces';
return null;
}
onCheckedChange(e: Event) {
this.setState({ honeyPotValue: (e.target as HTMLInputElement).checked });
}
componentDidUpdate() {
setTimeout(() => {
this.inputRef && this.inputRef.focus();
}, 100);
}
render(props: RenderableProps<Props>) {
// TODO: will be great to `b` to accept `string | undefined | (string|undefined)[]` as classname
let className = b('auth-panel-anonymous-login-form', {}, { theme: props.theme });
if (props.className) {
className += ' ' + b('auth-panel-anonymous-login-form', {}, { theme: props.theme });
}
const usernameInvalidReason = this.getUsernameInvalidReason();
return (
<form className={className} onSubmit={this.onSubmit}>
<input
className="auth-panel-anonymous-login-form__input"
ref={ref => (this.inputRef = ref)}
type="text"
placeholder="Username"
value={this.state.inputValue}
onInput={this.onChange}
/>
{/* honeypot input */}
<input
className="auth-panel-anonymous-login-form__remember-me"
type="checkbox"
tabIndex={-1}
autocomplete="off"
onChange={this.onCheckedChange}
checked={this.state.honeyPotValue}
/>
<input
className="auth-panel-anonymous-login-form__submit"
type="submit"
value="Log in"
title={usernameInvalidReason || ''}
disabled={usernameInvalidReason !== null}
/>
</form>
);
}
}
@@ -0,0 +1,3 @@
export { AnonymousLoginForm } from './auth-panel__anonymous-login-form';
require('./auth-panel__anonymous-login-form.scss');
@@ -3,9 +3,11 @@
font-weight: 700;
white-space: nowrap;
cursor: pointer;
color: #0aa;
/* important so "anonymous" label in Dropdown component
will not be overriden by `.dropdown_theme_light .dropdown__title` style */
color: #0aa !important;
&:hover {
color: #06c5c5;
color: #06c5c5 !important;
}
}
@@ -1,4 +1,6 @@
.auth-panel__user-id {
overflow: hidden;
text-overflow: ellipsis;
padding: 5px 15px;
cursor: pointer;
}
@@ -2,14 +2,19 @@
import { h } from 'preact';
import b from 'bem-react-helper';
import { Theme } from '@app/common/types';
import { exclude } from '@app/utils/exclude';
interface Props {
id: string;
theme: Theme;
}
export const UserID = (props: Props) => (
<div className={b('auth-panel__user-id', {}, { theme: props.theme })} title={props.id}>
export const UserID = (props: JSX.HTMLAttributes & Props) => (
<div
{...exclude(props, 'id', 'theme')}
className={b('auth-panel__user-id', {}, { theme: props.theme })}
title={props.id}
>
{props.id}
</div>
);
@@ -6,7 +6,7 @@ import { User, PostInfo } from '../../common/types';
const DefaultProps: Partial<Props> = {
sort: '-score',
providers: [`google`, `github`],
providers: ['google', 'github'],
postInfo: {
read_only: false,
url: 'https://example.com',
+71 -15
View File
@@ -5,22 +5,23 @@ import b from 'bem-react-helper';
import { PROVIDER_NAMES, IS_STORAGE_AVAILABLE, IS_THIRD_PARTY } from '@app/common/constants';
import { requestDeletion } from '@app/utils/email';
import { getHandleClickProps } from '@app/common/accessibility';
import { User, Provider, Sorting, Theme, PostInfo } from '@app/common/types';
import { User, AuthProvider, Sorting, Theme, PostInfo } from '@app/common/types';
import Dropdown, { DropdownItem } from '@app/components/dropdown';
import { Button } from '@app/components/button';
import { UserID } from './__user-id';
import { AnonymousLoginForm } from './__anonymous-login-form';
export interface Props {
user: User | null;
providers: Provider[];
providers: (AuthProvider['name'])[];
sort: Sorting;
isCommentsDisabled: boolean;
theme: Theme;
postInfo: PostInfo;
onSortChange(s: Sorting): Promise<void>;
onSignIn(p: Provider): Promise<User | null>;
onSignIn(p: AuthProvider): Promise<User | null>;
onSignOut(): Promise<void>;
onCommentsEnable(): Promise<boolean>;
onCommentsDisable(): Promise<boolean>;
@@ -30,6 +31,7 @@ export interface Props {
interface State {
isBlockedVisible: boolean;
anonymousUsernameInputValue: string;
}
export class AuthPanel extends Component<Props, State> {
@@ -38,11 +40,16 @@ export class AuthPanel extends Component<Props, State> {
this.state = {
isBlockedVisible: false,
anonymousUsernameInputValue: 'anon',
};
this.toggleBlockedVisibility = this.toggleBlockedVisibility.bind(this);
this.toggleCommentsAvailability = this.toggleCommentsAvailability.bind(this);
this.onSortChange = this.onSortChange.bind(this);
this.onSignIn = this.onSignIn.bind(this);
this.handleAnonymousLoginFormSubmut = this.handleAnonymousLoginFormSubmut.bind(this);
this.handleOAuthLogin = this.handleOAuthLogin.bind(this);
this.toggleUserInfoVisibility = this.toggleUserInfoVisibility.bind(this);
}
onSortChange(e: Event) {
@@ -67,16 +74,40 @@ export class AuthPanel extends Component<Props, State> {
}
}
toggleUserInfoVisibility() {
const user = this.props.user;
if (window.parent && user) {
const data = JSON.stringify({ isUserInfoShown: true, user });
window.parent.postMessage(data, '*');
}
}
getUserTitle() {
const { user } = this.props;
return <span className="auth-panel__username">{user!.name}</span>;
}
/** wrapper function to handle both oauth and anonymous providers*/
onSignIn(provider: AuthProvider) {
this.props.onSignIn(provider);
}
async handleAnonymousLoginFormSubmut(username: string) {
this.onSignIn({ name: 'anonymous', username });
}
async handleOAuthLogin(e: MouseEvent | KeyboardEvent) {
const p = (e.target as HTMLButtonElement).dataset.provider! as AuthProvider['name'];
// eslint-disable-next-line @typescript-eslint/no-object-literal-type-assertion
this.onSignIn({ name: p } as AuthProvider);
}
render(props: RenderableProps<Props>, { isBlockedVisible }: State) {
const { user, providers = [], sort, isCommentsDisabled } = props;
const sortArray = getSortArray(sort);
const loggedIn = !!user;
const signInMessage = props.postInfo.read_only ? 'Sign in using ' : 'Sign in to comment using ';
const isUserAnonymous = user && user.id.substr(0, 10) === 'anonymous_';
return (
<div className={b('auth-panel', {}, { theme: props.theme, loggedIn })}>
@@ -84,19 +115,21 @@ export class AuthPanel extends Component<Props, State> {
<div className="auth-panel__column">
You signed in as{' '}
<Dropdown title={user.name} theme={this.props.theme}>
<DropdownItem separator={true}>
<UserID id={user.id} theme={this.props.theme} />
<DropdownItem separator={!isUserAnonymous}>
<UserID id={user.id} theme={this.props.theme} {...getHandleClickProps(this.toggleUserInfoVisibility)} />
</DropdownItem>
<DropdownItem>
<Button
kind="link"
theme={this.props.theme}
onClick={() => requestDeletion().then(() => props.onSignOut())}
>
Request my data removal
</Button>
</DropdownItem>
{!isUserAnonymous && (
<DropdownItem>
<Button
kind="link"
theme={this.props.theme}
onClick={() => requestDeletion().then(() => props.onSignOut())}
>
Request my data removal
</Button>
</DropdownItem>
)}
</Dropdown>{' '}
<Button
className="auth-panel__sign-out"
@@ -115,12 +148,35 @@ export class AuthPanel extends Component<Props, State> {
{providers.map((provider, i) => {
const comma = i === 0 ? '' : i === providers.length - 1 ? ' or ' : ', ';
if (provider === 'anonymous') {
return (
<span>
{comma}{' '}
<Dropdown
title={PROVIDER_NAMES[provider]}
titleClass="auth-panel__pseudo-link"
theme={this.props.theme}
>
<DropdownItem>
<AnonymousLoginForm
onSubmit={this.handleAnonymousLoginFormSubmut}
theme={this.props.theme}
className="auth-panel__anonymous-login-form"
/>
</DropdownItem>
</Dropdown>
</span>
);
}
return (
<span>
{comma}
<span
className="auth-panel__pseudo-link"
{...getHandleClickProps(() => props.onSignIn(provider))}
data-provider={provider}
// eslint-disable-next-line @typescript-eslint/no-object-literal-type-assertion
{...getHandleClickProps(this.handleOAuthLogin)}
role="link"
>
{PROVIDER_NAMES[provider]}
@@ -0,0 +1,22 @@
<svg viewBox="0 0 24 24" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<g id="avatar-icon" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g transform="translate(4.000000, 2.000000)">
<g id="bg" transform="translate(0.600000, 0.497571)" fill-rule="nonzero">
<path d="M1.0658141e-14,18.0024291 C0.9,18.0024291 14.2,18.1024291 15.1,18.1024291 L14.4,14.6024291 C14.2,13.3024291 14.1,11.9024291 14.1,10.6024291 L14.1,7.00242909 C14.1,3.40242909 11.2,0.102429093 7.6,0.00242909307 C3.8,-0.0975709069 0.7,2.90242909 0.7,6.70242909 L0.7,10.6024291 C0.7,11.9024291 0.6,13.3024291 0.4,14.6024291 L1.0658141e-14,18.0024291 Z" id="Path" fill="#F5F4F4"></path>
<path d="M3.6,10.6024291 L3.6,6.70242909 C3.6,3.10242909 5.2,0.202429093 7.3,0.00242909307 C3.7,0.102429093 0.8,3.00242909 0.8,6.70242909 L0.8,10.6024291 C0.8,11.9024291 0.7,13.3024291 0.5,14.6024291 L5.77315973e-15,18.1024291 C0.9,18.1024291 2.62326389,17.9024291 3.02326389,18.1024291 L3.4,14.6024291 C3.5,13.3024291 3.6,11.9024291 3.6,10.6024291 Z" id="Path" fill="#FFFFFF" opacity="0.5"></path>
</g>
<path d="M15,11.1 L15,7.4 C15,3.6 12.1,0.4 8.4,0.1 C6.5,1.2490009e-16 4.6,0.7 3.2,2 C1.8,3.4 0.9,5.2 0.9,7.2 L0.9,11.1 C0.9,12.4 0.8,13.7 0.6,15 L0.1,18.5 C0.1,18.6 0.1,18.7 0.2,18.8 C0.3,18.9 0.4,19 0.5,19 C1.1,19 15.1,19 15.7,19 C15.8,19 15.9,19 16,18.9 C16.1,18.8 16.1,18.7 16.1,18.6 L15.3,15 C15.1,13.7 15,12.4 15,11.1 Z M1,18 L1.4,15.1 C1.6,13.8 1.7,12.4 1.7,11.1 L1.7,7.2 C1.7,5.4 2.4,3.8 3.7,2.6 C5,1.4 6.6,0.8 8.4,0.9 C11.7,1.1 14.3,4 14.3,7.4 L14.3,11 C14.3,12.3 14.4,13.7 14.6,15 L15,18 C14.9,18 1.1,18 1,18 Z" id="Shape" fill="#8ED0CF" fill-rule="nonzero"></path>
<g id="tie" transform="translate(1.000000, 12.000000)" stroke="#8ED0CF">
<polygon id="Triangle" stroke-width="0.9" fill="#8ED0CF" transform="translate(7.000000, 4.500000) scale(1, -1) translate(-7.000000, -4.500000) " points="7 3 9 6 5 6"></polygon>
<path d="M5,3 L3.5,6.5" id="Line-4" stroke-linecap="round"></path>
<path d="M9,3 L13.5,0.5" id="Line" stroke-linecap="round"></path>
<path d="M5,3 L0.5,0.5" id="Line-2" stroke-linecap="round"></path>
<path d="M9,3 L10.5,6.5" id="Line-3" stroke-linecap="round"></path>
</g>
<g id="eyes" transform="translate(4.000000, 5.000000)" fill="#8ED0CF" fill-rule="nonzero">
<path d="M1.7,0.6 C1.1,0.6 0.9,1.4 0.9,2.2 C0.9,3 1.1,3.8 1.7,3.8 C2.3,3.8 2.5,3 2.5,2.2 C2.5,1.4 2.2,0.6 1.7,0.6 Z" id="Path"></path>
<path d="M6.4,0.6 C5.8,0.6 5.6,1.4 5.6,2.2 C5.6,3 5.8,3.8 6.4,3.8 C7,3.8 7.2,3 7.2,2.2 C7.2,1.4 6.9,0.6 6.4,0.6 Z" id="Path"></path>
</g>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.8 KiB

+18 -7
View File
@@ -62,10 +62,25 @@ export class Button extends Component<JSX.HTMLAttributes & Props, State> {
this.props.onFocus!(e);
}
render(props: RenderableProps<Props>, state: State) {
const { children } = props;
render(props: RenderableProps<JSX.HTMLAttributes & Props>, state: State) {
const { children, className } = props;
const { isClicked, isFocused } = state;
let rclassName = b(
'button',
{ mix: props.mix },
{ theme: props.theme, type: props.type, kind: props.kind, clicked: isClicked, focused: isFocused }
);
if (className) {
rclassName +=
' ' +
b(
className,
{},
{ theme: props.theme, type: props.type, kind: props.kind, clicked: isClicked, focused: isFocused }
);
}
const localProps = { ...props };
delete localProps.children;
delete localProps.mix;
@@ -73,11 +88,7 @@ export class Button extends Component<JSX.HTMLAttributes & Props, State> {
return (
<button
{...localProps}
className={b(
'button',
{ mix: props.mix },
{ theme: props.theme, type: props.type, kind: props.kind, clicked: isClicked, focused: isFocused }
)}
className={rclassName}
onMouseDown={this.onMouseDown}
onBlur={this.onBlur}
onFocus={this.onFocus}
+11 -3
View File
@@ -320,13 +320,19 @@ export class Comment extends Component<Props, State> {
return !this.props.user;
}
/**
* Defines whether current client is logged in via `Anonymous provider`
*/
isAnonymous(): boolean {
return this.props.user! && this.props.user!.id.substr(0, 10) === 'anonymous_';
}
/**
* Defines whether comment made by logged in user
*/
isCurrentUser(): boolean {
if (this.isGuest()) {
return false;
}
if (this.isGuest()) return false;
return this.props.data.user.id === this.props.user!.id;
}
@@ -340,6 +346,7 @@ export class Comment extends Component<Props, State> {
if (this.isCurrentUser()) return "Can't vote for your own comment";
if (StaticStore.config.positive_score && this.props.data.score < 1) return 'Only positive score allowed';
if (this.isGuest()) return 'Sign in to vote';
if (this.isAnonymous()) return "Anonymous users can't vote";
return null;
}
@@ -352,6 +359,7 @@ export class Comment extends Component<Props, State> {
if (this.props.data.delete) return "Can't vote for deleted comment";
if (this.isCurrentUser()) return "Can't vote for your own comment";
if (this.isGuest()) return 'Sign in to vote';
if (this.isAnonymous()) return "Anonymous users can't vote";
return null;
}
@@ -4,9 +4,10 @@
outline-width: 0;
display: none;
top: 100%;
left: 50%;
transform: translate(-50%, 5px);
width: 170px;
left: 0%;
transform: translate(-0.5em, 5px);
min-width: 170px;
max-width: 260px;
border: 2px solid #259c9a;
border-radius: 3px;
padding: 0 0 5px;
@@ -10,6 +10,5 @@
&_separator {
border-bottom: 1px solid #259c9a;
margin-bottom: 5px;
padding: 5px 15px;
}
}
@@ -1,3 +1,7 @@
.dropdown__items {
padding: 5px 0;
}
.dropdown__items:last-child {
padding-bottom: 0;
}
+3 -1
View File
@@ -7,6 +7,7 @@ import { Theme } from '@app/common/types';
interface Props {
title: string;
titleClass?: string;
heading?: string;
isActive?: boolean;
onTitleClick?: () => void;
@@ -76,7 +77,7 @@ export default class Dropdown extends Component<Props, State> {
}
render(props: RenderableProps<Props>, { isActive }: State) {
const { title, heading, children, mix } = props;
const { title, titleClass, heading, children, mix } = props;
return (
<div className={b('dropdown', { mix }, { theme: props.theme, active: isActive })} ref={r => (this.rootNode = r)}>
@@ -87,6 +88,7 @@ export default class Dropdown extends Component<Props, State> {
type="button"
onClick={() => this.onTitleClick()}
theme="light"
className={titleClass}
>
{title}
</Button>
+3 -3
View File
@@ -12,7 +12,7 @@ import {
Tree,
Sorting,
Theme,
Provider,
AuthProvider,
BlockTTL,
} from '@app/common/types';
import {
@@ -61,7 +61,7 @@ interface Props {
fetchComments(sort: Sorting): Promise<Tree>;
fetchUser(): Promise<User | null>;
fetchBlockedUsers(): Promise<BlockedUser[]>;
logIn(): Promise<User | null>;
logIn(p: AuthProvider): Promise<User | null>;
logOut(): Promise<void>;
setTheme: (theme: Theme) => void;
setBlockedVisible: (value: boolean) => boolean;
@@ -289,7 +289,7 @@ const mapDispatchToProps = (dispatch: StoreDispatch) => {
fetchUser: () => dispatch(fetchUser()),
fetchBlockedUsers: () => dispatch(fetchBlockedUsers()),
setBlockedVisible: (value: boolean) => dispatch(setBlockedVisibleState(value)),
logIn: (provider: Provider) => dispatch(logIn(provider)),
logIn: (provider: AuthProvider) => dispatch(logIn(provider)),
logOut: () => dispatch(logout()),
setTheme: (theme: Theme) => dispatch(setTheme(theme)),
enableComments: () => dispatch(setCommentsReadOnlyState(false)),
+2 -2
View File
@@ -1,5 +1,5 @@
import api from '@app/common/api';
import { User, BlockedUser, Provider, BlockTTL } from '@app/common/types';
import { User, BlockedUser, AuthProvider, BlockTTL } from '@app/common/types';
import { ttlToTime } from '@app/utils/ttl-to-time';
import { StoreAction } from '../index';
@@ -16,7 +16,7 @@ export const fetchUser = (): StoreAction<Promise<User | null>> => async dispatch
return user;
};
export const logIn = (provider: Provider): StoreAction<Promise<User | null>> => async dispatch => {
export const logIn = (provider: AuthProvider): StoreAction<Promise<User | null>> => async dispatch => {
const user = await api.logIn(provider);
dispatch({
type: USER_SET,
+2 -2
View File
@@ -51,7 +51,7 @@ describe('user', () => {
);
const dispatch = jest.fn();
const getState = jest.fn();
await logIn('google')(dispatch, getState, undefined);
await logIn({ name: 'google' })(dispatch, getState, undefined);
expect(dispatch).toBeCalledWith({
type: USER_SET,
user: {
@@ -70,7 +70,7 @@ describe('user', () => {
);
const dispatch = jest.fn();
const getState = jest.fn();
await logIn('google')(dispatch, getState, undefined).catch(() => {});
await logIn({ name: 'google' })(dispatch, getState, undefined).catch(() => {});
expect(dispatch).not.toBeCalled();
});
+533 -222
View File
File diff suppressed because it is too large Load Diff
+10 -10
View File
@@ -30,24 +30,24 @@
}
},
"devDependencies": {
"@babel/core": "^7.3.4",
"@babel/core": "^7.4.0",
"@babel/plugin-syntax-dynamic-import": "^7.2.0",
"@babel/plugin-transform-react-jsx": "^7.3.0",
"@babel/preset-env": "^7.3.4",
"@babel/preset-env": "^7.4.2",
"@babel/preset-react": "^7.0.0",
"@types/core-js": "^2.5.0",
"@types/jest": "^24.0.11",
"@types/node": "^11.11.3",
"@typescript-eslint/eslint-plugin": "^1.4.2",
"@typescript-eslint/parser": "^1.4.2",
"@types/node": "^11.11.6",
"@typescript-eslint/eslint-plugin": "^1.5.0",
"@typescript-eslint/parser": "^1.5.0",
"autoprefixer": "^9.5.0",
"babel-eslint": "^10.0.1",
"babel-jest": "^24.5.0",
"babel-loader": "^8.0.5",
"clean-webpack-plugin": "^1.0.1",
"copy-webpack-plugin": "^5.0.1",
"clean-webpack-plugin": "^2.0.1",
"copy-webpack-plugin": "^5.0.2",
"css-loader": "^2.1.1",
"eslint": "^5.15.2",
"eslint": "^5.15.3",
"eslint-config-prettier": "^4.1.0",
"eslint-plugin-jsx-a11y": "^6.2.1",
"eslint-plugin-prettier": "^3.0.1",
@@ -72,7 +72,7 @@
"style-loader": "^0.23.1",
"ts-jest": "^24.0.0",
"ts-loader": "^5.3.3",
"typescript": "^3.3.3333",
"typescript": "^3.3.4000",
"webpack": "^4.29.6",
"webpack-bundle-analyzer": "^3.1.0",
"webpack-cli": "^3.3.0",
@@ -81,7 +81,7 @@
},
"dependencies": {
"bem-react-helper": "^1.1.2",
"core-js": "^2.6.5",
"core-js": "^3.0.0",
"focus-visible": "^4.1.5",
"preact": "^8.4.2",
"preact-redux": "^2.0.3",
+1 -1
View File
@@ -91,7 +91,7 @@ module.exports = () => ({
],
},
plugins: [
new Clean(publicFolder),
new Clean(),
new Define({
'process.env.NODE_ENV': JSON.stringify(env),
'process.env.REMARK_NODE': JSON.stringify(NODE_ID),