moving online

This commit is contained in:
QkoSad
2022-11-07 19:37:10 +02:00
parent 8918854436
commit ac917e6e26
26 changed files with 1829 additions and 133 deletions
+19
View File
@@ -0,0 +1,19 @@
{
"feedback":[
{
"id":3,
"rating":10,
"text":"This is feedback item 3 comfing from the backend"
},
{
"id":2,
"rating":10,
"text":"This is feedback item 2 comfing from the backend"
},
{
"id":1,
"rating":10,
"text":"This is feedback item 1 comfing from the backend"
}
]
}
+1154
View File
File diff suppressed because it is too large Load Diff
+9 -1
View File
@@ -2,12 +2,18 @@
"name": "feed-app",
"version": "0.1.0",
"private": true,
"proxy":"http://localhost:5000",
"dependencies": {
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"concurrently": "^7.5.0",
"framer-motion": "^4.1.17",
"json-server": "^0.17.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-icons": "^4.6.0",
"react-router-dom": "^6.4.3",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},
@@ -15,7 +21,9 @@
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
"eject": "react-scripts eject",
"server": "json-server --watch db.json --port 5000",
"dev": "concurrently \"npm run server\" \"npm start\""
},
"eslintConfig": {
"extends": [
+1 -24
View File
@@ -10,34 +10,11 @@
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>React App</title>
<title>Feedback UI</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>
-38
View File
@@ -1,38 +0,0 @@
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
background-color: #282c34;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
+30 -18
View File
@@ -1,24 +1,36 @@
import logo from './logo.svg';
import './App.css';
import { BrowserRouter as Router, Route, Routes } from "react-router-dom";
import Header from "./components/Header";
import FeedbackList from "./components/FeedbackList";
import FeedbackStats from "./components/FeedbackStats";
import FeedbackForm from "./components/FeedbackForm";
import AboutPage from "./pages/AboutPage";
import AboutIconLink from "./components/shared/AboutIconLink";
import { FeedbackProvider } from "./context/FeedbackContext";
function App() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.js</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
<FeedbackProvider>
<Router>
<Header />
<div className="container">
<Routes>
<Route
exact
path="/"
element={
<>
<FeedbackForm />
<FeedbackStats />
<FeedbackList />
</>
}
></Route>
<Route path="/about" element={<AboutPage />} />
</Routes>
<AboutIconLink />
</div>
</Router>
</FeedbackProvider>
);
}
-8
View File
@@ -1,8 +0,0 @@
import { render, screen } from '@testing-library/react';
import App from './App';
test('renders learn react link', () => {
render(<App />);
const linkElement = screen.getByText(/learn react/i);
expect(linkElement).toBeInTheDocument();
});
+76
View File
@@ -0,0 +1,76 @@
import { useState, useContext, useEffect } from "react";
import FeedbackContext from "../context/FeedbackContext";
import Card from "./shared/Card";
import Button from "./shared/Button";
import RatingSelect from "./shared/RatingSelect";
function FeedbackForm() {
const [text, setText] = useState("");
const [btnDisabled, setBtnDisabled] = useState(true);
const [rating, setRating] = useState(10);
const [message, setMessage] = useState("");
const { addFeedback, updateFeedback, feedbackEdit } =
useContext(FeedbackContext);
useEffect(() => {
if (feedbackEdit.edit === true) {
setBtnDisabled(false);
setText(feedbackEdit.item.text);
setRating(feedbackEdit.item.rating);
}
}, [feedbackEdit]);
const handleTextChange = ({target:{value}}) => {
if (value === "") {
setBtnDisabled(true);
setMessage(null);
} else if (value.trim().length<10){
setMessage("Text must be at least 10 characters");
setBtnDisabled(true);
} else {
setMessage(null);
setBtnDisabled(false);
}
setText(value)
};
const handleSubmit = (e) => {
e.preventDefault();
if (text.trim().length > 10) {
const newFeedback = {
text,
rating,
};
if (feedbackEdit.edit === true) {
updateFeedback(feedbackEdit.item.id, newFeedback);
} else {
addFeedback(newFeedback);
}
setText("");
}
};
return (
<Card>
<form onSubmit={handleSubmit}>
<h2> How would you rate your service with us?</h2>
<RatingSelect select={(rating) => setRating(rating)} />
<div className="input-group">
<input
type="text"
placeholder="write a review"
onChange={handleTextChange}
value={text}
/>
<Button type="submit" isDisabled={btnDisabled}>
Send
</Button>
</div>
{message && <div className="message">{message}</div>}
</form>
</Card>
);
}
export default FeedbackForm;
+21
View File
@@ -0,0 +1,21 @@
import { FaTimes, FaEdit } from "react-icons/fa";
import { useContext } from "react";
import Card from "./shared/Card"
import FeedbackContext from "../context/FeedbackContext";
function FeedbackItem({ item }) {
const { deleteFeedback, editFeedback } = useContext(FeedbackContext);
return (
<Card>
<div className="num-display">{item.rating}</div>
<button className="close" onClick={() => deleteFeedback(item.id)}>
<FaTimes color="purple" />
</button>
<button className="edit" onClick={()=>editFeedback(item)}>
<FaEdit color="purple" />
</button>
<div className="text-display">{item.text}</div>
</Card>
);
}
export default FeedbackItem;
+32
View File
@@ -0,0 +1,32 @@
import { useContext } from "react";
import { AnimatePresence, motion } from "framer-motion";
import FeedbackItem from "./FeedbackItem";
import Spinner from "./shared/Spinner";
import FeedbackContext from "../context/FeedbackContext";
function FeedbackList() {
const { feedback, isLoading } = useContext(FeedbackContext);
if (!isLoading && (!feedback || feedback.length === 0)) {
return <p>No Feedback Yet</p>;
}
return isLoading ? (
<Spinner />
) : (
<div className="feedback-list">
<AnimatePresence>
{feedback.map((item) => (
<motion.div
key={item.id}
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
>
<FeedbackItem key={item.id} item={item} />
</motion.div>
))}
</AnimatePresence>
</div>
);
}
export default FeedbackList;
+20
View File
@@ -0,0 +1,20 @@
import { useContext } from "react";
import FeedbackContext from "../context/FeedbackContext";
function FeedbackStats() {
const { feedback } = useContext(FeedbackContext);
let average =
feedback.reduce((acc, cur) => {
return acc + cur.rating;
}, 0) / feedback.length;
average = average.toFixed(1).replace(/[.,]0$/, "");
return (
<div className="feedback-stats">
<h4>{feedback.length} Reviews</h4>
<h4>Average Rating: {isNaN(average) ? 0 : average}</h4>
</div>
);
}
export default FeedbackStats;
+25
View File
@@ -0,0 +1,25 @@
import PropTypes from "prop-types"
function Header ({text,bgColor,textColor}){
const headerStyles = {
color: textColor,
backgroundColor:bgColor
}
return(
<header style={headerStyles}>
<div className="container"><h2>{text}</h2></div>
</header>
)
}
Header.defaultProps = {
text:"FeedBack UI",
bgColor:'rgba(0,0,0,0.4)',
textColor: '#ff6a95',
}
Header.propTypes = {
text: PropTypes.string,
bgColor:PropTypes.string,
textColor: PropTypes.string
}
export default Header
Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

