diff --git a/.eslintrc.json b/.eslintrc.json index 641c61e..7d401e6 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -22,7 +22,8 @@ "react/jsx-filename-extension": ["warn", { "extensions": [".js", ".jsx"] }], "react/react-in-jsx-scope": "off", "import/no-unresolved": "off", - "no-shadow": "off" + "no-shadow": "off", + "quotes": "off" }, "overrides": [ { diff --git a/package-lock.json b/package-lock.json index e18a84c..a66e55b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@testing-library/user-event": "^13.5.0", "axios": "^1.6.0", "jwt-decode": "^4.0.0", + "prop-types": "^15.8.1", "react": "^18.2.0", "react-dom": "^18.2.0", "react-redux": "^8.1.3", diff --git a/package.json b/package.json index 439b0fb..24263e7 100644 --- a/package.json +++ b/package.json @@ -8,6 +8,7 @@ "@testing-library/react": "^13.4.0", "@testing-library/user-event": "^13.5.0", "axios": "^1.6.0", + "prop-types": "^15.8.1", "jwt-decode": "^4.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", diff --git a/src/App.css b/src/App.css index 8c7466b..c3d4cf3 100644 --- a/src/App.css +++ b/src/App.css @@ -50,3 +50,22 @@ section.signup > form { .form-switch-link { color: var(--color-white); } + +main { + display: flex; +} + +.mobmenu { + position: fixed; + left: 1rem; + top: 1rem; + width: 30px; + z-index: 5; + background: none; + border: none; + cursor: pointer; +} + +.mobmenu > img { + width: 100%; +} diff --git a/src/App.js b/src/App.js index 39130d9..4d8e474 100644 --- a/src/App.js +++ b/src/App.js @@ -1,9 +1,11 @@ -import './App.css'; -import { Route, Routes } from 'react-router-dom'; -import { useDispatch, useSelector } from 'react-redux'; -import { useEffect } from 'react'; -import { isLogIn } from './redux/login/loginSlice'; -import Login from './components/login/login'; +import "./App.css"; +import { Route, Routes } from "react-router-dom"; +import { useDispatch, useSelector } from "react-redux"; +import { useEffect } from "react"; +import { isLogIn } from "./redux/login/loginSlice"; +import Login from "./components/login/login"; +import Cars from "./components/car/Cars"; +import NavMenu from "./components/nav/NavMenu"; function App() { const loginData = useSelector((state) => state.login); @@ -13,7 +15,7 @@ function App() { }, [dispatch]); // Checking if the authentication token key exists - const authKey = localStorage.getItem('authKey'); + const authKey = localStorage.getItem("authKey"); // If authKey does not exist display login page if (!authKey) { return ; @@ -26,9 +28,10 @@ function App() { return (
-

Main Page

+ } /> + } />
); diff --git a/src/CSS/Car.module.css b/src/CSS/Car.module.css new file mode 100644 index 0000000..d72453e --- /dev/null +++ b/src/CSS/Car.module.css @@ -0,0 +1,44 @@ +.car { + display: flex; + flex-direction: column; + gap: 35px; + align-items: center; + text-align: center; + width: 350px; + height: 450px; +} + +.carImg { + width: 100%; +} + +.carDetailsBox { + display: flex; + flex-direction: column; + gap: 25px; +} + +.carName { + text-transform: uppercase; + font-weight: 700; + font-size: 1.3rem; +} + +.carDescription { + color: #c7c9c8; + text-transform: capitalize; +} + +.carSocialBox { + width: 50%; + display: flex; + justify-content: space-evenly; +} + +.carSocialBox > img { + width: 30px; + padding: 5px; + border-radius: 50%; + border: 1px solid #c7c9c8; + cursor: pointer; +} diff --git a/src/CSS/Cars.module.css b/src/CSS/Cars.module.css new file mode 100644 index 0000000..2b97e92 --- /dev/null +++ b/src/CSS/Cars.module.css @@ -0,0 +1,46 @@ +.carsSection { + width: 100%; + min-height: 100vh; + position: relative; + display: flex; + flex-direction: column; + align-items: center; + justify-content: space-around; + overflow-x: hidden; + overflow-y: unset; +} + +.titleBox { + display: flex; + flex-direction: column; + gap: 11px; +} + +.titleBox > * { + text-transform: uppercase; + text-align: center; +} + +.titleBox > h2 { + letter-spacing: 11px; +} + +.titleBox > p { + color: #c7c9c8; + font-size: 0.9rem; +} + +.cars { + width: 70%; + display: flex; + gap: 25px; + transition: all 1s; +} + +.dblock { + display: block; +} + +.dnone { + display: none; +} diff --git a/src/CSS/NavMenu.module.css b/src/CSS/NavMenu.module.css new file mode 100644 index 0000000..8944685 --- /dev/null +++ b/src/CSS/NavMenu.module.css @@ -0,0 +1,29 @@ +.sidebarMenu { + width: 250px; + min-height: 100vh; + background-color: #fff; + display: flex; + flex-direction: column; + align-items: center; +} + +.logo { + width: 75%; +} + +.navigation { + margin-top: 10rem; +} + +.navlist { + display: flex; + flex-direction: column; + list-style-type: none; + gap: 2rem; + font-weight: 700; +} + +a { + text-decoration: none; + color: rgb(0, 17, 17); +} diff --git a/src/CSS/Slider.module.css b/src/CSS/Slider.module.css new file mode 100644 index 0000000..432e9ce --- /dev/null +++ b/src/CSS/Slider.module.css @@ -0,0 +1,34 @@ +.prevnextbtns { + display: flex; + justify-content: space-between; + position: absolute; + width: 100%; +} + +.prevnextbtns > button { + padding: 15px 35px; + background-color: var(--color-bg-2); + border: none; + color: #fff; + font-size: 1rem; + cursor: pointer; +} + +.prevnextbtns > button > img { + width: 15px; +} + +.prevnextbtns > button:nth-child(1) { + border-top-right-radius: 16px; + border-bottom-right-radius: 16px; + background-color: #c7c9c8; +} + +.prevnextbtns > button:nth-child(1) > img { + transform: rotateZ(180deg); +} + +.prevnextbtns > button:nth-child(2) { + border-top-left-radius: 16px; + border-bottom-left-radius: 16px; +} diff --git a/src/assets/arrow.png b/src/assets/arrow.png new file mode 100644 index 0000000..2f1e2cf Binary files /dev/null and b/src/assets/arrow.png differ diff --git a/src/assets/car-1.png b/src/assets/car-1.png new file mode 100644 index 0000000..c9fb2c7 Binary files /dev/null and b/src/assets/car-1.png differ diff --git a/src/assets/fb.png b/src/assets/fb.png new file mode 100644 index 0000000..fe4d148 Binary files /dev/null and b/src/assets/fb.png differ diff --git a/src/assets/insta.png b/src/assets/insta.png new file mode 100644 index 0000000..a421848 Binary files /dev/null and b/src/assets/insta.png differ diff --git a/src/assets/logo.jpg b/src/assets/logo.jpg new file mode 100644 index 0000000..e408fbf Binary files /dev/null and b/src/assets/logo.jpg differ diff --git a/src/assets/menu.png b/src/assets/menu.png new file mode 100644 index 0000000..c8f355e Binary files /dev/null and b/src/assets/menu.png differ diff --git a/src/assets/twitter.png b/src/assets/twitter.png new file mode 100644 index 0000000..de5dd37 Binary files /dev/null and b/src/assets/twitter.png differ diff --git a/src/components/Slider.js b/src/components/Slider.js new file mode 100644 index 0000000..170caea --- /dev/null +++ b/src/components/Slider.js @@ -0,0 +1,38 @@ +import React from "react"; +// import { useDispatch, useSelector } from "react-redux"; +// import { nextCar } from "../redux/cars/carsSlice"; +import styles from "../CSS/Slider.module.css"; +import arrow from "../assets/arrow.png"; + +const Slider = () => { + // const sliderIndex = useSelector((state) => state.cars.value); + // const dispatch = useDispatch(); + const slider = document.querySelector("#slider"); + const slideLeft = () => { + const leftMargin = +window.getComputedStyle(slider).marginLeft.slice(0, -2); + if (leftMargin < (slider.childElementCount + 1) * -350) return; + document.querySelector("#slider").style.marginLeft = `${ + leftMargin - 350 + }px`; + }; + + const slideRight = () => { + const leftMargin = +window.getComputedStyle(slider).marginLeft.slice(0, -2); + if (leftMargin >= 0) return; + document.querySelector("#slider").style.marginLeft = `${ + leftMargin + 350 + }px`; + }; + return ( +
+ + +
+ ); +}; + +export default Slider; diff --git a/src/components/car/Car.js b/src/components/car/Car.js new file mode 100644 index 0000000..8f6a162 --- /dev/null +++ b/src/components/car/Car.js @@ -0,0 +1,38 @@ +import React from "react"; +import PropTypes from "prop-types"; +import styles from "../../CSS/Car.module.css"; +import carImg from "../../assets/car-1.png"; +import fb from "../../assets/fb.png"; +import twitter from "../../assets/twitter.png"; +import insta from "../../assets/insta.png"; + +const Car = ({ name, image, description }) => ( +
+
+ {image} +
+
+

{name}

+

{description}

+
+
+ Social Media Icon + Social Media Icon + Social Media Icon +
+
+); + +Car.propTypes = { + name: PropTypes.string, + image: PropTypes.string, + description: PropTypes.string, +}; + +Car.defaultProps = { + name: "", + image: "", + description: "", +}; + +export default Car; diff --git a/src/components/car/Cars.js b/src/components/car/Cars.js new file mode 100644 index 0000000..757e0f8 --- /dev/null +++ b/src/components/car/Cars.js @@ -0,0 +1,46 @@ +import React, { useEffect } from "react"; +import { useDispatch, useSelector } from "react-redux"; +import { getCars } from "../../redux/cars/carsSlice"; +import Car from "./Car"; +import styles from "../../CSS/Cars.module.css"; +import Slider from "../Slider"; + +const Cars = () => { + const { cars, isLoading } = useSelector((state) => state.cars); + const dispatch = useDispatch(); + useEffect(() => { + dispatch(getCars()); + }, [dispatch, cars.length]); + + if (isLoading) { + return ( +
+

Loading...

+
+ ); + } + + return ( +
+
+

Latest Models

+ +

Please select a car model

+
+
+ {cars.map((car) => ( +
+ +
+ ))} +
+ +
+ ); +}; + +export default Cars; diff --git a/src/components/nav/NavMenu.js b/src/components/nav/NavMenu.js new file mode 100644 index 0000000..e862e7f --- /dev/null +++ b/src/components/nav/NavMenu.js @@ -0,0 +1,47 @@ +import React, { useState } from "react"; +import { NavLink } from "react-router-dom"; +import styles from "../../CSS/NavMenu.module.css"; +import menu from "../../assets/menu.png"; +import logo from "../../assets/logo.jpg"; + +const NavMenu = () => { + const [isOpen, setIsOpen] = useState(true); + + const toggleMenu = () => { + setIsOpen(!isOpen); + }; + + return ( +
+ + {isOpen && ( +
+ Logo + +
+ )} +
+ ); +}; + +export default NavMenu; diff --git a/src/redux/cars/carsSlice.js b/src/redux/cars/carsSlice.js new file mode 100644 index 0000000..90cd22f --- /dev/null +++ b/src/redux/cars/carsSlice.js @@ -0,0 +1,63 @@ +import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; +import axios from "axios"; +import { getLocalStorageAuth } from "../../utility/helper"; +import { API_URL } from "../../utility/globalVariable"; + +const initialState = { + cars: [], + isLoading: false, + error: null, + value: null, + length: null, +}; + +export const getCars = createAsyncThunk("cars/getCars", async (_, thunkAPI) => { + try { + const authToken = getLocalStorageAuth(); + const response = await axios(`${API_URL}/users/1/cars`, { + headers: { + Authorization: `Bearer ${authToken}`, // Include the Authorization header with the token + }, + }); + return response.data; + } catch (error) { + return thunkAPI.rejectWithValue("Error fetching cars"); + } +}); + +const carsSlice = createSlice({ + name: "cars", + initialState, + reducers: { + nextCar(state, action) { + state.value = action.payload > state.length ? 6 : action.payload; + }, + prevCar(state, action) { + state.value = action.payload < 6 ? state.length : action.payload; + }, + dotCar() {}, + }, + extraReducers: { + [getCars.pending]: (state) => { + state.isLoading = true; + }, + [getCars.fulfilled]: (state, action) => { + const data = action.payload; + const newdata = data.map((car) => ({ + id: car.id, + name: car.name, + description: car.description, + })); + state.isLoading = false; + state.cars = newdata; + state.length = newdata.length; + state.value = newdata.length - (newdata.length - 1); + }, + [getCars.rejected]: (state) => { + state.isLoading = false; + }, + }, +}); + +export const { nextCar, prevCar, dotCar } = carsSlice.actions; +export default carsSlice.reducer; diff --git a/src/redux/login/loginSlice.js b/src/redux/login/loginSlice.js index a7a8362..b569f33 100644 --- a/src/redux/login/loginSlice.js +++ b/src/redux/login/loginSlice.js @@ -1,12 +1,12 @@ -import axios from 'axios'; -import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; +import axios from "axios"; +import { createSlice, createAsyncThunk } from "@reduxjs/toolkit"; import { getUserIdFromToken, getUserNameFromToken, getLocalStorageAuth, setLocalStorageAuth, -} from '../../utility/helper'; -import { API_URL } from '../../utility/globalVariable'; +} from "../../utility/helper"; +import { API_URL } from "../../utility/globalVariable"; const initialState = { username: null, @@ -18,7 +18,7 @@ const initialState = { }; export const isLogIn = createAsyncThunk( - 'login/isLogIn', + "login/isLogIn", async (_, thunkAPI) => { try { const Authorization = getLocalStorageAuth(); @@ -28,20 +28,20 @@ export const isLogIn = createAsyncThunk( { username }, { headers: { - 'Content-Type': 'multipart/form-data', + "Content-Type": "multipart/form-data", }, }, ); - if (res.status !== 200) throw new Error('Error'); + if (res.status !== 200) throw new Error("Error"); return res; } catch (err) { - return thunkAPI.rejectWithValue('Something went wrong'); + return thunkAPI.rejectWithValue("Something went wrong"); } }, ); export const logIn = createAsyncThunk( - 'login/logIn', + "login/logIn", async (username, thunkAPI) => { try { const res = await axios.post( @@ -49,21 +49,20 @@ export const logIn = createAsyncThunk( { username }, { headers: { - 'Content-Type': 'multipart/form-data', + "Content-Type": "multipart/form-data", }, }, ); - console.log(res); - if (res.status !== 200) throw new Error('Error'); + if (res.status !== 200) throw new Error("Error"); return res; } catch (err) { - return thunkAPI.rejectWithValue('Something went wrong'); + return thunkAPI.rejectWithValue("Something went wrong"); } }, ); export const loginSlice = createSlice({ - name: 'loginSlice', + name: "loginSlice", initialState, extraReducers: (builder) => { builder.addCase(isLogIn.fulfilled, (state, action) => { @@ -78,7 +77,7 @@ export const loginSlice = createSlice({ }); builder.addCase(isLogIn.rejected, (state) => { state.isLoading = false; - state.isError = 'Error Logging In'; + state.isError = "Error Logging In"; }); builder.addCase(logIn.fulfilled, (state, action) => { state.isLoading = false; diff --git a/src/redux/store.js b/src/redux/store.js index 4a65990..f066ed7 100644 --- a/src/redux/store.js +++ b/src/redux/store.js @@ -1,11 +1,13 @@ import { configureStore } from '@reduxjs/toolkit'; import loginReducer from './login/loginSlice'; import signupReducer from './signup/signupSlice'; +import carsReducer from './cars/carsSlice'; const store = configureStore({ reducer: { login: loginReducer, signup: signupReducer, + cars: carsReducer, }, });