diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..7e257db --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": [] +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 0e5ad34..6377851 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,6 +20,9 @@ "react-scripts": "5.0.1", "sass": "^1.61.0", "web-vitals": "^2.1.4" + }, + "devDependencies": { + "tailwindcss": "^3.3.1" } }, "node_modules/@adobe/css-tools": { diff --git a/package.json b/package.json index dbe10d9..ed26391 100644 --- a/package.json +++ b/package.json @@ -39,5 +39,8 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "devDependencies": { + "tailwindcss": "^3.3.1" } } diff --git a/src/App.js b/src/App.js index 59d5fbc..16dac44 100644 --- a/src/App.js +++ b/src/App.js @@ -1,9 +1,30 @@ -import './App.css'; +import { BrowserRouter as Router, Routes, Route } from 'react-router-dom'; +import LoginPage from './pages/LoginPage/LoginPage'; +import AllCoursesPage from './pages/AllCoursesPage/AllCoursesPage'; +import MyCoursesPage from './pages/MyCoursesPage/MyCoursesPage'; +import NewCoursePage from './pages/NewCoursePage/NewCoursePage'; +import HomePage from './pages/HomePage/HomePage'; +import Layout from './components/Layout/Layout'; +import PrivateRoute from './router/PrivateRoute'; function App() { return ( -
- gkgkgkgkg +
+ + {/*
*/} + + + } /> + }> + } /> + } /> + } /> + } /> + {/* }> + */} + + +
); } diff --git a/src/axios/index.js b/src/axios/index.js new file mode 100644 index 0000000..7d5cf83 --- /dev/null +++ b/src/axios/index.js @@ -0,0 +1,17 @@ +import axios from "axios"; + +const token = localStorage.getItem("token"); + +const BASE_URL = 'https://inverse-tracker.ru/api'; + +export const instanceLogged = axios.create({ + baseURL: BASE_URL, + timeout: 30000, + headers: { Authorization: `Token ${token}` }, //Переделать + // headers: { Authorization: "Token db87a3d921c5f19f4367c808a85287d0f82cd258" }, //Переделать +}); + +export const instance = axios.create({ + baseURL: BASE_URL, + timeout: 1000, +}); \ No newline at end of file diff --git a/src/components/AllCourses/AllCourses.js b/src/components/AllCourses/AllCourses.js new file mode 100644 index 0000000..8a7559b --- /dev/null +++ b/src/components/AllCourses/AllCourses.js @@ -0,0 +1,47 @@ +import React, { useEffect } from 'react'; +import CardCourse from '../CardCourse/CardCourse'; +import { getAllCourses } from './getAllCourses'; +import { useSelector, useDispatch } from 'react-redux'; +import { Statuses } from '../../constant/statuses'; + +const AllCourses = () => { + const dispatch = useDispatch(); + + const isLoading = + useSelector((state) => state.courses.status) === Statuses.inProgress; + // console.log(isLoading); + + + const coursesData = useSelector((state) => state.courses.courses); + // console.log(coursesData); + + useEffect(() => { + getAllCourses(dispatch, coursesData.length); + }, []); + + if (isLoading) { + return Loading...; + } + return ( +
+ {coursesData.length > 0 && + coursesData.map((course) => ( + // console.log(course.name) + // + + ))} +
+ ); +}; + +export default AllCourses; diff --git a/src/components/AllCourses/getAllCourses.js b/src/components/AllCourses/getAllCourses.js new file mode 100644 index 0000000..f005722 --- /dev/null +++ b/src/components/AllCourses/getAllCourses.js @@ -0,0 +1,21 @@ +import { instanceLogged } from '../../axios'; +import {coursesSlice} from '../../store/slices/courses'; + +export const getAllCourses = async (dispatch, len) => { + console.log(len) + if (len > 0) { + return; + } + try { + //Get courses + dispatch(coursesSlice.actions.startLoading()); + const getCourses = await instanceLogged.get('courses/'); + dispatch(coursesSlice.actions.successLoading(getCourses.data)); + + // console.log(getCourses.data); + } catch (e) { + console.log(e); + dispatch(coursesSlice.actions.failLoading()); + return e; + } +}; diff --git a/src/components/CardCourse/CardCourse.js b/src/components/CardCourse/CardCourse.js new file mode 100644 index 0000000..3679996 --- /dev/null +++ b/src/components/CardCourse/CardCourse.js @@ -0,0 +1,42 @@ +import React from 'react'; +import time from '../../img/Course/time.svg'; +import title from '../../img/titleWrap.svg'; +import laptop from '../../img/laptop.svg'; +import './CardCourse.scss' + +const CardCourse = (props) => { + // const course = { + // name: 'Вязание бархатных тяг', + // description: 'Вы научитесь вязать лучшие бархатные тяги в мире.', + // category: 'вязание', + // }; + // console.log(props) + + return ( +
+
+ + +
+
+ + {props.name} + +
+ + + Пн, Ср, Пт + +
+ + {props.description} + + + {props.category} + +
+
+ ); +}; + +export default CardCourse; diff --git a/src/components/CardCourse/CardCourse.scss b/src/components/CardCourse/CardCourse.scss new file mode 100644 index 0000000..3d4404c --- /dev/null +++ b/src/components/CardCourse/CardCourse.scss @@ -0,0 +1,10 @@ + + +.description { +// @apply font-mont-semibold text-xs text-darkGray mb-3; + + -webkit-line-clamp: 1; /* Число отображаемых строк */ + display: -webkit-box; /* Включаем флексбоксы */ + -webkit-box-orient: vertical; /* Вертикальная ориентация */ + overflow: hidden; +} diff --git a/src/components/Layout/Layout.js b/src/components/Layout/Layout.js new file mode 100644 index 0000000..a312ac9 --- /dev/null +++ b/src/components/Layout/Layout.js @@ -0,0 +1,21 @@ +import React from 'react'; +import { Navigate, Outlet } from 'react-router-dom'; +import LeftBar from '../LeftBar/LeftBar'; +import { useAuth } from '../../hooks/useAuth'; + +// import "./Layout.scss"; + +const Layout = () => { + const auth = useAuth(); + + return auth ? ( +
+ + +
+ ) : ( + + ); +}; + +export default Layout; diff --git a/src/components/LeftBar/LeftBar.js b/src/components/LeftBar/LeftBar.js new file mode 100644 index 0000000..339c4e6 --- /dev/null +++ b/src/components/LeftBar/LeftBar.js @@ -0,0 +1,102 @@ +import React from 'react'; +import group from '../../img/LeftBar/Group.svg'; +import vector from '../../img/LeftBar/Vector.svg'; +import allcoursesActive from '../../img/LeftBar/allCoursesActive.svg'; +import title from '../../img/title.svg'; +import './LeftBar.scss'; +import AllCoursesIcon from '../../img/icon/AllCoursesIcon'; +import { Link } from 'react-router-dom'; + +const LeftBar = () => { + const pathName = window.location.pathname; + + return ( +
+ {/* */} + {/* */} + {/* */} + + +
    +
  • + + {pathName === '/my-courses' ? ( +
    +
    + + Мои курсы + +
    + ) : ( + <> +
    + + Мои курсы + + + )} + +
  • +
  • + + {pathName === '/courses' ? ( + <> +
    + + Все курсы + + + ) : ( + <> +
    + + Все курсы + + + )} + +
  • +
  • + + {pathName === '/news' ? ( + <> +
    + + Новости + + + ) : ( + <> +
    + + Новости + + + )} + +
  • +
  • + + {pathName === '/profile' ? ( + <> +
    + + Профиль + + + ) : ( + <> +
    + + Профиль + + + )} + +
  • +
+
+ ); +}; + +export default LeftBar; diff --git a/src/components/LeftBar/LeftBar.scss b/src/components/LeftBar/LeftBar.scss new file mode 100644 index 0000000..bae7b36 --- /dev/null +++ b/src/components/LeftBar/LeftBar.scss @@ -0,0 +1,115 @@ +// .li-title{ +// @apply font-mont-bold text-2xl text-darkGrey; +// } +// .li-title { +// @apply font-mont-bold text-2xl text-darkGrey; +// } + +// .test{ +// fill: red; +// } + +.link-my-courses { + @apply flex mb-8; + + &:hover .icon { + background-image: url('../../img/LeftBar/myCoursesActive.svg'); + } + .icon { + @apply w-6 h-6 mr-2; + background-image: url('../../img/LeftBar/myCoursesGrey.svg'); + } + + .iconActive { + @apply w-6 h-6 mr-2; + background-image: url('../../img/LeftBar/myCoursesActive.svg'); + } +} + +.link-all-courses { + @apply flex mb-8; + + &:hover .icon { + background-image: url('../../img/LeftBar/allCoursesActive.svg'); + } + .icon { + @apply w-6 h-6 mr-2; + background-image: url('../../img/LeftBar/allCoursesGrey.svg'); + } + + .iconActive { + @apply w-6 h-6 mr-2; + background-image: url('../../img/LeftBar/allCoursesActive.svg'); + } +} + + + +.link-all-courses { + @apply flex mb-8; + + &:hover .icon { + background-image: url('../../img/LeftBar/allCoursesActive.svg'); + } + .icon { + @apply w-6 h-6 mr-2; + background-image: url('../../img/LeftBar/allCoursesGrey.svg'); + } + + .iconActive { + @apply w-6 h-6 mr-2; + background-image: url('../../img/LeftBar/allCoursesActive.svg'); + } +} +.link-all-courses { + @apply flex mb-8; + + &:hover .icon { + background-image: url('../../img/LeftBar/allCoursesActive.svg'); + } + .icon { + @apply w-6 h-6 mr-2; + background-image: url('../../img/LeftBar/allCoursesGrey.svg'); + } + + .iconActive { + @apply w-6 h-6 mr-2; + background-image: url('../../img/LeftBar/allCoursesActive.svg'); + } +} + + +.link-news { + @apply flex mb-8; + + &:hover .icon { + background-image: url('../../img/LeftBar/newsActive.svg'); + } + .icon { + @apply w-6 h-6 mr-2; + background-image: url('../../img/LeftBar/newsGrey.svg'); + } + + .iconActive { + @apply w-6 h-6 mr-2; + background-image: url('../../img/LeftBar/newsActive.svg'); + } +} + + +.link-all-courses { + @apply flex; + + &:hover .icon { + background-image: url('../../img/LeftBar/allCoursesActive.svg'); + } + .icon { + @apply w-6 h-6 mr-2; + background-image: url('../../img/LeftBar/allCoursesGrey.svg'); + } + + .iconActive { + @apply w-6 h-6 mr-2; + background-image: url('../../img/LeftBar/allCoursesActive.svg'); + } +} diff --git a/src/components/Login/HandleLogin.js b/src/components/Login/HandleLogin.js new file mode 100644 index 0000000..ffb3b62 --- /dev/null +++ b/src/components/Login/HandleLogin.js @@ -0,0 +1,35 @@ +import { instance } from '../../axios'; +import { userSlice } from '../../store/slices/user'; + + +export const handleLogin = async (dispatch, email, password) => { + + try { + const userData = { email, password }; + + //Login user + const loginUser = await instance.post('users/auth/token/login/', userData); + + // console.log(loginUser.data); + + localStorage.setItem('token', loginUser.data.auth_token); + + + // Get user`s data + + dispatch(userSlice.actions.startLoading()); + + const getUserData = await instance.get('users/auth/users/me/', { + headers: { Authorization: `Token ${loginUser.data.auth_token}` }, + }); + + dispatch(userSlice.actions.successLoading(getUserData.data)); + + // console.log(getUserData.data); + + + } catch (e) { + dispatch(userSlice.actions.failLoading()); + return e; + } +}; diff --git a/src/components/Login/Login.js b/src/components/Login/Login.js new file mode 100644 index 0000000..6833542 --- /dev/null +++ b/src/components/Login/Login.js @@ -0,0 +1,72 @@ +import React, { useState } from 'react'; +import { handleLogin } from './HandleLogin'; +import { useDispatch, useSelector } from 'react-redux'; +import { Statuses } from '../../constant/statuses'; +import title from '../../img/title.svg'; +import { useAuth } from '../../hooks/useAuth'; + +import './Login.scss'; +import laptop from '../../img/laptop.svg'; +import { useNavigate } from 'react-router-dom'; + +const Login = ({ setModal }) => { + const dispatch = useDispatch(); + const navigate = useNavigate(); + + const auth = useAuth(); + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + + const isLoading = + useSelector((state) => state.user.status) === Statuses.inProgress; + + const usersData = useSelector((state) => state.user.user); + + if (auth) { + navigate('/courses') + } + // console.log(isLoading); + // console.log(usersData); + + return ( +
+
+ + +
+
+
+ Вход + setEmail(e.target.value)} + /> + setPassword(e.target.value)} + /> + + setModal(true)} + > + Забыли пароль? + +
+
+
+ ); +}; + +export default Login; diff --git a/src/components/Login/Login.scss b/src/components/Login/Login.scss new file mode 100644 index 0000000..a9dd8de --- /dev/null +++ b/src/components/Login/Login.scss @@ -0,0 +1,9 @@ +.img-laptop { + /* width: 470px; */ + width: 100%; + height: 40%; +} + +.data-layout { + @apply w-96 flex flex-col justify-center items-start; +} diff --git a/src/components/MyCourses/MyCourses.js b/src/components/MyCourses/MyCourses.js new file mode 100644 index 0000000..a263d33 --- /dev/null +++ b/src/components/MyCourses/MyCourses.js @@ -0,0 +1,28 @@ +import React from 'react'; +import CardCourse from '../CardCourse/CardCourse'; +import { useDispatch, useSelector } from 'react-redux'; +import { Statuses } from '../../constant/statuses'; + +const MyCourses = () => { + const dispatch = useDispatch(); + + const isLoading = + useSelector((state) => state.myCourses.status) === Statuses.inProgress; + const myCoursesData = useSelector((state) => state.myCourses.myCourses); + + return ( +
+ {myCoursesData.length > 0 && + myCoursesData.map((course) => ( + + ))} +
+ ); +}; + +export default MyCourses; diff --git a/src/components/MyCourses/getMyCourses.js b/src/components/MyCourses/getMyCourses.js new file mode 100644 index 0000000..38c30fd --- /dev/null +++ b/src/components/MyCourses/getMyCourses.js @@ -0,0 +1,20 @@ +import { instanceLogged } from '../../axios'; +import myCoursesSlice from '../../store/slices/myCourses'; + +export const getMyCourses = async (dispatch, len) => { + if (len > 0) { + return; + } + try { + //Get courses + dispatch(myCoursesSlice.actions.startLoading()); + const getMyCourses = await instanceLogged.get('users/me/teacher/courses/'); + dispatch(myCoursesSlice.actions.successLoading(getMyCourses.data)); + + // console.log(getCourses.data); + } catch (e) { + console.log(e); + dispatch(myCoursesSlice.actions.failLoading()); + return e; + } +}; diff --git a/src/components/NewCourse/ListCategories/ListCategories.js b/src/components/NewCourse/ListCategories/ListCategories.js new file mode 100644 index 0000000..dc468df --- /dev/null +++ b/src/components/NewCourse/ListCategories/ListCategories.js @@ -0,0 +1,129 @@ +import React, { useEffect } from 'react'; +import { useSelector, useDispatch } from 'react-redux'; +import { Statuses } from '../../../constant/statuses'; +import { getCategories } from './getCategories'; + +const ListCategories = () => { + const dispatch = useDispatch(); + + // const isLoading = + // useSelector((state) => state.categories.status) === Statuses.inProgress; + + // const categoriesData = useSelector((state) => state.categories.categories); + // console.log(coursesData.length); + + // useEffect(() => { + // getCategories(dispatch, categoriesData.length); + // }, []); + + return ( +
+
+ Категория +
    +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
  • + + +
  • +
+
+
+ ); +}; + +export default ListCategories; diff --git a/src/components/NewCourse/ListCategories/getCategories.js b/src/components/NewCourse/ListCategories/getCategories.js new file mode 100644 index 0000000..b1ffa19 --- /dev/null +++ b/src/components/NewCourse/ListCategories/getCategories.js @@ -0,0 +1,20 @@ +import { categoriesSlice } from '../../../store/slices/categories'; +import { instanceLogged } from '../../../axios'; + +export const getCategories = async (dispatch, len) => { + if (len > 0) { + return; + } + try { + //Get categories + dispatch(categoriesSlice.actions.startLoading()); + const getCategories = await instanceLogged.get('courses/categories/'); + dispatch(categoriesSlice.actions.successLoading(getCategories.data)); + + console.log(getCategories.data); + } catch (e) { + console.log(e); + dispatch(categoriesSlice.actions.failLoading()); + return e; + } +}; diff --git a/src/components/NewCourse/NewCourse.js b/src/components/NewCourse/NewCourse.js new file mode 100644 index 0000000..a23344f --- /dev/null +++ b/src/components/NewCourse/NewCourse.js @@ -0,0 +1,168 @@ +import React, { useState } from 'react'; +import folder from '../../img/Course/Folder.svg'; +import ListCategories from './ListCategories/ListCategories'; +import { useSelector } from 'react-redux'; + +const NewCourse = () => { + const [name, setName] = useState(''); + const [place, setPlace] = useState(''); + const [description, setDescription] = useState(''); + const [groupName, setGroupName] = useState(''); + const [number, setNumber] = useState(''); + const [time, setTime] = useState(''); + + const teacherData = useSelector((state) => state.user.user); + console.log(teacherData); + + const courseData = { + name: name, + description: description, + }; + + return ( +
+ + Новый курс + +
+
+ + Название + + setName(e.target.value)} + /> + Место + setName(e.target.value)} + /> + + Описание + +