diff --git a/lab1/README.md b/lab1/README.md index 581ae35..0b328d4 100644 --- a/lab1/README.md +++ b/lab1/README.md @@ -2,20 +2,18 @@ 1. Skapa ett nytt projekt med create-react-app CLI - 1. `$ npx create-react-app my-app` + 1. `$ npx create-react-app my-app`. _`npx` är en package runner för att köra npm-paket direkt från kommandoraden utan att behöva installera dem globalt_. -2. Kör projektet - 1. `$ cd my-app` - 2. `$ npm start` - 3. Nu bör ett browser-fönster med en grundapp i React öppnas. - 4. Om du inte redan har React Developer Tools till din browser, ladda ner detta nu. - [React Developer Tools](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en) - +2. Kör projektet + 1. `$ cd my-app` + 2. `$ npm start` + 3. Nu bör ett browser-fönster med en grundapp i React öppnas. + 4. Om du inte redan har React Developer Tools till din browser, ladda ner detta nu. + [React Developer Tools](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi?hl=en) 3. Kolla runt i källkoden och fundera på/testa att... - 1. Vad får jag out of the box? - 2. Vilka paket är installerade? - 3. Vilka beroenden har React? - 4. Vilka komponenter existerar från början? ___Hint: React Devtools Extension___ - 5. Ersätt texten "Edit src/App.jsx and save to reload." med "Peace, Love & OP Web". Du behöver INTE kompilera om vid ändringar! - 6. Lek runt lite! - + 1. Vad får jag out of the box? + 2. Vilka paket är installerade? + 3. Vilka beroenden har React? + 4. Vilka komponenter existerar från början? **_Hint: React Devtools Extension_** + 5. Ersätt texten "Edit src/App.jsx and save to reload." med "Peace, Love & OP Web". Du behöver INTE kompilera om vid ändringar! + 6. Lek runt lite! diff --git a/lab2/README.md b/lab2/README.md index 487324a..c783c95 100644 --- a/lab2/README.md +++ b/lab2/README.md @@ -19,4 +19,5 @@ Från projektroten: `$ cd lab2`, `$ npm install` följt av `$ npm start`. Gå se 1. Hjälp en vän. 2. Utöka List-komponenten till att innehålla lite mer detaljerad användarinformation för varje item i listan. -3. Ta en kaffe ☕️ +3. Testa att skapa och styla en egen komponent från grunden. +4. Ta en kaffe ☕️ diff --git a/lab2/src/components/App/App.jsx b/lab2/src/components/App/App.jsx index 03e394d..28d9ad1 100644 --- a/lab2/src/components/App/App.jsx +++ b/lab2/src/components/App/App.jsx @@ -16,7 +16,7 @@ const App = () => { }, []); const changeBackgroundColor = (e) => { - // const color = e.target.value; + const color = e.target.value; // TODO: Task 3 - change background color by setting state. }; @@ -35,10 +35,7 @@ const App = () => {

- + ); }; diff --git a/lab2/src/components/List/List.jsx b/lab2/src/components/List/List.jsx index a678423..f52496f 100644 --- a/lab2/src/components/List/List.jsx +++ b/lab2/src/components/List/List.jsx @@ -18,6 +18,7 @@ const List = ({ items = [], title }) => { // TODO: Task 8: Set up filtering by declaring filteredItems. // const filteredItems = ... + return (

{title}

diff --git a/lab3/DO_NOT_README.md b/lab3/DO_NOT_README.md index 6b0b9fe..a96b061 100644 --- a/lab3/DO_NOT_README.md +++ b/lab3/DO_NOT_README.md @@ -4,7 +4,7 @@ ### Task 2 -I `backgroundReducers.js`: +I `avatarSlice.js`: ```javascript const initialState = { @@ -14,47 +14,64 @@ const initialState = { ### Task 3 -I `userActions.js`: +I `avatarSlice.js`: ```javascript - .then((users) => { - dispatch({ - type: USERS_RECEIVED, - payload: users - }) - }) +const avatarSlice = createSlice({ + name: "avatar", + initialState, + reducers: { + updatedAvatar: (state, action) => { + state.currentAvatar = action.payload; + }, + }, +}); ``` ### Task 4 -I `userReducers.js`: +I `avatarSlice.js`: ```javascript - ... - case USERS_RECEIVED: - return { - all: action.payload, - error: null - }; - ... +const avatarSlice = createSlice({ + name: "avatar", + initialState, + reducers: { + updatedAvatar: (state, action) => { + state.currentAvatar = action.payload; + }, + resetAvatar: (state, action) => { + state.currentAvatar = null; + }, + }, +}); +... +... +export const { updatedAvatar, resetAvatar } = avatarSlice.actions; ``` ### Task 5 -I `Container.jsx`: +I `AvatarPicker.jsx`: ```javascript -useEffect(() => { - dispatch(getAllUsers()); -}, []); +const dispatch = useDispatch(); + +const handleOnClick = (avatar) => { + dispatch(updatedAvatar(avatar)); +}; + +const handleOnReset = () => { + dispatch(resetAvatar()); +}; ``` ### Task 6 -I `List.jsx`: +I `NavBar.jsx`: ```javascript -const users = useSelector((state) => state.users.all); +const avatar = useSelector(selectCurrentAvatar); ``` ## Del 2 diff --git a/lab3/README.md b/lab3/README.md index bb5cc84..a93c1a1 100644 --- a/lab3/README.md +++ b/lab3/README.md @@ -7,24 +7,24 @@ Från projektroten: `$ cd lab3`, `$ npm install` följt av `$ npm start`. Gå se ## Del 1 - Redux-snurran 1. Ta en till på befintliga komponenter och hur Redux-snurran är implementerad för att välja bakgrund. Ladda ner och använd [Redux DevTools](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd) för detta. -2. Bakgrundsfärgen är satt till `lavender` från början via initialState. Byt default från `lavender` till `gold`._Hint: Detta görs i en reducer_ -3. Användarna visas inte i listan just nu. Börja med att dispatcha ett action när användare har hämtats asynkront i `userActions`. -4. När vi har dispatchat en action måste vi hantera den i våra reducers. Öppna `userReducers.js` och stoppa in resultatet från din nya action i applikationens state. -5. I `Container.jsx` har vi nu möjlighet att dipatcha actions tack vare vår hooks `useDispatch()`. Vi vill här hämta alla användare när komponenten mountas i `useEffect()` med vår nyss implementerade action creator. Implementera detta och bekräfta att det fungerar med Redux Dev Tools. -6. Nu när vi har användaren i vår store vill vi kunna läsa dom i komponenten `List.jsx`. Deklarera konstanten `users` i `List.jsx` med hjälp av `useSelector()`. Just nu är den bara en tom array. _Hint: Hur används `useSelector()` i `container.js`?_ +2. Bakgrundsfärgen är satt till `lavender` från början via initialState. Byt default från `lavender` till `gold`._Hint: Detta görs i en slice_ +3. Vår applikation har fått ny feature, en avatar-väljare. Men just nu fungererar den inte alls. Det ska vi fixa med Redux! Börja med att göra klart logien för att uppdatera vårt state i action-funktionen `updatedAvatar` i `avatarSlice.js`. +4. Lägg även till ytterligare en ny action i `avatarSlice.js` som nollställer `currentAvatar`. Glöm inte bort att exportera den tillsammans med `updatedAvatar`! +5. Nu vill vi dispatch:a våra två nya actions från `AvatarPicker.jsx`. Kontrollera att det fungerar med Redux Dev Tools. Du ska nu kunna se actions som skickas och att vårt state uppdateras. +6. Nu när vi har rätt värde i vår store kan vi läsa ut det vart vi vill. Använd `useSelector()` i `NavBar.jsx` för att välja ut vår avatar. Deklarera om konstanten `avatar`. 7. Nu ska applikationen fungera fullt ut! ## Del 2 - Egen komponent -Nu är det dags att sätta allt du lärt dig hittills på prov. Gör så långt du hinner. Uppgiften är att du ska skapa en egen komponent och visa detaljerad användarinformation i denna. I `List.jsx` finns det nu en callback, `onClick`, som kommer att skicka med ett id för användaren som klickades på. I `api.js` finns det en funktion som hämtar användardata baserat på ett id. +Nu är det dags att sätta allt du lärt dig hittills på prov. Gör så långt du hinner. Uppgiften är att du ska skapa en egen komponent och visa detaljerad användarinformation i denna när man klickat på en av användarna i listan. I `List.jsx` finns det nu en callback, `onClick`, som kommer att skicka med ett id för användaren som klickades på. I `api.js` finns det en funktion som hämtar användardata baserat på ett id. För att lösa uppgiften behöver du: - Skapa en ny komponent `` (eller liknande namn) och använda denna i `Container.jsx`. - `` behöver rendera ut JSX för detaljerad användardata. -- Skapa en ny action creator i `userActions.js` som tar in ett id och gör ett asynkront anrop mot `api.js` för att hämta ut detaljerad användardata. -- Utöka state för `userReducers.js` till att även innehålla detaljerad användardata och hantera en ny typ av inkommande action. -- Dispatcha ett nytt action för detaljerad användardata från `onItemClick()` i `List.jsx` via `useDispatch()`. +- Skapa en ny action `userSlice.js` som tar in ett id och gör ett asynkront anrop mot `api.js` för att hämta ut detaljerad användardata för en användare. +- Utöka state i `userSlice.js` till att även innehålla detaljerad användardata. +- Dispatcha en nytt action för detaljerad användardata från `onItemClick()` i `List.jsx` via `useDispatch()`. - Läsa in den detaljerade användardatan från applikationens state till din nya komponent. ##### _Del två finns löst i solutions-branchen om du bara vill se lösningen_ 😇 diff --git a/lab3/src/actions/backgroundActions.js b/lab3/src/actions/backgroundActions.js deleted file mode 100644 index 0a686dc..0000000 --- a/lab3/src/actions/backgroundActions.js +++ /dev/null @@ -1,10 +0,0 @@ -export const SET_BG_COLOR = "SET_BG_COLOR"; - -export const setBackgroundColor = (color) => { - return { - type: SET_BG_COLOR, - payload: { - color - } - }; -}; \ No newline at end of file diff --git a/lab3/src/actions/userActions.js b/lab3/src/actions/userActions.js deleted file mode 100644 index 283ff4d..0000000 --- a/lab3/src/actions/userActions.js +++ /dev/null @@ -1,27 +0,0 @@ -import { getUsers } from '../api'; - -export const USERS_RECEIVED = "USERS_RECEIVED"; -export const GET_USERS_ERROR = "GET_USERS_ERROR"; - -export const getAllUsers = () => { - /* - Actions should generally be POJO (Plain JavaScript Object), but getting the users is made - asynchronously. - Thanks to the thunk middleware (declared in store.js) we can handle asynchronous events - in our actions. - */ - return dispatch => { - getUsers() // Gets users from our API and returns a promise. - .then((users) => { - // TODO: Task 3 - Dispatch an action of type USERS_RECEIVED and payload: users. - }) - .catch((e) => { - dispatch({ - type: GET_USERS_ERROR, - payload: { - error: e - } - }); - }) - }; -}; diff --git a/lab3/src/components/App/App.jsx b/lab3/src/components/App/App.jsx index d69530c..1a1029e 100644 --- a/lab3/src/components/App/App.jsx +++ b/lab3/src/components/App/App.jsx @@ -1,13 +1,10 @@ import React from "react"; import Container from "../Container/Container"; import { Provider } from "react-redux"; -import configureStore from "../../store"; +import store from "../../store"; import "./App.css"; -const preloadedState = window.__PRELOADED_STATE__; -const store = configureStore(preloadedState); - const App = () => ( diff --git a/lab3/src/components/AvatarPicker/AvatarPicker.css b/lab3/src/components/AvatarPicker/AvatarPicker.css new file mode 100644 index 0000000..a7917b3 --- /dev/null +++ b/lab3/src/components/AvatarPicker/AvatarPicker.css @@ -0,0 +1,4 @@ +.buttonContainer { + display: flex; + gap: 10px; +} \ No newline at end of file diff --git a/lab3/src/components/AvatarPicker/AvatarPicker.jsx b/lab3/src/components/AvatarPicker/AvatarPicker.jsx new file mode 100644 index 0000000..58707ec --- /dev/null +++ b/lab3/src/components/AvatarPicker/AvatarPicker.jsx @@ -0,0 +1,42 @@ +import React from "react"; +import { useDispatch } from "react-redux"; +import { + resetAvatar, + updatedAvatar, + selectCurrentAvatar, +} from "../../features/avatar/avatarSlice"; +import "./AvatarPicker.css"; + +const AvatarPicker = () => { + const dispatch = useDispatch(); + + const handleOnClick = (avatar) => { + // TODO: Task 5 - dispatch + }; + + const handleOnReset = () => { + // TODO: Task 5 - dispatch + }; + + return ( +
+
Välj avatar
+
+ + + + +
+
+ ); +}; + +export default AvatarPicker; diff --git a/lab3/src/components/BackgroundColorPicker/BackgroundColorPicker.jsx b/lab3/src/components/BackgroundColorPicker/BackgroundColorPicker.jsx index fa2498d..46dbd3d 100644 --- a/lab3/src/components/BackgroundColorPicker/BackgroundColorPicker.jsx +++ b/lab3/src/components/BackgroundColorPicker/BackgroundColorPicker.jsx @@ -1,14 +1,14 @@ import React from "react"; import { useDispatch } from "react-redux"; -import { setBackgroundColor } from "../../actions/backgroundActions"; import colors from "./colors"; +import { updatedBackgroundColor } from "../../features/background/backgroundSlice"; import "./BackgroundColorPicker.css"; const BackgroundColorPicker = () => { const dispatch = useDispatch(); const handleOnChange = (e) => { - dispatch(setBackgroundColor(e.target.value)); + dispatch(updatedBackgroundColor(e.target.value)); }; return ( diff --git a/lab3/src/components/Container/Container.jsx b/lab3/src/components/Container/Container.jsx index cdc415b..b7407db 100644 --- a/lab3/src/components/Container/Container.jsx +++ b/lab3/src/components/Container/Container.jsx @@ -1,38 +1,46 @@ import React, { useEffect } from "react"; import BackgroundColorPicker from "../BackgroundColorPicker/BackgroundColorPicker"; import List from "../List/List"; +import AvatarPicker from "../AvatarPicker/AvatarPicker"; import { useDispatch, useSelector } from "react-redux"; -import { getAllUsers } from "../../actions/userActions"; +import { selectBackgroundColor } from "../../features/background/backgroundSlice"; +import { fetchUsers } from "../../features/users/usersSlice"; import "./Container.css"; +import NavBar from "../NavBar/NavBar"; const Container = () => { const dispatch = useDispatch(); - const backgroundColor = useSelector((state) => state.background.bgColor); + const backgroundColor = useSelector(selectBackgroundColor); - // TODO: Task 5 - Get all users upon mounting the component - useEffect(() => {}, []); + useEffect(() => { + dispatch(fetchUsers()); + }, []); return ( -
-
-

Lab 3

-

- - 👩‍💻 - {" "} - Öppna upp README.md och följ instruktionerna{" "} - - 👨‍💻 - -

+ <> +
+ +
+

Lab 3

+

+ + 👩‍💻 + {" "} + Öppna upp README.md och följ instruktionerna{" "} + + 👨‍💻 + +

+
+ + +
- - -
+ ); }; diff --git a/lab3/src/components/List/List.jsx b/lab3/src/components/List/List.jsx index 1947438..cb69164 100644 --- a/lab3/src/components/List/List.jsx +++ b/lab3/src/components/List/List.jsx @@ -1,13 +1,14 @@ import React, { useState } from "react"; import SearchBar from "../SearchBar/SearchBar"; +import { useSelector } from "react-redux"; +import { selectUsers } from "../../features/users/usersSlice"; import "./List.css"; const List = ({ title }) => { const [searchTerm, setSearchTerm] = useState(""); - // Task 6: Get users from store - const users = []; + const users = useSelector(selectUsers); const onItemClick = (userId) => { console.log("Clicked on user", userId); @@ -18,7 +19,7 @@ const List = ({ title }) => { }; const filterUsersBySearchTerm = (searchTerm) => - users.filter((user) => + users.all.filter((user) => user.name.toLowerCase().includes(searchTerm.toLowerCase()) ); diff --git a/lab3/src/components/NavBar/NavBar.css b/lab3/src/components/NavBar/NavBar.css new file mode 100644 index 0000000..faa3096 --- /dev/null +++ b/lab3/src/components/NavBar/NavBar.css @@ -0,0 +1,12 @@ +.circle { + width: 80px; /* Diameter of the circle */ + height: 80px; /* Diameter of the circle */ + background-color: lightgrey; /* Circle background color */ + border-radius: 50%; /* Makes it a circle */ + display: flex; /* To center content */ + align-items: center; /* Vertically center content */ + justify-content: center; /* Horizontally center content */ + color: white; /* Text color */ + font-size: 40px; + border: thick double #007bff; + } \ No newline at end of file diff --git a/lab3/src/components/NavBar/NavBar.jsx b/lab3/src/components/NavBar/NavBar.jsx new file mode 100644 index 0000000..e0227d4 --- /dev/null +++ b/lab3/src/components/NavBar/NavBar.jsx @@ -0,0 +1,21 @@ +import React from "react"; +import { useSelector } from "react-redux"; +import { selectCurrentAvatar } from "../../features/avatar/avatarSlice"; +import "./NavBar.css"; + +const NavBar = () => { + // TODO: Task 6 - Implement selector + const avatar = null; + + return ( + + ); +}; + +export default NavBar; diff --git a/lab3/src/features/avatar/avatarSlice.js b/lab3/src/features/avatar/avatarSlice.js new file mode 100644 index 0000000..365034f --- /dev/null +++ b/lab3/src/features/avatar/avatarSlice.js @@ -0,0 +1,27 @@ +import { createSlice } from "@reduxjs/toolkit"; + +// Initial state of the avatar +const initialState = { + currentAvatar: null, +}; + +const avatarSlice = createSlice({ + name: "avatar", + initialState, + reducers: { + updatedAvatar: (state, action) => { + // TODO: Task 3 - update the state + }, + // TODO: Task 4 - add a reset action + }, +}); + +// Export actions +// TODO: Task 4 - Don't forget to export the reset action! +export const { updatedAvatar } = avatarSlice.actions; + +// Avatar selector +export const selectCurrentAvatar = (state) => state.avatar.currentAvatar; + +// Export reducer +export default avatarSlice.reducer; diff --git a/lab3/src/features/background/backgroundSlice.js b/lab3/src/features/background/backgroundSlice.js new file mode 100644 index 0000000..ff0a31f --- /dev/null +++ b/lab3/src/features/background/backgroundSlice.js @@ -0,0 +1,22 @@ +import { createSlice } from "@reduxjs/toolkit"; + +const initialState = { + // TODO: Task 2 + bgColor: "lavender", +}; + +const backgroundSlice = createSlice({ + name: "background", + initialState, + reducers: { + updatedBackgroundColor: (state, action) => { + state.bgColor = action.payload; + }, + }, +}); + +export const { updatedBackgroundColor } = backgroundSlice.actions; + +export const selectBackgroundColor = (state) => state.background.bgColor; + +export default backgroundSlice.reducer; diff --git a/lab3/src/features/users/usersSlice.js b/lab3/src/features/users/usersSlice.js new file mode 100644 index 0000000..9fac305 --- /dev/null +++ b/lab3/src/features/users/usersSlice.js @@ -0,0 +1,38 @@ +import { createSlice, createAsyncThunk } from "@reduxjs/toolkit"; +import { getUsers } from "../../api"; + +const initialState = { + status: "uninitialized", + all: [], + error: null, +}; + +// We use createAsyncThunk to create an action creator that returns a function we can dispatch. +export const fetchUsers = createAsyncThunk("users/fetchUsers", async () => { + const users = await getUsers(); + return users; +}); + +export const usersSlice = createSlice({ + name: "users", + initialState, + reducers: {}, + // Extra reducers are used to handle actions that are dispatched outside of the slice. In our case, an asycn action that fetches api data. + extraReducers: (builder) => { + builder + .addCase(fetchUsers.pending, (state) => { + state.status = "loading"; + }) + .addCase(fetchUsers.fulfilled, (state, action) => { + state.status = "success"; + state.all = action.payload; + }) + .addCase(fetchUsers.rejected, (state, action) => { + state.status = "failed"; + }); + }, +}); + +export const selectUsers = (state) => state.users; + +export default usersSlice.reducer; diff --git a/lab3/src/reducers/backgroundReducer.js b/lab3/src/reducers/backgroundReducer.js deleted file mode 100644 index 242c81e..0000000 --- a/lab3/src/reducers/backgroundReducer.js +++ /dev/null @@ -1,19 +0,0 @@ -import { SET_BG_COLOR } from "../actions/backgroundActions"; - -const initialState = { - // TODO: Task 2 - bgColor: 'lavender' -}; - -const backgroundReducer = (state = initialState, action = {}) => { - switch (action.type) { - case SET_BG_COLOR: - return { - bgColor: action.payload.color - }; - default: - return state; - } -}; - -export default backgroundReducer; \ No newline at end of file diff --git a/lab3/src/reducers/reducers.js b/lab3/src/reducers/reducers.js deleted file mode 100644 index 990d62d..0000000 --- a/lab3/src/reducers/reducers.js +++ /dev/null @@ -1,10 +0,0 @@ -import {combineReducers} from 'redux'; -import backgroundReducer from './backgroundReducer'; -import userReducer from './userReducer'; - -const rootReducer = combineReducers({ - background: backgroundReducer, - users: userReducer -}); - -export default rootReducer; diff --git a/lab3/src/reducers/userReducer.js b/lab3/src/reducers/userReducer.js deleted file mode 100644 index 7b9a457..0000000 --- a/lab3/src/reducers/userReducer.js +++ /dev/null @@ -1,21 +0,0 @@ -import {USERS_RECEIVED, GET_USERS_ERROR} from '../actions/userActions'; - -const initialState = { - all: [], - error: null -}; - -const userReducer = (state = initialState, action = {}) => { - switch (action.type) { - // TODO: Task 4 - Handle the action received from getAllUsers() - case GET_USERS_ERROR: - return { - all: [], - error: action.payload.error - }; - default: - return state; - } -}; - -export default userReducer; diff --git a/lab3/src/store.js b/lab3/src/store.js index cac7260..9c70b05 100644 --- a/lab3/src/store.js +++ b/lab3/src/store.js @@ -1,18 +1,14 @@ - -import { applyMiddleware, createStore } from "redux"; -import { composeWithDevTools } from "redux-devtools-extension"; -import logger from "redux-logger"; -import thunk from "redux-thunk"; -import rootReducer from "./reducers/reducers"; - -const configureStore = (preloadedState) => { - - const middleware = [ - thunk, - logger - ]; - - return createStore(rootReducer, preloadedState, composeWithDevTools(applyMiddleware(...middleware))); -}; - -export default configureStore; \ No newline at end of file +import { configureStore } from "@reduxjs/toolkit"; +import backgroundReducer from "./features/background/backgroundSlice"; +import userReducer from "./features/users/usersSlice"; +import avatarReducer from "./features/avatar/avatarSlice"; + +const store = configureStore({ + reducer: { + background: backgroundReducer, + users: userReducer, + avatar: avatarReducer, + }, +}); + +export default store; diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..49da432 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,67 @@ +{ + "name": "academy-react-redux", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "@reduxjs/toolkit": "^2.2.8" + } + }, + "node_modules/@reduxjs/toolkit": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.2.8.tgz", + "integrity": "sha512-eK/ieXftPRQfaBSmzsamXEyDwkntMTY0e9SG5ETsEOv5JIPKhu3mj992t6B8FJjlnSrZBAAqdT8oMkPe4j+P9g==", + "license": "MIT", + "dependencies": { + "immer": "^10.0.3", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, + "node_modules/immer": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.1.1.tgz", + "integrity": "sha512-s2MPrmjovJcoMaHtx6K11Ra7oD05NT97w1IC5zpMkT6Atjr7H8LjaDd81iIxUYpMKSRRNMJE703M1Fhr/TctHw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..5a40ae5 --- /dev/null +++ b/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "@reduxjs/toolkit": "^2.2.8" + } +}