kopia lustrzana https://gitlab.com/soapbox-pub/soapbox
Merge branch 'improve-toasts' into 'develop'
Introduce Toast module as Snackbar replacement See merge request soapbox-pub/soapbox!2023environments/review-develop-3zknud/deployments/1816
commit
a9e05859d1
|
@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Admin: custom badges. Admins can add non-federating badges to any user's profile (on Rebased, Pleroma).
|
||||
- Admin: consolidated user dropdown actions (verify/suggest/etc) into a unified "Moderate User" modal.
|
||||
- i18n: updated translations for Italian, Polish, Arabic, Hebrew, and German.
|
||||
- Toast: added the ability to dismiss toast notifications
|
||||
|
||||
### Changed
|
||||
- UI: the whole UI has been overhauled both inside and out. 97% of the codebase has been rewritten to TypeScript, and a new component library has been introduced with Tailwind CSS.
|
||||
|
@ -43,6 +44,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Performance: improve scrolling/navigation between feeds by using a virtual window library.
|
||||
- Admin: reorganize UI into 3-column layout.
|
||||
- Admin: include external link to frontend repo for the running commit.
|
||||
- Toast: redesigned toast notifications
|
||||
|
||||
### Removed
|
||||
- Theme: Halloween theme.
|
||||
|
|
|
@ -0,0 +1,166 @@
|
|||
import { render } from '@testing-library/react';
|
||||
import { AxiosError } from 'axios';
|
||||
import React from 'react';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
|
||||
import { act, screen } from 'soapbox/jest/test-helpers';
|
||||
|
||||
function renderApp() {
|
||||
const { Toaster } = require('react-hot-toast');
|
||||
const toast = require('../toast').default;
|
||||
|
||||
return {
|
||||
toast,
|
||||
...render(
|
||||
<IntlProvider locale='en'>
|
||||
<Toaster />,
|
||||
</IntlProvider>,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
||||
beforeAll(() => {
|
||||
jest.spyOn(console, 'error').mockImplementation(() => {});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
(console.error as any).mockClear();
|
||||
});
|
||||
|
||||
afterAll(() => {
|
||||
(console.error as any).mockRestore();
|
||||
});
|
||||
|
||||
describe('toasts', () =>{
|
||||
it('renders successfully', async() => {
|
||||
const { toast } = renderApp();
|
||||
|
||||
act(() => {
|
||||
toast.success('hello');
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('toast')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('toast-message')).toHaveTextContent('hello');
|
||||
});
|
||||
|
||||
describe('actionable button', () => {
|
||||
it('renders the button', async() => {
|
||||
const { toast } = renderApp();
|
||||
|
||||
act(() => {
|
||||
toast.success('hello', { action: () => null, actionLabel: 'click me' });
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('toast-action')).toHaveTextContent('click me');
|
||||
});
|
||||
|
||||
it('does not render the button', async() => {
|
||||
const { toast } = renderApp();
|
||||
|
||||
act(() => {
|
||||
toast.success('hello');
|
||||
});
|
||||
|
||||
expect(screen.queryAllByTestId('toast-action')).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('showAlertForError()', () => {
|
||||
const buildError = (message: string, status: number) => new AxiosError<any>(message, String(status), undefined, null, {
|
||||
data: {
|
||||
error: message,
|
||||
},
|
||||
statusText: String(status),
|
||||
status,
|
||||
headers: {},
|
||||
config: {},
|
||||
});
|
||||
|
||||
describe('with a 502 status code', () => {
|
||||
it('renders the correct message', async() => {
|
||||
const message = 'The server is down';
|
||||
const error = buildError(message, 502);
|
||||
const { toast } = renderApp();
|
||||
|
||||
act(() => {
|
||||
toast.showAlertForError(error);
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('toast')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('toast-message')).toHaveTextContent('The server is down');
|
||||
});
|
||||
});
|
||||
|
||||
describe('with a 404 status code', () => {
|
||||
it('renders the correct message', async() => {
|
||||
const error = buildError('', 404);
|
||||
const { toast } = renderApp();
|
||||
|
||||
act(() => {
|
||||
toast.showAlertForError(error);
|
||||
});
|
||||
|
||||
expect(screen.queryAllByTestId('toast')).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with a 410 status code', () => {
|
||||
it('renders the correct message', async() => {
|
||||
const error = buildError('', 410);
|
||||
const { toast } = renderApp();
|
||||
|
||||
act(() => {
|
||||
toast.showAlertForError(error);
|
||||
});
|
||||
|
||||
expect(screen.queryAllByTestId('toast')).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with an accepted status code', () => {
|
||||
describe('with a message from the server', () => {
|
||||
it('renders the correct message', async() => {
|
||||
const message = 'custom message';
|
||||
const error = buildError(message, 200);
|
||||
const { toast } = renderApp();
|
||||
|
||||
act(() => {
|
||||
toast.showAlertForError(error);
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('toast')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('toast-message')).toHaveTextContent(message);
|
||||
});
|
||||
});
|
||||
|
||||
describe('without a message from the server', () => {
|
||||
it('renders the correct message', async() => {
|
||||
const message = 'The request has been accepted for processing';
|
||||
const error = buildError(message, 202);
|
||||
const { toast } = renderApp();
|
||||
|
||||
act(() => {
|
||||
toast.showAlertForError(error);
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('toast')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('toast-message')).toHaveTextContent(message);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('without a response', () => {
|
||||
it('renders the default message', async() => {
|
||||
const error = new AxiosError();
|
||||
const { toast } = renderApp();
|
||||
|
||||
act(() => {
|
||||
toast.showAlertForError(error);
|
||||
});
|
||||
|
||||
expect(screen.getByTestId('toast')).toBeInTheDocument();
|
||||
expect(screen.getByTestId('toast-message')).toHaveTextContent('An unexpected error occurred.');
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,146 +0,0 @@
|
|||
import { AxiosError } from 'axios';
|
||||
|
||||
import { mockStore, rootState } from 'soapbox/jest/test-helpers';
|
||||
|
||||
import { dismissAlert, showAlert, showAlertForError } from '../alerts';
|
||||
|
||||
const buildError = (message: string, status: number) => new AxiosError<any>(message, String(status), undefined, null, {
|
||||
data: {
|
||||
error: message,
|
||||
},
|
||||
statusText: String(status),
|
||||
status,
|
||||
headers: {},
|
||||
config: {},
|
||||
});
|
||||
|
||||
let store: ReturnType<typeof mockStore>;
|
||||
|
||||
beforeEach(() => {
|
||||
const state = rootState;
|
||||
store = mockStore(state);
|
||||
});
|
||||
|
||||
describe('dismissAlert()', () => {
|
||||
it('dispatches the proper actions', async() => {
|
||||
const alert = 'hello world';
|
||||
const expectedActions = [
|
||||
{ type: 'ALERT_DISMISS', alert },
|
||||
];
|
||||
await store.dispatch(dismissAlert(alert as any));
|
||||
const actions = store.getActions();
|
||||
|
||||
expect(actions).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
describe('showAlert()', () => {
|
||||
it('dispatches the proper actions', async() => {
|
||||
const title = 'title';
|
||||
const message = 'msg';
|
||||
const severity = 'info';
|
||||
const expectedActions = [
|
||||
{ type: 'ALERT_SHOW', title, message, severity },
|
||||
];
|
||||
await store.dispatch(showAlert(title, message, severity));
|
||||
const actions = store.getActions();
|
||||
|
||||
expect(actions).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
describe('showAlert()', () => {
|
||||
describe('with a 502 status code', () => {
|
||||
it('dispatches the proper actions', async() => {
|
||||
const message = 'The server is down';
|
||||
const error = buildError(message, 502);
|
||||
|
||||
const expectedActions = [
|
||||
{ type: 'ALERT_SHOW', title: '', message, severity: 'error' },
|
||||
];
|
||||
await store.dispatch(showAlertForError(error));
|
||||
const actions = store.getActions();
|
||||
|
||||
expect(actions).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with a 404 status code', () => {
|
||||
it('dispatches the proper actions', async() => {
|
||||
const error = buildError('', 404);
|
||||
|
||||
await store.dispatch(showAlertForError(error));
|
||||
const actions = store.getActions();
|
||||
|
||||
expect(actions).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with a 410 status code', () => {
|
||||
it('dispatches the proper actions', async() => {
|
||||
const error = buildError('', 410);
|
||||
|
||||
await store.dispatch(showAlertForError(error));
|
||||
const actions = store.getActions();
|
||||
|
||||
expect(actions).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with an accepted status code', () => {
|
||||
describe('with a message from the server', () => {
|
||||
it('dispatches the proper actions', async() => {
|
||||
const message = 'custom message';
|
||||
const error = buildError(message, 200);
|
||||
|
||||
const expectedActions = [
|
||||
{ type: 'ALERT_SHOW', title: '', message, severity: 'error' },
|
||||
];
|
||||
await store.dispatch(showAlertForError(error));
|
||||
const actions = store.getActions();
|
||||
|
||||
expect(actions).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
|
||||
describe('without a message from the server', () => {
|
||||
it('dispatches the proper actions', async() => {
|
||||
const message = 'The request has been accepted for processing';
|
||||
const error = buildError(message, 202);
|
||||
|
||||
const expectedActions = [
|
||||
{ type: 'ALERT_SHOW', title: '', message, severity: 'error' },
|
||||
];
|
||||
await store.dispatch(showAlertForError(error));
|
||||
const actions = store.getActions();
|
||||
|
||||
expect(actions).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('without a response', () => {
|
||||
it('dispatches the proper actions', async() => {
|
||||
const error = new AxiosError();
|
||||
|
||||
const expectedActions = [
|
||||
{
|
||||
type: 'ALERT_SHOW',
|
||||
title: {
|
||||
defaultMessage: 'Oops!',
|
||||
id: 'alert.unexpected.title',
|
||||
},
|
||||
message: {
|
||||
defaultMessage: 'An unexpected error occurred.',
|
||||
id: 'alert.unexpected.message',
|
||||
},
|
||||
severity: 'error',
|
||||
},
|
||||
];
|
||||
await store.dispatch(showAlertForError(error));
|
||||
const actions = store.getActions();
|
||||
|
||||
expect(actions).toEqual(expectedActions);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -46,13 +46,6 @@ describe('uploadCompose()', () => {
|
|||
|
||||
const expectedActions = [
|
||||
{ type: 'COMPOSE_UPLOAD_REQUEST', id: 'home', skipLoading: true },
|
||||
{
|
||||
type: 'ALERT_SHOW',
|
||||
message: 'Image exceeds the current file size limit (10 Bytes)',
|
||||
actionLabel: undefined,
|
||||
actionLink: undefined,
|
||||
severity: 'error',
|
||||
},
|
||||
{ type: 'COMPOSE_UPLOAD_FAIL', id: 'home', error: true, skipLoading: true },
|
||||
];
|
||||
|
||||
|
@ -99,13 +92,6 @@ describe('uploadCompose()', () => {
|
|||
|
||||
const expectedActions = [
|
||||
{ type: 'COMPOSE_UPLOAD_REQUEST', id: 'home', skipLoading: true },
|
||||
{
|
||||
type: 'ALERT_SHOW',
|
||||
message: 'Video exceeds the current file size limit (10 Bytes)',
|
||||
actionLabel: undefined,
|
||||
actionLink: undefined,
|
||||
severity: 'error',
|
||||
},
|
||||
{ type: 'COMPOSE_UPLOAD_FAIL', id: 'home', error: true, skipLoading: true },
|
||||
];
|
||||
|
||||
|
|
|
@ -1,75 +0,0 @@
|
|||
import { defineMessages, MessageDescriptor } from 'react-intl';
|
||||
|
||||
import { httpErrorMessages } from 'soapbox/utils/errors';
|
||||
|
||||
import type { SnackbarActionSeverity } from './snackbar';
|
||||
import type { AnyAction } from '@reduxjs/toolkit';
|
||||
import type { AxiosError } from 'axios';
|
||||
import type { NotificationObject } from 'react-notification';
|
||||
|
||||
const messages = defineMessages({
|
||||
unexpectedTitle: { id: 'alert.unexpected.title', defaultMessage: 'Oops!' },
|
||||
unexpectedMessage: { id: 'alert.unexpected.message', defaultMessage: 'An unexpected error occurred.' },
|
||||
});
|
||||
|
||||
export const ALERT_SHOW = 'ALERT_SHOW';
|
||||
export const ALERT_DISMISS = 'ALERT_DISMISS';
|
||||
export const ALERT_CLEAR = 'ALERT_CLEAR';
|
||||
|
||||
const noOp = () => { };
|
||||
|
||||
function dismissAlert(alert: NotificationObject) {
|
||||
return {
|
||||
type: ALERT_DISMISS,
|
||||
alert,
|
||||
};
|
||||
}
|
||||
|
||||
function showAlert(
|
||||
title: MessageDescriptor | string = messages.unexpectedTitle,
|
||||
message: MessageDescriptor | string = messages.unexpectedMessage,
|
||||
severity: SnackbarActionSeverity = 'info',
|
||||
) {
|
||||
return {
|
||||
type: ALERT_SHOW,
|
||||
title,
|
||||
message,
|
||||
severity,
|
||||
};
|
||||
}
|
||||
|
||||
const showAlertForError = (error: AxiosError<any>) => (dispatch: React.Dispatch<AnyAction>, _getState: any) => {
|
||||
if (error?.response) {
|
||||
const { data, status, statusText } = error.response;
|
||||
|
||||
if (status === 502) {
|
||||
return dispatch(showAlert('', 'The server is down', 'error'));
|
||||
}
|
||||
|
||||
if (status === 404 || status === 410) {
|
||||
// Skip these errors as they are reflected in the UI
|
||||
return dispatch(noOp as any);
|
||||
}
|
||||
|
||||
let message: string | undefined = statusText;
|
||||
|
||||
if (data?.error) {
|
||||
message = data.error;
|
||||
}
|
||||
|
||||
if (!message) {
|
||||
message = httpErrorMessages.find((httpError) => httpError.code === status)?.description;
|
||||
}
|
||||
|
||||
return dispatch(showAlert('', message, 'error'));
|
||||
} else {
|
||||
console.error(error);
|
||||
return dispatch(showAlert(undefined, undefined, 'error'));
|
||||
}
|
||||
};
|
||||
|
||||
export {
|
||||
dismissAlert,
|
||||
showAlert,
|
||||
showAlertForError,
|
||||
};
|
|
@ -1,14 +1,13 @@
|
|||
import { defineMessages } from 'react-intl';
|
||||
|
||||
import toast from 'soapbox/toast';
|
||||
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||
import { getFeatures } from 'soapbox/utils/features';
|
||||
|
||||
import api from '../api';
|
||||
|
||||
import { showAlertForError } from './alerts';
|
||||
import { importFetchedAccounts } from './importer';
|
||||
import { patchMeSuccess } from './me';
|
||||
import snackbar from './snackbar';
|
||||
|
||||
import type { AxiosError } from 'axios';
|
||||
import type { AppDispatch, RootState } from 'soapbox/store';
|
||||
|
@ -80,7 +79,7 @@ const fetchAliasesSuggestions = (q: string) =>
|
|||
api(getState).get('/api/v1/accounts/search', { params }).then(({ data }) => {
|
||||
dispatch(importFetchedAccounts(data));
|
||||
dispatch(fetchAliasesSuggestionsReady(q, data));
|
||||
}).catch(error => dispatch(showAlertForError(error)));
|
||||
}).catch(error => toast.showAlertForError(error));
|
||||
};
|
||||
|
||||
const fetchAliasesSuggestionsReady = (query: string, accounts: APIEntity[]) => ({
|
||||
|
@ -114,7 +113,7 @@ const addToAliases = (account: Account) =>
|
|||
|
||||
api(getState).patch('/api/v1/accounts/update_credentials', { also_known_as: [...alsoKnownAs, account.pleroma.get('ap_id')] })
|
||||
.then((response => {
|
||||
dispatch(snackbar.success(messages.createSuccess));
|
||||
toast.success(messages.createSuccess);
|
||||
dispatch(addToAliasesSuccess);
|
||||
dispatch(patchMeSuccess(response.data));
|
||||
}))
|
||||
|
@ -129,7 +128,7 @@ const addToAliases = (account: Account) =>
|
|||
alias: account.acct,
|
||||
})
|
||||
.then(() => {
|
||||
dispatch(snackbar.success(messages.createSuccess));
|
||||
toast.success(messages.createSuccess);
|
||||
dispatch(addToAliasesSuccess);
|
||||
dispatch(fetchAliases);
|
||||
})
|
||||
|
@ -165,7 +164,7 @@ const removeFromAliases = (account: string) =>
|
|||
|
||||
api(getState).patch('/api/v1/accounts/update_credentials', { also_known_as: alsoKnownAs.filter((id: string) => id !== account) })
|
||||
.then(response => {
|
||||
dispatch(snackbar.success(messages.removeSuccess));
|
||||
toast.success(messages.removeSuccess);
|
||||
dispatch(removeFromAliasesSuccess);
|
||||
dispatch(patchMeSuccess(response.data));
|
||||
})
|
||||
|
@ -182,7 +181,7 @@ const removeFromAliases = (account: string) =>
|
|||
},
|
||||
})
|
||||
.then(response => {
|
||||
dispatch(snackbar.success(messages.removeSuccess));
|
||||
toast.success(messages.removeSuccess);
|
||||
dispatch(removeFromAliasesSuccess);
|
||||
dispatch(fetchAliases);
|
||||
})
|
||||
|
|
|
@ -14,10 +14,10 @@ import { createApp } from 'soapbox/actions/apps';
|
|||
import { fetchMeSuccess, fetchMeFail } from 'soapbox/actions/me';
|
||||
import { obtainOAuthToken, revokeOAuthToken } from 'soapbox/actions/oauth';
|
||||
import { startOnboarding } from 'soapbox/actions/onboarding';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import { custom } from 'soapbox/custom';
|
||||
import { queryClient } from 'soapbox/queries/client';
|
||||
import KVStore from 'soapbox/storage/kv-store';
|
||||
import toast from 'soapbox/toast';
|
||||
import { getLoggedInAccount, parseBaseURL } from 'soapbox/utils/auth';
|
||||
import sourceCode from 'soapbox/utils/code';
|
||||
import { getFeatures } from 'soapbox/utils/features';
|
||||
|
@ -216,7 +216,7 @@ export const logIn = (username: string, password: string) =>
|
|||
throw error;
|
||||
} else {
|
||||
// Return "wrong password" message.
|
||||
dispatch(snackbar.error(messages.invalidCredentials));
|
||||
toast.error(messages.invalidCredentials);
|
||||
}
|
||||
throw error;
|
||||
});
|
||||
|
@ -246,7 +246,7 @@ export const logOut = () =>
|
|||
|
||||
dispatch({ type: AUTH_LOGGED_OUT, account, standalone });
|
||||
|
||||
return dispatch(snackbar.success(messages.loggedOut));
|
||||
toast.success(messages.loggedOut);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -3,16 +3,15 @@ import { List as ImmutableList } from 'immutable';
|
|||
import throttle from 'lodash/throttle';
|
||||
import { defineMessages, IntlShape } from 'react-intl';
|
||||
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import api from 'soapbox/api';
|
||||
import { search as emojiSearch } from 'soapbox/features/emoji/emoji-mart-search-light';
|
||||
import { tagHistory } from 'soapbox/settings';
|
||||
import toast from 'soapbox/toast';
|
||||
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||
import { getFeatures, parseVersion } from 'soapbox/utils/features';
|
||||
import { formatBytes, getVideoDuration } from 'soapbox/utils/media';
|
||||
import resizeImage from 'soapbox/utils/resize-image';
|
||||
|
||||
import { showAlert, showAlertForError } from './alerts';
|
||||
import { useEmoji } from './emojis';
|
||||
import { importFetchedAccounts } from './importer';
|
||||
import { uploadMedia, fetchMedia, updateMedia } from './media';
|
||||
|
@ -93,7 +92,7 @@ const messages = defineMessages({
|
|||
editSuccess: { id: 'compose.edit_success', defaultMessage: 'Your post was edited' },
|
||||
uploadErrorLimit: { id: 'upload_error.limit', defaultMessage: 'File upload limit exceeded.' },
|
||||
uploadErrorPoll: { id: 'upload_error.poll', defaultMessage: 'File upload not allowed with polls.' },
|
||||
view: { id: 'snackbar.view', defaultMessage: 'View' },
|
||||
view: { id: 'toast.view', defaultMessage: 'View' },
|
||||
replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
|
||||
replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
|
||||
});
|
||||
|
@ -211,7 +210,10 @@ const handleComposeSubmit = (dispatch: AppDispatch, getState: () => RootState, c
|
|||
|
||||
dispatch(insertIntoTagHistory(composeId, data.tags || [], status));
|
||||
dispatch(submitComposeSuccess(composeId, { ...data }));
|
||||
dispatch(snackbar.success(edit ? messages.editSuccess : messages.success, messages.view, `/@${data.account.acct}/posts/${data.id}`));
|
||||
toast.success(edit ? messages.editSuccess : messages.success, {
|
||||
actionLabel: messages.view,
|
||||
actionLink: `/@${data.account.acct}/posts/${data.id}`,
|
||||
});
|
||||
};
|
||||
|
||||
const needsDescriptions = (state: RootState, composeId: string) => {
|
||||
|
@ -245,7 +247,7 @@ const submitCompose = (composeId: string, routerHistory?: History, force = false
|
|||
let to = compose.to;
|
||||
|
||||
if (!validateSchedule(state, composeId)) {
|
||||
dispatch(snackbar.error(messages.scheduleError));
|
||||
toast.error(messages.scheduleError);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -330,7 +332,7 @@ const uploadCompose = (composeId: string, files: FileList, intl: IntlShape) =>
|
|||
const mediaCount = media ? media.size : 0;
|
||||
|
||||
if (files.length + mediaCount > attachmentLimit) {
|
||||
dispatch(showAlert(undefined, messages.uploadErrorLimit, 'error'));
|
||||
toast.error(messages.uploadErrorLimit);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -346,18 +348,18 @@ const uploadCompose = (composeId: string, files: FileList, intl: IntlShape) =>
|
|||
if (isImage && maxImageSize && (f.size > maxImageSize)) {
|
||||
const limit = formatBytes(maxImageSize);
|
||||
const message = intl.formatMessage(messages.exceededImageSizeLimit, { limit });
|
||||
dispatch(snackbar.error(message));
|
||||
toast.error(message);
|
||||
dispatch(uploadComposeFail(composeId, true));
|
||||
return;
|
||||
} else if (isVideo && maxVideoSize && (f.size > maxVideoSize)) {
|
||||
const limit = formatBytes(maxVideoSize);
|
||||
const message = intl.formatMessage(messages.exceededVideoSizeLimit, { limit });
|
||||
dispatch(snackbar.error(message));
|
||||
toast.error(message);
|
||||
dispatch(uploadComposeFail(composeId, true));
|
||||
return;
|
||||
} else if (isVideo && maxVideoDuration && (videoDurationInSeconds > maxVideoDuration)) {
|
||||
const message = intl.formatMessage(messages.exceededVideoDurationLimit, { limit: maxVideoDuration });
|
||||
dispatch(snackbar.error(message));
|
||||
toast.error(message);
|
||||
dispatch(uploadComposeFail(composeId, true));
|
||||
return;
|
||||
}
|
||||
|
@ -496,7 +498,7 @@ const fetchComposeSuggestionsAccounts = throttle((dispatch, getState, composeId,
|
|||
dispatch(readyComposeSuggestionsAccounts(composeId, token, response.data));
|
||||
}).catch(error => {
|
||||
if (!isCancel(error)) {
|
||||
dispatch(showAlertForError(error));
|
||||
toast.showAlertForError(error);
|
||||
}
|
||||
});
|
||||
}, 200, { leading: true, trailing: true });
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import { defineMessages, IntlShape } from 'react-intl';
|
||||
|
||||
import api, { getLinks } from 'soapbox/api';
|
||||
import toast from 'soapbox/toast';
|
||||
import { formatBytes } from 'soapbox/utils/media';
|
||||
import resizeImage from 'soapbox/utils/resize-image';
|
||||
|
||||
import { importFetchedAccounts, importFetchedStatus, importFetchedStatuses } from './importer';
|
||||
import { fetchMedia, uploadMedia } from './media';
|
||||
import { closeModal, openModal } from './modals';
|
||||
import snackbar from './snackbar';
|
||||
import {
|
||||
STATUS_FETCH_SOURCE_FAIL,
|
||||
STATUS_FETCH_SOURCE_REQUEST,
|
||||
|
@ -91,7 +91,7 @@ const messages = defineMessages({
|
|||
editSuccess: { id: 'compose_event.edit_success', defaultMessage: 'Your event was edited' },
|
||||
joinSuccess: { id: 'join_event.success', defaultMessage: 'Joined the event' },
|
||||
joinRequestSuccess: { id: 'join_event.request_success', defaultMessage: 'Requested to join the event' },
|
||||
view: { id: 'snackbar.view', defaultMessage: 'View' },
|
||||
view: { id: 'toast.view', defaultMessage: 'View' },
|
||||
authorized: { id: 'compose_event.participation_requests.authorize_success', defaultMessage: 'User accepted' },
|
||||
rejected: { id: 'compose_event.participation_requests.reject_success', defaultMessage: 'User rejected' },
|
||||
});
|
||||
|
@ -163,7 +163,7 @@ const uploadEventBanner = (file: File, intl: IntlShape) =>
|
|||
if (maxImageSize && (file.size > maxImageSize)) {
|
||||
const limit = formatBytes(maxImageSize);
|
||||
const message = intl.formatMessage(messages.exceededImageSizeLimit, { limit });
|
||||
dispatch(snackbar.error(message));
|
||||
toast.error(message);
|
||||
dispatch(uploadEventBannerFail(true));
|
||||
return;
|
||||
}
|
||||
|
@ -264,7 +264,13 @@ const submitEvent = () =>
|
|||
dispatch(closeModal('COMPOSE_EVENT'));
|
||||
dispatch(importFetchedStatus(data));
|
||||
dispatch(submitEventSuccess(data));
|
||||
dispatch(snackbar.success(id ? messages.editSuccess : messages.success, messages.view, `/@${data.account.acct}/events/${data.id}`));
|
||||
toast.success(
|
||||
id ? messages.editSuccess : messages.success,
|
||||
{
|
||||
actionLabel: messages.view,
|
||||
actionLink: `/@${data.account.acct}/events/${data.id}`,
|
||||
},
|
||||
);
|
||||
}).catch(function(error) {
|
||||
dispatch(submitEventFail(error));
|
||||
});
|
||||
|
@ -299,11 +305,13 @@ const joinEvent = (id: string, participationMessage?: string) =>
|
|||
}).then(({ data }) => {
|
||||
dispatch(importFetchedStatus(data));
|
||||
dispatch(joinEventSuccess(data));
|
||||
dispatch(snackbar.success(
|
||||
toast.success(
|
||||
data.pleroma.event?.join_state === 'pending' ? messages.joinRequestSuccess : messages.joinSuccess,
|
||||
messages.view,
|
||||
`/@${data.account.acct}/events/${data.id}`,
|
||||
));
|
||||
{
|
||||
actionLabel: messages.view,
|
||||
actionLink: `/@${data.account.acct}/events/${data.id}`,
|
||||
},
|
||||
);
|
||||
}).catch(function(error) {
|
||||
dispatch(joinEventFail(error, status, status?.event?.join_state || null));
|
||||
});
|
||||
|
@ -504,7 +512,7 @@ const authorizeEventParticipationRequest = (id: string, accountId: string) =>
|
|||
.post(`/api/v1/pleroma/events/${id}/participation_requests/${accountId}/authorize`)
|
||||
.then(() => {
|
||||
dispatch(authorizeEventParticipationRequestSuccess(id, accountId));
|
||||
dispatch(snackbar.success(messages.authorized));
|
||||
toast.success(messages.authorized);
|
||||
})
|
||||
.catch(error => dispatch(authorizeEventParticipationRequestFail(id, accountId, error)));
|
||||
};
|
||||
|
@ -536,7 +544,7 @@ const rejectEventParticipationRequest = (id: string, accountId: string) =>
|
|||
.post(`/api/v1/pleroma/events/${id}/participation_requests/${accountId}/reject`)
|
||||
.then(() => {
|
||||
dispatch(rejectEventParticipationRequestSuccess(id, accountId));
|
||||
dispatch(snackbar.success(messages.rejected));
|
||||
toast.success(messages.rejected);
|
||||
})
|
||||
.catch(error => dispatch(rejectEventParticipationRequestFail(id, accountId, error)));
|
||||
};
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import { defineMessages } from 'react-intl';
|
||||
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import api, { getLinks } from 'soapbox/api';
|
||||
import { normalizeAccount } from 'soapbox/normalizers';
|
||||
import toast from 'soapbox/toast';
|
||||
|
||||
import type { SnackbarAction } from './snackbar';
|
||||
import type { AxiosResponse } from 'axios';
|
||||
import type { RootState } from 'soapbox/store';
|
||||
|
||||
|
@ -37,7 +36,7 @@ type ExportDataActions = {
|
|||
| typeof EXPORT_MUTES_SUCCESS
|
||||
| typeof EXPORT_MUTES_FAIL,
|
||||
error?: any,
|
||||
} | SnackbarAction
|
||||
}
|
||||
|
||||
function fileExport(content: string, fileName: string) {
|
||||
const fileToDownload = document.createElement('a');
|
||||
|
@ -75,7 +74,7 @@ export const exportFollows = () => (dispatch: React.Dispatch<ExportDataActions>,
|
|||
followings.unshift('Account address,Show boosts');
|
||||
fileExport(followings.join('\n'), 'export_followings.csv');
|
||||
|
||||
dispatch(snackbar.success(messages.followersSuccess));
|
||||
toast.success(messages.followersSuccess);
|
||||
dispatch({ type: EXPORT_FOLLOWS_SUCCESS });
|
||||
}).catch(error => {
|
||||
dispatch({ type: EXPORT_FOLLOWS_FAIL, error });
|
||||
|
@ -90,7 +89,7 @@ export const exportBlocks = () => (dispatch: React.Dispatch<ExportDataActions>,
|
|||
.then((blocks) => {
|
||||
fileExport(blocks.join('\n'), 'export_block.csv');
|
||||
|
||||
dispatch(snackbar.success(messages.blocksSuccess));
|
||||
toast.success(messages.blocksSuccess);
|
||||
dispatch({ type: EXPORT_BLOCKS_SUCCESS });
|
||||
}).catch(error => {
|
||||
dispatch({ type: EXPORT_BLOCKS_FAIL, error });
|
||||
|
@ -105,7 +104,7 @@ export const exportMutes = () => (dispatch: React.Dispatch<ExportDataActions>, g
|
|||
.then((mutes) => {
|
||||
fileExport(mutes.join('\n'), 'export_mutes.csv');
|
||||
|
||||
dispatch(snackbar.success(messages.mutesSuccess));
|
||||
toast.success(messages.mutesSuccess);
|
||||
dispatch({ type: EXPORT_MUTES_SUCCESS });
|
||||
}).catch(error => {
|
||||
dispatch({ type: EXPORT_MUTES_FAIL, error });
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { defineMessages } from 'react-intl';
|
||||
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import toast from 'soapbox/toast';
|
||||
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||
import { getFeatures } from 'soapbox/utils/features';
|
||||
|
||||
|
@ -66,7 +66,7 @@ const createFilter = (phrase: string, expires_at: string, context: Array<string>
|
|||
expires_at,
|
||||
}).then(response => {
|
||||
dispatch({ type: FILTERS_CREATE_SUCCESS, filter: response.data });
|
||||
dispatch(snackbar.success(messages.added));
|
||||
toast.success(messages.added);
|
||||
}).catch(error => {
|
||||
dispatch({ type: FILTERS_CREATE_FAIL, error });
|
||||
});
|
||||
|
@ -77,7 +77,7 @@ const deleteFilter = (id: string) =>
|
|||
dispatch({ type: FILTERS_DELETE_REQUEST });
|
||||
return api(getState).delete(`/api/v1/filters/${id}`).then(response => {
|
||||
dispatch({ type: FILTERS_DELETE_SUCCESS, filter: response.data });
|
||||
dispatch(snackbar.success(messages.removed));
|
||||
toast.success(messages.removed);
|
||||
}).catch(error => {
|
||||
dispatch({ type: FILTERS_DELETE_FAIL, error });
|
||||
});
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
import { defineMessages } from 'react-intl';
|
||||
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import toast from 'soapbox/toast';
|
||||
|
||||
import api from '../api';
|
||||
|
||||
import type { SnackbarAction } from './snackbar';
|
||||
import type { RootState } from 'soapbox/store';
|
||||
|
||||
export const IMPORT_FOLLOWS_REQUEST = 'IMPORT_FOLLOWS_REQUEST';
|
||||
|
@ -31,7 +30,7 @@ type ImportDataActions = {
|
|||
| typeof IMPORT_MUTES_FAIL,
|
||||
error?: any,
|
||||
config?: string
|
||||
} | SnackbarAction
|
||||
}
|
||||
|
||||
const messages = defineMessages({
|
||||
blocksSuccess: { id: 'import_data.success.blocks', defaultMessage: 'Blocks imported successfully' },
|
||||
|
@ -45,7 +44,7 @@ export const importFollows = (params: FormData) =>
|
|||
return api(getState)
|
||||
.post('/api/pleroma/follow_import', params)
|
||||
.then(response => {
|
||||
dispatch(snackbar.success(messages.followersSuccess));
|
||||
toast.success(messages.followersSuccess);
|
||||
dispatch({ type: IMPORT_FOLLOWS_SUCCESS, config: response.data });
|
||||
}).catch(error => {
|
||||
dispatch({ type: IMPORT_FOLLOWS_FAIL, error });
|
||||
|
@ -58,7 +57,7 @@ export const importBlocks = (params: FormData) =>
|
|||
return api(getState)
|
||||
.post('/api/pleroma/blocks_import', params)
|
||||
.then(response => {
|
||||
dispatch(snackbar.success(messages.blocksSuccess));
|
||||
toast.success(messages.blocksSuccess);
|
||||
dispatch({ type: IMPORT_BLOCKS_SUCCESS, config: response.data });
|
||||
}).catch(error => {
|
||||
dispatch({ type: IMPORT_BLOCKS_FAIL, error });
|
||||
|
@ -71,7 +70,7 @@ export const importMutes = (params: FormData) =>
|
|||
return api(getState)
|
||||
.post('/api/pleroma/mutes_import', params)
|
||||
.then(response => {
|
||||
dispatch(snackbar.success(messages.mutesSuccess));
|
||||
toast.success(messages.mutesSuccess);
|
||||
dispatch({ type: IMPORT_MUTES_SUCCESS, config: response.data });
|
||||
}).catch(error => {
|
||||
dispatch({ type: IMPORT_MUTES_FAIL, error });
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { defineMessages } from 'react-intl';
|
||||
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import toast from 'soapbox/toast';
|
||||
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||
|
||||
import api from '../api';
|
||||
|
@ -63,7 +63,7 @@ const REMOTE_INTERACTION_FAIL = 'REMOTE_INTERACTION_FAIL';
|
|||
const messages = defineMessages({
|
||||
bookmarkAdded: { id: 'status.bookmarked', defaultMessage: 'Bookmark added.' },
|
||||
bookmarkRemoved: { id: 'status.unbookmarked', defaultMessage: 'Bookmark removed.' },
|
||||
view: { id: 'snackbar.view', defaultMessage: 'View' },
|
||||
view: { id: 'toast.view', defaultMessage: 'View' },
|
||||
});
|
||||
|
||||
const reblog = (status: StatusEntity) =>
|
||||
|
@ -222,7 +222,10 @@ const bookmark = (status: StatusEntity) =>
|
|||
api(getState).post(`/api/v1/statuses/${status.get('id')}/bookmark`).then(function(response) {
|
||||
dispatch(importFetchedStatus(response.data));
|
||||
dispatch(bookmarkSuccess(status, response.data));
|
||||
dispatch(snackbar.success(messages.bookmarkAdded, messages.view, '/bookmarks'));
|
||||
toast.success(messages.bookmarkAdded, {
|
||||
actionLabel: messages.view,
|
||||
actionLink: '/bookmarks',
|
||||
});
|
||||
}).catch(function(error) {
|
||||
dispatch(bookmarkFail(status, error));
|
||||
});
|
||||
|
@ -235,7 +238,7 @@ const unbookmark = (status: StatusEntity) =>
|
|||
api(getState).post(`/api/v1/statuses/${status.get('id')}/unbookmark`).then(response => {
|
||||
dispatch(importFetchedStatus(response.data));
|
||||
dispatch(unbookmarkSuccess(status, response.data));
|
||||
dispatch(snackbar.success(messages.bookmarkRemoved));
|
||||
toast.success(messages.bookmarkRemoved);
|
||||
}).catch(error => {
|
||||
dispatch(unbookmarkFail(status, error));
|
||||
});
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
import toast from 'soapbox/toast';
|
||||
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||
|
||||
import api from '../api';
|
||||
|
||||
import { showAlertForError } from './alerts';
|
||||
import { importFetchedAccounts } from './importer';
|
||||
|
||||
import type { AxiosError } from 'axios';
|
||||
|
@ -265,7 +265,7 @@ const fetchListSuggestions = (q: string) => (dispatch: AppDispatch, getState: ()
|
|||
api(getState).get('/api/v1/accounts/search', { params }).then(({ data }) => {
|
||||
dispatch(importFetchedAccounts(data));
|
||||
dispatch(fetchListSuggestionsReady(q, data));
|
||||
}).catch(error => dispatch(showAlertForError(error)));
|
||||
}).catch(error => toast.showAlertForError(error));
|
||||
};
|
||||
|
||||
const fetchListSuggestionsReady = (query: string, accounts: APIEntity[]) => ({
|
||||
|
|
|
@ -4,10 +4,10 @@ import { defineMessages, IntlShape } from 'react-intl';
|
|||
import { fetchAccountByUsername } from 'soapbox/actions/accounts';
|
||||
import { deactivateUsers, deleteUsers, deleteStatus, toggleStatusSensitivity } from 'soapbox/actions/admin';
|
||||
import { openModal } from 'soapbox/actions/modals';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import OutlineBox from 'soapbox/components/outline-box';
|
||||
import { Stack, Text } from 'soapbox/components/ui';
|
||||
import AccountContainer from 'soapbox/containers/account-container';
|
||||
import toast from 'soapbox/toast';
|
||||
import { isLocal } from 'soapbox/utils/accounts';
|
||||
|
||||
import type { AppDispatch, RootState } from 'soapbox/store';
|
||||
|
@ -65,7 +65,7 @@ const deactivateUserModal = (intl: IntlShape, accountId: string, afterConfirm =
|
|||
onConfirm: () => {
|
||||
dispatch(deactivateUsers([accountId])).then(() => {
|
||||
const message = intl.formatMessage(messages.userDeactivated, { acct });
|
||||
dispatch(snackbar.success(message));
|
||||
toast.success(message);
|
||||
afterConfirm();
|
||||
}).catch(() => {});
|
||||
},
|
||||
|
@ -105,7 +105,7 @@ const deleteUserModal = (intl: IntlShape, accountId: string, afterConfirm = () =
|
|||
dispatch(deleteUsers([accountId])).then(() => {
|
||||
const message = intl.formatMessage(messages.userDeleted, { acct });
|
||||
dispatch(fetchAccountByUsername(acct));
|
||||
dispatch(snackbar.success(message));
|
||||
toast.success(message);
|
||||
afterConfirm();
|
||||
}).catch(() => {});
|
||||
},
|
||||
|
@ -147,7 +147,7 @@ const toggleStatusSensitivityModal = (intl: IntlShape, statusId: string, sensiti
|
|||
onConfirm: () => {
|
||||
dispatch(toggleStatusSensitivity(statusId, sensitive)).then(() => {
|
||||
const message = intl.formatMessage(sensitive === false ? messages.statusMarkedSensitive : messages.statusMarkedNotSensitive, { acct });
|
||||
dispatch(snackbar.success(message));
|
||||
toast.success(message);
|
||||
}).catch(() => {});
|
||||
afterConfirm();
|
||||
},
|
||||
|
@ -168,7 +168,7 @@ const deleteStatusModal = (intl: IntlShape, statusId: string, afterConfirm = ()
|
|||
onConfirm: () => {
|
||||
dispatch(deleteStatus(statusId)).then(() => {
|
||||
const message = intl.formatMessage(messages.statusDeleted, { acct });
|
||||
dispatch(snackbar.success(message));
|
||||
toast.success(message);
|
||||
}).catch(() => {});
|
||||
afterConfirm();
|
||||
},
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
* @see module:soapbox/actions/auth
|
||||
*/
|
||||
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import toast from 'soapbox/toast';
|
||||
import { getLoggedInAccount } from 'soapbox/utils/auth';
|
||||
import { parseVersion, TRUTHSOCIAL } from 'soapbox/utils/features';
|
||||
import { normalizeUsername } from 'soapbox/utils/input';
|
||||
|
@ -152,7 +152,7 @@ const deleteAccount = (password: string) =>
|
|||
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, account });
|
||||
dispatch(snackbar.success(messages.loggedOut));
|
||||
toast.success(messages.loggedOut);
|
||||
}).catch(error => {
|
||||
dispatch({ type: DELETE_ACCOUNT_FAIL, error, skipAlert: true });
|
||||
throw error;
|
||||
|
|
|
@ -4,11 +4,9 @@ import { createSelector } from 'reselect';
|
|||
import { v4 as uuid } from 'uuid';
|
||||
|
||||
import { patchMe } from 'soapbox/actions/me';
|
||||
import toast from 'soapbox/toast';
|
||||
import { isLoggedIn } from 'soapbox/utils/auth';
|
||||
|
||||
import { showAlertForError } from './alerts';
|
||||
import snackbar from './snackbar';
|
||||
|
||||
import type { AppDispatch, RootState } from 'soapbox/store';
|
||||
|
||||
const SETTING_CHANGE = 'SETTING_CHANGE';
|
||||
|
@ -222,10 +220,10 @@ const saveSettingsImmediate = (opts?: SettingOpts) =>
|
|||
dispatch({ type: SETTING_SAVE });
|
||||
|
||||
if (opts?.showAlert) {
|
||||
dispatch(snackbar.success(messages.saveSuccess));
|
||||
toast.success(messages.saveSuccess);
|
||||
}
|
||||
}).catch(error => {
|
||||
dispatch(showAlertForError(error));
|
||||
toast.showAlertForError(error);
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -1,50 +0,0 @@
|
|||
import { ALERT_SHOW } from './alerts';
|
||||
|
||||
import type { MessageDescriptor } from 'react-intl';
|
||||
|
||||
export type SnackbarActionSeverity = 'info' | 'success' | 'error';
|
||||
|
||||
type SnackbarMessage = string | MessageDescriptor;
|
||||
|
||||
export type SnackbarAction = {
|
||||
type: typeof ALERT_SHOW,
|
||||
message: SnackbarMessage,
|
||||
actionLabel?: SnackbarMessage,
|
||||
actionLink?: string,
|
||||
action?: () => void,
|
||||
severity: SnackbarActionSeverity,
|
||||
};
|
||||
|
||||
type SnackbarOpts = {
|
||||
actionLabel?: SnackbarMessage,
|
||||
actionLink?: string,
|
||||
action?: () => void,
|
||||
dismissAfter?: number | false,
|
||||
};
|
||||
|
||||
export const show = (
|
||||
severity: SnackbarActionSeverity,
|
||||
message: SnackbarMessage,
|
||||
opts?: SnackbarOpts,
|
||||
): SnackbarAction => ({
|
||||
type: ALERT_SHOW,
|
||||
message,
|
||||
severity,
|
||||
...opts,
|
||||
});
|
||||
|
||||
export const info = (message: SnackbarMessage, actionLabel?: SnackbarMessage, actionLink?: string) =>
|
||||
show('info', message, { actionLabel, actionLink });
|
||||
|
||||
export const success = (message: SnackbarMessage, actionLabel?: SnackbarMessage, actionLink?: string) =>
|
||||
show('success', message, { actionLabel, actionLink });
|
||||
|
||||
export const error = (message: SnackbarMessage, actionLabel?: SnackbarMessage, actionLink?: string) =>
|
||||
show('error', message, { actionLabel, actionLink });
|
||||
|
||||
export default {
|
||||
info,
|
||||
success,
|
||||
error,
|
||||
show,
|
||||
};
|
|
@ -4,7 +4,6 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
|||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
import { blockAccount } from 'soapbox/actions/accounts';
|
||||
import { showAlertForError } from 'soapbox/actions/alerts';
|
||||
import { launchChat } from 'soapbox/actions/chats';
|
||||
import { directCompose, mentionCompose, quoteCompose, replyCompose } from 'soapbox/actions/compose';
|
||||
import { editEvent } from 'soapbox/actions/events';
|
||||
|
@ -19,6 +18,7 @@ import StatusActionButton from 'soapbox/components/status-action-button';
|
|||
import { HStack } from 'soapbox/components/ui';
|
||||
import DropdownMenuContainer from 'soapbox/containers/dropdown-menu-container';
|
||||
import { useAppDispatch, useAppSelector, useFeatures, useOwnAccount, useSettings, useSoapboxConfig } from 'soapbox/hooks';
|
||||
import toast from 'soapbox/toast';
|
||||
import { isLocal, isRemote } from 'soapbox/utils/accounts';
|
||||
import copy from 'soapbox/utils/copy';
|
||||
import { getReactForStatus, reduceEmoji } from 'soapbox/utils/emoji-reacts';
|
||||
|
@ -254,7 +254,7 @@ const StatusActionBar: React.FC<IStatusActionBar> = ({
|
|||
const handleEmbed = () => {
|
||||
dispatch(openModal('EMBED', {
|
||||
url: status.get('url'),
|
||||
onError: (error: any) => dispatch(showAlertForError(error)),
|
||||
onError: (error: any) => toast.showAlertForError(error),
|
||||
}));
|
||||
};
|
||||
|
||||
|
|
|
@ -2,9 +2,12 @@ import classNames from 'clsx';
|
|||
import React from 'react';
|
||||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import { HStack, Icon, Text } from 'soapbox/components/ui';
|
||||
import DropdownMenu from 'soapbox/containers/dropdown-menu-container';
|
||||
|
||||
import HStack from '../hstack/hstack';
|
||||
import Icon from '../icon/icon';
|
||||
import Text from '../text/text';
|
||||
|
||||
import type { Menu } from 'soapbox/components/dropdown-menu';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
|
|
@ -49,6 +49,7 @@ export { default as Tabs } from './tabs/tabs';
|
|||
export { default as TagInput } from './tag-input/tag-input';
|
||||
export { default as Text } from './text/text';
|
||||
export { default as Textarea } from './textarea/textarea';
|
||||
export { default as Toast } from './toast/toast';
|
||||
export { default as Toggle } from './toggle/toggle';
|
||||
export { default as Tooltip } from './tooltip/tooltip';
|
||||
export { default as Widget } from './widget/widget';
|
||||
|
|
|
@ -0,0 +1,146 @@
|
|||
import classNames from 'clsx';
|
||||
import React from 'react';
|
||||
import toast, { Toast as RHToast } from 'react-hot-toast';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { ToastText, ToastType } from 'soapbox/toast';
|
||||
|
||||
import Icon from '../icon/icon';
|
||||
|
||||
const renderText = (text: ToastText) => {
|
||||
if (typeof text === 'string') {
|
||||
return text;
|
||||
} else {
|
||||
return <FormattedMessage {...text} />;
|
||||
}
|
||||
};
|
||||
|
||||
interface IToast {
|
||||
t: RHToast
|
||||
message: ToastText
|
||||
type: ToastType
|
||||
action?(): void
|
||||
actionLink?: string
|
||||
actionLabel?: ToastText
|
||||
}
|
||||
|
||||
/**
|
||||
* Customizable Toasts for in-app notifications.
|
||||
*/
|
||||
const Toast = (props: IToast) => {
|
||||
const { t, message, type, action, actionLink, actionLabel } = props;
|
||||
|
||||
const dismissToast = () => toast.dismiss(t.id);
|
||||
|
||||
const renderIcon = () => {
|
||||
switch (type) {
|
||||
case 'success':
|
||||
return (
|
||||
<Icon
|
||||
src={require('@tabler/icons/circle-check.svg')}
|
||||
className='w-6 h-6 text-success-500 dark:text-success-400'
|
||||
aria-hidden
|
||||
/>
|
||||
);
|
||||
case 'info':
|
||||
return (
|
||||
<Icon
|
||||
src={require('@tabler/icons/info-circle.svg')}
|
||||
className='w-6 h-6 text-primary-600 dark:text-accent-blue'
|
||||
aria-hidden
|
||||
/>
|
||||
);
|
||||
case 'error':
|
||||
return (
|
||||
<Icon
|
||||
src={require('@tabler/icons/alert-circle.svg')}
|
||||
className='w-6 h-6 text-danger-600'
|
||||
aria-hidden
|
||||
/>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const renderAction = () => {
|
||||
const classNames = 'ml-3 mt-0.5 flex-shrink-0 rounded-full text-sm font-medium text-primary-600 dark:text-accent-blue hover:underline focus:outline-none';
|
||||
|
||||
if (action && actionLabel) {
|
||||
return (
|
||||
<button
|
||||
type='button'
|
||||
className={classNames}
|
||||
onClick={() => {
|
||||
dismissToast();
|
||||
action();
|
||||
}}
|
||||
data-testid='toast-action'
|
||||
>
|
||||
{renderText(actionLabel)}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
if (actionLink && actionLabel) {
|
||||
return (
|
||||
<Link
|
||||
to={actionLink}
|
||||
onClick={dismissToast}
|
||||
className={classNames}
|
||||
data-testid='toast-action-link'
|
||||
>
|
||||
{renderText(actionLabel)}
|
||||
</Link>
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
data-testid='toast'
|
||||
className={
|
||||
classNames({
|
||||
'pointer-events-auto w-full max-w-sm overflow-hidden rounded-lg bg-white dark:bg-gray-900 shadow-lg dark:ring-2 dark:ring-gray-800': true,
|
||||
'animate-enter': t.visible,
|
||||
'animate-leave': !t.visible,
|
||||
})
|
||||
}
|
||||
>
|
||||
<div className='p-4'>
|
||||
<div className='flex items-start'>
|
||||
<div className='flex w-0 flex-1 justify-between items-start'>
|
||||
<div className='w-0 flex-1 flex items-start'>
|
||||
<div className='flex-shrink-0'>
|
||||
{renderIcon()}
|
||||
</div>
|
||||
|
||||
<p className='ml-3 pt-0.5 text-sm text-gray-900 dark:text-gray-100' data-testid='toast-message'>
|
||||
{renderText(message)}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Action */}
|
||||
{renderAction()}
|
||||
</div>
|
||||
|
||||
{/* Dismiss Button */}
|
||||
<div className='ml-4 flex flex-shrink-0 pt-0.5'>
|
||||
<button
|
||||
type='button'
|
||||
className='inline-flex rounded-md text-gray-600 dark:text-gray-600 hover:text-gray-700 dark:hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2'
|
||||
onClick={dismissToast}
|
||||
data-testid='toast-dismiss'
|
||||
>
|
||||
<span className='sr-only'>Close</span>
|
||||
<Icon src={require('@tabler/icons/x.svg')} className='w-5 h-5' />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default Toast;
|
|
@ -3,12 +3,14 @@
|
|||
import { QueryClientProvider } from '@tanstack/react-query';
|
||||
import classNames from 'clsx';
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Toaster } from 'react-hot-toast';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
import { Provider } from 'react-redux';
|
||||
import { BrowserRouter, Switch, Redirect, Route } from 'react-router-dom';
|
||||
// @ts-ignore: it doesn't have types
|
||||
import { ScrollContext } from 'react-router-scroll-4';
|
||||
|
||||
|
||||
import { loadInstance } from 'soapbox/actions/instance';
|
||||
import { fetchMe } from 'soapbox/actions/me';
|
||||
import { loadSoapboxConfig, getSoapboxConfig } from 'soapbox/actions/soapbox';
|
||||
|
@ -24,7 +26,6 @@ import PublicLayout from 'soapbox/features/public-layout';
|
|||
import BundleContainer from 'soapbox/features/ui/containers/bundle-container';
|
||||
import {
|
||||
ModalContainer,
|
||||
NotificationsContainer,
|
||||
OnboardingWizard,
|
||||
WaitlistPage,
|
||||
} from 'soapbox/features/ui/util/async-components';
|
||||
|
@ -185,15 +186,12 @@ const SoapboxMount = () => {
|
|||
<Route>
|
||||
{renderBody()}
|
||||
|
||||
<BundleContainer fetchComponent={NotificationsContainer}>
|
||||
{(Component) => <Component />}
|
||||
</BundleContainer>
|
||||
|
||||
<BundleContainer fetchComponent={ModalContainer}>
|
||||
{Component => <Component />}
|
||||
</BundleContainer>
|
||||
|
||||
<GdprBanner />
|
||||
<Toaster position='top-right' containerClassName='top-10' containerStyle={{ top: 75 }} />
|
||||
</Route>
|
||||
</Switch>
|
||||
</ScrollContext>
|
||||
|
|
|
@ -15,7 +15,6 @@ import { initMuteModal } from 'soapbox/actions/mutes';
|
|||
import { initReport } from 'soapbox/actions/reports';
|
||||
import { setSearchAccount } from 'soapbox/actions/search';
|
||||
import { getSettings } from 'soapbox/actions/settings';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import Badge from 'soapbox/components/badge';
|
||||
import StillImage from 'soapbox/components/still-image';
|
||||
import { Avatar, HStack, IconButton, Menu, MenuButton, MenuDivider, MenuItem, MenuLink, MenuList } from 'soapbox/components/ui';
|
||||
|
@ -27,6 +26,7 @@ import { useAppDispatch, useFeatures, useOwnAccount } from 'soapbox/hooks';
|
|||
import { normalizeAttachment } from 'soapbox/normalizers';
|
||||
import { ChatKeys, useChats } from 'soapbox/queries/chats';
|
||||
import { queryClient } from 'soapbox/queries/client';
|
||||
import toast from 'soapbox/toast';
|
||||
import { Account } from 'soapbox/types/entities';
|
||||
import { isDefaultHeader, isRemote } from 'soapbox/utils/accounts';
|
||||
|
||||
|
@ -92,7 +92,7 @@ const Header: React.FC<IHeader> = ({ account }) => {
|
|||
}, {
|
||||
onError: (error: AxiosError) => {
|
||||
const data = error.response?.data as any;
|
||||
dispatch(snackbar.error(data?.error));
|
||||
toast.error(data?.error);
|
||||
},
|
||||
onSuccess: (response) => {
|
||||
history.push(`/chats/${response.data.id}`);
|
||||
|
@ -158,11 +158,11 @@ const Header: React.FC<IHeader> = ({ account }) => {
|
|||
const onEndorseToggle = () => {
|
||||
if (account.relationship?.endorsed) {
|
||||
dispatch(unpinAccount(account.id))
|
||||
.then(() => dispatch(snackbar.success(intl.formatMessage(messages.userUnendorsed, { acct: account.acct }))))
|
||||
.then(() => toast.success(intl.formatMessage(messages.userUnendorsed, { acct: account.acct })))
|
||||
.catch(() => { });
|
||||
} else {
|
||||
dispatch(pinAccount(account.id))
|
||||
.then(() => dispatch(snackbar.success(intl.formatMessage(messages.userEndorsed, { acct: account.acct }))))
|
||||
.then(() => toast.success(intl.formatMessage(messages.userEndorsed, { acct: account.acct })))
|
||||
.catch(() => { });
|
||||
}
|
||||
};
|
||||
|
|
|
@ -2,9 +2,9 @@ import React from 'react';
|
|||
import { useIntl, defineMessages, FormattedMessage } from 'react-intl';
|
||||
|
||||
import { updateConfig } from 'soapbox/actions/admin';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import { RadioGroup, RadioItem } from 'soapbox/components/radio';
|
||||
import { useAppDispatch, useInstance } from 'soapbox/hooks';
|
||||
import toast from 'soapbox/toast';
|
||||
|
||||
import type { Instance } from 'soapbox/types/entities';
|
||||
|
||||
|
@ -44,7 +44,7 @@ const RegistrationModePicker: React.FC = () => {
|
|||
const onChange: React.ChangeEventHandler<HTMLInputElement> = e => {
|
||||
const config = generateConfig(e.target.value as RegistrationMode);
|
||||
dispatch(updateConfig(config)).then(() => {
|
||||
dispatch(snackbar.success(intl.formatMessage(messages.saved)));
|
||||
toast.success(intl.formatMessage(messages.saved));
|
||||
}).catch(() => {});
|
||||
};
|
||||
|
||||
|
|
|
@ -4,13 +4,13 @@ import { Link } from 'react-router-dom';
|
|||
|
||||
import { closeReports } from 'soapbox/actions/admin';
|
||||
import { deactivateUserModal, deleteUserModal } from 'soapbox/actions/moderation';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import Avatar from 'soapbox/components/avatar';
|
||||
import HoverRefWrapper from 'soapbox/components/hover-ref-wrapper';
|
||||
import { Accordion, Button, Stack, HStack, Text } from 'soapbox/components/ui';
|
||||
import DropdownMenu from 'soapbox/containers/dropdown-menu-container';
|
||||
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||
import { makeGetReport } from 'soapbox/selectors';
|
||||
import toast from 'soapbox/toast';
|
||||
|
||||
import ReportStatus from './report-status';
|
||||
|
||||
|
@ -58,7 +58,7 @@ const Report: React.FC<IReport> = ({ id }) => {
|
|||
const handleCloseReport = () => {
|
||||
dispatch(closeReports([report.id])).then(() => {
|
||||
const message = intl.formatMessage(messages.reportClosed, { name: targetAccount.username as string });
|
||||
dispatch(snackbar.success(message));
|
||||
toast.success(message);
|
||||
}).catch(() => {});
|
||||
};
|
||||
|
||||
|
|
|
@ -3,10 +3,10 @@ import { defineMessages, useIntl } from 'react-intl';
|
|||
|
||||
import { approveUsers } from 'soapbox/actions/admin';
|
||||
import { rejectUserModal } from 'soapbox/actions/moderation';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import { Stack, HStack, Text, IconButton } from 'soapbox/components/ui';
|
||||
import { useAppSelector, useAppDispatch } from 'soapbox/hooks';
|
||||
import { makeGetAccount } from 'soapbox/selectors';
|
||||
import toast from 'soapbox/toast';
|
||||
|
||||
const messages = defineMessages({
|
||||
approved: { id: 'admin.awaiting_approval.approved_message', defaultMessage: '{acct} was approved!' },
|
||||
|
@ -32,7 +32,7 @@ const UnapprovedAccount: React.FC<IUnapprovedAccount> = ({ accountId }) => {
|
|||
dispatch(approveUsers([account.id]))
|
||||
.then(() => {
|
||||
const message = intl.formatMessage(messages.approved, { acct: `@${account.acct}` });
|
||||
dispatch(snackbar.success(message));
|
||||
toast.success(message);
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
@ -40,7 +40,7 @@ const UnapprovedAccount: React.FC<IUnapprovedAccount> = ({ accountId }) => {
|
|||
const handleReject = () => {
|
||||
dispatch(rejectUserModal(intl, account.id, () => {
|
||||
const message = intl.formatMessage(messages.rejected, { acct: `@${account.acct}` });
|
||||
dispatch(snackbar.info(message));
|
||||
toast.info(message);
|
||||
}));
|
||||
};
|
||||
|
||||
|
|
|
@ -3,9 +3,9 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
|||
import { Redirect } from 'react-router-dom';
|
||||
|
||||
import { resetPassword } from 'soapbox/actions/security';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import { Button, Form, FormActions, FormGroup, Input } from 'soapbox/components/ui';
|
||||
import { useAppDispatch } from 'soapbox/hooks';
|
||||
import toast from 'soapbox/toast';
|
||||
|
||||
const messages = defineMessages({
|
||||
nicknameOrEmail: { id: 'password_reset.fields.username_placeholder', defaultMessage: 'Email or username' },
|
||||
|
@ -25,7 +25,7 @@ const PasswordReset = () => {
|
|||
dispatch(resetPassword(nicknameOrEmail)).then(() => {
|
||||
setIsLoading(false);
|
||||
setSuccess(true);
|
||||
dispatch(snackbar.info(intl.formatMessage(messages.confirmation)));
|
||||
toast.info(intl.formatMessage(messages.confirmation));
|
||||
}).catch(() => {
|
||||
setIsLoading(false);
|
||||
});
|
||||
|
|
|
@ -3,13 +3,13 @@ import { AxiosError } from 'axios';
|
|||
import React, { useState } from 'react';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import { Icon, Input, Stack } from 'soapbox/components/ui';
|
||||
import { ChatWidgetScreens, useChatContext } from 'soapbox/contexts/chat-context';
|
||||
import { useAppDispatch, useDebounce } from 'soapbox/hooks';
|
||||
import { useDebounce } from 'soapbox/hooks';
|
||||
import { useChats } from 'soapbox/queries/chats';
|
||||
import { queryClient } from 'soapbox/queries/client';
|
||||
import useAccountSearch from 'soapbox/queries/search';
|
||||
import toast from 'soapbox/toast';
|
||||
|
||||
import { ChatKeys } from '../../../../queries/chats';
|
||||
|
||||
|
@ -25,7 +25,6 @@ const ChatSearch = (props: IChatSearch) => {
|
|||
const { isMainPage = false } = props;
|
||||
|
||||
const debounce = useDebounce;
|
||||
const dispatch = useAppDispatch();
|
||||
const history = useHistory();
|
||||
|
||||
const { changeScreen } = useChatContext();
|
||||
|
@ -45,7 +44,7 @@ const ChatSearch = (props: IChatSearch) => {
|
|||
}, {
|
||||
onError: (error: AxiosError) => {
|
||||
const data = error.response?.data as any;
|
||||
dispatch(snackbar.error(data?.error));
|
||||
toast.error(data?.error);
|
||||
},
|
||||
onSuccess: (response) => {
|
||||
if (isMainPage) {
|
||||
|
|
|
@ -2,9 +2,9 @@ import React from 'react';
|
|||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import { deleteAccount } from 'soapbox/actions/security';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import { Button, Card, CardBody, CardHeader, CardTitle, Form, FormActions, FormGroup, Input, Stack, Text } from 'soapbox/components/ui';
|
||||
import { useAppDispatch, useFeatures } from 'soapbox/hooks';
|
||||
import toast from 'soapbox/toast';
|
||||
|
||||
const messages = defineMessages({
|
||||
passwordFieldLabel: { id: 'security.fields.password.label', defaultMessage: 'Password' },
|
||||
|
@ -34,12 +34,12 @@ const DeleteAccount = () => {
|
|||
setLoading(true);
|
||||
dispatch(deleteAccount(password)).then(() => {
|
||||
setPassword('');
|
||||
dispatch(snackbar.success(intl.formatMessage(messages.deleteAccountSuccess)));
|
||||
toast.success(intl.formatMessage(messages.deleteAccountSuccess));
|
||||
}).finally(() => {
|
||||
setLoading(false);
|
||||
}).catch(() => {
|
||||
setPassword('');
|
||||
dispatch(snackbar.error(intl.formatMessage(messages.deleteAccountFail)));
|
||||
toast.error(intl.formatMessage(messages.deleteAccountFail));
|
||||
});
|
||||
}, [password, dispatch, intl]);
|
||||
|
||||
|
|
|
@ -3,8 +3,8 @@ import { FormattedMessage, defineMessages, useIntl } from 'react-intl';
|
|||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { changeSettingImmediate } from 'soapbox/actions/settings';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import { Column, Button, Form, FormActions, FormGroup, Input, Text } from 'soapbox/components/ui';
|
||||
import toast from 'soapbox/toast';
|
||||
|
||||
const messages = defineMessages({
|
||||
heading: { id: 'column.developers', defaultMessage: 'Developers' },
|
||||
|
@ -27,9 +27,9 @@ const DevelopersChallenge = () => {
|
|||
const handleSubmit = () => {
|
||||
if (answer === 'boxsoap') {
|
||||
dispatch(changeSettingImmediate(['isDeveloper'], true));
|
||||
dispatch(snackbar.success(intl.formatMessage(messages.success)));
|
||||
toast.success(intl.formatMessage(messages.success));
|
||||
} else {
|
||||
dispatch(snackbar.error(intl.formatMessage(messages.fail)));
|
||||
toast.error(intl.formatMessage(messages.fail));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -4,9 +4,9 @@ import { useDispatch } from 'react-redux';
|
|||
import { Link, useHistory } from 'react-router-dom';
|
||||
|
||||
import { changeSettingImmediate } from 'soapbox/actions/settings';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import { Column, Text } from 'soapbox/components/ui';
|
||||
import SvgIcon from 'soapbox/components/ui/icon/svg-icon';
|
||||
import toast from 'soapbox/toast';
|
||||
import sourceCode from 'soapbox/utils/code';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
@ -39,10 +39,19 @@ const Developers: React.FC = () => {
|
|||
e.preventDefault();
|
||||
|
||||
dispatch(changeSettingImmediate(['isDeveloper'], false));
|
||||
dispatch(snackbar.success(intl.formatMessage(messages.leave)));
|
||||
toast.success(intl.formatMessage(messages.leave));
|
||||
history.push('/');
|
||||
};
|
||||
|
||||
const showToast = (event: React.MouseEvent<HTMLButtonElement>) => {
|
||||
event.preventDefault();
|
||||
|
||||
toast.success('Hello world!', {
|
||||
action: () => alert('hi'),
|
||||
actionLabel: 'Click me',
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<Column label={intl.formatMessage(messages.heading)}>
|
||||
|
@ -102,6 +111,14 @@ const Developers: React.FC = () => {
|
|||
<FormattedMessage id='developers.navigation.leave_developers_label' defaultMessage='Leave developers' />
|
||||
</Text>
|
||||
</DashWidget>
|
||||
|
||||
<DashWidget onClick={showToast}>
|
||||
<SvgIcon src={require('@tabler/icons/urgent.svg')} className='text-gray-700 dark:text-gray-600' />
|
||||
|
||||
<Text>
|
||||
<FormattedMessage id='developers.navigation.show_toast' defaultMessage='Trigger Toast' />
|
||||
</Text>
|
||||
</DashWidget>
|
||||
</div>
|
||||
</Column>
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { useIntl, FormattedMessage, defineMessages } from 'react-intl';
|
||||
|
||||
import { showAlertForError } from 'soapbox/actions/alerts';
|
||||
import { patchMe } from 'soapbox/actions/me';
|
||||
import { FE_NAME, SETTINGS_UPDATE, changeSetting } from 'soapbox/actions/settings';
|
||||
import List, { ListItem } from 'soapbox/components/list';
|
||||
|
@ -17,6 +16,7 @@ import {
|
|||
} from 'soapbox/components/ui';
|
||||
import SettingToggle from 'soapbox/features/notifications/components/setting-toggle';
|
||||
import { useAppSelector, useAppDispatch, useSettings } from 'soapbox/hooks';
|
||||
import toast from 'soapbox/toast';
|
||||
|
||||
const isJSONValid = (text: any): boolean => {
|
||||
try {
|
||||
|
@ -65,7 +65,7 @@ const SettingsStore: React.FC = () => {
|
|||
dispatch({ type: SETTINGS_UPDATE, settings });
|
||||
setLoading(false);
|
||||
}).catch(error => {
|
||||
dispatch(showAlertForError(error));
|
||||
toast.showAlertForError(error);
|
||||
setLoading(false);
|
||||
});
|
||||
};
|
||||
|
|
|
@ -2,9 +2,9 @@ import React from 'react';
|
|||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import { changeEmail } from 'soapbox/actions/security';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import { Button, Card, CardBody, CardHeader, CardTitle, Column, Form, FormActions, FormGroup, Input } from 'soapbox/components/ui';
|
||||
import { useAppDispatch } from 'soapbox/hooks';
|
||||
import toast from 'soapbox/toast';
|
||||
|
||||
const messages = defineMessages({
|
||||
header: { id: 'edit_email.header', defaultMessage: 'Change Email' },
|
||||
|
@ -38,12 +38,12 @@ const EditEmail = () => {
|
|||
setLoading(true);
|
||||
dispatch(changeEmail(email, password)).then(() => {
|
||||
setState(initialState);
|
||||
dispatch(snackbar.success(intl.formatMessage(messages.updateEmailSuccess)));
|
||||
toast.success(intl.formatMessage(messages.updateEmailSuccess));
|
||||
}).finally(() => {
|
||||
setLoading(false);
|
||||
}).catch(() => {
|
||||
setState((prevState) => ({ ...prevState, password: '' }));
|
||||
dispatch(snackbar.error(intl.formatMessage(messages.updateEmailFail)));
|
||||
toast.error(intl.formatMessage(messages.updateEmailFail));
|
||||
});
|
||||
}, [email, password, dispatch, intl]);
|
||||
|
||||
|
|
|
@ -2,9 +2,9 @@ import React from 'react';
|
|||
import { defineMessages, useIntl } from 'react-intl';
|
||||
|
||||
import { changePassword } from 'soapbox/actions/security';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import { Button, Card, CardBody, CardHeader, CardTitle, Column, Form, FormActions, FormGroup, Input } from 'soapbox/components/ui';
|
||||
import { useAppDispatch, useFeatures } from 'soapbox/hooks';
|
||||
import toast from 'soapbox/toast';
|
||||
|
||||
import PasswordIndicator from '../verification/components/password-indicator';
|
||||
|
||||
|
@ -44,13 +44,13 @@ const EditPassword = () => {
|
|||
setLoading(true);
|
||||
dispatch(changePassword(currentPassword, newPassword, newPasswordConfirmation)).then(() => {
|
||||
resetState();
|
||||
dispatch(snackbar.success(intl.formatMessage(messages.updatePasswordSuccess)));
|
||||
toast.success(intl.formatMessage(messages.updatePasswordSuccess));
|
||||
|
||||
}).finally(() => {
|
||||
setLoading(false);
|
||||
}).catch(() => {
|
||||
resetState();
|
||||
dispatch(snackbar.error(intl.formatMessage(messages.updatePasswordFail)));
|
||||
toast.error(intl.formatMessage(messages.updatePasswordFail));
|
||||
});
|
||||
}, [currentPassword, newPassword, newPasswordConfirmation, dispatch, intl]);
|
||||
|
||||
|
|
|
@ -3,7 +3,6 @@ import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
|
|||
|
||||
import { updateNotificationSettings } from 'soapbox/actions/accounts';
|
||||
import { patchMe } from 'soapbox/actions/me';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import BirthdayInput from 'soapbox/components/birthday-input';
|
||||
import List, { ListItem } from 'soapbox/components/list';
|
||||
import {
|
||||
|
@ -21,6 +20,7 @@ import {
|
|||
} from 'soapbox/components/ui';
|
||||
import { useAppDispatch, useOwnAccount, useFeatures, useInstance } from 'soapbox/hooks';
|
||||
import { normalizeAccount } from 'soapbox/normalizers';
|
||||
import toast from 'soapbox/toast';
|
||||
import resizeImage from 'soapbox/utils/resize-image';
|
||||
|
||||
import ProfilePreview from './components/profile-preview';
|
||||
|
@ -217,10 +217,10 @@ const EditProfile: React.FC = () => {
|
|||
|
||||
Promise.all(promises).then(() => {
|
||||
setLoading(false);
|
||||
dispatch(snackbar.success(intl.formatMessage(messages.success)));
|
||||
toast.success(intl.formatMessage(messages.success));
|
||||
}).catch(() => {
|
||||
setLoading(false);
|
||||
dispatch(snackbar.error(intl.formatMessage(messages.error)));
|
||||
toast.error(intl.formatMessage(messages.error));
|
||||
});
|
||||
|
||||
event.preventDefault();
|
||||
|
|
|
@ -3,9 +3,9 @@ import { defineMessages, useIntl } from 'react-intl';
|
|||
import { Redirect } from 'react-router-dom';
|
||||
|
||||
import { confirmChangedEmail } from 'soapbox/actions/security';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import { Spinner } from 'soapbox/components/ui';
|
||||
import { useAppDispatch } from 'soapbox/hooks';
|
||||
import toast from 'soapbox/toast';
|
||||
import { buildErrorMessage } from 'soapbox/utils/errors';
|
||||
|
||||
const Statuses = {
|
||||
|
@ -32,11 +32,7 @@ const EmailConfirmation = () => {
|
|||
.then(() => {
|
||||
setStatus(Statuses.SUCCESS);
|
||||
|
||||
dispatch(
|
||||
snackbar.success(
|
||||
intl.formatMessage(messages.success),
|
||||
),
|
||||
);
|
||||
toast.success(intl.formatMessage(messages.success));
|
||||
})
|
||||
.catch((error) => {
|
||||
setStatus(Statuses.FAIL);
|
||||
|
@ -44,14 +40,12 @@ const EmailConfirmation = () => {
|
|||
if (error.response.data.error) {
|
||||
const message = buildErrorMessage(error.response.data.error);
|
||||
|
||||
dispatch(
|
||||
snackbar.error(
|
||||
message,
|
||||
// intl.formatMessage({
|
||||
// id: 'email_confirmation.fail',
|
||||
// defaultMessage,
|
||||
// }),
|
||||
),
|
||||
toast.error(
|
||||
message,
|
||||
// intl.formatMessage({
|
||||
// id: 'email_confirmation.fail',
|
||||
// defaultMessage,
|
||||
// }),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
|
|
@ -2,9 +2,9 @@ import React, { useState, useEffect } from 'react';
|
|||
import { useIntl, FormattedMessage, defineMessages } from 'react-intl';
|
||||
|
||||
import { externalLogin, loginWithCode } from 'soapbox/actions/external-auth';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import { Button, Form, FormActions, FormGroup, Input, Spinner } from 'soapbox/components/ui';
|
||||
import { useAppDispatch } from 'soapbox/hooks';
|
||||
import toast from 'soapbox/toast';
|
||||
|
||||
import type { AxiosError } from 'axios';
|
||||
|
||||
|
@ -39,9 +39,9 @@ const ExternalLoginForm: React.FC = () => {
|
|||
const status = error.response?.status;
|
||||
|
||||
if (status) {
|
||||
dispatch(snackbar.error(intl.formatMessage(messages.instanceFailed)));
|
||||
toast.error(intl.formatMessage(messages.instanceFailed));
|
||||
} else if (!status && error.code === 'ERR_NETWORK') {
|
||||
dispatch(snackbar.error(intl.formatMessage(messages.networkFailed)));
|
||||
toast.error(intl.formatMessage(messages.networkFailed));
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
|
|
|
@ -2,7 +2,6 @@ import React, { useEffect, useState } from 'react';
|
|||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import { fetchFilters, createFilter, deleteFilter } from 'soapbox/actions/filters';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import Icon from 'soapbox/components/icon';
|
||||
import ScrollableList from 'soapbox/components/scrollable-list';
|
||||
import { Button, CardHeader, CardTitle, Column, Form, FormActions, FormGroup, Input, Text } from 'soapbox/components/ui';
|
||||
|
@ -11,6 +10,7 @@ import {
|
|||
Checkbox,
|
||||
} from 'soapbox/features/forms';
|
||||
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||
import toast from 'soapbox/toast';
|
||||
|
||||
const messages = defineMessages({
|
||||
heading: { id: 'column.filters', defaultMessage: 'Muted words' },
|
||||
|
@ -81,7 +81,7 @@ const Filters = () => {
|
|||
dispatch(createFilter(phrase, expiresAt, context, wholeWord, irreversible)).then(() => {
|
||||
return dispatch(fetchFilters());
|
||||
}).catch(error => {
|
||||
dispatch(snackbar.error(intl.formatMessage(messages.create_error)));
|
||||
toast.error(intl.formatMessage(messages.create_error));
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -89,7 +89,7 @@ const Filters = () => {
|
|||
dispatch(deleteFilter(e.currentTarget.dataset.value!)).then(() => {
|
||||
return dispatch(fetchFilters());
|
||||
}).catch(() => {
|
||||
dispatch(snackbar.error(intl.formatMessage(messages.delete_error)));
|
||||
toast.error(intl.formatMessage(messages.delete_error));
|
||||
});
|
||||
};
|
||||
|
||||
|
|
|
@ -3,9 +3,9 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
|||
import { Link } from 'react-router-dom';
|
||||
|
||||
import { moveAccount } from 'soapbox/actions/security';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import { Button, Column, Form, FormActions, FormGroup, Input, Text } from 'soapbox/components/ui';
|
||||
import { useAppDispatch, useInstance } from 'soapbox/hooks';
|
||||
import toast from 'soapbox/toast';
|
||||
|
||||
const messages = defineMessages({
|
||||
heading: { id: 'column.migration', defaultMessage: 'Account migration' },
|
||||
|
@ -43,7 +43,7 @@ const Migration = () => {
|
|||
setIsLoading(true);
|
||||
return dispatch(moveAccount(targetAccount, password)).then(() => {
|
||||
clearForm();
|
||||
dispatch(snackbar.success(intl.formatMessage(messages.moveAccountSuccess)));
|
||||
toast.success(intl.formatMessage(messages.moveAccountSuccess));
|
||||
}).catch(error => {
|
||||
let message = intl.formatMessage(messages.moveAccountFail);
|
||||
|
||||
|
@ -52,7 +52,7 @@ const Migration = () => {
|
|||
message = intl.formatMessage(messages.moveAccountFailCooldownPeriod);
|
||||
}
|
||||
|
||||
dispatch(snackbar.error(message));
|
||||
toast.error(message);
|
||||
}).then(() => {
|
||||
setIsLoading(false);
|
||||
});
|
||||
|
|
|
@ -4,9 +4,9 @@ import { defineMessages, FormattedMessage } from 'react-intl';
|
|||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { patchMe } from 'soapbox/actions/me';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import { Avatar, Button, Card, CardBody, Icon, Spinner, Stack, Text } from 'soapbox/components/ui';
|
||||
import { useOwnAccount } from 'soapbox/hooks';
|
||||
import toast from 'soapbox/toast';
|
||||
import { isDefaultAvatar } from 'soapbox/utils/accounts';
|
||||
import resizeImage from 'soapbox/utils/resize-image';
|
||||
|
||||
|
@ -56,9 +56,9 @@ const AvatarSelectionStep = ({ onNext }: { onNext: () => void }) => {
|
|||
setSelectedFile(null);
|
||||
|
||||
if (error.response?.status === 422) {
|
||||
dispatch(snackbar.error((error.response.data as any).error.replace('Validation failed: ', '')));
|
||||
toast.error((error.response.data as any).error.replace('Validation failed: ', ''));
|
||||
} else {
|
||||
dispatch(snackbar.error(messages.error));
|
||||
toast.error(messages.error);
|
||||
}
|
||||
});
|
||||
}).catch(console.error);
|
||||
|
|
|
@ -3,9 +3,9 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
|||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { patchMe } from 'soapbox/actions/me';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import { Button, Card, CardBody, FormGroup, Stack, Text, Textarea } from 'soapbox/components/ui';
|
||||
import { useOwnAccount } from 'soapbox/hooks';
|
||||
import toast from 'soapbox/toast';
|
||||
|
||||
import type { AxiosError } from 'axios';
|
||||
|
||||
|
@ -38,7 +38,7 @@ const BioStep = ({ onNext }: { onNext: () => void }) => {
|
|||
if (error.response?.status === 422) {
|
||||
setErrors([(error.response.data as any).error.replace('Validation failed: ', '')]);
|
||||
} else {
|
||||
dispatch(snackbar.error(messages.error));
|
||||
toast.error(messages.error);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
@ -4,10 +4,10 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
|||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { patchMe } from 'soapbox/actions/me';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import StillImage from 'soapbox/components/still-image';
|
||||
import { Avatar, Button, Card, CardBody, Icon, Spinner, Stack, Text } from 'soapbox/components/ui';
|
||||
import { useOwnAccount } from 'soapbox/hooks';
|
||||
import toast from 'soapbox/toast';
|
||||
import { isDefaultHeader } from 'soapbox/utils/accounts';
|
||||
import resizeImage from 'soapbox/utils/resize-image';
|
||||
|
||||
|
@ -59,9 +59,9 @@ const CoverPhotoSelectionStep = ({ onNext }: { onNext: () => void }) => {
|
|||
setSelectedFile(null);
|
||||
|
||||
if (error.response?.status === 422) {
|
||||
dispatch(snackbar.error((error.response.data as any).error.replace('Validation failed: ', '')));
|
||||
toast.error((error.response.data as any).error.replace('Validation failed: ', ''));
|
||||
} else {
|
||||
dispatch(snackbar.error(messages.error));
|
||||
toast.error(messages.error);
|
||||
}
|
||||
});
|
||||
}).catch(console.error);
|
||||
|
|
|
@ -3,9 +3,9 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
|||
import { useDispatch } from 'react-redux';
|
||||
|
||||
import { patchMe } from 'soapbox/actions/me';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import { Button, Card, CardBody, FormGroup, Input, Stack, Text } from 'soapbox/components/ui';
|
||||
import { useOwnAccount } from 'soapbox/hooks';
|
||||
import toast from 'soapbox/toast';
|
||||
|
||||
import type { AxiosError } from 'axios';
|
||||
|
||||
|
@ -49,7 +49,7 @@ const DisplayNameStep = ({ onNext }: { onNext: () => void }) => {
|
|||
if (error.response?.status === 422) {
|
||||
setErrors([(error.response.data as any).error.replace('Validation failed: ', '')]);
|
||||
} else {
|
||||
dispatch(snackbar.error(messages.error));
|
||||
toast.error(messages.error);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
@ -3,9 +3,9 @@ import { useIntl, defineMessages, FormattedMessage } from 'react-intl';
|
|||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
import { disableMfa } from 'soapbox/actions/mfa';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import { Button, Form, FormGroup, Input, FormActions, Stack, Text } from 'soapbox/components/ui';
|
||||
import { useAppDispatch } from 'soapbox/hooks';
|
||||
import toast from 'soapbox/toast';
|
||||
|
||||
const messages = defineMessages({
|
||||
mfa_setup_disable_button: { id: 'column.mfa_disable_button', defaultMessage: 'Disable' },
|
||||
|
@ -25,12 +25,12 @@ const DisableOtpForm: React.FC = () => {
|
|||
const handleSubmit = useCallback(() => {
|
||||
setIsLoading(true);
|
||||
dispatch(disableMfa('totp', password)).then(() => {
|
||||
dispatch(snackbar.success(intl.formatMessage(messages.mfaDisableSuccess)));
|
||||
toast.success(intl.formatMessage(messages.mfaDisableSuccess));
|
||||
history.push('../auth/edit');
|
||||
}).finally(() => {
|
||||
setIsLoading(false);
|
||||
}).catch(() => {
|
||||
dispatch(snackbar.error(intl.formatMessage(messages.disableFail)));
|
||||
toast.error(intl.formatMessage(messages.disableFail));
|
||||
});
|
||||
}, [password, dispatch, intl]);
|
||||
|
||||
|
|
|
@ -3,9 +3,9 @@ import { useIntl, defineMessages, FormattedMessage } from 'react-intl';
|
|||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
import { fetchBackupCodes } from 'soapbox/actions/mfa';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import { Button, FormActions, Spinner, Stack, Text } from 'soapbox/components/ui';
|
||||
import { useAppDispatch } from 'soapbox/hooks';
|
||||
import toast from 'soapbox/toast';
|
||||
|
||||
const messages = defineMessages({
|
||||
mfaCancelButton: { id: 'column.mfa_cancel', defaultMessage: 'Cancel' },
|
||||
|
@ -30,7 +30,7 @@ const EnableOtpForm: React.FC<IEnableOtpForm> = ({ displayOtpForm, handleSetupPr
|
|||
setBackupCodes(backupCodes);
|
||||
})
|
||||
.catch(() => {
|
||||
dispatch(snackbar.error(intl.formatMessage(messages.codesFail)));
|
||||
toast.error(intl.formatMessage(messages.codesFail));
|
||||
});
|
||||
}, []);
|
||||
|
||||
|
|
|
@ -7,9 +7,9 @@ import {
|
|||
setupMfa,
|
||||
confirmMfa,
|
||||
} from 'soapbox/actions/mfa';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import { Button, Form, FormActions, FormGroup, Input, Stack, Text } from 'soapbox/components/ui';
|
||||
import { useAppDispatch } from 'soapbox/hooks';
|
||||
import toast from 'soapbox/toast';
|
||||
|
||||
const messages = defineMessages({
|
||||
mfaCancelButton: { id: 'column.mfa_cancel', defaultMessage: 'Cancel' },
|
||||
|
@ -38,7 +38,7 @@ const OtpConfirmForm: React.FC = () => {
|
|||
dispatch(setupMfa('totp')).then((data: any) => {
|
||||
setState((prevState) => ({ ...prevState, qrCodeURI: data.provisioning_uri, confirmKey: data.key }));
|
||||
}).catch(() => {
|
||||
dispatch(snackbar.error(intl.formatMessage(messages.qrFail)));
|
||||
toast.error(intl.formatMessage(messages.qrFail));
|
||||
});
|
||||
}, []);
|
||||
|
||||
|
@ -52,10 +52,10 @@ const OtpConfirmForm: React.FC = () => {
|
|||
setState((prevState) => ({ ...prevState, isLoading: true }));
|
||||
|
||||
dispatch(confirmMfa('totp', state.code, state.password) as any).then((r: any) => {
|
||||
dispatch(snackbar.success(intl.formatMessage(messages.mfaConfirmSuccess)));
|
||||
toast.success(intl.formatMessage(messages.mfaConfirmSuccess));
|
||||
history.push('../auth/edit');
|
||||
}).catch(() => {
|
||||
dispatch(snackbar.error(intl.formatMessage(messages.confirmFail)));
|
||||
toast.error(intl.formatMessage(messages.confirmFail));
|
||||
setState((prevState) => ({ ...prevState, isLoading: false }));
|
||||
});
|
||||
|
||||
|
|
|
@ -5,7 +5,6 @@ import { useHistory } from 'react-router-dom';
|
|||
|
||||
import { updateSoapboxConfig } from 'soapbox/actions/admin';
|
||||
import { uploadMedia } from 'soapbox/actions/media';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import List, { ListItem } from 'soapbox/components/list';
|
||||
import {
|
||||
Accordion,
|
||||
|
@ -25,6 +24,7 @@ import {
|
|||
import ThemeSelector from 'soapbox/features/ui/components/theme-selector';
|
||||
import { useAppSelector, useAppDispatch, useFeatures } from 'soapbox/hooks';
|
||||
import { normalizeSoapboxConfig } from 'soapbox/normalizers';
|
||||
import toast from 'soapbox/toast';
|
||||
|
||||
import CryptoAddressInput from './components/crypto-address-input';
|
||||
import FooterLinkInput from './components/footer-link-input';
|
||||
|
@ -102,7 +102,7 @@ const SoapboxConfig: React.FC = () => {
|
|||
const handleSubmit: React.FormEventHandler = (e) => {
|
||||
dispatch(updateSoapboxConfig(data.toJS())).then(() => {
|
||||
setLoading(false);
|
||||
dispatch(snackbar.success(intl.formatMessage(messages.saved)));
|
||||
toast.success(intl.formatMessage(messages.saved));
|
||||
}).catch(() => {
|
||||
setLoading(false);
|
||||
});
|
||||
|
|
|
@ -4,7 +4,6 @@ import { v4 as uuidv4 } from 'uuid';
|
|||
|
||||
import { updateSoapboxConfig } from 'soapbox/actions/admin';
|
||||
import { getHost } from 'soapbox/actions/instance';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import { fetchSoapboxConfig } from 'soapbox/actions/soapbox';
|
||||
import List, { ListItem } from 'soapbox/components/list';
|
||||
import { Button, Column, Form, FormActions } from 'soapbox/components/ui';
|
||||
|
@ -12,6 +11,7 @@ import DropdownMenuContainer from 'soapbox/containers/dropdown-menu-container';
|
|||
import ColorWithPicker from 'soapbox/features/soapbox-config/components/color-with-picker';
|
||||
import { useAppDispatch, useAppSelector, useSoapboxConfig } from 'soapbox/hooks';
|
||||
import { normalizeSoapboxConfig } from 'soapbox/normalizers';
|
||||
import toast from 'soapbox/toast';
|
||||
import { download } from 'soapbox/utils/download';
|
||||
|
||||
import Palette, { ColorGroup } from './components/palette';
|
||||
|
@ -103,7 +103,7 @@ const ThemeEditor: React.FC<IThemeEditor> = () => {
|
|||
const colors = normalizeSoapboxConfig({ colors: json }).colors.toJS();
|
||||
|
||||
setTheme(colors);
|
||||
dispatch(snackbar.success(intl.formatMessage(messages.importSuccess)));
|
||||
toast.success(intl.formatMessage(messages.importSuccess));
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -113,7 +113,7 @@ const ThemeEditor: React.FC<IThemeEditor> = () => {
|
|||
try {
|
||||
await dispatch(fetchSoapboxConfig(host));
|
||||
await updateTheme();
|
||||
dispatch(snackbar.success(intl.formatMessage(messages.saved)));
|
||||
toast.success(intl.formatMessage(messages.saved));
|
||||
setSubmitting(false);
|
||||
} catch (e) {
|
||||
setSubmitting(false);
|
||||
|
@ -199,7 +199,7 @@ const ThemeEditor: React.FC<IThemeEditor> = () => {
|
|||
text: intl.formatMessage(messages.restore),
|
||||
action: restoreDefaultTheme,
|
||||
icon: require('@tabler/icons/refresh.svg'),
|
||||
},{
|
||||
}, {
|
||||
text: intl.formatMessage(messages.import),
|
||||
action: importTheme,
|
||||
icon: require('@tabler/icons/upload.svg'),
|
||||
|
|
|
@ -9,7 +9,6 @@ import {
|
|||
setBadges as saveBadges,
|
||||
} from 'soapbox/actions/admin';
|
||||
import { deactivateUserModal, deleteUserModal } from 'soapbox/actions/moderation';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import Account from 'soapbox/components/account';
|
||||
import List, { ListItem } from 'soapbox/components/list';
|
||||
import MissingIndicator from 'soapbox/components/missing-indicator';
|
||||
|
@ -17,6 +16,7 @@ import OutlineBox from 'soapbox/components/outline-box';
|
|||
import { Button, Text, HStack, Modal, Stack, Toggle } from 'soapbox/components/ui';
|
||||
import { useAppDispatch, useAppSelector, useFeatures, useOwnAccount } from 'soapbox/hooks';
|
||||
import { makeGetAccount } from 'soapbox/selectors';
|
||||
import toast from 'soapbox/toast';
|
||||
import { isLocal } from 'soapbox/utils/accounts';
|
||||
import { getBadges } from 'soapbox/utils/badges';
|
||||
|
||||
|
@ -75,7 +75,7 @@ const AccountModerationModal: React.FC<IAccountModerationModal> = ({ onClose, ac
|
|||
const action = checked ? verifyUser : unverifyUser;
|
||||
|
||||
dispatch(action(account.id))
|
||||
.then(() => dispatch(snackbar.success(intl.formatMessage(message, { acct: account.acct }))))
|
||||
.then(() => toast.success(intl.formatMessage(message, { acct: account.acct })))
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
|
@ -86,7 +86,7 @@ const AccountModerationModal: React.FC<IAccountModerationModal> = ({ onClose, ac
|
|||
const action = checked ? suggestUsers : unsuggestUsers;
|
||||
|
||||
dispatch(action([account.id]))
|
||||
.then(() => dispatch(snackbar.success(intl.formatMessage(message, { acct: account.acct }))))
|
||||
.then(() => toast.success(intl.formatMessage(message, { acct: account.acct })))
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
|
@ -100,7 +100,7 @@ const AccountModerationModal: React.FC<IAccountModerationModal> = ({ onClose, ac
|
|||
|
||||
const handleSaveBadges = () => {
|
||||
dispatch(saveBadges(account.id, accountBadges, badges))
|
||||
.then(() => dispatch(snackbar.success(intl.formatMessage(messages.badgesSaved))))
|
||||
.then(() => toast.success(intl.formatMessage(messages.badgesSaved)))
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
|
|
|
@ -2,9 +2,9 @@ import React, { useMemo } from 'react';
|
|||
import { defineMessages, MessageDescriptor, useIntl } from 'react-intl';
|
||||
|
||||
import { setRole } from 'soapbox/actions/admin';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import { SelectDropdown } from 'soapbox/features/forms';
|
||||
import { useAppDispatch } from 'soapbox/hooks';
|
||||
import toast from 'soapbox/toast';
|
||||
|
||||
import type { Account as AccountEntity } from 'soapbox/types/entities';
|
||||
|
||||
|
@ -66,7 +66,7 @@ const StaffRolePicker: React.FC<IStaffRolePicker> = ({ account }) => {
|
|||
}
|
||||
|
||||
if (message) {
|
||||
dispatch(snackbar.success(intl.formatMessage(message, { acct: account.acct })));
|
||||
toast.success(intl.formatMessage(message, { acct: account.acct }));
|
||||
}
|
||||
})
|
||||
.catch(() => {});
|
||||
|
|
|
@ -4,11 +4,11 @@ import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
|||
import Toggle from 'react-toggle';
|
||||
|
||||
import { updateMrf } from 'soapbox/actions/mrf';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import List, { ListItem } from 'soapbox/components/list';
|
||||
import { Modal } from 'soapbox/components/ui';
|
||||
import { useAppSelector, useAppDispatch } from 'soapbox/hooks';
|
||||
import { makeGetRemoteInstance } from 'soapbox/selectors';
|
||||
import toast from 'soapbox/toast';
|
||||
|
||||
const messages = defineMessages({
|
||||
mediaRemoval: { id: 'edit_federation.media_removal', defaultMessage: 'Strip media' },
|
||||
|
@ -56,7 +56,7 @@ const EditFederationModal: React.FC<IEditFederationModal> = ({ host, onClose })
|
|||
|
||||
const handleSubmit = () => {
|
||||
dispatch(updateMrf(host, data))
|
||||
.then(() => dispatch(snackbar.success(intl.formatMessage(messages.success, { host }))))
|
||||
.then(() => toast.success(intl.formatMessage(messages.success, { host })))
|
||||
.catch(() => {});
|
||||
|
||||
onClose();
|
||||
|
|
|
@ -3,9 +3,9 @@ import { defineMessages, useIntl, FormattedMessage } from 'react-intl';
|
|||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
import { remoteInteraction } from 'soapbox/actions/interactions';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import { Button, Modal, Stack, Text } from 'soapbox/components/ui';
|
||||
import { useAppSelector, useAppDispatch, useFeatures, useSoapboxConfig, useInstance } from 'soapbox/hooks';
|
||||
import toast from 'soapbox/toast';
|
||||
|
||||
const messages = defineMessages({
|
||||
close: { id: 'lightbox.close', defaultMessage: 'Close' },
|
||||
|
@ -55,7 +55,7 @@ const UnauthorizedModal: React.FC<IUnauthorizedModal> = ({ action, onClose, acco
|
|||
})
|
||||
.catch(error => {
|
||||
if (error.message === 'Couldn\'t find user') {
|
||||
dispatch(snackbar.error(intl.formatMessage(messages.userNotFoundError)));
|
||||
toast.error(intl.formatMessage(messages.userNotFoundError));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
|
|
@ -4,10 +4,10 @@ import OtpInput from 'react-otp-input';
|
|||
|
||||
import { verifyCredentials } from 'soapbox/actions/auth';
|
||||
import { closeModal } from 'soapbox/actions/modals';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import { reConfirmPhoneVerification, reRequestPhoneVerification } from 'soapbox/actions/verification';
|
||||
import { FormGroup, PhoneInput, Modal, Stack, Text } from 'soapbox/components/ui';
|
||||
import { useAppDispatch, useAppSelector, useInstance } from 'soapbox/hooks';
|
||||
import toast from 'soapbox/toast';
|
||||
import { getAccessToken } from 'soapbox/utils/auth';
|
||||
|
||||
const messages = defineMessages({
|
||||
|
@ -76,28 +76,18 @@ const VerifySmsModal: React.FC<IVerifySmsModal> = ({ onClose }) => {
|
|||
|
||||
if (!isValid) {
|
||||
setStatus(Statuses.IDLE);
|
||||
dispatch(
|
||||
snackbar.error(
|
||||
intl.formatMessage(messages.verificationInvalid),
|
||||
),
|
||||
);
|
||||
toast.error(intl.formatMessage(messages.verificationInvalid));
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(reRequestPhoneVerification(phone!)).then(() => {
|
||||
dispatch(
|
||||
snackbar.success(
|
||||
intl.formatMessage(messages.verificationSuccess),
|
||||
),
|
||||
toast.success(
|
||||
intl.formatMessage(messages.verificationSuccess),
|
||||
);
|
||||
})
|
||||
.finally(() => setStatus(Statuses.REQUESTED))
|
||||
.catch(() => {
|
||||
dispatch(
|
||||
snackbar.error(
|
||||
intl.formatMessage(messages.verificationFail),
|
||||
),
|
||||
);
|
||||
toast.error(intl.formatMessage(messages.verificationFail));
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -196,11 +186,7 @@ const VerifySmsModal: React.FC<IVerifySmsModal> = ({ onClose }) => {
|
|||
.then(() => dispatch(closeModal('VERIFY_SMS')));
|
||||
|
||||
})
|
||||
.catch(() => dispatch(
|
||||
snackbar.error(
|
||||
intl.formatMessage(messages.verificationExpired),
|
||||
),
|
||||
));
|
||||
.catch(() => toast.error(intl.formatMessage(messages.verificationExpired)));
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
|
|
|
@ -6,9 +6,9 @@ import {
|
|||
subscribeAccount,
|
||||
unsubscribeAccount,
|
||||
} from 'soapbox/actions/accounts';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import { IconButton } from 'soapbox/components/ui';
|
||||
import { useAppDispatch, useFeatures } from 'soapbox/hooks';
|
||||
import toast from 'soapbox/toast';
|
||||
|
||||
import type { Account as AccountEntity } from 'soapbox/types/entities';
|
||||
|
||||
|
@ -40,16 +40,16 @@ const SubscriptionButton = ({ account }: ISubscriptionButton) => {
|
|||
intl.formatMessage(messages.subscribe, { name: account.get('username') });
|
||||
|
||||
const onSubscribeSuccess = () =>
|
||||
dispatch(snackbar.success(intl.formatMessage(messages.subscribeSuccess)));
|
||||
toast.success(intl.formatMessage(messages.subscribeSuccess));
|
||||
|
||||
const onSubscribeFailure = () =>
|
||||
dispatch(snackbar.error(intl.formatMessage(messages.subscribeFailure)));
|
||||
toast.error(intl.formatMessage(messages.subscribeFailure));
|
||||
|
||||
const onUnsubscribeSuccess = () =>
|
||||
dispatch(snackbar.success(intl.formatMessage(messages.unsubscribeSuccess)));
|
||||
toast.success(intl.formatMessage(messages.unsubscribeSuccess));
|
||||
|
||||
const onUnsubscribeFailure = () =>
|
||||
dispatch(snackbar.error(intl.formatMessage(messages.unsubscribeFailure)));
|
||||
toast.error(intl.formatMessage(messages.unsubscribeFailure));
|
||||
|
||||
const onNotifyToggle = () => {
|
||||
if (account.relationship?.notifying) {
|
||||
|
|
|
@ -1,84 +0,0 @@
|
|||
import React from 'react';
|
||||
import { useIntl, MessageDescriptor } from 'react-intl';
|
||||
import { NotificationStack, NotificationObject, StyleFactoryFn } from 'react-notification';
|
||||
import { useHistory } from 'react-router-dom';
|
||||
|
||||
import { dismissAlert } from 'soapbox/actions/alerts';
|
||||
import { Button } from 'soapbox/components/ui';
|
||||
import { useAppSelector, useAppDispatch } from 'soapbox/hooks';
|
||||
|
||||
import type { Alert } from 'soapbox/reducers/alerts';
|
||||
|
||||
/** Portal for snackbar alerts. */
|
||||
const SnackbarContainer: React.FC = () => {
|
||||
const intl = useIntl();
|
||||
const history = useHistory();
|
||||
const dispatch = useAppDispatch();
|
||||
|
||||
const alerts = useAppSelector(state => state.alerts);
|
||||
|
||||
/** Apply i18n to the message if it's an object. */
|
||||
const maybeFormatMessage = (message: MessageDescriptor | string): string => {
|
||||
switch (typeof message) {
|
||||
case 'string': return message;
|
||||
case 'object': return intl.formatMessage(message);
|
||||
default: return '';
|
||||
}
|
||||
};
|
||||
|
||||
/** Convert a reducer Alert into a react-notification object. */
|
||||
const buildAlert = (item: Alert): NotificationObject => {
|
||||
// Backwards-compatibility
|
||||
if (item.actionLink) {
|
||||
item = item.set('action', () => history.push(item.actionLink));
|
||||
}
|
||||
|
||||
const alert: NotificationObject = {
|
||||
message: maybeFormatMessage(item.message),
|
||||
title: maybeFormatMessage(item.title),
|
||||
key: item.key,
|
||||
className: `notification-bar-${item.severity}`,
|
||||
activeClassName: 'snackbar--active',
|
||||
dismissAfter: item.dismissAfter,
|
||||
style: false,
|
||||
};
|
||||
|
||||
if (item.action && item.actionLabel) {
|
||||
// HACK: it's a JSX.Element instead of a string!
|
||||
// react-notification displays it just fine.
|
||||
alert.action = (
|
||||
<Button theme='tertiary' size='sm' onClick={item.action} text={maybeFormatMessage(item.actionLabel)} />
|
||||
) as any;
|
||||
}
|
||||
|
||||
return alert;
|
||||
};
|
||||
|
||||
const onDismiss = (alert: NotificationObject) => {
|
||||
dispatch(dismissAlert(alert));
|
||||
};
|
||||
|
||||
const defaultBarStyleFactory: StyleFactoryFn = (index, style, _notification) => {
|
||||
return Object.assign(
|
||||
{},
|
||||
style,
|
||||
{ bottom: `${14 + index * 12 + index * 42}px` },
|
||||
);
|
||||
};
|
||||
|
||||
const notifications = alerts.toArray().map(buildAlert);
|
||||
|
||||
return (
|
||||
<div role='assertive' data-testid='toast' className='z-1000 fixed inset-0 flex items-end px-4 py-6 pointer-events-none pt-16 lg:pt-20 sm:items-start'>
|
||||
<NotificationStack
|
||||
onDismiss={onDismiss}
|
||||
onClick={onDismiss}
|
||||
barStyleFactory={defaultBarStyleFactory}
|
||||
activeBarStyleFactory={defaultBarStyleFactory}
|
||||
notifications={notifications}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SnackbarContainer;
|
|
@ -374,10 +374,6 @@ export function UploadArea() {
|
|||
return import(/* webpackChunkName: "features/compose" */'../components/upload-area');
|
||||
}
|
||||
|
||||
export function NotificationsContainer() {
|
||||
return import(/* webpackChunkName: "features/ui" */'../containers/notifications-container');
|
||||
}
|
||||
|
||||
export function ModalContainer() {
|
||||
return import(/* webpackChunkName: "features/ui" */'../containers/modal-container');
|
||||
}
|
||||
|
|
|
@ -2,10 +2,10 @@ import React from 'react';
|
|||
import { defineMessages, useIntl } from 'react-intl';
|
||||
import { useHistory, useParams } from 'react-router-dom';
|
||||
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import { confirmEmailVerification } from 'soapbox/actions/verification';
|
||||
import { Icon, Spinner, Stack, Text } from 'soapbox/components/ui';
|
||||
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||
import toast from 'soapbox/toast';
|
||||
|
||||
import { ChallengeTypes } from './index';
|
||||
|
||||
|
@ -122,7 +122,7 @@ const EmailPassThru = () => {
|
|||
dispatch(confirmEmailVerification(token))
|
||||
.then(() => {
|
||||
setStatus(Statuses.SUCCESS);
|
||||
dispatch(snackbar.success(intl.formatMessage(messages.emailConfirmed)));
|
||||
toast.success(intl.formatMessage(messages.emailConfirmed));
|
||||
})
|
||||
.catch((error: AxiosError<any>) => {
|
||||
const errorKey = error?.response?.data?.error;
|
||||
|
@ -145,7 +145,7 @@ const EmailPassThru = () => {
|
|||
}
|
||||
}
|
||||
|
||||
dispatch(snackbar.error(message));
|
||||
toast.error(message);
|
||||
});
|
||||
}
|
||||
}, [token]);
|
||||
|
|
|
@ -5,10 +5,10 @@ import { Redirect } from 'react-router-dom';
|
|||
import { logIn, verifyCredentials } from 'soapbox/actions/auth';
|
||||
import { fetchInstance } from 'soapbox/actions/instance';
|
||||
import { startOnboarding } from 'soapbox/actions/onboarding';
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import { createAccount, removeStoredVerification } from 'soapbox/actions/verification';
|
||||
import { Button, Form, FormGroup, Input, Text } from 'soapbox/components/ui';
|
||||
import { useAppDispatch, useAppSelector, useInstance, useSoapboxConfig } from 'soapbox/hooks';
|
||||
import toast from 'soapbox/toast';
|
||||
import { getRedirectUrl } from 'soapbox/utils/redirect';
|
||||
|
||||
import PasswordIndicator from './components/password-indicator';
|
||||
|
@ -54,25 +54,15 @@ const Registration = () => {
|
|||
setShouldRedirect(true);
|
||||
removeStoredVerification();
|
||||
dispatch(startOnboarding());
|
||||
dispatch(
|
||||
snackbar.success(
|
||||
intl.formatMessage(messages.success, { siteTitle: instance.title }),
|
||||
),
|
||||
toast.success(
|
||||
intl.formatMessage(messages.success, { siteTitle: instance.title }),
|
||||
);
|
||||
})
|
||||
.catch((error: AxiosError) => {
|
||||
if (error?.response?.status === 422) {
|
||||
dispatch(
|
||||
snackbar.error(
|
||||
intl.formatMessage(messages.usernameTaken),
|
||||
),
|
||||
);
|
||||
toast.error(intl.formatMessage(messages.usernameTaken));
|
||||
} else {
|
||||
dispatch(
|
||||
snackbar.error(
|
||||
intl.formatMessage(messages.error),
|
||||
),
|
||||
);
|
||||
toast.error(intl.formatMessage(messages.error));
|
||||
}
|
||||
});
|
||||
}, [username, password]);
|
||||
|
|
|
@ -27,13 +27,13 @@ describe('<EmailVerification />', () => {
|
|||
|
||||
await waitFor(() => {
|
||||
fireEvent.submit(
|
||||
screen.getByRole('button'), {
|
||||
screen.getByTestId('button'), {
|
||||
preventDefault: () => {},
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
expect(screen.getByRole('button')).toHaveTextContent('Resend verification email');
|
||||
expect(screen.getByTestId('button')).toHaveTextContent('Resend verification email');
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -54,7 +54,7 @@ describe('<EmailVerification />', () => {
|
|||
|
||||
await waitFor(() => {
|
||||
fireEvent.submit(
|
||||
screen.getByRole('button'), {
|
||||
screen.getByTestId('button'), {
|
||||
preventDefault: () => {},
|
||||
},
|
||||
);
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
import userEvent from '@testing-library/user-event';
|
||||
import React from 'react';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { toast } from 'react-hot-toast';
|
||||
|
||||
import { __stub } from 'soapbox/api';
|
||||
import { fireEvent, render, screen, waitFor } from 'soapbox/jest/test-helpers';
|
||||
|
@ -38,6 +40,10 @@ describe('<SmsVerification />', () => {
|
|||
expect(screen.getByRole('heading')).toHaveTextContent('Verification code');
|
||||
expect(screen.getByTestId('toast')).toHaveTextContent('A verification code has been sent to your phone number.');
|
||||
|
||||
act(() => {
|
||||
toast.remove();
|
||||
});
|
||||
|
||||
await userEvent.type(screen.getByLabelText('Please enter verification code. Digit 1'), '1');
|
||||
await userEvent.type(screen.getByLabelText('Digit 2'), '2');
|
||||
await userEvent.type(screen.getByLabelText('Digit 3'), '3');
|
||||
|
@ -65,6 +71,10 @@ describe('<SmsVerification />', () => {
|
|||
expect(screen.getByRole('heading')).toHaveTextContent('Verification code');
|
||||
expect(screen.getByTestId('toast')).toHaveTextContent('A verification code has been sent to your phone number.');
|
||||
|
||||
act(() => {
|
||||
toast.remove();
|
||||
});
|
||||
|
||||
await userEvent.type(screen.getByLabelText('Please enter verification code. Digit 1'), '1');
|
||||
await userEvent.type(screen.getByLabelText('Digit 2'), '2');
|
||||
await userEvent.type(screen.getByLabelText('Digit 3'), '3');
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
import React from 'react';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import { verifyAge } from 'soapbox/actions/verification';
|
||||
import { Button, Datepicker, Form, Text } from 'soapbox/components/ui';
|
||||
import { useAppDispatch, useAppSelector, useInstance } from 'soapbox/hooks';
|
||||
import toast from 'soapbox/toast';
|
||||
|
||||
const messages = defineMessages({
|
||||
fail: {
|
||||
|
@ -42,9 +42,7 @@ const AgeVerification = () => {
|
|||
if (meetsAgeMinimum(birthday, ageMinimum)) {
|
||||
dispatch(verifyAge(birthday));
|
||||
} else {
|
||||
dispatch(
|
||||
snackbar.error(intl.formatMessage(messages.fail, { ageMinimum })),
|
||||
);
|
||||
toast.error(intl.formatMessage(messages.fail, { ageMinimum }));
|
||||
}
|
||||
}, [date, ageMinimum]);
|
||||
|
||||
|
|
|
@ -2,11 +2,11 @@ import { AxiosError } from 'axios';
|
|||
import React from 'react';
|
||||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import { checkEmailVerification, postEmailVerification, requestEmailVerification } from 'soapbox/actions/verification';
|
||||
import Icon from 'soapbox/components/icon';
|
||||
import { Button, Form, FormGroup, Input, Text } from 'soapbox/components/ui';
|
||||
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||
import toast from 'soapbox/toast';
|
||||
|
||||
const messages = defineMessages({
|
||||
verificationSuccess: { id: 'email_verification.success', defaultMessage: 'Verification email sent successfully.' },
|
||||
|
@ -83,11 +83,7 @@ const EmailVerification = () => {
|
|||
.then(() => {
|
||||
setStatus(Statuses.REQUESTED);
|
||||
|
||||
dispatch(
|
||||
snackbar.success(
|
||||
intl.formatMessage(messages.verificationSuccess),
|
||||
),
|
||||
);
|
||||
toast.success(intl.formatMessage(messages.verificationSuccess));
|
||||
})
|
||||
.catch((error: AxiosError) => {
|
||||
const errorMessage = (error.response?.data as any)?.error;
|
||||
|
@ -104,7 +100,7 @@ const EmailVerification = () => {
|
|||
setErrors([intl.formatMessage(messages.verificationFailTaken)]);
|
||||
}
|
||||
|
||||
dispatch(snackbar.error(message));
|
||||
toast.error(message);
|
||||
setStatus(Statuses.FAIL);
|
||||
});
|
||||
};
|
||||
|
|
|
@ -3,10 +3,10 @@ import React from 'react';
|
|||
import { defineMessages, FormattedMessage, useIntl } from 'react-intl';
|
||||
import OtpInput from 'react-otp-input';
|
||||
|
||||
import snackbar from 'soapbox/actions/snackbar';
|
||||
import { confirmPhoneVerification, requestPhoneVerification } from 'soapbox/actions/verification';
|
||||
import { Button, Form, FormGroup, PhoneInput, Text } from 'soapbox/components/ui';
|
||||
import { useAppDispatch, useAppSelector } from 'soapbox/hooks';
|
||||
import toast from 'soapbox/toast';
|
||||
|
||||
const messages = defineMessages({
|
||||
verificationInvalid: { id: 'sms_verification.invalid', defaultMessage: 'Please enter a valid phone number.' },
|
||||
|
@ -44,25 +44,17 @@ const SmsVerification = () => {
|
|||
|
||||
if (!isValid) {
|
||||
setStatus(Statuses.IDLE);
|
||||
dispatch(
|
||||
snackbar.error(
|
||||
intl.formatMessage(messages.verificationInvalid),
|
||||
),
|
||||
);
|
||||
toast.error(intl.formatMessage(messages.verificationInvalid));
|
||||
return;
|
||||
}
|
||||
|
||||
dispatch(requestPhoneVerification(phone!)).then(() => {
|
||||
dispatch(
|
||||
snackbar.success(
|
||||
intl.formatMessage(messages.verificationSuccess),
|
||||
),
|
||||
);
|
||||
toast.success(intl.formatMessage(messages.verificationSuccess));
|
||||
setStatus(Statuses.REQUESTED);
|
||||
}).catch((error: AxiosError) => {
|
||||
const message = (error.response?.data as any)?.message || intl.formatMessage(messages.verificationFail);
|
||||
|
||||
dispatch(snackbar.error(message));
|
||||
toast.error(message);
|
||||
setStatus(Statuses.FAIL);
|
||||
});
|
||||
}, [phone, isValid]);
|
||||
|
@ -75,11 +67,9 @@ const SmsVerification = () => {
|
|||
const submitVerification = () => {
|
||||
// TODO: handle proper validation from Pepe -- expired vs invalid
|
||||
dispatch(confirmPhoneVerification(verificationCode))
|
||||
.catch(() => dispatch(
|
||||
snackbar.error(
|
||||
intl.formatMessage(messages.verificationExpired),
|
||||
),
|
||||
));
|
||||
.catch(() => {
|
||||
toast.error(intl.formatMessage(messages.verificationExpired));
|
||||
});
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import { useAppSelector } from 'soapbox/hooks';
|
||||
import { makeGetAccount } from 'soapbox/selectors';
|
||||
|
||||
const getAccount = makeGetAccount();
|
||||
export const useAccount = (id: string) => {
|
||||
const getAccount = makeGetAccount();
|
||||
|
||||
export const useAccount = (id: string) => useAppSelector((state) => getAccount(state, id));
|
||||
return useAppSelector((state) => getAccount(state, id));
|
||||
};
|
||||
|
|
|
@ -4,6 +4,7 @@ import { render, RenderOptions } from '@testing-library/react';
|
|||
import { renderHook, RenderHookOptions } from '@testing-library/react-hooks';
|
||||
import { merge } from 'immutable';
|
||||
import React, { FC, ReactElement } from 'react';
|
||||
import { Toaster } from 'react-hot-toast';
|
||||
import { IntlProvider } from 'react-intl';
|
||||
import { Provider } from 'react-redux';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
@ -15,7 +16,6 @@ import { ChatProvider } from 'soapbox/contexts/chat-context';
|
|||
import { StatProvider } from 'soapbox/contexts/stat-context';
|
||||
import { queryClient } from 'soapbox/queries/client';
|
||||
|
||||
import NotificationsContainer from '../features/ui/containers/notifications-container';
|
||||
import { default as rootReducer } from '../reducers';
|
||||
|
||||
import type { AnyAction } from 'redux';
|
||||
|
@ -59,7 +59,7 @@ const TestApp: FC<any> = ({ children, storeProps, routerProps = {} }) => {
|
|||
<IntlProvider locale={props.locale}>
|
||||
{children}
|
||||
|
||||
<NotificationsContainer />
|
||||
<Toaster />
|
||||
</IntlProvider>
|
||||
</ChatProvider>
|
||||
</QueryClientProvider>
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
'use strict';
|
||||
|
||||
import { toast } from 'react-hot-toast';
|
||||
|
||||
import { __clear as clearApiMocks } from '../api/__mocks__';
|
||||
|
||||
// API mocking
|
||||
|
@ -16,6 +18,11 @@ require('fake-indexeddb/auto');
|
|||
// Mock external dependencies
|
||||
jest.mock('uuid', () => ({ v4: jest.fn(() => '1') }));
|
||||
|
||||
// Clear toasts after each test.
|
||||
afterEach(() => {
|
||||
toast.remove();
|
||||
});
|
||||
|
||||
const intersectionObserverMock = () => ({ observe: () => null, disconnect: () => null });
|
||||
window.IntersectionObserver = jest.fn().mockImplementation(intersectionObserverMock);
|
||||
|
||||
|
|
|
@ -1016,7 +1016,7 @@
|
|||
"sms_verification.sent.body": "لقد أرسلنا لك رمزًا مكونًا من 6 أرقام عبر رسالة نصية قصيرة. رجاءً أدخله في الأسفل.",
|
||||
"sms_verification.sent.header": "تأكيد الرمز",
|
||||
"sms_verification.success": "تم إرسال رمز التحقق إلى رقم هاتفك.",
|
||||
"snackbar.view": "عرض",
|
||||
"toast.view": "عرض",
|
||||
"soapbox_config.authenticated_profile_hint": "على المستخدمين أن يُسجلوا دخولهم كي يتمكنوا من عرض الردود والوسائط على الحسابات الشخصية للمستخدمين.",
|
||||
"soapbox_config.authenticated_profile_label": "حسابات شخصية تتطلب تسجيل الدخول لتصفحها",
|
||||
"soapbox_config.copyright_footer.meta_fields.label_placeholder": "حاشية حقوق الطبع والنشر",
|
||||
|
|
|
@ -1016,7 +1016,7 @@
|
|||
"sms_verification.sent.body": "We sent you a 6-digit code via SMS. Enter it below.",
|
||||
"sms_verification.sent.header": "Verification code",
|
||||
"sms_verification.success": "A verification code has been sent to your phone number.",
|
||||
"snackbar.view": "View",
|
||||
"toast.view": "View",
|
||||
"soapbox_config.authenticated_profile_hint": "Users must be logged-in to view replies and media on user profiles.",
|
||||
"soapbox_config.authenticated_profile_label": "Profiles require authentication",
|
||||
"soapbox_config.copyright_footer.meta_fields.label_placeholder": "Copyright footer",
|
||||
|
|
|
@ -1016,7 +1016,7 @@
|
|||
"sms_verification.sent.body": "We sent you a 6-digit code via SMS. Enter it below.",
|
||||
"sms_verification.sent.header": "Verification code",
|
||||
"sms_verification.success": "A verification code has been sent to your phone number.",
|
||||
"snackbar.view": "View",
|
||||
"toast.view": "View",
|
||||
"soapbox_config.authenticated_profile_hint": "Users must be logged-in to view replies and media on user profiles.",
|
||||
"soapbox_config.authenticated_profile_label": "Profiles require authentication",
|
||||
"soapbox_config.copyright_footer.meta_fields.label_placeholder": "Copyright footer",
|
||||
|
|
|
@ -1016,7 +1016,7 @@
|
|||
"sms_verification.sent.body": "We sent you a 6-digit code via SMS. Enter it below.",
|
||||
"sms_verification.sent.header": "Verification code",
|
||||
"sms_verification.success": "A verification code has been sent to your phone number.",
|
||||
"snackbar.view": "View",
|
||||
"toast.view": "View",
|
||||
"soapbox_config.authenticated_profile_hint": "Users must be logged-in to view replies and media on user profiles.",
|
||||
"soapbox_config.authenticated_profile_label": "Profiles require authentication",
|
||||
"soapbox_config.copyright_footer.meta_fields.label_placeholder": "Copyright footer",
|
||||
|
|
|
@ -1016,7 +1016,7 @@
|
|||
"sms_verification.sent.body": "We sent you a 6-digit code via SMS. Enter it below.",
|
||||
"sms_verification.sent.header": "Verification code",
|
||||
"sms_verification.success": "A verification code has been sent to your phone number.",
|
||||
"snackbar.view": "View",
|
||||
"toast.view": "View",
|
||||
"soapbox_config.authenticated_profile_hint": "Users must be logged-in to view replies and media on user profiles.",
|
||||
"soapbox_config.authenticated_profile_label": "Profiles require authentication",
|
||||
"soapbox_config.copyright_footer.meta_fields.label_placeholder": "Copyright footer",
|
||||
|
|
|
@ -1016,7 +1016,7 @@
|
|||
"sms_verification.sent.body": "We sent you a 6-digit code via SMS. Enter it below.",
|
||||
"sms_verification.sent.header": "Verification code",
|
||||
"sms_verification.success": "A verification code has been sent to your phone number.",
|
||||
"snackbar.view": "View",
|
||||
"toast.view": "View",
|
||||
"soapbox_config.authenticated_profile_hint": "Users must be logged-in to view replies and media on user profiles.",
|
||||
"soapbox_config.authenticated_profile_label": "Profiles require authentication",
|
||||
"soapbox_config.copyright_footer.meta_fields.label_placeholder": "Peu de pàgina dels drets d'autor",
|
||||
|
|
|
@ -1016,7 +1016,7 @@
|
|||
"sms_verification.sent.body": "We sent you a 6-digit code via SMS. Enter it below.",
|
||||
"sms_verification.sent.header": "Verification code",
|
||||
"sms_verification.success": "A verification code has been sent to your phone number.",
|
||||
"snackbar.view": "View",
|
||||
"toast.view": "View",
|
||||
"soapbox_config.authenticated_profile_hint": "Users must be logged-in to view replies and media on user profiles.",
|
||||
"soapbox_config.authenticated_profile_label": "Profiles require authentication",
|
||||
"soapbox_config.copyright_footer.meta_fields.label_placeholder": "Copyright footer",
|
||||
|
|
|
@ -1016,7 +1016,7 @@
|
|||
"sms_verification.sent.body": "We sent you a 6-digit code via SMS. Enter it below.",
|
||||
"sms_verification.sent.header": "Verification code",
|
||||
"sms_verification.success": "A verification code has been sent to your phone number.",
|
||||
"snackbar.view": "View",
|
||||
"toast.view": "View",
|
||||
"soapbox_config.authenticated_profile_hint": "Pouze přihlášení uživatelé mohou prohlížet odpovědi a média na uživatelských profilech.",
|
||||
"soapbox_config.authenticated_profile_label": "Profily vyžadují přihlášení",
|
||||
"soapbox_config.copyright_footer.meta_fields.label_placeholder": "Copyright v zápatí",
|
||||
|
|
|
@ -1016,7 +1016,7 @@
|
|||
"sms_verification.sent.body": "We sent you a 6-digit code via SMS. Enter it below.",
|
||||
"sms_verification.sent.header": "Verification code",
|
||||
"sms_verification.success": "A verification code has been sent to your phone number.",
|
||||
"snackbar.view": "View",
|
||||
"toast.view": "View",
|
||||
"soapbox_config.authenticated_profile_hint": "Users must be logged-in to view replies and media on user profiles.",
|
||||
"soapbox_config.authenticated_profile_label": "Profiles require authentication",
|
||||
"soapbox_config.copyright_footer.meta_fields.label_placeholder": "Copyright footer",
|
||||
|
|
|
@ -1016,7 +1016,7 @@
|
|||
"sms_verification.sent.body": "We sent you a 6-digit code via SMS. Enter it below.",
|
||||
"sms_verification.sent.header": "Verification code",
|
||||
"sms_verification.success": "A verification code has been sent to your phone number.",
|
||||
"snackbar.view": "View",
|
||||
"toast.view": "View",
|
||||
"soapbox_config.authenticated_profile_hint": "Users must be logged-in to view replies and media on user profiles.",
|
||||
"soapbox_config.authenticated_profile_label": "Profiles require authentication",
|
||||
"soapbox_config.copyright_footer.meta_fields.label_placeholder": "Copyright footer",
|
||||
|
|
|
@ -1016,7 +1016,7 @@
|
|||
"sms_verification.sent.body": "We sent you a 6-digit code via SMS. Enter it below.",
|
||||
"sms_verification.sent.header": "Verification code",
|
||||
"sms_verification.success": "A verification code has been sent to your phone number.",
|
||||
"snackbar.view": "Anzeigen",
|
||||
"toast.view": "Anzeigen",
|
||||
"soapbox_config.authenticated_profile_hint": "Nur angemeldete Nutzer können Antworten und Medien auf Nutzerprofilen sehen.",
|
||||
"soapbox_config.authenticated_profile_label": "Profil erfordert Authentifizierung",
|
||||
"soapbox_config.copyright_footer.meta_fields.label_placeholder": "Copyright Fußnote",
|
||||
|
|
|
@ -74,7 +74,7 @@
|
|||
},
|
||||
{
|
||||
"defaultMessage": "View",
|
||||
"id": "snackbar.view"
|
||||
"id": "toast.view"
|
||||
},
|
||||
{
|
||||
"defaultMessage": "Reply",
|
||||
|
@ -146,7 +146,7 @@
|
|||
},
|
||||
{
|
||||
"defaultMessage": "View",
|
||||
"id": "snackbar.view"
|
||||
"id": "toast.view"
|
||||
}
|
||||
],
|
||||
"path": "app/soapbox/actions/interactions.json"
|
||||
|
|
|
@ -1016,7 +1016,7 @@
|
|||
"sms_verification.sent.body": "We sent you a 6-digit code via SMS. Enter it below.",
|
||||
"sms_verification.sent.header": "Verification code",
|
||||
"sms_verification.success": "A verification code has been sent to your phone number.",
|
||||
"snackbar.view": "View",
|
||||
"toast.view": "View",
|
||||
"soapbox_config.authenticated_profile_hint": "Users must be logged-in to view replies and media on user profiles.",
|
||||
"soapbox_config.authenticated_profile_label": "Profiles require authentication",
|
||||
"soapbox_config.copyright_footer.meta_fields.label_placeholder": "Copyright footer",
|
||||
|
|
|
@ -1016,7 +1016,7 @@
|
|||
"sms_verification.sent.body": "We sent you a 6-digit code via SMS. Enter it below.",
|
||||
"sms_verification.sent.header": "Verification code",
|
||||
"sms_verification.success": "A verification code has been sent to your phone number.",
|
||||
"snackbar.view": "View",
|
||||
"toast.view": "View",
|
||||
"soapbox_config.authenticated_profile_hint": "𐑿𐑟𐑼𐑟 𐑥𐑳𐑕𐑑 𐑚𐑰 𐑤𐑪𐑜𐑛-𐑦𐑯 𐑑 𐑝𐑿 𐑮𐑦𐑐𐑤𐑲𐑟 𐑯 𐑥𐑰𐑛𐑾 𐑪𐑯 𐑿𐑟𐑼 𐑐𐑮𐑴𐑓𐑲𐑤𐑟.",
|
||||
"soapbox_config.authenticated_profile_label": "𐑐𐑮𐑴𐑓𐑲𐑤𐑟 𐑮𐑦𐑒𐑢𐑲𐑼 𐑷𐑔𐑧𐑯𐑑𐑦𐑒𐑱𐑖𐑩𐑯",
|
||||
"soapbox_config.copyright_footer.meta_fields.label_placeholder": "Copyright footer",
|
||||
|
|
|
@ -1016,7 +1016,7 @@
|
|||
"sms_verification.sent.body": "We sent you a 6-digit code via SMS. Enter it below.",
|
||||
"sms_verification.sent.header": "Verification code",
|
||||
"sms_verification.success": "A verification code has been sent to your phone number.",
|
||||
"snackbar.view": "View",
|
||||
"toast.view": "View",
|
||||
"soapbox_config.authenticated_profile_hint": "Users must be logged-in to view replies and media on user profiles.",
|
||||
"soapbox_config.authenticated_profile_label": "Profiles require authentication",
|
||||
"soapbox_config.copyright_footer.meta_fields.label_placeholder": "Copyright footer",
|
||||
|
|
|
@ -1016,7 +1016,7 @@
|
|||
"sms_verification.sent.body": "We sent you a 6-digit code via SMS. Enter it below.",
|
||||
"sms_verification.sent.header": "Verification code",
|
||||
"sms_verification.success": "A verification code has been sent to your phone number.",
|
||||
"snackbar.view": "View",
|
||||
"toast.view": "View",
|
||||
"soapbox_config.authenticated_profile_hint": "Users must be logged-in to view replies and media on user profiles.",
|
||||
"soapbox_config.authenticated_profile_label": "Profiles require authentication",
|
||||
"soapbox_config.copyright_footer.meta_fields.label_placeholder": "Copyright footer",
|
||||
|
|
|
@ -1016,7 +1016,7 @@
|
|||
"sms_verification.sent.body": "We sent you a 6-digit code via SMS. Enter it below.",
|
||||
"sms_verification.sent.header": "Verification code",
|
||||
"sms_verification.success": "A verification code has been sent to your phone number.",
|
||||
"snackbar.view": "View",
|
||||
"toast.view": "View",
|
||||
"soapbox_config.authenticated_profile_hint": "Users must be logged-in to view replies and media on user profiles.",
|
||||
"soapbox_config.authenticated_profile_label": "Profiles require authentication",
|
||||
"soapbox_config.copyright_footer.meta_fields.label_placeholder": "Copyright footer",
|
||||
|
|
|
@ -1016,7 +1016,7 @@
|
|||
"sms_verification.sent.body": "We sent you a 6-digit code via SMS. Enter it below.",
|
||||
"sms_verification.sent.header": "Verification code",
|
||||
"sms_verification.success": "A verification code has been sent to your phone number.",
|
||||
"snackbar.view": "View",
|
||||
"toast.view": "View",
|
||||
"soapbox_config.authenticated_profile_hint": "Users must be logged-in to view replies and media on user profiles.",
|
||||
"soapbox_config.authenticated_profile_label": "Profiles require authentication",
|
||||
"soapbox_config.copyright_footer.meta_fields.label_placeholder": "Copyright footer",
|
||||
|
|
|
@ -1016,7 +1016,7 @@
|
|||
"sms_verification.sent.body": "We sent you a 6-digit code via SMS. Enter it below.",
|
||||
"sms_verification.sent.header": "Verification code",
|
||||
"sms_verification.success": "A verification code has been sent to your phone number.",
|
||||
"snackbar.view": "View",
|
||||
"toast.view": "View",
|
||||
"soapbox_config.authenticated_profile_hint": "Users must be logged-in to view replies and media on user profiles.",
|
||||
"soapbox_config.authenticated_profile_label": "Profiles require authentication",
|
||||
"soapbox_config.copyright_footer.meta_fields.label_placeholder": "Copyright footer",
|
||||
|
|
|
@ -1016,7 +1016,7 @@
|
|||
"sms_verification.sent.body": "We sent you a 6-digit code via SMS. Enter it below.",
|
||||
"sms_verification.sent.header": "Verification code",
|
||||
"sms_verification.success": "A verification code has been sent to your phone number.",
|
||||
"snackbar.view": "View",
|
||||
"toast.view": "View",
|
||||
"soapbox_config.authenticated_profile_hint": "Users must be logged-in to view replies and media on user profiles.",
|
||||
"soapbox_config.authenticated_profile_label": "Profiles require authentication",
|
||||
"soapbox_config.copyright_footer.meta_fields.label_placeholder": "Copyright footer",
|
||||
|
|
|
@ -1016,7 +1016,7 @@
|
|||
"sms_verification.sent.body": "We sent you a 6-digit code via SMS. Enter it below.",
|
||||
"sms_verification.sent.header": "Verification code",
|
||||
"sms_verification.success": "A verification code has been sent to your phone number.",
|
||||
"snackbar.view": "View",
|
||||
"toast.view": "View",
|
||||
"soapbox_config.authenticated_profile_hint": "Users must be logged-in to view replies and media on user profiles.",
|
||||
"soapbox_config.authenticated_profile_label": "Profiles require authentication",
|
||||
"soapbox_config.copyright_footer.meta_fields.label_placeholder": "Copyright footer",
|
||||
|
|
|
@ -1016,7 +1016,7 @@
|
|||
"sms_verification.sent.body": "We sent you a 6-digit code via SMS. Enter it below.",
|
||||
"sms_verification.sent.header": "Verification code",
|
||||
"sms_verification.success": "A verification code has been sent to your phone number.",
|
||||
"snackbar.view": "View",
|
||||
"toast.view": "View",
|
||||
"soapbox_config.authenticated_profile_hint": "Users must be logged-in to view replies and media on user profiles.",
|
||||
"soapbox_config.authenticated_profile_label": "Profiles require authentication",
|
||||
"soapbox_config.copyright_footer.meta_fields.label_placeholder": "Copyright footer",
|
||||
|
|
|
@ -1016,7 +1016,7 @@
|
|||
"sms_verification.sent.body": "We sent you a 6-digit code via SMS. Enter it below.",
|
||||
"sms_verification.sent.header": "Verification code",
|
||||
"sms_verification.success": "A verification code has been sent to your phone number.",
|
||||
"snackbar.view": "View",
|
||||
"toast.view": "View",
|
||||
"soapbox_config.authenticated_profile_hint": "Users must be logged-in to view replies and media on user profiles.",
|
||||
"soapbox_config.authenticated_profile_label": "Profiles require authentication",
|
||||
"soapbox_config.copyright_footer.meta_fields.label_placeholder": "Copyright footer",
|
||||
|
|
|
@ -1016,7 +1016,7 @@
|
|||
"sms_verification.sent.body": "We sent you a 6-digit code via SMS. Enter it below.",
|
||||
"sms_verification.sent.header": "Verification code",
|
||||
"sms_verification.success": "A verification code has been sent to your phone number.",
|
||||
"snackbar.view": "View",
|
||||
"toast.view": "View",
|
||||
"soapbox_config.authenticated_profile_hint": "Users must be logged-in to view replies and media on user profiles.",
|
||||
"soapbox_config.authenticated_profile_label": "Profiles require authentication",
|
||||
"soapbox_config.copyright_footer.meta_fields.label_placeholder": "Copyright footer",
|
||||
|
|
|
@ -1016,7 +1016,7 @@
|
|||
"sms_verification.sent.body": "We sent you a 6-digit code via SMS. Enter it below.",
|
||||
"sms_verification.sent.header": "Verification code",
|
||||
"sms_verification.success": "A verification code has been sent to your phone number.",
|
||||
"snackbar.view": "View",
|
||||
"toast.view": "View",
|
||||
"soapbox_config.authenticated_profile_hint": "Users must be logged-in to view replies and media on user profiles.",
|
||||
"soapbox_config.authenticated_profile_label": "Profiles require authentication",
|
||||
"soapbox_config.copyright_footer.meta_fields.label_placeholder": "Copyright footer",
|
||||
|
|
|
@ -1016,7 +1016,7 @@
|
|||
"sms_verification.sent.body": "We sent you a 6-digit code via SMS. Enter it below.",
|
||||
"sms_verification.sent.header": "Verification code",
|
||||
"sms_verification.success": "A verification code has been sent to your phone number.",
|
||||
"snackbar.view": "View",
|
||||
"toast.view": "View",
|
||||
"soapbox_config.authenticated_profile_hint": "משתמשים חייבים להיות מחוברים כדי לראות תגובות ומדיה בפרופילי משתמש.",
|
||||
"soapbox_config.authenticated_profile_label": "פרופילים שדורשים אימות",
|
||||
"soapbox_config.copyright_footer.meta_fields.label_placeholder": "כותרת תחתונה של זכויות יוצרים",
|
||||
|
|
|
@ -1016,7 +1016,7 @@
|
|||
"sms_verification.sent.body": "We sent you a 6-digit code via SMS. Enter it below.",
|
||||
"sms_verification.sent.header": "Verification code",
|
||||
"sms_verification.success": "A verification code has been sent to your phone number.",
|
||||
"snackbar.view": "View",
|
||||
"toast.view": "View",
|
||||
"soapbox_config.authenticated_profile_hint": "Users must be logged-in to view replies and media on user profiles.",
|
||||
"soapbox_config.authenticated_profile_label": "Profiles require authentication",
|
||||
"soapbox_config.copyright_footer.meta_fields.label_placeholder": "Copyright footer",
|
||||
|
|
|
@ -1016,7 +1016,7 @@
|
|||
"sms_verification.sent.body": "We sent you a 6-digit code via SMS. Enter it below.",
|
||||
"sms_verification.sent.header": "Verification code",
|
||||
"sms_verification.success": "A verification code has been sent to your phone number.",
|
||||
"snackbar.view": "View",
|
||||
"toast.view": "View",
|
||||
"soapbox_config.authenticated_profile_hint": "Users must be logged-in to view replies and media on user profiles.",
|
||||
"soapbox_config.authenticated_profile_label": "Profiles require authentication",
|
||||
"soapbox_config.copyright_footer.meta_fields.label_placeholder": "Copyright footer",
|
||||
|
|
|
@ -1016,7 +1016,7 @@
|
|||
"sms_verification.sent.body": "We sent you a 6-digit code via SMS. Enter it below.",
|
||||
"sms_verification.sent.header": "Verification code",
|
||||
"sms_verification.success": "A verification code has been sent to your phone number.",
|
||||
"snackbar.view": "View",
|
||||
"toast.view": "View",
|
||||
"soapbox_config.authenticated_profile_hint": "Users must be logged-in to view replies and media on user profiles.",
|
||||
"soapbox_config.authenticated_profile_label": "Profiles require authentication",
|
||||
"soapbox_config.copyright_footer.meta_fields.label_placeholder": "Copyright footer",
|
||||
|
|
|
@ -1016,7 +1016,7 @@
|
|||
"sms_verification.sent.body": "We sent you a 6-digit code via SMS. Enter it below.",
|
||||
"sms_verification.sent.header": "Verification code",
|
||||
"sms_verification.success": "A verification code has been sent to your phone number.",
|
||||
"snackbar.view": "View",
|
||||
"toast.view": "View",
|
||||
"soapbox_config.authenticated_profile_hint": "Users must be logged-in to view replies and media on user profiles.",
|
||||
"soapbox_config.authenticated_profile_label": "Profiles require authentication",
|
||||
"soapbox_config.copyright_footer.meta_fields.label_placeholder": "Copyright footer",
|
||||
|
|
|
@ -1016,7 +1016,7 @@
|
|||
"sms_verification.sent.body": "We sent you a 6-digit code via SMS. Enter it below.",
|
||||
"sms_verification.sent.header": "Verification code",
|
||||
"sms_verification.success": "A verification code has been sent to your phone number.",
|
||||
"snackbar.view": "View",
|
||||
"toast.view": "View",
|
||||
"soapbox_config.authenticated_profile_hint": "Users must be logged-in to view replies and media on user profiles.",
|
||||
"soapbox_config.authenticated_profile_label": "Profiles require authentication",
|
||||
"soapbox_config.copyright_footer.meta_fields.label_placeholder": "Copyright footer",
|
||||
|
|
|
@ -1016,7 +1016,7 @@
|
|||
"sms_verification.sent.body": "We sent you a 6-digit code via SMS. Enter it below.",
|
||||
"sms_verification.sent.header": "Verification code",
|
||||
"sms_verification.success": "A verification code has been sent to your phone number.",
|
||||
"snackbar.view": "View",
|
||||
"toast.view": "View",
|
||||
"soapbox_config.authenticated_profile_hint": "Users must be logged-in to view replies and media on user profiles.",
|
||||
"soapbox_config.authenticated_profile_label": "Profiles require authentication",
|
||||
"soapbox_config.copyright_footer.meta_fields.label_placeholder": "Copyright footer",
|
||||
|
|
|
@ -1016,7 +1016,7 @@
|
|||
"sms_verification.sent.body": "We sent you a 6-digit code via SMS. Enter it below.",
|
||||
"sms_verification.sent.header": "Verification code",
|
||||
"sms_verification.success": "A verification code has been sent to your phone number.",
|
||||
"snackbar.view": "Skoða",
|
||||
"toast.view": "Skoða",
|
||||
"soapbox_config.authenticated_profile_hint": "Notendur verða að vera skráðir inn til að sjá svör og myndefni á notandasniðum.",
|
||||
"soapbox_config.authenticated_profile_label": "Snið krefst auðkenningar",
|
||||
"soapbox_config.copyright_footer.meta_fields.label_placeholder": "Höfundarréttarfótur",
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Ładowanie…
Reference in New Issue