Initial commit: monorepo Match Live TV.

Rails API, app Flutter, infrastruttura Docker/MediaMTX, sito marketing e documentazione di deploy.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-05-26 17:45:37 +02:00
commit bba6df52c0
381 changed files with 20599 additions and 0 deletions

View File

@@ -0,0 +1,49 @@
<% content_for :title, "Abbonamento — #{@team.name}" %>
<div class="wrap" style="padding-top:20px">
<h1>Abbonamento</h1>
<p>Squadra: <strong><%= @team.name %></strong> · Piano attuale: <strong><%= @entitlements.plan.name %></strong></p>
<div class="plans">
<% @plans.each do |plan| %>
<div class="plan-card <%= 'featured' if plan.slug == @entitlements.plan.slug %>">
<h3><%= plan.name %></h3>
<% if plan.slug == "free" %>
<div class="plan-price">€0</div>
<% elsif plan.slug == "premium_light" %>
<div class="plan-price" style="color:#e53935">€40/anno</div>
<p style="color:#888;font-size:0.85rem">oppure €5/mese</p>
<% else %>
<div class="plan-price">€200/anno</div>
<p style="color:#888;font-size:0.85rem">oppure €20/mese</p>
<% end %>
<ul>
<li>Staff trasmissione: <%= plan.max_staff_transmission || "illimitato" %> / anno</li>
<li>Staff regia: <%= plan.max_staff_regia || "illimitato" %> / anno</li>
<li>Partite: <%= plan.concurrent_streams_limit || "illimitate" %> in contemporanea</li>
<li>Replay: <%= plan.recordings_enabled? ? "#{plan.recording_days} giorni" : "no" %></li>
<li>YouTube: <% if plan.slug == "premium_light" %>Match TV Light<% elsif plan.youtube_enabled? %>canale società<% else %>no<% end %></li>
</ul>
<% if plan.slug == @entitlements.plan.slug %>
<span class="btn btn-secondary" style="opacity:0.7">Piano attuale</span>
<% elsif plan.slug == "free" %>
<p style="color:#888;font-size:0.85rem">Contatta il supporto per downgrade.</p>
<% elsif MatchLiveTv.stripe_enabled? %>
<%= link_to "Attiva #{plan.name}", public_team_checkout_path(@team, plan: plan.slug), class: "btn btn-primary" %>
<% else %>
<p style="color:#888">Stripe non configurato</p>
<% end %>
</div>
<% end %>
</div>
<div class="card" style="margin-top:24px;text-align:center">
<p style="color:#aaa">Esigenze diverse? <%= mail_to "info@matchlivetv.eminux.it", "Contattaci" %> per un'offerta su misura.</p>
</div>
<% if @entitlements.premium_active? && MatchLiveTv.stripe_enabled? %>
<p style="margin-top:20px"><%= button_to "Gestisci su Stripe", public_team_portal_path(@team), class: "btn btn-secondary" %></p>
<% end %>
<p><%= link_to "← Dashboard", public_team_dashboard_path(@team) %></p>
</div>

View File

