Skip to content
Open

Upd #56

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

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@
"preview": "vite preview"
},
"dependencies": {
"@reduxjs/toolkit": "^2.5.0",
"@reduxjs/toolkit": "^2.6.0",
"axios": "^1.8.1",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-redux": "^9.2.0"
},
"devDependencies": {
"@eslint/js": "^9.17.0",
"@types/axios": "^0.9.36",
"@types/react": "^18.3.18",
"@types/react-dom": "^18.3.5",
"@types/react-redux": "^7.1.34",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import React from 'react';
import Counter from './components/counter';
import CounterAsync from './components/counterAsync';
import CurrencyRates from './components/currencyRatesAdvanced';

const App: React.FC = () => {
return (
<div>
<h1>Redux + React</h1>
{/* <Counter /> */}
<CounterAsync />
<Counter />
{/* <CounterAsync /> */}
{/* <CurrencyRates /> */}
</div>
);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const Counter: React.FC = () => {

return (
<div>
<h1>{count}</h1>
<h1>Счетчик: {count}</h1>
<button onClick={() => dispatch(increment())}>Увеличить</button>
<button onClick={() => dispatch(decrement())}>Уменьшить</button>
<button onClick={() => dispatch(incrementByAmount(5))}>Увеличить на 5</button>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
import React, { useEffect } from 'react';
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { RootState, AppDispatch } from '../store/store';
import { increment, decrement, incrementByAmount, fetchRandomNumber } from '../store/counterSliceAsync';

const Counter: React.FC = () => {
const count = useSelector((state: RootState) => state.counter.value);
const status = useSelector((state: RootState) => state.counter.status);
const error = useSelector((state: RootState) => state.counter.error);
const count = useSelector((state: RootState) => state.counterAsync.value);
const status = useSelector((state: RootState) => state.counterAsync.status);
const error = useSelector((state: RootState) => state.counterAsync.error);
const dispatch: AppDispatch = useDispatch();

// Загружаем случайное число при монтировании компонента с помощью useEffect
useEffect(() => {
dispatch(fetchRandomNumber());
}, [dispatch]);


return (
<div>
<h1>Счетчик: {count}</h1>
Expand All @@ -25,7 +20,7 @@ const Counter: React.FC = () => {
<button onClick={() => dispatch(decrement())}>Уменьшить</button>
<button onClick={() => dispatch(incrementByAmount(5))}>Увеличить на 5</button>
{/* Кнопка для повторной загрузки случайного числа */}
<button onClick={() => dispatch(fetchRandomNumber())}>Загрузить случайное число</button>
<button onClick={() => dispatch(fetchRandomNumber())}>Загрузить случайное число (Async)</button>
</div>
);
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// CurrencyRates.tsx
import React, { useEffect, useState } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fetchCurrencyRates, setBaseCurrency } from '../store/currencySlice';
import { RootState, AppDispatch } from '../store/store';

const CurrencyRates: React.FC = () => {
const dispatch: AppDispatch = useDispatch();
const { rates, baseCurrency, status, error } = useSelector((state: RootState) => state.currency);
const [selectedCurrency, setSelectedCurrency] = useState(baseCurrency);

useEffect(() => {
if (status === 'idle') {
dispatch(fetchCurrencyRates(baseCurrency));
}
}, [status, baseCurrency, dispatch]);

const handleCurrencyChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
const newCurrency = event.target.value;
setSelectedCurrency(newCurrency);
dispatch(setBaseCurrency(newCurrency));
dispatch(fetchCurrencyRates(newCurrency));
};

if (status === 'loading') {
return <div>Загрузка...</div>;
}

if (status === 'failed') {
return <div>Ошибка: {error}</div>;
}

return (
<div>
<h1>Курсы валют</h1>
<div>
<label htmlFor="baseCurrency">Валюта: </label>
<select id="baseCurrency" value={selectedCurrency} onChange={handleCurrencyChange}>
{Object.keys(rates).map((currency) => (
<option key={currency} value={currency}>
{currency}
</option>
))}
</select>
</div>
<ul>
{Object.entries(rates).map(([currency, rate]) => (
<li key={currency}>
{currency}: {rate}
</li>
))}
</ul>
</div>
);
};

export default CurrencyRates;
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ interface CounterState {

// initialState — это начальное состояние счетчика
const initialState: CounterState = {
value: 0,
value: 1,
};

//Слайс — это часть хранилища, которая содержит редьюсер и действия
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ interface CounterStateAsync {

// Начальное состояние счетчика
const initialState: CounterStateAsync = {
value: 0,
value: 2,
status: 'idle',
error: null,
};
Expand All @@ -34,7 +34,7 @@ export const fetchRandomNumber = createAsyncThunk(

// Создаем слайс (slice) для счетчика
const counterSliceAsync = createSlice({
name: 'counter',
name: 'counterAsync',
initialState,
reducers: {
increment: (state) => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { createSlice, createAsyncThunk, PayloadAction } from '@reduxjs/toolkit';
import axios from 'axios';

interface CurrencyState {
rates: { [key: string]: number };
baseCurrency: string;
status: 'idle' | 'loading' | 'succeeded' | 'failed';
error: string | null;
}

const initialState: CurrencyState = {
rates: {},
baseCurrency: 'USD', // Начальная основная валюта
status: 'idle',
error: null,
};

// Асинхронный thunk для получения курсов валют
export const fetchCurrencyRates = createAsyncThunk(
'currency/fetchCurrencyRates',
async (baseCurrency: string) => {
const response = await axios.get(`https://api.exchangerate-api.com/v4/latest/${baseCurrency}`);
const data = response.data as { rates: { [key: string]: number } };
return { rates: data.rates, baseCurrency };
}
);

const currencySlice = createSlice({
name: 'currency',
initialState,
reducers: {
// редьюсер для изменения основной валюты
setBaseCurrency: (state, action: PayloadAction<string>) => {
state.baseCurrency = action.payload;
},
},
// Добавляем обработчики для асинхронного thunk
extraReducers: (builder) => {
builder
// Обработчики для состояния загрузки
.addCase(fetchCurrencyRates.pending, (state) => {
state.status = 'loading';
})
// Обработчики для успешного получения данных
.addCase(fetchCurrencyRates.fulfilled, (state, action: PayloadAction<{ rates: { [key: string]: number }, baseCurrency: string }>) => {
state.status = 'succeeded';
state.rates = action.payload.rates;
state.baseCurrency = action.payload.baseCurrency;
})
// Обработчики для ошибки
.addCase(fetchCurrencyRates.rejected, (state, action) => {
state.status = 'failed';
state.error = action.error.message || 'Something went wrong';
});
},
});

export const { setBaseCurrency } = currencySlice.actions; // Экспортируем действие
export default currencySlice.reducer;
Original file line number Diff line number Diff line change
@@ -1,12 +1,16 @@
import { configureStore } from '@reduxjs/toolkit'; // Импортируем функцию configureStore из библиотеки @reduxjs/toolkit
import counterReducerAsync from './counterSliceAsync'; // Импортируем редьюсер из слайса
import counterReducer from './counterSlice'; // Импортируем редьюсер из слайса
import counterReducerAsync from './counterSliceAsync';
import currencyReducer from './currencySlice';

// configureStore — это функция, которая создает хранилище
// reducer — это объект, который содержит редьюсеры
// counterReducerAsync — это редьюсер счетчика
export const store = configureStore({
reducer: {
counter: counterReducerAsync,
counter: counterReducer,
counterAsync: counterReducerAsync,
currency: currencyReducer,
},
});

Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,20 @@
import React from 'react';
import { BrowserRouter as Router, Route, Routes, Link } from 'react-router-dom';
import './App.css';
import { NotFound } from './Components/NotFound';

// Компонент для главной страницы
// Компонент главной страницы
const Home: React.FC = () => {
return <>
<div className="page">
<h1>Добро пожаловать на главную страницу!</h1>
<h1>Добро пожаловать!</h1>
</div>
</>;
};

// Компонент для страницы "О нас"
// Компонент страницы "О нас"
const About: React.FC = () => {
return <h1>О нас</h1>;
};

// Компонент для страницы 404
const NotFound: React.FC = () => {
return <h1>Страница не найдена</h1>;
return <div className="page"><h2>Тема занятия:</h2><div>Изучаем React Router</div></div>;
};

// Основной компонент приложения
Expand All @@ -31,14 +27,14 @@ const BasicApp: React.FC = () => {
<Link to="/">Главная</Link>
</li>
<li>
<Link to="/about">О нас</Link>
<Link to="/topic">Тема занятия</Link>
</li>
</ul>
</nav>

<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
<Route path="/topic" element={<About />} />
<Route path="*" element={<NotFound />} /> {/* Страница 404 */}
</Routes>
</Router>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,60 @@
import React from 'react';
import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom';
import React, { useState } from 'react';
import { BrowserRouter as Router, Routes, Route, Navigate, useNavigate, Link } from 'react-router-dom';

const PrivatePage: React.FC = () => {
const isAuthenticated = false; // Пример проверки авторизации
const Home: React.FC = () => {
return <>
<div className="page">
<h2>Главная страница!</h2>
<Link to="/private">Приватная страница</Link>
</div>
</>;
};

// Приватная страница с примером использования Navigate - перенаправление на страницу входа
const PrivatePage: React.FC<{ isAuthenticated: boolean }> = ({ isAuthenticated }) => {
if (!isAuthenticated) {
return <Navigate to="/login" />; // Перенаправление на страницу входа
return <Navigate to="/login" />; // Перенаправление на страницу входа если не авторизован
}

return <h1>Приватная страница</h1>;
return <div className='page'>
<h2>Приватная страница</h2>
<Link to="/">Главная страница</Link>
</div>;
};

const Login: React.FC = () => {
return <h1>Страница входа</h1>;
const Login: React.FC<{ onLogin: () => void }> = ({ onLogin }) => {
const navigate = useNavigate();

const handleLogin = () => {
onLogin();
navigate('/private');
};

return (
<div className="page">
<h2>Авторизация</h2>
<p>Введите свои учетные данные:</p>
<input type="text" placeholder="Логин" />
<input type="password" placeholder="Пароль" />
<br />
<button onClick={handleLogin}>Войти</button>
</div>
);
};

const NavigateApp: React.FC = () => {
const [isAuthenticated, setIsAuthenticated] = useState(false);

const handleLogin = () => {
setIsAuthenticated(true);
};

return (
<Router>
<Routes>
<Route path="/private" element={<PrivatePage />} />
<Route path="/login" element={<Login />} />
<Route path="/" element={<Home />} />
<Route path="/private" element={<PrivatePage isAuthenticated={isAuthenticated} />} />
<Route path="/login" element={<Login onLogin={handleLogin} />} />
</Routes>
</Router>
);
Expand Down
Loading