why is this react login page not working?

24 views Asked by At

I am trying to make a react app I want to make some login that will then give access to the main site. the problem is that it just doesn't really do its job at all. on initial login with correct password the private page doesn't load. but if i then go back and just click login without inputing password or login it loads fine. it also doesn't redirect if an incorrect password has been entered.

This app is built in react and typescript in next.js but I cannot use any server side rendering as this is going on a static site.

here is the login page code

'use client'
import React, {useState} from 'react';
import { AuthProvider, useAuth } from './AuthContext';
import { useNavigate } from 'react-router-dom';
import './index.css';

const LoginComp: React.FC = () => {
    const navigate = useNavigate();
    const { signin, isAuthenticated } = useAuth();
    const [username, setUsername] = useState('');
    const [password, setPassword] = useState('');
  
    const handleLogin = () => {
      try{
        signin(username, password);
        navigate('/test');
      }
      catch{
        navigate('/');
      }
      
    };
  
    return (
      <div className="container">
          <div className="content">
              <form id='form'>
                  <img src='picture url' width='100%' alt='Logo' /><br />
                  <input id="uname" type="username" placeholder="Username" value={username} onChange={(e) => setUsername(e.target.value)} /><br />
                  <input id="pword" type="password" placeholder="Password" value={password} onChange={(e) => setPassword(e.target.value)} /><br />
                  <p id="error"></p>
                  <button id="submit" onClick={handleLogin}>Login</button>
              </form>
          </div>
      </div>
    );
  };
  const LoginPage: React.FC = () => {
    return(
        <AuthProvider>
            <LoginComp />
        </AuthProvider>
    );
  }
  export default LoginPage;

here is the routing file

'use client'
import React from 'react';
import { BrowserRouter, Route, Routes, HashRouter  } from 'react-router-dom';
import PrivateRoutes from './PrivateRoutes';
import LoginPage from './login';
import PrivatePage from './home';

const App: React.FC = () => {
    return (
        <BrowserRouter >
            <Routes>
                <Route  element={<LoginPage />} path="/"/>
                <Route element={<PrivateRoutes />}>
                    <Route element={<PrivatePage comp_name='/test'/>} path='/test'/>
                </Route>
            </Routes>
        </BrowserRouter>
    );
};
export default App;

here is the private page file

'use client'
import React from 'react';
import { Navigate, Outlet } from 'react-router-dom';

import { AuthProvider, useAuth } from './AuthContext';

function PrivateRouting() {
    const { isAuthenticated } = useAuth();
    return (
        isAuthenticated ? <Outlet /> : <Navigate to="/test" />
    );
}
function PrivateRoutes() {
    return(
        <AuthProvider>
            <PrivateRouting />
        </AuthProvider>
    );
};
export default PrivateRoutes;

and here is my auth file

'use client'
import React, { createContext, useContext, useState } from 'react';
import Cookies from 'js-cookie';

interface AuthContextType {
    isAuthenticated: boolean;
    signin: (username: string, password: string) => Promise<void>; // Update signin function to return a promise
    signout: () => void;
}

const AuthContext = createContext<AuthContextType | undefined>(undefined);

export const useAuth = () => {
    const context = useContext(AuthContext);
    if (!context) {
        throw new Error('useAuth must be used within an AuthProvider');
    }
    return context;
};

interface AuthProviderProps {
    children: React.ReactNode; // Define children prop
}

export const AuthProvider: React.FC<AuthProviderProps> = ({ children }) => {
    const [isAuthenticated, setIsAuthenticated] = useState<boolean>(() => {
        // Check cookies for authentication token
        return !!Cookies.get('token');
    });

    const signin = async (username: string, password: string) => {
        try {
            const formData = new FormData();
            formData.append('uname', username);
            formData.append('pword', password);

            const response = await fetch('https link to my public domain', {
                method: 'POST',
                body: formData,
            });

            if (!response.ok) {
                throw new Error('Authentication failed');
            }

            const data = await response.json();
            const token = data.token;
            const expiry = data.expiry;

            // Check if token and expiry exist
            if (!token || !expiry) {
                throw new Error('Token or expiry not received');
            }

            // Store the token in cookies with the provided expiry time
            const expirationTime = new Date();
            expirationTime.setMinutes(expirationTime.getMinutes() + 2);

            Cookies.set('token', token, { expires: expirationTime });
            setIsAuthenticated(true);
        } catch (error) {
            console.error('Signin failed:', error);
            throw error;
        }
    };


    const signout = () => {
        // Remove token from cookies
        Cookies.remove('token');
        setIsAuthenticated(false);
    };

    return (
        <AuthContext.Provider value={{ isAuthenticated, signin, signout }}>
            {children}
        </AuthContext.Provider>
    );
};

every time I make some change it just breaks more.

I am very new to react and have some amount of web development experience, so any general tips for react would be appreciated as well.

1

There are 1 answers

0
Hashan Hemachandra On BEST ANSWER

Your handleLogin function in the login component calls the signin method but does not wait for the signin to complete before navigating. Since signin is an async function, you should await its completion before navigating.

const handleLogin = async (e) => {
  e.preventDefault(); // Prevent default form submission
  try {
    await signin(username, password);
    navigate('/test');
  } catch {
    navigate('/');
  }
};

Also you're using useNavigate from react-router-dom, which is not a part of Next.js. In Next.js, you should use useRouter from next/navigation for navigation,

import { useRouter } from 'next/navigation';

// Inside your component
const router = useRouter();

// To navigate
router.push('/test');

In your PrivateRouting component, you're redirecting unauthenticated users to /test, which seems to be your protected route. You should redirect them to the login page (/) instead if they are not authenticated,

return (
    isAuthenticated ? <Outlet /> : <Navigate to="/" />
);