From d714f3382b413f71ae1ab50e7a1cd745b3eeed85 Mon Sep 17 00:00:00 2001 From: "John R. D'Orazio" Date: Sat, 18 Apr 2026 01:34:35 +0200 Subject: [PATCH 01/14] feat: upgrade to Next.js 16 and next-intl 4.9.1 - Rename middleware.ts to proxy.ts with named export (Next.js 16 convention) - Add second argument to revalidateTag calls (new required signature) - Create ESLint flat config (eslint.config.mjs) and update lint script - Fix all lint errors: replace refs-during-render with state, derive values instead of setState-in-effect, remove `as any` casts, use next/image for logo SVG Co-Authored-By: Claude Opus 4.6 (1M context) --- app/[lang]/layout.tsx | 2 +- app/api/revalidate/route.ts | 2 +- app/api/stats/route.ts | 2 +- components/blog/ShareButtons.tsx | 16 +- components/projects/RepoLanguages.tsx | 9 +- components/sections/FishBackground.tsx | 10 +- components/sections/HeroBanner.tsx | 5 +- .../sections/ReferCommunityProjectModal.tsx | 30 +- components/sections/ReferLocalGroupModal.tsx | 24 +- components/sections/SubmitProjectModal.tsx | 32 +- eslint.config.mjs | 16 + package-lock.json | 847 +++++++++++++----- package.json | 16 +- middleware.ts => proxy.ts | 2 +- src/i18n/request.ts | 2 +- tsconfig.json | 24 +- 16 files changed, 754 insertions(+), 285 deletions(-) create mode 100644 eslint.config.mjs rename middleware.ts => proxy.ts (83%) diff --git a/app/[lang]/layout.tsx b/app/[lang]/layout.tsx index a836513..7d1af82 100644 --- a/app/[lang]/layout.tsx +++ b/app/[lang]/layout.tsx @@ -83,7 +83,7 @@ export default async function LangLayout({ }) { const { lang } = await params - if (!locales.includes(lang as any)) { + if (!(locales as readonly string[]).includes(lang)) { notFound() } diff --git a/app/api/revalidate/route.ts b/app/api/revalidate/route.ts index 015fa14..50bdd6a 100644 --- a/app/api/revalidate/route.ts +++ b/app/api/revalidate/route.ts @@ -24,7 +24,7 @@ export async function POST(request: NextRequest) { if (Array.isArray(body.tags)) { for (const tag of body.tags) { if (typeof tag === 'string') { - revalidateTag(tag) + revalidateTag(tag, { expire: 0 }) revalidated.tags.push(tag) } } diff --git a/app/api/stats/route.ts b/app/api/stats/route.ts index 085f36c..a628efa 100644 --- a/app/api/stats/route.ts +++ b/app/api/stats/route.ts @@ -8,6 +8,6 @@ export async function GET() { } export async function POST() { - revalidateTag('site-stats') + revalidateTag('site-stats', { expire: 0 }) return NextResponse.json({ revalidated: true }) } diff --git a/components/blog/ShareButtons.tsx b/components/blog/ShareButtons.tsx index 803f94d..e61d5d1 100644 --- a/components/blog/ShareButtons.tsx +++ b/components/blog/ShareButtons.tsx @@ -1,6 +1,6 @@ 'use client' -import { useEffect, useState } from 'react' +import { useSyncExternalStore } from 'react' import { usePathname } from '@/src/i18n/navigation' import { useTranslations } from 'next-intl' @@ -11,17 +11,15 @@ interface ShareButtonsProps { const SITE_URL = process.env.NEXT_PUBLIC_SITE_URL || 'https://catholicdigitalcommons.org' +const subscribeNoop = () => () => {} +const getCanShare = () => typeof navigator.share === 'function' +const getCanShareServer = () => false + export default function ShareButtons({ title, namespace = 'blog' }: ShareButtonsProps) { const t = useTranslations(namespace) const pathname = usePathname() - const [canShare, setCanShare] = useState(false) - const [url, setUrl] = useState('') - - useEffect(() => { - const fullUrl = `${SITE_URL}${pathname}` - setUrl(fullUrl) - setCanShare(typeof navigator.share === 'function') - }, [pathname]) + const canShare = useSyncExternalStore(subscribeNoop, getCanShare, getCanShareServer) + const url = `${SITE_URL}${pathname}` const encodedUrl = encodeURIComponent(url) const encodedTitle = encodeURIComponent(title) diff --git a/components/projects/RepoLanguages.tsx b/components/projects/RepoLanguages.tsx index aa6383c..072567e 100644 --- a/components/projects/RepoLanguages.tsx +++ b/components/projects/RepoLanguages.tsx @@ -38,8 +38,6 @@ type LanguageData = Record> export default function RepoLanguages({ repos, label }: RepoLanguagesProps) { const [languages, setLanguages] = useState(null) - const [loading, setLoading] = useState(true) - // Extract owner/repo from GitHub URLs const repoIds = repos .map((url) => { @@ -55,11 +53,10 @@ export default function RepoLanguages({ repos, label }: RepoLanguagesProps) { }) .filter((id): id is string => id !== null) + const [loading, setLoading] = useState(repoIds.length > 0) + useEffect(() => { - if (repoIds.length === 0) { - setLoading(false) - return - } + if (repoIds.length === 0) return fetch(`/api/github/languages?repos=${repoIds.join(',')}`) .then((res) => (res.ok ? res.json() : null)) diff --git a/components/sections/FishBackground.tsx b/components/sections/FishBackground.tsx index 07c787d..f2ffb96 100644 --- a/components/sections/FishBackground.tsx +++ b/components/sections/FishBackground.tsx @@ -1,6 +1,6 @@ 'use client' -import { useEffect, useState } from 'react' +import { useState } from 'react' /** * Inline SVG path data for stylized fish in various poses. @@ -57,9 +57,7 @@ function randomBetween(min: number, max: number) { } export default function FishBackground({ count = 5 }: { count?: number }) { - const [fish, setFish] = useState([]) - - useEffect(() => { + const [fish] = useState(() => { const placed: PlacedFish[] = [] const minDistance = 8 // minimum % distance between fish centers @@ -88,8 +86,8 @@ export default function FishBackground({ count = 5 }: { count?: number }) { if (candidate) placed.push(candidate) } - setFish(placed) - }, [count]) + return placed + }) if (fish.length === 0) return null diff --git a/components/sections/HeroBanner.tsx b/components/sections/HeroBanner.tsx index 38073af..f9a5af4 100644 --- a/components/sections/HeroBanner.tsx +++ b/components/sections/HeroBanner.tsx @@ -48,10 +48,13 @@ export default function HeroBanner({ hero }: HeroBannerProps) {
{hero.heroShowLogo && ( - )} diff --git a/components/sections/ReferCommunityProjectModal.tsx b/components/sections/ReferCommunityProjectModal.tsx index 9ff3bef..2834079 100644 --- a/components/sections/ReferCommunityProjectModal.tsx +++ b/components/sections/ReferCommunityProjectModal.tsx @@ -13,7 +13,7 @@ export default function ReferCommunityProjectModal({ buttonLabel }: ReferCommuni const t = useTranslations('communityProjects') const dialogRef = useRef(null) const openedAtRef = useRef(0) - const formDataRef = useRef<{ fields: Record; tags: string[] }>({ fields: {}, tags: [] }) + const [formData, setFormData] = useState<{ fields: Record; tags: string[] }>({ fields: {}, tags: [] }) const [status, setStatus] = useState('idle') const [verificationCode, setVerificationCode] = useState('') const [codeError, setCodeError] = useState('') @@ -27,7 +27,7 @@ export default function ReferCommunityProjectModal({ buttonLabel }: ReferCommuni setTags([]) setTagInput('') openedAtRef.current = Date.now() - formDataRef.current = { fields: {}, tags: [] } + setFormData({ fields: {}, tags: [] }) dialogRef.current?.showModal() }, []) @@ -53,7 +53,7 @@ export default function ReferCommunityProjectModal({ buttonLabel }: ReferCommuni website: data.get('website') as string, } - formDataRef.current = { fields, tags } + setFormData({ fields, tags }) const payload = { ...fields, @@ -88,8 +88,8 @@ export default function ReferCommunityProjectModal({ buttonLabel }: ReferCommuni method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ - ...formDataRef.current.fields, - tags: formDataRef.current.tags, + ...formData.fields, + tags: formData.tags, verification_code: verificationCode, elapsed_ms: Date.now() - openedAtRef.current, }), @@ -127,8 +127,8 @@ export default function ReferCommunityProjectModal({ buttonLabel }: ReferCommuni method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ - ...formDataRef.current.fields, - tags: formDataRef.current.tags, + ...formData.fields, + tags: formData.tags, elapsed_ms: Date.now() - openedAtRef.current, }), }) @@ -209,7 +209,7 @@ export default function ReferCommunityProjectModal({ buttonLabel }: ReferCommuni

{t('checkEmailTitle')}

- {t('checkEmailMessage', { email: formDataRef.current.fields.submitter_email })} + {t('checkEmailMessage', { email: formData.fields.submitter_email })}

@@ -294,7 +294,7 @@ export default function ReferCommunityProjectModal({ buttonLabel }: ReferCommuni id="cp_project_name" name="project_name" required - defaultValue={formDataRef.current.fields.project_name} + defaultValue={formData.fields.project_name} placeholder={t('fieldProjectNamePlaceholder')} className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-cdcf-gold focus:ring-1 focus:ring-cdcf-gold focus:outline-none" /> @@ -309,7 +309,7 @@ export default function ReferCommunityProjectModal({ buttonLabel }: ReferCommuni id="cp_category" name="category" list="cp-categories" - defaultValue={formDataRef.current.fields.category} + defaultValue={formData.fields.category} placeholder={t('fieldCategoryPlaceholder')} className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-cdcf-gold focus:ring-1 focus:ring-cdcf-gold focus:outline-none" /> @@ -373,7 +373,7 @@ export default function ReferCommunityProjectModal({ buttonLabel }: ReferCommuni name="description" required rows={3} - defaultValue={formDataRef.current.fields.description} + defaultValue={formData.fields.description} placeholder={t('fieldDescriptionPlaceholder')} className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-cdcf-gold focus:ring-1 focus:ring-cdcf-gold focus:outline-none" /> @@ -387,7 +387,7 @@ export default function ReferCommunityProjectModal({ buttonLabel }: ReferCommuni type="url" id="cp_project_url" name="project_url" - defaultValue={formDataRef.current.fields.project_url} + defaultValue={formData.fields.project_url} placeholder={t('fieldProjectUrlPlaceholder')} className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-cdcf-gold focus:ring-1 focus:ring-cdcf-gold focus:outline-none" /> @@ -401,7 +401,7 @@ export default function ReferCommunityProjectModal({ buttonLabel }: ReferCommuni type="url" id="cp_github_url" name="github_url" - defaultValue={formDataRef.current.fields.github_url} + defaultValue={formData.fields.github_url} placeholder={t('fieldGithubUrlPlaceholder')} className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-cdcf-gold focus:ring-1 focus:ring-cdcf-gold focus:outline-none" /> @@ -418,7 +418,7 @@ export default function ReferCommunityProjectModal({ buttonLabel }: ReferCommuni id="cp_submitter_name" name="submitter_name" required - defaultValue={formDataRef.current.fields.submitter_name} + defaultValue={formData.fields.submitter_name} placeholder={t('fieldYourNamePlaceholder')} className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-cdcf-gold focus:ring-1 focus:ring-cdcf-gold focus:outline-none" /> @@ -433,7 +433,7 @@ export default function ReferCommunityProjectModal({ buttonLabel }: ReferCommuni id="cp_submitter_email" name="submitter_email" required - defaultValue={formDataRef.current.fields.submitter_email} + defaultValue={formData.fields.submitter_email} placeholder={t('fieldYourEmailPlaceholder')} className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-cdcf-gold focus:ring-1 focus:ring-cdcf-gold focus:outline-none" /> diff --git a/components/sections/ReferLocalGroupModal.tsx b/components/sections/ReferLocalGroupModal.tsx index 866cf38..97749e5 100644 --- a/components/sections/ReferLocalGroupModal.tsx +++ b/components/sections/ReferLocalGroupModal.tsx @@ -13,7 +13,7 @@ export default function ReferLocalGroupModal({ buttonLabel }: ReferLocalGroupMod const t = useTranslations('community') const dialogRef = useRef(null) const openedAtRef = useRef(0) - const formDataRef = useRef>({}) + const [formData, setFormData] = useState>({}) const [status, setStatus] = useState('idle') const [verificationCode, setVerificationCode] = useState('') const [codeError, setCodeError] = useState('') @@ -49,7 +49,7 @@ export default function ReferLocalGroupModal({ buttonLabel }: ReferLocalGroupMod } // Store form data for the final submission - formDataRef.current = { + setFormData({ group_name: payload.group_name, location: payload.location, url: payload.url, @@ -57,7 +57,7 @@ export default function ReferLocalGroupModal({ buttonLabel }: ReferLocalGroupMod submitter_name: payload.submitter_name, submitter_email: payload.submitter_email, website: payload.website, - } + }) try { const res = await fetch('/api/refer-local-group/send-code', { @@ -86,7 +86,7 @@ export default function ReferLocalGroupModal({ buttonLabel }: ReferLocalGroupMod method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ - ...formDataRef.current, + ...formData, verification_code: verificationCode, elapsed_ms: Date.now() - openedAtRef.current, }), @@ -124,7 +124,7 @@ export default function ReferLocalGroupModal({ buttonLabel }: ReferLocalGroupMod method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ - ...formDataRef.current, + ...formData, elapsed_ms: Date.now() - openedAtRef.current, }), }) @@ -205,7 +205,7 @@ export default function ReferLocalGroupModal({ buttonLabel }: ReferLocalGroupMod

{t('checkEmailTitle')}

- {t('checkEmailMessage', { email: formDataRef.current.submitter_email })} + {t('checkEmailMessage', { email: formData.submitter_email })}

@@ -290,7 +290,7 @@ export default function ReferLocalGroupModal({ buttonLabel }: ReferLocalGroupMod id="group_name" name="group_name" required - defaultValue={formDataRef.current.group_name} + defaultValue={formData.group_name} placeholder={t('fieldGroupNamePlaceholder')} className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-cdcf-gold focus:ring-1 focus:ring-cdcf-gold focus:outline-none" /> @@ -304,7 +304,7 @@ export default function ReferLocalGroupModal({ buttonLabel }: ReferLocalGroupMod type="text" id="location" name="location" - defaultValue={formDataRef.current.location} + defaultValue={formData.location} placeholder={t('fieldLocationPlaceholder')} className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-cdcf-gold focus:ring-1 focus:ring-cdcf-gold focus:outline-none" /> @@ -319,7 +319,7 @@ export default function ReferLocalGroupModal({ buttonLabel }: ReferLocalGroupMod id="url" name="url" required - defaultValue={formDataRef.current.url} + defaultValue={formData.url} placeholder={t('fieldUrlPlaceholder')} className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-cdcf-gold focus:ring-1 focus:ring-cdcf-gold focus:outline-none" /> @@ -334,7 +334,7 @@ export default function ReferLocalGroupModal({ buttonLabel }: ReferLocalGroupMod name="description" required rows={3} - defaultValue={formDataRef.current.description} + defaultValue={formData.description} placeholder={t('fieldDescriptionPlaceholder')} className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-cdcf-gold focus:ring-1 focus:ring-cdcf-gold focus:outline-none" /> @@ -351,7 +351,7 @@ export default function ReferLocalGroupModal({ buttonLabel }: ReferLocalGroupMod id="submitter_name" name="submitter_name" required - defaultValue={formDataRef.current.submitter_name} + defaultValue={formData.submitter_name} placeholder={t('fieldYourNamePlaceholder')} className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-cdcf-gold focus:ring-1 focus:ring-cdcf-gold focus:outline-none" /> @@ -366,7 +366,7 @@ export default function ReferLocalGroupModal({ buttonLabel }: ReferLocalGroupMod id="submitter_email" name="submitter_email" required - defaultValue={formDataRef.current.submitter_email} + defaultValue={formData.submitter_email} placeholder={t('fieldYourEmailPlaceholder')} className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-cdcf-gold focus:ring-1 focus:ring-cdcf-gold focus:outline-none" /> diff --git a/components/sections/SubmitProjectModal.tsx b/components/sections/SubmitProjectModal.tsx index 89c5005..3e40217 100644 --- a/components/sections/SubmitProjectModal.tsx +++ b/components/sections/SubmitProjectModal.tsx @@ -13,7 +13,7 @@ export default function SubmitProjectModal({ buttonLabel }: SubmitProjectModalPr const t = useTranslations('projects') const dialogRef = useRef(null) const openedAtRef = useRef(0) - const formDataRef = useRef<{ fields: Record; repoUrls: string[]; tags: string[] }>({ + const [formData, setFormData] = useState<{ fields: Record; repoUrls: string[]; tags: string[] }>({ fields: {}, repoUrls: [''], tags: [], @@ -33,7 +33,7 @@ export default function SubmitProjectModal({ buttonLabel }: SubmitProjectModalPr setTags([]) setTagInput('') openedAtRef.current = Date.now() - formDataRef.current = { fields: {}, repoUrls: [''], tags: [] } + setFormData({ fields: {}, repoUrls: [''], tags: [] }) dialogRef.current?.showModal() }, []) @@ -72,7 +72,7 @@ export default function SubmitProjectModal({ buttonLabel }: SubmitProjectModalPr const filteredRepoUrls = repoUrls.filter((u) => u.trim() !== '') - formDataRef.current = { fields, repoUrls: filteredRepoUrls, tags } + setFormData({ fields, repoUrls: filteredRepoUrls, tags }) try { const res = await fetch('/api/submit-project/send-code', { @@ -106,9 +106,9 @@ export default function SubmitProjectModal({ buttonLabel }: SubmitProjectModalPr method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ - ...formDataRef.current.fields, - repo_urls: formDataRef.current.repoUrls, - tags: formDataRef.current.tags, + ...formData.fields, + repo_urls: formData.repoUrls, + tags: formData.tags, verification_code: verificationCode, elapsed_ms: Date.now() - openedAtRef.current, }), @@ -146,9 +146,9 @@ export default function SubmitProjectModal({ buttonLabel }: SubmitProjectModalPr method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ - ...formDataRef.current.fields, - repo_urls: formDataRef.current.repoUrls, - tags: formDataRef.current.tags, + ...formData.fields, + repo_urls: formData.repoUrls, + tags: formData.tags, elapsed_ms: Date.now() - openedAtRef.current, }), }) @@ -229,7 +229,7 @@ export default function SubmitProjectModal({ buttonLabel }: SubmitProjectModalPr

{t('checkEmailTitle')}

- {t('checkEmailMessage', { email: formDataRef.current.fields.submitter_email })} + {t('checkEmailMessage', { email: formData.fields.submitter_email })}

@@ -314,7 +314,7 @@ export default function SubmitProjectModal({ buttonLabel }: SubmitProjectModalPr id="project_name" name="project_name" required - defaultValue={formDataRef.current.fields.project_name} + defaultValue={formData.fields.project_name} placeholder={t('fieldProjectNamePlaceholder')} className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-cdcf-gold focus:ring-1 focus:ring-cdcf-gold focus:outline-none" /> @@ -329,7 +329,7 @@ export default function SubmitProjectModal({ buttonLabel }: SubmitProjectModalPr id="category" name="category" list="project-categories" - defaultValue={formDataRef.current.fields.category} + defaultValue={formData.fields.category} placeholder={t('fieldCategoryPlaceholder')} className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-cdcf-gold focus:ring-1 focus:ring-cdcf-gold focus:outline-none" /> @@ -393,7 +393,7 @@ export default function SubmitProjectModal({ buttonLabel }: SubmitProjectModalPr name="description" required rows={3} - defaultValue={formDataRef.current.fields.description} + defaultValue={formData.fields.description} placeholder={t('fieldDescriptionPlaceholder')} className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-cdcf-gold focus:ring-1 focus:ring-cdcf-gold focus:outline-none" /> @@ -407,7 +407,7 @@ export default function SubmitProjectModal({ buttonLabel }: SubmitProjectModalPr type="url" id="url" name="url" - defaultValue={formDataRef.current.fields.url} + defaultValue={formData.fields.url} placeholder={t('fieldProjectUrlPlaceholder')} className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-cdcf-gold focus:ring-1 focus:ring-cdcf-gold focus:outline-none" /> @@ -458,7 +458,7 @@ export default function SubmitProjectModal({ buttonLabel }: SubmitProjectModalPr id="submitter_name" name="submitter_name" required - defaultValue={formDataRef.current.fields.submitter_name} + defaultValue={formData.fields.submitter_name} placeholder={t('fieldYourNamePlaceholder')} className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-cdcf-gold focus:ring-1 focus:ring-cdcf-gold focus:outline-none" /> @@ -473,7 +473,7 @@ export default function SubmitProjectModal({ buttonLabel }: SubmitProjectModalPr id="submitter_email" name="submitter_email" required - defaultValue={formDataRef.current.fields.submitter_email} + defaultValue={formData.fields.submitter_email} placeholder={t('fieldYourEmailPlaceholder')} className="mt-1 block w-full rounded-md border border-gray-300 px-3 py-2 text-sm focus:border-cdcf-gold focus:ring-1 focus:ring-cdcf-gold focus:outline-none" /> diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..5f18eb6 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,16 @@ +import { defineConfig, globalIgnores } from 'eslint/config' +import nextVitals from 'eslint-config-next/core-web-vitals' +import nextTs from 'eslint-config-next/typescript' + +const eslintConfig = defineConfig([ + ...nextVitals, + ...nextTs, + globalIgnores([ + '.next/**', + 'out/**', + 'build/**', + 'next-env.d.ts', + ]), +]) + +export default eslintConfig diff --git a/package-lock.json b/package-lock.json index e183400..387540f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,10 +10,10 @@ "dependencies": { "@heroicons/react": "^2.2.0", "clsx": "^2.1.1", - "next": "^15.5.14", - "next-intl": "^4.1.0", - "react": "^19.0.0", - "react-dom": "^19.0.0", + "next": "^16.2.4", + "next-intl": "^4.9.1", + "react": "^19.2.5", + "react-dom": "^19.2.5", "striptags": "^3.2.0" }, "devDependencies": { @@ -21,10 +21,10 @@ "@tailwindcss/postcss": "^4.0.0", "@tailwindcss/typography": "^0.5.16", "@types/node": "^22.0.0", - "@types/react": "^19.0.0", - "@types/react-dom": "^19.0.0", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", "eslint": "^9.0.0", - "eslint-config-next": "^15.2.0", + "eslint-config-next": "^16.2.4", "postcss": "^8.5.0", "tailwindcss": "^4.0.0", "typescript": "^5.7.0" @@ -58,6 +58,176 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/core/node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/core/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "license": "ISC", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/@babel/helper-compilation-targets/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", + "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", + "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/traverse": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@babel/helper-validator-identifier": { "version": "7.28.5", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", @@ -68,6 +238,46 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/helper-validator-option": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", + "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/@babel/runtime": { "version": "7.29.2", "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", @@ -78,6 +288,54 @@ "node": ">=6.9.0" } }, + "node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/@emnapi/core": { "version": "1.9.2", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", @@ -321,55 +579,34 @@ "npm": ">=6.0.0" } }, - "node_modules/@formatjs/bigdecimal": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@formatjs/bigdecimal/-/bigdecimal-0.2.0.tgz", - "integrity": "sha512-GeaxHZbUoYvHL9tC5eltHLs+1zU70aPw0s7LwqgktIzF5oMhNY4o4deEtusJMsq7WFJF3Ye2zQEzdG8beVk73w==", - "license": "MIT" - }, - "node_modules/@formatjs/ecma402-abstract": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@formatjs/ecma402-abstract/-/ecma402-abstract-3.2.0.tgz", - "integrity": "sha512-dHnqHgBo6GXYGRsepaE1wmsC2etaivOWd5VaJstZd+HI2zR3DCUjbDVZRtoPGkkXZmyHvBwrdEUuqfvzhF/DtQ==", - "license": "MIT", - "dependencies": { - "@formatjs/bigdecimal": "0.2.0", - "@formatjs/fast-memoize": "3.1.1", - "@formatjs/intl-localematcher": "0.8.2" - } - }, "node_modules/@formatjs/fast-memoize": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-3.1.1.tgz", - "integrity": "sha512-CbNbf+tlJn1baRnPkNePnBqTLxGliG6DDgNa/UtV66abwIjwsliPMOt0172tzxABYzSuxZBZfcp//qI8AvBWPg==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@formatjs/fast-memoize/-/fast-memoize-3.1.2.tgz", + "integrity": "sha512-vPnriihkfK0lzoQGaXq+qXH23VsYyansRTkTgo2aTG0k1NjLFyZimFVdfj4C9JkSE5dm7CEngcQ5TTc1yAyBfQ==", "license": "MIT" }, "node_modules/@formatjs/icu-messageformat-parser": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.3.tgz", - "integrity": "sha512-HJWZ9S6JWey6iY5+YXE3Kd0ofWU1sC2KTTp56e1168g/xxWvVvr8k9G4fexIgwYV9wbtjY7kGYK5FjoWB3B2OQ==", + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/@formatjs/icu-messageformat-parser/-/icu-messageformat-parser-3.5.4.tgz", + "integrity": "sha512-JVY39ROgLt+pIYngo6piyj4OVfZmXs/2FkC4wLS+ql1Eig/sGJKB7YwDO/5bkJFkfwaFAeIpgEiJc8hiYxNalw==", "license": "MIT", "dependencies": { - "@formatjs/ecma402-abstract": "3.2.0", - "@formatjs/icu-skeleton-parser": "2.1.3" + "@formatjs/icu-skeleton-parser": "2.1.4" } }, "node_modules/@formatjs/icu-skeleton-parser": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-2.1.3.tgz", - "integrity": "sha512-9mFp8TJ166ZM2pcjKwsBWXrDnOJGT7vMEScVgLygUODPOsE8S6f/FHoacvrlHK1B4dYZk8vSCNruyPU64AfgJQ==", - "license": "MIT", - "dependencies": { - "@formatjs/ecma402-abstract": "3.2.0" - } + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@formatjs/icu-skeleton-parser/-/icu-skeleton-parser-2.1.4.tgz", + "integrity": "sha512-8bSFZbrlvGX11ywMZxtgkPBt5Q8/etyts7j7j+GWpOVK1g43zwMIH3LZxk43HAtEP7L/jtZ+OZaMiFTOiBj9CA==", + "license": "MIT" }, "node_modules/@formatjs/intl-localematcher": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.8.2.tgz", - "integrity": "sha512-q05KMYGJLyqFNFtIb8NhWLF5X3aK/k0wYt7dnRFuy6aLQL+vUwQ1cg5cO4qawEiINybeCPXAWlprY2mSBjSXAQ==", + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/@formatjs/intl-localematcher/-/intl-localematcher-0.8.3.tgz", + "integrity": "sha512-pHUjWb9NuhnMs8+PxQdzBtZRFJHlGhrURGAbm6Ltwl82BFajeuiIR3jblSa7ia3r62rXe/0YtVpUG3xWr5bFCA==", "license": "MIT", "dependencies": { - "@formatjs/fast-memoize": "3.1.1" + "@formatjs/fast-memoize": "3.1.2" } }, "node_modules/@heroicons/react": { @@ -973,15 +1210,15 @@ } }, "node_modules/@next/env": { - "version": "15.5.14", - "resolved": "https://registry.npmjs.org/@next/env/-/env-15.5.14.tgz", - "integrity": "sha512-aXeirLYuASxEgi4X4WhfXsShCFxWDfNn/8ZeC5YXAS2BB4A8FJi1kwwGL6nvMVboE7fZCzmJPNdMvVHc8JpaiA==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/env/-/env-16.2.4.tgz", + "integrity": "sha512-dKkkOzOSwFYe5RX6y26fZgkSpVAlIOJKQHIiydQcrWH6y/97+RceSOAdjZ14Qa3zLduVUy0TXcn+EiM6t4rPgw==", "license": "MIT" }, "node_modules/@next/eslint-plugin-next": { - "version": "15.5.14", - "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-15.5.14.tgz", - "integrity": "sha512-ogBjgsFrPPz19abP3VwcYSahbkUOMMvJjxCOYWYndw+PydeMuLuB4XrvNkNutFrTjC9St2KFULRdKID8Sd/CMQ==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/eslint-plugin-next/-/eslint-plugin-next-16.2.4.tgz", + "integrity": "sha512-tOX826JJ96gYK/go18sPUgMq9FK1tqxBFfUCEufJb5XIkWFFmpgU7mahJANKGkHs7F41ir3tReJ3Lv5La0RvhA==", "dev": true, "license": "MIT", "dependencies": { @@ -989,9 +1226,9 @@ } }, "node_modules/@next/swc-darwin-arm64": { - "version": "15.5.14", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.5.14.tgz", - "integrity": "sha512-Y9K6SPzobnZvrRDPO2s0grgzC+Egf0CqfbdvYmQVaztV890zicw8Z8+4Vqw8oPck8r1TjUHxVh8299Cg4TrxXg==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-16.2.4.tgz", + "integrity": "sha512-OXTFFox5EKN1Ym08vfrz+OXxmCcEjT4SFMbNRsWZE99dMqt2Kcusl5MqPXcW232RYkMLQTy0hqgAMEsfEd/l2A==", "cpu": [ "arm64" ], @@ -1005,9 +1242,9 @@ } }, "node_modules/@next/swc-darwin-x64": { - "version": "15.5.14", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.5.14.tgz", - "integrity": "sha512-aNnkSMjSFRTOmkd7qoNI2/rETQm/vKD6c/Ac9BZGa9CtoOzy3c2njgz7LvebQJ8iPxdeTuGnAjagyis8a9ifBw==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-16.2.4.tgz", + "integrity": "sha512-XhpVnUfmYWvD3YrXu55XdcAkQtOnvaI6wtQa8fuF5fGoKoxIUZ0kWPtcOfqJEWngFF/lOS9l3+O9CcownhiQxQ==", "cpu": [ "x64" ], @@ -1021,12 +1258,15 @@ } }, "node_modules/@next/swc-linux-arm64-gnu": { - "version": "15.5.14", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.5.14.tgz", - "integrity": "sha512-tjlpia+yStPRS//6sdmlVwuO1Rioern4u2onafa5n+h2hCS9MAvMXqpVbSrjgiEOoCs0nJy7oPOmWgtRRNSM5Q==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-16.2.4.tgz", + "integrity": "sha512-Mx/tjlNA3G8kg14QvuGAJ4xBwPk1tUHq56JxZ8CXnZwz1Etz714soCEzGQQzVMz4bEnGPowzkV6Xrp6wAkEWOQ==", "cpu": [ "arm64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1037,12 +1277,15 @@ } }, "node_modules/@next/swc-linux-arm64-musl": { - "version": "15.5.14", - "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.5.14.tgz", - "integrity": "sha512-8B8cngBaLadl5lbDRdxGCP1Lef8ipD6KlxS3v0ElDAGil6lafrAM3B258p1KJOglInCVFUjk751IXMr2ixeQOQ==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-16.2.4.tgz", + "integrity": "sha512-iVMMp14514u7Nup2umQS03nT/bN9HurK8ufylC3FZNykrwjtx7V1A7+4kvhbDSCeonTVqV3Txnv0Lu+m2oDXNg==", "cpu": [ "arm64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1053,12 +1296,15 @@ } }, "node_modules/@next/swc-linux-x64-gnu": { - "version": "15.5.14", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.5.14.tgz", - "integrity": "sha512-bAS6tIAg8u4Gn3Nz7fCPpSoKAexEt2d5vn1mzokcqdqyov6ZJ6gu6GdF9l8ORFrBuRHgv3go/RfzYz5BkZ6YSQ==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-16.2.4.tgz", + "integrity": "sha512-EZOvm1aQWgnI/N/xcWOlnS3RQBk0VtVav5Zo7n4p0A7UKyTDx047k8opDbXgBpHl4CulRqRfbw3QrX2w5UOXMQ==", "cpu": [ "x64" ], + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1069,12 +1315,15 @@ } }, "node_modules/@next/swc-linux-x64-musl": { - "version": "15.5.14", - "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.5.14.tgz", - "integrity": "sha512-mMxv/FcrT7Gfaq4tsR22l17oKWXZmH/lVqcvjX0kfp5I0lKodHYLICKPoX1KRnnE+ci6oIUdriUhuA3rBCDiSw==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-16.2.4.tgz", + "integrity": "sha512-h9FxsngCm9cTBf71AR4fGznDEDx1hS7+kSEiIRjq5kO1oXWm07DxVGZjCvk0SGx7TSjlUqhI8oOyz7NfwAdPoA==", "cpu": [ "x64" ], + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1085,9 +1334,9 @@ } }, "node_modules/@next/swc-win32-arm64-msvc": { - "version": "15.5.14", - "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.5.14.tgz", - "integrity": "sha512-OTmiBlYThppnvnsqx0rBqjDRemlmIeZ8/o4zI7veaXoeO1PVHoyj2lfTfXTiiGjCyRDhA10y4h6ZvZvBiynr2g==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-16.2.4.tgz", + "integrity": "sha512-3NdJV5OXMSOeJYijX+bjaLge3mJBlh4ybydbT4GFoB/2hAojWHtMhl3CYlYoMrjPuodp0nzFVi4Tj2+WaMg+Ow==", "cpu": [ "arm64" ], @@ -1101,9 +1350,9 @@ } }, "node_modules/@next/swc-win32-x64-msvc": { - "version": "15.5.14", - "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.5.14.tgz", - "integrity": "sha512-+W7eFf3RS7m4G6tppVTOSyP9Y6FsJXfOuKzav1qKniiFm3KFByQfPEcouHdjlZmysl4zJGuGLQ/M9XyVeyeNEg==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-16.2.4.tgz", + "integrity": "sha512-kMVGgsqhO5YTYODD9IPGGhA6iprWidQckK3LmPeW08PIFENRmgfb4MjXHO+p//d+ts2rpjvK5gXWzXSMrPl9cw==", "cpu": [ "x64" ], @@ -1910,13 +2159,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@rushstack/eslint-patch": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.16.1.tgz", - "integrity": "sha512-TvZbIpeKqGQQ7X0zSCvPH9riMSFQFSggnfBjFZ1mEoILW+UuXCKwOoPcgjMwiUtRqFZ8jWhPJc4um14vC6I4ag==", - "dev": true, - "license": "MIT" - }, "node_modules/@schummar/icu-type-parser": { "version": "1.21.5", "resolved": "https://registry.npmjs.org/@schummar/icu-type-parser/-/icu-type-parser-1.21.5.tgz", @@ -2501,17 +2743,17 @@ "optional": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.0.tgz", - "integrity": "sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg==", + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.2.tgz", + "integrity": "sha512-aC2qc5thQahutKjP+cl8cgN9DWe3ZUqVko30CMSZHnFEHyhOYoZSzkGtAI2mcwZ38xeImDucI4dnqsHiOYuuCw==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.58.0", - "@typescript-eslint/type-utils": "8.58.0", - "@typescript-eslint/utils": "8.58.0", - "@typescript-eslint/visitor-keys": "8.58.0", + "@typescript-eslint/scope-manager": "8.58.2", + "@typescript-eslint/type-utils": "8.58.2", + "@typescript-eslint/utils": "8.58.2", + "@typescript-eslint/visitor-keys": "8.58.2", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -2524,7 +2766,7 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.58.0", + "@typescript-eslint/parser": "^8.58.2", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } @@ -2540,16 +2782,16 @@ } }, "node_modules/@typescript-eslint/parser": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.0.tgz", - "integrity": "sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA==", + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.2.tgz", + "integrity": "sha512-/Zb/xaIDfxeJnvishjGdcR4jmr7S+bda8PKNhRGdljDM+elXhlvN0FyPSsMnLmJUrVG9aPO6dof80wjMawsASg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.58.0", - "@typescript-eslint/types": "8.58.0", - "@typescript-eslint/typescript-estree": "8.58.0", - "@typescript-eslint/visitor-keys": "8.58.0", + "@typescript-eslint/scope-manager": "8.58.2", + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/typescript-estree": "8.58.2", + "@typescript-eslint/visitor-keys": "8.58.2", "debug": "^4.4.3" }, "engines": { @@ -2565,14 +2807,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.0.tgz", - "integrity": "sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg==", + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.2.tgz", + "integrity": "sha512-Cq6UfpZZk15+r87BkIh5rDpi38W4b+Sjnb8wQCPPDDweS/LRCFjCyViEbzHk5Ck3f2QDfgmlxqSa7S7clDtlfg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.58.0", - "@typescript-eslint/types": "^8.58.0", + "@typescript-eslint/tsconfig-utils": "^8.58.2", + "@typescript-eslint/types": "^8.58.2", "debug": "^4.4.3" }, "engines": { @@ -2587,14 +2829,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.0.tgz", - "integrity": "sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ==", + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.2.tgz", + "integrity": "sha512-SgmyvDPexWETQek+qzZnrG6844IaO02UVyOLhI4wpo82dpZJY9+6YZCKAMFzXb7qhx37mFK1QcPQ18tud+vo6Q==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.58.0", - "@typescript-eslint/visitor-keys": "8.58.0" + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/visitor-keys": "8.58.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2605,9 +2847,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.0.tgz", - "integrity": "sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A==", + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.2.tgz", + "integrity": "sha512-3SR+RukipDvkkKp/d0jP0dyzuls3DbGmwDpVEc5wqk5f38KFThakqAAO0XMirWAE+kT00oTauTbzMFGPoAzB0A==", "dev": true, "license": "MIT", "engines": { @@ -2622,15 +2864,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.0.tgz", - "integrity": "sha512-aGsCQImkDIqMyx1u4PrVlbi/krmDsQUs4zAcCV6M7yPcPev+RqVlndsJy9kJ8TLihW9TZ0kbDAzctpLn5o+lOg==", + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.2.tgz", + "integrity": "sha512-Z7EloNR/B389FvabdGeTo2XMs4W9TjtPiO9DAsmT0yom0bwlPyRjkJ1uCdW1DvrrrYP50AJZ9Xc3sByZA9+dcg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.58.0", - "@typescript-eslint/typescript-estree": "8.58.0", - "@typescript-eslint/utils": "8.58.0", + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/typescript-estree": "8.58.2", + "@typescript-eslint/utils": "8.58.2", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -2647,9 +2889,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.0.tgz", - "integrity": "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww==", + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.2.tgz", + "integrity": "sha512-9TukXyATBQf/Jq9AMQXfvurk+G5R2MwfqQGDR2GzGz28HvY/lXNKGhkY+6IOubwcquikWk5cjlgPvD2uAA7htQ==", "dev": true, "license": "MIT", "engines": { @@ -2661,16 +2903,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.0.tgz", - "integrity": "sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA==", + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.2.tgz", + "integrity": "sha512-ELGuoofuhhoCvNbQjFFiobFcGgcDCEm0ThWdmO4Z0UzLqPXS3KFvnEZ+SHewwOYHjM09tkzOWXNTv9u6Gqtyuw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.58.0", - "@typescript-eslint/tsconfig-utils": "8.58.0", - "@typescript-eslint/types": "8.58.0", - "@typescript-eslint/visitor-keys": "8.58.0", + "@typescript-eslint/project-service": "8.58.2", + "@typescript-eslint/tsconfig-utils": "8.58.2", + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/visitor-keys": "8.58.2", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -2728,16 +2970,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.0.tgz", - "integrity": "sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA==", + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.2.tgz", + "integrity": "sha512-QZfjHNEzPY8+l0+fIXMvuQ2sJlplB4zgDZvA+NmvZsZv3EQwOcc1DuIU1VJUTWZ/RKouBMhDyNaBMx4sWvrzRA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.58.0", - "@typescript-eslint/types": "8.58.0", - "@typescript-eslint/typescript-estree": "8.58.0" + "@typescript-eslint/scope-manager": "8.58.2", + "@typescript-eslint/types": "8.58.2", + "@typescript-eslint/typescript-estree": "8.58.2" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2752,13 +2994,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.58.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.0.tgz", - "integrity": "sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ==", + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.2.tgz", + "integrity": "sha512-f1WO2Lx8a9t8DARmcWAUPJbu0G20bJlj8L4z72K00TMeJAoyLr/tHhI/pzYBLrR4dXWkcxO1cWYZEOX8DKHTqA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/types": "8.58.2", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -3396,6 +3638,18 @@ "dev": true, "license": "MIT" }, + "node_modules/baseline-browser-mapping": { + "version": "2.10.19", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.19.tgz", + "integrity": "sha512-qCkNLi2sfBOn8XhZQ0FXsT1Ki/Yo5P90hrkRamVFRS7/KV9hpfA4HkoWNU152+8w0zPjnxo5psx5NL3PSGgv5g==", + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/better-ajv-errors": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/better-ajv-errors/-/better-ajv-errors-1.2.0.tgz", @@ -3440,6 +3694,40 @@ "node": ">=8" } }, + "node_modules/browserslist": { + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, "node_modules/call-bind": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", @@ -3622,6 +3910,13 @@ "dev": true, "license": "MIT" }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true, + "license": "MIT" + }, "node_modules/cookie": { "version": "0.7.2", "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", @@ -3890,6 +4185,13 @@ "node": ">= 0.4" } }, + "node_modules/electron-to-chromium": { + "version": "1.5.340", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.340.tgz", + "integrity": "sha512-908qahOGocRMinT2nM3ajCEM99H4iPdv84eagPP3FfZy/1ZGeOy2CZYzjhms81ckOPCXPlW7LkY4XpxD8r1DrA==", + "dev": true, + "license": "ISC" + }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", @@ -4180,25 +4482,24 @@ } }, "node_modules/eslint-config-next": { - "version": "15.5.14", - "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-15.5.14.tgz", - "integrity": "sha512-lmJ5F8ZgOYogq0qtH4L5SpxuASY2SPdOzqUprN2/56+P3GPsIpXaUWIJC66kYIH+yZdsM4nkHE5MIBP6s1NiBw==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/eslint-config-next/-/eslint-config-next-16.2.4.tgz", + "integrity": "sha512-A6ekXYFj/YQxBPMl45g3e+U8zJo+X2+ZQwcz34pPKjpc/3S4roBA2Rd9xWB4FKuSxhofo1/95WjzmUY+wHrOhg==", "dev": true, "license": "MIT", "dependencies": { - "@next/eslint-plugin-next": "15.5.14", - "@rushstack/eslint-patch": "^1.10.3", - "@typescript-eslint/eslint-plugin": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", - "@typescript-eslint/parser": "^5.4.2 || ^6.0.0 || ^7.0.0 || ^8.0.0", + "@next/eslint-plugin-next": "16.2.4", "eslint-import-resolver-node": "^0.3.6", "eslint-import-resolver-typescript": "^3.5.2", - "eslint-plugin-import": "^2.31.0", + "eslint-plugin-import": "^2.32.0", "eslint-plugin-jsx-a11y": "^6.10.0", "eslint-plugin-react": "^7.37.0", - "eslint-plugin-react-hooks": "^5.0.0" + "eslint-plugin-react-hooks": "^7.0.0", + "globals": "16.4.0", + "typescript-eslint": "^8.46.0" }, "peerDependencies": { - "eslint": "^7.23.0 || ^8.0.0 || ^9.0.0", + "eslint": ">=9.0.0", "typescript": ">=3.3.1" }, "peerDependenciesMeta": { @@ -4207,6 +4508,19 @@ } } }, + "node_modules/eslint-config-next/node_modules/globals": { + "version": "16.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-16.4.0.tgz", + "integrity": "sha512-ob/2LcVVaVGCYN+r14cnwnoDPUufjiYgSqRhiFD0Q1iI4Odora5RE8Iv1D24hAz5oMophRGkGz+yuvQmmUMnMw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/eslint-import-resolver-node": { "version": "0.3.10", "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.10.tgz", @@ -4410,16 +4724,23 @@ } }, "node_modules/eslint-plugin-react-hooks": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz", - "integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.1.1.tgz", + "integrity": "sha512-f2I7Gw6JbvCexzIInuSbZpfdQ44D7iqdWX01FKLvrPgqxoE7oMj8clOfto8U6vYiz4yd5oKu39rRSVOe1zRu0g==", "dev": true, "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, "engines": { - "node": ">=10" + "node": ">=18" }, "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 || ^10.0.0" } }, "node_modules/eslint-plugin-react/node_modules/semver": { @@ -4845,6 +5166,16 @@ "node": ">= 0.4" } }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, "node_modules/get-caller-file": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", @@ -5161,6 +5492,23 @@ "node": ">= 0.4" } }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, "node_modules/http2-client": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/http2-client/-/http2-client-1.3.5.tgz", @@ -5183,9 +5531,9 @@ } }, "node_modules/icu-minify": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/icu-minify/-/icu-minify-4.9.0.tgz", - "integrity": "sha512-9ev7MqkN29jcIelUAqJRfNCxzGOEkBJPnr+scYATMp2bfpU4Bm1eIwYU0/o5xRy8BBnSWMUjK58WTB3132P0bg==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/icu-minify/-/icu-minify-4.9.1.tgz", + "integrity": "sha512-6NkfF9GHHFouqnz+wuiLjCWQiyxoEyJ5liUv4Jxxo/8wyhV7MY0L0iTEGDAVEa4aAD58WqTxFMa20S5nyMjwNw==", "funding": [ { "type": "individual", @@ -5257,14 +5605,13 @@ } }, "node_modules/intl-messageformat": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-11.2.0.tgz", - "integrity": "sha512-IhghAA8n4KSlXuWKzYsWyWb82JoYTzShfyvdSF85oJPnNOjvv4kAo7S7Jtkm3/vJ53C7dQNRO+Gpnj3iWgTjBQ==", + "version": "11.2.1", + "resolved": "https://registry.npmjs.org/intl-messageformat/-/intl-messageformat-11.2.1.tgz", + "integrity": "sha512-1gAVEUt3wEPvTqML4Fsw9klZV5j0vszQxayP/fi6gUroAc8AUHiNaisBKLWxybL1AdWq1mP07YV1q8v4N92ilQ==", "license": "BSD-3-Clause", "dependencies": { - "@formatjs/ecma402-abstract": "3.2.0", - "@formatjs/fast-memoize": "3.1.1", - "@formatjs/icu-messageformat-parser": "3.5.3" + "@formatjs/fast-memoize": "3.1.2", + "@formatjs/icu-messageformat-parser": "3.5.4" } }, "node_modules/is-array-buffer": { @@ -5749,6 +6096,19 @@ "js-yaml": "bin/js-yaml.js" } }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "license": "MIT", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -6457,13 +6817,14 @@ "license": "MIT" }, "node_modules/next": { - "version": "15.5.14", - "resolved": "https://registry.npmjs.org/next/-/next-15.5.14.tgz", - "integrity": "sha512-M6S+4JyRjmKic2Ssm7jHUPkE6YUJ6lv4507jprsSZLulubz0ihO2E+S4zmQK3JZ2ov81JrugukKU4Tz0ivgqqQ==", + "version": "16.2.4", + "resolved": "https://registry.npmjs.org/next/-/next-16.2.4.tgz", + "integrity": "sha512-kPvz56wF5frc+FxlHI5qnklCzbq53HTwORaWBGdT0vNoKh1Aya9XC8aPauH4NJxqtzbWsS5mAbctm4cr+EkQ2Q==", "license": "MIT", "dependencies": { - "@next/env": "15.5.14", + "@next/env": "16.2.4", "@swc/helpers": "0.5.15", + "baseline-browser-mapping": "^2.9.19", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", "styled-jsx": "5.1.6" @@ -6472,18 +6833,18 @@ "next": "dist/bin/next" }, "engines": { - "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" + "node": ">=20.9.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "15.5.14", - "@next/swc-darwin-x64": "15.5.14", - "@next/swc-linux-arm64-gnu": "15.5.14", - "@next/swc-linux-arm64-musl": "15.5.14", - "@next/swc-linux-x64-gnu": "15.5.14", - "@next/swc-linux-x64-musl": "15.5.14", - "@next/swc-win32-arm64-msvc": "15.5.14", - "@next/swc-win32-x64-msvc": "15.5.14", - "sharp": "^0.34.3" + "@next/swc-darwin-arm64": "16.2.4", + "@next/swc-darwin-x64": "16.2.4", + "@next/swc-linux-arm64-gnu": "16.2.4", + "@next/swc-linux-arm64-musl": "16.2.4", + "@next/swc-linux-x64-gnu": "16.2.4", + "@next/swc-linux-x64-musl": "16.2.4", + "@next/swc-win32-arm64-msvc": "16.2.4", + "@next/swc-win32-x64-msvc": "16.2.4", + "sharp": "^0.34.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", @@ -6509,9 +6870,9 @@ } }, "node_modules/next-intl": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/next-intl/-/next-intl-4.9.0.tgz", - "integrity": "sha512-MMNAjewHUw9Ke93E5/Yzhf8lqesesaXJTPlrK3FwECgn4EXG9m7Tuzy4rnDes0ogjDhQIa/Ksj/qmFnHJAOluw==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/next-intl/-/next-intl-4.9.1.tgz", + "integrity": "sha512-N7ga0CjtYcdxNvaKNIi6eJ2mmatlHK5hp8rt0YO2Omoc1m0gean242/Ukdj6+gJNiReBVcYIjK0HZeNx7CV1ug==", "funding": [ { "type": "individual", @@ -6523,11 +6884,11 @@ "@formatjs/intl-localematcher": "^0.8.1", "@parcel/watcher": "^2.4.1", "@swc/core": "^1.15.2", - "icu-minify": "^4.9.0", + "icu-minify": "^4.9.1", "negotiator": "^1.0.0", - "next-intl-swc-plugin-extractor": "^4.9.0", + "next-intl-swc-plugin-extractor": "^4.9.1", "po-parser": "^2.1.1", - "use-intl": "^4.9.0" + "use-intl": "^4.9.1" }, "peerDependencies": { "next": "^12.0.0 || ^13.0.0 || ^14.0.0 || ^15.0.0 || ^16.0.0", @@ -6540,9 +6901,9 @@ } }, "node_modules/next-intl-swc-plugin-extractor": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/next-intl-swc-plugin-extractor/-/next-intl-swc-plugin-extractor-4.9.0.tgz", - "integrity": "sha512-CAu6Qy6XiCenKsvzyCPm2cZFkGfcvhJi8N93TCnOowmzD4Br3ked7QdROusRRp4MQ1iG9u+KCLgVcM9CLDUOIQ==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/next-intl-swc-plugin-extractor/-/next-intl-swc-plugin-extractor-4.9.1.tgz", + "integrity": "sha512-8whJJ6oxJz8JqkHarggmmuEDyXgC7nEnaPhZD91CJwEWW4xp0AST3Mw17YxvHyP2vAF3taWfFbs1maD+WWtz3w==", "license": "MIT" }, "node_modules/next-intl/node_modules/@swc/core": { @@ -6585,16 +6946,6 @@ } } }, - "node_modules/next-intl/node_modules/@swc/helpers": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.21.tgz", - "integrity": "sha512-jI/VAmtdjB/RnI8GTnokyX7Ug8c+g+ffD6QRLa6XQewtnGyukKkKSk3wLTM3b5cjt1jNh9x0jfVlagdN2gDKQg==", - "extraneous": true, - "license": "Apache-2.0", - "dependencies": { - "tslib": "^2.8.0" - } - }, "node_modules/next/node_modules/postcss": { "version": "8.4.31", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz", @@ -6702,6 +7053,13 @@ "es6-promise": "^3.2.1" } }, + "node_modules/node-releases": { + "version": "2.0.37", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz", + "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==", + "dev": true, + "license": "MIT" + }, "node_modules/oas-kit-common": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/oas-kit-common/-/oas-kit-common-1.0.8.tgz", @@ -7280,24 +7638,24 @@ } }, "node_modules/react": { - "version": "19.2.4", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", - "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.5.tgz", + "integrity": "sha512-llUJLzz1zTUBrskt2pwZgLq59AemifIftw4aB7JxOqf1HY2FDaGDxgwpAPVzHU1kdWabH7FauP4i1oEeer2WCA==", "license": "MIT", "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "19.2.4", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", - "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", + "version": "19.2.5", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.5.tgz", + "integrity": "sha512-J5bAZz+DXMMwW/wV3xzKke59Af6CHY7G4uYLN1OvBcKEsWOs4pQExj86BBKamxl/Ik5bx9whOrvBlSDfWzgSag==", "license": "MIT", "dependencies": { "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^19.2.4" + "react": "^19.2.5" } }, "node_modules/react-is": { @@ -8594,6 +8952,30 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.58.2", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.58.2.tgz", + "integrity": "sha512-V8iSng9mRbdZjl54VJ9NKr6ZB+dW0J3TzRXRGcSbLIej9jV86ZRtlYeTKDR/QLxXykocJ5icNzbsl2+5TzIvcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.58.2", + "@typescript-eslint/parser": "8.58.2", + "@typescript-eslint/typescript-estree": "8.58.2", + "@typescript-eslint/utils": "8.58.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, "node_modules/uglify-js": { "version": "3.19.3", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", @@ -8689,6 +9071,37 @@ "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" } }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -8714,9 +9127,9 @@ "license": "BSD" }, "node_modules/use-intl": { - "version": "4.9.0", - "resolved": "https://registry.npmjs.org/use-intl/-/use-intl-4.9.0.tgz", - "integrity": "sha512-GehJvP7gu8SvmaDHNDNrRHt2TCNSZt4l1cGJMpUX77TGeZPAQKVQokAVvoYkeTT1UWPtv9RJ6N16UJNButzrgg==", + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/use-intl/-/use-intl-4.9.1.tgz", + "integrity": "sha512-iGVV/xFYlhe3btafRlL8RPLD2Jsuet4yqn9DR6LWWbMhULsJnXgLonDkzDmsAIBIwFtk02oJuX/Ox2vwHKF+UQ==", "funding": [ { "type": "individual", @@ -8727,7 +9140,7 @@ "dependencies": { "@formatjs/fast-memoize": "^3.1.0", "@schummar/icu-type-parser": "1.21.5", - "icu-minify": "^4.9.0", + "icu-minify": "^4.9.1", "intl-messageformat": "^11.1.0" }, "peerDependencies": { @@ -8941,6 +9354,13 @@ "node": ">=10" } }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "license": "ISC" + }, "node_modules/yaml": { "version": "1.10.3", "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", @@ -8999,6 +9419,29 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } } } } diff --git a/package.json b/package.json index 1dd6be7..e882de7 100644 --- a/package.json +++ b/package.json @@ -7,17 +7,17 @@ "dev": "next dev", "build": "next build", "start": "next start", - "lint": "next lint", + "lint": "eslint .", "lint:api": "redocly lint --config .redocly.yaml", "generate-icons": "node scripts/generate-icons.mjs" }, "dependencies": { "@heroicons/react": "^2.2.0", "clsx": "^2.1.1", - "next": "^15.5.14", - "next-intl": "^4.1.0", - "react": "^19.0.0", - "react-dom": "^19.0.0", + "next": "^16.2.4", + "next-intl": "^4.9.1", + "react": "^19.2.5", + "react-dom": "^19.2.5", "striptags": "^3.2.0" }, "devDependencies": { @@ -25,10 +25,10 @@ "@tailwindcss/postcss": "^4.0.0", "@tailwindcss/typography": "^0.5.16", "@types/node": "^22.0.0", - "@types/react": "^19.0.0", - "@types/react-dom": "^19.0.0", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", "eslint": "^9.0.0", - "eslint-config-next": "^15.2.0", + "eslint-config-next": "^16.2.4", "postcss": "^8.5.0", "tailwindcss": "^4.0.0", "typescript": "^5.7.0" diff --git a/middleware.ts b/proxy.ts similarity index 83% rename from middleware.ts rename to proxy.ts index be3f43d..6120705 100644 --- a/middleware.ts +++ b/proxy.ts @@ -1,7 +1,7 @@ import createMiddleware from 'next-intl/middleware' import { routing } from './src/i18n/routing' -export default createMiddleware(routing) +export const proxy = createMiddleware(routing) export const config = { matcher: [ diff --git a/src/i18n/request.ts b/src/i18n/request.ts index 0d5af21..eeff944 100644 --- a/src/i18n/request.ts +++ b/src/i18n/request.ts @@ -4,7 +4,7 @@ import { routing } from './routing' export default getRequestConfig(async ({ requestLocale }) => { let locale = await requestLocale - if (!locale || !routing.locales.includes(locale as any)) { + if (!locale || !(routing.locales as readonly string[]).includes(locale)) { locale = routing.defaultLocale } diff --git a/tsconfig.json b/tsconfig.json index d8b9323..e7ff3a2 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,7 +1,11 @@ { "compilerOptions": { "target": "ES2017", - "lib": ["dom", "dom.iterable", "esnext"], + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], "allowJs": true, "skipLibCheck": true, "strict": true, @@ -11,7 +15,7 @@ "moduleResolution": "bundler", "resolveJsonModule": true, "isolatedModules": true, - "jsx": "preserve", + "jsx": "react-jsx", "incremental": true, "plugins": [ { @@ -19,9 +23,19 @@ } ], "paths": { - "@/*": ["./*"] + "@/*": [ + "./*" + ] } }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + ".next/dev/types/**/*.ts" + ], + "exclude": [ + "node_modules" + ] } From 68f76bfb55d0f5f3c56515b9cd12ac20fd93ebc1 Mon Sep 17 00:00:00 2001 From: "John R. D'Orazio" Date: Sat, 18 Apr 2026 01:37:54 +0200 Subject: [PATCH 02/14] fix: handle stale loading state and add cleanup to RepoLanguages fetch Co-Authored-By: Claude Opus 4.6 (1M context) --- components/projects/RepoLanguages.tsx | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/components/projects/RepoLanguages.tsx b/components/projects/RepoLanguages.tsx index 072567e..ca7e0c7 100644 --- a/components/projects/RepoLanguages.tsx +++ b/components/projects/RepoLanguages.tsx @@ -58,11 +58,13 @@ export default function RepoLanguages({ repos, label }: RepoLanguagesProps) { useEffect(() => { if (repoIds.length === 0) return + let cancelled = false fetch(`/api/github/languages?repos=${repoIds.join(',')}`) .then((res) => (res.ok ? res.json() : null)) - .then((data) => setLanguages(data)) - .catch(() => setLanguages(null)) - .finally(() => setLoading(false)) + .then((data) => { if (!cancelled) setLanguages(data) }) + .catch(() => { if (!cancelled) setLanguages(null) }) + .finally(() => { if (!cancelled) setLoading(false) }) + return () => { cancelled = true } // eslint-disable-next-line react-hooks/exhaustive-deps }, [repos.join(',')]) From 7a29034dcdcbdf57a7d15e816a112c2c39d3e32c Mon Sep 17 00:00:00 2001 From: "John R. D'Orazio" Date: Sat, 18 Apr 2026 02:11:05 +0200 Subject: [PATCH 03/14] fix: derive loading state and use AbortController in RepoLanguages Replace imperative loading/languages reset with derived loading state based on fetchedKey comparison. Use AbortController instead of a cancelled flag for proper fetch cancellation. Co-Authored-By: Claude Opus 4.6 (1M context) --- components/projects/RepoLanguages.tsx | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/components/projects/RepoLanguages.tsx b/components/projects/RepoLanguages.tsx index ca7e0c7..6abb929 100644 --- a/components/projects/RepoLanguages.tsx +++ b/components/projects/RepoLanguages.tsx @@ -38,6 +38,8 @@ type LanguageData = Record> export default function RepoLanguages({ repos, label }: RepoLanguagesProps) { const [languages, setLanguages] = useState(null) + const [fetchedKey, setFetchedKey] = useState(null) + // Extract owner/repo from GitHub URLs const repoIds = repos .map((url) => { @@ -53,20 +55,28 @@ export default function RepoLanguages({ repos, label }: RepoLanguagesProps) { }) .filter((id): id is string => id !== null) - const [loading, setLoading] = useState(repoIds.length > 0) + const repoKey = repoIds.join(',') + const loading = repoIds.length > 0 && fetchedKey !== repoKey useEffect(() => { if (repoIds.length === 0) return - let cancelled = false - fetch(`/api/github/languages?repos=${repoIds.join(',')}`) + const controller = new AbortController() + fetch(`/api/github/languages?repos=${repoIds.join(',')}`, { signal: controller.signal }) .then((res) => (res.ok ? res.json() : null)) - .then((data) => { if (!cancelled) setLanguages(data) }) - .catch(() => { if (!cancelled) setLanguages(null) }) - .finally(() => { if (!cancelled) setLoading(false) }) - return () => { cancelled = true } + .then((data) => { + setLanguages(data) + setFetchedKey(repoKey) + }) + .catch((err) => { + if (err.name !== 'AbortError') { + setLanguages(null) + setFetchedKey(repoKey) + } + }) + return () => controller.abort() // eslint-disable-next-line react-hooks/exhaustive-deps - }, [repos.join(',')]) + }, [repoKey]) if (loading) { return ( From 40ac58194ef4c8703128b1db357520b03c4ab84d Mon Sep 17 00:00:00 2001 From: "John R. D'Orazio" Date: Sat, 18 Apr 2026 02:11:48 +0200 Subject: [PATCH 04/14] fix: use deterministic seeded RNG in FishBackground for SSR consistency Replace Math.random() with a mulberry32 seeded PRNG derived from the count prop. This ensures server and client produce identical fish layouts (no hydration mismatch) and the layout updates when count changes via useMemo. Co-Authored-By: Claude Opus 4.6 (1M context) --- components/sections/FishBackground.tsx | 34 +++++++++++++++++--------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/components/sections/FishBackground.tsx b/components/sections/FishBackground.tsx index f2ffb96..ea6ed8b 100644 --- a/components/sections/FishBackground.tsx +++ b/components/sections/FishBackground.tsx @@ -1,6 +1,6 @@ 'use client' -import { useState } from 'react' +import { useMemo } from 'react' /** * Inline SVG path data for stylized fish in various poses. @@ -52,20 +52,32 @@ interface PlacedFish { opacity: number } -function randomBetween(min: number, max: number) { - return min + Math.random() * (max - min) +/** Simple deterministic PRNG (mulberry32) seeded from count */ +function createSeededRng(seed: number) { + let s = seed | 0 + return () => { + s = (s + 0x6d2b79f5) | 0 + let t = Math.imul(s ^ (s >>> 15), 1 | s) + t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t + return ((t ^ (t >>> 14)) >>> 0) / 4294967296 + } +} + +function seededBetween(rng: () => number, min: number, max: number) { + return min + rng() * (max - min) } export default function FishBackground({ count = 5 }: { count?: number }) { - const [fish] = useState(() => { + const fish = useMemo(() => { + const rng = createSeededRng(count * 7919) const placed: PlacedFish[] = [] const minDistance = 8 // minimum % distance between fish centers for (let i = 0; i < count; i++) { let candidate: PlacedFish | null = null for (let attempt = 0; attempt < 50; attempt++) { - const x = randomBetween(5, 90) - const y = randomBetween(5, 90) + const x = seededBetween(rng, 5, 90) + const y = seededBetween(rng, 5, 90) const tooClose = placed.some((p) => { const dx = p.x - x const dy = p.y - y @@ -73,12 +85,12 @@ export default function FishBackground({ count = 5 }: { count?: number }) { }) if (!tooClose) { candidate = { - pathIndex: Math.floor(Math.random() * fishPaths.length), + pathIndex: Math.floor(rng() * fishPaths.length), x, y, - rotation: randomBetween(-35, 35), - size: randomBetween(80, 140), - opacity: randomBetween(0.1, 0.18), + rotation: seededBetween(rng, -35, 35), + size: seededBetween(rng, 80, 140), + opacity: seededBetween(rng, 0.1, 0.18), } break } @@ -87,7 +99,7 @@ export default function FishBackground({ count = 5 }: { count?: number }) { } return placed - }) + }, [count]) if (fish.length === 0) return null From bcbca5354d1344f8c475e2b1cf2e371d82251a80 Mon Sep 17 00:00:00 2001 From: "John R. D'Orazio" Date: Sat, 18 Apr 2026 02:12:13 +0200 Subject: [PATCH 05/14] fix: require ESLint >= 9.22.0 for defineConfig/globalIgnores exports The eslint.config.mjs uses defineConfig and globalIgnores which were added in ESLint 9.22.0. Bump the minimum version to prevent installs that resolve to older 9.x versions where these exports are missing. Co-Authored-By: Claude Opus 4.6 (1M context) --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 387540f..d6d59ec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,7 +23,7 @@ "@types/node": "^22.0.0", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", - "eslint": "^9.0.0", + "eslint": "^9.22.0", "eslint-config-next": "^16.2.4", "postcss": "^8.5.0", "tailwindcss": "^4.0.0", diff --git a/package.json b/package.json index e882de7..580b525 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "@types/node": "^22.0.0", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", - "eslint": "^9.0.0", + "eslint": "^9.22.0", "eslint-config-next": "^16.2.4", "postcss": "^8.5.0", "tailwindcss": "^4.0.0", From 2644219d99b068def6b3a3752a82532b4b4610ab Mon Sep 17 00:00:00 2001 From: "John R. D'Orazio" Date: Sat, 18 Apr 2026 02:12:36 +0200 Subject: [PATCH 06/14] fix: reset formData when reopening ReferLocalGroupModal Add setFormData({}) to openDialog to clear stale form values, matching the reset behavior of the other two modal components. Co-Authored-By: Claude Opus 4.6 (1M context) --- components/sections/ReferLocalGroupModal.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/components/sections/ReferLocalGroupModal.tsx b/components/sections/ReferLocalGroupModal.tsx index 97749e5..4898402 100644 --- a/components/sections/ReferLocalGroupModal.tsx +++ b/components/sections/ReferLocalGroupModal.tsx @@ -22,6 +22,7 @@ export default function ReferLocalGroupModal({ buttonLabel }: ReferLocalGroupMod setStatus('idle') setVerificationCode('') setCodeError('') + setFormData({}) openedAtRef.current = Date.now() dialogRef.current?.showModal() }, []) From d4b58e4b9cb510a9caf0ed5d1e77234cd3e163df Mon Sep 17 00:00:00 2001 From: "John R. D'Orazio" Date: Sat, 18 Apr 2026 02:27:42 +0200 Subject: [PATCH 07/14] fix: add formKey to force remount of uncontrolled form inputs Uncontrolled inputs with defaultValue only read the value on mount. Add a formKey counter that increments on openDialog and handleBackToForm, used as key on the
element to force React to remount inputs with current formData values. Co-Authored-By: Claude Opus 4.6 (1M context) --- components/sections/ReferCommunityProjectModal.tsx | 5 ++++- components/sections/ReferLocalGroupModal.tsx | 5 ++++- components/sections/SubmitProjectModal.tsx | 5 ++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/components/sections/ReferCommunityProjectModal.tsx b/components/sections/ReferCommunityProjectModal.tsx index 2834079..3f58a64 100644 --- a/components/sections/ReferCommunityProjectModal.tsx +++ b/components/sections/ReferCommunityProjectModal.tsx @@ -14,6 +14,7 @@ export default function ReferCommunityProjectModal({ buttonLabel }: ReferCommuni const dialogRef = useRef(null) const openedAtRef = useRef(0) const [formData, setFormData] = useState<{ fields: Record; tags: string[] }>({ fields: {}, tags: [] }) + const [formKey, setFormKey] = useState(0) const [status, setStatus] = useState('idle') const [verificationCode, setVerificationCode] = useState('') const [codeError, setCodeError] = useState('') @@ -26,6 +27,7 @@ export default function ReferCommunityProjectModal({ buttonLabel }: ReferCommuni setCodeError('') setTags([]) setTagInput('') + setFormKey((k) => k + 1) openedAtRef.current = Date.now() setFormData({ fields: {}, tags: [] }) dialogRef.current?.showModal() @@ -147,6 +149,7 @@ export default function ReferCommunityProjectModal({ buttonLabel }: ReferCommuni setStatus('idle') setVerificationCode('') setCodeError('') + setFormKey((k) => k + 1) } const isCodeView = status === 'awaiting_code' || status === 'submitting' @@ -272,7 +275,7 @@ export default function ReferCommunityProjectModal({ buttonLabel }: ReferCommuni )} - + {/* Honeypot — hidden from real users */} )} - + {/* Honeypot — hidden from real users */} )} - + {/* Honeypot — hidden from real users */} )} - + { handleSendCode(e) }} className="space-y-4"> {/* Honeypot — hidden from real users */} )} - + { handleSendCode(e) }} className="space-y-4"> {/* Honeypot — hidden from real users */} )} - + { handleSendCode(e) }} className="space-y-4"> {/* Honeypot — hidden from real users */} )} - { handleSendCode(e) }} className="space-y-4"> + { void handleSendCode(e) }} className="space-y-4"> {/* Honeypot — hidden from real users */} )} - { handleSendCode(e) }} className="space-y-4"> + { void handleSendCode(e) }} className="space-y-4"> {/* Honeypot — hidden from real users */} )} - { handleSendCode(e) }} className="space-y-4"> + { void handleSendCode(e) }} className="space-y-4"> {/* Honeypot — hidden from real users */}