feat: add inbox view, to be tested with publishing

This commit is contained in:
plebeius.eth
2023-12-09 18:01:08 +01:00
parent 3c3a4f5e76
commit e90115b748
10 changed files with 102 additions and 41 deletions

View File

@@ -2,6 +2,8 @@ import { useEffect } from 'react';
import { Outlet, Route, Routes } from 'react-router-dom';
import useTheme from './hooks/use-theme';
import styles from './app.module.css';
import All from './views/all';
import Inbox from './views/inbox';
import Home from './views/home';
import PendingPost from './views/pending-post';
import Post from './views/post';
@@ -16,7 +18,6 @@ import ChallengeModal from './components/challenge-modal';
import Header from './components/header';
import StickyHeader from './components/sticky-header';
import TopBar from './components/topbar';
import All from './views/all';
function App() {
const [theme] = useTheme();
@@ -63,8 +64,8 @@ function App() {
<Route path='/p/:subplebbitAddress/:sortType?/:timeFilterName?' element={<Subplebbit />} />
<Route path='/profile' element={<Profile />} />
<Route path='/profile/:sortType?' element={<Profile />} />
<Route path='u/:authorAddress/c/:commentCid?' element={<Author/>} />
<Route path='u/:authorAddress/c/:commentCid?/:sortType?' element={<Author/>} />
<Route path='u/:authorAddress/c/:commentCid?' element={<Author />} />
<Route path='u/:authorAddress/c/:commentCid?/:sortType?' element={<Author />} />
</Route>
<Route element={pagesLayout}>
<Route path='/submit' element={<Submit />} />
@@ -76,6 +77,7 @@ function App() {
<Route path='/profile/:accountCommentIndex' element={<PendingPost />} />
<Route path='/profile/about' element={<About />} />
<Route path='/u/:authorAddress/c/:commentCid/about' element={<About />} />
<Route path='/inbox' element={<Inbox />} />
</Route>
</Route>
</Routes>

View File

@@ -29,8 +29,8 @@ const AccountBar = () => {
const accountDropdownClass = isAccountDropdownOpen ? styles.visible : styles.hidden;
const accountSelectButtonRef = useRef<HTMLDivElement>(null);
const [isMailUnread, setIsMailUnread] = useState(false);
const mailClass = isMailUnread ? styles.mailIconUnread : styles.mailIconRead;
const unreadNotificationCount = account?.unreadNotificationCount ? ` (${account.unreadNotificationCount})` : '';
const mailClass = unreadNotificationCount ? styles.mailIconUnread : styles.mailIconRead;
let submitLink = '/submit';
if (isSubplebbit) {
@@ -100,16 +100,9 @@ const AccountBar = () => {
</Link>
</span>
<span className={styles.separator}>|</span>
<Link
to='/settings'
className={styles.iconButton}
onClick={(e) => {
e.preventDefault();
setIsMailUnread(!isMailUnread);
}}
>
<Link to='/inbox' className={styles.iconButton}>
<span className={`${styles.mailIcon} ${mailClass}`} />
{isMailUnread && <span className={styles.mailUnreadCount}>1</span>}
{unreadNotificationCount && <span className={styles.mailUnreadCount}>{unreadNotificationCount}</span>}
</Link>
<span className={styles.searchButton}>
<span className={styles.separator}>|</span>

View File

@@ -9,6 +9,7 @@ import {
isAllView,
isAuthorView,
isHomeView,
isInboxView,
isPostView,
isSettingsView,
isSubplebbitView,
@@ -88,16 +89,13 @@ const SortItems = () => {
const sortLink = isSubplebbit ? `/p/${params.subplebbitAddress}/${choice}` : isAll ? `p/all/${choice}` : choice;
return (
<li key={choice}>
<Link
to={sortLink}
className={selectedSortType === choice ? styles.selected : styles.choice}
onClick={() => handleSelect(choice)}
>
{sortLabels[index]}
</Link>
</li>
)});
<li key={choice}>
<Link to={sortLink} className={selectedSortType === choice ? styles.selected : styles.choice} onClick={() => handleSelect(choice)}>
{sortLabels[index]}
</Link>
</li>
);
});
};
const AuthorHeaderTabs = () => {
@@ -202,6 +200,7 @@ const Header = () => {
const isAll = isAllView(location.pathname);
const isAuthor = isAuthorView(location.pathname);
const isHome = isHomeView(location.pathname, params);
const isInbox = isInboxView(location.pathname);
const isPost = isPostView(location.pathname, params);
const isProfile = isProfileView(location.pathname);
const isSettings = isSettingsView(location.pathname);
@@ -209,7 +208,7 @@ const Header = () => {
const isSubmit = isSubmitView(location.pathname);
const isSubplebbitSubmit = isSubplebbitSubmitView(location.pathname, params);
const hasFewTabs = isPost || isSubmit || isSubplebbitSubmit || isSettings;
const hasFewTabs = isPost || isSubmit || isSubplebbitSubmit || isSettings || isInbox;
const hasStickyHeader = isHome || (isSubplebbit && !isSubplebbitSubmit && !isPost && !isAbout) || (isProfile && !isAbout) || isAll || (isAuthor && !isAbout);
const logoSrc = isSubplebbit ? suggested?.avatarUrl : isProfile ? imageUrl : '/assets/logo/seedit.png';
const logoIsAvatar = (isSubplebbit && suggested?.avatarUrl) || (isProfile && imageUrl);

View File

@@ -88,7 +88,7 @@ const Sidebar = ({ address, cid, createdAt, description, downvoteCount = 0, role
const isPost = isPostView(location.pathname, params);
const subplebbitCreator = findSubplebbitCreator(roles);
const creatorAddress = subplebbitCreator === 'anonymous' ? 'anonymous' : `${getShortAddress(subplebbitCreator)}`;
const submitRoute = (isHome || isAll) ? '/submit' : `/p/${address}/submit`;
const submitRoute = isHome || isAll ? '/submit' : `/p/${address}/submit`;
return (
<div className={`${isAbout ? styles.about : styles.sidebar}`}>

View File

@@ -46,7 +46,11 @@ const TopBar = () => {
const [selectedSortType, setSelectedSortType] = useState(params.sortType || '/hot');
const getTimeFilterLink = (choice: string) => {
return isSubplebbit ? `/p/${params.subplebbitAddress}/${selectedSortType}/${choice}` : isAll ? `p/all/${selectedSortType}/${choice}` : `/${selectedSortType}/${choice}`;
return isSubplebbit
? `/p/${params.subplebbitAddress}/${selectedSortType}/${choice}`
: isAll
? `p/all/${selectedSortType}/${choice}`
: `/${selectedSortType}/${choice}`;
};
const getSelectedSortLabel = () => {

View File

@@ -42,6 +42,10 @@ export const isHomeView = (pathname: string, params: ParamsType): boolean => {
return pathname === '/' || (sortTypes.includes(pathname) || (timeFilterNames.includes(params.timeFilterName as TimeFilterKey) && !pathname.startsWith('/p/all')));
};
export const isInboxView = (pathname: string): boolean => {
return pathname.startsWith('/inbox');
}
export const isPendingView = (pathname: string, params: ParamsType): boolean => {
return pathname === `/profile/${params.accountCommentIndex}`;
};

View File

@@ -1,21 +1,21 @@
import { useEffect, useRef } from "react";
import { useNavigate, useParams } from "react-router-dom";
import { useAuthorComments } from "@plebbit/plebbit-react-hooks"
import { StateSnapshot, Virtuoso, VirtuosoHandle } from "react-virtuoso";
import styles from "./author.module.css";
import AuthorSidebar from "../../components/author-sidebar";
import Post from "../../components/post";
import { useEffect, useRef } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { useAuthorComments } from '@plebbit/plebbit-react-hooks';
import { StateSnapshot, Virtuoso, VirtuosoHandle } from 'react-virtuoso';
import styles from './author.module.css';
import AuthorSidebar from '../../components/author-sidebar';
import Post from '../../components/post';
const lastVirtuosoStates: { [key: string]: StateSnapshot } = {};
const Loading = () => 'loading...'
const Loading = () => 'loading...';
const Author = () => {
const { authorAddress, commentCid, sortType } = useParams();
const navigate = useNavigate();
const {authorComments, lastCommentCid, hasMore, loadMore} = useAuthorComments({commentCid, authorAddress});
const { authorComments, lastCommentCid, hasMore, loadMore } = useAuthorComments({ commentCid, authorAddress });
const Footer = hasMore ? Loading : undefined
const Footer = hasMore ? Loading : undefined;
const virtuosoRef = useRef<VirtuosoHandle | null>(null);
@@ -37,9 +37,9 @@ const Author = () => {
// always redirect to latest author cid
useEffect(() => {
if (authorAddress && lastCommentCid && commentCid && lastCommentCid !== commentCid) {
navigate(`/u/${authorAddress}/c/${lastCommentCid}`, {replace: true})
navigate(`/u/${authorAddress}/c/${lastCommentCid}`, { replace: true });
}
}, [authorAddress, lastCommentCid, commentCid, navigate])
}, [authorAddress, lastCommentCid, commentCid, navigate]);
return (
<div className={styles.content}>
@@ -51,9 +51,9 @@ const Author = () => {
increaseViewportBy={{ bottom: 1200, top: 600 }}
totalCount={authorComments?.length || 0}
data={authorComments}
itemContent={(index, post) => post ? <Post index={index} post={post} /> : null}
itemContent={(index, post) => (post ? <Post index={index} post={post} /> : null)}
useWindowScroll={true}
components={{Footer}}
components={{ Footer }}
endReached={loadMore}
ref={virtuosoRef}
restoreStateFrom={lastVirtuosoState}

View File

58
src/views/inbox/inbox.tsx Normal file
View File

@@ -0,0 +1,58 @@
import { useEffect, useRef } from 'react';
import { StateSnapshot, Virtuoso, VirtuosoHandle } from 'react-virtuoso';
import { useAccount, useNotifications } from '@plebbit/plebbit-react-hooks';
import styles from './inbox.module.css';
import Post from '../../components/post';
const lastVirtuosoStates: { [key: string]: StateSnapshot } = {};
const Inbox = () => {
const account = useAccount();
const { unreadNotificationCount } = account || {};
const { notifications, markAsRead } = useNotifications();
const virtuosoRef = useRef<VirtuosoHandle | null>(null);
useEffect(() => {
const setLastVirtuosoState = () =>
virtuosoRef.current?.getState((snapshot) => {
// TODO: not sure if checking for empty snapshot.ranges works for all scenarios
if (snapshot?.ranges?.length) {
lastVirtuosoStates[unreadNotificationCount] = snapshot;
}
});
window.addEventListener('scroll', setLastVirtuosoState);
// clean listener on unmount
return () => window.removeEventListener('scroll', setLastVirtuosoState);
}, [unreadNotificationCount]);
const lastVirtuosoState = lastVirtuosoStates?.[unreadNotificationCount];
if (account && !notifications.length) {
return 'empty';
}
return (
<div>
<button onClick={markAsRead} disabled={!unreadNotificationCount} className={styles.markAsReadButton}>
mark as read
</button>
<Virtuoso
increaseViewportBy={{ bottom: 1200, top: 600 }}
totalCount={notifications?.length || 0}
data={notifications}
itemContent={(index, notification) => (
<div className={notification.markedAsRead === false ? styles.unreadNotification : styles.readNotification}>
<Post index={index} post={notification} />
</div>
)}
useWindowScroll={true}
ref={virtuosoRef}
restoreStateFrom={lastVirtuosoState}
initialScrollTop={lastVirtuosoState?.scrollTop}
/>
</div>
);
};
export default Inbox;

1
src/views/inbox/index.ts Normal file
View File

@@ -0,0 +1 @@
export {default} from './inbox';