├── .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 ├── .gitignore ├── .prettierrc.json ├── jest.config.js ├── jest.setup.js ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── images │ ├── dark_theme.png │ └── light_theme.png └── 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 ├── tests │ ├── Home.test.tsx │ └── mocks │ │ ├── fileMock.css │ │ └── fileMock.ts ├── theme │ ├── palette.ts │ └── theme.ts └── utils │ └── ColorModeContext.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 -------------------------------------------------------------------------------- /.github/workflows/django.yml: -------------------------------------------------------------------------------- 1 | name: Django CI 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | 7 | pull_request: 8 | branches: [master] 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | strategy: 14 | max-parallel: 4 15 | matrix: 16 | python-version: [3.8, 3.9, '3.10'] 17 | 18 | steps: 19 | - name: Checkout source Git branch 20 | uses: actions/checkout@v4 21 | 22 | - name: Set up Python ${{ matrix.python-version }} 23 | uses: actions/setup-python@v4 24 | with: 25 | python-version: ${{ matrix.python-version }} 26 | 27 | - name: Install Dependencies 28 | run: | 29 | cd backend 30 | python -m pip install --upgrade pip 31 | pip install -r requirements.txt 32 | 33 | - name: Run migrations 34 | run: | 35 | cd backend 36 | python manage.py makemigrations 37 | python manage.py migrate 38 | 39 | - name: Lint with flake8 40 | run: | 41 | cd backend 42 | flake8 43 | 44 | - name: Run Tests 45 | run: | 46 | cd backend 47 | python manage.py test 48 | -------------------------------------------------------------------------------- /.github/workflows/react.yml: -------------------------------------------------------------------------------- 1 | name: React CI 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | 7 | pull_request: 8 | branches: [master] 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 | .vscode 5 | db.sqlite3 6 | media/ 7 | node_modules/ 8 | venv/ 9 | */migrations/* 10 | !*/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 | # Business Website Template 2 | 3 | This is a business website template for a company built using **React 18** (with **TypeScript 4**), **Material UI 5**, **Django 4**, and **Django REST Framework 3**. 4 | 5 | ### Dark mode: 6 | 7 | ![plot](https://github.com/BobsProgrammingAcademy/business-website-template/blob/master/frontend/public/images/dark_theme.png?raw=true) 8 | 9 | ### Light mode: 10 | 11 | ![plot](https://github.com/BobsProgrammingAcademy/business-website-template/blob/master/frontend/public/images/light_theme.png?raw=true) 12 | 13 | ## Table of Contents 14 | - [Prerequisites](#prerequisites) 15 | - [Installation](#installation) 16 | - [Running the application](#run-the-application) 17 | - [Running the tests](#run-the-tests) 18 | - [Adding data to the application](#add-data-to-the-application) 19 | - [Customizing the application](#customize-the-application) 20 | - [Copyright and License](#copyright-and-license) 21 | 22 | ## Prerequisites 23 | 24 | Install the following prerequisites: 25 | 26 | 1. [Python 3.8-3.11](https://www.python.org/downloads/) 27 |
This project uses **Django v4.2.4**. 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). 28 | 2. [Node.js](https://nodejs.org/en/) 29 | 3. [Visual Studio Code](https://code.visualstudio.com/download) 30 | 31 | ## Installation 32 | 33 | ### Backend 34 | 35 | #### 1. Create a virtual environment 36 | 37 | From the **root** directory, run: 38 | 39 | ```bash 40 | cd backend 41 | ``` 42 | ```bash 43 | python -m venv venv 44 | ``` 45 | 46 | #### 2. Activate the virtual environment 47 | 48 | From the **backend** directory, run: 49 | 50 | On macOS: 51 | 52 | ```bash 53 | source venv/bin/activate 54 | ``` 55 | 56 | On Windows: 57 | 58 | ```bash 59 | venv\scripts\activate 60 | ``` 61 | 62 | #### 3. Install required backend dependencies 63 | 64 | From the **backend** directory, run: 65 | 66 | ```bash 67 | pip install -r requirements.txt 68 | ``` 69 | 70 | #### 4. Run migrations 71 | 72 | From the **backend** directory, run: 73 | 74 | ```bash 75 | python manage.py makemigrations 76 | ``` 77 | ```bash 78 | python manage.py migrate 79 | ``` 80 | 81 | #### 5. Create an admin user to access the Django Admin interface 82 | 83 | From the **backend** directory, run: 84 | 85 | ```bash 86 | python manage.py createsuperuser 87 | ``` 88 | 89 | When prompted, enter a username, email, and password. 90 | 91 | ### Frontend 92 | 93 | #### 1. Install required frontend dependencies 94 | 95 | From the **root** directory, run: 96 | 97 | ```bash 98 | cd frontend 99 | ``` 100 | ```bash 101 | npm install 102 | ``` 103 | 104 | ## Run the application 105 | 106 | To run the application, you need to have both the backend and the frontend up and running. 107 | 108 | ### 1. Run backend 109 | 110 | From the **backend** directory, run: 111 | 112 | ```bash 113 | python manage.py runserver 114 | ``` 115 | 116 | ### 2. Run frontend 117 | 118 | From the **frontend** directory, run: 119 | 120 | ```bash 121 | npm start 122 | ``` 123 | 124 | ## View the application 125 | 126 | Go to http://localhost:3000/ to view the application. 127 | 128 | ## Run the tests 129 | 130 | ### 1. Run the backend tests 131 | 132 | From the **backend** directory, run: 133 | 134 | ```bash 135 | python manage.py test --pattern="tests.py" 136 | 137 | ``` 138 | 139 | ### 2. Run the frontend tests 140 | 141 | From the **frontend** directory, run: 142 | 143 | ```bash 144 | npm run test 145 | ``` 146 | 147 | ## Add data to the application 148 | 149 | Add data through Django Admin. 150 | 151 | Go to http://127.0.0.1:8000/admin to access the Django Admin interface and sign in using the admin credentials. 152 | 153 | ## Customize the application 154 | 155 | This section describes how to customize the application. 156 | 157 | ### Changing Section Titles and Subtitles 158 | 159 | #### 1. Products 160 | 161 | To modify the title and subtitle of the **Products** section, make changes in the ```frontend/src/components/Products.tsx``` file. 162 | 163 | #### 2. Services 164 | 165 | To modify the title and subtitle of the **Services** section, make changes in the ```frontend/src/components/Services.tsx``` file. 166 | 167 | #### 3. Pricing 168 | 169 | To modify the title and subtitle of the **Pricing** section, make changes in the ```frontend/src/components/Pricing.tsx``` file. 170 | 171 | #### 4. About 172 | 173 | To modify the title and subtitle of the **About** section, make changes in the ```frontend/src/components/About.tsx``` file. 174 | 175 | #### 5. Contact 176 | 177 | To modify the title and subtitle of the **Contact** section, make changes in the ```frontend/src/components/Contact.tsx``` file. 178 | 179 | ### Changing Colors 180 | 181 | To modify the colors in the application, make changes in the ```frontend/src/theme/palette.ts``` file. 182 | 183 | ### Changing Fonts 184 | 185 | 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. 186 | 187 | ### Changing Logo 188 | 189 | To modify the logo in the application, make changes in the ```frontend/src/layout/Header.tsx``` and ```frontend/src/layout/Sidebar.tsx``` files. 190 | 191 | ### Changing Buttons in the Hero Section 192 | 193 | To modify the two buttons in the Hero section, make changes in the ```frontend/src/components/HeroButtons.tsx``` file. 194 | 195 | ## Copyright and License 196 | 197 | Copyright © 2022 Bob's Programming Academy. Code released under the MIT license. 198 | -------------------------------------------------------------------------------- /backend/about/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/business-website-template/e835c7b6b1afb883ab3b16a636d6236081a82bbd/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/business-website-template/e835c7b6b1afb883ab3b16a636d6236081a82bbd/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/business-website-template/e835c7b6b1afb883ab3b16a636d6236081a82bbd/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 | # Quick-start development settings - unsuitable for production 8 | # See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/ 9 | 10 | # SECURITY WARNING: keep the secret key used in production secret! 11 | SECRET_KEY = 'django-insecure--$g-e%io^a4vhd3-1t8=(2ij#%#kk5s!yjkg=s&5bt_zoroxls' 12 | 13 | # SECURITY WARNING: don't run with debug turned on in production! 14 | DEBUG = True 15 | 16 | ALLOWED_HOSTS = [] 17 | 18 | 19 | # Application definition 20 | 21 | INSTALLED_APPS = [ 22 | 'django.contrib.admin', 23 | 'django.contrib.auth', 24 | 'django.contrib.contenttypes', 25 | 'django.contrib.sessions', 26 | 'django.contrib.messages', 27 | 'django.contrib.staticfiles', 28 | 29 | # 3rd party 30 | 'rest_framework', 31 | 'corsheaders', 32 | 33 | # Local 34 | 'products', 35 | 'services', 36 | 'pricing', 37 | 'about', 38 | 'contact', 39 | 'hero', 40 | 'footer' 41 | ] 42 | 43 | MIDDLEWARE = [ 44 | 'corsheaders.middleware.CorsMiddleware', 45 | 'django.middleware.security.SecurityMiddleware', 46 | 'django.contrib.sessions.middleware.SessionMiddleware', 47 | 'django.middleware.common.CommonMiddleware', 48 | 'django.middleware.csrf.CsrfViewMiddleware', 49 | 'django.contrib.auth.middleware.AuthenticationMiddleware', 50 | 'django.contrib.messages.middleware.MessageMiddleware', 51 | 'django.middleware.clickjacking.XFrameOptionsMiddleware', 52 | ] 53 | 54 | ROOT_URLCONF = 'config.urls' 55 | 56 | TEMPLATES = [ 57 | { 58 | 'BACKEND': 'django.template.backends.django.DjangoTemplates', 59 | 'DIRS': [os.path.join(BASE_DIR, 'build')], 60 | 'APP_DIRS': True, 61 | 'OPTIONS': { 62 | 'context_processors': [ 63 | 'django.template.context_processors.debug', 64 | 'django.template.context_processors.request', 65 | 'django.contrib.auth.context_processors.auth', 66 | 'django.contrib.messages.context_processors.messages', 67 | ], 68 | }, 69 | }, 70 | ] 71 | 72 | WSGI_APPLICATION = 'config.wsgi.application' 73 | 74 | 75 | # Database 76 | # https://docs.djangoproject.com/en/4.1/ref/settings/#databases 77 | 78 | DATABASES = { 79 | 'default': { 80 | 'ENGINE': 'django.db.backends.sqlite3', 81 | 'NAME': BASE_DIR / 'db.sqlite3', 82 | } 83 | } 84 | 85 | 86 | # Password validation 87 | # https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators 88 | 89 | AUTH_PASSWORD_VALIDATORS = [ 90 | { 91 | 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', 92 | }, 93 | { 94 | 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', 95 | }, 96 | { 97 | 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', 98 | }, 99 | { 100 | 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', 101 | }, 102 | ] 103 | 104 | 105 | # Internationalization 106 | # https://docs.djangoproject.com/en/4.1/topics/i18n/ 107 | 108 | LANGUAGE_CODE = 'en-us' 109 | 110 | TIME_ZONE = 'UTC' 111 | 112 | USE_I18N = True 113 | 114 | USE_TZ = True 115 | 116 | 117 | # Static files (CSS, JavaScript, Images) 118 | # https://docs.djangoproject.com/en/4.1/howto/static-files/ 119 | 120 | # MEDIA_ROOT is for the user-uploaded content 121 | 122 | STATIC_URL = '/static/' 123 | STATIC_ROOT = os.path.join(BASE_DIR, 'static') 124 | 125 | MEDIA_URL = '/media/' 126 | MEDIA_ROOT = os.path.join(BASE_DIR, 'media') 127 | 128 | 129 | # Default primary key field type 130 | # https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field 131 | 132 | DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' 133 | 134 | CORS_ALLOWED_ORIGINS = [ 135 | 'http://localhost:3000' 136 | ] 137 | 138 | 139 | FILE_UPLOAD_PERMISSIONS = 0o640 140 | -------------------------------------------------------------------------------- /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/business-website-template/e835c7b6b1afb883ab3b16a636d6236081a82bbd/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/business-website-template/e835c7b6b1afb883ab3b16a636d6236081a82bbd/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/business-website-template/e835c7b6b1afb883ab3b16a636d6236081a82bbd/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/business-website-template/e835c7b6b1afb883ab3b16a636d6236081a82bbd/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/business-website-template/e835c7b6b1afb883ab3b16a636d6236081a82bbd/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/business-website-template/e835c7b6b1afb883ab3b16a636d6236081a82bbd/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/business-website-template/e835c7b6b1afb883ab3b16a636d6236081a82bbd/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/business-website-template/e835c7b6b1afb883ab3b16a636d6236081a82bbd/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/business-website-template/e835c7b6b1afb883ab3b16a636d6236081a82bbd/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/business-website-template/e835c7b6b1afb883ab3b16a636d6236081a82bbd/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: -------------------------------------------------------------------------------- 1 | asgiref==3.7.2 2 | Django==4.2.4 3 | django-cors-headers==4.2.0 4 | djangorestframework==3.14.0 5 | flake8==6.1.0 6 | mccabe==0.7.0 7 | Pillow==10.0.0 8 | pycodestyle==2.11.0 9 | pyflakes==3.1.0 10 | pytz==2023.3 11 | sqlparse==0.4.4 12 | tzdata==2023.3 13 | -------------------------------------------------------------------------------- /backend/services/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/business-website-template/e835c7b6b1afb883ab3b16a636d6236081a82bbd/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/business-website-template/e835c7b6b1afb883ab3b16a636d6236081a82bbd/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/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /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": "business-website", 3 | "version": "0.1.0", 4 | "description": "Business 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.6", 55 | "@testing-library/react": "^14.1.2", 56 | "@testing-library/user-event": "^14.5.2", 57 | "@types/jest": "^29.5.4", 58 | "@types/leaflet": "^1.9.3", 59 | "@types/node": "^20.5.6", 60 | "@types/react": "^18.2.21", 61 | "@types/react-dom": "^18.2.7", 62 | "@typescript-eslint/parser": "^5.62.0", 63 | "eslint": "^8.56.0", 64 | "eslint-config-prettier": "^9.1.0", 65 | "eslint-plugin-import": "^2.29.1", 66 | "eslint-plugin-n": "^16.6.1", 67 | "eslint-plugin-prettier": "^5.1.2", 68 | "eslint-plugin-promise": "^6.1.1", 69 | "eslint-plugin-react": "^7.33.2", 70 | "husky": "^8.0.3", 71 | "jest": "^29.7.0", 72 | "jest-canvas-mock": "^2.5.2", 73 | "jest-environment-jsdom": "^29.7.0", 74 | "lint-staged": "^15.2.0", 75 | "prettier": "^3.1.1", 76 | "ts-jest": "^29.1.1", 77 | "typescript": "^4.7.4" 78 | }, 79 | "browserslist": { 80 | "production": [ 81 | ">0.2%", 82 | "not dead", 83 | "not op_mini all" 84 | ], 85 | "development": [ 86 | "last 1 chrome version", 87 | "last 1 firefox version", 88 | "last 1 safari version" 89 | ] 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/business-website-template/e835c7b6b1afb883ab3b16a636d6236081a82bbd/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/images/dark_theme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/business-website-template/e835c7b6b1afb883ab3b16a636d6236081a82bbd/frontend/public/images/dark_theme.png -------------------------------------------------------------------------------- /frontend/public/images/light_theme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/business-website-template/e835c7b6b1afb883ab3b16a636d6236081a82bbd/frontend/public/images/light_theme.png -------------------------------------------------------------------------------- /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 { useState, useEffect, useMemo } from 'react'; 2 | import { BrowserRouter, Routes, Route } from 'react-router-dom'; 3 | import { HelmetProvider, Helmet } from 'react-helmet-async'; 4 | import { ThemeProvider } from '@mui/material/styles'; 5 | import CssBaseline from '@mui/material/CssBaseline'; 6 | 7 | import getTheme from './theme/theme'; 8 | import ColorModeContext from './utils/ColorModeContext'; 9 | import Layout from './layout/Layout'; 10 | import Home from './pages/Home'; 11 | 12 | const App = (): JSX.Element => { 13 | const [mode, setMode] = useState('dark'); 14 | const colorMode = useMemo( 15 | () => ({ 16 | // The theme mode switch will invoke this method 17 | toggleColorMode: () => { 18 | window.localStorage.setItem( 19 | 'themeMode', 20 | mode === 'dark' ? 'light' : 'dark' 21 | ); 22 | setMode((prevMode) => (prevMode === 'dark' ? 'light' : 'dark')); 23 | }, 24 | }), 25 | [mode] 26 | ); 27 | 28 | useEffect(() => { 29 | try { 30 | const localTheme = window.localStorage.getItem('themeMode'); 31 | localTheme ? setMode(localTheme) : setMode('dark'); 32 | } catch { 33 | setMode('dark'); 34 | } 35 | }, []); 36 | 37 | return ( 38 | 39 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | } /> 50 | 51 | 52 | 53 | 54 | 55 | 56 | ); 57 | }; 58 | 59 | export default App; 60 | -------------------------------------------------------------------------------- /frontend/src/assets/images/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/business-website-template/e835c7b6b1afb883ab3b16a636d6236081a82bbd/frontend/src/assets/images/1.png -------------------------------------------------------------------------------- /frontend/src/assets/images/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/business-website-template/e835c7b6b1afb883ab3b16a636d6236081a82bbd/frontend/src/assets/images/2.png -------------------------------------------------------------------------------- /frontend/src/assets/images/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/business-website-template/e835c7b6b1afb883ab3b16a636d6236081a82bbd/frontend/src/assets/images/3.png -------------------------------------------------------------------------------- /frontend/src/assets/images/chart1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/business-website-template/e835c7b6b1afb883ab3b16a636d6236081a82bbd/frontend/src/assets/images/chart1.jpg -------------------------------------------------------------------------------- /frontend/src/assets/images/chart2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/business-website-template/e835c7b6b1afb883ab3b16a636d6236081a82bbd/frontend/src/assets/images/chart2.jpg -------------------------------------------------------------------------------- /frontend/src/assets/images/chart3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/business-website-template/e835c7b6b1afb883ab3b16a636d6236081a82bbd/frontend/src/assets/images/chart3.jpg -------------------------------------------------------------------------------- /frontend/src/assets/images/chart4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/business-website-template/e835c7b6b1afb883ab3b16a636d6236081a82bbd/frontend/src/assets/images/chart4.jpg -------------------------------------------------------------------------------- /frontend/src/assets/logo/react.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/business-website-template/e835c7b6b1afb883ab3b16a636d6236081a82bbd/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 | 120 | boolean) 125 | ) => setViewPortVisibility(isVisible)} 126 | delayedCall 127 | > 128 | 134 | 135 | 136 | 137 | 141 | {item.description} 142 | 143 | 144 | 145 | 146 | 147 | ))} 148 | 149 | 150 | 151 |
152 | ); 153 | }; 154 | 155 | export default About; 156 | -------------------------------------------------------------------------------- /frontend/src/components/Contact.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 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 | 96 | 100 | 101 | 102 | 103 | 110 | 118 | 129 | 130 | 131 | 139 | 150 | 151 | 152 | 160 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | ))} 178 | 179 |
180 | ); 181 | }; 182 | 183 | export default Contact; 184 | -------------------------------------------------------------------------------- /frontend/src/components/CustomButton.tsx: -------------------------------------------------------------------------------- 1 | import Button from '@mui/material/Button'; 2 | import { useTheme } from '@mui/material/styles'; 3 | 4 | interface Props { 5 | href: string; 6 | text: string; 7 | } 8 | 9 | const CustomButton = ({ href, text }: Props): JSX.Element => { 10 | const theme = useTheme(); 11 | 12 | return ( 13 | 39 | ); 40 | }; 41 | 42 | export default CustomButton; 43 | -------------------------------------------------------------------------------- /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 | 67 | 72 | 114 | 115 | 116 | 117 | ); 118 | }; 119 | 120 | export default HeroButtons; 121 | -------------------------------------------------------------------------------- /frontend/src/components/Map.tsx: -------------------------------------------------------------------------------- 1 | import 'leaflet/dist/leaflet.css'; 2 | import { LatLngExpression } from 'leaflet'; 3 | import { MapContainer, Circle, TileLayer } from 'react-leaflet'; 4 | import { useTheme } from '@mui/material/styles'; 5 | 6 | interface Props { 7 | coordinates: [number, number]; 8 | zoom: number; 9 | } 10 | 11 | const Map = ({ coordinates, zoom }: Props): JSX.Element => { 12 | const theme = useTheme(); 13 | const position: LatLngExpression = coordinates; 14 | const fillBlueOptions = { 15 | fillColor: 16 | theme.palette.mode === 'dark' 17 | ? theme.palette.primary.main 18 | : theme.palette.success.dark, 19 | }; 20 | 21 | return ( 22 | 28 | 32 | 33 | 34 | ); 35 | }; 36 | 37 | export default Map; 38 | -------------------------------------------------------------------------------- /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 | 122 | {item.currency} 123 | 124 | 125 | 126 | 136 | {item.price} 137 | 138 | 139 | 140 | 144 | Per user, per month 145 | 146 | 147 | 148 | {item.features?.map((feature, j) => ( 149 | 150 | 157 | {feature.name} 158 | 159 | 160 | ))} 161 | 162 | 163 | 164 | 165 | 200 | 201 | 202 | 203 | ))} 204 | 205 | 206 | 207 |
208 | ); 209 | }; 210 | 211 | export default Pricing; 212 | -------------------------------------------------------------------------------- /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 | 95 | 96 | 103 | {item.name} 104 | 105 | {item.description} 106 | 107 | 108 | 119 | 134 | 135 | 136 | 137 | 138 | ))} 139 | 140 | 141 | 142 |
143 | ); 144 | }; 145 | 146 | export default Products; 147 | -------------------------------------------------------------------------------- /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 | 94 | 95 | {item.icon} 96 | 97 | 98 | 103 | {item.title} 104 | 105 | 106 | {item.description} 107 | 108 | 109 | 110 | 111 | ))} 112 | 113 | 114 | 115 |
116 | ); 117 | }; 118 | 119 | export default Services; 120 | -------------------------------------------------------------------------------- /frontend/src/components/Spacer.tsx: -------------------------------------------------------------------------------- 1 | import Box from '@mui/material/Box'; 2 | import { useTheme } from '@mui/material'; 3 | 4 | interface Props { 5 | [x: string]: any; 6 | } 7 | 8 | const Spacer = ({ sx = [] }: Props): JSX.Element => { 9 | const theme = useTheme(); 10 | 11 | return ( 12 | 20 | ); 21 | }; 22 | 23 | export default Spacer; 24 | -------------------------------------------------------------------------------- /frontend/src/index.tsx: -------------------------------------------------------------------------------- 1 | import ReactDOM from 'react-dom/client'; 2 | import App from './App'; 3 | 4 | const root = ReactDOM.createRoot( 5 | document.getElementById('root') as HTMLElement 6 | ); 7 | root.render(); 8 | -------------------------------------------------------------------------------- /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 | import { useContext } from 'react'; 2 | 3 | // Material UI 4 | import AppBar from '@mui/material/AppBar'; 5 | import Button from '@mui/material/Button'; 6 | import Box from '@mui/material/Box'; 7 | import Divider from '@mui/material/Divider'; 8 | import IconButton from '@mui/material/IconButton'; 9 | import Link from '@mui/material/Link'; 10 | import Toolbar from '@mui/material/Toolbar'; 11 | import Tooltip from '@mui/material/Tooltip'; 12 | import Typography from '@mui/material/Typography'; 13 | import { alpha, useTheme } from '@mui/material/styles'; 14 | 15 | // Material Icons 16 | import DarkModeIcon from '@mui/icons-material/DarkMode'; 17 | import LightModeIcon from '@mui/icons-material/LightMode'; 18 | import StormIcon from '@mui/icons-material/Storm'; 19 | import MenuIcon from '@mui/icons-material/Menu'; 20 | 21 | import CustomButton from '../components/CustomButton'; 22 | import ColorModeContext from '../utils/ColorModeContext'; 23 | 24 | interface Props { 25 | onSidebarOpen: () => void; 26 | } 27 | 28 | const Header = ({ onSidebarOpen }: Props): JSX.Element => { 29 | const theme = useTheme(); 30 | const colorMode = useContext(ColorModeContext); 31 | 32 | return ( 33 | <> 34 | 46 | 47 | 48 | 49 | 59 | 60 | 71 | Bob's Company 72 | 73 | 74 | 75 | 76 | 77 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 97 | 98 | 103 | {theme.palette.mode === 'dark' ? ( 104 | 105 | 106 | 107 | ) : ( 108 | 109 | 110 | 111 | )} 112 | 113 | 114 | 120 | 140 | 141 | 142 | 143 | 144 | ); 145 | }; 146 | 147 | export default Header; 148 | -------------------------------------------------------------------------------- /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 Props { 20 | children: React.ReactNode; 21 | } 22 | 23 | const Layout = ({ children }: Props): 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 |