mirror of
https://github.com/plebbit/seedit.git
synced 2026-04-21 23:58:21 -04:00
feat: add inbox view, to be tested with publishing
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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}`}>
|
||||
|
||||
@@ -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 = () => {
|
||||
|
||||
@@ -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}`;
|
||||
};
|
||||
|
||||
@@ -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}
|
||||
|
||||
0
src/views/inbox/inbox.module.css
Normal file
0
src/views/inbox/inbox.module.css
Normal file
58
src/views/inbox/inbox.tsx
Normal file
58
src/views/inbox/inbox.tsx
Normal 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
1
src/views/inbox/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export {default} from './inbox';
|
||||
Reference in New Issue
Block a user