+12
View File
@@ -0,0 +1,12 @@
import { Link } from "react-router-dom";
import { FaQuestion } from "react-icons/fa";
function AboutIconLink() {
return (
<div className="about-link">
<Link to="/">
<FaQuestion size={30} />
</Link>
</div>
);
}
export default AboutIconLink;
+20
View File
@@ -0,0 +1,20 @@
import PropTypes from 'prop-types'
function Button ({children, version, type, isDisabled}){
return (
<button type={type} disabled={isDisabled} className={`btn btn-${version}`}>
{children}</button>
)
}
Button.defaultProps={
version: 'primavry',
type: 'button'
,
isDisabled:false
}
Button.propTypes ={
children: PropTypes.node.isRequired,
version:PropTypes.string,
type:PropTypes.string,
isDisabled:PropTypes.bool
}
export default Button;
+15
View File
@@ -0,0 +1,15 @@
import PropTypes from 'prop-types'
function Card({children, reverse}){
//return (<div className={`card ${reverse && 'reverse'}`}>{children}</div>)
return (<div className='card' style={{backgroundColor: reverse ? 'rga(0,0,0,0.4)' :'#fff',
color: reverse ? '#fff' : '#000',}}>{children}</div> )
}
Card.defaultProps = {
reverse:false
}
Card.propTypes={
children:PropTypes.node.isRequired,
reverse:PropTypes.bool
}
export default Card;
+33
View File
@@ -0,0 +1,33 @@
import { useEffect,useState ,useContext} from "react";
import FeedbackContext from "../../context/FeedbackContext";
function RatingSelect({ select }) {
const [selected, setSelected] = useState(10);
const { feedbackEdit } = useContext(FeedbackContext);
const handleChange = (e) => {
setSelected(+e.currentTarget.value);
select(+e.currentTarget.value);
};
useEffect(()=>{
setSelected(feedbackEdit.item.rating)
},[feedbackEdit])
return (
<ul className="rating">
{Array.from({ length: 10 }, (_, i) => (
<li key={`rating-${i + 1}`}>
<input
type="radio"
id={`num${i + 1}`}
name="rating"
value={i + 1}
onChange={handleChange}
checked={selected === i + 1}
/>
<label htmlFor={`num${i + 1}`}>{i + 1}</label>
</li>
))}
</ul>
);
}
export default RatingSelect;
+12
View File
@@ -0,0 +1,12 @@
import spinner from "../assets/spinner.gif";
function Spinner() {
return (
<img
src={spinner}
alt="Loading..."
style={{ width: "100px", margin: "auto", display: "block" }}
/>
);
}
export default Spinner
+80
View File
@@ -0,0 +1,80 @@
import { createContext, useState, useEffect } from "react";
const FeedbackContext = createContext();
export const FeedbackProvider = ({ children }) => {
const [isloading, setIsLoading] = useState(true);
const [feedback, setFeedback] = useState([]);
const [feedbackEdit, setFeedbackEdit] = useState({
item: {},
edit: false,
});
useEffect(() => {
fetchFeedback();
}, []);
// Fetch feedback
const fetchFeedback = async () => {
const response = await fetch(`/feedback?_sort=id&_order=desc`);
const data = await response.json();
setFeedback(data);
setIsLoading(false);
};
const addFeedback = async (newFeedback) => {
const response = await fetch("/feedback", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(newFeedback),
});
const data = await response.json();
setFeedback([data, ...feedback]);
};
const deleteFeedback = async (id) => {
if (window.confirm("Are you sure you want to delete?")) {
await fetch(`/feedback/${id}`, { method: "DELETE" });
setFeedback(feedback.filter((item) => item.id !== id));
}
};
const updateFeedback = async (id, updItem) => {
const response = await fetch(`/feedback/${id}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(updItem),
});
const data = await response.json();
setFeedback(feedback.map((item) => (item.id === id ? data : item)));
setFeedbackEdit({ item: {}, edit: false });
};
//set item to be updated
const editFeedback = (item) => {
setFeedbackEdit({
item,
edit: true,
});
};
return (
<FeedbackContext.Provider
value={{
feedback,
deleteFeedback,
addFeedback,
editFeedback,
feedbackEdit,
updateFeedback,
isloading,
}}
>
{children}
</FeedbackContext.Provider>
);
};
export default FeedbackContext;
+18
View File
@@ -0,0 +1,18 @@
const FeedbackData = [
{
id: 1,
rating: 10,
text: "Lorem ipsum dolor sit amet consectetur adipisicing elit. consequuntur vel vitae commodi alias voluptatem est voluptatum ipsa quae.",
},
{
id: 2,
rating: 9,
text: "Lorem ipsum dolor sit amet consectetur adipisicing elit. consequuntur vel vitae commodi alias voluptatem est voluptatum ipsa quae.",
},
{
id: 3,
rating: 8,
text: "Lorem ipsum dolor sit amet consectetur adipisicing elit. consequuntur vel vitae commodi alias voluptatem est voluptatum ipsa quae.",
},
];
export default FeedbackData;
+226 -9
View File
@@ -1,13 +1,230 @@
body {
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;600;700&display=swap');
* {
box-sizing: border-box;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
padding: 0;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
body {
font-family: 'Poppins', sans-serif;
background-color: #202142;
color: #fff;
line-height: 1.6;
}
ul {
list-style: none;
}
.container {
max-width: 768px;
margin: auto;
padding: 0 20px;
}
header {
height: 70px;
width: 100%;
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 30px;
}
.card {
background-color: #fff;
color: #333;
border-radius: 15px;
padding: 40px 50px;
margin: 20px 0;
position: relative;
}
.card.reverse {
background-color: rgba(0, 0, 0, 0.4);
color: #fff;
}
.card h2 {
font-size: 22px;
font-weight: 600;
text-align: center;
}
.rating {
display: flex;
align-items: center;
justify-content: space-around;
margin: 30px 0 40px;
}
.rating li,
.num-display {
position: relative;
background: #f4f4f4;
width: 50px;
height: 50px;
padding: 10px;
text-align: center;
border-radius: 50%;
font-size: 19px;
border: 1px #eee solid;
transition: 0.3s;
}
.rating li label {
position: absolute;
top: 50%;
left: 50%;
width: 50px;
height: 50px;
padding: 10px;
border-radius: 50%;
transform: translate(-50%, -50%);
cursor: pointer;
}
.rating li:hover,
.num-display {
background: #ff6a95;
color: #fff;
}
[type='radio'] {
opacity: 0;
}
[type='radio']:checked ~ label {
background: #ff6a95;
color: #fff;
}
.input-group {
display: flex;
flex-direction: row;
border: 1px solid #ccc;
padding: 8px 10px;
border-radius: 8px;
}
input {
flex-grow: 2;
border: none;
font-size: 16px;
}
input:focus {
outline: none;
}
.feedback-stats {
color: #fff;
display: flex;
justify-content: space-between;
align-items: center;
}
.num-display {
position: absolute;
top: -10px;
left: -10px;
}
.close,
.edit {
position: absolute;
top: 10px;
right: 20px;
cursor: pointer;
background: none;
border: none;
}
.edit {
right: 40px;
}
.btn {
color: #fff;
border: 0;
border-radius: 8px;
color: #fff;
width: 100px;
height: 40px;
cursor: pointer;
}
.btn-primary {
background-color: #202142;
}
.btn-secondary {
background: #ff6a95;
}
.btn:hover {
transform: scale(0.98);
opacity: 0.9;
}
.btn:disabled {
background-color: #cccccc;
color: #333;
cursor: auto;
}
.btn:disabled:hover {
transform: scale(1);
opacity: 1;
}
.message {
padding-top: 10px;
text-align: center;
color: rebeccapurple;
}
/* FIX: Remove position: absolute to keep about icon at the bottom of the
* document */
.about-link {
display: flex;
justify-content: flex-end;
}
/* FIX: Remove position: absolute to keep about icon at the bottom of the
* document */
.about-link a {
color: #fff;
cursor: pointer;
}
.about-link a:hover {
color: #ff6a95;
}
.about h1 {
margin-bottom: 20px;
}
.about p {
margin: 10px 0;
}
@media (max-width: 600px) {
.rating li {
margin: 10px 3px;
}
.rating {
display: flex;
align-items: center;
justify-content: center;
flex-wrap: wrap;
margin: 30px 0 40px;
}
.input-group input {
width: 80%;
}
}
+9 -16
View File
@@ -1,17 +1,10 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
const root = ReactDOM.createRoot(document.getElementById('root'));
import ReactDOM from "react-dom/client";
import React from "react";
import App from "./App";
import './index.css'
const container = document.getElementById('root')
const root = ReactDOM.createRoot(container)
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
<React.StrictMode>
<App />
</ React.StrictMode>)
-1
View File
@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3"><g fill="#61DAFB"><path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/><circle cx="420.9" cy="296.5" r="45.7"/><path d="M520.5 78.1z"/></g></svg>

Before

Width:  |  Height:  |  Size: 2.6 KiB

+17
View File
@@ -0,0 +1,17 @@
import { Link } from "react-router-dom";
import Card from "../components/shared/Card";
function AboutPage() {
return (
<Card>
<div className="about">
<h1>About this Project</h1>
<p>This is a React app to leave a feedback for a product or servicec</p>
<p>Version: 1.0.0</p>
<p>
<Link to="/">Back to Home</Link>
</p>
</div>
</Card>
);
}
export default AboutPage;
-13
View File
@@ -1,13 +0,0 @@
const reportWebVitals = onPerfEntry => {
if (onPerfEntry && onPerfEntry instanceof Function) {
import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => {
getCLS(onPerfEntry);
getFID(onPerfEntry);
getFCP(onPerfEntry);
getLCP(onPerfEntry);
getTTFB(onPerfEntry);
});
}
};
export default reportWebVitals;
-5
View File
@@ -1,5 +0,0 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom';