diff --git a/src/components/error-display/error-display.module.css b/src/components/error-display/error-display.module.css new file mode 100644 index 00000000..bfd4f307 --- /dev/null +++ b/src/components/error-display/error-display.module.css @@ -0,0 +1,9 @@ +.errorMessage { + color: var(--red); +} + +.showFullErrorButton { + font-weight: 700; + cursor: pointer; + padding: 5px 0; +} diff --git a/src/components/error-display/error-display.tsx b/src/components/error-display/error-display.tsx new file mode 100644 index 00000000..9462fead --- /dev/null +++ b/src/components/error-display/error-display.tsx @@ -0,0 +1,39 @@ +import { useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import styles from './error-display.module.css'; + +const ErrorDisplay = ({ error }: { error: Error | null }) => { + const { t } = useTranslation(); + const [showFullError, setShowFullError] = useState(false); + + return ( + (error?.message || error?.stack) && ( +
+
+ + {error?.message && ( + + {t('error')}: {error.message} + + )} + {error?.stack && ( + <> + {' — '} + setShowFullError(!showFullError)}> + {showFullError ? 'hide' : 'show'} full error + + + )} + + {showFullError && ( + <> +
+
{error.stack}
+ + )} +
+ ) + ); +}; + +export default ErrorDisplay; diff --git a/src/components/error-display/index.ts b/src/components/error-display/index.ts new file mode 100644 index 00000000..ea4eb1f3 --- /dev/null +++ b/src/components/error-display/index.ts @@ -0,0 +1 @@ +export { default } from './error-display'; diff --git a/src/stores/use-error-store.ts b/src/stores/use-error-store.ts new file mode 100644 index 00000000..d6348550 --- /dev/null +++ b/src/stores/use-error-store.ts @@ -0,0 +1,24 @@ +import { create } from 'zustand'; + +interface ErrorStoreState { + errors: Record; + setError: (source: string, error: Error | null | undefined) => void; + clearAllErrors: () => void; +} + +const useErrorStore = create((set) => ({ + errors: {}, + setError: (source, error) => + set((state) => { + const newErrors = { ...state.errors }; + if (error) { + newErrors[source] = error; + } else { + delete newErrors[source]; + } + return { errors: newErrors }; + }), + clearAllErrors: () => set({ errors: {} }), +})); + +export default useErrorStore; diff --git a/src/views/author/author.module.css b/src/views/author/author.module.css index 5b27f046..91f02e02 100644 --- a/src/views/author/author.module.css +++ b/src/views/author/author.module.css @@ -30,4 +30,11 @@ div[data-viewport-type="window"] { display: inline-block; padding: 10px 0 0px 5px; } +} + +.error { + padding-left: 10px; + font-size: 12px; + margin-top: -10px; + padding-bottom: 10px; } \ No newline at end of file diff --git a/src/views/author/author.tsx b/src/views/author/author.tsx index f997fe97..5cb12bb9 100644 --- a/src/views/author/author.tsx +++ b/src/views/author/author.tsx @@ -10,6 +10,7 @@ import Post from '../../components/post'; import Reply from '../../components/reply/'; import AuthorSidebar from '../../components/author-sidebar'; import styles from './author.module.css'; +import ErrorDisplay from '../../components/error-display'; const lastVirtuosoStates: { [key: string]: StateSnapshot } = {}; @@ -24,7 +25,13 @@ const Author = () => { const isInAuthorSubmittedView = isAuthorSubmittedView(location.pathname, params); const isMobile = useWindowWidth() < 640; - const { authorComments, lastCommentCid, hasMore, loadMore } = useAuthorComments({ commentCid, authorAddress }); + const { authorComments, error, lastCommentCid, hasMore, loadMore } = useAuthorComments({ commentCid, authorAddress }); + + useEffect(() => { + if (error) { + console.log(error); + } + }, [error]); const replyComments = useMemo(() => authorComments?.filter((comment) => comment && comment.parentCid) || [], [authorComments]); const postComments = useMemo(() => authorComments?.filter((comment) => comment && !comment.parentCid) || [], [authorComments]); @@ -82,6 +89,11 @@ const Author = () => {
+ {error && ( +
+ +
+ )} { const { t } = useTranslation(); const account = useAccount(); const { unreadNotificationCount } = account || {}; - const { notifications, markAsRead } = useNotifications(); + const { error, notifications, markAsRead } = useNotifications(); const location = useLocation(); const isInInboxCommentRepliesView = isInboxCommentRepliesView(location.pathname); @@ -97,6 +98,12 @@ const Inbox = () => { document.title = documentTitle; }, [documentTitle]); + useEffect(() => { + if (error) { + console.log(error); + } + }, [error]); + return (
@@ -109,6 +116,11 @@ const Inbox = () => {
{t('nothing_found')}
)}
+ {error && ( +
+ +
+ )} { const postComment = useComment({ commentCid: postCid }); + useEffect(() => { + if (error) { + console.log(error); + } + }, [error]); + return ( <> {(deleted || locked || removed) && ( @@ -172,7 +179,7 @@ const Post = ({ post }: { post: Comment }) => { )} {error && (
- {t('error')}: {error.message} +
)} {isSingleComment ? ( @@ -285,6 +292,12 @@ const PostPage = () => { const { hasAcceptedWarning } = useContentOptionsStore(); const isBroadlyNsfwSubplebbit = useIsBroadlyNsfwSubplebbit(subplebbitAddress || ''); + useEffect(() => { + if (post?.error) { + console.log(post.error); + } + }, [post?.error]); + const postTitle = post.title?.slice(0, 40) || post?.content?.slice(0, 40); const subplebbitTitle = subplebbit?.title || subplebbit?.shortAddress; useEffect(() => { @@ -303,6 +316,11 @@ const PostPage = () => { {isInPendingPostView && params?.accountCommentIndex ? : isInPostContextView ? : } + {post?.error && ( +
+ +
+ )} ); }; diff --git a/src/views/profile/profile.module.css b/src/views/profile/profile.module.css index 0e6e3502..480faa33 100644 --- a/src/views/profile/profile.module.css +++ b/src/views/profile/profile.module.css @@ -147,3 +147,10 @@ div[data-viewport-type="window"] { margin-right: -3px; margin-top: -3px; } + +.error { + padding-left: 10px; + font-size: 12px; + margin-top: -10px; + padding-bottom: 10px; +} \ No newline at end of file diff --git a/src/views/profile/profile.tsx b/src/views/profile/profile.tsx index 61601d96..ea71917e 100644 --- a/src/views/profile/profile.tsx +++ b/src/views/profile/profile.tsx @@ -9,6 +9,7 @@ import AuthorSidebar from '../../components/author-sidebar'; import Post from '../../components/post'; import Reply from '../../components/reply'; import styles from './profile.module.css'; +import ErrorDisplay from '../../components/error-display'; const pageSize = 10; const sortTypes: string[] = ['new', 'old']; @@ -135,15 +136,26 @@ const VirtualizedCommentList = ({ comments }: { comments: any[] }) => { const Overview = () => { const { t } = useTranslation(); - const { accountComments } = useAccountComments(); + const { error, accountComments } = useAccountComments(); const [sortType, setSortType] = useState('new'); const sortedComments = useMemo(() => { return [...accountComments].sort((a, b) => (sortType === 'new' ? b.timestamp - a.timestamp : a.timestamp - b.timestamp)); }, [accountComments, sortType]); + useEffect(() => { + if (error) { + console.log(error); + } + }, [error]); + return (
+ {error && ( +
+ +
+ )} {sortedComments.length === 0 ?
{t('nothing_found')}
: }
@@ -152,7 +164,7 @@ const Overview = () => { const Comments = () => { const { t } = useTranslation(); - const { accountComments } = useAccountComments(); + const { error, accountComments } = useAccountComments(); const [sortType, setSortType] = useState('new'); const replyComments = useMemo(() => accountComments?.filter((comment) => comment.parentCid) || [], [accountComments]); @@ -161,8 +173,19 @@ const Comments = () => { return [...replyComments].sort((a, b) => (sortType === 'new' ? b.timestamp - a.timestamp : a.timestamp - b.timestamp)); }, [replyComments, sortType]); + useEffect(() => { + if (error) { + console.log(error); + } + }, [error]); + return (
+ {error && ( +
+ +
+ )} {sortedComments.length === 0 ?
{t('nothing_found')}
: }
@@ -171,7 +194,7 @@ const Comments = () => { const Submitted = () => { const { t } = useTranslation(); - const { accountComments } = useAccountComments(); + const { error, accountComments } = useAccountComments(); const [sortType, setSortType] = useState('new'); const postComments = useMemo(() => accountComments?.filter((comment) => !comment.parentCid) || [], [accountComments]); @@ -180,8 +203,19 @@ const Submitted = () => { return [...postComments].sort((a, b) => (sortType === 'new' ? b.timestamp - a.timestamp : a.timestamp - b.timestamp)); }, [postComments, sortType]); + useEffect(() => { + if (error) { + console.log(error); + } + }, [error]); + return (
+ {error && ( +
+ +
+ )} {sortedComments.length === 0 ?
{t('nothing_found')}
: }
@@ -190,7 +224,7 @@ const Submitted = () => { const VotedComments = ({ voteType }: { voteType: 1 | -1 }) => { const { t } = useTranslation(); - const { accountVotes } = useAccountVotes(); + const { error, accountVotes } = useAccountVotes(); const [currentPage, setCurrentPage] = useState(1); const [sortType, setSortType] = useState('new'); @@ -203,8 +237,19 @@ const VotedComments = ({ voteType }: { voteType: 1 | -1 }) => { const paginatedCids = votedCommentCids.slice((currentPage - 1) * pageSize, currentPage * pageSize); const hasMore = currentPage * pageSize < votedCommentCids.length; + useEffect(() => { + if (error) { + console.log(error); + } + }, [error]); + return (
+ {error && ( +
+ +
+ )} {paginatedCids.length === 0 ?
{t('nothing_found')}
: paginatedCids.map((cid) => )} diff --git a/src/views/subplebbit/subplebbit.tsx b/src/views/subplebbit/subplebbit.tsx index 93925ef6..54fe2ca6 100644 --- a/src/views/subplebbit/subplebbit.tsx +++ b/src/views/subplebbit/subplebbit.tsx @@ -12,6 +12,7 @@ import useFeedResetStore from '../../stores/use-feed-reset-store'; import { usePinnedPostsStore } from '../../stores/use-pinned-posts-store'; import { useIsBroadlyNsfwSubplebbit } from '../../hooks/use-is-broadly-nsfw-subplebbit'; import useTimeFilter, { isValidTimeFilterName } from '../../hooks/use-time-filter'; +import ErrorDisplay from '../../components/error-display'; import LoadingEllipsis from '../../components/loading-ellipsis'; import Over18Warning from '../../components/over-18-warning'; import Post from '../../components/post'; @@ -27,7 +28,6 @@ interface FooterProps { isOnline: boolean; started: boolean; isSubCreatedButNotYetPublished: boolean; - error: Error | null; hasMore: boolean; timeFilterName: string; reset: () => void; @@ -43,7 +43,6 @@ const Footer = ({ isOnline, started, isSubCreatedButNotYetPublished, - error, hasMore, timeFilterName, reset, @@ -85,12 +84,6 @@ const Footer = ({ const loadingString = ( <>
{loadingStateString === 'Failed' ? 'failed' : }
- {error && ( -
-
- {t('error')}: {error.message} -
- )} ); @@ -332,7 +325,6 @@ const Subplebbit = () => { isOnline, started, isSubCreatedButNotYetPublished, - error: error || null, hasMore, timeFilterName: searchQuery ? 'all' : timeFilterName || '', reset, @@ -361,6 +353,12 @@ const Subplebbit = () => { const hasUnhiddenAnyNsfwCommunity = !hideAdultCommunities || !hideGoreCommunities || !hideAntiCommunities || !hideVulgarCommunities; const isBroadlyNsfwSubplebbit = useIsBroadlyNsfwSubplebbit(subplebbitAddress || ''); + useEffect(() => { + if (error) { + console.log(error); + } + }, [error]); + // page title useEffect(() => { document.title = title ? title : shortAddress || subplebbitAddress; @@ -373,6 +371,11 @@ const Subplebbit = () => {
+ {error && ( +
+ +
+ )}
{ const Infobar = () => { const account = useAccount(); - const { accountSubplebbits } = useAccountSubplebbits(); + const { accountSubplebbits, error: accountSubplebbitsError } = useAccountSubplebbits(); + const { setError } = useErrorStore(); + + useEffect(() => { + setError('Infobar_useAccountSubplebbits', accountSubplebbitsError); + }, [accountSubplebbitsError, setError]); + const subscriptions = account?.subscriptions || []; const { t } = useTranslation(); const location = useLocation(); @@ -285,7 +293,12 @@ const Subplebbit = ({ subplebbit, tags, index }: SubplebbitProps) => { const AccountSubplebbits = ({ viewRole }: { viewRole: string }) => { const account = useAccount(); - const { accountSubplebbits } = useAccountSubplebbits(); + const { accountSubplebbits, error: accountSubplebbitsError } = useAccountSubplebbits(); + const { setError } = useErrorStore(); + + useEffect(() => { + setError('AccountSubplebbits_useAccountSubplebbits', accountSubplebbitsError); + }, [accountSubplebbitsError, setError, viewRole]); const filteredSubplebbitsArray = useMemo(() => { return Object.values(accountSubplebbits).filter((subplebbit: any) => { @@ -300,7 +313,13 @@ const AccountSubplebbits = ({ viewRole }: { viewRole: string }) => { const SubscriberSubplebbits = () => { const account = useAccount(); - const { subplebbits } = useSubplebbits({ subplebbitAddresses: account?.subscriptions }); + const { subplebbits, error: subplebbitsError } = useSubplebbits({ subplebbitAddresses: account?.subscriptions }); + const { setError } = useErrorStore(); + + useEffect(() => { + setError('SubscriberSubplebbits_useSubplebbits', subplebbitsError); + }, [subplebbitsError, setError]); + const subplebbitsArray = useMemo(() => Object.values(subplebbits), [subplebbits]); return subplebbitsArray?.map((subplebbit, index) => subplebbit && ).filter(Boolean); }; @@ -314,7 +333,13 @@ const AllDefaultSubplebbits = () => { const urlTag = pathname.includes('/tag/') ? pathname.split('/').pop() : undefined; const currentTag = urlTag && validTags.includes(urlTag) ? urlTag : undefined; - const { subplebbits } = useSubplebbits({ subplebbitAddresses }); + const { subplebbits, error: subplebbitsError } = useSubplebbits({ subplebbitAddresses }); + const { setError } = useErrorStore(); + + useEffect(() => { + setError('AllDefaultSubplebbits_useSubplebbits', subplebbitsError); + }, [subplebbitsError, setError]); + const subplebbitsArray = useMemo(() => Object.values(subplebbits), [subplebbits]); return subplebbitsArray @@ -329,11 +354,21 @@ const AllDefaultSubplebbits = () => { const AllAccountSubplebbits = () => { const account = useAccount(); - const { accountSubplebbits } = useAccountSubplebbits(); + const { accountSubplebbits, error: accountSubplebbitsError } = useAccountSubplebbits(); + const { setError } = useErrorStore(); + + useEffect(() => { + setError('AllAccountSubplebbits_useAccountSubplebbits', accountSubplebbitsError); + }, [accountSubplebbitsError, setError]); + const accountSubplebbitAddresses = Object.keys(accountSubplebbits); const subscriptionsArray = account?.subscriptions ?? []; const uniqueAddresses = Array.from(new Set([...accountSubplebbitAddresses, ...subscriptionsArray])); - const { subplebbits } = useSubplebbits({ subplebbitAddresses: uniqueAddresses }); + const { subplebbits, error: subplebbitsError } = useSubplebbits({ subplebbitAddresses: uniqueAddresses }); + + useEffect(() => { + setError('AllAccountSubplebbits_useSubplebbits', subplebbitsError); + }, [subplebbitsError, setError]); const subplebbitsArray = useMemo(() => Object.values(subplebbits ?? {}), [subplebbits]); return subplebbitsArray?.map((subplebbit, index) => subplebbit && ).filter(Boolean); }; @@ -341,6 +376,24 @@ const AllAccountSubplebbits = () => { const Subplebbits = () => { const { t } = useTranslation(); const location = useLocation(); + const { errors, clearAllErrors } = useErrorStore(); + + // Clear errors on component unmount or location change + useEffect(() => { + return () => { + clearAllErrors(); + }; + }, [location, clearAllErrors]); + + // Console log errors + useEffect(() => { + Object.entries(errors).forEach(([source, errorObj]) => { + if (errorObj) { + console.error(`Error from ${source}:`, errorObj.message, errorObj.stack); + } + }); + }, [errors]); + const isInSubplebbitsSubscriberView = isSubplebbitsSubscriberView(location.pathname); const isInSubplebbitsModeratorView = isSubplebbitsModeratorView(location.pathname); const isInSubplebbitsAdminView = isSubplebbitsAdminView(location.pathname); @@ -385,6 +438,32 @@ const Subplebbits = () => { document.title = documentTitle; }, [documentTitle]); + const renderErrors = () => { + const errorsToDisplay: JSX.Element[] = []; + Object.entries(errors).forEach(([source, errorObj]) => { + if (!errorObj) return; + + if ( + source === 'Infobar_useAccountSubplebbits' && + (isInSubplebbitsView || isInSubplebbitsSubscriberView || isInSubplebbitsModeratorView || isInSubplebbitsAdminView || isInSubplebbitsOwnerView) + ) { + errorsToDisplay.push(); + } else if (source === 'AccountSubplebbits_useAccountSubplebbits' && (isInSubplebbitsModeratorView || isInSubplebbitsAdminView || isInSubplebbitsOwnerView)) { + errorsToDisplay.push(); + } else if (source === 'SubscriberSubplebbits_useSubplebbits' && isInSubplebbitsSubscriberView) { + errorsToDisplay.push(); + } else if (source === 'AllDefaultSubplebbits_useSubplebbits' && isInSubplebbitsVoteView) { + errorsToDisplay.push(); + } else if (source === 'AllAccountSubplebbits_useAccountSubplebbits' && isInSubplebbitsView) { + errorsToDisplay.push(); + } else if (source === 'AllAccountSubplebbits_useSubplebbits' && isInSubplebbitsView) { + // Avoid duplicate key if both errors from AllAccountSubplebbits are present + errorsToDisplay.push(); + } + }); + return errorsToDisplay; + }; + return (
@@ -396,6 +475,7 @@ const Subplebbits = () => { )} +
{renderErrors()}
{isInSubplebbitsVoteView && } {(isInSubplebbitsModeratorView || isInSubplebbitsAdminView || isInSubplebbitsOwnerView) && } {isInSubplebbitsSubscriberView && }