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:
Ridhwana 2023-09-07 12:03:41 +02:00 committed by GitHub
parent 4a8dc9abf6
commit 3c34de120b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 105 additions and 27 deletions

View file

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

View file

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

View file

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