feat: add repost and original author fields

This commit is contained in:
enes 2024-11-27 17:17:54 +01:00
parent 6c0ac7d59d
commit 35cedba3db
8 changed files with 157 additions and 27 deletions

View File

@ -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

View File

@ -57,6 +57,11 @@ export const ModCard = React.memo((props: ModDetails) => {
<p>NSFW</p>
</div>
)}
{props.repost && (
<div className='IBMSMSMBSSTagsTag IBMSMSMBSSTagsTagRepost IBMSMSMBSSTagsTagRepostCard'>
<p>REPOST</p>
</div>
)}
</div>
<div className='cMMBody'>
<h3 className='cMMBodyTitle'>{props.title}</h3>

View File

@ -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'
/>
<CheckboxField
label='This is a repost of a mod I did not create'
name='repost'
isChecked={formState.repost}
handleChange={handleCheckboxChange}
type='stylized'
/>
{formState.repost && (
<>
<InputField
label={
<span>
Created by:{' '}
{<OriginalAuthor value={formState.originalAuthor || ''} />}
</span>
}
type='text'
placeholder="Original author's name, npub or nprofile"
name='originalAuthor'
value={formState.originalAuthor || ''}
error={formErrors.originalAuthor || ''}
onChange={handleInputChange}
/>
</>
)}
<div className='inputLabelWrapperMain'>
<div className='labelWrapperMain'>
<label className='form-label labelMain'>Screenshots URLs</label>

View File

@ -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 <ProfileLink pubkey={profilePubkey} profileRoute={profileRoute} />
return fallback ? displayName : null
}

View File

@ -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 <Link to={profileRoute}>{displayName}</Link>
}

View File

@ -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}
/>
<Interactions
addressable={mod}
@ -424,6 +427,8 @@ type BodyProps = {
screenshotsUrls: string[]
tags: string[]
nsfw: boolean
repost: boolean
originalAuthor?: string
}
const Body = ({
@ -432,7 +437,9 @@ const Body = ({
body,
screenshotsUrls,
tags,
nsfw
nsfw,
repost,
originalAuthor
}: BodyProps) => {
const postBodyRef = useRef<HTMLDivElement>(null)
const viewFullPostBtnRef = useRef<HTMLDivElement>(null)
@ -504,6 +511,17 @@ const Body = ({
</div>
)}
{repost && (
<div className='IBMSMSMBSSTagsTag IBMSMSMBSSTagsTagRepost'>
<p>
REPOST. Original Author:{' '}
{!!originalAuthor && (
<OriginalAuthor value={originalAuthor} fallback={true} />
)}
</p>
</div>
)}
{tags.map((tag, index) => (
<a className='IBMSMSMBSSTagsTag' href='#' key={`tag-${index}`}>
{tag}

View File

@ -28,6 +28,7 @@ export interface ModFormState {
summary: string
nsfw: boolean
repost: boolean
originalAuthor?: string
screenshotsUrls: string[]
tags: string
downloadUrls: DownloadUrl[]

View File

@ -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 || [