Merge branch 'snackbar' into 'develop'
Snackbars See merge request soapbox-pub/soapbox-fe!243
This commit is contained in:
commit
d022978078
18 changed files with 122 additions and 44 deletions
|
@ -10,7 +10,7 @@ describe('logOut()', () => {
|
|||
it('creates expected actions', () => {
|
||||
const expectedActions = [
|
||||
{ type: AUTH_LOGGED_OUT },
|
||||
{ type: ALERT_SHOW, title: 'Successfully logged out.', message: '' },
|
||||
{ type: ALERT_SHOW, message: 'Logged out.', severity: 'success' },
|
||||
];
|
||||
const store = mockStore(ImmutableMap());
|
||||
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
//test
|
||||
import { defineMessages } from 'react-intl';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
@ -23,11 +22,12 @@ export function clearAlert() {
|
|||
};
|
||||
};
|
||||
|
||||
export function showAlert(title = messages.unexpectedTitle, message = messages.unexpectedMessage) {
|
||||
export function showAlert(title = messages.unexpectedTitle, message = messages.unexpectedMessage, severity = 'info') {
|
||||
return {
|
||||
type: ALERT_SHOW,
|
||||
title,
|
||||
message,
|
||||
severity,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -47,9 +47,9 @@ export function showAlertForError(error) {
|
|||
message = data.error;
|
||||
}
|
||||
|
||||
return showAlert(title, message);
|
||||
return showAlert(title, message, 'error');
|
||||
} else {
|
||||
console.error(error);
|
||||
return showAlert();
|
||||
return showAlert(undefined, undefined, 'error');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import api from '../api';
|
||||
import { showAlert } from 'soapbox/actions/alerts';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import { fetchMe } from 'soapbox/actions/me';
|
||||
|
||||
export const AUTH_APP_CREATED = 'AUTH_APP_CREATED';
|
||||
|
@ -136,7 +136,7 @@ export function logIn(username, password) {
|
|||
if (error.response.data.error === 'mfa_required') {
|
||||
throw error;
|
||||
} else {
|
||||
dispatch(showAlert('Login failed.', 'Invalid username or password.'));
|
||||
dispatch(snackbar.error('Invalid username or password.'));
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
|
@ -156,7 +156,7 @@ export function logOut() {
|
|||
token: state.getIn(['auth', 'user', 'access_token']),
|
||||
});
|
||||
|
||||
dispatch(showAlert('Successfully logged out.', ''));
|
||||
dispatch(snackbar.success('Logged out.'));
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -172,9 +172,9 @@ export function register(params) {
|
|||
dispatch({ type: AUTH_REGISTER_SUCCESS, token: response.data });
|
||||
dispatch(authLoggedIn(response.data));
|
||||
if (needsConfirmation) {
|
||||
return dispatch(showAlert('', 'Check your email for further instructions.'));
|
||||
return dispatch(snackbar.info('You must confirm your email.'));
|
||||
} else if (needsApproval) {
|
||||
return dispatch(showAlert('', 'Your account has been submitted for approval.'));
|
||||
return dispatch(snackbar.info('Your account is being reviewed.'));
|
||||
} else {
|
||||
return dispatch(fetchMe());
|
||||
}
|
||||
|
@ -232,7 +232,7 @@ export function deleteAccount(password) {
|
|||
if (response.data.error) throw response.data.error; // This endpoint returns HTTP 200 even on failure
|
||||
dispatch({ type: DELETE_ACCOUNT_SUCCESS, response });
|
||||
dispatch({ type: AUTH_LOGGED_OUT });
|
||||
dispatch(showAlert('Successfully logged out.', ''));
|
||||
dispatch(snackbar.success('Logged out.'));
|
||||
}).catch(error => {
|
||||
dispatch({ type: DELETE_ACCOUNT_FAIL, error, skipAlert: true });
|
||||
throw error;
|
||||
|
|
|
@ -224,7 +224,7 @@ export function uploadCompose(files) {
|
|||
let total = Array.from(files).reduce((a, v) => a + v.size, 0);
|
||||
|
||||
if (files.length + media.size > uploadLimit) {
|
||||
dispatch(showAlert(undefined, messages.uploadErrorLimit));
|
||||
dispatch(showAlert(undefined, messages.uploadErrorLimit, 'error'));
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import api from '../api';
|
||||
import { showAlert } from 'soapbox/actions/alerts';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
|
||||
export const FILTERS_FETCH_REQUEST = 'FILTERS_FETCH_REQUEST';
|
||||
export const FILTERS_FETCH_SUCCESS = 'FILTERS_FETCH_SUCCESS';
|
||||
|
@ -47,7 +47,7 @@ export function createFilter(phrase, expires_at, context, whole_word, irreversib
|
|||
expires_at,
|
||||
}).then(response => {
|
||||
dispatch({ type: FILTERS_CREATE_SUCCESS, filter: response.data });
|
||||
dispatch(showAlert('', 'Filter added'));
|
||||
dispatch(snackbar.success('Filter added.'));
|
||||
}).catch(error => {
|
||||
dispatch({ type: FILTERS_CREATE_FAIL, error });
|
||||
});
|
||||
|
@ -60,7 +60,7 @@ export function deleteFilter(id) {
|
|||
dispatch({ type: FILTERS_DELETE_REQUEST });
|
||||
return api(getState).delete('/api/v1/filters/'+id).then(response => {
|
||||
dispatch({ type: FILTERS_DELETE_SUCCESS, filter: response.data });
|
||||
dispatch(showAlert('', 'Filter deleted'));
|
||||
dispatch(snackbar.success('Filter deleted.'));
|
||||
}).catch(error => {
|
||||
dispatch({ type: FILTERS_DELETE_FAIL, error });
|
||||
});
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import api from '../api';
|
||||
import { showAlert } from 'soapbox/actions/alerts';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
|
||||
export const IMPORT_FOLLOWS_REQUEST = 'IMPORT_FOLLOWS_REQUEST';
|
||||
export const IMPORT_FOLLOWS_SUCCESS = 'IMPORT_FOLLOWS_SUCCESS';
|
||||
|
@ -19,7 +19,7 @@ export function importFollows(params) {
|
|||
return api(getState)
|
||||
.post('/api/pleroma/follow_import', params)
|
||||
.then(response => {
|
||||
dispatch(showAlert('', 'Followers imported successfully'));
|
||||
dispatch(snackbar.success('Followers imported successfully'));
|
||||
dispatch({ type: IMPORT_FOLLOWS_SUCCESS, config: response.data });
|
||||
}).catch(error => {
|
||||
dispatch({ type: IMPORT_FOLLOWS_FAIL, error });
|
||||
|
@ -33,7 +33,7 @@ export function importBlocks(params) {
|
|||
return api(getState)
|
||||
.post('/api/pleroma/blocks_import', params)
|
||||
.then(response => {
|
||||
dispatch(showAlert('', 'Blocks imported successfully'));
|
||||
dispatch(snackbar.success('Blocks imported successfully'));
|
||||
dispatch({ type: IMPORT_BLOCKS_SUCCESS, config: response.data });
|
||||
}).catch(error => {
|
||||
dispatch({ type: IMPORT_BLOCKS_FAIL, error });
|
||||
|
@ -47,7 +47,7 @@ export function importMutes(params) {
|
|||
return api(getState)
|
||||
.post('/api/pleroma/mutes_import', params)
|
||||
.then(response => {
|
||||
dispatch(showAlert('', 'Mutes imported successfully'));
|
||||
dispatch(snackbar.success('Mutes imported successfully'));
|
||||
dispatch({ type: IMPORT_MUTES_SUCCESS, config: response.data });
|
||||
}).catch(error => {
|
||||
dispatch({ type: IMPORT_MUTES_FAIL, error });
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import api from '../api';
|
||||
import { importFetchedAccounts, importFetchedStatus } from './importer';
|
||||
import { showAlert } from 'soapbox/actions/alerts';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
|
||||
export const REBLOG_REQUEST = 'REBLOG_REQUEST';
|
||||
export const REBLOG_SUCCESS = 'REBLOG_SUCCESS';
|
||||
|
@ -211,7 +211,7 @@ export function bookmark(status) {
|
|||
api(getState).post(`/api/v1/statuses/${status.get('id')}/bookmark`).then(function(response) {
|
||||
dispatch(importFetchedStatus(response.data));
|
||||
dispatch(bookmarkSuccess(status, response.data));
|
||||
dispatch(showAlert('', 'Bookmark added'));
|
||||
dispatch(snackbar.success('Bookmark added'));
|
||||
}).catch(function(error) {
|
||||
dispatch(bookmarkFail(status, error));
|
||||
});
|
||||
|
@ -225,7 +225,7 @@ export function unbookmark(status) {
|
|||
api(getState).post(`/api/v1/statuses/${status.get('id')}/unbookmark`).then(response => {
|
||||
dispatch(importFetchedStatus(response.data));
|
||||
dispatch(unbookmarkSuccess(status, response.data));
|
||||
dispatch(showAlert('', 'Bookmark removed'));
|
||||
dispatch(snackbar.success('Bookmark removed'));
|
||||
}).catch(error => {
|
||||
dispatch(unbookmarkFail(status, error));
|
||||
});
|
||||
|
|
25
app/soapbox/actions/snackbar.js
Normal file
25
app/soapbox/actions/snackbar.js
Normal file
|
@ -0,0 +1,25 @@
|
|||
import { ALERT_SHOW } from './alerts';
|
||||
|
||||
const show = (severity, message) => ({
|
||||
type: ALERT_SHOW,
|
||||
message,
|
||||
severity,
|
||||
});
|
||||
|
||||
export function info(message) {
|
||||
return show('info', message);
|
||||
};
|
||||
|
||||
export function success(message) {
|
||||
return show('success', message);
|
||||
};
|
||||
|
||||
export function error(message) {
|
||||
return show('error', message);
|
||||
};
|
||||
|
||||
export default {
|
||||
info,
|
||||
success,
|
||||
error,
|
||||
};
|
|
@ -4,7 +4,7 @@ import ImmutablePureComponent from 'react-immutable-pure-component';
|
|||
import { resetPassword } from 'soapbox/actions/auth';
|
||||
import { SimpleForm, FieldsGroup, TextInput } from 'soapbox/features/forms';
|
||||
import { Redirect } from 'react-router-dom';
|
||||
import { showAlert } from 'soapbox/actions/alerts';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
|
||||
export default @connect()
|
||||
class PasswordReset extends ImmutablePureComponent {
|
||||
|
@ -20,7 +20,7 @@ class PasswordReset extends ImmutablePureComponent {
|
|||
this.setState({ isLoading: true });
|
||||
dispatch(resetPassword(nicknameOrEmail)).then(() => {
|
||||
this.setState({ isLoading: false, success: true });
|
||||
dispatch(showAlert('Password reset received. Check your email for further instructions.', ''));
|
||||
dispatch(snackbar.info('Check your email for confirmation.'));
|
||||
}).catch(error => {
|
||||
this.setState({ isLoading: false });
|
||||
});
|
||||
|
|
|
@ -4,7 +4,7 @@ import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
|
|||
import ImmutablePureComponent from 'react-immutable-pure-component';
|
||||
import PropTypes from 'prop-types';
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes';
|
||||
import { showAlert } from 'soapbox/actions/alerts';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import Column from '../ui/components/column';
|
||||
import {
|
||||
SimpleForm,
|
||||
|
@ -124,7 +124,7 @@ class EditProfile extends ImmutablePureComponent {
|
|||
const { dispatch } = this.props;
|
||||
dispatch(patchMe(this.getFormdata())).then(() => {
|
||||
this.setState({ isLoading: false });
|
||||
dispatch(showAlert('', 'Profile saved!'));
|
||||
dispatch(snackbar.success('Profile saved!'));
|
||||
}).catch((error) => {
|
||||
this.setState({ isLoading: false });
|
||||
});
|
||||
|
|
|
@ -14,7 +14,7 @@ import {
|
|||
SelectDropdown,
|
||||
Checkbox,
|
||||
} from 'soapbox/features/forms';
|
||||
import { showAlert } from 'soapbox/actions/alerts';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import Icon from 'soapbox/components/icon';
|
||||
import ColumnSubheading from '../ui/components/column_subheading';
|
||||
|
||||
|
@ -114,7 +114,7 @@ class Filters extends ImmutablePureComponent {
|
|||
dispatch(createFilter(phrase, expires_at, context, whole_word, irreversible)).then(response => {
|
||||
return dispatch(fetchFilters());
|
||||
}).catch(error => {
|
||||
dispatch(showAlert('', intl.formatMessage(messages.create_error)));
|
||||
dispatch(snackbar.error(intl.formatMessage(messages.create_error)));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -123,7 +123,7 @@ class Filters extends ImmutablePureComponent {
|
|||
dispatch(deleteFilter(e.currentTarget.dataset.value)).then(response => {
|
||||
return dispatch(fetchFilters());
|
||||
}).catch(error => {
|
||||
dispatch(showAlert('', intl.formatMessage(messages.delete_error)));
|
||||
dispatch(snackbar.error(intl.formatMessage(messages.delete_error)));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,7 @@ import {
|
|||
deleteAccount,
|
||||
} from 'soapbox/actions/auth';
|
||||
import { fetchUserMfaSettings } from '../../actions/mfa';
|
||||
import { showAlert } from 'soapbox/actions/alerts';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import { changeSetting, getSettings } from 'soapbox/actions/settings';
|
||||
|
||||
/*
|
||||
|
@ -119,10 +119,10 @@ class ChangeEmailForm extends ImmutablePureComponent {
|
|||
this.setState({ isLoading: true });
|
||||
return dispatch(changeEmail(email, password)).then(() => {
|
||||
this.setState({ email: '', password: '' }); // TODO: Maybe redirect user
|
||||
dispatch(showAlert('', intl.formatMessage(messages.updateEmailSuccess)));
|
||||
dispatch(snackbar.success(intl.formatMessage(messages.updateEmailSuccess)));
|
||||
}).catch(error => {
|
||||
this.setState({ password: '' });
|
||||
dispatch(showAlert('', intl.formatMessage(messages.updateEmailFail)));
|
||||
dispatch(snackbar.error(intl.formatMessage(messages.updateEmailFail)));
|
||||
}).then(() => {
|
||||
this.setState({ isLoading: false });
|
||||
});
|
||||
|
@ -193,10 +193,10 @@ class ChangePasswordForm extends ImmutablePureComponent {
|
|||
this.setState({ isLoading: true });
|
||||
return dispatch(changePassword(oldPassword, newPassword, confirmation)).then(() => {
|
||||
this.clearForm(); // TODO: Maybe redirect user
|
||||
dispatch(showAlert('', intl.formatMessage(messages.updatePasswordSuccess)));
|
||||
dispatch(snackbar.success(intl.formatMessage(messages.updatePasswordSuccess)));
|
||||
}).catch(error => {
|
||||
this.clearForm();
|
||||
dispatch(showAlert('', intl.formatMessage(messages.updatePasswordFail)));
|
||||
dispatch(snackbar.error(intl.formatMessage(messages.updatePasswordFail)));
|
||||
}).then(() => {
|
||||
this.setState({ isLoading: false });
|
||||
});
|
||||
|
@ -374,10 +374,10 @@ class DeactivateAccount extends ImmutablePureComponent {
|
|||
this.setState({ isLoading: true });
|
||||
return dispatch(deleteAccount(password)).then(() => {
|
||||
//this.setState({ email: '', password: '' }); // TODO: Maybe redirect user
|
||||
dispatch(showAlert('', intl.formatMessage(messages.deleteAccountSuccess)));
|
||||
dispatch(snackbar.success(intl.formatMessage(messages.deleteAccountSuccess)));
|
||||
}).catch(error => {
|
||||
this.setState({ password: '' });
|
||||
dispatch(showAlert('', intl.formatMessage(messages.deleteAccountFail)));
|
||||
dispatch(snackbar.error(intl.formatMessage(messages.deleteAccountFail)));
|
||||
}).then(() => {
|
||||
this.setState({ isLoading: false });
|
||||
});
|
||||
|
|
|
@ -10,7 +10,7 @@ import ColumnSubheading from '../ui/components/column_subheading';
|
|||
import LoadingIndicator from 'soapbox/components/loading_indicator';
|
||||
import Button from 'soapbox/components/button';
|
||||
import { changeSetting, getSettings } from 'soapbox/actions/settings';
|
||||
import { showAlert } from 'soapbox/actions/alerts';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import {
|
||||
SimpleForm,
|
||||
SimpleInput,
|
||||
|
@ -129,7 +129,7 @@ class DisableOtpForm extends ImmutablePureComponent {
|
|||
this.context.router.history.push('../auth/edit');
|
||||
dispatch(changeSetting(['otpEnabled'], false));
|
||||
}).catch(error => {
|
||||
dispatch(showAlert('', intl.formatMessage(messages.disableFail)));
|
||||
dispatch(snackbar.error(intl.formatMessage(messages.disableFail)));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -180,7 +180,7 @@ class EnableOtpForm extends ImmutablePureComponent {
|
|||
dispatch(fetchBackupCodes()).then(response => {
|
||||
this.setState({ backupCodes: response.data.codes });
|
||||
}).catch(error => {
|
||||
dispatch(showAlert('', intl.formatMessage(messages.codesFail)));
|
||||
dispatch(snackbar.error(intl.formatMessage(messages.codesFail)));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -261,7 +261,7 @@ class OtpConfirmForm extends ImmutablePureComponent {
|
|||
dispatch(fetchToptSetup()).then(response => {
|
||||
this.setState({ qrCodeURI: response.data.provisioning_uri, confirm_key: response.data.key });
|
||||
}).catch(error => {
|
||||
dispatch(showAlert('', intl.formatMessage(messages.qrFail)));
|
||||
dispatch(snackbar.error(intl.formatMessage(messages.qrFail)));
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -276,7 +276,7 @@ class OtpConfirmForm extends ImmutablePureComponent {
|
|||
dispatch(confirmToptSetup(code, password)).then(response => {
|
||||
dispatch(changeSetting(['otpEnabled'], true));
|
||||
}).catch(error => {
|
||||
dispatch(showAlert('', intl.formatMessage(messages.confirmFail)));
|
||||
dispatch(snackbar.error(intl.formatMessage(messages.confirmFail)));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -4,6 +4,14 @@ import { NotificationStack } from 'react-notification';
|
|||
import { dismissAlert } from '../../../actions/alerts';
|
||||
import { getAlerts } from '../../../selectors';
|
||||
|
||||
const defaultBarStyleFactory = (index, style, notification) => {
|
||||
return Object.assign(
|
||||
{},
|
||||
style,
|
||||
{ bottom: `${14 + index * 12 + index * 42}px` }
|
||||
);
|
||||
};
|
||||
|
||||
const mapStateToProps = (state, { intl }) => {
|
||||
const notifications = getAlerts(state);
|
||||
|
||||
|
@ -23,6 +31,8 @@ const mapDispatchToProps = (dispatch) => {
|
|||
onDismiss: alert => {
|
||||
dispatch(dismissAlert(alert));
|
||||
},
|
||||
barStyleFactory: defaultBarStyleFactory,
|
||||
activeBarStyleFactory: defaultBarStyleFactory,
|
||||
};
|
||||
};
|
||||
|
||||
|
|
|
@ -14,6 +14,7 @@ export default function alerts(state = initialState, action) {
|
|||
key: state.size > 0 ? state.last().get('key') + 1 : 0,
|
||||
title: action.title,
|
||||
message: action.message,
|
||||
severity: action.severity || 'info',
|
||||
}));
|
||||
case ALERT_DISMISS:
|
||||
return state.filterNot(item => item.get('key') === action.alert.key);
|
||||
|
|
|
@ -124,10 +124,9 @@ export const getAlerts = createSelector([getAlertsBase], (base) => {
|
|||
message: item.get('message'),
|
||||
title: item.get('title'),
|
||||
key: item.get('key'),
|
||||
className: `snackbar snackbar--${item.get('severity', 'info')}`,
|
||||
activeClassName: 'snackbar--active',
|
||||
dismissAfter: 5000,
|
||||
barStyle: {
|
||||
zIndex: 200,
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -70,6 +70,7 @@
|
|||
@import 'components/profile_hover_card';
|
||||
@import 'components/filters';
|
||||
@import 'components/mfa_form';
|
||||
@import 'components/snackbar';
|
||||
|
||||
// Holiday
|
||||
@import 'holiday/halloween';
|
||||
|
|
42
app/styles/components/snackbar.scss
Normal file
42
app/styles/components/snackbar.scss
Normal file
|
@ -0,0 +1,42 @@
|
|||
.snackbar {
|
||||
font-size: 16px !important;
|
||||
padding: 10px 20px 10px 14px !important;
|
||||
z-index: 9999 !important;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&::before {
|
||||
font-family: ForkAwesome;
|
||||
font-size: 20px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
|
||||
&--info {
|
||||
background-color: #19759e !important;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
|
||||
&--success {
|
||||
background-color: #199e5a !important;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
|
||||
&--error {
|
||||
background-color: #9e1919 !important;
|
||||
|
||||
&::before {
|
||||
content: '';
|
||||
}
|
||||
}
|
||||
|
||||
.notification-bar-wrapper {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue