From 57c0ef1982c0dd79466297a9557b76febd30e7e6 Mon Sep 17 00:00:00 2001 From: Anthony Ettinger Date: Tue, 3 Mar 2026 02:28:03 +0000 Subject: [PATCH 1/2] fix: address QA issues #28-35 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #30: Add favicon.ico (Next.js app router convention) #32: Change X-Frame-Options from DENY to SAMEORIGIN (match nginx, avoid duplicate) #34: Enable pinch-to-zoom (userScalable: true, maximumScale: 5) — WCAG 2.1 compliance #35: Add page-specific tags via layout.tsx for movies, tvshows, music, books, search, find-torrents, trending, dht #31: Add reason message on login page when redirected from live-tv #28, #29: Browse API works without auth — category pages render content for unauthenticated users (verified). Search pages require a query parameter by design. #33: Trending page depends on TheTVDB API — separate investigation needed. --- next.config.ts | 2 +- src/app/books/layout.tsx | 10 ++++++++++ src/app/books/page.tsx | 1 + src/app/dht/layout.tsx | 10 ++++++++++ src/app/dht/page.tsx | 1 + src/app/favicon.ico | Bin 0 -> 4286 bytes src/app/find-torrents/layout.tsx | 10 ++++++++++ src/app/find-torrents/page.tsx | 1 + src/app/layout.tsx | 4 ++-- src/app/live-tv/page.tsx | 2 +- src/app/login/page.tsx | 6 ++++++ src/app/movies/layout.tsx | 10 ++++++++++ src/app/movies/page.tsx | 1 + src/app/music/layout.tsx | 10 ++++++++++ src/app/music/page.tsx | 1 + src/app/search/layout.tsx | 10 ++++++++++ src/app/search/page.tsx | 1 + src/app/trending/layout.tsx | 10 ++++++++++ src/app/trending/page.tsx | 1 + src/app/tvshows/layout.tsx | 10 ++++++++++ src/app/tvshows/page.tsx | 1 + 21 files changed, 98 insertions(+), 4 deletions(-) create mode 100644 src/app/books/layout.tsx create mode 100644 src/app/dht/layout.tsx create mode 100644 src/app/favicon.ico create mode 100644 src/app/find-torrents/layout.tsx create mode 100644 src/app/movies/layout.tsx create mode 100644 src/app/music/layout.tsx create mode 100644 src/app/search/layout.tsx create mode 100644 src/app/trending/layout.tsx create mode 100644 src/app/tvshows/layout.tsx diff --git a/next.config.ts b/next.config.ts index 0b4bdde9..85cff73d 100644 --- a/next.config.ts +++ b/next.config.ts @@ -41,7 +41,7 @@ const nextConfig: NextConfig = { }, { key: 'X-Frame-Options', - value: 'DENY', + value: 'SAMEORIGIN', }, { key: 'X-XSS-Protection', diff --git a/src/app/books/layout.tsx b/src/app/books/layout.tsx new file mode 100644 index 00000000..176d6be2 --- /dev/null +++ b/src/app/books/layout.tsx @@ -0,0 +1,10 @@ +import type { Metadata } from 'next'; + +export const metadata: Metadata = { + title: 'Books — BitTorrented', + description: 'Browse Books on BitTorrented', +}; + +export default function Layout({ children }: { children: React.ReactNode }) { + return children; +} diff --git a/src/app/books/page.tsx b/src/app/books/page.tsx index 696acd02..e4bfa1e3 100644 --- a/src/app/books/page.tsx +++ b/src/app/books/page.tsx @@ -1,5 +1,6 @@ 'use client'; + /** * Books Page * diff --git a/src/app/dht/layout.tsx b/src/app/dht/layout.tsx new file mode 100644 index 00000000..81b1a758 --- /dev/null +++ b/src/app/dht/layout.tsx @@ -0,0 +1,10 @@ +import type { Metadata } from 'next'; + +export const metadata: Metadata = { + title: 'DHT Torrents — BitTorrented', + description: 'Browse DHT Torrents on BitTorrented', +}; + +export default function Layout({ children }: { children: React.ReactNode }) { + return children; +} diff --git a/src/app/dht/page.tsx b/src/app/dht/page.tsx index 50efbd50..75727c69 100644 --- a/src/app/dht/page.tsx +++ b/src/app/dht/page.tsx @@ -1,5 +1,6 @@ 'use client'; + /** * DHT Index Page * diff --git a/src/app/favicon.ico b/src/app/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..1c7b4a52e33c300658c71bb37985aab9ee462268 GIT binary patch literal 4286 zcmeI0*>6=<6vmJL0Z%^jNTM>yppq~MMhGIPL5&j?1qB%k2#AG>OiF+h85F6gEntCA z6l!fD$fOJsgfdm8mQtVvWU@?B__;ndJH7YZQ(8(jKCsE|IcJ}}*Z8e(ol6irVE-C6 z4D>hnu0aqy7z9Bho3<3Rx2g48!`_7l_ebp`aMQM4yaWf1!Nvmkaf_Q9c0pkYT&RGX z+EmZudL`^W0%=R(wF%H+Ftq9m&EIyjrQNq51h0&TX<4xI5d3|;evBwCgNYeVXX7_P zvuST=_E!AVnt$JJAfzsU!^i7xgW|Y;C%ll7s8hQ94BMe+Z%g+NVD)ymQHPu;w@(gn zx+SxT>1<-YKlZMRMaQAg*8EiL<+t92ZPA+cWN*AXT>P>dB;U)FKV3({>}=S!9}bqf z*zP~-Vl;CZ^c-tgxNT>w9kRQfVPox%+U4TJ8F=}lc)xp&gS|x#<MUTRN;-`C0w!g` z;x(|xuvvN<zRiQDhgI3_?T{bOe+a_TorZFrX1>T~e^%G5wXnSqo=S#?d%+_~Ra1Kv z<I(frj}pWFL3k-O+J-3$4GTA_x1i_*bQuw^Up1Du4Zhz5uTOyFS@E{XK94u$NSIZv z>OR&F?NLq(Eoaq@jQqsfQtZZj2`B%Ag2V2->R<9=W2AfGu=8-(R}5d}B<_){c|SyJ z7heoYtIB(<>{jo<j3sd90$jcdeWyn6^EP~)3!C>CN7=bpIkHb@uUt#Cq5K>?J32mQ z{;Ygeo%Ndrhm2E%L194aP0erZ)4lvtzH+U0bGN#CFGE-5qqF6=^i-mLVMiPq6<5i9 zV<PM_UR4YfUp;rSe75NikN0yJd~OWPT><51VfgIC7|GT)@3}mNEf8*>Fj?N8Si5DD zaz4lEU$tLp7*-!DItjnqxg7^UT%a0MZ%SPNE&9aA-pgxk{Iz9oSiAXV*ph8g`gPXF zx9j`P|7(Vq%1RJlDBt71f&SB7jYh@L>lc4+-Iu6ey`rP>ZFOhm&!d)G<znG#=l9gb zjt9kws!fjxuPc0&Y;lG17anIXIu4cm@Hz47tmTgP#6LqmgOYOSHVS&&?lH$L4nvI_ zs<U4*g}?5GabZ0?)T31AjSbgf{gS!lXHZ{I9f)I(7*~x=hlhGO++-Se94&(v$GI4V zW$0P5#b5fKh#EPYEyfYJ@pUT>8u_{NQT_2Jt1<1DU+Vd)kMtb(9Hn1t%hp9{c5A24 zTklDYwqJTw_rjZaYVb_gds-XE2(z*=dxNt{`3mb-UR9quf2Xn3?2D=$<v{hhXpPIC z_)uIcJ>neoU)7#)5{V((lVSa_rcrSjhDC6P^vDL)@27@);ZMC}<*$(c8yq_Yn+gmY zW>Xl0ip_u-aK54@?e4vJ<<*Z9`ODiN`8_O_)3ace=~qru<~xpx((PkYJMC_l_)Oor zvOS8EggL$QHr?81YkKxmYx)*Y->l0}jP;%4zwx}>ny%6-3?*A0Rj2nd)Nd8zq)C>0 z!@6W^%74XL)5k*ft=|QTn`)ur%DrfgKdaw1`nLIG5%iwoxUW;P>w(W04<=29$(imu r^W0zYXY2Dx--wEddTg=bRI|+PFWK6^@!P9DZT}z23jcq=fBpOqGwNEO literal 0 HcmV?d00001 diff --git a/src/app/find-torrents/layout.tsx b/src/app/find-torrents/layout.tsx new file mode 100644 index 00000000..79f28117 --- /dev/null +++ b/src/app/find-torrents/layout.tsx @@ -0,0 +1,10 @@ +import type { Metadata } from 'next'; + +export const metadata: Metadata = { + title: 'Find Torrents — BitTorrented', + description: 'Browse Find Torrents on BitTorrented', +}; + +export default function Layout({ children }: { children: React.ReactNode }) { + return children; +} diff --git a/src/app/find-torrents/page.tsx b/src/app/find-torrents/page.tsx index 7c4f5a2e..0eca2585 100644 --- a/src/app/find-torrents/page.tsx +++ b/src/app/find-torrents/page.tsx @@ -1,5 +1,6 @@ 'use client'; + /** * Find Torrents Page * diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 8cea83a1..77fd486f 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -75,8 +75,8 @@ export const viewport: Viewport = { themeColor: '#8b5cf6', width: 'device-width', initialScale: 1, - maximumScale: 1, - userScalable: false, + maximumScale: 5, + userScalable: true, viewportFit: 'cover', }; diff --git a/src/app/live-tv/page.tsx b/src/app/live-tv/page.tsx index 75c2f9d1..17c66554 100644 --- a/src/app/live-tv/page.tsx +++ b/src/app/live-tv/page.tsx @@ -19,7 +19,7 @@ export default async function LiveTvPage(): Promise<React.ReactElement> { const user = await getCurrentUser(); if (!user) { - redirect('/login?redirect=/live-tv'); + redirect('/login?redirect=/live-tv&reason=live-tv'); } return <LiveTvContent />; diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index 5be9ac90..edd0da17 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -53,6 +53,12 @@ export default function LoginPage(): React.ReactElement { <MainLayout> <div className="flex min-h-[60vh] items-center justify-center"> <div className="w-full max-w-md"> + {/* Reason message for redirects */} + {typeof window !== 'undefined' && new URLSearchParams(window.location.search).get('reason') === 'live-tv' && ( + <div className="mb-4 rounded-lg border border-accent-primary/30 bg-accent-primary/10 px-4 py-3 text-center text-sm text-text-primary"> + Sign in to access Live TV — add your IPTV playlists and stream live channels. + </div> + )} {/* Logo */} <div className="mb-8 text-center"> <Link href="/" className="inline-block mb-4"> diff --git a/src/app/movies/layout.tsx b/src/app/movies/layout.tsx new file mode 100644 index 00000000..a2d3372a --- /dev/null +++ b/src/app/movies/layout.tsx @@ -0,0 +1,10 @@ +import type { Metadata } from 'next'; + +export const metadata: Metadata = { + title: 'Movies — BitTorrented', + description: 'Browse Movies on BitTorrented', +}; + +export default function Layout({ children }: { children: React.ReactNode }) { + return children; +} diff --git a/src/app/movies/page.tsx b/src/app/movies/page.tsx index 5e242659..e075377c 100644 --- a/src/app/movies/page.tsx +++ b/src/app/movies/page.tsx @@ -1,5 +1,6 @@ 'use client'; + /** * Movies Page * diff --git a/src/app/music/layout.tsx b/src/app/music/layout.tsx new file mode 100644 index 00000000..31ffdd30 --- /dev/null +++ b/src/app/music/layout.tsx @@ -0,0 +1,10 @@ +import type { Metadata } from 'next'; + +export const metadata: Metadata = { + title: 'Music — BitTorrented', + description: 'Browse Music on BitTorrented', +}; + +export default function Layout({ children }: { children: React.ReactNode }) { + return children; +} diff --git a/src/app/music/page.tsx b/src/app/music/page.tsx index 77bf0c40..4d7aac36 100644 --- a/src/app/music/page.tsx +++ b/src/app/music/page.tsx @@ -1,5 +1,6 @@ 'use client'; + /** * Music Page * diff --git a/src/app/search/layout.tsx b/src/app/search/layout.tsx new file mode 100644 index 00000000..64decf56 --- /dev/null +++ b/src/app/search/layout.tsx @@ -0,0 +1,10 @@ +import type { Metadata } from 'next'; + +export const metadata: Metadata = { + title: 'Search — BitTorrented', + description: 'Browse Search on BitTorrented', +}; + +export default function Layout({ children }: { children: React.ReactNode }) { + return children; +} diff --git a/src/app/search/page.tsx b/src/app/search/page.tsx index c9471ade..44084678 100644 --- a/src/app/search/page.tsx +++ b/src/app/search/page.tsx @@ -1,5 +1,6 @@ 'use client'; + /** * Search Page * diff --git a/src/app/trending/layout.tsx b/src/app/trending/layout.tsx new file mode 100644 index 00000000..8044f8b1 --- /dev/null +++ b/src/app/trending/layout.tsx @@ -0,0 +1,10 @@ +import type { Metadata } from 'next'; + +export const metadata: Metadata = { + title: 'Trending — BitTorrented', + description: 'Browse Trending on BitTorrented', +}; + +export default function Layout({ children }: { children: React.ReactNode }) { + return children; +} diff --git a/src/app/trending/page.tsx b/src/app/trending/page.tsx index f75a7876..bf8bbb04 100644 --- a/src/app/trending/page.tsx +++ b/src/app/trending/page.tsx @@ -1,5 +1,6 @@ 'use client'; + /** * Trending Page * diff --git a/src/app/tvshows/layout.tsx b/src/app/tvshows/layout.tsx new file mode 100644 index 00000000..5df4aae8 --- /dev/null +++ b/src/app/tvshows/layout.tsx @@ -0,0 +1,10 @@ +import type { Metadata } from 'next'; + +export const metadata: Metadata = { + title: 'TV Shows — BitTorrented', + description: 'Browse TV Shows on BitTorrented', +}; + +export default function Layout({ children }: { children: React.ReactNode }) { + return children; +} diff --git a/src/app/tvshows/page.tsx b/src/app/tvshows/page.tsx index b727a832..f0b90553 100644 --- a/src/app/tvshows/page.tsx +++ b/src/app/tvshows/page.tsx @@ -1,5 +1,6 @@ 'use client'; + /** * TV Shows Page * From deb0b2a61648f53839deba2acbb0342f7f37ad0a Mon Sep 17 00:00:00 2001 From: Anthony Ettinger <anthony@chovy.com> Date: Tue, 3 Mar 2026 02:44:38 +0000 Subject: [PATCH 2/2] fix: address Copilot review - title duplication, hydration, X-Frame-Options MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1-2, 4-7, 9-10: Remove '— BitTorrented' from layout titles (root template adds it) 3: Fix login reason banner hydration mismatch - use useEffect + state 8: Remove app-level X-Frame-Options entirely (nginx is single source of truth) --- next.config.ts | 4 ---- src/app/books/layout.tsx | 2 +- src/app/dht/layout.tsx | 2 +- src/app/find-torrents/layout.tsx | 2 +- src/app/login/page.tsx | 10 ++++++++-- src/app/movies/layout.tsx | 2 +- src/app/music/layout.tsx | 2 +- src/app/search/layout.tsx | 2 +- src/app/trending/layout.tsx | 2 +- src/app/tvshows/layout.tsx | 2 +- 10 files changed, 16 insertions(+), 14 deletions(-) diff --git a/next.config.ts b/next.config.ts index 85cff73d..c414beb3 100644 --- a/next.config.ts +++ b/next.config.ts @@ -39,10 +39,6 @@ const nextConfig: NextConfig = { key: 'X-Content-Type-Options', value: 'nosniff', }, - { - key: 'X-Frame-Options', - value: 'SAMEORIGIN', - }, { key: 'X-XSS-Protection', value: '1; mode=block', diff --git a/src/app/books/layout.tsx b/src/app/books/layout.tsx index 176d6be2..da0ae9f6 100644 --- a/src/app/books/layout.tsx +++ b/src/app/books/layout.tsx @@ -1,7 +1,7 @@ import type { Metadata } from 'next'; export const metadata: Metadata = { - title: 'Books — BitTorrented', + title: 'Books', description: 'Browse Books on BitTorrented', }; diff --git a/src/app/dht/layout.tsx b/src/app/dht/layout.tsx index 81b1a758..140cfd6d 100644 --- a/src/app/dht/layout.tsx +++ b/src/app/dht/layout.tsx @@ -1,7 +1,7 @@ import type { Metadata } from 'next'; export const metadata: Metadata = { - title: 'DHT Torrents — BitTorrented', + title: 'DHT Torrents', description: 'Browse DHT Torrents on BitTorrented', }; diff --git a/src/app/find-torrents/layout.tsx b/src/app/find-torrents/layout.tsx index 79f28117..d4943352 100644 --- a/src/app/find-torrents/layout.tsx +++ b/src/app/find-torrents/layout.tsx @@ -1,7 +1,7 @@ import type { Metadata } from 'next'; export const metadata: Metadata = { - title: 'Find Torrents — BitTorrented', + title: 'Find Torrents', description: 'Browse Find Torrents on BitTorrented', }; diff --git a/src/app/login/page.tsx b/src/app/login/page.tsx index edd0da17..3dff8fcd 100644 --- a/src/app/login/page.tsx +++ b/src/app/login/page.tsx @@ -6,7 +6,7 @@ * User authentication with email/password. */ -import { useState } from 'react'; +import { useState, useEffect } from 'react'; import Link from 'next/link'; import Image from 'next/image'; import { MainLayout } from '@/components/layout'; @@ -17,6 +17,12 @@ export default function LoginPage(): React.ReactElement { const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [isLoading, setIsLoading] = useState(false); + const [reason, setReason] = useState<string | null>(null); + + useEffect(() => { + const params = new URLSearchParams(window.location.search); + setReason(params.get('reason')); + }, []); const [error, setError] = useState<string | null>(null); const handleSubmit = async (e: React.FormEvent): Promise<void> => { @@ -54,7 +60,7 @@ export default function LoginPage(): React.ReactElement { <div className="flex min-h-[60vh] items-center justify-center"> <div className="w-full max-w-md"> {/* Reason message for redirects */} - {typeof window !== 'undefined' && new URLSearchParams(window.location.search).get('reason') === 'live-tv' && ( + {reason === 'live-tv' && ( <div className="mb-4 rounded-lg border border-accent-primary/30 bg-accent-primary/10 px-4 py-3 text-center text-sm text-text-primary"> Sign in to access Live TV — add your IPTV playlists and stream live channels. </div> diff --git a/src/app/movies/layout.tsx b/src/app/movies/layout.tsx index a2d3372a..8c412580 100644 --- a/src/app/movies/layout.tsx +++ b/src/app/movies/layout.tsx @@ -1,7 +1,7 @@ import type { Metadata } from 'next'; export const metadata: Metadata = { - title: 'Movies — BitTorrented', + title: 'Movies', description: 'Browse Movies on BitTorrented', }; diff --git a/src/app/music/layout.tsx b/src/app/music/layout.tsx index 31ffdd30..1b223a73 100644 --- a/src/app/music/layout.tsx +++ b/src/app/music/layout.tsx @@ -1,7 +1,7 @@ import type { Metadata } from 'next'; export const metadata: Metadata = { - title: 'Music — BitTorrented', + title: 'Music', description: 'Browse Music on BitTorrented', }; diff --git a/src/app/search/layout.tsx b/src/app/search/layout.tsx index 64decf56..e378cfa4 100644 --- a/src/app/search/layout.tsx +++ b/src/app/search/layout.tsx @@ -1,7 +1,7 @@ import type { Metadata } from 'next'; export const metadata: Metadata = { - title: 'Search — BitTorrented', + title: 'Search', description: 'Browse Search on BitTorrented', }; diff --git a/src/app/trending/layout.tsx b/src/app/trending/layout.tsx index 8044f8b1..faf4dd58 100644 --- a/src/app/trending/layout.tsx +++ b/src/app/trending/layout.tsx @@ -1,7 +1,7 @@ import type { Metadata } from 'next'; export const metadata: Metadata = { - title: 'Trending — BitTorrented', + title: 'Trending', description: 'Browse Trending on BitTorrented', }; diff --git a/src/app/tvshows/layout.tsx b/src/app/tvshows/layout.tsx index 5df4aae8..6647321d 100644 --- a/src/app/tvshows/layout.tsx +++ b/src/app/tvshows/layout.tsx @@ -1,7 +1,7 @@ import type { Metadata } from 'next'; export const metadata: Metadata = { - title: 'TV Shows — BitTorrented', + title: 'TV Shows', description: 'Browse TV Shows on BitTorrented', };