lint fix wip

This commit is contained in:
Nicolas Cantu 2026-01-06 15:57:34 +01:00
parent 390f895920
commit 303c0bf7df
15 changed files with 340 additions and 30 deletions

View File

@ -202,7 +202,7 @@ const ArticleFieldsRight = ({
<MarkdownEditorTwoColumns <MarkdownEditorTwoColumns
value={draft.content} value={draft.content}
onChange={(value) => onDraftChange({ ...draft, content: value })} onChange={(value) => onDraftChange({ ...draft, content: value })}
pages={draft.pages} {...(draft.pages ? { pages: draft.pages } : {})}
onPagesChange={(pages) => onDraftChange({ ...draft, pages })} onPagesChange={(pages) => onDraftChange({ ...draft, pages })}
onMediaAdd={(media: MediaRef) => { onMediaAdd={(media: MediaRef) => {
const nextMedia = [...(draft.media ?? []), media] const nextMedia = [...(draft.media ?? []), media]

View File

@ -3,6 +3,7 @@ import { nostrAuthService } from '@/lib/nostrAuth'
import { keyManagementService } from '@/lib/keyManagement' import { keyManagementService } from '@/lib/keyManagement'
import { nip19 } from 'nostr-tools' import { nip19 } from 'nostr-tools'
import { t } from '@/lib/i18n' import { t } from '@/lib/i18n'
import { SyncProgressBar } from './SyncProgressBar'
interface PublicKeys { interface PublicKeys {
publicKey: string publicKey: string
@ -251,6 +252,9 @@ export function KeyManagementManager(): React.ReactElement {
</div> </div>
)} )}
{/* Sync Progress Bar */}
{publicKeys && <SyncProgressBar />}
{!publicKeys && !accountExists && ( {!publicKeys && !accountExists && (
<div className="bg-yellow-900/20 border border-yellow-400/50 rounded-lg p-4 mb-6"> <div className="bg-yellow-900/20 border border-yellow-400/50 rounded-lg p-4 mb-6">
<p className="text-yellow-400 font-semibold mb-2">{t('settings.keyManagement.noAccount.title')}</p> <p className="text-yellow-400 font-semibold mb-2">{t('settings.keyManagement.noAccount.title')}</p>

View File

@ -89,7 +89,7 @@ export function MarkdownEditorTwoColumns({
}} }}
uploading={uploading} uploading={uploading}
error={error} error={error}
onAddPage={onPagesChange ? handleAddPage : undefined} {...(onPagesChange ? { onAddPage: handleAddPage } : {})}
/> />
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<div className="space-y-2"> <div className="space-y-2">
@ -205,8 +205,8 @@ function PagesManager({
onContentChange={(content) => onPageContentChange(page.number, content)} onContentChange={(content) => onPageContentChange(page.number, content)}
onTypeChange={(type) => onPageTypeChange(page.number, type)} onTypeChange={(type) => onPageTypeChange(page.number, type)}
onRemove={() => onRemovePage(page.number)} onRemove={() => onRemovePage(page.number)}
onImageUpload={(file) => { onImageUpload={async (file) => {
void onImageUpload(file, page.number) await onImageUpload(file, page.number)
}} }}
/> />
))} ))}

View File

@ -41,18 +41,18 @@ export function ReviewForm({ article, onSuccess, onCancel }: ReviewFormProps): R
return return
} }
const category = article.category ?? 'science-fiction' const category = article.category === 'author-presentation' ? 'science-fiction' : (article.category ?? 'science-fiction')
const seriesId = article.seriesId ?? '' const seriesId = article.seriesId ?? ''
await publishReview({ await publishReview({
articleId: article.id, articleId: article.id,
seriesId, seriesId,
category, category: category === 'science-fiction' || category === 'scientific-research' ? category : 'science-fiction',
authorPubkey: article.pubkey, authorPubkey: article.pubkey,
reviewerPubkey: pubkey, reviewerPubkey: pubkey,
content: content.trim(), content: content.trim(),
title: title.trim() || undefined, ...(title.trim() ? { title: title.trim() } : {}),
text: text.trim() || undefined, ...(text.trim() ? { text: text.trim() } : {}),
authorPrivateKey: privateKey, authorPrivateKey: privateKey,
}) })

View File

@ -38,17 +38,17 @@ export function ReviewTipForm({ review, article, onSuccess, onCancel }: ReviewTi
} }
const split = calculateReviewSplit() const split = calculateReviewSplit()
const category = article.category === 'science-fiction' ? 'sciencefiction' : article.category === 'scientific-research' ? 'research' : 'sciencefiction'
// Build zap request tags // Build zap request tags
const category = article.category === 'author-presentation' ? undefined : (article.category === 'science-fiction' || article.category === 'scientific-research' ? article.category : undefined)
const zapRequestTags = buildReviewTipZapRequestTags({ const zapRequestTags = buildReviewTipZapRequestTags({
articleId: article.id, articleId: article.id,
reviewId: review.id, reviewId: review.id,
authorPubkey: article.pubkey, authorPubkey: article.pubkey,
reviewerPubkey: review.reviewerPubkey, reviewerPubkey: review.reviewerPubkey,
category: article.category, ...(category ? { category } : {}),
seriesId: article.seriesId, ...(article.seriesId ? { seriesId: article.seriesId } : {}),
text: text.trim() || undefined, ...(text.trim() ? { text: text.trim() } : {}),
}) })
// Create zap request event (kind 9734) and publish it // Create zap request event (kind 9734) and publish it

