Add snackbars to /tags (#20046)
* feat: add snackbars to /tags * chore: update the props for the component" * feat: update snackbar for following * feat: add tests for snackbars * feat: add error handling and show error on snackbar * chore: tweaks to syntax * refactor: abstract out the details * fix: check for null explicitly * refactor: use hidden instead of assigning a var
This commit is contained in:
parent
4a8dc9abf6
commit
3c34de120b
3 changed files with 105 additions and 27 deletions
|
|
@ -1,4 +1,5 @@
|
|||
import { h, render } from 'preact';
|
||||
import { Snackbar } from '../Snackbar/Snackbar';
|
||||
import { getUserDataAndCsrfToken } from '@utilities/getUserDataAndCsrfToken';
|
||||
|
||||
/* global showLoginModal */
|
||||
|
|
@ -62,8 +63,17 @@ function handleButtonClick({ target }) {
|
|||
}
|
||||
}
|
||||
|
||||
function loadSnackbar() {
|
||||
const root = document.getElementsByClassName('tags-index');
|
||||
if (root.length > 0) {
|
||||
render(<Snackbar lifespan="1" />, document.getElementById('snack-zone'));
|
||||
}
|
||||
}
|
||||
|
||||
document.ready.then(() => {
|
||||
const userStatus = document.body.getAttribute('data-user-status');
|
||||
loadSnackbar();
|
||||
|
||||
if (userStatus === 'logged-out') {
|
||||
listenForButtonClicks();
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import { h } from 'preact';
|
||||
import { useState } from 'preact/hooks';
|
||||
import PropTypes from 'prop-types';
|
||||
import { addSnackbarItem } from '../Snackbar';
|
||||
|
||||
/* global browserStoreCache */
|
||||
|
||||
/**
|
||||
|
|
@ -19,29 +21,69 @@ export const Tag = ({ id, name, isFollowing, isHidden }) => {
|
|||
let followingButton;
|
||||
|
||||
const toggleFollowButton = () => {
|
||||
setFollowing(!following);
|
||||
browserStoreCache('remove');
|
||||
postFollowItem({ following: !following, hidden });
|
||||
};
|
||||
const updatedFollowState = !following;
|
||||
|
||||
const toggleHideButton = () => {
|
||||
const updatedHiddenState = !hidden;
|
||||
setHidden(updatedHiddenState);
|
||||
|
||||
// if the tag's new state will be hidden (clicked on the hide button) then we we set it to following.
|
||||
// if the tags new state is to be unhidden (clicked on the unhide button) then we set it to not following.
|
||||
const updatedFollowingState = updatedHiddenState;
|
||||
setFollowing(updatedFollowingState);
|
||||
|
||||
browserStoreCache('remove');
|
||||
postFollowItem({
|
||||
hidden: updatedHiddenState,
|
||||
following: updatedFollowingState,
|
||||
following: updatedFollowState,
|
||||
hidden,
|
||||
}).then((response) => {
|
||||
if (response.ok) {
|
||||
updateItem(
|
||||
null,
|
||||
updatedFollowState,
|
||||
`You have ${following ? 'un' : ''}followed ${name}.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
addSnackbarItem({
|
||||
message: `An error has occurred.`,
|
||||
addCloseButton: true,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const toggleHideButton = () => {
|
||||
// if the tag's new state will be hidden (clicked on the hide button) then we we set it to following.
|
||||
// if the tags new state is to be unhidden (clicked on the unhide button) then we set it to unfollow.
|
||||
const updatedHiddenState = !hidden;
|
||||
const updatedFollowState = updatedHiddenState;
|
||||
|
||||
postFollowItem({
|
||||
hidden: updatedHiddenState,
|
||||
following: updatedFollowState,
|
||||
}).then((response) => {
|
||||
if (response.ok) {
|
||||
updateItem(
|
||||
updatedHiddenState,
|
||||
updatedFollowState,
|
||||
`You have ${hidden ? 'un' : ''}hidden ${name}.`,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
addSnackbarItem({
|
||||
message: `An error has occurred.`,
|
||||
addCloseButton: true,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const updateItem = (updatedHiddenState, updatedFollowState, message) => {
|
||||
if (updatedHiddenState !== null) {
|
||||
setHidden(updatedHiddenState);
|
||||
}
|
||||
setFollowing(updatedFollowState);
|
||||
browserStoreCache('remove');
|
||||
addSnackbarItem({
|
||||
message,
|
||||
addCloseButton: true,
|
||||
});
|
||||
return;
|
||||
};
|
||||
|
||||
const postFollowItem = ({ following, hidden }) => {
|
||||
fetch('/follows', {
|
||||
return fetch('/follows', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
|
|
@ -55,11 +97,6 @@ export const Tag = ({ id, name, isFollowing, isHidden }) => {
|
|||
explicit_points: hidden ? -1 : 1,
|
||||
}),
|
||||
credentials: 'same-origin',
|
||||
}).then((response) => {
|
||||
if (response.status !== 200) {
|
||||
// TODO: replace this with an actual modal
|
||||
alert('An error occurred');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
|
|
@ -98,8 +135,8 @@ export const Tag = ({ id, name, isFollowing, isHidden }) => {
|
|||
};
|
||||
|
||||
Tag.propTypes = {
|
||||
id: PropTypes.number.isRequired,
|
||||
id: PropTypes.string.isRequired,
|
||||
name: PropTypes.string.isRequired,
|
||||
following: PropTypes.bool.isRequired,
|
||||
hidden: PropTypes.bool.isRequired,
|
||||
following: PropTypes.bool,
|
||||
hidden: PropTypes.bool,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -11,7 +11,24 @@ describe('Follow tag', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('Follows and unfollows a tag from the tag index page', () => {
|
||||
it('shows an error message when following a tag fails', () => {
|
||||
cy.intercept('POST', '/follows', {
|
||||
statusCode: 422,
|
||||
body: {
|
||||
error: 'Something went wrong.',
|
||||
},
|
||||
}).as('followsRequest');
|
||||
cy.findByRole('button', { name: 'Follow tag: tag0' }).as('followButton');
|
||||
|
||||
cy.get('@followButton').click();
|
||||
cy.wait('@followsRequest');
|
||||
|
||||
cy.findByTestId('snackbar').within(() => {
|
||||
cy.findByRole('alert').should('have.text', 'An error has occurred.');
|
||||
});
|
||||
});
|
||||
|
||||
it('follows and unfollows a tag from the tag index page', () => {
|
||||
cy.intercept('/follows').as('followsRequest');
|
||||
cy.findByRole('button', { name: 'Follow tag: tag0' }).as('followButton');
|
||||
|
||||
|
|
@ -21,11 +38,19 @@ describe('Follow tag', () => {
|
|||
cy.get('@followButton').should('have.text', 'Following');
|
||||
cy.get('@followButton').should('have.attr', 'aria-pressed', 'true');
|
||||
|
||||
cy.findByTestId('snackbar').within(() => {
|
||||
cy.findByRole('alert').should('have.text', 'You have followed tag0.');
|
||||
});
|
||||
|
||||
cy.get('@followButton').click();
|
||||
cy.wait('@followsRequest');
|
||||
|
||||
cy.get('@followButton').should('have.text', 'Follow');
|
||||
cy.get('@followButton').should('have.attr', 'aria-pressed', 'false');
|
||||
|
||||
cy.findByTestId('snackbar').within(() => {
|
||||
cy.findByRole('alert').should('have.text', 'You have unfollowed tag0.');
|
||||
});
|
||||
});
|
||||
|
||||
it('hides and unhides a tag', () => {
|
||||
|
|
@ -42,6 +67,9 @@ describe('Follow tag', () => {
|
|||
// and remove the Follow button
|
||||
cy.get('@hideButton').should('have.text', 'Unhide');
|
||||
cy.get('@toBeHiddenFollowButton').should('not.exist');
|
||||
cy.findByTestId('snackbar').within(() => {
|
||||
cy.findByRole('alert').should('have.text', 'You have hidden tag0.');
|
||||
});
|
||||
|
||||
// clicking on 'Unhide' should change it back to 'Hide'
|
||||
// and show a 'Follow' button
|
||||
|
|
@ -61,6 +89,9 @@ describe('Follow tag', () => {
|
|||
'aria-pressed',
|
||||
'false',
|
||||
);
|
||||
cy.findByTestId('snackbar').within(() => {
|
||||
cy.findByRole('alert').should('have.text', 'You have unhidden tag0.');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -77,7 +108,7 @@ describe('Follow tag', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('Follows and unfollows a tag from the tag feed page', () => {
|
||||
it('follows and unfollows a tag from the tag feed page', () => {
|
||||
cy.intercept('/follows').as('followsRequest');
|
||||
cy.findByRole('button', { name: 'Follow tag: tag1' }).as('followButton');
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue