degmods.com/src/components/Notes/NoteSubmit.tsx

221 lines
6.7 KiB
TypeScript
Raw Normal View History

import { NDKSubscriptionCacheUsage } from '@nostr-dev-kit/ndk'
import { FALLBACK_PROFILE_IMAGE } from '../../constants'
import { useAppSelector, useLocalCache } from 'hooks'
import { useProfile } from 'hooks/useProfile'
import {
Navigate,
useActionData,
useNavigation,
useSubmit
} from 'react-router-dom'
import { appRoutes } from 'routes'
import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import { adjustTextareaHeight, NOTE_DRAFT_CACHE_KEY } from 'utils'
import { NotePreview } from './NotePreview'
import { NoteSubmitActionResult, NoteSubmitForm } from 'types'
import { InputError } from 'components/Inputs/Error'
import { AlertPopup } from 'components/AlertPopup'
interface NoteSubmitProps {
initialContent?: string | undefined
2025-02-14 13:50:22 +01:00
handleClose?: () => void | undefined
}
2025-02-14 13:50:22 +01:00
export const NoteSubmit = ({
initialContent,
handleClose
}: NoteSubmitProps) => {
const navigation = useNavigation()
const userState = useAppSelector((state) => state.user)
const profile = useProfile(userState.user?.pubkey as string | undefined, {
cacheUsage: NDKSubscriptionCacheUsage.PARALLEL
})
const [cache, setCache] = useLocalCache<NoteSubmitForm>(NOTE_DRAFT_CACHE_KEY)
const [content, setContent] = useState(initialContent ?? cache?.content ?? '')
const [nsfw, setNsfw] = useState(cache?.nsfw ?? false)
2025-02-14 13:50:22 +01:00
const [showPreview, setShowPreview] = useState(!!initialContent)
const image = useMemo(
() => profile?.image || FALLBACK_PROFILE_IMAGE,
[profile?.image]
)
2025-02-14 13:50:22 +01:00
const ref = useRef<HTMLTextAreaElement>(null)
const submit = useSubmit()
const actionData = useActionData() as NoteSubmitActionResult
const formErrors = useMemo(
() =>
actionData?.type === 'validation' ? actionData.formErrors : undefined,
[actionData]
)
2025-02-14 13:50:22 +01:00
useEffect(() => {
if (ref.current && (!!initialContent || !!cache?.content)) {
2025-02-14 13:50:22 +01:00
adjustTextareaHeight(ref.current)
ref.current.focus()
}
}, [cache?.content, initialContent])
useEffect(() => {
setCache({
content,
nsfw
})
}, [content, nsfw, setCache])
2025-02-14 13:50:22 +01:00
useEffect(() => {
if (ref.current) adjustTextareaHeight(ref.current)
}, [content])
const [showTryAgainPopup, setShowTryAgainPopup] = useState<boolean>(false)
useEffect(() => {
const isTimeout = actionData?.type === 'timeout'
setShowTryAgainPopup(isTimeout)
if (isTimeout && actionData.action.intent === 'submit') {
setContent(actionData.action.data.content)
setNsfw(actionData.action.data.nsfw)
}
}, [actionData])
const handleFormSubmit = useCallback(
async (event?: React.FormEvent<HTMLFormElement>) => {
event?.preventDefault()
const formSubmit = {
intent: 'submit',
data: {
content,
nsfw
}
2025-02-19 21:22:56 +01:00
}
2025-02-14 17:39:49 +01:00
// Reset form
setContent('')
setNsfw(false)
2025-02-14 17:39:49 +01:00
setShowPreview(false)
submit(JSON.stringify(formSubmit), {
method: 'post',
encType: 'application/json',
action: appRoutes.feed
})
2025-02-14 17:40:49 +01:00
typeof handleClose === 'function' && handleClose()
},
[content, handleClose, nsfw, submit]
)
const handleTryAgainConfirm = useCallback(
(confirm: boolean) => {
setShowTryAgainPopup(false)
// Cancel if not confirmed
if (!confirm) return
handleFormSubmit()
},
[handleFormSubmit]
)
const handleContentChange = (
event: React.ChangeEvent<HTMLTextAreaElement>
) => {
setContent(event.currentTarget.value)
}
const handlePreviewToggle = () => {
setShowPreview((prev) => !prev)
}
if (!userState.user?.pubkey) return <Navigate to={appRoutes.home} />
return (
<>
<form className='feedPostsPost' onSubmit={handleFormSubmit}>
<div className='feedPostsPostInside'>
<div className='feedPostsPostInsideInputWrapper'>
<div className='feedPostsPostInsidePP'>
<div className='IBMSMSMBSSCL_CommentTopPPWrapper'>
<div
className='IBMSMSMBSSCL_CommentTopPP'
style={{
background: `url(${image}) center/cover no-repeat`,
width: '50px',
height: '50px',
borderRadius: '8px'
}}
></div>
</div>
</div>
<textarea
id='postSocialTextarea'
name='content'
className='inputMain feedPostsPostInsideInput'
placeholder='Watcha thinking about?'
2025-02-14 13:50:22 +01:00
ref={ref}
value={content}
onChange={handleContentChange}
/>
</div>
<div className='feedPostsPostInsideAction'>
<div
className='inputLabelWrapperMain inputLabelWrapperMainAlt'
style={{ width: 'unset' }}
>
<input
type='checkbox'
className='CheckboxMain'
checked={nsfw}
id='nsfw'
name='nsfw'
onChange={() => setNsfw((nsfw) => !nsfw)}
/>
<label htmlFor='nsfw' className='form-label labelMain'>
NSFW
</label>
</div>
<div
style={{ display: 'flex', flexDirection: 'row', gridGap: '10px' }}
>
2025-02-14 13:50:22 +01:00
{typeof handleClose === 'function' && (
<button
className='btn btnMain'
type='button'
style={{ padding: '5px 20px', borderRadius: '8px' }}
onClick={handleClose}
>
Close
</button>
)}
<button
className='btn btnMain'
type='button'
style={{ padding: '5px 20px', borderRadius: '8px' }}
onClick={handlePreviewToggle}
>
Preview
</button>
<button
className='btn btnMain'
type='submit'
style={{ padding: '5px 20px', borderRadius: '8px' }}
disabled={navigation.state !== 'idle' || !content.length}
>
{navigation.state === 'submitting' ? 'Posting...' : 'Post'}
</button>
</div>
</div>
{typeof formErrors?.content !== 'undefined' && (
<InputError message={formErrors?.content} />
)}
{showPreview && <NotePreview content={content} />}
{showTryAgainPopup && (
<AlertPopup
handleConfirm={handleTryAgainConfirm}
handleClose={() => setShowTryAgainPopup(false)}
header={'Post'}
label={`Posting timed out. Do you want to try again?`}
/>
)}
</div>
</form>
</>
)
}