Compare commits

..

No commits in common. "master" and "model" have entirely different histories.

34 changed files with 727 additions and 1952 deletions

BIN
ER.dia

Binary file not shown.

BIN
ER.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 95 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 87 KiB

686
app.py
View File

@ -1,9 +1,7 @@
import datetime
import hashlib
import os
from PIL import Image, ImageSequence
import concurrent.futures
from PIL import Image
from flask import Flask, render_template, redirect, url_for, request, jsonify
from flask_login import login_required, LoginManager, login_user, logout_user, current_user
@ -17,14 +15,13 @@ from utils import anonymous_required
# Create a new Flask instance
app = Flask(__name__)
app.secret_key = 'PSSSSSHHHT!'
UPLOAD_FOLDER = 'static/profile_images/' # Folder to store profile images
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
# Initialize the Flask-Login extension
login_manager = LoginManager()
login_manager.login_view = 'login'
login_manager.init_app(app)
@login_manager.user_loader
def load_user(user_id):
return User.get(user_id)
@ -35,73 +32,18 @@ def inject_user():
return dict(user=current_user)
@app.context_processor
def inject_notifications():
if current_user.is_authenticated:
habit_lists = current_user.get_unaccepted_habitLists()
lists = []
for habit_list in habit_lists:
# Check if the user is the first user in the list
if habit_list.get_users()[0].id != current_user.id:
lists.append(habit_list)
return dict(notifications=lists)
return dict(notifications=[])
# Create a new route
@app.route('/')
def index():
if current_user.is_authenticated:
habit_lists = current_user.get_habitLists()
name = "Hallo " + current_user.name
heatmap_values, day = current_user.get_heatmap()
heatmap_color = current_user.heatmap_color
else:
habit_lists = []
name = "Bitte melde dich an."
heatmap_values = []
day = None
heatmap_color = None
# Sort habit_lists based on their order attribute
habit_lists = sorted(habit_lists, key=lambda habitList: habitList.slot)
# Sort habits within each habit_list by slot
for habit_list in habit_lists:
habit_list.habits = sorted(habit_list.get_habits(), key=lambda habit: (habit.checked, habit.slot))
for habit in habit_list.get_habits():
habit.load_statistics()
# Get active_list from query parameter
try:
active_list = int(request.args.get('list'))
except (ValueError, TypeError):
active_list = None
return render_template(
'index.html',
title=name,
habit_lists=habit_lists,
heatmap_values=heatmap_values,
day=day,
color=heatmap_color,
errors={},
active_list=active_list
)
###################### Login & Signup #####################
@app.route('/login')
@anonymous_required
def login():
return render_template('auth/login.html', errors={})
@app.route('/signup')
@anonymous_required
def signup():
return render_template('auth/signup.html', errors={})
@app.route('/login', methods=['POST'])
def login_post():
email = request.form.get('email')
@ -135,6 +77,7 @@ def login_post():
# Redirect to login page
return redirect(url_for('index'))
@app.route('/signup', methods=['POST'])
def signup_post():
email = request.form.get('email')
@ -150,10 +93,6 @@ def signup_post():
if not password:
errors['password'] = 'Das Passwort ist erforderlich.'
# Check if email is already in use
if User.get_by_email(email):
errors['email'] = 'E-Mail Adresse bereits in Benutzung.'
if errors:
return render_template(
'auth/signup.html',
@ -170,6 +109,7 @@ def signup_post():
# Redirect to login page
return redirect(url_for('index'))
@app.route('/logout')
@login_required
def logout():
@ -177,11 +117,31 @@ def logout():
logout_user()
return redirect(url_for('index'))
###########################################################
# Create a new route
@app.route('/')
def index():
if current_user.is_authenticated:
habit_lists = current_user.get_habitLists()
name = "Hallo " + current_user.name
else:
habit_lists = []
name = "Bitte melde dich an."
# Sort habits by whether they have been checked today and then by slot
for habit_list in habit_lists:
habit_list.habits = sorted(habit_list.get_habits(), key=lambda habit: (not habit.checked, habit.slot))
return render_template(
'index.html',
title=name,
utc_dt=datetime.datetime.now().strftime("%d.%m.%Y %H:%M %A"),
habit_lists=habit_lists,
errors={},
)
########################## Habit ##########################
@app.route('/habit')
@login_required
def habit_creation():
@ -192,6 +152,7 @@ def habit_creation():
errors={},
)
@app.route('/habit', methods=['POST'])
@login_required
def habit_create():
@ -234,16 +195,6 @@ def habit_create():
except ValueError:
errors['list_query'] = 'Die Anzahl muss eine Zahl sein.'
# Check if times is possible to achieve
if unit == 'Tag' and times != 1:
errors['times'] = 'Die Anzahl muss 1 sein, wenn das Habit täglich ist.'
if unit == 'Woche' and times > 7:
errors['times'] = 'Die Anzahl darf höchstens 7 sein, wenn das Habit wöchentlich ist.'
if unit == 'Monat' and times > 31:
errors['times'] = 'Die Anzahl darf höchstens 31 sein, wenn das Habit monatlich ist.'
if unit == 'Jahr' and times > 365:
errors['times'] = 'Die Anzahl darf höchstens 365 sein, wenn das Habit jährlich ist.'
if errors:
return render_template(
'habit.html',
@ -269,201 +220,12 @@ def habit_create():
unit = 1
# Save habit to database
Habit.create(list_id, name, times, note, unit)
habit = Habit.create(list_id, name, times, note, unit)
# Back to index
return redirect(url_for('index'))
@app.route('/edit-habit')
@login_required
def edit_habit():
habit_id = int(request.args.get("habit"))
habit = Habit.get(habit_id)
units = ["Tag", "Woche", "Monat", "Jahr"]
return render_template(
"edit-habit.html",
title=habit.name,
habit=habit.id,
name=habit.name,
note=habit.note,
times=habit.times,
unit=units[habit.unit],
errors={}
)
@app.route('/edit-habit', methods=['POST'])
@login_required
def edit_habit_change():
units = ["Tag", "Woche", "Monat", "Jahr"]
name = request.form.get('name')
note = request.form.get('note')
times = request.form.get('times')
unit = request.form.get('unit')
list_id = request.form.get('habit')
habit = Habit.get(list_id)
# Check for errors
errors = {}
if not name:
errors['name'] = 'Der Name ist erforderlich.'
if not times:
errors['times'] = 'Die Anzahl ist erforderlich.'
if not note:
note = ''
if not unit:
errors['unit'] = 'Die Einheit ist erforderlich.'
if not list_id:
errors['habit'] = 'Das Habit ist erforderlich.'
# Check if times is an integer
try:
print(times)
times = int(times)
# Check that times is greater than 0
if times <= 0:
errors['times'] = 'Die Anzahl muss größer als 0 sein.'
except ValueError:
errors['times'] = 'Die Anzahl muss eine Zahl sein.'
# Check that unit is valid
if unit not in ['Tag', 'Woche', 'Monat', 'Jahr']:
errors['unit'] = 'Die Einheit ist ungültig.'
# check if list_id is an int
try:
int(list_id)
except ValueError:
errors['list_query'] = 'Die Anzahl muss eine Zahl sein.'
# Check if times is possible to achieve
if unit == 'Tag' and times != 1:
errors['times'] = 'Die Anzahl muss 1 sein, wenn das Habit täglich ist.'
if unit == 'Woche' and times > 7:
errors['times'] = 'Die Anzahl darf höchstens 7 sein, wenn das Habit wöchentlich ist.'
if unit == 'Monat' and times > 31:
errors['times'] = 'Die Anzahl darf höchstens 31 sein, wenn das Habit monatlich ist.'
if unit == 'Jahr' and times > 365:
errors['times'] = 'Die Anzahl darf höchstens 365 sein, wenn das Habit jährlich ist.'
if errors:
return render_template(
"edit-habit.html",
title=habit.name,
habit=habit.id,
name=habit.name,
note=habit.note,
times=habit.times,
unit=units[habit.unit],
errors=errors
)
# Map unit to integer
if unit == 'Tag':
unit = 0
elif unit == 'Woche':
unit = 1
elif unit == 'Monat':
unit = 2
elif unit == 'Jahr':
unit = 3
else:
unit = 1
# Save habit to database
print(name, note, times, unit)
habit.name, habit.note, habit.times, habit.unit = name, note, times, unit
habit.update()
# Back to index
return redirect(url_for('index'))
@app.route('/check', methods=['POST'])
@login_required
def check_habit():
habit_id = request.get_json()["habitId"]
habit = Habit.get(habit_id)
if habit is None:
return {"error": "Habit not found"}
# Check if habit belongs to user
users = habit.habit_list().get_users()
if current_user not in users:
return {"error": "Habit does not belong to user"}
trackings = habit.get_habitTrackings()
# Check if habit has been tracked today
delete_tracking = None
for tracking in trackings:
if tracking.created_at.date() == datetime.date.today():
delete_tracking = tracking
if not delete_tracking:
HabitTracking.create(habit_id)
habit.fill_statistics()
else:
delete_tracking.delete()
habit.reset_statistics()
habit.load_statistics()
heatmap_values, day = current_user.get_heatmap()
return {
"habitId": habit_id,
"unchecked": not delete_tracking,
"percentage": habit.percentage,
"streak": habit.streak,
"heatmap": heatmap_values,
"day": day,
}
@app.route('/delete', methods=['POST'])
@login_required
def delete_habit():
habit_id = request.get_json()["habitId"]
habit = Habit.get(habit_id)
if habit is None:
return {"error": "Habit not found"}
# Check if habit belongs to user
if current_user not in habit.habit_list().get_users():
return {"error": "Habit does not belong to user"}
habit.delete()
return {}
@app.route('/reorder-habit', methods=['POST'])
@login_required
def reorder_habits():
new_index = request.get_json()["newIndex"] + 1
habit = Habit.get(request.get_json()["habitId"])
if habit is None:
return {"error": "Habit not found"}
# Check if habit belongs to user
users = habit.habit_list().get_users()
if current_user not in users:
return {"error": "Habit does not belong to user"}
habit.update_slot(new_index)
return {}
###########################################################
######################## HabitList ########################
@app.route('/habit-list')
@login_required
def habit_list_creation():
@ -473,6 +235,7 @@ def habit_list_creation():
errors={},
)
@app.route('/habit-list', methods=['POST'])
@login_required
def habit_list_create():
@ -484,7 +247,7 @@ def habit_list_create():
if not name:
errors['name'] = 'Der Name ist erforderlich.'
if not description:
description = ''
note = ''
if errors:
return render_template(
@ -496,236 +259,12 @@ def habit_list_create():
)
# Save habit to database
HabitList.create(current_user.id, name, description)
habit = HabitList.create(current_user.id, name, description)
# Back to index
return redirect(url_for('index'))
@app.route('/delete-list', methods=['POST'])
@login_required
def delete_list():
list_id = request.get_json()["listId"]
habit_list = HabitList.get(list_id)
if habit_list is None:
return {"error": "List not found"}
# Check if habit belongs to user
if current_user not in habit_list.get_users():
return {"error": "List does not belong to user"}
habit_list.delete(current_user.id)
return {}
@app.route('/users')
@login_required
def users():
habit_list_id = request.args.get('habit_list', current_user.get_habitLists()[0].id)
habit_list = HabitList.get(int(habit_list_id))
users = habit_list.get_users()
# Remove the current user from the list
users = [user for user in users if user.id != current_user.id]
return render_template(
'users.html',
title='Teilnehmer',
habit_list=habit_list,
users=users,
errors={},
)
@app.route('/users', methods=['POST'])
@login_required
def add_user():
email = request.form.get('email')
habit_list_id = request.form.get('habit_list_id')
habit_list = HabitList.get(int(habit_list_id))
# Check for errors
errors = {}
if not email:
errors['email'] = 'Die E-Mail Adresse ist erforderlich.'
if not habit_list_id:
errors['habit_list'] = 'Die Habitliste ist erforderlich.'
if errors:
return render_template(
'users.html',
title='Teilnehmer',
email=email,
habit_list=habit_list,
errors=errors,
users=habit_list.get_users(),
)
# Check if user exists
user = User.get_by_email(email)
if not user:
errors['email'] = 'E-Mail Adresse nicht gefunden.'
if user and user.id == current_user.id:
errors['email'] = 'Du kannst dich nicht selbst hinzufügen.'
# Check if user is already in the habit list
already = False
if user:
for u in habit_list.get_users():
if u.id == user.id:
already = True
break
if already:
errors['email'] = 'Teilnehmer ist bereits in der Liste.'
if errors:
return render_template(
'users.html',
title='Teilnehmer',
email=email,
habit_list=habit_list,
errors=errors,
users=habit_list.get_users(),
)
# Add user to habit list
habit_list = HabitList.get(int(habit_list_id))
habit_list.add_user(user)
return redirect(url_for('index', habit_list=habit_list.id))
@app.route('/users-edit')
@login_required
def edit_users():
habit_list_id = request.args.get('habit_list', current_user.get_habitLists()[0].id)
habit_list = HabitList.get(int(habit_list_id))
users = habit_list.get_users()
# Remove the current user from the list
users = [user for user in users if user.id != current_user.id]
return render_template(
'users-edit.html',
title='Teilnehmer bearbeiten',
habit_list=habit_list,
users=users,
errors={},
)
@app.route('/user-delete', methods=['POST'])
@login_required
def delete_user_from_list():
habit_list_id = request.form.get('habit_list_id')
habit_list = HabitList.get(int(habit_list_id))
habit_user_id = request.form.get('habit_user_id')
users = habit_list.get_users()
# Remove the current user from the list
users = [user for user in users if user.id != current_user.id]
# Check for errors
errors = {}
if not habit_list_id:
errors['habit_list'] = 'Die Habitliste ist erforderlich.'
if not habit_list_id:
errors['habit_user'] = 'Ein User ist erforderlich.'
if errors:
return render_template(
'users-edit.html',
title='Teilnehmer bearbeiten',
habit_list=habit_list,
users=users,
errors={},
)
# delete user from habit list
id = int(habit_user_id)
habit_list.delete(id)
return redirect(url_for('index', habit_list=habit_list.id))
@app.route('/users-leave')
@login_required
def user_leave():
list_id = request.args.get('habit_list')
habit_list = HabitList.get(list_id)
if habit_list is None:
return {"error": "List not found"}
# Check if habit belongs to user
if current_user not in habit_list.get_users():
return {"error": "List does not belong to user"}
habit_list.remove_user(current_user.id)
return redirect(url_for("index"))
@app.route('/accept-list', methods=['POST'])
@login_required
def accept_list():
list_id = request.json.get('list_id')
habit_list = HabitList.get(int(list_id))
users = habit_list.get_users()
# Check if user is part of the list
found = False
for user in users:
if user.id == current_user.id:
found = True
break
if not found:
return {}
current_user.accept_List(habit_list.id)
return {}
@app.route('/deny-list', methods=['POST'])
@login_required
def deny_list():
list_id = request.json.get('list_id')
habit_list = HabitList.get(int(list_id))
users = habit_list.get_users()
# Check if user is part of the list
found = False
for user in users:
if user.id == current_user.id:
found = True
break
if not found:
return {}
habit_list.remove_user(current_user.id)
return {}
@app.route('/reorder-list', methods=['POST'])
@login_required
def reorder_habit_list():
new_index = request.get_json()["newIndex"] + 1
habitList = HabitList.get(request.get_json()["listId"])
if habitList is None:
return {"error": "HabitList not found"}
# Check if habit belongs to user
users = habitList.get_users()
if current_user not in users:
return {"error": "HabitList does not belong to user"}
habitList.update_slot(current_user.id, new_index)
return {}
###########################################################
######################### Profile #########################
@app.route('/profile')
@login_required
def profile():
@ -734,10 +273,9 @@ def profile():
name=current_user.name,
email=current_user.email,
profile_image_url=current_user.profile_image,
color = current_user.heatmap_color,
title="Profil",
)
@app.route('/profile', methods=['POST'])
@login_required
def profile_change():
@ -755,7 +293,6 @@ def profile_change():
name=current_user.name,
email=current_user.email,
profile_image_url=current_user.profile_image,
color=current_user.heatmap_color,
)
@app.route('/check_password', methods=['POST'])
@ -780,13 +317,16 @@ def password_change():
current_user.update()
# Back to profile
return render_template("profile.html",
return render_template(
"profile.html",
name=current_user.name,
email=current_user.email,
profile_image_url=current_user.profile_image,
color=current_user.heatmap_color,
)
UPLOAD_FOLDER = 'static/profile_images/' # Folder to store profile images
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER
def save_profile_image(image_file):
filename = image_file.filename
if '.' not in filename:
@ -805,65 +345,35 @@ def save_profile_image(image_file):
# Open the uploaded image
image = Image.open(image_file)
# Function to crop and resize frames
def process_frame(frame, size):
new_size = min(frame.size)
left = (frame.width - new_size) // 2
top = (frame.height - new_size) // 2
right = left + new_size
bottom = top + new_size
cropped_frame = frame.crop((left, top, right, bottom))
return cropped_frame.resize(size)
# Convert the image to RGB mode (required for JPEG)
image = image.convert('RGB')
# Function to process frames in parallel
def process_frames_parallel(frames, size):
with concurrent.futures.ThreadPoolExecutor() as executor:
resized_frames = list(executor.map(lambda f: process_frame(f, size), frames))
return resized_frames
# Determine the size of the square image
min_dimension = min(image.size)
square_size = (min_dimension, min_dimension)
# Check if the image is an animated gif
if file_extension == 'gif':
# Process frames
gif_frames = [frame.copy() for frame in ImageSequence.Iterator(image)]
processed_frames = process_frames_parallel(gif_frames, size=(128, 128))
# Calculate the coordinates for cropping
left = (image.width - min_dimension) / 2
top = (image.height - min_dimension) / 2
right = (image.width + min_dimension) / 2
bottom = (image.height + min_dimension) / 2
# Save the modified frames as a new GIF
output_gif_path = os.path.join(app.config['UPLOAD_FOLDER'], filename.replace(".jpg", ".gif"))
processed_frames[0].save(output_gif_path, save_all=True, append_images=processed_frames[1:], loop=0)
return output_gif_path
else:
# Process single image
processed_image = process_frame(image, size=(256, 256))
processed_image = processed_image.convert('RGB')
# Crop the image to a square and resize it
image = image.crop((left, top, right, bottom))
image.thumbnail(square_size)
image = image.resize((256, 256))
# Save the processed image
image_path = os.path.join(app.config['UPLOAD_FOLDER'], filename.replace(".gif", ".jpg"))
processed_image.save(image_path, 'JPEG', quality=100)
return image_path
# Save the processed image
image_path = os.path.join(app.config['UPLOAD_FOLDER'], filename)
image.save(image_path, 'JPEG', quality=100)
@app.route('/upload', methods=['POST'])
@login_required
def upload_profile_image():
if 'file' not in request.files:
return 'No file part'
file = request.files['file']
image_path = save_profile_image(file)
# Update the User
current_user.profile_image = image_path
current_user.update()
# Back to profile
return redirect(url_for('profile'))
@app.route('/save_color', methods=['POST'])
@login_required
def save_heatmap_color():
# Get the color value from the form
new_color = request.form['color']
current_user.heatmap_color = new_color
current_user.update()
save_profile_image(file)
# Back to profile
return render_template(
@ -871,19 +381,83 @@ def save_heatmap_color():
name=current_user.name,
email=current_user.email,
profile_image_url=current_user.profile_image,
color=current_user.heatmap_color,
errors={}
)
@app.route('/delete_account', methods=['POST'])
@login_required
def delete_account():
os.remove(current_user.profile_image)
current_user.delete()
return redirect(url_for('index'))
###########################################################
@app.route('/check', methods=['POST'])
@login_required
def check_habit():
habit_id = request.get_json()["habitId"]
habit = Habit.get(habit_id)
if habit is None:
return {"error": "Habit not found"}
# Check if habit belongs to user
users = habit.habit_list().get_users()
if current_user not in users:
return {"error": "Habit does not belong to user"}
trackings = habit.get_habitTracking()
# Check if habit has been tracked today
delete_tracking = None
for tracking in trackings:
if tracking.created_at.date() == datetime.date.today():
delete_tracking = tracking
if not delete_tracking:
HabitTracking.create(habit_id)
else:
delete_tracking.delete()
# Update habit
habit.fill_statistics()
return {
"habitId": habit_id,
"unchecked": not delete_tracking,
"percentage": habit.percentage,
}
@app.route('/delete', methods=['POST'])
@login_required
def delete_habit():
habit_id = request.get_json()["habitId"]
habit = Habit.get(habit_id)
if habit is None:
return {"error": "Habit not found"}
# Check if habit belongs to user
if current_user not in habit.habit_list().get_users():
return {"error": "Habit does not belong to user"}
habit.delete()
return {}
@app.route('/reorder', methods=['POST'])
@login_required
def reorder_habits():
new_index = request.get_json()["newIndex"]+1
habit = Habit.get(request.get_json()["habitId"])
if habit is None:
return {"error": "Habit not found"}
# Check if habit belongs to user
users = habit.habit_list().get_users()
if current_user not in users:
return {"error": "Habit does not belong to user"}
habit.update_slot(new_index)
return {}
# Run the application
if __name__ == '__main__':
app.run(host="0.0.0.0", port=5000, debug=True)
app.run(port=5000, debug=True)

View File

@ -27,11 +27,11 @@ def create_user_profile_image(user_id):
return relative_destination_path
def create_user(name: str, email: str, password: str, heatmap_color: str, profile_image: str = None):
def create_user(name: str, email: str, password: str, profile_image:str = None):
password = hashlib.sha256(password.encode()).hexdigest()
now = datetime.now().isoformat()
query = (f"INSERT INTO users (name, email, password, profile_image, heatmap_color, created_at, updated_at) VALUES "
f"('{name}', '{email}', '{password}', '{profile_image}', '{heatmap_color}', '{now}', '{now}');")
query = (f"INSERT INTO users (name, email, password, profile_image, created_at, updated_at) VALUES "
f"('{name}', '{email}', '{password}', '{profile_image}', '{now}', '{now}');")
conn = con3()
cursor = conn.cursor()
cursor.execute(query)
@ -66,15 +66,13 @@ def get_user_by_email(email: str):
return user
def update_user(id: int, name: str, email: str, password: str, profile_image: str, heatmap_color: str):
def update_user(id: int, name: str, email: str, password: str):
now = datetime.now().isoformat()
if password:
query = (f"UPDATE users SET name = '{name}', email = '{email}', password = '{password}', "
f"profile_image ='{profile_image}', heatmap_color = '{heatmap_color}', updated_at = '{now}' "
query = (f"UPDATE users SET name = '{name}', email = '{email}', password = '{password}', updated_at = '{now}' "
f"WHERE id = {id};")
else:
query = (f"UPDATE users SET name = '{name}', email = '{email}', profile_image ='{profile_image}', "
f"heatmap_color = '{heatmap_color}', updated_at = '{now}' WHERE id = {id};")
query = f"UPDATE users SET name = '{name}', email = '{email}', updated_at = '{now}' WHERE id = {id};"
conn = con3()
cursor = conn.cursor()
cursor.execute(query)
@ -94,10 +92,10 @@ def delete_user(id: int):
### Habit ###
def create_habit(list_id: int, name: str, note: str, times: int, unit: int, slot: int, checked: bool, count:int, streak: int):
def create_habit(list_id: int, name: str, note: str, times: int, unit: int, slot: int):
now = datetime.now().isoformat()
query = (f"INSERT INTO habits (list_id, name, note, times, unit, slot, checked, count, streak, created_at, updated_at) "
f"VALUES ('{list_id}', '{name}', '{note}', '{times}', '{unit}', '{slot}', '{checked}', '{count}', '{streak}', '{now}', '{now}');")
query = (f"INSERT INTO habits (list_id, name, note, times, unit, slot, created_at, updated_at) "
f"VALUES ('{list_id}', '{name}', '{note}', '{times}', '{unit}', '{slot}', '{now}', '{now}');")
conn = con3()
cursor = conn.cursor()
cursor.execute(query)
@ -128,6 +126,7 @@ def get_habits(list_id: int):
def get_heatmap_value(user_id: int, days: int):
date = (datetime.now() - timedelta(days=days)).date()
print(date)
# Uses JOINs to get all Habits
query = (f"SELECT habits.id FROM habits "
@ -159,7 +158,7 @@ def get_heatmap_value(user_id: int, days: int):
return 0
def habit_get_next_slot(list_id: int):
def get_next_slot(list_id: int):
query = f"SELECT slot FROM habits WHERE list_id = {list_id} ORDER BY slot DESC LIMIT 1;"
conn = con3()
cursor = conn.cursor()
@ -169,7 +168,7 @@ def habit_get_next_slot(list_id: int):
return slot[0] + 1 if slot else 1
def habit_get_slots(list_id: int):
def get_slots(list_id: int):
query = f"SELECT id, slot FROM habits WHERE list_id = {list_id} ORDER BY slot;"
conn = con3()
cursor = conn.cursor()
@ -179,7 +178,7 @@ def habit_get_slots(list_id: int):
return slots
def habit_update_slot(id: int, slot: int):
def update_slot(id: int, slot: int):
now = datetime.now().isoformat()
query = f"UPDATE habits SET slot = {slot}, updated_at = '{now}' WHERE id = {id};"
conn = con3()
@ -192,19 +191,7 @@ def habit_update_slot(id: int, slot: int):
def update_habit(id: int, name: str, note: str, times: int, unit: int):
now = datetime.now().isoformat()
query = (f"UPDATE habits SET name = '{name}', note = '{note}', times = {times}, unit = {unit}, updated_at = '{now}' "
f"WHERE id = {id};")
conn = con3()
cursor = conn.cursor()
cursor.execute(query)
conn.commit()
conn.close()
return cursor.lastrowid
def update_habit_statistics(id: int, checked: bool, count: int, streak: int):
now = datetime.now().isoformat()
query = (f"UPDATE habits SET checked = {checked}, count = {count}, streak = {streak}, updated_at = '{now}' "
query = (f"UPDATE habits SET name = {name}, note = {note}, times = {times}, unit = {unit}, updated_at = '{now}' "
f"WHERE id = {id};")
conn = con3()
cursor = conn.cursor()
@ -265,15 +252,15 @@ def delete_habitTracking(id: int):
### HabitList ###
def create_habitList(user_id: int, name: str, description: str, slot: int):
def create_habitList(user_id: int, name: str, description: str):
now = datetime.now().isoformat()
query = (f"INSERT INTO habit_lists (name, description, slot, created_at, updated_at) "
f"VALUES ('{name}', '{description}', '{slot}', '{now}', '{now}');")
query = (f"INSERT INTO habit_lists (name, description, created_at, updated_at) "
f"VALUES ('{name}', '{description}', '{now}', '{now}');")
conn = con3()
cursor = conn.cursor()
cursor.execute(query)
query2 = (f"INSERT INTO habit_users (user_id, list_id, created_at, updated_at, accepted)"
f" VALUES ('{user_id}', '{cursor.lastrowid}', '{now}', '{now}', 1);")
query2 = (f"INSERT INTO habit_users (user_id, list_id, created_at, updated_at)"
f" VALUES ('{user_id}', '{cursor.lastrowid}', '{now}', '{now}');")
cursor.execute(query2)
conn.commit()
conn.close()
@ -290,53 +277,9 @@ def get_habitList(id: int):
return habit_list
def habitList_get_next_slot(user_id: int):
query = (f"SELECT slot FROM habit_lists JOIN habit_users ON habit_lists.id = habit_users.list_id "
f"WHERE habit_users.user_id = {user_id} ORDER BY slot DESC LIMIT 1;")
conn = con3()
cursor = conn.cursor()
cursor.execute(query)
slot = cursor.fetchone()
conn.close()
return slot[0] + 1 if slot else 1
def habitList_get_slots(user_id: int):
query = (f"SELECT habit_lists.id, slot FROM habit_lists JOIN habit_users ON habit_lists.id = habit_users.list_id "
f"WHERE habit_users.user_id = {user_id} ORDER BY slot;")
conn = con3()
cursor = conn.cursor()
cursor.execute(query)
slots = cursor.fetchall()
conn.close()
return slots
def habitList_update_slot(id: int, slot: int):
now = datetime.now().isoformat()
query = f"UPDATE habit_lists SET slot = {slot}, updated_at = '{now}' WHERE id = {id};"
conn = con3()
cursor = conn.cursor()
cursor.execute(query)
conn.commit()
conn.close()
return cursor.lastrowid
def get_habitLists(user_id: int):
query = (f"SELECT habit_lists.*, habit_users.accepted FROM habit_lists JOIN habit_users ON habit_lists.id = habit_users.list_id "
f"WHERE habit_users.user_id = {user_id};")
conn = con3()
cursor = conn.cursor()
cursor.execute(query)
habit_lists = cursor.fetchall()
conn.close()
return habit_lists
def get_unaccepted_habitLists(user_id: int):
query = (f"SELECT habit_lists.* FROM habit_lists JOIN habit_users ON habit_lists.id = habit_users.list_id "
f"WHERE habit_users.user_id = {user_id} AND habit_users.accepted = false;")
f"WHERE habit_users.user_id = {user_id};")
conn = con3()
cursor = conn.cursor()
cursor.execute(query)
@ -367,17 +310,8 @@ def add_user(list_id: int, user_id: int):
conn.close()
def accept_List(list_id: int, user_id: int):
query = f"UPDATE habit_users SET accepted = 1 WHERE habit_users.user_id = {user_id} AND habit_users.list_id = {list_id};"
conn = con3()
cursor = conn.cursor()
cursor.execute(query)
conn.commit()
conn.close()
def remove_user(list_id: int, user_id: int):
query = f"DELETE FROM habit_users WHERE user_id = {user_id} AND list_id = {list_id};"
query = f"DELETE FROM habit_lists WHERE user_id = {user_id} AND list_id = {list_id};"
conn = con3()
cursor = conn.cursor()
cursor.execute(query)

View File

@ -1 +0,0 @@
DROP TABLE habits;

View File

@ -1,16 +0,0 @@
CREATE TABLE IF NOT EXISTS habits
(
id INTEGER PRIMARY KEY AUTOINCREMENT,
list_id INTEGER NOT NULL,
name TEXT NOT NULL,
note TEXT,
times INTEGER NOT NULL,
unit INTEGER,
slot INTEGER NOT NULL,
checked BOOLEAN NOT NULL,
count INTEGER NOT NULL,
streak INTEGER NOT NULL,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY (list_id) REFERENCES habit_lists(id)
);

View File

@ -1 +0,0 @@
DROP TABLE users;

View File

@ -1,11 +0,0 @@
CREATE TABLE IF NOT EXISTS users
(
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT NOT NULL,
password TEXT NOT NULL,
profile_image TEXT NOT NULL,
heatmap_color TEXT NOT NULL,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);

View File

@ -1,2 +0,0 @@
ALTER TABLE habit_users
ADD COLUMN accepted BOOLEAN DEFAULT false;

View File

@ -1 +0,0 @@
DROP TABLE habit_lists;

View File

@ -1,8 +0,0 @@
CREATE TABLE IF NOT EXISTS habit_lists (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
description TEXT,
slot INTEGER NOT NULL,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL
);

View File

@ -1,11 +1,10 @@
import json
from dataclasses import dataclass
from datetime import datetime
from datetime import timedelta
from models.HabitTracking import HabitTracking
from db.SQLiteClient import (create_habit, get_habit, update_habit, delete_habit, habit_get_next_slot, habit_get_slots,
habit_update_slot, get_habitTrackings, get_habitList, update_habit_statistics)
from db.SQLiteClient import (create_habit, get_habit, update_habit, delete_habit, get_next_slot, get_slots, update_slot,
get_habitTrackings, get_habitList)
# unit will be represented by integers like this:
@ -23,24 +22,21 @@ class Habit:
times: int
unit: int
slot: int
checked: bool
count: int
streak: int
percentage: int = 0
def __post_init__(self):
self.load_statistics()
self.fill_statistics()
@staticmethod
def create(list_id: int, name: str, times: int, note: str = None, unit: int = 1, checked: bool = False, count: int = 0, streak: int = 0):
slot = habit_get_next_slot(list_id)
id = create_habit(list_id, name, note, times, unit, slot, checked, count, streak)
return Habit(id, list_id, name, note, times, unit, slot, checked, count, streak)
def create(list_id: int, name: str, times: int, note: str = None, unit: int = 1):
slot = get_next_slot(list_id)
id = create_habit(list_id, name, note, times, unit, slot)
return Habit(id, list_id, name, note, times, unit, slot)
@staticmethod
def get(id: int):
habit = get_habit(id)
return Habit(habit[0], habit[1], habit[2], habit[3], habit[4], habit[5], habit[6], habit[7], habit[8], habit[9]) if habit else None
return Habit(habit[0], habit[1], habit[2], habit[3], habit[4], habit[5], habit[6]) if habit else None
# Updates: name, note, times, unit
@ -51,28 +47,28 @@ class Habit:
# Updates the slot and reorders the HabitList accordingly
def update_slot(self, new_slot: int):
# Fetches a list with the following structure [(id, slot), (id, slot), ...]
slots = habit_get_slots(self.list_id)
slots = get_slots(self.list_id)
# Splits the list depending on whether the new slot is higher or lower than the current one
if new_slot > self.slot: # Example self.slot=1 new_slot=4
slots = slots[self.slot:new_slot] # Expected list: [(id, 2), (id, 3), (id, 4)]
for slot in slots:
habit_update_slot(slot[0], slot[1]-1)
update_slot(slot[0], slot[1]-1)
if new_slot < self.slot: # Example self.slot=4 new_slot=1
slots = slots[new_slot-1:self.slot-1] # Expected list: [(id, 1), (id, 2), (id, 3)]
for slot in slots:
habit_update_slot(slot[0], slot[1]+1)
update_slot(slot[0], slot[1]+1)
# Update the slot of the current habit
habit_update_slot(self.id, new_slot)
update_slot(self.id, new_slot)
# Deletes the Habit
def delete(self):
# Reorders the slots
slots = habit_get_slots(self.list_id)[self.slot+1:]
slots = get_slots(self.list_id)[self.slot+1:]
for slot in slots:
habit_update_slot(slot[0], slot[1] - 1)
update_slot(slot[0], slot[1] - 1)
# Deletes all track-records associated with the Habit
trackings = self.get_habitTrackings()
@ -96,47 +92,37 @@ class Habit:
def habit_list(self) -> list:
from models.HabitList import HabitList
raw_habitLists = get_habitList(self.list_id)
return HabitList(raw_habitLists[0], raw_habitLists[1], raw_habitLists[2], raw_habitLists[3]) if raw_habitLists else None
return HabitList(raw_habitLists[0], raw_habitLists[1], raw_habitLists[2]) if raw_habitLists else None
# Loads the progress and checks if the streak is not broken
def load_statistics(self):
today = datetime.today().date()
yesterday = today - timedelta(days=1)
tracking_dates = [tracking.created_at.date() for tracking in self.get_habitTrackings()]
if not today in tracking_dates:
self.checked = False
if not yesterday in tracking_dates:
self.streak = 0
update_habit_statistics(self.id, self.count, self.count, self.streak)
# Reset count based on time unit
if self.unit == 0:
self.count = 0
elif self.unit == 1 and today.weekday() == 0:
self.count = 0
elif self.unit == 2 and today.day == 1:
self.count = 0
elif self.unit == 3 and today.month == 1 and today.day == 1:
self.count = 0
self.percentage = int(self.count / self.times * 100)
# Saves the progress count and streak
# Saves the progress of the Habit in the attribute percentage
def fill_statistics(self):
self.checked = True
self.streak += 1
self.count += 1
update_habit_statistics(self.id, self.checked, self.count, self.streak)
# Turns the statistics back to the unchecked state
def reset_statistics(self):
count = 0
self.checked = False
self.streak -= 1
self.count -= 1
update_habit_statistics(self.id, self.checked, self.count, self.streak)
for tracking in self.get_habitTrackings():
if tracking.created_at.date() == datetime.today().date():
self.checked = True
# day
if self.unit == 0:
if tracking.created_at.date() == datetime.today().date():
count += 1
# week
elif self.unit == 1:
if tracking.created_at.isocalendar()[1] == datetime.today().isocalendar()[1]:
count += 1
# month
elif self.unit == 2:
if tracking.created_at.month == datetime.today().month:
count += 1
# year
elif self.unit == 3:
if tracking.created_at.year == datetime.today().year:
count += 1
self.percentage = int(count / self.times * 100)
# Converts the Habit data to a json format
def to_json(self):
return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True, indent=4)

View File

@ -3,8 +3,7 @@ from dataclasses import dataclass
from models.Habit import Habit
from models.User import User
from db.SQLiteClient import (create_habitList, get_habitList, get_habits, get_users, add_user, remove_user,
update_habitList, delete_habitList, habitList_get_next_slot, habitList_get_slots,
habitList_update_slot)
update_habitList, delete_habitList)
@dataclass
@ -12,19 +11,17 @@ class HabitList:
id: int
name: str
description: str
slot: int
habits: list = None
habits: list = None #? unclear usage
@staticmethod
def create(user_id: int, name: str, description: str):
slot = habitList_get_next_slot(user_id)
id = create_habitList(user_id, name, description, slot)
return HabitList(id, name, description, slot)
id = create_habitList(user_id, name, description)
return HabitList(id, name, description)
@staticmethod
def get(id: int):
habitList = get_habitList(id)
return HabitList(habitList[0], habitList[1], habitList[2], habitList[3]) if habitList else None
return HabitList(habitList[0], habitList[1], habitList[2]) if habitList else None
# Updates: name, description
@ -32,37 +29,11 @@ class HabitList:
update_habitList(self.id, self.name, self.description)
# Updates the slot and reorders the HabitLists accordingly
def update_slot(self,user_id: int, new_slot: int):
# Fetches a list with the following structure [(id, slot), (id, slot), ...]
slots = habitList_get_slots(user_id)
# Splits the list depending on whether the new slot is higher or lower than the current one
if new_slot > self.slot: # Example self.slot=1 new_slot=4
slots = slots[self.slot:new_slot] # Expected list: [(id, 2), (id, 3), (id, 4)]
for slot in slots:
habitList_update_slot(slot[0], slot[1]-1)
if new_slot < self.slot: # Example self.slot=4 new_slot=1
slots = slots[new_slot-1:self.slot-1] # Expected list: [(id, 1), (id, 2), (id, 3)]
for slot in slots:
habitList_update_slot(slot[0], slot[1]+1)
# Update the slot of the current habitList
habitList_update_slot(self.id, new_slot)
# Deletes the HabitList | The id of the current user is necessary
def delete(self, user_id):
# Reorders the slots
slots = habitList_get_slots(user_id)[self.slot+1:]
for slot in slots:
habitList_update_slot(slot[0], slot[1] - 1)
if len(get_users(self.id)) > 1:
if len(get_users) > 1:
self.remove_user(user_id)
else:
for habit in self.get_habits():
habit.delete()
delete_habitList(self.id)
@ -71,7 +42,7 @@ class HabitList:
raw_habits = get_habits(self.id)
habits = []
for habit in raw_habits:
habit = Habit(habit[0], habit[1], habit[2], habit[3], habit[4], habit[5], habit[6], habit[7], habit[8], habit[9])
habit = Habit(habit[0], habit[1], habit[2], habit[3], habit[4], habit[5], habit[6])
habits.append(habit)
return habits
@ -82,18 +53,17 @@ class HabitList:
raw_users = get_users(self.id)
users = []
for user in raw_users:
user = User(user[0], user[1], user[2], user[3], user[4], user[5])
user = User(user[0], user[1], user[2], user[3])
users.append(user)
return users
# Adds a User by email to the HabitList
def add_user(self, user: User):
if user:
add_user(self.id, user.id)
else:
return None
def add_user(self, email: str):
user = User.get_by_email(email)
add_user(self.id, user.id)
# Removes a User from the HabitList
def remove_user(self, user_id):

View File

@ -1,40 +1,37 @@
from datetime import datetime
from flask_login import UserMixin
from db.SQLiteClient import (create_user, get_user, get_user_by_email, update_user, delete_user,
get_habitLists, get_heatmap_value, accept_List, get_unaccepted_habitLists)
get_habitLists, get_heatmap_value)
class User(UserMixin):
def __init__(self, id: int, name: str, email: str, password: str=None, profile_image:str=None, heatmap_color: str=None):
def __init__(self, id: int, name: str, email: str, password: str = None, profile_image:str = None):
self.id = id
self.name = name
self.email = email
self.password = password
self.profile_image = profile_image
self.heatmap_color = heatmap_color
@staticmethod
def create(name: str, email: str, password: str):
heatmap_color = "#00FF00"
id, profile_image = create_user(name, email, password, heatmap_color)
return User(id=id, name=name, email=email, profile_image=profile_image, heatmap_color=heatmap_color)
id, profile_image = create_user(name, email, password)
return User(id=id, name=name, email=email, profile_image=profile_image)
@staticmethod
def get(id: int):
user = get_user(id)
return User(user[0], user[1], user[2], user[3], user[4], user[5]) if user else None
return User(user[0], user[1], user[2], user[3], user[4]) if user else None
@staticmethod
def get_by_email(email: str):
user = get_user_by_email(email)
return User(user[0], user[1], user[2], user[3], user[4], user[5]) if user else None
return User(user[0], user[1], user[2], user[3], user[4]) if user else None
# Updates: name, email, password
def update(self):
update_user(self.id, self.name, self.email, self.password if self.password else None, self.profile_image, self.heatmap_color)
update_user(self.id, self.name, self.email, self.password if self.password else None)
# Deletes the User
def delete(self):
@ -54,42 +51,16 @@ class User(UserMixin):
raw_habitLists = get_habitLists(self.id)
habitLists = []
for habitList in raw_habitLists:
accepted = habitList[6]
habitList = HabitList(habitList[0], habitList[1], habitList[2], habitList[3])
if accepted == 1:
habitLists.append(habitList)
return habitLists
def get_unaccepted_habitLists(self) -> list:
from models.HabitList import HabitList
raw_habitLists = get_unaccepted_habitLists(self.id)
habitLists = []
for habitList in raw_habitLists:
habitList = HabitList(habitList[0], habitList[1], habitList[2], habitList[3])
habitList = HabitList(habitList[0], habitList[1], habitList[2])
habitLists.append(habitList)
return habitLists
# Returns all heatmap-values from the last 28 days
def get_heatmap(self) -> tuple:
# get current day of week as integer. monday is 0 and sunday is 6
weekday = 6 - datetime.today().weekday()
heatmap = [100]
# append the heatmap values of the current week
for day in range(0, weekday):
heatmap.append(0)
for day in range (0, 28-weekday):
def get_heatmap(self) -> list:
heatmap = []
for day in range (0, 28):
value = get_heatmap_value(self.id, day)
heatmap.append(value)
heatmap.reverse()
day = 27-weekday
return heatmap, day
def accept_List(self, HabitList_id):
accept_List(HabitList_id, self.id)
return heatmap

