├── .eslintignore ├── .github └── workflows │ ├── django.yml │ └── react.yml ├── .gitignore ├── .husky └── pre-commit ├── .prettierignore ├── LICENSE ├── README.md ├── backend ├── about │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ ├── urls.py │ └── views.py ├── config │ ├── __init__.py │ ├── asgi.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── contact │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ ├── urls.py │ └── views.py ├── footer │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ ├── urls.py │ └── views.py ├── hero │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ ├── urls.py │ └── views.py ├── manage.py ├── pricing │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ ├── urls.py │ └── views.py ├── products │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ ├── urls.py │ └── views.py ├── requirements.txt ├── services │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ ├── urls.py │ └── views.py └── setup.cfg └── frontend ├── .eslintrc.json ├── .prettierrc.json ├── jest.config.js ├── jest.setup.js ├── package-lock.json ├── package.json ├── public ├── favicon.ico └── index.html ├── src ├── App.tsx ├── assets │ ├── images │ │ ├── 1.png │ │ ├── 2.png │ │ ├── 3.png │ │ ├── chart1.jpg │ │ ├── chart2.jpg │ │ ├── chart3.jpg │ │ └── chart4.jpg │ └── logo │ │ └── react.png ├── components │ ├── About.tsx │ ├── Contact.tsx │ ├── CustomButton.tsx │ ├── Hero.tsx │ ├── HeroButtons.tsx │ ├── Map.tsx │ ├── Pricing.tsx │ ├── Products.tsx │ ├── Services.tsx │ └── Spacer.tsx ├── index.tsx ├── layout │ ├── Footer.tsx │ ├── Header.tsx │ ├── Layout.tsx │ └── Sidebar.tsx ├── pages │ └── Home.tsx ├── react-app-env.d.ts ├── reportWebVitals.ts ├── tests │ ├── Home.test.tsx │ └── mocks │ │ ├── fileMock.css │ │ └── fileMock.ts └── theme │ └── theme.ts └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | 4 | # testing 5 | /coverage 6 | 7 | # next.js 8 | /.next/ 9 | /out/ 10 | 11 | # production 12 | /build 13 | 14 | 15 | # others 16 | /code-stages 17 | /scripts 18 | -------------------------------------------------------------------------------- /.github/workflows/django.yml: -------------------------------------------------------------------------------- 1 | name: Django CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | 7 | pull_request: 8 | branches: [main] 9 | 10 | jobs: 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | strategy: 15 | max-parallel: 4 16 | matrix: 17 | python-version: [3.8, 3.9, '3.10'] 18 | 19 | steps: 20 | - name: Checkout source Git branch 21 | uses: actions/checkout@v4 22 | 23 | - name: Set up Python ${{ matrix.python-version }} 24 | uses: actions/setup-python@v4 25 | with: 26 | python-version: ${{ matrix.python-version }} 27 | 28 | - name: Install Dependencies 29 | run: | 30 | cd backend 31 | python -m pip install --upgrade pip 32 | pip install -r requirements.txt 33 | 34 | - name: Run migrations 35 | run: | 36 | cd backend 37 | python manage.py makemigrations 38 | python manage.py migrate 39 | 40 | - name: Lint with flake8 41 | run: | 42 | cd backend 43 | flake8 44 | 45 | - name: Run Tests 46 | run: | 47 | cd backend 48 | python manage.py test 49 | -------------------------------------------------------------------------------- /.github/workflows/react.yml: -------------------------------------------------------------------------------- 1 | name: React CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | 7 | pull_request: 8 | branches: [main] 9 | 10 | jobs: 11 | build: 12 | runs-on: ${{ matrix.os }} 13 | strategy: 14 | matrix: 15 | os: [macos-latest, ubuntu-latest, windows-latest] 16 | node-version: ['18.x', '20.x'] 17 | 18 | steps: 19 | - name: Checkout source Git branch 20 | uses: actions/checkout@v4 21 | 22 | - name: Use Node.js ${{ matrix.node-version }} 23 | uses: actions/setup-node@v4 24 | with: 25 | node-version: ${{ matrix.node-version }} 26 | 27 | - name: Install Dependencies 28 | run: | 29 | cd frontend 30 | npm install 31 | 32 | - name: Run Tests 33 | run: | 34 | cd frontend 35 | npm run test 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .DS_Store 3 | .next/ 4 | db.sqlite3 5 | media/ 6 | node_modules/ 7 | venv/ 8 | */migrations/* 9 | !*/migrations/__init__.py -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | cd frontend && npx lint-staged 5 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # dependencies 2 | /node_modules 3 | 4 | # testing 5 | /coverage 6 | 7 | # next.js 8 | /.next/ 9 | /out/ 10 | 11 | # production 12 | /build 13 | 14 | 15 | # others 16 | # /code-stages 17 | /scripts 18 | 19 | *.html 20 | *.css -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Bob's Programming Academy 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Enterprise Website Template 2 | 3 | This is an enterprise website template built using **React 18** (with **TypeScript 4**), **Material UI 5**, **Django 4**, and **Django REST Framework 3**. 4 | 5 | ![plot](https://github.com/BobsProgrammingAcademy/enterprise-website-template/blob/main/frontend/src/assets/images/1.png?raw=true) 6 | 7 | 8 | ## Table of Contents 9 | - [Prerequisites](#prerequisites) 10 | - [Installation](#installation) 11 | - [Running the application](#run-the-application) 12 | - [Running the tests](#run-the-tests) 13 | - [Adding data to the application](#add-data-to-the-application) 14 | - [Customizing the application](#customize-the-application) 15 | - [Copyright and License](#copyright-and-license) 16 | 17 | 18 | ## Prerequisites 19 | 20 | Install the following prerequisites: 21 | 22 | 1. [Python 3.8-3.11](https://www.python.org/downloads/) 23 |
This project uses **Django v4.1.5**. For Django to work, you must install a correct version of Python on your machine. More information [here](https://django.readthedocs.io/en/stable/faq/install.html). 24 | 2. [Node.js](https://nodejs.org/en/) 25 | 3. [Visual Studio Code](https://code.visualstudio.com/download) 26 | 27 | 28 | ## Installation 29 | 30 | ### Backend 31 | 32 | #### 1. Create a virtual environment 33 | 34 | From the **root** directory, run: 35 | 36 | ```bash 37 | cd backend 38 | ``` 39 | ```bash 40 | python -m venv venv 41 | ``` 42 | 43 | #### 2. Activate the virtual environment 44 | 45 | From the **backend** directory, run: 46 | 47 | On macOS: 48 | 49 | ```bash 50 | source venv/bin/activate 51 | ``` 52 | 53 | On Windows: 54 | 55 | ```bash 56 | venv\scripts\activate 57 | ``` 58 | 59 | #### 3. Install required backend dependencies 60 | 61 | From the **backend** directory, run: 62 | 63 | ```bash 64 | pip install -r requirements.txt 65 | ``` 66 | 67 | #### 4. Run migrations 68 | 69 | From the **backend** directory, run: 70 | 71 | ```bash 72 | python manage.py makemigrations 73 | ``` 74 | ```bash 75 | python manage.py migrate 76 | ``` 77 | 78 | #### 5. Create an admin user to access the Django Admin interface 79 | 80 | From the **backend** directory, run: 81 | 82 | ```bash 83 | python manage.py createsuperuser 84 | ``` 85 | 86 | When prompted, enter a username, email, and password. 87 | 88 | ### Frontend 89 | 90 | #### 1. Install required frontend dependencies 91 | 92 | From the **root** directory, run: 93 | 94 | ```bash 95 | cd frontend 96 | ``` 97 | ```bash 98 | npm install 99 | ``` 100 | 101 | ## Run the application 102 | 103 | To run the application, you need to have both the backend and the frontend up and running. 104 | 105 | ### 1. Run backend 106 | 107 | From the **backend** directory, run: 108 | 109 | ```bash 110 | python manage.py runserver 111 | ``` 112 | 113 | ### 2. Run frontend 114 | 115 | From the **frontend** directory, run: 116 | 117 | ```bash 118 | npm start 119 | ``` 120 | 121 | ## View the application 122 | 123 | Go to http://localhost:3000/ to view the application. 124 | 125 | ## Run the tests 126 | 127 | ### 1. Run the backend tests 128 | 129 | From the **backend** directory, run: 130 | 131 | ```bash 132 | python manage.py test --pattern="tests.py" 133 | 134 | ``` 135 | 136 | ### 2. Run the frontend tests 137 | 138 | From the **frontend** directory, run: 139 | 140 | ```bash 141 | npm run test 142 | ``` 143 | 144 | ## Add data to the application 145 | 146 | Add data through Django Admin. 147 | 148 | Go to http://127.0.0.1:8000/admin to access the Django Admin interface and sign in using the admin credentials. 149 | 150 | ## Customize the application 151 | 152 | This section describes how to customize the application. 153 | 154 | ### Changing Section Titles and Subtitles 155 | 156 | #### 1. Products 157 | 158 | To modify the title and subtitle of the **Products** section, make changes in the ```frontend/src/components/Products.tsx``` file. 159 | 160 | #### 2. Services 161 | 162 | To modify the title and subtitle of the **Services** section, make changes in the ```frontend/src/components/Services.tsx``` file. 163 | 164 | #### 3. Pricing 165 | 166 | To modify the title and subtitle of the **Pricing** section, make changes in the ```frontend/src/components/Pricing.tsx``` file. 167 | 168 | #### 4. About 169 | 170 | To modify the title and subtitle of the **About** section, make changes in the ```frontend/src/components/About.tsx``` file. 171 | 172 | #### 5. Contact 173 | 174 | To modify the title and subtitle of the **Contact** section, make changes in the ```frontend/src/components/Contact.tsx``` file. 175 | 176 | ### Changing Colors 177 | 178 | To modify the colors in the application, make changes in the ```frontend/src/theme/theme.ts``` file. 179 | 180 | #### Changing Fonts 181 | 182 | To modify the fonts in the application, first, add a new font to the ```frontend/public/index.html``` file, and then make changes in the ```frontend/src/theme/theme.ts``` file. 183 | 184 | #### Changing Logo 185 | 186 | To modify the logo in the application, make changes in the ```frontend/src/layout/Header.tsx``` and ```frontend/src/layout/Sidebar.tsx``` files. 187 | 188 | #### Changing Buttons in the Hero Section 189 | 190 | To modify the two buttons in the Hero section, make changes in the ```frontend/src/components/HeroButtons.tsx``` file. 191 | 192 | ## Copyright and License 193 | 194 | Copyright © 2022 Bob's Programming Academy. Code released under the MIT license. 195 | -------------------------------------------------------------------------------- /backend/about/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/enterprise-website-template/4a45533e177bd48612e7e848c83f187eda1b8219/backend/about/__init__.py -------------------------------------------------------------------------------- /backend/about/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import About 3 | 4 | admin.site.register(About) 5 | -------------------------------------------------------------------------------- /backend/about/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class AboutConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'about' 7 | -------------------------------------------------------------------------------- /backend/about/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1 on 2022-08-29 00:16 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='About', 16 | fields=[ 17 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('value', models.IntegerField()), 19 | ('suffix', models.CharField(max_length=1)), 20 | ('description', models.CharField(max_length=250)), 21 | ], 22 | options={ 23 | 'verbose_name': 'About', 24 | 'verbose_name_plural': 'About', 25 | }, 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /backend/about/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/enterprise-website-template/4a45533e177bd48612e7e848c83f187eda1b8219/backend/about/migrations/__init__.py -------------------------------------------------------------------------------- /backend/about/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class About(models.Model): 5 | value = models.IntegerField() 6 | suffix = models.CharField(max_length=1) 7 | description = models.CharField(max_length=250) 8 | 9 | class Meta: 10 | verbose_name = 'About' 11 | verbose_name_plural = 'About' 12 | 13 | def __str__(self): 14 | return f'{self.value}{self.suffix}' 15 | -------------------------------------------------------------------------------- /backend/about/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from .models import About 3 | 4 | 5 | class AboutSerializer(serializers.ModelSerializer): 6 | class Meta: 7 | model = About 8 | fields = '__all__' 9 | -------------------------------------------------------------------------------- /backend/about/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from .models import About 3 | 4 | 5 | class AboutModelUnitTestCase(TestCase): 6 | def setUp(self): 7 | self.about = About.objects.create( 8 | value=90, 9 | suffix='+', 10 | description='Lorem ipsum dolor sit amet' 11 | ) 12 | 13 | def test_about_model(self): 14 | data = self.about 15 | self.assertIsInstance(data, About) 16 | -------------------------------------------------------------------------------- /backend/about/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from .views import AboutListView 3 | 4 | urlpatterns = [ 5 | path('', AboutListView.as_view()), 6 | ] 7 | -------------------------------------------------------------------------------- /backend/about/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework.generics import ListAPIView 2 | from rest_framework import permissions 3 | 4 | from .models import About 5 | from .serializers import AboutSerializer 6 | 7 | 8 | class AboutListView(ListAPIView): 9 | permission_classes = (permissions.AllowAny, ) 10 | queryset = About.objects.all() 11 | serializer_class = AboutSerializer 12 | pagination_class = None 13 | -------------------------------------------------------------------------------- /backend/config/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/enterprise-website-template/4a45533e177bd48612e7e848c83f187eda1b8219/backend/config/__init__.py -------------------------------------------------------------------------------- /backend/config/asgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | ASGI config for config project. 3 | 4 | It exposes the ASGI callable as a module-level variable named ``application``. 5 | 6 | For more information on this file, see 7 | https://docs.djangoproject.com/en/4.1/howto/deployment/asgi/ 8 | """ 9 | 10 | import os 11 | 12 | from django.core.asgi import get_asgi_application 13 | 14 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') 15 | 16 | application = get_asgi_application() 17 | -------------------------------------------------------------------------------- /backend/config/settings.py: -------------------------------------------------------------------------------- 1 | import os 2 | from pathlib import Path 3 | 4 | # Build paths inside the project like this: BASE_DIR / 'subdir'. 5 | BASE_DIR = Path(__file__).resolve().parent.parent 6 | 7 | 8 | # Quick-start development settings - unsuitable for production 9 | # See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/ 10 | 11 | # SECURITY WARNING: keep the secret key used in production secret! 12 | SECRET_KEY = 'django-insecure--$g-e%io^a4vhd3-1t8=(2ij#%#kk5s!yjkg=s&5bt_zoroxls' 13 | 14 | # SECURITY WARNING: don't run with debug turned on in production! 15 | DEBUG = True 16 | 17 | ALLOWED_HOSTS = [] 18 | 19 | 20 | # Application definition 21 | 22 | INSTALLED_APPS = [ 23 | 'django.contrib.admin', 24 | 'django.contrib.auth', 25 | 'django.contrib.contenttypes', 26 | 'django.contrib.sessions', 27 | 'django.contrib.messages', 28 | 'django.contrib.staticfiles', 29 | 30 | # 3rd party 31 | 'rest_framework', 32 | 'corsheaders', 33 | 34 | # Local 35 | 'products', 36 | 'services', 37 | 'pricing', 38 | 'about', 39 | 'contact', 40 | 'hero', 41 | 'footer' 42 | ] 43 | 44 | MIDDLEWARE = [ 45 | 'corsheaders.middleware.CorsMiddleware', 46 | 'django.middleware.security.SecurityMiddleware', 47 | 'django.contrib.sessions.middleware.SessionMiddleware', 48 | 'django.middleware.common.CommonMiddleware', 49 | 'django.middleware.csrf.CsrfViewMiddleware', 50 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 51 | 'django.contrib.messages.middleware.MessageMiddleware', 52 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 53 | ] 54 | 55 | ROOT_URLCONF = 'config.urls' 56 | 57 | TEMPLATES = [ 58 | { 59 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 60 | 'DIRS': [os.path.join(BASE_DIR, 'build')], 61 | 'APP_DIRS': True, 62 | 'OPTIONS': { 63 | 'context_processors': [ 64 | 'django.template.context_processors.debug', 65 | 'django.template.context_processors.request', 66 | 'django.contrib.auth.context_processors.auth', 67 | 'django.contrib.messages.context_processors.messages', 68 | ], 69 | }, 70 | }, 71 | ] 72 | 73 | WSGI_APPLICATION = 'config.wsgi.application' 74 | 75 | 76 | # Database 77 | # https://docs.djangoproject.com/en/4.1/ref/settings/#databases 78 | 79 | DATABASES = { 80 | 'default': { 81 | 'ENGINE': 'django.db.backends.sqlite3', 82 | 'NAME': BASE_DIR / 'db.sqlite3', 83 | } 84 | } 85 | 86 | 87 | # Password validation 88 | # https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators 89 | 90 | AUTH_PASSWORD_VALIDATORS = [ 91 | { 92 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 93 | }, 94 | { 95 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 96 | }, 97 | { 98 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 99 | }, 100 | { 101 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 102 | }, 103 | ] 104 | 105 | 106 | # Internationalization 107 | # https://docs.djangoproject.com/en/4.1/topics/i18n/ 108 | 109 | LANGUAGE_CODE = 'en-us' 110 | 111 | TIME_ZONE = 'UTC' 112 | 113 | USE_I18N = True 114 | 115 | USE_TZ = True 116 | 117 | 118 | # Static files (CSS, JavaScript, Images) 119 | # https://docs.djangoproject.com/en/4.1/howto/static-files/ 120 | 121 | # MEDIA_ROOT is for the user-uploaded content 122 | 123 | STATIC_URL = '/static/' 124 | STATIC_ROOT = os.path.join(BASE_DIR, 'static') 125 | 126 | MEDIA_URL = '/media/' 127 | MEDIA_ROOT = os.path.join(BASE_DIR, 'media') 128 | 129 | 130 | # Default primary key field type 131 | # https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field 132 | 133 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' 134 | 135 | CORS_ALLOWED_ORIGINS = [ 136 | 'http://localhost:3000' 137 | ] 138 | 139 | 140 | FILE_UPLOAD_PERMISSIONS = 0o640 141 | -------------------------------------------------------------------------------- /backend/config/urls.py: -------------------------------------------------------------------------------- 1 | from django.conf import settings 2 | from django.conf.urls.static import static 3 | from django.contrib import admin 4 | from django.urls import path, include 5 | 6 | urlpatterns = [ 7 | path('admin/', admin.site.urls), 8 | path('products/', include('products.urls')), 9 | path('services/', include('services.urls')), 10 | path('pricing/', include('pricing.urls')), 11 | path('about/', include('about.urls')), 12 | path('contact/', include('contact.urls')), 13 | path('hero/', include('hero.urls')), 14 | path('footer/', include('footer.urls')), 15 | ] 16 | 17 | if settings.DEBUG: 18 | urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) 19 | urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) 20 | -------------------------------------------------------------------------------- /backend/config/wsgi.py: -------------------------------------------------------------------------------- 1 | """ 2 | WSGI config for config 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/4.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', 'config.settings') 15 | 16 | application = get_wsgi_application() 17 | -------------------------------------------------------------------------------- /backend/contact/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/enterprise-website-template/4a45533e177bd48612e7e848c83f187eda1b8219/backend/contact/__init__.py -------------------------------------------------------------------------------- /backend/contact/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Contact 3 | 4 | 5 | class ContactAdmin(admin.ModelAdmin): 6 | list_display = ('address', 'email') 7 | list_display_links = ('email',) 8 | search_fields = ('address', 'email') 9 | list_per_page = 20 10 | 11 | 12 | admin.site.register(Contact, ContactAdmin) 13 | -------------------------------------------------------------------------------- /backend/contact/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ContactConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'contact' 7 | -------------------------------------------------------------------------------- /backend/contact/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1 on 2022-08-29 00:16 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='Contact', 16 | fields=[ 17 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('address', models.CharField(max_length=250)), 19 | ('email', models.CharField(max_length=150)), 20 | ('phone', models.CharField(max_length=20)), 21 | ('latitude', models.DecimalField(decimal_places=6, max_digits=9)), 22 | ('longitude', models.DecimalField(decimal_places=6, max_digits=9)), 23 | ], 24 | options={ 25 | 'verbose_name': 'Contact', 26 | 'verbose_name_plural': 'Contact', 27 | }, 28 | ), 29 | ] 30 | -------------------------------------------------------------------------------- /backend/contact/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/enterprise-website-template/4a45533e177bd48612e7e848c83f187eda1b8219/backend/contact/migrations/__init__.py -------------------------------------------------------------------------------- /backend/contact/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class Contact(models.Model): 5 | address = models.CharField(max_length=250) 6 | email = models.CharField(max_length=150) 7 | phone = models.CharField(max_length=20) 8 | latitude = models.DecimalField(max_digits=9, decimal_places=6) 9 | longitude = models.DecimalField(max_digits=9, decimal_places=6) 10 | 11 | class Meta: 12 | verbose_name = 'Contact' 13 | verbose_name_plural = 'Contact' 14 | 15 | def __str__(self): 16 | return self.email 17 | -------------------------------------------------------------------------------- /backend/contact/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from .models import Contact 3 | 4 | 5 | class ContactSerializer(serializers.ModelSerializer): 6 | class Meta: 7 | model = Contact 8 | fields = '__all__' 9 | -------------------------------------------------------------------------------- /backend/contact/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from .models import Contact 3 | 4 | 5 | class ContactModelUnitTestCase(TestCase): 6 | def setUp(self): 7 | self.contact = Contact.objects.create( 8 | address='Dublin, Ireland', 9 | email='bob@test.com', 10 | phone='123456789', 11 | latitude='53.350140', 12 | longitude='-6.266155' 13 | ) 14 | 15 | def test_contact_model(self): 16 | data = self.contact 17 | self.assertIsInstance(data, Contact) 18 | -------------------------------------------------------------------------------- /backend/contact/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from .views import ContactListView 3 | 4 | urlpatterns = [ 5 | path('', ContactListView.as_view()), 6 | ] 7 | -------------------------------------------------------------------------------- /backend/contact/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework.generics import ListAPIView 2 | from rest_framework import permissions 3 | 4 | from .models import Contact 5 | from .serializers import ContactSerializer 6 | 7 | 8 | class ContactListView(ListAPIView): 9 | permission_classes = (permissions.AllowAny, ) 10 | queryset = Contact.objects.all() 11 | serializer_class = ContactSerializer 12 | pagination_class = None 13 | -------------------------------------------------------------------------------- /backend/footer/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/enterprise-website-template/4a45533e177bd48612e7e848c83f187eda1b8219/backend/footer/__init__.py -------------------------------------------------------------------------------- /backend/footer/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Footer 3 | 4 | admin.site.register(Footer) 5 | -------------------------------------------------------------------------------- /backend/footer/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class FooterConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'footer' 7 | -------------------------------------------------------------------------------- /backend/footer/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1 on 2022-08-29 00:16 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='Footer', 16 | fields=[ 17 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('copyright', models.CharField(max_length=200)), 19 | ], 20 | options={ 21 | 'verbose_name': 'Footer', 22 | 'verbose_name_plural': 'Footer', 23 | }, 24 | ), 25 | ] 26 | -------------------------------------------------------------------------------- /backend/footer/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/enterprise-website-template/4a45533e177bd48612e7e848c83f187eda1b8219/backend/footer/migrations/__init__.py -------------------------------------------------------------------------------- /backend/footer/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class Footer(models.Model): 5 | copyright = models.CharField(max_length=200) 6 | 7 | class Meta: 8 | verbose_name = 'Footer' 9 | verbose_name_plural = 'Footer' 10 | 11 | def __str__(self): 12 | return self.copyright 13 | -------------------------------------------------------------------------------- /backend/footer/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from .models import Footer 3 | 4 | 5 | class FooterSerializer(serializers.ModelSerializer): 6 | class Meta: 7 | model = Footer 8 | fields = '__all__' 9 | -------------------------------------------------------------------------------- /backend/footer/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from .models import Footer 3 | 4 | 5 | class FooterModelUnitTestCase(TestCase): 6 | def setUp(self): 7 | self.footer = Footer.objects.create( 8 | copyright="Copyright © 2023 Bob's Programming Academy.", 9 | ) 10 | 11 | def test_footer_model(self): 12 | data = self.footer 13 | self.assertIsInstance(data, Footer) 14 | -------------------------------------------------------------------------------- /backend/footer/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from .views import FooterListView 3 | 4 | urlpatterns = [ 5 | path('', FooterListView.as_view()), 6 | ] 7 | -------------------------------------------------------------------------------- /backend/footer/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework.generics import ListAPIView 2 | from rest_framework import permissions 3 | 4 | from .models import Footer 5 | from .serializers import FooterSerializer 6 | 7 | 8 | class FooterListView(ListAPIView): 9 | permission_classes = (permissions.AllowAny, ) 10 | queryset = Footer.objects.all() 11 | serializer_class = FooterSerializer 12 | pagination_class = None 13 | -------------------------------------------------------------------------------- /backend/hero/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/enterprise-website-template/4a45533e177bd48612e7e848c83f187eda1b8219/backend/hero/__init__.py -------------------------------------------------------------------------------- /backend/hero/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Hero 3 | 4 | admin.site.register(Hero) 5 | -------------------------------------------------------------------------------- /backend/hero/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class HeroConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'hero' 7 | -------------------------------------------------------------------------------- /backend/hero/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1 on 2022-08-29 00:16 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='Hero', 16 | fields=[ 17 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('title', models.CharField(max_length=200)), 19 | ('description', models.CharField(max_length=300)), 20 | ], 21 | options={ 22 | 'verbose_name': 'Hero', 23 | 'verbose_name_plural': 'Hero', 24 | }, 25 | ), 26 | ] 27 | -------------------------------------------------------------------------------- /backend/hero/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/enterprise-website-template/4a45533e177bd48612e7e848c83f187eda1b8219/backend/hero/migrations/__init__.py -------------------------------------------------------------------------------- /backend/hero/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class Hero(models.Model): 5 | title = models.CharField(max_length=200) 6 | description = models.CharField(max_length=300) 7 | 8 | class Meta: 9 | verbose_name = 'Hero' 10 | verbose_name_plural = 'Hero' 11 | 12 | def __str__(self): 13 | return self.title 14 | -------------------------------------------------------------------------------- /backend/hero/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from .models import Hero 3 | 4 | 5 | class HeroSerializer(serializers.ModelSerializer): 6 | class Meta: 7 | model = Hero 8 | fields = '__all__' 9 | -------------------------------------------------------------------------------- /backend/hero/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from .models import Hero 3 | 4 | 5 | class HeroModelUnitTestCase(TestCase): 6 | def setUp(self): 7 | self.hero = Hero.objects.create( 8 | title='Lorem ipsum dolor sit amet', 9 | description='Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed tincidunt quis odio id.', 10 | ) 11 | 12 | def test_hero_model(self): 13 | data = self.hero 14 | self.assertIsInstance(data, Hero) 15 | -------------------------------------------------------------------------------- /backend/hero/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from .views import HeroListView 3 | 4 | urlpatterns = [ 5 | path('', HeroListView.as_view()), 6 | ] 7 | -------------------------------------------------------------------------------- /backend/hero/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework.generics import ListAPIView 2 | from rest_framework import permissions 3 | 4 | from .models import Hero 5 | from .serializers import HeroSerializer 6 | 7 | 8 | class HeroListView(ListAPIView): 9 | permission_classes = (permissions.AllowAny, ) 10 | queryset = Hero.objects.all() 11 | serializer_class = HeroSerializer 12 | pagination_class = None 13 | -------------------------------------------------------------------------------- /backend/manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """Django's command-line utility for administrative tasks.""" 3 | import os 4 | import sys 5 | 6 | 7 | def main(): 8 | """Run administrative tasks.""" 9 | os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings') 10 | try: 11 | from django.core.management import execute_from_command_line 12 | except ImportError as exc: 13 | raise ImportError( 14 | "Couldn't import Django. Are you sure it's installed and " 15 | "available on your PYTHONPATH environment variable? Did you " 16 | "forget to activate a virtual environment?" 17 | ) from exc 18 | execute_from_command_line(sys.argv) 19 | 20 | 21 | if __name__ == '__main__': 22 | main() 23 | -------------------------------------------------------------------------------- /backend/pricing/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/enterprise-website-template/4a45533e177bd48612e7e848c83f187eda1b8219/backend/pricing/__init__.py -------------------------------------------------------------------------------- /backend/pricing/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Pricing, Feature 3 | 4 | 5 | class ProjectAdmin(admin.ModelAdmin): 6 | list_display = ('id', 'title') 7 | list_display_links = ('id', 'title') 8 | search_fields = ('title', 'features') 9 | list_per_page = 20 10 | 11 | 12 | admin.site.register(Pricing) 13 | admin.site.register(Feature) 14 | -------------------------------------------------------------------------------- /backend/pricing/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class PricingConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'pricing' 7 | -------------------------------------------------------------------------------- /backend/pricing/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1 on 2022-08-29 00:16 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='Feature', 16 | fields=[ 17 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('name', models.CharField(max_length=30)), 19 | ], 20 | ), 21 | migrations.CreateModel( 22 | name='Pricing', 23 | fields=[ 24 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 25 | ('title', models.CharField(max_length=50)), 26 | ('price', models.FloatField()), 27 | ('currency', models.CharField(max_length=5)), 28 | ('features', models.ManyToManyField(to='pricing.feature')), 29 | ], 30 | options={ 31 | 'verbose_name': 'Pricing', 32 | 'verbose_name_plural': 'Pricing', 33 | }, 34 | ), 35 | ] 36 | -------------------------------------------------------------------------------- /backend/pricing/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/enterprise-website-template/4a45533e177bd48612e7e848c83f187eda1b8219/backend/pricing/migrations/__init__.py -------------------------------------------------------------------------------- /backend/pricing/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class Feature(models.Model): 5 | name = models.CharField(max_length=30) 6 | 7 | def __str__(self): 8 | return self.name 9 | 10 | 11 | class Pricing(models.Model): 12 | title = models.CharField(max_length=50) 13 | price = models.FloatField() 14 | currency = models.CharField(max_length=5) 15 | features = models.ManyToManyField(Feature) 16 | 17 | class Meta: 18 | verbose_name = 'Pricing' 19 | verbose_name_plural = 'Pricing' 20 | 21 | def __str__(self): 22 | return self.title 23 | -------------------------------------------------------------------------------- /backend/pricing/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from .models import Pricing, Feature 3 | 4 | 5 | class FeatureSerializer(serializers.ModelSerializer): 6 | class Meta: 7 | model = Feature 8 | fields = ('name',) 9 | 10 | 11 | class PricingSerializer(serializers.ModelSerializer): 12 | features = FeatureSerializer(many=True) 13 | 14 | class Meta: 15 | model = Pricing 16 | fields = ('title', 'price', 'currency', 'features') 17 | -------------------------------------------------------------------------------- /backend/pricing/tests.py: -------------------------------------------------------------------------------- 1 | # from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /backend/pricing/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from .views import PricingListView 3 | 4 | urlpatterns = [ 5 | path('', PricingListView.as_view()), 6 | ] 7 | -------------------------------------------------------------------------------- /backend/pricing/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework.generics import ListAPIView 2 | from rest_framework import permissions 3 | 4 | from .models import Pricing 5 | from .serializers import PricingSerializer 6 | 7 | 8 | class PricingListView(ListAPIView): 9 | permission_classes = (permissions.AllowAny, ) 10 | queryset = Pricing.objects.all() 11 | serializer_class = PricingSerializer 12 | pagination_class = None 13 | -------------------------------------------------------------------------------- /backend/products/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/enterprise-website-template/4a45533e177bd48612e7e848c83f187eda1b8219/backend/products/__init__.py -------------------------------------------------------------------------------- /backend/products/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Product 3 | 4 | 5 | class ProductAdmin(admin.ModelAdmin): 6 | list_display = ('id', 'name') 7 | list_display_links = ('id', 'name') 8 | search_fields = ('name', 'description') 9 | list_per_page = 20 10 | 11 | 12 | admin.site.register(Product, ProductAdmin) 13 | -------------------------------------------------------------------------------- /backend/products/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ProductsConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'products' 7 | -------------------------------------------------------------------------------- /backend/products/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1 on 2022-08-29 00:16 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='Product', 16 | fields=[ 17 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('name', models.CharField(max_length=100)), 19 | ('description', models.CharField(max_length=200)), 20 | ('image', models.ImageField(upload_to='images')), 21 | ], 22 | ), 23 | ] 24 | -------------------------------------------------------------------------------- /backend/products/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/enterprise-website-template/4a45533e177bd48612e7e848c83f187eda1b8219/backend/products/migrations/__init__.py -------------------------------------------------------------------------------- /backend/products/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from django.core.files.base import ContentFile 3 | from io import BytesIO 4 | from PIL import Image 5 | 6 | 7 | class Product(models.Model): 8 | name = models.CharField(max_length=100) 9 | description = models.CharField(max_length=200) 10 | image = models.ImageField(upload_to='images') 11 | 12 | # override the save method and 13 | # use the Image class of the PIL package 14 | # to convert it to JPEG 15 | def save(self, *args, **kwargs): 16 | if self.image: 17 | filename = "%s.jpg" % self.image.name.split('.')[0] 18 | 19 | image = Image.open(self.image) 20 | # for PNG images discard the alpha channel and fill it with some color 21 | if image.mode in ('RGBA', 'LA'): 22 | background = Image.new(image.mode[:-1], image.size, '#fff') 23 | background.paste(image, image.split()[-1]) 24 | image = background 25 | image_io = BytesIO() 26 | image.save(image_io, format='JPEG', quality=100) 27 | 28 | # change the image field value to be the newly modified image value 29 | self.image.save(filename, ContentFile(image_io.getvalue()), save=False) 30 | super(Product, self).save(*args, **kwargs) 31 | 32 | def __str__(self): 33 | return self.name 34 | -------------------------------------------------------------------------------- /backend/products/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from .models import Product 3 | 4 | 5 | class ProductSerializer(serializers.ModelSerializer): 6 | class Meta: 7 | model = Product 8 | fields = '__all__' 9 | -------------------------------------------------------------------------------- /backend/products/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from .models import Product 3 | 4 | 5 | class ProductModelUnitTestCase(TestCase): 6 | def setUp(self): 7 | self.product = Product.objects.create( 8 | name='Lorem ipsum dolor sit amet', 9 | description='Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed tincidunt quis odio id.' 10 | ) 11 | 12 | def test_product_model(self): 13 | data = self.product 14 | self.assertIsInstance(data, Product) 15 | -------------------------------------------------------------------------------- /backend/products/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from .views import ProductListView 3 | 4 | urlpatterns = [ 5 | path('', ProductListView.as_view()), 6 | ] 7 | -------------------------------------------------------------------------------- /backend/products/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework.generics import ListAPIView 2 | from rest_framework import permissions 3 | 4 | from .models import Product 5 | from .serializers import ProductSerializer 6 | 7 | 8 | class ProductListView(ListAPIView): 9 | permission_classes = (permissions.AllowAny, ) 10 | queryset = Product.objects.all() 11 | serializer_class = ProductSerializer 12 | pagination_class = None 13 | -------------------------------------------------------------------------------- /backend/requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/enterprise-website-template/4a45533e177bd48612e7e848c83f187eda1b8219/backend/requirements.txt -------------------------------------------------------------------------------- /backend/services/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/enterprise-website-template/4a45533e177bd48612e7e848c83f187eda1b8219/backend/services/__init__.py -------------------------------------------------------------------------------- /backend/services/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Service 3 | 4 | admin.site.register(Service) 5 | -------------------------------------------------------------------------------- /backend/services/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ServicesConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'services' 7 | -------------------------------------------------------------------------------- /backend/services/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.1 on 2022-08-29 00:16 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='Service', 16 | fields=[ 17 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('title', models.CharField(max_length=50)), 19 | ('description', models.CharField(max_length=250)), 20 | ('icon', models.CharField(max_length=30)), 21 | ], 22 | options={ 23 | 'verbose_name': 'Service', 24 | 'verbose_name_plural': 'Services', 25 | }, 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /backend/services/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/enterprise-website-template/4a45533e177bd48612e7e848c83f187eda1b8219/backend/services/migrations/__init__.py -------------------------------------------------------------------------------- /backend/services/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class Service(models.Model): 5 | title = models.CharField(max_length=50) 6 | description = models.CharField(max_length=250) 7 | icon = models.CharField(max_length=30) # Material Icon name 8 | 9 | class Meta: 10 | verbose_name = 'Service' 11 | verbose_name_plural = 'Services' 12 | 13 | def __str__(self): 14 | return self.title 15 | -------------------------------------------------------------------------------- /backend/services/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from .models import Service 3 | 4 | 5 | class ServiceSerializer(serializers.ModelSerializer): 6 | class Meta: 7 | model = Service 8 | fields = '__all__' 9 | -------------------------------------------------------------------------------- /backend/services/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from .models import Service 3 | 4 | 5 | class ServiceModelUnitTestCase(TestCase): 6 | def setUp(self): 7 | self.service = Service.objects.create( 8 | title='Service 1', 9 | description='Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed tincidunt quis odio id.', 10 | icon='settings' 11 | ) 12 | 13 | def test_service_model(self): 14 | data = self.service 15 | self.assertIsInstance(data, Service) 16 | -------------------------------------------------------------------------------- /backend/services/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from .views import ServiceListView 3 | 4 | urlpatterns = [ 5 | path('', ServiceListView.as_view()), 6 | ] 7 | -------------------------------------------------------------------------------- /backend/services/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework.generics import ListAPIView 2 | from rest_framework import permissions 3 | 4 | from .models import Service 5 | from .serializers import ServiceSerializer 6 | 7 | 8 | class ServiceListView(ListAPIView): 9 | permission_classes = (permissions.AllowAny, ) 10 | queryset = Service.objects.all() 11 | serializer_class = ServiceSerializer 12 | pagination_class = None 13 | -------------------------------------------------------------------------------- /backend/setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | exclude = .git,*migrations*,*venv* 3 | max-line-length = 119 4 | indent-size = 2 -------------------------------------------------------------------------------- /frontend/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "root": true, 7 | "extends": ["react-app", "react-app/jest", "prettier"], 8 | "globals": { 9 | "__dirname": true 10 | }, 11 | "parserOptions": { 12 | "ecmaVersion": "latest", 13 | "sourceType": "module", 14 | "project": "*/tsconfig.json" 15 | }, 16 | "parser": "@typescript-eslint/parser", 17 | "plugins": ["react", "@typescript-eslint"], 18 | "settings": { 19 | "react": { 20 | "version": "detect" 21 | } 22 | }, 23 | "rules": { 24 | "@typescript-eslint/explicit-function-return-type": "off", 25 | "@typescript-eslint/no-explicit-any": "off", 26 | "@typescript-eslint/ban-ts-comment": "off", 27 | "@typescript-eslint/triple-slash-reference": "off", 28 | "@typescript-eslint/no-floating-promises": "off", 29 | "@typescript-eslint/consistent-type-definitions": "off", 30 | "@typescript-eslint/strict-boolean-expressions": "off", 31 | "@typescript-eslint/no-misused-promises": "off", 32 | "@typescript-eslint/no-error-on-unmatched-pattern": "off", 33 | "@typescript-eslint/restrict-template-expressions": "off", 34 | "@typescript-eslint/member-delimiter-style": [ 35 | "warn", 36 | { 37 | "multiline": { 38 | "delimiter": "semi", 39 | "requireLast": true 40 | }, 41 | "singleline": { 42 | "delimiter": "semi", 43 | "requireLast": false 44 | } 45 | } 46 | ] 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /frontend/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "arrowParens": "always", 3 | "bracketSpacing": true, 4 | "endOfLine": "lf", 5 | "jsxSingleQuote": true, 6 | "printWidth": 80, 7 | "proseWrap": "never", 8 | "quoteProps": "as-needed", 9 | "semi": true, 10 | "singleQuote": true, 11 | "tabWidth": 2, 12 | "trailingComma": "es5" 13 | } 14 | -------------------------------------------------------------------------------- /frontend/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: ['/src'], 3 | moduleDirectories: ['node_modules', './src'], 4 | preset: 'ts-jest', 5 | testEnvironment: 'jest-environment-jsdom', 6 | testEnvironmentOptions: { 7 | url: 'http://127.0.0.1:8000/', 8 | }, 9 | setupFilesAfterEnv: ['/jest.setup.js'], 10 | moduleNameMapper: { 11 | // Handle image imports 12 | '\\.(jpg|ico|jpeg|png|gif|eot|otf|webp|svg|ttf|woff|woff2|mp4|webm|wav|mp3|m4a|aac|oga)$': 13 | '/src/tests/mocks/fileMock.ts', 14 | 15 | // Handle CSS imports (without CSS modules) 16 | '\\.(css|less)$': '/src/tests/mocks/fileMock.css', 17 | 18 | // Handle react-leaflet imports 19 | 'react-leaflet': '/src/tests/mocks/fileMock.ts', 20 | }, 21 | transform: { 22 | '^.+\\.(ts|tsx|js|jsx)$': ['ts-jest', { isolatedModules: true }], 23 | }, 24 | }; 25 | -------------------------------------------------------------------------------- /frontend/jest.setup.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /frontend/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "enterprise-website", 3 | "version": "0.1.0", 4 | "description": "Enterprise Website", 5 | "author": "MG", 6 | "license": "MIT", 7 | "private": true, 8 | "scripts": { 9 | "start": "react-scripts start", 10 | "build": "react-scripts build", 11 | "eject": "react-scripts eject", 12 | "test": "jest --runInBand", 13 | "test:coverage": "jest --runInBand --coverage", 14 | "clear_jest": "jest --clearCache", 15 | "lint": "eslint --ext .ts,.tsx ./src", 16 | "lint:fix": "eslint --ext .ts,.tsx ./src", 17 | "prettier": "prettier \"**/*.+(json|ts|tsx)\"", 18 | "format:check": "npm run prettier -- --check", 19 | "format:fix": "npm run prettier -- --write", 20 | "types:check": "bash -c tsc --noEmit --pretty" 21 | }, 22 | "lint-staged": { 23 | "**/*.{js,jsx,ts,tsx}": [ 24 | "npm run lint", 25 | "npm run types:check", 26 | "npm run format:check" 27 | ] 28 | }, 29 | "eslintConfig": { 30 | "extends": [ 31 | "react-app", 32 | "react-app/jest" 33 | ] 34 | }, 35 | "dependencies": { 36 | "@emotion/react": "^11.11.1", 37 | "@emotion/styled": "^11.11.0", 38 | "@mui/icons-material": "^5.14.6", 39 | "@mui/material": "^5.14.6", 40 | "axios": "^1.4.0", 41 | "leaflet": "^1.9.4", 42 | "react": "^18.2.0", 43 | "react-countup": "^6.4.2", 44 | "react-dom": "^18.2.0", 45 | "react-helmet-async": "^1.3.0", 46 | "react-leaflet": "^4.2.1", 47 | "react-router-dom": "^6.15.0", 48 | "react-scripts": "5.0.1", 49 | "react-visibility-sensor": "^5.1.1", 50 | "web-vitals": "^3.4.0" 51 | }, 52 | "devDependencies": { 53 | "@babel/plugin-proposal-private-property-in-object": "^7.21.11", 54 | "@testing-library/jest-dom": "^6.1.5", 55 | "@testing-library/react": "^14.1.2", 56 | "@testing-library/user-event": "^14.5.1", 57 | "@types/jest": "^29.5.11", 58 | "@types/leaflet": "^1.9.3", 59 | "@types/node": "^20.10.4", 60 | "@types/react": "^18.2.43", 61 | "@typescript-eslint/parser": "^5.62.0", 62 | "eslint": "^8.56.0", 63 | "eslint-config-prettier": "^9.1.0", 64 | "eslint-plugin-import": "^2.29.1", 65 | "eslint-plugin-prettier": "^5.1.2", 66 | "eslint-plugin-promise": "^6.1.1", 67 | "eslint-plugin-react": "^7.33.2", 68 | "husky": "^8.0.3", 69 | "jest": "^29.7.0", 70 | "jest-canvas-mock": "^2.5.2", 71 | "jest-environment-jsdom": "^29.7.0", 72 | "lint-staged": "^15.2.0", 73 | "prettier": "^3.1.1", 74 | "ts-jest": "^29.1.1", 75 | "typescript": "^4.7.4" 76 | }, 77 | "browserslist": { 78 | "production": [ 79 | ">0.2%", 80 | "not dead", 81 | "not op_mini all" 82 | ], 83 | "development": [ 84 | "last 1 chrome version", 85 | "last 1 firefox version", 86 | "last 1 safari version" 87 | ] 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/enterprise-website-template/4a45533e177bd48612e7e848c83f187eda1b8219/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | Bob's Programming Academy 10 | 11 | 12 |
13 | 14 | 15 | -------------------------------------------------------------------------------- /frontend/src/App.tsx: -------------------------------------------------------------------------------- 1 | import { BrowserRouter, Routes, Route } from 'react-router-dom'; 2 | import { HelmetProvider, Helmet } from 'react-helmet-async'; 3 | import { ThemeProvider } from '@mui/material/styles'; 4 | import CssBaseline from '@mui/material/CssBaseline'; 5 | 6 | import getTheme from './theme/theme'; 7 | import Layout from './layout/Layout'; 8 | import Home from './pages/Home'; 9 | 10 | const App = (): JSX.Element => { 11 | return ( 12 | 13 | 17 | 18 | 19 | 20 | 21 | 22 | } /> 23 | 24 | 25 | 26 | 27 | 28 | ); 29 | }; 30 | 31 | export default App; 32 | -------------------------------------------------------------------------------- /frontend/src/assets/images/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/enterprise-website-template/4a45533e177bd48612e7e848c83f187eda1b8219/frontend/src/assets/images/1.png -------------------------------------------------------------------------------- /frontend/src/assets/images/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/enterprise-website-template/4a45533e177bd48612e7e848c83f187eda1b8219/frontend/src/assets/images/2.png -------------------------------------------------------------------------------- /frontend/src/assets/images/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/enterprise-website-template/4a45533e177bd48612e7e848c83f187eda1b8219/frontend/src/assets/images/3.png -------------------------------------------------------------------------------- /frontend/src/assets/images/chart1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/enterprise-website-template/4a45533e177bd48612e7e848c83f187eda1b8219/frontend/src/assets/images/chart1.jpg -------------------------------------------------------------------------------- /frontend/src/assets/images/chart2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/enterprise-website-template/4a45533e177bd48612e7e848c83f187eda1b8219/frontend/src/assets/images/chart2.jpg -------------------------------------------------------------------------------- /frontend/src/assets/images/chart3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/enterprise-website-template/4a45533e177bd48612e7e848c83f187eda1b8219/frontend/src/assets/images/chart3.jpg -------------------------------------------------------------------------------- /frontend/src/assets/images/chart4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/enterprise-website-template/4a45533e177bd48612e7e848c83f187eda1b8219/frontend/src/assets/images/chart4.jpg -------------------------------------------------------------------------------- /frontend/src/assets/logo/react.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/enterprise-website-template/4a45533e177bd48612e7e848c83f187eda1b8219/frontend/src/assets/logo/react.png -------------------------------------------------------------------------------- /frontend/src/components/About.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import axios from 'axios'; 3 | import CountUp from 'react-countup'; 4 | import VisibilitySensor from 'react-visibility-sensor'; 5 | 6 | // Material UI 7 | import Box from '@mui/material/Box'; 8 | import Card from '@mui/material/Card'; 9 | import CardContent from '@mui/material/CardContent'; 10 | import Container from '@mui/material/Container'; 11 | import Grid from '@mui/material/Grid'; 12 | import Typography from '@mui/material/Typography'; 13 | import { useTheme } from '@mui/material/styles'; 14 | 15 | interface AboutProps { 16 | value: number; 17 | suffix: string; 18 | description: string; 19 | } 20 | 21 | const About = (): JSX.Element => { 22 | const theme = useTheme(); 23 | const [viewPortEntered, setViewPortEntered] = useState(false); 24 | const setViewPortVisibility = ( 25 | isVisible: boolean | ((prevState: boolean) => boolean) 26 | ) => { 27 | if (viewPortEntered) { 28 | return; 29 | } 30 | setViewPortEntered(isVisible); 31 | }; 32 | 33 | const [about, setAbout] = useState([]); 34 | 35 | const fetchAbout = () => { 36 | axios 37 | .get('http://127.0.0.1:8000/about', { 38 | headers: { 39 | Accept: 'application/json', 40 | }, 41 | }) 42 | .then((response) => { 43 | setAbout(response.data); 44 | }) 45 | .catch((error) => console.log(error)); 46 | }; 47 | 48 | useEffect(() => { 49 | fetchAbout(); 50 | }, []); 51 | 52 | return ( 53 |
54 | 62 | 63 | 75 | About 76 | 77 | 84 | We help software developers learn new skills, gain more experience 85 | and create excellent applications 86 | 87 | 88 | 89 | 90 | {about.map((item, i) => ( 91 | 92 | 99 | 104 | 110 | 111 | 112 | boolean) 117 | ) => setViewPortVisibility(isVisible)} 118 | delayedCall 119 | > 120 | 126 | 127 | 128 | 129 | 133 | {item.description} 134 | 135 | 136 | 137 | 138 | 139 | ))} 140 | 141 | 142 | 143 |
144 | ); 145 | }; 146 | 147 | export default About; 148 | -------------------------------------------------------------------------------- /frontend/src/components/Contact.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import Box from '@mui/material/Box'; 3 | import axios from 'axios'; 4 | 5 | // Material UI 6 | import Container from '@mui/material/Container'; 7 | import Grid from '@mui/material/Grid'; 8 | import ListItem from '@mui/material/ListItem'; 9 | import ListItemText from '@mui/material/ListItemText'; 10 | import Typography from '@mui/material/Typography'; 11 | import { useTheme } from '@mui/material/styles'; 12 | 13 | // Material Icons 14 | import EmailIcon from '@mui/icons-material/Email'; 15 | import LocationIcon from '@mui/icons-material/LocationOn'; 16 | import PhoneIcon from '@mui/icons-material/Phone'; 17 | 18 | import Map from './Map'; 19 | 20 | interface ContactProps { 21 | address: string; 22 | email: string; 23 | phone: string; 24 | latitude: number; 25 | longitude: number; 26 | } 27 | 28 | const Contact = (): JSX.Element => { 29 | const theme = useTheme(); 30 | 31 | const [contact, setContact] = useState([]); 32 | 33 | const fetchContact = () => { 34 | axios 35 | .get('http://127.0.0.1:8000/contact', { 36 | headers: { 37 | Accept: 'application/json', 38 | }, 39 | }) 40 | .then((response) => { 41 | setContact(response.data); 42 | }) 43 | .catch((error) => console.log(error)); 44 | }; 45 | 46 | useEffect(() => { 47 | fetchContact(); 48 | }, []); 49 | 50 | return ( 51 |
52 | 60 | 61 | 72 | Contact 73 | 74 | 81 | We would love to hear from you 82 | 83 | 84 | {contact.slice(0, 1).map((item, i) => ( 85 | 86 | 87 | 88 | 89 | 93 | 94 | 95 | 96 | 103 | 111 | 119 | 120 | 121 | 129 | 137 | 138 | 139 | 147 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | ))} 162 | 163 |
164 | ); 165 | }; 166 | 167 | export default Contact; 168 | -------------------------------------------------------------------------------- /frontend/src/components/CustomButton.tsx: -------------------------------------------------------------------------------- 1 | // Material UI 2 | import Button from '@mui/material/Button'; 3 | import { useTheme } from '@mui/material/styles'; 4 | 5 | interface Props { 6 | href: string; 7 | text: string; 8 | } 9 | 10 | const CustomButton = ({ href, text }: Props): JSX.Element => { 11 | const theme = useTheme(); 12 | 13 | return ( 14 | 34 | ); 35 | }; 36 | 37 | export default CustomButton; 38 | -------------------------------------------------------------------------------- /frontend/src/components/Hero.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import axios from 'axios'; 3 | 4 | // Material UI 5 | import Box from '@mui/material/Box'; 6 | import Typography from '@mui/material/Typography'; 7 | import Container from '@mui/material/Container'; 8 | import { useTheme } from '@mui/material/styles'; 9 | 10 | import HeroButtons from './HeroButtons'; 11 | import Spacer from './Spacer'; 12 | 13 | interface HeroProps { 14 | title: number; 15 | description: string; 16 | } 17 | 18 | const Hero = (): JSX.Element => { 19 | const theme = useTheme(); 20 | 21 | const [hero, setHero] = useState([]); 22 | 23 | const fetchHero = () => { 24 | axios 25 | .get('http://127.0.0.1:8000/hero', { 26 | headers: { 27 | Accept: 'application/json', 28 | }, 29 | }) 30 | .then((response) => { 31 | setHero(response.data); 32 | }) 33 | .catch((error) => console.log(error)); 34 | }; 35 | 36 | useEffect(() => { 37 | fetchHero(); 38 | }, []); 39 | 40 | return ( 41 |
42 | 49 | {hero.slice(0, 1).map((item, i) => ( 50 | 59 | 60 | 67 | {item.title} 68 | 69 | 70 | 71 | 77 | {item.description} 78 | 79 | 80 | 81 | 82 | ))} 83 | 84 | 85 |
86 | ); 87 | }; 88 | 89 | export default Hero; 90 | -------------------------------------------------------------------------------- /frontend/src/components/HeroButtons.tsx: -------------------------------------------------------------------------------- 1 | // Material UI 2 | import Box from '@mui/material/Box'; 3 | import Button from '@mui/material/Button'; 4 | import useMediaQuery from '@mui/material/useMediaQuery'; 5 | import { useTheme } from '@mui/material/styles'; 6 | 7 | // Material Icons 8 | import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; 9 | import PlayIcon from '@mui/icons-material/PlayCircleOutline'; 10 | 11 | const HeroButtons = (): JSX.Element => { 12 | const theme = useTheme(); 13 | const isMd = useMediaQuery(theme.breakpoints.up('md'), { 14 | defaultMatches: true, 15 | }); 16 | 17 | return ( 18 | <> 19 | 26 | 49 | 54 | 78 | 79 | 80 | 81 | ); 82 | }; 83 | 84 | export default HeroButtons; 85 | -------------------------------------------------------------------------------- /frontend/src/components/Map.tsx: -------------------------------------------------------------------------------- 1 | import { useTheme } from '@mui/material/styles'; 2 | import { LatLngExpression } from 'leaflet'; 3 | import { MapContainer, Circle, TileLayer } from 'react-leaflet'; 4 | import 'leaflet/dist/leaflet.css'; 5 | 6 | interface MapProps { 7 | coordinates: [number, number]; 8 | zoom: number; 9 | } 10 | 11 | const Map = ({ coordinates, zoom }: MapProps): JSX.Element => { 12 | const theme = useTheme(); 13 | const position: LatLngExpression = coordinates; 14 | const fillBlueOptions = { fillColor: theme.palette.primary.main }; 15 | 16 | return ( 17 | 23 | 27 | 28 | 29 | ); 30 | }; 31 | 32 | export default Map; 33 | -------------------------------------------------------------------------------- /frontend/src/components/Pricing.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import axios from 'axios'; 3 | 4 | // Material UI 5 | import Box from '@mui/material/Box'; 6 | import Button from '@mui/material/Button'; 7 | import Card from '@mui/material/Card'; 8 | import CardContent from '@mui/material/CardContent'; 9 | import Container from '@mui/material/Container'; 10 | import Grid from '@mui/material/Grid'; 11 | import CardActions from '@mui/material/CardActions'; 12 | import Typography from '@mui/material/Typography'; 13 | import { useTheme } from '@mui/material/styles'; 14 | 15 | // Material Icons 16 | import ArrowForwardIcon from '@mui/icons-material/ArrowForward'; 17 | 18 | interface PricingProps { 19 | title: string; 20 | price: number; 21 | currency: string; 22 | features: { name: string }[]; 23 | } 24 | 25 | const Pricing = (): JSX.Element => { 26 | const theme = useTheme(); 27 | 28 | const [pricing, setPricing] = useState([]); 29 | 30 | const fetchPricing = () => { 31 | axios 32 | .get('http://127.0.0.1:8000/pricing', { 33 | headers: { 34 | Accept: 'application/json', 35 | }, 36 | }) 37 | .then((response) => { 38 | setPricing(response.data); 39 | }) 40 | .catch((error) => console.log(error)); 41 | }; 42 | 43 | useEffect(() => { 44 | fetchPricing(); 45 | }, []); 46 | 47 | return ( 48 |
49 | 57 | 58 | 69 | Pricing 70 | 71 | 78 | We offer a range of pricing plans to suit everyone 79 | 80 | 81 | 82 | 83 | {pricing.map((item, i) => ( 84 | 85 | 93 | 98 | 104 | 105 | 106 | {item.title} 107 | 108 | 109 | 110 | 111 | 116 | {item.currency} 117 | 118 | 119 | 120 | 121 | {item.price} 122 | 123 | 124 | 125 | 129 | Per user, per month 130 | 131 | 132 | 133 | {item.features?.map((feature, j) => ( 134 | 135 | 142 | {feature.name} 143 | 144 | 145 | ))} 146 | 147 | 148 | 149 | 150 | 167 | 168 | 169 | 170 | ))} 171 | 172 | 173 | 174 |
175 | ); 176 | }; 177 | 178 | export default Pricing; 179 | -------------------------------------------------------------------------------- /frontend/src/components/Products.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import axios from 'axios'; 3 | 4 | // Material UI 5 | import Box from '@mui/material/Box'; 6 | import Card from '@mui/material/Card'; 7 | import CardMedia from '@mui/material/CardMedia'; 8 | import Container from '@mui/material/Container'; 9 | import Grid from '@mui/material/Grid'; 10 | import Typography from '@mui/material/Typography'; 11 | import { useTheme } from '@mui/material/styles'; 12 | 13 | interface ProductsProps { 14 | name: number; 15 | description: string; 16 | image: string; 17 | } 18 | 19 | const Products = (): JSX.Element => { 20 | const theme = useTheme(); 21 | 22 | const [products, setProducts] = useState([]); 23 | 24 | const fetchProducts = () => { 25 | axios 26 | .get('http://127.0.0.1:8000/products', { 27 | headers: { 28 | Accept: 'application/json', 29 | }, 30 | }) 31 | .then((response) => { 32 | setProducts(response.data); 33 | }) 34 | .catch((error) => console.log(error)); 35 | }; 36 | 37 | useEffect(() => { 38 | fetchProducts(); 39 | }, []); 40 | 41 | return ( 42 |
43 | 51 | 52 | 63 | Products 64 | 65 | 72 | We offer a range of products to support your business 73 | 74 | 75 | 76 | 77 | {products.map((item, i) => ( 78 | 79 | 92 | 93 | 100 | {item.name} 101 | 102 | {item.description} 103 | 104 | 105 | 116 | 128 | 129 | 130 | 131 | 132 | ))} 133 | 134 | 135 | 136 |
137 | ); 138 | }; 139 | 140 | export default Products; 141 | -------------------------------------------------------------------------------- /frontend/src/components/Services.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import axios from 'axios'; 3 | 4 | // Material UI 5 | import Avatar from '@mui/material/Avatar'; 6 | import Box from '@mui/material/Box'; 7 | import Card from '@mui/material/Card'; 8 | import Container from '@mui/material/Container'; 9 | import Grid from '@mui/material/Grid'; 10 | import Icon from '@mui/material/Icon'; 11 | import Typography from '@mui/material/Typography'; 12 | import { useTheme } from '@mui/material/styles'; 13 | 14 | interface ServicesProps { 15 | title: string; 16 | description: string; 17 | icon: string; 18 | } 19 | 20 | const Services = (): JSX.Element => { 21 | const theme = useTheme(); 22 | 23 | const [services, setServices] = useState([]); 24 | 25 | const fetchServices = () => { 26 | axios 27 | .get('http://127.0.0.1:8000/services', { 28 | headers: { 29 | Accept: 'application/json', 30 | }, 31 | }) 32 | .then((response) => { 33 | setServices(response.data); 34 | }) 35 | .catch((error) => console.log(error)); 36 | }; 37 | 38 | useEffect(() => { 39 | fetchServices(); 40 | }, []); 41 | 42 | return ( 43 |
44 | 52 | 53 | 64 | Services 65 | 66 | 73 | We offer a range of services to support your business 74 | 75 | 76 | 77 | 78 | {services.map((item, i) => ( 79 | 80 | 81 | 82 | 90 | 91 | {item.icon} 92 | 93 | 94 | 99 | {item.title} 100 | 101 | 102 | {item.description} 103 | 104 | 105 | 106 | 107 | ))} 108 | 109 | 110 | 111 |
112 | ); 113 | }; 114 | 115 | export default Services; 116 | -------------------------------------------------------------------------------- /frontend/src/components/Spacer.tsx: -------------------------------------------------------------------------------- 1 | // Material UI 2 | import Box from '@mui/material/Box'; 3 | import { useTheme } from '@mui/material'; 4 | 5 | interface Props { 6 | [x: string]: any; 7 | } 8 | 9 | const Spacer = ({ sx = [] }: Props): JSX.Element => { 10 | const theme = useTheme(); 11 | 12 | return ( 13 | 21 | ); 22 | }; 23 | 24 | export default Spacer; 25 | -------------------------------------------------------------------------------- /frontend/src/index.tsx: -------------------------------------------------------------------------------- 1 | import ReactDOM from 'react-dom/client'; 2 | 3 | import App from './App'; 4 | import reportWebVitals from './reportWebVitals'; 5 | 6 | const root = ReactDOM.createRoot( 7 | document.getElementById('root') as HTMLElement 8 | ); 9 | root.render(); 10 | 11 | // If you want to start measuring performance in your app, pass a function 12 | // to log results (for example: reportWebVitals(console.log)) 13 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 14 | reportWebVitals(); 15 | -------------------------------------------------------------------------------- /frontend/src/layout/Footer.tsx: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import axios from 'axios'; 3 | 4 | // Material UI 5 | import Box from '@mui/material/Box'; 6 | import Grid from '@mui/material/Grid'; 7 | import Typography from '@mui/material/Typography'; 8 | import { useTheme } from '@mui/material/styles'; 9 | 10 | interface FooterProps { 11 | copyright: string; 12 | } 13 | 14 | const Footer = (): JSX.Element => { 15 | const theme = useTheme(); 16 | 17 | const [footer, setFooter] = useState([]); 18 | 19 | const fetchFooter = () => { 20 | axios 21 | .get('http://127.0.0.1:8000/footer', { 22 | headers: { 23 | Accept: 'application/json', 24 | }, 25 | }) 26 | .then((response) => { 27 | setFooter(response.data); 28 | }) 29 | .catch((error) => console.log(error)); 30 | }; 31 | 32 | useEffect(() => { 33 | fetchFooter(); 34 | }, []); 35 | 36 | return ( 37 | 38 | {footer.slice(0, 1).map((item, i) => ( 39 | 40 | 41 | 48 | Copyright © {new Date().getFullYear()} {item.copyright}. 49 | 50 | 51 | 52 | ))} 53 | 54 | ); 55 | }; 56 | 57 | export default Footer; 58 | -------------------------------------------------------------------------------- /frontend/src/layout/Header.tsx: -------------------------------------------------------------------------------- 1 | // Material UI 2 | import AppBar from '@mui/material/AppBar'; 3 | import Button from '@mui/material/Button'; 4 | import Box from '@mui/material/Box'; 5 | import IconButton from '@mui/material/IconButton'; 6 | import Link from '@mui/material/Link'; 7 | import Toolbar from '@mui/material/Toolbar'; 8 | import Typography from '@mui/material/Typography'; 9 | import { alpha, useTheme } from '@mui/material/styles'; 10 | 11 | // Material Icons 12 | import StormIcon from '@mui/icons-material/Storm'; 13 | import MenuIcon from '@mui/icons-material/Menu'; 14 | 15 | import CustomButton from '../components/CustomButton'; 16 | 17 | interface HeaderProps { 18 | onSidebarOpen: () => void; 19 | } 20 | 21 | const Header = ({ onSidebarOpen }: HeaderProps): JSX.Element => { 22 | const theme = useTheme(); 23 | 24 | return ( 25 | <> 26 | 38 | 39 | 40 | 41 | 48 | 49 | 60 | Bob's Company 61 | 62 | 63 | 64 | 65 | 66 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 84 | 97 | 98 | 99 | 100 | 101 | ); 102 | }; 103 | 104 | export default Header; 105 | -------------------------------------------------------------------------------- /frontend/src/layout/Layout.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | 3 | // Material UI 4 | import Box from '@mui/material/Box'; 5 | import Fab from '@mui/material/Fab'; 6 | import NoSsr from '@mui/material/NoSsr'; 7 | import Zoom from '@mui/material/Zoom'; 8 | import useMediaQuery from '@mui/material/useMediaQuery'; 9 | import useScrollTrigger from '@mui/material/useScrollTrigger'; 10 | import { useTheme } from '@mui/material/styles'; 11 | 12 | // Material Icons 13 | import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp'; 14 | 15 | import Header from './Header'; 16 | import Footer from './Footer'; 17 | import Sidebar from './Sidebar'; 18 | 19 | interface LayoutProps { 20 | children: React.ReactNode; 21 | } 22 | 23 | const Layout = ({ children }: LayoutProps): JSX.Element => { 24 | const theme = useTheme(); 25 | const isLg = useMediaQuery(theme.breakpoints.up('lg'), { 26 | defaultMatches: true, 27 | }); 28 | 29 | const [openSidebar, setOpenSidebar] = useState(false); 30 | 31 | const handleSidebarOpen = (): void => { 32 | setOpenSidebar(true); 33 | }; 34 | 35 | const handleSidebarClose = (): void => { 36 | setOpenSidebar(false); 37 | }; 38 | 39 | const open = isLg ? false : openSidebar; 40 | 41 | const trigger = useScrollTrigger({ 42 | disableHysteresis: true, 43 | threshold: 100, 44 | }); 45 | 46 | const scrollTo = (id: string): void => { 47 | setTimeout(() => { 48 | const element = document.querySelector(`#${id}`) as HTMLElement; 49 | if (!element) { 50 | return; 51 | } 52 | window.scrollTo({ left: 0, top: element.offsetTop, behavior: 'smooth' }); 53 | }); 54 | }; 55 | 56 | return ( 57 | 64 |
65 | 66 | 67 | {children} 68 | 69 |