Объясни ОЧЕНЬ ПОДРОБНО как работает флоу данных тут: loginpage.jsx import React, { useState } from 'react';
import { useDispatch } from 'react-redux';
import { useNavigate } from 'react-router-dom';
import { loginThunk } from '../../Redux/actions.js';
import logo from '../../../img/LogoSpring.svg';
import style from './AuthIn.module.css';

export const LoginPage = () => {
    const [username, setUsername] = useState('');
    const [password, setPassword] = useState('');
    const dispatch = useDispatch();
    const navigate = useNavigate();

    const handleSubmit = async (e) => {
        e.preventDefault();
        try {
            await dispatch(loginThunk(username, password));
            navigate('/');
        } catch (error) {
            console.error('Login error:', error);
            alert('Invalid username or password');
        }
    };

    return (
        <div className={style.form_login_wrapper}>
            <img
                className={style.header__logo}
                alt="Spring Framework Logo"
                src={logo}
            />
            <h1 className={style.form_title}>Welcome back!</h1>
            <form onSubmit={handleSubmit} className={style.form_login}>
                <input
                    className={style.form_login_input}
                    type="text"
                    placeholder="Username"
                    value={username}
                    onChange={(e) => setUsername(e.target.value)}
                />
                <input
                    className={style.form_login_input}
                    type="password"
                    placeholder="Password"
                    value={password}
                    onChange={(e) => setPassword(e.target.value)}
                />
                <button type="submit" className={style.form_button_submit}>
                    Login
                </button>
                <button onClick={() => navigate('/signup')} className={style.switch_form_button}>
                Сreate an account
            </button>
            </form>
        </div>
    );
}; actions.js import { fetchWithAuth } from './api.js';

export const LOGIN_SUCCESS = 'LOGIN_SUCCESS';
export const LOGIN_FAILURE = 'LOGIN_FAILURE';
export const FETCH_CARDS_SUCCESS = 'FETCH_CARDS_SUCCESS';
export const FETCH_CARDS_FAILURE = 'FETCH_CARDS_FAILURE';

export const loginSuccess = (username) => ({
    type: LOGIN_SUCCESS,
    payload: username
});

export const loginFailure = (error) => ({
    type: LOGIN_FAILURE,
    payload: error
});


export const loginThunk = (username, password) => {
    return async (dispatch) => {
        try {
            const response = await fetch('http://localhost:5000/login', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify({ username, password })
            });

            if (response.status === 200) {
                const data = await response.json();
                localStorage.setItem('accessToken', data.accessToken);
                localStorage.setItem('refreshToken', data.refreshToken);
                dispatch(loginSuccess(data.username));
            } else {
                const { message } = await response.json();
                dispatch(loginFailure(message));
            }
        } catch {
            dispatch(loginFailure('Network error'));
        }
    };
};
export const fetchCardsSuccess = (cards) => ({
    type: FETCH_CARDS_SUCCESS,
    payload: cards
});

export const fetchCardsFailure = (error) => ({
    type: FETCH_CARDS_FAILURE,
    payload: error
});

export const fetchCards = (searchTerm = '') => {
    return async (dispatch) => {
        try {
            const response = await fetchWithAuth(
                `http://localhost:5000/api/cards?search=${encodeURIComponent(searchTerm)}`
            );

            if (!response.ok) {
                throw new Error('Failed to fetch cards');
            }

            const data = await response.json();
            dispatch(fetchCardsSuccess(data));
        } catch (error) {
            console.error(error);
            dispatch(fetchCardsFailure(error.message));
        }
    };
};

export const logout = () => {
    return (dispatch) => {
        localStorage.removeItem('accessToken');
        localStorage.removeItem('refreshToken');
        localStorage.removeItem('username');
        dispatch({ type: 'LOGOUT' });
    };
}; api.js export const fetchWithAuth = async (url, options = {}) => {
    let accessToken = localStorage.getItem('accessToken');
    let refreshToken = localStorage.getItem('refreshToken');

    const redirectToLogin = () => {
        localStorage.removeItem('accessToken');
        localStorage.removeItem('refreshToken');
        window.location.href = '/login';
    };
    options.headers = {
        ...options.headers,
        'Authorization': `Bearer ${accessToken}`,
    };

    let response = await fetch(url, options);

    if (response.status === 401) {
        console.log('Access token expired. Attempting to refresh...');

        const refreshResponse = await fetch('http://localhost:5000/refresh', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ refreshToken })
        });

        if (refreshResponse.status === 200) {
            const data = await refreshResponse.json();
            accessToken = data.accessToken;
            localStorage.setItem('accessToken', accessToken);

            console.log('Access token refreshed.');

            options.headers['Authorization'] = `Bearer ${accessToken}`;
            response = await fetch(url, options);

            if (response.status === 401 || response.status === 403) {
                console.log('Retried request failed after token refresh. Redirecting to login.');
                redirectToLogin();
                throw new Error('Session expired. Please log in again.');
            }
        } else {
            console.log('Refresh token expired or invalid. Redirecting to login.');
            redirectToLogin();
            throw new Error('Session expired. Please log in again.');
        }
    } else if (response.status === 403) {
        console.log('Access forbidden. Redirecting to login.');
        redirectToLogin();
        throw new Error('Access forbidden. Please log in again.');
    }

    return response;
}; authReducer.js import { LOGIN_SUCCESS, LOGIN_FAILURE } from './actions';

const initialState = {
    isAuthenticated: !!localStorage.getItem('accessToken'),
    error: null,
    username: localStorage.getItem('username') || ''
};
export const authReducer = (state = initialState, action) => {
    switch (action.type) {
        case LOGIN_SUCCESS:
            localStorage.setItem('username', action.payload);
            return { ...state, isAuthenticated: true, error: null, username: action.payload };
        case LOGIN_FAILURE:
            return { ...state, isAuthenticated: false, error: action.payload, username: '' };
        case 'LOGOUT':
            localStorage.removeItem('username');
            return { ...state, isAuthenticated: false, error: null, username: '' };
        default:
            return state;
    }
}; logger.js export const loggerMiddleware = ({ getState }) => (next) => (action) => {
    console.log('Dispatching action:', action);
    const result = next(action);
    console.log('Next state:', getState()); 
    return result; 
}; store.js import { createStore, combineReducers, applyMiddleware } from 'redux';
import { thunkMiddleware } from './thunk';
import { cardsReducer } from './reducer';
import { authReducer } from './authReducer';
import { loggerMiddleware } from './logger'; 

const rootReducer = combineReducers({
    cards: cardsReducer,
    auth: authReducer,
});

export const store = createStore(
    rootReducer,
    applyMiddleware(thunkMiddleware, loggerMiddleware)
);
 thunk.js export const thunkMiddleware =
    ({ dispatch, getState }) =>
    (next) =>
    (action) => {
        if (typeof action === 'function') {
            return action(dispatch, getState);
        }
        return next(action);
    };