From 620f5955ca596536ce85cf54f6aeb70df1bee15a Mon Sep 17 00:00:00 2001 From: Nicolas Cantu Date: Sat, 10 Jan 2026 09:41:57 +0100 Subject: [PATCH] lint fix --- components/HomeView.tsx | 89 +- components/ImageUploadField.tsx | 72 +- components/KeyManagementManager.tsx | 573 ++++++++----- components/LanguageSettingsManager.tsx | 58 +- components/MarkdownEditorTwoColumns.tsx | 417 ++++++---- components/PageHeader.tsx | 88 +- components/PaymentModal.tsx | 82 +- components/SyncProgressBar.tsx | 294 ++++--- components/UserArticles.tsx | 97 ++- .../connectButton/useConnectButtonUiState.ts | 6 +- components/reviewForms/ReviewFormView.tsx | 53 +- components/reviewForms/ReviewTipFormView.tsx | 91 ++- hooks/useArticleEditing.ts | 193 +++-- hooks/useArticlePayment.ts | 222 +++-- hooks/useAuthorPresentation.ts | 220 ++--- hooks/useAuthorsProfiles.ts | 80 +- hooks/useDocs.ts | 80 +- hooks/useI18n.ts | 97 ++- hooks/useNostrAuth.ts | 96 ++- hooks/useNotifications.ts | 125 +-- hooks/useUserArticles.ts | 171 ++-- lib/articleDraftToParsedArticle.ts | 70 ++ lib/articleInvoice.ts | 79 +- lib/articleMutations.ts | 120 ++- lib/articlePublisher.ts | 221 ++--- lib/articlePublisherHelpersPresentation.ts | 99 +-- lib/articlePublisherPublish.ts | 16 +- lib/authorPresentationParsing.ts | 32 + lib/automaticTransfer.ts | 167 ++-- lib/configStorage.ts | 85 +- lib/helpers/syncSubscriptionHelper.ts | 206 +++-- lib/metadataExtractor.ts | 692 ++++++++++------ lib/nip98.ts | 58 +- lib/nostrEventParsing.ts | 220 +++-- lib/nostrTagSystemExtract.ts | 137 +++- lib/nostrZapVerification.ts | 83 +- lib/notificationDetector.ts | 98 +-- lib/objectCache.ts | 181 ++-- lib/paymentNotes.ts | 612 +++++++------- lib/paymentPollingMain.ts | 114 +-- lib/platformSync.ts | 481 +++++++---- lib/publishWorker.ts | 86 +- lib/relaySelection.ts | 10 + lib/reviewRewardUpdate.ts | 140 ++-- lib/swSyncHandler.ts | 139 ++-- lib/userConfirm.ts | 233 +++--- lib/websocketService.ts | 69 +- lib/writeOrchestrator.ts | 88 +- lib/writeService.ts | 166 ++-- next.config.js | 6 - pages/api/nip95-upload.ts | 773 ++++++++++-------- pages/author/[pubkey].tsx | 28 +- pages/profile.tsx | 6 +- pages/series/[id].tsx | 12 +- scripts/findLongFunctions.js | 225 +++++ 55 files changed, 5459 insertions(+), 3497 deletions(-) create mode 100644 lib/articleDraftToParsedArticle.ts create mode 100644 lib/authorPresentationParsing.ts create mode 100644 lib/relaySelection.ts create mode 100644 scripts/findLongFunctions.js diff --git a/components/HomeView.tsx b/components/HomeView.tsx index 34eab8e..c71425c 100644 --- a/components/HomeView.tsx +++ b/components/HomeView.tsx @@ -79,15 +79,6 @@ function HomeContent({ // At startup, we don't know yet if we're loading articles or authors // Use a generic loading message until we have content const isInitialLoad = loading && allArticles.length === 0 && allAuthors.length === 0 - const articlesListProps = { - articles, - allArticles, - loading: loading && !isInitialLoad, // Don't show loading if it's the initial generic state - error, - onUnlock, - unlockedArticles - } - const authorsListProps = { authors, allAuthors, loading: loading && !isInitialLoad, error } return (
@@ -102,23 +93,77 @@ function HomeContent({ )} - {(() => { - if (isInitialLoad) { - return ( -
-

{t('common.loading')}

-
- ) - } - if (shouldShowAuthors) { - return - } - return - })()} +
) } +function HomeMainList(params: { + isInitialLoad: boolean + shouldShowAuthors: boolean + articlesListProps: Parameters[0] + authorsListProps: Parameters[0] +}): React.ReactElement { + if (params.isInitialLoad) { + return ( +
+

{t('common.loading')}

+
+ ) + } + if (params.shouldShowAuthors) { + return + } + return +} + +function buildArticlesListProps(params: { + articles: Article[] + allArticles: Article[] + loading: boolean + isInitialLoad: boolean + error: string | null + onUnlock: (article: Article) => void + unlockedArticles: Set +}): Parameters[0] { + return { + articles: params.articles, + allArticles: params.allArticles, + loading: params.loading && !params.isInitialLoad, + error: params.error, + onUnlock: params.onUnlock, + unlockedArticles: params.unlockedArticles, + } +} + +function buildAuthorsListProps(params: { + authors: Article[] + allAuthors: Article[] + loading: boolean + isInitialLoad: boolean + error: string | null +}): Parameters[0] { + return { + authors: params.authors, + allAuthors: params.allAuthors, + loading: params.loading && !params.isInitialLoad, + error: params.error, + } +} + export function HomeView(props: HomeViewProps): React.ReactElement { return ( <> diff --git a/components/ImageUploadField.tsx b/components/ImageUploadField.tsx index fe8cfbf..9912ef5 100644 --- a/components/ImageUploadField.tsx +++ b/components/ImageUploadField.tsx @@ -106,7 +106,7 @@ function useImageUpload(onChange: (url: string) => void): { const [pendingFile, setPendingFile] = useState(null) const handleFileSelect = async (event: React.ChangeEvent): Promise => { - const file = event.target.files?.[0] + const file = readFirstFile(event) if (!file) { return } @@ -117,9 +117,8 @@ function useImageUpload(onChange: (url: string) => void): { try { await processFileUpload(file, onChange, setError) } catch (uploadError) { - const uploadErr = uploadError instanceof Error ? uploadError : new Error(String(uploadError)) - // Check if unlock is required - if (uploadErr.message === 'UNLOCK_REQUIRED' || ('unlockRequired' in uploadErr && (uploadErr as { unlockRequired?: boolean }).unlockRequired)) { + const uploadErr = normalizeError(uploadError) + if (isUnlockRequiredError(uploadErr)) { setPendingFile(file) setShowUnlockModal(true) setError(null) // Don't show error, show unlock modal instead @@ -132,25 +131,62 @@ function useImageUpload(onChange: (url: string) => void): { } const handleUnlockSuccess = async (): Promise => { - setShowUnlockModal(false) - if (pendingFile) { - // Retry upload after unlock - setUploading(true) - setError(null) - try { - await processFileUpload(pendingFile, onChange, setError) - setPendingFile(null) - } catch (retryError) { - setError(retryError instanceof Error ? retryError.message : t('presentation.field.picture.error.uploadFailed')) - } finally { - setUploading(false) - } - } + await retryPendingUpload({ + pendingFile, + onChange, + setError, + setPendingFile, + setShowUnlockModal, + setUploading, + }) } return { uploading, error, handleFileSelect, showUnlockModal, setShowUnlockModal, handleUnlockSuccess } } +function readFirstFile(event: React.ChangeEvent): File | null { + return event.target.files?.[0] ?? null +} + +function normalizeError(error: unknown): Error { + return error instanceof Error ? error : new Error(String(error)) +} + +function isUnlockRequiredError(error: Error): boolean { + if (error.message === 'UNLOCK_REQUIRED') { + return true + } + if (typeof error === 'object' && error !== null && 'unlockRequired' in error) { + return (error as { unlockRequired?: boolean }).unlockRequired === true + } + return false +} + +async function retryPendingUpload(params: { + pendingFile: File | null + onChange: (url: string) => void + setError: (error: string | null) => void + setPendingFile: (file: File | null) => void + setShowUnlockModal: (show: boolean) => void + setUploading: (uploading: boolean) => void +}): Promise { + params.setShowUnlockModal(false) + if (!params.pendingFile) { + return + } + + params.setUploading(true) + params.setError(null) + try { + await processFileUpload(params.pendingFile, params.onChange, params.setError) + params.setPendingFile(null) + } catch (retryError) { + params.setError(retryError instanceof Error ? retryError.message : t('presentation.field.picture.error.uploadFailed')) + } finally { + params.setUploading(false) + } +} + export function ImageUploadField({ id, label, value, onChange, helpText }: ImageUploadFieldProps): React.ReactElement { const { uploading, error, handleFileSelect, showUnlockModal, setShowUnlockModal, handleUnlockSuccess } = useImageUpload(onChange) const displayLabel = label ?? t('presentation.field.picture') diff --git a/components/KeyManagementManager.tsx b/components/KeyManagementManager.tsx index 7fb4a2b..e2b6c6a 100644 --- a/components/KeyManagementManager.tsx +++ b/components/KeyManagementManager.tsx @@ -11,8 +11,6 @@ interface PublicKeys { } export function KeyManagementManager(): React.ReactElement { - console.warn('[KeyManagementManager] Component rendered') - const [publicKeys, setPublicKeys] = useState(null) const [accountExists, setAccountExists] = useState(false) const [loading, setLoading] = useState(true) @@ -71,13 +69,32 @@ export function KeyManagementManager(): React.ReactElement { } return null } catch { - // Not a valid URL, try to extract nsec from text - const nsecMatch = url.match(/nsec1[a-z0-9]+/i) - if (nsecMatch) { - return nsecMatch[0] + return extractKeyFromText(url) + } + } + + function extractKeyFromText(text: string): string { + const nsec = extractNsec(text) + if (nsec) { + return nsec + } + return text.trim() + } + + function extractNsec(text: string): string | null { + const nsecMatch = text.match(/nsec1[a-z0-9]+/i) + return nsecMatch?.[0] ?? null + } + + function isValidPrivateKeyFormat(key: string): boolean { + try { + const decoded = nip19.decode(key) + if (decoded.type !== 'nsec') { + return false } - // Assume it's already a key (hex or nsec) - return url.trim() + return typeof decoded.data === 'string' || decoded.data instanceof Uint8Array + } catch { + return /^[0-9a-f]{64}$/i.test(key) } } @@ -95,22 +112,9 @@ export function KeyManagementManager(): React.ReactElement { } // Validate key format - try { - // Try to decode as nsec - const decoded = nip19.decode(extractedKey) - if (decoded.type !== 'nsec') { - throw new Error('Invalid nsec format') - } - // decoded.data can be string (hex) or Uint8Array, both are valid - if (typeof decoded.data !== 'string' && !(decoded.data instanceof Uint8Array)) { - throw new Error('Invalid nsec format') - } - } catch { - // If decoding failed, assume it's hex, validate length (64 hex chars = 32 bytes) - if (!/^[0-9a-f]{64}$/i.test(extractedKey)) { - setError(t('settings.keyManagement.import.error.invalid')) - return - } + if (!isValidPrivateKeyFormat(extractedKey)) { + setError(t('settings.keyManagement.import.error.invalid')) + return } // If account exists, show warning @@ -216,211 +220,340 @@ export function KeyManagementManager(): React.ReactElement {

{t('settings.keyManagement.title')}

- {error && ( -
-

{error}

-
- )} + - {/* Public Keys Display */} - {publicKeys && ( -
-
-
-

{t('settings.keyManagement.publicKey.npub')}

- -
-

{publicKeys.npub}

-
- -
-
-

{t('settings.keyManagement.publicKey.hex')}

- -
-

{publicKeys.publicKey}

-
-
- )} + {/* Sync Progress Bar - Always show if connected, even if publicKeys not loaded yet */} - {(() => { - console.warn('[KeyManagementManager] Rendering SyncProgressBar') - return - })()} + - {!publicKeys && !accountExists && ( -
-

{t('settings.keyManagement.noAccount.title')}

-

- {t('settings.keyManagement.noAccount.description')} -

-
- )} + - {/* Import Form */} - {!showImportForm && ( - - )} + { + setShowImportForm(true) + setError(null) + }} + /> - {showImportForm && ( -
-
-

{t('settings.keyManagement.import.warning.title')}

-

- {accountExists && ( -

- )} -

- -
- -