Compare commits

...

10 Commits

336 changed files with 50188 additions and 383 deletions

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
all_paw_care/invoice_ninja
all_paw_care/templates/markdown_content
__pycache__
*.py[cod]
.env*
!.env.project
!.env.vault

View File

@ -1,33 +1,21 @@
IP_ADDR = allpawcare.com # Scripts directory variable
DOMAIN = allpawcare.com SCRIPTS_DIR = "scripts"
USER = awkawb
IMG_ORIGINAL = "img_original" # Bootstrap variables
IMG_PUBLIC = "all_paw_care/static/img" BOOTSTRAP_MODULE = "bootstrap-custom/boostrap/scss"
LOCAL_ARCHIVE = $(shell pwd)/public.tar CUSTOM_SCSS = "bootstrap-custom/custom.scss"
PUBLIC_DIR = "$(shell pwd)/public" PUBLIC_CSS = "all_paw_care/static/css/custom.css"
REMOTE_DIR = /home/awkawb
.PHONY: dev-server .PHONY: dev-server
dev-server: dev-server:
flask run --no-reload flask run --no-reload
.PHONY: build-custom-bootstrap
build-custom-bootstrap:
sass -I $(BOOTSTRAP_MODULE) $(CUSTOM_SCSS) $(PUBLIC_CSS)
.PHONY: process-images .PHONY: process-images
process-images: process-images:
for ./$(SCRIPTS_DIR)/process-images.sh
.PHONY: push-archive
push-archive: archive
scp $(LOCAL_ARCHIVE) root@$(DOMAIN):/srv/www && rm $(LOCAL_ARCHIVE)
.PHONY: archive
archive:
tar -cf $(LOCAL_ARCHIVE) $(PUBLIC_DIR)
.PHONY: ssh
ssh:
ssh $(USER)@$(DOMAIN)
.PHONY: ssh-root
ssh-root:
ssh root@$(DOMAIN)

21
TODO.md Normal file
View File

@ -0,0 +1,21 @@
# Code clean-up
## scripts dir
move process-images.sh here
# Services/<service>
Route to service documentation page.
# Hand written typography
# Hand drawn doodles
# Banner for accepting/not accepting clients
Let users of the site know if I'm seeking new clients.

View File

@ -4,9 +4,6 @@ from all_paw_care.pages import pages
from all_paw_care.users import users from all_paw_care.users import users
from all_paw_care.db.actions import ensure_tables from all_paw_care.db.actions import ensure_tables
# App config
from config import Config
# Flask # Flask
from flask import Flask from flask import Flask
@ -16,10 +13,10 @@ from sqlalchemy.orm import Session
def create_app(): def create_app():
# Initialize app # Initialize app
all_paw_care = Flask(__name__) #
# Learn more about instance folders here:
# Import config # https://flask.palletsprojects.com/en/3.0.x/config/#instance-folders
all_paw_care.config.from_object(Config) all_paw_care = Flask(__name__, instance_relative_config=True)
# Register app blueprints # Register app blueprints
all_paw_care.register_blueprint(pages) all_paw_care.register_blueprint(pages)

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,4 @@
from all_paw_care.content.pages import home
from all_paw_care.content.pages import faqs
from all_paw_care.content.pages import services

View File

@ -0,0 +1,28 @@
import os
from markdown import markdown
import frontmatter
markdown_dir = '{}/markdown'.format(
os.path.dirname(os.path.abspath(__file__)))
def __get_content_dict():
faqs = dict()
for f in os.listdir(markdown_dir):
f_abspath = '{}/{}'.format(markdown_dir, f)
matter = frontmatter.load(f_abspath)
faq = {matter.metadata['question']: {
'tag': matter.metadata['tag'],
'answer': markdown(matter.content)}}
faqs.update(faq)
return faqs
content = __get_content_dict()
def get_content():
return content
title = 'Frequently asked questions'
def get_title():
return title

View File

@ -0,0 +1,23 @@
from markdown import markdown
import frontmatter
import os
class FAQ(object):
def __init__(self, markdown_file: str):
with frontmatter.load(markdown_file) as matter:
metadata = matter.metadata
self.answer = markdown(matter.content)
self.question = metadata['question']
self.tag = metadata['tag']
def get_question(self):
return self.question
def get_answer_html(self):
return self.answer
def get_tag(self):
return self.tag

View File

