Add landing page

This commit is contained in:
Peter Stockings
2024-12-25 11:27:25 +11:00
parent 541c328857
commit 191ac840c9
3 changed files with 154 additions and 55 deletions

View File

@@ -3,6 +3,7 @@ from io import StringIO
import io import io
from flask import Blueprint, Response, make_response, render_template, redirect, request, send_file, url_for, flash from flask import Blueprint, Response, make_response, render_template, redirect, request, send_file, url_for, flash
from werkzeug.security import generate_password_hash, check_password_hash from werkzeug.security import generate_password_hash, check_password_hash
from werkzeug.http import http_date
from app.models import Profile, Reading, db, User from app.models import Profile, Reading, db, User
from app.forms import DeleteForm, LoginForm, ProfileForm, ReadingForm, SignupForm from app.forms import DeleteForm, LoginForm, ProfileForm, ReadingForm, SignupForm
from flask_login import login_user, login_required, current_user, logout_user from flask_login import login_user, login_required, current_user, logout_user
@@ -46,8 +47,11 @@ def logout():
flash('You have been logged out.', 'success') flash('You have been logged out.', 'success')
return redirect(url_for('auth.login')) # Redirect to login page or home page return redirect(url_for('auth.login')) # Redirect to login page or home page
@main.route('/', methods=['GET'])
def landing():
return render_template('landing.html')
@main.route('/', methods=['GET', 'POST']) @main.route('/dashboard', methods=['GET', 'POST'])
@login_required @login_required
def dashboard(): def dashboard():
# Default values # Default values
@@ -245,8 +249,10 @@ def profile_image(user_id):
image_data = base64.b64decode(profile.profile_pic) image_data = base64.b64decode(profile.profile_pic)
response = make_response(image_data) response = make_response(image_data)
response.headers.set('Content-Type', 'image/jpeg') response.headers.set('Content-Type', 'image/jpeg')
# Cache for 1 day response.headers.set('Cache-Control', 'public, max-age=86400') # Cache for 1 day
response.headers.set('Cache-Control', 'public, max-age=86400') response.headers.set('ETag', str(hash(profile.profile_pic))) # Unique ETag for the image
response.headers.set('Last-Modified', http_date(datetime.utcnow().timestamp()))
return response return response
else: else:
# Serve the default SVG if no profile picture is found # Serve the default SVG if no profile picture is found

View File