View File

@ -0,0 +1,160 @@
import { useState, useEffect } from 'react'
import { nostrAuthService } from '@/lib/nostrAuth'
import { syncUserContentToCache, type SyncProgress } from '@/lib/userContentSync'
import { getLastSyncDate, getCurrentTimestamp, calculateDaysBetween } from '@/lib/syncStorage'
import { MIN_EVENT_DATE } from '@/lib/platformConfig'
import { t } from '@/lib/i18n'
export function SyncProgressBar(): React.ReactElement | null {
const [syncProgress, setSyncProgress] = useState<SyncProgress | null>(null)
const [isSyncing, setIsSyncing] = useState(false)
const [lastSyncDate, setLastSyncDate] = useState<number | null>(null)
const [totalDays, setTotalDays] = useState<number>(0)
useEffect(() => {
void loadSyncStatus()
}, [])
async function loadSyncStatus(): Promise<void> {
try {
const state = nostrAuthService.getState()
if (!state.connected || !state.pubkey) {
return
}
const storedLastSyncDate = await getLastSyncDate()
const currentTimestamp = getCurrentTimestamp()
const days = calculateDaysBetween(storedLastSyncDate, currentTimestamp)
setLastSyncDate(storedLastSyncDate)
setTotalDays(days)
// If everything is synced (no days to sync), don't show the progress bar
if (days === 0 && storedLastSyncDate >= currentTimestamp) {
setSyncProgress(null)
}
} catch (error) {
console.error('Error loading sync status:', error)
}
}
async function startSync(): Promise<void> {
try {
const state = nostrAuthService.getState()
if (!state.connected || !state.pubkey) {
return
}
setIsSyncing(true)
setSyncProgress({ currentDay: 0, totalDays, completed: false })
await syncUserContentToCache(state.pubkey, (progress) => {
setSyncProgress(progress)
if (progress.completed) {
setIsSyncing(false)
void loadSyncStatus()
}
})
} catch (error) {
console.error('Error starting sync:', error)
setIsSyncing(false)
}
}
// Don't show if not connected or if everything is synced
const state = nostrAuthService.getState()
if (!state.connected || !state.pubkey) {
return null
}
// If everything is synced, don't show the progress bar
if (totalDays === 0 && lastSyncDate !== null && lastSyncDate >= getCurrentTimestamp()) {
return null
}
// If sync is completed and no days to sync, don't show
if (syncProgress?.completed && totalDays === 0) {
return null
}
const progressPercentage = syncProgress && totalDays > 0
? Math.min(100, (syncProgress.currentDay / totalDays) * 100)
: 0
const formatDate = (timestamp: number): string => {
const date = new Date(timestamp * 1000)
const locale = typeof window !== 'undefined' ? navigator.language : 'fr-FR'
return date.toLocaleDateString(locale, { day: '2-digit', month: '2-digit', year: 'numeric' })
}
const getStartDate = (): number => {
if (lastSyncDate !== null) {
return lastSyncDate
}
return MIN_EVENT_DATE
}
const startDate = getStartDate()
const endDate = getCurrentTimestamp()
return (
<div className="bg-cyber-darker border border-neon-cyan/30 rounded-lg p-4 mt-6">
<div className="flex items-center justify-between mb-2">
<h3 className="text-lg font-semibold text-neon-cyan">
{t('settings.sync.title')}
</h3>
{!isSyncing && totalDays > 0 && (
<button
onClick={() => {
void startSync()
}}
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')}
</button>
)}
</div>
{totalDays > 0 && (
<div className="mb-2">
<p className="text-sm text-cyber-accent">
{t('settings.sync.daysRange', {
startDate: formatDate(startDate),
endDate: formatDate(endDate),
days: totalDays,
})}
</p>
</div>
)}
{isSyncing && syncProgress && (
<div className="space-y-2">
<div className="flex items-center justify-between text-sm">
<span className="text-cyber-accent">
{t('settings.sync.progress', {
current: syncProgress.currentDay,
total: syncProgress.totalDays,
})}
</span>
<span className="text-neon-cyan font-semibold">
{Math.round(progressPercentage)}%
</span>
</div>
<div className="w-full bg-cyber-dark rounded-full h-2 overflow-hidden">
<div
className="bg-neon-cyan h-full transition-all duration-300"
style={{ width: `${progressPercentage}%` }}
/>
</div>
</div>
)}
{!isSyncing && totalDays === 0 && lastSyncDate !== null && (
<p className="text-sm text-green-400">
{t('settings.sync.completed')}
</p>
)}
</div>
)
}

