feat: add repost and original author fields
This commit is contained in:
parent
6c0ac7d59d
commit
35cedba3db
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
48
src/components/OriginalAuthor.tsx
Normal file
48
src/components/OriginalAuthor.tsx
Normal 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
|
||||
}
|
15
src/components/ProfileLink.tsx
Normal file
15
src/components/ProfileLink.tsx
Normal 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>
|
||||
}
|
@ -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}
|
||||
|
@ -28,6 +28,7 @@ export interface ModFormState {
|
||||
summary: string
|
||||
nsfw: boolean
|
||||
repost: boolean
|
||||
originalAuthor?: string
|
||||
screenshotsUrls: string[]
|
||||
tags: string
|
||||
downloadUrls: DownloadUrl[]
|
||||
|
@ -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 || [
|
||||
|
Loading…
x
Reference in New Issue
Block a user