@@ -625,10 +625,18 @@ video {
margin-bottom: 1.5rem; margin-bottom: 1.5rem;
} }
.mb-8 {
margin-bottom: 2rem;
}
.ml-2 { .ml-2 {
margin-left: 0.5rem; margin-left: 0.5rem;
} }
.ml-4 {
margin-left: 1rem;
}
.mr-1 { .mr-1 {
margin-right: 0.25rem; margin-right: 0.25rem;
} }
@@ -697,6 +705,14 @@ video {
display: none; display: none;
} }
.h-12 {
height: 3rem;
}
.h-16 {
height: 4rem;
}
.h-24 { .h-24 {
height: 6rem; height: 6rem;
} }
@@ -717,6 +733,14 @@ video {
height: 2rem; height: 2rem;
} }
.w-12 {
width: 3rem;
}
.w-16 {
width: 4rem;
}
.w-24 { .w-24 {
width: 6rem; width: 6rem;
} }
@@ -813,24 +837,16 @@ video {
gap: 1rem; gap: 1rem;
} }
.gap-8 {
gap: 2rem;
}
.space-x-2 > :not([hidden]) ~ :not([hidden]) { .space-x-2 > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 0; --tw-space-x-reverse: 0;
margin-right: calc(0.5rem * var(--tw-space-x-reverse)); margin-right: calc(0.5rem * var(--tw-space-x-reverse));
margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse))); margin-left: calc(0.5rem * calc(1 - var(--tw-space-x-reverse)));
} }
.space-y-4 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0;
margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
margin-bottom: calc(1rem * var(--tw-space-y-reverse));
}
.space-y-6 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0;
margin-top: calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));
margin-bottom: calc(1.5rem * var(--tw-space-y-reverse));
}
.space-x-4 > :not([hidden]) ~ :not([hidden]) { .space-x-4 > :not([hidden]) ~ :not([hidden]) {
--tw-space-x-reverse: 0; --tw-space-x-reverse: 0;
margin-right: calc(1rem * var(--tw-space-x-reverse)); margin-right: calc(1rem * var(--tw-space-x-reverse));
@@ -843,6 +859,18 @@ video {
margin-left: calc(1.5rem * calc(1 - var(--tw-space-x-reverse))); margin-left: calc(1.5rem * calc(1 - var(--tw-space-x-reverse)));
} }
.space-y-4 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0;
margin-top: calc(1rem * calc(1 - var(--tw-space-y-reverse)));
margin-bottom: calc(1rem * var(--tw-space-y-reverse));
}
.space-y-6 > :not([hidden]) ~ :not([hidden]) {
--tw-space-y-reverse: 0;
margin-top: calc(1.5rem * calc(1 - var(--tw-space-y-reverse)));
margin-bottom: calc(1.5rem * var(--tw-space-y-reverse));
}
.overflow-hidden { .overflow-hidden {
overflow: hidden; overflow: hidden;
} }
@@ -894,6 +922,11 @@ video {
background-color: rgb(37 99 235 / var(--tw-bg-opacity, 1)); background-color: rgb(37 99 235 / var(--tw-bg-opacity, 1));
} }
.bg-blue-700 {
--tw-bg-opacity: 1;
background-color: rgb(29 78 216 / var(--tw-bg-opacity, 1));
}
.bg-gray-100 { .bg-gray-100 {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(243 244 246 / var(--tw-bg-opacity, 1)); background-color: rgb(243 244 246 / var(--tw-bg-opacity, 1));
@@ -949,20 +982,10 @@ video {
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to); --tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
} }
.from-gray-500 {
--tw-gradient-from: #6b7280 var(--tw-gradient-from-position);
--tw-gradient-to: rgb(107 114 128 / 0) var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}
.to-blue-700 { .to-blue-700 {
--tw-gradient-to: #1d4ed8 var(--tw-gradient-to-position); --tw-gradient-to: #1d4ed8 var(--tw-gradient-to-position);
} }
.to-gray-700 {
--tw-gradient-to: #374151 var(--tw-gradient-to-position);
}
.fill-current { .fill-current {
fill: currentColor; fill: currentColor;
} }
@@ -1007,16 +1030,31 @@ video {
padding-right: 1.5rem; padding-right: 1.5rem;
} }
.px-8 {
padding-left: 2rem;
padding-right: 2rem;
}
.py-1 { .py-1 {
padding-top: 0.25rem; padding-top: 0.25rem;
padding-bottom: 0.25rem; padding-bottom: 0.25rem;
} }
.py-16 {
padding-top: 4rem;
padding-bottom: 4rem;
}
.py-2 { .py-2 {
padding-top: 0.5rem; padding-top: 0.5rem;
padding-bottom: 0.5rem; padding-bottom: 0.5rem;
} }
.py-20 {
padding-top: 5rem;
padding-bottom: 5rem;
}
.py-3 { .py-3 {
padding-top: 0.75rem; padding-top: 0.75rem;
padding-bottom: 0.75rem; padding-bottom: 0.75rem;
@@ -1053,6 +1091,11 @@ video {
line-height: 2.25rem; line-height: 2.25rem;
} }
.text-4xl {
font-size: 2.25rem;
line-height: 2.5rem;
}
.text-base { .text-base {
font-size: 1rem; font-size: 1rem;
line-height: 1.5rem; line-height: 1.5rem;
@@ -1170,6 +1213,16 @@ video {
background-color: rgb(29 78 216 / var(--tw-bg-opacity, 1)); background-color: rgb(29 78 216 / var(--tw-bg-opacity, 1));
} }
.hover\:bg-blue-800:hover {
--tw-bg-opacity: 1;
background-color: rgb(30 64 175 / var(--tw-bg-opacity, 1));
}
.hover\:bg-gray-200:hover {
--tw-bg-opacity: 1;
background-color: rgb(229 231 235 / var(--tw-bg-opacity, 1));
}
.hover\:bg-gray-400:hover { .hover\:bg-gray-400:hover {
--tw-bg-opacity: 1; --tw-bg-opacity: 1;
background-color: rgb(156 163 175 / var(--tw-bg-opacity, 1)); background-color: rgb(156 163 175 / var(--tw-bg-opacity, 1));
@@ -1190,31 +1243,6 @@ video {
background-color: rgb(185 28 28 / var(--tw-bg-opacity, 1)); background-color: rgb(185 28 28 / var(--tw-bg-opacity, 1));
} }
.hover\:bg-gray-200:hover {
--tw-bg-opacity: 1;
background-color: rgb(229 231 235 / var(--tw-bg-opacity, 1));
}
.hover\:from-blue-600:hover {
--tw-gradient-from: #2563eb var(--tw-gradient-from-position);
--tw-gradient-to: rgb(37 99 235 / 0) var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}
.hover\:from-gray-600:hover {
--tw-gradient-from: #4b5563 var(--tw-gradient-from-position);
--tw-gradient-to: rgb(75 85 99 / 0) var(--tw-gradient-to-position);
--tw-gradient-stops: var(--tw-gradient-from), var(--tw-gradient-to);
}
.hover\:to-blue-800:hover {
--tw-gradient-to: #1e40af var(--tw-gradient-to-position);
}
.hover\:to-gray-800:hover {
--tw-gradient-to: #1f2937 var(--tw-gradient-to-position);
}
.hover\:text-blue-800:hover { .hover\:text-blue-800:hover {
--tw-text-opacity: 1; --tw-text-opacity: 1;
color: rgb(30 64 175 / var(--tw-text-opacity, 1)); color: rgb(30 64 175 / var(--tw-text-opacity, 1));
@@ -1225,6 +1253,11 @@ video {
color: rgb(229 231 235 / var(--tw-text-opacity, 1)); color: rgb(229 231 235 / var(--tw-text-opacity, 1));
} }
.hover\:text-gray-900:hover {
--tw-text-opacity: 1;
color: rgb(17 24 39 / var(--tw-text-opacity, 1));
}
.hover\:text-red-800:hover { .hover\:text-red-800:hover {
--tw-text-opacity: 1; --tw-text-opacity: 1;
color: rgb(153 27 27 / var(--tw-text-opacity, 1)); color: rgb(153 27 27 / var(--tw-text-opacity, 1));
@@ -1235,11 +1268,6 @@ video {
color: rgb(255 255 255 / var(--tw-text-opacity, 1)); color: rgb(255 255 255 / var(--tw-text-opacity, 1));
} }
.hover\:text-gray-900:hover {
--tw-text-opacity: 1;
color: rgb(17 24 39 / var(--tw-text-opacity, 1));
}
.hover\:underline:hover { .hover\:underline:hover {
text-decoration-line: underline; text-decoration-line: underline;
} }
@@ -1318,6 +1346,10 @@ video {
.md\:grid-cols-2 { .md\:grid-cols-2 {
grid-template-columns: repeat(2, minmax(0, 1fr)); grid-template-columns: repeat(2, minmax(0, 1fr));
} }
.md\:grid-cols-3 {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
} }
@media (min-width: 1024px) { @media (min-width: 1024px) {

View File

@@ -0,0 +1,61 @@
{% extends "_layout.html" %}
{% block content %}
<div class="bg-gray-50">
<!-- Hero Section -->
<section class="bg-blue-600 text-white">
<div class="container mx-auto px-4 py-20 text-center">
<h1 class="text-4xl font-bold mb-6">
Welcome to BP Tracker
</h1>
<p class="text-lg mb-8">
Track your blood pressure and heart rate effortlessly. Take control of your health today!
</p>
<div class="flex justify-center space-x-4">
<a href="{{ url_for('auth.signup') }}"
class="px-8 py-3 bg-white text-blue-600 font-semibold rounded-lg shadow hover:bg-gray-200">
Get Started
</a>
<a href="{{ url_for('auth.login') }}"
class="px-8 py-3 bg-blue-700 text-white font-semibold rounded-lg shadow hover:bg-blue-800">
Login
</a>
</div>
</div>
</section>
<!-- Features Section -->
<section class="container mx-auto px-4 py-16">
<h2 class="text-3xl font-bold text-center mb-8">Why Choose BP Tracker?</h2>
<div class="grid md:grid-cols-3 gap-8">
<div class="text-center bg-white p-6 rounded-lg shadow">
<svg xmlns="http://www.w3.org/2000/svg" class="w-16 h-16 text-blue-600 mx-auto mb-4" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M12 8c2.21 0 4 1.79 4 4s-1.79 4-4 4-4-1.79-4-4 1.79-4 4-4z"></path>
</svg>
<h3 class="text-lg font-semibold mb-2">Accurate Tracking</h3>
<p>Keep a detailed log of your blood pressure and heart rate over time.</p>
</div>
<div class="text-center bg-white p-6 rounded-lg shadow">
<svg xmlns="http://www.w3.org/2000/svg" class="w-16 h-16 text-blue-600 mx-auto mb-4" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9.75 16.5L15 12m0 0l-5.25-4.5m5.25 4.5H3"></path>
</svg>
<h3 class="text-lg font-semibold mb-2">Insightful Graphs</h3>
<p>Visualize your progress and identify trends with intuitive charts.</p>
</div>
<div class="text-center bg-white p-6 rounded-lg shadow">
<svg xmlns="http://www.w3.org/2000/svg" class="w-16 h-16 text-blue-600 mx-auto mb-4" fill="none"
viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M9 20l-5.5-5.5M9 20V9m0 11h11"></path>
</svg>
<h3 class="text-lg font-semibold mb-2">Secure and Private</h3>
<p>Your data is protected with state-of-the-art security measures.</p>
</div>
</div>
</section>
</div>
{% endblock %}