Refactor components to use custom hooks

- Extract login form logic into useLoginForm hook
- Create useAuthRedirect hook for auth-based navigation
- Refactor LoginForm component to use useLoginForm (147 -> 135 LOC)
- Refactor login page to use useAuthRedirect (23 -> 14 LOC)
- Update dashboard to use useAuthRedirect for cleaner code
- Improve code reusability and testability

https://claude.ai/code/session_01U3wVqokhrL3dTeq2dTq73n
This commit is contained in:
Claude
2026-01-30 22:54:14 +00:00
parent 2c34509d0f
commit 87daa3add3
5 changed files with 75 additions and 43 deletions

View File

@@ -16,14 +16,15 @@ import {
useTheme,
} from '@mui/material';
import { Logout, Refresh, Inventory2 } from '@mui/icons-material';
import { useAppDispatch, useAppSelector } from '@/lib/store/hooks';
import { useAppDispatch } from '@/lib/store/hooks';
import { logout as logoutAction } from '@/lib/store/authSlice';
import { useAuthRedirect } from '@/lib/hooks/useAuthRedirect';
import { apiClient, Container as ContainerType } from '@/lib/api';
import ContainerCard from '@/components/ContainerCard';
import TerminalModal from '@/components/TerminalModal';
export default function Dashboard() {
const { isAuthenticated, loading: authLoading } = useAppSelector((state) => state.auth);
const { isAuthenticated, loading: authLoading } = useAuthRedirect('/');
const dispatch = useAppDispatch();
const router = useRouter();
const theme = useTheme();
@@ -35,12 +36,6 @@ export default function Dashboard() {
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState('');
useEffect(() => {
if (!authLoading && !isAuthenticated) {
router.push('/');
}
}, [isAuthenticated, authLoading, router]);
const fetchContainers = async () => {
setIsRefreshing(true);
setError('');

View File

@@ -1,19 +1,10 @@
'use client';
import { useEffect } from 'react';
import { useRouter } from 'next/navigation';
import { useAppSelector } from '@/lib/store/hooks';
import { useAuthRedirect } from '@/lib/hooks/useAuthRedirect';
import LoginForm from '@/components/LoginForm';
export default function Home() {
const { isAuthenticated, loading } = useAppSelector((state) => state.auth);
const router = useRouter();
useEffect(() => {
if (!loading && isAuthenticated) {
router.push('/dashboard');
}
}, [isAuthenticated, loading, router]);
const { loading } = useAuthRedirect('/dashboard');
if (loading) {
return null;

View File

@@ -1,6 +1,6 @@
'use client';
import React, { useState } from 'react';
import React from 'react';
import {
Card,
CardContent,
@@ -11,31 +11,19 @@ import {
Alert,
} from '@mui/material';
import { LockOpen } from '@mui/icons-material';
import { useAppDispatch, useAppSelector } from '@/lib/store/hooks';
import { login, clearError } from '@/lib/store/authSlice';
import { useRouter } from 'next/navigation';
import { useLoginForm } from '@/lib/hooks/useLoginForm';
export default function LoginForm() {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [isShaking, setIsShaking] = useState(false);
const dispatch = useAppDispatch();
const { error, loading } = useAppSelector((state) => state.auth);
const router = useRouter();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
dispatch(clearError());
const result = await dispatch(login({ username, password }));
if (login.fulfilled.match(result)) {
router.push('/dashboard');
} else {
setIsShaking(true);
setTimeout(() => setIsShaking(false), 500);
}
};
const {
username,
setUsername,
password,
setPassword,
isShaking,
error,
loading,
handleSubmit,
} = useLoginForm();
return (
<Box

View File

@@ -0,0 +1,20 @@
import { useEffect } from 'react';
import { useRouter } from 'next/navigation';
import { useAppSelector } from '@/lib/store/hooks';
export function useAuthRedirect(redirectTo: '/dashboard' | '/') {
const { isAuthenticated, loading } = useAppSelector((state) => state.auth);
const router = useRouter();
useEffect(() => {
if (loading) return;
if (redirectTo === '/dashboard' && isAuthenticated) {
router.push('/dashboard');
} else if (redirectTo === '/' && !isAuthenticated) {
router.push('/');
}
}, [isAuthenticated, loading, router, redirectTo]);
return { isAuthenticated, loading };
}

View File

@@ -0,0 +1,38 @@
import { useState } from 'react';
import { useRouter } from 'next/navigation';
import { useAppDispatch, useAppSelector } from '@/lib/store/hooks';
import { login, clearError } from '@/lib/store/authSlice';
export function useLoginForm() {
const [username, setUsername] = useState('');
const [password, setPassword] = useState('');
const [isShaking, setIsShaking] = useState(false);
const dispatch = useAppDispatch();
const { error, loading } = useAppSelector((state) => state.auth);
const router = useRouter();
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
dispatch(clearError());
const result = await dispatch(login({ username, password }));
if (login.fulfilled.match(result)) {
router.push('/dashboard');
} else {
setIsShaking(true);
setTimeout(() => setIsShaking(false), 500);
}
};
return {
username,
setUsername,
password,
setPassword,
isShaking,
error,
loading,
handleSubmit,
};
}