Toast type (success/warning/etc)
This commit is contained in:
@ -112,7 +112,7 @@ class Layout extends React.Component<Props> {
|
|||||||
</Content>
|
</Content>
|
||||||
</Flex>
|
</Flex>
|
||||||
<Modals ui={ui} />
|
<Modals ui={ui} />
|
||||||
<Toasts />
|
<Toasts ui={ui} />
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,18 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { inject, observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import styled from 'styled-components';
|
import styled from 'styled-components';
|
||||||
import { layout } from 'shared/styles/constants';
|
import { layout } from 'shared/styles/constants';
|
||||||
import Toast from './components/Toast';
|
import Toast from './components/Toast';
|
||||||
|
import UiStore from '../../stores/UiStore';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
ui: UiStore,
|
||||||
|
};
|
||||||
@observer
|
@observer
|
||||||
class Toasts extends React.Component<*> {
|
class Toasts extends React.Component<Props> {
|
||||||
handleClose = index => {
|
handleClose = (index: number) => {
|
||||||
this.props.ui.remove(index);
|
this.props.ui.removeToast(index);
|
||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
@ -16,11 +20,11 @@ class Toasts extends React.Component<*> {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<List>
|
<List>
|
||||||
{ui.toasts.map((error, index) => (
|
{ui.toasts.map((toast, index) => (
|
||||||
<Toast
|
<Toast
|
||||||
key={index}
|
key={index}
|
||||||
onRequestClose={this.handleClose.bind(this, index)}
|
onRequestClose={this.handleClose.bind(this, index)}
|
||||||
message={error}
|
toast={toast}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</List>
|
</List>
|
||||||
@ -35,6 +39,7 @@ const List = styled.ol`
|
|||||||
list-style: none;
|
list-style: none;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
|
z-index: 1000;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default inject('ui')(Toasts);
|
export default Toasts;
|
||||||
|
@ -4,12 +4,12 @@ import styled from 'styled-components';
|
|||||||
import { darken } from 'polished';
|
import { darken } from 'polished';
|
||||||
import { color } from 'shared/styles/constants';
|
import { color } from 'shared/styles/constants';
|
||||||
import { fadeAndScaleIn } from 'shared/styles/animations';
|
import { fadeAndScaleIn } from 'shared/styles/animations';
|
||||||
|
import type { Toast as TToast } from '../../../types';
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
onRequestClose: () => void,
|
onRequestClose: () => void,
|
||||||
closeAfterMs: number,
|
closeAfterMs: number,
|
||||||
message: string,
|
toast: TToast,
|
||||||
type: 'warning' | 'error' | 'info',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class Toast extends React.Component<Props> {
|
class Toast extends React.Component<Props> {
|
||||||
@ -17,7 +17,6 @@ class Toast extends React.Component<Props> {
|
|||||||
|
|
||||||
static defaultProps = {
|
static defaultProps = {
|
||||||
closeAfterMs: 3000,
|
closeAfterMs: 3000,
|
||||||
type: 'warning',
|
|
||||||
};
|
};
|
||||||
|
|
||||||
componentDidMount() {
|
componentDidMount() {
|
||||||
@ -32,14 +31,14 @@ class Toast extends React.Component<Props> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { type, onRequestClose } = this.props;
|
const { toast, onRequestClose } = this.props;
|
||||||
const message =
|
const message =
|
||||||
typeof this.props.message === 'string'
|
typeof toast.message === 'string'
|
||||||
? this.props.message
|
? toast.message
|
||||||
: this.props.message.toString();
|
: toast.message.toString();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container onClick={onRequestClose} type={type}>
|
<Container onClick={onRequestClose} type={toast.type}>
|
||||||
<Message>{message}</Message>
|
<Message>{message}</Message>
|
||||||
</Container>
|
</Container>
|
||||||
);
|
);
|
||||||
|
@ -4,7 +4,7 @@ import { withRouter } from 'react-router-dom';
|
|||||||
import { inject } from 'mobx-react';
|
import { inject } from 'mobx-react';
|
||||||
import { MoreIcon } from 'outline-icons';
|
import { MoreIcon } from 'outline-icons';
|
||||||
|
|
||||||
import { Share } from 'types';
|
import type { Share } from 'types';
|
||||||
import CopyToClipboard from 'components/CopyToClipboard';
|
import CopyToClipboard from 'components/CopyToClipboard';
|
||||||
import SharesStore from 'stores/SharesStore';
|
import SharesStore from 'stores/SharesStore';
|
||||||
import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu';
|
import { DropdownMenu, DropdownMenuItem } from 'components/DropdownMenu';
|
||||||
|
@ -6,6 +6,7 @@ import styled from 'styled-components';
|
|||||||
import { color, size } from 'shared/styles/constants';
|
import { color, size } from 'shared/styles/constants';
|
||||||
|
|
||||||
import AuthStore from 'stores/AuthStore';
|
import AuthStore from 'stores/AuthStore';
|
||||||
|
import UiStore from 'stores/UiStore';
|
||||||
import ImageUpload from './components/ImageUpload';
|
import ImageUpload from './components/ImageUpload';
|
||||||
import Input, { LabelText } from 'components/Input';
|
import Input, { LabelText } from 'components/Input';
|
||||||
import Button from 'components/Button';
|
import Button from 'components/Button';
|
||||||
@ -15,6 +16,7 @@ import Flex from 'shared/components/Flex';
|
|||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
auth: AuthStore,
|
auth: AuthStore,
|
||||||
|
ui: UiStore,
|
||||||
};
|
};
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
@ -41,6 +43,7 @@ class Profile extends React.Component<Props> {
|
|||||||
name: this.name,
|
name: this.name,
|
||||||
avatarUrl: this.avatarUrl,
|
avatarUrl: this.avatarUrl,
|
||||||
});
|
});
|
||||||
|
this.props.ui.showToast('Profile saved', 'success');
|
||||||
};
|
};
|
||||||
|
|
||||||
handleNameChange = (ev: SyntheticInputEvent<*>) => {
|
handleNameChange = (ev: SyntheticInputEvent<*>) => {
|
||||||
@ -56,7 +59,7 @@ class Profile extends React.Component<Props> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { user } = this.props.auth;
|
const { user, isSaving } = this.props.auth;
|
||||||
if (!user) return null;
|
if (!user) return null;
|
||||||
const avatarUrl = this.avatarUrl || user.avatarUrl;
|
const avatarUrl = this.avatarUrl || user.avatarUrl;
|
||||||
|
|
||||||
@ -73,7 +76,7 @@ class Profile extends React.Component<Props> {
|
|||||||
>
|
>
|
||||||
<Avatar src={avatarUrl} />
|
<Avatar src={avatarUrl} />
|
||||||
<Flex auto align="center" justify="center">
|
<Flex auto align="center" justify="center">
|
||||||
Upload new image
|
Upload
|
||||||
</Flex>
|
</Flex>
|
||||||
</ImageUpload>
|
</ImageUpload>
|
||||||
</AvatarContainer>
|
</AvatarContainer>
|
||||||
@ -85,8 +88,8 @@ class Profile extends React.Component<Props> {
|
|||||||
onChange={this.handleNameChange}
|
onChange={this.handleNameChange}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<Button type="submit" disabled={this.isSaving || !this.name}>
|
<Button type="submit" disabled={isSaving || !this.name}>
|
||||||
Save
|
{isSaving ? 'Saving…' : 'Save'}
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
</CenteredContent>
|
</CenteredContent>
|
||||||
@ -101,7 +104,7 @@ const ProfilePicture = styled(Flex)`
|
|||||||
const avatarStyles = `
|
const avatarStyles = `
|
||||||
width: 80px;
|
width: 80px;
|
||||||
height: 80px;
|
height: 80px;
|
||||||
border-radius: 10px;
|
border-radius: 50%;
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const AvatarContainer = styled(Flex)`
|
const AvatarContainer = styled(Flex)`
|
||||||
|
@ -11,7 +11,7 @@ type Props = {
|
|||||||
auth: AuthStore,
|
auth: AuthStore,
|
||||||
scopes?: string[],
|
scopes?: string[],
|
||||||
redirectUri?: string,
|
redirectUri?: string,
|
||||||
state?: string,
|
state: string,
|
||||||
label?: string,
|
label?: string,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -12,6 +12,7 @@ class AuthStore {
|
|||||||
@observable user: ?User;
|
@observable user: ?User;
|
||||||
@observable team: ?Team;
|
@observable team: ?Team;
|
||||||
@observable token: ?string;
|
@observable token: ?string;
|
||||||
|
@observable isSaving: boolean = false;
|
||||||
@observable isLoading: boolean = false;
|
@observable isLoading: boolean = false;
|
||||||
@observable isSuspended: boolean = false;
|
@observable isSuspended: boolean = false;
|
||||||
@observable suspendedContactEmail: ?string;
|
@observable suspendedContactEmail: ?string;
|
||||||
@ -50,13 +51,19 @@ class AuthStore {
|
|||||||
};
|
};
|
||||||
|
|
||||||
@action
|
@action
|
||||||
updateUser = async (params: { name: string, avatarUrl?: string }) => {
|
updateUser = async (params: { name: string, avatarUrl: ?string }) => {
|
||||||
const res = await client.post(`/user.update`, params);
|
this.isSaving = true;
|
||||||
invariant(res && res.data, 'User response not available');
|
|
||||||
|
|
||||||
runInAction('AuthStore#updateUser', () => {
|
try {
|
||||||
this.user = res.data.user;
|
const res = await client.post(`/user.update`, params);
|
||||||
});
|
invariant(res && res.data, 'User response not available');
|
||||||
|
|
||||||
|
runInAction('AuthStore#updateUser', () => {
|
||||||
|
this.user = res.data;
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
this.isSaving = false;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -4,7 +4,6 @@ import { client } from 'utils/ApiClient';
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import invariant from 'invariant';
|
import invariant from 'invariant';
|
||||||
|
|
||||||
import stores from 'stores';
|
|
||||||
import BaseStore from './BaseStore';
|
import BaseStore from './BaseStore';
|
||||||
import UiStore from './UiStore';
|
import UiStore from './UiStore';
|
||||||
import Collection from 'models/Collection';
|
import Collection from 'models/Collection';
|
||||||
|
@ -2,6 +2,7 @@
|
|||||||
import { observable, action } from 'mobx';
|
import { observable, action } from 'mobx';
|
||||||
import Document from 'models/Document';
|
import Document from 'models/Document';
|
||||||
import Collection from 'models/Collection';
|
import Collection from 'models/Collection';
|
||||||
|
import type { Toast } from '../types';
|
||||||
|
|
||||||
class UiStore {
|
class UiStore {
|
||||||
@observable activeModalName: ?string;
|
@observable activeModalName: ?string;
|
||||||
@ -11,7 +12,7 @@ class UiStore {
|
|||||||
@observable progressBarVisible: boolean = false;
|
@observable progressBarVisible: boolean = false;
|
||||||
@observable editMode: boolean = false;
|
@observable editMode: boolean = false;
|
||||||
@observable mobileSidebarVisible: boolean = false;
|
@observable mobileSidebarVisible: boolean = false;
|
||||||
@observable toasts: string[] = observable.array([]);
|
@observable toasts: Toast[] = observable.array([]);
|
||||||
|
|
||||||
/* Actions */
|
/* Actions */
|
||||||
@action
|
@action
|
||||||
@ -82,8 +83,11 @@ class UiStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
showToast = (message: string): void => {
|
showToast = (
|
||||||
this.toasts.push(message);
|
message: string,
|
||||||
|
type?: 'warning' | 'error' | 'info' | 'success' = 'warning'
|
||||||
|
): void => {
|
||||||
|
this.toasts.push({ message, type });
|
||||||
};
|
};
|
||||||
|
|
||||||
@action
|
@action
|
||||||
|
@ -9,6 +9,11 @@ export type User = {
|
|||||||
isSuspended?: boolean,
|
isSuspended?: boolean,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type Toast = {
|
||||||
|
message: string,
|
||||||
|
type: 'warning' | 'error' | 'info' | 'success',
|
||||||
|
};
|
||||||
|
|
||||||
export type Share = {
|
export type Share = {
|
||||||
id: string,
|
id: string,
|
||||||
url: string,
|
url: string,
|
||||||
|
@ -47,9 +47,9 @@ export const color = {
|
|||||||
/* Brand */
|
/* Brand */
|
||||||
primary: '#1AB6FF',
|
primary: '#1AB6FF',
|
||||||
danger: '#D0021B',
|
danger: '#D0021B',
|
||||||
warning: '#f08a24' /* replace */,
|
warning: '#f08a24',
|
||||||
success: '#43AC6A' /* replace */,
|
success: '#1AB6FF',
|
||||||
info: '#a0d3e8' /* replace */,
|
info: '#a0d3e8',
|
||||||
offline: '#000000',
|
offline: '#000000',
|
||||||
|
|
||||||
/* Dark Grays */
|
/* Dark Grays */
|
||||||
|
Reference in New Issue
Block a user