View File

@ -1,3 +0,0 @@
pillow~=10.2.0
flask~=3.0.0
flask-login~=0.6.3

View File

@ -1,73 +0,0 @@
#heatmap {
display: grid;
grid-template-columns: repeat(7, 0fr); /* 7 Tage in einer Woche */
gap: 5px;
width: 100%;
table-layout: fixed;
}
.day {
width: 50px;
height: 50px;
border: 1px solid #ccc;
display: flex;
align-items: center;
justify-content: center;
table-layout: fixed;
}
@media (max-width: 1400px) {
.day {
width: 40px;
height: 40px;
}
}
@media (max-width: 1200px) {
.day {
width: 35px;
height: 35px;
}
}
@media (max-width: 992px) {
.day {
width: 30px;
height: 30px;
}
}
@media (max-width: 767px) {
.day {
width: 50px;
height: 50px;
}
}
@media (max-width: 450px) {
.day {
width: 40px;
height: 40px;
}
}
@media (max-width: 400px) {
.day {
width: 35px;
height: 35px;
}
}
@media (max-width: 350px) {
.day {
width: 30px;
height: 30px;
}
}
@media (max-width: 300px) {
.day {
width: 25px;
height: 25px;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.4 KiB

View File

@ -1,213 +0,0 @@
// Erstellen der Heatmap
function createHeatmap(data, day) {
const heatmapContainer = document.getElementById('heatmap');
const days = ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So']
for (let i = 0; i < 7; i++) {
const dayElement = document.createElement('div');
dayElement.classList.add('day');
dayElement.textContent = days[i];
heatmapContainer.appendChild(dayElement);
}
// Aktuelles Datum des Montags in der neuen linken Spalte
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 7; j++) {
const opacity = data[i * 7 + j] / (Math.max(...data) <= 0 ? 1 : Math.max(...data)); // Berechne die Opazität basierend auf Aktivitätsanzahl
const dayElement = document.createElement('div');
dayElement.classList.add('day');
dayElement.style.backgroundColor = `rgba(${color}, ${opacity})`;
if (day === i * 7 + j){
dayElement.style.borderColor = `rgba(0, 0, 0)`;
dayElement.style.borderWidth = "2px";
}
heatmapContainer.appendChild(dayElement);
}
}
}
// Löschen der Heatmap
function deleteHeatmap() {
const heatmapContainer = document.getElementById('heatmap');
const dayElements = heatmapContainer.getElementsByClassName('day');
// Convert HTMLCollection to array and iterate to remove each element
Array.from(dayElements).forEach(element => {
element.remove();
});
}
// Animation der Progressbar
function checkCompletionAndAnimate(habitId, percentage) {
const progressBar = document.getElementById("progress-bar-" + habitId);
const habitBlock = document.getElementById("habit-" + habitId);
if (percentage === 100) {
progressBar.style.backgroundColor = "green";
habitBlock.classList.add("animate-bounce");
setTimeout(function () {
habitBlock.classList.remove("animate-bounce");
}, 2000);
} else {
progressBar.style.backgroundColor = "";
habitBlock.classList.remove("animate-bounce");
}
}
// Senden einer Post-Request, sobald ein Habit abgehackt wird
function sendPostRequest(checkboxId) {
// Get the habit id from the checkbox id attribute
const habitId = checkboxId;
// Make a POST request to /check with the habit id
axios.post('/check', {habitId: habitId}, {
headers: {
'Content-Type': 'application/json'
}
}).then(function (response) {
// Handle the success response if needed
console.log(response.data);
// Set the percentage of the habit. percentage received as integer
const percentage = response.data.percentage;
const progressBar = document.getElementById("progress-bar-" + habitId);
progressBar.style.width = percentage + "%";
checkCompletionAndAnimate(habitId, percentage);
const streak = response.data.streak;
const streakSymbol = document.getElementById("streak-" + habitId);
streakSymbol.innerText = streak > 0 ? streak.toString() + " 🔥" : "";
const heatmapValues = response.data.heatmap;
deleteHeatmap()
createHeatmap(heatmapValues, day, color)
}).catch(function (error) {
// Handle the error if needed
console.error('Error:', error);
});
}
// Senden einer Post-Request, sobald ein Habit gelöscht wird
function deleteHabit(habitId) {
// Make a POST request to /delete with the habit id
axios.post('/delete', {habitId: habitId}, {
headers: {
'Content-Type': 'application/json'
}
}).then(function (response) {
// Handle the success response if needed
console.log(response.data);
// Remove the habit from the DOM
const habitElement = document.getElementById("habit-" + habitId);
habitElement.remove();
}).catch(function (error) {
// Handle the error if needed
console.error('Error:', error);
});
}
// Senden einer Post-Request, sobald eine HabitList gelöscht wird
function deleteList(listId) {
// Make a POST request to /delete with the habit id
axios.post('/delete-list', {listId: listId}, {
headers: {
'Content-Type': 'application/json'
}
}).then(function (response) {
// Handle the success response if needed
console.log(response.data);
// Remove the habit from the DOM
let habitElement = document.getElementById("simple-tabpanel-" + listId);
habitElement.remove();
habitElement = document.getElementById("tab-" + listId);
habitElement.remove();
}).catch(function (error) {
// Handle the error if needed
console.error('Error:', error);
});
}
document.addEventListener('DOMContentLoaded', () => {
const elements = document.querySelectorAll('.task-list').values()
// loop through the elements
for (let el of elements) {
Sortable.create(el, {
handle: '.drag-handle',
animation: 150,
onEnd: function (evt) {
const habitId = el.children[evt.newIndex].id.split('-')[1];
const oldIndex = evt.oldIndex;
const newIndex = evt.newIndex;
axios.post('/reorder-habit', {habitId: habitId, oldIndex: oldIndex, newIndex: newIndex}, {
headers: {
'Content-Type': 'application/json'
}
}).then(function (response) {
// Handle the success response if needed
}).catch(function (error) {
// Handle the error if needed
console.error('Error:', error);
});
}
});
}
});
document.addEventListener('DOMContentLoaded', () => {
const listElements = document.querySelectorAll('.nav-tabs').values();
// Loop through the list elements
for (let listEl of listElements) {
Sortable.create(listEl, {
handle: '.nav-link', // Use the nav-link as the handle for dragging
animation: 150,
onEnd: function (evt) {
const listId = evt.item.id.split('-')[1];
const oldIndex = evt.oldIndex;
const newIndex = evt.newIndex;
// Send a POST request to reorder the list
axios.post('/reorder-list', { listId: listId, oldIndex: oldIndex, newIndex: newIndex}, {
headers: {
'Content-Type': 'application/json'
}
}).then(function (response) {
// Handle the success response if needed
}).catch(function (error) {
// Handle the error if needed
console.error('Error:', error);
});
}
});
}
});
$(function () {
$('[data-toggle="tooltip"]').tooltip()
})
// Aktualisieren der Uhrzeit
function updateCurrentTime() {
const currentTimeElement = document.getElementById('current-time');
const currentDate = new Date();
const options = { day: '2-digit', month: '2-digit', year: 'numeric', hour: '2-digit', minute: '2-digit', hour12: false };
const currentDateTime = currentDate.toLocaleString('de-DE', options);
currentTimeElement.innerText = currentDateTime.replace(',', ',') + ' ' + currentDate.toLocaleString('de-DE', { weekday: 'long' });
}
// Erstellt die Heatmap mit den simulierten Daten
createHeatmap(activityData, day, color);
updateCurrentTime();
setInterval(updateCurrentTime, 1000);