View File

@ -1,5 +1,6 @@
import { useEffect, useState, type Dispatch, type SetStateAction } from 'react' import { useEffect, useState, type Dispatch, type SetStateAction } from 'react'
import type { Article } from '@/types/nostr' import type { Article } from '@/types/nostr'
import type { ArticleDraft } from '@/lib/articlePublisherTypes'
import { useArticleEditing } from '@/hooks/useArticleEditing' import { useArticleEditing } from '@/hooks/useArticleEditing'
import { UserArticlesView } from './UserArticlesList' import { UserArticlesView } from './UserArticlesList'
import { EditPanel } from './UserArticlesEditPanel' import { EditPanel } from './UserArticlesEditPanel'
@ -135,16 +136,29 @@ function buildUpdatedArticle(
pubkey: string, pubkey: string,
newId: string newId: string
): Article { ): Article {
const hash = newId.split('_')[0] ?? ''
const index = Number.parseInt(newId.split('_')[1] ?? '0', 10)
const version = Number.parseInt(newId.split('_')[2] ?? '0', 10)
return { return {
id: newId, id: newId,
hash,
version,
index,
pubkey, pubkey,
title: draft.title, title: draft.title,
preview: draft.preview, preview: draft.preview,
content: '', content: '',
description: draft.preview,
contentDescription: draft.preview,
createdAt: Math.floor(Date.now() / 1000), createdAt: Math.floor(Date.now() / 1000),
zapAmount: draft.zapAmount, zapAmount: draft.zapAmount,
paid: false, paid: false,
thumbnailUrl: draft.bannerUrl ?? '',
...(draft.category ? { category: draft.category } : {}), ...(draft.category ? { category: draft.category } : {}),
...(draft.seriesId ? { seriesId: draft.seriesId } : {}),
...(draft.bannerUrl ? { bannerUrl: draft.bannerUrl } : {}),
...(draft.media ? { media: draft.media } : {}),
...(draft.pages ? { pages: draft.pages } : {}),
} }
} }

