Enable configuration of an Imprint.
This commit is contained in:
24
app.py
24
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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
@@ -1281,6 +1326,11 @@ input[type="time"]::-webkit-datetime-edit {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.footer-content {
|
||||
flex-direction: column;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.mobile-overlay.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
99
templates/imprint.html
Normal file
99
templates/imprint.html
Normal file
@@ -0,0 +1,99 @@
|
||||
{% extends "layout.html" %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container">
|
||||
<div class="imprint-page">
|
||||
<h1>{{ title }}</h1>
|
||||
|
||||
<div class="imprint-content">
|
||||
{{ content|safe }}
|
||||
</div>
|
||||
|
||||
<div class="imprint-footer">
|
||||
<a href="{{ url_for('home') }}" class="btn btn-secondary">← Back to Home</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.imprint-page {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
background: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
||||
}
|
||||
|
||||
.imprint-page h1 {
|
||||
margin-bottom: 2rem;
|
||||
color: #333;
|
||||
border-bottom: 2px solid #e0e0e0;
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
|
||||
.imprint-content {
|
||||
margin-bottom: 3rem;
|
||||
line-height: 1.8;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.imprint-content h2 {
|
||||
margin-top: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.imprint-content h3 {
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 0.75rem;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
.imprint-content p {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.imprint-content a {
|
||||
color: var(--primary-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.imprint-content a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.imprint-content ul,
|
||||
.imprint-content ol {
|
||||
margin-bottom: 1rem;
|
||||
padding-left: 2rem;
|
||||
}
|
||||
|
||||
.imprint-content li {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.imprint-content strong {
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.imprint-footer {
|
||||
margin-top: 3rem;
|
||||
padding-top: 2rem;
|
||||
border-top: 1px solid #e0e0e0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.imprint-page {
|
||||
padding: 1rem;
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.imprint-page h1 {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
@@ -66,9 +66,6 @@
|
||||
{{ g.branding.app_name if g.branding else 'TimeTrack' }}
|
||||
{% endif %}
|
||||
</a>
|
||||
{% if g.company %}
|
||||
<small class="company-name">{{ g.company.name }}</small>
|
||||
{% endif %}
|
||||
</div>
|
||||
<button class="mobile-nav-toggle" id="mobile-nav-toggle">
|
||||
<span></span>
|
||||
@@ -82,7 +79,7 @@
|
||||
{% if g.user %}
|
||||
<aside class="sidebar" id="sidebar">
|
||||
<div class="sidebar-header">
|
||||
<h2>
|
||||
<!-- <h2>
|
||||
<a href="{{ url_for('home') }}">
|
||||
{% if g.branding and g.branding.logo_filename %}
|
||||
<img src="{{ url_for('static', filename='uploads/branding/' + g.branding.logo_filename) }}"
|
||||
@@ -92,12 +89,8 @@
|
||||
{{ g.branding.app_name if g.branding else 'TimeTrack' }}
|
||||
{% endif %}
|
||||
</a>
|
||||
-->
|
||||
</h2>
|
||||
{% if g.company %}
|
||||
<div class="company-info">
|
||||
<small class="text-muted">{{ g.company.name }}</small>
|
||||
</div>
|
||||
{% endif %}
|
||||
<button class="sidebar-toggle" id="sidebar-toggle">
|
||||
<span></span>
|
||||
<span></span>
|
||||
@@ -216,8 +209,19 @@
|
||||
{% block content %}{% endblock %}
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
<p>© {{ current_year }} {{ g.branding.app_name }}. All rights reserved.</p>
|
||||
<footer class="site-footer">
|
||||
<div class="footer-content">
|
||||
<div class="footer-info">
|
||||
<p>© {{ current_year }} {{ g.branding.app_name if g.branding else 'TimeTrack' }}{% if g.company %} - {{ g.company.name }}{% endif %}. All rights reserved.</p>
|
||||
</div>
|
||||
<div class="footer-links">
|
||||
<a href="{{ url_for('about') }}">About</a>
|
||||
{% if g.branding and g.branding.imprint_enabled %}
|
||||
<span class="footer-separator">•</span>
|
||||
<a href="{{ url_for('imprint') }}">{{ g.branding.imprint_title or 'Imprint' }}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<script src="{{ url_for('static', filename='js/script.js') }}"></script>
|
||||
|
||||
@@ -136,6 +136,46 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Imprint/Legal Page -->
|
||||
<div class="form-section">
|
||||
<h3>⚖️ Imprint / Legal Page</h3>
|
||||
<div class="form-group">
|
||||
<label class="toggle-label">
|
||||
<input type="checkbox" name="imprint_enabled" id="imprint_enabled"
|
||||
{% if branding.imprint_enabled %}checked{% endif %}>
|
||||
<span class="toggle-slider"></span>
|
||||
<span class="toggle-text">Enable Imprint Page</span>
|
||||
</label>
|
||||
<small class="form-text text-muted">
|
||||
When enabled, an "Imprint" link will appear in the footer linking to your custom legal page.
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="imprint-settings" id="imprint-settings" style="{% if not branding.imprint_enabled %}display: none;{% endif %}">
|
||||
<div class="form-group">
|
||||
<label for="imprint_title">Page Title</label>
|
||||
<input type="text" id="imprint_title" name="imprint_title"
|
||||
value="{{ branding.imprint_title or 'Imprint' }}"
|
||||
class="form-control"
|
||||
placeholder="Imprint">
|
||||
<small class="form-text text-muted">
|
||||
The title that will be displayed on the imprint page.
|
||||
</small>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="imprint_content">Page Content (HTML supported)</label>
|
||||
<textarea id="imprint_content" name="imprint_content"
|
||||
class="form-control content-editor"
|
||||
rows="15"
|
||||
placeholder="Enter your imprint/legal information here...">{{ branding.imprint_content or '' }}</textarea>
|
||||
<small class="form-text text-muted">
|
||||
You can use HTML to format your content. Common tags: <h2>, <h3>, <p>, <strong>, <br>, <a href="">
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Save Button -->
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn btn-primary">💾 Save Branding Settings</button>
|
||||
@@ -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;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
@@ -306,6 +417,18 @@ document.addEventListener('DOMContentLoaded', function() {
|
||||
colorPicker.value = this.value;
|
||||
}
|
||||
});
|
||||
|
||||
// Toggle imprint settings visibility
|
||||
const imprintEnabled = document.getElementById('imprint_enabled');
|
||||
const imprintSettings = document.getElementById('imprint-settings');
|
||||
|
||||
imprintEnabled.addEventListener('change', function() {
|
||||
if (this.checked) {
|
||||
imprintSettings.style.display = 'block';
|
||||
} else {
|
||||
imprintSettings.style.display = 'none';
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user