diff --git a/src/App.jsx b/src/App.jsx index e7e5503..b3fdf51 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -1,12 +1,12 @@ import { BrowserRouter, Routes, Route, Navigate } from "react-router-dom"; import { useState, useMemo } from "react"; import { ApolloProvider } from '@apollo/client/react'; - import NotifierContext from "./context/NotifierContext"; import client from './lib/apolloClient'; import HomePage from "./pages/HomePage"; import InfoPage from "./pages/InfoPage"; import ProjectsPage from "./pages/ProjectsPage"; +import ProjectPage from "./pages/ProjectPage"; const App = () => { const [message, setMessage] = useState(""); @@ -31,6 +31,7 @@ const App = () => { } /> } /> } /> + } /> } /> diff --git a/src/components/atoms/ErrorDisplay/ErrorDisplay.jsx b/src/components/atoms/ErrorDisplay/ErrorDisplay.jsx new file mode 100644 index 0000000..c72080e --- /dev/null +++ b/src/components/atoms/ErrorDisplay/ErrorDisplay.jsx @@ -0,0 +1,13 @@ +import React from "react"; +import { Alert } from "react-bootstrap"; + +const ErrorDisplay = ({ message }) => { + return ( + + Error +

{message}

+
+ ); +}; + +export default ErrorDisplay; diff --git a/src/components/atoms/ErrorDisplay/index.js b/src/components/atoms/ErrorDisplay/index.js new file mode 100644 index 0000000..7e63fb4 --- /dev/null +++ b/src/components/atoms/ErrorDisplay/index.js @@ -0,0 +1 @@ +export { default } from "./ErrorDisplay"; diff --git a/src/components/atoms/SkeletonLoading/ProjectSkeleton/ProjectSkeleton.jsx b/src/components/atoms/SkeletonLoading/ProjectSkeleton/ProjectSkeleton.jsx new file mode 100644 index 0000000..16d2a02 --- /dev/null +++ b/src/components/atoms/SkeletonLoading/ProjectSkeleton/ProjectSkeleton.jsx @@ -0,0 +1,13 @@ +import React from "react"; +import { SkeletonWrapper } from "./styled"; + +const ProjectsSkeleton = () => { + return ( + +
+
+ + ); +}; + +export default ProjectsSkeleton; diff --git a/src/components/atoms/SkeletonLoading/ProjectSkeleton/index.js b/src/components/atoms/SkeletonLoading/ProjectSkeleton/index.js new file mode 100644 index 0000000..166e818 --- /dev/null +++ b/src/components/atoms/SkeletonLoading/ProjectSkeleton/index.js @@ -0,0 +1 @@ +export { default } from "./ProjectSkeleton"; diff --git a/src/components/atoms/SkeletonLoading/ProjectSkeleton/styled.js b/src/components/atoms/SkeletonLoading/ProjectSkeleton/styled.js new file mode 100644 index 0000000..a0772fd --- /dev/null +++ b/src/components/atoms/SkeletonLoading/ProjectSkeleton/styled.js @@ -0,0 +1,40 @@ +import styled, { keyframes } from "styled-components"; + +const wave = keyframes` + 0% { + transform: translateX(-100%); + } + 50% { + transform: translateX(100%); + } + 100% { + transform: translateX(-100%); + } +`; + +export const SkeletonWrapper = styled.div` + .skeleton-line { + height: 20px; + background-color: #f0f0f0; + border-radius: 4px; + margin-bottom: 10px; + position: relative; + overflow: hidden; + } + + .skeleton-line:before { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient( + to right, + rgba(255, 255, 255, 0) 0%, + rgba(255, 255, 255, 0.7) 50%, + rgba(255, 255, 255, 0) 100% + ); + animation: ${wave} 3.5s infinite; + } +`; diff --git a/src/components/atoms/SkeletonLoading/ProjectsSkeleton/ProjectsSkeleton.jsx b/src/components/atoms/SkeletonLoading/ProjectsSkeleton/ProjectsSkeleton.jsx new file mode 100644 index 0000000..6493ce3 --- /dev/null +++ b/src/components/atoms/SkeletonLoading/ProjectsSkeleton/ProjectsSkeleton.jsx @@ -0,0 +1,43 @@ +import React from "react"; +import { SkeletonWrapper, TableCol, TableColActions, TableHead, Table } from "./styled"; + +const ProjectSkeleton = () => { + return ( + +
+ + + + id + Name + Description + Actions + + + + {[1, 2, 3, 4, 5, 6, 7].map((item) => ( + + +
+ + +
+ + +
+ + +
+
+
+ +
+ ))} + +
+
+
+ ); +}; + +export default ProjectSkeleton; diff --git a/src/components/atoms/SkeletonLoading/ProjectsSkeleton/index.js b/src/components/atoms/SkeletonLoading/ProjectsSkeleton/index.js new file mode 100644 index 0000000..bd72b77 --- /dev/null +++ b/src/components/atoms/SkeletonLoading/ProjectsSkeleton/index.js @@ -0,0 +1 @@ +export { default } from "./ProjectsSkeleton"; diff --git a/src/components/atoms/SkeletonLoading/ProjectsSkeleton/styled.js b/src/components/atoms/SkeletonLoading/ProjectsSkeleton/styled.js new file mode 100644 index 0000000..8a09ece --- /dev/null +++ b/src/components/atoms/SkeletonLoading/ProjectsSkeleton/styled.js @@ -0,0 +1,92 @@ +import styled, { keyframes } from "styled-components"; + +/* Wave Animation for Skeletons(from Yt Tutorial mixed with original table) */ +const wave = keyframes` + 0% { + transform: translateX(-100%); + } + 50% { + transform: translateX(100%); + } + 100% { + transform: translateX(-100%); + } +`; + +export const SkeletonWrapper = styled.div` + + .skeleton-line { + height: 20px; + background-color: #f0f0f0; + border-radius: 4px; + margin-bottom: 10px; + position: relative; + overflow: hidden; + } + + .skeleton-line:before { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient( + to right, + rgba(255, 255, 255, 0) 0%, + rgba(255, 255, 255, 0.7) 50%, + rgba(255, 255, 255, 0) 100% + ); + animation: ${wave} 3.5s infinite; + } + + .skeleton-button { + width: 100px; + height: 35px; + background-color: #f0f0f0; + border-radius: 4px; + margin-right: 10px; + display: inline-block; + position: relative; + overflow: hidden; + } + + .skeleton-button:before { + content: ""; + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: linear-gradient( + to right, + rgba(255, 255, 255, 0) 0%, + rgba(255, 255, 255, 0.7) 50%, + rgba(255, 255, 255, 0) 100% + ); + animation: ${wave} 3.5s infinite; + } +`; + +export const Table = styled.table` + margin: 1rem 0; + text-align: left; + width: 100%; + border-collapse: collapse; +`; + +export const TableHead = styled.th` + padding 1rem; + border-bottom: 1px solid grey; +`; + +export const TableCol = styled.td` + padding: 1rem; + border-bottom: 1px solid #dee2e6; +`; + +export const TableColActions = styled(TableCol)` + width: 20rem; +`; + +export default SkeletonWrapper; diff --git a/src/components/organisms/ProjectsTable/ProjectsTable.jsx b/src/components/organisms/ProjectsTable/ProjectsTable.jsx index 098c308..0be62cd 100644 --- a/src/components/organisms/ProjectsTable/ProjectsTable.jsx +++ b/src/components/organisms/ProjectsTable/ProjectsTable.jsx @@ -1,9 +1,7 @@ import Button from 'react-bootstrap/Button'; - import { useState, useContext } from 'react'; - +import { Link } from "react-router-dom"; import DeleteModal from '../../molecules/DeleteModal'; - import { Table, TableHead, TableCol, TableColActions } from './styled'; import NotifierContext from "../../../context/NotifierContext"; @@ -46,14 +44,19 @@ const ProjectsTable = ({ projects }) => { {id} {name} {description} - - - - + + + + + - ) + ); })} diff --git a/src/lib/apolloClient.js b/src/lib/apolloClient.js index b363fdb..73a299a 100644 --- a/src/lib/apolloClient.js +++ b/src/lib/apolloClient.js @@ -1,8 +1,9 @@ import { ApolloClient, InMemoryCache } from '@apollo/client'; const client = new ApolloClient({ - uri: process.env.REACT_APP_API_URL, - cache: new InMemoryCache() + uri: "https://tasktracker-itis-2024-269a840ae88e.herokuapp.com/graphql", + cache: new InMemoryCache(), + connectToDevTools: true, }); export default client; diff --git a/src/lib/hooks/project.js b/src/lib/hooks/project.js index 7ad2362..4086b8f 100644 --- a/src/lib/hooks/project.js +++ b/src/lib/hooks/project.js @@ -1,29 +1,12 @@ import { useQuery } from "@apollo/client"; -import Projects from "src/graphql/queries/projects"; -import Project from "src/graphql/queries/project"; - -export const useProjects = () => { - const { data, loading, error } = useQuery(Projects, { - fetchPolicy: "cache-and-network", - }); - - return { - loading, - error, - projects: data?.projects || [], - } -}; - - - - +import ProjectQuery from "src/graphql/queries/project"; export const useProject = ({ projectId }) => { - const { data, loading, error } = useQuery(Project, { + const { data, loading, error } = useQuery(ProjectQuery, { fetchPolicy: "cache-and-network", - variables: { projectId } + variables: { projectId }, }); return { @@ -31,4 +14,4 @@ export const useProject = ({ projectId }) => { loading, error, }; -} +}; diff --git a/src/lib/hooks/projects.js b/src/lib/hooks/projects.js new file mode 100644 index 0000000..061efb5 --- /dev/null +++ b/src/lib/hooks/projects.js @@ -0,0 +1,16 @@ +import { useQuery } from "@apollo/client"; + +import Projects from "src/graphql/queries/projects"; + +export const useProjects = () => { + const { data, loading, error } = useQuery(Projects, { + fetchPolicy: "cache-and-network", + }); + + return { + loading, + error, + projects: data?.projects || [], + }; +}; + diff --git a/src/pages/ProjectPage.jsx b/src/pages/ProjectPage.jsx new file mode 100644 index 0000000..0f92d1c --- /dev/null +++ b/src/pages/ProjectPage.jsx @@ -0,0 +1,24 @@ +import React from "react"; +import { useParams } from "react-router-dom"; +import DefaultTemplate from "../components/templates/DefaultTemplate"; +import { useProject } from "../lib/hooks/project"; +import ErrorDisplay from "../components/atoms/ErrorDisplay"; +import ProjectSkeleton from "../components/atoms/SkeletonLoading/ProjectSkeleton"; + +const ProjectPage = () => { + const { projectId } = useParams(); + const { loading, error, project } = useProject({ projectId }); + + return ( + + {error && } + {loading && } +
+

{project.name}

+

{project.description}

+
+
+ ); +}; + +export default ProjectPage; diff --git a/src/pages/ProjectsPage.jsx b/src/pages/ProjectsPage.jsx index dd75acd..e829299 100644 --- a/src/pages/ProjectsPage.jsx +++ b/src/pages/ProjectsPage.jsx @@ -1,7 +1,10 @@ +import React from "react"; import DefaultTemplate from "../components/templates/DefaultTemplate"; -import ProjectsTable from '../components/organisms/ProjectsTable'; +import ProjectsTable from "../components/organisms/ProjectsTable"; +import ErrorDisplay from "../components/atoms/ErrorDisplay"; +import ProjectSkeleton from "../components/atoms/SkeletonLoading/ProjectsSkeleton"; -import { useProjects } from "../lib/hooks/project"; +import { useProjects } from "../lib/hooks/projects"; const ProjectsPage = () => { const { projects, loading, error } = useProjects(); @@ -9,11 +12,8 @@ const ProjectsPage = () => { return (

Projects List

- - {error && !loading &&
Ошибка
} - - {loading &&
Загрузка...
} - + {error && } + {loading && } {projects && !loading && }
);