Aggiunge modulo Replay, Garage dev e YouTube a livello società.

Pipeline registrazione/upload con storage S3, archivio web e app, proxy replay
per il browser, OAuth YouTube sulla pagina club (Premium Full) e footer legale.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-06-03 07:53:11 +02:00
parent a87cda156b
commit 1f273f849d
87 changed files with 2952 additions and 195 deletions

View File

@@ -9,6 +9,18 @@
<%= 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>
<% if cred %>
<%= cred.channel_title.presence || cred.channel_id %> (collegato)
<% elsif @teams.any? { |t| t.entitlements.premium_full? } %>
<span class="muted">Premium Full — canale società non collegato (usa Match Live TV)</span>
<% else %>
<span class="muted">—</span>
<% end %>
</p>
<h3 style="font-size:1rem;margin-top:28px">Squadre</h3>
<% if @teams.empty? %>
<p class="muted">Nessuna squadra registrata.</p>
@@ -18,32 +30,14 @@
<tr>
<th>Squadra</th>
<th>Sport</th>
<th>YouTube</th>
<th></th>
</tr>
</thead>
<tbody>
<% @teams.each do |team| %>
<% yt = Youtube::TeamStatus.new(team) %>
<tr>
<td><strong><%= team.name %></strong></td>
<td><%= team.sport %></td>
<td>
<% if yt.selectable? %>
<% if yt.uses_platform_channel? %>
<span style="color:#81c784">Match Live TV</span>
<% if team.youtube_credential.blank? && team.entitlements.premium_full? %>
<span class="muted"> (default Full)</span>
<% end %>
<% else %>
<span style="color:#81c784">✓ <%= yt.channel_title.presence || "Canale società" %></span>
<% end %>
<% elsif team.entitlements.youtube_enabled? %>
<span class="muted">In configurazione</span>
<% else %>
<% end %>
</td>
<td><%= link_to "Partite e dettagli", admin_team_path(team) %></td>
</tr>
<% end %>

View File

@@ -13,7 +13,7 @@
<% if yt.selectable? %>
<strong><%= yt.channel_title || "—" %></strong>
<% if yt.uses_platform_channel? %>
(canale piattaforma<%= @team.entitlements.premium_full? && @team.youtube_credential.blank? ? ", default senza OAuth società" : "" %>)
(canale piattaforma<%= @team.entitlements.premium_full? && @team.club.youtube_credential.blank? ? ", default senza OAuth società" : "" %>)
<% else %>
(canale società)
<% end %>
@@ -21,10 +21,10 @@
non pronto
<% end %>
</p>
<% if @team.entitlements.premium_full? && @team.youtube_credential.blank? %>
<% 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 (sito pubblico)", public_team_details_path(@team) %>.
<%= link_to "Collega canale (pagina società)", public_club_path(@team.club) %>.
</p>
<% end %>

View File

@@ -7,7 +7,7 @@
<%= render "shared/meta_tags" %>
<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=34">
<link rel="stylesheet" href="/live.css?v=3">
<link rel="stylesheet" href="/live.css?v=4">
<%= yield :head %>
</head>
<body>

View File

