lint fix wip
This commit is contained in:
parent
9f7a0e1527
commit
d068b67deb
@ -2,7 +2,6 @@ import { useState } from 'react'
|
||||
import { useRouter } from 'next/router'
|
||||
import { nostrAuthService } from '@/lib/nostrAuth'
|
||||
import { objectCache } from '@/lib/objectCache'
|
||||
import { syncUserContentToCache } from '@/lib/userContentSync'
|
||||
|
||||
async function updateCache(): Promise<void> {
|
||||
const state = nostrAuthService.getState()
|
||||
@ -20,7 +19,13 @@ async function updateCache(): Promise<void> {
|
||||
objectCache.clear('review_tip'),
|
||||
])
|
||||
|
||||
await syncUserContentToCache(state.pubkey)
|
||||
const { swClient } = await import('@/lib/swClient')
|
||||
const isReady = await swClient.isReady()
|
||||
if (isReady) {
|
||||
await swClient.startUserSync(state.pubkey)
|
||||
} else {
|
||||
throw new Error('Service Worker is not ready')
|
||||
}
|
||||
}
|
||||
|
||||
function ErrorMessage({ error }: { error: string }): React.ReactElement {
|
||||
|
||||
@ -141,10 +141,13 @@ export function KeyManagementManager(): React.ReactElement {
|
||||
setShowImportForm(false)
|
||||
await loadKeys()
|
||||
|
||||
// Sync user content to IndexedDB cache
|
||||
// Sync user content via Service Worker
|
||||
if (result.publicKey) {
|
||||
const { syncUserContentToCache } = await import('@/lib/userContentSync')
|
||||
void syncUserContentToCache(result.publicKey)
|
||||
const { swClient } = await import('@/lib/swClient')
|
||||
const isReady = await swClient.isReady()
|
||||
if (isReady) {
|
||||
void swClient.startUserSync(result.publicKey)
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
const errorMessage = e instanceof Error ? e.message : t('settings.keyManagement.import.error.failed')
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { setLocale, getLocale, type Locale } from '@/lib/i18n'
|
||||
|
||||
const LOCALE_STORAGE_KEY = 'zapwall-locale'
|
||||
import { localeStorage } from '@/lib/localeStorage'
|
||||
|
||||
interface LocaleButtonProps {
|
||||
locale: Locale
|
||||
@ -30,30 +29,30 @@ export function LanguageSelector(): React.ReactElement {
|
||||
const [currentLocale, setCurrentLocale] = useState<Locale>(getLocale())
|
||||
|
||||
useEffect(() => {
|
||||
// Load saved locale from localStorage
|
||||
const loadLocale = (): void => {
|
||||
// Load saved locale from IndexedDB
|
||||
const loadLocale = async (): Promise<void> => {
|
||||
try {
|
||||
if (typeof window !== 'undefined') {
|
||||
const savedLocale = window.localStorage.getItem(LOCALE_STORAGE_KEY) as Locale | null
|
||||
if (savedLocale && (savedLocale === 'fr' || savedLocale === 'en')) {
|
||||
setLocale(savedLocale)
|
||||
setCurrentLocale(savedLocale)
|
||||
}
|
||||
// Migrate from localStorage if needed
|
||||
await localeStorage.migrateFromLocalStorage()
|
||||
|
||||
// Load from IndexedDB
|
||||
const savedLocale = await localeStorage.getLocale()
|
||||
if (savedLocale) {
|
||||
setLocale(savedLocale)
|
||||
setCurrentLocale(savedLocale)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('Error loading locale:', e)
|
||||
}
|
||||
}
|
||||
loadLocale()
|
||||
void loadLocale()
|
||||
}, [])
|
||||
|
||||
const handleLocaleChange = (locale: Locale): void => {
|
||||
const handleLocaleChange = async (locale: Locale): Promise<void> => {
|
||||
setLocale(locale)
|
||||
setCurrentLocale(locale)
|
||||
try {
|
||||
if (typeof window !== 'undefined') {
|
||||
window.localStorage.setItem(LOCALE_STORAGE_KEY, locale)
|
||||
}
|
||||
await localeStorage.saveLocale(locale)
|
||||
} catch (e) {
|
||||
console.error('Error saving locale:', e)
|
||||
}
|
||||
|
||||
@ -1,8 +1,7 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { setLocale, getLocale, type Locale } from '@/lib/i18n'
|
||||
import { t } from '@/lib/i18n'
|
||||
|
||||
const LOCALE_STORAGE_KEY = 'zapwall-locale'
|
||||
import { localeStorage } from '@/lib/localeStorage'
|
||||
|
||||
interface LocaleOptionProps {
|
||||
locale: Locale
|
||||
@ -32,14 +31,14 @@ export function LanguageSettingsManager(): React.ReactElement {
|
||||
const [loading, setLoading] = useState(true)
|
||||
|
||||
useEffect(() => {
|
||||
const loadLocale = (): void => {
|
||||
const loadLocale = async (): Promise<void> => {
|
||||
try {
|
||||
if (typeof window === 'undefined') {
|
||||
setLoading(false)
|
||||
return
|
||||
}
|
||||
const savedLocale = window.localStorage.getItem(LOCALE_STORAGE_KEY) as Locale | null
|
||||
if (savedLocale && (savedLocale === 'fr' || savedLocale === 'en')) {
|
||||
// Migrate from localStorage if needed
|
||||
await localeStorage.migrateFromLocalStorage()
|
||||
|
||||
// Load from IndexedDB
|
||||
const savedLocale = await localeStorage.getLocale()
|
||||
if (savedLocale) {
|
||||
setLocale(savedLocale)
|
||||
setCurrentLocale(savedLocale)
|
||||
}
|
||||
@ -49,16 +48,14 @@ export function LanguageSettingsManager(): React.ReactElement {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
loadLocale()
|
||||
void loadLocale()
|
||||
}, [])
|
||||
|
||||
const handleLocaleChange = (locale: Locale): void => {
|
||||
const handleLocaleChange = async (locale: Locale): Promise<void> => {
|
||||
setLocale(locale)
|
||||
setCurrentLocale(locale)
|
||||
try {
|
||||
if (typeof window !== 'undefined') {
|
||||
window.localStorage.setItem(LOCALE_STORAGE_KEY, locale)
|
||||
}
|
||||
await localeStorage.saveLocale(locale)
|
||||
} catch (e) {
|
||||
console.error('Error saving locale:', e)
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { nostrAuthService } from '@/lib/nostrAuth'
|
||||
import { syncUserContentToCache, type SyncProgress } from '@/lib/userContentSync'
|
||||
import type { SyncProgress } from '@/lib/userContentSync'
|
||||
import { getLastSyncDate, setLastSyncDate as setLastSyncDateStorage, getCurrentTimestamp, calculateDaysBetween } from '@/lib/syncStorage'
|
||||
import { MIN_EVENT_DATE } from '@/lib/platformConfig'
|
||||
import { objectCache } from '@/lib/objectCache'
|
||||
@ -94,15 +94,39 @@ export function SyncProgressBar(): React.ReactElement | null {
|
||||
setSyncProgress({ currentStep: 0, totalSteps: 6, completed: false })
|
||||
|
||||
try {
|
||||
await syncUserContentToCache(connectionState.pubkey, (progress) => {
|
||||
setSyncProgress(progress)
|
||||
if (progress.completed) {
|
||||
setIsSyncing(false)
|
||||
void loadSyncStatus()
|
||||
const { swClient } = await import('@/lib/swClient')
|
||||
const isReady = await swClient.isReady()
|
||||
if (isReady) {
|
||||
await swClient.startUserSync(connectionState.pubkey)
|
||||
// Progress is tracked via syncProgressManager
|
||||
// Listen to syncProgressManager for updates
|
||||
const { syncProgressManager } = await import('@/lib/syncProgressManager')
|
||||
const checkProgress = (): void => {
|
||||
const currentProgress = syncProgressManager.getProgress()
|
||||
if (currentProgress) {
|
||||
setSyncProgress(currentProgress)
|
||||
if (currentProgress.completed) {
|
||||
setIsSyncing(false)
|
||||
void loadSyncStatus()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
// Check if sync completed successfully (if it didn't, isSyncing should still be false)
|
||||
setIsSyncing(false)
|
||||
// Check progress periodically
|
||||
const progressInterval = setInterval(() => {
|
||||
checkProgress()
|
||||
const currentProgress = syncProgressManager.getProgress()
|
||||
if (currentProgress?.completed) {
|
||||
clearInterval(progressInterval)
|
||||
}
|
||||
}, 500)
|
||||
// Cleanup after 60 seconds max
|
||||
setTimeout(() => {
|
||||
clearInterval(progressInterval)
|
||||
setIsSyncing(false)
|
||||
}, 60000)
|
||||
} else {
|
||||
setIsSyncing(false)
|
||||
}
|
||||
} catch (autoSyncError) {
|
||||
console.error('[SyncProgressBar] Error during auto-sync:', autoSyncError)
|
||||
setIsSyncing(false)
|
||||
@ -141,15 +165,41 @@ export function SyncProgressBar(): React.ReactElement | null {
|
||||
// Reload sync status
|
||||
await loadSyncStatus()
|
||||
|
||||
// Start full resynchronization
|
||||
// Start full resynchronization via Service Worker
|
||||
if (state.pubkey !== null) {
|
||||
await syncUserContentToCache(state.pubkey, (progress) => {
|
||||
setSyncProgress(progress)
|
||||
if (progress.completed) {
|
||||
setIsSyncing(false)
|
||||
void loadSyncStatus()
|
||||
const { swClient } = await import('@/lib/swClient')
|
||||
const isReady = await swClient.isReady()
|
||||
if (isReady) {
|
||||
await swClient.startUserSync(state.pubkey)
|
||||
// Progress is tracked via syncProgressManager
|
||||
// Listen to syncProgressManager for updates
|
||||
const { syncProgressManager } = await import('@/lib/syncProgressManager')
|
||||
const checkProgress = (): void => {
|
||||
const currentProgress = syncProgressManager.getProgress()
|
||||
if (currentProgress) {
|
||||
setSyncProgress(currentProgress)
|
||||
if (currentProgress.completed) {
|
||||
setIsSyncing(false)
|
||||
void loadSyncStatus()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
// Check progress periodically
|
||||
const progressInterval = setInterval(() => {
|
||||
checkProgress()
|
||||
const currentProgress = syncProgressManager.getProgress()
|
||||
if (currentProgress?.completed) {
|
||||
clearInterval(progressInterval)
|
||||
}
|
||||
}, 500)
|
||||
// Cleanup after 60 seconds max
|
||||
setTimeout(() => {
|
||||
clearInterval(progressInterval)
|
||||
setIsSyncing(false)
|
||||
}, 60000)
|
||||
} else {
|
||||
setIsSyncing(false)
|
||||
}
|
||||
}
|
||||
} catch (resyncError) {
|
||||
console.error('Error resynchronizing:', resyncError)
|
||||
|
||||
@ -5,6 +5,11 @@ import { buildPresentationEvent, sendEncryptedContent } from './articlePublisher
|
||||
import type { ArticleDraft, AuthorPresentationDraft, PublishedArticle } from './articlePublisherTypes'
|
||||
import { prepareAuthorKeys, isValidCategory, type PublishValidationResult } from './articlePublisherValidation'
|
||||
import { buildFailure, encryptAndPublish } from './articlePublisherPublish'
|
||||
import { writeOrchestrator } from './writeOrchestrator'
|
||||
import { finalizeEvent } from 'nostr-tools'
|
||||
import { hexToBytes } from 'nostr-tools/utils'
|
||||
import { generateAuthorHashId } from './hashIdGenerator'
|
||||
import { buildObjectId } from './urlGenerator'
|
||||
|
||||
export type { ArticleDraft, AuthorPresentationDraft, PublishedArticle } from './articlePublisherTypes'
|
||||
|
||||
@ -174,31 +179,100 @@ export class ArticlePublisher {
|
||||
// Extract author name from title (format: "Présentation de <name>")
|
||||
const authorName = draft.title.replace(/^Présentation de /, '').trim() ?? 'Auteur'
|
||||
|
||||
// Build event with hash-based ID
|
||||
const eventTemplate = await buildPresentationEvent(draft, authorPubkey, authorName, 'sciencefiction')
|
||||
const publishedEvent = await nostrService.publishEvent(eventTemplate)
|
||||
// Extract presentation and contentDescription from draft.content
|
||||
const separator = '\n\n---\n\nDescription du contenu :\n'
|
||||
const separatorIndex = draft.content.indexOf(separator)
|
||||
const presentation = separatorIndex !== -1 ? draft.content.substring(0, separatorIndex) : draft.presentation
|
||||
let contentDescription = separatorIndex !== -1 ? draft.content.substring(separatorIndex + separator.length) : draft.contentDescription
|
||||
|
||||
if (!publishedEvent) {
|
||||
// Remove Bitcoin address from contentDescription if present
|
||||
if (contentDescription) {
|
||||
contentDescription = contentDescription
|
||||
.split('\n')
|
||||
.filter((line) => !line.includes('Adresse Bitcoin mainnet (pour le sponsoring)'))
|
||||
.join('\n')
|
||||
.trim()
|
||||
}
|
||||
|
||||
const category = 'sciencefiction'
|
||||
const version = 0
|
||||
const index = 0
|
||||
|
||||
// Generate hash ID
|
||||
const hashId = await generateAuthorHashId({
|
||||
pubkey: authorPubkey,
|
||||
authorName,
|
||||
presentation,
|
||||
contentDescription,
|
||||
mainnetAddress: draft.mainnetAddress ?? undefined,
|
||||
pictureUrl: draft.pictureUrl ?? undefined,
|
||||
category,
|
||||
})
|
||||
|
||||
const hash = hashId
|
||||
const id = buildObjectId(hash, index, version)
|
||||
|
||||
// Build parsed AuthorPresentationArticle object
|
||||
const parsedAuthor: import('@/types/nostr').AuthorPresentationArticle = {
|
||||
id,
|
||||
hash,
|
||||
version,
|
||||
index,
|
||||
pubkey: authorPubkey,
|
||||
title: draft.title,
|
||||
preview: draft.preview,
|
||||
content: draft.content,
|
||||
description: presentation,
|
||||
contentDescription,
|
||||
thumbnailUrl: draft.pictureUrl ?? '',
|
||||
createdAt: Math.floor(Date.now() / 1000),
|
||||
zapAmount: 0,
|
||||
paid: true,
|
||||
category: 'author-presentation',
|
||||
isPresentation: true,
|
||||
mainnetAddress: draft.mainnetAddress ?? '',
|
||||
totalSponsoring: 0,
|
||||
originalCategory: 'science-fiction',
|
||||
...(draft.pictureUrl ? { bannerUrl: draft.pictureUrl } : {}),
|
||||
}
|
||||
|
||||
// Build event template
|
||||
const eventTemplate = await buildPresentationEvent(draft, authorPubkey, authorName, category, version, index)
|
||||
|
||||
// Set private key in orchestrator
|
||||
writeOrchestrator.setPrivateKey(authorPrivateKey)
|
||||
|
||||
// Finalize event
|
||||
const secretKey = hexToBytes(authorPrivateKey)
|
||||
const event = finalizeEvent(eventTemplate, secretKey)
|
||||
|
||||
// Get active relays
|
||||
const { relaySessionManager } = await import('./relaySessionManager')
|
||||
const activeRelays = await relaySessionManager.getActiveRelays()
|
||||
const { getPrimaryRelay } = await import('./config')
|
||||
const relays = activeRelays.length > 0 ? activeRelays : [await getPrimaryRelay()]
|
||||
|
||||
// Publish via writeOrchestrator (parallel network + local write)
|
||||
const result = await writeOrchestrator.writeAndPublish(
|
||||
{
|
||||
objectType: 'author',
|
||||
hash,
|
||||
event,
|
||||
parsed: parsedAuthor,
|
||||
version,
|
||||
hidden: false,
|
||||
index,
|
||||
},
|
||||
relays
|
||||
)
|
||||
|
||||
if (!result.success) {
|
||||
return buildFailure('Failed to publish presentation article')
|
||||
}
|
||||
|
||||
// Parse and cache the published presentation immediately with published: false
|
||||
// The published status will be updated asynchronously by publishEvent
|
||||
const { parsePresentationEvent } = await import('./articlePublisherHelpers')
|
||||
const { extractTagsFromEvent } = await import('./nostrTagSystem')
|
||||
const parsed = await parsePresentationEvent(publishedEvent)
|
||||
if (parsed) {
|
||||
const tags = extractTagsFromEvent(publishedEvent)
|
||||
const { id: tagId, version: tagVersion, hidden: tagHidden } = tags
|
||||
if (tagId) {
|
||||
const { writeService } = await import('./writeService')
|
||||
await writeService.writeObject('author', tagId, publishedEvent, parsed, tagVersion ?? 0, tagHidden ?? false, undefined, false)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
articleId: publishedEvent.id,
|
||||
previewEventId: publishedEvent.id,
|
||||
articleId: event.id,
|
||||
previewEventId: event.id,
|
||||
success: true,
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
127
lib/localeStorage.ts
Normal file
127
lib/localeStorage.ts
Normal file
@ -0,0 +1,127 @@
|
||||
/**
|
||||
* IndexedDB storage for locale preference
|
||||
*/
|
||||
|
||||
const DB_NAME = 'nostr_paywall_settings'
|
||||
const DB_VERSION = 2 // Incremented to add locale store
|
||||
const STORE_NAME = 'locale'
|
||||
|
||||
export type Locale = 'fr' | 'en'
|
||||
|
||||
class LocaleStorageService {
|
||||
private db: IDBDatabase | null = null
|
||||
|
||||
private async initDB(): Promise<IDBDatabase> {
|
||||
if (this.db) {
|
||||
return this.db
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
if (typeof window === 'undefined' || !window.indexedDB) {
|
||||
reject(new Error('IndexedDB is not available'))
|
||||
return
|
||||
}
|
||||
|
||||
const request = indexedDB.open(DB_NAME, DB_VERSION)
|
||||
|
||||
request.onerror = () => {
|
||||
reject(new Error(`Failed to open IndexedDB: ${request.error}`))
|
||||
}
|
||||
|
||||
request.onsuccess = () => {
|
||||
this.db = request.result
|
||||
resolve(this.db)
|
||||
}
|
||||
|
||||
request.onupgradeneeded = (event) => {
|
||||
const db = (event.target as IDBOpenDBRequest).result
|
||||
if (!db.objectStoreNames.contains(STORE_NAME)) {
|
||||
db.createObjectStore(STORE_NAME, { keyPath: 'key' })
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Get locale from IndexedDB
|
||||
*/
|
||||
async getLocale(): Promise<Locale | null> {
|
||||
try {
|
||||
const db = await this.initDB()
|
||||
const transaction = db.transaction([STORE_NAME], 'readonly')
|
||||
const store = transaction.objectStore(STORE_NAME)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = store.get('locale')
|
||||
|
||||
request.onsuccess = () => {
|
||||
const result = request.result as { key: string; value: Locale } | undefined
|
||||
const locale = result?.value
|
||||
if (locale === 'fr' || locale === 'en') {
|
||||
resolve(locale)
|
||||
} else {
|
||||
resolve(null)
|
||||
}
|
||||
}
|
||||
|
||||
request.onerror = () => reject(request.error)
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error retrieving locale from IndexedDB:', error)
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save locale to IndexedDB
|
||||
*/
|
||||
async saveLocale(locale: Locale): Promise<void> {
|
||||
try {
|
||||
const db = await this.initDB()
|
||||
const transaction = db.transaction([STORE_NAME], 'readwrite')
|
||||
const store = transaction.objectStore(STORE_NAME)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const request = store.put({ key: 'locale', value: locale })
|
||||
|
||||
request.onsuccess = () => {
|
||||
resolve()
|
||||
}
|
||||
|
||||
request.onerror = () => reject(request.error)
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Error saving locale to IndexedDB:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Migrate locale from localStorage to IndexedDB if it exists
|
||||
*/
|
||||
async migrateFromLocalStorage(): Promise<void> {
|
||||
if (typeof window === 'undefined') {
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const LOCALE_STORAGE_KEY = 'zapwall-locale'
|
||||
const storedLocale = window.localStorage.getItem(LOCALE_STORAGE_KEY) as Locale | null
|
||||
|
||||
if (storedLocale && (storedLocale === 'fr' || storedLocale === 'en')) {
|
||||
// Check if already in IndexedDB
|
||||
const existingLocale = await this.getLocale()
|
||||
if (!existingLocale) {
|
||||
// Migrate from localStorage to IndexedDB
|
||||
await this.saveLocale(storedLocale)
|
||||
// Remove from localStorage after successful migration
|
||||
window.localStorage.removeItem(LOCALE_STORAGE_KEY)
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error migrating locale from localStorage:', error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const localeStorage = new LocaleStorageService()
|
||||
@ -4,6 +4,8 @@ import { PLATFORM_SERVICE } from './platformConfig'
|
||||
import { generatePurchaseHashId, generateReviewTipHashId, generateSponsoringHashId } from './hashIdGenerator'
|
||||
import { buildObjectId } from './urlGenerator'
|
||||
import type { Event, EventTemplate } from 'nostr-tools'
|
||||
import type { Purchase } from '@/types/nostr'
|
||||
import { writeOrchestrator } from './writeOrchestrator'
|
||||
|
||||
/**
|
||||
* Publish an explicit payment note (kind 1) for a purchase
|
||||
@ -68,6 +70,21 @@ export async function publishPurchaseNote(params: {
|
||||
|
||||
tags.push(['json', paymentJson])
|
||||
|
||||
// Build parsed Purchase object
|
||||
const parsedPurchase: Purchase = {
|
||||
id,
|
||||
hash: hashId,
|
||||
version: 0,
|
||||
index: 0,
|
||||
payerPubkey: params.payerPubkey,
|
||||
articleId: params.articleId,
|
||||
authorPubkey: params.authorPubkey,
|
||||
amount: params.amount,
|
||||
paymentHash: params.paymentHash,
|
||||
createdAt: Math.floor(Date.now() / 1000),
|
||||
kindType: 'purchase',
|
||||
}
|
||||
|
||||
const eventTemplate: EventTemplate = {
|
||||
kind: 1,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
@ -76,7 +93,37 @@ export async function publishPurchaseNote(params: {
|
||||
}
|
||||
|
||||
nostrService.setPrivateKey(params.payerPrivateKey)
|
||||
return nostrService.publishEvent(eventTemplate)
|
||||
writeOrchestrator.setPrivateKey(params.payerPrivateKey)
|
||||
|
||||
// Finalize event
|
||||
const secretKey = hexToBytes(params.payerPrivateKey)
|
||||
const event = finalizeEvent(eventTemplate, secretKey)
|
||||
|
||||
// Get active relays
|
||||
const { relaySessionManager } = await import('./relaySessionManager')
|
||||
const activeRelays = await relaySessionManager.getActiveRelays()
|
||||
const { getPrimaryRelay } = await import('./config')
|
||||
const relays = activeRelays.length > 0 ? activeRelays : [await getPrimaryRelay()]
|
||||
|
||||
// Publish via writeOrchestrator (parallel network + local write)
|
||||
const result = await writeOrchestrator.writeAndPublish(
|
||||
{
|
||||
objectType: 'purchase',
|
||||
hash: hashId,
|
||||
event,
|
||||
parsed: parsedPurchase,
|
||||
version: 0,
|
||||
hidden: false,
|
||||
index: 0,
|
||||
},
|
||||
relays
|
||||
)
|
||||
|
||||
if (!result.success) {
|
||||
return null
|
||||
}
|
||||
|
||||
return event
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -237,7 +237,7 @@ class WriteService {
|
||||
data: { type, objectType, objectId, eventId, notificationData: data },
|
||||
})
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// Fallback: direct write
|
||||
const { notificationService } = await import('./notificationService')
|
||||
await notificationService.createNotification({
|
||||
@ -245,8 +245,9 @@ class WriteService {
|
||||
objectType,
|
||||
objectId,
|
||||
eventId,
|
||||
data
|
||||
)
|
||||
data,
|
||||
})
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('[WriteService] Error creating notification:', error)
|
||||
|
||||
@ -9,31 +9,36 @@ import { swSyncHandler } from '@/lib/swSyncHandler'
|
||||
import { swClient } from '@/lib/swClient'
|
||||
|
||||
function I18nProvider({ children }: { children: React.ReactNode }): React.ReactElement {
|
||||
// Get saved locale from localStorage or default to French
|
||||
const getInitialLocale = (): 'fr' | 'en' => {
|
||||
if (typeof window === 'undefined') {
|
||||
return 'fr'
|
||||
}
|
||||
try {
|
||||
const savedLocale = window.localStorage.getItem('zapwall-locale') as 'fr' | 'en' | null
|
||||
if (savedLocale === 'fr' || savedLocale === 'en') {
|
||||
return savedLocale
|
||||
}
|
||||
} catch {
|
||||
// Fallback to browser locale detection
|
||||
}
|
||||
// Try to detect browser locale
|
||||
const browserLocale = navigator.language.split('-')[0]
|
||||
return browserLocale === 'en' ? 'en' : 'fr'
|
||||
}
|
||||
|
||||
const [initialLocale, setInitialLocale] = React.useState<'fr' | 'en'>('fr')
|
||||
const [localeLoaded, setLocaleLoaded] = React.useState(false)
|
||||
|
||||
React.useEffect(() => {
|
||||
const locale = getInitialLocale()
|
||||
setInitialLocale(locale)
|
||||
setLocaleLoaded(true)
|
||||
const loadLocale = async (): Promise<void> => {
|
||||
try {
|
||||
// Migrate from localStorage if needed
|
||||
const { localeStorage } = await import('@/lib/localeStorage')
|
||||
await localeStorage.migrateFromLocalStorage()
|
||||
|
||||
// Load from IndexedDB
|
||||
const savedLocale = await localeStorage.getLocale()
|
||||
if (savedLocale) {
|
||||
setInitialLocale(savedLocale)
|
||||
setLocaleLoaded(true)
|
||||
return
|
||||
}
|
||||
} catch {
|
||||
// Fallback to browser locale detection
|
||||
}
|
||||
|
||||
// Fallback: Try to detect browser locale
|
||||
if (typeof window !== 'undefined') {
|
||||
const browserLocale = navigator.language.split('-')[0]
|
||||
setInitialLocale(browserLocale === 'en' ? 'en' : 'fr')
|
||||
}
|
||||
setLocaleLoaded(true)
|
||||
}
|
||||
|
||||
void loadLocale()
|
||||
}, [])
|
||||
|
||||
const { loaded } = useI18n(initialLocale)
|
||||
@ -143,4 +148,3 @@ export default function App({ Component, pageProps }: AppProps): React.ReactElem
|
||||
</I18nProvider>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user