@ -0,0 +1,15 @@
---
tag: accepted-payments-faq
question: What types of payments do you accept?
---
A broad range of payment types are accepted to make the process
easy. Below is a list of what is accepted:
- cash
- credit/debit
- ACH (bank transfers)
- Paypal
- Apple Pay
- Zelle

View File

@ -0,0 +1,11 @@
---
tag: animal-types-faq
question: What kind of animals do you caretake?
---
I have experience caring for many types of animals. Below is a
non-exhaustive list of animals I have experience with:
- reptiles (e.g. snakes, lizards, and spiders)
- mammals (e.g. cats, dogs, and guinea pigs)

View File

@ -0,0 +1,10 @@
---
tag: basics-faq
question: How does this process work?
---
First go to the [services](/pages/services#request-meet-greet) page. On the bottom
of the page, you'll find a "Request meet and & greet" button. After
filling out the form, I'll get an email with the form information.
I will contact you about the request within 24 hours.

View File

@ -0,0 +1,8 @@
---
tag: house-sitting-faq
question: Can you watch my pets while I'm away?
---
I offer house sitting services. The service includes overnight
in-home stays with your pet.

View File

@ -0,0 +1,8 @@
---
tag: payment-terms-faq
question: When is payment for a service due?
---
Payments are due 7 days after invoicing. Invoices are typically sent
after services are completed.

View File

@ -0,0 +1,8 @@
---
tag: plant-faq
question: I have plants, can you care for them while I'm away?
---
I'll gladly make sure your plants stay healthy while your gone. I love
plants and have a collection of my own.

View File

@ -0,0 +1,8 @@
---
tag: poop-scoop-faq
question: My yard is a mess, can you remove the dog poop?
---
I do offer dog poop clean-up as an add-on services. I can provide
this service along with a scheduled primary service.

View File

@ -0,0 +1,8 @@
---
tag: special-needs-faq
question: Can you care for special needs pets?
---
I have experience caring for animals with special needs, including
older pets.

View File

@ -0,0 +1,31 @@
from flask import url_for
from markdown import markdown
import frontmatter
import os
markdown_dir = '{}/markdown'.format(
os.path.dirname(os.path.abspath(__file__)))
title = 'About me'
def get_title():
return title
def __get_content_dict():
sections = dict()
for file in os.listdir(markdown_dir):
images = list()
matter = frontmatter.load('{}/{}'.format(markdown_dir, file))
for img in matter.metadata['images']:
images.append('{}/{}'.format(
matter.metadata['image-dir'], img))
sections[file.removesuffix('.md')] = {'images':
images, 'content': markdown(matter.content)}
return sections
content = __get_content_dict()
def get_content():
return content.values()

View File

@ -0,0 +1,16 @@
---
image-dir: img/about_me
images:
- about_me_1_sm.jpg
- about_me_1_md.jpg
- about_me_1_lg.jpg
- about_me_1_xl.jpg
- about_me_1_xxl.jpg
---
Before I provided animal caretaking services, I focused on my interest
in computer technologies. I studied computer networking and computer
science at [AACC](https://www.aacc.edu). The knowledge I gained from my
studies was harnessed writing the code that powers this website and
configuring the servers that the code runs on.

View File

@ -0,0 +1,18 @@
---
image-dir: img/about_me
images:
- about_me_2_sm.jpg
- about_me_2_md.jpg
- about_me_2_lg.jpg
- about_me_2_xl.jpg
- about_me_2_xxl.jpg
---
Plants and horticulture are a passion of mine, too. My interest has
actively grown since starting caretaking services. Caring for
animals in the Baltimore area, I have the opportunity of observing
a broad range of vegetation. I've collected cuttings for propagation
and many survive and thrive enough to make it to my garden or potted
house plants. Succulent type plants are a favorite of mine and I have
a ever growing collection.

View File

@ -0,0 +1,17 @@
---
image-dir: img/about_me
images:
- about_me_3_sm.jpg
- about_me_3_md.jpg
- about_me_3_lg.jpg
- about_me_3_xl.jpg
- about_me_3_xxl.jpg
---
I've been lucky enough to have the opportunity to grow my animal
caretaking services to my primary source of income. I started
listing availability on the [Rover](https://www.rover.com/) platform
in 2018. After gaining loyal clients and reputation, moving my services
to being independent was a logical move. I really love providing great
and affordable caretaking services.

View File

@ -0,0 +1,25 @@
from all_paw_care.content.pages.services.cards import primary_services
#def __get_content_dict():
# content = {'primary-services': dict(), 'addon-services': dict()}
# for file in os.listdir(markdown_dir):
# images = list()
# matter = frontmatter.load('{}/{}'.format(markdown_dir, file))
# if matter.metadata['type'] == 'primary':
# for img in matter.metadata['images']:
# images.append('{}/{}'.format(
# matter.metadata['image-dir'], img))
#
# content['primary-services'].update()
#
# return content
#
#content = __get_content_dict()
#
__title = 'Services & Pricing'
def get_title():
return __title
def get_primary_service_cards():
return primary_services

View File

@ -0,0 +1,6 @@
from all_paw_care.content.pages.services.cards.drop_ins import drop_ins
from all_paw_care.content.pages.services.cards.house_sitting import house_sitting
from all_paw_care.content.pages.services.cards.walking import walking
primary_services = [drop_ins, house_sitting, walking]

View File

@ -0,0 +1,37 @@
from markdown import markdown
import frontmatter
import os
class Card(object):
def __init__(self, card_file: str):
metadata = frontmatter.load(card_file).metadata
self.service_type = metadata['type']
self.title = metadata['title']
self.images = self._get_image_list(
metadata['image-dir'], metadata['images'])
self.price = metadata['price']
self.included = metadata['included']
def _get_image_list(self, base_dir: str, images: list):
image_list = list()
for img in images:
image_list.append(os.path.join(base_dir, img))
return image_list
def get_title(self):
return self.title
def get_price(self):
return self.price
def get_included(self):
return self.included
def get_images(self):
return self.images
def get_default_image(self):
return self.images[1]

View File

@ -0,0 +1,8 @@
from all_paw_care.content.pages.services.cards.card import Card
import os
__markdown_file = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'drop-ins.md')
drop_ins = Card(__markdown_file)

View File

@ -0,0 +1,16 @@
---
type: primary
title: Drop-ins
image-dir: img/services
images:
- drop_in_card_sm.png
- drop_in_card_md.png
- drop_in_card_lg.png
- drop_in_card_xl.png
- drop_in_card_xxl.png
price: $20/visit
included:
- 30 minute at home visit
- Bags for poop clean-up
---

View File

@ -0,0 +1,8 @@
from all_paw_care.content.pages.services.cards.card import Card
import os
__markdown_file = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'house-sitting.md')
house_sitting = Card(__markdown_file)

View File

@ -0,0 +1,17 @@
---
type: primary
title: House sitting
image-dir: img/services
images:
- house_sitting_card_sm.png
- house_sitting_card_md.png
- house_sitting_card_lg.png
- house_sitting_card_xl.png
- house_sitting_card_xxl.png
price: $38/night
included:
- Animal care
- At home overnight stays
- Bags for poop clean-up
---

View File

@ -0,0 +1,11 @@
from all_paw_care.content.pages.services.cards.card import Card
from markdown import markdown
import frontmatter
import os
__markdown_file = os.path.join(os.path.abspath(os.path.dirname(__file__)), 'walking.md')
walking = Card(__markdown_file)

View File

@ -0,0 +1,16 @@
---
type: primary
title: Walking
image-dir: img/services
images:
- dog_walking_card_sm.png
- dog_walking_card_md.png
- dog_walking_card_lg.png
- dog_walking_card_xl.png
- dog_walking_card_xxl.png
price: $20/visit
included:
- 30 minute walk
- Bags for poop clean-up
---

View File

@ -0,0 +1,7 @@
---
title: Services & Pricing
---
Below are animal caretaking services I provide. New clients should
fill out the [meet & greet request](/forms/meet_and_gree) form

View File

@ -1,25 +1,10 @@
# Python Invoice Ninja library
#from all_paw_care.invoice_ninja import swagger_client
#from all_paw_care.swagger_client.rest import ApiException
# Invoice Ninja library dependencies
#from __future__ import print_function
#import time
#from pprint import pprint
BASE_URL='https://invoice.allpawcare.com/api/v1' BASE_URL='https://invoice.allpawcare.com/api/v1'
API_TOKEN='r4AKEz9xRgk2SA7IotAvLrj64f7s0BczLDVjVwmjiPWeyG0fpu2eyib4VKI23QNO' API_TOKEN='r4AKEz9xRgk2SA7IotAvLrj64f7s0BczLDVjVwmjiPWeyG0fpu2eyib4VKI23QNO'
INVOICE_NINJA_USERNAME = 'billing@allpawcare.com' INVOICE_NINJA_USERNAME = 'billing@allpawcare.com'
INVOICE_NINJA_PASSWORD = """sw'1eMqN6#9fO!3"RY$L""" INVOICE_NINJA_PASSWORD = """sw'1eMqN6#9fO!3"RY$L"""
INVOICE_NINJA_LOGIN_DICT = { INVOICE_NINJA_LOGIN_DICT = {
'email': INVOICE_NINJA_USERNAME, 'email': INVOICE_NINJA_USERNAME,
'pasword': INVOICE_NINJA_PASSWORD 'pasword': INVOICE_NINJA_PASSWORD
} }
#invoice_ninja = swagger_client()

View File

@ -0,0 +1,94 @@
from all_paw_care.invoice_ninja import API_TOKEN
from all_paw_care.invoice_ninja import BASE_URL
from all_paw_care.invoice_ninja.types.client import Client
import requests
class Clients(object):
CLIENTS_URL = '{}/clients'.format(BASE_URL)
HEADERS = {'X-API-TOKEN': API_TOKEN}
def __build_sort_params(self, sort: dict):
sort_params = {'sort': str()}
is_first_entry = True
for option in sort.keys():
if is_first_entry:
sort_params['sort'] += '{}|{}'.format(option, sort[option])
is_first_entry = False
else:
sort_params['sort'] += ' {}|{}'.format(option, sort[option])
return sort_params
def __client_from_dict(self, client: dict):
return Client(client_id=client['id'],
name=client['name'],
address=client['address1'],
city=client['city'],
state=client['state'],
postal_code=client['postal_code'],
phone=client['phone'],
email=client['contacts'][0]['email'],
pets=client['custom_value1'])
def __clients_from_response(self, response: requests.Response):
clients = list()
for client_dict in response.json()['data']:
clients.append(self.__client_from_dict(client_dict))
return clients
def list_client(self, client_id: str = None):
"""
Get client based on client id.
"""
if client_id:
request_url = '{}/{}'.format(self.CLIENTS_URL, client_id)
response = requests.get(url=request_url,
headers=self.HEADERS)
if response.ok:
return self.__client_from_dict(response.json()['data'])
return None
def list_clients(self, include: str = 'activities',
sort: dict = dict(), status: str = 'active',
name: str = None):
"""
Finds Invoice Ninja clients.
"""
request_params = dict()
# Add sort parameters to request
if len(sort) > 0:
request_params.update(self.__build_sort_params(sort))
# Add include parameters to request
request_params.update({'include': include})
# Add status parameters to request
request_params.update({'status': status})
# Add name parameters to request
if name:
request_params.update({'name': name})
# Check is request should be sent with parameters
if len(request_params) > 0:
response = requests.get(url=self.CLIENTS_URL,
params=request_params,
headers=self.HEADERS)
else:
response = requests.get(url=self.CLIENTS_URL,
headers=self.HEADERS)
if response.ok:
return self.__clients_from_response(response)
else:
return None

View File

@ -1,15 +0,0 @@
class Sort(object):
def __init__(self, id: str = None,
name: str = None,
balance: str = None):
self.sort_params = {'sort': str()}
is_first_entry = True
if id:
if is_first_entry:
self.sort_params['sort'] += '{}|{}'.format(option, sort[option])
is_first_entry = False
else:
self.sort_params['sort'] += ' {}|{}'.format(option, sort[option])

View File

@ -1,10 +1,10 @@
from all_paw_care.invoice_ninja import API_TOKEN from all_paw_care.invoice_ninja import API_TOKEN
from all_paw_care.invoice_ninja import BASE_URL from all_paw_care.invoice_ninja import BASE_URL
from all_paw_care.invoice_ninja.mappings.client import Client from all_paw_care.invoice_ninja.types.product import Product
import requests import requests
class Clients(object): class Products(object):
CLIENTS_URL = '{}/clients'.format(BASE_URL) CLIENTS_URL = '{}/clients'.format(BASE_URL)
HEADERS = {'X-API-TOKEN': API_TOKEN} HEADERS = {'X-API-TOKEN': API_TOKEN}

View File

@ -1,17 +1,18 @@
from all_paw_care.pages import pages from all_paw_care.pages import pages
from all_paw_care.content import pages as site_pages
from flask import render_template,url_for from flask import render_template,url_for
@pages.route('/') @pages.route('/')
@pages.route('/home') @pages.route('/home')
def home(): def home():
return render_template("pages/index.html") return render_template("pages/index.html", page=site_pages.home)
@pages.route('/faq') @pages.route('/faq')
def faq(): def faq():
return render_template("pages/faq.html") return render_template("pages/faq.html", page=site_pages.faqs)
@pages.route('/services') @pages.route('/services')
def services(): def services():
return render_template("pages/services.html") return render_template("pages/services.html", page=site_pages.services)

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

After

Width:  |  Height:  |  Size: 76 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

After

Width:  |  Height:  |  Size: 102 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

After

Width:  |  Height:  |  Size: 99 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.9 KiB

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 67 KiB

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 50 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

View File

@ -1,10 +1,14 @@
<footer class="d-flex flex-wrap justify-content-between align-items-center py-3 my-4 border-top"> <footer class="footer mt-5 bg-tertiary">
<div class="container">
<nav class="navbar navbar-expand-sm text-light">
<div class="container">
<p class="col-md-4 mb-0 text-muted"> <p class="col-md-4 mb-0 text-muted">
© {{ current_year }} Andrew Bryant © {{ current_year }} Andrew Bryant
</p> </p>
<nav class="navbar navbar-expand-lg navbar-light bg-light">
{% include "jinja/menu.html" %} {% include "jinja/menu.html" %}
</div>
</nav> </nav>
</div>
</footer> </footer>

View File

@ -1,8 +1,18 @@
<header> <header>
<nav class="navbar navbar-expand-md sticky-top navbar-dark bg-dark"> <nav class="navbar navbar-expand-sm sticky-top bg-light" data-bs-theme="light">
<div class="container-fluid"> <div class="container">
<button class="navbar-toggler" type="button"
data-bs-toggle="collapse"
data-bs-target="#menu-bar-items"
aria-controls="menu-bar-items"
aria-expanded="false"
aria-label="Toggle menu bar">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="menu-bar-items">
{% include "jinja/menu.html" %} {% include "jinja/menu.html" %}
</div> </div>
</div>
</nav> </nav>
</header> </header>

View File

@ -0,0 +1,19 @@
{% for page in pages %}
<li>
{% if title == page %}
<a href="{{ pages[page] }}" class="nav-link text-secondary">
<svg class="bi d-block mx-auto mb-1" width="24" height="24">
<use xlink:href="#home"></use>
</svg>
{{ page }}
</a>
{% else %}
<a href="{{ pages[page] }}" class="nav-link text-white">
<svg class="bi d-block mx-auto mb-1" width="24" height="24">
<use xlink:href="#home"></use>
</svg>
{{ page }}
</a>
{% endif %}
</li>
{% endfor %}

View File

@ -1,30 +1,30 @@
<ul class="navbar-nav"> <ul class="navbar-nav">
<li class="nav-item"> <li class="nav-item">
<a href="{{ url_for('pages.home') }}" class="nav-link text-secondary"> <a href="{{ url_for('pages.home') }}" class="nav-link link-white">
Home Home
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a href="{{ url_for('pages.faq') }}" class="nav-link text-secondary"> <a href="{{ url_for('pages.faq') }}" class="nav-link link-white">
FAQ FAQ
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a href="{{ url_for('pages.services') }}" class="nav-link text-secondary"> <a href="{{ url_for('pages.services') }}" class="nav-link link-white">
Services Services
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a href="{{ url_for('users.user_login') }}" class="nav-link text-secondary"> <a href="{{ url_for('users.user_login') }}" class="nav-link link-white">
Login Login
</a> </a>
</li> </li>
<li class="nav-item"> <li class="nav-item">
<a href="{{ url_for('users.user_create') }}" class="nav-link text-secondary"> <a href="{{ url_for('users.user_create') }}" class="nav-link link-white">
Create account Create account
</a> </a>
</li> </li>

View File

@ -6,8 +6,7 @@
<title> <title>
{% block title %}{% endblock %} {% block title %}{% endblock %}
</title> </title>
<link href="{{ url_for('static', filename="css/custom.css") }}" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0-beta1/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-0evHe/X+R7YkIZDRvuzKMRqM+OrBnVFBL6DOitfPri4tjfHxaWutUpFmBp4vmVor" crossorigin="anonymous">
</head> </head>
<body> <body>
{% include "jinja/header.html" %} {% include "jinja/header.html" %}

View File

@ -4,15 +4,12 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>{{ title }}</title> <title>{{ title }}</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-rbsA2VBKQhggwzxH7pPCaAqO46MgnOM80zW1RWuH61DGLwZJEdK2Kadq2F9CUG65" crossorigin="anonymous"> <link href="{{ url_for('static', filename="css/custom.css") }}" rel="stylesheet">
</head> </head>
<body> <body class="bg-white">
{% include "jinja/header.html" %} {% include "jinja/header.html" %}
<main> <main>
<hr class="invisible pb-3">
<div class="container"> <div class="container">
{% block content %} {% block content %}
{% endblock %} {% endblock %}

View File

@ -1,36 +1,33 @@
{% extends "jinja/types/page.html" %} {% extends "jinja/types/page.html" %}
{% set title %}FAQ{% endset %} {% set title %}{{ page.get_title() }}{% endset %}
{% block content %} {% block content %}
<div class="row text-center mb-2"> <div class="row text-center mb-2">
<h1 class="text-primary display-1"> <h1 class="text-primary display-1">
Frequently asked questions {{ page.get_title() }}
</h1> </h1>
</div> </div>
{% for faq in config.FAQS.items() %} <div class="d-grid gap-3 btn-group-vertical" role="group" id="faq-group">
<div class="row"> {% for faq in page.get_content() %}
<div class="col-1"> <button class="btn btn-secondary text-light text-center"
</div> type="button"
data-bs-toggle="collapse"
data-bs-target="#{{ page.get_content()[faq]['tag'] }}"
aria-expanded="true"
aria-controls="{{ page.get_content()[faq]['tag'] }}">
<h2 class="accordion-header">
{{ faq }}
</h2>
</button>
<a class="btn btn-primary text-center" data-bs-toggle="collapse" <div class="collapse" id="{{ page.get_content()[faq]['tag'] }}">
href="#{{ faq[1][0] }}" role="button" aria-expanded="false" <div class="card card-body text-center text-secondary">
aria-controls="{{ faq[1][0] }}"> {{ page.get_content()[faq]['answer']|safe }}
<h2>{{ faq[0] }}</h2>
</a>
<div class="collapse" id="{{ faq[1][0] }}">
<div class="card card-body text-center">
{{ faq[1][1] }}
</div> </div>
</div> </div>
<div class="col-1">
</div>
</div>
<hr class="invisible">
{% endfor %} {% endfor %}
</div>
{% endblock %} {% endblock %}

View File

@ -1,37 +1,47 @@
{% extends "jinja/types/page.html" %} {% extends "jinja/types/page.html" %}
{% set title %}About me{% endset %} {% set title %}{{ page.get_title() }}{% endset %}
{% block content %} {% block content %}
<div class="row text-center"> <div class="row text-center">
<h1 class="text-primary display-1"> <h1 class="text-primary display-1">
About me {{ page.get_title() }}
</h1> </h1>
</div> </div>
{% for row in config.ABOUT_ME %} <div class="d-grid gap-3">
<div class="row pb-3"> {% for section in page.get_content() %}
<div class="col-8 text-center {{ loop.cycle('order-first','order-last') }}"> <div class="card border-0 d-flex rounded-1 bg-dark">
<p class="text-secondary lead"> <div class="card-body">
{{ row[1] }} <div class="row">
</p> <div class="col-sm-8 {{ loop.cycle('order-first','order-last') }}">
<div class="card-text text-light text-center lead">
{{ section['content']|safe }}
</div>
</div> </div>
<div class="col-4 {{ loop.cycle('order-last','order-first') }}"> <div class="col-sm-4 {{ loop.cycle('order-last','order-first') }}">
<div class="card-img">
<picture> <picture>
{% for img in row[0] %} {% for img in section['images'] %}
{% if not loop.last %} {% if not loop.last %}
<source srcset="{{ img }}" <source srcset="{{ url_for('static', filename=img) }}"
media="(max-width: {{ config.IMG_BREAKPOINTS[loop.index] }})" /> media="(max-width: {{ config.IMG_BREAKPOINTS[loop.index] }})" />
{% else %} {% else %}
<source srcset="{{ img }}" <source srcset="{{ url_for('static', filename=img) }}"
media="(min-width: {{ config.IMG_BREAKPOINTS[loop.index] }})" /> media="(min-width: {{ config.IMG_BREAKPOINTS[loop.index] }})" />
{% endif %} {% endif %}
{% endfor %} {% endfor %}
<img class="img-fluid" src="{{ row[0][1] }}"> <img class="img-fluid rounded-1"
src="{{ url_for('static', filename=section['images'][1]) }}">
</picture> </picture>
</div> </div>
</div> </div>
</div>
</div>
</div>
{% endfor %} {% endfor %}
</div>
{% endblock %} {% endblock %}

Some files were not shown because too many files have changed in this diff Show More