Add follow requests section in Notifications

This commit is contained in:
Lim Chee Aun 2023-05-06 17:13:39 +08:00
parent d37537c61e
commit dcf7d3c750
6 changed files with 162 additions and 48 deletions

View file

@ -1312,6 +1312,9 @@ body:has(.media-modal-container + .status-deck) .media-post-link {
.tag .icon {
vertical-align: middle;
}
.tag.collapsed {
margin: 0;
}
/* MENU POPUP */

View file

@ -0,0 +1,54 @@
import { useState } from 'preact/hooks';
import { api } from '../utils/api';
import Loader from './loader';
function FollowRequestButtons({ accountID, onChange }) {
const { masto } = api();
const [uiState, setUIState] = useState('default');
return (
<p class="follow-request-buttons">
<button
type="button"
disabled={uiState === 'loading'}
onClick={() => {
setUIState('loading');
(async () => {
try {
await masto.v1.followRequests.authorize(accountID);
onChange();
} catch (e) {
console.error(e);
setUIState('default');
}
})();
}}
>
Accept
</button>{' '}
<button
type="button"
disabled={uiState === 'loading'}
class="light danger"
onClick={() => {
setUIState('loading');
(async () => {
try {
await masto.v1.followRequests.reject(accountID);
onChange();
} catch (e) {
console.error(e);
setUIState('default');
}
})();
}}
>
Reject
</button>
<Loader hidden={uiState !== 'loading'} />
</p>
);
}
export default FollowRequestButtons;

View file

@ -2,6 +2,7 @@ import states from '../utils/states';
import store from '../utils/store';
import Avatar from './avatar';
import FollowRequestButtons from './follow-request-buttons';
import Icon from './icon';
import Link from './link';
import NameText from './name-text';
@ -205,51 +206,4 @@ function Notification({ notification, instance }) {
);
}
function FollowRequestButtons({ accountID, onChange }) {
const { masto } = api();
const [uiState, setUIState] = useState('default');
return (
<p>
<button
type="button"
disabled={uiState === 'loading'}
onClick={() => {
setUIState('loading');
(async () => {
try {
await masto.v1.followRequests.authorize(accountID);
onChange();
} catch (e) {
console.error(e);
setUIState('default');
}
})();
}}
>
Accept
</button>{' '}
<button
type="button"
disabled={uiState === 'loading'}
class="light danger"
onClick={() => {
setUIState('loading');
(async () => {
try {
await masto.v1.followRequests.reject(accountID);
onChange();
} catch (e) {
console.error(e);
setUIState('default');
}
})();
}}
>
Reject
</button>
<Loader hidden={uiState !== 'loading'} />
</p>
);
}
export default Notification;

View file

@ -135,11 +135,20 @@ function NotificationsMenu({ anchorRef, state, onClose }) {
return allNotifications;
}
const [hasFollowRequests, setHasFollowRequests] = useState(false);
function fetchFollowRequests() {
return masto.v1.followRequests.list({
limit: 1,
});
}
function loadNotifications() {
setUIState('loading');
(async () => {
try {
await fetchNotifications();
const followRequests = await fetchFollowRequests();
setHasFollowRequests(!!followRequests?.length);
setUIState('default');
} catch (e) {
setUIState('error');
@ -204,7 +213,15 @@ function NotificationsMenu({ anchorRef, state, onClose }) {
<Icon icon="at" /> <span>Mentions</span>
</Link>
<Link to="/notifications" class="button plain2">
<b>See all</b> <Icon icon="arrow-right" />
{hasFollowRequests ? (
<>
<span class="tag collapsed">New</span>{' '}
<span>Follow Requests</span>
</>
) : (
<b>See all</b>
)}{' '}
<Icon icon="arrow-right" />
</Link>
</footer>
</ControlledMenu>

View file

@ -152,3 +152,41 @@
color: var(--text-color);
background-color: var(--bg-color);
}
.follow-requests {
padding-block-end: 16px;
}
.follow-requests ul {
list-style: none;
padding: 0;
margin: 0;
max-height: 50vh;
max-height: 50dvh;
overflow: auto;
border-bottom: var(--hairline-width) solid var(--outline-color);
}
.follow-requests ul li {
display: flex;
align-items: center;
padding: 16px;
border-bottom: var(--hairline-width) solid var(--outline-color);
justify-content: space-between;
column-gap: 16px;
row-gap: 4px;
flex-wrap: wrap;
}
.follow-requests ul li:last-child {
border-bottom: none;
}
.follow-requests ul li .follow-request-buttons {
margin: 0;
padding: 0;
display: flex;
flex: 1;
gap: 4px;
justify-content: flex-end;
align-items: center;
}
.follow-requests ul li .follow-request-buttons .loader-container {
order: -1;
}

View file

@ -4,6 +4,8 @@ import { memo } from 'preact/compat';
import { useCallback, useEffect, useRef, useState } from 'preact/hooks';
import { useSnapshot } from 'valtio';
import AccountBlock from '../components/account-block';
import FollowRequestButtons from '../components/follow-request-buttons';
import Icon from '../components/icon';
import Link from '../components/link';
import Loader from '../components/loader';
@ -31,6 +33,7 @@ function Notifications() {
scrollableRef,
});
const hiddenUI = scrollDirection === 'end' && !nearReachStart;
const [followRequests, setFollowRequests] = useState([]);
console.debug('RENDER Notifications');
@ -67,12 +70,39 @@ function Notifications() {
return allNotifications;
}
async function fetchFollowRequests() {
const followRequests = await masto.v1.followRequests.list({
limit: 80,
});
// Note: no pagination here yet because this better be on a separate page. Should be rare use-case???
return followRequests;
}
const loadFollowRequests = () => {
setUIState('loading');
(async () => {
try {
const requests = await fetchFollowRequests();
setFollowRequests(requests);
setUIState('default');
} catch (e) {
setUIState('error');
}
})();
};
const loadNotifications = (firstLoad) => {
setUIState('loading');
(async () => {
try {
const { done } = await fetchNotifications(firstLoad);
setShowMore(!done);
if (firstLoad) {
const requests = await fetchFollowRequests();
setFollowRequests(requests);
}
setUIState('default');
} catch (e) {
setUIState('error');
@ -184,6 +214,24 @@ function Notifications() {
</button>
)}
</header>
{followRequests.length > 0 && (
<div class="follow-requests">
<h2 class="timeline-header">Follow requests</h2>
<ul>
{followRequests.map((account) => (
<li>
<AccountBlock account={account} />
<FollowRequestButtons
accountID={account.id}
onChange={() => {
loadFollowRequests();
}}
/>
</li>
))}
</ul>
</div>
)}
<div id="mentions-option">
<label>
<input