View File

@ -4,6 +4,7 @@ import { storePrivateContent, getStoredPrivateContent } from './articleStorage'
import { buildTags } from './nostrTagSystem' import { buildTags } from './nostrTagSystem'
import { PLATFORM_SERVICE } from './platformConfig' import { PLATFORM_SERVICE } from './platformConfig'
import { generateSeriesHashId, generatePublicationHashId } from './hashIdGenerator' import { generateSeriesHashId, generatePublicationHashId } from './hashIdGenerator'
import { parseObjectId } from './urlGenerator'
import type { ArticleDraft, PublishedArticle } from './articlePublisher' import type { ArticleDraft, PublishedArticle } from './articlePublisher'
import type { AlbyInvoice } from '@/types/alby' import type { AlbyInvoice } from '@/types/alby'
import type { Review, Series } from '@/types/nostr' import type { Review, Series } from '@/types/nostr'
@ -64,12 +65,20 @@ export async function publishSeries(params: {
if (!published) { if (!published) {
throw new Error('Failed to publish series') throw new Error('Failed to publish series')
} }
const parsed = parseObjectId(published.id)
const hash = parsed.hash ?? published.id
const version = parsed.version ?? 0
const index = parsed.index ?? 0
return { return {
id: published.id, id: published.id,
hash,
version,
index,
pubkey: params.authorPubkey, pubkey: params.authorPubkey,
title: params.title, title: params.title,
description: params.description, description: params.description,
preview: params.preview ?? params.description.substring(0, 200), preview: params.preview ?? params.description.substring(0, 200),
thumbnailUrl: params.coverUrl ?? '',
category, category,
...(params.coverUrl ? { coverUrl: params.coverUrl } : {}), ...(params.coverUrl ? { coverUrl: params.coverUrl } : {}),
kindType: 'series', kindType: 'series',
@ -156,12 +165,20 @@ export async function publishReview(params: {
if (!published) { if (!published) {
throw new Error('Failed to publish review') throw new Error('Failed to publish review')
} }
const parsed = parseObjectId(published.id)
const hash = parsed.hash ?? published.id
const version = parsed.version ?? 0
const index = parsed.index ?? 0
return { return {
id: published.id, id: published.id,
hash,
version,
index,
articleId: params.articleId, articleId: params.articleId,
authorPubkey: params.authorPubkey, authorPubkey: params.authorPubkey,
reviewerPubkey: params.reviewerPubkey, reviewerPubkey: params.reviewerPubkey,
content: params.content, content: params.content,
description: params.content.substring(0, 200),
createdAt: published.created_at, createdAt: published.created_at,
...(params.title ? { title: params.title } : {}), ...(params.title ? { title: params.title } : {}),
...(params.text ? { text: params.text } : {}), ...(params.text ? { text: params.text } : {}),

View File

@ -181,8 +181,8 @@ export async function parsePresentationEvent(event: Event): Promise<import('@/ty
authorName: profileData?.authorName ?? '', authorName: profileData?.authorName ?? '',
presentation: profileData?.presentation ?? '', presentation: profileData?.presentation ?? '',
contentDescription: profileData?.contentDescription ?? '', contentDescription: profileData?.contentDescription ?? '',
mainnetAddress: profileData?.mainnetAddress ?? tags.mainnetAddress, mainnetAddress: profileData?.mainnetAddress ?? (typeof tags.mainnetAddress === 'string' ? tags.mainnetAddress : undefined),
pictureUrl: profileData?.pictureUrl ?? tags.pictureUrl, pictureUrl: profileData?.pictureUrl ?? (typeof tags.pictureUrl === 'string' ? tags.pictureUrl : undefined),
category: profileData?.category ?? tags.category ?? 'sciencefiction', category: profileData?.category ?? tags.category ?? 'sciencefiction',
}) })
} }
@ -202,7 +202,7 @@ export async function parsePresentationEvent(event: Event): Promise<import('@/ty
content: event.content, content: event.content,
description: profileData?.presentation ?? tags.description ?? '', // Required field description: profileData?.presentation ?? tags.description ?? '', // Required field
contentDescription: profileData?.contentDescription ?? tags.description ?? '', // Required field contentDescription: profileData?.contentDescription ?? tags.description ?? '', // Required field
thumbnailUrl: (profileData?.pictureUrl ?? tags.pictureUrl) ?? '', // Required field thumbnailUrl: (typeof profileData?.pictureUrl === 'string' ? profileData.pictureUrl : typeof tags.pictureUrl === 'string' ? tags.pictureUrl : ''), // Required field
createdAt: event.created_at, createdAt: event.created_at,
zapAmount: 0, zapAmount: 0,
paid: true, paid: true,

View File

@ -1,5 +1,5 @@
import type { Event } from 'nostr-tools' import type { Event } from 'nostr-tools'
import type { Article, KindType, Purchase, Review, ReviewTip, Series, Sponsoring } from '@/types/nostr' import type { Article, KindType, Page, Purchase, Review, ReviewTip, Series, Sponsoring } from '@/types/nostr'
import { extractTagsFromEvent } from './nostrTagSystem' import { extractTagsFromEvent } from './nostrTagSystem'
import { buildObjectId, parseObjectId } from './urlGenerator' import { buildObjectId, parseObjectId } from './urlGenerator'
import { generateHashId } from './hashIdGenerator' import { generateHashId } from './hashIdGenerator'
@ -229,7 +229,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: tags.bannerUrl ?? 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 } : {}),

51
lib/syncStorage.ts Normal file
View File

@ -0,0 +1,51 @@
import { storageService } from './storage/indexedDB'
const LAST_SYNC_DATE_KEY = 'last_sync_date'
const SYNC_STORAGE_SECRET = 'sync_storage_secret'
/**
* Get the last synchronization date
* Returns MIN_EVENT_DATE if no date is stored
*/
export async function getLastSyncDate(): Promise<number> {
try {
const stored = await storageService.get<number>(LAST_SYNC_DATE_KEY, SYNC_STORAGE_SECRET)
if (stored !== null && typeof stored === 'number') {
return stored
}
} catch (error) {
console.error('Error getting last sync date:', error)
}
// Return MIN_EVENT_DATE if no date is stored
const { MIN_EVENT_DATE } = await import('./platformConfig')
return MIN_EVENT_DATE
}
/**
* Store the last synchronization date
*/
export async function setLastSyncDate(timestamp: number): Promise<void> {
try {
await storageService.set(LAST_SYNC_DATE_KEY, timestamp, SYNC_STORAGE_SECRET)
} catch (error) {
console.error('Error setting last sync date:', error)
}
}
/**
* Calculate the number of days between two timestamps
*/
export function calculateDaysBetween(startTimestamp: number, endTimestamp: number): number {
const startDate = new Date(startTimestamp * 1000)
const endDate = new Date(endTimestamp * 1000)
const diffTime = endDate.getTime() - startDate.getTime()
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
return Math.max(0, diffDays)
}
/**
* Get the current date as Unix timestamp
*/
export function getCurrentTimestamp(): number {
return Math.floor(Date.now() / 1000)
}

View File

@ -67,20 +67,23 @@ async function fetchAndCachePublications(
} }
// Cache each publication // Cache each publication
for (const [hash, hashEvents] of eventsByHashId.entries()) { for (const [_hash, hashEvents] of eventsByHashId.entries()) {
const latestEvent = getLatestVersion(hashEvents) const latestEvent = getLatestVersion(hashEvents)
if (latestEvent) { if (latestEvent) {
const extracted = await extractPublicationFromEvent(latestEvent) const extracted = await extractPublicationFromEvent(latestEvent)
if (extracted?.hash) { if (extracted) {
const parsed = parseObjectId(extracted.id)
const extractedHash = parsed.hash ?? extracted.id
const extractedIndex = parsed.index ?? 0
const tags = extractTagsFromEvent(latestEvent) const tags = extractTagsFromEvent(latestEvent)
await objectCache.set( await objectCache.set(
'publication', 'publication',
extracted.hash, extractedHash,
latestEvent, latestEvent,
extracted, extracted,
tags.version ?? 0, tags.version ?? 0,
tags.hidden ?? false, tags.hidden ?? false,
extracted.index extractedIndex
) )
} }
} }
@ -158,20 +161,23 @@ async function fetchAndCacheSeries(
} }
// Cache each series // Cache each series
for (const [hash, hashEvents] of eventsByHashId.entries()) { for (const [_hash, hashEvents] of eventsByHashId.entries()) {
const latestEvent = getLatestVersion(hashEvents) const latestEvent = getLatestVersion(hashEvents)
if (latestEvent) { if (latestEvent) {
const extracted = await extractSeriesFromEvent(latestEvent) const extracted = await extractSeriesFromEvent(latestEvent)
if (extracted?.hash) { if (extracted) {
const parsed = parseObjectId(extracted.id)
const extractedHash = parsed.hash ?? extracted.id
const extractedIndex = parsed.index ?? 0
const tags = extractTagsFromEvent(latestEvent) const tags = extractTagsFromEvent(latestEvent)
await objectCache.set( await objectCache.set(
'series', 'series',
extracted.hash, extractedHash,
latestEvent, latestEvent,
extracted, extracted,
tags.version ?? 0, tags.version ?? 0,
tags.hidden ?? false, tags.hidden ?? false,
extracted.index extractedIndex
) )
} }
} }
@ -233,9 +239,6 @@ async function fetchAndCachePurchases(
for (const event of events) { for (const event of events) {
const extracted = await extractPurchaseFromEvent(event) const extracted = await extractPurchaseFromEvent(event)
if (extracted) { if (extracted) {
const parsed = parseObjectId(extracted.id)
const hash = parsed.hash ?? extracted.id
const index = parsed.index ?? 0
// Parse to Purchase object for cache // Parse to Purchase object for cache
const { parsePurchaseFromEvent } = await import('./nostrEventParsing') const { parsePurchaseFromEvent } = await import('./nostrEventParsing')
const purchase = await parsePurchaseFromEvent(event) const purchase = await parsePurchaseFromEvent(event)
@ -386,11 +389,22 @@ async function fetchAndCacheReviewTips(
}) })
} }
export interface SyncProgress {
currentDay: number
totalDays: number
completed: boolean
}
/** /**
* Synchronize all user content to IndexedDB cache * Synchronize all user content to IndexedDB cache
* Fetches profile, series, publications, purchases, sponsoring, and review tips and caches them * Fetches profile, series, publications, purchases, sponsoring, and review tips and caches them
* @param userPubkey - The user's public key
* @param onProgress - Optional callback to report progress (currentDay, totalDays, completed)
*/ */
export async function syncUserContentToCache(userPubkey: string): Promise<void> { export async function syncUserContentToCache(
userPubkey: string,
onProgress?: (progress: SyncProgress) => void
): Promise<void> {
try { try {
const pool = nostrService.getPool() const pool = nostrService.getPool()
if (!pool) { if (!pool) {
@ -400,23 +414,63 @@ export async function syncUserContentToCache(userPubkey: string): Promise<void>
const poolWithSub = pool as unknown as SimplePoolWithSub const poolWithSub = pool as unknown as SimplePoolWithSub
// Get last sync date and calculate days
const { getLastSyncDate, setLastSyncDate, getCurrentTimestamp, calculateDaysBetween } = await import('./syncStorage')
const lastSyncDate = await getLastSyncDate()
const currentTimestamp = getCurrentTimestamp()
const totalDays = calculateDaysBetween(lastSyncDate, currentTimestamp)
// Report initial progress
if (onProgress) {
onProgress({ currentDay: 0, totalDays, completed: false })
}
let currentDay = 0
// Fetch and cache author profile (already caches itself) // Fetch and cache author profile (already caches itself)
await fetchAuthorPresentationFromPool(poolWithSub, userPubkey) await fetchAuthorPresentationFromPool(poolWithSub, userPubkey)
currentDay++
if (onProgress) {
onProgress({ currentDay, totalDays, completed: false })
}
// Fetch and cache all series // Fetch and cache all series
await fetchAndCacheSeries(poolWithSub, userPubkey) await fetchAndCacheSeries(poolWithSub, userPubkey)
currentDay++
if (onProgress) {
onProgress({ currentDay, totalDays, completed: false })
}
// Fetch and cache all publications // Fetch and cache all publications
await fetchAndCachePublications(poolWithSub, userPubkey) await fetchAndCachePublications(poolWithSub, userPubkey)
currentDay++
if (onProgress) {
onProgress({ currentDay, totalDays, completed: false })
}
// Fetch and cache all purchases (as payer) // Fetch and cache all purchases (as payer)
await fetchAndCachePurchases(poolWithSub, userPubkey) await fetchAndCachePurchases(poolWithSub, userPubkey)
currentDay++
if (onProgress) {
onProgress({ currentDay, totalDays, completed: false })
}
// Fetch and cache all sponsoring (as author) // Fetch and cache all sponsoring (as author)
await fetchAndCacheSponsoring(poolWithSub, userPubkey) await fetchAndCacheSponsoring(poolWithSub, userPubkey)
currentDay++
if (onProgress) {
onProgress({ currentDay, totalDays, completed: false })
}
// Fetch and cache all review tips (as author) // Fetch and cache all review tips (as author)
await fetchAndCacheReviewTips(poolWithSub, userPubkey) await fetchAndCacheReviewTips(poolWithSub, userPubkey)
currentDay++
if (onProgress) {
onProgress({ currentDay, totalDays, completed: true })
}
// Store the current timestamp as last sync date
await setLastSyncDate(currentTimestamp)
} 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 // Don't throw - this is a background operation

View File

@ -1,7 +1,7 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import { useRouter } from 'next/router' import { useRouter } from 'next/router'
import type { ArticleFilters } from '@/components/ArticleFilters' import type { ArticleFilters } from '@/components/ArticleFilters'
import type { NostrProfile } from '@/types/nostr' import type { Article, NostrProfile } from '@/types/nostr'
import { ProfileView } from '@/components/ProfileView' import { ProfileView } from '@/components/ProfileView'
import { useNostrAuth } from '@/hooks/useNostrAuth' import { useNostrAuth } from '@/hooks/useNostrAuth'
import { useUserArticles } from '@/hooks/useUserArticles' import { useUserArticles } from '@/hooks/useUserArticles'

View File

@ -236,6 +236,11 @@ settings.keyManagement.recovery.copy=Copy Recovery Words
settings.keyManagement.recovery.copied=✓ Copied! 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.start=Start Synchronization
settings.sync.daysRange=From {{startDate}} to {{endDate}} ({{days}} days)
settings.sync.progress=Day {{current}} of {{total}}
settings.sync.completed=Everything is synchronized
settings.nip95.title=NIP-95 Upload Endpoints settings.nip95.title=NIP-95 Upload Endpoints
settings.nip95.loading=Loading... settings.nip95.loading=Loading...
settings.nip95.error.loadFailed=Failed to load NIP-95 APIs settings.nip95.error.loadFailed=Failed to load NIP-95 APIs

View File

@ -236,6 +236,11 @@ settings.keyManagement.recovery.copy=Copier les mots de récupération
settings.keyManagement.recovery.copied=✓ Copié ! 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.start=Démarrer la synchronisation
settings.sync.daysRange=Du {{startDate}} au {{endDate}} ({{days}} jours)
settings.sync.progress=Jour {{current}} sur {{total}}
settings.sync.completed=Tout est synchronisé
settings.language.title=Langue de préférence settings.language.title=Langue de préférence
settings.language.description=Choisissez votre langue préférée pour l'interface settings.language.description=Choisissez votre langue préférée pour l'interface
settings.language.loading=Chargement... settings.language.loading=Chargement...