renamed
@@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
missing 2.19,2.20
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "coutry-app",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"axios": "^1.4.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1,43 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
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>
|
||||
</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>
|
||||
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 9.4 KiB |
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
@@ -0,0 +1,38 @@
|
||||
.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);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import axios from "axios";
|
||||
import { useState } from "react";
|
||||
|
||||
function App() {
|
||||
const [countries, setCountries] = useState([]);
|
||||
const [filter, setFilter] = useState("");
|
||||
useState(() => {
|
||||
axios
|
||||
.get("https://studies.cs.helsinki.fi/restcountries/api/all")
|
||||
.then((response) => setCountries(response.data))
|
||||
.catch((err) => console.log(err));
|
||||
}, []);
|
||||
const countriesDisplay = countries.filter((el, index) =>
|
||||
el.name.common.toLowerCase().includes(filter.toLowerCase())
|
||||
);
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>Find Countries</div>
|
||||
<input value={filter} onChange={(e) => setFilter(e.target.value)} />
|
||||
<ul>
|
||||
{countriesDisplay.length > 10 ? (
|
||||
"too many countries"
|
||||
) : countriesDisplay.length > 1 ? (
|
||||
countriesDisplay.map((el, index) => (
|
||||
<li key={index}>{el.name.common}</li>
|
||||
))
|
||||
) : countriesDisplay.length === 1 ? (
|
||||
<>
|
||||
<li key={2}>
|
||||
<h3>{countriesDisplay[0]?.name.official}</h3>
|
||||
</li>
|
||||
<li key={1}>catial{countriesDisplay[0]?.capital[0]}</li>
|
||||
<li key={2}>languages</li>
|
||||
</>
|
||||
) : null}
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
@@ -0,0 +1,8 @@
|
||||
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();
|
||||
});
|
||||
@@ -0,0 +1,13 @@
|
||||
body {
|
||||
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;
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
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'));
|
||||
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();
|
||||
@@ -0,0 +1 @@
|
||||
<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>
|
||||
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1,13 @@
|
||||
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;
|
||||
@@ -0,0 +1,5 @@
|
||||
// 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';
|
||||
@@ -0,0 +1,2 @@
|
||||
PORT=3001
|
||||
MONGODB_URI=mongodb+srv://admin:admin@cluster1.ciuxwxd.mongodb.net/personApp?retryWrites=true&w=majority
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"files": {
|
||||
"main.css": "/static/css/main.305410ed.css",
|
||||
"main.js": "/static/js/main.dde63dc0.js",
|
||||
"index.html": "/index.html",
|
||||
"main.305410ed.css.map": "/static/css/main.305410ed.css.map",
|
||||
"main.dde63dc0.js.map": "/static/js/main.dde63dc0.js.map"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/css/main.305410ed.css",
|
||||
"static/js/main.dde63dc0.js"
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>React App</title><script defer="defer" src="/static/js/main.dde63dc0.js"></script><link href="/static/css/main.305410ed.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
||||
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 9.4 KiB |
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
@@ -0,0 +1,2 @@
|
||||
.error{color:red}.error,.normal{background:#d3d3d3;border-radius:5px;border-style:solid;font-size:20px;margin-bottom:10px;padding:10px}.normal{color:green}
|
||||
/*# sourceMappingURL=main.305410ed.css.map*/
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"static/css/main.305410ed.css","mappings":"AAAA,OACE,SAOF,CACA,eAPE,kBAAqB,CAGrB,iBAAkB,CADlB,kBAAmB,CADnB,cAAe,CAIf,kBAAmB,CADnB,YAWF,CARA,QACE,WAOF","sources":["index.css"],"sourcesContent":[".error {\n color: red;\n background: lightgrey;\n font-size: 20px;\n border-style: solid;\n border-radius: 5px;\n padding: 10px;\n margin-bottom: 10px;\n}\n.normal {\n color: green;\n background: lightgrey;\n font-size: 20px;\n border-style: solid;\n border-radius: 5px;\n padding: 10px;\n margin-bottom: 10px;\n}\n"],"names":[],"sourceRoot":""}
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* @license React
|
||||
* react-dom.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react-jsx-runtime.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* scheduler.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* @license React
|
||||
* react-dom.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react-jsx-runtime.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* scheduler.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* @license React
|
||||
* react-dom.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react-jsx-runtime.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* scheduler.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* @license React
|
||||
* react-dom.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react-jsx-runtime.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* scheduler.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* @license React
|
||||
* react-dom.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react-jsx-runtime.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* scheduler.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* @license React
|
||||
* react-dom.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react-jsx-runtime.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* scheduler.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* @license React
|
||||
* react-dom.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react-jsx-runtime.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* scheduler.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* @license React
|
||||
* react-dom.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react-jsx-runtime.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* scheduler.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* @license React
|
||||
* react-dom.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react-jsx-runtime.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* scheduler.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
@@ -0,0 +1,36 @@
|
||||
const mongoose = require("mongoose");
|
||||
|
||||
mongoose.set("strictQuery", false);
|
||||
const url = process.env.MONGODB_URI;
|
||||
|
||||
console.log("connecting to MongoDB");
|
||||
mongoose
|
||||
.connect(url)
|
||||
.then((res) => {
|
||||
console.log("connected to MongoDB");
|
||||
})
|
||||
.catch((err) => console.log("error connecting to MongoDB", err.message));
|
||||
|
||||
const personSchema = new mongoose.Schema({
|
||||
name: { type: String, minLength: 3 },
|
||||
number: {
|
||||
type: String,
|
||||
validate: {
|
||||
validator: function (v) {
|
||||
return /\d{2,3}-\d{5,25}/.test(v);
|
||||
},
|
||||
message: (props) => `${props.value} is not a valid phone number!`,
|
||||
},
|
||||
required: [true, "User phone number required"],
|
||||
},
|
||||
});
|
||||
|
||||
personSchema.set("toJSON", {
|
||||
transform: (document, returnedObject) => {
|
||||
returnedObject.id = returnedObject._id.toString();
|
||||
delete returnedObject._id;
|
||||
delete returnedObject.__v;
|
||||
},
|
||||
});
|
||||
|
||||
module.exports = mongoose.model("Person", personSchema);
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "p3exercise",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1",
|
||||
"start": "nodemon server.js"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.1.4",
|
||||
"express": "^4.18.2",
|
||||
"mongoose": "^7.2.2",
|
||||
"morgan": "^1.10.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^2.0.22"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
require("dotenv").config();
|
||||
const express = require("express");
|
||||
const cors = require("cors");
|
||||
const morgan = require("morgan");
|
||||
const Person = require("./models/person");
|
||||
|
||||
const app = express();
|
||||
|
||||
const errorHandler = (error, req, res, next) => {
|
||||
console.log(err.message);
|
||||
if (err.name === "CastError")
|
||||
return res.status(404).send({ error: "malformatted id" });
|
||||
else if (err.name === "ValidationError")
|
||||
return res.status(400).json({ error: err.message });
|
||||
next(err);
|
||||
};
|
||||
|
||||
morgan.token("type", function (req, res) {
|
||||
return String(JSON.stringify(req.body));
|
||||
});
|
||||
// middleware to deal with same origin
|
||||
app.use(cors());
|
||||
// middleware to allow express to deliver html
|
||||
// it is unclear to me why i cant just make a get method that deals with that
|
||||
app.use(express.static("build"));
|
||||
// transfprms it to jsontransfprms it to json
|
||||
app.use(express.json());
|
||||
// adding additional information on request
|
||||
app.use(
|
||||
morgan(":method :url :status :res[content-length] - :response-time ms :type")
|
||||
);
|
||||
app.use(errorHandler);
|
||||
|
||||
app.get("/api/persons", (req, res, next) => {
|
||||
Person.find({})
|
||||
.then((persons) => {
|
||||
res.json(persons);
|
||||
})
|
||||
.catch((err) => next(err));
|
||||
});
|
||||
/* currently not used
|
||||
const date = new Date();
|
||||
app.get("/api/info", (req, res) => {
|
||||
res.send(`Phonebook has info for ${data.length} persons <br />${date}`);
|
||||
});
|
||||
*/
|
||||
app.get("/api/persons/:id", (req, res, next) => {
|
||||
Person.findById(req.params.id)
|
||||
.then((person) => {
|
||||
if (person) res.json(person);
|
||||
else res.status(404).end();
|
||||
})
|
||||
.catch((err) => next(err));
|
||||
});
|
||||
app.post("/api/persons", (req, res, next) => {
|
||||
const body = req.body;
|
||||
const person = new Person({
|
||||
name: body.name,
|
||||
number: body.number,
|
||||
id: Math.floor(Math.random() * 1000),
|
||||
});
|
||||
person
|
||||
.save()
|
||||
.then((savedPerson) => {
|
||||
res.json(savedPerson);
|
||||
})
|
||||
.catch((err) => next(err));
|
||||
});
|
||||
app.put("/api/persons/:id", (req, res, next) => {
|
||||
const { name, number } = req.body;
|
||||
Person.findOneAndUpdate(
|
||||
{ id: req.params.id },
|
||||
{ name, number },
|
||||
{ new: true, runValidators: true, context: "query" }
|
||||
)
|
||||
.then((updatedPerson) => {
|
||||
res.json(updatedPerson);
|
||||
})
|
||||
.catch((err) => next(err));
|
||||
});
|
||||
app.delete("/api/persons/:id", (req, res, next) => {
|
||||
Person.findByIdAndRemove(req.params.id)
|
||||
.then((result) => {
|
||||
console.log("result:", result);
|
||||
res.status(204).end();
|
||||
})
|
||||
.catch((err) => next(err));
|
||||
});
|
||||
|
||||
const PORT = process.env.PORT || 3001;
|
||||
app.listen(PORT, () => {
|
||||
console.log(`Server running on port ${PORT}`);
|
||||
});
|
||||
@@ -0,0 +1,8 @@
|
||||
#! /usr/bin/bash
|
||||
|
||||
rm -r ./frontend/build
|
||||
echo -n "removed build from frontend"
|
||||
npm run build --prefix frontend
|
||||
echo -n "made new build"
|
||||
cp -r ./frontend/build ./backend
|
||||
echo -n "copied build to frontend"
|
||||
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"files": {
|
||||
"main.css": "/static/css/main.305410ed.css",
|
||||
"main.js": "/static/js/main.dde63dc0.js",
|
||||
"index.html": "/index.html",
|
||||
"main.305410ed.css.map": "/static/css/main.305410ed.css.map",
|
||||
"main.dde63dc0.js.map": "/static/js/main.dde63dc0.js.map"
|
||||
},
|
||||
"entrypoints": [
|
||||
"static/css/main.305410ed.css",
|
||||
"static/js/main.dde63dc0.js"
|
||||
]
|
||||
}
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Web site created using create-react-app"/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>React App</title><script defer="defer" src="/static/js/main.dde63dc0.js"></script><link href="/static/css/main.305410ed.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
||||
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 9.4 KiB |
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
@@ -0,0 +1,2 @@
|
||||
.error{color:red}.error,.normal{background:#d3d3d3;border-radius:5px;border-style:solid;font-size:20px;margin-bottom:10px;padding:10px}.normal{color:green}
|
||||
/*# sourceMappingURL=main.305410ed.css.map*/
|
||||
@@ -0,0 +1 @@
|
||||
{"version":3,"file":"static/css/main.305410ed.css","mappings":"AAAA,OACE,SAOF,CACA,eAPE,kBAAqB,CAGrB,iBAAkB,CADlB,kBAAmB,CADnB,cAAe,CAIf,kBAAmB,CADnB,YAWF,CARA,QACE,WAOF","sources":["index.css"],"sourcesContent":[".error {\n color: red;\n background: lightgrey;\n font-size: 20px;\n border-style: solid;\n border-radius: 5px;\n padding: 10px;\n margin-bottom: 10px;\n}\n.normal {\n color: green;\n background: lightgrey;\n font-size: 20px;\n border-style: solid;\n border-radius: 5px;\n padding: 10px;\n margin-bottom: 10px;\n}\n"],"names":[],"sourceRoot":""}
|
||||
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* @license React
|
||||
* react-dom.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react-jsx-runtime.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* react.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
|
||||
/**
|
||||
* @license React
|
||||
* scheduler.production.min.js
|
||||
*
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*/
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"persons": [
|
||||
{
|
||||
"name": "dsdsd",
|
||||
"number": "sdsdsds",
|
||||
"id": 6
|
||||
},
|
||||
{
|
||||
"name": "dfadf",
|
||||
"number": "dafasdfa",
|
||||
"id": 7
|
||||
},
|
||||
{
|
||||
"name": "sdfadfadaf",
|
||||
"number": "adfadfadsf",
|
||||
"id": 9
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
{
|
||||
"name": "p1",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@testing-library/jest-dom": "^5.16.5",
|
||||
"@testing-library/react": "^13.4.0",
|
||||
"@testing-library/user-event": "^13.5.0",
|
||||
"axios": "^1.4.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-scripts": "5.0.1",
|
||||
"web-vitals": "^2.1.4"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject",
|
||||
"server": "npx json-server --port 3001 --watch db.json"
|
||||
},
|
||||
"eslintConfig": {
|
||||
"extends": [
|
||||
"react-app",
|
||||
"react-app/jest"
|
||||
]
|
||||
},
|
||||
"browserslist": {
|
||||
"production": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not op_mini all"
|
||||
],
|
||||
"development": [
|
||||
"last 1 chrome version",
|
||||
"last 1 firefox version",
|
||||
"last 1 safari version"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"json-serve": "^0.1.0"
|
||||
},
|
||||
"proxy": "http://localhost:3001"
|
||||
}
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
@@ -0,0 +1,43 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="theme-color" content="#000000" />
|
||||
<meta
|
||||
name="description"
|
||||
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>
|
||||
</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>
|
||||
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 9.4 KiB |
@@ -0,0 +1,25 @@
|
||||
{
|
||||
"short_name": "React App",
|
||||
"name": "Create React App Sample",
|
||||
"icons": [
|
||||
{
|
||||
"src": "favicon.ico",
|
||||
"sizes": "64x64 32x32 24x24 16x16",
|
||||
"type": "image/x-icon"
|
||||
},
|
||||
{
|
||||
"src": "logo192.png",
|
||||
"type": "image/png",
|
||||
"sizes": "192x192"
|
||||
},
|
||||
{
|
||||
"src": "logo512.png",
|
||||
"type": "image/png",
|
||||
"sizes": "512x512"
|
||||
}
|
||||
],
|
||||
"start_url": ".",
|
||||
"display": "standalone",
|
||||
"theme_color": "#000000",
|
||||
"background_color": "#ffffff"
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# https://www.robotstxt.org/robotstxt.html
|
||||
User-agent: *
|
||||
Disallow:
|
||||
@@ -0,0 +1,44 @@
|
||||
import Persons from "./components/Persons";
|
||||
import Filter from "./components/Filter";
|
||||
import PersonForm from "./components/PersonForm";
|
||||
import Notification from "./components/Notification";
|
||||
import { useState, useEffect } from "react";
|
||||
import personServices from "./services/persons";
|
||||
|
||||
const App = () => {
|
||||
const [persons, setPersons] = useState([]);
|
||||
const [filter, setFilter] = useState("");
|
||||
const [message, setMessage] = useState(null);
|
||||
useEffect(() => {
|
||||
personServices.getAll().then((response) => setPersons(response.data));
|
||||
}, []);
|
||||
|
||||
function displayMessage(message, type = "normal") {
|
||||
setMessage({message,type});
|
||||
setTimeout(() => {
|
||||
setMessage(null);
|
||||
}, 5000);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<h2>Phonebook</h2>
|
||||
<Notification message={message} />
|
||||
<Filter persons={persons} filter={filter} setFilter={setFilter} />
|
||||
<h2>add a new</h2>
|
||||
<PersonForm
|
||||
persons={persons}
|
||||
setPersons={setPersons}
|
||||
displayMessage={displayMessage}
|
||||
/>
|
||||
<h2>Numbers</h2>
|
||||
<Persons
|
||||
persons={persons.filter((el) =>
|
||||
el.name.toLowerCase().includes(filter.toLowerCase())
|
||||
)}
|
||||
setPersons={setPersons}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
@@ -0,0 +1,19 @@
|
||||
function Course({ course }) {
|
||||
const total = course.parts.reduce((acc, curr) => acc + curr.exercises, 0);
|
||||
return (
|
||||
<>
|
||||
<h1>{course.name}</h1>
|
||||
<ul>
|
||||
{course.parts.map((c) => (
|
||||
<li key={c.id}>
|
||||
{c.name} {c.exercises}
|
||||
</li>
|
||||
))}
|
||||
<li>
|
||||
<b>total of</b> {total} <b>exercises</b>
|
||||
</li>
|
||||
</ul>
|
||||
</>
|
||||
);
|
||||
}
|
||||
export default Course;
|
||||
@@ -0,0 +1,12 @@
|
||||
function Filter({ filter, setFilter}) {
|
||||
function onFilter(e) {
|
||||
setFilter(e.target.value);
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
filter shown with <input value={filter} onChange={onFilter} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
export default Filter;
|
||||
@@ -0,0 +1,5 @@
|
||||
const Notification= ({message}) => {
|
||||
if (message === null) return null;
|
||||
return <div className={message.type}>{message.message}</div>;
|
||||
};
|
||||
export default Notification
|
||||
@@ -0,0 +1,60 @@
|
||||
import { useState } from "react";
|
||||
import personServices from "../services/persons";
|
||||
|
||||
function PersonForm({ persons, setPersons, displayMessage }) {
|
||||
const [newName, setNewName] = useState("");
|
||||
const [newNumber, setNewNumber] = useState("");
|
||||
|
||||
function onAddPerson(e) {
|
||||
e.preventDefault();
|
||||
const newPerson = { name: newName, number: newNumber };
|
||||
const id = persons.find((el) => el.name === newName)?.id;
|
||||
if (id) {
|
||||
personServices
|
||||
.update(id, newPerson)
|
||||
.then(() => {
|
||||
setPersons(
|
||||
persons.map((person) => (person.id !== id ? person : newPerson))
|
||||
);
|
||||
displayMessage("Number changed");
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(err.message);
|
||||
displayMessage("Number has already been removed", "error");
|
||||
});
|
||||
} else {
|
||||
personServices
|
||||
.create(newPerson)
|
||||
.then(() => {
|
||||
setPersons([...persons, newPerson]);
|
||||
displayMessage("Person Added");
|
||||
})
|
||||
.catch((error) => {
|
||||
console.log(error.response.data)
|
||||
displayMessage('Validation Error', "error");
|
||||
});
|
||||
}
|
||||
setNewName("");
|
||||
setNewNumber("");
|
||||
}
|
||||
|
||||
return (
|
||||
<form onSubmit={onAddPerson}>
|
||||
<div>
|
||||
name:{" "}
|
||||
<input value={newName} onChange={(e) => setNewName(e.target.value)} />
|
||||
</div>
|
||||
<div>
|
||||
number:{" "}
|
||||
<input
|
||||
value={newNumber}
|
||||
onChange={(e) => setNewNumber(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<button type="submit">add</button>
|
||||
</div>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
export default PersonForm;
|
||||
@@ -0,0 +1,20 @@
|
||||
import personServices from "../services/persons";
|
||||
|
||||
function Persons({ persons, setPersons }) {
|
||||
function onDelete(id) {
|
||||
personServices
|
||||
.remove(id)
|
||||
.then(setPersons(persons.filter((pred) => pred.id != id)));
|
||||
}
|
||||
return (
|
||||
<ul>
|
||||
{persons.map((person, index) => (
|
||||
<li key={index}>
|
||||
{person.name} {person.number}{" "}
|
||||
<button onClick={() => onDelete(person.id)}>delete</button>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
export default Persons;
|
||||
@@ -0,0 +1,18 @@
|
||||
.error {
|
||||
color: red;
|
||||
background: lightgrey;
|
||||
font-size: 20px;
|
||||
border-style: solid;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.normal {
|
||||
color: green;
|
||||
background: lightgrey;
|
||||
font-size: 20px;
|
||||
border-style: solid;
|
||||
border-radius: 5px;
|
||||
padding: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import './index.css'
|
||||
|
||||
import App from './App'
|
||||
|
||||
const notes = [
|
||||
{
|
||||
id: 1,
|
||||
content: 'HTML is easy',
|
||||
important: true
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
content: 'Browser can execute only JavaScript',
|
||||
important: false
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
content: 'GET and POST are the most important methods of HTTP protocol',
|
||||
important: true
|
||||
}
|
||||
]
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')).render(
|
||||
<App notes={notes} />
|
||||
)
|
||||
@@ -0,0 +1,15 @@
|
||||
import axios from "axios";
|
||||
const baseUrl = "/api/persons/";
|
||||
|
||||
|
||||
const getAll = () => axios.get(baseUrl);
|
||||
const create = (newPerson) => axios.post(baseUrl, newPerson);
|
||||
const remove = (id) => axios.delete(baseUrl.concat(id.toString()));
|
||||
const update = (id, newPerson) => axios.put(`${baseUrl}${id}`, newPerson);
|
||||
|
||||
export default {
|
||||
getAll: getAll,
|
||||
create: create,
|
||||
remove: remove,
|
||||
update: update,
|
||||
};
|
||||
@@ -0,0 +1,78 @@
|
||||
import { useState } from "react";
|
||||
function Statistics({ good, bad, neutral }) {
|
||||
return (
|
||||
<div>
|
||||
<h1>statistics</h1>
|
||||
<ul>
|
||||
<StatisticsLine text={"good"} value={good} />
|
||||
<StatisticsLine text={"neutral"} value={neutral} />
|
||||
<StatisticsLine text={"bad"} value={bad} />
|
||||
<StatisticsLine text={"all"} value={bad + good + neutral} />
|
||||
<StatisticsLine
|
||||
text={"average"}
|
||||
value={(good - bad) / (good + bad + neutral)}
|
||||
/>
|
||||
</ul>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function StatisticsLine({ text, value }) {
|
||||
return (
|
||||
<li>
|
||||
{text}: {value}
|
||||
</li>
|
||||
);
|
||||
}
|
||||
|
||||
function Anecdotes({ anecdotes, selected }) {
|
||||
return (
|
||||
<>
|
||||
<div>{anecdotes[selected]}</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
const App = () => {
|
||||
// save clicks of each button to its own state
|
||||
const [good, setGood] = useState(0);
|
||||
const [neutral, setNeutral] = useState(0);
|
||||
const [bad, setBad] = useState(0);
|
||||
const [selected, setSelected] = useState(0);
|
||||
const [votes, setVotes] = useState(new Array(7).fill(0));
|
||||
const anecdotes = [
|
||||
"If it hurts, do it more often.",
|
||||
"Adding manpower to a late software project makes it later!",
|
||||
"The first 90 percent of the code accounts for the first 10 percent of the development time...The remaining 10 percent of the code accounts for the other 90 percent of the development time.",
|
||||
"Any fool can write code that a computer can understand. Good programmers write code that humans can understand.",
|
||||
"Premature optimization is the root of all evil.",
|
||||
"Debugging is twice as hard as writing the code in the first place. Therefore, if you write the code as cleverly as possible, you are, by definition, not smart enough to debug it.",
|
||||
"Programming without an extremely heavy use of console.log is same as if a doctor would refuse to use x-rays or blood tests when diagnosing patients.",
|
||||
"The only way to go fast, is to go well.",
|
||||
];
|
||||
|
||||
let highest = Math.max(...votes)
|
||||
function addVote() {
|
||||
let newVotes = [...votes];
|
||||
newVotes[selected] += 1;
|
||||
setVotes(newVotes);
|
||||
}
|
||||
return (
|
||||
<div>
|
||||
<h1>give feedback</h1>
|
||||
<button onClick={() => setGood(good + 1)}>good</button>
|
||||
<button onClick={() => setNeutral(neutral + 1)}>neutral</button>
|
||||
<button onClick={() => setBad(bad + 1)}>bad</button>
|
||||
<Statistics good={good} bad={bad} neutral={neutral} />
|
||||
<Anecdotes anecdotes={anecdotes} selected={selected} />
|
||||
<h1>has {votes[selected]} votes</h1>
|
||||
<button onClick={() => addVote()}>vote</button>
|
||||
<button onClick={() => setSelected(Math.floor(Math.random() * 7))}>
|
||||
next anecdote{" "}
|
||||
</button>
|
||||
<h1>Is the most liked</h1>
|
||||
<Anecdotes anecdotes={anecdotes} selected={Math.max(...votes)}/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default App;
|
||||
@@ -0,0 +1,6 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
|
||||
import App from './App'
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root')).render(<App />)
|
||||
@@ -0,0 +1,16 @@
|
||||
#! /usr/bin/bash
|
||||
echo -n "1 for get All, 2 for get with id, 3 for create, 4 for delete "
|
||||
read action
|
||||
if [[ $action == 1 ]]; then
|
||||
curl http://localhost:3001/api/persons | json_pp -json_opt pretty,canonical
|
||||
elif [[ $action == 2 ]]; then
|
||||
echo -n "gib id "
|
||||
read id
|
||||
curl http://localhost:3001/api/persons/${id} | json_pp -json_opt pretty,canonical
|
||||
elif [[ $action == 3 ]]; then
|
||||
curl -H "Content-Type: application/json" -X POST -d '{"name":"xyz","number":"xyz"}' http://localhost:3001/api/persons | json_pp -json_opt pretty,canonical
|
||||
elif [[ $action == 4 ]]; then
|
||||
echo -n "gib id "
|
||||
read id
|
||||
curl -H "Content-Type: application/json" -X DELETE http://localhost:3001/api/persons/6482b803a270efb823a3cfb0 | json_pp -json_opt pretty,canonical
|
||||
fi
|
||||
@@ -0,0 +1,4 @@
|
||||
PORT=3003
|
||||
MONGODB_URI='mongodb+srv://admin:admin@cluster1.ciuxwxd.mongodb.net/blogApp?retryWrites=true&w=majority'
|
||||
TEST_MONGODB_URI='mongodb+srv://admin:admin@cluster1.ciuxwxd.mongodb.net/testBlogApp?retryWrites=true&w=majority'
|
||||
SECRET='kdsajf90u234ijkasl;sdafds90812jpsakdy3894ioeklj321;ljx098zxxc'
|
||||
@@ -0,0 +1 @@
|
||||
missing testing execise 4.23 maybe even 4.8 -4.14
|
||||
@@ -0,0 +1,22 @@
|
||||
const express = require("express");
|
||||
const cors = require("cors");
|
||||
const mongoose = require("mongoose");
|
||||
const blogsRouter = require("./controllers/blogs");
|
||||
const usersRouter = require("./controllers/users");
|
||||
const loginRouter = require("./controllers/login");
|
||||
const config = require("./utils/config");
|
||||
const middleware = require("./utils/middleware");
|
||||
|
||||
const app = express();
|
||||
|
||||
mongoose.connect(config.MONGODB_URI).then(() => {
|
||||
console.log("connected to mongodb");
|
||||
});
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
app.use(middleware.tokenExtractor);
|
||||
app.use("/api/blogs" , blogsRouter);
|
||||
app.use("/api/users", usersRouter);
|
||||
app.use("/api/login", loginRouter);
|
||||
|
||||
module.exports = app;
|
||||
@@ -0,0 +1,56 @@
|
||||
const blogsRouter = require("express").Router();
|
||||
const Blog = require("../models/blog");
|
||||
const User = require("../models/user");
|
||||
const middleware = require("../utils/middleware");
|
||||
|
||||
blogsRouter.get("", (request, response) => {
|
||||
Blog.find({})
|
||||
.populate("user", { name: 1 })
|
||||
.then((blogs) => {
|
||||
response.json(blogs);
|
||||
});
|
||||
});
|
||||
|
||||
blogsRouter.delete("/:id", middleware.userExtractor, async (req, res) => {
|
||||
const id = req.params.id;
|
||||
const blog = await Blog.findById(id);
|
||||
|
||||
let result = null;
|
||||
const userId = req.user._id.toString();
|
||||
|
||||
if (blog.user.toString() === userId) {
|
||||
const newUser = {
|
||||
...req.user._doc,
|
||||
blogs: [...req.user._doc.blogs.filter((el) => el.toString() !== id)],
|
||||
};
|
||||
await User.findOneAndUpdate({ _id: userId }, newUser);
|
||||
result = await Blog.findByIdAndDelete(id);
|
||||
return res.json(result);
|
||||
}
|
||||
return res.end();
|
||||
});
|
||||
|
||||
blogsRouter.put("/:id", (req, res) => {
|
||||
const id = req.params.id;
|
||||
Blog.findOneAndUpdate({ _id: id }, req.body).then((result) => {
|
||||
res.json(result);
|
||||
});
|
||||
});
|
||||
|
||||
blogsRouter.post("", middleware.userExtractor, async (req, res) => {
|
||||
const { title, url } = req.body;
|
||||
const user = req.user;
|
||||
const blog = new Blog({
|
||||
title,
|
||||
author: user.name,
|
||||
url,
|
||||
likes: 0,
|
||||
user: user.id,
|
||||
});
|
||||
const savedBlog = await blog.save();
|
||||
user.blogs = user.blogs.concat(savedBlog._id);
|
||||
user.save();
|
||||
res.json(savedBlog);
|
||||
});
|
||||
|
||||
module.exports = blogsRouter;
|
||||
@@ -0,0 +1,24 @@
|
||||
const loginRouter = require("express").Router();
|
||||
const jwt = require("jsonwebtoken");
|
||||
const bcrypt = require("bcrypt");
|
||||
const User = require("../models/user");
|
||||
const config = require("../utils/config");
|
||||
|
||||
loginRouter.post("", async (req, res) => {
|
||||
const { username, password } = req.body;
|
||||
|
||||
const user = await User.findOne({ username });
|
||||
const passwordCorrect =
|
||||
user === null ? false : await bcrypt.compare(password, user.password);
|
||||
if (!(user && passwordCorrect))
|
||||
return res.status(401).json({ error: "invalid username or passowrd" });
|
||||
|
||||
const userForToken = {
|
||||
username: user.username,
|
||||
id: user._id,
|
||||
};
|
||||
const token = jwt.sign(userForToken, config.SECRET);
|
||||
res.status(200).send({ token, username: user.username, name: user.name });
|
||||
});
|
||||
|
||||
module.exports = loginRouter;
|
||||
@@ -0,0 +1,36 @@
|
||||
const usersRouter = require("express").Router();
|
||||
const User = require("../models/user");
|
||||
const bcrypt = require("bcrypt");
|
||||
|
||||
usersRouter.get("", (request, response) => {
|
||||
User.find({}).then((users) => {
|
||||
response.json(users);
|
||||
});
|
||||
});
|
||||
/*
|
||||
usersRouter.delete("/:id", (request, response) => {
|
||||
const id = request.params.id;
|
||||
User.findByIdAndDelete(id).then((users) => response.json(users));
|
||||
});
|
||||
|
||||
usersRouter.put("/:id", (request, response) => {
|
||||
const id = request.params.id;
|
||||
const data = { ...request.body };
|
||||
User.findOneAndUpdate({ _id: id }, data).then((result) => {
|
||||
response.json(result);
|
||||
});
|
||||
});
|
||||
*/
|
||||
usersRouter.post("", async (request, response) => {
|
||||
const { username, password, name } = request.body;
|
||||
const hashedPassword = await bcrypt.hash(password, 10);
|
||||
const user = new User({ username, password: hashedPassword, name });
|
||||
try {
|
||||
const savedUser = await user.save();
|
||||
response.status(201).json(savedUser);
|
||||
} catch (err) {
|
||||
response.status(400).send(err._message);
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = usersRouter;
|
||||
@@ -0,0 +1,25 @@
|
||||
const mongoose = require("mongoose");
|
||||
|
||||
const blogSchema = new mongoose.Schema({
|
||||
title: String,
|
||||
author: String,
|
||||
url: String,
|
||||
likes: Number,
|
||||
comments: [String],
|
||||
user: {
|
||||
type: mongoose.Schema.Types.ObjectId,
|
||||
ref: "User",
|
||||
},
|
||||
});
|
||||
|
||||
blogSchema.set("toJSON", {
|
||||
transform: (document, returnedObject) => {
|
||||
returnedObject.id = returnedObject._id.toString();
|
||||
delete returnedObject._id;
|
||||
delete returnedObject.__v;
|
||||
},
|
||||
});
|
||||
|
||||
const Blog = mongoose.model("Blog", blogSchema);
|
||||
|
||||
module.exports = Blog;
|
||||