changes to the function and the slyle in regarts to the downgrade and the Werberemail poption

This commit is contained in:
2026-10-05 15:50:05 +02:00
parent 1aa46c4a43
commit 30f6ee0cbf
5 changed files with 160 additions and 46 deletions
+32 -12
View File
@@ -2164,6 +2164,19 @@ def user_profile():
flash("Ihre Profilinformationen wurden erfolgreich aktualisiert.", "success")
return redirect(url_for('user_profile'))
elif action == 'update_marketing':
marketing_opt_in = request.form.get('marketing_opt_in') == 'on'
if user_store.update_marketing_opt_in(username, marketing_opt_in):
message = (
"Sie wurden vom E-Mail-Werbeverteiler abgemeldet."
if not marketing_opt_in
else "Sie erhalten künftig wieder Produktupdates und Marketinginformationen per E-Mail."
)
flash(message, "success")
else:
flash("Die E-Mail-Einstellung konnte nicht gespeichert werden.", "error")
return redirect(url_for('user_profile'))
elif action == 'change_password':
current_password = request.form.get('current_password')
new_password = request.form.get('new_password')
@@ -2218,17 +2231,6 @@ def preise():
paid_instance = owned_instance if owned_instance and not owned_instance.get("is_trial") else None
current_package_keys = _instance_package_keys(paid_instance)
current_module_keys = sorted(_package_modules(current_package_keys))
downgrade_options = []
if paid_instance:
current_modules = set(current_module_keys)
for package_key in ("startpaket", "inventarsystem", "buecherei", "terminverwaltung"):
package_modules = _package_modules([package_key])
if package_modules and package_modules < current_modules:
downgrade_options.append({
"id": package_key,
"name": BOOKING_PACKAGE_MAP[package_key],
"price": BOOKING_PRICE_MAP[package_key],
})
return render_template(
"preise.html",
trial_instance=trial_instance,
@@ -2236,7 +2238,6 @@ def preise():
paid_instance=paid_instance,
current_package_keys=current_package_keys,
current_module_keys=current_module_keys,
downgrade_options=downgrade_options,
)
@@ -2294,6 +2295,23 @@ def _instance_package_keys(instance: dict | None) -> list[str]:
))
def _get_downgrade_options(instance: dict | None) -> list[dict]:
if not instance or instance.get("is_trial"):
return []
current_modules = _package_modules(_instance_package_keys(instance))
options = []
for package_key in ("startpaket", "inventarsystem", "buecherei", "terminverwaltung"):
package_modules = _package_modules([package_key])
if package_modules and package_modules < current_modules:
options.append({
"id": package_key,
"name": BOOKING_PACKAGE_MAP[package_key],
"price": BOOKING_PRICE_MAP[package_key],
})
return options
def _booking_restriction(instance: dict | None, package_list: list[str], booking_action: str) -> str | None:
if not instance or instance.get("is_trial"):
return None
@@ -3805,6 +3823,7 @@ def my_instance_management():
finally:
req_client.close()
paid_instance = instance_doc if instance_doc and not instance_doc.get("is_trial") else None
return render_template(
"my_instance.html",
instance=instance_view,
@@ -3813,6 +3832,7 @@ def my_instance_management():
suggested_subdomain=suggested_subdomain,
parent_domain=INSTANCE_PARENT_DOMAIN,
default_school_name=display_name,
downgrade_options=_get_downgrade_options(paid_instance),
)
+73 -20
View File
@@ -8,26 +8,33 @@
<p>Hier sehen Sie ausschließlich Ihre zugewiesene Instanz und können diese direkt nutzen.</p>
</section>
<section class="instance-card">
<h2>Instanzdaten</h2>
<section class="instance-card instance-card--primary">
<div class="instance-card__header">
<div>
<span class="instance-eyebrow">Ihre Umgebung</span>
<h2>Instanzdaten</h2>
</div>
{% if instance %}<span class="instance-status">{{ instance.status }}</span>{% endif %}
</div>
{% if instance %}
<p><strong>Schule:</strong> {{ instance.school_name }}</p>
<p><strong>Subdomain:</strong> {{ instance.subdomain }}</p>
<p><strong>Status:</strong> {{ instance.status }}</p>
<p><strong>Zahlungsstatus:</strong> {{ instance.billing_state }}{% if instance.payment_due_at %} | Zahlungsziel: {{ instance.payment_due_at }}{% endif %}</p>
<div class="instance-details">
<p><strong>Schule</strong><span>{{ instance.school_name }}</span></p>
<p><strong>Subdomain</strong><span>{{ instance.subdomain }}</span></p>
<p><strong>Zahlungsstatus</strong><span>{{ instance.billing_state }}{% if instance.payment_due_at %} | Zahlungsziel: {{ instance.payment_due_at }}{% endif %}</span></p>
</div>
{% if instance.billing_state == "suspended" %}
<p style="color:#8b2a27; font-weight:700;">Die Instanz ist gesperrt, weil die Rechnung nicht innerhalb des 14-tägigen Zahlungsziels als bezahlt bestätigt wurde.</p>
<p class="instance-alert instance-alert--error">Die Instanz ist gesperrt, weil die Rechnung nicht innerhalb des 14-tägigen Zahlungsziels als bezahlt bestätigt wurde.</p>
{% elif instance.payment_status == "pending" and instance.payment_due_at %}
<p style="color:#7a5b00;">Die Instanz bleibt bis zum Zahlungsziel aktiv. Nach der Admin-Bestätigung „Bezahlt“ bleibt sie dauerhaft freigeschaltet.</p>
<p class="instance-alert instance-alert--warning">Die Instanz bleibt bis zum Zahlungsziel aktiv. Nach der Admin-Bestätigung „Bezahlt“ bleibt sie dauerhaft freigeschaltet.</p>
{% endif %}
{% if instance.last_message %}<p><strong>Hinweis:</strong> {{ instance.last_message }}</p>{% endif %}
<p><strong>Zugang:</strong> <a href="https://{{ instance.domain }}" target="_blank" rel="noopener noreferrer">https://{{ instance.domain }}</a></p>
{% if instance.last_message %}<p class="instance-note"><strong>Hinweis:</strong> {{ instance.last_message }}</p>{% endif %}
<a class="instance-access" href="https://{{ instance.domain }}" target="_blank" rel="noopener noreferrer">Instanz öffnen <span aria-hidden="true">→</span></a>
{% if instance.is_trial %}
<p><strong>Testversion:</strong> aktiv bis {{ instance.expires_at }}</p>
<p class="instance-trial"><strong>Testversion</strong> aktiv bis {{ instance.expires_at }}</p>
{% if instance.trial_state == "active" or instance.trial_state == "in_progress" or instance.trial_state == "expired" and not instance.upgrade_requested %}
<a href="{{ url_for('preise', upgrade=1) }}" style="display:inline-block; margin-top:1rem;">Reguläre Module buchen</a>
<a class="instance-action" href="{{ url_for('preise', upgrade=1) }}">Reguläre Module buchen</a>
{% elif instance.upgrade_requested %}
<p>Ihre Upgrade-Anfrage wird vom Team bearbeitet.</p>
<p class="instance-note">Ihre Upgrade-Anfrage wird vom Team bearbeitet.</p>
{% endif %}
{% endif %}
{% elif pending_request %}
@@ -43,16 +50,62 @@
{% endif %}
</section>
{% if instance and not instance.is_trial and downgrade_options %}
<section class="instance-card instance-card--secondary">
<div class="instance-card__header">
<div>
<span class="instance-eyebrow">Paketverwaltung</span>
<h2>Version herabstufen</h2>
</div>
</div>
<p>Wählen Sie eine kleinere Version. Ihre bestehende Instanz und Daten bleiben erhalten.</p>
<div class="downgrade-options">
{% for option in downgrade_options %}
<button class="instance-action instance-action--outline" type="button" onclick="window.location.href='{{ url_for('booking_payment', packages=option.id, flow='payment', booking_action='downgrade') }}'">
{{ option.name }} <span>{{ option.price|int }} € netto/Jahr</span>
</button>
{% endfor %}
</div>
</section>
{% endif %}
<style>
.instance-head { margin-bottom: 1rem; }
.instance-head { margin-bottom: 1.4rem; }
.instance-head p { max-width: 720px; margin-top: 0.5rem; }
.instance-card {
max-width: 780px;
border: 1px solid #d8e1e8;
border-radius: 14px;
background: #ffffff;
padding: 1rem;
border: 1px solid var(--line);
border-radius: var(--radius-md);
background: rgba(255, 255, 255, 0.94);
padding: 1.4rem;
box-shadow: var(--shadow-sm);
margin-bottom: 1rem;
}
.instance-card__header { display: flex; align-items: flex-start; justify-content: space-between; gap: 1rem; margin-bottom: 1rem; }
.instance-card h2 { margin: 0.15rem 0 0; }
.instance-eyebrow { color: var(--brand); font-size: 0.78rem; font-weight: 800; letter-spacing: 0.08em; text-transform: uppercase; }
.instance-status { border-radius: 999px; background: #e6f2ed; color: #1d6b50; padding: 0.35rem 0.7rem; font-size: 0.82rem; font-weight: 700; }
.instance-details { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 0.8rem; margin-bottom: 1rem; }
.instance-details p { margin: 0; padding: 0.8rem; border: 1px solid #e1e6df; border-radius: 10px; background: #fbfcf9; }
.instance-details strong, .instance-details span { display: block; }
.instance-details strong { color: var(--text-soft); font-size: 0.82rem; margin-bottom: 0.2rem; }
.instance-card p { margin: 0.5rem 0; }
.instance-alert { padding: 0.75rem 0.9rem; border-radius: 10px; font-weight: 700; }
.instance-alert--error { color: #8b2a27; background: #fff2f0; }
.instance-alert--warning { color: #7a5b00; background: #fff9e7; }
.instance-note { color: var(--text-soft); }
.instance-access, .instance-action { display: inline-flex; align-items: center; justify-content: center; gap: 0.5rem; border-radius: 999px; padding: 0.7rem 1rem; margin-top: 0.7rem; font-weight: 700; }
.instance-access, .instance-action { color: #ffffff; background: linear-gradient(120deg, #286f78 0%, #194f56 100%); }
.instance-access:hover, .instance-action:hover { transform: translateY(-1px); box-shadow: 0 6px 16px rgba(20, 53, 58, 0.14); }
.instance-action--outline { border: 1px solid #bcd0de; color: var(--brand); background: #ffffff; cursor: pointer; text-align: left; }
.instance-action--outline span { color: var(--text-soft); font-size: 0.9rem; font-weight: 600; }
.downgrade-options { display: flex; flex-wrap: wrap; gap: 0.7rem; margin-top: 1rem; }
.instance-trial { margin-top: 1rem !important; }
.instance-card--secondary { background: rgba(250, 252, 248, 0.95); }
@media (max-width: 620px) {
.instance-card__header { flex-direction: column; }
.instance-details { grid-template-columns: 1fr; }
.instance-action--outline { width: 100%; }
}
.instance-card h2 { margin: 0 0 0.6rem; }
.instance-card p { margin: 0.28rem 0; }
</style>
{% endblock %}
-13
View File
@@ -158,19 +158,6 @@
<strong>Ihre gebuchte Version bleibt bestehen.</strong>
<span> Bereits enthalten: {{ paid_instance.package or 'aktueller Paketumfang' }}. Sie können nur zusätzliche Module buchen.</span>
</div>
{% if downgrade_options %}
<section class="notice" style="margin-bottom: 1.8rem;">
<strong>Version herabstufen</strong>
<p style="margin: 0.5rem 0 1rem;">Wählen Sie eine kleinere Version. Ihre bestehende Instanz und Daten bleiben erhalten.</p>
<div class="custom-modal-actions" style="justify-content: flex-start; margin-top: 0;">
{% for option in downgrade_options %}
<button class="btn outline" type="button" onclick="goToCheckout('payment', 'downgrade', '{{ option.id }}')">
{{ option.name }} ({{ option.price|int }} € netto)
</button>
{% endfor %}
</div>
</section>
{% endif %}
{% endif %}
<section class="packages-grid" aria-label="Buchungspakete">
+38 -1
View File
@@ -122,6 +122,29 @@
.btn-danger:hover {
background-color: #b91c1c;
}
.marketing-status {
color: #475569;
font-size: 0.9rem;
line-height: 1.5;
margin-bottom: 16px;
}
.marketing-option {
display: flex;
align-items: flex-start;
gap: 0.65rem;
color: #475569;
font-size: 0.95rem;
line-height: 1.45;
cursor: pointer;
}
.marketing-option input {
width: 18px;
height: 18px;
margin-top: 0.1rem;
accent-color: #1c5a63;
cursor: pointer;
flex: 0 0 auto;
}
.alert-success { background-color: #dcfce7; color: #166534; border: 1px solid #bbf7d0; }
.alert-danger { background-color: #fee2e2; color: #991b1b; border: 1px solid #fecaca; }
.alert-warning { background-color: #fef3c7; color: #92400e; border: 1px solid #fde68a; }
@@ -194,8 +217,22 @@
<button type="submit" class="btn-submit">Passwort aktualisieren</button>
</form>
</div>
<!-- Karte 3: E-Mail-Einstellungen -->
<div class="card">
<h3>E-Mail-Einstellungen</h3>
<p class="marketing-status">Verwalten Sie hier den Versand von Produktupdates, Neuigkeiten und Marketinginformationen.</p>
<form action="{{ url_for('user_profile') }}" method="POST">
<input type="hidden" name="action" value="update_marketing">
<label class="marketing-option" for="marketing_opt_in">
<input id="marketing_opt_in" name="marketing_opt_in" type="checkbox" {% if user.get('marketing_opt_in', false) %}checked{% endif %}>
<span>Ich möchte E-Mails zu Produktupdates, Neuigkeiten und Marketinginformationen erhalten.</span>
</label>
<button type="submit" class="btn-submit">Einstellung speichern</button>
</form>
</div>
<!-- Karte 3: Konto löschen -->
<!-- Karte 4: Konto löschen -->
<div class="card card-danger">
<h3>Konto löschen</h3>
<p style="color: #7f1d1d; font-size: 0.9rem; margin-bottom: 15px;">
+17
View File
@@ -243,6 +243,23 @@ def update_user_profile(username, contact_person, email):
except Exception as e:
print(f"Error updating profile: {e}")
return False
finally:
if client:
client.close()
def update_marketing_opt_in(username, marketing_opt_in):
"""Aktualisiert die Zustimmung zu Produkt- und Marketing-E-Mails."""
client = None
try:
client, users = _get_users_collection()
result = users.update_one(
{'Username': username},
{'$set': {'marketing_opt_in': bool(marketing_opt_in)}}
)
return result.matched_count > 0
except Exception as e:
print(f"Error updating marketing preference: {e}")
return False
finally:
if client:
client.close()