@@ -0,0 +1,91 @@
<% content_for :title, "Replay — #{@club.name}" %>
<% content_for :robots, "noindex, nofollow" %>
<div class="wrap club-dashboard" style="padding-top:20px">
<%= link_to "← #{@club.name}", public_club_path(@club), class: "back-link" %>
<h1>Archivio Replay</h1>
<p class="muted">Gestisci registrazioni, visibilità, download e pubblicazione YouTube.</p>
<div class="kpi-grid" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:12px;margin:20px 0">
<div class="card" style="padding:16px">
<div class="kpi-value"><%= @stats[:available_count] %></div>
<div class="kpi-sub">Replay disponibili</div>
</div>
<div class="card" style="padding:16px">
<div class="kpi-value"><%= @stats[:expiring_soon_count] %></div>
<div class="kpi-sub">In scadenza (7 gg)</div>
</div>
<div class="card" style="padding:16px">
<div class="kpi-value"><%= number_to_human_size(@stats[:total_bytes]) %></div>
<div class="kpi-sub">Spazio archivio</div>
</div>
<div class="card" style="padding:16px">
<div class="kpi-value"><%= @stats[:total_views] %></div>
<div class="kpi-sub">Visualizzazioni totali</div>
</div>
</div>
<% if @recordings.any? %>
<div class="card">
<table class="data">
<thead>
<tr>
<th></th>
<th>Titolo</th>
<th>Data</th>
<th>Durata</th>
<th>Views</th>
<th>Scadenza</th>
<th>Stato</th>
<th>Visibilità</th>
<th></th>
</tr>
</thead>
<tbody>
<% @recordings.each do |rec| %>
<tr>
<td style="width:72px">
<% if rec.thumbnail_url %>
<img src="<%= rec.thumbnail_url %>" alt="" width="64" height="36" style="object-fit:cover;border-radius:4px">
<% end %>
</td>
<td><%= rec.title_or_default %></td>
<td><%= l_local(rec.recorded_at_or_fallback) %></td>
<td><%= rec.duration_label %></td>
<td><%= rec.view_count %></td>
<td><%= rec.expires_at ? l_local(rec.expires_at) : "—" %></td>
<td><%= rec.status_label %></td>
<td>
<%= form_with url: public_club_recording_path(@club, rec), method: :patch, local: true do %>
<%= select_tag "recording[privacy_status]",
options_for_select([["Pubblico", "public"], ["Privato", "unlisted"]], rec.privacy_status),
onchange: "this.form.submit()" %>
<% end %>
</td>
<td style="white-space:nowrap;font-size:0.8rem">
<% if rec.replay_url %>
<%= link_to "Guarda", rec.replay_url, target: "_blank", rel: "noopener" %>
<% end %>
<% if rec.team.entitlements.phone_download_enabled? && rec.ready? %>
· <%= link_to "MP4", public_replay_download_path(rec.stream_session_id) %>
<% end %>
<% ent = rec.team.entitlements %>
<% if ent.premium_full? && ent.youtube_enabled? && rec.ready? && rec.youtube_video_id.blank? %>
· <%= button_to "YouTube", public_club_recording_publish_youtube_path(@club, rec), method: :post, class: "btn-link", style: "display:inline;padding:0;border:0;background:none;color:inherit;cursor:pointer;text-decoration:underline" %>
<% elsif rec.youtube_watch_url %>
· <%= link_to "YT", rec.youtube_watch_url, target: "_blank", rel: "noopener" %>
<% end %>
· <%= button_to "Elimina", public_club_recording_path(@club, rec), method: :delete,
class: "btn-link", style: "display:inline;padding:0;border:0;background:none;color:#e53935;cursor:pointer",
form: { data: { turbo_confirm: "Eliminare definitivamente questo replay?" } } %>
</td>
</tr>
<% end %>
</tbody>
</table>
</div>
<% else %>
<p class="muted">Nessuna registrazione ancora. I replay compaiono al termine delle dirette Premium.</p>
<% end %>
</div>

View File

@@ -1,31 +1,32 @@
<% yt = Youtube::TeamStatus.new(team) %>
<% return unless entitlements.youtube_enabled? %>
<% return unless entitlements&.youtube_enabled? %>
<% yt = Youtube::TeamStatus.new(entitlements_team) if entitlements_team %>
<% cred = club.youtube_credential %>
<section class="card team-youtube" id="youtube">
<section class="card club-youtube" id="youtube">
<h2>YouTube Live</h2>
<% if params[:youtube] == "connected" %>
<p class="notice" style="color:#2e7d32;margin-bottom:12px">Canale YouTube collegato con successo.</p>
<p class="notice" style="color:#2e7d32;margin-bottom:12px">Canale YouTube della società collegato con successo.</p>
<% end %>
<% if entitlements.plan.youtube_mode == "matchlivetv_light" %>
<p>
Con il piano <strong>Premium Light</strong> la diretta va sul canale ufficiale
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à.
</p>
<% if yt.selectable? %>
<% if yt&.selectable? %>
<p class="muted">Canale pronto · seleziona «YouTube Live» nellapp mobile.</p>
<% else %>
<p class="muted">Canale Match Live TV in configurazione lato server. Contatta il supporto se YouTube non compare in app.</p>
<% end %>
<% elsif entitlements.premium_full? %>
<% cred = team.youtube_credential %>
<% if cred %>
<p>
Canale collegato:
Canale collegato per <strong>tutta la società</strong>:
<strong><%= cred.channel_title.presence || cred.channel_id || "YouTube" %></strong>
</p>
<%= button_to "Scollega canale", public_team_youtube_disconnect_path(team),
<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),
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." } } %>
@@ -33,13 +34,13 @@
<p>
Senza canale collegato, le dirette YouTube dallapp vanno sul canale ufficiale
<strong>Match Live TV</strong>.
Puoi collegare il canale della società quando vuoi per trasmettere sul tuo profilo.
Collega qui il canale YouTube della società (vale per tutte le squadre).
</p>
<% if yt.selectable? %>
<p class="muted">YouTube pronto in app (canale Match Live TV).</p>
<% if yt&.selectable? %>
<p class="muted">YouTube pronto in app (canale Match Live TV finché non colleghi il tuo).</p>
<% end %>
<% if ENV["YOUTUBE_CLIENT_ID"].present? %>
<%= link_to "Collega il tuo canale YouTube", public_team_youtube_connect_path(team), class: "btn btn-primary" %>
<%= link_to "Collega il canale YouTube della società", public_club_youtube_connect_path(club), class: "btn btn-primary" %>
<% else %>
<p class="muted">OAuth YouTube non ancora configurato sul server.</p>
<% end %>

View File

@@ -22,8 +22,38 @@
<%= 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" %>
<% if @entitlements&.can_access_recordings? %>
<%= link_to "Archivio Replay", public_club_recordings_path(@club), class: "btn btn-secondary" %>
<% end %>
</div>
<%= render "public/clubs/youtube",
club: @club,
entitlements: @entitlements,
entitlements_team: @entitlements_team %>
<% if @replay_stats %>
<h2>Replay</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>
<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>
<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>
<div>
<div style="font-size:1.6rem;font-weight:700"><%= @replay_stats[:total_views] %></div>
<div class="muted">visualizzazioni</div>
</div>
</div>
<% end %>
<h2>Squadre</h2>
<div class="card">
<% if @teams.any? %>

View File

@@ -12,11 +12,17 @@
<p class="results-hint">
Solo le squadre di questa società: dirette in corso e partite programmate da app o sito.
</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" %>
</div>
<% else %>
<h1>Dirette e partite in programma</h1>
<p class="results-hint">
Cerca per società, squadra, avversario o luogo: trovi le dirette attive e le partite programmate.
</p>
<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" %>
</div>
<% end %>
<% if @club && @can_schedule_match %>

View File

@@ -0,0 +1,90 @@
<% 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." %>
<div class="wrap replay-index">
<%= link_to "← Dirette 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>
<%= form_with url: public_replay_index_path, method: :get, local: true, class: "search-form" do %>
<% if params[:club_id].present? %>
<%= hidden_field_tag :club_id, params[:club_id] %>
<% end %>
<input
type="search"
name="q"
value="<%= @query %>"
placeholder="Es. Tigers Volley, avversario, società, luogo…"
aria-label="Cerca replay"
autocomplete="off"
/>
<button type="submit" class="btn btn-primary">Cerca</button>
<% if @query.present? || params[:club_id].present? %>
<%= link_to "Azzera", public_replay_index_path, class: "btn btn-secondary" %>
<% end %>
<% end %>
<% if @clubs.many? %>
<%= form_with url: public_replay_index_path, method: :get, local: true, class: "replay-filter-form" do %>
<% if @query.present? %>
<%= hidden_field_tag :q, @query %>
<% end %>
<label for="club_id" class="muted">Società</label>
<select name="club_id" id="club_id" class="input" onchange="this.form.submit()">
<option value="">Tutte le società</option>
<% @clubs.each do |club| %>
<option value="<%= club.id %>"<%= " selected" if params[:club_id].to_s == club.id.to_s %>><%= club.name %></option>
<% end %>
</select>
<% end %>
<% end %>
<% if @query.present? %>
<p class="results-hint">
Risultati per «<%= h @query %>»<% if @filter_club %> · <%= @filter_club.name %><% end %>:
<%= @recordings.size %> replay
</p>
<% end %>
<% if @recordings.any? %>
<div class="replay-grid">
<% @recordings.each do |rec| %>
<% match = rec.stream_session.match %>
<%= link_to public_replay_path(rec.stream_session_id), class: "replay-card" do %>
<div class="replay-card__media">
<% if rec.thumbnail_url %>
<img src="<%= rec.thumbnail_url %>" alt="" class="replay-card__thumb" loading="lazy" width="320" height="180">
<% else %>
<div class="replay-card__thumb replay-card__thumb--placeholder" aria-hidden="true">
<span class="replay-card__placeholder-icon">▶</span>
</div>
<% end %>
<span class="replay-card__duration"><%= rec.duration_label %></span>
<span class="replay-card__play" aria-hidden="true">▶</span>
</div>
<div class="replay-card__body">
<strong class="replay-card__title"><%= rec.title_or_default %></strong>
<p class="replay-card__meta">
<%= match.team.club.name %> · <%= l_local(rec.recorded_at_or_fallback) %>
</p>
<p class="replay-card__meta replay-card__meta--sub">
<%= rec.views_label %>
</p>
</div>
<% end %>
<% end %>
</div>
<% 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>
<% 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" %>
<% end %>
</div>
<% end %>
</div>

View File

@@ -1,39 +1,58 @@
<% content_for :title, "Replay — #{@match.team.name} vs #{@match.opponent_name}" %>
<% content_for :meta_description, "Replay partita #{@match.team.name} vs #{@match.opponent_name} su Match Live TV." %>
<% content_for :robots, "noindex, nofollow" %>
<% content_for :title, "Replay — #{@recording.title_or_default}" %>
<% content_for :meta_description, "Replay #{@recording.title_or_default} su Match Live TV." %>
<% content_for :robots, @recording.publicly_listed? ? "index, follow" : "noindex, nofollow" %>
<div class="wrap">
<%= link_to "← Tutte le dirette", public_live_index_path, class: "back-link" %>
<div class="wrap replay-show">
<%= link_to "← Live passate", public_replay_index_path, class: "back-link" %>
<h1>Replay</h1>
<p style="color:#aaa"><%= @match.team.name %> vs <%= @match.opponent_name %></p>
<header class="replay-show__header">
<h1><%= @recording.title_or_default %></h1>
<p class="replay-show__meta">
<%= @match.team.club.name %> · <%= l_local(@recording.recorded_at_or_fallback) %>
· <%= @recording.views_label %>
</p>
</header>
<% if @recording %>
<div class="card" style="margin:16px 0">
<% if @session.hls_playback_url.present? %>
<video id="replay-player" class="replay-video" controls playsinline></video>
<script src="https://cdn.jsdelivr.net/npm/hls.js@1.5.7"></script>
<script>
(function() {
var src = "<%= j @session.hls_playback_url %>";
var video = document.getElementById("replay-player");
if (video.canPlayType("application/vnd.apple.mpegurl")) {
video.src = src;
} else if (typeof Hls !== "undefined" && Hls.isSupported()) {
var hls = new Hls();
hls.loadSource(src);
hls.attachMedia(video);
} else {
video.outerHTML = "<p>Il replay non è disponibile su questo browser.</p>";
}
})();
</script>
<% else %>
<p>Registrazione indicizzata. Il playback dipende dalla retention del piano Premium.</p>
<% end %>
<% if @recording.expires_at %>
<p class="stream-ended-meta" style="margin-top:12px">Disponibile fino al <%= l_local(@recording.expires_at) %></p>
<% end %>
<% if @recording.status == "processing" %>
<div class="card replay-show__status">
<p>Registrazione in elaborazione. Riceverai unemail quando sarà pronta.</p>
</div>
<% elsif @recording.ready? %>
<div class="replay-show__player card">
<video id="replay-player" class="replay-video" controls playsinline preload="metadata"
<% if @recording.thumbnail_url %> poster="<%= @recording.thumbnail_url %>"<% end %>>
<source src="<%= public_replay_stream_path(@session) %>" type="video/mp4">
</video>
</div>
<div class="replay-show__details card">
<p class="replay-show__details-line">
<span>Durata <%= @recording.duration_label %></span>
<% if @recording.expires_at %>
<span>· Disponibile fino al <%= l_local(@recording.expires_at) %></span>
<% end %>
<span>· <%= @recording.publicly_listed? ? "Pubblico" : "Privato (link)" %></span>
<% if @recording.byte_size.to_i.positive? %>
<span>· <%= @recording.byte_size_label %></span>
<% end %>
</p>
<% ent = @recording.team.entitlements %>
<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" %>
<% end %>
<% if @recording.youtube_watch_url %>
<%= link_to "Apri su YouTube", @recording.youtube_watch_url, class: "btn btn-secondary", target: "_blank", rel: "noopener" %>
<% end %>
</div>
</div>
<% elsif @recording.status == "failed" %>
<div class="card replay-show__status">
<p>Replay non disponibile: <%= @recording.error_message.presence || "errore di elaborazione" %>.</p>
</div>
<% else %>
<div class="card replay-show__status">
<p>Replay non più disponibile.</p>
</div>
<% end %>
</div>

View File

@@ -81,20 +81,37 @@
</div>
<% end %>
<% if entitlements.can_access_recordings? && recordings.any? %>
<h2>Archivio gare</h2>
<% if entitlements.can_access_recordings? %>
<h2>Archivio replay</h2>
<div class="card">
<table class="data">
<thead><tr><th>Partita</th><th>Replay</th></tr></thead>
<tbody>
<% recordings.each do |rec| %>
<tr>
<td><%= rec.stream_session.match.team.name %> vs <%= rec.stream_session.match.opponent_name %></td>
<td><%= link_to "Guarda", rec.replay_url %></td>
</tr>
<% end %>
</tbody>
</table>
<% 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>
<tbody>
<% recordings.each do |rec| %>
<tr>
<td><%= rec.title_or_default %></td>
<td><%= l_local(rec.recorded_at_or_fallback) %></td>
<td><%= rec.duration_label %></td>
<td><%= rec.expires_at ? l_local(rec.expires_at) : "—" %></td>
<td><%= rec.status_label %></td>
<td>
<% if rec.replay_url %>
<%= link_to "Guarda", rec.replay_url, target: "_blank", rel: "noopener" %>
<% else %>
<% end %>
</td>
</tr>
<% end %>
</tbody>
</table>
<p style="margin-top:12px">
<%= link_to "Gestisci tutti i replay", public_club_recordings_path(team.club) %>
</p>
<% else %>
<p class="muted">Nessun replay ancora. Al termine delle dirette Premium le registrazioni compaiono qui.</p>
<% end %>
</div>
<% end %>
</section>

View File

@@ -92,7 +92,12 @@
<% end %>
</div>
<%= render "public/teams/youtube", team: @team, entitlements: @entitlements %>
<% 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).
</p>
<% end %>
<%= render "public/teams/streaming_staff", team: @team, club: @club, can_manage: @can_manage,
entitlements: @entitlements, owner_membership: @owner_membership,

View File

@@ -0,0 +1,13 @@
<p>Ciao <%= @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>Dopo la scadenza verrà eliminato automaticamente dallo storage secondo il piano della società.</p>
<p>
<a href="<%= @replay_url %>">Apri il replay</a>
·
<a href="<%= MatchLiveTv.app_public_url.chomp('/') %>/clubs/<%= @club.id %>/replays">Archivio Replay</a>
</p>
<p style="color:#666;font-size:12px">Match Live TV — <%= MatchLiveTv.privacy_controller_email %></p>

View File

@@ -0,0 +1,8 @@
Ciao <%= @recipient.name %>,
Il replay "<%= @recording.title_or_default %>" scade tra circa <%= @days_left %> giorni (<%= l @expires_at, format: :long %>).
Apri: <%= @replay_url %>
Archivio: <%= MatchLiveTv.app_public_url.chomp('/') %>/clubs/<%= @club.id %>/replays
Match Live TV

View File

@@ -0,0 +1,15 @@
<p>Ciao <%= @recipient.name %>,</p>
<p>Il replay della partita <strong><%= @recording.title_or_default %></strong> (<%= @club.name %>) è pronto e disponibile sulla piattaforma.</p>
<p>
<a href="<%= @replay_url %>">Guarda il replay</a>
</p>
<% if @expires_at %>
<p style="color:#666">Disponibile fino al <%= 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 style="color:#666;font-size:12px">Match Live TV — <%= MatchLiveTv.privacy_controller_email %></p>

View File

@@ -0,0 +1,12 @@
Ciao <%= @recipient.name %>,
Il replay "<%= @recording.title_or_default %>" (<%= @club.name %>) è pronto.
Guarda: <%= @replay_url %>
<% if @expires_at %>
Disponibile fino al <%= l @expires_at, format: :long %>.
<% end %>
Archivio: <%= MatchLiveTv.app_public_url.chomp('/') %>/clubs/<%= @club.id %>/replays
Match Live TV

View File

@@ -10,8 +10,9 @@
<%= link_to "Privacy", public_privacy_path %> ·
<%= link_to "Termini", public_termini_path %>
</div>
<div class="site-footer__stripe">
<%= render "shared/stripe_secure_payment", compact: true %>
<div class="site-footer__legal">
<p>© 2026 Emiliano Frascaro P. IVA 14230270960</p>
<p>I contenuti trasmessi sono di esclusiva responsabilità delle società sportive che li pubblicano.</p>
</div>
</div>
</footer>