├── leadmanager ├── accounts │ ├── __init__.py │ ├── migrations │ │ └── __init__.py │ ├── models.py │ ├── tests.py │ ├── admin.py │ ├── views.py │ ├── apps.py │ ├── urls.py │ ├── serializers.py │ └── api.py ├── frontend │ ├── __init__.py │ ├── migrations │ │ └── __init__.py │ ├── src │ │ ├── index.js │ │ ├── components │ │ │ ├── leads │ │ │ │ ├── Dashboard.js │ │ │ │ ├── Leads.js │ │ │ │ └── Form.js │ │ │ ├── common │ │ │ │ └── PrivateRoute.js │ │ │ ├── layout │ │ │ │ ├── Alerts.js │ │ │ │ └── Header.js │ │ │ ├── App.js │ │ │ └── accounts │ │ │ │ ├── Login.js │ │ │ │ └── Register.js │ │ ├── reducers │ │ │ ├── index.js │ │ │ ├── messages.js │ │ │ ├── errors.js │ │ │ ├── leads.js │ │ │ └── auth.js │ │ ├── actions │ │ │ ├── messages.js │ │ │ ├── types.js │ │ │ ├── leads.js │ │ │ └── auth.js │ │ └── store.js │ ├── models.py │ ├── tests.py │ ├── admin.py │ ├── urls.py │ ├── apps.py │ ├── views.py │ └── templates │ │ └── frontend │ │ └── index.html ├── leads │ ├── __init__.py │ ├── migrations │ │ ├── __init__.py │ │ ├── 0002_lead_owner.py │ │ └── 0001_initial.py │ ├── tests.py │ ├── admin.py │ ├── views.py │ ├── apps.py │ ├── urls.py │ ├── serializers.py │ ├── models.py │ └── api.py ├── leadmanager │ ├── __init__.py │ ├── urls.py │ ├── wsgi.py │ └── settings.py └── manage.py ├── .babelrc ├── .prettierrc ├── webpack.config.js ├── Pipfile ├── README.md ├── .editorconfig ├── .eslintrc ├── package.json ├── .gitignore └── Pipfile.lock /leadmanager/accounts/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /leadmanager/frontend/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /leadmanager/leads/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /leadmanager/leadmanager/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /leadmanager/accounts/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /leadmanager/frontend/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /leadmanager/leads/migrations/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /leadmanager/frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import App from './components/App'; 2 | -------------------------------------------------------------------------------- /leadmanager/leads/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /leadmanager/accounts/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /leadmanager/accounts/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /leadmanager/frontend/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | # Create your models here. 4 | -------------------------------------------------------------------------------- /leadmanager/frontend/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /leadmanager/leads/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /leadmanager/leads/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /leadmanager/accounts/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /leadmanager/accounts/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | # Create your views here. 4 | -------------------------------------------------------------------------------- /leadmanager/frontend/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | 3 | # Register your models here. 4 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env", "@babel/preset-react"], 3 | "plugins": ["transform-class-properties"] 4 | } -------------------------------------------------------------------------------- /leadmanager/leads/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class LeadsConfig(AppConfig): 5 | name = 'leads' 6 | -------------------------------------------------------------------------------- /leadmanager/frontend/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from . import views 3 | urlpatterns = [ 4 | path('', views.index) 5 | ] -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 100, 3 | "trailingComma": "all", 4 | "tabWidth": 2, 5 | "semi": true, 6 | "singleQuote": true 7 | } -------------------------------------------------------------------------------- /leadmanager/accounts/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class AccountsConfig(AppConfig): 5 | name = 'accounts' 6 | -------------------------------------------------------------------------------- /leadmanager/frontend/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class FrontendConfig(AppConfig): 5 | name = 'frontend' 6 | -------------------------------------------------------------------------------- /leadmanager/frontend/views.py: -------------------------------------------------------------------------------- 1 | from django.shortcuts import render 2 | 3 | def index(request): 4 | return render(request, 'frontend/index.html') 5 | -------------------------------------------------------------------------------- /leadmanager/leads/urls.py: -------------------------------------------------------------------------------- 1 | from rest_framework import routers 2 | from .api import LeadViewSet 3 | 4 | router = routers.DefaultRouter() 5 | router.register('api/leads', LeadViewSet, 'leads') 6 | 7 | urlpatterns = router.urls -------------------------------------------------------------------------------- /leadmanager/leads/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from leads.models import Lead 3 | 4 | # Lead Serializer 5 | class LeadSerializer(serializers.ModelSerializer): 6 | class Meta: 7 | model = Lead 8 | fields = '__all__' -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | module: { 3 | rules: [ 4 | { 5 | test: /\.js$/, 6 | exclude: /node_modules/, 7 | use: { 8 | loader: "babel-loader" 9 | } 10 | } 11 | ] 12 | } 13 | } -------------------------------------------------------------------------------- /leadmanager/leadmanager/urls.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from django.urls import path, include 3 | 4 | urlpatterns = [ 5 | path('', include('frontend.urls')), 6 | path('', include('leads.urls')), 7 | path('', include('accounts.urls')) 8 | ] 9 | -------------------------------------------------------------------------------- /Pipfile: -------------------------------------------------------------------------------- 1 | [[source]] 2 | name = "pypi" 3 | url = "https://pypi.org/simple" 4 | verify_ssl = true 5 | 6 | [dev-packages] 7 | 8 | [packages] 9 | django = "*" 10 | djangorestframework = "*" 11 | django-rest-knox = "*" 12 | psycopg2 = "*" 13 | 14 | [requires] 15 | python_version = "3.8" 16 | -------------------------------------------------------------------------------- /leadmanager/frontend/src/components/leads/Dashboard.js: -------------------------------------------------------------------------------- 1 | import React, { Fragment } from 'react'; 2 | import Form from './Form'; 3 | import Leads from './Leads'; 4 | 5 | export default function Dashboard() { 6 | return ( 7 | 8 |
9 | 10 | 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /leadmanager/frontend/src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import { combineReducers } from 'redux'; 2 | import leads from './leads'; 3 | import errors from './errors'; 4 | import messages from './messages'; 5 | import auth from './auth'; 6 | 7 | export default combineReducers({ 8 | leads, 9 | errors, 10 | messages, 11 | auth, 12 | }); 13 | -------------------------------------------------------------------------------- /leadmanager/frontend/src/reducers/messages.js: -------------------------------------------------------------------------------- 1 | import { CREATE_MESSAGE } from '../actions/types'; 2 | 3 | const initialState = {}; 4 | 5 | export default function (state = initialState, action) { 6 | switch (action.type) { 7 | case CREATE_MESSAGE: 8 | return (state = action.payload); 9 | default: 10 | return state; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lead Manager 2 | 3 | > Full stack Django/React/Redux app that uses token based authentication with Knox. 4 | 5 | ## Quick Start 6 | 7 | ```bash 8 | # Install dependencies 9 | npm install 10 | 11 | # Serve API on localhost:8000 12 | python leadmanager/manage.py runserver 13 | 14 | # Run webpack (from root) 15 | npm run dev 16 | 17 | # Build for production 18 | npm run build 19 | ``` 20 | -------------------------------------------------------------------------------- /leadmanager/frontend/src/actions/messages.js: -------------------------------------------------------------------------------- 1 | import { CREATE_MESSAGE, GET_ERRORS } from './types'; 2 | 3 | // CREATE MESSAGE 4 | export const createMessage = (msg) => { 5 | return { 6 | type: CREATE_MESSAGE, 7 | payload: msg, 8 | }; 9 | }; 10 | 11 | // RETURN ERRORS 12 | export const returnErrors = (msg, status) => { 13 | return { 14 | type: GET_ERRORS, 15 | payload: { msg, status }, 16 | }; 17 | }; 18 | -------------------------------------------------------------------------------- /leadmanager/frontend/src/reducers/errors.js: -------------------------------------------------------------------------------- 1 | import { GET_ERRORS } from '../actions/types'; 2 | 3 | const initialState = { 4 | msg: {}, 5 | status: null, 6 | }; 7 | 8 | export default function (state = initialState, action) { 9 | switch (action.type) { 10 | case GET_ERRORS: 11 | return { 12 | msg: action.payload.msg, 13 | status: action.payload.status, 14 | }; 15 | default: 16 | return state; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /leadmanager/accounts/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path, include 2 | from .api import RegisterAPI, LoginAPI, UserAPI 3 | from knox import views as knox_views 4 | 5 | urlpatterns = [ 6 | path('api/auth', include('knox.urls')), 7 | path('api/auth/register', RegisterAPI.as_view()), 8 | path('api/auth/login', LoginAPI.as_view()), 9 | path('api/auth/user', UserAPI.as_view()), 10 | path('api/auth/logout', knox_views.LogoutView.as_view(), name='knox_logout') 11 | ] -------------------------------------------------------------------------------- /leadmanager/frontend/src/store.js: -------------------------------------------------------------------------------- 1 | import { createStore, applyMiddleware } from 'redux'; 2 | import { composeWithDevTools } from 'redux-devtools-extension'; 3 | import thunk from 'redux-thunk'; 4 | import rootReducer from './reducers'; 5 | 6 | const initialState = {}; 7 | 8 | const middleware = [thunk]; 9 | 10 | const store = createStore( 11 | rootReducer, 12 | initialState, 13 | composeWithDevTools(applyMiddleware(...middleware)), 14 | ); 15 | 16 | export default store; 17 | -------------------------------------------------------------------------------- /leadmanager/leads/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.contrib.auth.models import User 3 | 4 | 5 | class Lead(models.Model): 6 | name = models.CharField(max_length=100) 7 | email = models.EmailField(max_length=100, unique=True) 8 | message = models.CharField(max_length=500, blank=True) 9 | owner = models.ForeignKey( 10 | User, related_name="leads", on_delete=models.CASCADE, null=True) 11 | created_at = models.DateTimeField(auto_now_add=True) 12 | -------------------------------------------------------------------------------- /leadmanager/leadmanager/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for leadmanager project. 3 | 4 | It exposes the WSGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/2.1/howto/deployment/wsgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.wsgi import get_wsgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'leadmanager.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | root = true 3 | 4 | [*] 5 | charset = utf-8 6 | end_of_line = lf 7 | indent_size = 2 8 | indent_style = space 9 | insert_final_newline = true 10 | max_line_length = 100 11 | trim_trailing_whitespace = true 12 | 13 | [*.md] 14 | max_line_length = 0 15 | trim_trailing_whitespace = false 16 | 17 | [{Makefile, **.mk}] 18 | # Use tabs for indentation (Makefiles require tabs) 19 | indent_style = tab 20 | 21 | [*.scss] 22 | indent_size = 2 23 | indent_style = space -------------------------------------------------------------------------------- /leadmanager/leads/api.py: -------------------------------------------------------------------------------- 1 | from leads.models import Lead 2 | from rest_framework import viewsets, permissions 3 | from .serializers import LeadSerializer 4 | 5 | # Lead Viewset 6 | 7 | 8 | class LeadViewSet(viewsets.ModelViewSet): 9 | permission_classes = [ 10 | permissions.IsAuthenticated, 11 | ] 12 | serializer_class = LeadSerializer 13 | 14 | def get_queryset(self): 15 | return self.request.user.leads.all() 16 | 17 | def perform_create(self, serializer): 18 | serializer.save(owner=self.request.user) 19 | -------------------------------------------------------------------------------- /leadmanager/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | 5 | if __name__ == '__main__': 6 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'leadmanager.settings') 7 | try: 8 | from django.core.management import execute_from_command_line 9 | except ImportError as exc: 10 | raise ImportError( 11 | "Couldn't import Django. Are you sure it's installed and " 12 | "available on your PYTHONPATH environment variable? Did you " 13 | "forget to activate a virtual environment?" 14 | ) from exc 15 | execute_from_command_line(sys.argv) 16 | -------------------------------------------------------------------------------- /leadmanager/frontend/src/actions/types.js: -------------------------------------------------------------------------------- 1 | export const GET_LEADS = 'GET_LEADS'; 2 | export const DELETE_LEAD = 'DELETE_LEAD'; 3 | export const ADD_LEAD = 'ADD_LEAD'; 4 | export const GET_ERRORS = 'GET_ERRORS'; 5 | export const CREATE_MESSAGE = 'CREATE_MESSAGE'; 6 | export const USER_LOADING = 'USER_LOADING'; 7 | export const USER_LOADED = 'USER_LOADED'; 8 | export const AUTH_ERROR = 'AUTH_ERROR'; 9 | export const LOGIN_SUCCESS = 'LOGIN_SUCCESS'; 10 | export const LOGIN_FAIL = 'LOGIN_FAIL'; 11 | export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS'; 12 | export const REGISTER_SUCCESS = 'REGISTER_SUCCESS'; 13 | export const REGISTER_FAIL = 'REGISTER_FAIL'; 14 | export const CLEAR_LEADS = 'CLEAR_LEADS'; 15 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "parser": "babel-eslint", 3 | "parserOptions": { 4 | "sourceType": "module", 5 | "allowImportExportEverywhere": false, 6 | "codeFrame": false 7 | }, 8 | "extends": [ 9 | "airbnb", 10 | "prettier" 11 | ], 12 | "env": { 13 | "browser": true, 14 | "jest": true 15 | }, 16 | "rules": { 17 | "max-len": [ 18 | "error", 19 | { 20 | "code": 100 21 | } 22 | ], 23 | "prefer-promise-reject-errors": [ 24 | "off" 25 | ], 26 | "react/jsx-filename-extension": [ 27 | "off" 28 | ], 29 | "react/prop-types": [ 30 | "warn" 31 | ], 32 | "no-return-assign": [ 33 | "off" 34 | ] 35 | } 36 | } -------------------------------------------------------------------------------- /leadmanager/leads/migrations/0002_lead_owner.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.5 on 2019-01-30 13:37 2 | 3 | from django.conf import settings 4 | from django.db import migrations, models 5 | import django.db.models.deletion 6 | 7 | 8 | class Migration(migrations.Migration): 9 | 10 | dependencies = [ 11 | migrations.swappable_dependency(settings.AUTH_USER_MODEL), 12 | ('leads', '0001_initial'), 13 | ] 14 | 15 | operations = [ 16 | migrations.AddField( 17 | model_name='lead', 18 | name='owner', 19 | field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='leads', to=settings.AUTH_USER_MODEL), 20 | ), 21 | ] 22 | -------------------------------------------------------------------------------- /leadmanager/frontend/src/components/common/PrivateRoute.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Route, Redirect } from 'react-router-dom'; 3 | import { connect } from 'react-redux'; 4 | import PropTypes from 'prop-types'; 5 | 6 | const PrivateRoute = ({ component: Component, auth, ...rest }) => ( 7 | { 10 | if (auth.isLoading) { 11 | return

Loading...

; 12 | } else if (!auth.isAuthenticated) { 13 | return ; 14 | } else { 15 | return ; 16 | } 17 | }} 18 | /> 19 | ); 20 | 21 | const mapStateToProps = (state) => ({ 22 | auth: state.auth, 23 | }); 24 | 25 | export default connect(mapStateToProps)(PrivateRoute); 26 | -------------------------------------------------------------------------------- /leadmanager/frontend/src/reducers/leads.js: -------------------------------------------------------------------------------- 1 | import { GET_LEADS, DELETE_LEAD, ADD_LEAD, CLEAR_LEADS } from '../actions/types.js'; 2 | 3 | const initialState = { 4 | leads: [], 5 | }; 6 | 7 | export default function (state = initialState, action) { 8 | switch (action.type) { 9 | case GET_LEADS: 10 | return { 11 | ...state, 12 | leads: action.payload, 13 | }; 14 | case DELETE_LEAD: 15 | return { 16 | ...state, 17 | leads: state.leads.filter((lead) => lead.id !== action.payload), 18 | }; 19 | case ADD_LEAD: 20 | return { 21 | ...state, 22 | leads: [...state.leads, action.payload], 23 | }; 24 | case CLEAR_LEADS: 25 | return { 26 | ...state, 27 | leads: [], 28 | }; 29 | default: 30 | return state; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /leadmanager/leads/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 2.1.5 on 2019-01-28 22:36 2 | 3 | from django.db import migrations, models 4 | 5 | 6 | class Migration(migrations.Migration): 7 | 8 | initial = True 9 | 10 | dependencies = [ 11 | ] 12 | 13 | operations = [ 14 | migrations.CreateModel( 15 | name='Lead', 16 | fields=[ 17 | ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('name', models.CharField(max_length=100)), 19 | ('email', models.EmailField(max_length=100, unique=True)), 20 | ('message', models.CharField(blank=True, max_length=500)), 21 | ('created_at', models.DateTimeField(auto_now_add=True)), 22 | ], 23 | ), 24 | ] 25 | -------------------------------------------------------------------------------- /leadmanager/accounts/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from django.contrib.auth.models import User 3 | from django.contrib.auth import authenticate 4 | 5 | # User Serializer 6 | class UserSerializer(serializers.ModelSerializer): 7 | class Meta: 8 | model = User 9 | fields = ('id', 'username', 'email') 10 | 11 | # Register Serializer 12 | class RegisterSerializer(serializers.ModelSerializer): 13 | class Meta: 14 | model = User 15 | fields = ('id', 'username', 'email', 'password') 16 | extra_kwargs = {'password': {'write_only': True}} 17 | 18 | def create(self, validated_data): 19 | user = User.objects.create_user(validated_data['username'], validated_data['email'], validated_data['password']) 20 | 21 | return user 22 | 23 | # Login Serializer 24 | class LoginSerializer(serializers.Serializer): 25 | username = serializers.CharField() 26 | password = serializers.CharField() 27 | 28 | def validate(self, data): 29 | user = authenticate(**data) 30 | if user and user.is_active: 31 | return user 32 | raise serializers.ValidationError("Incorrect Credentials") -------------------------------------------------------------------------------- /leadmanager/frontend/templates/frontend/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Lead Manager 9 | 10 | 11 |
12 | {% load static %} 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /leadmanager/frontend/src/reducers/auth.js: -------------------------------------------------------------------------------- 1 | import { 2 | USER_LOADED, 3 | USER_LOADING, 4 | AUTH_ERROR, 5 | LOGIN_SUCCESS, 6 | LOGIN_FAIL, 7 | LOGOUT_SUCCESS, 8 | REGISTER_SUCCESS, 9 | REGISTER_FAIL, 10 | } from '../actions/types'; 11 | 12 | const initialState = { 13 | token: localStorage.getItem('token'), 14 | isAuthenticated: null, 15 | isLoading: false, 16 | user: null, 17 | }; 18 | 19 | export default function (state = initialState, action) { 20 | switch (action.type) { 21 | case USER_LOADING: 22 | return { 23 | ...state, 24 | isLoading: true, 25 | }; 26 | case USER_LOADED: 27 | return { 28 | ...state, 29 | isAuthenticated: true, 30 | isLoading: false, 31 | user: action.payload, 32 | }; 33 | case LOGIN_SUCCESS: 34 | case REGISTER_SUCCESS: 35 | localStorage.setItem('token', action.payload.token); 36 | return { 37 | ...state, 38 | ...action.payload, 39 | isAuthenticated: true, 40 | isLoading: false, 41 | }; 42 | case AUTH_ERROR: 43 | case LOGIN_FAIL: 44 | case LOGOUT_SUCCESS: 45 | case REGISTER_FAIL: 46 | localStorage.removeItem('token'); 47 | return { 48 | ...state, 49 | token: null, 50 | user: null, 51 | isAuthenticated: false, 52 | isLoading: false, 53 | }; 54 | default: 55 | return state; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /leadmanager/frontend/src/actions/leads.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios'; 2 | import { createMessage, returnErrors } from './messages'; 3 | import { tokenConfig } from './auth'; 4 | 5 | import { GET_LEADS, DELETE_LEAD, ADD_LEAD } from './types'; 6 | 7 | // GET LEADS 8 | export const getLeads = () => (dispatch, getState) => { 9 | axios 10 | .get('/api/leads/', tokenConfig(getState)) 11 | .then((res) => { 12 | dispatch({ 13 | type: GET_LEADS, 14 | payload: res.data, 15 | }); 16 | }) 17 | .catch((err) => dispatch(returnErrors(err.response.data, err.response.status))); 18 | }; 19 | 20 | // DELETE LEAD 21 | export const deleteLead = (id) => (dispatch, getState) => { 22 | axios 23 | .delete(`/api/leads/${id}/`, tokenConfig(getState)) 24 | .then((res) => { 25 | dispatch(createMessage({ deleteLead: 'Lead Deleted' })); 26 | dispatch({ 27 | type: DELETE_LEAD, 28 | payload: id, 29 | }); 30 | }) 31 | .catch((err) => console.log(err)); 32 | }; 33 | 34 | // ADD LEAD 35 | export const addLead = (lead) => (dispatch, getState) => { 36 | axios 37 | .post('/api/leads/', lead, tokenConfig(getState)) 38 | .then((res) => { 39 | dispatch(createMessage({ addLead: 'Lead Added' })); 40 | dispatch({ 41 | type: ADD_LEAD, 42 | payload: res.data, 43 | }); 44 | }) 45 | .catch((err) => dispatch(returnErrors(err.response.data, err.response.status))); 46 | }; 47 | -------------------------------------------------------------------------------- /leadmanager/accounts/api.py: -------------------------------------------------------------------------------- 1 | from rest_framework import generics, permissions 2 | from rest_framework.response import Response 3 | from knox.models import AuthToken 4 | from .serializers import UserSerializer, RegisterSerializer, LoginSerializer 5 | 6 | # Register API 7 | class RegisterAPI(generics.GenericAPIView): 8 | serializer_class = RegisterSerializer 9 | 10 | def post(self, request, *args, **kwargs): 11 | serializer = self.get_serializer(data=request.data) 12 | serializer.is_valid(raise_exception=True) 13 | user = serializer.save() 14 | return Response({ 15 | "user": UserSerializer(user, context=self.get_serializer_context()).data, 16 | "token": AuthToken.objects.create(user)[1] 17 | }) 18 | 19 | # Login API 20 | class LoginAPI(generics.GenericAPIView): 21 | serializer_class = LoginSerializer 22 | 23 | def post(self, request, *args, **kwargs): 24 | serializer = self.get_serializer(data=request.data) 25 | serializer.is_valid(raise_exception=True) 26 | user = serializer.validated_data 27 | _, token = AuthToken.objects.create(user) 28 | return Response({ 29 | "user": UserSerializer(user, context=self.get_serializer_context()).data, 30 | "token": token 31 | }) 32 | 33 | # Get User API 34 | class UserAPI(generics.RetrieveAPIView): 35 | permission_classes = [ 36 | permissions.IsAuthenticated, 37 | ] 38 | serializer_class = UserSerializer 39 | 40 | def get_object(self): 41 | return self.request.user 42 | -------------------------------------------------------------------------------- /leadmanager/frontend/src/components/layout/Alerts.js: -------------------------------------------------------------------------------- 1 | import React, { Component, Fragment } from 'react'; 2 | import { withAlert } from 'react-alert'; 3 | import { connect } from 'react-redux'; 4 | import PropTypes from 'prop-types'; 5 | 6 | export class Alerts extends Component { 7 | static propTypes = { 8 | error: PropTypes.object.isRequired, 9 | message: PropTypes.object.isRequired, 10 | }; 11 | 12 | componentDidUpdate(prevProps) { 13 | const { error, alert, message } = this.props; 14 | if (error !== prevProps.error) { 15 | if (error.msg.name) alert.error(`Name: ${error.msg.name.join()}`); 16 | if (error.msg.email) alert.error(`Email: ${error.msg.email.join()}`); 17 | if (error.msg.message) alert.error(`Message: ${error.msg.message.join()}`); 18 | if (error.msg.non_field_errors) alert.error(error.msg.non_field_errors.join()); 19 | if (error.msg.username) alert.error(error.msg.username.join()); 20 | } 21 | 22 | if (message !== prevProps.message) { 23 | if (message.deleteLead) alert.success(message.deleteLead); 24 | if (message.addLead) alert.success(message.addLead); 25 | if (message.passwordNotMatch) alert.error(message.passwordNotMatch); 26 | } 27 | } 28 | 29 | render() { 30 | return ; 31 | } 32 | } 33 | 34 | const mapStateToProps = (state) => ({ 35 | error: state.errors, 36 | message: state.messages, 37 | }); 38 | 39 | export default connect(mapStateToProps)(withAlert()(Alerts)); 40 | -------------------------------------------------------------------------------- /leadmanager/frontend/src/components/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component, Fragment } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import { HashRouter as Router, Route, Switch, Redirect } from 'react-router-dom'; 4 | 5 | import { Provider as AlertProvider } from 'react-alert'; 6 | import AlertTemplate from 'react-alert-template-basic'; 7 | 8 | import Header from './layout/Header'; 9 | import Dashboard from './leads/Dashboard'; 10 | import Alerts from './layout/Alerts'; 11 | import Login from './accounts/Login'; 12 | import Register from './accounts/Register'; 13 | import PrivateRoute from './common/PrivateRoute'; 14 | 15 | import { Provider } from 'react-redux'; 16 | import store from '../store'; 17 | import { loadUser } from '../actions/auth'; 18 | 19 | // Alert Options 20 | const alertOptions = { 21 | timeout: 3000, 22 | position: 'top center', 23 | }; 24 | 25 | class App extends Component { 26 | componentDidMount() { 27 | store.dispatch(loadUser()); 28 | } 29 | 30 | render() { 31 | return ( 32 | 33 | 34 | 35 | 36 |
37 | 38 |
39 | 40 | 41 | 42 | 43 | 44 |
45 | 46 | 47 | 48 | 49 | ); 50 | } 51 | } 52 | 53 | ReactDOM.render(, document.getElementById('app')); 54 | -------------------------------------------------------------------------------- /leadmanager/frontend/src/components/leads/Leads.js: -------------------------------------------------------------------------------- 1 | import React, { Component, Fragment } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import PropTypes from 'prop-types'; 4 | import { getLeads, deleteLead } from '../../actions/leads'; 5 | 6 | export class Leads extends Component { 7 | static propTypes = { 8 | leads: PropTypes.array.isRequired, 9 | getLeads: PropTypes.func.isRequired, 10 | deleteLead: PropTypes.func.isRequired, 11 | }; 12 | 13 | componentDidMount() { 14 | this.props.getLeads(); 15 | } 16 | 17 | render() { 18 | return ( 19 | 20 |

Leads

21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 31 | 32 | {this.props.leads.map((lead) => ( 33 | 34 | 35 | 36 | 37 | 38 | 47 | 48 | ))} 49 | 50 |
IDNameEmailMessage 29 |
{lead.id}{lead.name}{lead.email}{lead.message} 39 | 46 |
51 |
52 | ); 53 | } 54 | } 55 | 56 | const mapStateToProps = (state) => ({ 57 | leads: state.leads.leads, 58 | }); 59 | 60 | export default connect(mapStateToProps, { getLeads, deleteLead })(Leads); 61 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lead_manager_react_django", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "dev": "webpack --mode development --watch ./leadmanager/frontend/src/index.js --output ./leadmanager/frontend/static/frontend/main.js", 8 | "build": "webpack --mode production ./leadmanager/frontend/src/index.js --output ./leadmanager/frontend/static/frontend/main.js" 9 | }, 10 | "keywords": [], 11 | "author": "", 12 | "license": "ISC", 13 | "dependencies": { 14 | "axios": "^0.18.1", 15 | "prop-types": "^15.7.2", 16 | "react": "^16.13.1", 17 | "react-alert": "^7.0.0", 18 | "react-alert-template-basic": "^1.0.0", 19 | "react-dom": "^16.13.1", 20 | "react-redux": "^6.0.1", 21 | "react-router-dom": "^5.1.2", 22 | "react-transition-group": "^2.9.0", 23 | "redux": "^4.0.5", 24 | "redux-devtools-extension": "^2.13.8", 25 | "redux-thunk": "^2.3.0" 26 | }, 27 | "devDependencies": { 28 | "@babel/core": "^7.9.0", 29 | "@babel/preset-env": "^7.9.0", 30 | "@babel/preset-react": "^7.9.4", 31 | "babel-eslint": "^10.1.0", 32 | "babel-loader": "^8.1.0", 33 | "babel-plugin-transform-class-properties": "^6.24.1", 34 | "eslint": "^6.8.0", 35 | "eslint-config-airbnb": "^18.1.0", 36 | "eslint-config-prettier": "^6.10.1", 37 | "eslint-plugin-import": "^2.20.1", 38 | "eslint-plugin-jsx-a11y": "^6.2.3", 39 | "eslint-plugin-react": "^7.19.0", 40 | "husky": "^4.2.3", 41 | "prettier": "^2.0.2", 42 | "pretty-quick": "^2.0.1", 43 | "webpack": "^4.42.1", 44 | "webpack-cli": "^3.3.11" 45 | }, 46 | "husky": { 47 | "hooks": { 48 | "pre-commit": "lint-staged" 49 | } 50 | }, 51 | "lint-staged": { 52 | "src/**/*.js": [ 53 | "eslint", 54 | "pretty-quick — staged", 55 | "git add" 56 | ] 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /leadmanager/frontend/src/components/leads/Form.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import { connect } from 'react-redux'; 3 | import PropTypes from 'prop-types'; 4 | import { addLead } from '../../actions/leads'; 5 | 6 | export class Form extends Component { 7 | state = { 8 | name: '', 9 | email: '', 10 | message: '', 11 | }; 12 | 13 | static propTypes = { 14 | addLead: PropTypes.func.isRequired, 15 | }; 16 | 17 | onChange = (e) => this.setState({ [e.target.name]: e.target.value }); 18 | 19 | onSubmit = (e) => { 20 | e.preventDefault(); 21 | const { name, email, message } = this.state; 22 | const lead = { name, email, message }; 23 | this.props.addLead(lead); 24 | this.setState({ 25 | name: '', 26 | email: '', 27 | message: '', 28 | }); 29 | }; 30 | 31 | render() { 32 | const { name, email, message } = this.state; 33 | return ( 34 |
35 |

Add Lead

36 | 37 |
38 | 39 | 46 |
47 |
48 | 49 | 56 |
57 |
58 | 59 |