View File

@ -1,195 +0,0 @@
document.addEventListener("DOMContentLoaded", function() {
// Get elements
const profileImage = document.getElementById("profileImage");
const profileImageOverlay = document.getElementById("profileImageOverlay");
const profileImageInput = document.getElementById("profileImageInput");
const uploadForm = document.getElementById("uploadForm");
const editButton = document.getElementById("editButton");
const saveChangesButton = document.getElementById("saveChangesButton");
const editForm = document.getElementById("editForm");
const editModal = new bootstrap.Modal(document.getElementById('editModal'));
const submitPasswordChangeButton = document.getElementById("submitPasswordChange");
const DeleteAccountButton = document.getElementById('deleteAccountButton')
const confirmDeleteModal = new bootstrap.Modal(document.getElementById('confirmDeleteModal'));
const ConfirmDeleteButton = document.getElementById('confirmDeleteButton')
const deleteAccountForm = document.getElementById('deleteAccountForm')
// Open file input when profile image is clicked
profileImageOverlay.addEventListener("click", function() {
profileImageInput.click();
});
// Change profile image when a new file is selected
profileImageInput.addEventListener("change", function() {
const file = this.files[0];
const reader = new FileReader();
reader.onload = function(e) {
profileImage.src = e.target.result;
};
reader.readAsDataURL(file);
// Submit the form
uploadForm.submit();
});
// Add event listener to edit button to open modal
editButton.addEventListener("click", function() {
editModal.show();
});
// Add event listener to save changes button to submit form
saveChangesButton.addEventListener("click", function() {
// Perform client-side validation before submitting the form
validateForm()
.then(isValid => {
if (isValid) {
editForm.submit();
}
})
.catch(error => {
// Handle validation error
console.log("Account Form validation failed", error);
});
});
// Function to perform client-side validation
async function validateForm() {
let isValid = true;
isValid = validateInput("newName", "nameFeedback", "Bitte geben Sie einen neuen Namen ein.") && isValid;
isValid = validateEmail("newEmail", "emailFeedback", "Bitte geben Sie eine gültige E-Mail-Adresse ein.") && isValid;
try {
const passwordValid = await validatePassword("password", "passwordFeedback", "Bitte geben Sie Ihr Passwort ein.");
isValid = passwordValid && isValid;
} catch (error) {
isValid = false;
}
return isValid;
}
// Function to validate input fields
function validateInput(inputId, feedbackId, errorMessage) {
const input = document.getElementById(inputId);
const feedback = document.getElementById(feedbackId);
if (!input.value.trim()) {
feedback.textContent = errorMessage;
input.classList.add("is-invalid");
return false;
} else {
feedback.textContent = "";
input.classList.remove("is-invalid");
return true;
}
}
// Function to validate email
function validateEmail(emailId, feedbackId, errorMessage) {
const input = document.getElementById(emailId);
const feedback = document.getElementById(feedbackId);
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; // Regular expression for email validation
if (!input.value.trim()) {
feedback.textContent = "Bitte geben Sie eine neue E-Mail-Adresse ein.";
input.classList.add("is-invalid");
return false;
} else if (!emailRegex.test(input.value.trim())) {
feedback.textContent = errorMessage;
input.classList.add("is-invalid");
return false;
} else {
feedback.textContent = "";
input.classList.remove("is-invalid");
return true;
}
}
// Function to validate password
function validatePassword(passwordId, passwordFeedbackId, errorMessage) {
return new Promise((resolve, reject) => {
const passwordInput = document.getElementById(passwordId);
const passwordFeedback = document.getElementById(passwordFeedbackId);
const password = passwordInput.value; // Get the password entered by the user
if (!passwordInput.value.trim()) {
passwordFeedback.textContent = errorMessage;
passwordInput.classList.add("is-invalid");
reject(false);
} else {
axios.post('/check_password', { password: password })
.then(response => {
if (!response.data.valid) {
passwordFeedback.textContent = "Falsches Passwort.";
passwordInput.classList.add("is-invalid");
passwordInput.value = "";
reject(false);
} else {
passwordInput.classList.remove("is-invalid");
resolve(true);
}
})
.catch(error => {
console.error('Error checking password:', error);
reject(false);
});
}
});
}
submitPasswordChangeButton.addEventListener("click", function() {
// Perform client-side validation before submitting the form
validatePasswordChangeForm()
.then(isValid => {
if (isValid) {
document.getElementById("editPasswordForm").submit();
}
})
.catch(error => {
// Handle validation error
console.log("Password change form validation failed", error);
});
});
async function validatePasswordChangeForm() {
let isValid = true;
try {
const passwordValid = await validatePassword("oldPassword", "oldPasswordFeedback", "Bitte geben Sie Ihr altes Passwort ein.");
isValid = passwordValid && isValid;
} catch (error) {
isValid = false;
}
isValid = validateInput("newPassword", "newPasswordFeedback", "Bitte geben Sie Ihr neues Passwort ein.") && isValid;
isValid = validateInput("confirmPassword", "confirmPasswordFeedback", "Bitte bestätigen Sie Ihr neues Passwort.") && isValid;
// Check if new password matches confirm password
const newPassword = document.getElementById("newPassword").value.trim();
const confirmPassword = document.getElementById("confirmPassword").value.trim();
if (newPassword !== confirmPassword) {
document.getElementById("confirmPasswordFeedback").textContent = "Die Passwörter stimmen nicht überein.";
document.getElementById("confirmPassword").classList.add("is-invalid");
isValid = false;
} else {
document.getElementById("confirmPasswordFeedback").textContent = "";
document.getElementById("confirmPassword").classList.remove("is-invalid");
}
return isValid;
}
// Add event listener to edit button to open modal
DeleteAccountButton.addEventListener("click", function() {
confirmDeleteModal.show();
});
//Submit delete account form
ConfirmDeleteButton.addEventListener('click', function () {
deleteAccountForm.submit();
});
});

View File

@ -1,8 +1,8 @@
{% extends 'layouts/main.html' %}
{% block content %}
<div class="card bg-light mt-4">
<h1 class="card-header">Login</h1>
<div class="card">
<h5 class="card-header">Login</h5>
<div class="card-body column">
<form method="POST" action="/login">
<div class="mb-3 row">

View File

@ -1,38 +1,33 @@
{% extends 'layouts/main.html' %}
{% block content %}
<div class="card bg-light mt-4 p-5">
<div class="column">
<h1>Registrieren</h1>
<form method="POST" action="/signup">
<div class="mb-3">
<label for="email" class="form-label">Email-Adresse</label>
<input type="email" class="form-control {% if errors.get('email') %} is-invalid {% endif %}" id="email"
name="email" placeholder="name@example.com" value="{{ email }}">
<div class="invalid-feedback">
{{ errors.get('email', '') }}
</div>
<div class="column">
<h3>Registrieren</h3>
<form method="POST" action="/signup">
<div class="mb-3">
<label for="email" class="form-label">Email-Adresse</label>
<input type="email" class="form-control {% if errors.get('email') %} is-invalid {% endif %}" id="email" name="email" placeholder="name@example.com" value="{{ email }}">
<div class="invalid-feedback">
{{ errors.get('email', '') }}
</div>
<div class="mb-3">
<label for="name" class="form-label">Name</label>
<input type="text" class="form-control {% if errors.get('name') %} is-invalid {% endif %}" id="name"
name="name" placeholder="Max" value="{{ name }}">
<div class="invalid-feedback">
{{ errors.get('name', '') }}
</div>
</div>
<div class="mb-3">
<label for="name" class="form-label">Name</label>
<input type="text" class="form-control {% if errors.get('name') %} is-invalid {% endif %}" id="name" name="name" placeholder="Max" value="{{ name }}">
<div class="invalid-feedback">
{{ errors.get('name', '') }}
</div>
<div class="mb-3">
<label for="password" class="form-label">Passwort</label>
<input type="password" class="form-control {% if errors.get('password') %} is-invalid {% endif %}"
id="password" name="password" value="{{ password }}">
<div class="invalid-feedback">
{{ errors.get('password', '') }}
</div>
</div>
<div class="mb-3">
<label for="password" class="form-label">Passwort</label>
<input type="password" class="form-control {% if errors.get('password') %} is-invalid {% endif %}" id="password" name="password" value="{{ password }}">
<div class="invalid-feedback">
{{ errors.get('password', '') }}
</div>
<div class="col-auto">
<button type="submit" class="btn btn-primary mb-3">Registrieren</button>
</div>
</form>
</div>
</div>
<div class="col-auto">
<button type="submit" class="btn btn-primary mb-3">Registrieren</button>
</div>
</form>
</div>
{% endblock %}

