feat: Refine Header UI, inline Firestore rules, and fix mobile layout bugs

This commit is contained in:
Moritz Graf 2026-01-06 22:03:20 +01:00
parent edbb90e5e2
commit 36c3bcc98b
9 changed files with 141 additions and 33 deletions

3
.gitignore vendored
View File

@ -32,3 +32,6 @@ dist-ssr
*.njsproj
*.sln
*.sw?
# Deprecated (Inlined in Terraform)
firestore.rules

View File

@ -1,5 +1,8 @@
# GEMINI.md - Haumdaucher Project Handbook
## 🚨 Rules
**1. Infrastructure as Code is rule #1.** Manual creation of resources (e.g., via `gcloud` or Console) is forbidden. The use of Terraform/Tofu is mandatory.
This document serves as the "Source of Truth" for the Haumdaucher website. It outlines the design principles, technical architecture, and specialized features to ensure consistent future development.
## 🦢 Project Essence

View File

@ -16,8 +16,8 @@ kubectl create namespace $NAMESPACE --dry-run=client -o yaml | kubectl apply -f
echo "📦 Building Docker image..."
# Try to fetch Firebase config from Terraform
if [ -d "terraform" ] && [ -f "terraform/terraform.tfstate" ]; then
echo "🔍 Detected Terraform state. Fetching Firebase config..."
if [ -d "terraform" ]; then
echo "🔍 Detected Terraform directory. Fetching Firebase config..."
cd terraform
TF_OUT=$(terraform output -json firebase_config 2>/dev/null)
cd ..

View File

@ -6,7 +6,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
<title>Haumdaucher Regensburg</title>
<meta name="theme-color" content="#ffffff" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
<link rel="apple-touch-icon" href="/icon-192.png" />
</head>

View File

