Merge branch 'improve-toasts' into 'develop'

Introduce Toast module as Snackbar replacement

See merge request soapbox-pub/soapbox!2023
environments/review-develop-3zknud/deployments/1816
Chewbacca 2022-12-21 19:47:35 +00:00
commit a9e05859d1
145 zmienionych plików z 741 dodań i 903 usunięć

Wyświetl plik

@ -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.

Wyświetl plik

@ -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.');
});
});
});
});

Wyświetl plik

@ -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);
});
});
});

Wyświetl plik

@ -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 },
];

Wyświetl plik

@ -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,
};

Wyświetl plik

@ -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);
})

Wyświetl plik

@ -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);
});
};

Wyświetl plik

@ -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 });

Wyświetl plik

@ -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)));
};

Wyświetl plik

@ -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 });

Wyświetl plik

@ -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 });
});

Wyświetl plik

@ -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 });

Wyświetl plik

@ -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));
});

Wyświetl plik

@ -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[]) => ({

Wyświetl plik

@ -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();
},

Wyświetl plik

@ -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;

Wyświetl plik

@ -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);
});
};

Wyświetl plik

@ -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,
};

Wyświetl plik

@ -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),
}));
};

Wyświetl plik

@ -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({

Wyświetl plik

@ -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';

Wyświetl plik

@ -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;

Wyświetl plik

@ -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>

Wyświetl plik

@ -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(() => { });
}
};

Wyświetl plik

@ -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(() => {});
};

Wyświetl plik

@ -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(() => {});
};

Wyświetl plik

@ -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);
}));
};

Wyświetl plik

@ -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);
});

Wyświetl plik

@ -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) {

Wyświetl plik

@ -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]);

Wyświetl plik

@ -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));
}
};

Wyświetl plik

@ -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>

Wyświetl plik

@ -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);
});
};

Wyświetl plik

@ -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]);

Wyświetl plik

@ -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]);

Wyświetl plik

@ -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();

Wyświetl plik

@ -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,
// }),
);
}
});

Wyświetl plik

@ -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);

Wyświetl plik

@ -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));
});
};

Wyświetl plik

@ -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);
});

Wyświetl plik

@ -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);

Wyświetl plik

@ -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);
}
});
};

Wyświetl plik

@ -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);

Wyświetl plik

@ -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);
}
});
};

Wyświetl plik

@ -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]);

Wyświetl plik

@ -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));
});
}, []);

Wyświetl plik

@ -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 }));
});

Wyświetl plik

@ -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);
});

Wyświetl plik

@ -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'),

Wyświetl plik

@ -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(() => {});
};

Wyświetl plik

@ -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(() => {});

Wyświetl plik

@ -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();

Wyświetl plik

@ -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));
}
});
};

Wyświetl plik

@ -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(() => {

Wyświetl plik

@ -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) {

Wyświetl plik

@ -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;

Wyświetl plik

@ -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');
}

Wyświetl plik

@ -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]);

Wyświetl plik

@ -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]);

Wyświetl plik

@ -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: () => {},
},
);

Wyświetl plik

@ -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');

Wyświetl plik

@ -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]);

Wyświetl plik

@ -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);
});
};

Wyświetl plik

@ -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(() => {

Wyświetl plik

@ -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));
};

Wyświetl plik

@ -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>

Wyświetl plik

@ -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);

Wyświetl plik

@ -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": "حاشية حقوق الطبع والنشر",

Wyświetl plik

@ -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",

Wyświetl plik

@ -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",

Wyświetl plik

@ -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",

Wyświetl plik

@ -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",

Wyświetl plik

@ -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",

Wyświetl plik

@ -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",

Wyświetl plik

@ -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í",

Wyświetl plik

@ -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",

Wyświetl plik

@ -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",

Wyświetl plik

@ -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",

Wyświetl plik

@ -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"

Wyświetl plik

@ -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",

Wyświetl plik

@ -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",

Wyświetl plik

@ -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",

Wyświetl plik

@ -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",

Wyświetl plik

@ -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",

Wyświetl plik

@ -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",

Wyświetl plik

@ -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",

Wyświetl plik

@ -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",

Wyświetl plik

@ -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",

Wyświetl plik

@ -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",

Wyświetl plik

@ -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",

Wyświetl plik

@ -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",

Wyświetl plik

@ -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",

Wyświetl plik

@ -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": "כותרת תחתונה של זכויות יוצרים",

Wyświetl plik

@ -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",

Wyświetl plik

@ -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",

Wyświetl plik

@ -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",

Wyświetl plik

@ -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",

Wyświetl plik

@ -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",

Wyświetl plik

@ -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",

Wyświetl plik

@ -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