fix trasnfer ownership

This commit is contained in:
SowinskiBraeden committed 2026-06-27 11:00:01 -07:00
1 parent d1b9feab82
commit 964a0651f5
5 files changed
+121 -3

No files matched your search

+1 -1
View File
@@ -9,7 +9,7 @@ DATA_PATH = BASE_DIR / "data" / "entries.csv"
DEFAULT_DATABASE_URL = f"sqlite:///{BASE_DIR / 'data' / 'boker-dev.sqlite3'}"
ELIGIBLE_MIN_SESSIONS = 3
APP_VERSION = "2.5.7"
APP_VERSION = "2.5.9"
def load_local_env(env_path: Path) -> None:
+27
View File
@@ -348,3 +348,30 @@ def set_league_member_role(league_id: str, user_id: str, role: str) -> LeagueMem
membership.role = role
return membership
def transfer_league_ownership(
league: League,
current_owner_user_id: str,
new_owner_user_id: str,
) -> tuple[LeagueMembership, LeagueMembership] | None:
if current_owner_user_id == new_owner_user_id:
return None
current_owner = LeagueMembership.query.filter_by(
league_id=league.id,
user_id=current_owner_user_id,
disabled_at=None,
).one_or_none()
new_owner = LeagueMembership.query.filter_by(
league_id=league.id,
user_id=new_owner_user_id,
disabled_at=None,
).one_or_none()
if current_owner is None or current_owner.role != "owner" or new_owner is None:
return None
current_owner.role = "manager"
new_owner.role = "owner"
league.created_by_user_id = new_owner_user_id
return current_owner, new_owner
+28
View File
@@ -1295,6 +1295,34 @@ def update_member_role(league_ref: str, user_id: str):
return redirect(url_for("leagues.league_settings", league_ref=league.url_ref))
@leagues_bp.post("/l/<league_ref>/settings/transfer-ownership")
@login_required
def transfer_ownership(league_ref: str):
if not db_ready():
flash("League database is not available.", "error")
return redirect(url_for("public.home"))
from league_repositories import transfer_league_ownership
league = require_league(league_ref, {"owner"})
new_owner_user_id = request.form.get("new_owner_user_id", "").strip()
current_owner_user_id = current_user_id() or ""
if not new_owner_user_id:
flash("Choose a member to transfer ownership to.", "error")
return redirect(url_for("leagues.league_settings", league_ref=league.url_ref))
transfer = transfer_league_ownership(league, current_owner_user_id, new_owner_user_id)
if transfer is None:
flash("Ownership can only be transferred to an active member.", "error")
return redirect(url_for("leagues.league_settings", league_ref=league.url_ref))
_old_owner, _new_owner = transfer
db.session.commit()
flash("League ownership transferred. Your role is now manager.", "success")
return redirect(url_for("leagues.dashboard", league_ref=league.url_ref))
@leagues_bp.post("/l/<league_ref>/archive")
@login_required
def archive_league(league_ref: str):
+27
View File
@@ -1372,7 +1372,19 @@ td a:hover { color: var(--accent); }
.btn--ghost:hover { border-color: var(--border-hi); color: var(--text-strong); }
.btn--outline { background: var(--accent-tint); color: var(--accent); border: 1px solid var(--accent-chip-bd); }
.btn--outline:hover { background: var(--accent-chip); }
.btn--warning {
background: var(--warn-tint);
color: var(--warn);
border: 1px solid var(--warn-tint-bd);
font-weight: 700;
}
.btn--warning:hover {
background: color-mix(in srgb, var(--warn) 18%, transparent);
border-color: color-mix(in srgb, var(--warn) 54%, transparent);
color: var(--warn);
}
.btn--sm { padding: 7px 13px; font-size: 13px; }
.btn:disabled { opacity: .45; cursor: not-allowed; box-shadow: none; }
/* legacy button classes mapped to new */
.primary-button {
@@ -2300,12 +2312,26 @@ select.control { cursor: pointer; }
justify-content: space-between;
gap: 20px;
flex-wrap: wrap;
padding: 4px 0 18px;
border-bottom: 1px solid var(--border);
}
.danger-row + .danger-row {
padding-top: 18px;
}
.danger-row:last-child {
padding-bottom: 0;
border-bottom: none;
}
.danger-row__title {
display: block;
color: var(--text-strong);
margin-bottom: 4px;
}
.danger-row__btn {
width: 168px;
justify-content: center;
text-align: center;
}
/* Danger button */
.btn--danger {
@@ -5199,4 +5225,5 @@ select.control { cursor: pointer; }
font-weight: 600;
color: var(--accent);
transition: gap .15s;
}
+38 -2
View File
@@ -117,18 +117,55 @@
</div>
</div>
<div class="panel__body">
<div class="danger-row">
<div>
<strong class="danger-row__title">Transfer ownership</strong>
<p class="muted-text">Choose an existing member to become the league owner. Your role will change to manager.</p>
</div>
<button class="btn btn--warning btn--sm danger-row__btn" type="button" data-modal="modal-transfer-owner" {% if members|length <= 1 %}disabled{% endif %}>Transfer ownership</button>
</div>
<div class="danger-row">
<div>
<strong class="danger-row__title">Archive league</strong>
<p class="muted-text">Removes this league from your dashboard. All players, sessions, and ledger data are preserved.</p>
</div>
<button class="btn btn--ghost btn--sm" type="button" data-modal="modal-archive">Archive league</button>
<button class="btn btn--warning btn--sm danger-row__btn" type="button" data-modal="modal-archive">Archive league</button>
</div>
</div>
</div>
</div>
<div class="modal-backdrop" id="modal-transfer-owner" hidden>
<div class="modal-card" role="dialog" aria-modal="true" aria-labelledby="modal-transfer-owner-title">
<div class="modal-card__head">
<span class="kicker" style="margin:0;">Ownership</span>
<h2 class="panel__title" id="modal-transfer-owner-title">Transfer ownership</h2>
</div>
<p class="modal-card__text">The selected member will become the owner of <strong>{{ league.name }}</strong>. Your role will change to manager.</p>
<form method="post" action="{{ url_for('leagues.transfer_ownership', league_ref=league.url_ref) }}">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<div class="form-card" style="padding:0 20px 16px;gap:12px;">
<label>
<span>New owner</span>
<select name="new_owner_user_id" id="transfer-owner-select" required>
<option value="">Select a member</option>
{% for membership, member_user in members %}
{% if membership.role != 'owner' %}
<option value="{{ member_user.id }}">{{ member_user.email }} · {{ membership.role }}</option>
{% endif %}
{% endfor %}
</select>
</label>
</div>
<div class="modal-card__actions">
<button class="btn btn--ghost btn--sm" type="button" data-close-modal>Cancel</button>
<button class="btn btn--primary btn--sm" type="submit">Transfer ownership</button>
</div>
</form>
</div>
</div>
<div class="modal-backdrop" id="modal-member-role" hidden>
<div class="modal-card" role="dialog" aria-modal="true" aria-labelledby="modal-member-role-title">
<div class="modal-card__head">
@@ -193,7 +230,6 @@
</form>
</div>
</div>
<script>
(function () {
function openModal(id) {