├── 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 |
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
24 |
25 | Po utworzeniu repozytorium, należy przejść do jego ustawień w zakładce `Settings` > `Actions` > `General`, jak pokazano na rysunku.
26 |
27 | 
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 | 
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 | 
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 | 
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 | 
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 |
--------------------------------------------------------------------------------