lint fix wip
This commit is contained in:
parent
5b7b77aa9a
commit
c1442886cf
@ -226,7 +226,7 @@ function PresentationForm({
|
|||||||
>
|
>
|
||||||
{loading ?? deleting
|
{loading ?? deleting
|
||||||
? t('publish.publishing')
|
? t('publish.publishing')
|
||||||
: hasExistingPresentation
|
: hasExistingPresentation === true
|
||||||
? t('presentation.update.button')
|
? t('presentation.update.button')
|
||||||
: t('publish.button')}
|
: t('publish.button')}
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@ -15,6 +15,8 @@ export function Nip95ConfigManager({ onConfigChange }: Nip95ConfigManagerProps):
|
|||||||
const [editingId, setEditingId] = useState<string | null>(null)
|
const [editingId, setEditingId] = useState<string | null>(null)
|
||||||
const [newUrl, setNewUrl] = useState('')
|
const [newUrl, setNewUrl] = useState('')
|
||||||
const [showAddForm, setShowAddForm] = useState(false)
|
const [showAddForm, setShowAddForm] = useState(false)
|
||||||
|
const [draggedId, setDraggedId] = useState<string | null>(null)
|
||||||
|
const [dragOverId, setDragOverId] = useState<string | null>(null)
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
void loadApis()
|
void loadApis()
|
||||||
@ -47,18 +49,95 @@ export function Nip95ConfigManager({ onConfigChange }: Nip95ConfigManagerProps):
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function handleUpdatePriority(id: string, priority: number): Promise<void> {
|
async function handleUpdatePriorities(newOrder: Nip95Config[]): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await configStorage.updateNip95Api(id, { priority })
|
// Update priorities based on new order (priority = index + 1, lower number = higher priority)
|
||||||
|
const updatePromises = newOrder.map((api, index) => {
|
||||||
|
const newPriority = index + 1
|
||||||
|
if (api.priority !== newPriority) {
|
||||||
|
return configStorage.updateNip95Api(api.id, { priority: newPriority })
|
||||||
|
}
|
||||||
|
return Promise.resolve()
|
||||||
|
})
|
||||||
|
|
||||||
|
await Promise.all(updatePromises)
|
||||||
await loadApis()
|
await loadApis()
|
||||||
onConfigChange?.()
|
onConfigChange?.()
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const errorMessage = e instanceof Error ? e.message : t('settings.nip95.error.priorityFailed')
|
const errorMessage = e instanceof Error ? e.message : t('settings.nip95.error.priorityFailed')
|
||||||
setError(errorMessage)
|
setError(errorMessage)
|
||||||
console.error('Error updating priority:', e)
|
console.error('Error updating priorities:', e)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleDragStart(e: React.DragEvent<HTMLDivElement>, id: string): void {
|
||||||
|
setDraggedId(id)
|
||||||
|
e.dataTransfer.effectAllowed = 'move'
|
||||||
|
e.dataTransfer.setData('text/plain', id)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDragOver(e: React.DragEvent<HTMLDivElement>, id: string): void {
|
||||||
|
e.preventDefault()
|
||||||
|
e.dataTransfer.dropEffect = 'move'
|
||||||
|
setDragOverId(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDragLeave(): void {
|
||||||
|
setDragOverId(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleDrop(e: React.DragEvent<HTMLDivElement>, targetId: string): void {
|
||||||
|
e.preventDefault()
|
||||||
|
setDragOverId(null)
|
||||||
|
|
||||||
|
if (!draggedId || draggedId === targetId) {
|
||||||
|
setDraggedId(null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const draggedIndex = apis.findIndex((api) => api.id === draggedId)
|
||||||
|
const targetIndex = apis.findIndex((api) => api.id === targetId)
|
||||||
|
|
||||||
|
if (draggedIndex === -1 || targetIndex === -1) {
|
||||||
|
setDraggedId(null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reorder the array
|
||||||
|
const newApis = [...apis]
|
||||||
|
const removed = newApis[draggedIndex]
|
||||||
|
if (!removed) {
|
||||||
|
setDraggedId(null)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
newApis.splice(draggedIndex, 1)
|
||||||
|
newApis.splice(targetIndex, 0, removed)
|
||||||
|
|
||||||
|
setApis(newApis)
|
||||||
|
setDraggedId(null)
|
||||||
|
|
||||||
|
// Update priorities based on new order
|
||||||
|
void handleUpdatePriorities(newApis)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DragHandle(): React.ReactElement {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-1 cursor-grab active:cursor-grabbing text-cyber-accent/50 hover:text-neon-cyan transition-colors">
|
||||||
|
<svg width="12" height="12" viewBox="0 0 12 12" fill="currentColor">
|
||||||
|
<circle cx="2" cy="2" r="1.5" />
|
||||||
|
<circle cx="6" cy="2" r="1.5" />
|
||||||
|
<circle cx="10" cy="2" r="1.5" />
|
||||||
|
<circle cx="2" cy="6" r="1.5" />
|
||||||
|
<circle cx="6" cy="6" r="1.5" />
|
||||||
|
<circle cx="10" cy="6" r="1.5" />
|
||||||
|
<circle cx="2" cy="10" r="1.5" />
|
||||||
|
<circle cx="6" cy="10" r="1.5" />
|
||||||
|
<circle cx="10" cy="10" r="1.5" />
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
async function handleUpdateUrl(id: string, url: string): Promise<void> {
|
async function handleUpdateUrl(id: string, url: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await configStorage.updateNip95Api(id, { url })
|
await configStorage.updateNip95Api(id, { url })
|
||||||
@ -186,13 +265,40 @@ export function Nip95ConfigManager({ onConfigChange }: Nip95ConfigManagerProps):
|
|||||||
{t('settings.nip95.empty')}
|
{t('settings.nip95.empty')}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
apis.map((api) => (
|
apis.map((api, index) => (
|
||||||
<div
|
<div
|
||||||
key={api.id}
|
key={api.id}
|
||||||
className="bg-cyber-dark border border-neon-cyan/30 rounded p-4 space-y-3"
|
onDragOver={(e) => {
|
||||||
|
handleDragOver(e, api.id)
|
||||||
|
}}
|
||||||
|
onDragLeave={handleDragLeave}
|
||||||
|
onDrop={(e) => {
|
||||||
|
handleDrop(e, api.id)
|
||||||
|
}}
|
||||||
|
className={`bg-cyber-dark border rounded p-4 space-y-3 transition-all ${
|
||||||
|
draggedId === api.id
|
||||||
|
? 'opacity-50 border-neon-cyan'
|
||||||
|
: dragOverId === api.id
|
||||||
|
? 'border-neon-green shadow-lg'
|
||||||
|
: 'border-neon-cyan/30'
|
||||||
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-start justify-between gap-4">
|
<div className="flex items-start justify-between gap-4">
|
||||||
<div className="flex-1">
|
<div className="flex items-center gap-3 flex-1">
|
||||||
|
<div
|
||||||
|
className="drag-handle cursor-grab active:cursor-grabbing"
|
||||||
|
draggable
|
||||||
|
onDragStart={(e) => {
|
||||||
|
handleDragStart(e, api.id)
|
||||||
|
e.stopPropagation()
|
||||||
|
}}
|
||||||
|
onMouseDown={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<DragHandle />
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
{editingId === api.id ? (
|
{editingId === api.id ? (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<input
|
<input
|
||||||
@ -225,6 +331,7 @@ export function Nip95ConfigManager({ onConfigChange }: Nip95ConfigManagerProps):
|
|||||||
{api.url}
|
{api.url}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
<label className="flex items-center gap-2 cursor-pointer">
|
<label className="flex items-center gap-2 cursor-pointer">
|
||||||
@ -247,23 +354,10 @@ export function Nip95ConfigManager({ onConfigChange }: Nip95ConfigManagerProps):
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2 text-xs text-cyber-accent/70">
|
||||||
<label className="flex items-center gap-2">
|
<span>
|
||||||
<span className="text-sm text-cyber-accent">{t('settings.nip95.list.priority')}:</span>
|
{t('settings.nip95.list.priorityLabel', { priority: index + 1, id: api.id })}
|
||||||
<input
|
</span>
|
||||||
type="number"
|
|
||||||
min="1"
|
|
||||||
value={api.priority}
|
|
||||||
onChange={(e) => {
|
|
||||||
const priority = parseInt(e.target.value, 10)
|
|
||||||
if (!isNaN(priority) && priority > 0) {
|
|
||||||
void handleUpdatePriority(api.id, priority)
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
className="w-20 px-2 py-1 bg-cyber-darker border border-cyber-accent/30 rounded text-cyber-light focus:border-neon-cyan focus:outline-none"
|
|
||||||
/>
|
|
||||||
<span className="text-sm text-cyber-accent">| ID: {api.id}</span>
|
|
||||||
</label>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
|
|||||||
@ -78,7 +78,7 @@ function ProfileHeaderSection({
|
|||||||
<BackButton />
|
<BackButton />
|
||||||
{loadingProfile ? (
|
{loadingProfile ? (
|
||||||
<ProfileLoading />
|
<ProfileLoading />
|
||||||
) : profile ? (
|
) : profile !== null && profile !== undefined ? (
|
||||||
<UserProfile profile={profile} pubkey={currentPubkey} articleCount={articleCount} />
|
<UserProfile profile={profile} pubkey={currentPubkey} articleCount={articleCount} />
|
||||||
) : null}
|
) : null}
|
||||||
</>
|
</>
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
import { useState, useEffect } from 'react'
|
import { useState, useEffect } from 'react'
|
||||||
import { nostrAuthService } from '@/lib/nostrAuth'
|
import { nostrAuthService } from '@/lib/nostrAuth'
|
||||||
import { syncUserContentToCache, type SyncProgress } from '@/lib/userContentSync'
|
import { syncUserContentToCache, type SyncProgress } from '@/lib/userContentSync'
|
||||||
import { getLastSyncDate, getCurrentTimestamp, calculateDaysBetween } from '@/lib/syncStorage'
|
import { getLastSyncDate, setLastSyncDate as setLastSyncDateStorage, getCurrentTimestamp, calculateDaysBetween } from '@/lib/syncStorage'
|
||||||
import { MIN_EVENT_DATE } from '@/lib/platformConfig'
|
import { MIN_EVENT_DATE } from '@/lib/platformConfig'
|
||||||
|
import { objectCache } from '@/lib/objectCache'
|
||||||
import { t } from '@/lib/i18n'
|
import { t } from '@/lib/i18n'
|
||||||
|
|
||||||
export function SyncProgressBar(): React.ReactElement | null {
|
export function SyncProgressBar(): React.ReactElement | null {
|
||||||
@ -10,6 +11,9 @@ export function SyncProgressBar(): React.ReactElement | null {
|
|||||||
const [isSyncing, setIsSyncing] = useState(false)
|
const [isSyncing, setIsSyncing] = useState(false)
|
||||||
const [lastSyncDate, setLastSyncDate] = useState<number | null>(null)
|
const [lastSyncDate, setLastSyncDate] = useState<number | null>(null)
|
||||||
const [totalDays, setTotalDays] = useState<number>(0)
|
const [totalDays, setTotalDays] = useState<number>(0)
|
||||||
|
const [isInitialized, setIsInitialized] = useState(false)
|
||||||
|
const [connectionState, setConnectionState] = useState<{ connected: boolean; pubkey: string | null }>({ connected: false, pubkey: null })
|
||||||
|
const [error, setError] = useState<string | null>(null)
|
||||||
|
|
||||||
async function loadSyncStatus(): Promise<void> {
|
async function loadSyncStatus(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
@ -30,10 +34,64 @@ export function SyncProgressBar(): React.ReactElement | null {
|
|||||||
}
|
}
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
void loadSyncStatus()
|
// Check connection state
|
||||||
|
const checkConnection = (): void => {
|
||||||
|
const state = nostrAuthService.getState()
|
||||||
|
setConnectionState({ connected: state.connected ?? false, pubkey: state.pubkey ?? null })
|
||||||
|
setIsInitialized(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initial check
|
||||||
|
checkConnection()
|
||||||
|
|
||||||
|
// Listen to connection changes
|
||||||
|
const unsubscribe = nostrAuthService.subscribe((state) => {
|
||||||
|
setConnectionState({ connected: state.connected ?? false, pubkey: state.pubkey ?? null })
|
||||||
|
})
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
unsubscribe()
|
||||||
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
async function startSync(): Promise<void> {
|
useEffect(() => {
|
||||||
|
if (!isInitialized || !connectionState.connected || !connectionState.pubkey) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
void (async () => {
|
||||||
|
await loadSyncStatus()
|
||||||
|
|
||||||
|
// Auto-start sync if not recently synced
|
||||||
|
const storedLastSyncDate = await getLastSyncDate()
|
||||||
|
const currentTimestamp = getCurrentTimestamp()
|
||||||
|
const isRecentlySynced = storedLastSyncDate >= currentTimestamp - 3600
|
||||||
|
|
||||||
|
// Only auto-start if not recently synced
|
||||||
|
if (!isRecentlySynced && !isSyncing && connectionState.pubkey) {
|
||||||
|
setIsSyncing(true)
|
||||||
|
setSyncProgress({ currentStep: 0, totalSteps: 6, completed: false })
|
||||||
|
|
||||||
|
try {
|
||||||
|
await syncUserContentToCache(connectionState.pubkey, (progress) => {
|
||||||
|
setSyncProgress(progress)
|
||||||
|
if (progress.completed) {
|
||||||
|
setIsSyncing(false)
|
||||||
|
void loadSyncStatus()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// Check if sync completed successfully (if it didn't, isSyncing should still be false)
|
||||||
|
setIsSyncing(false)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error during auto-sync:', error)
|
||||||
|
setIsSyncing(false)
|
||||||
|
setError(error instanceof Error ? error.message : 'Erreur de synchronisation')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
}, [isInitialized, connectionState.connected, connectionState.pubkey])
|
||||||
|
|
||||||
|
async function resynchronize(): Promise<void> {
|
||||||
try {
|
try {
|
||||||
const state = nostrAuthService.getState()
|
const state = nostrAuthService.getState()
|
||||||
if (!state.connected || !state.pubkey) {
|
if (!state.connected || !state.pubkey) {
|
||||||
@ -43,22 +101,41 @@ export function SyncProgressBar(): React.ReactElement | null {
|
|||||||
setIsSyncing(true)
|
setIsSyncing(true)
|
||||||
setSyncProgress({ currentStep: 0, totalSteps: 6, completed: false })
|
setSyncProgress({ currentStep: 0, totalSteps: 6, completed: false })
|
||||||
|
|
||||||
await syncUserContentToCache(state.pubkey, (progress) => {
|
// Clear cache for user content (but keep other data)
|
||||||
setSyncProgress(progress)
|
await Promise.all([
|
||||||
if (progress.completed) {
|
objectCache.clear('author'),
|
||||||
setIsSyncing(false)
|
objectCache.clear('series'),
|
||||||
void loadSyncStatus()
|
objectCache.clear('publication'),
|
||||||
}
|
objectCache.clear('review'),
|
||||||
})
|
objectCache.clear('purchase'),
|
||||||
|
objectCache.clear('sponsoring'),
|
||||||
|
objectCache.clear('review_tip'),
|
||||||
|
])
|
||||||
|
|
||||||
|
// Reset last sync date to force full resync
|
||||||
|
await setLastSyncDateStorage(MIN_EVENT_DATE)
|
||||||
|
|
||||||
|
// Reload sync status
|
||||||
|
await loadSyncStatus()
|
||||||
|
|
||||||
|
// Start full resynchronization
|
||||||
|
if (state.pubkey !== null) {
|
||||||
|
await syncUserContentToCache(state.pubkey, (progress) => {
|
||||||
|
setSyncProgress(progress)
|
||||||
|
if (progress.completed) {
|
||||||
|
setIsSyncing(false)
|
||||||
|
void loadSyncStatus()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error starting sync:', error)
|
console.error('Error resynchronizing:', error)
|
||||||
setIsSyncing(false)
|
setIsSyncing(false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Don't show if not connected
|
// Don't show if not initialized or not connected
|
||||||
const state = nostrAuthService.getState()
|
if (!isInitialized || !connectionState.connected || !connectionState.pubkey) {
|
||||||
if (!state.connected || !state.pubkey) {
|
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,6 +164,19 @@ export function SyncProgressBar(): React.ReactElement | null {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-cyber-darker border border-neon-cyan/30 rounded-lg p-4 mt-6">
|
<div className="bg-cyber-darker border border-neon-cyan/30 rounded-lg p-4 mt-6">
|
||||||
|
{error && (
|
||||||
|
<div className="mb-4 bg-red-900/30 border border-red-500/50 rounded p-3 text-red-300 text-sm">
|
||||||
|
{error}
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setError(null)
|
||||||
|
}}
|
||||||
|
className="ml-2 text-red-400 hover:text-red-200"
|
||||||
|
>
|
||||||
|
×
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div className="flex items-center justify-between mb-2">
|
<div className="flex items-center justify-between mb-2">
|
||||||
<h3 className="text-lg font-semibold text-neon-cyan">
|
<h3 className="text-lg font-semibold text-neon-cyan">
|
||||||
{t('settings.sync.title')}
|
{t('settings.sync.title')}
|
||||||
@ -94,11 +184,11 @@ export function SyncProgressBar(): React.ReactElement | null {
|
|||||||
{!isSyncing && (
|
{!isSyncing && (
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
void startSync()
|
void resynchronize()
|
||||||
}}
|
}}
|
||||||
className="px-3 py-1 text-xs bg-neon-cyan/20 hover:bg-neon-cyan/30 text-neon-cyan rounded border border-neon-cyan/50 hover:border-neon-cyan transition-colors"
|
className="px-3 py-1 text-xs bg-neon-cyan/20 hover:bg-neon-cyan/30 text-neon-cyan rounded border border-neon-cyan/50 hover:border-neon-cyan transition-colors"
|
||||||
>
|
>
|
||||||
{t('settings.sync.start')}
|
{t('settings.sync.resync')}
|
||||||
</button>
|
</button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -160,7 +160,7 @@ function useUnlockAccount(words: string[], setWords: (words: string[]) => void,
|
|||||||
setWords(pastedWords.map((w) => w.toLowerCase()))
|
setWords(pastedWords.map((w) => w.toLowerCase()))
|
||||||
setError(null)
|
setError(null)
|
||||||
}
|
}
|
||||||
} catch (_e) {
|
} catch {
|
||||||
// Ignore clipboard errors
|
// Ignore clipboard errors
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -24,7 +24,7 @@ export function useArticles(searchQuery: string = '', filters: ArticleFilters |
|
|||||||
setError(null)
|
setError(null)
|
||||||
|
|
||||||
// Load authors from cache first
|
// Load authors from cache first
|
||||||
const loadAuthorsFromCache = async (): Promise<void> => {
|
const loadAuthorsFromCache = async (): Promise<boolean> => {
|
||||||
try {
|
try {
|
||||||
const cachedAuthors = await objectCache.getAll('author')
|
const cachedAuthors = await objectCache.getAll('author')
|
||||||
const authors = cachedAuthors as Article[]
|
const authors = cachedAuthors as Article[]
|
||||||
@ -40,69 +40,90 @@ export function useArticles(searchQuery: string = '', filters: ArticleFilters |
|
|||||||
return merged
|
return merged
|
||||||
})
|
})
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
|
||||||
|
|
||||||
// Calculate totalSponsoring asynchronously from cache (non-blocking)
|
// Calculate totalSponsoring asynchronously from cache (non-blocking)
|
||||||
// Only update authors that don't have totalSponsoring yet
|
// Only update authors that don't have totalSponsoring yet
|
||||||
const authorsNeedingSponsoring = authors.filter(
|
const authorsNeedingSponsoring = authors.filter(
|
||||||
(author) => author.isPresentation && author.pubkey && author.totalSponsoring === undefined
|
(author) => author.isPresentation && author.pubkey && author.totalSponsoring === undefined
|
||||||
)
|
|
||||||
|
|
||||||
if (authorsNeedingSponsoring.length > 0) {
|
|
||||||
// Load sponsoring from cache in parallel (fast, no network)
|
|
||||||
const sponsoringPromises = authorsNeedingSponsoring.map(async (author) => {
|
|
||||||
if (author.pubkey) {
|
|
||||||
const totalSponsoring = await getAuthorSponsoring(author.pubkey, true)
|
|
||||||
return { authorId: author.id, totalSponsoring }
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
})
|
|
||||||
|
|
||||||
const sponsoringResults = await Promise.all(sponsoringPromises)
|
|
||||||
|
|
||||||
// Update articles with sponsoring amounts
|
|
||||||
setArticles((prev) =>
|
|
||||||
prev.map((article) => {
|
|
||||||
const sponsoringResult = sponsoringResults.find((r) => r?.authorId === article.id)
|
|
||||||
if (sponsoringResult && article.isPresentation) {
|
|
||||||
return { ...article, totalSponsoring: sponsoringResult.totalSponsoring }
|
|
||||||
}
|
|
||||||
return article
|
|
||||||
})
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (authorsNeedingSponsoring.length > 0) {
|
||||||
|
// Load sponsoring from cache in parallel (fast, no network)
|
||||||
|
const sponsoringPromises = authorsNeedingSponsoring.map(async (author) => {
|
||||||
|
if (author.pubkey) {
|
||||||
|
const totalSponsoring = await getAuthorSponsoring(author.pubkey, true)
|
||||||
|
return { authorId: author.id, totalSponsoring }
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
})
|
||||||
|
|
||||||
|
const sponsoringResults = await Promise.all(sponsoringPromises)
|
||||||
|
|
||||||
|
// Update articles with sponsoring amounts
|
||||||
|
setArticles((prev) =>
|
||||||
|
prev.map((article) => {
|
||||||
|
const sponsoringResult = sponsoringResults.find((r) => r?.authorId === article.id)
|
||||||
|
if (sponsoringResult && article.isPresentation) {
|
||||||
|
return { ...article, totalSponsoring: sponsoringResult.totalSponsoring }
|
||||||
|
}
|
||||||
|
return article
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Cache is empty - stop loading immediately, no network requests needed
|
||||||
|
setLoading(false)
|
||||||
|
hasArticlesRef.current = false
|
||||||
|
return false
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error loading authors from cache:', error)
|
console.error('Error loading authors from cache:', error)
|
||||||
|
setLoading(false)
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void loadAuthorsFromCache()
|
let unsubscribe: (() => void) | null = null
|
||||||
|
let timeout: NodeJS.Timeout | null = null
|
||||||
|
|
||||||
const unsubscribe = nostrService.subscribeToArticles(
|
void loadAuthorsFromCache().then((hasCachedAuthors) => {
|
||||||
(article) => {
|
// Only subscribe to network if cache is empty (to fetch new content)
|
||||||
setArticles((prev) => {
|
// If cache has authors, we can skip network subscription for faster load
|
||||||
if (prev.some((a) => a.id === article.id)) {
|
if (!hasCachedAuthors) {
|
||||||
return prev
|
unsubscribe = nostrService.subscribeToArticles(
|
||||||
|
(article) => {
|
||||||
|
setArticles((prev) => {
|
||||||
|
if (prev.some((a) => a.id === article.id)) {
|
||||||
|
return prev
|
||||||
|
}
|
||||||
|
const next = [article, ...prev].sort((a, b) => b.createdAt - a.createdAt)
|
||||||
|
hasArticlesRef.current = next.length > 0
|
||||||
|
return next
|
||||||
|
})
|
||||||
|
setLoading(false)
|
||||||
|
},
|
||||||
|
50
|
||||||
|
)
|
||||||
|
|
||||||
|
// Shorter timeout if cache is empty (5 seconds instead of 10)
|
||||||
|
timeout = setTimeout(() => {
|
||||||
|
setLoading(false)
|
||||||
|
if (!hasArticlesRef.current) {
|
||||||
|
setError(t('common.error.noContent'))
|
||||||
}
|
}
|
||||||
const next = [article, ...prev].sort((a, b) => b.createdAt - a.createdAt)
|
}, 5000)
|
||||||
hasArticlesRef.current = next.length > 0
|
|
||||||
return next
|
|
||||||
})
|
|
||||||
setLoading(false)
|
|
||||||
},
|
|
||||||
50
|
|
||||||
)
|
|
||||||
|
|
||||||
const timeout = setTimeout(() => {
|
|
||||||
setLoading(false)
|
|
||||||
if (!hasArticlesRef.current) {
|
|
||||||
setError(t('common.error.noContent'))
|
|
||||||
}
|
}
|
||||||
}, 10000)
|
})
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unsubscribe()
|
if (unsubscribe) {
|
||||||
clearTimeout(timeout)
|
unsubscribe()
|
||||||
|
}
|
||||||
|
if (timeout) {
|
||||||
|
clearTimeout(timeout)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
|
|||||||
@ -63,7 +63,8 @@ export class ArticlePublisher {
|
|||||||
try {
|
try {
|
||||||
const validation = await this.validatePublishRequest(draft, authorPubkey, authorPrivateKey)
|
const validation = await this.validatePublishRequest(draft, authorPubkey, authorPrivateKey)
|
||||||
if (!validation.success) {
|
if (!validation.success) {
|
||||||
return buildFailure(validation.error)
|
const { error } = validation
|
||||||
|
return buildFailure(error)
|
||||||
}
|
}
|
||||||
|
|
||||||
const presentation = await this.getAuthorPresentation(authorPubkey)
|
const presentation = await this.getAuthorPresentation(authorPubkey)
|
||||||
@ -188,8 +189,9 @@ export class ArticlePublisher {
|
|||||||
const parsed = parsePresentationEvent(publishedEvent)
|
const parsed = parsePresentationEvent(publishedEvent)
|
||||||
if (parsed) {
|
if (parsed) {
|
||||||
const tags = extractTagsFromEvent(publishedEvent)
|
const tags = extractTagsFromEvent(publishedEvent)
|
||||||
if (tags.id) {
|
const { id: tagId, version: tagVersion, hidden: tagHidden } = tags
|
||||||
await objectCache.set('author', tags.id, publishedEvent, parsed, tags.version, tags.hidden)
|
if (tagId) {
|
||||||
|
await objectCache.set('author', tagId, publishedEvent, parsed, tagVersion ?? 0, tagHidden ?? false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -44,11 +44,13 @@ export function getArticlesBySeries(seriesId: string, timeoutMs: number = 5000,
|
|||||||
resolve(results)
|
resolve(results)
|
||||||
}
|
}
|
||||||
|
|
||||||
sub.on('event', async (event: Event): Promise<void> => {
|
sub.on('event', (event: Event): void => {
|
||||||
const parsed = await parseArticleFromEvent(event)
|
void (async (): Promise<void> => {
|
||||||
if (parsed) {
|
const parsed = await parseArticleFromEvent(event)
|
||||||
results.push(parsed)
|
if (parsed) {
|
||||||
}
|
results.push(parsed)
|
||||||
|
}
|
||||||
|
})()
|
||||||
})
|
})
|
||||||
|
|
||||||
sub.on('eose', (): void => {
|
sub.on('eose', (): void => {
|
||||||
|
|||||||
@ -31,7 +31,7 @@ export class AutomaticTransferService {
|
|||||||
platformCommission,
|
platformCommission,
|
||||||
timestamp: new Date().toISOString(),
|
timestamp: new Date().toISOString(),
|
||||||
}
|
}
|
||||||
console.log(`Automatic transfer required${type === 'review' ? ' for review' : ''}`, logData)
|
console.warn(`Automatic transfer required${type === 'review' ? ' for review' : ''}`, logData)
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildTransferError(error: unknown, recipient: string, amount: number = 0): TransferResult {
|
private buildTransferError(error: unknown, recipient: string, amount: number = 0): TransferResult {
|
||||||
|
|||||||
@ -16,7 +16,7 @@ export async function getPrimaryRelay(): Promise<string> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await configStorage.getPrimaryRelay()
|
return configStorage.getPrimaryRelay()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error getting primary relay from IndexedDB:', error)
|
console.error('Error getting primary relay from IndexedDB:', error)
|
||||||
return getPrimaryRelaySync()
|
return getPrimaryRelaySync()
|
||||||
@ -33,7 +33,7 @@ export async function getEnabledRelays(): Promise<string[]> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await configStorage.getEnabledRelays()
|
return configStorage.getEnabledRelays()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error getting enabled relays from IndexedDB:', error)
|
console.error('Error getting enabled relays from IndexedDB:', error)
|
||||||
return [getPrimaryRelaySync()]
|
return [getPrimaryRelaySync()]
|
||||||
@ -50,7 +50,7 @@ export async function getPrimaryNip95Api(): Promise<string> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await configStorage.getPrimaryNip95Api()
|
return configStorage.getPrimaryNip95Api()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error getting primary NIP-95 API from IndexedDB:', error)
|
console.error('Error getting primary NIP-95 API from IndexedDB:', error)
|
||||||
return getPrimaryNip95ApiSync()
|
return getPrimaryNip95ApiSync()
|
||||||
@ -67,7 +67,7 @@ export async function getEnabledNip95Apis(): Promise<string[]> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await configStorage.getEnabledNip95Apis()
|
return configStorage.getEnabledNip95Apis()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error getting enabled NIP-95 APIs from IndexedDB:', error)
|
console.error('Error getting enabled NIP-95 APIs from IndexedDB:', error)
|
||||||
return [getPrimaryNip95ApiSync()]
|
return [getPrimaryNip95ApiSync()]
|
||||||
@ -84,7 +84,7 @@ export async function getPlatformLightningAddress(): Promise<string> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await configStorage.getPlatformLightningAddress()
|
return configStorage.getPlatformLightningAddress()
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error getting platform Lightning address from IndexedDB:', error)
|
console.error('Error getting platform Lightning address from IndexedDB:', error)
|
||||||
return getPlatformLightningAddressSync()
|
return getPlatformLightningAddressSync()
|
||||||
|
|||||||
@ -53,9 +53,9 @@ export async function waitForConfirmation(
|
|||||||
const startTime = Date.now()
|
const startTime = Date.now()
|
||||||
|
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const checkConfirmation = async (): Promise<void> => {
|
const checkConfirmation = (): void => {
|
||||||
await checkTransactionStatus(txid, startTime, timeout, interval, resolve, checkConfirmation)
|
void checkTransactionStatus(txid, startTime, timeout, interval, resolve, checkConfirmation)
|
||||||
}
|
}
|
||||||
void checkConfirmation()
|
checkConfirmation()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,7 +17,7 @@ export async function parseArticleFromEvent(event: Event): Promise<Article | nul
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
const { previewContent } = getPreviewContent(event.content, tags.preview)
|
const { previewContent } = getPreviewContent(event.content, tags.preview)
|
||||||
return await buildArticle(event, tags, previewContent)
|
return buildArticle(event, tags, previewContent)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error parsing article:', e)
|
console.error('Error parsing article:', e)
|
||||||
return null
|
return null
|
||||||
@ -35,7 +35,12 @@ export async function parseSeriesFromEvent(event: Event): Promise<Series | null>
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
// Map category from new system to old system
|
// Map category from new system to old system
|
||||||
const category = tags.category === 'sciencefiction' ? 'science-fiction' : tags.category === 'research' ? 'scientific-research' : 'science-fiction'
|
let category: 'science-fiction' | 'scientific-research' = 'science-fiction'
|
||||||
|
if (tags.category === 'sciencefiction') {
|
||||||
|
category = 'science-fiction'
|
||||||
|
} else if (tags.category === 'research') {
|
||||||
|
category = 'scientific-research'
|
||||||
|
}
|
||||||
|
|
||||||
// Extract hash, version, index from id tag or parse it
|
// Extract hash, version, index from id tag or parse it
|
||||||
let hash: string
|
let hash: string
|
||||||
@ -44,10 +49,11 @@ export async function parseSeriesFromEvent(event: Event): Promise<Series | null>
|
|||||||
|
|
||||||
if (tags.id) {
|
if (tags.id) {
|
||||||
const parsed = parseObjectId(tags.id)
|
const parsed = parseObjectId(tags.id)
|
||||||
if (parsed.hash) {
|
const { hash: parsedHash, version: parsedVersion, index: parsedIndex } = parsed
|
||||||
hash = parsed.hash
|
if (parsedHash) {
|
||||||
version = parsed.version ?? version
|
hash = parsedHash
|
||||||
index = parsed.index ?? index
|
version = parsedVersion ?? version
|
||||||
|
index = parsedIndex ?? index
|
||||||
} else {
|
} else {
|
||||||
// If id is just a hash, use it directly
|
// If id is just a hash, use it directly
|
||||||
hash = tags.id
|
hash = tags.id
|
||||||
@ -109,10 +115,11 @@ export async function parseReviewFromEvent(event: Event): Promise<Review | null>
|
|||||||
|
|
||||||
if (tags.id) {
|
if (tags.id) {
|
||||||
const parsed = parseObjectId(tags.id)
|
const parsed = parseObjectId(tags.id)
|
||||||
if (parsed.hash) {
|
const { hash: parsedHash, version: parsedVersion, index: parsedIndex } = parsed
|
||||||
hash = parsed.hash
|
if (parsedHash) {
|
||||||
version = parsed.version ?? version
|
hash = parsedHash
|
||||||
index = parsed.index ?? index
|
version = parsedVersion ?? version
|
||||||
|
index = parsedIndex ?? index
|
||||||
} else {
|
} else {
|
||||||
// If id is just a hash, use it directly
|
// If id is just a hash, use it directly
|
||||||
hash = tags.id
|
hash = tags.id
|
||||||
@ -207,8 +214,9 @@ async function buildArticle(event: Event, tags: ReturnType<typeof extractTagsFro
|
|||||||
const jsonTag = event.tags.find((tag) => tag[0] === 'json')?.[1]
|
const jsonTag = event.tags.find((tag) => tag[0] === 'json')?.[1]
|
||||||
if (jsonTag) {
|
if (jsonTag) {
|
||||||
const metadata = JSON.parse(jsonTag) as { pages?: Page[] }
|
const metadata = JSON.parse(jsonTag) as { pages?: Page[] }
|
||||||
if (metadata.pages && Array.isArray(metadata.pages)) {
|
const { pages: metadataPages } = metadata
|
||||||
pages = metadata.pages
|
if (metadataPages && Array.isArray(metadataPages)) {
|
||||||
|
pages = metadataPages
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -229,7 +237,7 @@ async function buildArticle(event: Event, tags: ReturnType<typeof extractTagsFro
|
|||||||
createdAt: event.created_at,
|
createdAt: event.created_at,
|
||||||
zapAmount: tags.zapAmount ?? 800,
|
zapAmount: tags.zapAmount ?? 800,
|
||||||
paid: false,
|
paid: false,
|
||||||
thumbnailUrl: (typeof tags.bannerUrl === 'string' ? tags.bannerUrl : typeof tags.pictureUrl === 'string' ? tags.pictureUrl : ''), // Required field with default
|
thumbnailUrl: typeof tags.bannerUrl === 'string' ? tags.bannerUrl : typeof tags.pictureUrl === 'string' ? tags.pictureUrl : '', // Required field with default
|
||||||
...(tags.invoice ? { invoice: tags.invoice } : {}),
|
...(tags.invoice ? { invoice: tags.invoice } : {}),
|
||||||
...(tags.paymentHash ? { paymentHash: tags.paymentHash } : {}),
|
...(tags.paymentHash ? { paymentHash: tags.paymentHash } : {}),
|
||||||
...(category ? { category } : {}),
|
...(category ? { category } : {}),
|
||||||
@ -239,7 +247,7 @@ async function buildArticle(event: Event, tags: ReturnType<typeof extractTagsFro
|
|||||||
...(tags.seriesId ? { seriesId: tags.seriesId } : {}),
|
...(tags.seriesId ? { seriesId: tags.seriesId } : {}),
|
||||||
...(tags.bannerUrl ? { bannerUrl: tags.bannerUrl } : {}),
|
...(tags.bannerUrl ? { bannerUrl: tags.bannerUrl } : {}),
|
||||||
...(pages && pages.length > 0 ? { pages } : {}),
|
...(pages && pages.length > 0 ? { pages } : {}),
|
||||||
...(tags.type === 'publication' ? { kindType: 'article' as KindType } : tags.type === 'author' ? { kindType: 'article' as KindType } : {}),
|
...(tags.type === 'publication' || tags.type === 'author' ? { kindType: 'article' as KindType } : {}),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -255,9 +263,10 @@ export async function parsePurchaseFromEvent(event: Event): Promise<Purchase | n
|
|||||||
|
|
||||||
// Extract hash, version, index from id
|
// Extract hash, version, index from id
|
||||||
const parsed = parseObjectId(extracted.id)
|
const parsed = parseObjectId(extracted.id)
|
||||||
const hash = parsed.hash ?? extracted.id
|
const { hash: parsedHash, version: parsedVersion, index: parsedIndex } = parsed
|
||||||
const version = parsed.version ?? 0
|
const hash = parsedHash ?? extracted.id
|
||||||
const index = parsed.index ?? 0
|
const version = parsedVersion ?? 0
|
||||||
|
const index = parsedIndex ?? 0
|
||||||
const id = buildObjectId(hash, index, version)
|
const id = buildObjectId(hash, index, version)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -291,9 +300,10 @@ export async function parseSponsoringFromEvent(event: Event): Promise<Sponsoring
|
|||||||
|
|
||||||
// Extract hash, version, index from id
|
// Extract hash, version, index from id
|
||||||
const parsed = parseObjectId(extracted.id)
|
const parsed = parseObjectId(extracted.id)
|
||||||
const hash = parsed.hash ?? extracted.id
|
const { hash: parsedHash, version: parsedVersion, index: parsedIndex } = parsed
|
||||||
const version = parsed.version ?? 0
|
const hash = parsedHash ?? extracted.id
|
||||||
const index = parsed.index ?? 0
|
const version = parsedVersion ?? 0
|
||||||
|
const index = parsedIndex ?? 0
|
||||||
const id = buildObjectId(hash, index, version)
|
const id = buildObjectId(hash, index, version)
|
||||||
|
|
||||||
// Extract text from tags if present
|
// Extract text from tags if present
|
||||||
@ -332,9 +342,10 @@ export async function parseReviewTipFromEvent(event: Event): Promise<ReviewTip |
|
|||||||
|
|
||||||
// Extract hash, version, index from id
|
// Extract hash, version, index from id
|
||||||
const parsed = parseObjectId(extracted.id)
|
const parsed = parseObjectId(extracted.id)
|
||||||
const hash = parsed.hash ?? extracted.id
|
const { hash: parsedHash, version: parsedVersion, index: parsedIndex } = parsed
|
||||||
const version = parsed.version ?? 0
|
const hash = parsedHash ?? extracted.id
|
||||||
const index = parsed.index ?? 0
|
const version = parsedVersion ?? 0
|
||||||
|
const index = parsedIndex ?? 0
|
||||||
const id = buildObjectId(hash, index, version)
|
const id = buildObjectId(hash, index, version)
|
||||||
|
|
||||||
// Extract text from tags if present
|
// Extract text from tags if present
|
||||||
|
|||||||
@ -35,9 +35,11 @@ export function subscribeWithTimeout<T>(
|
|||||||
resolve(value)
|
resolve(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
sub.on('event', async (event: Event): Promise<void> => {
|
sub.on('event', (event: Event): void => {
|
||||||
const result = await parser(event)
|
void (async (): Promise<void> => {
|
||||||
resolveOnce(result)
|
const result = await parser(event)
|
||||||
|
resolveOnce(result)
|
||||||
|
})()
|
||||||
})
|
})
|
||||||
sub.on('eose', (): void => {
|
sub.on('eose', (): void => {
|
||||||
resolveOnce(null)
|
resolveOnce(null)
|
||||||
|
|||||||
@ -20,7 +20,12 @@ export async function publishPurchaseNote(params: {
|
|||||||
seriesId?: string
|
seriesId?: string
|
||||||
payerPrivateKey: string
|
payerPrivateKey: string
|
||||||
}): Promise<Event | null> {
|
}): Promise<Event | null> {
|
||||||
const category = params.category === 'science-fiction' ? 'sciencefiction' : params.category === 'scientific-research' ? 'research' : 'sciencefiction'
|
let category: 'sciencefiction' | 'research' = 'sciencefiction'
|
||||||
|
if (params.category === 'science-fiction') {
|
||||||
|
category = 'sciencefiction'
|
||||||
|
} else if (params.category === 'scientific-research') {
|
||||||
|
category = 'research'
|
||||||
|
}
|
||||||
|
|
||||||
const purchaseData = {
|
const purchaseData = {
|
||||||
payerPubkey: params.payerPubkey,
|
payerPubkey: params.payerPubkey,
|
||||||
@ -71,7 +76,7 @@ export async function publishPurchaseNote(params: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
nostrService.setPrivateKey(params.payerPrivateKey)
|
nostrService.setPrivateKey(params.payerPrivateKey)
|
||||||
return await nostrService.publishEvent(eventTemplate)
|
return nostrService.publishEvent(eventTemplate)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -92,7 +97,12 @@ export async function publishReviewTipNote(params: {
|
|||||||
text?: string
|
text?: string
|
||||||
payerPrivateKey: string
|
payerPrivateKey: string
|
||||||
}): Promise<Event | null> {
|
}): Promise<Event | null> {
|
||||||
const category = params.category === 'science-fiction' ? 'sciencefiction' : params.category === 'scientific-research' ? 'research' : 'sciencefiction'
|
let category: 'sciencefiction' | 'research' = 'sciencefiction'
|
||||||
|
if (params.category === 'science-fiction') {
|
||||||
|
category = 'sciencefiction'
|
||||||
|
} else if (params.category === 'scientific-research') {
|
||||||
|
category = 'research'
|
||||||
|
}
|
||||||
|
|
||||||
const tipData = {
|
const tipData = {
|
||||||
payerPubkey: params.payerPubkey,
|
payerPubkey: params.payerPubkey,
|
||||||
@ -152,7 +162,7 @@ export async function publishReviewTipNote(params: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
nostrService.setPrivateKey(params.payerPrivateKey)
|
nostrService.setPrivateKey(params.payerPrivateKey)
|
||||||
return await nostrService.publishEvent(eventTemplate)
|
return nostrService.publishEvent(eventTemplate)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -171,7 +181,12 @@ export async function publishSponsoringNote(params: {
|
|||||||
transactionId?: string // Bitcoin transaction ID for mainnet payments
|
transactionId?: string // Bitcoin transaction ID for mainnet payments
|
||||||
payerPrivateKey: string
|
payerPrivateKey: string
|
||||||
}): Promise<Event | null> {
|
}): Promise<Event | null> {
|
||||||
const category = params.category === 'science-fiction' ? 'sciencefiction' : params.category === 'scientific-research' ? 'research' : 'sciencefiction'
|
let category: 'sciencefiction' | 'research' = 'sciencefiction'
|
||||||
|
if (params.category === 'science-fiction') {
|
||||||
|
category = 'sciencefiction'
|
||||||
|
} else if (params.category === 'scientific-research') {
|
||||||
|
category = 'research'
|
||||||
|
}
|
||||||
|
|
||||||
const sponsoringData = {
|
const sponsoringData = {
|
||||||
payerPubkey: params.payerPubkey,
|
payerPubkey: params.payerPubkey,
|
||||||
@ -234,5 +249,5 @@ export async function publishSponsoringNote(params: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
nostrService.setPrivateKey(params.payerPrivateKey)
|
nostrService.setPrivateKey(params.payerPrivateKey)
|
||||||
return await nostrService.publishEvent(eventTemplate)
|
return nostrService.publishEvent(eventTemplate)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,7 +5,7 @@ export async function createReviewInvoice(split: { total: number; reviewer: numb
|
|||||||
const alby = getAlbyService()
|
const alby = getAlbyService()
|
||||||
await alby.enable()
|
await alby.enable()
|
||||||
|
|
||||||
return await alby.createInvoice({
|
return alby.createInvoice({
|
||||||
amount: split.total,
|
amount: split.total,
|
||||||
description: `Review reward: ${request.reviewId} (${split.reviewer} sats to reviewer, ${split.platform} sats commission)`,
|
description: `Review reward: ${request.reviewId} (${split.reviewer} sats to reviewer, ${split.platform} sats commission)`,
|
||||||
expiry: 3600, // 1 hour
|
expiry: 3600, // 1 hour
|
||||||
|
|||||||
@ -76,7 +76,7 @@ export class SponsoringPaymentService {
|
|||||||
authorPubkey: string,
|
authorPubkey: string,
|
||||||
authorMainnetAddress: string
|
authorMainnetAddress: string
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
return await verifySponsoringPayment(transactionId, authorPubkey, authorMainnetAddress)
|
return verifySponsoringPayment(transactionId, authorPubkey, authorMainnetAddress)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -88,7 +88,7 @@ export class SponsoringPaymentService {
|
|||||||
authorMainnetAddress: string,
|
authorMainnetAddress: string,
|
||||||
authorPrivateKey: string
|
authorPrivateKey: string
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
return await trackSponsoringPayment(transactionId, authorPubkey, authorMainnetAddress, authorPrivateKey)
|
return trackSponsoringPayment(transactionId, authorPubkey, authorMainnetAddress, authorPrivateKey)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -408,8 +408,9 @@ export async function syncUserContentToCache(
|
|||||||
try {
|
try {
|
||||||
const pool = nostrService.getPool()
|
const pool = nostrService.getPool()
|
||||||
if (!pool) {
|
if (!pool) {
|
||||||
console.warn('Pool not initialized, cannot sync user content')
|
const errorMsg = 'Pool not initialized, cannot sync user content'
|
||||||
return
|
console.warn(errorMsg)
|
||||||
|
throw new Error(errorMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
const poolWithSub = pool as unknown as SimplePoolWithSub
|
const poolWithSub = pool as unknown as SimplePoolWithSub
|
||||||
@ -428,6 +429,7 @@ export async function syncUserContentToCache(
|
|||||||
let currentStep = 0
|
let currentStep = 0
|
||||||
|
|
||||||
// Fetch and cache author profile (already caches itself)
|
// Fetch and cache author profile (already caches itself)
|
||||||
|
console.log('[Sync] Step 1/6: Fetching author profile...')
|
||||||
await fetchAuthorPresentationFromPool(poolWithSub, userPubkey)
|
await fetchAuthorPresentationFromPool(poolWithSub, userPubkey)
|
||||||
currentStep++
|
currentStep++
|
||||||
if (onProgress) {
|
if (onProgress) {
|
||||||
@ -435,6 +437,7 @@ export async function syncUserContentToCache(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fetch and cache all series
|
// Fetch and cache all series
|
||||||
|
console.log('[Sync] Step 2/6: Fetching series...')
|
||||||
await fetchAndCacheSeries(poolWithSub, userPubkey)
|
await fetchAndCacheSeries(poolWithSub, userPubkey)
|
||||||
currentStep++
|
currentStep++
|
||||||
if (onProgress) {
|
if (onProgress) {
|
||||||
@ -442,6 +445,7 @@ export async function syncUserContentToCache(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fetch and cache all publications
|
// Fetch and cache all publications
|
||||||
|
console.log('[Sync] Step 3/6: Fetching publications...')
|
||||||
await fetchAndCachePublications(poolWithSub, userPubkey)
|
await fetchAndCachePublications(poolWithSub, userPubkey)
|
||||||
currentStep++
|
currentStep++
|
||||||
if (onProgress) {
|
if (onProgress) {
|
||||||
@ -449,6 +453,7 @@ export async function syncUserContentToCache(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fetch and cache all purchases (as payer)
|
// Fetch and cache all purchases (as payer)
|
||||||
|
console.log('[Sync] Step 4/6: Fetching purchases...')
|
||||||
await fetchAndCachePurchases(poolWithSub, userPubkey)
|
await fetchAndCachePurchases(poolWithSub, userPubkey)
|
||||||
currentStep++
|
currentStep++
|
||||||
if (onProgress) {
|
if (onProgress) {
|
||||||
@ -456,6 +461,7 @@ export async function syncUserContentToCache(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fetch and cache all sponsoring (as author)
|
// Fetch and cache all sponsoring (as author)
|
||||||
|
console.log('[Sync] Step 5/6: Fetching sponsoring...')
|
||||||
await fetchAndCacheSponsoring(poolWithSub, userPubkey)
|
await fetchAndCacheSponsoring(poolWithSub, userPubkey)
|
||||||
currentStep++
|
currentStep++
|
||||||
if (onProgress) {
|
if (onProgress) {
|
||||||
@ -463,6 +469,7 @@ export async function syncUserContentToCache(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Fetch and cache all review tips (as author)
|
// Fetch and cache all review tips (as author)
|
||||||
|
console.log('[Sync] Step 6/6: Fetching review tips...')
|
||||||
await fetchAndCacheReviewTips(poolWithSub, userPubkey)
|
await fetchAndCacheReviewTips(poolWithSub, userPubkey)
|
||||||
currentStep++
|
currentStep++
|
||||||
if (onProgress) {
|
if (onProgress) {
|
||||||
@ -471,8 +478,9 @@ export async function syncUserContentToCache(
|
|||||||
|
|
||||||
// Store the current timestamp as last sync date
|
// Store the current timestamp as last sync date
|
||||||
await setLastSyncDate(currentTimestamp)
|
await setLastSyncDate(currentTimestamp)
|
||||||
|
console.log('[Sync] Synchronization completed successfully')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error syncing user content to cache:', error)
|
console.error('Error syncing user content to cache:', error)
|
||||||
// Don't throw - this is a background operation
|
throw error // Re-throw to allow UI to handle it
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -237,7 +237,7 @@ settings.keyManagement.recovery.copied=✓ Copied!
|
|||||||
settings.keyManagement.recovery.newNpub=Your new public key (npub)
|
settings.keyManagement.recovery.newNpub=Your new public key (npub)
|
||||||
settings.keyManagement.recovery.done=Done
|
settings.keyManagement.recovery.done=Done
|
||||||
settings.sync.title=Notes Synchronization
|
settings.sync.title=Notes Synchronization
|
||||||
settings.sync.start=Start Synchronization
|
settings.sync.resync=Resynchronize
|
||||||
settings.sync.daysRange=From {{startDate}} to {{endDate}} ({{days}} days)
|
settings.sync.daysRange=From {{startDate}} to {{endDate}} ({{days}} days)
|
||||||
settings.sync.progress=Step {{current}} of {{total}}
|
settings.sync.progress=Step {{current}} of {{total}}
|
||||||
settings.sync.completed=Everything is synchronized
|
settings.sync.completed=Everything is synchronized
|
||||||
@ -269,7 +269,7 @@ settings.nip95.list.cancel=Cancel
|
|||||||
settings.nip95.list.remove=Remove
|
settings.nip95.list.remove=Remove
|
||||||
settings.nip95.remove.confirm=Are you sure you want to remove this endpoint?
|
settings.nip95.remove.confirm=Are you sure you want to remove this endpoint?
|
||||||
settings.nip95.empty=No endpoints configured
|
settings.nip95.empty=No endpoints configured
|
||||||
settings.nip95.list.priorityLabel=Priority: {{priority}} | ID: {{id}}
|
settings.nip95.list.priorityLabel=Priority {{priority}} (ID: {{id}})
|
||||||
settings.nip95.list.editUrl=Click to edit URL
|
settings.nip95.list.editUrl=Click to edit URL
|
||||||
settings.nip95.note.title=Note:
|
settings.nip95.note.title=Note:
|
||||||
settings.nip95.note.priority=Endpoints are tried in priority order (lower number = higher priority). Only enabled endpoints will be used for uploads.
|
settings.nip95.note.priority=Endpoints are tried in priority order (lower number = higher priority). Only enabled endpoints will be used for uploads.
|
||||||
|
|||||||
@ -237,7 +237,7 @@ settings.keyManagement.recovery.copied=✓ Copié !
|
|||||||
settings.keyManagement.recovery.newNpub=Votre nouvelle clé publique (npub)
|
settings.keyManagement.recovery.newNpub=Votre nouvelle clé publique (npub)
|
||||||
settings.keyManagement.recovery.done=Terminé
|
settings.keyManagement.recovery.done=Terminé
|
||||||
settings.sync.title=Synchronisation des notes
|
settings.sync.title=Synchronisation des notes
|
||||||
settings.sync.start=Démarrer la synchronisation
|
settings.sync.resync=Resynchroniser
|
||||||
settings.sync.daysRange=Du {{startDate}} au {{endDate}} ({{days}} jours)
|
settings.sync.daysRange=Du {{startDate}} au {{endDate}} ({{days}} jours)
|
||||||
settings.sync.progress=Étape {{current}} sur {{total}}
|
settings.sync.progress=Étape {{current}} sur {{total}}
|
||||||
settings.sync.completed=Tout est synchronisé
|
settings.sync.completed=Tout est synchronisé
|
||||||
@ -274,7 +274,7 @@ settings.nip95.list.cancel=Annuler
|
|||||||
settings.nip95.list.remove=Supprimer
|
settings.nip95.list.remove=Supprimer
|
||||||
settings.nip95.remove.confirm=Êtes-vous sûr de vouloir supprimer cet endpoint ?
|
settings.nip95.remove.confirm=Êtes-vous sûr de vouloir supprimer cet endpoint ?
|
||||||
settings.nip95.empty=Aucun endpoint configuré
|
settings.nip95.empty=Aucun endpoint configuré
|
||||||
settings.nip95.list.priorityLabel=Priorité: {{priority}} | ID: {{id}}
|
settings.nip95.list.priorityLabel=Priorité {{priority}} (ID: {{id}})
|
||||||
settings.nip95.list.editUrl=Cliquer pour modifier l'URL
|
settings.nip95.list.editUrl=Cliquer pour modifier l'URL
|
||||||
settings.nip95.note.title=Note :
|
settings.nip95.note.title=Note :
|
||||||
settings.nip95.note.priority=Les endpoints sont essayés dans l'ordre de priorité (nombre plus bas = priorité plus haute). Seuls les endpoints activés seront utilisés pour les uploads.
|
settings.nip95.note.priority=Les endpoints sont essayés dans l'ordre de priorité (nombre plus bas = priorité plus haute). Seuls les endpoints activés seront utilisés pour les uploads.
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user