View File

@ -1,20 +0,0 @@
<div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="exampleModalLabel">Bestätige</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
Möchtest du dieses Habit wirklich löschen?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">abbrechen</button>
<button type="button" class="btn btn-primary btn-danger" data-bs-dismiss="modal"
onclick="deleteHabit(selectedHabitId)">Löschen
</button>
</div>
</div>
</div>
</div>

View File

@ -1,20 +0,0 @@
<div class="modal fade" id="listenModal" tabindex="-1" aria-labelledby="listenModal" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="listenModalLabel">Bestätige</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
Möchtest du diese Liste wirklich löschen?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">abbrechen</button>
<button type="button" class="btn btn-primary btn-danger" data-bs-dismiss="modal"
onclick="deleteList(localStorage.getItem('selectedListId'));">Löschen
</button>
</div>
</div>
</div>
</div>

View File

@ -1,221 +0,0 @@
<!--suppress HtmlUnknownTarget -->
<div class="flex-fill col-md-7 col-lg-8 col-12 card bg-light p-6 mb-6">
<!-- Listen erstellen -->
<div class="row mb-3">
<h5 class="col-9">📋 Gewohnheiten</h5>
<a class="col-3 btn btn-primary p" role="button" href="/habit-list">Neue Liste erstellen</a>
</div>
<!-- Tabs zur Auswahl -->
<ul class="nav nav-tabs card-header-tabs" role="tablist">
{% for habit_list in habit_lists %}
<li class="nav-item" role="presentation" id="tab-{{ habit_list.id }}">
<a class="nav-link {% if (active_list is not none and active_list == habit_list.id) or (active_list is none and habit_list == habit_lists[0]) %} active {% endif %}"
id="simple-tab-{{habit_list.id}}"
data-bs-toggle="tab" href="#simple-tabpanel-{{habit_list.id}}" role="tab"
aria-controls="simple-tabpanel-{{habit_list.id}}" aria-selected="true">
{{habit_list.name}}
</a>
</li>
{% endfor %}
</ul>
<script>
document.addEventListener('DOMContentLoaded', function () {
// Select all the tab links
const tabLinks = document.querySelectorAll('.nav-link');
tabLinks.forEach(function(link) {
// Add a click event listener to each tab link
link.addEventListener('click', function(e) {
// Prevent the default action
e.preventDefault();
// Get the tab ID
const tabId = this.getAttribute('href').match(/simple-tabpanel-(\d+)/)[1];
// Update the URL with the new query parameter
const newUrl = updateQueryStringParameter(window.location.href, 'list', tabId);
window.history.pushState({path:newUrl}, '', newUrl);
});
});
// Update the URL with the new query parameter
function updateQueryStringParameter(uri, key, value) {
const re = new RegExp("([?&])" + key + "=.*?(&|$)", "i");
const separator = uri.indexOf('?') !== -1 ? "&" : "?";
if (uri.match(re)) {
return uri.replace(re, '$1' + key + "=" + value + '$2');
}
else {
return uri + separator + key + "=" + value;
}
}
});
</script>
<div class="tab-content pt-5" id="tab-content">
{% for habit_list in habit_lists %}
<div class="tab-pane {% if (active_list is not none and active_list == habit_list.id) or (active_list is none and habit_list == habit_lists[0]) %} active {% endif %}"
id="simple-tabpanel-{{habit_list.id}}" role="tabpanel" aria-labelledby="simple-tab-{{habit_list.id}}">
<!-- Beschreibung und Löschen von der Liste -->
<div class="row">
<div class="col">
{{ habit_list.description }}
</div>
<div class="col-1">
<button type="button" class="btn btn-xs me-3" data-bs-toggle="modal"
data-bs-target="#listenModal"
onclick="{
localStorage.setItem('selectedListId', {{ habit_list.id }});
}">
<!---onclick="setSelectedListId({{ habit_list.id }})"-->
<i class="bi bi-trash3"></i>
</button>
</div>
</div>
<div class="row mb-3 align-items-center">
<!-- Personen die zur Liste gehören -->
{% if habit_list.get_users()[0].id == current_user.id %}
{% if habit_list.get_users()|length > 1 %}
<div class="col">
<div class="avatar-stack">
{% for user in habit_list.get_users() %}
{% if current_user.id != user.id %}
<img class="avatar" src="/{{user.profile_image}}" data-toggle="tooltip" data-placement="top"
title="{{user.name}}" alt=""/>
{% endif %}
{% endfor %}
</div>
</div>
{% endif %}
<div class="col">
<!-- Knopf für das Hinzufügen einer Person zur gemeinsamen Liste -->
<a class="me-5" href="/users?habit_list={{habit_list.id}}" style="width: 40px; height: 40px; min-height: 3em;"
data-toggle="tooltip" data-placement="top" title="Benutzer einladen">
<i class="bi bi-person-fill-add" style="font-size: 24px;"></i>
</a>
<!-- Knopf für das Bearbeiten von Personen zur gemeinsamen Liste -->
{% if habit_list.get_users()|length > 1 %}
<a href="/users-edit?habit_list={{habit_list.id}}" style="width: 40px; height: 40px; min-height: 3em;"
data-toggle="tooltip" data-placement="top" title="Benutzer bearbeiten">
<i class="bi bi-pencil"></i>
</a>
{% endif %}
</div>
{% else %}
<div class="row">
<a class="me-5" href="/users-leave?habit_list={{habit_list.id}}" style="width: 40px; height: 40px; min-height: 3em;"
data-toggle="tooltip" data-placement="top" title="Liste verlassen">
<i class="bi bi-box-arrow-left" style="font-size: 24px;"></i>
</a>
<div class="col">
<div class="avatar-stack">
{% for user in habit_list.get_users() %}
{% if current_user.id != user.id %}
<img class="avatar" src="/{{user.profile_image}}" data-toggle="tooltip" data-placement="top"
title="{{user.name}}" alt=""/>
{% endif %}
{% endfor %}
</div>
</div>
{% endif %}
<div class="col-4"></div>
<!-- neue Gewohnheiten erstellen -->
<a class="col-3 btn btn-primary" role="button" href="/habit?list={{ habit_list.id }}">
Gewohnheit erstellen
</a>
</div>
<ul class="task-list row">
{% for habit in habit_list.habits %}
<li class="row d-flex align-items-center mb-2" id="habit-{{ habit.id }}">
<!-- Handle zum Verschieben -->
<div class="col-auto drag-handle" style="cursor: grab;">
<i class="bi bi-grip-vertical"></i>
</div>
<!-- Checkbox -->
<div class="col-auto">
<input {% if habit.checked %} checked {% endif %} type="checkbox" class="task-checkbox"
id="{{ habit.id }}" onclick="sendPostRequest('{{ habit.id }}')">
</div>
<!-- Name -->
<div class="col" style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis">
{{ habit.name }}
</div>
<!-- Beschreibung -->
<div class="col-md-4 d-none d-md-block text-black text-opacity-50"
style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis">
{{ habit.note }}
</div>
<!-- Streak -->
<div class="col-2" id="streak-{{ habit.id }}"
style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis">
{% if not habit.streak == 0 %}
{{ habit.streak }} 🔥
{% endif %}
</div>
<!-- Knopf für das Löschen einer Gewohnheit -->
<button type="button" class="btn btn-xs me-3" data-bs-toggle="modal"
data-bs-target="#exampleModal" style="width: 40px; height: 40px"
onclick="setSelectedHabitId({{ habit.id }})">
<i class="bi bi-trash3"></i>
</button>
<!-- Knopf für das Bearbeiten der Gewohnheit -->
<a type="button" class="btn" href="{{ url_for('edit_habit') }}?habit={{ habit.id }}"
aria-current="page"
style="width: 40px; height: 40px; min-height: 3em;">
<i class="bi bi-pencil"></i>
</a>
<!-- Progressbar -->
<div class="col-12">
<div class="progress" style="height: 2px; width: 90%">
<div class="progress-bar" id="progress-bar-{{ habit.id }}" role="progressbar"
style="width: {{ habit.percentage }}%; background-color: {% if habit.percentage >= 100 %} green {% else %} primary {% endif %}"
aria-valuenow="{{ habit.percentage }}" aria-valuemin="0"
aria-valuemax="100"></div>
</div>
</div>
</li>
{% endfor %}
</ul>
</div>
{% endfor %}
</div>
</div>
</div>
<script>
var selectedHabitId = null;
function setSelectedHabitId(habitId) {
selectedHabitId = habitId;
}
var selectedListId = null;
function setSelectedListId(listId) {
selectedlistId = listId;
}
</script>

