Completa i18n IT/EN/FR/DE/ES su sito pubblico, area autenticata e admin.

Tutte le view marketing, legali, viewer, dashboard e admin usano t(); mailer utente-facing e flash localizzati; admin con LocaleResolver e selettore lingua.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-24 00:38:50 +02:00
co-authored by Cursor
parent fb7068f86c
commit b88f44ab1c
158 changed files with 9444 additions and 1536 deletions
+5 -5
View File
@@ -1,26 +1,26 @@
<div style="max-width:360px;margin:4rem auto">
<h2>Accesso admin</h2>
<h2><%= t("admin.auth.new.title") %></h2>
<% if flash[:alert] %>
<p style="color:#ff6b6b"><%= flash[:alert] %></p>
<% end %>
<%= form_with url: admin_login_path, method: :post, local: true do %>
<p>
<label for="username">Username</label><br>
<label for="username"><%= t("admin.auth.new.username_label") %></label><br>
<input type="text" name="username" id="username" required autofocus autocomplete="username"
style="width:100%;padding:0.5rem;margin-top:0.25rem;background:#1E1E1E;border:1px solid #333;color:#fff;border-radius:6px">
</p>
<p>
<label for="password">Password</label><br>
<label for="password"><%= t("admin.auth.new.password_label") %></label><br>
<input type="password" name="password" id="password" required autocomplete="current-password"
style="width:100%;padding:0.5rem;margin-top:0.25rem;background:#1E1E1E;border:1px solid #333;color:#fff;border-radius:6px">
</p>
<p>
<button type="submit" style="background:#FF2D2D;color:#fff;border:0;padding:0.6rem 1.2rem;border-radius:6px;cursor:pointer;font-weight:700">
Accedi
<%= t("admin.auth.new.submit") %>
</button>
</p>
<% end %>
<p style="color:#888;font-size:0.85rem;margin-top:1.5rem">
Credenziali iniziali: <code>admin</code> / <code>admin</code>. Cambia la password dopo il primo accesso.
<%= t("admin.auth.new.initial_credentials_html") %>
</p>
</div>
+17 -17
View File
@@ -1,12 +1,12 @@
<h2>Pagamenti da fatturare</h2>
<h2><%= t("admin.billing.index.title") %></h2>
<p style="color:#666;margin-bottom:16px">
Pagamenti Stripe <strong>pagati</strong> senza PDF fattura. Genera il PDF nei tuoi sistemi, poi caricalo qui: viene associato al pagamento e inviato via email al cliente.
<%= t("admin.billing.index.description") %>
</p>
<% if @clubs.many? %>
<p style="margin-bottom:16px">
Filtra società:
<%= link_to "Tutte", admin_billing_path, class: (@filter_club ? nil : "admin-nav-active") %>
<%= t("admin.billing.index.filter_label") %>
<%= link_to t("admin.billing.index.filter_all"), admin_billing_path, class: (@filter_club ? nil : "admin-nav-active") %>
<% @clubs.each do |club| %>
· <%= link_to club.name, admin_billing_path(club_id: club.id), class: (@filter_club&.id == club.id ? "admin-nav-active" : nil) %>
<% end %>
@@ -33,7 +33,7 @@
· <strong><%= payment.formatted_amount %></strong>
</div>
<% unless club.billing_profile_complete? %>
<span class="billing-pending-card__warn">Dati fatturazione incompleti</span>
<span class="billing-pending-card__warn"><%= t("admin.billing.index.incomplete_profile_warning") %></span>
<% end %>
</header>
@@ -46,16 +46,16 @@
<% end %>
</dl>
<% else %>
<p class="billing-pending-card__warn">Nessun dato di fatturazione — il cliente deve completare il profilo.</p>
<p class="billing-pending-card__warn"><%= t("admin.billing.index.no_billing_data") %></p>
<% end %>
<%= form_with url: admin_billing_payment_attach_pdf_path(payment),
method: :post, multipart: true, local: true, class: "billing-upload-form" do %>
<label class="billing-upload-form__label">
PDF fattura
<%= t("admin.billing.index.pdf_label") %>
<%= file_field_tag :pdf, accept: "application/pdf", required: true %>
</label>
<%= submit_tag "Carica PDF e invia al cliente", class: "admin-btn admin-btn--primary" %>
<%= submit_tag t("admin.billing.index.upload_button"), class: "admin-btn admin-btn--primary" %>
<% end %>
</div>
</article>
@@ -63,21 +63,21 @@
</div>
<% else %>
<p class="admin-flash" style="background:#1b3d1b;border-color:#2e7d32">
Nessun pagamento in attesa di fattura<%= @filter_club ? " per #{@filter_club.name}" : "" %>.
<%= t("admin.billing.index.none_pending", club: (@filter_club ? t("admin.billing.index.none_pending_for_club", club: @filter_club.name) : "")) %>
</p>
<% end %>
<% if @completed_payments.any? %>
<h2 style="margin-top:40px;font-size:1.15rem">Fatture già caricate</h2>
<h2 style="margin-top:40px;font-size:1.15rem"><%= t("admin.billing.index.uploaded_title") %></h2>
<table class="admin-table">
<thead>
<tr>
<th>Data</th>
<th>Società</th>
<th>Descrizione</th>
<th>Importo</th>
<th>Fattura</th>
<th>Stato</th>
<th><%= t("admin.billing.index.table.date") %></th>
<th><%= t("admin.billing.index.table.club") %></th>
<th><%= t("admin.billing.index.table.description") %></th>
<th><%= t("admin.billing.index.table.amount") %></th>
<th><%= t("admin.billing.index.table.invoice") %></th>
<th><%= t("admin.billing.index.table.status") %></th>
</tr>
</thead>
<tbody>
@@ -96,4 +96,4 @@
</table>
<% end %>
<p style="margin-top:24px"><%= link_to "← Dashboard", admin_root_path %></p>
<p style="margin-top:24px"><%= link_to t("admin.billing.index.back_dashboard"), admin_root_path %></p>
@@ -1,45 +1,45 @@
<h1>Fattura <%= @invoice.number %><%= @club.name %></h1>
<h1><%= t("admin.billing_invoices.edit.title", number: @invoice.number, club: @club.name) %></h1>
<p style="color:#666">
Stato: <strong><%= @invoice.status %></strong>
<%= t("admin.billing_invoices.edit.status_label") %> <strong><%= @invoice.status %></strong>
<% if @invoice.emailed_at.present? %>
· Inviata il <%= @invoice.emailed_at.in_time_zone.strftime("%d/%m/%Y %H:%M") %>
· <%= t("admin.billing_invoices.edit.sent_at", date: @invoice.emailed_at.in_time_zone.strftime("%d/%m/%Y %H:%M")) %>
<% end %>
</p>
<div class="card">
<%= form_with model: @invoice, url: admin_club_billing_invoice_path(@club, @invoice), multipart: true, method: :patch do |f| %>
<%= f.label :number, "Numero fattura" %>
<%= f.label :number, t("admin.billing_invoices.edit.number_label") %>
<%= f.text_field :number, required: true, disabled: @invoice.status.in?(%w[sent]) %>
<%= f.label :issued_on, "Data emissione" %>
<%= f.label :issued_on, t("admin.billing_invoices.edit.issued_on_label") %>
<%= f.date_field :issued_on, required: true, disabled: @invoice.status.in?(%w[sent]) %>
<%= label_tag :amount_euros, "Importo (€)" %>
<%= label_tag :amount_euros, t("admin.billing_invoices.edit.amount_label") %>
<%= number_field_tag "billing_invoice[amount_euros]",
@invoice.amount_cents / 100.0,
step: 0.01, min: 0.01, required: true, disabled: @invoice.status.in?(%w[sent]) %>
<% if @payment %>
<p style="color:#888;font-size:0.9rem">Pagamento: <%= @payment.display_description %><%= @payment.formatted_amount %></p>
<p style="color:#888;font-size:0.9rem"><%= t("admin.billing_invoices.edit.payment_info", description: @payment.display_description, amount: @payment.formatted_amount) %></p>
<% end %>
<%= f.label :pdf, "PDF fattura" %>
<%= f.label :pdf, t("admin.billing_invoices.edit.pdf_label") %>
<% if @invoice.pdf.attached? %>
<p style="color:#888">PDF già caricato: <%= @invoice.pdf.filename %></p>
<p style="color:#888"><%= t("admin.billing_invoices.edit.pdf_already", filename: @invoice.pdf.filename) %></p>
<% end %>
<%= f.file_field :pdf, accept: "application/pdf" %>
<%= f.label :notes, "Note interne" %>
<%= f.label :notes, t("admin.billing_invoices.edit.notes_label") %>
<%= f.text_area :notes, rows: 2 %>
<div style="margin-top:16px;display:flex;gap:10px;flex-wrap:wrap">
<% unless @invoice.status.in?(%w[sent]) %>
<%= f.submit "Salva bozza", class: "btn btn-secondary" %>
<%= f.submit "Emetti e invia via email", class: "btn btn-primary",
data: { turbo_confirm: "Inviare la fattura a #{@club.billing_email}?" } %>
<%= f.submit t("admin.billing_invoices.edit.save_draft"), name: "commit_action", value: "draft", class: "btn btn-secondary" %>
<%= f.submit t("admin.billing_invoices.edit.issue_send"), name: "commit_action", value: "issue", class: "btn btn-primary",
data: { turbo_confirm: t("admin.billing_invoices.edit.issue_confirm", email: @club.billing_email) } %>
<% end %>
</div>
<% end %>
</div>
<p><%= link_to "← Pagamenti e fatture", admin_club_billing_invoices_path(@club) %></p>
<p><%= link_to t("admin.billing_invoices.edit.back"), admin_club_billing_invoices_path(@club) %></p>
@@ -1,18 +1,18 @@
<h1>Fatturazione — <%= @club.name %></h1>
<h1><%= t("admin.billing_invoices.index.title", club: @club.name) %></h1>
<p>
<%= link_to "← Dashboard", admin_root_path %>
· <%= link_to "Nuova fattura (senza pagamento)", new_admin_club_billing_invoice_path(@club), class: "btn btn-secondary" %>
<%= link_to t("admin.billing_invoices.index.back_dashboard"), admin_root_path %>
· <%= link_to t("admin.billing_invoices.index.new_invoice"), new_admin_club_billing_invoice_path(@club), class: "btn btn-secondary" %>
</p>
<h2 style="margin-top:24px;font-size:1.1rem">Pagamenti</h2>
<h2 style="margin-top:24px;font-size:1.1rem"><%= t("admin.billing_invoices.index.payments_title") %></h2>
<% if @payments.any? %>
<table class="data">
<thead>
<tr>
<th>Data</th>
<th>Descrizione</th>
<th>Importo</th>
<th>Fattura</th>
<th><%= t("admin.billing_invoices.index.table.date") %></th>
<th><%= t("admin.billing_invoices.index.table.description") %></th>
<th><%= t("admin.billing_invoices.index.table.amount") %></th>
<th><%= t("admin.billing_invoices.index.table.invoice") %></th>
<th></th>
</tr>
</thead>
@@ -26,16 +26,16 @@
<td>
<% if inv %>
<%= inv.number %><%= inv.status %>
<% if inv.pdf.attached? %> (PDF)<% end %>
<% if inv.pdf.attached? %><%= t("admin.billing_invoices.index.pdf_suffix") %><% end %>
<% else %>
<%= t("admin.common.dash") %>
<% end %>
</td>
<td>
<% if inv %>
<%= link_to "Modifica / invia PDF", edit_admin_club_billing_invoice_path(@club, inv) %>
<%= link_to t("admin.billing_invoices.index.edit_send_pdf"), edit_admin_club_billing_invoice_path(@club, inv) %>
<% else %>
<%= link_to "Crea fattura", new_admin_club_billing_invoice_path(@club, billing_payment_id: payment.id), class: "btn btn-primary", style: "padding:6px 10px;font-size:0.85rem" %>
<%= link_to t("admin.billing_invoices.index.create_invoice"), new_admin_club_billing_invoice_path(@club, billing_payment_id: payment.id), class: "btn btn-primary", style: "padding:6px 10px;font-size:0.85rem" %>
<% end %>
</td>
</tr>
@@ -43,14 +43,21 @@
</tbody>
</table>
<% else %>
<p>Nessun pagamento registrato per questa società.</p>
<p><%= t("admin.billing_invoices.index.no_payments") %></p>
<% end %>
<h2 style="margin-top:32px;font-size:1.1rem">Tutte le fatture</h2>
<h2 style="margin-top:32px;font-size:1.1rem"><%= t("admin.billing_invoices.index.all_invoices_title") %></h2>
<% if @invoices.any? %>
<table class="data">
<thead>
<tr><th>Numero</th><th>Data</th><th>Importo</th><th>Stato</th><th>Pagamento</th><th></th></tr>
<tr>
<th><%= t("admin.billing_invoices.index.table2.number") %></th>
<th><%= t("admin.billing_invoices.index.table2.date") %></th>
<th><%= t("admin.billing_invoices.index.table2.amount") %></th>
<th><%= t("admin.billing_invoices.index.table2.status") %></th>
<th><%= t("admin.billing_invoices.index.table2.payment") %></th>
<th></th>
</tr>
</thead>
<tbody>
<% @invoices.each do |inv| %>
@@ -59,12 +66,12 @@
<td><%= inv.issued_on %></td>
<td><%= inv.formatted_amount %></td>
<td><%= inv.status %></td>
<td><%= inv.billing_payment_id.present? ? "Sì" : "No" %></td>
<td><%= link_to "Modifica", edit_admin_club_billing_invoice_path(@club, inv) %></td>
<td><%= inv.billing_payment_id.present? ? t("admin.common.yes") : t("admin.common.no") %></td>
<td><%= link_to t("admin.billing_invoices.index.edit"), edit_admin_club_billing_invoice_path(@club, inv) %></td>
</tr>
<% end %>
</tbody>
</table>
<% else %>
<p>Nessuna fattura.</p>
<p><%= t("admin.billing_invoices.index.no_invoices") %></p>
<% end %>
@@ -1,32 +1,32 @@
<h1>Nuova fattura — <%= @club.name %></h1>
<h1><%= t("admin.billing_invoices.new.title", club: @club.name) %></h1>
<% if @payment %>
<p style="color:#666">Collegata al pagamento del <%= @payment.paid_at&.to_date || @payment.created_at.to_date %> (<%= @payment.formatted_amount %>).</p>
<p style="color:#666"><%= t("admin.billing_invoices.new.linked_payment", date: (@payment.paid_at&.to_date || @payment.created_at.to_date), amount: @payment.formatted_amount) %></p>
<% end %>
<div class="card">
<%= form_with model: @invoice, url: admin_club_billing_invoices_path(@club) do |f| %>
<%= f.hidden_field :billing_payment_id if @payment %>
<%= f.label :number, "Numero fattura" %>
<%= f.label :number, t("admin.billing_invoices.new.number_label") %>
<%= f.text_field :number, required: true %>
<%= f.label :issued_on, "Data emissione" %>
<%= f.label :issued_on, t("admin.billing_invoices.new.issued_on_label") %>
<%= f.date_field :issued_on, required: true %>
<%= label_tag :amount_euros, "Importo (€)" %>
<%= label_tag :amount_euros, t("admin.billing_invoices.new.amount_label") %>
<%= number_field_tag "billing_invoice[amount_euros]",
(@invoice.amount_cents ? @invoice.amount_cents / 100.0 : nil),
step: 0.01, min: 0.01, required: true %>
<%= f.label :notes, "Note interne (opzionale)" %>
<%= f.label :notes, t("admin.billing_invoices.new.notes_label") %>
<%= f.text_area :notes, rows: 2 %>
<p style="color:#666;font-size:0.9rem;margin-top:12px">
Salva la bozza, poi carica il PDF e inviala al cliente dalla schermata successiva.
<%= t("admin.billing_invoices.new.hint") %>
</p>
<%= f.submit "Crea bozza fattura", class: "btn btn-primary" %>
<%= f.submit t("admin.billing_invoices.new.submit"), class: "btn btn-primary" %>
<% end %>
</div>
<p><%= link_to "← Pagamenti e fatture", admin_club_billing_invoices_path(@club) %></p>
<p><%= link_to t("admin.billing_invoices.new.back"), admin_club_billing_invoices_path(@club) %></p>
@@ -1,4 +1,4 @@
<% content_for :title, "Replay — #{@club.name}" %>
<% content_for :title, t("admin.club_recordings.index.title", club: @club.name) %>
<% content_for :replay_archive_styles, true %>
<%= render "recordings/club_archive", admin_mode: true %>
@@ -1,43 +1,43 @@
<%# locals: (club:, subscription:, return_to: nil) %>
<section class="admin-comped-card" style="margin-bottom:24px;padding:16px;border:1px solid #3d3520;border-radius:8px;background:#2a2618">
<h2 style="margin:0 0 8px;font-size:1.1rem">Abbonamento omaggio</h2>
<h2 style="margin:0 0 8px;font-size:1.1rem"><%= t("admin.comped.title") %></h2>
<p style="color:#bbb;font-size:0.9rem;margin:0 0 14px">
Sponsor o promozione: assegna Premium Light/Full senza pagamento Stripe. Revocabile in qualsiasi momento.
<%= t("admin.comped.description") %>
</p>
<% sub = subscription %>
<% if sub&.admin_comped? %>
<p style="margin:0 0 12px">
<strong>Attivo:</strong> <%= sub.plan.name %>
<strong><%= t("admin.comped.active_label") %></strong> <%= sub.plan.name %>
<% if sub.admin_comped_reason.present? %>
· <%= sub.admin_comped_reason %>
<% end %>
<% if sub.admin_comped_at.present? %>
<br><span style="color:#888;font-size:0.85rem">Dal <%= l(sub.admin_comped_at, format: :long) %>
<% if sub.admin_comped_by.present? %> · admin <%= sub.admin_comped_by.username %><% end %>
<br><span style="color:#888;font-size:0.85rem"><%= t("admin.comped.since_label_html", date: l(sub.admin_comped_at, format: :long)) %>
<% if sub.admin_comped_by.present? %> <%= t("admin.comped.by_admin", username: sub.admin_comped_by.username) %><% end %>
</span>
<% end %>
<% if sub.stripe_subscription_id.present? %>
<br><span style="color:#888;font-size:0.85rem">Nota: esiste anche un abbonamento Stripe collegato; lomaggio ha priorità sul piano.</span>
<br><span style="color:#888;font-size:0.85rem"><%= t("admin.comped.stripe_note") %></span>
<% end %>
</p>
<%= button_to "Revoca omaggio (torna Free o ripristina Stripe)",
<%= button_to t("admin.comped.revoke_button"),
revoke_comped_admin_club_path(club, return_to: return_to),
method: :delete,
class: "admin-btn admin-btn--secondary",
form: { data: { turbo_confirm: "Revocare labbonamento omaggio per #{club.name}?" } } %>
form: { data: { turbo_confirm: t("admin.comped.revoke_confirm", club: club.name) } } %>
<% else %>
<p style="margin:0 0 12px;color:#888">
Piano attuale: <strong><%= sub&.plan&.name || "Free" %></strong>
<%= t("admin.comped.current_plan", plan: sub&.plan&.name || t("admin.common.free_plan")) %>
<% if sub&.stripe_subscription_id.present? %>
· pagamento Stripe attivo
<%= t("admin.comped.stripe_active") %>
<% end %>
</p>
<%= form_with url: grant_comped_admin_club_path(club), method: :post, local: true, class: "admin-comped-form" do %>
<%= hidden_field_tag :return_to, return_to if return_to.present? %>
<div style="display:flex;flex-wrap:wrap;gap:12px;align-items:flex-end">
<label style="display:flex;flex-direction:column;gap:4px;font-size:0.85rem">
Piano
<%= t("admin.comped.plan_label") %>
<%= select_tag :plan_slug,
options_for_select(
[["Premium Light", "premium_light"], ["Premium Full", "premium_full"]]
@@ -46,10 +46,10 @@
class: "admin-input" %>
</label>
<label style="display:flex;flex-direction:column;gap:4px;font-size:0.85rem;flex:1;min-width:200px">
Motivo (es. sponsor 2026)
<%= text_field_tag :reason, nil, placeholder: "Sponsor, promozione…", class: "admin-input", style: "width:100%" %>
<%= t("admin.comped.reason_label") %>
<%= text_field_tag :reason, nil, placeholder: t("admin.comped.reason_placeholder"), class: "admin-input", style: "width:100%" %>
</label>
<%= submit_tag "Concedi omaggio", class: "admin-btn admin-btn--primary" %>
<%= submit_tag t("admin.comped.grant_button"), class: "admin-btn admin-btn--primary" %>
</div>
<% end %>
<% end %>
+13 -14
View File
@@ -1,17 +1,16 @@
<h2>Società e squadre</h2>
<h2><%= t("admin.clubs.index.title") %></h2>
<p style="color:#888;margin-bottom:16px">
Abbonamenti omaggio (sponsor / promozioni) e stato YouTube per squadra.
Pagamenti Stripe: <%= link_to "Fatturazione", admin_billing_path %>.
<%= t("admin.clubs.index.subtitle_html", billing_link: link_to(t("admin.clubs.index.billing_link"), admin_billing_path)) %>
</p>
<table class="admin-table">
<thead>
<tr>
<th>Società</th>
<th>Piano</th>
<th>Squadre</th>
<th>Omaggio</th>
<th>Stripe</th>
<th><%= t("admin.clubs.index.table.club") %></th>
<th><%= t("admin.clubs.index.table.plan") %></th>
<th><%= t("admin.clubs.index.table.teams") %></th>
<th><%= t("admin.clubs.index.table.comped") %></th>
<th><%= t("admin.clubs.index.table.stripe") %></th>
<th></th>
</tr>
</thead>
@@ -20,20 +19,20 @@
<% sub = club.subscription %>
<tr>
<td><strong><%= club.name %></strong></td>
<td><%= sub&.plan&.name || "Free" %></td>
<td><%= sub&.plan&.name || t("admin.common.free_plan") %></td>
<td><%= club.teams.size %></td>
<td>
<% if sub&.admin_comped? %>
<span style="color:#ffb74d"></span>
<span style="color:#ffb74d"><%= t("admin.common.yes") %></span>
<% if sub.admin_comped_reason.present? %> · <%= sub.admin_comped_reason %><% end %>
<% else %>
<%= t("admin.common.dash") %>
<% end %>
</td>
<td><%= sub&.stripe_subscription_id.present? ? "Sì" : "—" %></td>
<td><%= sub&.stripe_subscription_id.present? ? t("admin.common.yes") : t("admin.common.dash") %></td>
<td>
<%= link_to "Gestisci", admin_club_path(club) %>
· <%= link_to "Fatture", admin_billing_path(club_id: club.id) %>
<%= link_to t("admin.clubs.index.manage"), admin_club_path(club) %>
· <%= link_to t("admin.clubs.index.invoices"), admin_billing_path(club_id: club.id) %>
</td>
</tr>
<% end %>
+14 -14
View File
@@ -1,36 +1,36 @@
<p style="margin-bottom:16px"><%= link_to "← Società e squadre", admin_clubs_path %></p>
<p style="margin-bottom:16px"><%= link_to t("admin.clubs.show.back"), admin_clubs_path %></p>
<h2><%= @club.name %></h2>
<p style="color:#888">
Sport: <%= @club.sport %>
· <%= link_to "Archivio replay", admin_club_recordings_path(@club) %>
· <%= link_to "Pagamenti e fatture", admin_billing_path(club_id: @club.id) %>
· <%= link_to "Canale YouTube piattaforma", admin_youtube_platform_path %>
<%= t("admin.clubs.show.sport_label", sport: @club.sport) %>
· <%= link_to t("admin.clubs.show.recordings_archive"), admin_club_recordings_path(@club) %>
· <%= link_to t("admin.clubs.show.billing_link"), admin_billing_path(club_id: @club.id) %>
· <%= link_to t("admin.clubs.show.youtube_platform_link"), admin_youtube_platform_path %>
</p>
<%= render "admin/clubs/comped_form", club: @club, subscription: @subscription, return_to: admin_club_path(@club) %>
<% cred = @club.youtube_credential %>
<p style="margin-top:16px">
<strong>YouTube società:</strong>
<strong><%= t("admin.clubs.show.youtube_club_label") %></strong>
<% if cred %>
<%= cred.channel_title.presence || cred.channel_id %> (collegato)
<%= cred.channel_title.presence || cred.channel_id %> <%= t("admin.clubs.show.youtube_connected") %>
<% elsif @teams.any? { |t| t.entitlements.premium_full? } %>
<span class="muted">Premium Full — canale società non collegato (usa Match Live TV)</span>
<span class="muted"><%= t("admin.clubs.show.youtube_premium_full_not_connected") %></span>
<% else %>
<span class="muted"></span>
<span class="muted"><%= t("admin.common.dash") %></span>
<% end %>
</p>
<h3 style="font-size:1rem;margin-top:28px">Squadre</h3>
<h3 style="font-size:1rem;margin-top:28px"><%= t("admin.clubs.show.teams_title") %></h3>
<% if @teams.empty? %>
<p class="muted">Nessuna squadra registrata.</p>
<p class="muted"><%= t("admin.clubs.show.no_teams") %></p>
<% else %>
<table class="admin-table">
<thead>
<tr>
<th>Squadra</th>
<th>Sport</th>
<th><%= t("admin.clubs.show.table.team") %></th>
<th><%= t("admin.clubs.show.table.sport") %></th>
<th></th>
</tr>
</thead>
@@ -39,7 +39,7 @@
<tr>
<td><strong><%= team.name %></strong></td>
<td><%= team.sport %></td>
<td><%= link_to "Partite e dettagli", admin_team_path(team) %> · <%= link_to "Replay", admin_club_recordings_path(@club, team_id: team.id) %></td>
<td><%= link_to t("admin.clubs.show.matches_and_details"), admin_team_path(team) %> · <%= link_to t("admin.clubs.show.replay"), admin_club_recordings_path(@club, team_id: team.id) %></td>
</tr>
<% end %>
</tbody>
@@ -2,147 +2,153 @@
<section class="kpi-grid">
<div class="kpi-card kpi-card--accent">
<div class="kpi-label">Sessioni attive</div>
<div class="kpi-label"><%= t("admin.dashboard.kpi.active_sessions") %></div>
<div class="kpi-value kpi-value--live" id="kpi-active-sessions"><%= @stats[:active_sessions] %></div>
<div class="kpi-sub">in diretta ora</div>
<div class="kpi-sub"><%= t("admin.dashboard.kpi.active_sessions_sub") %></div>
</div>
<div class="kpi-card">
<div class="kpi-label">Trasmesse oggi</div>
<div class="kpi-label"><%= t("admin.dashboard.kpi.today") %></div>
<div class="kpi-value" id="kpi-sessions-today"><%= @stats[:sessions_today] %></div>
<div class="kpi-sub"><%= format_duration_minutes(@stats[:stream_minutes_today]) %> di streaming</div>
<div class="kpi-sub"><%= t("admin.dashboard.kpi.today_sub", duration: format_duration_minutes(@stats[:stream_minutes_today])) %></div>
</div>
<div class="kpi-card">
<div class="kpi-label">Trasmesse questo mese</div>
<div class="kpi-label"><%= t("admin.dashboard.kpi.month") %></div>
<div class="kpi-value" id="kpi-sessions-month"><%= @stats[:sessions_month] %></div>
<div class="kpi-sub"><%= format_duration_minutes(@stats[:stream_minutes_month]) %> totali</div>
<div class="kpi-sub"><%= t("admin.dashboard.kpi.month_sub", duration: format_duration_minutes(@stats[:stream_minutes_month])) %></div>
</div>
<div class="kpi-card">
<div class="kpi-label">Squadre</div>
<div class="kpi-label"><%= t("admin.dashboard.kpi.teams") %></div>
<div class="kpi-value"><%= @stats[:teams_count] %></div>
<div class="kpi-sub"><%= @stats[:users_count] %> utenti</div>
<div class="kpi-sub"><%= t("admin.dashboard.kpi.teams_sub", count: @stats[:users_count]) %></div>
</div>
<div class="kpi-card">
<div class="kpi-label">CPU</div>
<div class="kpi-label"><%= t("admin.dashboard.kpi.cpu") %></div>
<div class="kpi-value" id="kpi-cpu"><%= @host[:cpu] %>%</div>
<div class="kpi-sub">load <%= @host[:load_avg]["1m"] %> / <%= @host[:load_avg]["5m"] %> / <%= @host[:load_avg]["15m"] %></div>
<div class="kpi-sub"><%= t("admin.dashboard.kpi.cpu_sub", one: @host[:load_avg]["1m"], five: @host[:load_avg]["5m"], fifteen: @host[:load_avg]["15m"]) %></div>
</div>
<div class="kpi-card">
<div class="kpi-label">RAM</div>
<div class="kpi-label"><%= t("admin.dashboard.kpi.ram") %></div>
<div class="kpi-value" id="kpi-mem"><%= @host[:memory][:used_percent] %>%</div>
<div class="kpi-sub"><%= format_bytes(@host[:memory][:used_bytes]) %> / <%= format_bytes(@host[:memory][:total_bytes]) %></div>
<div class="kpi-sub"><%= t("admin.dashboard.kpi.ram_sub", used: format_bytes(@host[:memory][:used_bytes]), total: format_bytes(@host[:memory][:total_bytes])) %></div>
</div>
<div class="kpi-card">
<div class="kpi-label">Banda (totale)</div>
<div class="kpi-label"><%= t("admin.dashboard.kpi.network") %></div>
<div class="kpi-value" style="font-size:1.2rem" id="kpi-network"><%= format_bytes(@host[:network][:total_bytes]) %></div>
<div class="kpi-sub"><%= format_bytes(@host[:network][:tx_bytes_total]) %> · ↓ <%= format_bytes(@host[:network][:rx_bytes_total]) %></div>
<div class="kpi-sub"><%= t("admin.dashboard.kpi.network_sub", tx: format_bytes(@host[:network][:tx_bytes_total]), rx: format_bytes(@host[:network][:rx_bytes_total])) %></div>
</div>
<div class="kpi-card">
<div class="kpi-label">Replay in archivio</div>
<div class="kpi-label"><%= t("admin.dashboard.kpi.recordings") %></div>
<div class="kpi-value"><%= @stats[:recordings_ready] %></div>
<div class="kpi-sub"><%= @stats[:recordings_count] %> registrazioni</div>
<div class="kpi-sub"><%= t("admin.dashboard.kpi.recordings_sub", count: @stats[:recordings_count]) %></div>
</div>
</section>
<section class="charts-grid">
<div class="chart-card">
<h3>CPU (%)</h3>
<h3><%= t("admin.dashboard.charts.cpu") %></h3>
<div class="chart-wrap"><canvas id="chart-cpu"></canvas></div>
</div>
<div class="chart-card">
<h3>RAM (%)</h3>
<h3><%= t("admin.dashboard.charts.ram") %></h3>
<div class="chart-wrap"><canvas id="chart-mem"></canvas></div>
</div>
</section>
<section class="panel" style="margin-bottom:1.25rem">
<h2>Stato piattaforma (Ops)</h2>
<h2><%= t("admin.dashboard.ops_panel.title") %></h2>
<% critical = Ops::Incident.critical_open.count %>
<% warnings = Ops::Incident.open.where(severity: "warning").count %>
<% if critical.positive? %>
<p class="admin-flash" style="margin-bottom:0.75rem"><%= critical %> incidente/i critico/i aperto/i — <%= link_to "Vedi dashboard Ops", admin_ops_path %></p>
<p class="admin-flash" style="margin-bottom:0.75rem"><%= t("admin.dashboard.ops_panel.critical", count: critical) %><%= link_to t("admin.dashboard.ops_panel.view_dashboard"), admin_ops_path %></p>
<% elsif warnings.positive? %>
<p class="kpi-sub" style="margin-bottom:0.75rem"><%= warnings %> warning — <%= link_to "Dashboard Ops", admin_ops_path %></p>
<p class="kpi-sub" style="margin-bottom:0.75rem"><%= t("admin.dashboard.ops_panel.warnings", count: warnings) %><%= link_to t("admin.dashboard.ops_panel.dashboard_link"), admin_ops_path %></p>
<% else %>
<p class="empty" style="margin:0">Nessun incidente aperto. <%= link_to "Dashboard Ops", admin_ops_path %></p>
<p class="empty" style="margin:0"><%= t("admin.dashboard.ops_panel.none_html", link: link_to(t("admin.dashboard.ops_panel.dashboard_link"), admin_ops_path)) %></p>
<% end %>
</section>
<section class="charts-grid" style="grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));">
<div class="chart-card">
<h3>Disco sistema</h3>
<h3><%= t("admin.dashboard.disk.system_title") %></h3>
<% root = @host[:disk][:root] %>
<% if root[:total_bytes] %>
<div class="disk-row">
<header><span><%= root[:path] %></span><span><%= format_bytes(root[:used_bytes]) %> / <%= format_bytes(root[:total_bytes]) %></span></header>
<div class="progress-bar <%= 'progress-bar--ok' if root[:used_percent].to_f < 80 %>"><span style="width:<%= root[:used_percent] %>%"></span></div>
<p class="kpi-sub" style="margin-top:0.35rem">Libero: <%= format_bytes(root[:free_bytes]) %> (<%= root[:used_percent] %>% usato)</p>
<p class="kpi-sub" style="margin-top:0.35rem"><%= t("admin.dashboard.disk.free_label", free: format_bytes(root[:free_bytes]), percent: root[:used_percent]) %></p>
</div>
<% else %>
<p class="empty">Metriche disco non disponibili</p>
<p class="empty"><%= t("admin.dashboard.disk.not_available") %></p>
<% end %>
</div>
<div class="chart-card">
<h3>Archivio registrazioni</h3>
<h3><%= t("admin.dashboard.disk.recordings_title") %></h3>
<% rec = @host[:disk][:recordings] %>
<% if rec[:total_bytes] %>
<div class="disk-row">
<header><span><%= rec[:path] %></span><span><%= format_bytes(rec[:used_bytes]) %> / <%= format_bytes(rec[:total_bytes]) %></span></header>
<div class="progress-bar <%= 'progress-bar--ok' if rec[:used_percent].to_f < 80 %>"><span style="width:<%= rec[:used_percent] %>%"></span></div>
<% if rec[:dir_size_bytes] %>
<p class="kpi-sub" style="margin-top:0.35rem">Contenuto registrazioni: <%= format_bytes(rec[:dir_size_bytes]) %></p>
<p class="kpi-sub" style="margin-top:0.35rem"><%= t("admin.dashboard.disk.recordings_content", size: format_bytes(rec[:dir_size_bytes])) %></p>
<% end %>
</div>
<% else %>
<p class="empty">Percorso <%= Admin::HostMetrics::RECORDINGS_PATH %> non montato</p>
<p class="empty"><%= t("admin.dashboard.disk.recordings_not_mounted", path: Admin::HostMetrics::RECORDINGS_PATH) %></p>
<% end %>
</div>
</section>
<section class="admin-panels">
<div class="panel">
<h2>Sessioni attive</h2>
<h2><%= t("admin.dashboard.sessions.title") %></h2>
<% if @active_sessions.any? %>
<table class="admin-table">
<thead>
<tr><th>Partita</th><th>Stato</th><th>Inizio</th><th>Link</th><th></th></tr>
<tr>
<th><%= t("admin.dashboard.sessions.table.match") %></th>
<th><%= t("admin.dashboard.sessions.table.status") %></th>
<th><%= t("admin.dashboard.sessions.table.start") %></th>
<th><%= t("admin.dashboard.sessions.table.link") %></th>
<th></th>
</tr>
</thead>
<tbody id="active-sessions-body">
<% @active_sessions.each do |s| %>
<tr>
<td><%= s.match.team.name %> vs <%= s.match.opponent_name %></td>
<td><span class="badge badge--<%= s.status == 'live' ? 'live' : (s.status == 'paused' ? 'paused' : 'connecting') %>"><%= s.status %></span></td>
<td class="muted"><%= s.started_at&.strftime("%d/%m %H:%M") || "—" %></td>
<td class="muted"><%= s.started_at&.strftime("%d/%m %H:%M") || t("admin.common.dash") %></td>
<td class="admin-link-compact">
<% admin_session_watch_links(s).each do |link| %>
<%= link_to link[:label], link[:url], target: "_blank", rel: "noopener", class: "admin-link-chip" %>
<% end %>
<%= link_to "Regia", admin_session_path(s), class: "admin-link-chip admin-link-chip--regia" %>
<%= link_to t("admin.dashboard.sessions.regia"), admin_session_path(s), class: "admin-link-chip admin-link-chip--regia" %>
</td>
<td class="admin-actions">
<%= link_to "Dettaglio", admin_session_path(s) %>
<%= button_to "Termina", stop_admin_session_path(s), method: :post, class: "admin-btn admin-btn--danger admin-btn--sm", form: { data: { turbo_confirm: "Terminare questa sessione?" } } %>
<%= link_to t("admin.dashboard.sessions.detail"), admin_session_path(s) %>
<%= button_to t("admin.dashboard.sessions.stop"), stop_admin_session_path(s), method: :post, class: "admin-btn admin-btn--danger admin-btn--sm", form: { data: { turbo_confirm: t("admin.dashboard.sessions.stop_confirm") } } %>
</td>
</tr>
<% end %>
</tbody>
</table>
<% else %>
<p class="empty">Nessuna sessione attiva in questo momento.</p>
<p class="empty"><%= t("admin.dashboard.sessions.none") %></p>
<% end %>
</div>
<div class="panel">
<h2>Squadre</h2>
<h2><%= t("admin.dashboard.teams.title") %></h2>
<ul class="team-list">
<% @teams.each do |t| %>
<% @teams.each do |t_| %>
<li>
<%= link_to t.name, admin_team_path(t) %>
<span class="muted"><%= t.matches.count %> partite</span>
<%= link_to t_.name, admin_team_path(t_) %>
<span class="muted"><%= t("admin.dashboard.teams.matches_count", count: t_.matches.count) %></span>
</li>
<% end %>
</ul>
<% if @stats[:teams_count] > @teams.size %>
<p class="kpi-sub" style="margin-top:0.75rem"><%= link_to "Vedi società (#{@stats[:teams_count]} squadre)", admin_clubs_path %></p>
<p class="kpi-sub" style="margin-top:0.75rem"><%= link_to t("admin.dashboard.teams.view_all", count: @stats[:teams_count]), admin_clubs_path %></p>
<% end %>
</div>
</section>
+22 -16
View File
@@ -2,30 +2,30 @@
<section class="kpi-grid">
<div class="kpi-card <%= @summary[:open_critical].positive? ? 'kpi-card--danger' : 'kpi-card--accent' %>">
<div class="kpi-label">Critici aperti</div>
<div class="kpi-label"><%= t("admin.ops.kpi.critical") %></div>
<div class="kpi-value"><%= @summary[:open_critical] %></div>
</div>
<div class="kpi-card">
<div class="kpi-label">Warning aperti</div>
<div class="kpi-label"><%= t("admin.ops.kpi.warning") %></div>
<div class="kpi-value"><%= @summary[:open_warning] %></div>
</div>
<div class="kpi-card">
<div class="kpi-label">Totale aperti</div>
<div class="kpi-label"><%= t("admin.ops.kpi.total") %></div>
<div class="kpi-value"><%= @summary[:open_total] %></div>
</div>
</section>
<div class="panel" style="margin-bottom:1.5rem">
<h2>Incidenti aperti</h2>
<h2><%= t("admin.ops.open.title") %></h2>
<% if @open_incidents.any? %>
<table class="admin-table">
<thead>
<tr>
<th>Severità</th>
<th>Tipo</th>
<th>Titolo</th>
<th>Occ.</th>
<th>Ultimo</th>
<th><%= t("admin.ops.open.table.severity") %></th>
<th><%= t("admin.ops.open.table.kind") %></th>
<th><%= t("admin.ops.open.table.title") %></th>
<th><%= t("admin.ops.open.table.occurrences") %></th>
<th><%= t("admin.ops.open.table.last_seen") %></th>
<th></th>
</tr>
</thead>
@@ -44,26 +44,32 @@
<td class="muted"><%= inc.last_seen_at&.strftime("%d/%m %H:%M") %></td>
<td class="admin-actions">
<% unless inc.status == 'acknowledged' %>
<%= button_to "Preso in carico", acknowledge_admin_op_path(inc), method: :post, class: "admin-btn admin-btn--sm" %>
<%= button_to t("admin.ops.actions.acknowledge"), acknowledge_admin_op_path(inc), method: :post, class: "admin-btn admin-btn--sm" %>
<% end %>
<%= button_to "Risolvi", resolve_admin_op_path(inc), method: :post, class: "admin-btn admin-btn--sm" %>
<%= button_to "Mute 24h", mute_admin_op_path(inc), method: :post, class: "admin-btn admin-btn--sm admin-btn--outline" %>
<%= button_to t("admin.ops.actions.resolve"), resolve_admin_op_path(inc), method: :post, class: "admin-btn admin-btn--sm" %>
<%= button_to t("admin.ops.actions.mute"), mute_admin_op_path(inc), method: :post, class: "admin-btn admin-btn--sm admin-btn--outline" %>
</td>
</tr>
<% end %>
</tbody>
</table>
<% else %>
<p class="empty">Nessun incidente aperto. La piattaforma risulta sana.</p>
<p class="empty"><%= t("admin.ops.open.none") %></p>
<% end %>
</div>
<div class="panel">
<h2>Risolti (ultimi 30 giorni)</h2>
<h2><%= t("admin.ops.resolved.title") %></h2>
<% if @resolved_incidents.any? %>
<table class="admin-table">
<thead>
<tr><th>Severità</th><th>Tipo</th><th>Titolo</th><th>Occ.</th><th>Risolto</th></tr>
<tr>
<th><%= t("admin.ops.open.table.severity") %></th>
<th><%= t("admin.ops.open.table.kind") %></th>
<th><%= t("admin.ops.open.table.title") %></th>
<th><%= t("admin.ops.open.table.occurrences") %></th>
<th><%= t("admin.ops.resolved.table.resolved_at") %></th>
</tr>
</thead>
<tbody>
<% @resolved_incidents.each do |inc| %>
@@ -78,6 +84,6 @@
</tbody>
</table>
<% else %>
<p class="empty">Nessun incidente risolto di recente.</p>
<p class="empty"><%= t("admin.ops.resolved.none") %></p>
<% end %>
</div>
@@ -1,29 +1,29 @@
<div style="max-width:400px">
<h2>Cambia password</h2>
<h2><%= t("admin.passwords.edit.title") %></h2>
<% if flash[:alert] %>
<p style="color:#ff6b6b"><%= flash[:alert] %></p>
<% end %>
<%= form_with url: admin_password_path, method: :patch, local: true do %>
<p>
<label for="current_password">Password attuale</label><br>
<label for="current_password"><%= t("admin.passwords.edit.current_password_label") %></label><br>
<input type="password" name="current_password" id="current_password" required autocomplete="current-password"
style="width:100%;padding:0.5rem;margin-top:0.25rem;background:#1E1E1E;border:1px solid #333;color:#fff;border-radius:6px">
</p>
<p>
<label for="password">Nuova password (min. 8 caratteri)</label><br>
<label for="password"><%= t("admin.passwords.edit.new_password_label") %></label><br>
<input type="password" name="password" id="password" required autocomplete="new-password"
style="width:100%;padding:0.5rem;margin-top:0.25rem;background:#1E1E1E;border:1px solid #333;color:#fff;border-radius:6px">
</p>
<p>
<label for="password_confirmation">Conferma nuova password</label><br>
<label for="password_confirmation"><%= t("admin.passwords.edit.confirm_password_label") %></label><br>
<input type="password" name="password_confirmation" id="password_confirmation" required autocomplete="new-password"
style="width:100%;padding:0.5rem;margin-top:0.25rem;background:#1E1E1E;border:1px solid #333;color:#fff;border-radius:6px">
</p>
<p>
<button type="submit" style="background:#FF2D2D;color:#fff;border:0;padding:0.6rem 1.2rem;border-radius:6px;cursor:pointer;font-weight:700">
Salva password
<%= t("admin.passwords.edit.submit") %>
</button>
<%= link_to "Annulla", admin_root_path %>
<%= link_to t("admin.passwords.edit.cancel"), admin_root_path %>
</p>
<% end %>
</div>
@@ -1,6 +1,6 @@
<% watch_links = admin_session_watch_links(session) %>
<section class="admin-links-panel">
<h3>Link diretta</h3>
<h3><%= t("admin.sessions.links.watch_title") %></h3>
<% if watch_links.any? %>
<ul class="admin-link-list">
<% watch_links.each do |link| %>
@@ -11,28 +11,28 @@
<% end %>
</ul>
<% else %>
<p class="muted">Nessun link video disponibile per questa sessione.</p>
<p class="muted"><%= t("admin.sessions.links.none") %></p>
<% end %>
<h3>Link regia</h3>
<h3><%= t("admin.sessions.links.regia_title") %></h3>
<% if session.terminal? %>
<p class="muted">Sessione terminata — link regia non disponibile.</p>
<p class="muted"><%= t("admin.sessions.links.terminated") %></p>
<% else %>
<% if flash[:regia_url].present? %>
<p class="admin-link-generated">
<span class="admin-link-label">Regia</span>
<span class="admin-link-label"><%= t("admin.sessions.links.regia_label") %></span>
<%= link_to flash[:regia_url], flash[:regia_url], target: "_blank", rel: "noopener", class: "admin-link-url" %>
</p>
<p><code class="admin-link-code"><%= flash[:regia_url] %></code></p>
<% if (expires = admin_regia_expires_label(flash[:regia_expires_at])) %>
<p class="muted">Valido fino a <%= expires %></p>
<p class="muted"><%= t("admin.sessions.links.valid_until", date: expires) %></p>
<% end %>
<% elsif session.regia_token_active? %>
<p class="muted">Esiste già un link regia attivo (lURL non è recuperabile). Generane uno nuovo se serve condividerlo di nuovo.</p>
<p class="muted"><%= t("admin.sessions.links.active_exists") %></p>
<% else %>
<p class="muted">Nessun link regia attivo. Genera un link da condividere con chi gestisce il punteggio.</p>
<p class="muted"><%= t("admin.sessions.links.none_active") %></p>
<% end %>
<%= button_to "Genera link regia",
<%= button_to t("admin.sessions.links.generate_button"),
regia_link_admin_session_path(session),
method: :post,
class: "admin-btn admin-btn--secondary admin-btn--sm",
@@ -1,6 +1,14 @@
<h2>Stream Sessions</h2>
<h2><%= t("admin.sessions.index.title") %></h2>
<table class="admin-table">
<thead><tr><th>Match</th><th>Status</th><th>Disconnects</th><th>Link</th><th></th></tr></thead>
<thead>
<tr>
<th><%= t("admin.sessions.index.table.match") %></th>
<th><%= t("admin.sessions.index.table.status") %></th>
<th><%= t("admin.sessions.index.table.disconnects") %></th>
<th><%= t("admin.sessions.index.table.link") %></th>
<th></th>
</tr>
</thead>
<tbody>
<% @sessions.each do |s| %>
<tr>
@@ -11,9 +19,9 @@
<% admin_session_watch_links(s).each do |link| %>
<%= link_to link[:label], link[:url], target: "_blank", rel: "noopener", class: "admin-link-chip" %>
<% end %>
<%= link_to "Regia", admin_session_path(s), class: "admin-link-chip admin-link-chip--regia" %>
<%= link_to t("admin.sessions.index.regia"), admin_session_path(s), class: "admin-link-chip admin-link-chip--regia" %>
</td>
<td><%= link_to "Dettaglio", admin_session_path(s) %></td>
<td><%= link_to t("admin.sessions.index.detail"), admin_session_path(s) %></td>
</tr>
<% end %>
</tbody>
+15 -9
View File
@@ -1,26 +1,32 @@
<h2>Session <%= @session.id %></h2>
<p>Status: <strong><%= @session.status %></strong></p>
<h2><%= t("admin.sessions.show.title", id: @session.id) %></h2>
<p><%= t("admin.sessions.show.status_label") %> <strong><%= @session.status %></strong></p>
<% unless @session.terminal? %>
<p>
<%= button_to "Termina sessione",
<%= button_to t("admin.sessions.show.stop_button"),
stop_admin_session_path(@session),
method: :post,
class: "admin-btn admin-btn--danger",
form: { data: { turbo_confirm: "Terminare la trasmissione? Il path RTMP verrà rimosso e lo stato passerà a ended." } } %>
form: { data: { turbo_confirm: t("admin.sessions.show.stop_confirm") } } %>
</p>
<% end %>
<p>Match: <%= @session.match.team.name %> vs <%= @session.match.opponent_name %></p>
<p><%= t("admin.sessions.show.match_label", team: @session.match.team.name, opponent: @session.match.opponent_name) %></p>
<%= render "admin/sessions/links", session: @session %>
<% if @session.youtube_broadcast_id %>
<p>YouTube Studio: <a href="https://studio.youtube.com/video/<%= @session.youtube_broadcast_id %>/livestreaming" target="_blank" rel="noopener">Broadcast</a></p>
<p><%= t("admin.sessions.show.youtube_studio") %>: <a href="https://studio.youtube.com/video/<%= @session.youtube_broadcast_id %>/livestreaming" target="_blank" rel="noopener"><%= t("admin.sessions.show.broadcast") %></a></p>
<% end %>
<p>RTMP ingest: <code><%= @session.rtmp_ingest_url %></code></p>
<p><%= t("admin.sessions.show.rtmp_ingest") %> <code><%= @session.rtmp_ingest_url %></code></p>
<h3>Eventi</h3>
<h3><%= t("admin.sessions.show.events_title") %></h3>
<table>
<thead><tr><th>Tipo</th><th>Quando</th><th>Meta</th></tr></thead>
<thead>
<tr>
<th><%= t("admin.sessions.show.table.type") %></th>
<th><%= t("admin.sessions.show.table.when") %></th>
<th><%= t("admin.sessions.show.table.meta") %></th>
</tr>
</thead>
<tbody>
<% @events.each do |e| %>
<tr>
+13 -11
View File
@@ -1,34 +1,36 @@
<% if @team.club %>
<p style="margin-bottom:16px"><%= link_to "← #{@team.club.name}", admin_club_path(@team.club) %></p>
<p style="margin-bottom:16px"><%= link_to t("admin.teams.show.back", club: @team.club.name), admin_club_path(@team.club) %></p>
<% end %>
<h2><%= @team.name %></h2>
<p>Sport: <%= @team.sport %></p>
<p><%= t("admin.teams.show.sport_label", sport: @team.sport) %></p>
<% if @team.club %>
<p>Società: <%= @team.club.name %> · <%= link_to "Replay squadra", admin_club_recordings_path(@team.club, team_id: @team.id) %> · <%= link_to "Pagamenti e fatture", admin_billing_path(club_id: @team.club.id) %></p>
<p><%= t("admin.teams.show.club_label_html",
club: @team.club.name,
replay_link: link_to(t("admin.teams.show.replay_link"), admin_club_recordings_path(@team.club, team_id: @team.id)),
billing_link: link_to(t("admin.teams.show.billing_link"), admin_billing_path(club_id: @team.club.id))) %></p>
<% end %>
<% yt = Youtube::TeamStatus.new(@team) %>
<p>
YouTube:
<%= t("admin.teams.show.youtube_label") %>
<% if yt.selectable? %>
<strong><%= yt.channel_title || "—" %></strong>
<strong><%= yt.channel_title || t("admin.common.dash") %></strong>
<% if yt.uses_platform_channel? %>
(canale piattaforma<%= @team.entitlements.premium_full? && @team.club.youtube_credential.blank? ? ", default senza OAuth società" : "" %>)
<%= t("admin.teams.show.youtube_platform_channel", suffix: (@team.entitlements.premium_full? && @team.club.youtube_credential.blank? ? t("admin.teams.show.youtube_platform_default_no_oauth") : "")) %>
<% else %>
(canale società)
<%= t("admin.teams.show.youtube_club_channel") %>
<% end %>
<% else %>
non pronto
<%= t("admin.teams.show.youtube_not_ready") %>
<% end %>
</p>
<% if @team.entitlements.premium_full? && @team.club.youtube_credential.blank? %>
<p class="muted">
Senza canale collegato, lapp usa il canale Match Live TV.
<%= link_to "Collega canale (pagina società)", public_club_path(@team.club) %>.
<%= t("admin.teams.show.no_channel_hint_html", link: link_to(t("admin.teams.show.link_channel"), public_club_path(@team.club))) %>
</p>
<% end %>
<h3>Partite</h3>
<h3><%= t("admin.teams.show.matches_title") %></h3>
<ul>
<% @matches.each do |m| %>
<li><%= m.opponent_name %><%= m.scheduled_at %></li>
@@ -1,10 +1,10 @@
<!DOCTYPE html>
<html lang="it">
<html lang="<%= I18n.locale %>">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex, nofollow">
<title>Token YouTube — Match Live TV</title>
<title><%= t("admin.youtube.platform_token.page_title") %></title>
<style>
body { font-family: system-ui, sans-serif; background: #0a0a0e; color: #f0f0f5; margin: 0; padding: 32px 20px; line-height: 1.5; }
.wrap { max-width: 720px; margin: 0 auto; }
@@ -17,27 +17,24 @@
</head>
<body>
<div class="wrap">
<h1>Token canale Match Live TV</h1>
<h1><%= t("admin.youtube.platform_token.title") %></h1>
<% if @channel_title.present? %>
<p>Canale: <strong><%= @channel_title %></strong></p>
<p><%= t("admin.youtube.platform_token.channel_label", title: @channel_title) %></p>
<% end %>
<% if @refresh_token.present? %>
<p class="ok">Collegamento riuscito. Copia subito questa riga:</p>
<p>Aggiungi in <code>/opt/matchlivetv/infra/.env</code>:</p>
<p class="ok"><%= t("admin.youtube.platform_token.success") %></p>
<p><%= t("admin.youtube.platform_token.env_hint_html") %></p>
<pre id="yt-token">YOUTUBE_PLATFORM_REFRESH_TOKEN=<%= @refresh_token %></pre>
<p class="muted">Non verrà mostrato di nuovo. Poi: <code>docker compose -f docker-compose.prod.yml restart rails sidekiq</code></p>
<p class="muted"><%= t("admin.youtube.platform_token.not_shown_again_html") %></p>
<% else %>
<p class="muted">
Google non ha restituito un refresh token. In
<a href="https://myaccount.google.com/permissions" target="_blank" rel="noopener">account Google → accesso app</a>
revoca «Match Live TV», poi ripeti da
<a href="/admin/youtube/platform">/admin/youtube/platform</a>.
<%= t("admin.youtube.platform_token.no_refresh_token_html") %>
</p>
<% end %>
<p><a href="/admin/">← Dashboard admin</a></p>
<p><a href="/admin/"><%= t("admin.youtube.platform_token.back_dashboard") %></a></p>
</div>
</body>
</html>
@@ -1,8 +1,11 @@
<p>Buongiorno,</p>
<p><%= t("mailers.invoice.hello") %></p>
<p>in allegato trovi la fattura <strong><%= @invoice.display_number %></strong> del <%= l(@invoice.issued_on, locale: :it, format: :long) %>
per un importo di <strong><%= @invoice.formatted_amount %></strong>, relativa alla società <strong><%= @club.billing_legal_name.presence || @club.name %></strong>.</p>
<p><%= raw t("mailers.invoice.body_html",
number: @invoice.display_number,
date: l(@invoice.issued_on, format: :long),
amount: @invoice.formatted_amount,
club: @club.billing_legal_name.presence || @club.name) %></p>
<p>Puoi scaricarla anche dalla sezione <strong>Abbonamento</strong> del tuo account Match Live TV.</p>
<p><%= raw t("mailers.invoice.also_html") %></p>
<p>Grazie,<br>Match Live TV</p>
<p><%= t("mailers.invoice.thanks") %><br><%= t("mailers.invoice.signoff") %></p>
@@ -1,9 +1,12 @@
Buongiorno,
<%= t("mailers.invoice.hello") %>
in allegato la fattura <%= @invoice.display_number %> del <%= l(@invoice.issued_on, locale: :it, format: :long) %>
(<%= @invoice.formatted_amount %>) per <%= @club.billing_legal_name.presence || @club.name %>.
<%= t("mailers.invoice.body_text",
number: @invoice.display_number,
date: l(@invoice.issued_on, format: :long),
amount: @invoice.formatted_amount,
club: @club.billing_legal_name.presence || @club.name) %>
È disponibile anche in Abbonamento sul sito Match Live TV.
<%= t("mailers.invoice.also_text") %>
Grazie,
Match Live TV
<%= t("mailers.invoice.thanks") %>
<%= t("mailers.invoice.signoff") %>
+11 -10
View File
@@ -1,7 +1,7 @@
<!DOCTYPE html>
<html>
<html lang="<%= current_locale %>">
<head>
<title>Match Live TV Admin</title>
<title><%= t("admin.layout.title") %></title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex, nofollow">
<link rel="stylesheet" href="/admin.css?v=2">
@@ -20,18 +20,19 @@
<h1>MATCH <span>LIVE</span> TV — Admin</h1>
<nav class="admin-nav">
<% if admin_logged_in? %>
<%= link_to "Dashboard", admin_root_path, class: ("active" if controller_name == "dashboard") %>
<%= link_to t("admin.layout.nav.dashboard"), admin_root_path, class: ("active" if controller_name == "dashboard") %>
<% ops_critical = Ops::Incident.critical_open.count %>
<%= link_to admin_ops_path, class: ("active" if controller_name == "ops") do %>
Ops<% if ops_critical.positive? %> <span class="admin-nav-badge"><%= ops_critical %></span><% end %>
<%= t("admin.layout.nav.ops") %><% if ops_critical.positive? %> <span class="admin-nav-badge"><%= ops_critical %></span><% end %>
<% end %>
<%= link_to "Società e squadre", admin_clubs_path, class: ("active" if controller_name.in?(%w[clubs teams club_recordings])) %>
<%= link_to "Fatturazione", admin_billing_path, class: ("active" if controller_name.in?(%w[billing billing_invoices])) %>
<%= link_to "YouTube", admin_youtube_platform_path, class: ("active" if controller_name == "youtube") %>
<%= link_to "Sessions", admin_sessions_path, class: ("active" if controller_name == "sessions") %>
<%= link_to "Password", edit_admin_password_path %>
<%= button_to "Esci", admin_logout_path, method: :delete %>
<%= link_to t("admin.layout.nav.clubs"), admin_clubs_path, class: ("active" if controller_name.in?(%w[clubs teams club_recordings])) %>
<%= link_to t("admin.layout.nav.billing"), admin_billing_path, class: ("active" if controller_name.in?(%w[billing billing_invoices])) %>
<%= link_to t("admin.layout.nav.youtube"), admin_youtube_platform_path, class: ("active" if controller_name == "youtube") %>
<%= link_to t("admin.layout.nav.sessions"), admin_sessions_path, class: ("active" if controller_name == "sessions") %>
<%= link_to t("admin.layout.nav.password"), edit_admin_password_path %>
<%= button_to t("admin.layout.nav.logout"), admin_logout_path, method: :delete %>
<% end %>
<%= render "shared/language_switcher", locale_path: admin_locale_path %>
</nav>
</header>
+1 -1
View File
@@ -10,7 +10,7 @@
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css" integrity="sha512-SnH5WK+bZxgPHs44uWIX+LLJAJ9/2PkPKZ5QiAj6Ta86w+fsb2TkcmfRyVX3pBnMFcV7oQPJkl9QevSCWr3W6A==" crossorigin="anonymous" referrerpolicy="no-referrer">
<link rel="stylesheet" href="/marketing.css?v=44">
</head>
<body<% if MatchLiveTv.google_analytics_configured? %> data-ga-id="<%= MatchLiveTv.google_analytics_measurement_id %>"<% end %>>
<body data-confirm-i18n='<%= raw confirm_dialog_i18n_json %>'<% if MatchLiveTv.google_analytics_configured? %> data-ga-id="<%= MatchLiveTv.google_analytics_measurement_id %>"<% end %>>
<%= render "shared/cookie_banner" %>
<%= render "shared/marketing_nav" %>
<main>
@@ -10,7 +10,7 @@
<link rel="stylesheet" href="/live.css?v=26">
<%= yield :head %>
</head>
<body<% if MatchLiveTv.google_analytics_configured? %> data-ga-id="<%= MatchLiveTv.google_analytics_measurement_id %>"<% end %>>
<body data-confirm-i18n='<%= raw confirm_dialog_i18n_json %>'<% if MatchLiveTv.google_analytics_configured? %> data-ga-id="<%= MatchLiveTv.google_analytics_measurement_id %>"<% end %>>
<%= render "shared/cookie_banner" %>
<%= render "shared/marketing_nav" %>
<main class="live-main">
+7 -7
View File
@@ -1,5 +1,5 @@
<!DOCTYPE html>
<html lang="it">
<html lang="<%= html_lang %>">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
@@ -34,17 +34,17 @@
<div class="wrap">
<%= link_to public_pricing_path, class: "brand" do %>Match <span>Live TV</span><% end %>
<nav>
<%= link_to "Prezzi", public_pricing_path %>
<%= link_to t("nav.pricing"), public_pricing_path %>
<% if logged_in? %>
<% if current_user.primary_club %>
· <%= link_to "La mia società", public_club_path(current_user.primary_club) %>
· <%= link_to t("nav.my_club"), public_club_path(current_user.primary_club) %>
<% elsif current_user.manageable_teams.any? %>
· <%= link_to "La mia squadra", public_team_details_path(current_user.manageable_teams.first) %>
· <%= link_to t("nav.my_team"), public_team_details_path(current_user.manageable_teams.first) %>
<% end %>
· <%= button_to "Esci", public_logout_path, method: :delete, form: { style: "display:inline" }, class: "btn btn-secondary", style: "padding:6px 12px;font-size:0.85rem" %>
· <%= button_to t("nav.logout"), public_logout_path, method: :delete, form: { style: "display:inline" }, class: "btn btn-secondary", style: "padding:6px 12px;font-size:0.85rem" %>
<% else %>
· <%= link_to "Accedi", public_login_path %>
· <%= link_to "Registrati", public_signup_path, class: "btn btn-primary", style: "padding:6px 12px;font-size:0.85rem" %>
· <%= link_to t("nav.login"), public_login_path %>
· <%= link_to t("nav.signup"), public_signup_path, class: "btn btn-primary", style: "padding:6px 12px;font-size:0.85rem" %>
<% end %>
</nav>
</div>
+2 -2
View File
@@ -1,11 +1,11 @@
<!DOCTYPE html>
<html lang="it">
<html lang="<%= html_lang %>">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="theme-color" content="#0a0a0e">
<title><%= content_for?(:title) ? yield(:title) : "Regia — Match Live TV" %></title>
<title><%= content_for?(:title) ? yield(:title) : t("regia.meta_title_fallback") %></title>
<%= render "shared/meta_tags" %>
<link rel="stylesheet" href="/regia.css?v=1">
<%= yield :head %>
@@ -1,17 +1,17 @@
<% content_for :title, "Dati di fatturazione — #{@club.name}" %>
<% content_for :title, t("billing.profile.title", name: @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 lintestazione delle fatture e linvio tramite SDI o PEC.</p>
<h1><%= t("billing.profile.heading") %></h1>
<p style="color:#aaa"><%= t("billing.profile.lead", name: @club.name) %></p>
<% plan_return = params[:plan].presence_in(%w[premium_light premium_full]) %>
<% unless @club.billing_profile_complete? %>
<div class="flash alert" style="margin:16px 0">
<strong>Obbligatori per abbonarti a un piano premium.</strong>
<strong><%= t("billing.profile.required_notice") %></strong>
<%= billing_profile_incomplete_message(@club) %>
<% if plan_return %>
Dopo il salvataggio potrai procedere al pagamento di <strong><%= Plan[plan_return].name %></strong>.
<%= raw t("billing.profile.after_save_hint_html", plan: Plan[plan_return].name) %>
<% end %>
</div>
<% end %>
@@ -21,11 +21,11 @@
<%= hidden_field_tag :plan, plan_return if plan_return %>
<%= hidden_field_tag :interval, params[:interval] if params[:interval].present? %>
<%= render "shared/billing_profile_fields", record: @club %>
<%= submit_tag "Salva dati di fatturazione", class: "btn btn-primary" %>
<%= submit_tag t("billing.profile.submit"), class: "btn btn-primary" %>
<% end %>
</div>
<p style="margin-top:20px">
<%= link_to "← Abbonamento", public_club_billing_path(@club) %>
<%= link_to t("billing.profile.back_to_billing"), public_club_billing_path(@club) %>
</p>
</div>
@@ -1,23 +1,23 @@
<% content_for :title, "Programma partita — #{@club.name}" %>
<% content_for :title, t("matches.club_new.title", name: @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" %>
<%= link_to t("matches.club_new.back_link"), public_live_index_path(club_id: @club.id), class: "back-link" %>
</p>
<h1>Programma partita</h1>
<h1><%= t("matches.club_new.heading") %></h1>
<p style="color:#888">
<%= @club.name %>
<% if @club_admin %>
· Puoi scegliere qualsiasi squadra della società
<%= t("matches.club_new.admin_hint") %>
<% else %>
· Solo le squadre in cui sei responsabile trasmissione
<%= t("matches.club_new.staff_hint") %>
<% 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" %>
<%= label_tag :team_id, t("matches.club_new.team_label") %>
<% 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>
@@ -29,7 +29,7 @@
<%= render "public/matches/schedule_fields", match: @match %>
<%= submit_tag "Salva in programma", class: "btn btn-primary", style: "margin-top:20px" %>
<%= submit_tag t("matches.club_new.submit"), class: "btn btn-primary", style: "margin-top:20px" %>
<% end %>
</div>
</div>
@@ -1,4 +1,4 @@
<% content_for :title, "Replay#{@club.name}" %>
<% content_for :title, t("recordings.archive.heading") + " — #{@club.name}" %>
<% content_for :robots, "noindex, nofollow" %>
<div class="wrap club-dashboard" style="padding-top:20px">
@@ -3,50 +3,46 @@
<% cred = club.youtube_credential %>
<section class="card club-youtube" id="youtube">
<h2>YouTube Live</h2>
<h2><%= t("club.youtube.heading") %></h2>
<% if params[:youtube] == "connected" %>
<p class="notice" style="color:#2e7d32;margin-bottom:12px">Canale YouTube della società collegato con successo.</p>
<p class="notice" style="color:#2e7d32;margin-bottom:12px"><%= t("club.youtube.connected_notice") %></p>
<% end %>
<% if entitlements.plan.youtube_mode == "matchlivetv_light" %>
<p>
Con il piano <strong>Premium Light</strong> le dirette YouTube di tutte le squadre vanno sul canale ufficiale
<strong>Match Live TV</strong>. Non serve collegare un canale della società.
<%= raw t("club.youtube.light_body_html") %>
</p>
<% if yt&.selectable? %>
<p class="muted">Canale pronto · seleziona «YouTube Live» nellapp mobile.</p>
<p class="muted"><%= t("club.youtube.light_ready") %></p>
<% else %>
<p class="muted">Canale Match Live TV in configurazione lato server. Contatta il supporto se YouTube non compare in app.</p>
<p class="muted"><%= t("club.youtube.light_configuring") %></p>
<% end %>
<% elsif entitlements.premium_full? %>
<% if cred %>
<p>
Canale collegato per <strong>tutta la società</strong>:
<strong><%= cred.channel_title.presence || cred.channel_id || "YouTube" %></strong>
<%= raw t("club.youtube.full_connected_html", channel: cred.channel_title.presence || cred.channel_id || "YouTube") %>
</p>
<p class="muted">Tutte le squadre possono trasmettere su questo canale quando scelgono il canale società in app.</p>
<%= button_to "Scollega canale", public_club_youtube_disconnect_path(club),
<p class="muted"><%= t("club.youtube.full_connected_hint") %></p>
<%= button_to t("club.youtube.disconnect"), public_club_youtube_disconnect_path(club),
method: :delete,
class: "btn btn-secondary",
form: { data: { turbo_confirm: "Scollegare il canale YouTube? Le dirette useranno il canale Match Live TV finché non ricolleghi il tuo." } } %>
form: { data: { turbo_confirm: t("club.youtube.disconnect_confirm"), confirm_kind: "delete" } } %>
<% else %>
<p>
Senza canale collegato, le dirette YouTube dallapp vanno sul canale ufficiale
<strong>Match Live TV</strong>.
Collega qui il canale YouTube della società (vale per tutte le squadre).
<%= raw t("club.youtube.full_not_connected_html") %>
</p>
<% if yt&.selectable? %>
<p class="muted">YouTube pronto in app (canale Match Live TV finché non colleghi il tuo).</p>
<p class="muted"><%= t("club.youtube.ready_in_app") %></p>
<% end %>
<% if ENV["YOUTUBE_CLIENT_ID"].present? %>
<%= link_to "Collega il canale YouTube della società", public_club_youtube_connect_path(club), class: "btn btn-primary" %>
<%= link_to t("club.youtube.connect"), public_club_youtube_connect_path(club), class: "btn btn-primary" %>
<% else %>
<p class="muted">OAuth YouTube non ancora configurato sul server.</p>
<p class="muted"><%= t("club.youtube.oauth_not_configured") %></p>
<% end %>
<% end %>
<% else %>
<p class="muted">YouTube sul canale della società richiede il piano Premium Full.</p>
<%= link_to "Vedi piani", public_prezzi_path, class: "btn btn-secondary" %>
<p class="muted"><%= t("club.youtube.requires_full") %></p>
<%= link_to t("club.youtube.see_plans"), public_prezzi_path, class: "btn btn-secondary" %>
<% end %>
</section>
@@ -1,20 +1,19 @@
<% content_for :title, "Abbonamento — #{@club.name}" %>
<% content_for :title, t("club.billing.title", name: @club.name) %>
<% content_for :robots, "noindex, nofollow" %>
<div class="wrap" style="padding-top:20px">
<h1>Abbonamento</h1>
<h1><%= t("billing.heading") %></h1>
<%= render "shared/club_subscription_status", club: @club, entitlements: @entitlements, subscription: @subscription, on_billing_page: true %>
<% if @subscription&.admin_comped? %>
<div class="card" style="margin-top:16px;border-color:#3d3520;background:#1a1810">
<p style="margin:0;color:#ddd">
<strong>Abbonamento omaggio Match Live TV</strong>
— piano <strong><%= @subscription.plan.name %></strong>
<strong><%= t("club.billing.comped.title") %></strong>
<%= t("club.billing.comped.plan_prefix") %> <strong><%= @subscription.plan.name %></strong>
<% if @subscription.admin_comped_reason.present? %>
(<%= @subscription.admin_comped_reason %>)
<% end %>.
Non è richiesto alcun pagamento. Per modifiche o passaggio a un piano a pagamento, contatta
<%= mail_to "info@matchlive.it", "info@matchlive.it" %>.
<%= raw t("club.billing.comped.no_payment_html", email_link: mail_to("info@matchlive.it", "info@matchlive.it")) %>
</p>
</div>
<% else %>
@@ -28,5 +27,5 @@
<%= render "shared/billing_documents", club: @club, payments: @payments %>
<p style="margin-top:24px"><%= link_to "← Società", public_club_path(@club) %></p>
<p style="margin-top:24px"><%= link_to t("club.back_to_club"), public_club_path(@club) %></p>
</div>
+7 -7
View File
@@ -1,18 +1,18 @@
<% content_for :title, "Modifica società — #{@club.name}" %>
<% content_for :title, t("club.edit.title", name: @club.name) %>
<% content_for :robots, "noindex, nofollow" %>
<div class="wrap" style="padding-top:20px;max-width:560px">
<h1>Modifica società</h1>
<h1><%= t("club.edit.heading") %></h1>
<div class="card">
<%= form_with url: public_club_path(@club), method: :patch, multipart: true do %>
<%= label_tag "club[name]", "Nome società" %>
<%= label_tag "club[name]", t("club.edit.name_label") %>
<%= text_field_tag "club[name]", @club.name, required: true %>
<%= label_tag "club[sport]", "Sport principale" %>
<%= label_tag "club[sport]", t("club.sport_label") %>
<%= select_tag "club[sport]", sport_catalog_options(@club.sport) %>
<%= render "shared/branding_fields", record: @club, legend: "Logo e colori societari" %>
<%= render "shared/branding_fields", record: @club, legend: t("club.branding_legend") %>
<%= render "shared/billing_profile_fields", record: @club %>
<%= submit_tag "Salva", class: "btn btn-primary" %>
<%= submit_tag t("club.edit.save"), class: "btn btn-primary" %>
<% end %>
</div>
<p><%= link_to "← Società", public_club_path(@club) %></p>
<p><%= link_to t("club.back_to_club"), public_club_path(@club) %></p>
</div>
+20 -20
View File
@@ -1,39 +1,39 @@
<% 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 :title, t("club.new.title") %>
<% content_for :meta_description, t("club.new.meta_description") %>
<% 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>
<h1><%= t("club.new.heading") %></h1>
<p class="auth-lead"><%= t("club.new.lead") %></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" %>
<h2 class="form-section-title"><%= t("club.new.section_club") %></h2>
<%= label_tag "club[name]", t("club.new.name_label") %>
<%= text_field_tag "club[name]", params.dig(:club, :name), required: true, placeholder: t("club.new.name_placeholder") %>
<%= label_tag "club[sport]", t("club.sport_label") %>
<%= select_tag "club[sport]", sport_catalog_options(params.dig(:club, :sport) || "pallavolo") %>
<%= render "shared/branding_fields", record: Club.new(primary_color: "#e53935", secondary_color: "#ffffff"), legend: "Logo e colori societari" %>
<%= render "shared/branding_fields", record: Club.new(primary_color: "#e53935", secondary_color: "#ffffff"), legend: t("club.branding_legend") %>
<%= 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" %>
<%= text_field_tag "first_team[name]", params.dig(:first_team, :name), required: true, placeholder: "es. Under 15" %>
<h2 class="form-section-title" style="margin-top:24px"><%= t("club.new.section_first_team") %></h2>
<%= label_tag "first_team[name]", t("club.new.first_team_name_label") %>
<%= text_field_tag "first_team[name]", params.dig(:first_team, :name), required: true, placeholder: t("club.new.first_team_name_placeholder") %>
<%= label_tag :plan, "Piano iniziale (per tutta la società)" %>
<%= label_tag :plan, t("club.new.plan_label") %>
<%= select_tag :plan, options_for_select([
["Free — 1 responsabile trasmissione per squadra, 1 live", "free"],
["Premium Light", "premium_light"],
["Premium Full", "premium_full"]
[t("club.new.plan_free"), "free"],
[t("club.new.plan_light"), "premium_light"],
[t("club.new.plan_full"), "premium_full"]
], params[:plan] || "free"), id: "club_plan_select" %>
<div id="club_plan_interval" style="margin-top:10px">
<%= label_tag :interval, "Fatturazione premium" %>
<%= label_tag :interval, t("club.new.interval_label") %>
<%= select_tag :interval, options_for_select([
["Annuale — €40/anno (Light) o €200/anno (Full)", "yearly"],
["Mensile — €5/mese (Light) o €20/mese (Full)", "monthly"]
[t("club.new.interval_yearly"), "yearly"],
[t("club.new.interval_monthly"), "monthly"]
], params[:interval] || "yearly") %>
</div>
<%= render "shared/stripe_secure_payment", compact: true %>
<%= submit_tag "Crea società", class: "btn btn-primary" %>
<%= submit_tag t("club.new.submit"), class: "btn btn-primary" %>
<% end %>
</div>
</section>
+23 -23
View File
@@ -1,4 +1,4 @@
<% content_for :title, "#{@club.name} — Società" %>
<% content_for :title, t("club.dashboard.title", name: @club.name) %>
<% content_for :robots, "noindex, nofollow" %>
<div class="wrap club-dashboard" style="padding-top:20px">
@@ -10,20 +10,20 @@
<h1><%= @club.name %></h1>
<% if @entitlements %>
<p class="club-meta">
Piano: <strong><%= @entitlements.plan.name %></strong>
· Squadre: <strong><%= @teams.size %></strong>
<%= t("club.dashboard.plan_label") %> <strong><%= @entitlements.plan.name %></strong>
· <%= t("club.dashboard.teams_count_label") %> <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(club_id: @club.id), class: "btn btn-secondary" %>
<%= link_to t("club.dashboard.edit_club"), public_edit_club_path(@club), class: "btn btn-secondary" %>
<%= link_to t("club.dashboard.subscription"), public_club_billing_path(@club), class: "btn btn-primary" %>
<%= link_to t("club.dashboard.new_team"), public_new_club_team_path(@club), class: "btn btn-secondary" %>
<%= link_to t("club.dashboard.live_streams"), public_live_index_path(club_id: @club.id), class: "btn btn-secondary" %>
<% if @entitlements&.can_access_recordings? %>
<%= link_to "Archivio Replay", public_club_recordings_path(@club), class: "btn btn-secondary" %>
<%= link_to t("club.dashboard.replay_archive"), public_club_recordings_path(@club), class: "btn btn-secondary" %>
<% end %>
</div>
@@ -33,32 +33,32 @@
entitlements_team: @entitlements_team %>
<% if @replay_stats %>
<h2>Replay</h2>
<h2><%= t("club.dashboard.replay_heading") %></h2>
<div class="card" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:16px;padding:16px;margin-bottom:24px">
<div>
<div style="font-size:1.6rem;font-weight:700"><%= @replay_stats[:available_count] %></div>
<div class="muted">disponibili</div>
<div class="muted"><%= t("club.dashboard.available") %></div>
</div>
<div>
<div style="font-size:1.6rem;font-weight:700"><%= @replay_stats[:expiring_soon_count] %></div>
<div class="muted">in scadenza (7 gg)</div>
<div class="muted"><%= t("club.dashboard.expiring_soon") %></div>
</div>
<div>
<div style="font-size:1.6rem;font-weight:700"><%= number_to_human_size(@replay_stats[:total_bytes]) %></div>
<div class="muted">spazio occupato</div>
<div class="muted"><%= t("club.dashboard.space_used") %></div>
</div>
<div>
<div style="font-size:1.6rem;font-weight:700"><%= @replay_stats[:total_views] %></div>
<div class="muted">visualizzazioni</div>
<div class="muted"><%= t("club.dashboard.views") %></div>
</div>
</div>
<% end %>
<h2>Squadre</h2>
<h2><%= t("club.dashboard.teams_heading") %></h2>
<div class="card">
<% if @teams.any? %>
<table class="data">
<thead><tr><th>Squadra</th><th>Branding</th><th></th></tr></thead>
<thead><tr><th><%= t("club.dashboard.team_col") %></th><th><%= t("club.dashboard.branding_col") %></th><th></th></tr></thead>
<tbody>
<% @teams.each do |team| %>
<tr>
@@ -68,28 +68,28 @@
</td>
<td style="color:#888;font-size:0.88rem">
<% if team.logo_url.present? || team.logo_file.attached? || team.primary_color.present? %>
Personalizzato
<%= t("club.dashboard.branding_custom") %>
<% else %>
Eredita dalla società
<%= t("club.dashboard.branding_inherited") %>
<% end %>
</td>
<td style="white-space:nowrap">
<%= link_to "Pagina pubblica", public_team_page_path(team.slug), target: "_blank", rel: "noopener" %>
<%= link_to t("club.dashboard.public_page"), public_team_page_path(team.slug), target: "_blank", rel: "noopener" %>
·
<%= link_to "Dettagli", public_team_details_path(team) %>
<%= link_to t("club.dashboard.details"), public_team_details_path(team) %>
·
<%= link_to "Partite", public_team_matches_path(team) %>
<%= link_to t("club.dashboard.matches"), public_team_matches_path(team) %>
·
<%= link_to "Modifica", public_edit_team_path(team) %>
<%= link_to t("club.dashboard.edit"), 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>
<p style="color:#888"><%= raw t("club.dashboard.no_teams_html", link: link_to(t("club.dashboard.add_first_team"), public_new_club_team_path(@club))) %></p>
<% end %>
</div>
<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). Labbonamento vale per tutta la società.</p>
<p style="color:#888;margin-top:24px"><%= t("club.dashboard.footer_note") %></p>
</div>
@@ -1,21 +1,26 @@
<% content_for :title, "Invito squadra — Match Live TV" %>
<% content_for :meta_description, "Accetta l'invito per entrare nello staff della squadra su Match Live TV." %>
<% content_for :title, t("auth.invitation.meta_title") %>
<% content_for :meta_description, t("auth.invitation.meta_description") %>
<% content_for :robots, "noindex, nofollow" %>
<div class="card" style="max-width:480px">
<h1>Accesso staff — <%= @invitation.team.name %></h1>
<p>Sei stato aggiunto come <strong>responsabile trasmissione</strong> (<%= @invitation.email %>).</p>
<h1><%= raw t("auth.invitation.title_html", team_name: @invitation.team.name) %></h1>
<p><%= raw t("auth.invitation.role_notice_html", email: @invitation.email) %></p>
<p class="muted" style="font-size:0.9rem;margin-bottom:16px">
Nellapp Match Live TV accedi con <strong><%= @invitation.email %></strong>, accetta linvito e collega il tuo canale YouTube.
<%= raw t("auth.invitation.instructions_html", email: @invitation.email) %>
</p>
<% if logged_in? %>
<%= button_to "Accetta invito", public_invitation_path(token: @token), method: :post, class: "btn btn-primary" %>
<%= button_to t("auth.invitation.accept"), public_invitation_path(token: @token), method: :post, class: "btn btn-primary" %>
<% else %>
<p><%= link_to "Accedi", public_login_path %> o <%= link_to "registrati", public_signup_path %> con <%= @invitation.email %>.</p>
<%= button_to "Accetta (se già loggato)", public_invitation_path(token: @token), method: :post, class: "btn btn-secondary" %>
<p><%= raw t(
"auth.invitation.login_or_signup_html",
login_link: link_to(t("auth.invitation.login_link"), public_login_path),
signup_link: link_to(t("auth.invitation.signup_link"), public_signup_path),
email: @invitation.email
) %></p>
<%= button_to t("auth.invitation.accept_if_logged_in"), public_invitation_path(token: @token), method: :post, class: "btn btn-secondary" %>
<% end %>
<p style="margin-top:16px;font-size:0.88rem;color:#888">
App mobile:
<a href="matchlivetv://join/<%= @token %>">Apri invito nellapp</a>
<%= t("auth.invitation.mobile_app_label") %>
<a href="matchlivetv://join/<%= @token %>"><%= t("auth.invitation.open_in_app") %></a>
</p>
</div>
@@ -5,7 +5,7 @@
badge_class = local_assigns[:badge_class]
unless badge_label
badge_label = closed ? "Terminata" : (session.paused? ? "In pausa" : (on_air ? "LIVE" : "In attesa"))
badge_label = closed ? t("live.overlays.badge_ended") : (session.paused? ? t("live.index.badge_paused") : (on_air ? t("live.index.badge_live") : t("live.overlays.badge_waiting")))
end
unless badge_class
+51 -51
View File
@@ -1,33 +1,33 @@
<% content_for :title, "Dirette live partite giovanili in corso — Match Live TV" %>
<% 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 :title, t("live.index.meta_title") %>
<% content_for :meta_description, t("live.index.meta_description") %>
<% content_for :canonical_url, seo_absolute_url(public_live_index_path) %>
<% live_index_params = @club ? { club_id: @club.id } : {} %>
<div class="wrap">
<% if @club %>
<p class="results-hint" style="margin-bottom:8px">
<%= link_to "← #{@club.name}", public_club_path(@club), class: "back-link" %>
<%= link_to t("live.index.club_back_link", name: @club.name), public_club_path(@club), class: "back-link" %>
</p>
<h1>Dirette e partite — <%= @club.name %></h1>
<h1><%= t("live.index.club_title", name: @club.name) %></h1>
<p class="results-hint">
Solo le squadre di questa società: dirette in corso e partite programmate da app o sito.
<%= t("live.index.club_hint") %>
</p>
<div class="live-actions" style="display:flex;gap:12px;flex-wrap:wrap;margin-bottom:16px">
<%= link_to "Replay di #{@club.name}", public_replay_index_path(club_id: @club.id), class: "btn btn-secondary" %>
<%= link_to t("live.index.club_replay_link", name: @club.name), public_replay_index_path(club_id: @club.id), class: "btn btn-secondary" %>
</div>
<% else %>
<h1>Dirette e partite in programma</h1>
<h1><%= t("live.index.default_title") %></h1>
<p class="results-hint">
Cerca per società, squadra, avversario o luogo: trovi le dirette attive e le partite programmate.
<%= t("live.index.default_hint") %>
</p>
<div class="live-actions" style="display:flex;gap:12px;flex-wrap:wrap;margin-bottom:20px">
<%= link_to "Live passate — archivio replay", public_replay_index_path, class: "btn btn-secondary" %>
<%= link_to t("live.index.replay_archive_link"), public_replay_index_path, class: "btn btn-secondary" %>
</div>
<% 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" %>
<%= link_to t("live.index.schedule_match_link"), public_new_club_match_path(@club), class: "btn btn-primary" %>
</div>
<% end %>
@@ -39,25 +39,25 @@
type="search"
name="q"
value="<%= @query %>"
placeholder="<%= @club ? "Cerca società, squadra, avversario o luogo…" : "Es. Crazy Volley, Serie D, avversario…" %>"
aria-label="Cerca squadra"
placeholder="<%= @club ? t("live.index.search_placeholder_club") : t("live.index.search_placeholder_default") %>"
aria-label="<%= t("live.index.search_aria_label") %>"
autocomplete="off"
/>
<button type="submit" class="btn btn-primary">Cerca</button>
<button type="submit" class="btn btn-primary"><%= t("live.index.search_button") %></button>
<% if @query.present? %>
<%= link_to "Azzera", public_live_index_path(live_index_params), class: "btn btn-secondary" %>
<%= link_to t("live.index.reset_link"), public_live_index_path(live_index_params), class: "btn btn-secondary" %>
<% end %>
<% end %>
<% if @query.present? %>
<% club_suffix = @club ? t("live.index.results_hint_club_suffix", name: @club.name) : "" %>
<p class="results-hint">
Risultati per «<%= h @query %>»<%= @club ? " in #{@club.name}" : "" %>:
<%= @sessions.size %> in diretta · <%= @upcoming_matches.size %> in programma
<%= t("live.index.results_hint", query: @query, club_suffix: club_suffix, live_count: @sessions.size, upcoming_count: @upcoming_matches.size) %>
</p>
<% end %>
<% if @sessions.any? %>
<h2 class="section-heading">In diretta adesso</h2>
<h2 class="section-heading"><%= t("live.index.section_live_now") %></h2>
<div class="live-grid">
<% @sessions.each do |session| %>
<% match = session.match %>
@@ -72,33 +72,33 @@
<p class="card-score">
<span class="card-sets"><%= live_score_sets_label(session.score_state) %></span>
<% if live_score_partials_label(session.score_state).present? %>
<span class="card-partials">Parziali: <%= live_score_partials_label(session.score_state) %></span>
<span class="card-partials"><%= t("score.partials_prefix", value: live_score_partials_label(session.score_state)) %></span>
<% end %>
<span class="card-points"><%= session.score_state.home_points %> - <%= session.score_state.away_points %></span>
</p>
<% end %>
<div class="badges">
<% if on_air %>
<span class="badge badge-on-air">In onda</span>
<span class="badge badge-on-air"><%= t("live.index.badge_on_air") %></span>
<% elsif session.status == "live" %>
<span class="badge badge-live">Live</span>
<span class="badge badge-live"><%= t("live.index.badge_live") %></span>
<% elsif session.status == "paused" %>
<span class="badge badge-connecting">In pausa</span>
<span class="badge badge-connecting"><%= t("live.index.badge_paused") %></span>
<% elsif session.status == "reconnecting" %>
<span class="badge badge-connecting">Riconnessione</span>
<span class="badge badge-connecting"><%= t("live.index.badge_reconnecting") %></span>
<% else %>
<span class="badge badge-wait">In avvio</span>
<span class="badge badge-wait"><%= t("live.index.badge_starting") %></span>
<% end %>
</div>
<%= link_to "Guarda diretta →", public_live_path(session), class: "btn-watch" %>
<%= link_to t("live.index.watch_link"), public_live_path(session), class: "btn-watch" %>
</article>
<% end %>
</div>
<% end %>
<% if @upcoming_matches.any? %>
<h2 class="section-heading<%= ' section-heading--spaced' if @sessions.any? %>">Prossime partite</h2>
<p class="results-hint">Programmate da app o sito: la diretta partirà quando lo staff avvierà la trasmissione.</p>
<h2 class="section-heading<%= ' section-heading--spaced' if @sessions.any? %>"><%= t("live.index.section_upcoming") %></h2>
<p class="results-hint"><%= t("live.index.upcoming_hint") %></p>
<div class="live-grid upcoming-grid">
<% @upcoming_matches.each do |match| %>
<article class="live-card live-card--upcoming">
@@ -111,7 +111,7 @@
<%= live_scheduled_relative(match.scheduled_at) %>
</p>
<div class="badges">
<span class="badge badge-scheduled">In programma</span>
<span class="badge badge-scheduled"><%= t("live.index.badge_scheduled") %></span>
</div>
</article>
<% end %>
@@ -121,59 +121,59 @@
<% if @sessions.empty? %>
<% if @upcoming_matches.any? %>
<div class="empty-state empty-state--soft">
<p><strong>Nessuna diretta in questo momento.</strong></p>
<p>Torna allorario indicato: quando la squadra avvierà lo streaming, la partita comparirà in «In diretta adesso».</p>
<p><strong><%= t("live.index.empty_soft_title") %></strong></p>
<p><%= t("live.index.empty_soft_body") %></p>
</div>
<% elsif @club %>
<div class="empty-state">
<p><strong>Nessuna diretta o partita in programma per <%= @club.name %>.</strong></p>
<p><strong><%= t("live.index.empty_club_title", name: @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 dallapp Match Live TV.
<%= link_to t("live.index.empty_club_schedule_link"), public_new_club_match_path(@club), class: "btn btn-primary" %>
<%= t("live.index.empty_club_schedule_suffix") %>
<% elsif @club.teams.any? %>
Chiedi al titolare della società o a un responsabile trasmissione di programmare una gara.
<%= t("live.index.empty_club_ask_staff") %>
<% else %>
Aggiungi una squadra dalla
<%= link_to "pagina società", public_club_path(@club) %>
e programma la prima partita.
<%= raw t(
"live.index.empty_club_add_team_html",
club_link: link_to(t("live.index.empty_club_page_link"), public_club_path(@club))
) %>
<% end %>
</p>
<p style="margin-top:16px">
<%= link_to "← Torna alla società", public_club_path(@club), class: "btn btn-secondary" %>
<%= link_to t("live.index.empty_club_back_link"), public_club_path(@club), class: "btn btn-secondary" %>
</p>
</div>
<% else %>
<div class="empty-hero">
<div class="empty-hero-copy">
<h2>Nessuna diretta adesso — ma puoi già organizzarti</h2>
<h2><%= t("live.index.empty_hero_title") %></h2>
<p>
Se la tua squadra usa Match Live TV, dal telefono si programma la partita con data e ora.
Genitori e parenti trovano qui la diretta quando il fischio dinizio è vicino.
<%= t("live.index.empty_hero_body") %>
</p>
<ul class="empty-hero-list">
<li>Programma la gara dallapp (senza avviare subito la diretta)</li>
<li>La partita compare in «Prossime partite» con orario e campo</li>
<li>Lo streaming parte solo quando premi avvio in palestra</li>
<li><%= t("live.index.empty_hero_list_item1") %></li>
<li><%= t("live.index.empty_hero_list_item2") %></li>
<li><%= t("live.index.empty_hero_list_item3") %></li>
</ul>
<p class="empty-hero-cta">
<%= link_to "Registra la tua squadra", public_signup_path, class: "btn btn-primary" %>
<%= link_to "Scopri come funziona", public_features_path, class: "btn btn-secondary" %>
<%= link_to t("live.index.empty_hero_cta_signup"), public_signup_path, class: "btn btn-primary" %>
<%= link_to t("live.index.empty_hero_cta_features"), public_features_path, class: "btn btn-secondary" %>
</p>
</div>
<aside class="demo-live-card" aria-label="Esempio di diretta attiva">
<p class="demo-label">Esempio — così appare una diretta attiva</p>
<aside class="demo-live-card" aria-label="<%= t("live.index.demo_aria_label") %>">
<p class="demo-label"><%= t("live.index.demo_label") %></p>
<article class="live-card live-card--demo">
<h3>Tigers Volley vs ASD Eagles</h3>
<p class="meta">PalaTigers · Match Live TV</p>
<p class="meta"><%= t("live.index.demo_meta") %></p>
<p class="card-score">
<span class="card-sets">Set 2 · Set vinti 1-0</span>
<span class="card-sets"><%= t("live.index.demo_sets") %></span>
<span class="card-points">18 - 16</span>
</p>
<div class="badges">
<span class="badge badge-on-air">In onda</span>
<span class="badge badge-on-air"><%= t("live.index.badge_on_air") %></span>
</div>
<span class="btn-watch btn-watch--demo">Guarda diretta →</span>
<span class="btn-watch btn-watch--demo"><%= t("live.index.watch_link") %></span>
</article>
</aside>
</div>
+32 -26
View File
@@ -1,6 +1,12 @@
<% 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 :title do %><%= t("live.show.title", club_name: club_name, team_name: @match.team.name, opponent_name: @match.opponent_name) %><% end %>
<% content_for :meta_description do %><%= t(
"live.show.meta_description",
club_name: club_name,
team_name: @match.team.name,
opponent_name: @match.opponent_name,
location: @match.location.present? ? t("live.show.meta_description_location", location: @match.location) : ""
) %><% end %>
<% content_for :robots, @session.publicly_listed? ? "index, follow" : "noindex, nofollow" %>
<% content_for :head do %>
@@ -10,45 +16,45 @@
<% end %>
<div class="wrap">
<%= link_to "← Tutte le dirette", public_live_index_path, class: "back-link" %>
<%= link_to t("live.show.back_to_all"), public_live_index_path, class: "back-link" %>
<%= live_match_page_heading(@match) %>
<% if @stream_closed %>
<div id="stream-ended" class="stream-ended" role="status" aria-live="polite">
<div class="stream-ended-icon" aria-hidden="true">📡</div>
<h2>Diretta terminata</h2>
<p>Lo streaming di questa partita è stato chiuso.</p>
<h2><%= t("live.show.stream_ended_title") %></h2>
<p><%= t("live.show.stream_ended_body") %></p>
<% if @session.ended_at %>
<p class="stream-ended-meta">Chiusa il <%= l_local(@session.ended_at) %></p>
<p class="stream-ended-meta"><%= t("live.show.stream_ended_closed_at", date: l_local(@session.ended_at)) %></p>
<% end %>
<p class="stream-ended-hint">Il punteggio è visibile nel video se la diretta era ancora in corso.</p>
<%= link_to "Tutte le dirette", public_live_index_path, class: "btn btn-secondary stream-ended-btn" %>
<p class="stream-ended-hint"><%= t("live.show.stream_ended_hint") %></p>
<%= link_to t("live.show.all_live_link"), public_live_index_path, class: "btn btn-secondary stream-ended-btn" %>
</div>
<% elsif @session.platform == "youtube" %>
<div class="stream-ended youtube-live-cta" role="status" aria-live="polite">
<div class="stream-ended-icon" aria-hidden="true"></div>
<h2>Diretta su YouTube</h2>
<h2><%= t("live.show.youtube_title") %></h2>
<% if @session.youtube_watch_url.present? %>
<p>Questa partita è trasmessa sul canale YouTube Match Live TV, non sul player del sito.</p>
<%= link_to "Guarda su YouTube", @session.youtube_watch_url, class: "btn stream-ended-btn", target: "_blank", rel: "noopener" %>
<p><%= t("live.show.youtube_available_body") %></p>
<%= link_to t("live.show.youtube_watch_link"), @session.youtube_watch_url, class: "btn stream-ended-btn", target: "_blank", rel: "noopener" %>
<% else %>
<p>Stiamo preparando la diretta sul canale YouTube. Aggiorna tra qualche secondo.</p>
<p><%= t("live.show.youtube_preparing_body") %></p>
<meta http-equiv="refresh" content="8">
<% end %>
<p class="stream-ended-hint">Condividi solo il link YouTube con tifosi e società.</p>
<p class="stream-ended-hint"><%= t("live.show.youtube_hint") %></p>
</div>
<% else %>
<div class="live-player-wrap">
<video id="player" controls playsinline muted autoplay poster="/images/copertina-canale.png"></video>
<%= render "public/live/player_overlays", match: @match, session: @session %>
<button type="button" id="play-hint" class="live-play-hint" hidden>
▶ Avvia la diretta
<%= t("live.show.play_hint") %>
</button>
</div>
<p id="offline-msg" class="live-status-msg" hidden>La diretta non è ancora disponibile. Si aggiorna automaticamente quando torna in onda.</p>
<p id="awaiting-msg" class="live-status-msg" hidden>In attesa del segnale dal telefono — apri la camera nell'app e verifica che la diretta sia avviata (non in pausa).</p>
<p id="paused-msg" class="live-status-msg" hidden>Trasmissione in pausa — il flusso continua (copertina server).</p>
<p id="offline-msg" class="live-status-msg" hidden><%= t("live.show.offline_msg") %></p>
<p id="awaiting-msg" class="live-status-msg" hidden><%= t("live.show.awaiting_msg") %></p>
<p id="paused-msg" class="live-status-msg" hidden><%= t("live.show.paused_msg") %></p>
<script>
const sessionId = "<%= @session.id %>";
@@ -63,19 +69,19 @@
function syncStreamBadge(data) {
if (!streamBadge) return;
if (data.stream_closed) {
streamBadge.textContent = "Terminata";
streamBadge.textContent = "<%= j t("live.overlays.badge_ended") %>";
streamBadge.className = "live-ovl-badge live-ovl-badge--right badge-ended";
return;
}
const paused = !!(data.paused || data.status === "paused");
if (paused) {
streamBadge.textContent = "In pausa";
streamBadge.textContent = "<%= j t("live.index.badge_paused") %>";
streamBadge.className = "live-ovl-badge live-ovl-badge--right badge-wait";
} else if (data.on_air) {
streamBadge.textContent = "LIVE";
streamBadge.textContent = "<%= j t("live.index.badge_live") %>";
streamBadge.className = "live-ovl-badge live-ovl-badge--right badge-live";
} else {
streamBadge.textContent = "In attesa";
streamBadge.textContent = "<%= j t("live.overlays.badge_waiting") %>";
streamBadge.className = "live-ovl-badge live-ovl-badge--right badge-wait";
}
}
@@ -158,10 +164,10 @@
panel.setAttribute("role", "status");
panel.innerHTML = `
<div class="stream-ended-icon" aria-hidden="true">📡</div>
<h2>Diretta terminata</h2>
<p>Lo streaming di questa partita è stato chiuso.</p>
<p class="stream-ended-hint">Ricarica la pagina per lultimo stato registrato.</p>
<a href="<%= public_live_index_path %>" class="btn btn-secondary stream-ended-btn">Tutte le dirette</a>
<h2><%= j t("live.show.stream_ended_title") %></h2>
<p><%= j t("live.show.stream_ended_body") %></p>
<p class="stream-ended-hint"><%= j t("live.show.stream_ended_reload_hint") %></p>
<a href="<%= public_live_index_path %>" class="btn btn-secondary stream-ended-btn"><%= j t("live.show.all_live_link") %></a>
`;
video.parentNode.insertBefore(panel, video.nextSibling);
}
@@ -277,7 +283,7 @@
offlineMsg.hidden = true;
if (!publisherOnline && showingCover && !paused) {
awaitingMsg.hidden = false;
awaitingMsg.textContent = "In riconnessione - in attesa del video dall'evento";
awaitingMsg.textContent = "<%= j t("live.show.js_reconnecting_awaiting") %>";
} else {
awaitingMsg.hidden = !awaitingSignal;
}
@@ -1,13 +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[opponent_name]", t("matches.opponent_label") %>
<%= text_field_tag "match[opponent_name]", match.opponent_name, required: true, placeholder: t("matches.opponent_placeholder") %>
<%= label_tag "match[location]", "Luogo (opzionale)" %>
<%= text_field_tag "match[location]", match.location, placeholder: "Palestra, città" %>
<%= label_tag "match[location]", t("matches.location_label") %>
<%= text_field_tag "match[location]", match.location, placeholder: t("matches.location_placeholder") %>
<%= label_tag "match[scheduled_at]", "Data e ora" %>
<%= label_tag "match[scheduled_at]", t("matches.datetime_label") %>
<% 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.
<%= t("matches.form_hint") %>
</p>
@@ -1,14 +1,14 @@
<% content_for :title, "Modifica partita — #{@team.name}" %>
<% content_for :title, t("matches.edit.title", name: @team.name) %>
<% content_for :robots, "noindex, nofollow" %>
<div class="wrap" style="padding-top:20px;max-width:560px">
<h1>Modifica partita</h1>
<h1><%= t("matches.edit.heading") %></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" %>
submit_label: t("matches.edit.submit") %>
</div>
<p style="margin-top:16px"><%= link_to "← Elenco partite", public_team_matches_path(@team) %></p>
<p style="margin-top:16px"><%= link_to t("matches.back_to_list"), public_team_matches_path(@team) %></p>
</div>
+16 -18
View File
@@ -1,4 +1,4 @@
<% content_for :title, "Partite — #{@team.name}" %>
<% content_for :title, t("matches.index.title", name: @team.name) %>
<% content_for :robots, "noindex, nofollow" %>
<div class="wrap" style="padding-top:20px;max-width:720px">
@@ -7,17 +7,16 @@
</nav>
<header style="margin:16px 0 20px">
<h1>Partite — <%= @team.name %></h1>
<h1><%= t("matches.index.heading", name: @team.name) %></h1>
<p style="color:#888;margin-top:8px">
Programma qui da PC data, avversario e luogo. Quando sei in palestra apri lapp Match Live TV,
seleziona questa squadra e avvia la diretta sulla partita.
<%= t("matches.index.lead") %>
</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" %>
<%= link_to t("matches.index.schedule_match"), new_public_team_match_path(@team), class: "btn btn-primary" %>
<%= link_to t("matches.index.team_details"), public_team_details_path(@team), class: "btn btn-secondary" %>
<%= link_to t("matches.index.live_and_schedule"), public_live_index_path(club_id: @club.id), class: "btn btn-secondary" %>
</div>
<div class="card">
@@ -25,10 +24,10 @@
<table class="data">
<thead>
<tr>
<th>Avversario</th>
<th>Data e ora</th>
<th>Luogo</th>
<th>Stato</th>
<th><%= t("matches.index.col_opponent") %></th>
<th><%= t("matches.index.col_datetime") %></th>
<th><%= t("matches.index.col_location") %></th>
<th><%= t("matches.index.col_status") %></th>
<th></th>
</tr>
</thead>
@@ -40,25 +39,25 @@
<% if match.scheduled_at %>
<%= l_local(match.scheduled_at) %>
<% else %>
<span style="color:#888">Senza orario</span>
<span style="color:#888"><%= t("matches.index.no_time") %></span>
<% end %>
</td>
<td><%= match.location.presence || "—" %></td>
<td>
<% label = match.public_status_label %>
<% if label == "In corso / da riprendere" %>
<% if match.active_stream_session %>
<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) %>
<%= link_to t("matches.index.edit"), edit_public_team_match_path(@team, match) %>
<% if match.deletable? %>
·
<%= button_to "Elimina", public_team_match_path(@team, match),
<%= button_to t("matches.index.delete"), public_team_match_path(@team, match),
method: :delete,
form: { data: { turbo_confirm: "Eliminare questa partita?" } },
form: { data: { turbo_confirm: t("matches.index.delete_confirm"), confirm_kind: "delete" } },
class: "btn btn-secondary",
style: "padding:4px 10px;font-size:0.8rem;display:inline" %>
<% end %>
@@ -69,8 +68,7 @@
</table>
<% else %>
<p style="color:#888;margin:0">
Nessuna partita in calendario.
<%= link_to "Programma la prima", new_public_team_match_path(@team) %>.
<%= raw t("matches.index.empty_html", link: link_to(t("matches.index.schedule_first"), new_public_team_match_path(@team))) %>
</p>
<% end %>
</div>
@@ -1,14 +1,14 @@
<% content_for :title, "Programma partita — #{@team.name}" %>
<% content_for :title, t("matches.new.title", name: @team.name) %>
<% content_for :robots, "noindex, nofollow" %>
<div class="wrap" style="padding-top:20px;max-width:560px">
<h1>Programma partita</h1>
<h1><%= t("matches.new.heading") %></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" %>
submit_label: t("matches.new.submit") %>
</div>
<p style="margin-top:16px"><%= link_to "← Elenco partite", public_team_matches_path(@team) %></p>
<p style="margin-top:16px"><%= link_to t("matches.back_to_list"), public_team_matches_path(@team) %></p>
</div>
+65 -69
View File
@@ -1,150 +1,146 @@
<% content_for :title, "Cookie policy — Match Live TV" %>
<% content_for :meta_description, "Cookie policy di Match Live TV: cookie tecnici, sessione, Google Analytics e gestione del consenso." %>
<% content_for :title, t("legal.cookies.title") %>
<% content_for :meta_description, t("legal.cookies.meta_description") %>
<% content_for :canonical_url, seo_absolute_url(public_cookies_path) %>
<div class="wrap legal-doc">
<h1>Cookie policy</h1>
<h1><%= t("legal.cookies.h1") %></h1>
<p class="legal-meta">
Ultimo aggiornamento: <%= cookie_policy_last_updated %>
· Vedi anche <%= link_to "informativa privacy", public_privacy_path %>
<%= raw t(
"legal.cookies.meta_html",
date: cookie_policy_last_updated,
privacy_link: link_to(t("legal.cookies.privacy_link_text"), public_privacy_path)
) %>
</p>
<section>
<h2>1. Cosa sono i cookie</h2>
<h2><%= t("legal.cookies.s1_title") %></h2>
<p>
I cookie sono piccoli file di testo che i siti salvano sul dispositivo del visitatore.
Servono a far funzionare il sito, ricordare preferenze o, previo consenso, analizzare luso del servizio.
<%= t("legal.cookies.s1_body") %>
</p>
</section>
<section>
<h2>2. Titolare</h2>
<h2><%= t("legal.cookies.s2_title") %></h2>
<p>
<strong><%= MatchLiveTv.privacy_controller_name %></strong>
<%= MatchLiveTv.privacy_controller_address %>
<a href="mailto:<%= MatchLiveTv.privacy_controller_email %>"><%= MatchLiveTv.privacy_controller_email %></a>
<% if MatchLiveTv.privacy_controller_vat.present? %>
· P. IVA <%= MatchLiveTv.privacy_controller_vat %>
· <%= t("legal.cookies.s2_vat_label") %> <%= MatchLiveTv.privacy_controller_vat %>
<% end %>
</p>
</section>
<section>
<h2>3. Come gestiamo il consenso</h2>
<h2><%= t("legal.cookies.s3_title") %></h2>
<p>
Al primo accesso mostriamo un banner che ti permette di accettare tutti i cookie,
usare <strong>solo quelli necessari</strong> o aprire questa pagina per maggiori dettagli.
Puoi modificare la scelta in qualsiasi momento con il link
<strong>«Gestisci cookie»</strong> nel footer.
<%= raw t("legal.cookies.s3_p1_html") %>
</p>
<p>
La preferenza viene memorizzata in un cookie tecnico di prima parte
(<code>mltv_cookie_consent</code>) e in <code>localStorage</code> del browser.
<%= raw t("legal.cookies.s3_p2_html") %>
</p>
</section>
<section>
<h2>4. Cookie e tecnologie che utilizziamo</h2>
<h2><%= t("legal.cookies.s4_title") %></h2>
<h3>4.1 Strettamente necessari (sempre attivi)</h3>
<p>Non richiedono consenso perché indispensabili al funzionamento del sito e dellarea riservata.</p>
<h3><%= t("legal.cookies.s4_1_title") %></h3>
<p><%= t("legal.cookies.s4_1_intro") %></p>
<table class="legal-table">
<thead>
<tr><th>Nome / tipo</th><th>Finalità</th><th>Durata</th><th>Fornitore</th></tr>
<tr><th><%= t("legal.cookies.table_col_name") %></th><th><%= t("legal.cookies.table_col_purpose") %></th><th><%= t("legal.cookies.table_col_duration") %></th><th><%= t("legal.cookies.table_col_provider") %></th></tr>
</thead>
<tbody>
<tr>
<td><code>_app_session</code> (cookie di sessione)</td>
<td>Mantiene laccesso dellutente registrato, protezione CSRF, preferenze di navigazione sicura</td>
<td>Sessione / fino a chiusura browser</td>
<td>Match Live TV (prima parte)</td>
<td><%= raw t("legal.cookies.s4_1_row1_name_html") %></td>
<td><%= t("legal.cookies.s4_1_row1_purpose") %></td>
<td><%= t("legal.cookies.s4_1_row1_duration") %></td>
<td><%= t("legal.cookies.s4_1_row1_provider") %></td>
</tr>
<tr>
<td><code>mltv_cookie_consent</code></td>
<td>Memorizza le scelte sul banner cookie (necessari / analytics)</td>
<td>12 mesi</td>
<td>Match Live TV (prima parte)</td>
<td><%= raw t("legal.cookies.s4_1_row2_name_html") %></td>
<td><%= t("legal.cookies.s4_1_row2_purpose") %></td>
<td><%= t("legal.cookies.s4_1_row2_duration") %></td>
<td><%= t("legal.cookies.s4_1_row2_provider") %></td>
</tr>
</tbody>
</table>
<h3>4.2 Analytics (solo con il tuo consenso)</h3>
<h3><%= t("legal.cookies.s4_2_title") %></h3>
<p>
Se accetti «Tutti i cookie» o abiliti le statistiche nel banner, carichiamo
<strong>Google Analytics 4</strong> per capire come viene usato il sito (pagine visitate, provenienza aggregata, dispositivo).
I dati sono trattati da Google Ireland Limited / Google LLC secondo le loro policy.
<%= raw t("legal.cookies.s4_2_p1_html") %>
</p>
<% if MatchLiveTv.google_analytics_configured? %>
<p class="muted">ID misurazione attivo sul sito: <code><%= MatchLiveTv.google_analytics_measurement_id %></code></p>
<p class="muted"><%= raw t("legal.cookies.s4_2_active_html", id: MatchLiveTv.google_analytics_measurement_id) %></p>
<% else %>
<p class="muted">Google Analytics è configurato solo quando il titolare imposta lID misurazione sul server.</p>
<p class="muted"><%= t("legal.cookies.s4_2_inactive") %></p>
<% end %>
<table class="legal-table">
<thead>
<tr><th>Nome</th><th>Finalità</th><th>Durata</th><th>Fornitore</th></tr>
<tr><th><%= t("legal.cookies.table2_col_name") %></th><th><%= t("legal.cookies.table_col_purpose") %></th><th><%= t("legal.cookies.table_col_duration") %></th><th><%= t("legal.cookies.table_col_provider") %></th></tr>
</thead>
<tbody>
<tr>
<td><code>_ga</code></td>
<td>Distingue gli utenti (statistiche)</td>
<td>2 anni</td>
<td>Google</td>
<td><%= raw t("legal.cookies.s4_2_row1_name_html") %></td>
<td><%= t("legal.cookies.s4_2_row1_purpose") %></td>
<td><%= t("legal.cookies.s4_2_row1_duration") %></td>
<td><%= t("legal.cookies.s4_2_row1_provider") %></td>
</tr>
<tr>
<td><code>_ga_*</code></td>
<td>Mantiene lo stato della sessione Analytics</td>
<td>2 anni</td>
<td>Google</td>
<td><%= raw t("legal.cookies.s4_2_row2_name_html") %></td>
<td><%= t("legal.cookies.s4_2_row2_purpose") %></td>
<td><%= t("legal.cookies.s4_2_row2_duration") %></td>
<td><%= t("legal.cookies.s4_2_row2_provider") %></td>
</tr>
<tr>
<td><code>_gid</code></td>
<td>Distingue gli utenti (statistiche)</td>
<td>24 ore</td>
<td>Google</td>
<td><%= raw t("legal.cookies.s4_2_row3_name_html") %></td>
<td><%= t("legal.cookies.s4_2_row3_purpose") %></td>
<td><%= t("legal.cookies.s4_2_row3_duration") %></td>
<td><%= t("legal.cookies.s4_2_row3_provider") %></td>
</tr>
</tbody>
</table>
<p>
Puoi revocare il consenso dal banner o dalle impostazioni del browser.
Informazioni Google:
<a href="https://policies.google.com/privacy" target="_blank" rel="noopener noreferrer">Privacy Policy Google</a>,
<a href="https://tools.google.com/dlpage/gaoptout" target="_blank" rel="noopener noreferrer">componente opt-out Analytics</a>.
<%= raw t(
"legal.cookies.s4_2_p2_html",
google_privacy_link: link_to(t("legal.cookies.s4_2_google_privacy_link_text"), "https://policies.google.com/privacy", target: "_blank", rel: "noopener noreferrer"),
google_optout_link: link_to(t("legal.cookies.s4_2_google_optout_link_text"), "https://tools.google.com/dlpage/gaoptout", target: "_blank", rel: "noopener noreferrer")
) %>
</p>
<h3>4.3 Cookie di terze parti durante il pagamento</h3>
<h3><%= t("legal.cookies.s4_3_title") %></h3>
<p>
Se acquisti un piano Premium, vieni reindirizzato a <strong>Stripe</strong> (checkout sicuro).
Stripe può impostare cookie propri sul dominio <code>stripe.com</code> per prevenire frodi e completare il pagamento.
Non controlliamo direttamente tali cookie: consulta la
<a href="https://stripe.com/privacy" target="_blank" rel="noopener noreferrer">privacy policy di Stripe</a>.
<%= raw t(
"legal.cookies.s4_3_body_html",
stripe_link: link_to(t("legal.cookies.s4_3_stripe_link_text"), "https://stripe.com/privacy", target: "_blank", rel: "noopener noreferrer")
) %>
</p>
<h3>4.4 YouTube e servizi collegati</h3>
<h3><%= t("legal.cookies.s4_4_title") %></h3>
<p>
Lintegrazione YouTube per le dirette avviene tramite API lato server; il sito pubblico non incorpora
player YouTube con cookie di profilazione. I visitatori che aprono un link YouTube esterno
sono soggetti alle policy di Google/YouTube su quel dominio.
<%= t("legal.cookies.s4_4_body") %>
</p>
</section>
<section>
<h2>5. Come disabilitare i cookie dal browser</h2>
<h2><%= t("legal.cookies.s5_title") %></h2>
<p>
Puoi bloccare o cancellare i cookie dalle impostazioni del browser (Chrome, Firefox, Safari, Edge).
Disabilitando i cookie necessari alcune funzioni (es. login) potrebbero non funzionare.
<%= t("legal.cookies.s5_body") %>
</p>
</section>
<section>
<h2>6. Diritti e contatti</h2>
<h2><%= t("legal.cookies.s6_title") %></h2>
<p>
Per esercitare i diritti previsti dal GDPR (accesso, cancellazione, opposizione, revoca consenso)
scrivi a <a href="mailto:<%= MatchLiveTv.privacy_controller_email %>"><%= MatchLiveTv.privacy_controller_email %></a>.
Dettagli nel <%= link_to "documento privacy", public_privacy_path %>.
<%= raw t(
"legal.cookies.s6_body_html",
email_link: link_to(MatchLiveTv.privacy_controller_email, "mailto:#{MatchLiveTv.privacy_controller_email}"),
privacy_doc_link: link_to(t("legal.cookies.s6_privacy_doc_link_text"), public_privacy_path)
) %>
</p>
</section>
<p style="margin-top:24px">
<button type="button" class="btn btn-secondary" data-cookie-manage>Gestisci preferenze cookie</button>
<button type="button" class="btn btn-secondary" data-cookie-manage><%= t("legal.cookies.manage_button") %></button>
</p>
</div>
+28 -32
View File
@@ -1,83 +1,79 @@
<% content_for :title, "FAQ — Diretta live partite giovanili | Match Live TV" %>
<% content_for :meta_description, "Domande frequenti su streaming partite giovanili: come guardare la diretta senza app, costi, archivio, pallavolo Under 16, inviti staff e piani Free e Premium." %>
<% content_for :title, t("pages.faq.meta_title") %>
<% content_for :meta_description, t("pages.faq.meta_description") %>
<% content_for :canonical_url, seo_absolute_url(public_faq_path) %>
<div class="wrap seo-page">
<h1 style="margin-top:24px">Domande frequenti sullo streaming delle partite giovanili</h1>
<h1 style="margin-top:24px"><%= t("pages.faq.title") %></h1>
<p class="seo-lead">
Risposte per genitori, allenatori e dirigenti che vogliono mandare in diretta live le partite
di pallavolo, calcio e altri sport dilettantistici.
<%= t("pages.faq.lead") %>
</p>
<div class="faq-list">
<details class="faq-item" open>
<summary>Come guardo la partita di mio figlio senza installare app?</summary>
<summary><%= t("pages.faq.q1_question") %></summary>
<p>
La società condivide un link (via WhatsApp, email o gruppo squadra). Apri il link dal telefono o dal computer:
vedi la <strong>diretta live</strong> nel browser, senza registrarti. Funziona per nonni, parenti e amici lontani.
<%= raw t("pages.faq.q1_answer_html") %>
</p>
</details>
<details class="faq-item">
<summary>Chi può avviare la diretta dalla palestra?</summary>
<summary><%= t("pages.faq.q2_question") %></summary>
<p>
Coach, dirigenti o volontari invitati dalla società. Usano lapp Match Live TV sul telefono per filmare e,
se serve, un secondo telefono per aggiornare il punteggio. La diretta parte solo quando premo avvio in palestra.
<%= t("pages.faq.q2_answer") %>
</p>
</details>
<details class="faq-item">
<summary>È adatto alla pallavolo giovanile (Under 14, Under 16)?</summary>
<summary><%= t("pages.faq.q3_question") %></summary>
<p>
Sì. Il punteggio a set è pensato per la pallavolo; puoi personalizzare regole per tornei particolari.
Scopri la pagina dedicata: <%= link_to "Match Live TV per pallavolo giovanile", public_pallavolo_path %>.
<%= raw t(
"pages.faq.q3_answer_html",
volleyball_link: link_to(t("pages.faq.q3_volleyball_link"), public_pallavolo_path)
) %>
</p>
</details>
<details class="faq-item">
<summary>Cosa succede se perdo la diretta?</summary>
<summary><%= t("pages.faq.q4_question") %></summary>
<p>
Con i piani Premium Light o Full la partita viene salvata in archivio (30 o 90 giorni) e puoi rivederla dal sito.
Il piano Free non include replay su server, ma la diretta resta gratuita per chi trasmette e per chi guarda.
<%= t("pages.faq.q4_answer") %>
</p>
</details>
<details class="faq-item">
<summary>Quanto costa per la società?</summary>
<summary><%= t("pages.faq.q5_question") %></summary>
<p>
Puoi iniziare con il piano <strong>Free</strong> (limiti su staff e una diretta alla volta).
Premium Light e Full aggiungono più partite in parallelo, archivio e YouTube.
<%= link_to "Confronta i prezzi", public_prezzi_path %>.
<%= raw t(
"pages.faq.q5_answer_html",
pricing_link: link_to(t("pages.faq.q5_pricing_link"), public_prezzi_path)
) %>
</p>
</details>
<details class="faq-item">
<summary>Posso programmare una partita prima del giorno gara?</summary>
<summary><%= t("pages.faq.q6_question") %></summary>
<p>
Sì, dallapp si imposta data e ora: la partita compare in «Prossime partite» sul sito.
Lo streaming parte solo quando lo staff avvia la trasmissione in palestra.
<%= t("pages.faq.q6_answer") %>
</p>
</details>
<details class="faq-item">
<summary>La diretta va anche su YouTube?</summary>
<summary><%= t("pages.faq.q7_question") %></summary>
<p>
Con Premium Light puoi mandarla sul canale Match Live TV; con Premium Full anche sul canale YouTube della società.
Chi preferisce resta sul link Match Live TV, comodo per le famiglie.
<%= t("pages.faq.q7_answer") %>
</p>
</details>
<details class="faq-item">
<summary>Devo aprire porte sul router o avere attrezzatura da TV?</summary>
<summary><%= t("pages.faq.q8_question") %></summary>
<p>
No per chi guarda. Per la società che trasmette basta lo smartphone e una buona connessione in palestra;
lo staff tecnico della piattaforma gestisce linfrastruttura. Niente camere broadcast o mixer.
<%= t("pages.faq.q8_answer") %>
</p>
</details>
</div>
<p style="text-align:center;margin:40px 0">
<%= link_to "Registra la squadra", public_signup_path, class: "btn btn-primary" %>
<%= link_to "Guarda le dirette", public_live_index_path, class: "btn btn-secondary" %>
<%= link_to t("pages.faq.cta_signup"), public_signup_path, class: "btn btn-primary" %>
<%= link_to t("pages.faq.cta_live"), public_live_index_path, class: "btn btn-secondary" %>
</p>
</div>
@@ -1,64 +1,64 @@
<% content_for :title, "Funzionalità streaming partite giovanili — Match Live TV" %>
<% content_for :meta_description, "App per diretta live da telefono: filma la partita, aggiorna il punteggio, condividi il link con genitori e nonni. Archivio gare e piani per società sportive giovanili." %>
<% content_for :title, t("pages.features.meta_title") %>
<% content_for :meta_description, t("pages.features.meta_description") %>
<% content_for :canonical_url, seo_absolute_url(public_features_path) %>
<div class="wrap">
<h1 style="margin-top:24px">Funzionalità</h1>
<p style="color:#aaa;max-width:640px">Tutto ciò che serve per far seguire le partite a chi non può essere in palestra: semplice per coach e volontari, comodo per genitori, nonni e parenti.</p>
<h1 style="margin-top:24px"><%= t("pages.features.title") %></h1>
<p style="color:#aaa;max-width:640px"><%= t("pages.features.lead") %></p>
<section class="section">
<h2>Per la società e lo staff</h2>
<h2><%= t("pages.features.staff_section_title") %></h2>
<div class="features-grid">
<div class="feature-card">
<h3>Inviti con la propria email</h3>
<p>Il presidente o lowner invita chi filma e chi aggiorna il punteggio: ognuno accede con la sua email, senza condividere password.</p>
<h3><%= t("pages.features.staff_invite_title") %></h3>
<p><%= t("pages.features.staff_invite_body") %></p>
</div>
<div class="feature-card">
<h3>Gestione accessi</h3>
<p>Puoi togliere laccesso a un volontario o annullare un invito ancora in sospeso dalla dashboard della squadra.</p>
<h3><%= t("pages.features.staff_access_title") %></h3>
<p><%= t("pages.features.staff_access_body") %></p>
</div>
<div class="feature-card">
<h3>Piani su misura</h3>
<p>Con Free, Premium Light o Premium Full aumenti quante persone possono aiutare in diretta e quante partite possono andare in parallelo.</p>
<h3><%= t("pages.features.staff_plans_title") %></h3>
<p><%= t("pages.features.staff_plans_body") %></p>
</div>
</div>
</section>
<section class="section">
<h2>In campo e in diretta</h2>
<h2><%= t("pages.features.live_section_title") %></h2>
<div class="features-grid">
<div class="feature-card">
<h3>Filma dal telefono</h3>
<p>Appoggi il telefono e avvii la diretta: anche con il 4G debole la trasmissione resta stabile il più possibile, senza impazzire con cavi o mixer.</p>
<h3><%= t("pages.features.live_phone_title") %></h3>
<p><%= t("pages.features.live_phone_body") %></p>
</div>
<div class="feature-card">
<h3>Punteggio da un secondo telefono</h3>
<p>Un altro volontario aggiorna set e punti da un secondo cellulare: basta inquadrare il codice che compare sullo schermo di chi filma.</p>
<h3><%= t("pages.features.live_score_title") %></h3>
<p><%= t("pages.features.live_score_body") %></p>
</div>
<div class="feature-card">
<h3>Link per famiglie e tifosi</h3>
<p>Condividi un link: genitori, nonni e amici guardano dal browser del telefono o del computer, senza installare app.</p>
<h3><%= t("pages.features.live_link_title") %></h3>
<p><%= t("pages.features.live_link_body") %></p>
</div>
<div class="feature-card">
<h3>Archivio partite</h3>
<p>Con Premium Light o Full le gare restano salvate sul server (30 o 90 giorni) e puoi scaricarle sul telefono quando serve.</p>
<h3><%= t("pages.features.live_archive_title") %></h3>
<p><%= t("pages.features.live_archive_body") %></p>
</div>
<div class="feature-card">
<h3>Anche su YouTube (Match Live TV)</h3>
<p>Con Premium Light la diretta è visibile anche sul canale YouTube Match Live TV, oltre al link sul sito.</p>
<h3><%= t("pages.features.live_youtube_mltv_title") %></h3>
<p><%= t("pages.features.live_youtube_mltv_body") %></p>
</div>
<div class="feature-card">
<h3>Canale YouTube della società</h3>
<p>Con Premium Full puoi mandare la stessa diretta anche sul canale YouTube ufficiale del club.</p>
<h3><%= t("pages.features.live_youtube_club_title") %></h3>
<p><%= t("pages.features.live_youtube_club_body") %></p>
</div>
<div class="feature-card">
<h3>Sul sito della società</h3>
<p>Integrazione per incorporare il player sul sito del club — in arrivo con Premium Full.</p>
<h3><%= t("pages.features.live_website_title") %></h3>
<p><%= t("pages.features.live_website_body") %></p>
</div>
</div>
</section>
<p style="text-align:center;margin:40px 0">
<%= link_to "Registra la squadra", public_signup_path, class: "btn btn-primary" %>
<%= link_to t("pages.features.cta_signup"), public_signup_path, class: "btn btn-primary" %>
</p>
</div>
@@ -1,67 +1,60 @@
<% content_for :title, "Diretta live pallavolo giovanile — Under 14, Under 16 | Match Live TV" %>
<% content_for :meta_description, "Streaming partite di pallavolo giovanile da telefono: diretta live per genitori e nonni, punteggio a set, link da condividere. Ideale per società Under 14, Under 16 e Under 18." %>
<% content_for :title, t("pages.volleyball.meta_title") %>
<% content_for :meta_description, t("pages.volleyball.meta_description") %>
<% content_for :canonical_url, seo_absolute_url(public_pallavolo_path) %>
<div class="wrap seo-page">
<h1 style="margin-top:24px">Match Live TV per la pallavolo giovanile</h1>
<h1 style="margin-top:24px"><%= t("pages.volleyball.title") %></h1>
<p class="seo-lead">
La soluzione per società, coach e genitori che vogliono una <strong>diretta live</strong> affidabile
delle partite <strong>Under 14, Under 16, Under 18</strong> e settori giovanili — senza diventare telecronisti.
<%= raw t("pages.volleyball.lead_html") %>
</p>
<section class="seo-prose">
<h2>Perché la pallavolo giovanile ha bisogno di uno streaming semplice</h2>
<h2><%= t("pages.volleyball.section1_title") %></h2>
<p>
In palestra i genitori non sono sempre tutti in tribuna: turni di lavoro, fratelli piccoli, trasferte.
Un nonno in unaltra città o un parente allestero vuole comunque vedere il tie-break del terzo set.
Match Live TV nasce per questo: <strong>streaming partite pallavolo giovanile</strong> dal telefono,
con punteggio aggiornato e link immediato da mandare su WhatsApp.
<%= raw t("pages.volleyball.section1_body_html") %>
</p>
<h2>Cosa fa il coach in due minuti</h2>
<h2><%= t("pages.volleyball.section2_title") %></h2>
<ul>
<li>Programma la partita con data, avversario e campo dallapp</li>
<li>Il giorno gara appoggia il telefono e avvia la diretta</li>
<li>Un volontario può aggiornare set e punti da un secondo cellulare</li>
<li>Condivide il link con il gruppo genitori: tutti guardano dal browser</li>
<li><%= t("pages.volleyball.section2_item1") %></li>
<li><%= t("pages.volleyball.section2_item2") %></li>
<li><%= t("pages.volleyball.section2_item3") %></li>
<li><%= t("pages.volleyball.section2_item4") %></li>
</ul>
<h2>Cosa vede la famiglia a casa</h2>
<h2><%= t("pages.volleyball.section3_title") %></h2>
<p>
Nessuna app da installare. Apri il link e segui la partita in diretta live: nome squadre, punteggio,
stato del set. Se hai perso linizio, con Premium la gara resta in archivio per rivederla quando vuoi.
<%= t("pages.volleyball.section3_body") %>
</p>
<h2>Pallavolo: punteggio e regole</h2>
<h2><%= t("pages.volleyball.section4_title") %></h2>
<p>
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.
<%= t("pages.volleyball.section4_body") %>
</p>
<h2>Altri sport giovanili</h2>
<h2><%= t("pages.volleyball.section5_title") %></h2>
<p>
La stessa piattaforma funziona anche per calcio, basket e sport di squadra dilettantistici.
La pallavolo resta lo sport con cui molte società iniziano perché il punteggio a set è già integrato.
<%= t("pages.volleyball.section5_body") %>
</p>
</section>
<div class="features-grid" style="margin-top:32px">
<div class="feature-card">
<h3>Genitori e nonni</h3>
<p>Link su WhatsApp, visione da telefono o TV con browser. Zero registrazione per chi guarda.</p>
<h3><%= t("pages.volleyball.card1_title") %></h3>
<p><%= t("pages.volleyball.card1_body") %></p>
</div>
<div class="feature-card">
<h3>Società e staff</h3>
<p>Inviti con email, più operatori con Premium, revoca accessi dalla dashboard web.</p>
<h3><%= t("pages.volleyball.card2_title") %></h3>
<p><%= t("pages.volleyball.card2_body") %></p>
</div>
<div class="feature-card">
<h3>Archivio gare</h3>
<p>Premium Light/Full: replay per chi ha perso la diretta o vuole rivedere un punto chiave.</p>
<h3><%= t("pages.volleyball.card3_title") %></h3>
<p><%= t("pages.volleyball.card3_body") %></p>
</div>
</div>
<p style="text-align:center;margin:40px 0">
<%= link_to "Inizia gratis", public_signup_path, class: "btn btn-primary" %>
<%= link_to "Domande frequenti", public_faq_path, class: "btn btn-secondary" %>
<%= link_to t("pages.volleyball.cta_start"), public_signup_path, class: "btn btn-primary" %>
<%= link_to t("pages.volleyball.cta_faq"), public_faq_path, class: "btn btn-secondary" %>
</p>
</div>
+20 -16
View File
@@ -1,18 +1,22 @@
<% content_for :title, "Prezzi streaming partite giovanili — Match Live TV" %>
<% content_for :meta_description, "Piani Free, Premium Light e Premium Full per dirette live e archivio partite. Abbonamento annuale per società: più staff, più partite in parallelo, replay e YouTube." %>
<% content_for :title, t("pages.pricing.meta_title") %>
<% content_for :meta_description, t("pages.pricing.meta_description") %>
<% content_for :canonical_url, seo_absolute_url(public_prezzi_path) %>
<div class="wrap" style="padding-top:24px">
<% if @club %>
<h1>Piani per la tua società</h1>
<h1><%= t("pages.pricing.club_title") %></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>
<p style="color:#aaa"><%= raw t(
"pages.pricing.club_info_html",
name: @club.name,
billing_link: link_to(t("pages.pricing.manage_subscription"), 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 dallapp; il punteggio si gestisce con un link condivisibile.</p>
<h1><%= t("pages.pricing.team_title") %></h1>
<p style="color:#aaa"><%= t("pages.pricing.team_lead") %></p>
<% end %>
<%= render "shared/stripe_secure_payment" %>
@@ -23,19 +27,19 @@
<tr><th></th><th>Free</th><th>Premium Light</th><th>Premium Full</th></tr>
</thead>
<tbody>
<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></td><td></td><td></td></tr>
<tr><td>YouTube</td><td>No</td><td>Match Live TV</td><td>Canale società</td></tr>
<tr><td>Replay server</td><td>No</td><td>30 gg</td><td>90 gg</td></tr>
<tr><td>Download telefono</td><td>No</td><td></td><td></td></tr>
<tr><td>Prezzo</td><td>€0</td><td>€40/anno o €5/mese</td><td>€200/anno o €20/mese</td></tr>
<tr><td><%= t("pages.pricing.table_staff") %></td><td>1</td><td>5</td><td><%= t("pages.pricing.table_unlimited") %></td></tr>
<tr><td><%= t("pages.pricing.table_matches") %></td><td>1</td><td>3</td><td>10</td></tr>
<tr><td><%= t("pages.pricing.table_live_mltv") %></td><td><%= t("pages.pricing.table_yes") %></td><td><%= t("pages.pricing.table_yes") %></td><td><%= t("pages.pricing.table_yes") %></td></tr>
<tr><td><%= t("pages.pricing.table_youtube") %></td><td><%= t("pages.pricing.table_no") %></td><td>Match Live TV</td><td><%= t("pages.pricing.table_youtube_club") %></td></tr>
<tr><td><%= t("pages.pricing.table_replay") %></td><td><%= t("pages.pricing.table_no") %></td><td><%= t("pages.plans.replay_days", count: 30) %></td><td><%= t("pages.plans.replay_days", count: 90) %></td></tr>
<tr><td><%= t("pages.pricing.table_download") %></td><td><%= t("pages.pricing.table_no") %></td><td><%= t("pages.pricing.table_yes") %></td><td><%= t("pages.pricing.table_yes") %></td></tr>
<tr><td><%= t("pages.pricing.table_price") %></td><td><%= t("pages.pricing.table_price_free") %></td><td><%= raw t("pages.pricing.table_price_light_html") %></td><td><%= raw t("pages.pricing.table_price_full_html") %></td></tr>
</tbody>
</table>
<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@matchlive.it", "Contattaci", class: "btn btn-primary" %>
<h3 style="margin-top:0"><%= t("pages.pricing.different_title") %></h3>
<p style="color:#aaa;margin-bottom:16px"><%= t("pages.pricing.different_body") %></p>
<%= mail_to "info@matchlive.it", t("pages.pricing.contact_button"), class: "btn btn-primary" %>
</div>
</div>
+109 -101
View File
@@ -1,192 +1,200 @@
<% content_for :title, "Informativa privacy — Match Live TV" %>
<% content_for :meta_description, "Informativa privacy GDPR di Match Live TV: dati account, streaming partite giovanili, immagini di minori, pagamenti Stripe e diritti degli interessati." %>
<% content_for :title, t("legal.privacy.title") %>
<% content_for :meta_description, t("legal.privacy.meta_description") %>
<% content_for :canonical_url, seo_absolute_url(public_privacy_path) %>
<div class="wrap legal-doc">
<h1>Informativa sul trattamento dei dati personali</h1>
<p class="legal-meta">Ultimo aggiornamento: <%= legal_last_updated %> · Ai sensi del Regolamento (UE) 2016/679 (GDPR)</p>
<h1><%= t("legal.privacy.h1") %></h1>
<p class="legal-meta"><%= raw t("legal.privacy.meta_html", date: legal_last_updated) %></p>
<section>
<h2>1. Titolare del trattamento</h2>
<h2><%= t("legal.privacy.s1_title") %></h2>
<p>
Il titolare del trattamento dei dati personali raccolti tramite il sito e i servizi Match Live TV è:
<%= t("legal.privacy.s1_intro") %>
</p>
<ul>
<li><strong><%= MatchLiveTv.privacy_controller_name %></strong></li>
<li>Indirizzo: <%= MatchLiveTv.privacy_controller_address %></li>
<li>Email per privacy e diritti: <a href="mailto:<%= MatchLiveTv.privacy_controller_email %>"><%= MatchLiveTv.privacy_controller_email %></a></li>
<li><%= t("legal.privacy.s1_label_address") %> <%= MatchLiveTv.privacy_controller_address %></li>
<li><%= t("legal.privacy.s1_label_email") %> <a href="mailto:<%= MatchLiveTv.privacy_controller_email %>"><%= MatchLiveTv.privacy_controller_email %></a></li>
<% if MatchLiveTv.privacy_controller_vat.present? %>
<li>Partita IVA / CF: <%= MatchLiveTv.privacy_controller_vat %></li>
<li><%= t("legal.privacy.s1_label_vat") %> <%= MatchLiveTv.privacy_controller_vat %></li>
<% end %>
</ul>
<p>Per richieste relative a questa informativa o per esercitare i diritti previsti dal GDPR, scrivi allindirizzo email indicato.</p>
<p><%= t("legal.privacy.s1_outro") %></p>
</section>
<section>
<h2>2. Ambito di applicazione</h2>
<p>La presente informativa si applica a:</p>
<h2><%= t("legal.privacy.s2_title") %></h2>
<p><%= t("legal.privacy.s2_intro") %></p>
<ul>
<li>visitatori del sito web Match Live TV (pagine informative, elenco dirette, registrazione squadra);</li>
<li>utenti registrati (referenti di società sportive, coach, staff invitato);</li>
<li>spettatori che accedono alle dirette tramite link (in genere senza creare un account);</li>
<li>utilizzo dellapp mobile collegata al servizio.</li>
<li><%= t("legal.privacy.s2_item1") %></li>
<li><%= t("legal.privacy.s2_item2") %></li>
<li><%= t("legal.privacy.s2_item3") %></li>
<li><%= t("legal.privacy.s2_item4") %></li>
</ul>
</section>
<section>
<h2>3. Tipologie di dati trattati</h2>
<p>Possiamo trattare, a seconda delluso del servizio:</p>
<h2><%= t("legal.privacy.s3_title") %></h2>
<p><%= t("legal.privacy.s3_intro") %></p>
<ul>
<li><strong>Dati identificativi e di contatto:</strong> nome, indirizzo email, credenziali di accesso (password conservata in forma crittografata).</li>
<li><strong>Dati della squadra:</strong> nome società, sport, partite programmate, avversario, luogo, orari.</li>
<li><strong>Dati di streaming e tecnici:</strong> stato sessione, punteggio, telemetria di rete/bitrate, log di connessione al servizio video.</li>
<li><strong>Contenuti audiovisivi:</strong> flussi video e audio delle partite trasmesse in diretta e, con i piani Premium, registrazioni (replay) conservate per un periodo limitato.</li>
<li><strong>Dati di pagamento:</strong> per i piani a pagamento, Stripe tratta i dati necessari al pagamento (es. metodo di pagamento, intestazione fattura). Il titolare non conserva i numeri completi della carta.</li>
<li><strong>Dati di navigazione:</strong> log del server, indirizzo IP, user agent, necessari per sicurezza e funzionamento del sito.</li>
<li><%= raw t("legal.privacy.s3_item1_html") %></li>
<li><%= raw t("legal.privacy.s3_item2_html") %></li>
<li><%= raw t("legal.privacy.s3_item3_html") %></li>
<li><%= raw t("legal.privacy.s3_item4_html") %></li>
<li><%= raw t("legal.privacy.s3_item5_html") %></li>
<li><%= raw t("legal.privacy.s3_item6_html") %></li>
</ul>
</section>
<section>
<h2>4. Trattamento di immagini e dati di atleti minorenni</h2>
<h2><%= t("legal.privacy.s4_title") %></h2>
<p>
Match Live TV è destinato anche a competizioni e categorie giovanili (es. Under 14, Under 16, Under 18).
Le dirette possono riprendere <strong>atleti minorenni</strong> identificabili (immagine, voce, numero di maglia, dati di gioco).
<%= raw t("legal.privacy.s4_p1_html") %>
</p>
<p><strong>Chi è responsabile del trattamento delle immagini dei minori</strong></p>
<p><strong><%= t("legal.privacy.s4_lead1") %></strong></p>
<p>
La <strong>società sportiva</strong> che avvia la trasmissione, in qualità di organizzatrice dellevento sportivo,
è in primo luogo responsabile di assicurare una base giuridica valida (tipicamente <strong>consenso dei genitori/tutori</strong>,
oppure altro titolo previsto dalla legge e dal regolamento della federazione di riferimento) prima di filmare e diffondere le immagini.
<%= raw t("legal.privacy.s4_p2_html") %>
</p>
<p><strong>Ruolo di Match Live TV</strong></p>
<p><strong><%= t("legal.privacy.s4_lead2") %></strong></p>
<p>
Il titolare del servizio fornisce la piattaforma tecnica e, per le trasmissioni effettuate dagli utenti autorizzati,
può agire come <strong>responsabile del trattamento</strong> su istruzioni della società o, in alcuni casi, come titolare
per i trattamenti strettamente necessari allerogazione del servizio (hosting, sicurezza, fatturazione).
<%= raw t("legal.privacy.s4_p3_html") %>
</p>
<p><strong>Misure richieste alla società sportiva</strong></p>
<p><strong><%= t("legal.privacy.s4_lead3") %></strong></p>
<ul>
<li>informare genitori/tutori sulluso di video in diretta e in archivio;</li>
<li>raccogliere e documentare i consensi necessari prima della diretta;</li>
<li>limitare la diffusione del link a persone autorizzate (familiari, staff);</li>
<li>non pubblicare su canali aperti se non previsto dal consenso o dal regolamento sportivo;</li>
<li>richiedere rimozione di registrazioni quando ne ha diritto linteressato o il tutore.</li>
<li><%= t("legal.privacy.s4_item1") %></li>
<li><%= t("legal.privacy.s4_item2") %></li>
<li><%= t("legal.privacy.s4_item3") %></li>
<li><%= t("legal.privacy.s4_item4") %></li>
<li><%= t("legal.privacy.s4_item5") %></li>
</ul>
<p>
Le richieste dei genitori/tutori relative a immagini di minori possono essere inoltrate alla società sportiva
e, per supporto tecnico o cancellazione da archivio, anche a
<a href="mailto:<%= MatchLiveTv.privacy_controller_email %>"><%= MatchLiveTv.privacy_controller_email %></a>.
<%= raw t(
"legal.privacy.s4_p4_html",
email_link: link_to(MatchLiveTv.privacy_controller_email, "mailto:#{MatchLiveTv.privacy_controller_email}")
) %>
</p>
</section>
<section>
<h2>5. Finalità e basi giuridiche</h2>
<h2><%= t("legal.privacy.s5_title") %></h2>
<table class="legal-table">
<thead>
<tr><th>Finalità</th><th>Base giuridica (art. 6 GDPR)</th></tr>
<tr><th><%= t("legal.privacy.s5_col_purpose") %></th><th><%= t("legal.privacy.s5_col_basis") %></th></tr>
</thead>
<tbody>
<tr>
<td>Registrazione account, gestione squadra e staff</td>
<td>Esecuzione del contratto / misure precontrattuali</td>
<td><%= t("legal.privacy.s5_row1_purpose") %></td>
<td><%= t("legal.privacy.s5_row1_basis") %></td>
</tr>
<tr>
<td>Erogazione dirette, punteggio live, archivio (piani Premium)</td>
<td>Esecuzione del contratto; legittimo interesse alla sicurezza del servizio</td>
<td><%= t("legal.privacy.s5_row2_purpose") %></td>
<td><%= t("legal.privacy.s5_row2_basis") %></td>
</tr>
<tr>
<td>Pagamenti e fatturazione (Stripe)</td>
<td>Esecuzione del contratto; obblighi di legge</td>
<td><%= t("legal.privacy.s5_row3_purpose") %></td>
<td><%= t("legal.privacy.s5_row3_basis") %></td>
</tr>
<tr>
<td>Assistenza, sicurezza, prevenzione abusi</td>
<td>Legittimo interesse; obblighi di legge</td>
<td><%= t("legal.privacy.s5_row4_purpose") %></td>
<td><%= t("legal.privacy.s5_row4_basis") %></td>
</tr>
<tr>
<td>Comunicazioni di servizio (es. reset password)</td>
<td>Esecuzione del contratto</td>
<td><%= t("legal.privacy.s5_row5_purpose") %></td>
<td><%= t("legal.privacy.s5_row5_basis") %></td>
</tr>
<tr>
<td>Marketing diretto (solo se previsto e con consenso ove richiesto)</td>
<td>Consenso o legittimo interesse, secondo i casi</td>
<td><%= t("legal.privacy.s5_row6_purpose") %></td>
<td><%= t("legal.privacy.s5_row6_basis") %></td>
</tr>
</tbody>
</table>
</section>
<section>
<h2>6. Destinatari e responsabili del trattamento</h2>
<p>I dati possono essere comunicati a fornitori che agiscono come responsabili del trattamento, tra cui:</p>
<h2><%= t("legal.privacy.s6_title") %></h2>
<p><%= t("legal.privacy.s6_intro") %></p>
<ul>
<li>fornitore di hosting e infrastruttura cloud/server;</li>
<li><strong>Stripe</strong> (pagamenti) — <a href="https://stripe.com/it/privacy" rel="noopener" target="_blank">privacy Stripe</a>;</li>
<li>fornitore streaming video (MediaMTX / infrastruttura RTMP-HLS);</li>
<li>eventuale integrazione <strong>YouTube</strong> se attivata dalla società (soggetto a policy Google);</li>
<li>fornitore email transazionale (invio link reset password, inviti).</li>
<li><%= t("legal.privacy.s6_item1") %></li>
<li>
<%= raw t(
"legal.privacy.s6_item2_html",
stripe_link: link_to(t("legal.privacy.s6_item2_link_text"), "https://stripe.com/it/privacy", rel: "noopener", target: "_blank")
) %>
</li>
<li><%= t("legal.privacy.s6_item3") %></li>
<li><%= raw t("legal.privacy.s6_item4_html") %></li>
<li><%= t("legal.privacy.s6_item5") %></li>
</ul>
<p>I dati non sono venduti a terzi per finalità di profilazione commerciale.</p>
<p><%= t("legal.privacy.s6_outro") %></p>
</section>
<section>
<h2>7. Trasferimenti extra SEE</h2>
<h2><%= t("legal.privacy.s7_title") %></h2>
<p>
Alcuni fornitori (es. Stripe, Google/YouTube) possono trattare dati anche fuori dallo Spazio Economico Europeo.
In tal caso si applicano garanzie adeguate previste dal GDPR (clausole contrattuali standard, decisioni di adeguatezza).
<%= t("legal.privacy.s7_body") %>
</p>
</section>
<section>
<h2>8. Conservazione</h2>
<h2><%= t("legal.privacy.s8_title") %></h2>
<ul>
<li><strong>Account utente:</strong> per tutta la durata del rapporto contrattuale e, dopo la cessazione, per il tempo necessario a obblighi di legge o contestazioni (in genere fino a 10 anni per documenti contabili ove applicabile).</li>
<li><strong>Registrazioni partite (replay):</strong> secondo il piano (es. 30 o 90 giorni), poi cancellazione automatica o su richiesta.</li>
<li><strong>Log tecnici:</strong> periodo limitato (es. 3090 giorni) salvo obblighi di sicurezza.</li>
<li><strong>Token reset password:</strong> validità massima <%= MatchLiveTv.password_reset_expiry_hours %> ore.</li>
<li><%= raw t("legal.privacy.s8_item1_html") %></li>
<li><%= raw t("legal.privacy.s8_item2_html") %></li>
<li><%= raw t("legal.privacy.s8_item3_html") %></li>
<li><%= raw t("legal.privacy.s8_item4_html", hours: MatchLiveTv.password_reset_expiry_hours) %></li>
</ul>
</section>
<section>
<h2>9. Diritti degli interessati</h2>
<p>In qualità di interessato hai diritto di:</p>
<h2><%= t("legal.privacy.s9_title") %></h2>
<p><%= t("legal.privacy.s9_intro") %></p>
<ul>
<li>accedere ai tuoi dati e ottenerne copia;</li>
<li>rettificarli se inesatti;</li>
<li>chiederne la cancellazione (nei limiti di legge);</li>
<li>limitare o opporti al trattamento, ove applicabile;</li>
<li>revocare il consenso senza pregiudicare la liceità del trattamento basato sul consenso prestato prima della revoca;</li>
<li>proporre reclamo allAutorità Garante per la protezione dei dati personali (<a href="https://www.garanteprivacy.it" rel="noopener" target="_blank">garanteprivacy.it</a>).</li>
<li><%= t("legal.privacy.s9_item1") %></li>
<li><%= t("legal.privacy.s9_item2") %></li>
<li><%= t("legal.privacy.s9_item3") %></li>
<li><%= t("legal.privacy.s9_item4") %></li>
<li><%= t("legal.privacy.s9_item5") %></li>
<li>
<%= raw t(
"legal.privacy.s9_item6_html",
garante_link: link_to("garanteprivacy.it", "https://www.garanteprivacy.it", rel: "noopener", target: "_blank")
) %>
</li>
</ul>
<p>Per esercitare i diritti: <a href="mailto:<%= MatchLiveTv.privacy_controller_email %>"><%= MatchLiveTv.privacy_controller_email %></a>.</p>
</section>
<section>
<h2>10. Sicurezza</h2>
<p>
Adottiamo misure tecniche e organizzative adeguate (accesso autenticato, password crittografate,
comunicazioni HTTPS, segregazione ambienti, limitazione accessi staff). Nessun sistema è invulnerabile:
se sospetti un accesso non autorizzato, cambia password e contattaci.
<%= raw t(
"legal.privacy.s9_outro_html",
email_link: link_to(MatchLiveTv.privacy_controller_email, "mailto:#{MatchLiveTv.privacy_controller_email}")
) %>
</p>
</section>
<section>
<h2>11. Cookie e tecnologie simili</h2>
<h2><%= t("legal.privacy.s10_title") %></h2>
<p>
Il sito utilizza cookie tecnici necessari (sessione di login, sicurezza, memorizzazione delle preferenze cookie)
e, previo consenso tramite il banner, <strong>Google Analytics</strong> per statistiche aggregate.
</p>
<p>
Puoi gestire le preferenze in qualsiasi momento dal link «Gestisci cookie» nel footer
o consultare la <%= link_to "Cookie policy", public_cookies_path %>.
<%= t("legal.privacy.s10_body") %>
</p>
</section>
<section>
<h2>12. Modifiche</h2>
<h2><%= t("legal.privacy.s11_title") %></h2>
<p>
Il titolare può aggiornare questa informativa. La data in alto indica lultima revisione.
In caso di modifiche rilevanti, sarà fornita comunicazione adeguata (es. avviso sul sito o email agli account registrati).
<%= raw t("legal.privacy.s11_p1_html") %>
</p>
<p>
<%= raw t(
"legal.privacy.s11_p2_html",
cookie_policy_link: link_to(t("legal.privacy.s11_cookie_policy_link_text"), public_cookies_path)
) %>
</p>
</section>
<p class="legal-back"><%= link_to "← Torna alla home", root_path %></p>
<section>
<h2><%= t("legal.privacy.s12_title") %></h2>
<p>
<%= t("legal.privacy.s12_body") %>
</p>
</section>
<p class="legal-back"><%= link_to t("legal.common.back_home"), root_path %></p>
</div>
+60 -66
View File
@@ -1,134 +1,128 @@
<% content_for :title, "Termini di servizio — Match Live TV" %>
<% content_for :meta_description, "Termini e condizioni d'uso di Match Live TV per società sportive: dirette live, minori, piani, pagamenti Stripe e responsabilità degli utenti." %>
<% content_for :title, t("legal.terms.title") %>
<% content_for :meta_description, t("legal.terms.meta_description") %>
<% content_for :canonical_url, seo_absolute_url(public_termini_path) %>
<div class="wrap legal-doc">
<h1>Termini e condizioni di servizio</h1>
<p class="legal-meta">Ultimo aggiornamento: <%= legal_last_updated %></p>
<h1><%= t("legal.terms.h1") %></h1>
<p class="legal-meta"><%= t("legal.terms.meta", date: legal_last_updated) %></p>
<section>
<h2>1. Oggetto</h2>
<h2><%= t("legal.terms.s1_title") %></h2>
<p>
I presenti Termini regolano luso della piattaforma <strong>Match Live TV</strong> (sito web, applicazione mobile
e servizi connessi) per la gestione di dirette sportive dilettantistiche/giovanili, punteggio live,
condivisione link agli spettatori e funzioni Premium (archivio, YouTube, ecc.).
<%= raw t("legal.terms.s1_p1_html") %>
</p>
<p>
Il servizio è erogato da <strong><%= MatchLiveTv.privacy_controller_name %></strong>
(di seguito «Fornitore»). Registrandoti o utilizzando il servizio accetti integralmente questi Termini
e l<%= link_to "informativa privacy", public_privacy_path %>.
<%= raw t(
"legal.terms.s1_p2_html",
provider_name: MatchLiveTv.privacy_controller_name,
privacy_link: link_to(t("legal.terms.s1_privacy_link_text"), public_privacy_path)
) %>
</p>
</section>
<section>
<h2>2. Chi può usare il servizio</h2>
<h2><%= t("legal.terms.s2_title") %></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>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>
<li><%= raw t("legal.terms.s2_item1_html") %></li>
<li><%= t("legal.terms.s2_item2") %></li>
<li><%= t("legal.terms.s2_item3") %></li>
</ul>
</section>
<section>
<h2>3. Account, piani e pagamenti</h2>
<h2><%= t("legal.terms.s3_title") %></h2>
<p>
Sono disponibili piani Free e Premium (Light / Full) con limiti su staff, dirette concorrenti, archivio e integrazioni.
I prezzi sono indicati nella pagina <%= link_to "Prezzi", public_prezzi_path %>.
<%= raw t(
"legal.terms.s3_p1_html",
pricing_link: link_to(t("legal.terms.s3_pricing_link_text"), public_prezzi_path)
) %>
</p>
<p>
I pagamenti dei piani a pagamento sono gestiti da <strong>Stripe</strong>. Accettando il checkout,
accetti anche i termini di Stripe per il pagamento. Il Fornitore non memorizza i dati completi della carta.
<%= raw t("legal.terms.s3_p2_html") %>
</p>
<p>Rinnovi, disdetta e diritto di recesso seguono quanto comunicato al momento dellacquisto e la normativa applicabile.</p>
<p><%= t("legal.terms.s3_p3") %></p>
</section>
<section>
<h2>4. Dirette, contenuti e protezione dei minori</h2>
<p><strong>Responsabilità della società sportiva</strong></p>
<h2><%= t("legal.terms.s4_title") %></h2>
<p><strong><%= t("legal.terms.s4_lead1") %></strong></p>
<p>
La società che avvia una diretta è responsabile dei contenuti trasmessi (immagini, audio, commenti)
e della conformità alle leggi, al regolamento federale e ai consensi necessari, in particolare quando
sono ripresi <strong>atleti minorenni</strong>.
<%= raw t("legal.terms.s4_p1_html") %>
</p>
<p>La società si impegna a:</p>
<p><%= t("legal.terms.s4_p2") %></p>
<ul>
<li>ottenere e conservare i consensi genitoriali/tutori o altra base giuridica prima della ripresa;</li>
<li>informare genitori e atleti su dove e per quanto tempo è visibile la diretta/archivio;</li>
<li>non trasmettere contenuti illegali, diffamatori, violenti o non pertinenti allevento sportivo;</li>
<li>rispettare il diritto allimmagine e alla voce di tutte le persone inquadrate.</li>
<li><%= t("legal.terms.s4_item1") %></li>
<li><%= t("legal.terms.s4_item2") %></li>
<li><%= t("legal.terms.s4_item3") %></li>
<li><%= t("legal.terms.s4_item4") %></li>
</ul>
<p>
Il Fornitore può sospendere o interrompere trasmissioni e account in caso di segnalazioni fondate
o violazioni gravi, senza pregiudizio di azioni legali.
<%= t("legal.terms.s4_p3") %>
</p>
</section>
<section>
<h2>5. Uso consentito e divieti</h2>
<p>È vietato:</p>
<h2><%= t("legal.terms.s5_title") %></h2>
<p><%= t("legal.terms.s5_intro") %></p>
<ul>
<li>usare il servizio per finalità non sportive o commerciali non autorizzate;</li>
<li>aggirare limiti tecnici o di piano (staff, dirette concorrenti);</li>
<li>condividere credenziali o pubblicare link su canali che espongono minori a rischi non previsti dal consenso;</li>
<li>caricare malware, effettuare attacchi o interferire con linfrastruttura;</li>
<li>rivendere il servizio senza accordo scritto con il Fornitore.</li>
<li><%= t("legal.terms.s5_item1") %></li>
<li><%= t("legal.terms.s5_item2") %></li>
<li><%= t("legal.terms.s5_item3") %></li>
<li><%= t("legal.terms.s5_item4") %></li>
<li><%= t("legal.terms.s5_item5") %></li>
</ul>
</section>
<section>
<h2>6. Proprietà intellettuale</h2>
<h2><%= t("legal.terms.s6_title") %></h2>
<p>
Marchi, software, design e documentazione del servizio restano del Fornitore o dei suoi licenzianti.
La società sportiva conserva i diritti sui propri contenuti video; concede al Fornitore una licenza
limitata a ospitarli, elaborarli e distribuirli tramite la piattaforma per erogare il servizio.
<%= t("legal.terms.s6_body") %>
</p>
</section>
<section>
<h2>7. Disponibilità e limitazioni</h2>
<h2><%= t("legal.terms.s7_title") %></h2>
<p>
Il servizio dipende da reti mobili, hardware e fornitori terzi. Non garantiamo assenza assoluta di interruzioni,
ritardi o perdita di dati, pur adottando misure ragionevoli. La qualità video può variare in base alla connessione in palestra.
<%= t("legal.terms.s7_body") %>
</p>
</section>
<section>
<h2>8. Limitazione di responsabilità</h2>
<h2><%= t("legal.terms.s8_title") %></h2>
<p>
Nei limiti consentiti dalla legge italiana, il Fornitore non risponde di danni indiretti, perdita di profitto,
o contenuti trasmessi dagli utenti. La responsabilità complessiva verso un cliente professionale non eccede,
ove possibile, limporto pagato negli ultimi 12 mesi per il piano sottoscritto.
<%= t("legal.terms.s8_p1") %>
</p>
<p>Nulla esclude responsabilità per dolo o colpa grave, né diritti inderogabili del consumatore ove applicabili.</p>
<p><%= t("legal.terms.s8_p2") %></p>
</section>
<section>
<h2>9. Recesso, sospensione e cancellazione</h2>
<h2><%= t("legal.terms.s9_title") %></h2>
<p>
Puoi richiedere la chiusura dellaccount scrivendo a
<a href="mailto:<%= MatchLiveTv.privacy_controller_email %>"><%= MatchLiveTv.privacy_controller_email %></a>.
Il Fornitore può sospendere laccount per inadempienza, mancato pagamento o violazione dei Termini.
Alla cessazione, i dati sono trattati come indicato in privacy (conservazione limitata o cancellazione).
<%= raw t(
"legal.terms.s9_body_html",
email_link: link_to(MatchLiveTv.privacy_controller_email, "mailto:#{MatchLiveTv.privacy_controller_email}")
) %>
</p>
</section>
<section>
<h2>10. Legge applicabile e foro</h2>
<h2><%= t("legal.terms.s10_title") %></h2>
<p>
I Termini sono regolati dalla <strong>legge italiana</strong>. Per controversie con consumatori si applicano
le norme inderogabili di protezione del consumatore e il foro del consumatore ove previsto.
Per clienti professionali, foro competente in Italia presso la sede del Fornitore, salvo diverso accordo.
<%= raw t("legal.terms.s10_body_html") %>
</p>
</section>
<section>
<h2>11. Contatti</h2>
<h2><%= t("legal.terms.s11_title") %></h2>
<p>
<%= MatchLiveTv.privacy_controller_name %><br>
<%= MatchLiveTv.privacy_controller_address %><br>
Email: <a href="mailto:<%= MatchLiveTv.privacy_controller_email %>"><%= MatchLiveTv.privacy_controller_email %></a>
<%= raw t(
"legal.terms.s11_body_html",
provider_name: MatchLiveTv.privacy_controller_name,
provider_address: MatchLiveTv.privacy_controller_address,
email_link: link_to(MatchLiveTv.privacy_controller_email, "mailto:#{MatchLiveTv.privacy_controller_email}")
) %>
</p>
</section>
<p class="legal-back"><%= link_to "← Torna alla home", root_path %> · <%= link_to "Privacy", public_privacy_path %></p>
<p class="legal-back"><%= link_to t("legal.common.back_home"), root_path %> · <%= link_to t("legal.common.privacy_nav"), public_privacy_path %></p>
</div>
@@ -1,26 +1,26 @@
<% content_for :title, "Nuova password — Match Live TV" %>
<% content_for :title, t("auth.password_reset.meta_title") %>
<% content_for :robots, "noindex, nofollow" %>
<section class="auth-page">
<h1>Scegli una nuova password</h1>
<h1><%= t("auth.password_reset.title") %></h1>
<div class="card">
<%= form_with url: public_password_reset_path, method: :patch, local: true do %>
<%= hidden_field_tag :token, @token %>
<%= render "shared/input_toggle",
name: :password,
label: "Nuova password (min. 8 caratteri)",
label: t("auth.password_reset.new_password_label"),
input_type: "text",
required: true,
minlength: 8,
autocomplete: "new-password" %>
<%= render "shared/input_toggle",
name: :password_confirmation,
label: "Conferma password",
label: t("auth.password_confirmation"),
input_type: "text",
required: true,
autocomplete: "new-password" %>
<%= submit_tag "Salva password", class: "btn btn-primary" %>
<%= submit_tag t("auth.password_reset.submit"), class: "btn btn-primary" %>
<% end %>
<p class="auth-footer"><%= link_to "Torna al login", public_login_path %></p>
<p class="auth-footer"><%= link_to t("auth.password_reset.back_to_login"), public_login_path %></p>
</div>
</section>
@@ -1,15 +1,15 @@
<% content_for :title, "Password dimenticata — Match Live TV" %>
<% content_for :title, t("auth.password_forgot.meta_title") %>
<% content_for :robots, "noindex, nofollow" %>
<section class="auth-page">
<h1>Password dimenticata</h1>
<p class="auth-lead">Inserisci lemail dellaccount: ti invieremo un link per reimpostare la password.</p>
<h1><%= t("auth.password_forgot.title") %></h1>
<p class="auth-lead"><%= t("auth.password_forgot.lead") %></p>
<div class="card">
<%= form_with url: public_password_forgot_path, method: :post, local: true do %>
<%= label_tag :email, "Email" %>
<%= label_tag :email, t("auth.email") %>
<%= email_field_tag :email, params[:email], required: true, autocomplete: "email" %>
<%= submit_tag "Invia link di reset", class: "btn btn-primary" %>
<%= submit_tag t("auth.password_forgot.submit"), class: "btn btn-primary" %>
<% end %>
<p class="auth-footer"><%= link_to "Torna al login", public_login_path %></p>
<p class="auth-footer"><%= link_to t("auth.password_forgot.back_to_login"), public_login_path %></p>
</div>
</section>
@@ -22,4 +22,4 @@
<button type="button" class="regia-btn regia-btn--minus" data-action="away_undo" style="width:100%;margin-top:4px"></button>
</div>
</div>
<button type="button" class="regia-btn regia-btn--yellow" data-action="advance_period" style="margin-top:12px">Prossimo quarto</button>
<button type="button" class="regia-btn regia-btn--yellow" data-action="advance_period" style="margin-top:12px"><%= t("regia.board.next_period") %></button>
@@ -1,4 +1,4 @@
<p class="regia-sets" id="sets-line">Punteggio libero</p>
<p class="regia-sets" id="sets-line"><%= t("regia.board.generic_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></p>
@@ -18,4 +18,4 @@
<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="advance_period" style="margin-top:12px">Prossimo tempo</button>
<button type="button" class="regia-btn regia-btn--yellow" data-action="advance_period" style="margin-top:12px"><%= t("regia.board.next_half") %></button>
@@ -1,10 +1,10 @@
<% data = @score.data || {} %>
<p class="regia-sets" id="sets-line">Cronometro</p>
<p class="regia-sets" id="sets-line"><%= t("regia.board.stopwatch") %></p>
<p class="regia-score-line" id="score-line" hidden></p>
<p class="regia-partials" id="partials-line" hidden></p>
<p class="regia-clock-line" id="clock-line"><%= format_clock_secs(data["clock_secs"], count_up: true) %></p>
<div class="regia-clock-actions" style="margin-top:16px">
<button type="button" class="regia-btn regia-btn--outline" data-action="clock_toggle"><%= data["clock_running"] ? "Pausa" : "Avvia" %></button>
<button type="button" class="regia-btn regia-btn--outline" data-action="clock_reset">Reset</button>
<button type="button" class="regia-btn regia-btn--outline" data-action="clock_toggle"><%= data["clock_running"] ? t("regia.board.pause") : t("regia.board.start") %></button>
<button type="button" class="regia-btn regia-btn--outline" data-action="clock_reset"><%= t("regia.board.reset") %></button>
</div>
@@ -1,7 +1,7 @@
<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) %>
<%= t("regia.board.partials_prefix") %> <%= live_score_partials_label(@score) %>
</p>
<div class="regia-team-row">
@@ -12,7 +12,7 @@
<button type="button" class="regia-btn regia-btn--minus" data-action="home_undo" style="width:100%;margin-top:6px"></button>
</div>
<div class="regia-center-hint" id="center-hint">
<%= Scoring::Rules.from_match(@match).points_target(@score.current_set) %> pt
<%= Scoring::Rules.from_match(@match).points_target(@score.current_set) %> <%= t("regia.board.points_suffix") %>
</div>
<div>
<div class="regia-team-name"><%= @match.opponent_name %></div>
@@ -21,4 +21,4 @@
<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>
<button type="button" class="regia-btn regia-btn--yellow" data-action="close_set" style="margin-top:12px"><%= t("regia.board.close_set") %></button>
+16 -15
View File
@@ -1,4 +1,4 @@
<% content_for :title, "Regia#{@team.name} vs #{@match.opponent_name}" %>
<% content_for :title, t("regia.title", home: @team.name, away: @match.opponent_name) %>
<% content_for :robots, "noindex, nofollow" %>
<% content_for :head do %>
@@ -8,6 +8,7 @@
<% end %>
<div class="regia-page" id="regia-app"
data-i18n="<%= j regia_js_i18n_json %>"
data-token="<%= j params[:token] %>"
data-board="<%= @match.effective_board_type %>"
data-status-url="<%= j public_regia_status_path(params[:token]) %>"
@@ -29,34 +30,34 @@
<header class="regia-header">
<h1><%= @team.name %> vs <%= @match.opponent_name %></h1>
<p id="regia-subtitle">Regia · <%= regia_header_subtitle(@match, @score) %></p>
<p id="regia-subtitle"><%= t("regia.subtitle_prefix") %> <%= regia_header_subtitle(@match, @score) %></p>
<span id="regia-badge" class="regia-badge <%= @stream_closed ? 'regia-badge--ended' : (@session.paused? ? 'regia-badge--wait' : (@on_air ? 'regia-badge--live' : 'regia-badge--wait')) %>">
<%= @stream_closed ? "Terminata" : (@session.paused? ? "In pausa" : (@on_air ? "In onda" : "In attesa")) %>
<%= @stream_closed ? t("regia.status.ended") : (@session.paused? ? t("regia.status.paused") : (@on_air ? t("regia.status.live") : t("regia.status.waiting"))) %>
</span>
</header>
<% if @live_share_url.present? %>
<div class="regia-share-group">
<p class="regia-share-label">Link diretta (spettatori)</p>
<p class="regia-share-label"><%= t("regia.live_share_label") %></p>
<div class="regia-share">
<button type="button" class="regia-btn regia-btn--outline" id="btn-share-live" style="margin:0">
Condividi diretta
<%= t("regia.share_live") %>
</button>
<button type="button" class="regia-btn regia-btn--outline" id="btn-copy-live" style="margin:0">
Copia link
<%= t("regia.copy_link") %>
</button>
</div>
</div>
<% end %>
<div class="regia-share-group">
<p class="regia-share-label">Link regia (punteggio)</p>
<p class="regia-share-label"><%= t("regia.regia_share_label") %></p>
<div class="regia-share">
<button type="button" class="regia-btn regia-btn--outline" id="btn-share-regia" style="margin:0">
Condividi link regia
<%= t("regia.share_regia") %>
</button>
<button type="button" class="regia-btn regia-btn--outline" id="btn-copy-regia" style="margin:0">
Copia link
<%= t("regia.copy_link") %>
</button>
</div>
</div>
@@ -66,7 +67,7 @@
<video id="regia-preview" playsinline muted autoplay></video>
<% end %>
<div id="regia-preview-placeholder" class="regia-preview__placeholder">
<%= @stream_closed ? "Diretta terminata" : "Anteprima in attesa del segnale…" %>
<%= @stream_closed ? t("regia.preview_ended") : t("regia.preview_waiting") %>
</div>
</div>
@@ -75,8 +76,8 @@
</section>
<% unless @stream_closed %>
<button type="button" class="regia-btn regia-btn--outline" id="btn-pause"><%= @session.paused? ? "Riprendi diretta" : "Metti in pausa" %></button>
<button type="button" class="regia-btn regia-btn--danger" id="btn-stop">Chiudi diretta</button>
<button type="button" class="regia-btn regia-btn--outline" id="btn-pause"><%= @session.paused? ? t("regia.resume") : t("regia.pause") %></button>
<button type="button" class="regia-btn regia-btn--danger" id="btn-stop"><%= t("regia.stop") %></button>
<% end %>
</div>
@@ -84,11 +85,11 @@
<div id="regia-modal" class="regia-modal" aria-hidden="true">
<div class="regia-modal__card">
<h2 id="modal-title">Set vinto</h2>
<h2 id="modal-title"><%= t("regia.modal.set_won_title") %></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>
<button type="button" class="regia-btn regia-btn--yellow" id="modal-confirm"><%= t("regia.modal.confirm") %></button>
<button type="button" class="regia-btn regia-btn--outline" id="modal-cancel"><%= t("regia.modal.cancel") %></button>
</div>
</div>
</div>
@@ -1,18 +1,18 @@
<% content_for :title, "Registra la squadra — Match Live TV" %>
<% content_for :meta_description, "Iscrivi la tua società sportiva giovanile su Match Live TV: dirette live da telefono, link per genitori e piani Free o Premium." %>
<% content_for :title, t("auth.signup.meta_title") %>
<% content_for :meta_description, t("auth.signup.meta_description") %>
<% content_for :robots, "noindex, follow" %>
<section class="auth-page">
<h1>Registra la squadra</h1>
<p class="auth-lead">Crea l'account del referente (coach o dirigente), poi configurerai la squadra.</p>
<h1><%= t("auth.signup.title") %></h1>
<p class="auth-lead"><%= t("auth.signup.lead") %></p>
<div class="card">
<%= form_with model: @user, url: public_signup_path do |f| %>
<%= f.label :name, "Nome" %>
<%= f.label :name, t("auth.signup.name_label") %>
<%= f.text_field :name, required: true %>
<%= render "shared/input_toggle",
name: "user[email]",
id: "user_email",
label: "Email",
label: t("auth.email"),
value: @user.email,
input_type: "email",
required: true,
@@ -20,7 +20,7 @@
<%= render "shared/input_toggle",
name: "user[password]",
id: "user_password",
label: "Password",
label: t("auth.password"),
input_type: "text",
required: true,
minlength: 8,
@@ -28,24 +28,25 @@
<%= render "shared/input_toggle",
name: "user[password_confirmation]",
id: "user_password_confirmation",
label: "Conferma password",
label: t("auth.password_confirmation"),
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">
Ho letto e accetto l<%= link_to "informativa privacy", public_privacy_path, target: "_blank", rel: "noopener" %>
e i <%= link_to "termini di servizio", public_termini_path, target: "_blank", rel: "noopener" %>.
<%= raw t(
"auth.signup.legal_accept_html",
privacy_link: link_to(t("auth.signup.privacy_link"), public_privacy_path, target: "_blank", rel: "noopener"),
terms_link: link_to(t("auth.signup.terms_link"), public_termini_path, target: "_blank", rel: "noopener")
) %>
</span>
</label>
<p class="legal-accept-hint">
Registrandoti dichiari di essere maggiorenne e, se operi per una società sportiva con atleti minorenni,
di avere legittima facoltà (es. dirigente, coach autorizzato) e di rispettare le regole su immagini dei minori
descritte in privacy.
<%= t("auth.signup.legal_hint") %>
</p>
<%= f.submit "Continua", class: "btn btn-primary" %>
<%= f.submit t("auth.signup.submit"), class: "btn btn-primary" %>
<% end %>
<p class="auth-footer">Hai già un account? <%= link_to "Accedi", public_login_path %></p>
<p class="auth-footer"><%= raw t("auth.signup.has_account_html", login_link: link_to(t("auth.signup.login_link"), public_login_path)) %></p>
</div>
</section>
+18 -18
View File
@@ -1,11 +1,11 @@
<% content_for :title, "Replay — Match Live TV" %>
<% content_for :meta_description, "Archivio replay delle dirette sportive su Match Live TV. Cerca per società, squadra o avversario." %>
<% content_for :title, t("replay.index.meta_title") %>
<% content_for :meta_description, t("replay.index.meta_description") %>
<div class="wrap replay-index">
<%= link_to "← Dirette live", public_live_index_path, class: "back-link" %>
<%= link_to t("replay.index.back_to_live"), public_live_index_path, class: "back-link" %>
<h1>Live passate</h1>
<p class="results-hint">Replay pubblici delle società sportive — riguarda le partite già trasmesse.</p>
<h1><%= t("replay.index.title") %></h1>
<p class="results-hint"><%= t("replay.index.hint") %></p>
<%= form_with url: public_replay_index_path, method: :get, local: true, class: "search-form" do %>
<% if params[:club_id].present? %>
@@ -15,13 +15,13 @@
type="search"
name="q"
value="<%= @query %>"
placeholder="Es. Tigers Volley, avversario, società, luogo…"
aria-label="Cerca replay"
placeholder="<%= t("replay.index.search_placeholder") %>"
aria-label="<%= t("replay.index.search_aria_label") %>"
autocomplete="off"
/>
<button type="submit" class="btn btn-primary">Cerca</button>
<button type="submit" class="btn btn-primary"><%= t("replay.index.search_button") %></button>
<% if @query.present? || params[:club_id].present? %>
<%= link_to "Azzera", public_replay_index_path, class: "btn btn-secondary" %>
<%= link_to t("replay.index.reset_link"), public_replay_index_path, class: "btn btn-secondary" %>
<% end %>
<% end %>
@@ -30,9 +30,9 @@
<% if @query.present? %>
<%= hidden_field_tag :q, @query %>
<% end %>
<label for="club_id" class="muted">Società</label>
<label for="club_id" class="muted"><%= t("replay.index.club_filter_label") %></label>
<select name="club_id" id="club_id" class="input" onchange="this.form.submit()">
<option value="">Tutte le società</option>
<option value=""><%= t("replay.index.club_filter_all") %></option>
<% @clubs.each do |club| %>
<option value="<%= club.id %>"<%= " selected" if params[:club_id].to_s == club.id.to_s %>><%= club.name %></option>
<% end %>
@@ -41,9 +41,9 @@
<% end %>
<% if @query.present? %>
<% club_suffix = @filter_club ? t("replay.index.results_hint_club_suffix", name: @filter_club.name) : "" %>
<p class="results-hint">
Risultati per «<%= h @query %>»<% if @filter_club %> · <%= @filter_club.name %><% end %>:
<%= @recordings.size %> replay
<%= t("replay.index.results_hint", query: @query, club_suffix: club_suffix, count: @recordings.size) %>
</p>
<% end %>
@@ -81,12 +81,12 @@
<% else %>
<div class="empty-state">
<% if @query.present? || params[:club_id].present? %>
<p><strong>Nessun replay trovato</strong> con i filtri selezionati.</p>
<p><%= link_to "Mostra tutti i replay pubblici", public_replay_index_path, class: "btn btn-secondary" %></p>
<p><strong><%= t("replay.index.empty_filtered_title") %></strong><%= t("replay.index.empty_filtered_suffix") %></p>
<p><%= link_to t("replay.index.empty_filtered_show_all"), public_replay_index_path, class: "btn btn-secondary" %></p>
<% else %>
<p><strong>Nessun replay pubblico al momento.</strong></p>
<p>Quando una società rende pubblica una registrazione, comparirà qui.</p>
<%= link_to "Vai alle dirette live", public_live_index_path, class: "btn btn-secondary", style: "margin-top:12px;display:inline-block" %>
<p><strong><%= t("replay.index.empty_default_title") %></strong></p>
<p><%= t("replay.index.empty_default_body") %></p>
<%= link_to t("replay.index.empty_default_live_link"), public_live_index_path, class: "btn btn-secondary", style: "margin-top:12px;display:inline-block" %>
<% end %>
</div>
<% end %>
+21 -21
View File
@@ -1,18 +1,18 @@
<% club_name = @match.team.club&.name %>
<% content_for :title, "Replay#{@recording.title_or_default}" %>
<% content_for :meta_description, "Replay #{@recording.title_or_default} su Match Live TV." %>
<% content_for :title, t("replay.show.title", title: @recording.title_or_default) %>
<% content_for :meta_description, t("replay.show.meta_description", title: @recording.title_or_default) %>
<% content_for :robots, @recording.publicly_listed? ? "index, follow" : "noindex, nofollow" %>
<div class="wrap">
<%= link_to "← Live passate", public_replay_index_path, class: "back-link" %>
<%= link_to t("replay.show.back_to_replays"), public_replay_index_path, class: "back-link" %>
<%= live_match_page_heading(@match) %>
<% if @recording.status == "processing" %>
<div class="stream-ended" role="status">
<div class="stream-ended-icon" aria-hidden="true"></div>
<h2>Replay in elaborazione</h2>
<p>La registrazione viene preparata. Riceverai unemail quando sarà pronta.</p>
<h2><%= t("replay.show.processing_title") %></h2>
<p><%= t("replay.show.processing_body") %></p>
</div>
<% elsif @recording.ready? && @recording.storage_key.present? %>
<div class="live-player-wrap">
@@ -25,18 +25,18 @@
session: @session,
stream_closed: true,
on_air: false,
badge_label: "REPLAY",
badge_label: t("replay.show.badge_label"),
badge_class: "badge-ended" %>
</div>
<p class="replay-show__meta-line">
<%= l_local(@recording.recorded_at_or_fallback) %>
· Durata <%= @recording.duration_label %>
· <%= t("replay.show.meta_line_duration", value: @recording.duration_label) %>
· <%= @recording.views_label %>
<% if @recording.expires_at %>
· Fino al <%= l_local(@recording.expires_at) %>
· <%= t("replay.show.meta_line_until", date: l_local(@recording.expires_at)) %>
<% end %>
· <%= @recording.publicly_listed? ? "Pubblico" : "Privato (link)" %>
· <%= @recording.publicly_listed? ? t("replay.show.meta_line_public") : t("replay.show.meta_line_private") %>
<% if @recording.source_platform_label != "—" %>
· <%= @recording.source_platform_label %>
<% end %>
@@ -46,10 +46,10 @@
<% if (ent.phone_download_enabled? && (logged_in? || @recording.unlisted?)) || @recording.youtube_watch_url %>
<div class="replay-show__actions">
<% if ent.phone_download_enabled? && (logged_in? || @recording.unlisted?) %>
<%= link_to "Scarica MP4", public_replay_download_path(@session), class: "btn btn-primary" %>
<%= link_to t("replay.show.download_link"), public_replay_download_path(@session), class: "btn btn-primary" %>
<% end %>
<% if @recording.youtube_watch_url %>
<%= link_to "Apri su YouTube", @recording.youtube_watch_url, class: "btn btn-secondary", target: "_blank", rel: "noopener" %>
<%= link_to t("replay.show.youtube_link"), @recording.youtube_watch_url, class: "btn btn-secondary", target: "_blank", rel: "noopener" %>
<% end %>
</div>
<% end %>
@@ -65,28 +65,28 @@
session: @session,
stream_closed: true,
on_air: false,
badge_label: "REPLAY",
badge_label: t("replay.show.badge_label"),
badge_class: "badge-ended" %>
</div>
<p class="replay-show__meta-line">
Replay su YouTube (copia server non presente).
<%= link_to "Apri su YouTube", @recording.youtube_watch_url, target: "_blank", rel: "noopener" %>
<%= t("replay.show.youtube_only_body") %>
<%= link_to t("replay.show.youtube_link"), @recording.youtube_watch_url, target: "_blank", rel: "noopener" %>
</p>
<% elsif @recording.ready? %>
<div class="stream-ended" role="status">
<h2>File non disponibile</h2>
<p>Il video non è più sul server (archivio rimosso o in migrazione).</p>
<p class="stream-ended-meta">Durata registrata: <%= @recording.duration_label %> · <%= @recording.byte_size_label %></p>
<h2><%= t("replay.show.file_missing_title") %></h2>
<p><%= t("replay.show.file_missing_body") %></p>
<p class="stream-ended-meta"><%= t("replay.show.file_missing_meta", duration: @recording.duration_label, size: @recording.byte_size_label) %></p>
</div>
<% elsif @recording.status == "failed" %>
<div class="stream-ended" role="status">
<h2>Replay non disponibile</h2>
<p><%= @recording.error_message.presence || "Errore di elaborazione della registrazione." %></p>
<h2><%= t("replay.show.failed_title") %></h2>
<p><%= @recording.error_message.presence || t("replay.show.failed_default_body") %></p>
</div>
<% else %>
<div class="stream-ended" role="status">
<h2>Replay non disponibile</h2>
<p>Questa registrazione non è più accessibile.</p>
<h2><%= t("replay.show.unavailable_title") %></h2>
<p><%= t("replay.show.unavailable_body") %></p>
</div>
<% end %>
</div>
@@ -1,26 +1,26 @@
<% content_for :title, "Accedi — Match Live TV" %>
<% content_for :meta_description, "Accesso area riservata per staff e società registrate su Match Live TV." %>
<% content_for :title, t("auth.login.meta_title") %>
<% content_for :meta_description, t("auth.login.meta_description") %>
<% content_for :robots, "noindex, nofollow" %>
<section class="auth-page">
<h1>Accedi</h1>
<h1><%= t("auth.login.title") %></h1>
<div class="card">
<%= form_with url: public_login_path do |f| %>
<%= render "shared/input_toggle",
name: :email,
label: "Email",
label: t("auth.email"),
value: params[:email],
input_type: "email",
required: true,
autocomplete: "username" %>
<%= render "shared/input_toggle",
name: :password,
label: "Password",
label: t("auth.password"),
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" %>
<p class="auth-forgot"><%= link_to t("auth.login.forgot_password"), public_password_forgot_path %></p>
<%= submit_tag t("auth.login.submit"), class: "btn btn-primary" %>
<% end %>
<p class="auth-footer">Nuovo? <%= link_to "Registrati", public_signup_path %></p>
<p class="auth-footer"><%= raw t("auth.login.new_prompt_html", signup_link: link_to(t("auth.login.signup_link"), public_signup_path)) %></p>
</div>
</section>
@@ -1,12 +1,11 @@
<% content_for :title, "Squadre su Match Live TV — Dirette e replay sport giovanili" %>
<% content_for :meta_description, "Scopri le squadre sportive giovanili che trasmettono su Match Live TV. Cerca per nome, filtra per sport, trova dirette in corso e archivi replay." %>
<% content_for :title, t("team_pages.index.meta_title") %>
<% content_for :meta_description, t("team_pages.index.meta_description") %>
<% content_for :canonical_url, seo_absolute_url(public_team_pages_path(filter_params_for_canonical)) %>
<div class="wrap team-directory">
<h1>Squadre su Match Live TV</h1>
<h1><%= t("team_pages.index.heading") %></h1>
<p class="results-hint">
Società e squadre con dirette, replay pubblici o partite in programma.
Cerca la tua squadra e apri la pagina per seguire calendario e archivio.
<%= t("team_pages.index.hint") %>
</p>
<%= form_with url: public_team_pages_path, method: :get, local: true, class: "team-directory-filters" do %>
@@ -15,18 +14,18 @@
type="search"
name="q"
value="<%= @query %>"
placeholder="Nome squadra, società, città o palestra…"
aria-label="Cerca squadra"
placeholder="<%= t("team_pages.index.search_placeholder") %>"
aria-label="<%= t("team_pages.index.search_aria_label") %>"
autocomplete="off"
/>
<button type="submit" class="btn btn-primary">Cerca</button>
<button type="submit" class="btn btn-primary"><%= t("team_pages.index.search_button") %></button>
</div>
<div class="team-directory-filters__row">
<div class="team-directory-filters__field">
<label for="sport" class="team-directory-filters__label">Sport</label>
<label for="sport" class="team-directory-filters__label"><%= t("team_pages.index.sport_label") %></label>
<select name="sport" id="sport" class="team-directory-filters__select" onchange="this.form.requestSubmit()">
<option value="">Tutti gli sport</option>
<option value=""><%= t("team_pages.index.sport_all") %></option>
<% @sport_options.each do |sport| %>
<option value="<%= sport[:key] %>"<%= " selected" if @sport == sport[:key] %>><%= sport[:label] %></option>
<% end %>
@@ -34,20 +33,20 @@
</div>
<div class="team-directory-filters__field team-directory-filters__field--chips">
<span class="team-directory-filters__label" id="team-filter-chips-label">Mostra</span>
<span class="team-directory-filters__label" id="team-filter-chips-label"><%= t("team_pages.index.chips_label") %></span>
<div class="team-directory-chips" role="group" aria-labelledby="team-filter-chips-label">
<label class="filter-chip<%= " is-active" if @live_filter %>">
<%= check_box_tag :live, "1", @live_filter, id: "filter_live", class: "filter-chip__input", onchange: "this.form.requestSubmit()" %>
<span class="filter-chip__face" aria-hidden="true">
<span class="filter-chip__dot filter-chip__dot--live"></span>
In diretta
<%= t("team_pages.index.chip_live") %>
</span>
</label>
<label class="filter-chip<%= " is-active" if @replays_filter %>">
<%= check_box_tag :replays, "1", @replays_filter, id: "filter_replays", class: "filter-chip__input", onchange: "this.form.requestSubmit()" %>
<span class="filter-chip__face" aria-hidden="true">
<span class="filter-chip__icon"></span>
Con replay
<%= t("team_pages.index.chip_replays") %>
</span>
</label>
</div>
@@ -56,7 +55,7 @@
<% if @query.present? || @sport.present? || @live_filter || @replays_filter %>
<div class="team-directory-filters__actions">
<span class="team-directory-filters__label team-directory-filters__label--spacer" aria-hidden="true">&nbsp;</span>
<%= link_to "Azzera", public_team_pages_path, class: "btn btn-secondary team-directory-filters__reset" %>
<%= link_to t("team_pages.index.reset_link"), public_team_pages_path, class: "btn btn-secondary team-directory-filters__reset" %>
</div>
<% end %>
</div>
@@ -64,12 +63,12 @@
<% if @query.present? || @sport.present? || @live_filter || @replays_filter %>
<p class="results-hint">
<%= @entries.size %> squadre trovate
<% if @query.present? %> per «<%= h @query %>»<% end %>
<% if @sport.present? %> · <%= Sports::Catalog.find_optional(@sport)&.dig(:label) || @sport %><% end %>
<%= t("team_pages.index.results_found", count: @entries.size) %>
<% if @query.present? %><%= t("team_pages.index.results_query_suffix", query: @query) %><% end %>
<% if @sport.present? %><%= t("team_pages.index.results_sport_suffix", sport: Sports::Catalog.find_optional(@sport)&.dig(:label) || @sport) %><% end %>
</p>
<% else %>
<p class="results-hint"><%= @entries.size %> squadre attive</p>
<p class="results-hint"><%= t("team_pages.index.results_active", count: @entries.size) %></p>
<% end %>
<% if @entries.any? %>
@@ -96,13 +95,13 @@
<p class="team-directory-card__meta"><%= team.sport_label %></p>
<div class="team-directory-card__badges">
<% if entry.live_now %>
<span class="badge badge-on-air">In diretta</span>
<span class="badge badge-on-air"><%= t("team_pages.index.badge_live") %></span>
<% end %>
<% if entry.upcoming_count.positive? %>
<span class="badge badge-scheduled"><%= entry.upcoming_count %> in programma</span>
<span class="badge badge-scheduled"><%= t("team_pages.index.badge_upcoming", count: entry.upcoming_count) %></span>
<% end %>
<% if entry.replay_count.positive? %>
<span class="badge badge-wait"><%= entry.replay_count %> replay</span>
<span class="badge badge-wait"><%= t("team_pages.index.badge_replay", count: entry.replay_count) %></span>
<% end %>
</div>
</div>
@@ -112,18 +111,18 @@
<% else %>
<div class="empty-state">
<% if @query.present? || @sport.present? || @live_filter || @replays_filter %>
<p><strong>Nessuna squadra trovata</strong> con i filtri selezionati.</p>
<p><%= link_to "Mostra tutte le squadre attive", public_team_pages_path, class: "btn btn-secondary" %></p>
<p><strong><%= t("team_pages.index.empty_filtered_title") %></strong><%= t("team_pages.index.empty_filtered_suffix") %></p>
<p><%= link_to t("team_pages.index.empty_filtered_show_all"), public_team_pages_path, class: "btn btn-secondary" %></p>
<% else %>
<p><strong>Nessuna squadra attiva al momento.</strong></p>
<p>Quando una società avvierà dirette o pubblicherà replay, comparirà qui.</p>
<%= link_to "Vai alle dirette live", public_live_index_path, class: "btn btn-secondary", style: "margin-top:12px;display:inline-block" %>
<p><strong><%= t("team_pages.index.empty_default_title") %></strong></p>
<p><%= t("team_pages.index.empty_default_body") %></p>
<%= link_to t("team_pages.index.empty_default_live_link"), public_live_index_path, class: "btn btn-secondary", style: "margin-top:12px;display:inline-block" %>
<% end %>
</div>
<% end %>
<p class="team-directory-footer muted">
<%= link_to "Dirette in corso", public_live_index_path %> ·
<%= link_to "Archivio replay", public_replay_index_path %>
<%= link_to t("team_pages.index.footer_live_link"), public_live_index_path %> ·
<%= link_to t("team_pages.index.footer_replay_link"), public_replay_index_path %>
</p>
</div>
@@ -1,4 +1,4 @@
<% content_for :title, "#{@team.name}#{@club.name} | Match Live TV" %>
<% content_for :title, t("team_pages.show.title", team: @team.name, club: @club.name) %>
<% content_for :meta_description, team_page_meta_description(@team, @club) %>
<% content_for :canonical_url, seo_absolute_url(public_team_page_path(@team.slug)) %>
<% content_for :head do %>
@@ -9,7 +9,7 @@
<div class="wrap team-public-page">
<nav class="team-dashboard-nav" aria-label="Percorso">
<%= link_to "← Tutte le squadre", public_team_pages_path, class: "team-dashboard-nav__club" %>
<%= link_to t("team_pages.show.breadcrumb_all_teams"), public_team_pages_path, class: "team-dashboard-nav__club" %>
</nav>
<header class="roster-hero card team-public-hero" style="--club-primary:<%= @team.effective_primary_color %>;--club-secondary:<%= @team.effective_secondary_color %>">
@@ -19,7 +19,7 @@
<p class="team-public-meta">
<%= @team.sport_label %>
<% if @live_sessions.any? %>
· <span class="hero-live-dot" aria-hidden="true"></span> <strong>In diretta ora</strong>
· <span class="hero-live-dot" aria-hidden="true"></span> <strong><%= t("team_pages.show.live_now_label") %></strong>
<% end %>
</p>
<% if @team.description.present? %>
@@ -27,11 +27,11 @@
<% end %>
<div class="team-public-actions">
<% if @live_sessions.any? %>
<%= link_to "Guarda la diretta", public_live_path(@live_sessions.first), class: "btn btn-primary" %>
<%= link_to t("team_pages.show.watch_live_link"), public_live_path(@live_sessions.first), class: "btn btn-primary" %>
<% end %>
<%= link_to "Tutte le dirette", public_live_index_path(club_id: @club.id), class: "btn btn-secondary" %>
<%= link_to t("team_pages.show.all_live_link"), public_live_index_path(club_id: @club.id), class: "btn btn-secondary" %>
<% if @recordings.any? %>
<%= link_to "Archivio replay", public_replay_index_path(team_id: @team.id), class: "btn btn-secondary" %>
<%= link_to t("team_pages.show.replay_archive_link"), public_replay_index_path(team_id: @team.id), class: "btn btn-secondary" %>
<% end %>
</div>
</div>
@@ -51,7 +51,7 @@
<% if @live_sessions.any? %>
<section class="team-public-section" aria-labelledby="team-live-heading">
<h2 id="team-live-heading" class="section-heading">In diretta adesso</h2>
<h2 id="team-live-heading" class="section-heading"><%= t("team_pages.show.section_live_now") %></h2>
<div class="live-grid">
<% @live_sessions.each do |session| %>
<% match = session.match %>
@@ -66,21 +66,21 @@
<p class="card-score">
<span class="card-sets"><%= live_score_sets_label(session.score_state, match) %></span>
<% if live_score_partials_label(session.score_state).present? %>
<span class="card-partials">Parziali: <%= live_score_partials_label(session.score_state) %></span>
<span class="card-partials"><%= t("score.partials_prefix", value: live_score_partials_label(session.score_state)) %></span>
<% end %>
<span class="card-points"><%= session.score_state.home_points %> - <%= session.score_state.away_points %></span>
</p>
<% end %>
<div class="badges">
<% if on_air %>
<span class="badge badge-on-air">In onda</span>
<span class="badge badge-on-air"><%= t("live.index.badge_on_air") %></span>
<% elsif session.paused? %>
<span class="badge badge-connecting">In pausa</span>
<span class="badge badge-connecting"><%= t("live.index.badge_paused") %></span>
<% else %>
<span class="badge badge-live">Live</span>
<span class="badge badge-live"><%= t("live.index.badge_live") %></span>
<% end %>
</div>
<%= link_to "Guarda diretta →", public_live_path(session), class: "btn-watch" %>
<%= link_to t("live.index.watch_link"), public_live_path(session), class: "btn-watch" %>
</article>
<% end %>
</div>
@@ -89,8 +89,8 @@
<% if @upcoming_matches.any? %>
<section class="team-public-section" aria-labelledby="team-upcoming-heading">
<h2 id="team-upcoming-heading" class="section-heading<%= " section-heading--spaced" if @live_sessions.any? %>">Prossime partite</h2>
<p class="results-hint">Programmate dal club: la diretta partirà quando lo staff avvierà la trasmissione.</p>
<h2 id="team-upcoming-heading" class="section-heading<%= " section-heading--spaced" if @live_sessions.any? %>"><%= t("team_pages.show.section_upcoming") %></h2>
<p class="results-hint"><%= t("team_pages.show.upcoming_hint") %></p>
<div class="live-grid upcoming-grid">
<% @upcoming_matches.each do |match| %>
<article class="live-card live-card--upcoming">
@@ -103,7 +103,7 @@
</p>
<p class="upcoming-when"><%= live_scheduled_relative(match.scheduled_at) %></p>
<div class="badges">
<span class="badge badge-scheduled">In programma</span>
<span class="badge badge-scheduled"><%= t("live.index.badge_scheduled") %></span>
</div>
</article>
<% end %>
@@ -113,7 +113,7 @@
<% if @recordings.any? %>
<section class="team-public-section" aria-labelledby="team-replays-heading">
<h2 id="team-replays-heading" class="section-heading section-heading--spaced">Replay recenti</h2>
<h2 id="team-replays-heading" class="section-heading section-heading--spaced"><%= t("team_pages.show.section_replays") %></h2>
<div class="replay-grid">
<% @recordings.each do |rec| %>
<% match = rec.stream_session.match %>
@@ -137,21 +137,21 @@
<% end %>
</div>
<p class="team-public-more">
<%= link_to "Vedi tutti i replay di #{@team.name} →", public_replay_index_path(team_id: @team.id) %>
<%= link_to t("team_pages.show.replays_more_link", team: @team.name), public_replay_index_path(team_id: @team.id) %>
</p>
</section>
<% end %>
<% if @roster_by_category %>
<section class="team-public-section" aria-labelledby="team-roster-heading">
<h2 id="team-roster-heading" class="section-heading section-heading--spaced">Organico</h2>
<h2 id="team-roster-heading" class="section-heading section-heading--spaced"><%= t("team_pages.show.section_roster") %></h2>
<div class="team-public-roster">
<% TeamRosterMember::DISPLAY_ORDER.each do |category| %>
<% members = @roster_by_category[category] %>
<% next if members.blank? %>
<section class="roster-section card">
<header class="roster-section__head">
<h3 class="roster-section__title"><%= TeamRosterMember::CATEGORY_LABELS[category] %></h3>
<h3 class="roster-section__title"><%= TeamRosterMember.category_label(category) %></h3>
<span class="roster-section__count"><%= members.size %></span>
</header>
<div class="roster-list">
@@ -167,9 +167,9 @@
<% if @live_sessions.empty? && @upcoming_matches.empty? && @recordings.empty? %>
<div class="empty-state empty-state--soft team-public-empty">
<p><strong>Nessuna diretta o replay al momento.</strong></p>
<p>Torna a controllare prima delle prossime gare: qui compariranno dirette, calendario e archivio della squadra.</p>
<%= link_to "Vedi tutte le dirette", public_live_index_path, class: "btn btn-secondary" %>
<p><strong><%= t("team_pages.show.empty_title") %></strong></p>
<p><%= t("team_pages.show.empty_body") %></p>
<%= link_to t("team_pages.show.empty_link"), public_live_index_path, class: "btn btn-secondary" %>
</div>
<% end %>
</div>
@@ -1,8 +1,8 @@
<% content_for :title, "Modifica — #{@member.full_name}" %>
<% content_for :title, t("roster.edit.title", name: @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>
<p class="muted"><%= link_to t("roster.back_to_details"), public_team_details_path(@team) %></p>
<h1><%= @member.full_name %></h1>
<%= render "shared/roster_member_form", member: @member, team: @team %>
</div>
@@ -10,38 +10,37 @@
<% if can_manage %>
<% if owner_membership&.staff_kind.blank? %>
<div class="card team-streaming-staff__self">
<h2>Il tuo account (<%= current_user.email %>)</h2>
<h2><%= t("team.streaming_staff.self_account_heading", email: current_user.email) %></h2>
<p class="muted">
Puoi gestire la trasmissione dallapp. Per il punteggio da un altro telefono, condividi il
<strong>link regia</strong> durante la diretta (non serve un secondo account).
<%= raw t("team.streaming_staff.self_account_hint") %>
</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" %>
<%= button_to t("team.streaming_staff.self_assign_submit"), 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>
<h2><%= t("team.streaming_staff.staff_heading") %></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>
<thead><tr><th><%= t("team.streaming_staff.col_name") %></th><th><%= t("team.streaming_staff.col_email") %></th><th><%= t("team.streaming_staff.col_role") %></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 %>
<% if ut.user_id == current_user.id %><span class="muted"> <%= t("team.streaming_staff.you_suffix") %></span><% end %>
</td>
<td><%= ut.user.email %></td>
<td>Trasmissione</td>
<td><%= t("team.streaming_staff.role_transmission") %></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?" } } %>
<%= button_to t("team.streaming_staff.clear_self_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: t("team.streaming_staff.clear_self_staff_confirm"), confirm_kind: "delete" } } %>
<% 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}?" } } %>
<%= button_to t("team.streaming_staff.revoke"), 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: t("team.streaming_staff.revoke_confirm", name: ut.user.name), confirm_kind: "delete" } } %>
<% end %>
</td>
<% end %>
@@ -51,42 +50,42 @@
</table>
<% else %>
<p class="muted">
Nessun responsabile trasmissione.
<% if can_manage %><%= link_to "Invita qualcuno", public_team_invite_path(team) %>.<% end %>
<%= t("team.streaming_staff.no_staff") %>
<% if can_manage %><%= link_to t("team.streaming_staff.invite_someone"), public_team_invite_path(team) %>.<% end %>
</p>
<% end %>
</div>
<% if can_manage %>
<h2>Inviti in attesa</h2>
<h2><%= t("team.streaming_staff.pending_invitations_heading") %></h2>
<div class="card">
<% if pending_invitations.any? %>
<table class="data">
<thead><tr><th>Email</th><th>Scade</th><th></th></tr></thead>
<thead><tr><th><%= t("team.streaming_staff.col_email_short") %></th><th><%= t("team.streaming_staff.col_expires") %></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" %>
<%= button_to t("team.streaming_staff.cancel_invitation"), public_team_destroy_invitation_path(team, inv), method: :delete, class: "btn btn-secondary", style: "padding:4px 10px;font-size:0.8rem", form: { data: { confirm_kind: "delete" } } %>
</td>
</tr>
<% end %>
</tbody>
</table>
<% else %>
<p class="muted">Nessuna richiesta in sospeso.</p>
<p class="muted"><%= t("team.streaming_staff.no_pending") %></p>
<% end %>
</div>
<% end %>
<% if entitlements.can_access_recordings? %>
<h2>Archivio replay</h2>
<h2><%= t("team.streaming_staff.archive_heading") %></h2>
<div class="card">
<% if recordings.any? %>
<table class="data">
<thead><tr><th>Partita</th><th>Data</th><th>Durata</th><th>Scadenza</th><th>Stato</th><th></th></tr></thead>
<thead><tr><th><%= t("team.streaming_staff.col_match") %></th><th><%= t("team.streaming_staff.col_date") %></th><th><%= t("team.streaming_staff.col_duration") %></th><th><%= t("team.streaming_staff.col_expiry") %></th><th><%= t("team.streaming_staff.col_status") %></th><th></th></tr></thead>
<tbody>
<% recordings.each do |rec| %>
<tr>
@@ -97,7 +96,7 @@
<td><%= rec.status_label %></td>
<td>
<% if rec.replay_url %>
<%= link_to "Guarda", rec.replay_url, target: "_blank", rel: "noopener" %>
<%= link_to t("team.streaming_staff.watch"), rec.replay_url, target: "_blank", rel: "noopener" %>
<% else %>
<% end %>
@@ -107,10 +106,10 @@
</tbody>
</table>
<p style="margin-top:12px">
<%= link_to "Gestisci tutti i replay", public_club_recordings_path(team.club) %>
<%= link_to t("team.streaming_staff.manage_all_replays"), public_club_recordings_path(team.club) %>
</p>
<% else %>
<p class="muted">Nessun replay ancora. Al termine delle dirette Premium le registrazioni compaiono qui.</p>
<p class="muted"><%= t("team.streaming_staff.no_recordings") %></p>
<% end %>
</div>
<% end %>
+15 -15
View File
@@ -1,9 +1,9 @@
<% content_for :title, "Abbonamento — #{@team.name}" %>
<% content_for :title, t("team.billing.title", name: @team.name) %>
<% content_for :robots, "noindex, nofollow" %>
<div class="wrap" style="padding-top:20px">
<h1>Abbonamento</h1>
<p>Squadra: <strong><%= @team.name %></strong> · Piano attuale: <strong><%= @entitlements.plan.name %></strong></p>
<h1><%= t("billing.heading") %></h1>
<p><%= t("team.billing.team_label") %> <strong><%= @team.name %></strong> · <%= t("team.billing.current_plan_label") %> <strong><%= @entitlements.plan.name %></strong></p>
<div class="plans">
<% @plans.each do |plan| %>
@@ -13,34 +13,34 @@
<div class="plan-price">€0</div>
<% elsif plan.slug == "premium_light" %>
<div class="plan-price" style="color:#e53935"><%= Billing::Stripe::PriceCatalog.label(plan_slug: plan.slug, interval: "yearly") %></div>
<p style="color:#888;font-size:0.85rem">oppure <%= Billing::Stripe::PriceCatalog.label(plan_slug: plan.slug, interval: "monthly") %></p>
<p style="color:#888;font-size:0.85rem"><%= raw t("pages.plans.price_alt_html", price: Billing::Stripe::PriceCatalog.label(plan_slug: plan.slug, interval: "monthly")) %></p>
<% else %>
<div class="plan-price"><%= Billing::Stripe::PriceCatalog.label(plan_slug: plan.slug, interval: "yearly") %></div>
<p style="color:#888;font-size:0.85rem">oppure <%= Billing::Stripe::PriceCatalog.label(plan_slug: plan.slug, interval: "monthly") %></p>
<p style="color:#888;font-size:0.85rem"><%= raw t("pages.plans.price_alt_html", price: Billing::Stripe::PriceCatalog.label(plan_slug: plan.slug, interval: "monthly")) %></p>
<% end %>
<ul>
<li>Staff trasmissione: <%= plan.max_staff_transmission || "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 Live TV<% elsif plan.youtube_enabled? %>canale società<% else %>no<% end %></li>
<li><%= t("team.billing.staff_line", count: plan.max_staff_transmission || t("pages.plans.staff_unlimited")) %></li>
<li><%= t("team.billing.matches_line", count: plan.concurrent_streams_limit || t("pages.plans.matches_unlimited")) %></li>
<li><%= t("team.billing.replay_line", value: plan.recordings_enabled? ? t("team.billing.replay_days", count: plan.recording_days) : t("team.billing.replay_none")) %></li>
<li><%= t("team.billing.youtube_line", value: if plan.slug == "premium_light" then t("team.billing.youtube_mltv") elsif plan.youtube_enabled? then t("team.billing.youtube_club") else t("team.billing.youtube_none") end) %></li>
</ul>
<% if plan.slug == @entitlements.plan.slug %>
<span class="btn btn-secondary" style="opacity:0.7">Piano attuale</span>
<span class="btn btn-secondary" style="opacity:0.7"><%= t("team.billing.current_plan") %></span>
<% elsif plan.slug == "free" %>
<% elsif MatchLiveTv.stripe_enabled? %>
<%= link_to "Gestisci abbonamento società", public_club_billing_path(@team.club), class: "btn btn-primary" %>
<%= link_to t("team.billing.manage_club_subscription"), public_club_billing_path(@team.club), class: "btn btn-primary" %>
<% else %>
<p style="color:#888">Stripe non configurato</p>
<p style="color:#888"><%= t("team.billing.stripe_not_configured") %></p>
<% end %>
</div>
<% end %>
</div>
<div class="card" style="margin-top:24px;text-align:center">
<p style="color:#aaa">Esigenze diverse? <%= mail_to "info@matchlive.it", "Contattaci" %> per un'offerta su misura.</p>
<p style="color:#aaa"><%= t("team.billing.contact_lead") %> <%= mail_to "info@matchlive.it", t("team.billing.contact_cta") %> <%= t("team.billing.contact_trailer") %></p>
</div>
<p style="margin-top:20px"><%= link_to "Pagamenti e fatture della società", public_club_billing_path(@team.club) %></p>
<p style="margin-top:20px"><%= link_to t("team.billing.club_payments_link"), public_club_billing_path(@team.club) %></p>
<p><%= link_to "← Dettagli squadra", public_team_details_path(@team) %></p>
<p><%= link_to t("team.back_to_details"), public_team_details_path(@team) %></p>
</div>
+23 -24
View File
@@ -1,14 +1,14 @@
<% content_for :title, "Dettagli — #{@team.name}" %>
<% content_for :meta_description, "Dettagli squadra #{@team.name} su Match Live TV." %>
<% content_for :title, t("team.details.title", name: @team.name) %>
<% content_for :meta_description, t("team.details.meta_description", name: @team.name) %>
<% 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">
<nav class="team-dashboard-nav" aria-label="<%= t('team.details.nav_aria') %>">
<% 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) %>
<%= link_to t("team.details.edit_team"), public_edit_team_path(@team) %>
<% else %>
<span class="team-dashboard-nav__club"><%= @club.name %></span>
<% end %>
@@ -19,24 +19,24 @@
<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>
<%= t("team.details.plan_label") %> <strong><%= @entitlements.plan.name %></strong> <%= t("team.details.plan_scope_club") %>
· <%= t("team.details.staff_label") %> <strong><%= @entitlements.staff_count_for("transmission") %> / <%= @entitlements.max_staff_transmission || "∞" %></strong>
· <%= t("team.details.active_matches_label") %> <strong><%= @entitlements.concurrent_streams_used %><% if @entitlements.concurrent_streams_limit %> / <%= @entitlements.concurrent_streams_limit %><% else %> (<%= t("team.details.unlimited") %>)<% 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) %>.
<%= t("team.details.no_description") %>
<%= link_to t("team.details.add_description_link"), public_edit_team_path(@team) %>.
</p>
<% end %>
<div class="roster-stats">
<span class="roster-stat"><strong><%= total_people %></strong> in organico</span>
<span class="roster-stat"><strong><%= total_people %></strong> <%= t("team.details.roster_count_label") %></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>
<span class="roster-stat"><%= count %> <%= TeamRosterMember.category_label(cat).downcase %></span>
<% end %>
</div>
</div>
@@ -45,8 +45,8 @@
<%= 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" %>
<span><%= t("team.details.no_photo") %></span>
<%= link_to t("team.details.add_photo_link"), public_edit_team_path(@team), class: "btn btn-secondary", style: "margin-top:8px;padding:8px 14px;font-size:0.85rem" %>
</div>
<% end %>
</div>
@@ -54,14 +54,14 @@
<div class="team-details-actions">
<% if @can_manage %>
<%= link_to "← Società", public_club_path(@club), class: "btn btn-secondary" %>
<%= link_to "Pagina pubblica", public_team_page_path(@team.slug), class: "btn btn-secondary", target: "_blank", rel: "noopener" %>
<%= link_to "Responsabili trasmissione", public_team_invite_path(@team), class: "btn btn-secondary" %>
<%= link_to t("club.back_to_club"), public_club_path(@club), class: "btn btn-secondary" %>
<%= link_to t("team.details.public_page"), public_team_page_path(@team.slug), class: "btn btn-secondary", target: "_blank", rel: "noopener" %>
<%= link_to t("team.details.streaming_managers"), 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" %>
<%= link_to t("team.details.schedule_matches"), 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" %>
<%= link_to t("team.details.live_streams"), public_live_index_path(club_id: @club.id), class: "btn btn-secondary" %>
</div>
<div class="roster-layout<%= " roster-layout--readonly" unless @can_manage %>">
@@ -70,7 +70,7 @@
<% 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>
<h2 class="roster-section__title"><%= TeamRosterMember.category_label(category) %></h2>
<span class="roster-section__count"><%= members.size %></span>
</header>
<% if members.any? %>
@@ -80,14 +80,14 @@
<% end %>
</div>
<% else %>
<p class="roster-section__empty">Nessuna persona in questa categoria.</p>
<p class="roster-section__empty"><%= t("team.details.no_people_in_category") %></p>
<% end %>
</section>
<% end %>
</section>
<% if @can_manage %>
<aside class="roster-sidebar" aria-label="Aggiungi persona">
<aside class="roster-sidebar" aria-label="<%= t('roster.member_form.new_heading') %>">
<%= render "shared/roster_member_form", member: @roster_member, team: @team %>
</aside>
<% end %>
@@ -95,8 +95,7 @@
<% if @can_manage && @entitlements.premium_full? && @entitlements.youtube_enabled? %>
<p class="muted" style="margin:16px 0">
Il canale YouTube della società si configura dalla pagina
<%= link_to @club.name, public_club_path(@club) %> (sezione YouTube Live).
<%= raw t("team.details.youtube_hint_html", club_link: link_to(@club.name, public_club_path(@club))) %>
</p>
<% end %>
@@ -105,5 +104,5 @@
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>
<p class="team-details-footer"><%= t("team.details.footer_note") %></p>
</div>
+17 -18
View File
@@ -1,52 +1,51 @@
<% content_for :title, "Modifica squadra — #{@team.name}" %>
<% content_for :title, t("team.edit.title", name: @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>
<p style="color:#888"><%= t("team.edit.club_label") %> <%= 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" %>
<%= label_tag "team[name]", t("team.edit.name_label") %>
<%= text_field_tag "team[name]", @team.name, required: true %>
<%= label_tag "team[sport]", "Sport" %>
<%= label_tag "team[sport]", t("team.sport_label") %>
<%= select_tag "team[sport]", sport_catalog_options(@team.sport_key) %>
<%= label_tag "team[description]", "Descrizione squadra" %>
<%= text_area_tag "team[description]", @team.description, rows: 5, placeholder: "Presentazione della squadra, palmarès, obiettivi della stagione…" %>
<%= label_tag "team[description]", t("team.edit.description_label") %>
<%= text_area_tag "team[description]", @team.description, rows: 5, placeholder: t("team.edit.description_placeholder") %>
<div class="branding-logo-block">
<%= label_tag "team[photo_file]", "Foto di squadra (gruppo o stemma)" %>
<%= label_tag "team[photo_file]", t("team.edit.photo_label") %>
<% 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 %>
<%= image_tag @team.team_photo_url, alt: t("team.edit.photo_alt"), 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)" %>
<%= render "shared/branding_fields", record: @team, show_inherit_hint: true, legend: t("team.edit.branding_legend") %>
<%= label_tag "team[slug]", "Indirizzo pagina pubblica" %>
<%= label_tag "team[slug]", t("team.edit.slug_label") %>
<p class="branding-hint" style="margin-top:0">
Genitori e tifosi trovano la squadra su
<strong><%= MatchLiveTv.app_public_url.chomp("/") %>/squadre/<span id="team-slug-preview"><%= @team.slug %></span></strong>
<%= raw t("team.edit.slug_hint_html", base_url: MatchLiveTv.app_public_url.chomp("/"), slug: "<span id=\"team-slug-preview\">#{@team.slug}</span>") %>
</p>
<%= text_field_tag "team[slug]", @team.slug, pattern: "[a-z0-9]+(-[a-z0-9]+)*", title: "Solo lettere minuscole, numeri e trattini" %>
<%= text_field_tag "team[slug]", @team.slug, pattern: "[a-z0-9]+(-[a-z0-9]+)*", title: t("team.edit.slug_title") %>
<%= label_tag "team[roster_public]", class: "checkbox-label", style: "display:flex;align-items:center;gap:8px;margin-top:16px" do %>
<%= hidden_field_tag "team[roster_public]", "0" %>
<%= check_box_tag "team[roster_public]", "1", @team.roster_public?, id: "team_roster_public" %>
Mostra l'organico sulla pagina pubblica
<%= t("team.edit.roster_public_label") %>
<% end %>
<p class="branding-hint">Se attivo, giocatori e staff sono visibili a chiunque abbia il link. Puoi lasciarlo disattivato per privacy.</p>
<p class="branding-hint"><%= t("team.edit.roster_public_hint") %></p>
<p class="branding-hint">
Colori società:
<%= t("team.edit.club_colors_label") %>
<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" %>
<%= submit_tag t("team.edit.submit"), class: "btn btn-primary" %>
<% end %>
</div>
<p><%= link_to "← Dettagli squadra", public_team_details_path(@team) %></p>
<p><%= link_to t("team.back_to_details"), public_team_details_path(@team) %></p>
</div>
+12 -14
View File
@@ -1,40 +1,38 @@
<% content_for :title, "Staff trasmissione — #{@team.name}" %>
<% content_for :title, t("team.invite.title", name: @team.name) %>
<% content_for :robots, "noindex, nofollow" %>
<div class="wrap" style="padding-top:20px">
<h1>Responsabili trasmissione</h1>
<h1><%= t("team.invite.heading") %></h1>
<p>
Assegnati: <strong><%= @entitlements.staff_count_for("transmission") %> / <%= @entitlements.max_staff_transmission || "∞" %></strong>
(per squadra, rinnovo annuale)
<%= t("team.invite.assigned_label") %> <strong><%= @entitlements.staff_count_for("transmission") %> / <%= @entitlements.max_staff_transmission || "∞" %></strong>
<%= t("team.invite.per_team_note") %>
</p>
<p class="muted" style="max-width:640px">
Il responsabile trasmissione avvia la diretta dallapp (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.).
<%= raw t("team.invite.info") %>
</p>
<% 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> — puoi gestire tu camera e punteggio, oppure invitare altri per la trasmissione.
<%= raw t("team.invite.self_assign_hint_html", email: current_user.email) %>
</p>
<%= button_to "Sono io — responsabile trasmissione", public_team_assign_self_staff_path(@team), method: :post, params: { staff_kind: "transmission" }, class: "btn btn-primary" %>
<%= button_to t("team.invite.self_assign_submit"), 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 unaltra email</h2>
<h2 style="margin-top:0;font-size:1.05rem"><%= t("team.invite.invite_other_heading") %></h2>
<%= form_with url: public_team_invite_path(@team), method: :post do %>
<%= hidden_field_tag :staff_kind, "transmission" %>
<%= label_tag :email, "Email del responsabile trasmissione" %>
<%= label_tag :email, t("team.invite.email_label") %>
<%= email_field_tag :email, params[:email], required: true %>
<%= submit_tag "Genera link di accesso", class: "btn btn-primary" %>
<%= submit_tag t("team.invite.submit"), class: "btn btn-primary" %>
<% end %>
<% if defined?(@invite_url) && @invite_url.present? %>
<p style="margin-top:16px">Condividi (valido 7 giorni):</p>
<p style="margin-top:16px"><%= t("team.invite.share_hint") %></p>
<code style="word-break:break-all;display:block;background:#0a0a0e;padding:12px;border-radius:8px"><%= @invite_url %></code>
<% end %>
</div>
<p><%= link_to "← Dettagli squadra", public_team_details_path(@team) %></p>
<p><%= link_to t("team.back_to_details"), public_team_details_path(@team) %></p>
</div>
+9 -9
View File
@@ -1,18 +1,18 @@
<% content_for :title, "Nuova squadra — #{@club.name}" %>
<% content_for :title, t("team.new.title", name: @club.name) %>
<% content_for :robots, "noindex, nofollow" %>
<section class="auth-page">
<h1>Nuova squadra</h1>
<p class="auth-lead">Società: <strong><%= @club.name %></strong></p>
<h1><%= t("team.new.heading") %></h1>
<p class="auth-lead"><%= t("team.new.club_label") %> <strong><%= @club.name %></strong></p>
<div class="card card-wide">
<%= 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, placeholder: "es. Under 13, Serie C" %>
<%= label_tag "team[sport]", "Sport" %>
<%= label_tag "team[name]", t("team.new.name_label") %>
<%= text_field_tag "team[name]", nil, required: true, placeholder: t("team.new.name_placeholder") %>
<%= label_tag "team[sport]", t("team.sport_label") %>
<%= select_tag "team[sport]", sport_catalog_options(@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" %>
<%= render "shared/branding_fields", record: Team.new(club: @club), show_inherit_hint: true, legend: t("team.new.branding_legend") %>
<%= submit_tag t("team.new.submit"), class: "btn btn-primary" %>
<% end %>
</div>
<p class="auth-footer"><%= link_to "← Società", public_club_path(@club) %></p>
<p class="auth-footer"><%= link_to t("club.back_to_club"), public_club_path(@club) %></p>
</section>
@@ -3,48 +3,46 @@
<% filter_params = @archive_filter_params %>
<div class="replay-archive<%= " replay-archive--admin" if local_assigns[:admin_mode] %>">
<p style="margin-bottom:16px"><%= link_to " #{@club.name}", paths.back %></p>
<p style="margin-bottom:16px"><%= link_to "#{t('recordings.archive.back_prefix')} #{@club.name}", paths.back %></p>
<h1>Archivio Replay<% if local_assigns[:admin_mode] %> <span class="muted" style="font-size:0.85rem;font-weight:400">(admin)</span><% end %></h1>
<h1><%= t("recordings.archive.heading") %><% if local_assigns[:admin_mode] %> <span class="muted" style="font-size:0.85rem;font-weight:400"><%= t("recordings.archive.admin_suffix") %></span><% end %></h1>
<p class="muted">
Gestisci registrazioni, visibilità e download.
<strong>Privato</strong> = link non indicizzato, non compare in <code>/replay</code> pubblico.
Eliminando un replay vengono rimossi anche i file sul server e il video YouTube collegato.
<%= raw t("recordings.archive.lead_html") %>
</p>
<div class="kpi-grid replay-archive__kpis">
<div class="card replay-archive__kpi">
<div class="kpi-value"><%= @stats[:available_count] %></div>
<div class="kpi-sub">Replay disponibili</div>
<div class="kpi-sub"><%= t("recordings.archive.kpi_available") %></div>
</div>
<div class="card replay-archive__kpi">
<div class="kpi-value"><%= @stats[:expiring_soon_count] %></div>
<div class="kpi-sub">In scadenza (7 gg)</div>
<div class="kpi-sub"><%= t("recordings.archive.kpi_expiring") %></div>
</div>
<div class="card replay-archive__kpi">
<div class="kpi-value"><%= number_to_human_size(@stats[:total_bytes]) %></div>
<div class="kpi-sub">Spazio archivio</div>
<div class="kpi-sub"><%= t("recordings.archive.kpi_space") %></div>
</div>
<div class="card replay-archive__kpi">
<div class="kpi-value"><%= @stats[:total_views] %></div>
<div class="kpi-sub">Visualizzazioni totali</div>
<div class="kpi-sub"><%= t("recordings.archive.kpi_views") %></div>
</div>
</div>
<%= form_with url: paths.index, method: :get, local: true, class: "replay-filter-form" do %>
<label for="team_id" class="muted">Squadra</label>
<label for="team_id" class="muted"><%= t("recordings.archive.team_label") %></label>
<select name="team_id" id="team_id" class="input" onchange="this.form.submit()">
<option value="">Tutte</option>
<option value=""><%= t("recordings.archive.team_all") %></option>
<% @teams.each do |team| %>
<option value="<%= team.id %>"<%= " selected" if @filter_team_id.to_s == team.id.to_s %>><%= team.name %></option>
<% end %>
</select>
<label for="status" class="muted">Stato</label>
<label for="status" class="muted"><%= t("recordings.archive.status_label") %></label>
<select name="status" id="status" class="input" onchange="this.form.submit()">
<option value="">Tutti</option>
<option value="ready"<%= " selected" if @filter_status == "ready" %>>Disponibili</option>
<option value="processing"<%= " selected" if @filter_status == "processing" %>>In elaborazione</option>
<option value="expired"<%= " selected" if @filter_status == "expired" %>>Scaduti / errore</option>
<option value=""><%= t("recordings.archive.status_all") %></option>
<option value="ready"<%= " selected" if @filter_status == "ready" %>><%= t("recordings.archive.status_ready") %></option>
<option value="processing"<%= " selected" if @filter_status == "processing" %>><%= t("recordings.archive.status_processing") %></option>
<option value="expired"<%= " selected" if @filter_status == "expired" %>><%= t("recordings.archive.status_expired") %></option>
</select>
<% end %>
@@ -53,12 +51,12 @@
<table class="data replay-archive-table">
<thead>
<tr>
<th class="replay-archive-table__col-thumb" scope="col"><span class="visually-hidden">Anteprima</span></th>
<th scope="col">Partita</th>
<th scope="col">Squadra</th>
<th scope="col">Dettagli</th>
<th scope="col">Stato e visibilità</th>
<th class="replay-archive-table__col-actions" scope="col">Azioni</th>
<th class="replay-archive-table__col-thumb" scope="col"><span class="visually-hidden"><%= t("recordings.archive.col_preview") %></span></th>
<th scope="col"><%= t("recordings.archive.col_match") %></th>
<th scope="col"><%= t("recordings.archive.col_team") %></th>
<th scope="col"><%= t("recordings.archive.col_details") %></th>
<th scope="col"><%= t("recordings.archive.col_status") %></th>
<th class="replay-archive-table__col-actions" scope="col"><%= t("recordings.archive.col_actions") %></th>
</tr>
</thead>
<tbody>
@@ -68,7 +66,7 @@
<tr>
<td class="replay-archive-table__col-thumb">
<% if can_play %>
<%= link_to rec.replay_url, class: "replay-archive__thumb", target: "_blank", rel: "noopener", title: "Guarda replay" do %>
<%= link_to rec.replay_url, class: "replay-archive__thumb", target: "_blank", rel: "noopener", title: t("recordings.archive.watch_replay") do %>
<% if rec.thumbnail_url %>
<img src="<%= rec.thumbnail_url %>" alt="" class="replay-archive__thumb-img" width="80" height="45" loading="lazy">
<% else %>
@@ -77,7 +75,7 @@
<span class="replay-archive__play-btn" aria-hidden="true"></span>
<% end %>
<% else %>
<div class="replay-archive__thumb replay-archive__thumb--static" title="<%= rec.status == "processing" ? "Elaborazione in corso" : "Replay non disponibile" %>">
<div class="replay-archive__thumb replay-archive__thumb--static" title="<%= rec.status == "processing" ? t("recordings.archive.processing_title") : t("recordings.archive.not_available_title") %>">
<% if rec.thumbnail_url %>
<img src="<%= rec.thumbnail_url %>" alt="" class="replay-archive__thumb-img" width="80" height="45" loading="lazy">
<% else %>
@@ -100,16 +98,16 @@
<span class="replay-archive__detail-line">
<%= l_local(rec.recorded_at_or_fallback, format: :short) %>
· <%= rec.duration_label %>
· <%= rec.view_count %> vis.
· <%= rec.view_count %> <%= t("recordings.archive.views_suffix") %>
</span>
<span class="replay-archive__meta-line">
<% if rec.expires_at %>
Scade <%= l_local(rec.expires_at, format: :short) %>
<%= t("recordings.archive.expires_prefix") %> <%= l_local(rec.expires_at, format: :short) %>
<% if rec.days_until_expiry && rec.days_until_expiry.positive? %>
(<%= rec.days_until_expiry %> gg)
<%= t("recordings.archive.expires_days", count: rec.days_until_expiry) %>
<% end %>
<% else %>
Nessuna scadenza
<%= t("recordings.archive.no_expiry") %>
<% end %>
</span>
<% if local_assigns[:admin_mode] && !rec.deleted? && rec.expires_at.present? %>
@@ -119,16 +117,16 @@
rec.expires_at.to_date,
min: Date.tomorrow,
class: "replay-archive__expiry-input",
title: "Nuova data di scadenza" %>
<%= submit_tag "Salva", class: "replay-archive__expiry-save" %>
title: t("recordings.archive.new_expiry_title") %>
<%= submit_tag t("recordings.archive.save"), class: "replay-archive__expiry-save" %>
<% end %>
<div class="replay-archive__expiry-quick">
<% [[30, "+30 gg"], [90, "+90 gg"], [180, "+180 gg"]].each do |days, label| %>
<%= button_to label, paths.update.call(rec),
<% [30, 90, 180].each do |days| %>
<%= button_to t("recordings.archive.extend_days", count: days), paths.update.call(rec),
method: :patch,
params: filter_params.merge(recording: { extend_days: days }),
class: "replay-archive__expiry-extend",
title: "Prolunga la scadenza di #{days} giorni" %>
title: t("recordings.archive.extend_title", count: days) %>
<% end %>
</div>
<% end %>
@@ -139,8 +137,8 @@
<td class="replay-archive-table__col-actions">
<div class="replay-archive__actions">
<% next_privacy = rec.publicly_listed? ? "unlisted" : "public" %>
<% privacy_label = rec.publicly_listed? ? "Pubblico" : "Privato" %>
<% privacy_hint = rec.publicly_listed? ? "Visibile a tutti — clicca per rendere privato" : "Solo con link — clicca per rendere pubblico" %>
<% privacy_label = rec.publicly_listed? ? t("recordings.archive.privacy_public") : t("recordings.archive.privacy_private") %>
<% privacy_hint = rec.publicly_listed? ? t("recordings.archive.privacy_hint_public") : t("recordings.archive.privacy_hint_private") %>
<%= button_to paths.update.call(rec),
method: :patch,
params: filter_params.merge(recording: { privacy_status: next_privacy }),
@@ -161,26 +159,27 @@
<span class="visually-hidden"><%= privacy_label %></span>
<% end %>
<% if ent.phone_download_enabled? && rec.ready? %>
<%= link_to "MP4", public_replay_download_path(rec.stream_session_id), class: "replay-archive__action replay-archive__action--secondary", title: "Scarica MP4" %>
<%= link_to "MP4", public_replay_download_path(rec.stream_session_id), class: "replay-archive__action replay-archive__action--secondary", title: t("recordings.archive.download_mp4_title") %>
<% end %>
<% if ent.premium_full? && ent.youtube_enabled? && rec.ready? && rec.youtube_video_id.blank? %>
<%= button_to "YT", paths.publish_youtube.call(rec), method: :post, class: "replay-archive__action replay-archive__action--secondary", title: "Pubblica su YouTube", form: { class: "replay-archive__action-form" } %>
<%= button_to "YT", paths.publish_youtube.call(rec), method: :post, class: "replay-archive__action replay-archive__action--secondary", title: t("recordings.archive.publish_youtube_title"), form: { class: "replay-archive__action-form" } %>
<% elsif rec.youtube_watch_url %>
<%= link_to "YT", rec.youtube_watch_url, class: "replay-archive__action replay-archive__action--secondary", target: "_blank", rel: "noopener", title: "Apri su YouTube" %>
<%= link_to "YT", rec.youtube_watch_url, class: "replay-archive__action replay-archive__action--secondary", target: "_blank", rel: "noopener", title: t("recordings.archive.open_youtube_title") %>
<% end %>
<% has_youtube = rec.youtube_video_id.present? && !rec.youtube_video_id.to_s.start_with?("mock_") %>
<% has_site = rec.storage_key.present? || %w[ready processing failed].include?(rec.status) %>
<% delete_confirm = "Scegli dove eliminare il replay. L'operazione è irreversibile." %>
<% delete_confirm = t("recordings.archive.delete_confirm") %>
<%= button_to paths.destroy.call(rec), method: :delete,
params: filter_params,
class: "replay-archive__action replay-archive__action--danger",
title: "Elimina replay",
title: t("recordings.archive.delete_title"),
form: {
class: "replay-archive__action-form",
data: {
confirm: delete_confirm,
turbo_confirm: delete_confirm,
confirm_mode: "delete-replay",
confirm_kind: "delete",
has_site: has_site ? "1" : "0",
has_youtube: has_youtube ? "1" : "0"
}
@@ -195,6 +194,6 @@
</table>
</div>
<% else %>
<p class="muted">Nessuna registrazione per i filtri selezionati.</p>
<p class="muted"><%= t("recordings.archive.no_recordings") %></p>
<% end %>
</div>
@@ -1,13 +1,13 @@
<p>Ciao <%= @recipient.name %>,</p>
<p><%= t("mailers.replay_expiring.hello", name: @recipient.name) %></p>
<p>Il replay <strong><%= @recording.title_or_default %></strong> scadrà tra circa <strong><%= @days_left %> giorni</strong> (<%= l @expires_at, format: :long %>).</p>
<p><%= raw t("mailers.replay_expiring.body_html", title: @recording.title_or_default, days: @days_left, date: l(@expires_at, format: :long)) %></p>
<p>Dopo la scadenza verrà eliminato automaticamente dallo storage secondo il piano della società.</p>
<p><%= t("mailers.replay_expiring.after") %></p>
<p>
<a href="<%= @replay_url %>">Apri il replay</a>
<a href="<%= @replay_url %>"><%= t("mailers.replay_expiring.open") %></a>
·
<a href="<%= MatchLiveTv.app_public_url.chomp('/') %>/clubs/<%= @club.id %>/replays">Archivio Replay</a>
<a href="<%= @archive_url %>"><%= t("mailers.replay_expiring.archive") %></a>
</p>
<p style="color:#666;font-size:12px">Match Live TV — <%= MatchLiveTv.privacy_controller_email %></p>
<p style="color:#666;font-size:12px"><%= t("mailers.replay_expiring.footer", email: MatchLiveTv.privacy_controller_email) %></p>
@@ -1,8 +1,8 @@
Ciao <%= @recipient.name %>,
<%= t("mailers.replay_expiring.hello", name: @recipient.name) %>
Il replay "<%= @recording.title_or_default %>" scade tra circa <%= @days_left %> giorni (<%= l @expires_at, format: :long %>).
<%= t("mailers.replay_expiring.body_text", title: @recording.title_or_default, days: @days_left, date: l(@expires_at, format: :long)) %>
Apri: <%= @replay_url %>
Archivio: <%= MatchLiveTv.app_public_url.chomp('/') %>/clubs/<%= @club.id %>/replays
<%= t("mailers.replay_expiring.open_text", url: @replay_url) %>
<%= t("mailers.replay_expiring.archive_text", url: @archive_url) %>
Match Live TV
@@ -1,15 +1,15 @@
<p>Ciao <%= @recipient.name %>,</p>
<p><%= t("mailers.replay_ready.hello", name: @recipient.name) %></p>
<p>Il replay della partita <strong><%= @recording.title_or_default %></strong> (<%= @club.name %>) è pronto e disponibile sulla piattaforma.</p>
<p><%= raw t("mailers.replay_ready.body_html", title: @recording.title_or_default, club: @club.name) %></p>
<p>
<a href="<%= @replay_url %>">Guarda il replay</a>
<a href="<%= @replay_url %>"><%= t("mailers.replay_ready.watch") %></a>
</p>
<% if @expires_at %>
<p style="color:#666">Disponibile fino al <%= l @expires_at, format: :long %>.</p>
<p style="color:#666"><%= t("mailers.replay_ready.available_until", date: l(@expires_at, format: :long)) %></p>
<% end %>
<p>Puoi gestire visibilità ed eliminazione dall<a href="<%= MatchLiveTv.app_public_url.chomp('/') %>/clubs/<%= @club.id %>/replays">archivio Replay</a> della società.</p>
<p><%= raw t("mailers.replay_ready.archive_html", url: @archive_url) %></p>
<p style="color:#666;font-size:12px">Match Live TV — <%= MatchLiveTv.privacy_controller_email %></p>
<p style="color:#666;font-size:12px"><%= t("mailers.replay_ready.footer", email: MatchLiveTv.privacy_controller_email) %></p>
@@ -1,12 +1,12 @@
Ciao <%= @recipient.name %>,
<%= t("mailers.replay_ready.hello", name: @recipient.name) %>
Il replay "<%= @recording.title_or_default %>" (<%= @club.name %>) è pronto.
<%= t("mailers.replay_ready.body_text", title: @recording.title_or_default, club: @club.name) %>
Guarda: <%= @replay_url %>
<%= t("mailers.replay_ready.watch_text", url: @replay_url) %>
<% if @expires_at %>
Disponibile fino al <%= l @expires_at, format: :long %>.
<%= t("mailers.replay_ready.available_until", date: l(@expires_at, format: :long)) %>
<% end %>
Archivio: <%= MatchLiveTv.app_public_url.chomp('/') %>/clubs/<%= @club.id %>/replays
<%= t("mailers.replay_ready.archive_text", url: @archive_url) %>
Match Live TV
@@ -1,33 +1,33 @@
<%# locals: (club:, payments:) %>
<section class="billing-documents" style="margin-top:40px">
<h2>Pagamenti e fatture</h2>
<h2><%= t("billing.documents.heading") %></h2>
<p style="color:#888;font-size:0.9rem">
Storico dei pagamenti della società e relative fatture emesse da Match Live TV.
<%= t("billing.documents.lead") %>
</p>
<% unless club.billing_profile_complete? %>
<div class="flash alert" style="margin:16px 0">
<strong>Dati di fatturazione incompleti.</strong>
Compila ragione sociale, indirizzo, P.IVA/CF e SDI o PEC per ricevere le fatture via email.
<%= link_to "Completa ora", public_club_billing_profile_path(club), class: "btn btn-secondary", style: "margin-top:10px;display:inline-block" %>
<strong><%= t("billing.documents.incomplete_title") %></strong>
<%= t("billing.documents.incomplete_body") %>
<%= link_to t("billing.documents.complete_now"), public_club_billing_profile_path(club), class: "btn btn-secondary", style: "margin-top:10px;display:inline-block" %>
</div>
<% else %>
<p style="color:#888;font-size:0.88rem">
Intestatario: <%= club.billing_profile_summary %>
· <%= link_to "Modifica dati", public_club_billing_profile_path(club) %>
<%= t("billing.documents.holder_label", summary: club.billing_profile_summary) %>
· <%= link_to t("billing.documents.edit_data"), public_club_billing_profile_path(club) %>
</p>
<% end %>
<h3 style="margin-top:28px;font-size:1.1rem">Pagamenti e fatture</h3>
<h3 style="margin-top:28px;font-size:1.1rem"><%= t("billing.documents.table_heading") %></h3>
<% if payments.any? %>
<table class="data billing-table">
<thead>
<tr>
<th>Data</th>
<th>Descrizione</th>
<th>Importo</th>
<th>Stato pagamento</th>
<th>Fattura</th>
<th><%= t("billing.documents.col_date") %></th>
<th><%= t("billing.documents.col_description") %></th>
<th><%= t("billing.documents.col_amount") %></th>
<th><%= t("billing.documents.col_status") %></th>
<th><%= t("billing.documents.col_invoice") %></th>
<th></th>
</tr>
</thead>
@@ -49,9 +49,9 @@
</td>
<td>
<% if inv&.downloadable? %>
<%= link_to "Scarica PDF", public_club_billing_invoice_path(club, inv) %>
<%= link_to t("billing.documents.download_pdf"), public_club_billing_invoice_path(club, inv) %>
<% elsif inv&.draft? %>
<span style="color:#888">PDF in preparazione</span>
<span style="color:#888"><%= t("billing.documents.pdf_preparing") %></span>
<% else %>
<span style="color:#888"></span>
<% end %>
@@ -61,6 +61,6 @@
</tbody>
</table>
<% else %>
<p style="color:#888">Nessun pagamento registrato. Dopo un abbonamento premium comparirà qui.</p>
<p style="color:#888"><%= t("billing.documents.no_payments") %></p>
<% end %>
</section>
@@ -1,66 +1,64 @@
<%# locals: (record:, show_legend: true) %>
<% if show_legend %>
<h2 class="form-section-title" style="margin-top:24px">Dati di fatturazione</h2>
<h2 class="form-section-title" style="margin-top:24px"><%= t("billing.fields.legend") %></h2>
<p style="color:#888;font-size:0.88rem;margin:-8px 0 16px">
Tutti i campi contrassegnati sono obbligatori per abbonarti a un piano premium e per emettere le fatture.
Per le società serve la P.IVA; per le persone fisiche il Codice Fiscale. Serve SDI <em>oppure</em> PEC.
I pagamenti restano gestiti in modo sicuro da Stripe.
<%= t("billing.fields.legend_hint") %>
</p>
<% end %>
<%= label_tag "club[billing_entity_type]", "Tipo intestatario *" %>
<%= label_tag "club[billing_entity_type]", t("billing.fields.entity_type_label") %>
<%= select_tag "club[billing_entity_type]",
options_for_select(Club::BILLING_ENTITY_TYPES.map { |k, v| [v, k] }, record.billing_entity_type),
options_for_select(Club.billing_entity_types.map { |k, v| [v, k] }, record.billing_entity_type),
include_blank: false %>
<%= label_tag "club[billing_legal_name]", "Ragione sociale / nome e cognome *" %>
<%= text_field_tag "club[billing_legal_name]", record.billing_legal_name, placeholder: "es. ASD Tigers Volley" %>
<%= label_tag "club[billing_legal_name]", t("billing.fields.legal_name_label") %>
<%= text_field_tag "club[billing_legal_name]", record.billing_legal_name, placeholder: t("billing.fields.legal_name_placeholder") %>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px">
<div>
<%= label_tag "club[billing_vat_number]", "Partita IVA * (società)" %>
<%= label_tag "club[billing_vat_number]", t("billing.fields.vat_number_label") %>
<%= text_field_tag "club[billing_vat_number]", record.billing_vat_number, placeholder: "12345678901" %>
</div>
<div>
<%= label_tag "club[billing_fiscal_code]", "Codice Fiscale * (persona fisica)" %>
<%= text_field_tag "club[billing_fiscal_code]", record.billing_fiscal_code, placeholder: "RSSMRA80A01H501U" %>
<%= label_tag "club[billing_fiscal_code]", t("billing.fields.fiscal_code_label") %>
<%= text_field_tag "club[billing_fiscal_code]", record.billing_fiscal_code, placeholder: t("billing.fields.fiscal_code_placeholder") %>
</div>
</div>
<%= label_tag "club[billing_email]", "Email fatturazione *" %>
<%= email_field_tag "club[billing_email]", record.billing_email, placeholder: "amministrazione@societa.it" %>
<%= label_tag "club[billing_email]", t("billing.fields.email_label") %>
<%= email_field_tag "club[billing_email]", record.billing_email, placeholder: t("billing.fields.email_placeholder") %>
<%= label_tag "club[billing_phone]", "Telefono (opzionale)" %>
<%= label_tag "club[billing_phone]", t("billing.fields.phone_label") %>
<%= text_field_tag "club[billing_phone]", record.billing_phone %>
<%= label_tag "club[billing_address_line]", "Indirizzo *" %>
<%= text_field_tag "club[billing_address_line]", record.billing_address_line, placeholder: "Via Roma 1" %>
<%= label_tag "club[billing_address_line]", t("billing.fields.address_label") %>
<%= text_field_tag "club[billing_address_line]", record.billing_address_line, placeholder: t("billing.fields.address_placeholder") %>
<div style="display:grid;grid-template-columns:2fr 1fr 1fr;gap:12px">
<div>
<%= label_tag "club[billing_city]", "Città *" %>
<%= label_tag "club[billing_city]", t("billing.fields.city_label") %>
<%= text_field_tag "club[billing_city]", record.billing_city %>
</div>
<div>
<%= label_tag "club[billing_province]", "Prov. *" %>
<%= text_field_tag "club[billing_province]", record.billing_province, maxlength: 2, placeholder: "MI" %>
<%= label_tag "club[billing_province]", t("billing.fields.province_label") %>
<%= text_field_tag "club[billing_province]", record.billing_province, maxlength: 2, placeholder: t("billing.fields.province_placeholder") %>
</div>
<div>
<%= label_tag "club[billing_postal_code]", "CAP *" %>
<%= label_tag "club[billing_postal_code]", t("billing.fields.postal_code_label") %>
<%= text_field_tag "club[billing_postal_code]", record.billing_postal_code %>
</div>
</div>
<%= label_tag "club[billing_country]", "Paese (ISO) *" %>
<%= label_tag "club[billing_country]", t("billing.fields.country_label") %>
<%= text_field_tag "club[billing_country]", record.billing_country.presence || "IT", maxlength: 2 %>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:12px">
<div>
<%= label_tag "club[billing_recipient_code]", "Codice destinatario SDI (7 car.) *" %>
<%= text_field_tag "club[billing_recipient_code]", record.billing_recipient_code, maxlength: 7, placeholder: "XXXXXXX" %>
<%= label_tag "club[billing_recipient_code]", t("billing.fields.recipient_code_label") %>
<%= text_field_tag "club[billing_recipient_code]", record.billing_recipient_code, maxlength: 7, placeholder: t("billing.fields.recipient_code_placeholder") %>
</div>
<div>
<%= label_tag "club[billing_pec]", "PEC (alternativa a SDI) *" %>
<%= email_field_tag "club[billing_pec]", record.billing_pec, placeholder: "pec@societa.it" %>
<%= label_tag "club[billing_pec]", t("billing.fields.pec_label") %>
<%= email_field_tag "club[billing_pec]", record.billing_pec, placeholder: t("billing.fields.pec_placeholder") %>
</div>
</div>
@@ -6,50 +6,50 @@
<% logo_src = record.effective_logo_url.presence %>
<fieldset class="branding-fields">
<legend><%= local_assigns[:legend] || "Identità visiva" %></legend>
<legend><%= local_assigns[:legend] || t("club.branding.legend") %></legend>
<% if show_inherit_hint %>
<p class="branding-hint">Lascia vuoto per usare logo e colori della società.</p>
<p class="branding-hint"><%= t("club.branding.inherit_hint") %></p>
<% end %>
<div class="branding-logo-block">
<%= label_tag "branding[logo_file]", "Logo (immagine)" %>
<%= label_tag "branding[logo_file]", t("club.branding.logo_file_label") %>
<div class="branding-logo-preview" data-branding-logo-preview>
<%= image_tag logo_src.presence || "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7",
alt: "Anteprima logo",
alt: t("club.branding.logo_preview_alt"),
class: "branding-logo-preview__img",
data: { branding_logo_img: true },
width: 96,
height: 96,
hidden: logo_src.blank? %>
<span class="branding-logo-preview__placeholder" data-branding-logo-placeholder <%= "hidden" if logo_src.present? %>>
Nessun logo — scegli un file per lanteprima
<%= t("club.branding.no_logo_placeholder") %>
</span>
</div>
<%= file_field_tag "branding[logo_file]", accept: "image/png,image/jpeg,image/webp", data: { branding_logo_file: true }, class: "branding-file-input" %>
</div>
<%= label_tag "#{param_key}[logo_url]", "Logo (URL alternativo)" %>
<%= label_tag "#{param_key}[logo_url]", t("club.branding.logo_url_label") %>
<%= text_field_tag "#{param_key}[logo_url]", record.logo_url, placeholder: "https://…", class: "branding-url-input" %>
<%= label_tag "#{param_key}[primary_color]", "Colore principale" %>
<%= label_tag "#{param_key}[primary_color]", t("club.branding.primary_color_label") %>
<div class="branding-color-row" data-branding-color-row="primary">
<%= color_field_tag "#{param_key}[primary_color]", primary, data: { color_picker: true }, class: "branding-color-picker", title: "Scegli colore" %>
<%= color_field_tag "#{param_key}[primary_color]", primary, data: { color_picker: true }, class: "branding-color-picker", title: t("club.branding.choose_color") %>
<%= text_field_tag nil, primary, data: { color_hex: true }, class: "branding-hex-input", placeholder: "#e53935", autocomplete: "off", spellcheck: false %>
<span class="color-swatch branding-color-swatch" data-color-swatch aria-hidden="true"></span>
<div class="branding-color-presets" aria-label="Colori rapidi">
<div class="branding-color-presets" aria-label="<%= t("club.branding.quick_colors") %>">
<% %w[#e53935 #1565c0 #2e7d32 #ff9800 #212121 #ffffff].each do |hex| %>
<button type="button" class="branding-preset" data-color-preset="<%= hex %>" style="background:<%= hex %>" title="<%= hex %>"></button>
<% end %>
</div>
</div>
<%= label_tag "#{param_key}[secondary_color]", "Colore secondario" %>
<%= label_tag "#{param_key}[secondary_color]", t("club.branding.secondary_color_label") %>
<div class="branding-color-row" data-branding-color-row="secondary">
<%= color_field_tag "#{param_key}[secondary_color]", secondary, data: { color_picker: true }, class: "branding-color-picker", title: "Scegli colore" %>
<%= color_field_tag "#{param_key}[secondary_color]", secondary, data: { color_picker: true }, class: "branding-color-picker", title: t("club.branding.choose_color") %>
<%= text_field_tag nil, secondary, data: { color_hex: true }, class: "branding-hex-input", placeholder: "#ffffff", autocomplete: "off", spellcheck: false %>
<span class="color-swatch branding-color-swatch" data-color-swatch aria-hidden="true"></span>
<div class="branding-color-presets" aria-label="Colori rapidi">
<div class="branding-color-presets" aria-label="<%= t("club.branding.quick_colors") %>">
<% %w[#ffffff #f5f5f5 #212121 #ffd54f #90caf9 #e53935].each do |hex| %>
<button type="button" class="branding-preset" data-color-preset="<%= hex %>" style="background:<%= hex %>" title="<%= hex %>"></button>
<% end %>
@@ -57,14 +57,14 @@
</div>
<div class="branding-live-preview" data-branding-live-preview>
<span class="branding-live-preview__label">Anteprima intestazione</span>
<span class="branding-live-preview__label"><%= t("club.branding.header_preview_label") %></span>
<div class="branding-live-preview__header" data-preview-header style="--preview-primary:<%= primary %>;--preview-secondary:<%= secondary %>">
<span class="branding-live-preview__logo" data-preview-logo>
<% if logo_src %>
<%= image_tag logo_src, alt: "", width: 40, height: 40, style: "border-radius:8px;object-fit:contain" %>
<% end %>
</span>
<span class="branding-live-preview__name"><%= record.try(:name).presence || "Nome società / squadra" %></span>
<span class="branding-live-preview__name"><%= record.try(:name).presence || t("club.branding.name_placeholder") %></span>
</div>
</div>
</fieldset>
@@ -1,35 +1,33 @@
<%# locals: (club:, entitlements: nil, subscription: nil, on_billing_page: false) %>
<% current_plan = entitlements&.plan || subscription&.plan || Plan["free"] %>
<p style="color:#aaa;margin-bottom:8px">
Società: <strong><%= club.name %></strong>
· Piano attuale: <strong><%= current_plan.name %></strong>
<%= t("billing.subscription_status.club_label", name: club.name) %>
· <%= t("billing.subscription_status.current_plan_label", plan: current_plan.name) %>
<% if subscription&.admin_comped? %>
<span style="color:#ffb74d">(omaggio Match Live TV)</span>
<span style="color:#ffb74d"><%= t("billing.subscription_status.comped_suffix") %></span>
<% end %>
(valido per tutte le squadre)
<%= t("billing.subscription_status.valid_for_all_teams") %>
<% unless local_assigns[:on_billing_page] %>
· <%= link_to "Gestisci abbonamento", public_club_billing_path(club) %>
· <%= link_to t("billing.subscription_status.manage_subscription"), public_club_billing_path(club) %>
<% end %>
</p>
<% if subscription&.stripe_subscription_id.present? %>
<p style="color:#888;font-size:0.9rem;margin-top:0">
<% if subscription.cancel_at_period_end? %>
<span style="color:#e53935">
Abbonamento valido fino al
<strong><%= subscription.current_period_end.present? ? l_local(subscription.current_period_end.to_date) : "fine periodo" %></strong>,
poi piano Free.
<%= raw t("billing.subscription_status.cancel_scheduled_html", date: (subscription.current_period_end.present? ? l_local(subscription.current_period_end.to_date) : t("billing.subscription_status.end_of_period"))) %>
</span>
<% elsif subscription.plan_change_pending? && subscription.pending_plan.present? %>
<% interval_suffix = subscription.pending_billing_interval.present? ? " (#{Billing::Stripe::PriceCatalog.label(plan_slug: subscription.pending_plan.slug, interval: subscription.pending_billing_interval)})" : "" %>
<span style="color:#ffb74d">
Dal <strong><%= subscription.current_period_end.present? ? l_local(subscription.current_period_end) : "prossimo rinnovo" %></strong>
passerai a <strong><%= subscription.pending_plan.name %></strong>
<% if subscription.pending_billing_interval.present? %>
(<%= Billing::Stripe::PriceCatalog.label(plan_slug: subscription.pending_plan.slug, interval: subscription.pending_billing_interval) %>)
<% end %>.
Fino ad allora resta attivo <strong><%= current_plan.name %></strong>.
<%= raw t("billing.subscription_status.change_pending_html",
date: (subscription.current_period_end.present? ? l_local(subscription.current_period_end) : t("billing.subscription_status.next_renewal")),
plan: subscription.pending_plan.name,
interval: interval_suffix,
current_plan: current_plan.name) %>
</span>
<% elsif subscription.current_period_end.present? %>
Prossimo rinnovo: <strong><%= l_local(subscription.current_period_end) %></strong>
<%= t("billing.subscription_status.next_renewal_label", date: l_local(subscription.current_period_end)) %>
<% if subscription.billing_interval.present? %>
· <%= Billing::Stripe::PriceCatalog.label(plan_slug: current_plan.slug, interval: subscription.billing_interval) %>
<% end %>
@@ -1,5 +1,7 @@
<%# Selettore lingua a bandiere (allineato a destra nel menu) %>
<%# locals: (locale_path: nil) %>
<% current = current_language_option %>
<% target_path = locale_path || public_locale_path %>
<div class="lang-switcher" data-lang-switcher>
<button
type="button"
@@ -24,7 +26,7 @@
>
<% language_options.each do |opt| %>
<% selected = opt[:code].to_s == current_locale.to_s %>
<%= button_to public_locale_path,
<%= button_to target_path,
method: :patch,
params: { locale: opt[:code] },
class: "lang-switcher__option#{' is-active' if selected}",
+21 -9
View File
@@ -23,18 +23,30 @@
<% light_yearly = Billing::Stripe::PriceCatalog.label(plan_slug: plan.slug, interval: "yearly") %>
<% light_monthly = Billing::Stripe::PriceCatalog.label(plan_slug: plan.slug, interval: "monthly") %>
<div class="plan-price" style="color:#e53935"><%= light_yearly %></div>
<p class="plan-price-alt">oppure <strong><%= light_monthly %></strong></p>
<p class="plan-price-alt"><%= raw t("pages.plans.price_alt_html", price: light_monthly) %></p>
<% else %>
<% full_yearly = Billing::Stripe::PriceCatalog.label(plan_slug: plan.slug, interval: "yearly") %>
<% full_monthly = Billing::Stripe::PriceCatalog.label(plan_slug: plan.slug, interval: "monthly") %>
<div class="plan-price"><%= full_yearly %></div>
<p class="plan-price-alt">oppure <strong><%= full_monthly %></strong></p>
<p class="plan-price-alt"><%= raw t("pages.plans.price_alt_html", price: full_monthly) %></p>
<% end %>
<ul>
<li>Responsabili trasmissione: <strong><%= plan.max_staff_transmission || "illimitato" %></strong> / anno per squadra</li>
<li>Partite: <strong><%= plan.concurrent_streams_limit || "illimitate" %></strong> 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 Live TV<% elsif plan.youtube_enabled? %>canale società<% else %>no<% end %></li>
<li><%= raw t("pages.plans.staff_html", count: plan.max_staff_transmission || t("pages.plans.staff_unlimited")) %></li>
<li><%= raw t("pages.plans.matches_html", count: plan.concurrent_streams_limit || t("pages.plans.matches_unlimited")) %></li>
<li><%= raw t(
"pages.plans.replay_html",
value: plan.recordings_enabled? ? t("pages.plans.replay_days", count: plan.recording_days) : t("pages.plans.replay_none")
) %></li>
<li><%= raw t(
"pages.plans.youtube_html",
value: if plan.slug == "premium_light"
t("pages.plans.youtube_mltv")
elsif plan.youtube_enabled?
t("pages.plans.youtube_club")
else
t("pages.plans.youtube_none")
end
) %></li>
</ul>
<% if billing_mode %>
<% action_kind = action[:kind] %>
@@ -48,7 +60,7 @@
<p class="plan-action-hint" style="color:#e53935;margin-bottom:10px">
<%= billing_profile_incomplete_message(club) %>
</p>
<%= link_to "Completa dati di fatturazione",
<%= link_to t("pages.plans.complete_billing"),
public_club_billing_profile_path(club, plan: plan.slug),
class: "btn btn-primary" %>
<% else %>
@@ -63,11 +75,11 @@
<% end %>
<% end %>
<% elsif plan.slug == "free" %>
<%= link_to "Inizia gratis", public_signup_path, class: "btn btn-secondary" %>
<%= link_to t("pages.plans.start_free"), public_signup_path, class: "btn btn-secondary" %>
<% else %>
<div class="plan-interval-actions">
<% plan_intervals_for_display(Billing::Stripe::PriceCatalog.available_intervals(plan_slug: plan.slug)).each do |interval| %>
<%= link_to "Registrati — #{Billing::Stripe::PriceCatalog.label(plan_slug: plan.slug, interval: interval)}",
<%= link_to t("pages.plans.register_with_price", price: Billing::Stripe::PriceCatalog.label(plan_slug: plan.slug, interval: interval)),
public_signup_path(plan: plan.slug, interval: interval),
class: plan_interval_button_class(interval) %>
<% end %>
@@ -2,11 +2,11 @@
<% lines = billing_plan_change_info_lines(subscription: subscription) %>
<div class="plan-change-info-wrap">
<details class="plan-change-info">
<summary class="plan-change-info__trigger" aria-label="Info cambio piano e fatturazione" title="Info cambio piano e fatturazione">
<summary class="plan-change-info__trigger" aria-label="<%= t("billing.plan_change_info.trigger_label") %>" title="<%= t("billing.plan_change_info.trigger_label") %>">
<i class="fa-solid fa-circle-info" aria-hidden="true"></i>
</summary>
<div class="plan-change-info__box" role="note">
<p class="plan-change-info__title">Cambio piano e fatturazione</p>
<p class="plan-change-info__title"><%= t("billing.plan_change_info.title") %></p>
<ul>
<% lines.each do |line| %>
<li><%= line %></li>
@@ -7,45 +7,45 @@
<% player_role = member.role_label if member.role_label.in?(player_roles) %>
<%= form_with url: form_url, method: form_method, multipart: true, class: "roster-member-form card roster-sidebar__form", data: { roster_form: true } do %>
<h3 class="roster-sidebar__title"><%= member.persisted? ? "Modifica scheda" : "Aggiungi persona" %></h3>
<p class="roster-sidebar__lead">Compila i campi e salva per aggiornare lorganico.</p>
<h3 class="roster-sidebar__title"><%= member.persisted? ? t("roster.member_form.edit_heading") : t("roster.member_form.new_heading") %></h3>
<p class="roster-sidebar__lead"><%= t("roster.member_form.lead") %></p>
<%= label_tag "team_roster_member[category]", "Gruppo" %>
<%= label_tag "team_roster_member[category]", t("roster.member_form.category_label") %>
<%= select_tag "team_roster_member[category]", options_for_select(roster_category_options, member.category),
data: { roster_category: true } %>
<%= label_tag "team_roster_member[full_name]", "Nome e cognome" %>
<%= text_field_tag "team_roster_member[full_name]", member.full_name, required: true, placeholder: "es. Mario Rossi" %>
<%= label_tag "team_roster_member[full_name]", t("roster.member_form.full_name_label") %>
<%= text_field_tag "team_roster_member[full_name]", member.full_name, required: true, placeholder: t("roster.member_form.full_name_placeholder") %>
<div data-roster-role-fields>
<div data-roster-role-player <%= "hidden" unless is_player %>>
<%= label_tag "team_roster_member[role_label]", "Ruolo in campo" %>
<%= label_tag "team_roster_member[role_label]", t("roster.member_form.role_player_label") %>
<%= select_tag "team_roster_member[role_label]", roster_player_role_options(team, player_role),
data: { roster_role_player: true }, disabled: !is_player %>
</div>
<div data-roster-role-other <%= "hidden" if is_player %>>
<%= label_tag "team_roster_member[role_label_other]", "Ruolo (opzionale)" %>
<%= label_tag "team_roster_member[role_label_other]", t("roster.member_form.role_other_label") %>
<%= text_field_tag "team_roster_member[role_label_other]", (member.role_label unless is_player),
placeholder: "es. Capo allenatore, Presidente, Massaggiatore",
placeholder: t("roster.member_form.role_other_placeholder"),
data: { roster_role_other: true }, disabled: is_player %>
</div>
</div>
<div data-roster-jersey-row <%= "hidden" unless is_player %>>
<%= label_tag "team_roster_member[jersey_number]", "Numero maglia" %>
<%= label_tag "team_roster_member[jersey_number]", t("roster.member_form.jersey_label") %>
<%= number_field_tag "team_roster_member[jersey_number]", member.jersey_number, min: 1, max: 99,
placeholder: "es. 7", data: { roster_jersey: true }, disabled: !is_player %>
placeholder: t("roster.member_form.jersey_placeholder"), data: { roster_jersey: true }, disabled: !is_player %>
</div>
<%= label_tag "team_roster_member[bio]", "Descrizione breve" %>
<%= text_area_tag "team_roster_member[bio]", member.bio, rows: 3, placeholder: "Breve presentazione per la scheda…" %>
<%= label_tag "team_roster_member[bio]", t("roster.member_form.bio_label") %>
<%= text_area_tag "team_roster_member[bio]", member.bio, rows: 3, placeholder: t("roster.member_form.bio_placeholder") %>
<%= label_tag "team_roster_member[photo_file]", "Foto" %>
<%= label_tag "team_roster_member[photo_file]", t("roster.member_form.photo_label") %>
<% if member.photo_url.present? %>
<div class="roster-form-photo-preview"><%= roster_person_avatar(member, team, size: 72) %></div>
<% end %>
<%= file_field_tag "team_roster_member[photo_file]", accept: "image/png,image/jpeg,image/webp" %>
<p class="branding-hint">Senza foto verrà mostrato un avatar con i colori della squadra.</p>
<p class="branding-hint"><%= t("roster.member_form.photo_hint") %></p>
<%= submit_tag (member.persisted? ? "Salva scheda" : "Aggiungi all'organico"), class: "btn btn-primary" %>
<%= submit_tag (member.persisted? ? t("roster.member_form.submit_edit") : t("roster.member_form.submit_new")), class: "btn btn-primary" %>
<% end %>
@@ -21,9 +21,9 @@
</div>
<% if editable %>
<div class="roster-card__actions">
<%= link_to "Modifica", edit_public_team_roster_member_path(team, person), class: "roster-card__btn" %>
<%= button_to "Rimuovi", public_team_roster_member_path(team, person), method: :delete, class: "roster-card__btn roster-card__btn--danger",
form: { data: { turbo_confirm: "Rimuovere #{person.full_name} dall'organico?" } } %>
<%= link_to t("roster.person_card.edit"), edit_public_team_roster_member_path(team, person), class: "roster-card__btn" %>
<%= button_to t("roster.person_card.remove"), public_team_roster_member_path(team, person), method: :delete, class: "roster-card__btn roster-card__btn--danger",
form: { data: { turbo_confirm: t("roster.person_card.remove_confirm", name: person.full_name), confirm_kind: "delete" } } %>
</div>
<% end %>
</article>
@@ -3,10 +3,9 @@
<i class="fa-solid fa-lock" aria-hidden="true"></i>
<p>
<% if local_assigns.fetch(:compact, false) %>
Pagamenti premium elaborati in modo sicuro tramite <strong>Stripe</strong>.
<%= raw t("billing.stripe_secure.compact_html") %>
<% else %>
I pagamenti dei piani premium sono elaborati in modo <strong>sicuro</strong> tramite
<strong>Stripe</strong> (certificazione PCI DSS). Match Live TV non memorizza i numeri completi della carta.
<%= raw t("billing.stripe_secure.full_html") %>
<% end %>
</p>
</div>
@@ -4,32 +4,25 @@
<section id="subscription-cancel" class="subscription-cancel" style="margin-top:24px;padding:16px;border:1px solid #333;border-radius:8px">
<% if subscription.cancel_at_period_end? %>
<p style="color:#e53935;margin:0 0 8px">
<strong>Disdetta programmata.</strong>
Il piano <strong><%= subscription.plan.name %></strong> resta attivo fino al
<% if subscription.current_period_end.present? %>
<strong><%= l_local(subscription.current_period_end.to_date) %></strong>.
<% else %>
<strong>fine del periodo già pagato</strong>.
<% end %>
Da quel giorno la società passerà automaticamente al piano <strong>Free</strong>.
<strong><%= t("billing.subscription_cancel.scheduled_title") %></strong>
<%= raw t("billing.subscription_cancel.scheduled_body_html", plan: subscription.plan.name, date: (subscription.current_period_end.present? ? l_local(subscription.current_period_end.to_date) : t("billing.subscription_cancel.scheduled_end_of_period"))) %>
</p>
<p style="color:#888;font-size:0.88rem;margin:0">
Per ripristinare l'abbonamento prima della scadenza, contatta il supporto Match Live TV.
<%= t("billing.subscription_cancel.scheduled_support_hint") %>
</p>
<% else %>
<p style="color:#aaa;margin:0 0 12px;font-size:0.9rem">
Puoi disdire in qualsiasi momento: l'abbonamento resta valido fino alla fine del periodo già pagato,
poi passi al piano Free senza ulteriori addebiti.
<%= t("billing.subscription_cancel.info") %>
</p>
<%= button_to "Annulla abbonamento",
<%= button_to t("billing.subscription_cancel.cancel_button"),
public_club_billing_cancel_subscription_path(club),
method: :post,
class: "btn btn-outline",
form: {
data: {
turbo_confirm: subscription.current_period_end.present? ?
"Confermi la disdetta? Il piano #{subscription.plan.name} resterà attivo fino al #{l_local(subscription.current_period_end)}, poi passerai al Free." :
"Confermi la disdetta dell'abbonamento?"
t("billing.subscription_cancel.confirm_with_date", plan: subscription.plan.name, date: l_local(subscription.current_period_end)) :
t("billing.subscription_cancel.confirm_default")
}
} %>
<% end %>
@@ -1,11 +1,11 @@
<p>Ciao <%= @user.name %>,</p>
<p><%= t("mailers.password_reset.hello", name: @user.name) %></p>
<p>Hai richiesto di reimpostare la password del tuo account Match Live TV.</p>
<p><%= t("mailers.password_reset.intro") %></p>
<p>
<a href="<%= @reset_url %>">Clicca qui per scegliere una nuova password</a>
<%= raw t("mailers.password_reset.link_html", url: @reset_url) %>
</p>
<p>Il link è valido per <%= @expiry_hours %> ore. Se non hai richiesto tu il reset, ignora questa email: la password non verrà modificata.</p>
<p><%= raw t("mailers.password_reset.expiry_html", hours: @expiry_hours) %></p>
<p style="color:#666;font-size:12px">Match Live TV — <%= MatchLiveTv.privacy_controller_email %></p>
<p style="color:#666;font-size:12px"><%= t("mailers.password_reset.footer", email: MatchLiveTv.privacy_controller_email) %></p>
@@ -1,10 +1,10 @@
Ciao <%= @user.name %>,
<%= t("mailers.password_reset.hello", name: @user.name) %>
Hai richiesto di reimpostare la password del tuo account Match Live TV.
<%= t("mailers.password_reset.intro") %>
Apri questo link (valido <%= @expiry_hours %> ore):
<%= t("mailers.password_reset.link_text", hours: @expiry_hours) %>
<%= @reset_url %>
Se non hai richiesto tu il reset, ignora questa email.
<%= t("mailers.password_reset.ignore") %>
Match Live TV — <%= MatchLiveTv.privacy_controller_email %>
<%= t("mailers.password_reset.footer", email: MatchLiveTv.privacy_controller_email) %>