├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── index.html
└── manifest.json
└── src
├── constants.js
├── index.css
├── index.js
├── registerServiceWorker.js
└── todo_app
├── App.js
├── actions
└── actionCreator.js
├── components
├── footer
│ ├── footer.css
│ └── footer.jsx
├── title
│ ├── title.css
│ └── title.jsx
├── todo-input
│ ├── todo-input.css
│ └── todo-input.jsx
├── todo-item
│ ├── todo-item.css
│ └── todo-item.jsx
└── todo-list
│ ├── todo-list.css
│ └── todo-list.jsx
├── containers
└── todo
│ ├── todo.css
│ └── todo.jsx
├── reducers
├── filters.js
├── index.js
└── tasks.js
└── store.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # dependencies
2 | /node_modules
3 |
4 | # testing
5 | /coverage
6 |
7 | # production
8 | /build
9 |
10 | # misc
11 | .DS_Store
12 | .env.local
13 | .env.development.local
14 | .env.test.local
15 | .env.production.local
16 |
17 | npm-debug.log*
18 | yarn-debug.log*
19 | yarn-error.log*
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Simple ToDo app example
2 | https://simple-todo-list-with-react-re.herokuapp.com/
3 |
4 |
5 | ---------------------------------------------------------------------------------
6 | 
7 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "todo-list",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "prop-types": "^15.6.2",
7 | "react": "^16.4.2",
8 | "react-dom": "^16.4.2",
9 | "react-redux": "^5.0.7",
10 | "react-router-dom": "^4.3.1",
11 | "react-scripts": "1.1.5",
12 | "redux": "^4.0.0",
13 | "redux-localstorage-simple": "^2.1.3"
14 | },
15 | "scripts": {
16 | "start": "react-scripts start",
17 | "build": "react-scripts build",
18 | "test": "react-scripts test --env=jsdom",
19 | "eject": "react-scripts eject"
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cqxg/Simple-ToDo-With-React-Redux/316217eecdc2a7aeb32d8f556d5481c764c9effa/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
15 |
16 |
17 |
26 | React App
27 |
28 |
29 |
32 |
33 |
43 |
44 |
45 |
--------------------------------------------------------------------------------
/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 | "start_url": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/src/constants.js:
--------------------------------------------------------------------------------
1 | export const ADD_TASK = 'ADD_TASK';
2 | export const REMOVE_TASK = 'REMOVE_TASK';
3 | export const COMPLETE_TASK = 'COMPLETE_TASK';
4 | export const CHANGE_FILTER = 'CHANGE_FILTER';
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | padding: 0;
4 | font-family: 'Roboto', sans-serif;
5 | background-color: #f7f7f7;
6 | }
7 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './todo_app/App';
4 | import './index.css';
5 | import registerServiceWorker from './registerServiceWorker';
6 | import { Provider } from 'react-redux';
7 | import store from './todo_app/store';
8 |
9 | ReactDOM.render((
10 |
11 |
12 |
13 | ), document.getElementById('root'));
14 | registerServiceWorker();
15 |
--------------------------------------------------------------------------------
/src/registerServiceWorker.js:
--------------------------------------------------------------------------------
1 | // In production, we register a service worker to serve assets from local cache.
2 |
3 | // This lets the app load faster on subsequent visits in production, and gives
4 | // it offline capabilities. However, it also means that developers (and users)
5 | // will only see deployed updates on the "N+1" visit to a page, since previously
6 | // cached resources are updated in the background.
7 |
8 | // To learn more about the benefits of this model, read https://goo.gl/KwvDNy.
9 | // This link also includes instructions on opting out of this behavior.
10 |
11 | const isLocalhost = Boolean(
12 | window.location.hostname === 'localhost' ||
13 | // [::1] is the IPv6 localhost address.
14 | window.location.hostname === '[::1]' ||
15 | // 127.0.0.1/8 is considered localhost for IPv4.
16 | window.location.hostname.match(
17 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
18 | )
19 | );
20 |
21 | export default function register() {
22 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
23 | // The URL constructor is available in all browsers that support SW.
24 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location);
25 | if (publicUrl.origin !== window.location.origin) {
26 | // Our service worker won't work if PUBLIC_URL is on a different origin
27 | // from what our page is served on. This might happen if a CDN is used to
28 | // serve assets; see https://github.com/facebookincubator/create-react-app/issues/2374
29 | return;
30 | }
31 |
32 | window.addEventListener('load', () => {
33 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
34 |
35 | if (isLocalhost) {
36 | // This is running on localhost. Lets check if a service worker still exists or not.
37 | checkValidServiceWorker(swUrl);
38 |
39 | // Add some additional logging to localhost, pointing developers to the
40 | // service worker/PWA documentation.
41 | navigator.serviceWorker.ready.then(() => {
42 | console.log(
43 | 'This web app is being served cache-first by a service ' +
44 | 'worker. To learn more, visit https://goo.gl/SC7cgQ'
45 | );
46 | });
47 | } else {
48 | // Is not local host. Just register service worker
49 | registerValidSW(swUrl);
50 | }
51 | });
52 | }
53 | }
54 |
55 | function registerValidSW(swUrl) {
56 | navigator.serviceWorker
57 | .register(swUrl)
58 | .then(registration => {
59 | registration.onupdatefound = () => {
60 | const installingWorker = registration.installing;
61 | installingWorker.onstatechange = () => {
62 | if (installingWorker.state === 'installed') {
63 | if (navigator.serviceWorker.controller) {
64 | // At this point, the old content will have been purged and
65 | // the fresh content will have been added to the cache.
66 | // It's the perfect time to display a "New content is
67 | // available; please refresh." message in your web app.
68 | console.log('New content is available; please refresh.');
69 | } else {
70 | // At this point, everything has been precached.
71 | // It's the perfect time to display a
72 | // "Content is cached for offline use." message.
73 | console.log('Content is cached for offline use.');
74 | }
75 | }
76 | };
77 | };
78 | })
79 | .catch(error => {
80 | console.error('Error during service worker registration:', error);
81 | });
82 | }
83 |
84 | function checkValidServiceWorker(swUrl) {
85 | // Check if the service worker can be found. If it can't reload the page.
86 | fetch(swUrl)
87 | .then(response => {
88 | // Ensure service worker exists, and that we really are getting a JS file.
89 | if (
90 | response.status === 404 ||
91 | response.headers.get('content-type').indexOf('javascript') === -1
92 | ) {
93 | // No service worker found. Probably a different app. Reload the page.
94 | navigator.serviceWorker.ready.then(registration => {
95 | registration.unregister().then(() => {
96 | window.location.reload();
97 | });
98 | });
99 | } else {
100 | // Service worker found. Proceed as normal.
101 | registerValidSW(swUrl);
102 | }
103 | })
104 | .catch(() => {
105 | console.log(
106 | 'No internet connection found. App is running in offline mode.'
107 | );
108 | });
109 | }
110 |
111 | export function unregister() {
112 | if ('serviceWorker' in navigator) {
113 | navigator.serviceWorker.ready.then(registration => {
114 | registration.unregister();
115 | });
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/src/todo_app/App.js:
--------------------------------------------------------------------------------
1 | import React, { Fragment } from 'react';
2 |
3 | import ToDo from './containers/todo/todo';
4 | import Title from './components/title/title';
5 |
6 | const App = () => (
7 |
8 |
9 |
10 |
11 | );
12 |
13 | export default App;
14 |
--------------------------------------------------------------------------------
/src/todo_app/actions/actionCreator.js:
--------------------------------------------------------------------------------
1 | import { ADD_TASK, REMOVE_TASK, COMPLETE_TASK, CHANGE_FILTER } from '../../constants';
2 |
3 | export const addTast = (id, text, isCompleted) => ({
4 | type: ADD_TASK,
5 | id,
6 | text,
7 | isCompleted
8 | });
9 |
10 |
11 | export const removeTask = id => ({
12 | type: REMOVE_TASK,
13 | id
14 | });
15 |
16 | export const completeTask = id => ({
17 | type: COMPLETE_TASK,
18 | id
19 | });
20 |
21 | export const changeFilter = activeFilter => ({
22 | type: CHANGE_FILTER,
23 | activeFilter,
24 | })
--------------------------------------------------------------------------------
/src/todo_app/components/footer/footer.css:
--------------------------------------------------------------------------------
1 | .footer {
2 | position: relative;
3 | min-height: 50px;
4 | display: flex;
5 | align-items: center;
6 | padding: 0 6px 0 21px;
7 | justify-content: space-between;
8 | }
9 |
10 | .amount {
11 | font-size: 13.3333px;
12 | }
13 |
14 | .btn-group {
15 | position: relative;
16 | z-index: 1;
17 | }
18 |
19 | .filter-btn {
20 | margin: 3px;
21 | padding: 3px 7px;
22 | border: 1px solid transparent;
23 | border-radius: 3px;
24 | background-color: transparent;
25 | cursor: pointer;
26 | }
27 |
28 | .filter-btn:hover,
29 | .filter-btn.active {
30 | border-color: rgba(175, 47, 47, 0.2);
31 | }
32 |
33 | .footer:before {
34 | content: '';
35 | position: absolute;
36 | right: 0;
37 | bottom: 0;
38 | left: 0;
39 | height: 50px;
40 | box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
41 | 0 8px 0 -3px #f6f6f6,
42 | 0 9px 1px -3px rgba(0, 0, 0, 0.2),
43 | 0 16px 0 -6px #f6f6f6,
44 | 0 17px 2px -6px rgba(0, 0, 0, 0.2);
45 | }
46 |
--------------------------------------------------------------------------------
/src/todo_app/components/footer/footer.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import './footer.css';
5 | import { changeFilter } from '../../actions/actionCreator';
6 |
7 | const FILTERS_BTN = [
8 | {
9 | text: 'All',
10 | id: 'all',
11 | },
12 | {
13 | text: 'Active',
14 | id: 'active',
15 | },
16 | {
17 | text: 'Completed',
18 | id: 'completed'
19 | }
20 | ];
21 |
22 | const Footer = ({ amount, activeFilter, changeFilter }) => (
23 |
24 |
{`${amount} Tasks left`}
25 |
26 | {FILTERS_BTN.map(({ text, id }) => (
27 |
32 | ))}
33 |
34 |
35 | );
36 |
37 | Footer.propTypes = {
38 | amount: PropTypes.number,
39 | activeFilter: PropTypes.string,
40 | changeFilter: PropTypes.func,
41 | }
42 |
43 | Footer.defaultProps = {
44 | changeFilter: () => { },
45 | amount: 0,
46 | activeFilter: 'all',
47 | }
48 |
49 | export default Footer;
50 |
--------------------------------------------------------------------------------
/src/todo_app/components/title/title.css:
--------------------------------------------------------------------------------
1 | .title {
2 | font-family: 'Permanent Marker', cursive;
3 | font-size: 80px;
4 | text-align: center;
5 | font-weight: 900;
6 | color: rgba(175, 47, 47, 0.15);
7 | margin-bottom: 20px;
8 | }
9 |
--------------------------------------------------------------------------------
/src/todo_app/components/title/title.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import './title.css';
5 |
6 | const Title = ({ title }) => (
7 | {title}
8 | );
9 |
10 | Title.propTypes = {
11 | title: PropTypes.string,
12 | }
13 |
14 | Title.defaultProps = {
15 | title: 'Simple title',
16 | }
17 |
18 | export default Title;
19 |
--------------------------------------------------------------------------------
/src/todo_app/components/todo-input/todo-input.css:
--------------------------------------------------------------------------------
1 | .todo-input-wrapper {
2 | position: relative;
3 | border-bottom: 1px solid #ededed;
4 | }
5 |
6 | .todo-input {
7 | font-family: 'Indie Flower', cursive;
8 | padding: 16px 16px 16px 60px;
9 | border: none;
10 | background: rgba(0, 0, 0, 0.003);
11 | width: 100%;
12 | box-sizing: border-box;
13 | font-size: 28px;
14 | font-style: italic;
15 | box-shadow: inset 0 -2px 40px rgba(0,0,0,0.03);
16 | }
17 |
18 | .fa-plus {
19 | position: absolute;
20 | font-size: 24px;
21 | top: 50%;
22 | transform: translateY(-50%);
23 | left: 20px;
24 | }
25 |
26 | .todo-input::-webkit-input-placeholder {
27 | font-family: 'Indie Flower', cursive;
28 | }
29 | .todo-input::-moz-placeholder {
30 | font-family: 'Indie Flower', cursive;
31 | }
32 | .todo-input:-moz-placeholder {
33 | font-family: 'Indie Flower', cursive;
34 | }
35 | .todo-input:-ms-input-placeholder {
36 | font-family: 'Indie Flower', cursive;
37 | }
38 |
--------------------------------------------------------------------------------
/src/todo_app/components/todo-input/todo-input.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import './todo-input.css';
5 |
6 | const ToDoInput = ({ value, onChange, onKeyPress }) => (
7 |
8 |
9 |
16 |
17 | );
18 |
19 | ToDoInput.propTypes = {
20 | onChange: PropTypes.func,
21 | onKeyPress: PropTypes.func,
22 | value: PropTypes.string,
23 | }
24 |
25 | ToDoInput.defaultProps = {
26 | onChange: () => { },
27 | onKeyPress: () => { },
28 | value: '',
29 | }
30 |
31 | export default ToDoInput;
32 |
--------------------------------------------------------------------------------
/src/todo_app/components/todo-item/todo-item.css:
--------------------------------------------------------------------------------
1 | .todo-item {
2 | font-size: 24px;
3 | border-bottom: 1px solid #ededed;
4 | min-height: 60px;
5 | display: flex;
6 | align-items: center;
7 | list-style: none;
8 | padding: 0 16px 0 60px;
9 | cursor: pointer;
10 | display: flex;
11 | justify-content: space-between;
12 | align-items: center;
13 | position: relative;
14 | }
15 |
16 | .text {
17 | font-family: 'Indie Flower', cursive;
18 | }
19 |
20 | .completed {
21 | text-decoration: line-through;
22 | }
23 |
24 | .completed,
25 | .fa-check-circle {
26 | color: #ccc;
27 | }
28 |
29 | .fa-times {
30 | transition: all .3s ease;
31 | color: rgba(175, 47, 47, 0.15);
32 | }
33 |
34 | .mark {
35 | position: absolute;
36 | left: 20px;
37 | }
38 |
39 | .todo-item:hover .fa-times {
40 | color: rgba(175, 47, 47, 0.9);
41 | }
42 |
--------------------------------------------------------------------------------
/src/todo_app/components/todo-item/todo-item.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import './todo-item.css';
5 | import { completeTask } from '../../actions/actionCreator';
6 |
7 | const ToDoItem = ({ text, isCompleted, removeTask, id, completeTask }) => (
8 |
9 | completeTask(id)} className={isCompleted ? 'mark far fa-check-circle' : 'mark far fa-circle'} />
10 | {text}
11 | removeTask(id)} className="fas fa-times" />
12 |
13 | );
14 |
15 | ToDoItem.propTypes = {
16 | text: PropTypes.string,
17 | isCompleted: PropTypes.bool,
18 | removeTask: PropTypes.func,
19 | id: PropTypes.number,
20 | }
21 |
22 | ToDoItem.defaultProps = {
23 | text: '',
24 | isCompleted: false,
25 | removeTask: () => { },
26 | id: 0,
27 | }
28 |
29 | export default ToDoItem;
30 |
--------------------------------------------------------------------------------
/src/todo_app/components/todo-list/todo-list.css:
--------------------------------------------------------------------------------
1 | .todo-list {
2 | margin: 0;
3 | padding: 0;
4 | }
5 |
--------------------------------------------------------------------------------
/src/todo_app/components/todo-list/todo-list.jsx:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import PropTypes from 'prop-types';
3 |
4 | import ToDoItem from '../todo-item/todo-item';
5 |
6 | import './todo-list.css';
7 |
8 | const ToDoList = ({ tasksList, removeTask, completeTask }) => (
9 |
10 | {tasksList.map(({ id, text, isCompleted }) => (
11 |
12 | ))}
13 |
14 | );
15 |
16 | ToDoList.propTypes = {
17 | tasksList: PropTypes.array,
18 | removeTask: PropTypes.func,
19 | completeTask: PropTypes.func,
20 | }
21 |
22 | ToDoList.defaultProps = {
23 | tasksList: [],
24 | removeTask: () => { },
25 | completeTask: () => { },
26 | }
27 |
28 | export default ToDoList;
29 |
--------------------------------------------------------------------------------
/src/todo_app/containers/todo/todo.css:
--------------------------------------------------------------------------------
1 | .todo-wrapper {
2 | max-width: 600px;
3 | margin: 0 auto;
4 | position: relative;
5 | background-color: #fff;
6 | box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
7 | 0 25px 50px 0 rgba(0, 0, 0, 0.1);
8 | }
9 |
--------------------------------------------------------------------------------
/src/todo_app/containers/todo/todo.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component } from 'react';
2 | import { connect } from 'react-redux';
3 |
4 | import { addTast, removeTask, completeTask, changeFilter } from '../../actions/actionCreator';
5 |
6 | import ToDoInput from '../../components/todo-input/todo-input';
7 | import ToDoList from '../../components/todo-list/todo-list';
8 | import Footer from '../../components/footer/footer';
9 |
10 | import './todo.css';
11 |
12 | class ToDo extends Component {
13 |
14 | state = {
15 | taskText: ''
16 | }
17 |
18 | handleInputChange = ({ target: { value } }) => {
19 | this.setState({
20 | taskText: value,
21 | })
22 | }
23 |
24 | addTast = ({ key }) => {
25 | const { taskText } = this.state;
26 |
27 | if (taskText.length > 3 && key === 'Enter') {
28 | const { addTast } = this.props;
29 |
30 | addTast((new Date()).getTime(), taskText, false);
31 | this.setState({
32 | taskText: '',
33 | });
34 | }
35 | }
36 |
37 | filterTasks = (tasks, activeFilter) => {
38 | switch (activeFilter) {
39 | case 'all':
40 | return tasks;
41 | break;
42 | case 'completed':
43 | return tasks.filter(task => task.isCompleted);
44 | break;
45 | case 'active':
46 | return tasks.filter(task => !task.isCompleted);
47 | break;
48 | default: tasks;
49 | }
50 | }
51 |
52 | getActiveTasksCounter = tasks => tasks.filter(task => !task.isCompleted).length;
53 |
54 | render() {
55 | const { taskText } = this.state;
56 | const { tasks, removeTask, completeTask, filters, changeFilter } = this.props;
57 | const isTasksExist = tasks && tasks.length > 0;
58 | const filteredTasks = this.filterTasks(tasks, filters);
59 | const taskCounter = this.getActiveTasksCounter(tasks);
60 |
61 | return (
62 |
63 |
64 | {isTasksExist && }
65 | {isTasksExist && }
66 |
67 | );
68 | }
69 | }
70 |
71 | export default connect(({ tasks, filters }) => ({
72 | tasks,
73 | filters,
74 | }), { addTast, removeTask, completeTask, changeFilter })(ToDo);
75 |
--------------------------------------------------------------------------------
/src/todo_app/reducers/filters.js:
--------------------------------------------------------------------------------
1 | import { CHANGE_FILTER } from '../../constants';
2 |
3 | const BASE_FILTER = 'all';
4 |
5 | const filter = (state = BASE_FILTER, { type, activeFilter }) => {
6 | switch (type) {
7 | case CHANGE_FILTER:
8 | return activeFilter;
9 | break;
10 | default:
11 | return state;
12 | }
13 | }
14 |
15 | export default filter;
--------------------------------------------------------------------------------
/src/todo_app/reducers/index.js:
--------------------------------------------------------------------------------
1 | import { combineReducers } from 'redux';
2 | import tasks from './tasks';
3 | import filters from './filters';
4 |
5 | const rootReducer = combineReducers({ tasks, filters });
6 |
7 | export default rootReducer;
8 |
--------------------------------------------------------------------------------
/src/todo_app/reducers/tasks.js:
--------------------------------------------------------------------------------
1 | import { ADD_TASK, REMOVE_TASK, COMPLETE_TASK } from '../../constants';
2 | import { load } from 'redux-localstorage-simple';
3 |
4 | let TASKS = load({ namespace: 'todo-list' });
5 |
6 | if (!TASKS || !TASKS.tasks || !TASKS.tasks.length) {
7 | TASKS = {
8 | tasks: [],
9 | }
10 | }
11 |
12 | const tasks = (state = TASKS.tasks, { id, text, isCompleted, type }) => {
13 | switch (type) {
14 | case ADD_TASK:
15 | return [
16 | ...state, {
17 | id,
18 | text,
19 | isCompleted,
20 | }
21 | ];
22 | case REMOVE_TASK:
23 | return [...state].filter(task => task.id !== id);
24 | case COMPLETE_TASK:
25 | return [...state].map(task => {
26 | if (task.id === id) {
27 | task.isCompleted = !task.isCompleted;
28 | }
29 | return task;
30 | })
31 | default:
32 | return state;
33 | }
34 | }
35 |
36 | export default tasks;
37 |
--------------------------------------------------------------------------------
/src/todo_app/store.js:
--------------------------------------------------------------------------------
1 | import { createStore, compose, applyMiddleware } from 'redux';
2 | import rootReducer from './reducers/index';
3 | import { save } from 'redux-localstorage-simple';
4 |
5 | /* eslint-disable no-underscore-dangle */
6 | const composeEnhancers =
7 | process.env.NODE_ENV !== 'production' &&
8 | typeof window === 'object' &&
9 | window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ?
10 | window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({}) : compose;
11 | /* eslint-enable */
12 |
13 | const configureStore = preloadedState => (
14 | createStore(
15 | rootReducer,
16 | preloadedState,
17 | composeEnhancers(
18 | applyMiddleware(save({ namespace: 'todo-list' }))
19 | ),
20 | )
21 | );
22 |
23 | const store = configureStore({});
24 |
25 | export default store;
26 |
--------------------------------------------------------------------------------