View File

@ -1,20 +0,0 @@
<div class="flex-fill col-md-5 col-lg-4 col-12 card bg-light mb-6">
<div class="card-body">
<h5 class="card-title">📅 Heatmap</h5>
<div id="heatmap"></div>
</div>
</div>
<script>
function hexToRgb(hex) {
hex = hex.replace(/^#?([a-f\d])([a-f\d])([a-f\d])$/i, (m, r, g, b) => r + r + g + g + b + b);
const [, r, g, b] = hex.match(/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i);
return [parseInt(r, 16), parseInt(g, 16), parseInt(b, 16)];
}
// Generates activity based on the Values given by the Backend
const activityData = {{ heatmap_values }};
const day = {{ day }};
const color = hexToRgb("{{ color }}");
</script>

View File

@ -1,86 +0,0 @@
{% extends 'index.html' %}
{% block content %}
<div class="card bg-light p-5 mt-5">
<h1>Habit Bearbeiten📋</h1>
<form action="/edit-habit" method="POST">
<div class="mb-3">
<label for="name" class="form-label">Name der Gewohnheit</label>
<input type="text" class="form-control {% if errors.get('name') %} is-invalid {% endif %}" id="name"
name="name" value="{{name}}">
<div class="invalid-feedback">
{{ errors.get('name', '') }}
</div>
</div>
<div class="mb-3">
<label for="note" class="form-label">Beschreibung</label>
<input type="text" class="form-control {% if errors.get('note') %} is-invalid {% endif %}" id="note"
name="note" value="{{note}}">
<div class="invalid-feedback">
{{ errors.get('note', '') }}
</div>
</div>
<div class="row">
<div class="mb-3 col-2">
<label for="times" class="form-label">Häufigkeit</label>
<input type="number" min="1" class="form-control {% if errors.get('times') %} is-invalid {% endif %}"
id="times" name="times" value="{{times}}">
<div class="invalid-feedback">
{{ errors.get('times', '') }}
</div>
</div>
<div class="mb-3 col-10">
<label for="unit" class="form-label">Im Zeitraum</label>
<select class="form-select {% if errors.get('unit') %} is-invalid {% endif %}" id="unit" name="unit">
<option value="Tag">Tag</option>
<option value="Woche">Woche</option>
<option value="Monat">Monat</option>
<option value="Jahr">Jahr</option>
</select>
<script>
document.addEventListener('DOMContentLoaded', () => {
let selectedElement = document.getElementById('unit');
for (let option of selectedElement.options) {
if (option.value === '{{ unit }}') {
option.selected = true;
break;
}
}
});
</script>
<div class="invalid-feedback">
{{ errors.get('unit', '') }}
</div>
</div>
</div>
<input type="hidden" name="habit" id="habit" class="{% if errors.get('habit') %} is-invalid {% endif %}">
<div class="invalid-feedback">
{{ errors.get('list_query', '') }}
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Extracting the list-query from the URL
let listQuery = new URLSearchParams(window.location.search).get('habit');
if ("{{ habit }}" !== "") {
listQuery = "{{ habit }}";
// Add the list_id to the URL
const url = new URL(window.location.href);
url.searchParams.set('habit', listQuery);
// window.history.pushState({}, '', url);
}
// Setting the list-query as the value of the hidden input field
document.getElementById('habit').value = listQuery;
});
</script>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
{% endblock %}

View File

@ -1,8 +1,8 @@
{% extends 'layouts/main.html' %}
{% block content %}
<div class="card bg-light p-5 mt-5">
<h1>Gewohnheitsliste erstellen📋</h1>
<h1 class="mt-5">Habitliste erstellen📋</h1>
<form action="/habit-list" method="POST">
<div class="mb-3">
@ -21,5 +21,5 @@
</div>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
{% endblock %}

View File

