diff --git a/src/App.js b/src/App.js
index 4442af9..4b8246f 100644
--- a/src/App.js
+++ b/src/App.js
@@ -1,17 +1,29 @@
// src/App.js
-import React from "react";
+import React, { useEffect } from "react";
+import { useDispatch } from "react-redux";
import "./App.css";
import { Routes, Route } from "react-router-dom";
import Homepage from "./pages/Homepage";
import PostPage from "./pages/PostPage";
+import LoginPage from "./pages/Login";
+import Toolbar from "./components/Toolbar";
+import { bootstrapLoginState } from "./store/auth/actions";
export default function App() {
+ const dispatch = useDispatch();
+
+ useEffect(() => {
+ dispatch(bootstrapLoginState());
+ }, []);
+
return (
+
{/* more pages to be added here later */}
} />
} />
+ } />
);
diff --git a/src/components/Toolbar.js b/src/components/Toolbar.js
new file mode 100644
index 0000000..81a45ec
--- /dev/null
+++ b/src/components/Toolbar.js
@@ -0,0 +1,32 @@
+import { Link } from "react-router-dom";
+import { useSelector, useDispatch } from "react-redux";
+import { getUserProfile } from "../store/auth/selectors";
+import { logout } from "../store/auth/slice";
+import "./styles.css";
+
+const Toolbar = () => {
+ const user = useSelector(getUserProfile);
+ const dispatch = useDispatch();
+
+ return (
+
+
+
Codaisseur Coders Network!
+
+
+ {user ? (
+ <>
+
Welcome {user.name}
+
+ >
+ ) : (
+
+
+
+ )}
+
+
+ );
+};
+
+export default Toolbar;
diff --git a/src/components/styles.css b/src/components/styles.css
new file mode 100644
index 0000000..fc1d25c
--- /dev/null
+++ b/src/components/styles.css
@@ -0,0 +1,21 @@
+.toolbar {
+ display: flex;
+ justify-content: space-between;
+ padding-right: 30px;
+ padding-left: 30px;
+ background-color: lightsalmon;
+}
+
+.button-container {
+ display: flex;
+ align-items: center;
+}
+
+.welcome-text {
+ margin-right: 30px;
+}
+
+.logo-link {
+ text-decoration: none;
+ color: black;
+}
diff --git a/src/pages/Login.js b/src/pages/Login.js
new file mode 100644
index 0000000..f231b7f
--- /dev/null
+++ b/src/pages/Login.js
@@ -0,0 +1,52 @@
+// src/pages/LoginPage.js
+import React, { useState } from "react";
+import { useDispatch, useSelector } from "react-redux";
+import { useNavigate } from "react-router-dom";
+import { login } from "../store/auth/actions";
+import { getAuthLoading } from "../store/auth/selectors";
+
+export default function LoginPage() {
+ const [email, setEmail] = useState("");
+ const [password, setPassword] = useState("");
+ const dispatch = useDispatch();
+ const navigate = useNavigate();
+
+ const loading = useSelector(getAuthLoading);
+
+ function handleSubmit(event) {
+ event.preventDefault();
+ dispatch(login(email, password, navigate));
+ }
+
+ return (
+
+
Login
+
+ {loading && "Loading..."}
+
+ );
+}
diff --git a/src/store/auth/actions.js b/src/store/auth/actions.js
new file mode 100644
index 0000000..6e2f583
--- /dev/null
+++ b/src/store/auth/actions.js
@@ -0,0 +1,46 @@
+// src/store/auth/actions.js
+import axios from "axios";
+import { API_URL } from "../../config";
+import { startLoading, userLoggedIn } from "./slice";
+// A thunk creator
+export const login = (email, password, navigate) => {
+ return async function thunk(dispatch, getState) {
+ try {
+ dispatch(startLoading());
+
+ const response = await axios.post(`${API_URL}/login`, {
+ email,
+ password,
+ });
+
+ const { jwt } = response.data;
+
+ const profileResponse = await axios.get(`${API_URL}/me`, {
+ headers: { authorization: `Bearer ${jwt}` },
+ });
+
+ localStorage.setItem("token", jwt);
+
+ dispatch(userLoggedIn({ accessToken: jwt, user: profileResponse.data }));
+ navigate("/");
+ } catch (e) {
+ console.log("Error at login", e.message);
+ }
+ };
+};
+
+export const bootstrapLoginState = () => async (dispatch, getState) => {
+ try {
+ const token = localStorage.getItem("token");
+
+ if (!token) return;
+
+ const response = await axios.get(`${API_URL}/me`, {
+ headers: { authorization: `Bearer ${token}` },
+ });
+
+ dispatch(userLoggedIn({ accessToken: token, user: response.data }));
+ } catch (e) {
+ console.log(e.message);
+ }
+};
diff --git a/src/store/auth/selectors.js b/src/store/auth/selectors.js
new file mode 100644
index 0000000..635915a
--- /dev/null
+++ b/src/store/auth/selectors.js
@@ -0,0 +1,2 @@
+export const getUserProfile = (reduxState) => reduxState.auth.me;
+export const getAuthLoading = (reduxState) => reduxState.auth.loading;
diff --git a/src/store/auth/slice.js b/src/store/auth/slice.js
new file mode 100644
index 0000000..83ba55a
--- /dev/null
+++ b/src/store/auth/slice.js
@@ -0,0 +1,33 @@
+import { createSlice } from "@reduxjs/toolkit";
+
+const initialState = {
+ me: null, // the logged-in user
+ accessToken: null,
+ loading: false,
+};
+
+export const authSlice = createSlice({
+ name: "auth",
+ initialState,
+ reducers: {
+ startLoading: (state) => {
+ state.loading = true;
+ },
+ userLoggedIn: (state, action) => {
+ state.me = action.payload.user;
+ state.accessToken = action.payload.accessToken;
+ state.loading = false;
+ },
+ logout: (state) => {
+ localStorage.removeItem("token");
+ // when we want to update the whole state
+ // we can return a new object instead of updating
+ // each key one by one
+ return initialState;
+ },
+ },
+});
+
+export const { startLoading, userLoggedIn, logout } = authSlice.actions;
+
+export default authSlice.reducer;
diff --git a/src/store/index.js b/src/store/index.js
index 98aa5e1..5b27baa 100644
--- a/src/store/index.js
+++ b/src/store/index.js
@@ -2,11 +2,13 @@
import { configureStore } from "@reduxjs/toolkit";
import feedReducer from "./feed/slice";
import postPageReducer from "./postPage/slice";
+import authReducer from "./auth/slice";
const store = configureStore({
reducer: {
feed: feedReducer,
postPage: postPageReducer,
+ auth: authReducer,
},
});