mirror of
https://github.com/bitsocialnet/5chan.git
synced 2026-08-03 07:41:04 +02:00
fix: errors could be displayed unnecessarily
This commit is contained in:
@@ -0,0 +1,11 @@
|
|||||||
|
.errorMessage {
|
||||||
|
color: red;
|
||||||
|
}
|
||||||
|
|
||||||
|
.clickableErrorMessage {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.feedbackSuccessMessage {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import { useState } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { copyToClipboard } from '../../lib/utils/clipboard-utils';
|
||||||
|
import styles from './error-display.module.css';
|
||||||
|
|
||||||
|
const ErrorDisplay = ({ error }: { error: any }) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [feedbackMessageKey, setFeedbackMessageKey] = useState<string | null>(null);
|
||||||
|
|
||||||
|
const originalDisplayMessage = error?.message ? `${t('error')}: ${error.message}` : null;
|
||||||
|
|
||||||
|
const handleMessageClick = async () => {
|
||||||
|
if (!error || !error.message || feedbackMessageKey) return;
|
||||||
|
|
||||||
|
const errorString = JSON.stringify(error, null, 2);
|
||||||
|
try {
|
||||||
|
await copyToClipboard(errorString);
|
||||||
|
setFeedbackMessageKey('copied');
|
||||||
|
setTimeout(() => {
|
||||||
|
setFeedbackMessageKey(null);
|
||||||
|
}, 1500);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to copy error: ', err);
|
||||||
|
setFeedbackMessageKey('failed');
|
||||||
|
setTimeout(() => {
|
||||||
|
setFeedbackMessageKey(null);
|
||||||
|
}, 1500);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let currentDisplayMessage = '';
|
||||||
|
const classNames = [styles.errorMessage];
|
||||||
|
let isClickable = false;
|
||||||
|
|
||||||
|
if (feedbackMessageKey === 'copied') {
|
||||||
|
currentDisplayMessage = t('fullErrorCopiedToClipboard', 'full error copied to the clipboard');
|
||||||
|
classNames.pop();
|
||||||
|
classNames.push(styles.feedbackSuccessMessage);
|
||||||
|
} else if (feedbackMessageKey === 'failed') {
|
||||||
|
currentDisplayMessage = t('copyFailed', 'copy failed');
|
||||||
|
} else if (originalDisplayMessage) {
|
||||||
|
currentDisplayMessage = originalDisplayMessage;
|
||||||
|
isClickable = true;
|
||||||
|
classNames.push(styles.clickableErrorMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
(error?.message || error?.stack || error?.details || error) && (
|
||||||
|
<div className={styles.error}>
|
||||||
|
{currentDisplayMessage && (
|
||||||
|
<span
|
||||||
|
className={classNames.join(' ')}
|
||||||
|
onClick={isClickable ? handleMessageClick : undefined}
|
||||||
|
title={isClickable ? t('clickToCopyFullError', 'Click to copy full error') : undefined}
|
||||||
|
>
|
||||||
|
{currentDisplayMessage}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ErrorDisplay;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export { default } from './error-display';
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* Universal clipboard utility that works in both Electron and web environments
|
||||||
|
*/
|
||||||
|
export const copyToClipboard = async (text: string): Promise<void> => {
|
||||||
|
// Check if we're in Electron and use its clipboard API
|
||||||
|
if (typeof window !== 'undefined' && (window as any).electronApi?.copyToClipboard) {
|
||||||
|
try {
|
||||||
|
const result = await (window as any).electronApi.copyToClipboard(text);
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.error || 'Failed to copy to clipboard');
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Electron clipboard failed:', error);
|
||||||
|
// Fall back to web clipboard API
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to web clipboard API
|
||||||
|
if (navigator.clipboard) {
|
||||||
|
try {
|
||||||
|
await navigator.clipboard.writeText(text);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Web clipboard failed:', error);
|
||||||
|
throw new Error('Failed to copy to clipboard. Your browser may not support this feature.');
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw new Error('Your browser does not support clipboard API');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -13,6 +13,7 @@ import useTimeFilter from '../../hooks/use-time-filter';
|
|||||||
import useInterfaceSettingsStore from '../../stores/use-interface-settings-store';
|
import useInterfaceSettingsStore from '../../stores/use-interface-settings-store';
|
||||||
import useFeedResetStore from '../../stores/use-feed-reset-store';
|
import useFeedResetStore from '../../stores/use-feed-reset-store';
|
||||||
import useSortingStore from '../../stores/use-sorting-store';
|
import useSortingStore from '../../stores/use-sorting-store';
|
||||||
|
import ErrorDisplay from '../../components/error-display/error-display';
|
||||||
import LoadingEllipsis from '../../components/loading-ellipsis';
|
import LoadingEllipsis from '../../components/loading-ellipsis';
|
||||||
import SubplebbitDescription from '../../components/subplebbit-description';
|
import SubplebbitDescription from '../../components/subplebbit-description';
|
||||||
import SubplebbitRules from '../../components/subplebbit-rules';
|
import SubplebbitRules from '../../components/subplebbit-rules';
|
||||||
@@ -227,12 +228,6 @@ const Board = () => {
|
|||||||
) : (
|
) : (
|
||||||
hasMore && <LoadingEllipsis string={loadingStateString} />
|
hasMore && <LoadingEllipsis string={loadingStateString} />
|
||||||
)}
|
)}
|
||||||
{error && (
|
|
||||||
<div className='red'>
|
|
||||||
<br />
|
|
||||||
{error.message}
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
{blocked && (
|
{blocked && (
|
||||||
<>
|
<>
|
||||||
[
|
[
|
||||||
@@ -274,6 +269,16 @@ const Board = () => {
|
|||||||
document.title = boardTitle + ' - plebchan';
|
document.title = boardTitle + ' - plebchan';
|
||||||
}, [title, shortAddress, subplebbitAddress]);
|
}, [title, shortAddress, subplebbitAddress]);
|
||||||
|
|
||||||
|
// probably not necessary to show the error to the user if the feed loaded successfully
|
||||||
|
const [shouldShowErrorToUser, setShouldShowErrorToUser] = useState(false);
|
||||||
|
useEffect(() => {
|
||||||
|
if (error?.message && feed.length === 0) {
|
||||||
|
setShouldShowErrorToUser(true);
|
||||||
|
} else if (feed.length > 0) {
|
||||||
|
setShouldShowErrorToUser(false);
|
||||||
|
}
|
||||||
|
}, [error, feed]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{shouldShowSnow() && <hr />}
|
{shouldShowSnow() && <hr />}
|
||||||
@@ -289,6 +294,11 @@ const Board = () => {
|
|||||||
title={title}
|
title={title}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
{shouldShowErrorToUser && (
|
||||||
|
<div className={styles.error}>
|
||||||
|
<ErrorDisplay error={error} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{rules && !description && rules.length > 0 && <SubplebbitRules subplebbitAddress={subplebbitAddress} createdAt={createdAt} rules={rules} />}
|
{rules && !description && rules.length > 0 && <SubplebbitRules subplebbitAddress={subplebbitAddress} createdAt={createdAt} rules={rules} />}
|
||||||
<Virtuoso
|
<Virtuoso
|
||||||
increaseViewportBy={{ bottom: 1200, top: 1200 }}
|
increaseViewportBy={{ bottom: 1200, top: 1200 }}
|
||||||
|
|||||||
+17
-2
@@ -1,10 +1,11 @@
|
|||||||
import { useEffect } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { Comment, Role, useComment, useEditedComment, useSubplebbit } from '@plebbit/plebbit-react-hooks';
|
import { Comment, Role, useComment, useEditedComment, useSubplebbit } from '@plebbit/plebbit-react-hooks';
|
||||||
import useSubplebbitsStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits';
|
import useSubplebbitsStore from '@plebbit/plebbit-react-hooks/dist/stores/subplebbits';
|
||||||
import { useLocation, useParams } from 'react-router-dom';
|
import { useLocation, useParams } from 'react-router-dom';
|
||||||
import { isAllView, isDescriptionView, isRulesView } from '../../lib/utils/view-utils';
|
import { isAllView, isDescriptionView, isRulesView } from '../../lib/utils/view-utils';
|
||||||
import useIsMobile from '../../hooks/use-is-mobile';
|
import useIsMobile from '../../hooks/use-is-mobile';
|
||||||
|
import ErrorDisplay from '../../components/error-display/error-display';
|
||||||
import PostDesktop from '../../components/post-desktop';
|
import PostDesktop from '../../components/post-desktop';
|
||||||
import PostMobile from '../../components/post-mobile';
|
import PostMobile from '../../components/post-mobile';
|
||||||
import SubplebbitDescription from '../../components/subplebbit-description';
|
import SubplebbitDescription from '../../components/subplebbit-description';
|
||||||
@@ -83,11 +84,25 @@ const PostPage = () => {
|
|||||||
document.title = isInAllView ? `${t('all')} - plebchan` : postDucumentTitle;
|
document.title = isInAllView ? `${t('all')} - plebchan` : postDucumentTitle;
|
||||||
}, [title, shortAddress, subplebbitAddress, post?.title, post?.content, isInAllView, t]);
|
}, [title, shortAddress, subplebbitAddress, post?.title, post?.content, isInAllView, t]);
|
||||||
|
|
||||||
|
// probably not necessary to show the error to the user if the post loaded successfully
|
||||||
|
const [shouldShowErrorToUser, setShouldShowErrorToUser] = useState(false);
|
||||||
|
useEffect(() => {
|
||||||
|
if (post?.error && ((post?.replyCount > 0 && post?.replies?.length === 0) || (post?.state === 'failed' && post?.error))) {
|
||||||
|
setShouldShowErrorToUser(true);
|
||||||
|
} else if (post?.replyCount > 0 && post?.replies?.length > 0) {
|
||||||
|
setShouldShowErrorToUser(false);
|
||||||
|
}
|
||||||
|
}, [post]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={styles.content}>
|
<div className={styles.content}>
|
||||||
{/* TODO: remove this replyCount error once api supports scrolling replies pages */}
|
{/* TODO: remove this replyCount error once api supports scrolling replies pages */}
|
||||||
{replyCount > 60 && <span className={styles.error}>Error: this thread has too many replies, some of them cannot be displayed right now.</span>}
|
{replyCount > 60 && <span className={styles.error}>Error: this thread has too many replies, some of them cannot be displayed right now.</span>}
|
||||||
{error && <span className={styles.error}>Error: {error?.message || error?.toString?.()}</span>}
|
{shouldShowErrorToUser && (
|
||||||
|
<div className={styles.error}>
|
||||||
|
<ErrorDisplay error={error} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{isInDescriptionView ? (
|
{isInDescriptionView ? (
|
||||||
<SubplebbitDescription
|
<SubplebbitDescription
|
||||||
avatarUrl={suggested?.avatarUrl}
|
avatarUrl={suggested?.avatarUrl}
|
||||||
|
|||||||
Reference in New Issue
Block a user