mod publish - try gain #191
@ -18,9 +18,9 @@ import { useGames } from '../hooks'
|
||||
import '../styles/styles.css'
|
||||
import {
|
||||
DownloadUrl,
|
||||
FormErrors,
|
||||
ModFormState,
|
||||
ModPageLoaderResult
|
||||
ModPageLoaderResult,
|
||||
SubmitModActionResult
|
||||
} from '../types'
|
||||
import { initializeFormState, MOD_DRAFT_CACHE_KEY } from '../utils'
|
||||
import { CheckboxField, InputField, InputFieldWithImageUpload } from './Inputs'
|
||||
@ -41,7 +41,11 @@ interface GameOption {
|
||||
export const ModForm = () => {
|
||||
const data = useLoaderData() as ModPageLoaderResult
|
||||
const mod = data?.mod
|
||||
const formErrors = useActionData() as FormErrors
|
||||
const actionData = useActionData() as SubmitModActionResult
|
||||
const formErrors = useMemo(
|
||||
() => (actionData?.type === 'validation' ? actionData.error : undefined),
|
||||
[actionData]
|
||||
)
|
||||
const navigation = useNavigation()
|
||||
const submit = useSubmit()
|
||||
const games = useGames()
|
||||
@ -56,7 +60,17 @@ export const ModForm = () => {
|
||||
)
|
||||
|
||||
useEffect(() => {
|
||||
!isEditing && setCache(formState)
|
||||
if (!isEditing) {
|
||||
const newCache = _.cloneDeep(formState)
|
||||
|
||||
// Remove aTag, dTag and published_at from cache
|
||||
// These are used for editing and try again timeout
|
||||
newCache.aTag = ''
|
||||
newCache.dTag = ''
|
||||
newCache.published_at = 0
|
||||
|
||||
setCache(newCache)
|
||||
}
|
||||
}, [formState, isEditing, setCache])
|
||||
|
||||
const editorRef = useRef<EditorRef>(null)
|
||||
@ -154,6 +168,32 @@ export const ModForm = () => {
|
||||
},
|
||||
[]
|
||||
)
|
||||
const [showTryAgainPopup, setShowTryAgainPopup] = useState<boolean>(false)
|
||||
useEffect(() => {
|
||||
const isTimeout = actionData?.type === 'timeout'
|
||||
setShowTryAgainPopup(isTimeout)
|
||||
if (isTimeout) {
|
||||
setFormState((prev) => ({
|
||||
...prev,
|
||||
aTag: actionData.data.aTag,
|
||||
dTag: actionData.data.dTag,
|
||||
published_at: actionData.data.published_at
|
||||
}))
|
||||
}
|
||||
}, [actionData])
|
||||
const handleTryAgainConfirm = useCallback(
|
||||
(confirm: boolean) => {
|
||||
setShowTryAgainPopup(false)
|
||||
|
||||
// Cancel if not confirmed
|
||||
if (!confirm) return
|
||||
submit(JSON.stringify(formState), {
|
||||
method: isEditing ? 'put' : 'post',
|
||||
encType: 'application/json'
|
||||
})
|
||||
},
|
||||
[formState, isEditing, submit]
|
||||
)
|
||||
|
||||
const [showConfirmPopup, setShowConfirmPopup] = useState<boolean>(false)
|
||||
const handleReset = useCallback(() => {
|
||||
@ -426,13 +466,21 @@ export const ModForm = () => {
|
||||
{navigation.state === 'submitting' ? 'Publishing...' : 'Publish'}
|
||||
</button>
|
||||
</div>
|
||||
{showTryAgainPopup && (
|
||||
<AlertPopup
|
||||
handleConfirm={handleTryAgainConfirm}
|
||||
handleClose={() => setShowTryAgainPopup(false)}
|
||||
header={'Publish'}
|
||||
label={`Submission timed out. Do you want to try again?`}
|
||||
/>
|
||||
)}
|
||||
{showConfirmPopup && (
|
||||
<AlertPopup
|
||||
handleConfirm={handleResetConfirm}
|
||||
handleClose={() => setShowConfirmPopup(false)}
|
||||
header={'Are you sure?'}
|
||||
label={
|
||||
mod
|
||||
isEditing
|
||||
? `Are you sure you want to clear all changes?`
|
||||
: `Are you sure you want to clear all field data?`
|
||||
}
|
||||
|
@ -5,7 +5,12 @@ import { ActionFunctionArgs, redirect } from 'react-router-dom'
|
||||
import { toast } from 'react-toastify'
|
||||
import { getModPageRoute } from 'routes'
|
||||
import { store } from 'store'
|
||||
import { FormErrors, ModFormState } from 'types'
|
||||
import {
|
||||
FormErrors,
|
||||
ModFormState,
|
||||
SubmitModActionResult,
|
||||
TimeoutError
|
||||
} from 'types'
|
||||
import {
|
||||
isReachable,
|
||||
isValidImageUrl,
|
||||
@ -14,7 +19,8 @@ import {
|
||||
LogType,
|
||||
MOD_DRAFT_CACHE_KEY,
|
||||
now,
|
||||
removeLocalStorageItem
|
||||
removeLocalStorageItem,
|
||||
timeout
|
||||
} from 'utils'
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
import { T_TAG_VALUE } from '../../constants'
|
||||
@ -51,29 +57,31 @@ export const submitModRouteAction =
|
||||
// Check for errors
|
||||
const formErrors = await validateState(formState)
|
||||
|
||||
// Return earily if there are any errors
|
||||
if (Object.keys(formErrors).length) return formErrors
|
||||
// Return early if there are any errors
|
||||
if (Object.keys(formErrors).length) {
|
||||
return {
|
||||
type: 'validation',
|
||||
error: formErrors
|
||||
} as SubmitModActionResult
|
||||
}
|
||||
|
||||
const currentTimeStamp = now()
|
||||
|
||||
// Check if we are editing or this is a new mob
|
||||
const { naddr } = params
|
||||
// Check if we are editing or this is a new mob
|
||||
const isEditing = naddr && request.method === 'PUT'
|
||||
|
||||
const uuid = formState.dTag || uuidv4()
|
||||
const currentTimeStamp = now()
|
||||
const aTag =
|
||||
formState.aTag || `${kinds.ClassifiedListing}:${hexPubkey}:${uuid}`
|
||||
const published_at = formState.published_at || currentTimeStamp
|
||||
|
||||
const tags = [
|
||||
['d', uuid],
|
||||
['a', aTag],
|
||||
['r', formState.rTag],
|
||||
['t', T_TAG_VALUE],
|
||||
[
|
||||
'published_at',
|
||||
isEditing
|
||||
? formState.published_at.toString()
|
||||
: currentTimeStamp.toString()
|
||||
],
|
||||
['published_at', published_at.toString()],
|
||||
['game', formState.game],
|
||||
['title', formState.title],
|
||||
['featuredImageUrl', formState.featuredImageUrl],
|
||||
@ -131,8 +139,13 @@ export const submitModRouteAction =
|
||||
}
|
||||
|
||||
const ndkEvent = new NDKEvent(ndkContext.ndk, signedEvent)
|
||||
const publishedOnRelays = await ndkContext.publish(ndkEvent)
|
||||
|
||||
// Publishing a mod sometime hangs (ndk.publish has internal timeout of 10s)
|
||||
// Make sure to actually throw a timeout error (30s)
|
||||
try {
|
||||
const publishedOnRelays = await Promise.race([
|
||||
ndkContext.publish(ndkEvent),
|
||||
timeout(30000)
|
||||
])
|
||||
// Handle cases where publishing failed or succeeded
|
||||
if (publishedOnRelays.length === 0) {
|
||||
toast.error('Failed to publish event on any relay')
|
||||
@ -154,9 +167,24 @@ export const submitModRouteAction =
|
||||
|
||||
return redirect(getModPageRoute(naddr))
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof TimeoutError) {
|
||||
const result: SubmitModActionResult = {
|
||||
type: 'timeout',
|
||||
data: {
|
||||
dTag: uuid,
|
||||
aTag,
|
||||
published_at: published_at
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Rethrow non-timeout for general catch
|
||||
throw error
|
||||
}
|
||||
} catch (error) {
|
||||
log(true, LogType.Error, 'Failed to sign the event!', error)
|
||||
toast.error('Failed to sign the event!')
|
||||
return null
|
||||
}
|
||||
|
||||
|
@ -68,6 +68,17 @@ export interface ModPageLoaderResult {
|
||||
isRepost: boolean
|
||||
}
|
||||
|
||||
export type SubmitModActionResult =
|
||||
| { type: 'validation'; error: FormErrors }
|
||||
| {
|
||||
type: 'timeout'
|
||||
data: {
|
||||
dTag: string
|
||||
aTag: string
|
||||
published_at: number
|
||||
}
|
||||
}
|
||||
|
||||
export interface FormErrors {
|
||||
game?: string
|
||||
title?: string
|
||||
|
Loading…
x
Reference in New Issue
Block a user