@@ -0,0 +1,82 @@
<% content_for :title, "#{@team.name} — Dashboard" %>
<div class="wrap" style="padding-top:20px">
<h1><%= @team.name %></h1>
<p>
Piano: <strong><%= @entitlements.plan.name %></strong>
· Staff trasmissione: <strong><%= @entitlements.staff_count_for("transmission") %> / <%= @entitlements.max_staff_transmission || "∞" %></strong>
· Staff regia: <strong><%= @entitlements.staff_count_for("regia") %> / <%= @entitlements.max_staff_regia || "∞" %></strong>
· Partite attive: <strong><%= @entitlements.concurrent_streams_used %><% if @entitlements.concurrent_streams_limit %> / <%= @entitlements.concurrent_streams_limit %><% else %> (illimitate)<% end %></strong>
</p>
<div style="display:flex;gap:12px;flex-wrap:wrap;margin:20px 0">
<%= link_to "Abbonamento", public_team_billing_path(@team), class: "btn btn-primary" %>
<%= link_to "Aggiungi staff", public_team_invite_path(@team), class: "btn btn-secondary" %>
<%= link_to "Dirette live", public_live_index_path, class: "btn btn-secondary" %>
</div>
<h2>Staff attivo</h2>
<div class="card">
<% if @members.any? %>
<table class="data">
<thead><tr><th>Nome</th><th>Email</th><th>Ruolo</th><th></th></tr></thead>
<tbody>
<% @members.each do |ut| %>
<tr>
<td><%= ut.user.name %></td>
<td><%= ut.user.email %></td>
<td><%= ut.staff_kind == "regia" ? "Regia" : "Trasmissione" %></td>
<td>
<%= button_to "Revoca", public_team_remove_member_path(@team, ut.user), method: :delete, class: "btn btn-secondary", style: "padding:4px 10px;font-size:0.8rem", form: { data: { turbo_confirm: "Revocare l'accesso a #{ut.user.name}?" } } %>
</td>
</tr>
<% end %>
</tbody>
</table>
<% else %>
<p style="color:#888">Nessun membro staff. <%= link_to "Aggiungi qualcuno", public_team_invite_path(@team) %>.</p>
<% end %>
</div>
<h2>Staff in attesa</h2>
<div class="card">
<% if @pending_invitations.any? %>
<table class="data">
<thead><tr><th>Email</th><th>Ruolo</th><th>Scade</th><th></th></tr></thead>
<tbody>
<% @pending_invitations.each do |inv| %>
<tr>
<td><%= inv.email %></td>
<td><%= inv.staff_kind == "regia" ? "Regia" : "Trasmissione" %></td>
<td><%= l inv.expires_at, format: :short %></td>
<td>
<%= button_to "Annulla", public_team_destroy_invitation_path(@team, inv), method: :delete, class: "btn btn-secondary", style: "padding:4px 10px;font-size:0.8rem" %>
</td>
</tr>
<% end %>
</tbody>
</table>
<% else %>
<p style="color:#888">Nessuna richiesta in sospeso.</p>
<% end %>
</div>
<% if @entitlements.can_access_recordings? && @recordings.any? %>
<h2>Archivio gare</h2>
<div class="card">
<table class="data">
<thead><tr><th>Partita</th><th>Replay</th></tr></thead>
<tbody>
<% @recordings.each do |rec| %>
<tr>
<td><%= rec.stream_session.match.team.name %> vs <%= rec.stream_session.match.opponent_name %></td>
<td><%= link_to "Guarda", rec.replay_url %></td>
</tr>
<% end %>
</tbody>
</table>
</div>
<% end %>
<p style="color:#888;margin-top:24px">Usa l'app mobile con le stesse credenziali per avviare partite e dirette.</p>
</div>

View File

@@ -0,0 +1,28 @@
<% content_for :title, "Aggiungi staff — #{@team.name}" %>
<div class="wrap" style="padding-top:20px">
<h1>Aggiungi staff</h1>
<p>
Trasmissione: <strong><%= @entitlements.staff_count_for("transmission") %> / <%= @entitlements.max_staff_transmission || "∞" %></strong>
· Regia: <strong><%= @entitlements.staff_count_for("regia") %> / <%= @entitlements.max_staff_regia || "∞" %></strong>
</p>
<div class="card" style="max-width:520px">
<%= form_with url: public_team_invite_path(@team), method: :post do %>
<%= label_tag :staff_kind, "Ruolo" %>
<%= select_tag :staff_kind, options_for_select([
["Trasmissione (camera / streaming)", "transmission"],
["Regia (punteggio)", "regia"]
], params[:staff_kind] || "transmission") %>
<%= label_tag :email, "Email del membro staff" %>
<%= email_field_tag :email, params[:email], required: true %>
<%= submit_tag "Genera link di accesso", class: "btn btn-primary" %>
<% end %>
<% if defined?(@invite_url) && @invite_url.present? %>
<p style="margin-top:16px">Condividi (valido 7 giorni):</p>
<code style="word-break:break-all;display:block;background:#0a0a0e;padding:12px;border-radius:8px"><%= @invite_url %></code>
<p style="color:#888;font-size:0.88rem;margin-top:8px">Il membro dello staff si registra con la stessa email e accetta l'accesso.</p>
<% end %>
</div>
<p><%= link_to "← Dashboard", public_team_dashboard_path(@team) %></p>
</div>

View File

@@ -0,0 +1,21 @@
<% content_for :title, "Crea squadra — Match Live TV" %>
<section class="auth-page">
<h1>La tua squadra</h1>
<p class="auth-lead">Ultimo passo: nome, sport e piano iniziale.</p>
<div class="card card-wide">
<%= form_with url: public_teams_path do %>
<%= label_tag "team[name]", "Nome squadra" %>
<%= text_field_tag "team[name]", nil, required: true %>
<%= label_tag "team[sport]", "Sport" %>
<%= select_tag "team[sport]", options_for_select([["Pallavolo", "volleyball"], ["Calcio", "football"], ["Basket", "basketball"]], "volleyball") %>
<%= label_tag :plan, "Piano iniziale" %>
<%= select_tag :plan, options_for_select([
["Free — 1 staff trasmissione + 1 regia, 1 live", "free"],
["Premium Light — €40/anno o €5/mese", "premium_light"],
["Premium Full — €200/anno o €20/mese", "premium_full"]
], "free") %>
<%= submit_tag "Crea squadra", class: "btn btn-primary" %>
<% end %>
</div>
</section>