summaryrefslogtreecommitdiff
path: root/apps/web
diff options
context:
space:
mode:
authorFuwn <[email protected]>2026-02-10 00:12:25 -0800
committerFuwn <[email protected]>2026-02-10 00:12:25 -0800
commit32219ec9656e7f46e516c7c41c133133d008e9a4 (patch)
treee335fa4b2e32f562b0861cd02a1abe618e8cb6b9 /apps/web
parentfix: P2 security hardening and tier limit parity (diff)
downloadasa.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')
-rw-r--r--apps/web/app/(auth)/reset-password/page.tsx2
-rw-r--r--apps/web/app/(auth)/sign-up/page.tsx2
-rw-r--r--apps/web/app/reader/_components/add-feed-dialog.tsx8
-rw-r--r--apps/web/app/reader/_components/entry-detail-panel.tsx1
-rw-r--r--apps/web/app/reader/_components/entry-list.tsx2
-rw-r--r--apps/web/app/reader/_components/mfa-challenge.tsx1
-rw-r--r--apps/web/app/reader/_components/reader-layout-shell.tsx2
-rw-r--r--apps/web/app/reader/_components/reader-shell.tsx2
-rw-r--r--apps/web/app/reader/highlights/_components/highlights-content.tsx3
-rw-r--r--apps/web/app/reader/page.tsx7
-rw-r--r--apps/web/app/reader/saved/page.tsx9
-rw-r--r--apps/web/app/reader/settings/_components/account-settings.tsx1
-rw-r--r--apps/web/app/reader/settings/_components/billing-settings.tsx1
-rw-r--r--apps/web/app/reader/settings/_components/folders-settings.tsx10
-rw-r--r--apps/web/app/reader/settings/_components/import-export-settings.tsx4
-rw-r--r--apps/web/app/reader/shares/_components/shares-content.tsx3
-rw-r--r--apps/web/eslint-rules/no-comments.mjs1
-rw-r--r--apps/web/eslint.config.mjs1
-rw-r--r--apps/web/lib/hooks/use-realtime-entries.ts6
-rw-r--r--apps/web/lib/supabase/server.ts1
-rw-r--r--apps/web/lib/validate-webhook-url.ts2
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) {