@ -100,9 +100,6 @@ const t = (key: string) => {
<main @click="triggerBSOD">
<!-- Member Banner -->
<div v-if="isAllowed" class="member-banner">
Do bist a haumdaucher 🫵 🍻
</div>
<Hero :theme="theme" :t="t" />
<About :t="t" />
@ -174,20 +171,4 @@ const t = (key: string) => {
opacity: 0;
}
.member-banner {
background: linear-gradient(90deg, #ff9a9e 0%, #fad0c4 99%, #fad0c4 100%);
color: #555;
text-align: center;
padding: 10px;
font-weight: bold;
font-size: 1.2rem;
animation: slide-down 0.5s ease-out;
margin-top: 60px; /* Offset for fixed header */
border-bottom: 2px solid rgba(255,255,255,0.5);
}
@keyframes slide-down {
from { transform: translateY(-20px); opacity: 0; }
to { transform: translateY(0); opacity: 1; }
}
</style>

View File

@ -11,8 +11,9 @@ const emit = defineEmits(['update:theme', 'update:lang', 'open:game'])
const themes = ['classic', 'unicorn', 'luxury', 'win95', 'nat']
import { useAuth } from '../../composables/useAuth'
import { watch } from 'vue'
import { watch, ref } from 'vue'
const { user, login, logout, error } = useAuth()
const showSettings = ref(false)
watch(user, (u) => {
console.log("👤 Header: User changed:", u ? u.email : "null")
@ -23,22 +24,37 @@ watch(user, (u) => {
<template>
<header class="fancy-glass header-top">
<div class="container top-content">
<!-- Logo & Status Area -->
<div class="branding-area">
<div class="logo-text">HAUMDAUCHER</div>
<div v-if="user" class="status-message">
<span class="desktop-msg">{{ isNatUnlocked || isAllowed ? 'Du bist a Haumdaucher 🫵 🍻' : 'Vielleicht... bist du a Haumdaucher' }}</span>
<span class="mobile-msg">{{ isNatUnlocked || isAllowed ? 'Haumdaucher! 🫵' : 'Vielleicht... 🤔' }}</span>
</div>
</div>
<div class="controls">
<!-- Auth Control -->
<!-- Mobile Settings Toggle -->
<button class="settings-toggle" @click="showSettings = !showSettings">
</button>
<!-- Auth Control (Always Visible) -->
<div class="auth-control">
<div v-if="error" class="auth-error" :title="error"></div>
<button v-if="!user" @click="login" class="login-btn">
Login
</button>
<div v-else class="user-menu">
<img :src="user.photoURL || ''" class="avatar" :title="user.displayName || ''" />
<button @click="logout" class="logout-btn">Exit</button>
<img :src="user.photoURL || ''" class="avatar" :title="user.displayName || ''" />
</div>
</div>
<!-- Combined switch for better mobile spacing -->
<div class="control-wrapper">
<div class="control-wrapper" :class="{ 'show-mobile': showSettings }">
<div class="switch-group">
<button
@ -117,6 +133,12 @@ watch(user, (u) => {
white-space: nowrap;
}
/* Base Layout */
.branding-area {
display: flex;
align-items: center;
}
.controls {
display: flex;
align-items: center;
@ -132,6 +154,58 @@ watch(user, (u) => {
justify-content: flex-end;
}
.status-message {
font-size: 0.8rem;
font-weight: bold;
margin-left: 15px;
color: #555;
white-space: nowrap;
}
.mobile-msg { display: none; }
.settings-toggle { display: none; } /* Hidden on desktop */
/* Mobile adjustments */
@media (max-width: 480px) {
.branding-area {
flex-direction: column;
align-items: flex-start;
}
.status-message {
margin-left: 0;
margin-top: 2px;
}
.desktop-msg { display: none; }
.mobile-msg { display: inline; }
/* Settings Toggle Logic */
.control-wrapper {
display: none; /* Hidden by default on mobile */
position: absolute;
top: 60px;
right: 10px;
background: rgba(255, 255, 255, 0.95);
padding: 10px;
border-radius: 12px;
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
flex-direction: column;
align-items: flex-end;
}
.control-wrapper.show-mobile {
display: flex;
}
.settings-toggle {
display: block;
font-size: 1.2rem;
padding: 5px;
margin-left: 5px;
}
}
.switch-group {
display: flex;
background: rgba(0,0,0,0.05);

View File

@ -5,7 +5,7 @@ provider "registry.terraform.io/hashicorp/google" {
version = "5.45.2"
constraints = "~> 5.0"
hashes = [
"h1:iy2Q9VcnMu4z/bH3v/NmI/nEpgYY7bXgJmT/hVTAUS4=",
"h1:y2hf6zus1eKA5vpAfoyYkNBKDBQTVqmx4OVh3iRBaRo=",
"zh:0d09c8f20b556305192cdbe0efa6d333ceebba963a8ba91f9f1714b5a20c4b7a",
"zh:117143fc91be407874568df416b938a6896f94cb873f26bba279cedab646a804",
"zh:16ccf77d18dd2c5ef9c0625f9cf546ebdf3213c0a452f432204c69feed55081e",
@ -25,7 +25,7 @@ provider "registry.terraform.io/hashicorp/google-beta" {
version = "5.45.2"
constraints = "~> 5.0"
hashes = [
"h1:ME/cVZGNln4h166gyo9r7CuunzZ3FEqlIaNyQ0e9yjE=",
"h1:r9Tpv9w6j6hTI7MR7zeaUveGsyt/yNXjCmuO80asz98=",
"zh:16b77bac5d1555b7f066ba8014f4fc8a6d0de64e252a1988d3fbb400984a4b19",
"zh:1b13f515c4809343840aed8265915cc4191f138bdab5a8c5e1f542fdfc69989f",
"zh:1dcce4309aeab7c88fd36aea664d57e620d8a413b967ce513a5a866e8de901f2",

View File

@ -40,6 +40,13 @@ resource "google_project_service" "firestore" {
disable_on_destroy = false
}
resource "google_project_service" "firebaserules" {
provider = google-beta
project = var.project_id
service = "firebaserules.googleapis.com"
disable_on_destroy = false
}
# Firebase Project
resource "google_firebase_project" "default" {
provider = google-beta
@ -75,6 +82,7 @@ resource "google_identity_platform_config" "default" {
"localhost",
"${var.project_id}.firebaseapp.com",
"${var.project_id}.web.app",
"haumdaucher.de",
]
# Enable Google Sign-In (and others if needed, but keeping it simple)
@ -143,3 +151,38 @@ resource "google_firestore_document" "allowlist" {
}
})
}
# Firestore Security Rules
resource "google_firebaserules_ruleset" "firestore" {
provider = google
source {
files {
name = "firestore.rules"
content = <<-EOT
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /config/allowlist {
allow read: if request.auth != null;
}
}
}
EOT
}
}
depends_on = [
google_project_service.firestore,
google_project_service.firebaserules
]
}
resource "google_firebaserules_release" "firestore" {
provider = google
project = var.project_id
name = "cloud.firestore" # This specific name targets the default Firestore database
ruleset_name = google_firebaserules_ruleset.firestore.name
depends_on = [google_firebaserules_ruleset.firestore]
}

View File

@ -19,10 +19,13 @@ terraform {
provider "google" {
project = var.project_id
region = var.region
billing_project = var.project_id
user_project_override = true
}
provider "google-beta" {
project = var.project_id
region = var.region
billing_project = var.project_id
user_project_override = true
}