Club come entità principale, branding e fix infrastruttura streaming.
Introduce clubs con abbonamento, branding Active Storage e flusso registrazione società; corregge healthcheck MediaMTX (immagine distroless) e allinea dominio produzione matchlivetv.it. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
46
backend/app/views/public/clubs/billing.html.erb
Normal file
46
backend/app/views/public/clubs/billing.html.erb
Normal file
@@ -0,0 +1,46 @@
|
||||
<% content_for :title, "Abbonamento — #{@club.name}" %>
|
||||
<% content_for :robots, "noindex, nofollow" %>
|
||||
|
||||
<div class="wrap" style="padding-top:20px">
|
||||
<h1>Abbonamento</h1>
|
||||
<p>Società: <strong><%= @club.name %></strong> · Piano attuale: <strong><%= @entitlements.plan.name %></strong> (valido per tutte le squadre)</p>
|
||||
|
||||
<div class="plans">
|
||||
<% @plans.each do |plan| %>
|
||||
<div class="plan-card <%= 'featured' if plan.slug == @entitlements.plan.slug %>">
|
||||
<h3><%= plan.name %></h3>
|
||||
<% if plan.slug == "free" %>
|
||||
<div class="plan-price">€0</div>
|
||||
<% elsif plan.slug == "premium_light" %>
|
||||
<div class="plan-price" style="color:#e53935">€40/anno</div>
|
||||
<p style="color:#888;font-size:0.85rem">oppure €5/mese</p>
|
||||
<% else %>
|
||||
<div class="plan-price">€200/anno</div>
|
||||
<p style="color:#888;font-size:0.85rem">oppure €20/mese</p>
|
||||
<% end %>
|
||||
<ul>
|
||||
<li>Staff trasmissione: <%= plan.max_staff_transmission || "illimitato" %> / anno per squadra</li>
|
||||
<li>Staff regia: <%= plan.max_staff_regia || "illimitato" %> / anno per squadra</li>
|
||||
<li>Partite: <%= plan.concurrent_streams_limit || "illimitate" %> in contemporanea (tutta la società)</li>
|
||||
<li>Replay: <%= plan.recordings_enabled? ? "#{plan.recording_days} giorni" : "no" %></li>
|
||||
<li>YouTube: <% if plan.slug == "premium_light" %>Match TV Light<% elsif plan.youtube_enabled? %>canale società<% else %>no<% end %></li>
|
||||
</ul>
|
||||
<% if plan.slug == @entitlements.plan.slug %>
|
||||
<span class="btn btn-secondary" style="opacity:0.7">Piano attuale</span>
|
||||
<% elsif plan.slug == "free" %>
|
||||
<p style="color:#888;font-size:0.85rem">Contatta il supporto per downgrade.</p>
|
||||
<% elsif MatchLiveTv.stripe_enabled? %>
|
||||
<%= link_to "Attiva #{plan.name}", public_club_checkout_path(@club, plan: plan.slug), class: "btn btn-primary" %>
|
||||
<% else %>
|
||||
<p style="color:#888">Stripe non configurato</p>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<% if @entitlements.premium_active? && MatchLiveTv.stripe_enabled? %>
|
||||
<p style="margin-top:20px"><%= button_to "Gestisci su Stripe", public_club_portal_path(@club), class: "btn btn-secondary" %></p>
|
||||
<% end %>
|
||||
|
||||
<p><%= link_to "← Società", public_club_path(@club) %></p>
|
||||
</div>
|
||||
17
backend/app/views/public/clubs/edit.html.erb
Normal file
17
backend/app/views/public/clubs/edit.html.erb
Normal file
@@ -0,0 +1,17 @@
|
||||
<% content_for :title, "Modifica società — #{@club.name}" %>
|
||||
<% content_for :robots, "noindex, nofollow" %>
|
||||
|
||||
<div class="wrap" style="padding-top:20px;max-width:560px">
|
||||
<h1>Modifica società</h1>
|
||||
<div class="card">
|
||||
<%= form_with url: public_club_path(@club), method: :patch, multipart: true do %>
|
||||
<%= label_tag "club[name]", "Nome società" %>
|
||||
<%= text_field_tag "club[name]", @club.name, required: true %>
|
||||
<%= label_tag "club[sport]", "Sport principale" %>
|
||||
<%= select_tag "club[sport]", options_for_select([["Pallavolo", "volleyball"], ["Calcio", "football"], ["Basket", "basketball"]], @club.sport) %>
|
||||
<%= render "shared/branding_fields", record: @club, legend: "Logo e colori societari" %>
|
||||
<%= submit_tag "Salva", class: "btn btn-primary" %>
|
||||
<% end %>
|
||||
</div>
|
||||
<p><%= link_to "← Società", public_club_path(@club) %></p>
|
||||
</div>
|
||||
30
backend/app/views/public/clubs/new.html.erb
Normal file
30
backend/app/views/public/clubs/new.html.erb
Normal file
@@ -0,0 +1,30 @@
|
||||
<% content_for :title, "Registra società — Match Live TV" %>
|
||||
<% content_for :meta_description, "Crea la società sportiva e la prima squadra su Match Live TV." %>
|
||||
<% content_for :robots, "noindex, nofollow" %>
|
||||
|
||||
<section class="auth-page">
|
||||
<h1>La tua società</h1>
|
||||
<p class="auth-lead">Registra il club: potrai aggiungere più squadre (Under 13, Under 15, Serie C…).</p>
|
||||
<div class="card card-wide">
|
||||
<%= form_with url: public_clubs_path, multipart: true do %>
|
||||
<h2 class="form-section-title">Società</h2>
|
||||
<%= label_tag "club[name]", "Nome società / club" %>
|
||||
<%= text_field_tag "club[name]", params.dig(:club, :name), required: true, placeholder: "es. Crazy Volley Rozzano" %>
|
||||
<%= label_tag "club[sport]", "Sport principale" %>
|
||||
<%= select_tag "club[sport]", options_for_select([["Pallavolo", "volleyball"], ["Calcio", "football"], ["Basket", "basketball"]], params.dig(:club, :sport) || "volleyball") %>
|
||||
<%= render "shared/branding_fields", record: Club.new(primary_color: "#e53935", secondary_color: "#ffffff"), legend: "Logo e colori societari" %>
|
||||
|
||||
<h2 class="form-section-title" style="margin-top:24px">Prima squadra</h2>
|
||||
<%= label_tag "first_team[name]", "Nome squadra" %>
|
||||
<%= text_field_tag "first_team[name]", params.dig(:first_team, :name), required: true, placeholder: "es. Under 15" %>
|
||||
|
||||
<%= label_tag :plan, "Piano iniziale (per tutta la società)" %>
|
||||
<%= select_tag :plan, options_for_select([
|
||||
["Free — 1 staff trasmissione + 1 regia per squadra, 1 live", "free"],
|
||||
["Premium Light — €40/anno o €5/mese", "premium_light"],
|
||||
["Premium Full — €200/anno o €20/mese", "premium_full"]
|
||||
], params[:plan] || "free") %>
|
||||
<%= submit_tag "Crea società", class: "btn btn-primary" %>
|
||||
<% end %>
|
||||
</div>
|
||||
</section>
|
||||
61
backend/app/views/public/clubs/show.html.erb
Normal file
61
backend/app/views/public/clubs/show.html.erb
Normal file
@@ -0,0 +1,61 @@
|
||||
<% content_for :title, "#{@club.name} — Società" %>
|
||||
<% content_for :robots, "noindex, nofollow" %>
|
||||
|
||||
<div class="wrap club-dashboard" style="padding-top:20px">
|
||||
<header class="club-header" style="--club-primary:<%= @club.effective_primary_color %>;--club-secondary:<%= @club.effective_secondary_color %>">
|
||||
<% if @club.effective_logo_url.present? %>
|
||||
<%= image_tag @club.effective_logo_url, alt: "", class: "club-header-logo", width: 56, height: 56 %>
|
||||
<% end %>
|
||||
<div>
|
||||
<h1><%= @club.name %></h1>
|
||||
<% if @entitlements %>
|
||||
<p class="club-meta">
|
||||
Piano: <strong><%= @entitlements.plan.name %></strong>
|
||||
· Squadre: <strong><%= @teams.size %></strong>
|
||||
</p>
|
||||
<% end %>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div style="display:flex;gap:12px;flex-wrap:wrap;margin:20px 0">
|
||||
<%= link_to "Modifica società", public_edit_club_path(@club), class: "btn btn-secondary" %>
|
||||
<%= link_to "Abbonamento", public_club_billing_path(@club), class: "btn btn-primary" %>
|
||||
<%= link_to "Nuova squadra", public_new_club_team_path(@club), class: "btn btn-secondary" %>
|
||||
<%= link_to "Dirette live", public_live_index_path, class: "btn btn-secondary" %>
|
||||
</div>
|
||||
|
||||
<h2>Squadre</h2>
|
||||
<div class="card">
|
||||
<% if @teams.any? %>
|
||||
<table class="data">
|
||||
<thead><tr><th>Squadra</th><th>Branding</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
<% @teams.each do |team| %>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="team-swatch" style="background:<%= team.effective_primary_color %>"></span>
|
||||
<strong><%= team.name %></strong>
|
||||
</td>
|
||||
<td style="color:#888;font-size:0.88rem">
|
||||
<% if team.logo_url.present? || team.logo_file.attached? || team.primary_color.present? %>
|
||||
Personalizzato
|
||||
<% else %>
|
||||
Eredita dalla società
|
||||
<% end %>
|
||||
</td>
|
||||
<td style="white-space:nowrap">
|
||||
<%= link_to "Dashboard", public_team_dashboard_path(team) %>
|
||||
·
|
||||
<%= link_to "Modifica", public_edit_team_path(team) %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<% else %>
|
||||
<p style="color:#888">Nessuna squadra. <%= link_to "Aggiungi la prima", public_new_club_team_path(@club) %>.</p>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<p style="color:#888;margin-top:24px">Ogni squadra ha il proprio staff trasmissione/regia. L’abbonamento vale per tutta la società.</p>
|
||||
</div>
|
||||
@@ -69,6 +69,6 @@
|
||||
<div class="card" style="margin-top:32px;text-align:center">
|
||||
<h3 style="margin-top:0">Esigenze diverse?</h3>
|
||||
<p style="color:#aaa;margin-bottom:16px">Per società con più squadre, tornei o integrazioni custom, contattaci: troviamo insieme l'offerta migliore.</p>
|
||||
<%= mail_to "info@matchlivetv.eminux.it", "Contattaci", class: "btn btn-primary" %>
|
||||
<%= mail_to "info@matchlive.it", "Contattaci", class: "btn btn-primary" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -39,7 +39,7 @@
|
||||
</div>
|
||||
|
||||
<div class="card" style="margin-top:24px;text-align:center">
|
||||
<p style="color:#aaa">Esigenze diverse? <%= mail_to "info@matchlivetv.eminux.it", "Contattaci" %> per un'offerta su misura.</p>
|
||||
<p style="color:#aaa">Esigenze diverse? <%= mail_to "info@matchlive.it", "Contattaci" %> per un'offerta su misura.</p>
|
||||
</div>
|
||||
|
||||
<% if @entitlements.premium_active? && MatchLiveTv.stripe_enabled? %>
|
||||
|
||||
@@ -3,40 +3,73 @@
|
||||
<% content_for :robots, "noindex, nofollow" %>
|
||||
|
||||
<div class="wrap" style="padding-top:20px">
|
||||
<h1><%= @team.name %></h1>
|
||||
<p style="color:#888;margin:0 0 8px"><%= link_to @club.name, public_club_path(@club) %> · <%= link_to "Modifica squadra", public_edit_team_path(@team) %></p>
|
||||
<h1 style="display:flex;align-items:center;gap:12px">
|
||||
<% if @team.effective_logo_url.present? %>
|
||||
<%= image_tag @team.effective_logo_url, alt: "", width: 40, height: 40, style: "border-radius:8px" %>
|
||||
<% end %>
|
||||
<%= @team.name %>
|
||||
</h1>
|
||||
<p>
|
||||
Piano: <strong><%= @entitlements.plan.name %></strong>
|
||||
Piano: <strong><%= @entitlements.plan.name %></strong> (società)
|
||||
· Staff trasmissione: <strong><%= @entitlements.staff_count_for("transmission") %> / <%= @entitlements.max_staff_transmission || "∞" %></strong>
|
||||
· Staff regia: <strong><%= @entitlements.staff_count_for("regia") %> / <%= @entitlements.max_staff_regia || "∞" %></strong>
|
||||
· Partite attive: <strong><%= @entitlements.concurrent_streams_used %><% if @entitlements.concurrent_streams_limit %> / <%= @entitlements.concurrent_streams_limit %><% else %> (illimitate)<% end %></strong>
|
||||
</p>
|
||||
|
||||
<div style="display:flex;gap:12px;flex-wrap:wrap;margin:20px 0">
|
||||
<%= link_to "Abbonamento", public_team_billing_path(@team), class: "btn btn-primary" %>
|
||||
<%= link_to "Abbonamento società", public_club_billing_path(@club), class: "btn btn-primary" %>
|
||||
<%= link_to "Aggiungi staff", public_team_invite_path(@team), class: "btn btn-secondary" %>
|
||||
<%= link_to "Dirette live", public_live_index_path, class: "btn btn-secondary" %>
|
||||
</div>
|
||||
|
||||
<% owner_staff = @owner_membership&.staff_kind %>
|
||||
<% can_self_transmission = owner_staff != "transmission" && owner_staff != "regia" %>
|
||||
<% can_self_regia = owner_staff != "regia" && owner_staff != "transmission" %>
|
||||
<% if can_self_transmission || can_self_regia %>
|
||||
<div class="card" style="margin-bottom:20px">
|
||||
<h2 style="margin-top:0;font-size:1.1rem">Il tuo account (<%= current_user.email %>)</h2>
|
||||
<p style="color:#aaa;font-size:0.92rem;margin-bottom:12px">
|
||||
Puoi usare le stesse credenziali nell’app mobile. Trasmissione e regia devono essere <strong>email diverse</strong> — non puoi coprire entrambi i ruoli con questo account.
|
||||
</p>
|
||||
<div style="display:flex;gap:10px;flex-wrap:wrap">
|
||||
<% if can_self_transmission %>
|
||||
<%= button_to "Sono io — staff trasmissione", public_team_assign_self_staff_path(@team), method: :post, params: { staff_kind: "transmission" }, class: "btn btn-primary" %>
|
||||
<% end %>
|
||||
<% if can_self_regia %>
|
||||
<%= button_to "Sono io — staff regia", public_team_assign_self_staff_path(@team), method: :post, params: { staff_kind: "regia" }, class: "btn btn-secondary" %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<h2>Staff attivo</h2>
|
||||
<div class="card">
|
||||
<% if @members.any? %>
|
||||
<% if @staff_memberships.any? %>
|
||||
<table class="data">
|
||||
<thead><tr><th>Nome</th><th>Email</th><th>Ruolo</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
<% @members.each do |ut| %>
|
||||
<% @staff_memberships.each do |ut| %>
|
||||
<tr>
|
||||
<td><%= ut.user.name %></td>
|
||||
<td>
|
||||
<%= ut.user.name %>
|
||||
<% if ut.role == "owner" %><span style="color:#888;font-size:0.85rem"> (tu)</span><% end %>
|
||||
</td>
|
||||
<td><%= ut.user.email %></td>
|
||||
<td><%= ut.staff_kind == "regia" ? "Regia" : "Trasmissione" %></td>
|
||||
<td>
|
||||
<%= button_to "Revoca", public_team_remove_member_path(@team, ut.user), method: :delete, class: "btn btn-secondary", style: "padding:4px 10px;font-size:0.8rem", form: { data: { turbo_confirm: "Revocare l'accesso a #{ut.user.name}?" } } %>
|
||||
<% if ut.role == "owner" %>
|
||||
<%= button_to "Rimuovi ruolo staff", public_team_clear_self_staff_path(@team), method: :delete, class: "btn btn-secondary", style: "padding:4px 10px;font-size:0.8rem", form: { data: { turbo_confirm: "Rimuovere il ruolo staff dal tuo account?" } } %>
|
||||
<% else %>
|
||||
<%= button_to "Revoca", public_team_remove_member_path(@team, ut.user), method: :delete, class: "btn btn-secondary", style: "padding:4px 10px;font-size:0.8rem", form: { data: { turbo_confirm: "Revocare l'accesso a #{ut.user.name}?" } } %>
|
||||
<% end %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<% else %>
|
||||
<p style="color:#888">Nessun membro staff. <%= link_to "Aggiungi qualcuno", public_team_invite_path(@team) %>.</p>
|
||||
<p style="color:#888">Nessun membro staff. Usa i pulsanti sopra per il tuo account o <%= link_to "invita qualcuno", public_team_invite_path(@team) %>.</p>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
|
||||
23
backend/app/views/public/teams/edit.html.erb
Normal file
23
backend/app/views/public/teams/edit.html.erb
Normal file
@@ -0,0 +1,23 @@
|
||||
<% content_for :title, "Modifica squadra — #{@team.name}" %>
|
||||
<% content_for :robots, "noindex, nofollow" %>
|
||||
|
||||
<div class="wrap" style="padding-top:20px;max-width:560px">
|
||||
<h1><%= @team.name %></h1>
|
||||
<p style="color:#888">Società: <%= link_to @club.name, public_club_path(@club) %></p>
|
||||
<div class="card">
|
||||
<%= form_with url: public_team_path(@team), method: :patch, multipart: true do %>
|
||||
<%= label_tag "team[name]", "Nome squadra" %>
|
||||
<%= text_field_tag "team[name]", @team.name, required: true %>
|
||||
<%= label_tag "team[sport]", "Sport" %>
|
||||
<%= select_tag "team[sport]", options_for_select([["Pallavolo", "volleyball"], ["Calcio", "football"], ["Basket", "basketball"]], @team.sport) %>
|
||||
<%= render "shared/branding_fields", record: @team, show_inherit_hint: true, legend: "Branding squadra (override)" %>
|
||||
<p class="branding-hint">
|
||||
Colori società:
|
||||
<span class="color-swatch" style="background:<%= @club.effective_primary_color %>"></span>
|
||||
<span class="color-swatch" style="background:<%= @club.effective_secondary_color %>"></span>
|
||||
</p>
|
||||
<%= submit_tag "Salva squadra", class: "btn btn-primary" %>
|
||||
<% end %>
|
||||
</div>
|
||||
<p><%= link_to "← Dashboard squadra", public_team_dashboard_path(@team) %></p>
|
||||
</div>
|
||||
@@ -8,7 +8,21 @@
|
||||
· Regia: <strong><%= @entitlements.staff_count_for("regia") %> / <%= @entitlements.max_staff_regia || "∞" %></strong>
|
||||
</p>
|
||||
|
||||
<% owner_staff = current_user.user_teams.find_by(team: @team, role: "owner")&.staff_kind %>
|
||||
<% unless owner_staff.present? %>
|
||||
<div class="card" style="max-width:520px;margin-bottom:16px">
|
||||
<p style="margin:0 0 12px;color:#aaa;font-size:0.92rem">
|
||||
<strong><%= current_user.email %></strong> — aggiungi il tuo account senza invito (non potrai essere sia trasmissione sia regia).
|
||||
</p>
|
||||
<div style="display:flex;gap:10px;flex-wrap:wrap">
|
||||
<%= button_to "Sono io — trasmissione", public_team_assign_self_staff_path(@team), method: :post, params: { staff_kind: "transmission" }, class: "btn btn-primary" %>
|
||||
<%= button_to "Sono io — regia", public_team_assign_self_staff_path(@team), method: :post, params: { staff_kind: "regia" }, class: "btn btn-secondary" %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<div class="card" style="max-width:520px">
|
||||
<h2 style="margin-top:0;font-size:1.05rem">Invita un’altra email</h2>
|
||||
<%= form_with url: public_team_invite_path(@team), method: :post do %>
|
||||
<%= label_tag :staff_kind, "Ruolo" %>
|
||||
<%= select_tag :staff_kind, options_for_select([
|
||||
@@ -22,7 +36,7 @@
|
||||
<% if defined?(@invite_url) && @invite_url.present? %>
|
||||
<p style="margin-top:16px">Condividi (valido 7 giorni):</p>
|
||||
<code style="word-break:break-all;display:block;background:#0a0a0e;padding:12px;border-radius:8px"><%= @invite_url %></code>
|
||||
<p style="color:#888;font-size:0.88rem;margin-top:8px">Il membro dello staff si registra con la stessa email e accetta l'accesso.</p>
|
||||
<p style="color:#888;font-size:0.88rem;margin-top:8px">Il membro dello staff si registra con la stessa email e accetta l'accesso. L'email non può essere già usata per l'altro ruolo (trasmissione vs regia).</p>
|
||||
<% end %>
|
||||
</div>
|
||||
<p><%= link_to "← Dashboard", public_team_dashboard_path(@team) %></p>
|
||||
|
||||
@@ -1,23 +1,18 @@
|
||||
<% content_for :title, "Crea squadra — Match Live TV" %>
|
||||
<% content_for :meta_description, "Configura la squadra dopo la registrazione su Match Live TV." %>
|
||||
<% content_for :title, "Nuova squadra — #{@club.name}" %>
|
||||
<% content_for :robots, "noindex, nofollow" %>
|
||||
|
||||
<section class="auth-page">
|
||||
<h1>La tua squadra</h1>
|
||||
<p class="auth-lead">Ultimo passo: nome, sport e piano iniziale.</p>
|
||||
<h1>Nuova squadra</h1>
|
||||
<p class="auth-lead">Società: <strong><%= @club.name %></strong></p>
|
||||
<div class="card card-wide">
|
||||
<%= form_with url: public_teams_path do %>
|
||||
<%= form_with url: public_club_teams_path(@club), multipart: true do %>
|
||||
<%= label_tag "team[name]", "Nome squadra" %>
|
||||
<%= text_field_tag "team[name]", nil, required: true %>
|
||||
<%= text_field_tag "team[name]", nil, required: true, placeholder: "es. Under 13, Serie C" %>
|
||||
<%= label_tag "team[sport]", "Sport" %>
|
||||
<%= select_tag "team[sport]", options_for_select([["Pallavolo", "volleyball"], ["Calcio", "football"], ["Basket", "basketball"]], "volleyball") %>
|
||||
<%= label_tag :plan, "Piano iniziale" %>
|
||||
<%= select_tag :plan, options_for_select([
|
||||
["Free — 1 staff trasmissione + 1 regia, 1 live", "free"],
|
||||
["Premium Light — €40/anno o €5/mese", "premium_light"],
|
||||
["Premium Full — €200/anno o €20/mese", "premium_full"]
|
||||
], "free") %>
|
||||
<%= submit_tag "Crea squadra", class: "btn btn-primary" %>
|
||||
<%= select_tag "team[sport]", options_for_select([["Pallavolo", "volleyball"], ["Calcio", "football"], ["Basket", "basketball"]], @club.sport) %>
|
||||
<%= render "shared/branding_fields", record: Team.new(club: @club), show_inherit_hint: true, legend: "Override branding (opzionale)" %>
|
||||
<%= submit_tag "Aggiungi squadra", class: "btn btn-primary" %>
|
||||
<% end %>
|
||||
</div>
|
||||
<p class="auth-footer"><%= link_to "← Società", public_club_path(@club) %></p>
|
||||
</section>
|
||||
|
||||
Reference in New Issue
Block a user