@ -1,8 +1,8 @@
{% extends 'layouts/main.html' %}
{% block content %}
<div class="card bg-light p-5 mt-5">
<h1>Gewohnheit erstellen📋</h1>
<h1 class="mt-5">Habit erstellen📋</h1>
<form action="/habit" method="POST">
<div class="mb-3">
@ -37,11 +37,11 @@
<option value="Jahr">Jahr</option>
</select>
<script>
document.addEventListener('DOMContentLoaded', () => {
document.addEventListener('DOMContentLoaded', (event) => {
let selectedElement = document.getElementById('unit');
for (let option of selectedElement.options) {
if (option.value === '{{ unit }}') {
if (option.value == '{{ unit }}') {
option.selected = true;
break;
}
@ -61,13 +61,13 @@
<script>
document.addEventListener('DOMContentLoaded', function() {
// Extracting the list-query from the URL
let listQuery = new URLSearchParams(window.location.search).get('list');
var listQuery = new URLSearchParams(window.location.search).get('list');
if ("{{ list_id }}" !== "") {
if ("{{ list_id }}" != "") {
listQuery = "{{ list_id }}";
// Add the list_id to the URL
const url = new URL(window.location.href);
var url = new URL(window.location.href);
url.searchParams.set('list', listQuery);
// window.history.pushState({}, '', url);
}
@ -78,5 +78,5 @@
</script>
<button type="submit" class="btn btn-primary">Submit</button>
</form>
</div>
{% endblock %}

View File

@ -1,39 +1,271 @@
{% extends 'layouts/main.html' %}
{% block content %}
<div class = "row">
<div class="col">
<h1>
{% if (current_user.is_authenticated) %}<img class="avatar avatar-xl" src="{{user.profile_image}}" alt="no image"/>{% endif %}
{{ title }}
</h1>
<h3 id="current-time"></h3>
<h1>{{ title }}</h1>
<h3>{{ utc_dt }}</h3>
<style>
#heatmap {
display: grid;
grid-template-columns: repeat(7, 0fr); /* 7 Tage in einer Woche */
gap: 5px;
}
.day {
width: 50px;
height: 50px;
border: 1px solid #ccc;
display: flex;
align-items: center;
justify-content: center;
}
</style>
<div class="row">
<div class="col-md-5 col-12">
<div id="heatmap"></div>
</div>
<script>
// Funktion zur Rückgabe des Montagsdatums
function getMonday(date) {
const day = date.getDay();
const diff = date.getDate() - day + (day === 0 ? -6 : 1); // Anpassung für Sonntag
return new Date(date.setDate(diff));
}
// Simulierte Aktivitätsdaten (ersetze dies durch deine echten Daten)
const activityData = [5, 3, 10, 5, 24, 2, 10, 47, 32, 45, 9, 5, 11, 39, 24, 2, 10, 47, 32, 45];
// Funktion zum Erstellen der Heatmap
function createHeatmap(data) {
const heatmapContainer = document.getElementById('heatmap');
// Aktuelles Datum des Montags
const days = ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So']
for (let i = 0; i < 7; i++) {
const dayElement = document.createElement('div');
dayElement.classList.add('day');
dayElement.textContent = days[i];
heatmapContainer.appendChild(dayElement);
// currentDate.setDate(currentDate.getDate() + 1);
}
// Aktuelles Datum des Montags in der neuen linken Spalte
for (let i = 0; i < 7; i++) {
for (let j = 0; j < 7; j++) {
// console.log(i * 7 + j, data[i * 7 + j], Math.max(...data));
const opacity = data[i * 7 + j] / Math.max(...data); // Berechne die Opazität basierend auf Aktivitätsanzahl
if (data[i * 7 + j]) {
const dayElement = document.createElement('div');
dayElement.classList.add('day');
dayElement.style.backgroundColor = `rgba(0, 255, 0, ${opacity})`;
heatmapContainer.appendChild(dayElement);
} else {
const dayElement = document.createElement('div');
// dayElement.classList.add('day');
// dayElement.style.backgroundColor = `rgba(0, 255, 0, ${opacity})`;
heatmapContainer.appendChild(dayElement);
}
}
}
}
// Erstelle die Heatmap mit den simulierten Daten
createHeatmap(activityData);
</script>
<div class="col-md-7 col-12">
<div class="row mb-3">
<h2 class="col-9">Gewohnheiten</h2>
<a class="col-3 btn btn-primary" role="button" href="/habit-list">Neue Liste erstellen</a>
</div>
{% for habit_list in habit_lists %}
<div class="row mb-3">
<h2 class="col-9">{{ habit_list.name }}</h2>
<a class="col-3 btn btn-primary" role="button" href="/habit?list={{ habit_list.id }}">Gewohnheit erstellen</a>
</div>
<ul class="task-list row">
{% for habit in habit_list.habits %}
<li class="row d-flex align-items-center mb-2" id="habit-{{ habit.id }}">
<div class="col-auto drag-handle" style="cursor: grab;">
<i class="bi bi-grip-vertical"></i>
</div>
<div class="col-auto">
<input {% if habit.checked %} checked {% endif %} type="checkbox" class="task-checkbox"
id="{{ habit.id }}"
onclick="sendPostRequest('{{ habit.id }}')">
</div>
<div class="col" style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis">
{{ habit.name }}
</div>
<div class="col-6" style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis">
{{ habit.note }}
</div>
<div class="col-2" style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis">
{% if habit %}
5 🔥
{% endif %}
</div>
<button type="button" class="btn btn-xs btn-danger rounded-circle" data-bs-toggle="modal"
data-bs-target="#exampleModal" style="width: 40px; height: 40px"
onclick="setSelectedHabitId({{ habit.id }})">
<i class="bi bi-trash3"></i>
</button>
<div class="col-12">
<div class="progress" style="height: 2px; width: 90%">
<div class="progress-bar" id="progress-bar-{{ habit.id }}" role="progressbar"
style="width: {{ habit.percentage }}%; background-color: {% if habit.percentage >= 100 %} green {% else %} primary {% endif %}"
aria-valuenow="{{ habit.percentage }}" aria-valuemin="0"
aria-valuemax="100"></div>
</div>
</div>
</li>
{% endfor %}
</ul>
{% endfor %}
</div>
<script>
var selectedHabitId = null;
function setSelectedHabitId(habitId) {
selectedHabitId = habitId;
}
</script>
<div class="modal fade" id="exampleModal" tabindex="-1" aria-labelledby="exampleModalLabel" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h1 class="modal-title fs-5" id="exampleModalLabel">Bestätige</h1>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
Möchtest du dieses Habit wirklich löschen?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary btn-danger" data-bs-dismiss="modal"
onclick="deleteHabit(selectedHabitId)">Löschen
</button>
</div>
</div>
</div>
</div>
<script>
function checkCompletionAndAnimate(habitId, percentage) {
var progressBar = document.getElementById("progress-bar-" + habitId);
var habitBlock = document.getElementById("habit-" + habitId);
if (percentage == 100) {
habitBlock.classList.add("animate-bounce");
setTimeout(function () {
habitBlock.classList.remove("animate-bounce");
}, 2000);
}
if (percentage >= 100) {
progressBar.style.backgroundColor = "green";
} else {
progressBar.style.backgroundColor = "";
habitBlock.classList.remove("animate-bounce");
}
}
function sendPostRequest(checkboxId) {
// Get the checkbox element using the provided ID
var checkbox = document.getElementById(checkboxId);
// console.log(checkbox);
// Get the habit id from the checkbox id attribute
var habitId = checkboxId;
// Make a POST request to /check with the habit id
axios.post('/check', {habitId: habitId}, {
headers: {
'Content-Type': 'application/json'
}
}).then(function (response) {
// Handle the success response if needed
console.log(response.data);
// Set the percentage of the habit. percentage received as integer
var percentage = response.data.percentage;
var progressBar = document.getElementById("progress-bar-" + habitId);
progressBar.style.width = percentage + "%";
if (response.data.unchecked) {
checkCompletionAndAnimate(habitId, percentage);
}
}).catch(function (error) {
// Handle the error if needed
console.error('Error:', error);
});
}
function deleteHabit(habitId) {
// Make a POST request to /delete with the habit id
axios.post('/delete', {habitId: habitId}, {
headers: {
'Content-Type': 'application/json'
}
}).then(function (response) {
// Handle the success response if needed
console.log(response.data);
// Remove the habit from the DOM
var habitElement = document.getElementById("habit-" + habitId);
habitElement.remove();
}).catch(function (error) {
// Handle the error if needed
console.error('Error:', error);
});
}
</script>
</div>
<div class="col-2">
<a href="https://anti-stress-team.de/blog/selbstmanagement/80-spannende-ideen-fuer-deinen-habit-tracker/" role="button" class="btn btn-discovery">Beliebte Habits</a>
</div>
</div>
<div class="d-md-flex gap-3">
{% if current_user.is_authenticated %}
{% include 'components/heatmap.html' %}
{% endif %}
{% if current_user.is_authenticated %}
{% include 'components/habit_lists.html' %}
{% endif %}
{% if current_user.is_authenticated %}
{% include 'components/delete_button.html' %}
{% endif %}
{% if current_user.is_authenticated %}
{% include 'components/delete_list.html' %}
{% endif %}
</div>
<script src="../static/script/script-index.js"></script>
<script>
document.addEventListener('DOMContentLoaded', (event) => {
var el = document.querySelector('.task-list');
Sortable.create(el, {
handle: '.drag-handle',
animation: 150,
onEnd: function (evt) {
var habitId = el.children[evt.newIndex].id.split('-')[1];
var oldIndex = evt.oldIndex;
var newIndex = evt.newIndex;
axios.post('/reorder', {habitId: habitId, oldIndex: oldIndex, newIndex: newIndex}, {
headers: {
'Content-Type': 'application/json'
}
}).then(function (response) {
// Handle the success response if needed
}).catch(function (error) {
// Handle the error if needed
console.error('Error:', error);
});
}
});
});
</script>
{% endblock %}

View File

@ -2,17 +2,14 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<meta content="width=device-width, initial-scale=1" name="viewport">
<meta name="viewport" content="width=device-width, initial-scale=1" >
<title>{{ title }} - HabitTracker</title>
<link rel="icon" href="/static/icon.ico">
<!-- CSS -->
<link href="../../static/css/background.css" rel="stylesheet" type="text/css">
<link href="../../static/css/profile.css" rel="stylesheet" type="text/css">
<link href="../../static/css/heatmap.css" rel="stylesheet" type="text/css">
<link crossorigin="anonymous" href="https://cdn.jsdelivr.net/npm/fastbootstrap@2.2.0/dist/css/fastbootstrap.min.css"
integrity="sha256-V6lu+OdYNKTKTsVFBuQsyIlDiRWiOmtC8VQ8Lzdm2i4=" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="../../static/css/background.css">
<link rel="stylesheet" type="text/css" href="../../static/css/profile.css">
<link href="https://cdn.jsdelivr.net/npm/fastbootstrap@2.2.0/dist/css/fastbootstrap.min.css" rel="stylesheet" integrity="sha256-V6lu+OdYNKTKTsVFBuQsyIlDiRWiOmtC8VQ8Lzdm2i4=" crossorigin="anonymous">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<!-- jQuery -->
@ -20,9 +17,7 @@
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<!-- Bootstrap JS (including Popper.js for Bootstrap 5) -->
<script crossorigin="anonymous"
integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL"
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/sortablejs@1.15.2/Sortable.min.js"></script>
<!-- Axios Library-->
@ -35,99 +30,29 @@
<nav class="navbar navbar-expand-lg" style="background-color: #000000">
<div class="container-fluid">
<a class="navbar-brand" href="{{ url_for('index') }}" style="color: ivory">HabitTracker</a>
<button aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation" class="navbar-toggler"
data-bs-target="#navbarSupportedContent" data-bs-toggle="collapse" type="button">
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarSupportedContent">
<ul class="me-auto"></ul>
<ul class="navbar-nav me-auto mb-2 mb-lg-0">
<li class="nav-item">
<a class="nav-link text-white {% if title == 'Home' %} active {% endif %}" aria-current="page" href="{{ url_for('index') }}">Home</a>
</li>
</ul>
<ul class="navbar-nav mb-2 mb-lg-0">
{% if not current_user.is_authenticated %}
<li class="nav-item me-2">
<a aria-current="page" class="btn text-white btn-primary" href="{{ url_for('login') }}">Login</a>
<a class="btn text-white btn-primary" aria-current="page" href="{{ url_for('login') }}">Login</a>
</li>
<li class="nav-item">
<a aria-current="page" class="btn btn-outline-secondary" href="{{ url_for('signup') }}">Signup</a>
<a class="btn btn-outline-secondary" aria-current="page" href="{{ url_for('signup') }}">Signup</a>
</li>
{% else %}
{% if notifications %}
<li class="nav-item me-4 mb-2 mb-lg-0">
<div class="dropdown">
<!--<button class="btn btn-default dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded="false">
Page actions
</button>-->
<i class="bi bi-bell-fill dropdown-toggle" data-bs-toggle="dropdown"
style="color: white; cursor: pointer"></i>
<span class="position-absolute top-0 start-100 translate-middle badge rounded-pill text-bg-danger">
{{ notifications|length }}
<span class="visually-hidden">unread messages</span>
</span>
<ul class="dropdown-menu" role="menu">
{% for notification in notifications %}
<li>
<div class="dropdown-item">
<div class="row">
<div class="col">
{{ notification.name }}
</div>
<div class="col">
<a class="accept-button" data-id="{{ notification.id }}" style="cursor: pointer"><i class="bi bi-check-circle-fill" style="color: green"></i></a>
</div>
<div class="col">
<a class="deny-button" data-id="{{ notification.id }}" style="cursor: pointer"><i class="bi bi-x-circle-fill" style="color: red"></i></a>
</div>
</div>
</div>
</li>
{% endfor %}
</ul>
</div>
<script>
document.addEventListener('DOMContentLoaded', function() {
var acceptButtons = document.querySelectorAll('.accept-button');
acceptButtons.forEach(function(button) {
button.addEventListener('click', function() {
var notificationId = this.getAttribute('data-id');
console.log('Notification accepted:', notificationId);
axios.post('/accept-list', {list_id: notificationId}, {
headers: {
'Content-Type': 'application/json'
}
}).then(() => {
location.reload();
});
});
});
});
document.addEventListener('DOMContentLoaded', function() {
var acceptButtons = document.querySelectorAll('.deny-button');
acceptButtons.forEach(function(button) {
button.addEventListener('click', function() {
var notificationId = this.getAttribute('data-id');
console.log('Notification accepted:', notificationId);
axios.post('/deny-list', {list_id: notificationId}, {
headers: {
'Content-Type': 'application/json'
}
}).then(() => {
location.reload();
});
});
});
});
</script>
</li>
{% endif %}
<li class="nav-item me-2 mb-2 mb-lg-0">
<a aria-current="page" class="btn text-white btn-primary" href="{{ url_for('profile') }}">Profil</a>
<li class="nav-item me-2">
<a class="btn text-white btn-primary" aria-current="page" href="{{ url_for('profile') }}">Profil</a>
</li>
<li class="nav-item me-2">
<a aria-current="page" class="btn btn-primary" href="{{ url_for('logout') }}">Logout</a>
<a class="btn btn-primary" aria-current="page" href="{{ url_for('logout') }}">Logout</a>
</li>
{% endif %}
</ul>

View File

@ -6,7 +6,7 @@
<h1 class="mb-4">Account Einstellungen👤</h1>
<!-- Account information fields -->
<div class="card bg-light mb-4">
<div class="card mb-4">
<div class="card-body d-flex">
<div>
<h5 class="card-title">Profilbild</h5>
@ -31,20 +31,11 @@
Bearbeiten
</button>
</div>
<form id="colorForm" action="/save_color" method="POST">
<div class="ml-5" style="margin-left: 50px;">
<h5 class="card-title">Heatmap Farbe</h5>
<div style="display: flex; align-items: center;">
<input type="color" name="color" class="form-control form-control-color" id="exampleColorInput" value="{{color}}" title="Choose your color" style="margin-right: 10px;">
<button type="submit" class="btn btn-primary">Speichern</button>
</div>
</div>
</form>
</div>
</div>
<!-- Password change fields -->
<div class="card bg-light mb-4">
<!-- Password change fields -->
<div class="card mb-4">
<div class="card-body">
<h5 class="card-title">Passwort ändern</h5>
<form id="editPasswordForm" action="/password" method="POST">
@ -63,40 +54,8 @@
<input type="password" class="form-control" id="confirmPassword" name="confirmPassword" autocomplete="new-password">
<div class="invalid-feedback" id="confirmPasswordFeedback"></div>
</div>
<button type="button" class="btn btn-primary" id="submitPasswordChange">Änderungen speichern</button>
</form>
<div style="margin-top: 30px;">
<h5 class="card-title">Konto löschen</h5>
<form id="deleteAccountForm" action="/delete_account" method="POST">
<button type="button" class="btn btn-danger" id="deleteAccountButton" data-toggle="modal" data-target="#confirmDeleteModal">
Konto dauerhaft löschen
</button>
</form>
</div>
</div>
</div>
</div>
<!-- Delete Account Confirmation Modal -->
<div class="modal fade" id="confirmDeleteModal" tabindex="-1" role="dialog" aria-labelledby="confirmDeleteModalLabel" aria-hidden="true">
<div class="modal-dialog mt-20" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="confirmDeleteModalLabel">Konto löschen</h5>
<span class="close-icon" data-bs-dismiss="modal" aria-label="Close">
<svg xmlns="http://www.w3.org/2000/svg" x="0px" y="0px" width="24" height="24" viewBox="0 0 72 72" class="close-icon">
<path d="M 19 15 C 17.977 15 16.951875 15.390875 16.171875 16.171875 C 14.609875 17.733875 14.609875 20.266125 16.171875 21.828125 L 30.34375 36 L 16.171875 50.171875 C 14.609875 51.733875 14.609875 54.266125 16.171875 55.828125 C 16.951875 56.608125 17.977 57 19 57 C 20.023 57 21.048125 56.609125 21.828125 55.828125 L 36 41.65625 L 50.171875 55.828125 C 51.731875 57.390125 54.267125 57.390125 55.828125 55.828125 C 57.391125 54.265125 57.391125 51.734875 55.828125 50.171875 L 41.65625 36 L 55.828125 21.828125 C 57.390125 20.266125 57.390125 17.733875 55.828125 16.171875 C 54.268125 14.610875 51.731875 14.609875 50.171875 16.171875 L 36 30.34375 L 21.828125 16.171875 C 21.048125 15.391875 20.023 15 19 15 z" stroke="none"></path>
</svg>
</span>
</div>
<div class="modal-body">
Sind Sie sicher, dass Sie Ihr Konto dauerhaft löschen möchten?
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Abbrechen</button>
<button type="submit" class="btn btn-danger" id="confirmDeleteButton">Löschen</button>
</div>
<button type="button" class="btn btn-primary" id="submitPasswordChange">Änderungen speichern</button>
</div>
</div>
</div>
@ -104,7 +63,7 @@
<!-- Edit Modal -->
<div class="modal fade" id="editModal" tabindex="-1" role="dialog" aria-labelledby="editModalLabel" aria-hidden="true">
<div class="modal-dialog mt-20" role="document">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="editModalLabel">Bearbeiten</h5>
@ -118,12 +77,12 @@
<form id="editForm" action="/profile" method="POST">
<div class="form-group">
<label for="newName">Neuer Name:</label>
<input type="text" class="form-control" id="newName" name="newName" value="{{ name }}" autocomplete="username">
<input type="text" class="form-control" id="newName" name="newName" value="{{name}}" autocomplete="username">
<div class="invalid-feedback" id="nameFeedback"></div>
</div>
<div class="form-group">
<label for="newEmail">Neue E-Mail:</label>
<input type="email" class="form-control" id="newEmail" name="newEmail" value="{{ email }}" autocomplete="username">
<input type="email" class="form-control" id="newEmail" name="newEmail" value="{{email}}" autocomplete="username">
<div class="invalid-feedback" id="emailFeedback"></div>
</div>
<div class="form-group">
@ -141,7 +100,191 @@
</div>
</div>
<script src="../static/script/script-profile.js"></script>
<script>
document.addEventListener("DOMContentLoaded", function() {
// Get elements
const profileImage = document.getElementById("profileImage");
const profileImageOverlay = document.getElementById("profileImageOverlay");
const profileImageInput = document.getElementById("profileImageInput");
const uploadForm = document.getElementById("uploadForm");
const editButton = document.getElementById("editButton");
const saveChangesButton = document.getElementById("saveChangesButton");
const editForm = document.getElementById("editForm");
const editModal = new bootstrap.Modal(document.getElementById('editModal'));
const submitPasswordChangeButton = document.getElementById("submitPasswordChange");
// Open file input when profile image is clicked
profileImageOverlay.addEventListener("click", function() {
profileImageInput.click();
});
// Change profile image when a new file is selected
profileImageInput.addEventListener("change", function() {
const file = this.files[0];
const reader = new FileReader();
reader.onload = function(e) {
profileImage.src = e.target.result;
};
reader.readAsDataURL(file);
// Submit the form
uploadForm.submit();
});
// Add event listener to edit button to open modal
editButton.addEventListener("click", function() {
editModal.show();
document.getElementById("newName").value = "{{ name }}";
document.getElementById("newEmail").value = "{{ email }}";
document.getElementById("password").value = "";
});
// Add event listener to save changes button to submit form
saveChangesButton.addEventListener("click", function() {
// Perform client-side validation before submitting the form
validateForm()
.then(isValid => {
if (isValid) {
console.log("Validated 2");
editForm.submit();
}
})
.catch(error => {
// Handle validation error
console.log("Account Form validation failed", error);
});
});
// Function to perform client-side validation
async function validateForm() {
let isValid = true;
isValid = validateInput("newName", "nameFeedback", "Bitte geben Sie einen neuen Namen ein.") && isValid;
isValid = validateEmail("newEmail", "emailFeedback", "Bitte geben Sie eine gültige E-Mail-Adresse ein.") && isValid;
try {
const passwordValid = await validatePassword("password", "passwordFeedback", "Bitte geben Sie Ihr Passwort ein.");
isValid = passwordValid && isValid;
} catch (error) {
isValid = false;
}
return isValid;
}
// Function to validate input fields
function validateInput(inputId, feedbackId, errorMessage) {
const input = document.getElementById(inputId);
const feedback = document.getElementById(feedbackId);
if (!input.value.trim()) {
feedback.textContent = errorMessage;
input.classList.add("is-invalid");
return false;
} else {
feedback.textContent = "";
input.classList.remove("is-invalid");
return true;
}
}
// Function to validate email
function validateEmail(emailId, feedbackId, errorMessage) {
const input = document.getElementById(emailId);
const feedback = document.getElementById(feedbackId);
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; // Regular expression for email validation
if (!input.value.trim()) {
feedback.textContent = "Bitte geben Sie eine neue E-Mail-Adresse ein.";
input.classList.add("is-invalid");
return false;
} else if (!emailRegex.test(input.value.trim())) {
feedback.textContent = errorMessage;
input.classList.add("is-invalid");
return false;
} else {
feedback.textContent = "";
input.classList.remove("is-invalid");
return true;
}
}
// Function to validate password
function validatePassword(passwordId, passwordFeedbackId, errorMessage) {
return new Promise((resolve, reject) => {
const passwordInput = document.getElementById(passwordId);
const passwordFeedback = document.getElementById(passwordFeedbackId);
const password = passwordInput.value.trim(); // Get the password entered by the user
if (!passwordInput.value.trim()) {
passwordFeedback.textContent = errorMessage;
passwordInput.classList.add("is-invalid");
reject(false);
} else {
axios.post('/check_password', { password: password })
.then(response => {
if (!response.data.valid) {
passwordFeedback.textContent = "Falsches Passwort.";
passwordInput.classList.add("is-invalid");
passwordInput.value = "";
reject(false);
} else {
passwordInput.classList.remove("is-invalid");
resolve(true);
}
})
.catch(error => {
console.error('Error checking password:', error);
reject(false);
});
}
});
}
submitPasswordChangeButton.addEventListener("click", function() {
// Perform client-side validation before submitting the form
validatePasswordChangeForm()
.then(isValid => {
if (isValid) {
document.getElementById("editPasswordForm").submit();
}
})
.catch(error => {
// Handle validation error
console.log("Password change form validation failed", error);
});
});
async function validatePasswordChangeForm() {
let isValid = true;
try {
const passwordValid = await validatePassword("oldPassword", "oldPasswordFeedback", "Bitte geben Sie Ihr altes Passwort ein.");
isValid = passwordValid && isValid;
} catch (error) {
isValid = false;
}
isValid = validateInput("newPassword", "newPasswordFeedback", "Bitte geben Sie Ihr neues Passwort ein.") && isValid;
isValid = validateInput("confirmPassword", "confirmPasswordFeedback", "Bitte bestätigen Sie Ihr neues Passwort.") && isValid;
// Check if new password matches confirm password
const newPassword = document.getElementById("newPassword").value.trim();
const confirmPassword = document.getElementById("confirmPassword").value.trim();
if (newPassword !== confirmPassword) {
document.getElementById("confirmPasswordFeedback").textContent = "Die Passwörter stimmen nicht überein.";
document.getElementById("confirmPassword").classList.add("is-invalid");
isValid = false;
} else {
document.getElementById("confirmPasswordFeedback").textContent = "";
document.getElementById("confirmPassword").classList.remove("is-invalid");
}
return isValid;
}
});
</script>
{% endblock %}

View File

@ -1,37 +0,0 @@
{% extends 'index.html' %}
{% block content %}
<div class="card bg-light p-5 mt-5">
<h1 class="mb-5">
{{ title }}
</h1>
{% for user in users %}
<form action="/user-delete" class="row" method="POST">
<div class="col">
<img src="{{ user.profile_image }}" class="avatar" alt=""/>
</div>
<div class="col">
{{ user.name }}
</div>
<div class="col">
{{ user.email }}
</div>
<div class="col">
<label>
<input hidden="hidden" name="habit_list_id" value="{{ habit_list.id }}">
</label>
<label>
<input hidden="hidden" name="habit_user_id" value="{{ user.id }}">
</label>
<button type="submit" class="btn btn-primary btn-danger">
Löschen
</button>
</div>
</form>
{% endfor %}
</div>
{% endblock %}

View File

@ -1,27 +0,0 @@
{% extends 'layouts/main.html' %}
{% block content %}
<div class="card bg-light mt-4 p-5">
<h1>{{ habit_list.name }}: {{ title }}</h1>
<p>Lade Nutzer per ihrer E-Mail-Adresse ein</p>
<form action="/users" method="POST">
<div class="mb-3">
<label for="email" class="form-label">E-Mail</label>
<input type="text" placeholder="beispiel@cimeyclust.com"
class="form-control {% if errors.get('email') %} is-invalid {% endif %}" id="email" name="email"
value="{{email}}">
<div class="invalid-feedback">
{{ errors.get('email', '') }}
</div>
</div>
<label>
<input hidden="hidden" name="habit_list_id" value="{{ habit_list.id }}">
</label>
<!-- submit button -->
<button type="submit" class="btn btn-primary">Einladen</button>
</form>
</div>
{% endblock %}