Merge remote-tracking branch 'origin/main' into feature/308-web-form
Some checks reported errors
continuous-integration/drone/push Build was killed
continuous-integration/drone/promote/production Build is failing

This commit is contained in:
Simon 2026-03-12 13:52:33 +01:00
commit 4af80a8305
Signed by: simon
GPG key ID: 40E7A58C4AA1EDB2
16 changed files with 9624 additions and 9715 deletions

View file

@ -219,24 +219,8 @@ trigger:
- main
event:
- push
- tag
steps:
- name: build-and-publish-container
image: plugins/docker
settings:
registry: git.local-it.org
repo: git.local-it.org/local-it/mitgliederverwaltung
username:
from_secret: DRONE_REGISTRY_USERNAME
password:
from_secret: DRONE_REGISTRY_TOKEN
auto_tag: true
auto_tag_suffix: ${DRONE_COMMIT_SHA:0:8}
when:
event:
- tag
- name: build-and-publish-container-branch
image: plugins/docker
settings:
@ -256,6 +240,33 @@ steps:
depends_on:
- check-fast
---
kind: pipeline
type: docker
name: build-and-release
trigger:
event:
- tag
steps:
- name: build-and-publish-container
image: plugins/docker
settings:
registry: git.local-it.org
repo: git.local-it.org/local-it/mitgliederverwaltung
username:
from_secret: DRONE_REGISTRY_USERNAME
password:
from_secret: DRONE_REGISTRY_TOKEN
auto_tag: true
when:
event:
- tag
depends_on:
- check-fast
---
kind: pipeline
type: docker

View file

