From 27cd22f47b5b70c41e00862e042de91407a7fa3e Mon Sep 17 00:00:00 2001 From: en Date: Fri, 14 Feb 2025 13:11:12 +0100 Subject: [PATCH] feat(feed): add feed outlet, note action and types --- src/pages/feed/action.ts | 85 ++++++++++++++++++++++++++++++++++++++++ src/pages/feed/index.tsx | 7 +++- src/routes/index.tsx | 15 ++++++- src/types/index.ts | 1 + src/types/note.ts | 6 +++ 5 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 src/pages/feed/action.ts create mode 100644 src/types/note.ts diff --git a/src/pages/feed/action.ts b/src/pages/feed/action.ts new file mode 100644 index 0000000..1905c2c --- /dev/null +++ b/src/pages/feed/action.ts @@ -0,0 +1,85 @@ +import { NDKEvent, NDKKind } from '@nostr-dev-kit/ndk' +import { NDKContextType } from 'contexts/NDKContext' +import { ActionFunctionArgs, redirect } from 'react-router-dom' +import { toast } from 'react-toastify' +import { getFeedNotePageRoute } from 'routes' +import { store } from 'store' +import { NoteSubmitForm, NoteSubmitFormErrors } from 'types' +import { log, LogType, now } from 'utils' + +export const feedPostRouteAction = + (ndkContext: NDKContextType) => + async ({ request }: ActionFunctionArgs) => { + const userState = store.getState().user + let hexPubkey: string + if (userState.auth && userState.user?.pubkey) { + hexPubkey = userState.user.pubkey as string + } else { + try { + hexPubkey = (await window.nostr?.getPublicKey()) as string + } catch (error) { + if (error instanceof Error) { + log(true, LogType.Error, 'Failed to get public key.', error) + } + + toast.error('Failed to get public key.') + return null + } + } + + if (!hexPubkey) { + toast.error('Could not get pubkey') + return null + } + + const formSubmit = (await request.json()) as NoteSubmitForm + const formErrors = validateFormData(formSubmit) + + if (Object.keys(formErrors).length) return formErrors + + const content = decodeURIComponent(formSubmit.content!) + const currentTimeStamp = now() + + const ndkEvent = new NDKEvent(ndkContext.ndk, { + kind: NDKKind.Text, + created_at: currentTimeStamp, + content: content, + tags: [], + pubkey: hexPubkey + }) + + try { + await ndkEvent.generateTags() + + if (formSubmit.nsfw) ndkEvent.tags.push(['L', 'content-warning']) + + ndkEvent.tags.push(['L', 'source']) + ndkEvent.tags.push(['l', window.location.host, 'source']) + ndkEvent.tags.push(['client', 'DEG Mods']) + + await ndkEvent.sign() + const note1 = ndkEvent.encode() + const publishedOnRelays = await ndkEvent.publish() + if (publishedOnRelays.size === 0) { + toast.error('Failed to publish note on any relay') + return null + } else { + toast.success('Note published successfully') + return redirect(getFeedNotePageRoute(note1)) + } + } catch (error) { + log(true, LogType.Error, 'Failed to publish note', error) + toast.error('Failed to publish note') + return null + } + } + +const validateFormData = (formSubmit: NoteSubmitForm): NoteSubmitFormErrors => { + const errors: NoteSubmitFormErrors = {} + + if (!formSubmit.content.trim()) { + errors.content = 'Content is required' + } + + return errors +} diff --git a/src/pages/feed/index.tsx b/src/pages/feed/index.tsx index a2bef4c..1cc3d65 100644 --- a/src/pages/feed/index.tsx +++ b/src/pages/feed/index.tsx @@ -4,9 +4,12 @@ import { FeedTabBlogs } from './FeedTabBlogs' import { FeedTabMods } from './FeedTabMods' import { FeedTabPosts } from './FeedTabPosts' import { FeedFilter } from 'components/Filters/FeedFilter' +import { Outlet, useParams } from 'react-router-dom' export const FeedPage = () => { - const [tab, setTab] = useState(0) + const { note } = useParams() + // Open posts tab if note is present + const [tab, setTab] = useState(note ? 2 : 0) return ( <> @@ -17,6 +20,8 @@ export const FeedPage = () => { {tab === 0 && } {tab === 1 && } {tab === 2 && } + + ) } diff --git a/src/routes/index.tsx b/src/routes/index.tsx index 066c487..bd90666 100644 --- a/src/routes/index.tsx +++ b/src/routes/index.tsx @@ -33,6 +33,7 @@ import { BackupPage } from 'pages/backup' import { SupportersPage } from 'pages/supporters' import { commentsLoader } from 'loaders/comment' import { CommentsPopup } from 'components/comment/CommentsPopup' +import { feedPostRouteAction } from 'pages/feed/action' export const appRoutes = { home: '/', @@ -56,6 +57,7 @@ export const appRoutes = { settingsAdmin: '/settings-admin', profile: '/profile/:nprofile?', feed: '/feed', + note: '/feed/:note', notifications: '/notifications', backup: '/backup', supporters: '/supporters' @@ -76,6 +78,9 @@ export const getBlogPageRoute = (eventId: string) => export const getProfilePageRoute = (nprofile: string) => appRoutes.profile.replace(':nprofile', nprofile) +export const getFeedNotePageRoute = (note: string) => + appRoutes.note.replace(':note', note) + export const routerWithNdkContext = (context: NDKContextType) => createBrowserRouter([ { @@ -199,7 +204,15 @@ export const routerWithNdkContext = (context: NDKContextType) => { path: appRoutes.feed, element: , - loader: feedPageLoader(context) + loader: feedPageLoader(context), + action: feedPostRouteAction(context), + children: [ + { + path: ':note', + element: , + loader: commentsLoader(context) + } + ] }, { path: appRoutes.notifications, diff --git a/src/types/index.ts b/src/types/index.ts index 431b336..3d4c6f5 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -8,3 +8,4 @@ export * from './category' export * from './popup' export * from './errors' export * from './comments' +export * from './note' diff --git a/src/types/note.ts b/src/types/note.ts new file mode 100644 index 0000000..5e2f179 --- /dev/null +++ b/src/types/note.ts @@ -0,0 +1,6 @@ +export interface NoteSubmitForm { + content: string + nsfw: boolean +} + +export interface NoteSubmitFormErrors extends Partial {}