From b08ae5feca34c661a069f2d5091ac0fb89dd602b Mon Sep 17 00:00:00 2001 From: Jens Luedicke Date: Sun, 6 Jul 2025 17:49:14 +0200 Subject: [PATCH] Enable configuration of an Imprint. --- app.py | 24 +++++- models.py | 5 ++ static/css/style.css | 50 +++++++++++ templates/imprint.html | 99 +++++++++++++++++++++ templates/layout.html | 26 +++--- templates/system_admin_branding.html | 123 +++++++++++++++++++++++++++ 6 files changed, 315 insertions(+), 12 deletions(-) create mode 100644 templates/imprint.html diff --git a/app.py b/app.py index 4cde1e8..dde955d 100644 --- a/app.py +++ b/app.py @@ -1,4 +1,4 @@ -from flask import Flask, render_template, request, redirect, url_for, jsonify, flash, session, g, Response, send_file +from flask import Flask, render_template, request, redirect, url_for, jsonify, flash, session, g, Response, send_file, abort from models import db, TimeEntry, WorkConfig, User, SystemSettings, Team, Role, Project, Company, CompanyWorkConfig, CompanySettings, UserPreferences, WorkRegion, AccountType, ProjectCategory, Task, SubTask, TaskStatus, TaskPriority, TaskDependency, Sprint, SprintStatus, Announcement, SystemEvent, WidgetType, UserDashboard, DashboardWidget, WidgetTemplate, Comment, CommentVisibility, BrandingSettings from data_formatting import ( format_duration, prepare_export_data, prepare_team_hours_export_data, @@ -1583,6 +1583,22 @@ def verify_2fa(): def about(): return render_template('about.html', title='About') +@app.route('/imprint') +def imprint(): + """Display the imprint/legal page if enabled""" + branding = BrandingSettings.get_current() + + # Check if imprint is enabled + if not branding or not branding.imprint_enabled: + abort(404) + + title = branding.imprint_title or 'Imprint' + content = branding.imprint_content or '' + + return render_template('imprint.html', + title=title, + content=content) + @app.route('/contact', methods=['GET', 'POST']) @login_required def contact(): @@ -2483,6 +2499,12 @@ def system_admin_branding(): branding.app_name = request.form.get('app_name', g.branding.app_name).strip() branding.logo_alt_text = request.form.get('logo_alt_text', '').strip() branding.primary_color = request.form.get('primary_color', '#007bff').strip() + + # Handle imprint settings + branding.imprint_enabled = 'imprint_enabled' in request.form + branding.imprint_title = request.form.get('imprint_title', 'Imprint').strip() + branding.imprint_content = request.form.get('imprint_content', '').strip() + branding.updated_by_id = g.user.id # Handle logo upload diff --git a/models.py b/models.py index 3154611..573ac1e 100644 --- a/models.py +++ b/models.py @@ -277,6 +277,11 @@ class BrandingSettings(db.Model): favicon_filename = db.Column(db.String(255), nullable=True) # Filename of uploaded favicon primary_color = db.Column(db.String(7), nullable=True, default='#007bff') # Hex color + # Imprint/Legal page settings + imprint_enabled = db.Column(db.Boolean, default=False) # Enable/disable imprint page + imprint_title = db.Column(db.String(200), nullable=True, default='Imprint') # Page title + imprint_content = db.Column(db.Text, nullable=True) # HTML content for imprint page + # Meta fields created_at = db.Column(db.DateTime, default=datetime.now) updated_at = db.Column(db.DateTime, default=datetime.now, onupdate=datetime.now) diff --git a/static/css/style.css b/static/css/style.css index d9df640..d71b4e9 100644 --- a/static/css/style.css +++ b/static/css/style.css @@ -563,6 +563,51 @@ footer { transition: margin-left 0.3s ease; } +.site-footer { + border-top: 1px solid #e0e0e0; +} + +.footer-content { + max-width: 1200px; + margin: 0 auto; + display: flex; + justify-content: space-between; + align-items: center; + flex-wrap: wrap; + gap: 1rem; +} + +.footer-info { + font-size: 0.875rem; + color: #666; +} + +.footer-info p { + margin: 0; +} + +.footer-links { + display: flex; + align-items: center; + gap: 0.5rem; + font-size: 0.875rem; +} + +.footer-links a { + color: #666; + text-decoration: none; + transition: color 0.2s; +} + +.footer-links a:hover { + color: var(--primary-color); +} + +.footer-separator { + color: #999; + margin: 0 0.5rem; +} + /* Full width footer when no user (no sidebar) */ body:not(.has-user) footer { margin-left: 0; @@ -1280,6 +1325,11 @@ input[type="time"]::-webkit-datetime-edit { footer { margin-left: 0; } + + .footer-content { + flex-direction: column; + text-align: center; + } .mobile-overlay.active { display: block; diff --git a/templates/imprint.html b/templates/imprint.html new file mode 100644 index 0000000..0bfb2b8 --- /dev/null +++ b/templates/imprint.html @@ -0,0 +1,99 @@ +{% extends "layout.html" %} + +{% block content %} +
+
+

{{ title }}

+ +
+ {{ content|safe }} +
+ + +
+
+ + +{% endblock %} \ No newline at end of file diff --git a/templates/layout.html b/templates/layout.html index e8544fe..7830312 100644 --- a/templates/layout.html +++ b/templates/layout.html @@ -66,9 +66,6 @@ {{ g.branding.app_name if g.branding else 'TimeTrack' }} {% endif %} - {% if g.company %} - {{ g.company.name }} - {% endif %} @@ -289,6 +329,77 @@ } /* Sync color inputs */ + +/* Toggle label styling - ensuring proper alignment */ +.form-group .toggle-label { + display: inline-flex; + align-items: center; + gap: 0.75rem; + cursor: pointer; + margin-bottom: 0.5rem; + padding: 0; +} + +.toggle-label input[type="checkbox"] { + display: none; +} + +.toggle-slider { + position: relative; + display: inline-block; + width: 50px; + height: 24px; + background: #ccc; + border-radius: 24px; + transition: background 0.3s; + flex-shrink: 0; +} + +.toggle-slider::before { + content: ''; + position: absolute; + width: 20px; + height: 20px; + border-radius: 50%; + background: white; + top: 2px; + left: 2px; + transition: transform 0.3s; +} + +.toggle-label input[type="checkbox"]:checked + .toggle-slider { + background: var(--primary-color); +} + +.toggle-label input[type="checkbox"]:checked + .toggle-slider::before { + transform: translateX(26px); +} + +.toggle-text { + font-weight: 500; + color: #495057; + line-height: 1; +} + +/* Content editor styling */ +.content-editor { + font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace; + font-size: 0.875rem; + line-height: 1.5; + background: #f8f9fa; + border: 1px solid #ced4da; + border-radius: 4px; + padding: 0.75rem; + resize: vertical; +} + +.imprint-settings { + margin-top: 1.5rem; + padding: 1.5rem; + background: #f8f9fa; + border-radius: 8px; + transition: all 0.3s ease; +} {% endblock %} \ No newline at end of file