├── public ├── robots.txt ├── favicon.ico ├── logo192.png ├── logo512.png ├── manifest.json ├── 404.html └── index.html ├── jsconfig.json ├── assets ├── how-it-works.png ├── deploy-status.png ├── repo-settings.png ├── gh-actions-perm-1.png ├── gh-actions-perm-2.png ├── template-step-1.png └── template-step-2.png ├── uk_translation.yml ├── .editorconfig ├── src ├── components │ ├── notifyOptions │ │ └── notifyOptions.js │ ├── Filter │ │ ├── Filter.styled.js │ │ └── Filter.jsx │ ├── Layout │ │ ├── Title.jsx │ │ ├── Layout.jsx │ │ └── Layout.styled.js │ ├── ContactList │ │ ├── ContactList.styled.js │ │ └── ContactList.jsx │ ├── App.jsx │ ├── theme.jsx │ └── FormList │ │ ├── FormList.styled.js │ │ └── FormList.jsx ├── redux │ ├── selectors.js │ ├── filterSlice.js │ ├── store.js │ └── contactsSlice.js ├── index.js └── index.css ├── .prettierrc.json ├── .gitignore ├── .github └── workflows │ └── deploy.yml ├── package.json ├── README.md └── README.pl.md /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": "src" 4 | }, 5 | "include": ["src"] 6 | } 7 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanTymoshchuk/goit-react-hw-06-phonebook/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanTymoshchuk/goit-react-hw-06-phonebook/HEAD/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanTymoshchuk/goit-react-hw-06-phonebook/HEAD/public/logo512.png -------------------------------------------------------------------------------- /assets/how-it-works.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanTymoshchuk/goit-react-hw-06-phonebook/HEAD/assets/how-it-works.png -------------------------------------------------------------------------------- /assets/deploy-status.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanTymoshchuk/goit-react-hw-06-phonebook/HEAD/assets/deploy-status.png -------------------------------------------------------------------------------- /assets/repo-settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanTymoshchuk/goit-react-hw-06-phonebook/HEAD/assets/repo-settings.png -------------------------------------------------------------------------------- /assets/gh-actions-perm-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanTymoshchuk/goit-react-hw-06-phonebook/HEAD/assets/gh-actions-perm-1.png -------------------------------------------------------------------------------- /assets/gh-actions-perm-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanTymoshchuk/goit-react-hw-06-phonebook/HEAD/assets/gh-actions-perm-2.png -------------------------------------------------------------------------------- /assets/template-step-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanTymoshchuk/goit-react-hw-06-phonebook/HEAD/assets/template-step-1.png -------------------------------------------------------------------------------- /assets/template-step-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IvanTymoshchuk/goit-react-hw-06-phonebook/HEAD/assets/template-step-2.png -------------------------------------------------------------------------------- /uk_translation.yml: -------------------------------------------------------------------------------- 1 | files: 2 | - source: /react-homework-template/blob/main/README.md 3 | translation: react-homework-template/%two_letters_code%/%original_file_name% 4 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | end_of_line = lf 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | charset = utf-8 10 | 11 | [*.md] 12 | trim_trailing_whitespace = false 13 | -------------------------------------------------------------------------------- /src/components/notifyOptions/notifyOptions.js: -------------------------------------------------------------------------------- 1 | export const notifyOptions = { 2 | position: 'bottom-left', 3 | autoClose: 5000, 4 | hideProgressBar: false, 5 | closeOnClick: true, 6 | pauseOnHover: true, 7 | draggable: true, 8 | progress: undefined, 9 | theme: 'colored', 10 | }; 11 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 80, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": true, 6 | "singleQuote": true, 7 | "trailingComma": "es5", 8 | "bracketSpacing": true, 9 | "jsxBracketSameLine": false, 10 | "arrowParens": "avoid", 11 | "proseWrap": "always" 12 | } 13 | -------------------------------------------------------------------------------- /src/components/Filter/Filter.styled.js: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled' 2 | 3 | export const FormFilter = styled.form` 4 | display: flex; 5 | justify-content: center; 6 | ` 7 | export const LabelFilter = styled.label` 8 | color: ${(p) => p.theme.colors.grey}; 9 | ` 10 | export const InputFilter = styled.input`` 11 | -------------------------------------------------------------------------------- /src/components/Layout/Title.jsx: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | import {Title} from './Layout.styled' 3 | 4 | const GlobalTitle = ({title}) => { 5 | return ( 6 | {title} 7 | ); 8 | } 9 | 10 | GlobalTitle.propTypes = { 11 | title: PropTypes.string.isRequired 12 | } 13 | 14 | export default GlobalTitle; -------------------------------------------------------------------------------- /src/components/Layout/Layout.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Container } from './Layout.styled'; 3 | import PropTypes from 'prop-types'; 4 | 5 | const Layout = ({ children }) => { 6 | return ( 7 | 8 |
{children}
9 |
10 | ); 11 | }; 12 | 13 | Layout.propTypes = { 14 | children: PropTypes.any.isRequired, 15 | }; 16 | 17 | export default Layout; -------------------------------------------------------------------------------- /src/components/Layout/Layout.styled.js: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | 3 | export const Container = styled.div` 4 | padding: 40px; 5 | width: 400px; 6 | margin: 0 auto; 7 | `; 8 | 9 | export const Title = styled.h1` 10 | text-align: center; 11 | margin-top: 30px; 12 | margin-bottom: 30px; 13 | font-size: ${(p) => p.theme.fontSize.xl}; 14 | color: ${(p) => p.theme.colors.white}; 15 | `; -------------------------------------------------------------------------------- /src/redux/selectors.js: -------------------------------------------------------------------------------- 1 | export const getContacts = state => state.contacts.list; 2 | 3 | export const getFilter = state => state.filter; 4 | 5 | export const getVisibleContacts = state => { 6 | const contacts = getContacts(state); 7 | const filter = getFilter(state); 8 | const normalizedFilter = filter.toLowerCase(); 9 | 10 | return contacts.filter(contact => 11 | contact.name.toLowerCase().includes(normalizedFilter) 12 | ); 13 | }; 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | #Junk 4 | .vscode/ 5 | .idea/ 6 | 7 | # dependencies 8 | /node_modules 9 | /.pnp 10 | .pnp.js 11 | 12 | # testing 13 | /coverage 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | .env.local 21 | .env.development.local 22 | .env.test.local 23 | .env.production.local 24 | 25 | npm-debug.log* 26 | yarn-debug.log* 27 | yarn-error.log* 28 | -------------------------------------------------------------------------------- /src/redux/filterSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from '@reduxjs/toolkit'; 2 | 3 | const initialFilterState = ''; 4 | 5 | const filterSlice = createSlice({ 6 | name: 'filter', 7 | initialState: initialFilterState, 8 | reducers: { 9 | changeFilter(state, action) { 10 | return (state = action.payload); // Оновлення значення з попереднього 11 | }, 12 | }, 13 | }); 14 | 15 | export const { changeFilter } = filterSlice.actions; 16 | 17 | export const filterReducer = filterSlice.reducer; 18 | -------------------------------------------------------------------------------- /src/components/ContactList/ContactList.styled.js: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | 3 | export const ListWrap = styled.ul` 4 | display: flex; 5 | justify-content: center; 6 | flex-direction: column; 7 | width: 400px; 8 | padding: ${(p) => p.theme.space[4]}px; 9 | `; 10 | export const List = styled.li` 11 | padding: 10px; 12 | margin-bottom: 5px; 13 | display: flex; 14 | align-items: center; 15 | justify-content: space-between; 16 | font-size: ${(p) => p.theme.fontSize.m}; 17 | color: ${(p) => p.theme.colors.white}; 18 | `; 19 | -------------------------------------------------------------------------------- /.github/workflows/deploy.yml: -------------------------------------------------------------------------------- 1 | name: Build and deploy to GitHub Pages 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | 7 | jobs: 8 | build-and-deploy: 9 | runs-on: ubuntu-latest 10 | steps: 11 | - name: Checkout 🛎️ 12 | uses: actions/checkout@v2.3.1 13 | 14 | - name: Install, lint, build 🔧 15 | run: | 16 | npm install 17 | npm run lint:js 18 | npm run build 19 | 20 | - name: Deploy 🚀 21 | uses: JamesIves/github-pages-deploy-action@4.1.0 22 | with: 23 | branch: gh-pages 24 | folder: build 25 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 4 | "icons": [ 5 | { 6 | "src": "favicon.ico", 7 | "sizes": "64x64 32x32 24x24 16x16", 8 | "type": "image/x-icon" 9 | }, 10 | { 11 | "src": "logo192.png", 12 | "type": "image/png", 13 | "sizes": "192x192" 14 | }, 15 | { 16 | "src": "logo512.png", 17 | "type": "image/png", 18 | "sizes": "512x512" 19 | } 20 | ], 21 | "start_url": ".", 22 | "display": "standalone", 23 | "theme_color": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /src/components/App.jsx: -------------------------------------------------------------------------------- 1 | import { ToastContainer } from 'react-toastify'; 2 | import 'react-toastify/dist/ReactToastify.css'; 3 | import Layout from './Layout/Layout'; 4 | import GlobalTitle from './Layout/Title'; 5 | import FormList from './FormList/FormList'; 6 | import ContactList from './ContactList/ContactList'; 7 | import Filter from './Filter/Filter'; 8 | 9 | function App() { 10 | return ( 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | ); 20 | } 21 | 22 | export default App; 23 | -------------------------------------------------------------------------------- /src/components/theme.jsx: -------------------------------------------------------------------------------- 1 | export const theme = { 2 | colors: { 3 | black: '#000000', 4 | white: '#ffff', 5 | grey:'#fff', 6 | green:'green', 7 | orange: '#cd7305' 8 | }, 9 | space: [0, 2, 4, 8, 16, 32, 64, 128, 256], 10 | 11 | fontSize: { 12 | s:'14px', 13 | m: '16px', 14 | l: '24px', 15 | xl: '36px', 16 | 17 | }, 18 | 19 | lineHeight: { 20 | body: '1.5', 21 | heading: '1.125', 22 | }, 23 | border: { 24 | none: 'none', 25 | }, 26 | borderRadius: { 27 | none: '0', 28 | }, 29 | boxShadow: { 30 | textShadow: '0 1px 1px rgba(236, 230, 230, 0.05)', 31 | boxShadow:' inset 0 -5px 45px rgba(100, 100, 100, 0.2)', 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom/client'; 3 | import { Provider } from 'react-redux'; 4 | import { persistor, store } from 'redux/store'; 5 | import { PersistGate } from 'redux-persist/lib/integration/react'; 6 | import { ThemeProvider } from '@emotion/react'; 7 | import { theme } from './components/theme'; 8 | import App from 'components/App'; 9 | import './index.css'; 10 | 11 | ReactDOM.createRoot(document.getElementById('root')).render( 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ); 22 | -------------------------------------------------------------------------------- /src/redux/store.js: -------------------------------------------------------------------------------- 1 | import { configureStore } from '@reduxjs/toolkit'; 2 | import { 3 | persistStore, 4 | FLUSH, 5 | REHYDRATE, 6 | PAUSE, 7 | PERSIST, 8 | PURGE, 9 | REGISTER, 10 | } from 'redux-persist'; 11 | import { persistedContactReducer } from './contactsSlice'; 12 | import { filterReducer } from './filterSlice'; 13 | 14 | export const store = configureStore({ 15 | reducer: { 16 | contacts: persistedContactReducer, 17 | filter: filterReducer, 18 | }, 19 | 20 | middleware: getDefaultMiddleware => 21 | getDefaultMiddleware({ 22 | // якщо не зробити цю фігню, буде помилка 23 | serializableCheck: { 24 | ignoreActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER], 25 | }, 26 | }), 27 | }); 28 | 29 | export const persistor = persistStore(store); 30 | 31 | -------------------------------------------------------------------------------- /src/components/Filter/Filter.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { FormFilter, LabelFilter } from './Filter.styled'; 3 | import { Input } from '../FormList/FormList.styled'; 4 | import { useDispatch, useSelector } from 'react-redux'; 5 | import { getFilter } from 'redux/selectors'; 6 | import { changeFilter } from 'redux/filterSlice'; 7 | 8 | const Filter = () => { 9 | const value = useSelector(getFilter); 10 | const dispatch = useDispatch(); 11 | 12 | const handleChange = e => { 13 | dispatch(changeFilter(e.target.value)); 14 | }; 15 | 16 | return ( 17 | 18 | 19 | 25 | 26 | 27 | ); 28 | }; 29 | 30 | export default Filter; 31 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @import-normalize; /* bring in normalize.css styles */ 2 | 3 | body { 4 | margin: 0 auto; 5 | font-size: 14px; 6 | line-height: 18px; 7 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 8 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 9 | sans-serif; 10 | background: linear-gradient(-50deg, #d1520e, #0d4b97, #0d98be, #1c0dbe52); 11 | background-size: 100%; 12 | background-attachment: fixed; 13 | } 14 | 15 | code { 16 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 17 | monospace; 18 | } 19 | 20 | h1, 21 | h2, 22 | h3, 23 | h4, 24 | h5, 25 | h6, 26 | p, 27 | ul { 28 | margin: 0; 29 | } 30 | ul { 31 | padding: 0; 32 | list-style: none; 33 | } 34 | img { 35 | display: block; 36 | padding: 0; 37 | height: auto; 38 | } 39 | a, 40 | button, 41 | input { 42 | text-decoration: none; 43 | cursor: pointer; 44 | } 45 | -------------------------------------------------------------------------------- /src/components/ContactList/ContactList.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { ListWrap, List } from './ContactList.styled'; 3 | import { Button } from 'components/FormList/FormList.styled'; 4 | import { UserDeleteOutlined } from '@ant-design/icons'; 5 | import { useSelector, useDispatch } from 'react-redux'; 6 | import { getVisibleContacts } from 'redux/selectors'; 7 | import { deleteContacts } from 'redux/contactsSlice'; 8 | 9 | const ContactList = () => { 10 | const contacts = useSelector(getVisibleContacts); 11 | const dispatch = useDispatch(); 12 | 13 | return ( 14 | 15 | {contacts.map(({ id, name, number }) => ( 16 | 17 | {name + ' : ' + number} 18 | 19 | 22 | 23 | ))} 24 | 25 | ); 26 | }; 27 | 28 | export default ContactList; 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "goit-react-hw-07-phonebook", 3 | "version": "0.1.0", 4 | "private": true, 5 | "homepage": "https://vanTymoshchuk.github.io/goit-react-hw-07-phonebook/", 6 | "dependencies": { 7 | "@ant-design/icons": "^5.1.4", 8 | "@emotion/react": "^11.11.1", 9 | "@emotion/styled": "^11.11.0", 10 | "@reduxjs/toolkit": "^1.9.5", 11 | "@testing-library/jest-dom": "^5.16.3", 12 | "@testing-library/react": "^12.1.4", 13 | "@testing-library/user-event": "^13.5.0", 14 | "react": "^18.1.0", 15 | "react-dom": "^18.1.0", 16 | "react-redux": "^8.1.1", 17 | "react-scripts": "5.0.1", 18 | "react-toastify": "^9.1.3", 19 | "redux": "^4.2.1", 20 | "redux-persist": "^6.0.0", 21 | "shortid": "^2.2.16", 22 | "web-vitals": "^2.1.3" 23 | }, 24 | "scripts": { 25 | "start": "react-scripts start", 26 | "build": "react-scripts build", 27 | "test": "react-scripts test", 28 | "eject": "react-scripts eject", 29 | "lint:js": "eslint src/**/*.{js,jsx}" 30 | }, 31 | "eslintConfig": { 32 | "extends": [ 33 | "react-app", 34 | "react-app/jest" 35 | ] 36 | }, 37 | "browserslist": { 38 | "production": [ 39 | ">0.2%", 40 | "not dead", 41 | "not op_mini all" 42 | ], 43 | "development": [ 44 | "last 1 chrome version", 45 | "last 1 firefox version", 46 | "last 1 safari version" 47 | ] 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/components/FormList/FormList.styled.js: -------------------------------------------------------------------------------- 1 | import styled from '@emotion/styled'; 2 | 3 | export const Form = styled.form` 4 | display: flex; 5 | flex-direction: column; 6 | align-items: center; 7 | `; 8 | export const Label = styled.label` 9 | color: ${(p) => p.theme.colors.white}; 10 | `; 11 | export const Input = styled.input` 12 | width: 350px; 13 | margin-bottom: 15px; 14 | background: rgba(0, 0, 0, 0.3); 15 | border: ${(p) => p.theme.border.none}; 16 | outline: none; 17 | padding: 10px; 18 | font-size: ${(p) => p.theme.fontSize.s}; 19 | color: ${(p) => p.theme.colors.grey}; 20 | text-shadow: ${(p) => p.theme.boxShadow.textShadow}; 21 | border: 1px solid rgba(0, 0, 0, 0.3); 22 | border-radius: 4px; 23 | box-shadow:${(p) => p.theme.boxShadow.textShadow}; 24 | &:focus { 25 | box-shadow:${(p) => p.theme.boxShadow.boxShadow}; 26 | } 27 | `; 28 | 29 | export const Button = styled.button` 30 | display: flex; 31 | align-items: center; 32 | gap: 10px; 33 | color: ${(p) => p.theme.colors.white}; 34 | padding: 5px 10px 5px; 35 | 36 | background: rgba(0, 0, 0, 0.3); 37 | text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.3); 38 | border: 1px solid rgba(0, 0, 0, 0.3); 39 | border-radius: 4px; 40 | box-shadow: ${(p) => p.theme.boxShadow.boxShadow}; 41 | :focus, 42 | :hover { 43 | color: ${(p) => p.theme.colors.green}; 44 | box-shadow: ${(p) => p.theme.boxShadow.boxShadow}; 45 | } 46 | `; 47 | 48 | export const Span = styled.span` 49 | display: flex; 50 | margin-bottom: 3px; 51 | `; 52 | -------------------------------------------------------------------------------- /src/redux/contactsSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice, nanoid } from '@reduxjs/toolkit'; 2 | import { persistReducer } from 'redux-persist'; 3 | import storage from 'redux-persist/lib/storage'; 4 | 5 | const initialState = { 6 | list: [ 7 | { id: 'id-1', name: 'Rosie Simpson', number: '459-12-56' }, 8 | { id: 'id-2', name: 'Hermione Kline', number: '443-89-12' }, 9 | { id: 'id-3', name: 'Eden Clements', number: '645-17-79' }, 10 | { id: 'id-4', name: 'Annie Copeland', number: '227-91-26' }, 11 | ], 12 | }; 13 | 14 | const contactsSlice = createSlice({ 15 | name: 'contacts', 16 | initialState, 17 | reducers: { 18 | addContact: { 19 | reducer(state, action) { 20 | state.list = [...state.list, action.payload]; // Додавання нового контакта 21 | }, 22 | }, 23 | prepare: (name, number) => { 24 | // підготовка для додання нового контакту 25 | return { 26 | payload: { 27 | id: nanoid(), 28 | name: name.trim(), 29 | number: number.trim(), 30 | }, 31 | }; 32 | }, 33 | deleteContacts(state, action) { 34 | state.list = state.list.filter(contact => contact.id !== action.payload); 35 | }, 36 | }, 37 | }); 38 | 39 | const persistConfig = { 40 | key: 'root', 41 | storage, 42 | }; 43 | 44 | export const { addContact, deleteContacts } = contactsSlice.actions; 45 | export const contactsReducer = contactsSlice.reducer; 46 | 47 | export const persistedContactReducer = persistReducer( 48 | persistConfig, 49 | contactsSlice.reducer 50 | ); 51 | -------------------------------------------------------------------------------- /public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Single Page Apps for GitHub Pages 6 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /src/components/FormList/FormList.jsx: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import { UserAddOutlined } from '@ant-design/icons'; 3 | import { Form, Label, Input, Button, Span } from './FormList.styled'; 4 | import { toast } from 'react-toastify'; 5 | import { notifyOptions } from '../notifyOptions/notifyOptions'; 6 | import { useSelector, useDispatch } from 'react-redux'; 7 | import { getVisibleContacts } from 'redux/selectors'; 8 | import { addContact } from 'redux/contactsSlice'; 9 | 10 | const FormList = () => { 11 | const [name, setName] = useState(''); 12 | const [number, setNumber] = useState(''); 13 | 14 | const contacts = useSelector(getVisibleContacts); 15 | const dispatch = useDispatch(); 16 | 17 | const handleSubmit = event => { 18 | event.preventDefault(); 19 | 20 | const normalizedName = name.toLowerCase(); 21 | const isAdded = contacts.find( 22 | el => el.name.toLowerCase() === normalizedName 23 | ); 24 | 25 | if (isAdded) { 26 | toast.error(`${name}: is already in contacts`, notifyOptions); 27 | return; 28 | } 29 | 30 | dispatch(addContact({ name, number })); 31 | setName(''); 32 | setNumber(''); 33 | }; 34 | 35 | const handleChange = e => { 36 | const { name, value } = e.target; 37 | switch (name) { 38 | case 'name': 39 | setName(value); 40 | break; 41 | case 'number': 42 | setNumber(value); 43 | break; 44 | default: 45 | return; 46 | } 47 | }; 48 | 49 | return ( 50 |
51 | 64 | 77 | 81 |
82 | ); 83 | }; 84 | 85 | export default FormList; 86 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 58 | 59 | 60 | 61 | 62 |
63 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React homework template 2 | 3 | This project was created with 4 | [Create React App](https://github.com/facebook/create-react-app). To get 5 | acquainted and configure additional features 6 | [refer to documentation](https://facebook.github.io/create-react-app/docs/getting-started). 7 | 8 | ## Creating a repository by template 9 | 10 | Use this GoIT repository as a template for creating a repository 11 | of your project. To use it just tap the `«Use this template»` button and choose 12 | `«Create a new repository»` option, as you can see on the image below. 13 | 14 | ![Creating repo from a template step 1](./assets/template-step-1.png) 15 | 16 | The page for creating a new repository will open on the next step. Fill out 17 | the Name field and make sure the repository is public, then click 18 | `«Create repository from template»` button. 19 | 20 | ![Creating repo from a template step 2](./assets/template-step-2.png) 21 | 22 | You now have a personal project repository, having a repository-template file 23 | and folder structure. After that, you can work with it as you would with any 24 | other private repository: clone it on your computer, write code, commit, and 25 | send it to GitHub. 26 | 27 | ## Preparing for coding 28 | 29 | 1. Make sure you have an LTS version of Node.js installed on your computer. 30 | [Download and install](https://nodejs.org/en/) if needed. 31 | 2. Install the project's base dependencies with the `npm install` command. 32 | 3. Start development mode by running the `npm start` command. 33 | 4. Go to [http://localhost:3000](http://localhost:3000) in your browser. This 34 | page will automatically reload after saving changes to the project files. 35 | 36 | ## Deploy 37 | 38 | The production version of the project will automatically be linted, built, and 39 | deployed to GitHub Pages, in the `gh-pages` branch, every time the `main` branch 40 | is updated. For example, after a direct push or an accepted pull request. To do 41 | this, you need to edit the `homepage` field in the `package.json` file, 42 | replacing `your_username` and `your_repo_name` with your own, and submit the 43 | changes to GitHub. 44 | 45 | ```json 46 | "homepage": "https://your_username.github.io/your_repo_name/" 47 | ``` 48 | 49 | Next, you need to go to the settings of the GitHub repository (`Settings` > 50 | `Pages`) and set the distribution of the production version of files from the 51 | `/root` folder of the `gh-pages` branch, if this was not done automatically. 52 | 53 | ![GitHub Pages settings](./assets/repo-settings.png) 54 | 55 | ### Deployment status 56 | 57 | The deployment status of the latest commit is displayed with an icon next to its 58 | ID. 59 | 60 | - **Yellow color** - the project is being built and deployed. 61 | - **Green color** - deployment completed successfully. 62 | - **Red color** - an error occurred during linting, build or deployment. 63 | 64 | More detailed information about the status can be viewed by clicking on the 65 | icon, and in the drop-down window, follow the link `Details`. 66 | 67 | ![Deployment status](./assets/deploy-status.png) 68 | 69 | ### Live page 70 | 71 | After some time, usually a couple of minutes, the live page can be viewed at the 72 | address specified in the edited `homepage` property. For example, here is a link 73 | to a live version for this repository 74 | [https://goitacademy.github.io/react-homework-template](https://goitacademy.github.io/react-homework-template). 75 | 76 | If a blank page opens, make sure there are no errors in the `Console` tab 77 | related to incorrect paths to the CSS and JS files of the project (**404**). You 78 | most likely have the wrong value for the `homepage` property in the 79 | `package.json` file. 80 | 81 | ### Routing 82 | 83 | If your application uses the `react-router-dom` library for routing, you must 84 | additionally configure the `` component by passing the exact name 85 | of your repository in the `basename` prop. Slashes at the beginning and end of 86 | the line are required. 87 | 88 | ```jsx 89 | 90 | 91 | 92 | ``` 93 | 94 | ## How it works 95 | 96 | ![How it works](./assets/how-it-works.png) 97 | 98 | 1. After each push to the `main` branch of the GitHub repository, a special 99 | script (GitHub Action) is launched from the `.github/workflows/deploy.yml` 100 | file. 101 | 2. All repository files are copied to the server, where the project is 102 | initialized and linted and built before deployment. 103 | 3. If all steps are successful, the built production version of the project 104 | files is sent to the `gh-pages` branch. Otherwise, the script execution log 105 | will indicate what the problem is. -------------------------------------------------------------------------------- /README.pl.md: -------------------------------------------------------------------------------- 1 | **Read in other languages: [rosyjski](README.md), [polski](README.pl.md).** 2 | 3 | # React homework template 4 | 5 | Ten projekt został stworzony przy pomocy 6 | [Create React App](https://github.com/facebook/create-react-app). W celu 7 | zapoznania się z konfiguracją dodatkowych opcji 8 | [zobacz dokumentację](https://facebook.github.io/create-react-app/docs/getting-started). 9 | 10 | ## Utworzenie repozytorium zgodnie z szablonem 11 | 12 | Wykorzystaj to repozytorium organizacji GoIT jako szablon do utworzenia 13 | repozytorium własnego projektu. W tym celu kliknij na przycisk 14 | `"Use this template"` i wybierz opcję `"Create a new repository"`, jak pokazano 15 | na rysunku. 16 | 17 | ![Creating repo from a template step 1](./assets/template-step-1.png) 18 | 19 | W następnym kroku otworzy się strona utworzenia nowego repozytorium. Wypełnij 20 | pole nazwy i upewnij się, że repozytorium jest publiczne, a następnie kliknij na 21 | przycisk `"Create repository from template"`. 22 | 23 | ![Creating repo from a template step 2](./assets/template-step-2.png) 24 | 25 | Po utworzeniu repozytorium, należy przejść do jego ustawień w zakładce `Settings` > `Actions` > `General`, jak pokazano na rysunku. 26 | 27 | ![Settings GitHub Actions permissions step 1](./assets/gh-actions-perm-1.png) 28 | 29 | Przescrolluj stronę do samego końca, w sekcji `«Workflow permissions»` wybierz opcję `«Read and write permissions»` i zaznacz pole w checkboksie. Jest to niezbędne do automatyzacji procesu deploymentu projektu. 30 | 31 | ![Settings GitHub Actions permissions step 2](./assets/gh-actions-perm-2.png) 32 | 33 | Teraz masz własne repozytorium projektu, ze strukturą folderów i plików jak w 34 | szablonie. Pracuj z nim jak z innymi repozytoriami, klonuj je na swój komputer, 35 | pisz kod, rób commity i wysyłaj na GitHub. 36 | 37 | ## Przygotowanie do pracy 38 | 39 | 1. Upewnij się, że na komputerze zainstalowana jest wersja LTS Node.js. 40 | [Ściągnij i zainstaluj](https://nodejs.org/en/), jeżeli trzeba. 41 | 2. Utwórz bazowe zależności projektu przy pomocy polecenia `npm install`. 42 | 3. Włącz tryb pracy, wykonując polecenie `npm start`. 43 | 4. Przejdź w przeglądarce pod adres 44 | [http://localhost:3000](http://localhost:3000). Ta strona będzie 45 | automatycznie przeładowywać się po zapisaniu zmian w plikach projektu. 46 | 47 | ## Deployment 48 | 49 | Produkcyjna wersja projektu będzie automatycznie poddana pracy lintera, budowana 50 | i deployowana na GitHub Pages, w gałęzi `gh-pages` za każdym razem, gdy 51 | aktualizuje się gałąź `main`, na przykład po bezpośrednim pushu lub przyjętym 52 | pull requeście. W tym celu należy w pliku `package.json` zredagować pole 53 | `homepage`, zamieniając `your_username` i `your_repo_name` na swoje nazwy i 54 | wysłać zmiany do GitHub. 55 | 56 | ```json 57 | "homepage": "https://your_username.github.io/your_repo_name/" 58 | ``` 59 | 60 | Następnie należy przejść do ustawień repozytorium GitHub (`Settings` > `Pages`) 61 | i wydystrybuować wersję produkcyjną plików z folderu `/root` gałęzi `gh-pages`, 62 | jeśli nie zostało to wykonane automatycznie. 63 | 64 | ![GitHub Pages settings](./assets/repo-settings.png) 65 | 66 | ### Status deploymentu 67 | 68 | Status deploymentu ostatniego commitu wyświetla się jako ikona obok jego 69 | identyfikatora. 70 | 71 | - **Żółty kolor** - wykonuje się zbudowanie i deployment projektu. 72 | - **Zielony kolor** - deploymnt zakończył się sukcesem. 73 | - **Czerwony kolor** - podczas pracy lintera, budowania lub deploymentu wystąpił 74 | błąd. 75 | 76 | Bardziej szczegółowe informacje o statusie można zobaczyć po kliknięciu na 77 | ikonkę i przejściu w wyskakującym oknie do odnośnika `Details`. 78 | 79 | ![Deployment status](./assets/deploy-status.png) 80 | 81 | ### Deployowana strona 82 | 83 | Po jakimś czasie, zazwyczaj kilku minut, zdeployowaną stronę będzie można 84 | zobaczyć pod adresem wskazanym w zredagowanej właściwości `homepage`. Tutaj na 85 | przykład znajduje się odnośnik do zdeployowanej strony w wersji dla tego 86 | repozytorium 87 | [https://goitacademy.github.io/react-homework-template](https://goitacademy.github.io/react-homework-template). 88 | 89 | Jeżeli otwiera się pusta strona, upewnij się, że w zakładce `Console` nie ma 90 | błędów związanych z nieprawidłowymi ścieżkami do plików CSS i JS projektu 91 | (**404**). Najprawdopodobniej wprowadzona została niewłaściwa wartość 92 | właściwości `homepage` w pliku `package.json`. 93 | 94 | ### Trasowanie 95 | 96 | Jeżeli aplikacja wykorzystuje bibliotekę `react-router-dom` dla trasowania, 97 | należy uzupełniająco skonfigurować komponent ``, przekazując w 98 | propsie `basename` dokładną nazwę twojego repozytorium. Slash na początku i na 99 | końcu łańcucha jest obowiązkowy. 100 | 101 | ```jsx 102 | 103 | 104 | 105 | ``` 106 | 107 | ## Jak to działa 108 | 109 | ![How it works](./assets/how-it-works.png) 110 | 111 | 1. Po każdym pushu do gałęzi `main` repozytorium GitHub, uruchamia się specjalny 112 | skrypt (GitHub Action) z pliku `.github/workflows/deploy.yml`. 113 | 2. Wszystkie pliki repozytorium kopiują się na serwer, gdzie projekt zostaje 114 | zainicjowany i przechodzi pracę lintera oraz zbudowanie przed deploymentem. 115 | 3. Jeżeli wszystkie kroki zakończyły się sukcesem, zbudowana wersja produkcyjna 116 | plików projektu wysyłana jest do gałęzi `gh-pages`. W przeciwnym razie, w 117 | logu wykonania skryptu zostanie wskazane z czym jest problem. 118 | --------------------------------------------------------------------------------