feat: Refine Header UI, inline Firestore rules, and fix mobile layout bugs
This commit is contained in:
parent
edbb90e5e2
commit
36c3bcc98b
|
|
@ -32,3 +32,6 @@ dist-ssr
|
||||||
*.njsproj
|
*.njsproj
|
||||||
*.sln
|
*.sln
|
||||||
*.sw?
|
*.sw?
|
||||||
|
|
||||||
|
# Deprecated (Inlined in Terraform)
|
||||||
|
firestore.rules
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
# GEMINI.md - Haumdaucher Project Handbook
|
# 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.
|
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
|
## 🦢 Project Essence
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,8 @@ kubectl create namespace $NAMESPACE --dry-run=client -o yaml | kubectl apply -f
|
||||||
echo "📦 Building Docker image..."
|
echo "📦 Building Docker image..."
|
||||||
|
|
||||||
# Try to fetch Firebase config from Terraform
|
# Try to fetch Firebase config from Terraform
|
||||||
if [ -d "terraform" ] && [ -f "terraform/terraform.tfstate" ]; then
|
if [ -d "terraform" ]; then
|
||||||
echo "🔍 Detected Terraform state. Fetching Firebase config..."
|
echo "🔍 Detected Terraform directory. Fetching Firebase config..."
|
||||||
cd terraform
|
cd terraform
|
||||||
TF_OUT=$(terraform output -json firebase_config 2>/dev/null)
|
TF_OUT=$(terraform output -json firebase_config 2>/dev/null)
|
||||||
cd ..
|
cd ..
|
||||||
|
|
|
||||||
|
|
@ -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" />
|
<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>
|
<title>Haumdaucher Regensburg</title>
|
||||||
<meta name="theme-color" content="#ffffff" />
|
<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" />
|
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
||||||
<link rel="apple-touch-icon" href="/icon-192.png" />
|
<link rel="apple-touch-icon" href="/icon-192.png" />
|
||||||
</head>
|
</head>
|
||||||
|
|
|
||||||
19
src/App.vue
19
src/App.vue
|
|
@ -100,9 +100,6 @@ const t = (key: string) => {
|
||||||
<main @click="triggerBSOD">
|
<main @click="triggerBSOD">
|
||||||
|
|
||||||
<!-- Member Banner -->
|
<!-- Member Banner -->
|
||||||
<div v-if="isAllowed" class="member-banner">
|
|
||||||
Do bist a haumdaucher 🫵 🍻
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Hero :theme="theme" :t="t" />
|
<Hero :theme="theme" :t="t" />
|
||||||
<About :t="t" />
|
<About :t="t" />
|
||||||
|
|
@ -174,20 +171,4 @@ const t = (key: string) => {
|
||||||
opacity: 0;
|
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>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -11,8 +11,9 @@ const emit = defineEmits(['update:theme', 'update:lang', 'open:game'])
|
||||||
const themes = ['classic', 'unicorn', 'luxury', 'win95', 'nat']
|
const themes = ['classic', 'unicorn', 'luxury', 'win95', 'nat']
|
||||||
|
|
||||||
import { useAuth } from '../../composables/useAuth'
|
import { useAuth } from '../../composables/useAuth'
|
||||||
import { watch } from 'vue'
|
import { watch, ref } from 'vue'
|
||||||
const { user, login, logout, error } = useAuth()
|
const { user, login, logout, error } = useAuth()
|
||||||
|
const showSettings = ref(false)
|
||||||
|
|
||||||
watch(user, (u) => {
|
watch(user, (u) => {
|
||||||
console.log("👤 Header: User changed:", u ? u.email : "null")
|
console.log("👤 Header: User changed:", u ? u.email : "null")
|
||||||
|
|
@ -23,22 +24,37 @@ watch(user, (u) => {
|
||||||
<template>
|
<template>
|
||||||
<header class="fancy-glass header-top">
|
<header class="fancy-glass header-top">
|
||||||
<div class="container top-content">
|
<div class="container top-content">
|
||||||
|
|
||||||
|
<!-- Logo & Status Area -->
|
||||||
|
<div class="branding-area">
|
||||||
<div class="logo-text">HAUMDAUCHER</div>
|
<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">
|
<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 class="auth-control">
|
||||||
<div v-if="error" class="auth-error" :title="error">⚠️</div>
|
<div v-if="error" class="auth-error" :title="error">⚠️</div>
|
||||||
<button v-if="!user" @click="login" class="login-btn">
|
<button v-if="!user" @click="login" class="login-btn">
|
||||||
Login
|
Login
|
||||||
</button>
|
</button>
|
||||||
<div v-else class="user-menu">
|
<div v-else class="user-menu">
|
||||||
<img :src="user.photoURL || ''" class="avatar" :title="user.displayName || ''" />
|
|
||||||
<button @click="logout" class="logout-btn">Exit</button>
|
<button @click="logout" class="logout-btn">Exit</button>
|
||||||
|
<img :src="user.photoURL || ''" class="avatar" :title="user.displayName || ''" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Combined switch for better mobile spacing -->
|
<!-- Combined switch for better mobile spacing -->
|
||||||
<div class="control-wrapper">
|
<div class="control-wrapper" :class="{ 'show-mobile': showSettings }">
|
||||||
|
|
||||||
<div class="switch-group">
|
<div class="switch-group">
|
||||||
<button
|
<button
|
||||||
|
|
@ -117,6 +133,12 @@ watch(user, (u) => {
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Base Layout */
|
||||||
|
.branding-area {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
.controls {
|
.controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
@ -132,6 +154,58 @@ watch(user, (u) => {
|
||||||
justify-content: flex-end;
|
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 {
|
.switch-group {
|
||||||
display: flex;
|
display: flex;
|
||||||
background: rgba(0,0,0,0.05);
|
background: rgba(0,0,0,0.05);
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ provider "registry.terraform.io/hashicorp/google" {
|
||||||
version = "5.45.2"
|
version = "5.45.2"
|
||||||
constraints = "~> 5.0"
|
constraints = "~> 5.0"
|
||||||
hashes = [
|
hashes = [
|
||||||
"h1:iy2Q9VcnMu4z/bH3v/NmI/nEpgYY7bXgJmT/hVTAUS4=",
|
"h1:y2hf6zus1eKA5vpAfoyYkNBKDBQTVqmx4OVh3iRBaRo=",
|
||||||
"zh:0d09c8f20b556305192cdbe0efa6d333ceebba963a8ba91f9f1714b5a20c4b7a",
|
"zh:0d09c8f20b556305192cdbe0efa6d333ceebba963a8ba91f9f1714b5a20c4b7a",
|
||||||
"zh:117143fc91be407874568df416b938a6896f94cb873f26bba279cedab646a804",
|
"zh:117143fc91be407874568df416b938a6896f94cb873f26bba279cedab646a804",
|
||||||
"zh:16ccf77d18dd2c5ef9c0625f9cf546ebdf3213c0a452f432204c69feed55081e",
|
"zh:16ccf77d18dd2c5ef9c0625f9cf546ebdf3213c0a452f432204c69feed55081e",
|
||||||
|
|
@ -25,7 +25,7 @@ provider "registry.terraform.io/hashicorp/google-beta" {
|
||||||
version = "5.45.2"
|
version = "5.45.2"
|
||||||
constraints = "~> 5.0"
|
constraints = "~> 5.0"
|
||||||
hashes = [
|
hashes = [
|
||||||
"h1:ME/cVZGNln4h166gyo9r7CuunzZ3FEqlIaNyQ0e9yjE=",
|
"h1:r9Tpv9w6j6hTI7MR7zeaUveGsyt/yNXjCmuO80asz98=",
|
||||||
"zh:16b77bac5d1555b7f066ba8014f4fc8a6d0de64e252a1988d3fbb400984a4b19",
|
"zh:16b77bac5d1555b7f066ba8014f4fc8a6d0de64e252a1988d3fbb400984a4b19",
|
||||||
"zh:1b13f515c4809343840aed8265915cc4191f138bdab5a8c5e1f542fdfc69989f",
|
"zh:1b13f515c4809343840aed8265915cc4191f138bdab5a8c5e1f542fdfc69989f",
|
||||||
"zh:1dcce4309aeab7c88fd36aea664d57e620d8a413b967ce513a5a866e8de901f2",
|
"zh:1dcce4309aeab7c88fd36aea664d57e620d8a413b967ce513a5a866e8de901f2",
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,13 @@ resource "google_project_service" "firestore" {
|
||||||
disable_on_destroy = false
|
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
|
# Firebase Project
|
||||||
resource "google_firebase_project" "default" {
|
resource "google_firebase_project" "default" {
|
||||||
provider = google-beta
|
provider = google-beta
|
||||||
|
|
@ -75,6 +82,7 @@ resource "google_identity_platform_config" "default" {
|
||||||
"localhost",
|
"localhost",
|
||||||
"${var.project_id}.firebaseapp.com",
|
"${var.project_id}.firebaseapp.com",
|
||||||
"${var.project_id}.web.app",
|
"${var.project_id}.web.app",
|
||||||
|
"haumdaucher.de",
|
||||||
]
|
]
|
||||||
|
|
||||||
# Enable Google Sign-In (and others if needed, but keeping it simple)
|
# 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]
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,10 +19,13 @@ terraform {
|
||||||
provider "google" {
|
provider "google" {
|
||||||
project = var.project_id
|
project = var.project_id
|
||||||
region = var.region
|
region = var.region
|
||||||
|
billing_project = var.project_id
|
||||||
|
user_project_override = true
|
||||||
}
|
}
|
||||||
|
|
||||||
provider "google-beta" {
|
provider "google-beta" {
|
||||||
project = var.project_id
|
project = var.project_id
|
||||||
region = var.region
|
region = var.region
|
||||||
|
billing_project = var.project_id
|
||||||
user_project_override = true
|
user_project_override = true
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue