├── .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 ├── projects │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ ├── urls.py │ └── views.py ├── requirements.txt ├── setup.cfg ├── technologies │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ │ ├── 0001_initial.py │ │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ ├── urls.py │ ├── validators.py │ └── views.py └── testimonials │ ├── __init__.py │ ├── admin.py │ ├── apps.py │ ├── migrations │ ├── 0001_initial.py │ └── __init__.py │ ├── models.py │ ├── serializers.py │ ├── tests.py │ ├── urls.py │ └── views.py └── frontend ├── .eslintrc.json ├── .prettierrc.json ├── babel.config.js ├── jest.config.js ├── jest.setup.js ├── package-lock.json ├── package.json ├── public ├── favicon.ico └── index.html └── src ├── App.js ├── assets ├── images │ ├── bg.png │ ├── large.png │ ├── project1.png │ ├── project2.png │ ├── project3.png │ ├── project4.png │ ├── user1.png │ ├── user2.png │ └── user3.png └── logo │ ├── bootstrap.svg │ ├── css.svg │ ├── django.svg │ ├── git.svg │ ├── html.svg │ ├── javascript.svg │ ├── material-ui.svg │ ├── nextjs.svg │ ├── postgresql.svg │ ├── python.svg │ ├── react.svg │ └── tensorflow.svg ├── components ├── About.js ├── Contact.js ├── CustomButton.js ├── Hero.js ├── HeroButtons.js ├── Map.js ├── Projects.js ├── Technologies.js └── Testimonials.js ├── index.js ├── layout ├── Footer.js ├── Header.js ├── Layout.js └── Sidebar.js ├── pages └── Home.js ├── tests ├── Home.test.jsx └── mocks │ ├── fileMock.css │ ├── fileMock.js │ └── reactLeafletMock.js └── theme ├── components.js ├── theme.js └── typography.js /.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 | 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: [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 | db.sqlite3 4 | build/ 5 | coverage/ 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 | # Freelance Developer Portfolio Website 2 | 3 | This is a freelance developer portfolio website built using **Django 4**, **Django REST Framework 3**, **React 18**, and **Material UI 5**. 4 | 5 | ![plot](https://github.com/BobsProgrammingAcademy/Freelance-Developer-Website/blob/master/frontend/src/assets/images/large.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.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). 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 | ### 3. View the application 122 | 123 | Go to http://localhost:3000/ to view the application. 124 | 125 | 126 | ## Run the tests 127 | 128 | ### 1. Run the backend tests 129 | 130 | From the **backend** directory, run: 131 | 132 | ```bash 133 | python manage.py test --pattern="tests.py" 134 | 135 | ``` 136 | 137 | ### 2. Run the frontend tests 138 | 139 | From the **frontend** directory, run: 140 | 141 | ```bash 142 | npm run test 143 | ``` 144 | 145 | ## Add data to the application 146 | 147 | Add data through Django Admin. 148 | 149 | Go to http://127.0.0.1:8000/admin to access the Django Admin interface and sign in using the admin credentials. 150 | 151 | ## Customize the application 152 | 153 | This section describes how to customize the application. 154 | 155 | ### Changing Section Titles and Subtitles 156 | 157 | #### 1. About 158 | 159 | To modify the title and subtitle of the **About** section, make changes in the ```frontend/src/components/About.js``` file. 160 | 161 | #### 2. Projects 162 | 163 | To modify the title and subtitle of the **Projects** section, make changes in the ```frontend/src/components/Projects.js``` file. 164 | 165 | #### 3. Technologies 166 | 167 | To modify the title and subtitle of the **Technologies** section, make changes in the ```frontend/src/components/Technologies.js``` file. 168 | 169 | #### 4. Testimonials 170 | 171 | To modify the title and subtitle of the **Testimonials** section, make changes in the ```frontend/src/components/Testimonials.js``` file. 172 | 173 | #### 5. Contact 174 | 175 | To modify the title and subtitle of the **Contact** section, make changes in the ```frontend/src/components/Contact.js``` file. 176 | 177 | ### Changing Colors 178 | 179 | To modify the colors in the application, make changes in the ```frontend/src/theme/theme.js``` file. 180 | 181 | ### Changing Fonts 182 | 183 | 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/typography.js``` file. 184 | 185 | ### Changing Logo 186 | 187 | To modify the logo in the application, make changes in the ```frontend/src/layout/Header.js``` and ```frontend/src/layout/Sidebar.js``` files. 188 | 189 | ### Changing Buttons in the Hero Section 190 | 191 | To modify the two buttons in the Hero section, make changes in the ```frontend/src/components/HeroButtons.js``` file. 192 | 193 | ## Copyright and License 194 | 195 | Copyright © 2022 Bob's Programming Academy. Code released under the MIT license. 196 | -------------------------------------------------------------------------------- /backend/about/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/freelance-developer-website/e86fd15632efbcfab6c4faf7cb94de0530fa776a/backend/about/__init__.py -------------------------------------------------------------------------------- /backend/about/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import About 3 | 4 | 5 | class AboutAdmin(admin.ModelAdmin): 6 | list_display = ('id', 'title', 'icon') 7 | list_display_links = ('id', 'title') 8 | search_fields = ('title', 'icon') 9 | list_per_page = 20 10 | 11 | 12 | admin.site.register(About, AboutAdmin) 13 | -------------------------------------------------------------------------------- /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.0.5 on 2022-06-23 23:20 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 | ('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': 'About', 24 | 'verbose_name_plural': 'About', 25 | }, 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /backend/about/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/freelance-developer-website/e86fd15632efbcfab6c4faf7cb94de0530fa776a/backend/about/migrations/__init__.py -------------------------------------------------------------------------------- /backend/about/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class About(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 = 'About' 11 | verbose_name_plural = 'About' 12 | 13 | def __str__(self): 14 | return self.title 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 | title='Lorem ipsum dolor sit amet', 9 | description='Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed tincidunt quis odio id.', 10 | icon='settings' 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/freelance-developer-website/e86fd15632efbcfab6c4faf7cb94de0530fa776a/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.0/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.0/howto/deployment/checklist/ 9 | 10 | # SECURITY WARNING: keep the secret key used in production secret! 11 | SECRET_KEY = 'django-insecure-kngepvxt6e582&9*-xvhcln0uo!k*^%tm_@5uw42inc-h@6$lh' 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 | 'about', 35 | 'projects', 36 | 'technologies', 37 | 'testimonials', 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.0/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.0/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.0/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.0/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.0/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('about/', include('about.urls')), 9 | path('projects/', include('projects.urls')), 10 | path('technologies/', include('technologies.urls')), 11 | path('testimonials/', include('testimonials.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.0/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/freelance-developer-website/e86fd15632efbcfab6c4faf7cb94de0530fa776a/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 = ('id', 'address', 'email') 7 | list_display_links = ('id', 'email') 8 | search_fields = ('title', '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.0.5 on 2022-06-23 23:20 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/freelance-developer-website/e86fd15632efbcfab6c4faf7cb94de0530fa776a/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/freelance-developer-website/e86fd15632efbcfab6c4faf7cb94de0530fa776a/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.0.5 on 2022-06-23 23:20 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 | ('image_author_name', models.CharField(blank=True, max_length=100, null=True)), 20 | ('image_author_link', models.CharField(blank=True, max_length=250, null=True)), 21 | ('image_website_name', models.CharField(blank=True, max_length=100, null=True)), 22 | ('image_website_link', models.CharField(blank=True, max_length=250, null=True)), 23 | ], 24 | options={ 25 | 'verbose_name': 'Footer', 26 | 'verbose_name_plural': 'Footer', 27 | }, 28 | ), 29 | ] 30 | -------------------------------------------------------------------------------- /backend/footer/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/freelance-developer-website/e86fd15632efbcfab6c4faf7cb94de0530fa776a/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 | # four values below are for giving credit to the author of the Hero image 7 | # # and are displayed in the Footer section 8 | image_author_name = models.CharField(max_length=100, null=True, blank=True) 9 | image_author_link = models.CharField(max_length=250, null=True, blank=True) 10 | image_website_name = models.CharField(max_length=100, null=True, blank=True) 11 | image_website_link = models.CharField(max_length=250, null=True, blank=True) 12 | 13 | class Meta: 14 | verbose_name = 'Footer' 15 | verbose_name_plural = 'Footer' 16 | 17 | def __str__(self): 18 | return self.copyright 19 | -------------------------------------------------------------------------------- /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/freelance-developer-website/e86fd15632efbcfab6c4faf7cb94de0530fa776a/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.0.5 on 2022-06-23 23:20 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 | ('subtitle', models.CharField(max_length=300)), 20 | ('description', models.CharField(max_length=500)), 21 | ('image', models.ImageField(upload_to='images')), 22 | ], 23 | options={ 24 | 'verbose_name': 'Hero', 25 | 'verbose_name_plural': 'Hero', 26 | }, 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /backend/hero/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/freelance-developer-website/e86fd15632efbcfab6c4faf7cb94de0530fa776a/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 | subtitle = models.CharField(max_length=300) 7 | description = models.CharField(max_length=500) 8 | image = models.ImageField(upload_to='images') 9 | 10 | class Meta: 11 | verbose_name = 'Hero' 12 | verbose_name_plural = 'Hero' 13 | 14 | def __str__(self): 15 | return '{0} {1}'.format(self.title, self.subtitle) 16 | -------------------------------------------------------------------------------- /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 | import tempfile 2 | from django.test import TestCase 3 | from .models import Hero 4 | 5 | 6 | class HeroModelUnitTestCase(TestCase): 7 | def setUp(self): 8 | self.hero = Hero.objects.create( 9 | title='Lorem ipsum dolor sit amet', 10 | subtitle='Sed tincidunt quis odio id molestie', 11 | description='Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed tincidunt quis odio id.', 12 | image=tempfile.NamedTemporaryFile(suffix='.jpg').name 13 | ) 14 | 15 | def test_hero_model(self): 16 | data = self.hero 17 | self.assertIsInstance(data, Hero) 18 | -------------------------------------------------------------------------------- /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/projects/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/freelance-developer-website/e86fd15632efbcfab6c4faf7cb94de0530fa776a/backend/projects/__init__.py -------------------------------------------------------------------------------- /backend/projects/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Project, Tag 3 | 4 | 5 | class ProjectAdmin(admin.ModelAdmin): 6 | list_display = ('id', 'name') 7 | list_display_links = ('id', 'name') 8 | search_fields = ('name', 'tags') 9 | list_per_page = 20 10 | 11 | 12 | admin.site.register(Project, ProjectAdmin) 13 | admin.site.register(Tag) 14 | -------------------------------------------------------------------------------- /backend/projects/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class ProjectsConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'projects' 7 | -------------------------------------------------------------------------------- /backend/projects/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.5 on 2022-06-23 23:20 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='Tag', 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='Project', 23 | fields=[ 24 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 25 | ('name', models.CharField(max_length=100)), 26 | ('description', models.CharField(max_length=200)), 27 | ('link', models.CharField(max_length=250)), 28 | ('image', models.ImageField(upload_to='images')), 29 | ('tags', models.ManyToManyField(to='projects.tag')), 30 | ], 31 | ), 32 | ] 33 | -------------------------------------------------------------------------------- /backend/projects/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/freelance-developer-website/e86fd15632efbcfab6c4faf7cb94de0530fa776a/backend/projects/migrations/__init__.py -------------------------------------------------------------------------------- /backend/projects/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class Tag(models.Model): 5 | name = models.CharField(max_length=30) 6 | 7 | def __str__(self): 8 | return self.name 9 | 10 | 11 | class Project(models.Model): 12 | name = models.CharField(max_length=100) 13 | description = models.CharField(max_length=200) 14 | link = models.CharField(max_length=250) 15 | image = models.ImageField(upload_to='images') 16 | tags = models.ManyToManyField(Tag) 17 | 18 | def __str__(self): 19 | return self.name 20 | -------------------------------------------------------------------------------- /backend/projects/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from .models import Project, Tag 3 | 4 | 5 | class TagSerializer(serializers.ModelSerializer): 6 | class Meta: 7 | model = Tag 8 | fields = ('name',) 9 | 10 | 11 | class ProjectSerializer(serializers.ModelSerializer): 12 | tags = TagSerializer(many=True) 13 | 14 | class Meta: 15 | model = Project 16 | fields = ('name', 'description', 'link', 'image', 'tags') 17 | -------------------------------------------------------------------------------- /backend/projects/tests.py: -------------------------------------------------------------------------------- 1 | from django.test import TestCase 2 | from .models import Tag, Project 3 | 4 | 5 | class TagModelUnitTestCase(TestCase): 6 | def setUp(self): 7 | self.tag = Tag.objects.create( 8 | name='Django', 9 | ) 10 | 11 | def test_tag_model(self): 12 | data = self.tag 13 | self.assertIsInstance(data, Tag) 14 | 15 | 16 | class ProjectModelUnitTestCase(TestCase): 17 | def setUp(self): 18 | self.tag = Tag.objects.create(name='Django') 19 | self.project = Project.objects.create( 20 | name='Lorem ipsum dolor sit amet', 21 | description='Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed tincidunt quis odio id.', 22 | link='http://test.com/' 23 | ) 24 | self.project.tags.add(self.tag) 25 | 26 | def test_project_model(self): 27 | data = self.project 28 | self.assertIsInstance(data, Project) 29 | -------------------------------------------------------------------------------- /backend/projects/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from .views import ProjectListView 3 | 4 | urlpatterns = [ 5 | path('', ProjectListView.as_view()), 6 | ] 7 | -------------------------------------------------------------------------------- /backend/projects/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework.generics import ListAPIView 2 | from rest_framework import permissions 3 | 4 | from .models import Project 5 | from .serializers import ProjectSerializer 6 | 7 | 8 | class ProjectListView(ListAPIView): 9 | permission_classes = (permissions.AllowAny, ) 10 | queryset = Project.objects.all() 11 | serializer_class = ProjectSerializer 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 | -------------------------------------------------------------------------------- /backend/setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | exclude = .git,*migrations*,*venv* 3 | max-line-length = 119 4 | indent-size = 2 -------------------------------------------------------------------------------- /backend/technologies/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/freelance-developer-website/e86fd15632efbcfab6c4faf7cb94de0530fa776a/backend/technologies/__init__.py -------------------------------------------------------------------------------- /backend/technologies/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Technology 3 | 4 | 5 | class TechnologyAdmin(admin.ModelAdmin): 6 | list_display = ('id', 'name', 'icon') 7 | list_display_links = ('id', 'name') 8 | search_fields = ('name', 'icon') 9 | list_per_page = 20 10 | 11 | 12 | admin.site.register(Technology, TechnologyAdmin) 13 | -------------------------------------------------------------------------------- /backend/technologies/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class TechnologiesConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'technologies' 7 | -------------------------------------------------------------------------------- /backend/technologies/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.5 on 2022-06-23 23:20 2 | 3 | from django.db import migrations, models 4 | import technologies.validators 5 | 6 | 7 | class Migration(migrations.Migration): 8 | 9 | initial = True 10 | 11 | dependencies = [ 12 | ] 13 | 14 | operations = [ 15 | migrations.CreateModel( 16 | name='Technology', 17 | fields=[ 18 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 19 | ('name', models.CharField(max_length=100)), 20 | ('icon', models.FileField(upload_to='icons', validators=[technologies.validators.validate_file_extension])), 21 | ], 22 | options={ 23 | 'verbose_name': 'Technology', 24 | 'verbose_name_plural': 'Technologies', 25 | }, 26 | ), 27 | ] 28 | -------------------------------------------------------------------------------- /backend/technologies/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/freelance-developer-website/e86fd15632efbcfab6c4faf7cb94de0530fa776a/backend/technologies/migrations/__init__.py -------------------------------------------------------------------------------- /backend/technologies/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | from .validators import validate_file_extension 3 | 4 | 5 | class Technology(models.Model): 6 | name = models.CharField(max_length=100) 7 | icon = models.FileField(upload_to='icons', validators=[validate_file_extension]) 8 | 9 | class Meta: 10 | verbose_name = 'Technology' 11 | verbose_name_plural = 'Technologies' 12 | 13 | def __str__(self): 14 | return self.name 15 | -------------------------------------------------------------------------------- /backend/technologies/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from .models import Technology 3 | 4 | 5 | class TechnologySerializer(serializers.ModelSerializer): 6 | class Meta: 7 | model = Technology 8 | fields = '__all__' 9 | -------------------------------------------------------------------------------- /backend/technologies/tests.py: -------------------------------------------------------------------------------- 1 | import tempfile 2 | from django.test import TestCase 3 | from .models import Technology 4 | 5 | 6 | class TechnologyModelUnitTestCase(TestCase): 7 | def setUp(self): 8 | self.tag = Technology.objects.create( 9 | name='Django', 10 | icon=tempfile.NamedTemporaryFile(suffix='.svg').name 11 | ) 12 | 13 | def test_tag_model(self): 14 | data = self.tag 15 | self.assertIsInstance(data, Technology) 16 | -------------------------------------------------------------------------------- /backend/technologies/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from .views import TechnologyListView 3 | 4 | urlpatterns = [ 5 | path('', TechnologyListView.as_view()), 6 | ] 7 | -------------------------------------------------------------------------------- /backend/technologies/validators.py: -------------------------------------------------------------------------------- 1 | import os 2 | from django.core.exceptions import ValidationError 3 | 4 | 5 | def validate_file_extension(value): 6 | ext = os.path.splitext(value.name)[1] # [0] returns path and filename 7 | valid_extensions = ['.svg'] # populate with the allowed extensions 8 | if not ext.lower() in valid_extensions: 9 | raise ValidationError('Unsupported file extension.') 10 | -------------------------------------------------------------------------------- /backend/technologies/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework.generics import ListAPIView 2 | from rest_framework import permissions 3 | 4 | from .models import Technology 5 | from .serializers import TechnologySerializer 6 | 7 | 8 | class TechnologyListView(ListAPIView): 9 | permission_classes = (permissions.AllowAny, ) 10 | queryset = Technology.objects.all() 11 | serializer_class = TechnologySerializer 12 | pagination_class = None 13 | -------------------------------------------------------------------------------- /backend/testimonials/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/freelance-developer-website/e86fd15632efbcfab6c4faf7cb94de0530fa776a/backend/testimonials/__init__.py -------------------------------------------------------------------------------- /backend/testimonials/admin.py: -------------------------------------------------------------------------------- 1 | from django.contrib import admin 2 | from .models import Testimonial 3 | 4 | 5 | class TestimonialAdmin(admin.ModelAdmin): 6 | list_display = ('id', 'author_name', 'author_title', 'author_photo') 7 | list_display_links = ('id', 'author_name') 8 | search_fields = ('author_name', 'author_title', 'author_photo') 9 | list_per_page = 20 10 | 11 | 12 | admin.site.register(Testimonial, TestimonialAdmin) 13 | -------------------------------------------------------------------------------- /backend/testimonials/apps.py: -------------------------------------------------------------------------------- 1 | from django.apps import AppConfig 2 | 3 | 4 | class TestimonialsConfig(AppConfig): 5 | default_auto_field = 'django.db.models.BigAutoField' 6 | name = 'testimonials' 7 | -------------------------------------------------------------------------------- /backend/testimonials/migrations/0001_initial.py: -------------------------------------------------------------------------------- 1 | # Generated by Django 4.0.5 on 2022-06-23 23:49 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='Testimonial', 16 | fields=[ 17 | ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), 18 | ('testimonial', models.CharField(max_length=300)), 19 | ('author_name', models.CharField(max_length=100)), 20 | ('author_title', models.CharField(max_length=100)), 21 | ('author_photo', models.ImageField(upload_to='images')), 22 | ], 23 | options={ 24 | 'verbose_name': 'Testimonial', 25 | 'verbose_name_plural': 'Testimonials', 26 | }, 27 | ), 28 | ] 29 | -------------------------------------------------------------------------------- /backend/testimonials/migrations/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/freelance-developer-website/e86fd15632efbcfab6c4faf7cb94de0530fa776a/backend/testimonials/migrations/__init__.py -------------------------------------------------------------------------------- /backend/testimonials/models.py: -------------------------------------------------------------------------------- 1 | from django.db import models 2 | 3 | 4 | class Testimonial(models.Model): 5 | testimonial = models.CharField(max_length=300) 6 | author_name = models.CharField(max_length=100) 7 | author_title = models.CharField(max_length=100) 8 | author_photo = models.ImageField(upload_to='images') 9 | 10 | class Meta: 11 | verbose_name = 'Testimonial' 12 | verbose_name_plural = 'Testimonials' 13 | 14 | def __str__(self): 15 | return self.author_name 16 | -------------------------------------------------------------------------------- /backend/testimonials/serializers.py: -------------------------------------------------------------------------------- 1 | from rest_framework import serializers 2 | from .models import Testimonial 3 | 4 | 5 | class TestimonialSerializer(serializers.ModelSerializer): 6 | class Meta: 7 | model = Testimonial 8 | fields = '__all__' 9 | -------------------------------------------------------------------------------- /backend/testimonials/tests.py: -------------------------------------------------------------------------------- 1 | # from django.test import TestCase 2 | 3 | # Create your tests here. 4 | -------------------------------------------------------------------------------- /backend/testimonials/urls.py: -------------------------------------------------------------------------------- 1 | from django.urls import path 2 | from .views import TestimonialListView 3 | 4 | urlpatterns = [ 5 | path('', TestimonialListView.as_view()), 6 | ] 7 | -------------------------------------------------------------------------------- /backend/testimonials/views.py: -------------------------------------------------------------------------------- 1 | from rest_framework.generics import ListAPIView 2 | from rest_framework import permissions 3 | 4 | from .models import Testimonial 5 | from .serializers import TestimonialSerializer 6 | 7 | 8 | class TestimonialListView(ListAPIView): 9 | permission_classes = (permissions.AllowAny, ) 10 | queryset = Testimonial.objects.all() 11 | serializer_class = TestimonialSerializer 12 | pagination_class = None 13 | -------------------------------------------------------------------------------- /frontend/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true, 5 | "jest/globals": true 6 | }, 7 | "extends": ["react-app", "prettier", "plugin:jest/recommended"], 8 | "parserOptions": { 9 | "ecmaVersion": "latest", 10 | "sourceType": "module", 11 | "ecmaFeatures": { 12 | "jsx": true 13 | } 14 | }, 15 | "plugins": ["react", "react-hooks", "jest"], 16 | "settings": { 17 | "react": { 18 | "version": "detect" 19 | } 20 | }, 21 | "rules": { 22 | //add customize rules here as per your project's needs 23 | "import/extensions": "off" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /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/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | [ 4 | '@babel/preset-react', 5 | { 6 | runtime: 'automatic', 7 | }, 8 | ], 9 | '@babel/preset-env', 10 | ], 11 | }; 12 | -------------------------------------------------------------------------------- /frontend/jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | roots: ['/src'], 3 | moduleDirectories: ['node_modules', './src'], 4 | moduleFileExtensions: ['js', 'jsx'], 5 | testEnvironment: 'jest-environment-jsdom', 6 | setupFilesAfterEnv: ['/jest.setup.js'], 7 | transform: { 8 | '^.+\\.(j|t)sx?$': 'babel-jest', 9 | }, 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.js', 14 | 15 | // Handle CSS imports (without CSS modules) 16 | '\\.(css|less)$': '/src/tests/mocks/fileMock.css', 17 | 18 | // Handle Leaflet maps 19 | 'react-leaflet': '/src/tests/mocks/reactLeafletMock.js', 20 | }, 21 | }; 22 | -------------------------------------------------------------------------------- /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": "freelance-developer-website", 3 | "version": "1.0.0", 4 | "description": "Freelance Developer Website", 5 | "author": "MG", 6 | "license": "MIT", 7 | "scripts": { 8 | "start": "react-scripts start", 9 | "build": "react-scripts build", 10 | "test": "jest", 11 | "coverage": "jest --coverage", 12 | "eject": "react-scripts eject", 13 | "prettier": "prettier \"**/*.+(json|js|jsx)\"", 14 | "format:check": "npm run prettier -- --check", 15 | "format:fix": "npm run prettier -- --write", 16 | "lint": "eslint --ext .js,.jsx ./src --no-error-on-unmatched-pattern", 17 | "lint:fix": "eslint --ext .js,.jsx ./src --no-error-on-unmatched-pattern --fix" 18 | }, 19 | "lint-staged": { 20 | "**/*.{js,jsx}": [ 21 | "npm run lint", 22 | "npm run format:check" 23 | ] 24 | }, 25 | "dependencies": { 26 | "@emotion/react": "^11.11.1", 27 | "@emotion/styled": "^11.11.0", 28 | "@fortawesome/fontawesome-svg-core": "^6.4.2", 29 | "@fortawesome/free-regular-svg-icons": "^6.4.2", 30 | "@fortawesome/free-solid-svg-icons": "^6.4.2", 31 | "@fortawesome/react-fontawesome": "^0.2.0", 32 | "@mui/icons-material": "^5.14.6", 33 | "@mui/material": "^5.14.6", 34 | "aos": "^2.3.4", 35 | "axios": "^1.4.0", 36 | "leaflet": "^1.9.4", 37 | "prop-types": "^15.8.1", 38 | "react": "^18.2.0", 39 | "react-dom": "^18.2.0", 40 | "react-helmet-async": "^1.3.0", 41 | "react-lazy-load-image-component": "^1.6.0", 42 | "react-leaflet": "^4.2.1", 43 | "react-router-dom": "^6.15.0", 44 | "react-scripts": "^5.0.1" 45 | }, 46 | "devDependencies": { 47 | "@babel/plugin-proposal-private-property-in-object": "^7.21.11", 48 | "@babel/preset-env": "^7.23.8", 49 | "@babel/preset-react": "^7.23.3", 50 | "@testing-library/jest-dom": "^6.2.0", 51 | "@testing-library/react": "^14.1.2", 52 | "@testing-library/user-event": "^14.5.2", 53 | "babel-jest": "^29.7.0", 54 | "eslint": "^8.56.0", 55 | "eslint-plugin-jest": "^27.6.2", 56 | "eslint-plugin-react": "^7.33.2", 57 | "eslint-plugin-react-hooks": "^4.6.0", 58 | "husky": "^8.0.3", 59 | "jest": "^29.7.0", 60 | "jest-canvas-mock": "^2.5.2", 61 | "jest-environment-jsdom": "^29.7.0", 62 | "lint-staged": "^15.2.0", 63 | "prettier": "^3.1.1" 64 | }, 65 | "browserslist": { 66 | "production": [ 67 | ">0.2%", 68 | "not dead", 69 | "not op_mini all" 70 | ], 71 | "development": [ 72 | "last 1 chrome version", 73 | "last 1 firefox version", 74 | "last 1 safari version" 75 | ] 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/freelance-developer-website/e86fd15632efbcfab6c4faf7cb94de0530fa776a/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Bob Developer 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | -------------------------------------------------------------------------------- /frontend/src/App.js: -------------------------------------------------------------------------------- 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 | import 'aos/dist/aos.css'; 6 | 7 | import theme from './theme/theme'; 8 | import Layout from './layout/Layout'; 9 | import Home from './pages/Home'; 10 | 11 | const App = () => { 12 | return ( 13 | 14 | 18 | 19 | 20 | 21 | 22 | 23 | } /> 24 | 25 | 26 | 27 | 28 | 29 | ); 30 | }; 31 | 32 | export default App; 33 | -------------------------------------------------------------------------------- /frontend/src/assets/images/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/freelance-developer-website/e86fd15632efbcfab6c4faf7cb94de0530fa776a/frontend/src/assets/images/bg.png -------------------------------------------------------------------------------- /frontend/src/assets/images/large.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/freelance-developer-website/e86fd15632efbcfab6c4faf7cb94de0530fa776a/frontend/src/assets/images/large.png -------------------------------------------------------------------------------- /frontend/src/assets/images/project1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/freelance-developer-website/e86fd15632efbcfab6c4faf7cb94de0530fa776a/frontend/src/assets/images/project1.png -------------------------------------------------------------------------------- /frontend/src/assets/images/project2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/freelance-developer-website/e86fd15632efbcfab6c4faf7cb94de0530fa776a/frontend/src/assets/images/project2.png -------------------------------------------------------------------------------- /frontend/src/assets/images/project3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/freelance-developer-website/e86fd15632efbcfab6c4faf7cb94de0530fa776a/frontend/src/assets/images/project3.png -------------------------------------------------------------------------------- /frontend/src/assets/images/project4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/freelance-developer-website/e86fd15632efbcfab6c4faf7cb94de0530fa776a/frontend/src/assets/images/project4.png -------------------------------------------------------------------------------- /frontend/src/assets/images/user1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/freelance-developer-website/e86fd15632efbcfab6c4faf7cb94de0530fa776a/frontend/src/assets/images/user1.png -------------------------------------------------------------------------------- /frontend/src/assets/images/user2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/freelance-developer-website/e86fd15632efbcfab6c4faf7cb94de0530fa776a/frontend/src/assets/images/user2.png -------------------------------------------------------------------------------- /frontend/src/assets/images/user3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BobsProgrammingAcademy/freelance-developer-website/e86fd15632efbcfab6c4faf7cb94de0530fa776a/frontend/src/assets/images/user3.png -------------------------------------------------------------------------------- /frontend/src/assets/logo/bootstrap.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/logo/css.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/logo/django.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/logo/git.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/logo/html.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/logo/javascript.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/logo/material-ui.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/logo/nextjs.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/logo/postgresql.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/logo/python.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/logo/react.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/assets/logo/tensorflow.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /frontend/src/components/About.js: -------------------------------------------------------------------------------- 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 Divider from '@mui/material/Divider'; 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 | const About = () => { 15 | const theme = useTheme(); 16 | const [about, setAbout] = useState([]); 17 | 18 | const fetchAbout = () => { 19 | axios 20 | .get('http://127.0.0.1:8000/about', { 21 | headers: { 22 | Accept: 'application/json', 23 | }, 24 | }) 25 | .then((response) => { 26 | setAbout(response.data); 27 | }) 28 | .catch((error) => console.log(error)); 29 | }; 30 | 31 | useEffect(() => { 32 | fetchAbout(); 33 | }, []); 34 | 35 | return ( 36 |
37 | 44 | 45 | 46 | 54 | About 55 | 56 | 64 | I am a freelance web developer who builds elegant, fast, and 65 | scalable web applications 66 | 67 | 68 | 69 | {about.map((item, i) => ( 70 | 71 | 83 | 96 | 97 | 105 | {item.icon} 106 | 107 | 112 | {item.title} 113 | 114 | 115 | {item.description} 116 | 117 | 118 | 119 | 120 | 121 | ))} 122 | 123 | 124 | 125 | 126 |
127 | ); 128 | }; 129 | 130 | export default About; 131 | -------------------------------------------------------------------------------- /frontend/src/components/Contact.js: -------------------------------------------------------------------------------- 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 Grid from '@mui/material/Grid'; 8 | import ListItem from '@mui/material/ListItem'; 9 | import ListItemAvatar from '@mui/material/ListItemAvatar'; 10 | import ListItemText from '@mui/material/ListItemText'; 11 | import Typography from '@mui/material/Typography'; 12 | import { useTheme } from '@mui/material/styles'; 13 | 14 | // Material Icons 15 | import EmailIcon from '@mui/icons-material/Email'; 16 | import LocationIcon from '@mui/icons-material/LocationOn'; 17 | import PhoneIcon from '@mui/icons-material/Phone'; 18 | 19 | import Map from './Map'; 20 | 21 | const Contact = () => { 22 | const theme = useTheme(); 23 | 24 | const [contact, setContact] = useState([]); 25 | 26 | const fetchContact = () => { 27 | axios 28 | .get('http://127.0.0.1:8000/contact', { 29 | headers: { 30 | Accept: 'application/json', 31 | }, 32 | }) 33 | .then((response) => { 34 | setContact(response.data); 35 | }) 36 | .catch((error) => console.log(error)); 37 | }; 38 | 39 | useEffect(() => { 40 | fetchContact(); 41 | }, []); 42 | 43 | return ( 44 |
45 | 46 | 54 | 55 | 63 | Get in touch 64 | 65 | 73 | Do you need a full-stack web developer? I would love to hear from 74 | you. 75 | 76 | 77 | {contact.slice(0, 1).map((item, i) => ( 78 | 79 | 80 | 81 | 87 | Contact details 88 | 89 | 90 | 95 | 103 | 108 | 115 | 116 | 117 | 118 | 119 | 120 | 128 | 133 | 140 | 141 | 142 | 143 | 144 | 145 | 152 | 157 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | ))} 176 | 177 | 178 |
179 | ); 180 | }; 181 | 182 | export default Contact; 183 | -------------------------------------------------------------------------------- /frontend/src/components/CustomButton.js: -------------------------------------------------------------------------------- 1 | // Material UI 2 | import Button from '@mui/material/Button'; 3 | import { useTheme } from '@mui/material/styles'; 4 | 5 | const CustomButton = ({ href, icon, text }) => { 6 | const theme = useTheme(); 7 | 8 | return ( 9 | 34 | ); 35 | }; 36 | 37 | export default CustomButton; 38 | -------------------------------------------------------------------------------- /frontend/src/components/Hero.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import { LazyLoadImage } from 'react-lazy-load-image-component'; 3 | import AOS from 'aos'; 4 | import axios from 'axios'; 5 | 6 | // Material UI 7 | import Box from '@mui/material/Box'; 8 | import Divider from '@mui/material/Divider'; 9 | import Grid from '@mui/material/Grid'; 10 | import Typography from '@mui/material/Typography'; 11 | import useMediaQuery from '@mui/material/useMediaQuery'; 12 | import { useTheme } from '@mui/material/styles'; 13 | 14 | import HeroButtons from './HeroButtons'; 15 | 16 | const Hero = () => { 17 | const theme = useTheme(); 18 | const isMd = useMediaQuery(theme.breakpoints.up('md'), { 19 | defaultMatches: true, 20 | }); 21 | 22 | const [hero, setHero] = useState([]); 23 | 24 | const fetchHero = () => { 25 | axios 26 | .get('http://127.0.0.1:8000/hero', { 27 | headers: { 28 | Accept: 'application/json', 29 | }, 30 | }) 31 | .then((response) => { 32 | setHero(response.data); 33 | }) 34 | .catch((error) => console.log(error)); 35 | }; 36 | 37 | useEffect(() => { 38 | fetchHero(); 39 | }, []); 40 | 41 | useEffect(() => { 42 | AOS.init({ 43 | once: true, 44 | delay: 50, 45 | duration: 600, 46 | easing: 'ease-in-out', 47 | }); 48 | }, []); 49 | 50 | return ( 51 |
52 | 59 | {hero.slice(0, 1).map((item, i) => ( 60 | 61 | 62 | 63 | 64 | 71 | {item.title}{' '} 72 | 73 | 80 | {item.subtitle} 81 | 82 | 83 | 84 | 90 | {item.description} 91 | 92 | 93 | 94 | 95 | 96 | 105 | 117 | 128 | 129 | 130 | 131 | ))} 132 | 133 | 134 |
135 | ); 136 | }; 137 | 138 | export default Hero; 139 | -------------------------------------------------------------------------------- /frontend/src/components/HeroButtons.js: -------------------------------------------------------------------------------- 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 HelpIcon from '@mui/icons-material/HelpOutlineOutlined'; 9 | import ManageSearchIcon from '@mui/icons-material/ManageSearchOutlined'; 10 | 11 | const HeroButtons = () => { 12 | const theme = useTheme(); 13 | const isMd = useMediaQuery(theme.breakpoints.up('md'), { 14 | defaultMatches: true, 15 | }); 16 | 17 | return ( 18 | <> 19 | 26 | 47 | 52 | 72 | 73 | 74 | 75 | ); 76 | }; 77 | 78 | export default HeroButtons; 79 | -------------------------------------------------------------------------------- /frontend/src/components/Map.js: -------------------------------------------------------------------------------- 1 | import 'leaflet/dist/leaflet.css'; 2 | import { MapContainer, Circle, TileLayer } from 'react-leaflet'; 3 | import { useState, useEffect } from 'react'; 4 | import { useTheme } from '@mui/material/styles'; 5 | 6 | const Map = ({ coordinates, zoom }) => { 7 | const theme = useTheme(); 8 | const position = coordinates; 9 | const fillBlueOptions = { fillColor: theme.palette.primary.main }; 10 | const [map, setMap] = useState(null); 11 | 12 | useEffect(() => { 13 | if (map) { 14 | setInterval(function () { 15 | map.invalidateSize(); 16 | }, 100); 17 | } 18 | }, [map]); 19 | 20 | return ( 21 | 28 | 32 | 33 | 34 | ); 35 | }; 36 | 37 | export default Map; 38 | -------------------------------------------------------------------------------- /frontend/src/components/Projects.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import { LazyLoadImage } from 'react-lazy-load-image-component'; 3 | import axios from 'axios'; 4 | 5 | // Material UI 6 | import Box from '@mui/material/Box'; 7 | import Button from '@mui/material/Button'; 8 | import Card from '@mui/material/Card'; 9 | import CardContent from '@mui/material/CardContent'; 10 | import Chip from '@mui/material/Chip'; 11 | import Divider from '@mui/material/Divider'; 12 | import Grid from '@mui/material/Grid'; 13 | import Typography from '@mui/material/Typography'; 14 | import { useTheme } from '@mui/material/styles'; 15 | 16 | const Projects = () => { 17 | const theme = useTheme(); 18 | 19 | const [projects, setProjects] = useState([]); 20 | 21 | const fetchProjects = () => { 22 | axios 23 | .get('http://127.0.0.1:8000/projects', { 24 | headers: { 25 | Accept: 'application/json', 26 | }, 27 | }) 28 | .then((response) => { 29 | setProjects(response.data); 30 | }) 31 | .catch((error) => console.log(error)); 32 | }; 33 | 34 | useEffect(() => { 35 | fetchProjects(); 36 | }, []); 37 | 38 | return ( 39 |
40 | 47 | 48 | 56 | Projects 57 | 58 | 66 | View some of my latest projects 67 | 68 | 69 | 70 | {projects.map((item, i) => ( 71 | 72 | 84 | 97 | 106 | 124 | 125 | 135 | 141 | {item.name} 142 | 143 | 144 | {item.description} 145 | 146 | 147 | {item.tags.map((tag, i) => ( 148 | 166 | ))} 167 | 168 | 169 | 194 | 195 | 196 | 197 | 198 | 199 | ))} 200 | 201 | 202 | 203 |
204 | ); 205 | }; 206 | 207 | export default Projects; 208 | -------------------------------------------------------------------------------- /frontend/src/components/Technologies.js: -------------------------------------------------------------------------------- 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 CardContent from '@mui/material/CardContent'; 9 | import Divider from '@mui/material/Divider'; 10 | import Grid from '@mui/material/Grid'; 11 | import Typography from '@mui/material/Typography'; 12 | import { useTheme } from '@mui/material/styles'; 13 | 14 | const Technologies = () => { 15 | const theme = useTheme(); 16 | 17 | const [technologies, setTechnologies] = useState([]); 18 | 19 | const fetchTechnologies = () => { 20 | axios 21 | .get('http://127.0.0.1:8000/technologies', { 22 | headers: { 23 | Accept: 'application/json', 24 | }, 25 | }) 26 | .then((response) => { 27 | setTechnologies(response.data); 28 | }) 29 | .catch((error) => console.log(error)); 30 | }; 31 | 32 | useEffect(() => { 33 | fetchTechnologies(); 34 | }, []); 35 | 36 | return ( 37 |
38 | 45 | 46 | 47 | 55 | Technologies 56 | 57 | 65 | Technologies I use to build web applications 66 | 67 | 68 | 69 | {technologies.map((item, i) => ( 70 | 71 | 86 | 97 | 98 | 108 | 109 | 114 | {item.name} 115 | 116 | 117 | 118 | 119 | ))} 120 | 121 | 122 | 123 | 124 |
125 | ); 126 | }; 127 | 128 | export default Technologies; 129 | -------------------------------------------------------------------------------- /frontend/src/components/Testimonials.js: -------------------------------------------------------------------------------- 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 Divider from '@mui/material/Divider'; 9 | import Grid from '@mui/material/Grid'; 10 | import ListItem from '@mui/material/ListItem'; 11 | import ListItemAvatar from '@mui/material/ListItemAvatar'; 12 | import ListItemText from '@mui/material/ListItemText'; 13 | import Typography from '@mui/material/Typography'; 14 | import { useTheme } from '@mui/material/styles'; 15 | 16 | // Font Awesome Icons 17 | import { library } from '@fortawesome/fontawesome-svg-core'; 18 | import { faCommentDots } from '@fortawesome/free-solid-svg-icons'; 19 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 20 | library.add(faCommentDots); 21 | 22 | const Testimonials = () => { 23 | const theme = useTheme(); 24 | const [testimonials, setTestimonials] = useState([]); 25 | 26 | const fetchTestimonials = () => { 27 | axios 28 | .get('http://127.0.0.1:8000/testimonials', { 29 | headers: { 30 | Accept: 'application/json', 31 | }, 32 | }) 33 | .then((response) => { 34 | setTestimonials(response.data); 35 | }) 36 | .catch((error) => console.log(error)); 37 | }; 38 | 39 | useEffect(() => { 40 | fetchTestimonials(); 41 | }, []); 42 | 43 | return ( 44 |
45 | 52 | 53 | 54 | 62 | Testimonials 63 | 64 | 72 | Check what clients have said about my work 73 | 74 | 75 | 76 | {testimonials.map((item, i) => ( 77 | 78 | 90 | 102 | 103 | 104 | 112 | 113 | 117 | {item.testimonial} 118 | 119 | 124 | 125 | 126 | 127 | 132 | 133 | 134 | 135 | 136 | 137 | ))} 138 | 139 | 140 | 141 | 142 |
143 | ); 144 | }; 145 | 146 | export default Testimonials; 147 | -------------------------------------------------------------------------------- /frontend/src/index.js: -------------------------------------------------------------------------------- 1 | import { createRoot } from 'react-dom/client'; 2 | 3 | import App from './App'; 4 | 5 | const root = createRoot(document.getElementById('root')); 6 | root.render(); 7 | -------------------------------------------------------------------------------- /frontend/src/layout/Footer.js: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react'; 2 | import axios from 'axios'; 3 | 4 | // Material UI 5 | import Box from '@mui/material/Box'; 6 | import Divider from '@mui/material/Divider'; 7 | import Grid from '@mui/material/Grid'; 8 | import Hidden from '@mui/material/Hidden'; 9 | import Link from '@mui/material/Link'; 10 | import List from '@mui/material/List'; 11 | import ListItemButton from '@mui/material/ListItemButton'; 12 | import ListItemText from '@mui/material/ListItemText'; 13 | import Typography from '@mui/material/Typography'; 14 | import { useTheme } from '@mui/material/styles'; 15 | 16 | const Footer = () => { 17 | const theme = useTheme(); 18 | 19 | const [footer, setFooter] = useState([]); 20 | 21 | const fetchFooter = () => { 22 | axios 23 | .get('http://127.0.0.1:8000/footer', { 24 | headers: { 25 | Accept: 'application/json', 26 | }, 27 | }) 28 | .then((response) => { 29 | setFooter(response.data); 30 | }) 31 | .catch((error) => console.log(error)); 32 | }; 33 | 34 | useEffect(() => { 35 | fetchFooter(); 36 | }, []); 37 | 38 | return ( 39 | <> 40 | 50 | 51 | 56 | {footer.slice(0, 1).map((item, index) => ( 57 | 58 | 59 | 60 | 67 | 68 | 74 | Privacy Policy 75 | 76 | } 77 | /> 78 | 79 | 80 | 86 | Terms of Use 87 | 88 | } 89 | /> 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 103 | Copyright © {new Date().getFullYear()}{' '} 104 | {item.copyright}. 105 | 106 | } 107 | /> 108 | 109 | 110 | 111 | 112 | 113 | 114 | 120 | Photo by{' '} 121 | 128 | {item.image_author_name} 129 | {' '} 130 | on{' '} 131 | 138 | {item.image_website_name} 139 | 140 | . 141 | 142 | } 143 | /> 144 | 145 | 146 | 147 | 148 | ))} 149 | 150 | 151 | 152 | ); 153 | }; 154 | 155 | export default Footer; 156 | -------------------------------------------------------------------------------- /frontend/src/layout/Header.js: -------------------------------------------------------------------------------- 1 | import PropTypes from 'prop-types'; 2 | 3 | // Material UI 4 | import AppBar from '@mui/material/AppBar'; 5 | import Box from '@mui/material/Box'; 6 | import Divider from '@mui/material/Divider'; 7 | import IconButton from '@mui/material/IconButton'; 8 | import Link from '@mui/material/Link'; 9 | import Toolbar from '@mui/material/Toolbar'; 10 | import Typography from '@mui/material/Typography'; 11 | import useScrollTrigger from '@mui/material/useScrollTrigger'; 12 | import { useTheme } from '@mui/material/styles'; 13 | 14 | // Material Icons 15 | import InstagramIcon from '@mui/icons-material/Instagram'; 16 | import LinkedInIcon from '@mui/icons-material/LinkedIn'; 17 | import MenuIcon from '@mui/icons-material/Menu'; 18 | import YouTubeIcon from '@mui/icons-material/YouTube'; 19 | 20 | // Font Awesome Icons 21 | import { library } from '@fortawesome/fontawesome-svg-core'; 22 | import { faLaptopCode } from '@fortawesome/free-solid-svg-icons'; 23 | import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; 24 | library.add(faLaptopCode); 25 | 26 | import CustomButton from '../components/CustomButton'; 27 | 28 | const Header = ({ onSidebarOpen }) => { 29 | const theme = useTheme(); 30 | const trigger = useScrollTrigger({ 31 | disableHysteresis: true, 32 | threshold: 38, 33 | }); 34 | 35 | return ( 36 | <> 37 | 48 | 49 | onSidebarOpen()} 51 | aria-label='Menu' 52 | sx={{ 53 | color: theme.palette.primary.main, 54 | display: { xs: 'block', md: 'none' }, 55 | }} 56 | > 57 | 58 | 59 | 60 | 61 | 69 | 81 | Bob Developer 82 | 83 | 84 | 85 | 86 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 107 | 108 | 116 | 117 | 118 | 126 | 127 | 128 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | ); 143 | }; 144 | 145 | Header.propTypes = { 146 | onSidebarOpen: PropTypes.func, 147 | }; 148 | 149 | export default Header; 150 | -------------------------------------------------------------------------------- /frontend/src/layout/Layout.js: -------------------------------------------------------------------------------- 1 | import { useState } from 'react'; 2 | import PropTypes from 'prop-types'; 3 | 4 | // Material UI 5 | import Box from '@mui/material/Box'; 6 | import Divider from '@mui/material/Divider'; 7 | import Fab from '@mui/material/Fab'; 8 | import NoSsr from '@mui/material/NoSsr'; 9 | import Zoom from '@mui/material/Zoom'; 10 | import useMediaQuery from '@mui/material/useMediaQuery'; 11 | import useScrollTrigger from '@mui/material/useScrollTrigger'; 12 | import { useTheme } from '@mui/material/styles'; 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 | const Layout = ({ children }) => { 20 | const theme = useTheme(); 21 | const isMd = useMediaQuery(theme.breakpoints.up('md'), { 22 | defaultMatches: true, 23 | }); 24 | 25 | const [openSidebar, setOpenSidebar] = useState(false); 26 | 27 | const handleSidebarOpen = () => { 28 | setOpenSidebar(true); 29 | }; 30 | 31 | const handleSidebarClose = () => { 32 | setOpenSidebar(false); 33 | }; 34 | 35 | const open = isMd ? false : openSidebar; 36 | 37 | const trigger = useScrollTrigger({ 38 | disableHysteresis: true, 39 | threshold: 100, 40 | }); 41 | 42 | const scrollTo = (id) => { 43 | setTimeout(() => { 44 | const element = document.querySelector(`#${id}`); 45 | if (!element) { 46 | return; 47 | } 48 | window.scrollTo({ left: 0, top: element.offsetTop, behavior: 'smooth' }); 49 | }); 50 | }; 51 | 52 | return ( 53 | 60 |
61 | 62 | 70 | {children} 71 | 72 | 73 |