Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,25 @@ const eslintConfig = [
...nextTypeScript,
{
rules: {
"react-hooks/set-state-in-effect": "warn",
// React 19 compiler rule that fires on the standard initial-data-fetch
// pattern (useEffect on mount + setState with the result). Suppressing
// 33 individual call sites is noisier than the warning itself; revisit
// when we adopt a fetcher library (SWR/TanStack Query) that obviates
// the pattern.
"react-hooks/set-state-in-effect": "off",
// Keeps catching the real stale-closure risk (function referenced
// before declared in useEffect deps). Satisfied by wrapping fetchers
// in useCallback.
"react-hooks/immutability": "warn",
"react-hooks/preserve-manual-memoization": "warn",
"no-console": ["error", { allow: ["warn", "error"] }],
// Standard convention: a leading underscore signals "intentionally
// unused" (e.g. function-signature params kept for API compatibility).
"@typescript-eslint/no-unused-vars": ["warn", {
argsIgnorePattern: "^_",
varsIgnorePattern: "^_",
caughtErrorsIgnorePattern: "^_",
}],
},
},
{
Expand Down
54 changes: 27 additions & 27 deletions src/app/adif/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { useState, useEffect } from 'react';
import { useState, useEffect, useCallback } from 'react';
import { useRouter } from 'next/navigation';
import Link from 'next/link';
import { Button } from '@/components/ui/button';
Expand Down Expand Up @@ -66,37 +66,14 @@ export default function ADIFPage() {

const router = useRouter();

useEffect(() => {
fetchStations();
}, []); // eslint-disable-line react-hooks/exhaustive-deps

// Auto-select default station when stations are loaded
useEffect(() => {
if (stations.length > 0 && !selectedStationId) {
const defaultStation = stations.find((s: Station) => s.is_default);

let defaultId = '';
if (defaultStation) {
defaultId = defaultStation.id.toString();
} else if (stations.length === 1) {
defaultId = stations[0].id.toString();
}

if (defaultId) {
setSelectedStationId(defaultId);
setExportStationId(defaultId);
}
}
}, [stations, selectedStationId]);

const fetchStations = async () => {
const fetchStations = useCallback(async () => {
try {
const response = await fetch('/api/stations');
if (response.status === 401) {
router.push('/login');
return;
}

const data = await response.json();
if (response.ok) {
const stations = data.stations || [];
Expand All @@ -110,7 +87,30 @@ export default function ADIFPage() {
setImportError('Network error. Please try again.');
setStationsLoaded(true);
}
};
}, [router]);

useEffect(() => {
fetchStations();
}, [fetchStations]);

// Auto-select default station when stations are loaded
useEffect(() => {
if (stations.length > 0 && !selectedStationId) {
const defaultStation = stations.find((s: Station) => s.is_default);

let defaultId = '';
if (defaultStation) {
defaultId = defaultStation.id.toString();
} else if (stations.length === 1) {
defaultId = stations[0].id.toString();
}

if (defaultId) {
setSelectedStationId(defaultId);
setExportStationId(defaultId);
}
}
}, [stations, selectedStationId]);

const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const selectedFile = e.target.files?.[0];
Expand Down
42 changes: 21 additions & 21 deletions src/app/admin/storage/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { useEffect, useState } from 'react';
import { useEffect, useState, useCallback } from 'react';
import { useRouter } from 'next/navigation';
import { useUser } from '@/contexts/UserContext';
import Navbar from '@/components/Navbar';
Expand Down Expand Up @@ -49,29 +49,12 @@ export default function StorageConfigPage() {
is_enabled: false
});

useEffect(() => {
if (!loading) {
if (!user) {
router.push('/login');
return;
}

if (user.role !== 'admin') {
router.push('/dashboard');
return;
}

setIsAuthorized(true);
fetchConfigs();
}
}, [user, loading, router]);

const fetchConfigs = async () => {
const fetchConfigs = useCallback(async () => {
try {
setError('');
const response = await fetch('/api/admin/storage');
const data = await response.json();

if (response.ok) {
setConfigs(data.configs || []);
} else {
Expand All @@ -82,7 +65,24 @@ export default function StorageConfigPage() {
} finally {
setIsLoading(false);
}
};
}, []);

useEffect(() => {
if (!loading) {
if (!user) {
router.push('/login');
return;
}

if (user.role !== 'admin') {
router.push('/dashboard');
return;
}

setIsAuthorized(true);
fetchConfigs();
}
}, [user, loading, router, fetchConfigs]);

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
Expand Down
42 changes: 21 additions & 21 deletions src/app/admin/users/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { useEffect, useState } from 'react';
import { useEffect, useState, useCallback } from 'react';
import { useRouter } from 'next/navigation';
import { useUser } from '@/contexts/UserContext';
import Navbar from '@/components/Navbar';
Expand Down Expand Up @@ -51,29 +51,12 @@ export default function UserManagementPage() {
status: 'active'
});

useEffect(() => {
if (!loading) {
if (!user) {
router.push('/login');
return;
}

if (user.role !== 'admin') {
router.push('/dashboard');
return;
}

setIsAuthorized(true);
fetchUsers();
}
}, [user, loading, router]);

const fetchUsers = async () => {
const fetchUsers = useCallback(async () => {
try {
setError('');
const response = await fetch('/api/admin/users');
const data = await response.json();

if (response.ok) {
setUsers(data.users || []);
} else {
Expand All @@ -84,7 +67,24 @@ export default function UserManagementPage() {
} finally {
setIsLoading(false);
}
};
}, []);

useEffect(() => {
if (!loading) {
if (!user) {
router.push('/login');
return;
}

if (user.role !== 'admin') {
router.push('/dashboard');
return;
}

setIsAuthorized(true);
fetchUsers();
}
}, [user, loading, router, fetchUsers]);

const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
Expand Down
20 changes: 10 additions & 10 deletions src/app/awards/dxcc/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { useState, useEffect } from 'react';
import { useState, useEffect, useCallback } from 'react';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
Expand All @@ -19,17 +19,11 @@ export default function DXCCPage() {
const [error, setError] = useState<string | null>(null);
const [selectedTab, setSelectedTab] = useState('overview');

useEffect(() => {
if (user) {
fetchDXCCSummary();
}
}, [user]);

const fetchDXCCSummary = async () => {
const fetchDXCCSummary = useCallback(async () => {
try {
setLoading(true);
setError(null);

const response = await fetch('/api/awards/dxcc/summary');
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
Expand All @@ -47,7 +41,13 @@ export default function DXCCPage() {
} finally {
setLoading(false);
}
};
}, []);

useEffect(() => {
if (user) {
fetchDXCCSummary();
}
}, [user, fetchDXCCSummary]);

const getNeededEntities = (): DXCCEntityProgress[] => {
if (!summary) return [];
Expand Down
34 changes: 17 additions & 17 deletions src/app/awards/was/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use client';

import { useState, useEffect } from 'react';
import { useState, useEffect, useCallback } from 'react';
import { useRouter } from 'next/navigation';
import Link from 'next/link';
import { Button } from '@/components/ui/button';
Expand All @@ -24,21 +24,7 @@ export default function WASPage() {
const { user, loading: userLoading } = useUser();
const router = useRouter();

useEffect(() => {
// Wait for user context to finish loading
if (userLoading) return;

// Redirect to login if no user
if (!user) {
router.push('/login');
return;
}

// Load stations data
loadStations();
}, [user, userLoading, router]);

const loadStations = async () => {
const loadStations = useCallback(async () => {
try {
setPageLoading(true);
setError(null);
Expand All @@ -56,7 +42,21 @@ export default function WASPage() {
} finally {
setPageLoading(false);
}
};
}, []);

useEffect(() => {
// Wait for user context to finish loading
if (userLoading) return;

// Redirect to login if no user
if (!user) {
router.push('/login');
return;
}

// Load stations data
loadStations();
}, [user, userLoading, router, loadStations]);

if (pageLoading || userLoading) {
return (
Expand Down
Loading
Loading