3-column layout #699
39
packages/app/src/Element/ErrorBoundary.tsx
Normal file
39
packages/app/src/Element/ErrorBoundary.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import React from "react";
|
||||||
|
|
||||||
|
interface ErrorBoundaryState {
|
||||||
|
hasError: boolean;
|
||||||
|
errorMessage?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ErrorBoundaryProps {
|
||||||
|
children: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
|
||||||
|
constructor(props: ErrorBoundaryProps) {
|
||||||
|
super(props);
|
||||||
|
this.state = { hasError: false };
|
||||||
|
}
|
||||||
|
|
||||||
|
static getDerivedStateFromError(error: Error): ErrorBoundaryState {
|
||||||
|
return { hasError: true, errorMessage: error.message };
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidCatch(error: Error, errorInfo: React.ErrorInfo) {
|
||||||
|
console.error("Caught an error:", error, errorInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
if (this.state.hasError) {
|
||||||
|
// Render any custom fallback UI with the error message
|
||||||
|
return (
|
||||||
|
<div className="p-2">
|
||||||
|
<h1>Something went wrong.</h1>
|
||||||
|
<p>Error: {this.state.errorMessage}</p>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.props.children;
|
||||||
|
}
|
||||||
|
}
|
@ -22,6 +22,7 @@ import Toaster from "@/Toaster";
|
|||||||
import useLogin from "@/Hooks/useLogin";
|
import useLogin from "@/Hooks/useLogin";
|
||||||
import { LongFormText } from "@/Element/Event/LongFormText";
|
import { LongFormText } from "@/Element/Event/LongFormText";
|
||||||
import NavSidebar from "@/Pages/Layout/NavSidebar";
|
import NavSidebar from "@/Pages/Layout/NavSidebar";
|
||||||
|
import ErrorBoundary from "@/Element/ErrorBoundary";
|
||||||
|
|
||||||
type Cols = "notes" | "articles" | "media" | "streams" | "notifications";
|
type Cols = "notes" | "articles" | "media" | "streams" | "notifications";
|
||||||
|
|
||||||
@ -68,46 +69,48 @@ export function SnortDeckLayout() {
|
|||||||
reset: () => setDeckState({}),
|
reset: () => setDeckState({}),
|
||||||
}}>
|
}}>
|
||||||
<NavSidebar narrow={true} />
|
<NavSidebar narrow={true} />
|
||||||
<div className="deck-cols">
|
<ErrorBoundary>
|
||||||
{cols.map(c => {
|
<div className="deck-cols">
|
||||||
switch (c) {
|
{cols.map(c => {
|
||||||
case "notes":
|
switch (c) {
|
||||||
return <NotesCol />;
|
case "notes":
|
||||||
case "media":
|
return <NotesCol />;
|
||||||
return <MediaCol setThread={t => setDeckState({ thread: t })} />;
|
case "media":
|
||||||
case "articles":
|
return <MediaCol setThread={t => setDeckState({ thread: t })} />;
|
||||||
return <ArticlesCol />;
|
case "articles":
|
||||||
case "notifications":
|
return <ArticlesCol />;
|
||||||
return <NotificationsCol setThread={t => setDeckState({ thread: t })} />;
|
case "notifications":
|
||||||
}
|
return <NotificationsCol setThread={t => setDeckState({ thread: t })} />;
|
||||||
})}
|
}
|
||||||
</div>
|
})}
|
||||||
{deckState.thread && (
|
</div>
|
||||||
<>
|
{deckState.thread && (
|
||||||
<Modal id="thread-overlay" onClose={() => setDeckState({})} className="thread-overlay thread">
|
<>
|
||||||
<ThreadContextWrapper link={deckState.thread}>
|
<Modal id="thread-overlay" onClose={() => setDeckState({})} className="thread-overlay thread">
|
||||||
<SpotlightFromThread onClose={() => setDeckState({})} />
|
<ThreadContextWrapper link={deckState.thread}>
|
||||||
<div>
|
<SpotlightFromThread onClose={() => setDeckState({})} />
|
||||||
<Thread onBack={() => setDeckState({})} disableSpotlight={true} />
|
<div>
|
||||||
|
<Thread onBack={() => setDeckState({})} disableSpotlight={true} />
|
||||||
|
</div>
|
||||||
|
</ThreadContextWrapper>
|
||||||
|
</Modal>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{deckState.article && (
|
||||||
|
<>
|
||||||
|
<Modal
|
||||||
|
id="thread-overlay-article"
|
||||||
|
onClose={() => setDeckState({})}
|
||||||
|
className="thread-overlay long-form"
|
||||||
|
onClick={() => setDeckState({})}>
|
||||||
|
<div onClick={e => e.stopPropagation()}>
|
||||||
|
<LongFormText ev={deckState.article} isPreview={false} related={[]} />
|
||||||
</div>
|
</div>
|
||||||
</ThreadContextWrapper>
|
</Modal>
|
||||||
</Modal>
|
</>
|
||||||
</>
|
)}
|
||||||
)}
|
<Toaster />
|
||||||
{deckState.article && (
|
</ErrorBoundary>
|
||||||
<>
|
|
||||||
<Modal
|
|
||||||
id="thread-overlay-article"
|
|
||||||
onClose={() => setDeckState({})}
|
|
||||||
className="thread-overlay long-form"
|
|
||||||
onClick={() => setDeckState({})}>
|
|
||||||
<div onClick={e => e.stopPropagation()}>
|
|
||||||
<LongFormText ev={deckState.article} isPreview={false} related={[]} />
|
|
||||||
</div>
|
|
||||||
</Modal>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
<Toaster />
|
|
||||||
</DeckContext.Provider>
|
</DeckContext.Provider>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -17,6 +17,7 @@ import AccountHeader from "./AccountHeader";
|
|||||||
import RightColumn from "./RightColumn";
|
import RightColumn from "./RightColumn";
|
||||||
import { LogoHeader } from "./LogoHeader";
|
import { LogoHeader } from "./LogoHeader";
|
||||||
import useLoginFeed from "@/Feed/LoginFeed";
|
import useLoginFeed from "@/Feed/LoginFeed";
|
||||||
|
import ErrorBoundary from "@/Element/ErrorBoundary";
|
||||||
|
|
||||||
export default function Index() {
|
export default function Index() {
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
@ -62,7 +63,9 @@ export default function Index() {
|
|||||||
<div className="flex flex-row w-full">
|
<div className="flex flex-row w-full">
|
||||||
<NavSidebar />
|
<NavSidebar />
|
||||||
<div className="flex flex-1 flex-col overflow-x-hidden">
|
<div className="flex flex-1 flex-col overflow-x-hidden">
|
||||||
<Outlet />
|
<ErrorBoundary>
|
||||||
|
<Outlet />
|
||||||
|
</ErrorBoundary>
|
||||||
</div>
|
</div>
|
||||||
<RightColumn />
|
<RightColumn />
|
||||||
</div>
|
</div>
|
||||||
|
Loading…
Reference in New Issue
Block a user