diff options
| author | Fuwn <[email protected]> | 2026-02-10 00:12:25 -0800 |
|---|---|---|
| committer | Fuwn <[email protected]> | 2026-02-10 00:12:25 -0800 |
| commit | 32219ec9656e7f46e516c7c41c133133d008e9a4 (patch) | |
| tree | e335fa4b2e32f562b0861cd02a1abe618e8cb6b9 /apps/web | |
| parent | fix: P2 security hardening and tier limit parity (diff) | |
| download | asa.news-32219ec9656e7f46e516c7c41c133133d008e9a4.tar.xz asa.news-32219ec9656e7f46e516c7c41c133133d008e9a4.zip | |
fix: reduce lint warnings from 34 to 0
Disable no-img-element (RSS reader needs <img> for arbitrary external
URLs). Remove unused variables/imports and redundant getUser() calls
guarded by middleware. Fix exhaustive-deps by adding stable deps,
wrapping handlers in useCallback, and suppressing intentional omissions.
Fix ref cleanup in use-realtime-entries. Allow triple-slash TS reference
directives in no-comments rule.
Diffstat (limited to 'apps/web')
21 files changed, 26 insertions, 43 deletions
diff --git a/apps/web/app/(auth)/reset-password/page.tsx b/apps/web/app/(auth)/reset-password/page.tsx index cb7432a..e33bfe6 100644 --- a/apps/web/app/(auth)/reset-password/page.tsx +++ b/apps/web/app/(auth)/reset-password/page.tsx @@ -2,7 +2,6 @@ import { useState } from "react" import Link from "next/link" -import { useRouter } from "next/navigation" import { createSupabaseBrowserClient } from "@/lib/supabase/client" export default function ResetPasswordPage() { @@ -11,7 +10,6 @@ export default function ResetPasswordPage() { const [errorMessage, setErrorMessage] = useState<string | null>(null) const [isSubmitting, setIsSubmitting] = useState(false) const [isPasswordUpdated, setIsPasswordUpdated] = useState(false) - const router = useRouter() async function handlePasswordUpdate(event: React.FormEvent) { event.preventDefault() diff --git a/apps/web/app/(auth)/sign-up/page.tsx b/apps/web/app/(auth)/sign-up/page.tsx index 9b78d90..59a0933 100644 --- a/apps/web/app/(auth)/sign-up/page.tsx +++ b/apps/web/app/(auth)/sign-up/page.tsx @@ -2,7 +2,6 @@ import { useState } from "react" import Link from "next/link" -import { useRouter } from "next/navigation" import { createSupabaseBrowserClient } from "@/lib/supabase/client" export default function SignUpPage() { @@ -11,7 +10,6 @@ export default function SignUpPage() { const [errorMessage, setErrorMessage] = useState<string | null>(null) const [isSubmitting, setIsSubmitting] = useState(false) const [isComplete, setIsComplete] = useState(false) - const router = useRouter() async function handleSignUp(event: React.FormEvent) { event.preventDefault() diff --git a/apps/web/app/reader/_components/add-feed-dialog.tsx b/apps/web/app/reader/_components/add-feed-dialog.tsx index 4ffbd39..ba02197 100644 --- a/apps/web/app/reader/_components/add-feed-dialog.tsx +++ b/apps/web/app/reader/_components/add-feed-dialog.tsx @@ -1,6 +1,6 @@ "use client" -import { useState, useEffect, useRef } from "react" +import { useState, useEffect, useCallback, useRef } from "react" import { useSubscribeToFeed } from "@/lib/queries/use-subscribe-to-feed" import { useSubscriptions } from "@/lib/queries/use-subscriptions" import { useUserProfile } from "@/lib/queries/use-user-profile" @@ -23,14 +23,14 @@ export function AddFeedDialog() { const supportsAuthenticatedFeeds = userProfile?.tier === "pro" || userProfile?.tier === "developer" - function handleClose() { + const handleClose = useCallback(() => { setFeedUrl("") setCustomTitle("") setSelectedFolderIdentifier(null) setAuthenticationType("none") setFeedCredential("") setOpen(false) - } + }, [setOpen]) async function handleSubmit(event: React.FormEvent) { event.preventDefault() @@ -68,7 +68,7 @@ export function AddFeedDialog() { document.addEventListener("keydown", handleKeyDown, true) return () => document.removeEventListener("keydown", handleKeyDown, true) - }, [isOpen]) + }, [isOpen, handleClose]) useEffect(() => { if (isOpen) { diff --git a/apps/web/app/reader/_components/entry-detail-panel.tsx b/apps/web/app/reader/_components/entry-detail-panel.tsx index 9848d3f..7901100 100644 --- a/apps/web/app/reader/_components/entry-detail-panel.tsx +++ b/apps/web/app/reader/_components/entry-detail-panel.tsx @@ -150,6 +150,7 @@ export function EntryDetailPanel({ }, 1500) return () => clearTimeout(autoReadTimeout) + // eslint-disable-next-line react-hooks/exhaustive-deps }, [entryIdentifier, currentEntry?.isRead]) const contentHtml = diff --git a/apps/web/app/reader/_components/entry-list.tsx b/apps/web/app/reader/_components/entry-list.tsx index 65e35f7..7513d18 100644 --- a/apps/web/app/reader/_components/entry-list.tsx +++ b/apps/web/app/reader/_components/entry-list.tsx @@ -80,6 +80,7 @@ export function EntryList({ const prefetchIdentifiers = useMemo( () => allEntries.map((entry) => entry.entryIdentifier), + // eslint-disable-next-line react-hooks/exhaustive-deps [firstEntryIdentifier, lastEntryIdentifier, allEntries.length] ) usePrefetchEntryDetails(prefetchIdentifiers) @@ -141,6 +142,7 @@ export function EntryList({ if (focusedIndex !== -1) { virtualizer.scrollToIndex(focusedIndex, { align: "auto" }) } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [focusedEntryIdentifier]) if (isLoading) { diff --git a/apps/web/app/reader/_components/mfa-challenge.tsx b/apps/web/app/reader/_components/mfa-challenge.tsx index 347d8b4..b7f86a9 100644 --- a/apps/web/app/reader/_components/mfa-challenge.tsx +++ b/apps/web/app/reader/_components/mfa-challenge.tsx @@ -26,6 +26,7 @@ export function MfaChallenge({ onVerified }: { onVerified: () => void }) { } loadFactor() + // eslint-disable-next-line react-hooks/exhaustive-deps }, []) async function handleVerify(event?: React.FormEvent) { diff --git a/apps/web/app/reader/_components/reader-layout-shell.tsx b/apps/web/app/reader/_components/reader-layout-shell.tsx index 391e6a4..7ec1002 100644 --- a/apps/web/app/reader/_components/reader-layout-shell.tsx +++ b/apps/web/app/reader/_components/reader-layout-shell.tsx @@ -152,7 +152,7 @@ export function ReaderLayoutShell({ maximumItemWidth = Math.max(maximumItemWidth, 192) return `${Math.ceil(maximumItemWidth)}px` - }, [subscriptionsData, customFeedsData, userProfile, displayDensity, showFeedFavicons]) + }, [subscriptionsData, customFeedsData, userProfile, showFeedFavicons]) const sidebarLayout = useDefaultLayout({ id: "asa-sidebar-layout", diff --git a/apps/web/app/reader/_components/reader-shell.tsx b/apps/web/app/reader/_components/reader-shell.tsx index 65a4900..1d50dc2 100644 --- a/apps/web/app/reader/_components/reader-shell.tsx +++ b/apps/web/app/reader/_components/reader-shell.tsx @@ -18,7 +18,6 @@ import { useRealtimeEntries } from "@/lib/hooks/use-realtime-entries" import { useCustomFeeds } from "@/lib/queries/use-custom-feeds" interface ReaderShellProperties { - userEmailAddress: string | null feedFilter: "all" | "saved" folderIdentifier?: string | null feedIdentifier?: string | null @@ -26,7 +25,6 @@ interface ReaderShellProperties { } export function ReaderShell({ - userEmailAddress, feedFilter, folderIdentifier, feedIdentifier, diff --git a/apps/web/app/reader/highlights/_components/highlights-content.tsx b/apps/web/app/reader/highlights/_components/highlights-content.tsx index 4034210..0366935 100644 --- a/apps/web/app/reader/highlights/_components/highlights-content.tsx +++ b/apps/web/app/reader/highlights/_components/highlights-content.tsx @@ -355,10 +355,11 @@ export function HighlightsContent() { useEffect(() => { setSelectedEntryIdentifier(null) setNavigableEntryIdentifiers([]) - }, []) + }, [setSelectedEntryIdentifier, setNavigableEntryIdentifiers]) useEffect(() => { setNavigableEntryIdentifiers(entryIdentifiers) + // eslint-disable-next-line react-hooks/exhaustive-deps }, [entryIdentifiers.length, setNavigableEntryIdentifiers]) if (isLoading) { diff --git a/apps/web/app/reader/page.tsx b/apps/web/app/reader/page.tsx index 4773fd8..068de7d 100644 --- a/apps/web/app/reader/page.tsx +++ b/apps/web/app/reader/page.tsx @@ -1,4 +1,3 @@ -import { createSupabaseServerClient } from "@/lib/supabase/server" import { ReaderShell } from "./_components/reader-shell" export default async function ReaderPage({ @@ -6,16 +5,10 @@ export default async function ReaderPage({ }: { searchParams: Promise<{ folder?: string; feed?: string; custom_feed?: string }> }) { - const supabaseClient = await createSupabaseServerClient() - const { - data: { user }, - } = await supabaseClient.auth.getUser() - const resolvedSearchParams = await searchParams return ( <ReaderShell - userEmailAddress={user?.email ?? null} feedFilter="all" folderIdentifier={resolvedSearchParams.folder} feedIdentifier={resolvedSearchParams.feed} diff --git a/apps/web/app/reader/saved/page.tsx b/apps/web/app/reader/saved/page.tsx index 0ad5ba3..dd362da 100644 --- a/apps/web/app/reader/saved/page.tsx +++ b/apps/web/app/reader/saved/page.tsx @@ -1,15 +1,8 @@ -import { createSupabaseServerClient } from "@/lib/supabase/server" import { ReaderShell } from "../_components/reader-shell" -export default async function SavedPage() { - const supabaseClient = await createSupabaseServerClient() - const { - data: { user }, - } = await supabaseClient.auth.getUser() - +export default function SavedPage() { return ( <ReaderShell - userEmailAddress={user?.email ?? null} feedFilter="saved" /> ) diff --git a/apps/web/app/reader/settings/_components/account-settings.tsx b/apps/web/app/reader/settings/_components/account-settings.tsx index 84b8414..9679e3b 100644 --- a/apps/web/app/reader/settings/_components/account-settings.tsx +++ b/apps/web/app/reader/settings/_components/account-settings.tsx @@ -189,6 +189,7 @@ export function AccountSettings() { useEffect(() => { loadFactors() + // eslint-disable-next-line react-hooks/exhaustive-deps }, []) async function handleBeginEnrollment() { diff --git a/apps/web/app/reader/settings/_components/billing-settings.tsx b/apps/web/app/reader/settings/_components/billing-settings.tsx index 79269fc..1e38684 100644 --- a/apps/web/app/reader/settings/_components/billing-settings.tsx +++ b/apps/web/app/reader/settings/_components/billing-settings.tsx @@ -177,6 +177,7 @@ export function BillingSettings() { url.searchParams.delete("billing") window.history.replaceState({}, "", url.pathname) } + // eslint-disable-next-line react-hooks/exhaustive-deps }, [searchParameters, queryClient]) if (isLoading) { diff --git a/apps/web/app/reader/settings/_components/folders-settings.tsx b/apps/web/app/reader/settings/_components/folders-settings.tsx index 2c3d5f2..3bc0b7b 100644 --- a/apps/web/app/reader/settings/_components/folders-settings.tsx +++ b/apps/web/app/reader/settings/_components/folders-settings.tsx @@ -114,14 +114,7 @@ export function FoldersSettings() { ) } -function FolderRow({ - folderIdentifier, - name, - iconUrl, - feedCount, - onSave, - onDelete, -}: { +function FolderRow(properties: { folderIdentifier: string name: string iconUrl: string | null @@ -129,6 +122,7 @@ function FolderRow({ onSave: (name: string, iconUrl: string | null) => void onDelete: () => void }) { + const { name, iconUrl, feedCount, onSave, onDelete } = properties const [isEditing, setIsEditing] = useState(false) const [editedName, setEditedName] = useState(name) const [editedIconUrl, setEditedIconUrl] = useState(iconUrl ?? "") diff --git a/apps/web/app/reader/settings/_components/import-export-settings.tsx b/apps/web/app/reader/settings/_components/import-export-settings.tsx index 27f8933..f0ba3f3 100644 --- a/apps/web/app/reader/settings/_components/import-export-settings.tsx +++ b/apps/web/app/reader/settings/_components/import-export-settings.tsx @@ -61,7 +61,7 @@ export function ImportExportSettings() { for (const group of parsedGroups) { for (const feed of group.feeds) { try { - await new Promise<void>((resolve, reject) => { + await new Promise<void>((resolve) => { subscribeToFeed.mutate( { feedUrl: feed.url, @@ -72,7 +72,7 @@ export function ImportExportSettings() { importedCount++ resolve() }, - onError: (error) => { + onError: () => { failedCount++ resolve() }, diff --git a/apps/web/app/reader/shares/_components/shares-content.tsx b/apps/web/app/reader/shares/_components/shares-content.tsx index b05d562..db50bc7 100644 --- a/apps/web/app/reader/shares/_components/shares-content.tsx +++ b/apps/web/app/reader/shares/_components/shares-content.tsx @@ -411,12 +411,13 @@ export function SharesContent() { useEffect(() => { setSelectedEntryIdentifier(null) setNavigableEntryIdentifiers([]) - }, []) + }, [setSelectedEntryIdentifier, setNavigableEntryIdentifiers]) useEffect(() => { setNavigableEntryIdentifiers( sharesList.map((share) => share.entryIdentifier) ) + // eslint-disable-next-line react-hooks/exhaustive-deps }, [sharesList.length, setNavigableEntryIdentifiers]) if (isLoading) { diff --git a/apps/web/eslint-rules/no-comments.mjs b/apps/web/eslint-rules/no-comments.mjs index 7efafae..1a30c8c 100644 --- a/apps/web/eslint-rules/no-comments.mjs +++ b/apps/web/eslint-rules/no-comments.mjs @@ -13,6 +13,7 @@ const DIRECTIVE_PATTERNS = [ /^\s*@typedef\s/, /^\s*prettier-ignore/, /^\s*webpackChunkName/, + /^\s*\/ <reference /, ] function isDirectiveComment(value) { diff --git a/apps/web/eslint.config.mjs b/apps/web/eslint.config.mjs index ceb3b01..18a0e02 100644 --- a/apps/web/eslint.config.mjs +++ b/apps/web/eslint.config.mjs @@ -22,6 +22,7 @@ const eslintConfig = defineConfig([ rules: { "asa-lowercase/lowercase-strings": "warn", "asa-no-comments/no-comments": "warn", + "@next/next/no-img-element": "off", }, }, ]); diff --git a/apps/web/lib/hooks/use-realtime-entries.ts b/apps/web/lib/hooks/use-realtime-entries.ts index 0eaba77..3fec9f6 100644 --- a/apps/web/lib/hooks/use-realtime-entries.ts +++ b/apps/web/lib/hooks/use-realtime-entries.ts @@ -39,7 +39,9 @@ export function useRealtimeEntries() { }) } - const channel = supabaseClientReference.current + const supabaseClient = supabaseClientReference.current + + const channel = supabaseClient .channel("entries-realtime") .on( "postgres_changes", @@ -68,7 +70,7 @@ export function useRealtimeEntries() { clearTimeout(debounceTimerReference.current) } - supabaseClientReference.current.removeChannel(channel) + supabaseClient.removeChannel(channel) } }, [queryClient]) } diff --git a/apps/web/lib/supabase/server.ts b/apps/web/lib/supabase/server.ts index f781393..507dd1a 100644 --- a/apps/web/lib/supabase/server.ts +++ b/apps/web/lib/supabase/server.ts @@ -18,7 +18,6 @@ export async function createSupabaseServerClient() { cookieStore.set(name, value, options) ) } catch { - // no-op } }, }, diff --git a/apps/web/lib/validate-webhook-url.ts b/apps/web/lib/validate-webhook-url.ts index 60a41aa..75ec76e 100644 --- a/apps/web/lib/validate-webhook-url.ts +++ b/apps/web/lib/validate-webhook-url.ts @@ -94,14 +94,12 @@ export async function validateWebhookUrl(rawUrl: string): Promise<{ const ipv4Addresses = await resolve4(hostname) resolvedAddresses = resolvedAddresses.concat(ipv4Addresses) } catch { - // no-op } try { const ipv6Addresses = await resolve6(hostname) resolvedAddresses = resolvedAddresses.concat(ipv6Addresses) } catch { - // no-op } if (resolvedAddresses.length === 0) { |