lint fix wip
This commit is contained in:
parent
81f6d303dd
commit
526fb5af6f
@ -308,7 +308,8 @@ function useAuthorPresentationState(pubkey: string | null, existingAuthorName?:
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!userConfirm(t('presentation.delete.confirm'))) {
|
const confirmed = await userConfirm(t('presentation.delete.confirm'))
|
||||||
|
if (!confirmed) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -179,7 +179,8 @@ export function Nip95ConfigManager({ onConfigChange }: Nip95ConfigManagerProps):
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleRemoveApi(id: string): Promise<void> {
|
async function handleRemoveApi(id: string): Promise<void> {
|
||||||
if (!userConfirm(t('settings.nip95.remove.confirm'))) {
|
const confirmed = await userConfirm(t('settings.nip95.remove.confirm'))
|
||||||
|
if (!confirmed) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -210,7 +210,8 @@ export function RelayManager({ onConfigChange }: RelayManagerProps): React.React
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function handleRemoveRelay(id: string): Promise<void> {
|
async function handleRemoveRelay(id: string): Promise<void> {
|
||||||
if (!userConfirm(t('settings.relay.remove.confirm'))) {
|
const confirmed = await userConfirm(t('settings.relay.remove.confirm'))
|
||||||
|
if (!confirmed) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -16,6 +16,12 @@ export function SponsoringForm({ author, onSuccess, onCancel }: SponsoringFormPr
|
|||||||
const [text, setText] = useState('')
|
const [text, setText] = useState('')
|
||||||
const [loading, setLoading] = useState(false)
|
const [loading, setLoading] = useState(false)
|
||||||
const [error, setError] = useState<string | null>(null)
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
const [instructions, setInstructions] = useState<{
|
||||||
|
authorAddress: string
|
||||||
|
platformAddress: string
|
||||||
|
authorBtc: string
|
||||||
|
platformBtc: string
|
||||||
|
} | null>(null)
|
||||||
|
|
||||||
const handleSubmit = async (e: React.FormEvent): Promise<void> => {
|
const handleSubmit = async (e: React.FormEvent): Promise<void> => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
@ -67,16 +73,15 @@ export function SponsoringForm({ author, onSuccess, onCancel }: SponsoringFormPr
|
|||||||
totalAmount: result.split.totalSats,
|
totalAmount: result.split.totalSats,
|
||||||
})
|
})
|
||||||
|
|
||||||
// Show instructions to user
|
// Show instructions inline (no-alert)
|
||||||
alert(t('sponsoring.form.instructions', {
|
setInstructions({
|
||||||
authorAddress: result.authorAddress,
|
authorAddress: result.authorAddress,
|
||||||
platformAddress: result.platformAddress,
|
platformAddress: result.platformAddress,
|
||||||
authorAmount: (result.split.authorSats / 100_000_000).toFixed(8),
|
authorBtc: (result.split.authorSats / 100_000_000).toFixed(8),
|
||||||
platformAmount: (result.split.platformSats / 100_000_000).toFixed(8),
|
platformBtc: (result.split.platformSats / 100_000_000).toFixed(8),
|
||||||
}))
|
})
|
||||||
|
|
||||||
setText('')
|
setText('')
|
||||||
onSuccess?.()
|
|
||||||
} catch (submitError) {
|
} catch (submitError) {
|
||||||
setError(submitError instanceof Error ? submitError.message : t('sponsoring.form.error.paymentFailed'))
|
setError(submitError instanceof Error ? submitError.message : t('sponsoring.form.error.paymentFailed'))
|
||||||
} finally {
|
} finally {
|
||||||
@ -108,6 +113,44 @@ export function SponsoringForm({ author, onSuccess, onCancel }: SponsoringFormPr
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (instructions) {
|
||||||
|
return (
|
||||||
|
<div className="border border-neon-cyan/30 rounded-lg p-4 bg-cyber-dark space-y-4" role="dialog" aria-modal="true">
|
||||||
|
<h3 className="text-lg font-semibold text-neon-cyan">{t('sponsoring.form.title')}</h3>
|
||||||
|
<p className="text-sm text-cyber-accent/70">
|
||||||
|
{t('sponsoring.form.instructions', {
|
||||||
|
authorAddress: instructions.authorAddress,
|
||||||
|
platformAddress: instructions.platformAddress,
|
||||||
|
authorAmount: instructions.authorBtc,
|
||||||
|
platformAmount: instructions.platformBtc,
|
||||||
|
})}
|
||||||
|
</p>
|
||||||
|
<div className="flex gap-3">
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
setInstructions(null)
|
||||||
|
onSuccess?.()
|
||||||
|
}}
|
||||||
|
className="px-4 py-2 bg-neon-green/20 hover:bg-neon-green/30 text-neon-green rounded-lg font-medium transition-all border border-neon-green/50"
|
||||||
|
>
|
||||||
|
{t('common.close')}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
type="button"
|
||||||
|
onClick={() => {
|
||||||
|
setInstructions(null)
|
||||||
|
onCancel?.()
|
||||||
|
}}
|
||||||
|
className="px-4 py-2 bg-neon-cyan/10 hover:bg-neon-cyan/20 text-neon-cyan rounded-lg font-medium transition-all border border-neon-cyan/30"
|
||||||
|
>
|
||||||
|
{t('common.cancel')}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={(e) => {
|
<form onSubmit={(e) => {
|
||||||
void handleSubmit(e)
|
void handleSubmit(e)
|
||||||
|
|||||||
@ -50,7 +50,7 @@ export function useDocs(docs: DocLink[]): {
|
|||||||
}, [docs])
|
}, [docs])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadDoc('user-guide')
|
void loadDoc('user-guide')
|
||||||
}, [loadDoc])
|
}, [loadDoc])
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@ -31,12 +31,12 @@ export function useI18n(locale: Locale = 'fr'): {
|
|||||||
|
|
||||||
if (frResponse.ok) {
|
if (frResponse.ok) {
|
||||||
const frText = await frResponse.text()
|
const frText = await frResponse.text()
|
||||||
await loadTranslations('fr', frText)
|
loadTranslations('fr', frText)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (enResponse.ok) {
|
if (enResponse.ok) {
|
||||||
const enText = await enResponse.text()
|
const enText = await enResponse.text()
|
||||||
await loadTranslations('en', enText)
|
loadTranslations('en', enText)
|
||||||
}
|
}
|
||||||
|
|
||||||
setLocale(initialLocale)
|
setLocale(initialLocale)
|
||||||
|
|||||||
20
lib/alby.ts
20
lib/alby.ts
@ -193,19 +193,23 @@ export class AlbyService {
|
|||||||
// Generate a simple hash-like identifier
|
// Generate a simple hash-like identifier
|
||||||
// In practice, payment verification should be done via zap receipts
|
// In practice, payment verification should be done via zap receipts
|
||||||
if (typeof window !== 'undefined' && window.crypto) {
|
if (typeof window !== 'undefined' && window.crypto) {
|
||||||
// Use a simple hash for identification
|
const hash32 = hashStringToUint32(invoice)
|
||||||
let hash = 0
|
return hash32.toString(16).padStart(16, '0')
|
||||||
for (let i = 0; i < invoice.length; i++) {
|
|
||||||
const char = invoice.charCodeAt(i)
|
|
||||||
hash = ((hash << 5) - hash) + char
|
|
||||||
hash = hash & hash // Convert to 32-bit integer
|
|
||||||
}
|
|
||||||
return Math.abs(hash).toString(16).padStart(16, '0')
|
|
||||||
}
|
}
|
||||||
return Date.now().toString(16)
|
return Date.now().toString(16)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function hashStringToUint32(value: string): number {
|
||||||
|
// Deterministic 32-bit hash without bitwise operators (rule: no-bitwise)
|
||||||
|
const mod = 2n ** 32n
|
||||||
|
let hash = 0n
|
||||||
|
for (let i = 0; i < value.length; i++) {
|
||||||
|
hash = (hash * 31n + BigInt(value.charCodeAt(i))) % mod
|
||||||
|
}
|
||||||
|
return Number(hash)
|
||||||
|
}
|
||||||
|
|
||||||
// Singleton instance
|
// Singleton instance
|
||||||
let albyServiceInstance: AlbyService | null = null
|
let albyServiceInstance: AlbyService | null = null
|
||||||
|
|
||||||
|
|||||||
@ -32,6 +32,7 @@ function requireCategory(category?: ArticleDraft['category']): asserts category
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function ensurePresentation(authorPubkey: string): Promise<string> {
|
async function ensurePresentation(authorPubkey: string): Promise<string> {
|
||||||
|
const { articlePublisher } = await import('./articlePublisher')
|
||||||
const presentation = await articlePublisher.getAuthorPresentation(authorPubkey)
|
const presentation = await articlePublisher.getAuthorPresentation(authorPubkey)
|
||||||
if (!presentation) {
|
if (!presentation) {
|
||||||
throw new Error('Vous devez créer un article de présentation avant de publier des articles.')
|
throw new Error('Vous devez créer un article de présentation avant de publier des articles.')
|
||||||
@ -659,14 +660,14 @@ export async function deleteArticleEvent(articleId: string, authorPubkey: string
|
|||||||
if (!privateKey) {
|
if (!privateKey) {
|
||||||
throw new Error('Private key required for signing')
|
throw new Error('Private key required for signing')
|
||||||
}
|
}
|
||||||
const { writeOrchestrator } = await import('./writeOrchestrator')
|
const { writeOrchestrator: writeOrchestratorInstance } = await import('./writeOrchestrator')
|
||||||
writeOrchestrator.setPrivateKey(privateKey)
|
writeOrchestratorInstance.setPrivateKey(privateKey)
|
||||||
|
|
||||||
// Finalize event
|
// Finalize event
|
||||||
const { finalizeEvent } = await import('nostr-tools')
|
const { finalizeEvent: finalizeNostrEvent } = await import('nostr-tools')
|
||||||
const { hexToBytes } = await import('nostr-tools/utils')
|
const { hexToBytes: hexToBytesUtil } = await import('nostr-tools/utils')
|
||||||
const secretKey = hexToBytes(privateKey)
|
const secretKey = hexToBytesUtil(privateKey)
|
||||||
const event = finalizeEvent(deleteEventTemplate, secretKey)
|
const event = finalizeNostrEvent(deleteEventTemplate, secretKey)
|
||||||
|
|
||||||
// Get active relays
|
// Get active relays
|
||||||
const { relaySessionManager } = await import('./relaySessionManager')
|
const { relaySessionManager } = await import('./relaySessionManager')
|
||||||
@ -675,7 +676,7 @@ export async function deleteArticleEvent(articleId: string, authorPubkey: string
|
|||||||
const relays = activeRelays.length > 0 ? activeRelays : [await getPrimaryRelay()]
|
const relays = activeRelays.length > 0 ? activeRelays : [await getPrimaryRelay()]
|
||||||
|
|
||||||
// Publish via writeOrchestrator (parallel network + local write)
|
// Publish via writeOrchestrator (parallel network + local write)
|
||||||
const result = await writeOrchestrator.writeAndPublish(
|
const result = await writeOrchestratorInstance.writeAndPublish(
|
||||||
{
|
{
|
||||||
objectType: 'publication',
|
objectType: 'publication',
|
||||||
hash,
|
hash,
|
||||||
@ -694,5 +695,5 @@ export async function deleteArticleEvent(articleId: string, authorPubkey: string
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Re-export for convenience to avoid circular imports in hooks
|
// Re-export for convenience to avoid circular imports in hooks
|
||||||
import { articlePublisher } from './articlePublisher'
|
export { articlePublisher } from './articlePublisher'
|
||||||
export const getStoredContent = getStoredPrivateContent
|
export const getStoredContent = getStoredPrivateContent
|
||||||
|
|||||||
@ -193,19 +193,9 @@ export async function parsePresentationEvent(event: Event): Promise<import('@/ty
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Generate hash from author data
|
// Generate hash from author data
|
||||||
let mainnetAddress: string | undefined
|
const mainnetAddress = profileData?.mainnetAddress ?? (typeof tags.mainnetAddress === 'string' ? tags.mainnetAddress : undefined)
|
||||||
if (profileData?.mainnetAddress) {
|
|
||||||
mainnetAddress = profileData.mainnetAddress
|
|
||||||
} else if (typeof tags.mainnetAddress === 'string') {
|
|
||||||
mainnetAddress = tags.mainnetAddress
|
|
||||||
}
|
|
||||||
|
|
||||||
let pictureUrl: string | undefined
|
const pictureUrl = profileData?.pictureUrl ?? (typeof tags.pictureUrl === 'string' ? tags.pictureUrl : undefined)
|
||||||
if (profileData?.pictureUrl) {
|
|
||||||
pictureUrl = profileData.pictureUrl
|
|
||||||
} else if (typeof tags.pictureUrl === 'string') {
|
|
||||||
pictureUrl = tags.pictureUrl
|
|
||||||
}
|
|
||||||
|
|
||||||
hash = await generateAuthorHashId({
|
hash = await generateAuthorHashId({
|
||||||
pubkey: event.pubkey,
|
pubkey: event.pubkey,
|
||||||
|
|||||||
@ -205,8 +205,7 @@ export async function encryptAndPublish(
|
|||||||
|
|
||||||
if (publishResult && 'event' in publishResult && 'relayStatuses' in publishResult) {
|
if (publishResult && 'event' in publishResult && 'relayStatuses' in publishResult) {
|
||||||
// New format with statuses
|
// New format with statuses
|
||||||
event = publishResult.event
|
;({ event, relayStatuses } = publishResult)
|
||||||
relayStatuses = publishResult.relayStatuses
|
|
||||||
} else if (publishResult && 'id' in publishResult) {
|
} else if (publishResult && 'id' in publishResult) {
|
||||||
// Old format (Event)
|
// Old format (Event)
|
||||||
event = publishResult
|
event = publishResult
|
||||||
|
|||||||
@ -32,6 +32,25 @@ export function useSyncProgress(options: UseSyncProgressOptions = {}): UseSyncPr
|
|||||||
const onCompleteRef = useRef(onComplete)
|
const onCompleteRef = useRef(onComplete)
|
||||||
const isMonitoringRef = useRef(false)
|
const isMonitoringRef = useRef(false)
|
||||||
|
|
||||||
|
function stopMonitoring(): void {
|
||||||
|
if (!isMonitoringRef.current) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isMonitoringRef.current = false
|
||||||
|
setIsSyncing(false)
|
||||||
|
|
||||||
|
if (intervalRef.current) {
|
||||||
|
clearInterval(intervalRef.current)
|
||||||
|
intervalRef.current = null
|
||||||
|
}
|
||||||
|
|
||||||
|
if (timeoutRef.current) {
|
||||||
|
clearTimeout(timeoutRef.current)
|
||||||
|
timeoutRef.current = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Update onComplete ref when it changes
|
// Update onComplete ref when it changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
onCompleteRef.current = onComplete
|
onCompleteRef.current = onComplete
|
||||||
@ -72,25 +91,6 @@ export function useSyncProgress(options: UseSyncProgressOptions = {}): UseSyncPr
|
|||||||
}, maxDuration)
|
}, maxDuration)
|
||||||
}
|
}
|
||||||
|
|
||||||
const stopMonitoring = (): void => {
|
|
||||||
if (!isMonitoringRef.current) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
isMonitoringRef.current = false
|
|
||||||
setIsSyncing(false)
|
|
||||||
|
|
||||||
if (intervalRef.current) {
|
|
||||||
clearInterval(intervalRef.current)
|
|
||||||
intervalRef.current = null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (timeoutRef.current) {
|
|
||||||
clearTimeout(timeoutRef.current)
|
|
||||||
timeoutRef.current = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Cleanup on unmount
|
// Cleanup on unmount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
|
|||||||
@ -83,13 +83,13 @@ function expandDictionary(): MnemonicIcon[] {
|
|||||||
const DICTIONARY = expandDictionary()
|
const DICTIONARY = expandDictionary()
|
||||||
|
|
||||||
function hashString(str: string): number {
|
function hashString(str: string): number {
|
||||||
let hash = 0
|
// Deterministic hash without bitwise operators (rule: no-bitwise)
|
||||||
|
const mod = 2n ** 32n
|
||||||
|
let hash = 0n
|
||||||
for (let i = 0; i < str.length; i++) {
|
for (let i = 0; i < str.length; i++) {
|
||||||
const char = str.charCodeAt(i)
|
hash = (hash * 31n + BigInt(str.charCodeAt(i))) % mod
|
||||||
hash = ((hash << 5) - hash) + char
|
|
||||||
hash = hash & hash
|
|
||||||
}
|
}
|
||||||
return Math.abs(hash)
|
return Number(hash)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateMnemonicIcons(pubkey: string): string[] {
|
export function generateMnemonicIcons(pubkey: string): string[] {
|
||||||
|
|||||||
@ -496,7 +496,7 @@ class NostrService {
|
|||||||
* Searches all object types to find and update the event
|
* Searches all object types to find and update the event
|
||||||
*/
|
*/
|
||||||
private async updatePublishedStatus(eventId: string, published: false | string[]): Promise<void> {
|
private async updatePublishedStatus(eventId: string, published: false | string[]): Promise<void> {
|
||||||
const { objectCache } = await import('./objectCache')
|
const { objectCache: objectCacheModule } = await import('./objectCache')
|
||||||
const objectTypes: Array<import('./objectCache').ObjectType> = ['author', 'series', 'publication', 'review', 'purchase', 'sponsoring', 'review_tip', 'payment_note']
|
const objectTypes: Array<import('./objectCache').ObjectType> = ['author', 'series', 'publication', 'review', 'purchase', 'sponsoring', 'review_tip', 'payment_note']
|
||||||
|
|
||||||
// Load writeService once
|
// Load writeService once
|
||||||
@ -505,7 +505,7 @@ class NostrService {
|
|||||||
// First try to find in unpublished objects (faster)
|
// First try to find in unpublished objects (faster)
|
||||||
for (const objectType of objectTypes) {
|
for (const objectType of objectTypes) {
|
||||||
try {
|
try {
|
||||||
const unpublished = await objectCache.getUnpublished(objectType)
|
const unpublished = await objectCacheModule.getUnpublished(objectType)
|
||||||
const matching = unpublished.find((obj) => obj.event.id === eventId)
|
const matching = unpublished.find((obj) => obj.event.id === eventId)
|
||||||
if (matching) {
|
if (matching) {
|
||||||
await writeService.updatePublished(objectType, matching.id, published)
|
await writeService.updatePublished(objectType, matching.id, published)
|
||||||
@ -521,7 +521,7 @@ class NostrService {
|
|||||||
for (const objectType of objectTypes) {
|
for (const objectType of objectTypes) {
|
||||||
try {
|
try {
|
||||||
// Use getAll to search all objects
|
// Use getAll to search all objects
|
||||||
const allObjects = await objectCache.getAll(objectType)
|
const allObjects = await objectCacheModule.getAll(objectType)
|
||||||
const matching = allObjects.find((obj) => {
|
const matching = allObjects.find((obj) => {
|
||||||
const cachedObj = obj as CachedObject
|
const cachedObj = obj as CachedObject
|
||||||
return cachedObj.event?.id === eventId
|
return cachedObj.event?.id === eventId
|
||||||
|
|||||||
@ -162,20 +162,13 @@ class NotificationDetector {
|
|||||||
})
|
})
|
||||||
|
|
||||||
for (const obj of userObjects) {
|
for (const obj of userObjects) {
|
||||||
if (!Array.isArray(obj.published) || obj.published.length === 0) {
|
if (Array.isArray(obj.published) && obj.published.length > 0) {
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
const eventId = obj.id.split(':')[1] ?? obj.id
|
const eventId = obj.id.split(':')[1] ?? obj.id
|
||||||
const existing = await notificationService.getNotificationByEventId(eventId)
|
const existing = await notificationService.getNotificationByEventId(eventId)
|
||||||
if (existing?.type === 'published') {
|
const alreadyNotified = existing?.type === 'published'
|
||||||
continue
|
const recentlyCreated = obj.createdAt * 1000 > oneHourAgo
|
||||||
}
|
|
||||||
|
|
||||||
if (obj.createdAt * 1000 <= oneHourAgo) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
|
if (!alreadyNotified && recentlyCreated) {
|
||||||
const relays = obj.published
|
const relays = obj.published
|
||||||
await notificationService.createNotification({
|
await notificationService.createNotification({
|
||||||
type: 'published',
|
type: 'published',
|
||||||
@ -190,6 +183,8 @@ class NotificationDetector {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[NotificationDetector] Error scanning published status for ${objectType}:`, error)
|
console.error(`[NotificationDetector] Error scanning published status for ${objectType}:`, error)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -260,12 +260,12 @@ class ObjectCacheService {
|
|||||||
cursor.continue()
|
cursor.continue()
|
||||||
} else {
|
} else {
|
||||||
// Sort by version descending and return the latest
|
// Sort by version descending and return the latest
|
||||||
if (objects.length > 0) {
|
if (objects.length === 0) {
|
||||||
|
resolve(null)
|
||||||
|
return
|
||||||
|
}
|
||||||
objects.sort((a, b) => b.version - a.version)
|
objects.sort((a, b) => b.version - a.version)
|
||||||
resolve(objects[0]?.parsed ?? null)
|
resolve(objects[0]?.parsed ?? null)
|
||||||
} else {
|
|
||||||
resolve(null)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -336,12 +336,12 @@ class ObjectCacheService {
|
|||||||
cursor.continue()
|
cursor.continue()
|
||||||
} else {
|
} else {
|
||||||
// Sort by version descending and return the latest
|
// Sort by version descending and return the latest
|
||||||
if (objects.length > 0) {
|
if (objects.length === 0) {
|
||||||
|
resolve(null)
|
||||||
|
return
|
||||||
|
}
|
||||||
objects.sort((a, b) => b.version - a.version)
|
objects.sort((a, b) => b.version - a.version)
|
||||||
resolve((objects[0]?.parsed ?? null) as AuthorPresentationArticle | null)
|
resolve((objects[0]?.parsed ?? null) as AuthorPresentationArticle | null)
|
||||||
} else {
|
|
||||||
resolve(null)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -129,24 +129,20 @@ export function verifyPaymentSplit(
|
|||||||
platformAmount?: number
|
platformAmount?: number
|
||||||
): boolean {
|
): boolean {
|
||||||
switch (type) {
|
switch (type) {
|
||||||
case 'article':
|
case 'article': {
|
||||||
const articleSplit = calculateArticleSplit(totalAmount)
|
const articleSplit = calculateArticleSplit(totalAmount)
|
||||||
return (
|
return articleSplit.author === (authorAmount ?? 0) && articleSplit.platform === (platformAmount ?? 0)
|
||||||
articleSplit.author === (authorAmount ?? 0) && articleSplit.platform === (platformAmount ?? 0)
|
}
|
||||||
)
|
|
||||||
|
|
||||||
case 'review':
|
case 'review': {
|
||||||
const reviewSplit = calculateReviewSplit(totalAmount)
|
const reviewSplit = calculateReviewSplit(totalAmount)
|
||||||
return (
|
return reviewSplit.reviewer === (authorAmount ?? 0) && reviewSplit.platform === (platformAmount ?? 0)
|
||||||
reviewSplit.reviewer === (authorAmount ?? 0) && reviewSplit.platform === (platformAmount ?? 0)
|
}
|
||||||
)
|
|
||||||
|
|
||||||
case 'sponsoring':
|
case 'sponsoring': {
|
||||||
const sponsoringSplit = calculateSponsoringSplit(totalAmount)
|
const sponsoringSplit = calculateSponsoringSplit(totalAmount)
|
||||||
return (
|
return sponsoringSplit.authorSats === (authorAmount ?? 0) && sponsoringSplit.platformSats === (platformAmount ?? 0)
|
||||||
sponsoringSplit.authorSats === (authorAmount ?? 0) &&
|
}
|
||||||
sponsoringSplit.platformSats === (platformAmount ?? 0)
|
|
||||||
)
|
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return false
|
return false
|
||||||
|
|||||||
@ -188,7 +188,7 @@ class PlatformSyncService {
|
|||||||
|
|
||||||
sub.on('eose', (): void => {
|
sub.on('eose', (): void => {
|
||||||
console.warn(`[PlatformSync] Relay ${relayUrl} sent EOSE signal`)
|
console.warn(`[PlatformSync] Relay ${relayUrl} sent EOSE signal`)
|
||||||
finalize()
|
void finalize()
|
||||||
resolve()
|
resolve()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -424,7 +424,7 @@ class PlatformSyncService {
|
|||||||
if (isReady) {
|
if (isReady) {
|
||||||
await swClient.stopPlatformSync()
|
await swClient.stopPlatformSync()
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch {
|
||||||
// Ignore errors
|
// Ignore errors
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,9 +8,111 @@
|
|||||||
* that cannot be replicated with React modals without significant refactoring.
|
* that cannot be replicated with React modals without significant refactoring.
|
||||||
* Used only for critical destructive actions (delete operations).
|
* Used only for critical destructive actions (delete operations).
|
||||||
*/
|
*/
|
||||||
export function userConfirm(message: string): boolean {
|
export function userConfirm(message: string): Promise<boolean> {
|
||||||
// window.confirm is the native browser confirmation dialog
|
return confirmOverlay(message)
|
||||||
// This is intentionally used here for critical confirmations
|
}
|
||||||
// that must block the UI thread until user responds
|
|
||||||
return window.confirm(message)
|
function confirmOverlay(message: string): Promise<boolean> {
|
||||||
|
const doc = globalThis.document
|
||||||
|
if (!doc) {
|
||||||
|
return Promise.resolve(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Promise((resolve) => {
|
||||||
|
const overlay = doc.createElement('div')
|
||||||
|
overlay.setAttribute('role', 'dialog')
|
||||||
|
overlay.setAttribute('aria-modal', 'true')
|
||||||
|
overlay.tabIndex = -1
|
||||||
|
overlay.style.position = 'fixed'
|
||||||
|
overlay.style.inset = '0'
|
||||||
|
overlay.style.background = 'rgba(0,0,0,0.6)'
|
||||||
|
overlay.style.display = 'flex'
|
||||||
|
overlay.style.alignItems = 'center'
|
||||||
|
overlay.style.justifyContent = 'center'
|
||||||
|
overlay.style.zIndex = '9999'
|
||||||
|
|
||||||
|
const panel = doc.createElement('div')
|
||||||
|
panel.style.background = '#fff'
|
||||||
|
panel.style.borderRadius = '12px'
|
||||||
|
panel.style.padding = '16px'
|
||||||
|
panel.style.maxWidth = '520px'
|
||||||
|
panel.style.width = 'calc(100% - 32px)'
|
||||||
|
panel.style.boxShadow = '0 10px 30px rgba(0,0,0,0.35)'
|
||||||
|
|
||||||
|
const text = doc.createElement('p')
|
||||||
|
text.textContent = message
|
||||||
|
text.style.margin = '0 0 16px 0'
|
||||||
|
text.style.color = '#111827'
|
||||||
|
|
||||||
|
const buttons = doc.createElement('div')
|
||||||
|
buttons.style.display = 'flex'
|
||||||
|
buttons.style.gap = '12px'
|
||||||
|
buttons.style.justifyContent = 'flex-end'
|
||||||
|
|
||||||
|
const cancel = doc.createElement('button')
|
||||||
|
cancel.type = 'button'
|
||||||
|
cancel.textContent = 'Cancel'
|
||||||
|
cancel.style.padding = '8px 12px'
|
||||||
|
cancel.style.borderRadius = '10px'
|
||||||
|
cancel.style.border = '1px solid #e5e7eb'
|
||||||
|
cancel.style.background = '#f3f4f6'
|
||||||
|
|
||||||
|
const confirm = doc.createElement('button')
|
||||||
|
confirm.type = 'button'
|
||||||
|
confirm.textContent = 'Confirm'
|
||||||
|
confirm.style.padding = '8px 12px'
|
||||||
|
confirm.style.borderRadius = '10px'
|
||||||
|
confirm.style.border = '1px solid #ef4444'
|
||||||
|
confirm.style.background = '#fee2e2'
|
||||||
|
confirm.style.color = '#991b1b'
|
||||||
|
|
||||||
|
buttons.append(cancel, confirm)
|
||||||
|
panel.append(text, buttons)
|
||||||
|
overlay.append(panel)
|
||||||
|
doc.body.append(overlay)
|
||||||
|
|
||||||
|
overlay.focus()
|
||||||
|
|
||||||
|
let resolved = false
|
||||||
|
|
||||||
|
const resolveOnce = (next: boolean): void => {
|
||||||
|
if (resolved) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
resolved = true
|
||||||
|
cleanup()
|
||||||
|
resolve(next)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onCancel(): void {
|
||||||
|
resolveOnce(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onConfirm(): void {
|
||||||
|
resolveOnce(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
function onKeyDown(e: KeyboardEvent): void {
|
||||||
|
if (e.key === 'Escape') {
|
||||||
|
e.preventDefault()
|
||||||
|
resolveOnce(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
e.preventDefault()
|
||||||
|
resolveOnce(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function cleanup(): void {
|
||||||
|
overlay.removeEventListener('keydown', onKeyDown)
|
||||||
|
cancel.removeEventListener('click', onCancel)
|
||||||
|
confirm.removeEventListener('click', onConfirm)
|
||||||
|
overlay.remove()
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel.addEventListener('click', onCancel)
|
||||||
|
confirm.addEventListener('click', onConfirm)
|
||||||
|
overlay.addEventListener('keydown', onKeyDown)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -264,7 +264,7 @@ class WriteService {
|
|||||||
const errorData = readWorkerErrorData(responseData)
|
const errorData = readWorkerErrorData(responseData)
|
||||||
const { taskId } = errorData
|
const { taskId } = errorData
|
||||||
const isUpdatePublished =
|
const isUpdatePublished =
|
||||||
errorData.originalType === 'UPDATE_PUBLISHED' || (taskId !== undefined && taskId.startsWith('UPDATE_PUBLISHED'))
|
errorData.originalType === 'UPDATE_PUBLISHED' || taskId?.startsWith('UPDATE_PUBLISHED') === true
|
||||||
if (!isUpdatePublished) {
|
if (!isUpdatePublished) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -322,7 +322,7 @@ class WriteService {
|
|||||||
const errorData = readWorkerErrorData(responseData)
|
const errorData = readWorkerErrorData(responseData)
|
||||||
const { taskId } = errorData
|
const { taskId } = errorData
|
||||||
const isCreateNotification =
|
const isCreateNotification =
|
||||||
errorData.originalType === 'CREATE_NOTIFICATION' || (taskId !== undefined && taskId.startsWith('CREATE_NOTIFICATION'))
|
errorData.originalType === 'CREATE_NOTIFICATION' || taskId?.startsWith('CREATE_NOTIFICATION') === true
|
||||||
if (!isCreateNotification) {
|
if (!isCreateNotification) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@ -361,7 +361,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
// Try to extract error message from HTML if possible
|
// Try to extract error message from HTML if possible
|
||||||
const titleMatch = response.body.match(/<title[^>]*>([^<]+)<\/title>/i)
|
const titleMatch = response.body.match(/<title[^>]*>([^<]+)<\/title>/i)
|
||||||
const h1Match = response.body.match(/<h1[^>]*>([^<]+)<\/h1>/i)
|
const h1Match = response.body.match(/<h1[^>]*>([^<]+)<\/h1>/i)
|
||||||
const errorText = titleMatch?.[1] || h1Match?.[1] || 'HTML error page returned'
|
const errorText = titleMatch?.[1] ?? h1Match?.[1] ?? 'HTML error page returned'
|
||||||
|
|
||||||
// Check if it's a 404 or other error page
|
// Check if it's a 404 or other error page
|
||||||
const is404 = response.body.includes('404') || response.body.includes('Not Found') || titleMatch?.[1]?.includes('404')
|
const is404 = response.body.includes('404') || response.body.includes('Not Found') || titleMatch?.[1]?.includes('404')
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user