@ -61,7 +61,7 @@ We are building a membership management system (Mila) using the following techno
8. [Accessibility Guidelines](#8-accessibility-guidelines)
**Related documents:**
- **UI / UX:** [`DESIGN_DUIDELINES.md`](../DESIGN_DUIDELINES.md) defines visual and interaction consistency: use of CoreComponents (no raw DaisyUI in views), page skeleton (`<.header>`, `mt-6 space-y-6`), **Back button left in header for edit/new forms** (§2.2), typography, buttons, forms, tables, flash/toast, and microcopy (e.g. German "du" and glossary). Follow "components first" and semantic variants instead of hard-coded colors.
- **UI / UX:** [`DESIGN_GUIDELINES.md`](../DESIGN_GUIDELINES.md) defines visual and interaction consistency: use of CoreComponents (no raw DaisyUI in views), page skeleton (`<.header>`, `mt-6 space-y-6`), **Back button left in header for edit/new forms** (§2.2), typography, buttons, forms, tables, flash/toast, and microcopy (e.g. German "du" and glossary). Follow "components first" and semantic variants instead of hard-coded colors.
- **Vereinfacht API:** [`docs/vereinfacht-api.md`](docs/vereinfacht-api.md) describes the finance-contact sync (find by email filter, minimal create payload, no extra required member fields).
---
@ -1339,13 +1339,16 @@ dgettext("auth", "Sign in with email")
**Extract and Merge:**
```bash
# Extract new translatable strings
mix gettext.extract
# Extract new translatable strings and merge into existing .po files (recommended)
mix gettext.extract --merge
# Merge into existing translations
# Alternative: extract only, then merge separately
mix gettext.extract
mix gettext.merge priv/gettext --on-obsolete=mark_as_obsolete
```
**Gettext merge workflow:** Prefer `mix gettext.extract --merge` so the `.pot` template is regenerated from source and merged into all locale `.po` files in one step. Edit only the `msgstr` values in `.po` files for translations; do not manually change source references, entry order, or the `.pot` file structure. If Git merge conflicts appear in `.po` or `.pot` files, resolve by removing conflict markers (keeping both sides where appropriate), then run `mix gettext.extract --merge`. If the `.pot` file is corrupted, delete it and run `mix gettext.extract --merge` to regenerate it from source.
### 3.13 Task Runner: Just
**Common Commands:**

View file

@ -2,12 +2,12 @@
**Mila** — simple, usable, self-hostable membership management for small to mid-sized clubs.
[![Build Status](https://drone.dev.local-it.cloud/api/badges/local-it/mitgliederverwaltung/status.svg)](https://drone.dev.local-it.cloud/local-it/mitgliederverwaltung)
[![Build Status](https://drone.cicd.local-it.cloud/api/badges/local-it/mitgliederverwaltung/status.svg)](https://drone.cicd.local-it.cloud/local-it/mitgliederverwaltung)
![License](https://img.shields.io/badge/license-AGPL--v3-blue)
## 🚧 Project Status
⚠️ **Early development** — not production-ready. Expect breaking changes.
⚠️ **First Version** — Expect breaking changes.
Contributions and feedback are welcome!
## ✨ Overview
@ -48,9 +48,10 @@ You can find our documentation for users here: https://wiki.local-it.org/s/mila-
- ✅ SSO via OIDC (works with Authentik, Rauthy, Keycloak, etc.)
- ✅ Sidebar navigation (standard-compliant, accessible)
- ✅ Global settings management
- 🚧 Self-service & online application
- Self-service & online application
- ✅ Accessibility improvements (WCAG 2.1 AA compliant keyboard navigation)
- 🚧 Email sending
- ✅ Email sending
- ✅ Integration of Accounting-Software ([Vereinfacht](https://github.com/vereinfacht/vereinfacht))
## 🚀 Quick Start (Development)
@ -172,13 +173,7 @@ The `OIDC_REDIRECT_URI` is auto-generated as `https://{DOMAIN}/auth/user/oidc/ca
## ⚙️ Configuration
- **Env vars:** see `.env.example`
- `OIDC_CLIENT_SECRET` — secret for your OIDC client
- Database defaults (Docker Compose):
- Host: `localhost`
- Port: `5000`
- User/pass: `postgres` / `postgres`
- DB: `mila_dev`
- **Env vars:** see `.env.example`
## 🏗️ Architecture
@ -193,6 +188,8 @@ The `OIDC_REDIRECT_URI` is auto-generated as `https://{DOMAIN}/auth/user/oidc/ca
- `lib/mv_web/` — Phoenix controllers, LiveViews, components
- `lib/mv/` — Shared helpers and business logic
- `assets/` — Tailwind, JavaScript, static files
- `test/` — All tests
📚 **Full tech stack details:** See [`CODE_GUIDELINES.md`](CODE_GUIDELINES.md)
📖 **Implementation history:** See [`docs/development-progress-log.md`](docs/development-progress-log.md)
@ -228,42 +225,19 @@ For testing the production Docker build locally:
# Copy template and edit
cp .env.example .env
nano .env
# Required variables:
SECRET_KEY_BASE=<your-generated-secret>
TOKEN_SIGNING_SECRET=<your-generated-secret>
DOMAIN=localhost # or PHX_HOST=localhost
# Optional OIDC configuration:
# OIDC_CLIENT_ID=mv
# OIDC_BASE_URL=http://localhost:8080/auth/v1
# OIDC_CLIENT_SECRET=<from-your-oidc-provider>
# OIDC_REDIRECT_URI is auto-generated as https://{DOMAIN}/auth/user/oidc/callback
# Alternative: Use _FILE variables for Docker secrets (takes priority over regular vars):
# SECRET_KEY_BASE_FILE=/run/secrets/secret_key_base
# TOKEN_SIGNING_SECRET_FILE=/run/secrets/token_signing_secret
# OIDC_CLIENT_SECRET_FILE=/run/secrets/oidc_client_secret
# DATABASE_URL_FILE=/run/secrets/database_url
# DATABASE_PASSWORD_FILE=/run/secrets/database_password
```
3. **Start development environment** (for Rauthy):
```bash
docker compose up -d
```
4. **Start production environment:**
3. **Start production environment:**
```bash
docker compose -f docker-compose.prod.yml up
```
5. **Database migrations run automatically** on app start. For manual migration:
4. **Database migrations run automatically** on app start. For manual migration:
```bash
docker compose -f docker-compose.prod.yml exec app /app/bin/mv eval "Mv.Release.migrate"
```
6. **Access the production app:**
5. **Access the production app:**
- Production App: http://localhost:4001
- Uses same Rauthy instance as dev (localhost:8080)
@ -286,9 +260,9 @@ For actual production deployment:
## 🤝 Contributing
We welcome contributions!
- Open issues and PRs in this repo.
- Please follow existing code style and conventions.
- Expect breaking changes while the project is in early development.
- Open issues and PRs in this repo
- Please follow existing code style and conventions
- Expect breaking changes while the project is in early development
## 📄 License
@ -298,4 +272,4 @@ See the [LICENSE](LICENSE) file for details.
## 📬 Contact
- Issues: [GitLab Issues](https://git.local-it.org/local-it/mitgliederverwaltung/-/issues)
- Community links: coming soon.
- E-Mail: info@local-it.org

View file

@ -195,7 +195,7 @@
- Auto-dismiss: info/success 46s, warning 68s, error 812s; dismiss button kept for accessibility.
- Implement via JS hook (e.g. `FlashAutoDismiss`) + `data-dismiss-ms` (or `data-kind`) on flash component; on timeout push `lv:clear-flash` and hide element.
- LiveView: add shared `handle_event("lv:clear-flash", %{"key" => key}, socket)` (e.g. in `MvWeb` live_view quote) calling `clear_flash(socket, key)`.
- All flashes (including “Email copied”) use the same variants (info, success, warning, error); no special tone. See `DESIGN_DUIDELINES.md` §9.
- All flashes (including “Email copied”) use the same variants (info, success, warning, error); no special tone. See `DESIGN_GUIDELINES.md` §9.
---

View file

@ -442,7 +442,7 @@ defmodule MvWeb.GlobalSettingsLive do
</.form_section>
<%!-- Vereinfacht Integration Section --%>
<.form_section title={gettext("Vereinfacht Integration")}>
<.form_section title={gettext("Accounting-Software (Vereinfacht) Integration")}>
<%= if @vereinfacht_env_configured do %>
<p class="text-sm text-base-content/70 mb-4">
{gettext("Some values are set via environment variables. Those fields are read-only.")}
@ -550,7 +550,7 @@ defmodule MvWeb.GlobalSettingsLive do
</.form>
</.form_section>
<%!-- OIDC Section --%>
<.form_section title={gettext("OIDC")}>
<.form_section title={gettext("OIDC (Single Sign-On)")}>
<%= if @oidc_env_configured do %>
<p class="text-sm text-base-content/70 mb-4">
{gettext("Some values are set via environment variables. Those fields are read-only.")}

View file

@ -28,15 +28,6 @@ defmodule MvWeb.ImportLive.Components do
"Use the data field name as the CSV column header in your file. Data fields must exist in Mila before importing, because unknown data field columns will be ignored. Groups and membership fees are not supported for import."
)}
</p>
<p class="text-sm">
<.link
href={~p"/settings#custom_fields"}
class="link"
data-testid="custom-fields-link"
>
{gettext("Manage Member Data")}
</.link>
</p>
</div>
</div>
"""

View file

@ -230,7 +230,7 @@ defmodule MvWeb.MemberLive.Show do
<%!-- Custom Fields Section --%>
<%= if Enum.any?(@custom_fields) do %>
<div>
<.section_box title={gettext("Custom Fields")}>
<.section_box title={gettext("Individual datafields")}>
<div class="grid grid-cols-2 gap-4">
<%= for custom_field <- @custom_fields do %>
<% cfv = find_custom_field_value(@member.custom_field_values, custom_field.id) %>

View file

@ -2,7 +2,7 @@
<.header>
{gettext("Listing Roles")}
<:subtitle>
{gettext("Manage user roles and their permission sets.")}
{gettext("Manage roles and their permission sets.")}
</:subtitle>
<:actions>
<%= if can?(@current_user, :create, Mv.Authorization.Role) do %>

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -78,7 +78,7 @@ defmodule MvWeb.Components.MemberFilterComponentTest do
html = render(view)
# Should have both "Payments" and "Custom Fields" group labels
assert html =~ gettext("Payments") || html =~ "Payment"
assert html =~ gettext("Custom Fields")
assert html =~ gettext("Individual datafields")
end
test "renders only payment filter when no boolean custom fields exist", %{conn: conn} do

View file

@ -77,7 +77,7 @@ defmodule MvWeb.CustomFieldLive.DeletionTest do
# Edit mode: section titles must not reappear when modal opens (regression)
refute has_element?(view, "h2", "Member fields")
refute has_element?(view, "h2", "Custom fields")
refute has_element?(view, "h2", "Individual datafields")
# Should show correct member count (1 member)
assert render(view) =~ "1 member has a value assigned for this datafield"

View file

@ -238,7 +238,6 @@ defmodule MvWeb.ImportLiveTest do
assert has_element?(view, "[data-testid='import-page']")
assert has_element?(view, "[data-testid='csv-upload-form']")
assert has_element?(view, "[data-testid='start-import-button']")
assert has_element?(view, "[data-testid='custom-fields-link']")
end
test "template links and file input are present", %{conn: conn} do

View file

@ -52,7 +52,7 @@ defmodule MvWeb.MemberLive.ShowTest do
{:ok, _view, html} = live(conn, ~p"/members/#{member}")
# Custom Fields section should be visible
assert html =~ gettext("Custom Fields")
assert html =~ gettext("Individual datafields")
# Custom field label should be visible
assert html =~ custom_field.name
@ -97,7 +97,7 @@ defmodule MvWeb.MemberLive.ShowTest do
{:ok, _view, html} = live(conn, ~p"/members/#{member}")
# Custom Fields section should be visible
assert html =~ gettext("Custom Fields")
assert html =~ gettext("Individual datafields")
# Both field labels should be visible
assert html =~ field1.name