Billing Stripe, link regia mobile e staff solo trasmissione.
Aggiunge fatturazione club, pagina regia condivisibile senza account, roster squadre e rimuove la modalità controller dall'app (v1.1.0). Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
18
backend/app/views/public/club_billing/profile.html.erb
Normal file
18
backend/app/views/public/club_billing/profile.html.erb
Normal file
@@ -0,0 +1,18 @@
|
||||
<% content_for :title, "Dati di fatturazione — #{@club.name}" %>
|
||||
<% content_for :robots, "noindex, nofollow" %>
|
||||
|
||||
<div class="wrap" style="padding-top:20px;max-width:640px">
|
||||
<h1>Dati di fatturazione</h1>
|
||||
<p style="color:#aaa">Società: <strong><%= @club.name %></strong>. Inserisci i dati per l’intestazione delle fatture e l’invio tramite SDI o PEC.</p>
|
||||
|
||||
<div class="card">
|
||||
<%= form_with url: public_club_billing_profile_path(@club), method: :patch do %>
|
||||
<%= render "shared/billing_profile_fields", record: @club %>
|
||||
<%= submit_tag "Salva dati di fatturazione", class: "btn btn-primary" %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<p style="margin-top:20px">
|
||||
<%= link_to "← Abbonamento", public_club_billing_path(@club) %>
|
||||
</p>
|
||||
</div>
|
||||
35
backend/app/views/public/club_matches/new.html.erb
Normal file
35
backend/app/views/public/club_matches/new.html.erb
Normal file
@@ -0,0 +1,35 @@
|
||||
<% content_for :title, "Programma partita — #{@club.name}" %>
|
||||
<% content_for :robots, "noindex, nofollow" %>
|
||||
|
||||
<div class="wrap" style="padding-top:20px;max-width:560px">
|
||||
<p class="results-hint" style="margin-bottom:8px">
|
||||
<%= link_to "← Dirette e partite", public_live_index_path(club_id: @club.id), class: "back-link" %>
|
||||
</p>
|
||||
<h1>Programma partita</h1>
|
||||
<p style="color:#888">
|
||||
<%= @club.name %>
|
||||
<% if @club_admin %>
|
||||
· Puoi scegliere qualsiasi squadra della società
|
||||
<% else %>
|
||||
· Solo le squadre in cui sei responsabile trasmissione
|
||||
<% end %>
|
||||
</p>
|
||||
|
||||
<div class="card" style="margin-top:16px">
|
||||
<%= form_with url: public_club_matches_path(@club), method: :post, local: true do %>
|
||||
<%= label_tag :team_id, "Squadra" %>
|
||||
<% if @schedulable_teams.size == 1 %>
|
||||
<%= hidden_field_tag :team_id, @selected_team.id %>
|
||||
<p style="margin:0 0 16px;font-weight:600"><%= @selected_team.name %></p>
|
||||
<% else %>
|
||||
<%= select_tag :team_id,
|
||||
options_from_collection_for_select(@schedulable_teams, :id, :name, @selected_team&.id),
|
||||
required: true %>
|
||||
<% end %>
|
||||
|
||||
<%= render "public/matches/schedule_fields", match: @match %>
|
||||
|
||||
<%= submit_tag "Salva in programma", class: "btn btn-primary", style: "margin-top:20px" %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
@@ -3,44 +3,12 @@
|
||||
|
||||
<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>
|
||||
<%= render "shared/club_subscription_status", club: @club, entitlements: @entitlements, subscription: @subscription %>
|
||||
|
||||
<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>
|
||||
<%= render "shared/stripe_secure_payment" %>
|
||||
<%= render "shared/plan_cards", show_stripe_portal: true %>
|
||||
|
||||
<% 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 %>
|
||||
<%= render "shared/billing_documents", club: @club, payments: @payments, invoices: @invoices %>
|
||||
|
||||
<p><%= link_to "← Società", public_club_path(@club) %></p>
|
||||
<p style="margin-top:24px"><%= link_to "← Società", public_club_path(@club) %></p>
|
||||
</div>
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
<%= 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" %>
|
||||
<%= render "shared/billing_profile_fields", record: @club %>
|
||||
<%= submit_tag "Salva", class: "btn btn-primary" %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
<%= 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" %>
|
||||
<%= render "shared/billing_profile_fields", record: Club.new(billing_country: "IT") %>
|
||||
|
||||
<h2 class="form-section-title" style="margin-top:24px">Prima squadra</h2>
|
||||
<%= label_tag "first_team[name]", "Nome squadra" %>
|
||||
@@ -20,10 +21,11 @@
|
||||
|
||||
<%= 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"],
|
||||
["Free — 1 responsabile trasmissione 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") %>
|
||||
<%= render "shared/stripe_secure_payment", compact: true %>
|
||||
<%= submit_tag "Crea società", class: "btn btn-primary" %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
<%= 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" %>
|
||||
<%= link_to "Dirette live", public_live_index_path(club_id: @club.id), class: "btn btn-secondary" %>
|
||||
</div>
|
||||
|
||||
<h2>Squadre</h2>
|
||||
@@ -44,7 +44,9 @@
|
||||
<% end %>
|
||||
</td>
|
||||
<td style="white-space:nowrap">
|
||||
<%= link_to "Dashboard", public_team_dashboard_path(team) %>
|
||||
<%= link_to "Dettagli", public_team_details_path(team) %>
|
||||
·
|
||||
<%= link_to "Partite", public_team_matches_path(team) %>
|
||||
·
|
||||
<%= link_to "Modifica", public_edit_team_path(team) %>
|
||||
</td>
|
||||
@@ -57,5 +59,5 @@
|
||||
<% 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>
|
||||
<p style="color:#888;margin-top:24px">Ogni squadra ha i propri responsabili trasmissione. Il punteggio da un altro telefono usa il link regia (senza account). L’abbonamento vale per tutta la società.</p>
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
<div class="card" style="max-width:480px">
|
||||
<h1>Accesso staff — <%= @invitation.team.name %></h1>
|
||||
<p>Sei stato aggiunto allo staff come <strong><%= @invitation.staff_kind == "regia" ? "Regia" : "Trasmissione" %></strong> (<%= @invitation.email %>).</p>
|
||||
<p>Sei stato aggiunto come <strong>responsabile trasmissione</strong> (<%= @invitation.email %>).</p>
|
||||
<% if logged_in? %>
|
||||
<%= button_to "Accetta invito", public_invitation_path(token: @token), method: :post, class: "btn btn-primary" %>
|
||||
<% else %>
|
||||
|
||||
@@ -2,30 +2,50 @@
|
||||
<% content_for :meta_description, "Guarda le dirette live delle partite giovanili ora in corso e le prossime gare programmate. Cerca la tua squadra e apri il link senza installare app." %>
|
||||
<% content_for :canonical_url, seo_absolute_url(public_live_index_path) %>
|
||||
|
||||
<% live_index_params = @club ? { club_id: @club.id } : {} %>
|
||||
<div class="wrap">
|
||||
<h1>Dirette e partite in programma</h1>
|
||||
<p class="results-hint">
|
||||
Cerca la tua squadra: trovi le dirette attive e le partite già programmate dall’app.
|
||||
</p>
|
||||
<% if @club %>
|
||||
<p class="results-hint" style="margin-bottom:8px">
|
||||
<%= link_to "← #{@club.name}", public_club_path(@club), class: "back-link" %>
|
||||
</p>
|
||||
<h1>Dirette e partite — <%= @club.name %></h1>
|
||||
<p class="results-hint">
|
||||
Solo le squadre di questa società: dirette in corso e partite programmate da app o sito.
|
||||
</p>
|
||||
<% else %>
|
||||
<h1>Dirette e partite in programma</h1>
|
||||
<p class="results-hint">
|
||||
Cerca per società, squadra, avversario o luogo: trovi le dirette attive e le partite programmate.
|
||||
</p>
|
||||
<% end %>
|
||||
|
||||
<% if @club && @can_schedule_match %>
|
||||
<div style="display:flex;gap:12px;flex-wrap:wrap;margin-bottom:16px">
|
||||
<%= link_to "Programma partita", public_new_club_match_path(@club), class: "btn btn-primary" %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<%= form_with url: public_live_index_path, method: :get, local: true, class: "search-form" do %>
|
||||
<% if @club %>
|
||||
<%= hidden_field_tag :club_id, @club.id %>
|
||||
<% end %>
|
||||
<input
|
||||
type="search"
|
||||
name="q"
|
||||
value="<%= @query %>"
|
||||
placeholder="Es. Tigers, Eagles, nome squadra…"
|
||||
placeholder="<%= @club ? "Cerca società, squadra, avversario o luogo…" : "Es. Crazy Volley, Serie D, avversario…" %>"
|
||||
aria-label="Cerca squadra"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<button type="submit" class="btn btn-primary">Cerca</button>
|
||||
<% if @query.present? %>
|
||||
<%= link_to "Azzera", public_live_index_path, class: "btn btn-secondary" %>
|
||||
<%= link_to "Azzera", public_live_index_path(live_index_params), class: "btn btn-secondary" %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<% if @query.present? %>
|
||||
<p class="results-hint">
|
||||
Risultati per «<%= h @query %>»:
|
||||
Risultati per «<%= h @query %>»<%= @club ? " in #{@club.name}" : "" %>:
|
||||
<%= @sessions.size %> in diretta · <%= @upcoming_matches.size %> in programma
|
||||
</p>
|
||||
<% end %>
|
||||
@@ -37,7 +57,7 @@
|
||||
<% match = session.match %>
|
||||
<% on_air = @online_paths.include?(session.mediamtx_path_name) %>
|
||||
<article class="live-card">
|
||||
<h3><%= match.team.name %> vs <%= match.opponent_name %></h3>
|
||||
<%= live_match_card_heading(match) %>
|
||||
<p class="meta">
|
||||
<% if match.location.present? %><%= match.location %> · <% end %>
|
||||
<%= session.platform == "matchlivetv" ? "Match Live TV" : session.platform.capitalize %>
|
||||
@@ -70,11 +90,11 @@
|
||||
|
||||
<% if @upcoming_matches.any? %>
|
||||
<h2 class="section-heading<%= ' section-heading--spaced' if @sessions.any? %>">Prossime partite</h2>
|
||||
<p class="results-hint">Programmate dall’app: la diretta partirà quando lo staff avvierà la trasmissione.</p>
|
||||
<p class="results-hint">Programmate da app o sito: la diretta partirà quando lo staff avvierà la trasmissione.</p>
|
||||
<div class="live-grid upcoming-grid">
|
||||
<% @upcoming_matches.each do |match| %>
|
||||
<article class="live-card live-card--upcoming">
|
||||
<h3><%= match.team.name %> vs <%= match.opponent_name %></h3>
|
||||
<%= live_match_card_heading(match) %>
|
||||
<p class="meta">
|
||||
<% if match.location.present? %><%= match.location %> · <% end %>
|
||||
<%= match.category.presence || match.sport&.capitalize %>
|
||||
@@ -96,6 +116,25 @@
|
||||
<p><strong>Nessuna diretta in questo momento.</strong></p>
|
||||
<p>Torna all’orario indicato: quando la squadra avvierà lo streaming, la partita comparirà in «In diretta adesso».</p>
|
||||
</div>
|
||||
<% elsif @club %>
|
||||
<div class="empty-state">
|
||||
<p><strong>Nessuna diretta o partita in programma per <%= @club.name %>.</strong></p>
|
||||
<p>
|
||||
<% if @can_schedule_match %>
|
||||
<%= link_to "Programma la prima partita", public_new_club_match_path(@club), class: "btn btn-primary" %>
|
||||
oppure dall’app Match Live TV.
|
||||
<% elsif @club.teams.any? %>
|
||||
Chiedi al titolare della società o a un responsabile trasmissione di programmare una gara.
|
||||
<% else %>
|
||||
Aggiungi una squadra dalla
|
||||
<%= link_to "pagina società", public_club_path(@club) %>
|
||||
e programma la prima partita.
|
||||
<% end %>
|
||||
</p>
|
||||
<p style="margin-top:16px">
|
||||
<%= link_to "← Torna alla società", public_club_path(@club), class: "btn btn-secondary" %>
|
||||
</p>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="empty-hero">
|
||||
<div class="empty-hero-copy">
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<% content_for :title do %><%= @match.team.name %> vs <%= @match.opponent_name %> — Diretta live<% end %>
|
||||
<% content_for :meta_description do %>Segui in diretta live <%= @match.team.name %> vs <%= @match.opponent_name %><% if @match.location.present? %> — <%= @match.location %><% end %>. Guarda dal browser senza installare app.<% end %>
|
||||
<% club_name = @match.team.club&.name %>
|
||||
<% content_for :title do %><%= club_name %> · <%= @match.team.name %> vs <%= @match.opponent_name %> — Diretta live<% end %>
|
||||
<% content_for :meta_description do %>Segui in diretta live <%= club_name %> — <%= @match.team.name %> vs <%= @match.opponent_name %><% if @match.location.present? %> — <%= @match.location %><% end %>. Guarda dal browser senza installare app.<% end %>
|
||||
<% content_for :robots, "noindex, follow" %>
|
||||
|
||||
<% content_for :head do %>
|
||||
@@ -11,7 +12,7 @@
|
||||
<div class="wrap">
|
||||
<%= link_to "← Tutte le dirette", public_live_index_path, class: "back-link" %>
|
||||
|
||||
<h1><%= @match.team.name %> vs <%= @match.opponent_name %></h1>
|
||||
<%= live_match_page_heading(@match) %>
|
||||
<p>
|
||||
<% if @stream_closed %>
|
||||
<span id="status-badge" class="badge badge-ended">Diretta chiusa</span>
|
||||
|
||||
4
backend/app/views/public/matches/_form.html.erb
Normal file
4
backend/app/views/public/matches/_form.html.erb
Normal file
@@ -0,0 +1,4 @@
|
||||
<%= form_with model: @match, url: form_url, method: form_method do %>
|
||||
<%= render "schedule_fields", match: @match %>
|
||||
<%= submit_tag submit_label, class: "btn btn-primary", style: "margin-top:20px" %>
|
||||
<% end %>
|
||||
13
backend/app/views/public/matches/_schedule_fields.html.erb
Normal file
13
backend/app/views/public/matches/_schedule_fields.html.erb
Normal file
@@ -0,0 +1,13 @@
|
||||
<%= label_tag "match[opponent_name]", "Avversario" %>
|
||||
<%= text_field_tag "match[opponent_name]", match.opponent_name, required: true, placeholder: "es. Volley Milano" %>
|
||||
|
||||
<%= label_tag "match[location]", "Luogo (opzionale)" %>
|
||||
<%= text_field_tag "match[location]", match.location, placeholder: "Palestra, città" %>
|
||||
|
||||
<%= label_tag "match[scheduled_at]", "Data e ora" %>
|
||||
<% scheduled_value = match.scheduled_at&.in_time_zone&.strftime("%Y-%m-%dT%H:%M") %>
|
||||
<%= datetime_local_field_tag "match[scheduled_at]", scheduled_value, required: true %>
|
||||
|
||||
<p style="color:#888;font-size:0.88rem;margin:12px 0 0">
|
||||
La diretta non parte da sola: comparirà in app e sul sito. Avvierai lo streaming dal telefono quando sei in campo.
|
||||
</p>
|
||||
14
backend/app/views/public/matches/edit.html.erb
Normal file
14
backend/app/views/public/matches/edit.html.erb
Normal file
@@ -0,0 +1,14 @@
|
||||
<% content_for :title, "Modifica partita — #{@team.name}" %>
|
||||
<% content_for :robots, "noindex, nofollow" %>
|
||||
|
||||
<div class="wrap" style="padding-top:20px;max-width:560px">
|
||||
<h1>Modifica partita</h1>
|
||||
<p style="color:#888"><%= @team.name %> vs <strong><%= @match.opponent_name %></strong></p>
|
||||
<div class="card" style="margin-top:16px">
|
||||
<%= render "form",
|
||||
form_url: public_team_match_path(@team, @match),
|
||||
form_method: :patch,
|
||||
submit_label: "Salva modifiche" %>
|
||||
</div>
|
||||
<p style="margin-top:16px"><%= link_to "← Elenco partite", public_team_matches_path(@team) %></p>
|
||||
</div>
|
||||
77
backend/app/views/public/matches/index.html.erb
Normal file
77
backend/app/views/public/matches/index.html.erb
Normal file
@@ -0,0 +1,77 @@
|
||||
<% content_for :title, "Partite — #{@team.name}" %>
|
||||
<% content_for :robots, "noindex, nofollow" %>
|
||||
|
||||
<div class="wrap" style="padding-top:20px;max-width:720px">
|
||||
<nav class="team-dashboard-nav" aria-label="Percorso">
|
||||
<%= link_to "← #{@team.name}", public_team_details_path(@team), class: "team-dashboard-nav__club" %>
|
||||
</nav>
|
||||
|
||||
<header style="margin:16px 0 20px">
|
||||
<h1>Partite — <%= @team.name %></h1>
|
||||
<p style="color:#888;margin-top:8px">
|
||||
Programma qui da PC data, avversario e luogo. Quando sei in palestra apri l’app Match Live TV,
|
||||
seleziona questa squadra e avvia la diretta sulla partita.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<div style="display:flex;gap:12px;flex-wrap:wrap;margin-bottom:20px">
|
||||
<%= link_to "Programma partita", new_public_team_match_path(@team), class: "btn btn-primary" %>
|
||||
<%= link_to "Dettagli squadra", public_team_details_path(@team), class: "btn btn-secondary" %>
|
||||
<%= link_to "Dirette e programma", public_live_index_path(club_id: @club.id), class: "btn btn-secondary" %>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<% if @matches.any? %>
|
||||
<table class="data">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Avversario</th>
|
||||
<th>Data e ora</th>
|
||||
<th>Luogo</th>
|
||||
<th>Stato</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<% @matches.each do |match| %>
|
||||
<tr>
|
||||
<td><strong><%= match.opponent_name %></strong></td>
|
||||
<td>
|
||||
<% if match.scheduled_at %>
|
||||
<%= l match.scheduled_at.in_time_zone, format: :long %>
|
||||
<% else %>
|
||||
<span style="color:#888">Senza orario</span>
|
||||
<% end %>
|
||||
</td>
|
||||
<td><%= match.location.presence || "—" %></td>
|
||||
<td>
|
||||
<% label = match.public_status_label %>
|
||||
<% if label == "In corso / da riprendere" %>
|
||||
<span style="color:#e53935;font-weight:600"><%= label %></span>
|
||||
<% else %>
|
||||
<%= label %>
|
||||
<% end %>
|
||||
</td>
|
||||
<td style="white-space:nowrap">
|
||||
<%= link_to "Modifica", edit_public_team_match_path(@team, match) %>
|
||||
<% if match.deletable? %>
|
||||
·
|
||||
<%= button_to "Elimina", public_team_match_path(@team, match),
|
||||
method: :delete,
|
||||
form: { data: { turbo_confirm: "Eliminare questa partita?" } },
|
||||
class: "btn btn-secondary",
|
||||
style: "padding:4px 10px;font-size:0.8rem;display:inline" %>
|
||||
<% end %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<% else %>
|
||||
<p style="color:#888;margin:0">
|
||||
Nessuna partita in calendario.
|
||||
<%= link_to "Programma la prima", new_public_team_match_path(@team) %>.
|
||||
</p>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
14
backend/app/views/public/matches/new.html.erb
Normal file
14
backend/app/views/public/matches/new.html.erb
Normal file
@@ -0,0 +1,14 @@
|
||||
<% content_for :title, "Programma partita — #{@team.name}" %>
|
||||
<% content_for :robots, "noindex, nofollow" %>
|
||||
|
||||
<div class="wrap" style="padding-top:20px;max-width:560px">
|
||||
<h1>Programma partita</h1>
|
||||
<p style="color:#888"><%= @club.name %> · <strong><%= @team.name %></strong></p>
|
||||
<div class="card" style="margin-top:16px">
|
||||
<%= render "form",
|
||||
form_url: public_team_matches_path(@team),
|
||||
form_method: :post,
|
||||
submit_label: "Salva in programma" %>
|
||||
</div>
|
||||
<p style="margin-top:16px"><%= link_to "← Elenco partite", public_team_matches_path(@team) %></p>
|
||||
</div>
|
||||
@@ -34,7 +34,7 @@
|
||||
|
||||
<h2>Pallavolo: punteggio e regole</h2>
|
||||
<p>
|
||||
Set da vincere 2 o 3, punti 25/21/15 configurabili per tornei non standard. La regia è separata dalla ripresa:
|
||||
Set da vincere 2 o 3, punti 25/21/15 configurabili per tornei non standard. Il punteggio si gestisce da un link condiviso (senza account):
|
||||
in campo resta concentrato sul gioco, non sul telefono.
|
||||
</p>
|
||||
|
||||
|
||||
@@ -3,60 +3,27 @@
|
||||
<% content_for :canonical_url, seo_absolute_url(public_prezzi_path) %>
|
||||
|
||||
<div class="wrap" style="padding-top:24px">
|
||||
<h1>Piani per la tua squadra</h1>
|
||||
<p style="color:#aaa">Abbonamento annuale per squadra: lo staff gestisce trasmissione e regia con credenziali dedicate. Pagamento sicuro su Stripe.</p>
|
||||
<% if @club %>
|
||||
<h1>Piani per la tua società</h1>
|
||||
<% if @entitlements %>
|
||||
<%= render "shared/club_subscription_status", club: @club, entitlements: @entitlements, subscription: @subscription %>
|
||||
<% else %>
|
||||
<p style="color:#aaa">Società: <strong><%= @club.name %></strong> · <%= link_to "Gestisci abbonamento", public_club_billing_path(@club) %></p>
|
||||
<% end %>
|
||||
<% else %>
|
||||
<h1>Piani per la tua squadra</h1>
|
||||
<p style="color:#aaa">Abbonamento annuale per società: i responsabili trasmissione avviano la diretta dall’app; il punteggio si gestisce con un link condivisibile.</p>
|
||||
<% end %>
|
||||
<%= render "shared/stripe_secure_payment" %>
|
||||
|
||||
<div class="plans">
|
||||
<div class="plan-card">
|
||||
<h3>Free</h3>
|
||||
<div class="plan-price">€0</div>
|
||||
<ul>
|
||||
<li><strong>1</strong> persona staff / anno per la <strong>trasmissione</strong></li>
|
||||
<li><strong>1</strong> persona staff / anno per la <strong>regia</strong></li>
|
||||
<li><strong>1</strong> live alla volta su matchlivetv.eminux.it</li>
|
||||
<li>Punteggio live e regia da app</li>
|
||||
<li style="color:#888">No salvataggio / replay</li>
|
||||
<li style="color:#888">No YouTube</li>
|
||||
</ul>
|
||||
<%= link_to "Inizia gratis", public_signup_path, class: "btn btn-secondary" %>
|
||||
</div>
|
||||
|
||||
<div class="plan-card featured">
|
||||
<h3>Premium Light</h3>
|
||||
<div class="plan-price" style="color:#e53935">€40<span style="font-size:1rem;font-weight:400">/anno</span></div>
|
||||
<p style="color:#aaa;font-size:0.9rem;margin:-8px 0 12px">oppure <strong>€5</strong>/mese</p>
|
||||
<ul>
|
||||
<li><strong>5</strong> persone staff / anno per la <strong>trasmissione</strong></li>
|
||||
<li><strong>5</strong> persone staff / anno per la <strong>regia</strong></li>
|
||||
<li>Fino a <strong>3</strong> partite in contemporanea</li>
|
||||
<li>Live su matchlivetv.eminux.it e sul <strong>canale YouTube Match TV Light</strong></li>
|
||||
<li>Archivio server <strong>30 giorni</strong> e download su telefono</li>
|
||||
</ul>
|
||||
<%= link_to "Registrati", public_signup_path, class: "btn btn-primary" %>
|
||||
</div>
|
||||
|
||||
<div class="plan-card">
|
||||
<h3>Premium Full</h3>
|
||||
<div class="plan-price">€200<span style="font-size:1rem;font-weight:400">/anno</span></div>
|
||||
<p style="color:#aaa;font-size:0.9rem;margin:-8px 0 12px">oppure <strong>€20</strong>/mese</p>
|
||||
<ul>
|
||||
<li>Staff <strong>illimitato</strong> / anno (trasmissione e regia)</li>
|
||||
<li>Fino a <strong>10</strong> partite in contemporanea</li>
|
||||
<li>Collegamento con il <strong>canale YouTube della società</strong></li>
|
||||
<li>Archivio server <strong>90 giorni</strong> e download su telefono</li>
|
||||
<li>Sito società (in arrivo)</li>
|
||||
</ul>
|
||||
<%= link_to "Registrati", public_signup_path, class: "btn btn-primary" %>
|
||||
</div>
|
||||
</div>
|
||||
<%= render "shared/plan_cards" %>
|
||||
|
||||
<table class="compare-table">
|
||||
<thead>
|
||||
<tr><th></th><th>Free</th><th>Premium Light</th><th>Premium Full</th></tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr><td>Staff trasmissione / anno</td><td>1</td><td>5</td><td>Illimitato</td></tr>
|
||||
<tr><td>Staff regia / anno</td><td>1</td><td>5</td><td>Illimitato</td></tr>
|
||||
<tr><td>Responsabili trasmissione / anno</td><td>1</td><td>5</td><td>Illimitato</td></tr>
|
||||
<tr><td>Partite in contemporanea</td><td>1</td><td>3</td><td>10</td></tr>
|
||||
<tr><td>Live su Match Live TV</td><td>Sì</td><td>Sì</td><td>Sì</td></tr>
|
||||
<tr><td>YouTube</td><td>No</td><td>Match TV Light</td><td>Canale società</td></tr>
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
<h2>2. Chi può usare il servizio</h2>
|
||||
<ul>
|
||||
<li>Il referente che registra la squadra dichiara di avere almeno <strong>18 anni</strong> e poteri per vincolare la società sportiva.</li>
|
||||
<li>Lo staff invitato (trasmissione, regia) usa credenziali personali e si impegna a custodirle.</li>
|
||||
<li>I responsabili trasmissione invitati usano credenziali personali e si impegnano a custodirle.</li>
|
||||
<li>Gli spettatori che aprono il link della diretta di solito non necessitano di account.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
@@ -6,10 +6,19 @@
|
||||
<div class="card">
|
||||
<%= form_with url: public_password_reset_path, method: :patch, local: true do %>
|
||||
<%= hidden_field_tag :token, @token %>
|
||||
<%= label_tag :password, "Nuova password (min. 8 caratteri)" %>
|
||||
<%= password_field_tag :password, required: true, minlength: 8, autocomplete: "new-password" %>
|
||||
<%= label_tag :password_confirmation, "Conferma password" %>
|
||||
<%= password_field_tag :password_confirmation, required: true, autocomplete: "new-password" %>
|
||||
<%= render "shared/input_toggle",
|
||||
name: :password,
|
||||
label: "Nuova password (min. 8 caratteri)",
|
||||
input_type: "text",
|
||||
required: true,
|
||||
minlength: 8,
|
||||
autocomplete: "new-password" %>
|
||||
<%= render "shared/input_toggle",
|
||||
name: :password_confirmation,
|
||||
label: "Conferma password",
|
||||
input_type: "text",
|
||||
required: true,
|
||||
autocomplete: "new-password" %>
|
||||
<%= submit_tag "Salva password", class: "btn btn-primary" %>
|
||||
<% end %>
|
||||
<p class="auth-footer"><%= link_to "Torna al login", public_login_path %></p>
|
||||
|
||||
97
backend/app/views/public/regia/show.html.erb
Normal file
97
backend/app/views/public/regia/show.html.erb
Normal file
@@ -0,0 +1,97 @@
|
||||
<% content_for :title, "Regia — #{@team.name} vs #{@match.opponent_name}" %>
|
||||
<% content_for :robots, "noindex, nofollow" %>
|
||||
|
||||
<% content_for :head do %>
|
||||
<% unless @stream_closed %>
|
||||
<script src="https://cdn.jsdelivr.net/npm/hls.js@1.5.7"></script>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<div class="regia-page" id="regia-app"
|
||||
data-token="<%= j params[:token] %>"
|
||||
data-status-url="<%= j public_regia_status_path(params[:token]) %>"
|
||||
data-score-url="<%= j public_regia_score_path(params[:token]) %>"
|
||||
data-pause-url="<%= j public_regia_pause_path(params[:token]) %>"
|
||||
data-stop-url="<%= j public_regia_stop_path(params[:token]) %>"
|
||||
data-cable-url="<%= j "/cable?regia_token=#{params[:token]}" %>"
|
||||
data-hls-url="<%= j @session.hls_playback_url %>"
|
||||
data-home-name="<%= j @team.name %>"
|
||||
data-away-name="<%= j @match.opponent_name %>"
|
||||
data-share-url="<%= j @share_url %>"
|
||||
data-share-title="<%= j @share_title %>"
|
||||
data-points-target="<%= Scoring::Rules.from_match(@match).points_target(@score.current_set) %>"
|
||||
data-stream-closed="<%= @stream_closed %>">
|
||||
|
||||
<header class="regia-header">
|
||||
<h1><%= @team.name %> vs <%= @match.opponent_name %></h1>
|
||||
<p>Regia · Set <%= @score.current_set %></p>
|
||||
<span id="regia-badge" class="regia-badge <%= @on_air ? 'regia-badge--live' : (@stream_closed ? 'regia-badge--ended' : 'regia-badge--wait') %>">
|
||||
<%= @stream_closed ? "Terminata" : (@on_air ? "In onda" : "In attesa") %>
|
||||
</span>
|
||||
</header>
|
||||
|
||||
<div class="regia-share">
|
||||
<button type="button" class="regia-btn regia-btn--outline" id="btn-share" style="margin:0">
|
||||
Condividi link regia
|
||||
</button>
|
||||
<button type="button" class="regia-btn regia-btn--outline" id="btn-copy" style="margin:0">
|
||||
Copia link
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="regia-preview">
|
||||
<% unless @stream_closed %>
|
||||
<video id="regia-preview" playsinline muted autoplay></video>
|
||||
<% end %>
|
||||
<div id="regia-preview-placeholder" class="regia-preview__placeholder"<%= ' hidden' unless @stream_closed %>>
|
||||
<%= @stream_closed ? "Diretta terminata" : "Anteprima in attesa del segnale…" %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="regia-scoreboard">
|
||||
<p class="regia-sets" id="sets-line"><%= live_score_sets_label(@score) || "—" %></p>
|
||||
<p class="regia-score-line" id="score-line"><%= live_score_points_label(@match, @score) %></p>
|
||||
<p class="regia-partials" id="partials-line"<%= " hidden" unless live_score_partials_label(@score).present? %>>
|
||||
Parziali: <%= live_score_partials_label(@score) %>
|
||||
</p>
|
||||
|
||||
<div class="regia-team-row">
|
||||
<div>
|
||||
<div class="regia-team-name"><%= @team.name %></div>
|
||||
<div class="regia-points" id="home-points"><%= @score.home_points %></div>
|
||||
<button type="button" class="regia-btn regia-btn--point" data-action="home_point" style="width:100%;margin-top:8px">+1</button>
|
||||
<button type="button" class="regia-btn regia-btn--minus" data-action="home_undo" style="width:100%;margin-top:6px">−</button>
|
||||
</div>
|
||||
<div style="text-align:center;color:var(--regia-muted);font-size:0.72rem;padding-top:24px">
|
||||
<%= Scoring::Rules.from_match(@match).points_target(@score.current_set) %> pt
|
||||
</div>
|
||||
<div>
|
||||
<div class="regia-team-name"><%= @match.opponent_name %></div>
|
||||
<div class="regia-points" id="away-points"><%= @score.away_points %></div>
|
||||
<button type="button" class="regia-btn regia-btn--point" data-action="away_point" style="width:100%;margin-top:8px">+1</button>
|
||||
<button type="button" class="regia-btn regia-btn--minus" data-action="away_undo" style="width:100%;margin-top:6px">−</button>
|
||||
</div>
|
||||
</div>
|
||||
<button type="button" class="regia-btn regia-btn--yellow" data-action="close_set" style="margin-top:12px">Chiudi set</button>
|
||||
</section>
|
||||
|
||||
<% unless @stream_closed %>
|
||||
<button type="button" class="regia-btn regia-btn--outline" id="btn-pause">Interrompi (riprendibile)</button>
|
||||
<button type="button" class="regia-btn regia-btn--danger" id="btn-stop">Chiudi diretta</button>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<div id="regia-toast" class="regia-toast" role="status"></div>
|
||||
|
||||
<div id="regia-modal" class="regia-modal" aria-hidden="true">
|
||||
<div class="regia-modal__card">
|
||||
<h2 id="modal-title">Set vinto</h2>
|
||||
<p id="modal-body"></p>
|
||||
<div class="regia-modal__actions">
|
||||
<button type="button" class="regia-btn regia-btn--yellow" id="modal-confirm">Chiudi set</button>
|
||||
<button type="button" class="regia-btn regia-btn--outline" id="modal-cancel">Annulla</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="/regia.js?v=1"></script>
|
||||
@@ -9,12 +9,29 @@
|
||||
<%= form_with model: @user, url: public_signup_path do |f| %>
|
||||
<%= f.label :name, "Nome" %>
|
||||
<%= f.text_field :name, required: true %>
|
||||
<%= f.label :email, "Email" %>
|
||||
<%= f.email_field :email, required: true %>
|
||||
<%= f.label :password, "Password" %>
|
||||
<%= f.password_field :password, required: true, minlength: 8 %>
|
||||
<%= f.label :password_confirmation, "Conferma password" %>
|
||||
<%= f.password_field :password_confirmation, required: true %>
|
||||
<%= render "shared/input_toggle",
|
||||
name: "user[email]",
|
||||
id: "user_email",
|
||||
label: "Email",
|
||||
value: @user.email,
|
||||
input_type: "email",
|
||||
required: true,
|
||||
autocomplete: "email" %>
|
||||
<%= render "shared/input_toggle",
|
||||
name: "user[password]",
|
||||
id: "user_password",
|
||||
label: "Password",
|
||||
input_type: "text",
|
||||
required: true,
|
||||
minlength: 8,
|
||||
autocomplete: "new-password" %>
|
||||
<%= render "shared/input_toggle",
|
||||
name: "user[password_confirmation]",
|
||||
id: "user_password_confirmation",
|
||||
label: "Conferma password",
|
||||
input_type: "text",
|
||||
required: true,
|
||||
autocomplete: "new-password" %>
|
||||
<label class="legal-accept">
|
||||
<%= check_box_tag :accept_terms, "1", false, required: true %>
|
||||
<span class="legal-accept-text">
|
||||
|
||||
@@ -6,10 +6,19 @@
|
||||
<h1>Accedi</h1>
|
||||
<div class="card">
|
||||
<%= form_with url: public_login_path do |f| %>
|
||||
<%= label_tag :email, "Email" %>
|
||||
<%= email_field_tag :email, params[:email], required: true %>
|
||||
<%= label_tag :password, "Password" %>
|
||||
<%= password_field_tag :password, required: true, autocomplete: "current-password" %>
|
||||
<%= render "shared/input_toggle",
|
||||
name: :email,
|
||||
label: "Email",
|
||||
value: params[:email],
|
||||
input_type: "email",
|
||||
required: true,
|
||||
autocomplete: "username" %>
|
||||
<%= render "shared/input_toggle",
|
||||
name: :password,
|
||||
label: "Password",
|
||||
input_type: "text",
|
||||
required: true,
|
||||
autocomplete: "current-password" %>
|
||||
<p class="auth-forgot"><%= link_to "Password dimenticata?", public_password_forgot_path %></p>
|
||||
<%= submit_tag "Entra", class: "btn btn-primary" %>
|
||||
<% end %>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<% content_for :title, "Modifica — #{@member.full_name}" %>
|
||||
<% content_for :robots, "noindex, nofollow" %>
|
||||
|
||||
<div class="wrap" style="padding-top:20px;max-width:520px">
|
||||
<p class="muted"><%= link_to "← Dettagli squadra", public_team_details_path(@team) %></p>
|
||||
<h1><%= @member.full_name %></h1>
|
||||
<%= render "shared/roster_member_form", member: @member, team: @team %>
|
||||
</div>
|
||||
100
backend/app/views/public/teams/_streaming_staff.html.erb
Normal file
100
backend/app/views/public/teams/_streaming_staff.html.erb
Normal file
@@ -0,0 +1,100 @@
|
||||
<% team = local_assigns[:team] %>
|
||||
<% can_manage = local_assigns[:can_manage] %>
|
||||
<% owner_membership = local_assigns[:owner_membership] %>
|
||||
<% staff_memberships = local_assigns[:staff_memberships] %>
|
||||
<% pending_invitations = local_assigns[:pending_invitations] %>
|
||||
<% recordings = local_assigns[:recordings] %>
|
||||
<% entitlements = local_assigns[:entitlements] %>
|
||||
|
||||
<section class="team-streaming-staff">
|
||||
<% if can_manage %>
|
||||
<% if owner_membership&.staff_kind.blank? %>
|
||||
<div class="card team-streaming-staff__self">
|
||||
<h2>Il tuo account (<%= current_user.email %>)</h2>
|
||||
<p class="muted">
|
||||
Puoi gestire la trasmissione dall’app. Per il punteggio da un altro telefono, condividi il
|
||||
<strong>link regia</strong> durante la diretta (non serve un secondo account).
|
||||
</p>
|
||||
<div class="team-details-actions">
|
||||
<%= button_to "Sono io — responsabile trasmissione", public_team_assign_self_staff_path(team), method: :post, params: { staff_kind: "transmission" }, class: "btn btn-primary" %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
|
||||
<h2>Responsabili trasmissione (app)</h2>
|
||||
<div class="card">
|
||||
<% if staff_memberships.any? %>
|
||||
<table class="data">
|
||||
<thead><tr><th>Nome</th><th>Email</th><th>Ruolo</th><% if can_manage %><th></th><% end %></tr></thead>
|
||||
<tbody>
|
||||
<% staff_memberships.each do |ut| %>
|
||||
<tr>
|
||||
<td>
|
||||
<%= ut.user.name %>
|
||||
<% if ut.user_id == current_user.id %><span class="muted"> (tu)</span><% end %>
|
||||
</td>
|
||||
<td><%= ut.user.email %></td>
|
||||
<td>Trasmissione</td>
|
||||
<% if can_manage %>
|
||||
<td>
|
||||
<% if ut.user_id == current_user.id && team.club.owned_by?(current_user) %>
|
||||
<%= 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?" } } %>
|
||||
<% elsif ut.role == "member" %>
|
||||
<%= 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>
|
||||
<% end %>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<% else %>
|
||||
<p class="muted">
|
||||
Nessun responsabile trasmissione.
|
||||
<% if can_manage %><%= link_to "Invita qualcuno", public_team_invite_path(team) %>.<% end %>
|
||||
</p>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<% if can_manage %>
|
||||
<h2>Inviti in attesa</h2>
|
||||
<div class="card">
|
||||
<% if pending_invitations.any? %>
|
||||
<table class="data">
|
||||
<thead><tr><th>Email</th><th>Scade</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
<% pending_invitations.each do |inv| %>
|
||||
<tr>
|
||||
<td><%= inv.email %></td>
|
||||
<td><%= l inv.expires_at, format: :short %></td>
|
||||
<td>
|
||||
<%= button_to "Annulla", public_team_destroy_invitation_path(team, inv), method: :delete, class: "btn btn-secondary", style: "padding:4px 10px;font-size:0.8rem" %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<% else %>
|
||||
<p class="muted">Nessuna richiesta in sospeso.</p>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if entitlements.can_access_recordings? && recordings.any? %>
|
||||
<h2>Archivio gare</h2>
|
||||
<div class="card">
|
||||
<table class="data">
|
||||
<thead><tr><th>Partita</th><th>Replay</th></tr></thead>
|
||||
<tbody>
|
||||
<% recordings.each do |rec| %>
|
||||
<tr>
|
||||
<td><%= rec.stream_session.match.team.name %> vs <%= rec.stream_session.match.opponent_name %></td>
|
||||
<td><%= link_to "Guarda", rec.replay_url %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<% end %>
|
||||
</section>
|
||||
@@ -20,7 +20,6 @@
|
||||
<% end %>
|
||||
<ul>
|
||||
<li>Staff trasmissione: <%= plan.max_staff_transmission || "illimitato" %> / anno</li>
|
||||
<li>Staff regia: <%= plan.max_staff_regia || "illimitato" %> / anno</li>
|
||||
<li>Partite: <%= plan.concurrent_streams_limit || "illimitate" %> in contemporanea</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>
|
||||
@@ -46,5 +45,5 @@
|
||||
<p style="margin-top:20px"><%= button_to "Gestisci su Stripe", public_team_portal_path(@team), class: "btn btn-secondary" %></p>
|
||||
<% end %>
|
||||
|
||||
<p><%= link_to "← Dashboard", public_team_dashboard_path(@team) %></p>
|
||||
<p><%= link_to "← Dettagli squadra", public_team_details_path(@team) %></p>
|
||||
</div>
|
||||
|
||||
@@ -1,117 +0,0 @@
|
||||
<% content_for :title, "#{@team.name} — Dashboard" %>
|
||||
<% content_for :meta_description, "Dashboard squadra #{@team.name} su Match Live TV." %>
|
||||
<% content_for :robots, "noindex, nofollow" %>
|
||||
|
||||
<div class="wrap" style="padding-top:20px">
|
||||
<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> (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 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 @staff_memberships.any? %>
|
||||
<table class="data">
|
||||
<thead><tr><th>Nome</th><th>Email</th><th>Ruolo</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
<% @staff_memberships.each do |ut| %>
|
||||
<tr>
|
||||
<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>
|
||||
<% 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. Usa i pulsanti sopra per il tuo account o <%= link_to "invita qualcuno", public_team_invite_path(@team) %>.</p>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<h2>Staff in attesa</h2>
|
||||
<div class="card">
|
||||
<% if @pending_invitations.any? %>
|
||||
<table class="data">
|
||||
<thead><tr><th>Email</th><th>Ruolo</th><th>Scade</th><th></th></tr></thead>
|
||||
<tbody>
|
||||
<% @pending_invitations.each do |inv| %>
|
||||
<tr>
|
||||
<td><%= inv.email %></td>
|
||||
<td><%= inv.staff_kind == "regia" ? "Regia" : "Trasmissione" %></td>
|
||||
<td><%= l inv.expires_at, format: :short %></td>
|
||||
<td>
|
||||
<%= button_to "Annulla", public_team_destroy_invitation_path(@team, inv), method: :delete, class: "btn btn-secondary", style: "padding:4px 10px;font-size:0.8rem" %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
<% else %>
|
||||
<p style="color:#888">Nessuna richiesta in sospeso.</p>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<% if @entitlements.can_access_recordings? && @recordings.any? %>
|
||||
<h2>Archivio gare</h2>
|
||||
<div class="card">
|
||||
<table class="data">
|
||||
<thead><tr><th>Partita</th><th>Replay</th></tr></thead>
|
||||
<tbody>
|
||||
<% @recordings.each do |rec| %>
|
||||
<tr>
|
||||
<td><%= rec.stream_session.match.team.name %> vs <%= rec.stream_session.match.opponent_name %></td>
|
||||
<td><%= link_to "Guarda", rec.replay_url %></td>
|
||||
</tr>
|
||||
<% end %>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<p style="color:#888;margin-top:24px">Usa l'app mobile con le stesse credenziali per avviare partite e dirette.</p>
|
||||
</div>
|
||||
101
backend/app/views/public/teams/details.html.erb
Normal file
101
backend/app/views/public/teams/details.html.erb
Normal file
@@ -0,0 +1,101 @@
|
||||
<% content_for :title, "Dettagli — #{@team.name}" %>
|
||||
<% content_for :meta_description, "Dettagli squadra #{@team.name} su Match Live TV." %>
|
||||
<% content_for :robots, "noindex, nofollow" %>
|
||||
<% total_people = @roster_by_category.values.sum(&:size) %>
|
||||
|
||||
<div class="wrap team-roster-page">
|
||||
<nav class="team-dashboard-nav" aria-label="Percorso squadra">
|
||||
<% if @can_manage %>
|
||||
<%= link_to "← #{@club.name}", public_club_path(@club), class: "team-dashboard-nav__club" %>
|
||||
<span class="team-dashboard-nav__sep">·</span>
|
||||
<%= link_to "Modifica squadra", public_edit_team_path(@team) %>
|
||||
<% else %>
|
||||
<span class="team-dashboard-nav__club"><%= @club.name %></span>
|
||||
<% end %>
|
||||
</nav>
|
||||
|
||||
<header class="roster-hero card">
|
||||
<div class="roster-hero__intro">
|
||||
<p class="roster-hero__club"><%= @club.name %></p>
|
||||
<h1 class="roster-hero__title"><%= @team.name %></h1>
|
||||
<p class="team-details-meta">
|
||||
Piano: <strong><%= @entitlements.plan.name %></strong> (società)
|
||||
· Responsabili trasmissione: <strong><%= @entitlements.staff_count_for("transmission") %> / <%= @entitlements.max_staff_transmission || "∞" %></strong>
|
||||
· Partite attive: <strong><%= @entitlements.concurrent_streams_used %><% if @entitlements.concurrent_streams_limit %> / <%= @entitlements.concurrent_streams_limit %><% else %> (illimitate)<% end %></strong>
|
||||
</p>
|
||||
<% if @team.description.present? %>
|
||||
<div class="roster-hero__desc"><%= simple_format @team.description %></div>
|
||||
<% elsif @can_manage %>
|
||||
<p class="roster-hero__desc roster-hero__desc--muted">
|
||||
Nessuna descrizione.
|
||||
<%= link_to "Aggiungila dalla modifica squadra", public_edit_team_path(@team) %>.
|
||||
</p>
|
||||
<% end %>
|
||||
<div class="roster-stats">
|
||||
<span class="roster-stat"><strong><%= total_people %></strong> in organico</span>
|
||||
<% TeamRosterMember::DISPLAY_ORDER.each do |cat| %>
|
||||
<% count = @roster_by_category[cat].size %>
|
||||
<% next if count.zero? %>
|
||||
<span class="roster-stat"><%= count %> <%= TeamRosterMember::CATEGORY_LABELS[cat].downcase %></span>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="roster-hero__media">
|
||||
<% if @team.team_photo_url.present? %>
|
||||
<%= image_tag @team.team_photo_url, alt: @team.name, class: "roster-hero__photo" %>
|
||||
<% elsif @can_manage %>
|
||||
<div class="roster-hero__photo roster-hero__photo--empty">
|
||||
<span>Foto squadra non ancora caricata</span>
|
||||
<%= link_to "Aggiungi dalla modifica squadra", public_edit_team_path(@team), class: "btn btn-secondary", style: "margin-top:8px;padding:8px 14px;font-size:0.85rem" %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="team-details-actions">
|
||||
<% if @can_manage %>
|
||||
<%= link_to "← Società", public_club_path(@club), class: "btn btn-secondary" %>
|
||||
<%= link_to "Responsabili trasmissione", public_team_invite_path(@team), class: "btn btn-secondary" %>
|
||||
<% end %>
|
||||
<% if current_user.can_stream_for?(@team) %>
|
||||
<%= link_to "Programma partite", public_team_matches_path(@team), class: "btn btn-primary" %>
|
||||
<% end %>
|
||||
<%= link_to "Dirette live", public_live_index_path(club_id: @club.id), class: "btn btn-secondary" %>
|
||||
</div>
|
||||
|
||||
<div class="roster-layout<%= " roster-layout--readonly" unless @can_manage %>">
|
||||
<section class="roster-main" aria-label="Organico">
|
||||
<% TeamRosterMember::DISPLAY_ORDER.each do |category| %>
|
||||
<% members = @roster_by_category[category] %>
|
||||
<section class="roster-section card">
|
||||
<header class="roster-section__head">
|
||||
<h2 class="roster-section__title"><%= TeamRosterMember::CATEGORY_LABELS[category] %></h2>
|
||||
<span class="roster-section__count"><%= members.size %></span>
|
||||
</header>
|
||||
<% if members.any? %>
|
||||
<div class="roster-list">
|
||||
<% members.each do |person| %>
|
||||
<%= render "shared/roster_person_card", person: person, team: @team, editable: @can_manage %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% else %>
|
||||
<p class="roster-section__empty">Nessuna persona in questa categoria.</p>
|
||||
<% end %>
|
||||
</section>
|
||||
<% end %>
|
||||
</section>
|
||||
|
||||
<% if @can_manage %>
|
||||
<aside class="roster-sidebar" aria-label="Aggiungi persona">
|
||||
<%= render "shared/roster_member_form", member: @roster_member, team: @team %>
|
||||
</aside>
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
<%= render "public/teams/streaming_staff", team: @team, club: @club, can_manage: @can_manage,
|
||||
entitlements: @entitlements, owner_membership: @owner_membership,
|
||||
staff_memberships: @staff_memberships, pending_invitations: @pending_invitations,
|
||||
recordings: @recordings %>
|
||||
|
||||
<p class="team-details-footer">Usa l'app mobile con le stesse credenziali per avviare partite e dirette.</p>
|
||||
</div>
|
||||
@@ -10,6 +10,20 @@
|
||||
<%= 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) %>
|
||||
|
||||
<%= label_tag "team[description]", "Descrizione squadra" %>
|
||||
<%= text_area_tag "team[description]", @team.description, rows: 5, placeholder: "Presentazione della squadra, palmarès, obiettivi della stagione…" %>
|
||||
|
||||
<div class="branding-logo-block">
|
||||
<%= label_tag "team[photo_file]", "Foto di squadra (gruppo o stemma)" %>
|
||||
<% if @team.team_photo_url.present? %>
|
||||
<div class="branding-logo-preview">
|
||||
<%= image_tag @team.team_photo_url, alt: "Foto squadra", class: "team-profile-photo-preview", width: 200 %>
|
||||
</div>
|
||||
<% end %>
|
||||
<%= file_field_tag "team[photo_file]", accept: "image/png,image/jpeg,image/webp", class: "branding-file-input" %>
|
||||
</div>
|
||||
|
||||
<%= render "shared/branding_fields", record: @team, show_inherit_hint: true, legend: "Branding squadra (override)" %>
|
||||
<p class="branding-hint">
|
||||
Colori società:
|
||||
@@ -19,5 +33,5 @@
|
||||
<%= submit_tag "Salva squadra", class: "btn btn-primary" %>
|
||||
<% end %>
|
||||
</div>
|
||||
<p><%= link_to "← Dashboard squadra", public_team_dashboard_path(@team) %></p>
|
||||
<p><%= link_to "← Dettagli squadra", public_team_details_path(@team) %></p>
|
||||
</div>
|
||||
|
||||
@@ -1,43 +1,40 @@
|
||||
<% content_for :title, "Aggiungi staff — #{@team.name}" %>
|
||||
<% content_for :title, "Staff trasmissione — #{@team.name}" %>
|
||||
<% content_for :robots, "noindex, nofollow" %>
|
||||
|
||||
<div class="wrap" style="padding-top:20px">
|
||||
<h1>Aggiungi staff</h1>
|
||||
<h1>Responsabili trasmissione</h1>
|
||||
<p>
|
||||
Trasmissione: <strong><%= @entitlements.staff_count_for("transmission") %> / <%= @entitlements.max_staff_transmission || "∞" %></strong>
|
||||
· Regia: <strong><%= @entitlements.staff_count_for("regia") %> / <%= @entitlements.max_staff_regia || "∞" %></strong>
|
||||
Assegnati: <strong><%= @entitlements.staff_count_for("transmission") %> / <%= @entitlements.max_staff_transmission || "∞" %></strong>
|
||||
(per squadra, rinnovo annuale)
|
||||
</p>
|
||||
<p class="muted" style="max-width:640px">
|
||||
Il responsabile trasmissione avvia la diretta dall’app (camera sul cavalletto).
|
||||
Per il punteggio da un secondo telefono non serve un account: condivide il <strong>link regia</strong> dalla schermata di trasmissione
|
||||
(WhatsApp, email, SMS, ecc.).
|
||||
</p>
|
||||
|
||||
<% owner_staff = current_user.user_teams.find_by(team: @team, role: "owner")&.staff_kind %>
|
||||
<% unless owner_staff.present? %>
|
||||
<% owner_membership = current_user.user_teams.find_by(team: @team) %>
|
||||
<% if owner_membership&.staff_kind.blank? %>
|
||||
<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).
|
||||
<strong><%= current_user.email %></strong> — puoi gestire tu camera e punteggio, oppure invitare altri per la trasmissione.
|
||||
</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>
|
||||
<%= button_to "Sono io — responsabile trasmissione", public_team_assign_self_staff_path(@team), method: :post, params: { staff_kind: "transmission" }, class: "btn btn-primary" %>
|
||||
</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([
|
||||
["Trasmissione (camera / streaming)", "transmission"],
|
||||
["Regia (punteggio)", "regia"]
|
||||
], params[:staff_kind] || "transmission") %>
|
||||
<%= label_tag :email, "Email del membro staff" %>
|
||||
<%= hidden_field_tag :staff_kind, "transmission" %>
|
||||
<%= label_tag :email, "Email del responsabile trasmissione" %>
|
||||
<%= email_field_tag :email, params[:email], required: true %>
|
||||
<%= submit_tag "Genera link di accesso", class: "btn btn-primary" %>
|
||||
<% end %>
|
||||
<% 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. 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>
|
||||
<p><%= link_to "← Dettagli squadra", public_team_details_path(@team) %></p>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user