From 35cedba3dbe940ae35ae021a7c72d73ebdf97789 Mon Sep 17 00:00:00 2001 From: enes Date: Wed, 27 Nov 2024 17:17:54 +0100 Subject: [PATCH] feat: add repost and original author fields --- src/components/Inputs.tsx | 2 +- src/components/ModCard.tsx | 5 ++ src/components/ModForm.tsx | 91 ++++++++++++++++++++++--------- src/components/OriginalAuthor.tsx | 48 ++++++++++++++++ src/components/ProfileLink.tsx | 15 +++++ src/pages/mod/index.tsx | 20 ++++++- src/types/mod.ts | 1 + src/utils/mod.ts | 2 + 8 files changed, 157 insertions(+), 27 deletions(-) create mode 100644 src/components/OriginalAuthor.tsx create mode 100644 src/components/ProfileLink.tsx diff --git a/src/components/Inputs.tsx b/src/components/Inputs.tsx index 38d48af..95b4f48 100644 --- a/src/components/Inputs.tsx +++ b/src/components/Inputs.tsx @@ -7,7 +7,7 @@ import '../styles/styles.css' import '../styles/tiptap.scss' interface InputFieldProps { - label: string + label: string | React.ReactElement description?: string type?: 'text' | 'textarea' | 'richtext' placeholder: string diff --git a/src/components/ModCard.tsx b/src/components/ModCard.tsx index c54b300..108d33f 100644 --- a/src/components/ModCard.tsx +++ b/src/components/ModCard.tsx @@ -57,6 +57,11 @@ export const ModCard = React.memo((props: ModDetails) => {

NSFW

)} + {props.repost && ( +
+

REPOST

+
+ )}

{props.title}

diff --git a/src/components/ModForm.tsx b/src/components/ModForm.tsx index 7411c47..ea54912 100644 --- a/src/components/ModForm.tsx +++ b/src/components/ModForm.tsx @@ -29,6 +29,7 @@ import { import { CheckboxField, InputError, InputField } from './Inputs' import { LoadingSpinner } from './LoadingSpinner' import { NDKEvent } from '@nostr-dev-kit/ndk' +import { OriginalAuthor } from './OriginalAuthor' interface FormErrors { game?: string @@ -40,6 +41,8 @@ interface FormErrors { screenshotsUrls?: string[] tags?: string downloadUrls?: string[] + author?: string + originalAuthor?: string } interface GameOption { @@ -198,36 +201,42 @@ export const ModForm = ({ existingModData }: ModFormProps) => { const aTag = formState.aTag || `${kinds.ClassifiedListing}:${hexPubkey}:${uuid}` + const tags = [ + ['d', uuid], + ['a', aTag], + ['r', formState.rTag], + ['t', T_TAG_VALUE], + [ + 'published_at', + existingModData + ? existingModData.published_at.toString() + : currentTimeStamp.toString() + ], + ['game', formState.game], + ['title', formState.title], + ['featuredImageUrl', formState.featuredImageUrl], + ['summary', formState.summary], + ['nsfw', formState.nsfw.toString()], + ['repost', formState.repost.toString()], + ['screenshotsUrls', ...formState.screenshotsUrls], + ['tags', ...formState.tags.split(',')], + [ + 'downloadUrls', + ...formState.downloadUrls.map((downloadUrl) => + JSON.stringify(downloadUrl) + ) + ] + ] + if (formState.repost && formState.originalAuthor) { + tags.push(['originalAuthor', formState.originalAuthor]) + } + const unsignedEvent: UnsignedEvent = { kind: kinds.ClassifiedListing, created_at: currentTimeStamp, pubkey: hexPubkey, content: formState.body, - tags: [ - ['d', uuid], - ['a', aTag], - ['r', formState.rTag], - ['t', T_TAG_VALUE], - [ - 'published_at', - existingModData - ? existingModData.published_at.toString() - : currentTimeStamp.toString() - ], - ['game', formState.game], - ['title', formState.title], - ['featuredImageUrl', formState.featuredImageUrl], - ['summary', formState.summary], - ['nsfw', formState.nsfw.toString()], - ['screenshotsUrls', ...formState.screenshotsUrls], - ['tags', ...formState.tags.split(',')], - [ - 'downloadUrls', - ...formState.downloadUrls.map((downloadUrl) => - JSON.stringify(downloadUrl) - ) - ] - ] + tags } const signedEvent = await window.nostr @@ -318,6 +327,13 @@ export const ModForm = ({ existingModData }: ModFormProps) => { } } + if ( + formState.repost && + (!formState.originalAuthor || formState.originalAuthor === '') + ) { + errors.originalAuthor = 'Original author field can not be empty' + } + if (formState.tags === '') { errors.tags = 'Tags field can not be empty' } @@ -397,6 +413,31 @@ export const ModForm = ({ existingModData }: ModFormProps) => { handleChange={handleCheckboxChange} type='stylized' /> + + {formState.repost && ( + <> + + Created by:{' '} + {} + + } + type='text' + placeholder="Original author's name, npub or nprofile" + name='originalAuthor' + value={formState.originalAuthor || ''} + error={formErrors.originalAuthor || ''} + onChange={handleInputChange} + /> + + )}
diff --git a/src/components/OriginalAuthor.tsx b/src/components/OriginalAuthor.tsx new file mode 100644 index 0000000..60dc4dc --- /dev/null +++ b/src/components/OriginalAuthor.tsx @@ -0,0 +1,48 @@ +import { nip19 } from 'nostr-tools' +import { appRoutes, getProfilePageRoute } from 'routes' +import { npubToHex } from 'utils' +import { ProfileLink } from './ProfileLink' + +interface OriginalAuthorProps { + value: string + fallback?: boolean +} + +export const OriginalAuthor = ({ + value, + fallback = false +}: OriginalAuthorProps) => { + let profilePubkey + let displayName = '[name not set up]' + + // Try to decode/encode depending on what we send to link + let profileRoute = appRoutes.home + try { + if (value.startsWith('nprofile1')) { + const decoded = nip19.decode(value as `nprofile1${string}`) + profileRoute = getProfilePageRoute(value) + profilePubkey = decoded?.data.pubkey + } else if (value.startsWith('npub1')) { + profilePubkey = npubToHex(value) + const nprofile = profilePubkey + ? nip19.nprofileEncode({ + pubkey: profilePubkey + }) + : undefined + + if (nprofile) { + profileRoute = getProfilePageRoute(nprofile) + } + } else { + displayName = value + } + } catch (error) { + console.error('Failed to create profile link:', error) + displayName = value + } + + if (profileRoute && profilePubkey) + return + + return fallback ? displayName : null +} diff --git a/src/components/ProfileLink.tsx b/src/components/ProfileLink.tsx new file mode 100644 index 0000000..0fcdfca --- /dev/null +++ b/src/components/ProfileLink.tsx @@ -0,0 +1,15 @@ +import { useProfile } from 'hooks/useProfile' +import { Link } from 'react-router-dom' + +interface ProfileLinkProps { + pubkey: string + profileRoute: string +} + +export const ProfileLink = ({ pubkey, profileRoute }: ProfileLinkProps) => { + const profile = useProfile(pubkey) + const displayName = + profile?.displayName || profile?.name || '[name not set up]' + + return {displayName} +} diff --git a/src/pages/mod/index.tsx b/src/pages/mod/index.tsx index da3cb91..53d2944 100644 --- a/src/pages/mod/index.tsx +++ b/src/pages/mod/index.tsx @@ -38,6 +38,7 @@ import { Interactions } from 'components/Internal/Interactions' import { ReportPopup } from 'components/ReportPopup' import { Spinner } from 'components/Spinner' import { RouterLoadingSpinner } from 'components/LoadingSpinner' +import { OriginalAuthor } from 'components/OriginalAuthor' const MOD_REPORT_REASONS = [ { label: 'Actually CP', key: 'actuallyCP' }, @@ -105,6 +106,8 @@ export const ModPage = () => { screenshotsUrls={mod.screenshotsUrls} tags={mod.tags} nsfw={mod.nsfw} + repost={mod.repost} + originalAuthor={mod.originalAuthor} /> { const postBodyRef = useRef(null) const viewFullPostBtnRef = useRef(null) @@ -504,6 +511,17 @@ const Body = ({
)} + {repost && ( +
+

+ REPOST. Original Author:{' '} + {!!originalAuthor && ( + + )} +

+
+ )} + {tags.map((tag, index) => ( {tag} diff --git a/src/types/mod.ts b/src/types/mod.ts index bfad46f..bc27e18 100644 --- a/src/types/mod.ts +++ b/src/types/mod.ts @@ -28,6 +28,7 @@ export interface ModFormState { summary: string nsfw: boolean repost: boolean + originalAuthor?: string screenshotsUrls: string[] tags: string downloadUrls: DownloadUrl[] diff --git a/src/utils/mod.ts b/src/utils/mod.ts index 95f82a3..24b59bd 100644 --- a/src/utils/mod.ts +++ b/src/utils/mod.ts @@ -40,6 +40,7 @@ export const extractModData = (event: Event | NDKEvent): ModDetails => { summary: getFirstTagValue('summary'), nsfw: getFirstTagValue('nsfw') === 'true', repost: getFirstTagValue('repost') === 'true', + originalAuthor: getFirstTagValue('originalAuthor'), screenshotsUrls: getTagValue(event, 'screenshotsUrls') || [], tags: getTagValue(event, 'tags') || [], downloadUrls: (getTagValue(event, 'downloadUrls') || []).map((item) => @@ -120,6 +121,7 @@ export const initializeFormState = ( summary: existingModData?.summary || '', nsfw: existingModData?.nsfw || false, repost: existingModData?.repost || false, + originalAuthor: existingModData?.originalAuthor || undefined, screenshotsUrls: existingModData?.screenshotsUrls || [''], tags: existingModData?.tags.join(',') || '', downloadUrls: existingModData?.downloadUrls || [