snort/packages/app/src/Pages/Deck/DeckLayout.tsx

151 lines
4.8 KiB
TypeScript

import "./Deck.css";
import { NostrLink, TaggedNostrEvent } from "@snort/system";
import { createContext, useEffect, useState } from "react";
import { FormattedMessage } from "react-intl";
import { Link, useLocation, useNavigate } from "react-router-dom";
import ErrorBoundary from "@/Components/ErrorBoundary";
import { LongFormText } from "@/Components/Event/LongFormText";
import Modal from "@/Components/Modal/Modal";
import { SpotlightThreadModal } from "@/Components/Spotlight/SpotlightThreadModal";
import Toaster from "@/Components/Toaster/Toaster";
import useLoginFeed from "@/Feed/LoginFeed";
import useLogin from "@/Hooks/useLogin";
import { useLoginRelays } from "@/Hooks/useLoginRelays";
import usePreferences from "@/Hooks/usePreferences";
import { useTheme } from "@/Hooks/useTheme";
import { ArticlesCol, MediaCol, NotesCol, NotificationsCol } from "@/Pages/Deck/Columns";
import NavSidebar from "@/Pages/Layout/NavSidebar";
import { mapPlanName } from "@/Pages/subscribe/utils";
import { trackEvent } from "@/Utils";
import { getCurrentSubscription } from "@/Utils/Subscription";
type Cols = "notes" | "articles" | "media" | "streams" | "notifications";
interface DeckState {
thread?: NostrLink;
article?: TaggedNostrEvent;
}
interface DeckScope {
setThread: (e?: NostrLink) => void;
setArticle: (e?: TaggedNostrEvent) => void;
reset: () => void;
}
export const DeckContext = createContext<DeckScope | undefined>(undefined);
export function SnortDeckLayout() {
const location = useLocation();
const login = useLogin(s => ({
publicKey: s.publicKey,
subscriptions: s.subscriptions,
}));
const telemetry = usePreferences(s => s.telemetry);
const navigate = useNavigate();
const [deckState, setDeckState] = useState<DeckState>({
thread: undefined,
article: undefined,
});
const sub = getCurrentSubscription(login.subscriptions);
useLoginFeed();
useTheme();
useLoginRelays();
useEffect(() => {
if (!login.publicKey) {
navigate("/");
}
}, [login]);
useEffect(() => {
if (CONFIG.features.analytics && (telemetry ?? true)) {
trackEvent("pageview");
}
}, [location]);
if (!login.publicKey) return null;
const showDeck = CONFIG.showDeck || !(CONFIG.deckSubKind !== undefined && (sub?.type ?? -1) < CONFIG.deckSubKind);
if (!showDeck) {
return (
<div className="deck-layout">
<NavSidebar narrow={true} />
<div>
<div className="flex flex-col gap-2 m-2 bg-dark p br">
<div className="text-xl font-bold">
<FormattedMessage
defaultMessage="You must be a {tier} subscriber to access {app} deck"
id="IOu4Xh"
values={{
app: CONFIG.appNameCapitalized,
tier: mapPlanName(CONFIG.deckSubKind ?? -1),
}}
/>
</div>
<div>
<Link to="/subscribe">
<button>
<FormattedMessage defaultMessage="Subscribe" id="gczcC5" />
</button>
</Link>
</div>
</div>
</div>
</div>
);
}
const cols = ["notes", "media", "notifications", "articles"] as Array<Cols>;
return (
<div className="deck-layout">
<DeckContext.Provider
value={{
...deckState,
setThread: (e?: NostrLink) => setDeckState({ thread: e }),
setArticle: (e?: TaggedNostrEvent) => setDeckState({ article: e }),
reset: () => setDeckState({}),
}}>
<NavSidebar narrow={true} />
<ErrorBoundary>
<div className="deck-cols">
{cols.map(c => {
switch (c) {
case "notes":
return <NotesCol />;
case "media":
return <MediaCol setThread={t => setDeckState({ thread: t })} />;
case "articles":
return <ArticlesCol />;
case "notifications":
return <NotificationsCol setThread={t => setDeckState({ thread: t })} />;
}
})}
</div>
{deckState.thread && (
<SpotlightThreadModal
thread={deckState.thread}
onClose={() => setDeckState({})}
onBack={() => setDeckState({})}
/>
)}
{deckState.article && (
<>
<Modal
id="deck-article"
onClose={() => setDeckState({})}
className="long-form"
onClick={() => setDeckState({})}>
<div onClick={e => e.stopPropagation()}>
<LongFormText ev={deckState.article} isPreview={false} />
</div>
</Modal>
</>
)}
<Toaster />
</ErrorBoundary>
</DeckContext.Provider>
</div>
);
}