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:
@@ -10,16 +10,16 @@ module Admin
|
|||||||
account = AdminAccount.find_by(username: params[:username]&.strip)
|
account = AdminAccount.find_by(username: params[:username]&.strip)
|
||||||
if account&.authenticate(params[:password])
|
if account&.authenticate(params[:password])
|
||||||
session[:admin_account_id] = account.id
|
session[:admin_account_id] = account.id
|
||||||
redirect_to session.delete(:admin_return_to) || admin_root_path, notice: "Accesso effettuato"
|
redirect_to session.delete(:admin_return_to) || admin_root_path, notice: t("admin.flash.login_success")
|
||||||
else
|
else
|
||||||
flash.now[:alert] = "Username o password non validi"
|
flash.now[:alert] = t("admin.flash.invalid_credentials")
|
||||||
render :new, status: :unauthorized
|
render :new, status: :unauthorized
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
reset_session
|
reset_session
|
||||||
redirect_to admin_login_path, notice: "Disconnesso"
|
redirect_to admin_login_path, notice: t("admin.flash.logout_success")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -3,20 +3,40 @@ module Admin
|
|||||||
layout "admin"
|
layout "admin"
|
||||||
protect_from_forgery with: :exception
|
protect_from_forgery with: :exception
|
||||||
|
|
||||||
|
before_action :set_admin_locale
|
||||||
before_action :require_admin_login
|
before_action :require_admin_login
|
||||||
|
|
||||||
include ::AdminHelper
|
include ::AdminHelper
|
||||||
helper ApplicationHelper
|
helper ApplicationHelper
|
||||||
helper RecordingsArchiveHelper
|
helper RecordingsArchiveHelper
|
||||||
helper_method :current_admin_account, :admin_logged_in?
|
helper_method :current_admin_account, :admin_logged_in?, :current_locale, :language_options, :current_language_option
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def set_admin_locale
|
||||||
|
I18n.locale = LocaleResolver.resolve(
|
||||||
|
cookie_jar: request.cookie_jar,
|
||||||
|
accept_language: request.get_header("HTTP_ACCEPT_LANGUAGE")
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
def current_locale
|
||||||
|
I18n.locale
|
||||||
|
end
|
||||||
|
|
||||||
|
def language_options
|
||||||
|
LocaleResolver.language_options
|
||||||
|
end
|
||||||
|
|
||||||
|
def current_language_option
|
||||||
|
LocaleResolver.current_language_option
|
||||||
|
end
|
||||||
|
|
||||||
def require_admin_login
|
def require_admin_login
|
||||||
return if admin_logged_in?
|
return if admin_logged_in?
|
||||||
|
|
||||||
session[:admin_return_to] = request.fullpath unless request.path == admin_login_path
|
session[:admin_return_to] = request.fullpath unless request.path == admin_login_path
|
||||||
redirect_to admin_login_path, alert: "Accedi per continuare"
|
redirect_to admin_login_path, alert: t("admin.flash.login_required")
|
||||||
end
|
end
|
||||||
|
|
||||||
def current_admin_account
|
def current_admin_account
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ module Admin
|
|||||||
payment = Billing::Payment.find(params[:payment_id])
|
payment = Billing::Payment.find(params[:payment_id])
|
||||||
Billing::AttachPaymentInvoice.call(payment: payment, pdf: params[:pdf])
|
Billing::AttachPaymentInvoice.call(payment: payment, pdf: params[:pdf])
|
||||||
redirect_to admin_billing_path(billing_redirect_params(payment)),
|
redirect_to admin_billing_path(billing_redirect_params(payment)),
|
||||||
notice: "Fattura caricata e inviata a #{payment.club.billing_email}."
|
notice: t("admin.flash.invoice_uploaded", email: payment.club.billing_email)
|
||||||
rescue Billing::AttachPaymentInvoice::Error, Billing::IssueInvoice::Error => e
|
rescue Billing::AttachPaymentInvoice::Error, Billing::IssueInvoice::Error => e
|
||||||
payment ||= Billing::Payment.find_by(id: params[:payment_id])
|
payment ||= Billing::Payment.find_by(id: params[:payment_id])
|
||||||
redirect_to admin_billing_path(payment ? billing_redirect_params(payment) : {}),
|
redirect_to admin_billing_path(payment ? billing_redirect_params(payment) : {}),
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ module Admin
|
|||||||
end
|
end
|
||||||
|
|
||||||
def new
|
def new
|
||||||
redirect_to admin_billing_path(club_id: @club.id), alert: "Carica il PDF dalla lista «Pagamenti da fatturare»."
|
redirect_to admin_billing_path(club_id: @club.id), alert: t("admin.flash.invoice_upload_hint")
|
||||||
end
|
end
|
||||||
|
|
||||||
def create
|
def create
|
||||||
@@ -18,7 +18,7 @@ module Admin
|
|||||||
|
|
||||||
if @invoice.save
|
if @invoice.save
|
||||||
redirect_to edit_admin_club_billing_invoice_path(@club, @invoice),
|
redirect_to edit_admin_club_billing_invoice_path(@club, @invoice),
|
||||||
notice: "Bozza creata. Carica il PDF dalla pagina Fatturazione o qui sotto."
|
notice: t("admin.flash.invoice_draft_created")
|
||||||
else
|
else
|
||||||
@payment = @invoice.billing_payment
|
@payment = @invoice.billing_payment
|
||||||
flash.now[:alert] = @invoice.errors.full_messages.join(", ")
|
flash.now[:alert] = @invoice.errors.full_messages.join(", ")
|
||||||
@@ -36,9 +36,9 @@ module Admin
|
|||||||
if issuing?
|
if issuing?
|
||||||
Billing::IssueInvoice.call(invoice: @invoice, pdf: params.dig(:billing_invoice, :pdf))
|
Billing::IssueInvoice.call(invoice: @invoice, pdf: params.dig(:billing_invoice, :pdf))
|
||||||
redirect_to admin_billing_path(club_id: @club.id),
|
redirect_to admin_billing_path(club_id: @club.id),
|
||||||
notice: "Fattura #{@invoice.number} emessa e inviata a #{@club.billing_email}."
|
notice: t("admin.flash.invoice_issued", number: @invoice.number, email: @club.billing_email)
|
||||||
elsif @invoice.save
|
elsif @invoice.save
|
||||||
redirect_to admin_billing_path(club_id: @club.id), notice: "Fattura #{@invoice.number} aggiornata."
|
redirect_to admin_billing_path(club_id: @club.id), notice: t("admin.flash.invoice_updated", number: @invoice.number)
|
||||||
else
|
else
|
||||||
@payment = @invoice.billing_payment
|
@payment = @invoice.billing_payment
|
||||||
flash.now[:alert] = @invoice.errors.full_messages.join(", ")
|
flash.now[:alert] = @invoice.errors.full_messages.join(", ")
|
||||||
@@ -61,7 +61,7 @@ module Admin
|
|||||||
end
|
end
|
||||||
|
|
||||||
def issuing?
|
def issuing?
|
||||||
params[:commit].to_s == "Emetti e invia via email"
|
params[:commit_action].to_s == "issue"
|
||||||
end
|
end
|
||||||
|
|
||||||
def invoice_params
|
def invoice_params
|
||||||
|
|||||||
@@ -20,14 +20,14 @@ module Admin
|
|||||||
reason: params[:reason],
|
reason: params[:reason],
|
||||||
admin: current_admin_account
|
admin: current_admin_account
|
||||||
)
|
)
|
||||||
redirect_back_or_club notice: "Abbonamento omaggio #{Plan[params[:plan_slug]].name} attivato per #{@club.name}."
|
redirect_back_or_club notice: t("admin.flash.comped_granted", plan: Plan[params[:plan_slug]].name, club: @club.name)
|
||||||
rescue Billing::AdminCompedSubscription::Error, ActiveRecord::RecordInvalid => e
|
rescue Billing::AdminCompedSubscription::Error, ActiveRecord::RecordInvalid => e
|
||||||
redirect_back_or_club alert: e.message
|
redirect_back_or_club alert: e.message
|
||||||
end
|
end
|
||||||
|
|
||||||
def revoke_comped
|
def revoke_comped
|
||||||
Billing::AdminCompedSubscription.revoke(club: @club, admin: current_admin_account)
|
Billing::AdminCompedSubscription.revoke(club: @club, admin: current_admin_account)
|
||||||
redirect_back_or_club notice: "Abbonamento omaggio revocato per #{@club.name}."
|
redirect_back_or_club notice: t("admin.flash.comped_revoked", club: @club.name)
|
||||||
rescue Billing::AdminCompedSubscription::Error => e
|
rescue Billing::AdminCompedSubscription::Error => e
|
||||||
redirect_back_or_club alert: e.message
|
redirect_back_or_club alert: e.message
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
module Admin
|
||||||
|
class LocalesController < BaseController
|
||||||
|
skip_before_action :require_admin_login
|
||||||
|
|
||||||
|
def update
|
||||||
|
locale = LocaleResolver.persist!(cookies, params[:locale])
|
||||||
|
I18n.locale = locale
|
||||||
|
redirect_back fallback_location: admin_root_path
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
@@ -9,19 +9,19 @@ module Admin
|
|||||||
def acknowledge
|
def acknowledge
|
||||||
incident = Ops::Incident.find(params[:id])
|
incident = Ops::Incident.find(params[:id])
|
||||||
incident.acknowledge!
|
incident.acknowledge!
|
||||||
redirect_to admin_ops_path, notice: "Incidente preso in carico"
|
redirect_to admin_ops_path, notice: t("admin.flash.ops_acknowledged")
|
||||||
end
|
end
|
||||||
|
|
||||||
def resolve
|
def resolve
|
||||||
incident = Ops::Incident.find(params[:id])
|
incident = Ops::Incident.find(params[:id])
|
||||||
incident.resolve!
|
incident.resolve!
|
||||||
redirect_to admin_ops_path, notice: "Incidente risolto"
|
redirect_to admin_ops_path, notice: t("admin.flash.ops_resolved")
|
||||||
end
|
end
|
||||||
|
|
||||||
def mute
|
def mute
|
||||||
incident = Ops::Incident.find(params[:id])
|
incident = Ops::Incident.find(params[:id])
|
||||||
incident.mute!(duration: 24.hours)
|
incident.mute!(duration: 24.hours)
|
||||||
redirect_to admin_ops_path, notice: "Notifiche sospese per 24 ore"
|
redirect_to admin_ops_path, notice: t("admin.flash.ops_muted")
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -5,22 +5,22 @@ module Admin
|
|||||||
|
|
||||||
def update
|
def update
|
||||||
unless current_admin_account.authenticate(params[:current_password])
|
unless current_admin_account.authenticate(params[:current_password])
|
||||||
flash.now[:alert] = "Password attuale non corretta"
|
flash.now[:alert] = t("admin.flash.password_current_incorrect")
|
||||||
return render :edit, status: :unprocessable_entity
|
return render :edit, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
|
|
||||||
if params[:password].blank? || params[:password].length < 8
|
if params[:password].blank? || params[:password].length < 8
|
||||||
flash.now[:alert] = "La nuova password deve avere almeno 8 caratteri"
|
flash.now[:alert] = t("admin.flash.password_too_short")
|
||||||
return render :edit, status: :unprocessable_entity
|
return render :edit, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
|
|
||||||
if params[:password] != params[:password_confirmation]
|
if params[:password] != params[:password_confirmation]
|
||||||
flash.now[:alert] = "Le password non coincidono"
|
flash.now[:alert] = t("admin.flash.password_mismatch")
|
||||||
return render :edit, status: :unprocessable_entity
|
return render :edit, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
|
|
||||||
if current_admin_account.update(password: params[:password])
|
if current_admin_account.update(password: params[:password])
|
||||||
redirect_to admin_root_path, notice: "Password aggiornata"
|
redirect_to admin_root_path, notice: t("admin.flash.password_updated")
|
||||||
else
|
else
|
||||||
flash.now[:alert] = current_admin_account.errors.full_messages.to_sentence
|
flash.now[:alert] = current_admin_account.errors.full_messages.to_sentence
|
||||||
render :edit, status: :unprocessable_entity
|
render :edit, status: :unprocessable_entity
|
||||||
|
|||||||
@@ -12,28 +12,28 @@ module Admin
|
|||||||
def stop
|
def stop
|
||||||
@session = StreamSession.find(params[:id])
|
@session = StreamSession.find(params[:id])
|
||||||
if @session.terminal?
|
if @session.terminal?
|
||||||
redirect_to admin_session_path(@session), alert: "Sessione già terminata (#{@session.status})."
|
redirect_to admin_session_path(@session), alert: t("admin.flash.session_already_terminated", status: @session.status)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
Sessions::Stop.new(@session).call
|
Sessions::Stop.new(@session).call
|
||||||
redirect_to admin_session_path(@session), notice: "Sessione terminata."
|
redirect_to admin_session_path(@session), notice: t("admin.flash.session_stopped")
|
||||||
rescue StandardError => e
|
rescue StandardError => e
|
||||||
Rails.logger.error("[admin] stop session #{@session.id}: #{e.class} #{e.message}")
|
Rails.logger.error("[admin] stop session #{@session.id}: #{e.class} #{e.message}")
|
||||||
redirect_to admin_session_path(@session), alert: "Errore durante la chiusura: #{e.message}"
|
redirect_to admin_session_path(@session), alert: t("admin.flash.session_stop_error", error: e.message)
|
||||||
end
|
end
|
||||||
|
|
||||||
def regia_link
|
def regia_link
|
||||||
@session = StreamSession.find(params[:id])
|
@session = StreamSession.find(params[:id])
|
||||||
if @session.terminal?
|
if @session.terminal?
|
||||||
redirect_to admin_session_path(@session), alert: "Sessione terminata: impossibile generare il link regia."
|
redirect_to admin_session_path(@session), alert: t("admin.flash.session_terminated_no_regia")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
access = Sessions::RegiaAccess.new(@session)
|
access = Sessions::RegiaAccess.new(@session)
|
||||||
token = access.issue_token!
|
token = access.issue_token!
|
||||||
redirect_to admin_session_path(@session),
|
redirect_to admin_session_path(@session),
|
||||||
notice: "Link regia generato.",
|
notice: t("admin.flash.session_regia_generated"),
|
||||||
flash: {
|
flash: {
|
||||||
regia_url: access.url(token),
|
regia_url: access.url(token),
|
||||||
regia_expires_at: @session.regia_token_expires_at&.iso8601
|
regia_expires_at: @session.regia_token_expires_at&.iso8601
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ module Admin
|
|||||||
class YoutubeController < Admin::BaseController
|
class YoutubeController < Admin::BaseController
|
||||||
def platform
|
def platform
|
||||||
if ENV["YOUTUBE_CLIENT_ID"].blank?
|
if ENV["YOUTUBE_CLIENT_ID"].blank?
|
||||||
redirect_to admin_root_path, alert: "Configura YOUTUBE_CLIENT_ID e YOUTUBE_CLIENT_SECRET in .env"
|
redirect_to admin_root_path, alert: t("admin.flash.youtube_not_configured")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -15,9 +15,9 @@ module Public
|
|||||||
@club,
|
@club,
|
||||||
plan: premium_checkout_return_params[:plan],
|
plan: premium_checkout_return_params[:plan],
|
||||||
interval: premium_checkout_return_params[:interval]
|
interval: premium_checkout_return_params[:interval]
|
||||||
), notice: "Dati salvati. Procedi con il pagamento."
|
), notice: t("flash.club_billing.profile_saved_proceed_payment")
|
||||||
else
|
else
|
||||||
redirect_to public_club_billing_path(@club), notice: "Dati di fatturazione aggiornati."
|
redirect_to public_club_billing_path(@club), notice: t("flash.club_billing.profile_updated")
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
flash.now[:alert] = @club.errors.full_messages.join(", ")
|
flash.now[:alert] = @club.errors.full_messages.join(", ")
|
||||||
@@ -27,25 +27,25 @@ module Public
|
|||||||
|
|
||||||
def cancel_subscription
|
def cancel_subscription
|
||||||
unless MatchLiveTv.stripe_enabled?
|
unless MatchLiveTv.stripe_enabled?
|
||||||
redirect_to public_club_billing_path(@club), alert: "Stripe non configurato."
|
redirect_to public_club_billing_path(@club), alert: t("flash.club_billing.stripe_not_configured")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
Billing::Stripe::CancelSubscription.call(club: @club)
|
Billing::Stripe::CancelSubscription.call(club: @club)
|
||||||
sub = @club.subscription.reload
|
sub = @club.subscription.reload
|
||||||
date = sub.current_period_end ? helpers.l_local(sub.current_period_end.to_date) : "fine periodo"
|
date = sub.current_period_end ? helpers.l_local(sub.current_period_end.to_date) : t("billing.subscription_status.end_of_period")
|
||||||
redirect_to public_club_billing_path(@club),
|
redirect_to public_club_billing_path(@club),
|
||||||
notice: "Abbonamento disdetto. Resta attivo fino al #{date}; da quel giorno passerai al piano Free."
|
notice: t("flash.club_billing.subscription_canceled", date: date)
|
||||||
rescue ArgumentError, RuntimeError => e
|
rescue ArgumentError, RuntimeError => e
|
||||||
redirect_to public_club_billing_path(@club), alert: e.message
|
redirect_to public_club_billing_path(@club), alert: e.message
|
||||||
rescue ::Stripe::StripeError => e
|
rescue ::Stripe::StripeError => e
|
||||||
redirect_to public_club_billing_path(@club), alert: "Errore Stripe: #{e.message}"
|
redirect_to public_club_billing_path(@club), alert: t("flash.club_billing.stripe_error", message: e.message)
|
||||||
end
|
end
|
||||||
|
|
||||||
def download_invoice
|
def download_invoice
|
||||||
invoice = @club.billing_invoices.find(params[:invoice_id])
|
invoice = @club.billing_invoices.find(params[:invoice_id])
|
||||||
unless invoice.pdf.attached?
|
unless invoice.pdf.attached?
|
||||||
redirect_to public_club_billing_path(@club), alert: "PDF fattura non disponibile."
|
redirect_to public_club_billing_path(@club), alert: t("flash.club_billing.invoice_pdf_unavailable")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ module Public
|
|||||||
@selected_team = @schedulable_teams.find { |t| t.id.to_s == params[:team_id].to_s }
|
@selected_team = @schedulable_teams.find { |t| t.id.to_s == params[:team_id].to_s }
|
||||||
unless @selected_team
|
unless @selected_team
|
||||||
@match = default_match_for_form
|
@match = default_match_for_form
|
||||||
flash.now[:alert] = "Seleziona una squadra valida."
|
flash.now[:alert] = t("flash.club_matches.invalid_team")
|
||||||
return render :new, status: :unprocessable_entity
|
return render :new, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ module Public
|
|||||||
match_params.merge(sport: @selected_team.sport, sets_to_win: 3)
|
match_params.merge(sport: @selected_team.sport, sets_to_win: 3)
|
||||||
)
|
)
|
||||||
redirect_to public_live_index_path(club_id: @club.id),
|
redirect_to public_live_index_path(club_id: @club.id),
|
||||||
notice: "Partita programmata: #{@selected_team.name} vs #{@match.opponent_name}."
|
notice: t("flash.club_matches.scheduled", team: @selected_team.name, opponent: @match.opponent_name)
|
||||||
rescue ActiveRecord::RecordInvalid => e
|
rescue ActiveRecord::RecordInvalid => e
|
||||||
@match = @selected_team.matches.build(match_params)
|
@match = @selected_team.matches.build(match_params)
|
||||||
flash.now[:alert] = e.record.errors.full_messages.join(", ")
|
flash.now[:alert] = e.record.errors.full_messages.join(", ")
|
||||||
@@ -44,7 +44,7 @@ module Public
|
|||||||
return if @schedulable_teams.any?
|
return if @schedulable_teams.any?
|
||||||
|
|
||||||
redirect_to public_live_index_path(club_id: @club.id),
|
redirect_to public_live_index_path(club_id: @club.id),
|
||||||
alert: "Non hai permessi per programmare partite in questa società."
|
alert: t("flash.club_matches.no_schedulable_teams")
|
||||||
end
|
end
|
||||||
|
|
||||||
def resolve_selected_team
|
def resolve_selected_team
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ module Public
|
|||||||
return if current_user.owned_clubs.exists?(id: @club.id)
|
return if current_user.owned_clubs.exists?(id: @club.id)
|
||||||
return if current_user.manageable_teams.joins(:club).exists?(clubs: { id: @club.id })
|
return if current_user.manageable_teams.joins(:club).exists?(clubs: { id: @club.id })
|
||||||
|
|
||||||
redirect_to public_clubs_path, alert: "Non autorizzato"
|
redirect_to public_clubs_path, alert: t("flash.club_recordings.not_authorized")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ module Public
|
|||||||
|
|
||||||
def create
|
def create
|
||||||
if current_user.owned_clubs.exists?
|
if current_user.owned_clubs.exists?
|
||||||
redirect_to public_club_path(current_user.primary_club), alert: "Hai già registrato una società"
|
redirect_to public_club_path(current_user.primary_club), alert: t("flash.clubs.already_registered")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -22,7 +22,7 @@ module Public
|
|||||||
|
|
||||||
ClubMembership.create!(user: current_user, club: club, role: "owner")
|
ClubMembership.create!(user: current_user, club: club, role: "owner")
|
||||||
|
|
||||||
first_team_name = params.dig(:first_team, :name).presence || "Prima squadra"
|
first_team_name = params.dig(:first_team, :name).presence || t("club.new.default_first_team_name")
|
||||||
club.teams.create!(
|
club.teams.create!(
|
||||||
name: first_team_name,
|
name: first_team_name,
|
||||||
sport: club.sport
|
sport: club.sport
|
||||||
@@ -34,14 +34,14 @@ module Public
|
|||||||
if plan.in?(%w[premium_light premium_full]) && MatchLiveTv.stripe_enabled?
|
if plan.in?(%w[premium_light premium_full]) && MatchLiveTv.stripe_enabled?
|
||||||
unless club.billing_profile_complete?
|
unless club.billing_profile_complete?
|
||||||
redirect_to public_club_billing_profile_path(club, plan: plan, interval: checkout_interval_param),
|
redirect_to public_club_billing_profile_path(club, plan: plan, interval: checkout_interval_param),
|
||||||
alert: "Completa i dati di fatturazione prima di attivare un piano premium."
|
alert: t("flash.clubs.complete_billing_first")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
interval = checkout_interval_param
|
interval = checkout_interval_param
|
||||||
redirect_to public_club_checkout_path(club, plan: plan, interval: interval)
|
redirect_to public_club_checkout_path(club, plan: plan, interval: interval)
|
||||||
else
|
else
|
||||||
redirect_to public_club_path(club), notice: "Società e prima squadra create."
|
redirect_to public_club_path(club), notice: t("flash.clubs.created")
|
||||||
end
|
end
|
||||||
rescue ActiveRecord::RecordInvalid => e
|
rescue ActiveRecord::RecordInvalid => e
|
||||||
flash.now[:alert] = e.record.errors.full_messages.join(", ")
|
flash.now[:alert] = e.record.errors.full_messages.join(", ")
|
||||||
@@ -66,7 +66,7 @@ module Public
|
|||||||
@club.assign_attributes(club_params)
|
@club.assign_attributes(club_params)
|
||||||
attach_branding_logo(@club)
|
attach_branding_logo(@club)
|
||||||
@club.save!
|
@club.save!
|
||||||
redirect_to public_club_path(@club), notice: "Società aggiornata."
|
redirect_to public_club_path(@club), notice: t("flash.clubs.updated")
|
||||||
rescue ActiveRecord::RecordInvalid => e
|
rescue ActiveRecord::RecordInvalid => e
|
||||||
flash.now[:alert] = e.record.errors.full_messages.join(", ")
|
flash.now[:alert] = e.record.errors.full_messages.join(", ")
|
||||||
render :edit, status: :unprocessable_entity
|
render :edit, status: :unprocessable_entity
|
||||||
@@ -88,12 +88,12 @@ module Public
|
|||||||
require_club_owner!(@club)
|
require_club_owner!(@club)
|
||||||
if @club.subscription&.admin_comped?
|
if @club.subscription&.admin_comped?
|
||||||
redirect_to public_club_billing_path(@club),
|
redirect_to public_club_billing_path(@club),
|
||||||
alert: "Il piano è un abbonamento omaggio gestito da Match Live TV. Per passare a un abbonamento a pagamento contatta il supporto."
|
alert: t("flash.clubs.comped_change_denied")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
unless MatchLiveTv.stripe_enabled?
|
unless MatchLiveTv.stripe_enabled?
|
||||||
redirect_to public_club_billing_path(@club), alert: "Pagamenti non ancora configurati sul server"
|
redirect_to public_club_billing_path(@club), alert: t("flash.clubs.stripe_not_configured")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -105,15 +105,15 @@ module Public
|
|||||||
if sub&.stripe_subscription_id.present? && sub.active? &&
|
if sub&.stripe_subscription_id.present? && sub.active? &&
|
||||||
sub.plan.slug == plan_slug && sub.billing_interval == interval && !sub.plan_change_pending?
|
sub.plan.slug == plan_slug && sub.billing_interval == interval && !sub.plan_change_pending?
|
||||||
redirect_to public_club_billing_path(@club),
|
redirect_to public_club_billing_path(@club),
|
||||||
notice: "Sei già su #{target_plan.name} (#{Billing::Stripe::PriceCatalog.label(plan_slug: plan_slug, interval: interval)})."
|
notice: t("flash.clubs.already_on_plan", plan: target_plan.name, price: Billing::Stripe::PriceCatalog.label(plan_slug: plan_slug, interval: interval))
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if sub&.plan_change_pending? && sub.pending_plan&.slug == plan_slug &&
|
if sub&.plan_change_pending? && sub.pending_plan&.slug == plan_slug &&
|
||||||
sub.pending_billing_interval == interval
|
sub.pending_billing_interval == interval
|
||||||
when_label = sub.current_period_end.present? ? I18n.l(sub.current_period_end, format: :long) : "il prossimo rinnovo"
|
when_label = sub.current_period_end.present? ? I18n.l(sub.current_period_end, format: :long) : t("billing.messages.next_renewal")
|
||||||
redirect_to public_club_billing_path(@club),
|
redirect_to public_club_billing_path(@club),
|
||||||
notice: "Il passaggio a #{target_plan.name} è già programmato dal #{when_label}."
|
notice: t("flash.clubs.change_already_scheduled", plan: target_plan.name, date: when_label)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -134,7 +134,7 @@ module Public
|
|||||||
redirect_to public_club_billing_path(@club), alert: e.message
|
redirect_to public_club_billing_path(@club), alert: e.message
|
||||||
rescue ::Stripe::StripeError => e
|
rescue ::Stripe::StripeError => e
|
||||||
Rails.logger.warn("[Stripe checkout] #{e.message}")
|
Rails.logger.warn("[Stripe checkout] #{e.message}")
|
||||||
redirect_to public_club_billing_path(@club), alert: "Errore Stripe: #{e.message}"
|
redirect_to public_club_billing_path(@club), alert: t("flash.clubs.stripe_error", message: e.message)
|
||||||
end
|
end
|
||||||
|
|
||||||
def youtube_connect
|
def youtube_connect
|
||||||
@@ -143,7 +143,7 @@ module Public
|
|||||||
entitlements.assert_can_connect_youtube!
|
entitlements.assert_can_connect_youtube!
|
||||||
|
|
||||||
if ENV["YOUTUBE_CLIENT_ID"].blank?
|
if ENV["YOUTUBE_CLIENT_ID"].blank?
|
||||||
redirect_to public_club_path(@club), alert: "YouTube OAuth non configurato sul server"
|
redirect_to public_club_path(@club), alert: t("flash.clubs.youtube_oauth_not_configured")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -161,13 +161,13 @@ module Public
|
|||||||
def youtube_disconnect
|
def youtube_disconnect
|
||||||
require_club_owner!(@club)
|
require_club_owner!(@club)
|
||||||
@club.youtube_credential&.destroy!
|
@club.youtube_credential&.destroy!
|
||||||
redirect_to public_club_path(@club), notice: "Canale YouTube della società scollegato"
|
redirect_to public_club_path(@club), notice: t("flash.clubs.youtube_disconnected")
|
||||||
end
|
end
|
||||||
|
|
||||||
def portal
|
def portal
|
||||||
require_club_owner!(@club)
|
require_club_owner!(@club)
|
||||||
unless MatchLiveTv.stripe_enabled?
|
unless MatchLiveTv.stripe_enabled?
|
||||||
redirect_to public_club_billing_path(@club), alert: "Portale pagamenti non configurato"
|
redirect_to public_club_billing_path(@club), alert: t("flash.clubs.portal_not_configured")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -204,7 +204,7 @@ module Public
|
|||||||
@club,
|
@club,
|
||||||
plan: plan_slug,
|
plan: plan_slug,
|
||||||
interval: params[:interval].presence
|
interval: params[:interval].presence
|
||||||
), alert: "Completa i dati di fatturazione prima di attivare un piano premium."
|
), alert: t("flash.clubs.complete_billing_first")
|
||||||
end
|
end
|
||||||
|
|
||||||
def checkout_interval_param
|
def checkout_interval_param
|
||||||
@@ -236,10 +236,10 @@ module Public
|
|||||||
def apply_checkout_flash!
|
def apply_checkout_flash!
|
||||||
case params[:checkout]
|
case params[:checkout]
|
||||||
when "success"
|
when "success"
|
||||||
plan_name = @subscription&.plan&.name || "premium"
|
plan_name = @subscription&.plan&.name || "Premium"
|
||||||
flash.now[:notice] = "Pagamento completato! Piano attivo: #{plan_name}."
|
flash.now[:notice] = t("flash.clubs.checkout_success", plan: plan_name)
|
||||||
when "canceled"
|
when "canceled"
|
||||||
flash.now[:alert] = "Pagamento annullato. Nessun addebito è stato effettuato."
|
flash.now[:alert] = t("flash.clubs.checkout_canceled")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,24 +4,24 @@ module Public
|
|||||||
@token = params[:token]
|
@token = params[:token]
|
||||||
@invitation = TeamInvitation.pending.find_by(token_digest: Digest::SHA256.hexdigest(@token.to_s))
|
@invitation = TeamInvitation.pending.find_by(token_digest: Digest::SHA256.hexdigest(@token.to_s))
|
||||||
unless @invitation
|
unless @invitation
|
||||||
redirect_to public_pricing_path, alert: "Invito non valido o scaduto"
|
redirect_to public_pricing_path, alert: t("flash.invitations.invalid_or_expired")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def accept
|
def accept
|
||||||
invitation = TeamInvitation.pending.find_by(token_digest: Digest::SHA256.hexdigest(params[:token].to_s))
|
invitation = TeamInvitation.pending.find_by(token_digest: Digest::SHA256.hexdigest(params[:token].to_s))
|
||||||
return redirect_to public_pricing_path, alert: "Invito non valido" unless invitation
|
return redirect_to public_pricing_path, alert: t("flash.invitations.invalid") unless invitation
|
||||||
|
|
||||||
if logged_in?
|
if logged_in?
|
||||||
if current_user.email.downcase != invitation.email.downcase
|
if current_user.email.downcase != invitation.email.downcase
|
||||||
redirect_to public_pricing_path, alert: "Questo invito è per #{invitation.email}"
|
redirect_to public_pricing_path, alert: t("flash.invitations.wrong_email", email: invitation.email)
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
invitation.accept!(current_user)
|
invitation.accept!(current_user)
|
||||||
redirect_to public_team_details_path(invitation.team), notice: "Sei entrato nella squadra!"
|
redirect_to public_team_details_path(invitation.team), notice: t("flash.invitations.joined_team")
|
||||||
else
|
else
|
||||||
session[:pending_invite_token] = params[:token]
|
session[:pending_invite_token] = params[:token]
|
||||||
redirect_to public_signup_path, notice: "Registrati con #{invitation.email} per accettare l'invito"
|
redirect_to public_signup_path, notice: t("flash.invitations.signup_to_accept", email: invitation.email)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ module Public
|
|||||||
match_params.merge(sport: @team.sport, sets_to_win: match_params[:sets_to_win].presence || 3)
|
match_params.merge(sport: @team.sport, sets_to_win: match_params[:sets_to_win].presence || 3)
|
||||||
)
|
)
|
||||||
redirect_to public_team_matches_path(@team),
|
redirect_to public_team_matches_path(@team),
|
||||||
notice: "Partita programmata: #{@match.opponent_name}. Avvia lo streaming dall’app quando sei in palestra."
|
notice: t("flash.matches.scheduled", opponent: @match.opponent_name)
|
||||||
rescue ActiveRecord::RecordInvalid => e
|
rescue ActiveRecord::RecordInvalid => e
|
||||||
@match = @team.matches.build(match_params)
|
@match = @team.matches.build(match_params)
|
||||||
flash.now[:alert] = e.record.errors.full_messages.join(", ")
|
flash.now[:alert] = e.record.errors.full_messages.join(", ")
|
||||||
@@ -37,7 +37,7 @@ module Public
|
|||||||
|
|
||||||
def update
|
def update
|
||||||
@match.update!(match_params)
|
@match.update!(match_params)
|
||||||
redirect_to public_team_matches_path(@team), notice: "Partita aggiornata."
|
redirect_to public_team_matches_path(@team), notice: t("flash.matches.updated")
|
||||||
rescue ActiveRecord::RecordInvalid => e
|
rescue ActiveRecord::RecordInvalid => e
|
||||||
flash.now[:alert] = e.record.errors.full_messages.join(", ")
|
flash.now[:alert] = e.record.errors.full_messages.join(", ")
|
||||||
render :edit, status: :unprocessable_entity
|
render :edit, status: :unprocessable_entity
|
||||||
@@ -47,12 +47,12 @@ module Public
|
|||||||
active = active_session_for(@match)
|
active = active_session_for(@match)
|
||||||
if active&.resumable?
|
if active&.resumable?
|
||||||
redirect_to public_team_matches_path(@team),
|
redirect_to public_team_matches_path(@team),
|
||||||
alert: "Chiudi la diretta prima di eliminare questa partita."
|
alert: t("flash.matches.close_live_before_delete")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@match.destroy!
|
@match.destroy!
|
||||||
redirect_to public_team_matches_path(@team), notice: "Partita eliminata."
|
redirect_to public_team_matches_path(@team), notice: t("flash.matches.deleted")
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@@ -66,7 +66,7 @@ module Public
|
|||||||
return if current_user.can_schedule_for?(@team)
|
return if current_user.can_schedule_for?(@team)
|
||||||
|
|
||||||
redirect_to public_team_details_path(@team),
|
redirect_to public_team_details_path(@team),
|
||||||
alert: "Solo un responsabile trasmissione o il titolare della società possono programmare le partite."
|
alert: t("flash.matches.schedule_denied")
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_match
|
def set_match
|
||||||
|
|||||||
@@ -11,14 +11,14 @@ module Public
|
|||||||
end
|
end
|
||||||
|
|
||||||
redirect_to public_login_path,
|
redirect_to public_login_path,
|
||||||
notice: "Se l'email è registrata, riceverai a breve un link per reimpostare la password."
|
notice: t("flash.password_resets.email_sent")
|
||||||
end
|
end
|
||||||
|
|
||||||
def edit
|
def edit
|
||||||
@user = User.find_by_password_reset_token(params[:token])
|
@user = User.find_by_password_reset_token(params[:token])
|
||||||
if @user.nil? || @user.password_reset_expired?
|
if @user.nil? || @user.password_reset_expired?
|
||||||
redirect_to new_public_password_reset_path,
|
redirect_to new_public_password_reset_path,
|
||||||
alert: "Link non valido o scaduto. Richiedi un nuovo reset password."
|
alert: t("flash.password_resets.invalid_or_expired_link")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
@token = params[:token]
|
@token = params[:token]
|
||||||
@@ -28,25 +28,25 @@ module Public
|
|||||||
@user = User.find_by_password_reset_token(params[:token])
|
@user = User.find_by_password_reset_token(params[:token])
|
||||||
if @user.nil? || @user.password_reset_expired?
|
if @user.nil? || @user.password_reset_expired?
|
||||||
redirect_to new_public_password_reset_path,
|
redirect_to new_public_password_reset_path,
|
||||||
alert: "Link non valido o scaduto. Richiedi un nuovo reset password."
|
alert: t("flash.password_resets.invalid_or_expired_link")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
if params[:password].blank? || params[:password].length < 8
|
if params[:password].blank? || params[:password].length < 8
|
||||||
flash.now[:alert] = "La password deve avere almeno 8 caratteri"
|
flash.now[:alert] = t("flash.password_resets.password_min_length")
|
||||||
@token = params[:token]
|
@token = params[:token]
|
||||||
return render :edit, status: :unprocessable_entity
|
return render :edit, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
|
|
||||||
if params[:password] != params[:password_confirmation]
|
if params[:password] != params[:password_confirmation]
|
||||||
flash.now[:alert] = "Le password non coincidono"
|
flash.now[:alert] = t("flash.password_resets.password_mismatch")
|
||||||
@token = params[:token]
|
@token = params[:token]
|
||||||
return render :edit, status: :unprocessable_entity
|
return render :edit, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
|
|
||||||
@user.update!(password: params[:password])
|
@user.update!(password: params[:password])
|
||||||
@user.clear_password_reset!
|
@user.clear_password_reset!
|
||||||
redirect_to public_login_path, notice: "Password aggiornata. Ora puoi accedere."
|
redirect_to public_login_path, notice: t("flash.password_resets.password_updated")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ module Public
|
|||||||
def create
|
def create
|
||||||
unless params[:accept_terms] == "1"
|
unless params[:accept_terms] == "1"
|
||||||
@user = User.new(user_params.merge(role: "coach"))
|
@user = User.new(user_params.merge(role: "coach"))
|
||||||
flash.now[:alert] = "Devi accettare l’informativa privacy e i termini di servizio per registrarti."
|
flash.now[:alert] = t("flash.registrations.accept_terms_required")
|
||||||
return render :new, status: :unprocessable_entity
|
return render :new, status: :unprocessable_entity
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -20,10 +20,10 @@ module Public
|
|||||||
invitation = TeamInvitation.pending.find_by(token_digest: Digest::SHA256.hexdigest(token))
|
invitation = TeamInvitation.pending.find_by(token_digest: Digest::SHA256.hexdigest(token))
|
||||||
if invitation && invitation.email.downcase == @user.email.downcase
|
if invitation && invitation.email.downcase == @user.email.downcase
|
||||||
invitation.accept!(@user)
|
invitation.accept!(@user)
|
||||||
return redirect_to public_team_details_path(invitation.team), notice: "Benvenuto nella squadra!"
|
return redirect_to public_team_details_path(invitation.team), notice: t("flash.registrations.welcome_to_team")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
redirect_to public_new_club_path, notice: "Account creato. Ora registra la tua società."
|
redirect_to public_new_club_path, notice: t("flash.registrations.account_created")
|
||||||
else
|
else
|
||||||
flash.now[:alert] = @user.errors.full_messages.join(", ")
|
flash.now[:alert] = @user.errors.full_messages.join(", ")
|
||||||
render :new, status: :unprocessable_entity
|
render :new, status: :unprocessable_entity
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ module Public
|
|||||||
|
|
||||||
def download
|
def download
|
||||||
unless download_allowed?
|
unless download_allowed?
|
||||||
redirect_to public_replay_path(@session), alert: "Download non disponibile"
|
redirect_to public_replay_path(@session), alert: t("flash.replay.download_unavailable")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -105,13 +105,13 @@ module Public
|
|||||||
@recording = Recording.not_deleted.find_by(stream_session: @session)
|
@recording = Recording.not_deleted.find_by(stream_session: @session)
|
||||||
return if @recording
|
return if @recording
|
||||||
|
|
||||||
redirect_to public_live_path(@session), alert: "Replay non disponibile"
|
redirect_to public_live_path(@session), alert: t("flash.replay.not_available")
|
||||||
end
|
end
|
||||||
|
|
||||||
def authorize_replay_access!
|
def authorize_replay_access!
|
||||||
return if Recordings::AccessPolicy.new(@recording, viewer: current_user).allowed?
|
return if Recordings::AccessPolicy.new(@recording, viewer: current_user).allowed?
|
||||||
|
|
||||||
redirect_to public_live_index_path, alert: "Replay non disponibile"
|
redirect_to public_live_index_path, alert: t("flash.replay.not_available")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -17,16 +17,16 @@ module Public
|
|||||||
else
|
else
|
||||||
public_new_club_path
|
public_new_club_path
|
||||||
end
|
end
|
||||||
redirect_to dest, notice: "Bentornato!"
|
redirect_to dest, notice: t("flash.sessions.welcome_back")
|
||||||
else
|
else
|
||||||
flash.now[:alert] = "Email o password non validi"
|
flash.now[:alert] = t("flash.sessions.invalid_credentials")
|
||||||
render :new, status: :unauthorized
|
render :new, status: :unauthorized
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy
|
def destroy
|
||||||
reset_session
|
reset_session
|
||||||
redirect_to public_pricing_path, notice: "Disconnesso"
|
redirect_to public_pricing_path, notice: t("flash.sessions.logged_out")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ module Public
|
|||||||
member = @team.roster_members.build(member_params)
|
member = @team.roster_members.build(member_params)
|
||||||
attach_photo(member)
|
attach_photo(member)
|
||||||
member.save!
|
member.save!
|
||||||
redirect_to public_team_details_path(@team), notice: "#{member.full_name} aggiunto all'organico."
|
redirect_to public_team_details_path(@team), notice: t("flash.team_roster_members.added", name: member.full_name)
|
||||||
rescue ActiveRecord::RecordInvalid => e
|
rescue ActiveRecord::RecordInvalid => e
|
||||||
redirect_to public_team_details_path(@team), alert: e.record.errors.full_messages.join(", ")
|
redirect_to public_team_details_path(@team), alert: e.record.errors.full_messages.join(", ")
|
||||||
end
|
end
|
||||||
@@ -22,7 +22,7 @@ module Public
|
|||||||
@member.assign_attributes(member_params)
|
@member.assign_attributes(member_params)
|
||||||
attach_photo(@member)
|
attach_photo(@member)
|
||||||
@member.save!
|
@member.save!
|
||||||
redirect_to public_team_details_path(@team), notice: "Scheda aggiornata."
|
redirect_to public_team_details_path(@team), notice: t("flash.team_roster_members.updated")
|
||||||
rescue ActiveRecord::RecordInvalid => e
|
rescue ActiveRecord::RecordInvalid => e
|
||||||
@club = @team.club
|
@club = @team.club
|
||||||
flash.now[:alert] = e.record.errors.full_messages.join(", ")
|
flash.now[:alert] = e.record.errors.full_messages.join(", ")
|
||||||
@@ -32,7 +32,7 @@ module Public
|
|||||||
def destroy
|
def destroy
|
||||||
name = @member.full_name
|
name = @member.full_name
|
||||||
@member.destroy!
|
@member.destroy!
|
||||||
redirect_to public_team_details_path(@team), notice: "#{name} rimosso dall'organico."
|
redirect_to public_team_details_path(@team), notice: t("flash.team_roster_members.removed", name: name)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ module Public
|
|||||||
require_club_owner!(@club)
|
require_club_owner!(@club)
|
||||||
team = @club.teams.create!(team_params)
|
team = @club.teams.create!(team_params)
|
||||||
attach_branding_logo(team)
|
attach_branding_logo(team)
|
||||||
redirect_to public_club_path(@club), notice: "Squadra «#{team.name}» aggiunta."
|
redirect_to public_club_path(@club), notice: t("flash.teams.added", name: team.name)
|
||||||
rescue ActiveRecord::RecordInvalid => e
|
rescue ActiveRecord::RecordInvalid => e
|
||||||
flash.now[:alert] = e.record.errors.full_messages.join(", ")
|
flash.now[:alert] = e.record.errors.full_messages.join(", ")
|
||||||
render :new, status: :unprocessable_entity
|
render :new, status: :unprocessable_entity
|
||||||
@@ -49,7 +49,7 @@ module Public
|
|||||||
attach_branding_logo(@team)
|
attach_branding_logo(@team)
|
||||||
attach_team_photo(@team)
|
attach_team_photo(@team)
|
||||||
@team.save!
|
@team.save!
|
||||||
redirect_to public_team_details_path(@team), notice: "Squadra aggiornata."
|
redirect_to public_team_details_path(@team), notice: t("flash.teams.updated")
|
||||||
rescue ActiveRecord::RecordInvalid => e
|
rescue ActiveRecord::RecordInvalid => e
|
||||||
@club = @team.club
|
@club = @team.club
|
||||||
flash.now[:alert] = e.record.errors.full_messages.join(", ")
|
flash.now[:alert] = e.record.errors.full_messages.join(", ")
|
||||||
@@ -66,7 +66,7 @@ module Public
|
|||||||
membership = current_user.user_teams.find_or_initialize_by(team: @team)
|
membership = current_user.user_teams.find_or_initialize_by(team: @team)
|
||||||
membership.role = "member" if membership.new_record?
|
membership.role = "member" if membership.new_record?
|
||||||
Teams::StaffAssignment.call(team: @team, user: current_user, membership: membership)
|
Teams::StaffAssignment.call(team: @team, user: current_user, membership: membership)
|
||||||
redirect_to public_team_details_path(@team), notice: "Il tuo account è ora responsabile trasmissione."
|
redirect_to public_team_details_path(@team), notice: t("flash.teams.now_staff")
|
||||||
rescue Teams::StaffAssignmentError, Teams::EntitlementError => e
|
rescue Teams::StaffAssignmentError, Teams::EntitlementError => e
|
||||||
redirect_to public_team_details_path(@team), alert: e.message
|
redirect_to public_team_details_path(@team), alert: e.message
|
||||||
end
|
end
|
||||||
@@ -75,7 +75,7 @@ module Public
|
|||||||
require_club_owner_for_team!(@team)
|
require_club_owner_for_team!(@team)
|
||||||
membership = current_user.user_teams.find_by(team: @team)
|
membership = current_user.user_teams.find_by(team: @team)
|
||||||
membership&.update!(staff_kind: nil)
|
membership&.update!(staff_kind: nil)
|
||||||
redirect_to public_team_details_path(@team), notice: "Ruolo staff rimosso dal tuo account."
|
redirect_to public_team_details_path(@team), notice: t("flash.teams.staff_role_removed")
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_invitation
|
def create_invitation
|
||||||
@@ -95,7 +95,7 @@ module Public
|
|||||||
expires_at: 7.days.from_now
|
expires_at: 7.days.from_now
|
||||||
)
|
)
|
||||||
@invite_url = join_public_invitation_url(token: token)
|
@invite_url = join_public_invitation_url(token: token)
|
||||||
flash.now[:notice] = "Link invito generato (valido 7 giorni)"
|
flash.now[:notice] = t("flash.teams.invite_link_generated")
|
||||||
render :invite
|
render :invite
|
||||||
rescue Teams::EntitlementError, Teams::StaffAssignmentError => e
|
rescue Teams::EntitlementError, Teams::StaffAssignmentError => e
|
||||||
redirect_to public_team_invite_path(@team), alert: e.message
|
redirect_to public_team_invite_path(@team), alert: e.message
|
||||||
@@ -105,14 +105,14 @@ module Public
|
|||||||
require_club_owner_for_team!(@team)
|
require_club_owner_for_team!(@team)
|
||||||
ut = @team.user_teams.find_by!(user_id: params[:user_id], role: "member")
|
ut = @team.user_teams.find_by!(user_id: params[:user_id], role: "member")
|
||||||
ut.destroy!
|
ut.destroy!
|
||||||
redirect_to public_team_details_path(@team), notice: "Accesso revocato"
|
redirect_to public_team_details_path(@team), notice: t("flash.teams.access_revoked")
|
||||||
end
|
end
|
||||||
|
|
||||||
def destroy_invitation
|
def destroy_invitation
|
||||||
require_club_owner_for_team!(@team)
|
require_club_owner_for_team!(@team)
|
||||||
inv = @team.team_invitations.pending.find(params[:invitation_id])
|
inv = @team.team_invitations.pending.find(params[:invitation_id])
|
||||||
inv.destroy!
|
inv.destroy!
|
||||||
redirect_to public_team_details_path(@team), notice: "Invito annullato"
|
redirect_to public_team_details_path(@team), notice: t("flash.teams.invitation_canceled")
|
||||||
end
|
end
|
||||||
|
|
||||||
def youtube_connect
|
def youtube_connect
|
||||||
@@ -125,7 +125,7 @@ module Public
|
|||||||
def youtube_disconnect
|
def youtube_disconnect
|
||||||
require_club_owner_for_team!(@team)
|
require_club_owner_for_team!(@team)
|
||||||
@team.club.youtube_credential&.destroy!
|
@team.club.youtube_credential&.destroy!
|
||||||
redirect_to public_club_path(@team.club), notice: "Canale YouTube della società scollegato"
|
redirect_to public_club_path(@team.club), notice: t("flash.teams.youtube_disconnected")
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -7,15 +7,15 @@ module Public
|
|||||||
def require_login!
|
def require_login!
|
||||||
return if logged_in?
|
return if logged_in?
|
||||||
|
|
||||||
redirect_to public_login_path, alert: "Accedi per continuare"
|
redirect_to public_login_path, alert: t("flash.web_base.login_required")
|
||||||
end
|
end
|
||||||
|
|
||||||
def require_club_owner!(club)
|
def require_club_owner!(club)
|
||||||
redirect_to public_prezzi_path, alert: "Accesso non consentito" unless club.owned_by?(current_user)
|
redirect_to public_prezzi_path, alert: t("flash.web_base.access_denied") unless club.owned_by?(current_user)
|
||||||
end
|
end
|
||||||
|
|
||||||
def require_club_owner_for_team!(team)
|
def require_club_owner_for_team!(team)
|
||||||
redirect_to public_prezzi_path, alert: "Accesso non consentito" unless team.club&.owned_by?(current_user)
|
redirect_to public_prezzi_path, alert: t("flash.web_base.access_denied") unless team.club&.owned_by?(current_user)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ module AdminHelper
|
|||||||
def admin_session_watch_links(session)
|
def admin_session_watch_links(session)
|
||||||
links = []
|
links = []
|
||||||
if session.matchlivetv_platform?
|
if session.matchlivetv_platform?
|
||||||
links << { label: "Pagina live", url: session.watch_page_url }
|
links << { label: I18n.t("admin.common.live_page"), url: session.watch_page_url }
|
||||||
end
|
end
|
||||||
youtube = session.youtube_watch_url
|
youtube = session.youtube_watch_url
|
||||||
links << { label: "YouTube", url: youtube } if youtube.present?
|
links << { label: "YouTube", url: youtube } if youtube.present?
|
||||||
|
|||||||
@@ -31,6 +31,34 @@ module ApplicationHelper
|
|||||||
I18n.locale.to_s
|
I18n.locale.to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def confirm_dialog_i18n_json
|
||||||
|
{
|
||||||
|
eyebrow: t("common.confirm_eyebrow"),
|
||||||
|
title: t("common.confirm_title"),
|
||||||
|
deleteTitle: t("common.delete_title"),
|
||||||
|
confirmOk: t("common.confirm_ok"),
|
||||||
|
deleteOk: t("common.delete_ok"),
|
||||||
|
cancel: t("common.cancel"),
|
||||||
|
multiDeleteTitle: t("common.multi_delete_title"),
|
||||||
|
multiDeleteMessage: t("common.multi_delete_message"),
|
||||||
|
deleteYoutubeTitle: t("common.delete_youtube_title"),
|
||||||
|
deleteYoutubeMessage: t("common.delete_youtube_message"),
|
||||||
|
deleteYoutubeOk: t("common.delete_youtube_ok"),
|
||||||
|
deleteSiteTitle: t("common.delete_site_title"),
|
||||||
|
deleteSiteMessageWithYoutube: t("common.delete_site_message_with_youtube"),
|
||||||
|
deleteSiteMessageOnly: t("common.delete_site_message_only"),
|
||||||
|
deleteSiteOk: t("common.delete_site_ok"),
|
||||||
|
choiceSiteLabel: t("common.choice_site_label"),
|
||||||
|
choiceSiteHintWithYoutube: t("common.choice_site_hint_with_youtube"),
|
||||||
|
choiceSiteHintOnly: t("common.choice_site_hint_only"),
|
||||||
|
choiceYoutubeLabel: t("common.choice_youtube_label"),
|
||||||
|
choiceYoutubeHintWithSite: t("common.choice_youtube_hint_with_site"),
|
||||||
|
choiceYoutubeHintOnly: t("common.choice_youtube_hint_only"),
|
||||||
|
choiceBothLabel: t("common.choice_both_label"),
|
||||||
|
choiceBothHint: t("common.choice_both_hint")
|
||||||
|
}.to_json
|
||||||
|
end
|
||||||
|
|
||||||
def sport_catalog_options(selected = nil)
|
def sport_catalog_options(selected = nil)
|
||||||
options = Sports::Catalog.as_api_list.map { |entry| [entry[:label], entry[:key]] }
|
options = Sports::Catalog.as_api_list.map { |entry| [entry[:label], entry[:key]] }
|
||||||
options_for_select(options, Sports::Catalog.normalize_key(selected))
|
options_for_select(options, Sports::Catalog.normalize_key(selected))
|
||||||
|
|||||||
@@ -2,25 +2,25 @@ module Public
|
|||||||
module BillingHelper
|
module BillingHelper
|
||||||
def plan_billing_action(current_slug:, target_plan:, stripe_subscription_active:, current_interval: nil, subscription: nil)
|
def plan_billing_action(current_slug:, target_plan:, stripe_subscription_active:, current_interval: nil, subscription: nil)
|
||||||
if target_plan.slug == "free"
|
if target_plan.slug == "free"
|
||||||
return { kind: :current, label: "Piano attuale" } if current_slug == "free"
|
return { kind: :current, label: I18n.t("billing.actions.current_plan") } if current_slug == "free"
|
||||||
return { kind: :none } if stripe_subscription_active
|
return { kind: :none } if stripe_subscription_active
|
||||||
|
|
||||||
return { kind: :contact, label: "Per passare al piano Free, contatta il supporto." }
|
return { kind: :contact, label: I18n.t("billing.actions.contact_for_free") }
|
||||||
end
|
end
|
||||||
|
|
||||||
unless MatchLiveTv.stripe_enabled?
|
unless MatchLiveTv.stripe_enabled?
|
||||||
return { kind: :disabled, label: "Stripe non configurato" }
|
return { kind: :disabled, label: I18n.t("billing.actions.stripe_not_configured") }
|
||||||
end
|
end
|
||||||
|
|
||||||
intervals = Billing::Stripe::PriceCatalog.available_intervals(plan_slug: target_plan.slug)
|
intervals = Billing::Stripe::PriceCatalog.available_intervals(plan_slug: target_plan.slug)
|
||||||
return { kind: :disabled, label: "Prezzi Stripe non configurati" } if intervals.empty?
|
return { kind: :disabled, label: I18n.t("billing.actions.stripe_prices_not_configured") } if intervals.empty?
|
||||||
|
|
||||||
active_interval = current_interval.presence || Billing::Stripe::PriceCatalog::DEFAULT_INTERVAL
|
active_interval = current_interval.presence || Billing::Stripe::PriceCatalog::DEFAULT_INTERVAL
|
||||||
|
|
||||||
if current_slug == target_plan.slug && stripe_subscription_active && !subscription&.plan_change_pending?
|
if current_slug == target_plan.slug && stripe_subscription_active && !subscription&.plan_change_pending?
|
||||||
other_intervals = intervals - [active_interval]
|
other_intervals = intervals - [active_interval]
|
||||||
if other_intervals.empty?
|
if other_intervals.empty?
|
||||||
label = "Piano attuale — #{Billing::Stripe::PriceCatalog.label(plan_slug: target_plan.slug, interval: active_interval)}"
|
label = "#{I18n.t('billing.actions.current_plan')} — #{Billing::Stripe::PriceCatalog.label(plan_slug: target_plan.slug, interval: active_interval)}"
|
||||||
return { kind: :current, label: label }
|
return { kind: :current, label: label }
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -87,9 +87,9 @@ module Public
|
|||||||
|
|
||||||
def billing_profile_incomplete_message(club)
|
def billing_profile_incomplete_message(club)
|
||||||
missing = club.billing_profile_errors
|
missing = club.billing_profile_errors
|
||||||
return "Completa i dati di fatturazione prima di attivare un piano premium." if missing.empty?
|
return I18n.t("billing.actions.profile_incomplete_default") if missing.empty?
|
||||||
|
|
||||||
"Mancano: #{missing.join(", ")}."
|
I18n.t("billing.actions.profile_missing", fields: missing.join(", "))
|
||||||
end
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -8,16 +8,16 @@ module Public
|
|||||||
when "basket", "timed"
|
when "basket", "timed"
|
||||||
live_score_period_label(match, score_state)
|
live_score_period_label(match, score_state)
|
||||||
when "timer"
|
when "timer"
|
||||||
"Cronometro"
|
t("score.timer_label")
|
||||||
when "generic"
|
when "generic"
|
||||||
"Punteggio"
|
t("score.generic_label")
|
||||||
else
|
else
|
||||||
"Set #{score_state.current_set} · Set vinti #{score_state.home_sets}-#{score_state.away_sets}"
|
t("score.sets_label", set: score_state.current_set, home: score_state.home_sets, away: score_state.away_sets)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def live_score_period_label(match, score_state)
|
def live_score_period_label(match, score_state)
|
||||||
return "—" unless score_state
|
return t("score.fallback") unless score_state
|
||||||
|
|
||||||
score_state.current_period_label
|
score_state.current_period_label
|
||||||
end
|
end
|
||||||
@@ -35,16 +35,16 @@ module Public
|
|||||||
def live_scheduled_label(datetime)
|
def live_scheduled_label(datetime)
|
||||||
return nil unless datetime
|
return nil unless datetime
|
||||||
|
|
||||||
datetime.in_time_zone.strftime("%d/%m/%Y alle %H:%M")
|
datetime.in_time_zone.strftime(t("score.scheduled_date_format"))
|
||||||
end
|
end
|
||||||
|
|
||||||
def live_scheduled_relative(datetime)
|
def live_scheduled_relative(datetime)
|
||||||
return nil unless datetime
|
return nil unless datetime
|
||||||
|
|
||||||
if datetime.to_date == Time.zone.today
|
if datetime.to_date == Time.zone.today
|
||||||
"Oggi alle #{datetime.strftime('%H:%M')}"
|
t("score.scheduled_today", time: datetime.strftime("%H:%M"))
|
||||||
elsif datetime.to_date == Time.zone.tomorrow
|
elsif datetime.to_date == Time.zone.tomorrow
|
||||||
"Domani alle #{datetime.strftime('%H:%M')}"
|
t("score.scheduled_tomorrow", time: datetime.strftime("%H:%M"))
|
||||||
else
|
else
|
||||||
live_scheduled_label(datetime)
|
live_scheduled_label(datetime)
|
||||||
end
|
end
|
||||||
@@ -52,7 +52,7 @@ module Public
|
|||||||
|
|
||||||
def live_match_card_heading(match, link_team: true)
|
def live_match_card_heading(match, link_team: true)
|
||||||
team = match.team
|
team = match.team
|
||||||
club_name = team.club&.name.presence || "Società"
|
club_name = team.club&.name.presence || t("score.default_club_name")
|
||||||
team_slug = team.respond_to?(:slug) ? team.slug : nil
|
team_slug = team.respond_to?(:slug) ? team.slug : nil
|
||||||
team_label = if link_team && team_slug.present?
|
team_label = if link_team && team_slug.present?
|
||||||
link_to(team.name, public_team_page_path(team_slug), class: "live-card__team-link")
|
link_to(team.name, public_team_page_path(team_slug), class: "live-card__team-link")
|
||||||
@@ -69,7 +69,7 @@ module Public
|
|||||||
|
|
||||||
def live_match_page_heading(match)
|
def live_match_page_heading(match)
|
||||||
team = match.team
|
team = match.team
|
||||||
club_name = team.club&.name.presence || "Società"
|
club_name = team.club&.name.presence || t("score.default_club_name")
|
||||||
content_tag(:div, class: "live-page-heading") do
|
content_tag(:div, class: "live-page-heading") do
|
||||||
safe_join([
|
safe_join([
|
||||||
content_tag(:p, club_name, class: "live-page-heading__club"),
|
content_tag(:p, club_name, class: "live-page-heading__club"),
|
||||||
@@ -79,7 +79,7 @@ module Public
|
|||||||
end
|
end
|
||||||
|
|
||||||
def live_score_points_label(match, score_state)
|
def live_score_points_label(match, score_state)
|
||||||
return "—" unless score_state
|
return t("score.fallback") unless score_state
|
||||||
|
|
||||||
board = match.effective_board_type
|
board = match.effective_board_type
|
||||||
home = case board
|
home = case board
|
||||||
@@ -91,7 +91,7 @@ module Public
|
|||||||
else score_state.away_points
|
else score_state.away_points
|
||||||
end
|
end
|
||||||
|
|
||||||
"#{match.team.name} #{home} - #{away} #{match.opponent_name}"
|
t("score.points_label", team: match.team.name, home: home, away: away, opponent: match.opponent_name)
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
@@ -101,8 +101,11 @@ module Public
|
|||||||
set_no = data[:set] || data["set"]
|
set_no = data[:set] || data["set"]
|
||||||
home = data[:home] || data["home"]
|
home = data[:home] || data["home"]
|
||||||
away = data[:away] || data["away"]
|
away = data[:away] || data["away"]
|
||||||
label = set_no.present? ? "Set #{set_no}" : "Set"
|
if set_no.present?
|
||||||
"#{label} #{home}-#{away}"
|
t("score.partial_entry", set: set_no, home: home, away: away)
|
||||||
|
else
|
||||||
|
t("score.partial_entry_no_number", home: home, away: away)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -12,14 +12,60 @@ module Public
|
|||||||
when "basket", "timed"
|
when "basket", "timed"
|
||||||
live_score_period_label(match, score)
|
live_score_period_label(match, score)
|
||||||
when "timer"
|
when "timer"
|
||||||
"Cronometro"
|
t("regia.board.stopwatch")
|
||||||
when "generic"
|
when "generic"
|
||||||
"Punteggio"
|
t("regia.board.generic_score")
|
||||||
else
|
else
|
||||||
"Set #{score.current_set}"
|
t("regia.board.set_label", number: score.current_set)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def regia_js_i18n_json
|
||||||
|
{
|
||||||
|
periodUpdated: t("regia.js.period_updated"),
|
||||||
|
nowInPeriodTemplate: t("regia.js.now_in_period", period: "%{period}"),
|
||||||
|
setWonTitle: t("regia.modal.set_won_title"),
|
||||||
|
setWonBodyTemplate: t("regia.js.set_won_body", winner: "%{winner}"),
|
||||||
|
matchWonTitle: t("regia.js.match_won_title"),
|
||||||
|
matchWonBodyTemplate: t("regia.js.match_won_body", winner: "%{winner}"),
|
||||||
|
closeSetTitle: t("regia.board.close_set"),
|
||||||
|
closeSetConfirm: t("regia.js.close_set_confirm"),
|
||||||
|
scoreUpdateError: t("regia.js.score_update_error"),
|
||||||
|
genericError: t("regia.js.generic_error"),
|
||||||
|
setsLineTemplate: t("regia.js.sets_line", current: "%{current}", home: "%{home}", away: "%{away}"),
|
||||||
|
setPartialLabelTemplate: t("regia.board.set_label", number: "%{number}"),
|
||||||
|
freeScore: t("regia.board.free_score"),
|
||||||
|
stopwatch: t("regia.board.stopwatch"),
|
||||||
|
quarterLabelTemplate: t("regia.js.quarter_label", n: "%{n}"),
|
||||||
|
halfLabelTemplate: t("regia.js.half_label", n: "%{n}"),
|
||||||
|
overtimeBasketTemplate: t("regia.js.overtime_basket", n: "%{n}"),
|
||||||
|
overtimeOther: t("regia.js.overtime_other"),
|
||||||
|
unknownPeriod: t("regia.js.unknown_period"),
|
||||||
|
partialsPrefix: t("regia.board.partials_prefix"),
|
||||||
|
resumed: t("regia.js.resumed"),
|
||||||
|
pausedCover: t("regia.js.paused_cover"),
|
||||||
|
closed: t("regia.js.closed"),
|
||||||
|
resumeError: t("regia.js.resume_error"),
|
||||||
|
pauseError: t("regia.js.pause_error"),
|
||||||
|
closeError: t("regia.js.close_error"),
|
||||||
|
closeConfirm: t("regia.js.close_confirm"),
|
||||||
|
linkUnavailable: t("regia.js.link_unavailable"),
|
||||||
|
linkCopied: t("regia.js.link_copied"),
|
||||||
|
copyPrompt: t("regia.js.copy_prompt"),
|
||||||
|
shareRegiaText: t("regia.js.share_regia_text"),
|
||||||
|
shareLiveText: t("regia.js.share_live_text"),
|
||||||
|
previewWaiting: t("regia.preview_waiting"),
|
||||||
|
streamEnded: t("regia.preview_ended"),
|
||||||
|
resumeLabel: t("regia.resume"),
|
||||||
|
pauseLabel: t("regia.pause"),
|
||||||
|
endedBadge: t("regia.status.ended"),
|
||||||
|
pausedBadge: t("regia.status.paused"),
|
||||||
|
liveBadge: t("regia.status.live"),
|
||||||
|
waitingBadge: t("regia.status.waiting"),
|
||||||
|
subtitlePrefix: t("regia.subtitle_prefix")
|
||||||
|
}.to_json
|
||||||
|
end
|
||||||
|
|
||||||
def format_clock_secs(secs, count_up: false)
|
def format_clock_secs(secs, count_up: false)
|
||||||
total = secs.to_i
|
total = secs.to_i
|
||||||
return count_up ? "0:00" : "—" if total <= 0 && !count_up
|
return count_up ? "0:00" : "—" if total <= 0 && !count_up
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
module Public
|
module Public
|
||||||
module TeamPagesHelper
|
module TeamPagesHelper
|
||||||
def team_page_meta_description(team, club)
|
def team_page_meta_description(team, club)
|
||||||
"Segui #{team.name} (#{club.name}): dirette live, calendario partite e replay su Match Live TV."
|
t("team_pages.show.meta_description", team: team.name, club: club.name)
|
||||||
end
|
end
|
||||||
|
|
||||||
def filter_params_for_canonical
|
def filter_params_for_canonical
|
||||||
|
|||||||
@@ -19,14 +19,14 @@ module RosterHelper
|
|||||||
|
|
||||||
def roster_category_options
|
def roster_category_options
|
||||||
TeamRosterMember::DISPLAY_ORDER.map do |cat|
|
TeamRosterMember::DISPLAY_ORDER.map do |cat|
|
||||||
[TeamRosterMember::CATEGORY_LABELS[cat], cat]
|
[TeamRosterMember.category_label(cat), cat]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def roster_player_role_options(team, selected = nil)
|
def roster_player_role_options(team, selected = nil)
|
||||||
roles = Sports::PlayerRoles.for_team(team)
|
roles = Sports::PlayerRoles.for_team(team)
|
||||||
options_for_select(
|
options_for_select(
|
||||||
[["— Seleziona ruolo —", ""]] + roles.map { |r| [r, r] },
|
[[t("roster.role_select_blank"), ""]] + roles.map { |r| [r, r] },
|
||||||
selected
|
selected
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -4,15 +4,18 @@ module Billing
|
|||||||
@invoice = params[:invoice]
|
@invoice = params[:invoice]
|
||||||
@club = @invoice.club
|
@club = @invoice.club
|
||||||
|
|
||||||
attachments["fattura-#{@invoice.number.parameterize}.pdf"] = {
|
I18n.with_locale(I18n.locale) do
|
||||||
|
prefix = t("mailers.invoice.attachment_prefix")
|
||||||
|
attachments["#{prefix}-#{@invoice.number.parameterize}.pdf"] = {
|
||||||
mime_type: "application/pdf",
|
mime_type: "application/pdf",
|
||||||
content: @invoice.pdf.download
|
content: @invoice.pdf.download
|
||||||
}
|
}
|
||||||
|
|
||||||
mail(
|
mail(
|
||||||
to: @club.billing_email,
|
to: @club.billing_email,
|
||||||
subject: "Fattura #{@invoice.display_number} — Match Live TV"
|
subject: t("mailers.invoice.subject", number: @invoice.display_number)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|||||||
@@ -9,12 +9,15 @@ module Recordings
|
|||||||
@recipient = recipient
|
@recipient = recipient
|
||||||
@replay_url = recording.replay_url
|
@replay_url = recording.replay_url
|
||||||
@expires_at = recording.expires_at
|
@expires_at = recording.expires_at
|
||||||
|
@archive_url = "#{MatchLiveTv.app_public_url.chomp('/')}/clubs/#{@club.id}/replays"
|
||||||
|
|
||||||
|
I18n.with_locale(I18n.locale) do
|
||||||
mail(
|
mail(
|
||||||
to: recipient.email,
|
to: recipient.email,
|
||||||
subject: "Replay pronto — #{recording.title_or_default}"
|
subject: t("mailers.replay_ready.subject", title: recording.title_or_default)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def replay_expiring_soon(recording:, recipient:)
|
def replay_expiring_soon(recording:, recipient:)
|
||||||
@recording = recording
|
@recording = recording
|
||||||
@@ -24,11 +27,14 @@ module Recordings
|
|||||||
@replay_url = recording.replay_url
|
@replay_url = recording.replay_url
|
||||||
@expires_at = recording.expires_at
|
@expires_at = recording.expires_at
|
||||||
@days_left = ((recording.expires_at - Time.current) / 1.day).ceil
|
@days_left = ((recording.expires_at - Time.current) / 1.day).ceil
|
||||||
|
@archive_url = "#{MatchLiveTv.app_public_url.chomp('/')}/clubs/#{@club.id}/replays"
|
||||||
|
|
||||||
|
I18n.with_locale(I18n.locale) do
|
||||||
mail(
|
mail(
|
||||||
to: recipient.email,
|
to: recipient.email,
|
||||||
subject: "Replay in scadenza tra #{@days_left} giorni — #{recording.title_or_default}"
|
subject: t("mailers.replay_expiring.subject", days: @days_left, title: recording.title_or_default)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
end
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ class UserMailer < ApplicationMailer
|
|||||||
@reset_url = public_password_reset_url(token: token)
|
@reset_url = public_password_reset_url(token: token)
|
||||||
@expiry_hours = MatchLiveTv.password_reset_expiry_hours
|
@expiry_hours = MatchLiveTv.password_reset_expiry_hours
|
||||||
|
|
||||||
mail to: user.email, subject: "Reimposta la password — Match Live TV"
|
I18n.with_locale(I18n.locale) do
|
||||||
|
mail to: user.email, subject: t("mailers.password_reset.subject")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
module ClubBillingProfile
|
module ClubBillingProfile
|
||||||
extend ActiveSupport::Concern
|
extend ActiveSupport::Concern
|
||||||
|
|
||||||
BILLING_ENTITY_TYPES = {
|
BILLING_ENTITY_TYPE_KEYS = %w[company individual nonprofit].freeze
|
||||||
"company" => "Società / ASD con P.IVA",
|
|
||||||
"individual" => "Persona fisica",
|
class_methods do
|
||||||
"nonprofit" => "Associazione senza scopo di lucro"
|
def billing_entity_types
|
||||||
}.freeze
|
ClubBillingProfile::BILLING_ENTITY_TYPE_KEYS.index_with { |key| I18n.t("billing.entity_types.#{key}") }
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
included do
|
included do
|
||||||
validates :billing_entity_type, inclusion: { in: BILLING_ENTITY_TYPES.keys }, allow_nil: true
|
validates :billing_entity_type, inclusion: { in: BILLING_ENTITY_TYPE_KEYS }, allow_nil: true
|
||||||
validates :billing_email, format: { with: URI::MailTo::EMAIL_REGEXP }, allow_blank: true
|
validates :billing_email, format: { with: URI::MailTo::EMAIL_REGEXP }, allow_blank: true
|
||||||
validates :billing_recipient_code, length: { is: 7 }, allow_blank: true
|
validates :billing_recipient_code, length: { is: 7 }, allow_blank: true
|
||||||
validates :billing_province, length: { is: 2 }, allow_blank: true
|
validates :billing_province, length: { is: 2 }, allow_blank: true
|
||||||
@@ -23,28 +25,28 @@ module ClubBillingProfile
|
|||||||
# Campi minimi per intestazione fattura e invio (email PDF + SDI/PEC).
|
# Campi minimi per intestazione fattura e invio (email PDF + SDI/PEC).
|
||||||
def billing_profile_errors
|
def billing_profile_errors
|
||||||
errors = []
|
errors = []
|
||||||
errors << "Tipo intestatario" if billing_entity_type.blank?
|
errors << I18n.t("billing.profile_errors.entity_type") if billing_entity_type.blank?
|
||||||
errors << "Ragione sociale o nome intestatario" if billing_legal_name.blank?
|
errors << I18n.t("billing.profile_errors.legal_name") if billing_legal_name.blank?
|
||||||
errors << "Email di fatturazione" if billing_email.blank?
|
errors << I18n.t("billing.profile_errors.email") if billing_email.blank?
|
||||||
errors << "Indirizzo" if billing_address_line.blank?
|
errors << I18n.t("billing.profile_errors.address") if billing_address_line.blank?
|
||||||
errors << "Città" if billing_city.blank?
|
errors << I18n.t("billing.profile_errors.city") if billing_city.blank?
|
||||||
errors << "Provincia (sigla 2 lettere)" if billing_province.blank? || billing_province.to_s.length != 2
|
errors << I18n.t("billing.profile_errors.province") if billing_province.blank? || billing_province.to_s.length != 2
|
||||||
errors << "CAP" if billing_postal_code.blank?
|
errors << I18n.t("billing.profile_errors.postal_code") if billing_postal_code.blank?
|
||||||
errors << "Paese (ISO)" if billing_country.blank? || billing_country.to_s.length != 2
|
errors << I18n.t("billing.profile_errors.country") if billing_country.blank? || billing_country.to_s.length != 2
|
||||||
errors.concat(billing_tax_id_errors)
|
errors.concat(billing_tax_id_errors)
|
||||||
errors << "Codice destinatario SDI (7 caratteri) o PEC" if billing_recipient_code.blank? && billing_pec.blank?
|
errors << I18n.t("billing.profile_errors.sdi_or_pec") if billing_recipient_code.blank? && billing_pec.blank?
|
||||||
errors << "Codice destinatario SDI (7 caratteri)" if billing_recipient_code.present? && billing_recipient_code.length != 7
|
errors << I18n.t("billing.profile_errors.sdi_invalid") if billing_recipient_code.present? && billing_recipient_code.length != 7
|
||||||
errors
|
errors
|
||||||
end
|
end
|
||||||
|
|
||||||
def billing_tax_id_errors
|
def billing_tax_id_errors
|
||||||
case billing_entity_type
|
case billing_entity_type
|
||||||
when "company"
|
when "company"
|
||||||
billing_vat_number.blank? ? ["Partita IVA"] : []
|
billing_vat_number.blank? ? [I18n.t("billing.profile_errors.vat_number")] : []
|
||||||
when "individual"
|
when "individual"
|
||||||
billing_fiscal_code.blank? ? ["Codice Fiscale"] : []
|
billing_fiscal_code.blank? ? [I18n.t("billing.profile_errors.fiscal_code")] : []
|
||||||
else
|
else
|
||||||
billing_vat_number.blank? && billing_fiscal_code.blank? ? ["P.IVA o Codice Fiscale"] : []
|
billing_vat_number.blank? && billing_fiscal_code.blank? ? [I18n.t("billing.profile_errors.vat_or_fiscal")] : []
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -56,7 +58,7 @@ module ClubBillingProfile
|
|||||||
# Righe per intestazione fattura in admin (etichetta, valore).
|
# Righe per intestazione fattura in admin (etichetta, valore).
|
||||||
def billing_profile_invoice_lines
|
def billing_profile_invoice_lines
|
||||||
lines = []
|
lines = []
|
||||||
lines << ["Tipo", BILLING_ENTITY_TYPES[billing_entity_type]] if billing_entity_type.present?
|
lines << ["Tipo", self.class.billing_entity_types[billing_entity_type]] if billing_entity_type.present?
|
||||||
lines << ["Intestatario", billing_legal_name]
|
lines << ["Intestatario", billing_legal_name]
|
||||||
lines << ["P.IVA", billing_vat_number] if billing_vat_number.present?
|
lines << ["P.IVA", billing_vat_number] if billing_vat_number.present?
|
||||||
lines << ["Codice fiscale", billing_fiscal_code] if billing_fiscal_code.present?
|
lines << ["Codice fiscale", billing_fiscal_code] if billing_fiscal_code.present?
|
||||||
|
|||||||
@@ -47,11 +47,11 @@ class Match < ApplicationRecord
|
|||||||
|
|
||||||
def public_status_label
|
def public_status_label
|
||||||
active = active_stream_session
|
active = active_stream_session
|
||||||
return "In corso / da riprendere" if active
|
return I18n.t("matches.status.active") if active
|
||||||
return "Programmata" if scheduled_upcoming?
|
return I18n.t("matches.status.scheduled") if scheduled_upcoming?
|
||||||
return "Pronta" if scheduled_at.present?
|
return I18n.t("matches.status.ready") if scheduled_at.present?
|
||||||
|
|
||||||
"Senza orario"
|
I18n.t("matches.status.no_time")
|
||||||
end
|
end
|
||||||
|
|
||||||
scope :search_teams_or_opponents, lambda { |query|
|
scope :search_teams_or_opponents, lambda { |query|
|
||||||
|
|||||||
@@ -164,13 +164,13 @@ class Recording < ApplicationRecord
|
|||||||
end
|
end
|
||||||
|
|
||||||
def status_label
|
def status_label
|
||||||
return "Eliminato" if deleted?
|
return I18n.t("recordings.status.deleted") if deleted?
|
||||||
return "Scaduto" if status == "expired"
|
return I18n.t("recordings.status.expired") if status == "expired"
|
||||||
return "Errore" if status == "failed"
|
return I18n.t("recordings.status.failed") if status == "failed"
|
||||||
return "In elaborazione" if status == "processing"
|
return I18n.t("recordings.status.processing") if status == "processing"
|
||||||
return "Scade presto" if expires_at.present? && expires_at <= 7.days.from_now
|
return I18n.t("recordings.status.expiring_soon") if expires_at.present? && expires_at <= 7.days.from_now
|
||||||
|
|
||||||
"Disponibile"
|
I18n.t("recordings.status.available")
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|||||||
@@ -2,12 +2,10 @@ class TeamRosterMember < ApplicationRecord
|
|||||||
CATEGORIES = %w[staff coach manager player].freeze
|
CATEGORIES = %w[staff coach manager player].freeze
|
||||||
# Ordine di visualizzazione in pagina organico
|
# Ordine di visualizzazione in pagina organico
|
||||||
DISPLAY_ORDER = %w[coach manager player staff].freeze
|
DISPLAY_ORDER = %w[coach manager player staff].freeze
|
||||||
CATEGORY_LABELS = {
|
|
||||||
"staff" => "Staff",
|
def self.category_label(category)
|
||||||
"coach" => "Allenatori",
|
I18n.t("roster.category.#{category}", default: category.to_s.capitalize)
|
||||||
"manager" => "Dirigenti",
|
end
|
||||||
"player" => "Giocatori"
|
|
||||||
}.freeze
|
|
||||||
|
|
||||||
VOLLEYBALL_PLAYER_ROLES = Sports::PlayerRoles::BY_BOARD["volley"].freeze
|
VOLLEYBALL_PLAYER_ROLES = Sports::PlayerRoles::BY_BOARD["volley"].freeze
|
||||||
|
|
||||||
@@ -27,7 +25,7 @@ class TeamRosterMember < ApplicationRecord
|
|||||||
scope :by_category, ->(cat) { where(category: cat).ordered }
|
scope :by_category, ->(cat) { where(category: cat).ordered }
|
||||||
|
|
||||||
def category_label
|
def category_label
|
||||||
CATEGORY_LABELS[category] || category
|
self.class.category_label(category)
|
||||||
end
|
end
|
||||||
|
|
||||||
def initials
|
def initials
|
||||||
@@ -58,10 +56,11 @@ class TeamRosterMember < ApplicationRecord
|
|||||||
|
|
||||||
def default_role_label
|
def default_role_label
|
||||||
case category
|
case category
|
||||||
when "coach" then "Allenatore"
|
when "coach" then I18n.t("roster.role.coach")
|
||||||
when "manager" then "Dirigente"
|
when "manager" then I18n.t("roster.role.manager")
|
||||||
when "staff" then "Staff"
|
when "staff" then I18n.t("roster.role.staff")
|
||||||
when "player" then jersey_number.present? ? "Giocatore ##{jersey_number}" : "Giocatore"
|
when "player"
|
||||||
|
jersey_number.present? ? I18n.t("roster.role.player_numbered", number: jersey_number) : I18n.t("roster.role.player")
|
||||||
else category
|
else category
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -70,12 +69,12 @@ class TeamRosterMember < ApplicationRecord
|
|||||||
allowed = Sports::PlayerRoles.for_team(team)
|
allowed = Sports::PlayerRoles.for_team(team)
|
||||||
return if role_label.in?(allowed)
|
return if role_label.in?(allowed)
|
||||||
|
|
||||||
errors.add(:role_label, "non è un ruolo valido per questo sport")
|
errors.add(:role_label, I18n.t("roster.errors.role_invalid"))
|
||||||
end
|
end
|
||||||
|
|
||||||
def photo_file_type
|
def photo_file_type
|
||||||
return if photo_file.content_type.in?(%w[image/png image/jpeg image/webp])
|
return if photo_file.content_type.in?(%w[image/png image/jpeg image/webp])
|
||||||
|
|
||||||
errors.add(:photo_file, "deve essere PNG, JPEG o WebP")
|
errors.add(:photo_file, I18n.t("roster.errors.photo_invalid_type"))
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -10,38 +10,37 @@ module Billing
|
|||||||
if result.upgrade?
|
if result.upgrade?
|
||||||
amount = format_amount(result.amount_cents, result.currency)
|
amount = format_amount(result.amount_cents, result.currency)
|
||||||
if amount.present?
|
if amount.present?
|
||||||
"Upgrade a #{plan.name} (#{interval_label}) completato. Addebito immediato: #{amount}."
|
I18n.t("billing.messages.upgrade_completed_with_amount", plan: plan.name, interval: interval_label, amount: amount)
|
||||||
else
|
else
|
||||||
"Upgrade a #{plan.name} (#{interval_label}) completato. L'addebito della differenza è in elaborazione sulla carta salvata."
|
I18n.t("billing.messages.upgrade_completed_pending_charge", plan: plan.name, interval: interval_label)
|
||||||
end
|
end
|
||||||
else
|
else
|
||||||
when_label = result.effective_at.present? ? I18n.l(result.effective_at, format: :long) : "il prossimo rinnovo"
|
when_label = result.effective_at.present? ? I18n.l(result.effective_at, format: :long) : I18n.t("billing.messages.next_renewal")
|
||||||
current_name = current_plan&.name || "il piano attuale"
|
current_name = current_plan&.name || I18n.t("billing.messages.current_plan_generic")
|
||||||
"Passaggio a #{plan.name} (#{interval_label}) programmato: resti su #{current_name} fino al #{when_label}. " \
|
I18n.t("billing.messages.downgrade_scheduled", plan: plan.name, interval: interval_label, current_name: current_name, when: when_label)
|
||||||
"Nessun addebito aggiuntivo ora; dal prossimo ciclo pagherai il nuovo prezzo."
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def button_label(plan:, interval:, kind:)
|
def button_label(plan:, interval:, kind:)
|
||||||
price = PriceCatalog.label(plan_slug: plan.slug, interval: interval)
|
price = PriceCatalog.label(plan_slug: plan.slug, interval: interval)
|
||||||
if kind == :checkout
|
if kind == :checkout
|
||||||
"Attiva #{plan.name} — #{price}"
|
I18n.t("billing.messages.activate_button", plan: plan.name, price: price)
|
||||||
else
|
else
|
||||||
"Passa a #{plan.name} — #{price}"
|
I18n.t("billing.messages.switch_button", plan: plan.name, price: price)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def billing_info_lines(subscription: nil)
|
def billing_info_lines(subscription: nil)
|
||||||
lines = [
|
lines = [
|
||||||
"Upgrade (piano superiore o passaggio alla fatturazione annuale): la differenza viene addebitata subito sulla carta salvata in Stripe.",
|
I18n.t("billing.messages.upgrade_info_line"),
|
||||||
"Downgrade tra piani a pagamento (es. Full → Light): il nuovo piano è attivo dal prossimo rinnovo; fino ad allora restano le funzioni del piano attuale, senza addebito aggiuntivo ora."
|
I18n.t("billing.messages.downgrade_info_line")
|
||||||
]
|
]
|
||||||
|
|
||||||
if subscription&.plan_change_pending? && subscription.pending_plan.present?
|
if subscription&.plan_change_pending? && subscription.pending_plan.present?
|
||||||
when_at = subscription.current_period_end
|
when_at = subscription.current_period_end
|
||||||
when_label = when_at.present? ? I18n.l(when_at, format: :long) : "il prossimo rinnovo"
|
when_label = when_at.present? ? I18n.l(when_at, format: :long) : I18n.t("billing.messages.next_renewal")
|
||||||
lines.unshift(
|
lines.unshift(
|
||||||
"Hai già richiesto #{subscription.pending_plan.name} dal #{when_label}: fino ad allora resta attivo #{subscription.plan.name}."
|
I18n.t("billing.messages.already_requested_line", plan: subscription.pending_plan.name, when: when_label, current_plan: subscription.plan.name)
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -1,26 +1,26 @@
|
|||||||
<div style="max-width:360px;margin:4rem auto">
|
<div style="max-width:360px;margin:4rem auto">
|
||||||
<h2>Accesso admin</h2>
|
<h2><%= t("admin.auth.new.title") %></h2>
|
||||||
<% if flash[:alert] %>
|
<% if flash[:alert] %>
|
||||||
<p style="color:#ff6b6b"><%= flash[:alert] %></p>
|
<p style="color:#ff6b6b"><%= flash[:alert] %></p>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= form_with url: admin_login_path, method: :post, local: true do %>
|
<%= form_with url: admin_login_path, method: :post, local: true do %>
|
||||||
<p>
|
<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"
|
<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">
|
style="width:100%;padding:0.5rem;margin-top:0.25rem;background:#1E1E1E;border:1px solid #333;color:#fff;border-radius:6px">
|
||||||
</p>
|
</p>
|
||||||
<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"
|
<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">
|
style="width:100%;padding:0.5rem;margin-top:0.25rem;background:#1E1E1E;border:1px solid #333;color:#fff;border-radius:6px">
|
||||||
</p>
|
</p>
|
||||||
<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">
|
<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>
|
</button>
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
<p style="color:#888;font-size:0.85rem;margin-top:1.5rem">
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<h2>Pagamenti da fatturare</h2>
|
<h2><%= t("admin.billing.index.title") %></h2>
|
||||||
<p style="color:#666;margin-bottom:16px">
|
<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>
|
</p>
|
||||||
|
|
||||||
<% if @clubs.many? %>
|
<% if @clubs.many? %>
|
||||||
<p style="margin-bottom:16px">
|
<p style="margin-bottom:16px">
|
||||||
Filtra società:
|
<%= t("admin.billing.index.filter_label") %>
|
||||||
<%= link_to "Tutte", admin_billing_path, class: (@filter_club ? nil : "admin-nav-active") %>
|
<%= link_to t("admin.billing.index.filter_all"), admin_billing_path, class: (@filter_club ? nil : "admin-nav-active") %>
|
||||||
<% @clubs.each do |club| %>
|
<% @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) %>
|
· <%= link_to club.name, admin_billing_path(club_id: club.id), class: (@filter_club&.id == club.id ? "admin-nav-active" : nil) %>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
· <strong><%= payment.formatted_amount %></strong>
|
· <strong><%= payment.formatted_amount %></strong>
|
||||||
</div>
|
</div>
|
||||||
<% unless club.billing_profile_complete? %>
|
<% 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 %>
|
<% end %>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
@@ -46,16 +46,16 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</dl>
|
</dl>
|
||||||
<% else %>
|
<% 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 %>
|
<% end %>
|
||||||
|
|
||||||
<%= form_with url: admin_billing_payment_attach_pdf_path(payment),
|
<%= form_with url: admin_billing_payment_attach_pdf_path(payment),
|
||||||
method: :post, multipart: true, local: true, class: "billing-upload-form" do %>
|
method: :post, multipart: true, local: true, class: "billing-upload-form" do %>
|
||||||
<label class="billing-upload-form__label">
|
<label class="billing-upload-form__label">
|
||||||
PDF fattura
|
<%= t("admin.billing.index.pdf_label") %>
|
||||||
<%= file_field_tag :pdf, accept: "application/pdf", required: true %>
|
<%= file_field_tag :pdf, accept: "application/pdf", required: true %>
|
||||||
</label>
|
</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 %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
@@ -63,21 +63,21 @@
|
|||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<p class="admin-flash" style="background:#1b3d1b;border-color:#2e7d32">
|
<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>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<% if @completed_payments.any? %>
|
<% 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">
|
<table class="admin-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Data</th>
|
<th><%= t("admin.billing.index.table.date") %></th>
|
||||||
<th>Società</th>
|
<th><%= t("admin.billing.index.table.club") %></th>
|
||||||
<th>Descrizione</th>
|
<th><%= t("admin.billing.index.table.description") %></th>
|
||||||
<th>Importo</th>
|
<th><%= t("admin.billing.index.table.amount") %></th>
|
||||||
<th>Fattura</th>
|
<th><%= t("admin.billing.index.table.invoice") %></th>
|
||||||
<th>Stato</th>
|
<th><%= t("admin.billing.index.table.status") %></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
@@ -96,4 +96,4 @@
|
|||||||
</table>
|
</table>
|
||||||
<% end %>
|
<% 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">
|
<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? %>
|
<% 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 %>
|
<% end %>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<%= form_with model: @invoice, url: admin_club_billing_invoice_path(@club, @invoice), multipart: true, method: :patch do |f| %>
|
<%= 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.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]) %>
|
<%= 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]",
|
<%= number_field_tag "billing_invoice[amount_euros]",
|
||||||
@invoice.amount_cents / 100.0,
|
@invoice.amount_cents / 100.0,
|
||||||
step: 0.01, min: 0.01, required: true, disabled: @invoice.status.in?(%w[sent]) %>
|
step: 0.01, min: 0.01, required: true, disabled: @invoice.status.in?(%w[sent]) %>
|
||||||
|
|
||||||
<% if @payment %>
|
<% 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 %>
|
<% end %>
|
||||||
|
|
||||||
<%= f.label :pdf, "PDF fattura" %>
|
<%= f.label :pdf, t("admin.billing_invoices.edit.pdf_label") %>
|
||||||
<% if @invoice.pdf.attached? %>
|
<% 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 %>
|
<% end %>
|
||||||
<%= f.file_field :pdf, accept: "application/pdf" %>
|
<%= 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 %>
|
<%= f.text_area :notes, rows: 2 %>
|
||||||
|
|
||||||
<div style="margin-top:16px;display:flex;gap:10px;flex-wrap:wrap">
|
<div style="margin-top:16px;display:flex;gap:10px;flex-wrap:wrap">
|
||||||
<% unless @invoice.status.in?(%w[sent]) %>
|
<% unless @invoice.status.in?(%w[sent]) %>
|
||||||
<%= f.submit "Salva bozza", class: "btn btn-secondary" %>
|
<%= f.submit t("admin.billing_invoices.edit.save_draft"), name: "commit_action", value: "draft", class: "btn btn-secondary" %>
|
||||||
<%= f.submit "Emetti e invia via email", class: "btn btn-primary",
|
<%= f.submit t("admin.billing_invoices.edit.issue_send"), name: "commit_action", value: "issue", class: "btn btn-primary",
|
||||||
data: { turbo_confirm: "Inviare la fattura a #{@club.billing_email}?" } %>
|
data: { turbo_confirm: t("admin.billing_invoices.edit.issue_confirm", email: @club.billing_email) } %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</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>
|
<p>
|
||||||
<%= link_to "← Dashboard", admin_root_path %>
|
<%= link_to t("admin.billing_invoices.index.back_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.new_invoice"), new_admin_club_billing_invoice_path(@club), class: "btn btn-secondary" %>
|
||||||
</p>
|
</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? %>
|
<% if @payments.any? %>
|
||||||
<table class="data">
|
<table class="data">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Data</th>
|
<th><%= t("admin.billing_invoices.index.table.date") %></th>
|
||||||
<th>Descrizione</th>
|
<th><%= t("admin.billing_invoices.index.table.description") %></th>
|
||||||
<th>Importo</th>
|
<th><%= t("admin.billing_invoices.index.table.amount") %></th>
|
||||||
<th>Fattura</th>
|
<th><%= t("admin.billing_invoices.index.table.invoice") %></th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -26,16 +26,16 @@
|
|||||||
<td>
|
<td>
|
||||||
<% if inv %>
|
<% if inv %>
|
||||||
<%= inv.number %> — <%= inv.status %>
|
<%= inv.number %> — <%= inv.status %>
|
||||||
<% if inv.pdf.attached? %> (PDF)<% end %>
|
<% if inv.pdf.attached? %><%= t("admin.billing_invoices.index.pdf_suffix") %><% end %>
|
||||||
<% else %>
|
<% else %>
|
||||||
—
|
<%= t("admin.common.dash") %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<% if inv %>
|
<% 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 %>
|
<% 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 %>
|
<% end %>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -43,14 +43,21 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<% else %>
|
<% else %>
|
||||||
<p>Nessun pagamento registrato per questa società.</p>
|
<p><%= t("admin.billing_invoices.index.no_payments") %></p>
|
||||||
<% end %>
|
<% 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? %>
|
<% if @invoices.any? %>
|
||||||
<table class="data">
|
<table class="data">
|
||||||
<thead>
|
<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>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<% @invoices.each do |inv| %>
|
<% @invoices.each do |inv| %>
|
||||||
@@ -59,12 +66,12 @@
|
|||||||
<td><%= inv.issued_on %></td>
|
<td><%= inv.issued_on %></td>
|
||||||
<td><%= inv.formatted_amount %></td>
|
<td><%= inv.formatted_amount %></td>
|
||||||
<td><%= inv.status %></td>
|
<td><%= inv.status %></td>
|
||||||
<td><%= inv.billing_payment_id.present? ? "Sì" : "No" %></td>
|
<td><%= inv.billing_payment_id.present? ? t("admin.common.yes") : t("admin.common.no") %></td>
|
||||||
<td><%= link_to "Modifica", edit_admin_club_billing_invoice_path(@club, inv) %></td>
|
<td><%= link_to t("admin.billing_invoices.index.edit"), edit_admin_club_billing_invoice_path(@club, inv) %></td>
|
||||||
</tr>
|
</tr>
|
||||||
<% end %>
|
<% end %>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<% else %>
|
<% else %>
|
||||||
<p>Nessuna fattura.</p>
|
<p><%= t("admin.billing_invoices.index.no_invoices") %></p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -1,32 +1,32 @@
|
|||||||
<h1>Nuova fattura — <%= @club.name %></h1>
|
<h1><%= t("admin.billing_invoices.new.title", club: @club.name) %></h1>
|
||||||
<% if @payment %>
|
<% 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 %>
|
<% end %>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<%= form_with model: @invoice, url: admin_club_billing_invoices_path(@club) do |f| %>
|
<%= form_with model: @invoice, url: admin_club_billing_invoices_path(@club) do |f| %>
|
||||||
<%= f.hidden_field :billing_payment_id if @payment %>
|
<%= 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.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 %>
|
<%= 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]",
|
<%= number_field_tag "billing_invoice[amount_euros]",
|
||||||
(@invoice.amount_cents ? @invoice.amount_cents / 100.0 : nil),
|
(@invoice.amount_cents ? @invoice.amount_cents / 100.0 : nil),
|
||||||
step: 0.01, min: 0.01, required: true %>
|
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 %>
|
<%= f.text_area :notes, rows: 2 %>
|
||||||
|
|
||||||
<p style="color:#666;font-size:0.9rem;margin-top:12px">
|
<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>
|
</p>
|
||||||
|
|
||||||
<%= f.submit "Crea bozza fattura", class: "btn btn-primary" %>
|
<%= f.submit t("admin.billing_invoices.new.submit"), class: "btn btn-primary" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</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 %>
|
<% content_for :replay_archive_styles, true %>
|
||||||
|
|
||||||
<%= render "recordings/club_archive", admin_mode: true %>
|
<%= render "recordings/club_archive", admin_mode: true %>
|
||||||
|
|||||||
@@ -1,43 +1,43 @@
|
|||||||
<%# locals: (club:, subscription:, return_to: nil) %>
|
<%# 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">
|
<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">
|
<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>
|
</p>
|
||||||
|
|
||||||
<% sub = subscription %>
|
<% sub = subscription %>
|
||||||
<% if sub&.admin_comped? %>
|
<% if sub&.admin_comped? %>
|
||||||
<p style="margin:0 0 12px">
|
<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? %>
|
<% if sub.admin_comped_reason.present? %>
|
||||||
· <%= sub.admin_comped_reason %>
|
· <%= sub.admin_comped_reason %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if sub.admin_comped_at.present? %>
|
<% if sub.admin_comped_at.present? %>
|
||||||
<br><span style="color:#888;font-size:0.85rem">Dal <%= l(sub.admin_comped_at, format: :long) %>
|
<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? %> · admin <%= sub.admin_comped_by.username %><% end %>
|
<% if sub.admin_comped_by.present? %> <%= t("admin.comped.by_admin", username: sub.admin_comped_by.username) %><% end %>
|
||||||
</span>
|
</span>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% if sub.stripe_subscription_id.present? %>
|
<% if sub.stripe_subscription_id.present? %>
|
||||||
<br><span style="color:#888;font-size:0.85rem">Nota: esiste anche un abbonamento Stripe collegato; l’omaggio ha priorità sul piano.</span>
|
<br><span style="color:#888;font-size:0.85rem"><%= t("admin.comped.stripe_note") %></span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</p>
|
</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),
|
revoke_comped_admin_club_path(club, return_to: return_to),
|
||||||
method: :delete,
|
method: :delete,
|
||||||
class: "admin-btn admin-btn--secondary",
|
class: "admin-btn admin-btn--secondary",
|
||||||
form: { data: { turbo_confirm: "Revocare l’abbonamento omaggio per #{club.name}?" } } %>
|
form: { data: { turbo_confirm: t("admin.comped.revoke_confirm", club: club.name) } } %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<p style="margin:0 0 12px;color:#888">
|
<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? %>
|
<% if sub&.stripe_subscription_id.present? %>
|
||||||
· pagamento Stripe attivo
|
<%= t("admin.comped.stripe_active") %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</p>
|
</p>
|
||||||
<%= form_with url: grant_comped_admin_club_path(club), method: :post, local: true, class: "admin-comped-form" do %>
|
<%= 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? %>
|
<%= hidden_field_tag :return_to, return_to if return_to.present? %>
|
||||||
<div style="display:flex;flex-wrap:wrap;gap:12px;align-items:flex-end">
|
<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">
|
<label style="display:flex;flex-direction:column;gap:4px;font-size:0.85rem">
|
||||||
Piano
|
<%= t("admin.comped.plan_label") %>
|
||||||
<%= select_tag :plan_slug,
|
<%= select_tag :plan_slug,
|
||||||
options_for_select(
|
options_for_select(
|
||||||
[["Premium Light", "premium_light"], ["Premium Full", "premium_full"]]
|
[["Premium Light", "premium_light"], ["Premium Full", "premium_full"]]
|
||||||
@@ -46,10 +46,10 @@
|
|||||||
class: "admin-input" %>
|
class: "admin-input" %>
|
||||||
</label>
|
</label>
|
||||||
<label style="display:flex;flex-direction:column;gap:4px;font-size:0.85rem;flex:1;min-width:200px">
|
<label style="display:flex;flex-direction:column;gap:4px;font-size:0.85rem;flex:1;min-width:200px">
|
||||||
Motivo (es. sponsor 2026)
|
<%= t("admin.comped.reason_label") %>
|
||||||
<%= text_field_tag :reason, nil, placeholder: "Sponsor, promozione…", class: "admin-input", style: "width:100%" %>
|
<%= text_field_tag :reason, nil, placeholder: t("admin.comped.reason_placeholder"), class: "admin-input", style: "width:100%" %>
|
||||||
</label>
|
</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>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
<h2>Società e squadre</h2>
|
<h2><%= t("admin.clubs.index.title") %></h2>
|
||||||
<p style="color:#888;margin-bottom:16px">
|
<p style="color:#888;margin-bottom:16px">
|
||||||
Abbonamenti omaggio (sponsor / promozioni) e stato YouTube per squadra.
|
<%= t("admin.clubs.index.subtitle_html", billing_link: link_to(t("admin.clubs.index.billing_link"), admin_billing_path)) %>
|
||||||
Pagamenti Stripe: <%= link_to "Fatturazione", admin_billing_path %>.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<table class="admin-table">
|
<table class="admin-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Società</th>
|
<th><%= t("admin.clubs.index.table.club") %></th>
|
||||||
<th>Piano</th>
|
<th><%= t("admin.clubs.index.table.plan") %></th>
|
||||||
<th>Squadre</th>
|
<th><%= t("admin.clubs.index.table.teams") %></th>
|
||||||
<th>Omaggio</th>
|
<th><%= t("admin.clubs.index.table.comped") %></th>
|
||||||
<th>Stripe</th>
|
<th><%= t("admin.clubs.index.table.stripe") %></th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -20,20 +19,20 @@
|
|||||||
<% sub = club.subscription %>
|
<% sub = club.subscription %>
|
||||||
<tr>
|
<tr>
|
||||||
<td><strong><%= club.name %></strong></td>
|
<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><%= club.teams.size %></td>
|
||||||
<td>
|
<td>
|
||||||
<% if sub&.admin_comped? %>
|
<% if sub&.admin_comped? %>
|
||||||
<span style="color:#ffb74d">Sì</span>
|
<span style="color:#ffb74d"><%= t("admin.common.yes") %></span>
|
||||||
<% if sub.admin_comped_reason.present? %> · <%= sub.admin_comped_reason %><% end %>
|
<% if sub.admin_comped_reason.present? %> · <%= sub.admin_comped_reason %><% end %>
|
||||||
<% else %>
|
<% else %>
|
||||||
—
|
<%= t("admin.common.dash") %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</td>
|
</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>
|
<td>
|
||||||
<%= link_to "Gestisci", admin_club_path(club) %>
|
<%= link_to t("admin.clubs.index.manage"), admin_club_path(club) %>
|
||||||
· <%= link_to "Fatture", admin_billing_path(club_id: club.id) %>
|
· <%= link_to t("admin.clubs.index.invoices"), admin_billing_path(club_id: club.id) %>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -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>
|
<h2><%= @club.name %></h2>
|
||||||
<p style="color:#888">
|
<p style="color:#888">
|
||||||
Sport: <%= @club.sport %>
|
<%= t("admin.clubs.show.sport_label", sport: @club.sport) %>
|
||||||
· <%= link_to "Archivio replay", admin_club_recordings_path(@club) %>
|
· <%= link_to t("admin.clubs.show.recordings_archive"), admin_club_recordings_path(@club) %>
|
||||||
· <%= link_to "Pagamenti e fatture", admin_billing_path(club_id: @club.id) %>
|
· <%= link_to t("admin.clubs.show.billing_link"), admin_billing_path(club_id: @club.id) %>
|
||||||
· <%= link_to "Canale YouTube piattaforma", admin_youtube_platform_path %>
|
· <%= link_to t("admin.clubs.show.youtube_platform_link"), admin_youtube_platform_path %>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<%= render "admin/clubs/comped_form", club: @club, subscription: @subscription, return_to: admin_club_path(@club) %>
|
<%= render "admin/clubs/comped_form", club: @club, subscription: @subscription, return_to: admin_club_path(@club) %>
|
||||||
|
|
||||||
<% cred = @club.youtube_credential %>
|
<% cred = @club.youtube_credential %>
|
||||||
<p style="margin-top:16px">
|
<p style="margin-top:16px">
|
||||||
<strong>YouTube società:</strong>
|
<strong><%= t("admin.clubs.show.youtube_club_label") %></strong>
|
||||||
<% if cred %>
|
<% 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? } %>
|
<% 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 %>
|
<% else %>
|
||||||
<span class="muted">—</span>
|
<span class="muted"><%= t("admin.common.dash") %></span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</p>
|
</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? %>
|
<% if @teams.empty? %>
|
||||||
<p class="muted">Nessuna squadra registrata.</p>
|
<p class="muted"><%= t("admin.clubs.show.no_teams") %></p>
|
||||||
<% else %>
|
<% else %>
|
||||||
<table class="admin-table">
|
<table class="admin-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Squadra</th>
|
<th><%= t("admin.clubs.show.table.team") %></th>
|
||||||
<th>Sport</th>
|
<th><%= t("admin.clubs.show.table.sport") %></th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -39,7 +39,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td><strong><%= team.name %></strong></td>
|
<td><strong><%= team.name %></strong></td>
|
||||||
<td><%= team.sport %></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>
|
</tr>
|
||||||
<% end %>
|
<% end %>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -2,147 +2,153 @@
|
|||||||
|
|
||||||
<section class="kpi-grid">
|
<section class="kpi-grid">
|
||||||
<div class="kpi-card kpi-card--accent">
|
<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-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>
|
||||||
<div class="kpi-card">
|
<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-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>
|
||||||
<div class="kpi-card">
|
<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-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>
|
||||||
<div class="kpi-card">
|
<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-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>
|
||||||
<div class="kpi-card">
|
<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-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>
|
||||||
<div class="kpi-card">
|
<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-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>
|
||||||
<div class="kpi-card">
|
<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-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>
|
||||||
<div class="kpi-card">
|
<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-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>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="charts-grid">
|
<section class="charts-grid">
|
||||||
<div class="chart-card">
|
<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 class="chart-wrap"><canvas id="chart-cpu"></canvas></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="chart-card">
|
<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 class="chart-wrap"><canvas id="chart-mem"></canvas></div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="panel" style="margin-bottom:1.25rem">
|
<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 %>
|
<% critical = Ops::Incident.critical_open.count %>
|
||||||
<% warnings = Ops::Incident.open.where(severity: "warning").count %>
|
<% warnings = Ops::Incident.open.where(severity: "warning").count %>
|
||||||
<% if critical.positive? %>
|
<% 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? %>
|
<% 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 %>
|
<% 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 %>
|
<% end %>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="charts-grid" style="grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));">
|
<section class="charts-grid" style="grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));">
|
||||||
<div class="chart-card">
|
<div class="chart-card">
|
||||||
<h3>Disco sistema</h3>
|
<h3><%= t("admin.dashboard.disk.system_title") %></h3>
|
||||||
<% root = @host[:disk][:root] %>
|
<% root = @host[:disk][:root] %>
|
||||||
<% if root[:total_bytes] %>
|
<% if root[:total_bytes] %>
|
||||||
<div class="disk-row">
|
<div class="disk-row">
|
||||||
<header><span><%= root[:path] %></span><span><%= format_bytes(root[:used_bytes]) %> / <%= format_bytes(root[:total_bytes]) %></span></header>
|
<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>
|
<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>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<p class="empty">Metriche disco non disponibili</p>
|
<p class="empty"><%= t("admin.dashboard.disk.not_available") %></p>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<div class="chart-card">
|
<div class="chart-card">
|
||||||
<h3>Archivio registrazioni</h3>
|
<h3><%= t("admin.dashboard.disk.recordings_title") %></h3>
|
||||||
<% rec = @host[:disk][:recordings] %>
|
<% rec = @host[:disk][:recordings] %>
|
||||||
<% if rec[:total_bytes] %>
|
<% if rec[:total_bytes] %>
|
||||||
<div class="disk-row">
|
<div class="disk-row">
|
||||||
<header><span><%= rec[:path] %></span><span><%= format_bytes(rec[:used_bytes]) %> / <%= format_bytes(rec[:total_bytes]) %></span></header>
|
<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>
|
<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] %>
|
<% 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 %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% 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 %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="admin-panels">
|
<section class="admin-panels">
|
||||||
<div class="panel">
|
<div class="panel">
|
||||||
<h2>Sessioni attive</h2>
|
<h2><%= t("admin.dashboard.sessions.title") %></h2>
|
||||||
<% if @active_sessions.any? %>
|
<% if @active_sessions.any? %>
|
||||||
<table class="admin-table">
|
<table class="admin-table">
|
||||||
<thead>
|
<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>
|
</thead>
|
||||||
<tbody id="active-sessions-body">
|
<tbody id="active-sessions-body">
|
||||||
<% @active_sessions.each do |s| %>
|
<% @active_sessions.each do |s| %>
|
||||||
<tr>
|
<tr>
|
||||||
<td><%= s.match.team.name %> vs <%= s.match.opponent_name %></td>
|
<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><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">
|
<td class="admin-link-compact">
|
||||||
<% admin_session_watch_links(s).each do |link| %>
|
<% admin_session_watch_links(s).each do |link| %>
|
||||||
<%= link_to link[:label], link[:url], target: "_blank", rel: "noopener", class: "admin-link-chip" %>
|
<%= link_to link[:label], link[:url], target: "_blank", rel: "noopener", class: "admin-link-chip" %>
|
||||||
<% end %>
|
<% 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>
|
||||||
<td class="admin-actions">
|
<td class="admin-actions">
|
||||||
<%= link_to "Dettaglio", admin_session_path(s) %>
|
<%= link_to t("admin.dashboard.sessions.detail"), 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?" } } %>
|
<%= 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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<% end %>
|
<% end %>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<% else %>
|
<% else %>
|
||||||
<p class="empty">Nessuna sessione attiva in questo momento.</p>
|
<p class="empty"><%= t("admin.dashboard.sessions.none") %></p>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="panel">
|
<div class="panel">
|
||||||
<h2>Squadre</h2>
|
<h2><%= t("admin.dashboard.teams.title") %></h2>
|
||||||
<ul class="team-list">
|
<ul class="team-list">
|
||||||
<% @teams.each do |t| %>
|
<% @teams.each do |t_| %>
|
||||||
<li>
|
<li>
|
||||||
<%= link_to t.name, admin_team_path(t) %>
|
<%= link_to t_.name, admin_team_path(t_) %>
|
||||||
<span class="muted"><%= t.matches.count %> partite</span>
|
<span class="muted"><%= t("admin.dashboard.teams.matches_count", count: t_.matches.count) %></span>
|
||||||
</li>
|
</li>
|
||||||
<% end %>
|
<% end %>
|
||||||
</ul>
|
</ul>
|
||||||
<% if @stats[:teams_count] > @teams.size %>
|
<% 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 %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -2,30 +2,30 @@
|
|||||||
|
|
||||||
<section class="kpi-grid">
|
<section class="kpi-grid">
|
||||||
<div class="kpi-card <%= @summary[:open_critical].positive? ? 'kpi-card--danger' : 'kpi-card--accent' %>">
|
<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 class="kpi-value"><%= @summary[:open_critical] %></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="kpi-card">
|
<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 class="kpi-value"><%= @summary[:open_warning] %></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="kpi-card">
|
<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 class="kpi-value"><%= @summary[:open_total] %></div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div class="panel" style="margin-bottom:1.5rem">
|
<div class="panel" style="margin-bottom:1.5rem">
|
||||||
<h2>Incidenti aperti</h2>
|
<h2><%= t("admin.ops.open.title") %></h2>
|
||||||
<% if @open_incidents.any? %>
|
<% if @open_incidents.any? %>
|
||||||
<table class="admin-table">
|
<table class="admin-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Severità</th>
|
<th><%= t("admin.ops.open.table.severity") %></th>
|
||||||
<th>Tipo</th>
|
<th><%= t("admin.ops.open.table.kind") %></th>
|
||||||
<th>Titolo</th>
|
<th><%= t("admin.ops.open.table.title") %></th>
|
||||||
<th>Occ.</th>
|
<th><%= t("admin.ops.open.table.occurrences") %></th>
|
||||||
<th>Ultimo</th>
|
<th><%= t("admin.ops.open.table.last_seen") %></th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -44,26 +44,32 @@
|
|||||||
<td class="muted"><%= inc.last_seen_at&.strftime("%d/%m %H:%M") %></td>
|
<td class="muted"><%= inc.last_seen_at&.strftime("%d/%m %H:%M") %></td>
|
||||||
<td class="admin-actions">
|
<td class="admin-actions">
|
||||||
<% unless inc.status == 'acknowledged' %>
|
<% 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 %>
|
<% end %>
|
||||||
<%= button_to "Risolvi", resolve_admin_op_path(inc), method: :post, class: "admin-btn admin-btn--sm" %>
|
<%= button_to t("admin.ops.actions.resolve"), 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.mute"), mute_admin_op_path(inc), method: :post, class: "admin-btn admin-btn--sm admin-btn--outline" %>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<% end %>
|
<% end %>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<% else %>
|
<% else %>
|
||||||
<p class="empty">Nessun incidente aperto. La piattaforma risulta sana.</p>
|
<p class="empty"><%= t("admin.ops.open.none") %></p>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="panel">
|
<div class="panel">
|
||||||
<h2>Risolti (ultimi 30 giorni)</h2>
|
<h2><%= t("admin.ops.resolved.title") %></h2>
|
||||||
<% if @resolved_incidents.any? %>
|
<% if @resolved_incidents.any? %>
|
||||||
<table class="admin-table">
|
<table class="admin-table">
|
||||||
<thead>
|
<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>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<% @resolved_incidents.each do |inc| %>
|
<% @resolved_incidents.each do |inc| %>
|
||||||
@@ -78,6 +84,6 @@
|
|||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<% else %>
|
<% else %>
|
||||||
<p class="empty">Nessun incidente risolto di recente.</p>
|
<p class="empty"><%= t("admin.ops.resolved.none") %></p>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,29 +1,29 @@
|
|||||||
<div style="max-width:400px">
|
<div style="max-width:400px">
|
||||||
<h2>Cambia password</h2>
|
<h2><%= t("admin.passwords.edit.title") %></h2>
|
||||||
<% if flash[:alert] %>
|
<% if flash[:alert] %>
|
||||||
<p style="color:#ff6b6b"><%= flash[:alert] %></p>
|
<p style="color:#ff6b6b"><%= flash[:alert] %></p>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= form_with url: admin_password_path, method: :patch, local: true do %>
|
<%= form_with url: admin_password_path, method: :patch, local: true do %>
|
||||||
<p>
|
<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"
|
<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">
|
style="width:100%;padding:0.5rem;margin-top:0.25rem;background:#1E1E1E;border:1px solid #333;color:#fff;border-radius:6px">
|
||||||
</p>
|
</p>
|
||||||
<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"
|
<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">
|
style="width:100%;padding:0.5rem;margin-top:0.25rem;background:#1E1E1E;border:1px solid #333;color:#fff;border-radius:6px">
|
||||||
</p>
|
</p>
|
||||||
<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"
|
<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">
|
style="width:100%;padding:0.5rem;margin-top:0.25rem;background:#1E1E1E;border:1px solid #333;color:#fff;border-radius:6px">
|
||||||
</p>
|
</p>
|
||||||
<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">
|
<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>
|
</button>
|
||||||
<%= link_to "Annulla", admin_root_path %>
|
<%= link_to t("admin.passwords.edit.cancel"), admin_root_path %>
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<% watch_links = admin_session_watch_links(session) %>
|
<% watch_links = admin_session_watch_links(session) %>
|
||||||
<section class="admin-links-panel">
|
<section class="admin-links-panel">
|
||||||
<h3>Link diretta</h3>
|
<h3><%= t("admin.sessions.links.watch_title") %></h3>
|
||||||
<% if watch_links.any? %>
|
<% if watch_links.any? %>
|
||||||
<ul class="admin-link-list">
|
<ul class="admin-link-list">
|
||||||
<% watch_links.each do |link| %>
|
<% watch_links.each do |link| %>
|
||||||
@@ -11,28 +11,28 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</ul>
|
</ul>
|
||||||
<% else %>
|
<% else %>
|
||||||
<p class="muted">Nessun link video disponibile per questa sessione.</p>
|
<p class="muted"><%= t("admin.sessions.links.none") %></p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<h3>Link regia</h3>
|
<h3><%= t("admin.sessions.links.regia_title") %></h3>
|
||||||
<% if session.terminal? %>
|
<% if session.terminal? %>
|
||||||
<p class="muted">Sessione terminata — link regia non disponibile.</p>
|
<p class="muted"><%= t("admin.sessions.links.terminated") %></p>
|
||||||
<% else %>
|
<% else %>
|
||||||
<% if flash[:regia_url].present? %>
|
<% if flash[:regia_url].present? %>
|
||||||
<p class="admin-link-generated">
|
<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" %>
|
<%= link_to flash[:regia_url], flash[:regia_url], target: "_blank", rel: "noopener", class: "admin-link-url" %>
|
||||||
</p>
|
</p>
|
||||||
<p><code class="admin-link-code"><%= flash[:regia_url] %></code></p>
|
<p><code class="admin-link-code"><%= flash[:regia_url] %></code></p>
|
||||||
<% if (expires = admin_regia_expires_label(flash[:regia_expires_at])) %>
|
<% 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 %>
|
<% end %>
|
||||||
<% elsif session.regia_token_active? %>
|
<% elsif session.regia_token_active? %>
|
||||||
<p class="muted">Esiste già un link regia attivo (l’URL non è recuperabile). Generane uno nuovo se serve condividerlo di nuovo.</p>
|
<p class="muted"><%= t("admin.sessions.links.active_exists") %></p>
|
||||||
<% else %>
|
<% 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 %>
|
<% end %>
|
||||||
<%= button_to "Genera link regia",
|
<%= button_to t("admin.sessions.links.generate_button"),
|
||||||
regia_link_admin_session_path(session),
|
regia_link_admin_session_path(session),
|
||||||
method: :post,
|
method: :post,
|
||||||
class: "admin-btn admin-btn--secondary admin-btn--sm",
|
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">
|
<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>
|
<tbody>
|
||||||
<% @sessions.each do |s| %>
|
<% @sessions.each do |s| %>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -11,9 +19,9 @@
|
|||||||
<% admin_session_watch_links(s).each do |link| %>
|
<% admin_session_watch_links(s).each do |link| %>
|
||||||
<%= link_to link[:label], link[:url], target: "_blank", rel: "noopener", class: "admin-link-chip" %>
|
<%= link_to link[:label], link[:url], target: "_blank", rel: "noopener", class: "admin-link-chip" %>
|
||||||
<% end %>
|
<% 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>
|
||||||
<td><%= link_to "Dettaglio", admin_session_path(s) %></td>
|
<td><%= link_to t("admin.sessions.index.detail"), admin_session_path(s) %></td>
|
||||||
</tr>
|
</tr>
|
||||||
<% end %>
|
<% end %>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -1,26 +1,32 @@
|
|||||||
<h2>Session <%= @session.id %></h2>
|
<h2><%= t("admin.sessions.show.title", id: @session.id) %></h2>
|
||||||
<p>Status: <strong><%= @session.status %></strong></p>
|
<p><%= t("admin.sessions.show.status_label") %> <strong><%= @session.status %></strong></p>
|
||||||
<% unless @session.terminal? %>
|
<% unless @session.terminal? %>
|
||||||
<p>
|
<p>
|
||||||
<%= button_to "Termina sessione",
|
<%= button_to t("admin.sessions.show.stop_button"),
|
||||||
stop_admin_session_path(@session),
|
stop_admin_session_path(@session),
|
||||||
method: :post,
|
method: :post,
|
||||||
class: "admin-btn admin-btn--danger",
|
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>
|
</p>
|
||||||
<% end %>
|
<% 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 %>
|
<%= render "admin/sessions/links", session: @session %>
|
||||||
|
|
||||||
<% if @session.youtube_broadcast_id %>
|
<% 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 %>
|
<% 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>
|
<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>
|
<tbody>
|
||||||
<% @events.each do |e| %>
|
<% @events.each do |e| %>
|
||||||
<tr>
|
<tr>
|
||||||
|
|||||||
@@ -1,34 +1,36 @@
|
|||||||
<% if @team.club %>
|
<% 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 %>
|
<% end %>
|
||||||
|
|
||||||
<h2><%= @team.name %></h2>
|
<h2><%= @team.name %></h2>
|
||||||
<p>Sport: <%= @team.sport %></p>
|
<p><%= t("admin.teams.show.sport_label", sport: @team.sport) %></p>
|
||||||
<% if @team.club %>
|
<% 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 %>
|
<% end %>
|
||||||
<% yt = Youtube::TeamStatus.new(@team) %>
|
<% yt = Youtube::TeamStatus.new(@team) %>
|
||||||
<p>
|
<p>
|
||||||
YouTube:
|
<%= t("admin.teams.show.youtube_label") %>
|
||||||
<% if yt.selectable? %>
|
<% if yt.selectable? %>
|
||||||
<strong><%= yt.channel_title || "—" %></strong>
|
<strong><%= yt.channel_title || t("admin.common.dash") %></strong>
|
||||||
<% if yt.uses_platform_channel? %>
|
<% 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 %>
|
<% else %>
|
||||||
(canale società)
|
<%= t("admin.teams.show.youtube_club_channel") %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% else %>
|
<% else %>
|
||||||
non pronto
|
<%= t("admin.teams.show.youtube_not_ready") %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</p>
|
</p>
|
||||||
<% if @team.entitlements.premium_full? && @team.club.youtube_credential.blank? %>
|
<% if @team.entitlements.premium_full? && @team.club.youtube_credential.blank? %>
|
||||||
<p class="muted">
|
<p class="muted">
|
||||||
Senza canale collegato, l’app usa il canale Match Live TV.
|
<%= t("admin.teams.show.no_channel_hint_html", link: link_to(t("admin.teams.show.link_channel"), public_club_path(@team.club))) %>
|
||||||
<%= link_to "Collega canale (pagina società)", public_club_path(@team.club) %>.
|
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<h3>Partite</h3>
|
<h3><%= t("admin.teams.show.matches_title") %></h3>
|
||||||
<ul>
|
<ul>
|
||||||
<% @matches.each do |m| %>
|
<% @matches.each do |m| %>
|
||||||
<li><%= m.opponent_name %> — <%= m.scheduled_at %></li>
|
<li><%= m.opponent_name %> — <%= m.scheduled_at %></li>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="it">
|
<html lang="<%= I18n.locale %>">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<meta name="robots" content="noindex, nofollow">
|
<meta name="robots" content="noindex, nofollow">
|
||||||
<title>Token YouTube — Match Live TV</title>
|
<title><%= t("admin.youtube.platform_token.page_title") %></title>
|
||||||
<style>
|
<style>
|
||||||
body { font-family: system-ui, sans-serif; background: #0a0a0e; color: #f0f0f5; margin: 0; padding: 32px 20px; line-height: 1.5; }
|
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; }
|
.wrap { max-width: 720px; margin: 0 auto; }
|
||||||
@@ -17,27 +17,24 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
<h1>Token canale Match Live TV</h1>
|
<h1><%= t("admin.youtube.platform_token.title") %></h1>
|
||||||
|
|
||||||
<% if @channel_title.present? %>
|
<% if @channel_title.present? %>
|
||||||
<p>Canale: <strong><%= @channel_title %></strong></p>
|
<p><%= t("admin.youtube.platform_token.channel_label", title: @channel_title) %></p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<% if @refresh_token.present? %>
|
<% if @refresh_token.present? %>
|
||||||
<p class="ok">Collegamento riuscito. Copia subito questa riga:</p>
|
<p class="ok"><%= t("admin.youtube.platform_token.success") %></p>
|
||||||
<p>Aggiungi in <code>/opt/matchlivetv/infra/.env</code>:</p>
|
<p><%= t("admin.youtube.platform_token.env_hint_html") %></p>
|
||||||
<pre id="yt-token">YOUTUBE_PLATFORM_REFRESH_TOKEN=<%= @refresh_token %></pre>
|
<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 %>
|
<% else %>
|
||||||
<p class="muted">
|
<p class="muted">
|
||||||
Google non ha restituito un refresh token. In
|
<%= t("admin.youtube.platform_token.no_refresh_token_html") %>
|
||||||
<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>.
|
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<p><a href="/admin/">← Dashboard admin</a></p>
|
<p><a href="/admin/"><%= t("admin.youtube.platform_token.back_dashboard") %></a></p>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</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) %>
|
<p><%= raw t("mailers.invoice.body_html",
|
||||||
per un importo di <strong><%= @invoice.formatted_amount %></strong>, relativa alla società <strong><%= @club.billing_legal_name.presence || @club.name %></strong>.</p>
|
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) %>
|
<%= t("mailers.invoice.body_text",
|
||||||
(<%= @invoice.formatted_amount %>) per <%= @club.billing_legal_name.presence || @club.name %>.
|
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,
|
<%= t("mailers.invoice.thanks") %>
|
||||||
Match Live TV
|
<%= t("mailers.invoice.signoff") %>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html lang="<%= current_locale %>">
|
||||||
<head>
|
<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="viewport" content="width=device-width, initial-scale=1">
|
||||||
<meta name="robots" content="noindex, nofollow">
|
<meta name="robots" content="noindex, nofollow">
|
||||||
<link rel="stylesheet" href="/admin.css?v=2">
|
<link rel="stylesheet" href="/admin.css?v=2">
|
||||||
@@ -20,18 +20,19 @@
|
|||||||
<h1>MATCH <span>LIVE</span> TV — Admin</h1>
|
<h1>MATCH <span>LIVE</span> TV — Admin</h1>
|
||||||
<nav class="admin-nav">
|
<nav class="admin-nav">
|
||||||
<% if admin_logged_in? %>
|
<% 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 %>
|
<% ops_critical = Ops::Incident.critical_open.count %>
|
||||||
<%= link_to admin_ops_path, class: ("active" if controller_name == "ops") do %>
|
<%= 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 %>
|
<% end %>
|
||||||
<%= link_to "Società e squadre", admin_clubs_path, class: ("active" if controller_name.in?(%w[clubs teams club_recordings])) %>
|
<%= link_to t("admin.layout.nav.clubs"), 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 t("admin.layout.nav.billing"), 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 t("admin.layout.nav.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 t("admin.layout.nav.sessions"), admin_sessions_path, class: ("active" if controller_name == "sessions") %>
|
||||||
<%= link_to "Password", edit_admin_password_path %>
|
<%= link_to t("admin.layout.nav.password"), edit_admin_password_path %>
|
||||||
<%= button_to "Esci", admin_logout_path, method: :delete %>
|
<%= button_to t("admin.layout.nav.logout"), admin_logout_path, method: :delete %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<%= render "shared/language_switcher", locale_path: admin_locale_path %>
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|||||||
@@ -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="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">
|
<link rel="stylesheet" href="/marketing.css?v=44">
|
||||||
</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/cookie_banner" %>
|
||||||
<%= render "shared/marketing_nav" %>
|
<%= render "shared/marketing_nav" %>
|
||||||
<main>
|
<main>
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
<link rel="stylesheet" href="/live.css?v=26">
|
<link rel="stylesheet" href="/live.css?v=26">
|
||||||
<%= yield :head %>
|
<%= yield :head %>
|
||||||
</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/cookie_banner" %>
|
||||||
<%= render "shared/marketing_nav" %>
|
<%= render "shared/marketing_nav" %>
|
||||||
<main class="live-main">
|
<main class="live-main">
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="it">
|
<html lang="<%= html_lang %>">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
@@ -34,17 +34,17 @@
|
|||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
<%= link_to public_pricing_path, class: "brand" do %>Match <span>Live TV</span><% end %>
|
<%= link_to public_pricing_path, class: "brand" do %>Match <span>Live TV</span><% end %>
|
||||||
<nav>
|
<nav>
|
||||||
<%= link_to "Prezzi", public_pricing_path %>
|
<%= link_to t("nav.pricing"), public_pricing_path %>
|
||||||
<% if logged_in? %>
|
<% if logged_in? %>
|
||||||
<% if current_user.primary_club %>
|
<% 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? %>
|
<% 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 %>
|
<% 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 %>
|
<% else %>
|
||||||
· <%= link_to "Accedi", public_login_path %>
|
· <%= link_to t("nav.login"), 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.signup"), public_signup_path, class: "btn btn-primary", style: "padding:6px 12px;font-size:0.85rem" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</nav>
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="it">
|
<html lang="<%= html_lang %>">
|
||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8">
|
<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="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="apple-mobile-web-app-capable" content="yes">
|
||||||
<meta name="theme-color" content="#0a0a0e">
|
<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" %>
|
<%= render "shared/meta_tags" %>
|
||||||
<link rel="stylesheet" href="/regia.css?v=1">
|
<link rel="stylesheet" href="/regia.css?v=1">
|
||||||
<%= yield :head %>
|
<%= 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" %>
|
<% content_for :robots, "noindex, nofollow" %>
|
||||||
|
|
||||||
<div class="wrap" style="padding-top:20px;max-width:640px">
|
<div class="wrap" style="padding-top:20px;max-width:640px">
|
||||||
<h1>Dati di fatturazione</h1>
|
<h1><%= t("billing.profile.heading") %></h1>
|
||||||
<p style="color:#aaa">Società: <strong><%= @club.name %></strong>. Inserisci i dati per l’intestazione delle fatture e l’invio tramite SDI o PEC.</p>
|
<p style="color:#aaa"><%= t("billing.profile.lead", name: @club.name) %></p>
|
||||||
|
|
||||||
<% plan_return = params[:plan].presence_in(%w[premium_light premium_full]) %>
|
<% plan_return = params[:plan].presence_in(%w[premium_light premium_full]) %>
|
||||||
<% unless @club.billing_profile_complete? %>
|
<% unless @club.billing_profile_complete? %>
|
||||||
<div class="flash alert" style="margin:16px 0">
|
<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) %>
|
<%= billing_profile_incomplete_message(@club) %>
|
||||||
<% if plan_return %>
|
<% 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 %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -21,11 +21,11 @@
|
|||||||
<%= hidden_field_tag :plan, plan_return if plan_return %>
|
<%= hidden_field_tag :plan, plan_return if plan_return %>
|
||||||
<%= hidden_field_tag :interval, params[:interval] if params[:interval].present? %>
|
<%= hidden_field_tag :interval, params[:interval] if params[:interval].present? %>
|
||||||
<%= render "shared/billing_profile_fields", record: @club %>
|
<%= 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 %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p style="margin-top:20px">
|
<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>
|
</p>
|
||||||
</div>
|
</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" %>
|
<% content_for :robots, "noindex, nofollow" %>
|
||||||
|
|
||||||
<div class="wrap" style="padding-top:20px;max-width:560px">
|
<div class="wrap" style="padding-top:20px;max-width:560px">
|
||||||
<p class="results-hint" style="margin-bottom:8px">
|
<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>
|
</p>
|
||||||
<h1>Programma partita</h1>
|
<h1><%= t("matches.club_new.heading") %></h1>
|
||||||
<p style="color:#888">
|
<p style="color:#888">
|
||||||
<%= @club.name %>
|
<%= @club.name %>
|
||||||
<% if @club_admin %>
|
<% if @club_admin %>
|
||||||
· Puoi scegliere qualsiasi squadra della società
|
<%= t("matches.club_new.admin_hint") %>
|
||||||
<% else %>
|
<% else %>
|
||||||
· Solo le squadre in cui sei responsabile trasmissione
|
<%= t("matches.club_new.staff_hint") %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="card" style="margin-top:16px">
|
<div class="card" style="margin-top:16px">
|
||||||
<%= form_with url: public_club_matches_path(@club), method: :post, local: true do %>
|
<%= 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 %>
|
<% if @schedulable_teams.size == 1 %>
|
||||||
<%= hidden_field_tag :team_id, @selected_team.id %>
|
<%= hidden_field_tag :team_id, @selected_team.id %>
|
||||||
<p style="margin:0 0 16px;font-weight:600"><%= @selected_team.name %></p>
|
<p style="margin:0 0 16px;font-weight:600"><%= @selected_team.name %></p>
|
||||||
@@ -29,7 +29,7 @@
|
|||||||
|
|
||||||
<%= render "public/matches/schedule_fields", match: @match %>
|
<%= 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 %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</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" %>
|
<% content_for :robots, "noindex, nofollow" %>
|
||||||
|
|
||||||
<div class="wrap club-dashboard" style="padding-top:20px">
|
<div class="wrap club-dashboard" style="padding-top:20px">
|
||||||
|
|||||||
@@ -3,50 +3,46 @@
|
|||||||
<% cred = club.youtube_credential %>
|
<% cred = club.youtube_credential %>
|
||||||
|
|
||||||
<section class="card club-youtube" id="youtube">
|
<section class="card club-youtube" id="youtube">
|
||||||
<h2>YouTube Live</h2>
|
<h2><%= t("club.youtube.heading") %></h2>
|
||||||
|
|
||||||
<% if params[:youtube] == "connected" %>
|
<% 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 %>
|
<% end %>
|
||||||
|
|
||||||
<% if entitlements.plan.youtube_mode == "matchlivetv_light" %>
|
<% if entitlements.plan.youtube_mode == "matchlivetv_light" %>
|
||||||
<p>
|
<p>
|
||||||
Con il piano <strong>Premium Light</strong> le dirette YouTube di tutte le squadre vanno sul canale ufficiale
|
<%= raw t("club.youtube.light_body_html") %>
|
||||||
<strong>Match Live TV</strong>. Non serve collegare un canale della società.
|
|
||||||
</p>
|
</p>
|
||||||
<% if yt&.selectable? %>
|
<% if yt&.selectable? %>
|
||||||
<p class="muted">Canale pronto · seleziona «YouTube Live» nell’app mobile.</p>
|
<p class="muted"><%= t("club.youtube.light_ready") %></p>
|
||||||
<% else %>
|
<% 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 %>
|
<% end %>
|
||||||
<% elsif entitlements.premium_full? %>
|
<% elsif entitlements.premium_full? %>
|
||||||
<% if cred %>
|
<% if cred %>
|
||||||
<p>
|
<p>
|
||||||
Canale collegato per <strong>tutta la società</strong>:
|
<%= raw t("club.youtube.full_connected_html", channel: cred.channel_title.presence || cred.channel_id || "YouTube") %>
|
||||||
<strong><%= cred.channel_title.presence || cred.channel_id || "YouTube" %></strong>
|
|
||||||
</p>
|
</p>
|
||||||
<p class="muted">Tutte le squadre possono trasmettere su questo canale quando scelgono il canale società in app.</p>
|
<p class="muted"><%= t("club.youtube.full_connected_hint") %></p>
|
||||||
<%= button_to "Scollega canale", public_club_youtube_disconnect_path(club),
|
<%= button_to t("club.youtube.disconnect"), public_club_youtube_disconnect_path(club),
|
||||||
method: :delete,
|
method: :delete,
|
||||||
class: "btn btn-secondary",
|
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 %>
|
<% else %>
|
||||||
<p>
|
<p>
|
||||||
Senza canale collegato, le dirette YouTube dall’app vanno sul canale ufficiale
|
<%= raw t("club.youtube.full_not_connected_html") %>
|
||||||
<strong>Match Live TV</strong>.
|
|
||||||
Collega qui il canale YouTube della società (vale per tutte le squadre).
|
|
||||||
</p>
|
</p>
|
||||||
<% if yt&.selectable? %>
|
<% 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 %>
|
<% end %>
|
||||||
<% if ENV["YOUTUBE_CLIENT_ID"].present? %>
|
<% 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 %>
|
<% else %>
|
||||||
<p class="muted">OAuth YouTube non ancora configurato sul server.</p>
|
<p class="muted"><%= t("club.youtube.oauth_not_configured") %></p>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% end %>
|
<% end %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<p class="muted">YouTube sul canale della società richiede il piano Premium Full.</p>
|
<p class="muted"><%= t("club.youtube.requires_full") %></p>
|
||||||
<%= link_to "Vedi piani", public_prezzi_path, class: "btn btn-secondary" %>
|
<%= link_to t("club.youtube.see_plans"), public_prezzi_path, class: "btn btn-secondary" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</section>
|
</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" %>
|
<% content_for :robots, "noindex, nofollow" %>
|
||||||
|
|
||||||
<div class="wrap" style="padding-top:20px">
|
<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 %>
|
<%= render "shared/club_subscription_status", club: @club, entitlements: @entitlements, subscription: @subscription, on_billing_page: true %>
|
||||||
|
|
||||||
<% if @subscription&.admin_comped? %>
|
<% if @subscription&.admin_comped? %>
|
||||||
<div class="card" style="margin-top:16px;border-color:#3d3520;background:#1a1810">
|
<div class="card" style="margin-top:16px;border-color:#3d3520;background:#1a1810">
|
||||||
<p style="margin:0;color:#ddd">
|
<p style="margin:0;color:#ddd">
|
||||||
<strong>Abbonamento omaggio Match Live TV</strong>
|
<strong><%= t("club.billing.comped.title") %></strong>
|
||||||
— piano <strong><%= @subscription.plan.name %></strong>
|
<%= t("club.billing.comped.plan_prefix") %> <strong><%= @subscription.plan.name %></strong>
|
||||||
<% if @subscription.admin_comped_reason.present? %>
|
<% if @subscription.admin_comped_reason.present? %>
|
||||||
(<%= @subscription.admin_comped_reason %>)
|
(<%= @subscription.admin_comped_reason %>)
|
||||||
<% end %>.
|
<% end %>.
|
||||||
Non è richiesto alcun pagamento. Per modifiche o passaggio a un piano a pagamento, contatta
|
<%= raw t("club.billing.comped.no_payment_html", email_link: mail_to("info@matchlive.it", "info@matchlive.it")) %>
|
||||||
<%= mail_to "info@matchlive.it", "info@matchlive.it" %>.
|
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
@@ -28,5 +27,5 @@
|
|||||||
|
|
||||||
<%= render "shared/billing_documents", club: @club, payments: @payments %>
|
<%= 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>
|
</div>
|
||||||
|
|||||||
@@ -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" %>
|
<% content_for :robots, "noindex, nofollow" %>
|
||||||
|
|
||||||
<div class="wrap" style="padding-top:20px;max-width:560px">
|
<div class="wrap" style="padding-top:20px;max-width:560px">
|
||||||
<h1>Modifica società</h1>
|
<h1><%= t("club.edit.heading") %></h1>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<%= form_with url: public_club_path(@club), method: :patch, multipart: true do %>
|
<%= 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 %>
|
<%= 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) %>
|
<%= 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 %>
|
<%= 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 %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<p><%= link_to "← Società", public_club_path(@club) %></p>
|
<p><%= link_to t("club.back_to_club"), public_club_path(@club) %></p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,39 +1,39 @@
|
|||||||
<% content_for :title, "Registra società — Match Live TV" %>
|
<% content_for :title, t("club.new.title") %>
|
||||||
<% content_for :meta_description, "Crea la società sportiva e la prima squadra su Match Live TV." %>
|
<% content_for :meta_description, t("club.new.meta_description") %>
|
||||||
<% content_for :robots, "noindex, nofollow" %>
|
<% content_for :robots, "noindex, nofollow" %>
|
||||||
|
|
||||||
<section class="auth-page">
|
<section class="auth-page">
|
||||||
<h1>La tua società</h1>
|
<h1><%= t("club.new.heading") %></h1>
|
||||||
<p class="auth-lead">Registra il club: potrai aggiungere più squadre (Under 13, Under 15, Serie C…).</p>
|
<p class="auth-lead"><%= t("club.new.lead") %></p>
|
||||||
<div class="card card-wide">
|
<div class="card card-wide">
|
||||||
<%= form_with url: public_clubs_path, multipart: true do %>
|
<%= form_with url: public_clubs_path, multipart: true do %>
|
||||||
<h2 class="form-section-title">Società</h2>
|
<h2 class="form-section-title"><%= t("club.new.section_club") %></h2>
|
||||||
<%= label_tag "club[name]", "Nome società / club" %>
|
<%= label_tag "club[name]", t("club.new.name_label") %>
|
||||||
<%= text_field_tag "club[name]", params.dig(:club, :name), required: true, placeholder: "es. Crazy Volley Rozzano" %>
|
<%= text_field_tag "club[name]", params.dig(:club, :name), required: true, placeholder: t("club.new.name_placeholder") %>
|
||||||
<%= label_tag "club[sport]", "Sport principale" %>
|
<%= label_tag "club[sport]", t("club.sport_label") %>
|
||||||
<%= select_tag "club[sport]", sport_catalog_options(params.dig(:club, :sport) || "pallavolo") %>
|
<%= 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") %>
|
<%= render "shared/billing_profile_fields", record: Club.new(billing_country: "IT") %>
|
||||||
|
|
||||||
<h2 class="form-section-title" style="margin-top:24px">Prima squadra</h2>
|
<h2 class="form-section-title" style="margin-top:24px"><%= t("club.new.section_first_team") %></h2>
|
||||||
<%= label_tag "first_team[name]", "Nome squadra" %>
|
<%= 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: "es. Under 15" %>
|
<%= 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([
|
<%= select_tag :plan, options_for_select([
|
||||||
["Free — 1 responsabile trasmissione per squadra, 1 live", "free"],
|
[t("club.new.plan_free"), "free"],
|
||||||
["Premium Light", "premium_light"],
|
[t("club.new.plan_light"), "premium_light"],
|
||||||
["Premium Full", "premium_full"]
|
[t("club.new.plan_full"), "premium_full"]
|
||||||
], params[:plan] || "free"), id: "club_plan_select" %>
|
], params[:plan] || "free"), id: "club_plan_select" %>
|
||||||
<div id="club_plan_interval" style="margin-top:10px">
|
<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([
|
<%= select_tag :interval, options_for_select([
|
||||||
["Annuale — €40/anno (Light) o €200/anno (Full)", "yearly"],
|
[t("club.new.interval_yearly"), "yearly"],
|
||||||
["Mensile — €5/mese (Light) o €20/mese (Full)", "monthly"]
|
[t("club.new.interval_monthly"), "monthly"]
|
||||||
], params[:interval] || "yearly") %>
|
], params[:interval] || "yearly") %>
|
||||||
</div>
|
</div>
|
||||||
<%= render "shared/stripe_secure_payment", compact: true %>
|
<%= 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 %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -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" %>
|
<% content_for :robots, "noindex, nofollow" %>
|
||||||
|
|
||||||
<div class="wrap club-dashboard" style="padding-top:20px">
|
<div class="wrap club-dashboard" style="padding-top:20px">
|
||||||
@@ -10,20 +10,20 @@
|
|||||||
<h1><%= @club.name %></h1>
|
<h1><%= @club.name %></h1>
|
||||||
<% if @entitlements %>
|
<% if @entitlements %>
|
||||||
<p class="club-meta">
|
<p class="club-meta">
|
||||||
Piano: <strong><%= @entitlements.plan.name %></strong>
|
<%= t("club.dashboard.plan_label") %> <strong><%= @entitlements.plan.name %></strong>
|
||||||
· Squadre: <strong><%= @teams.size %></strong>
|
· <%= t("club.dashboard.teams_count_label") %> <strong><%= @teams.size %></strong>
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div style="display:flex;gap:12px;flex-wrap:wrap;margin:20px 0">
|
<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 t("club.dashboard.edit_club"), public_edit_club_path(@club), class: "btn btn-secondary" %>
|
||||||
<%= link_to "Abbonamento", public_club_billing_path(@club), class: "btn btn-primary" %>
|
<%= link_to t("club.dashboard.subscription"), 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 t("club.dashboard.new_team"), 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.live_streams"), public_live_index_path(club_id: @club.id), class: "btn btn-secondary" %>
|
||||||
<% if @entitlements&.can_access_recordings? %>
|
<% 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 %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -33,32 +33,32 @@
|
|||||||
entitlements_team: @entitlements_team %>
|
entitlements_team: @entitlements_team %>
|
||||||
|
|
||||||
<% if @replay_stats %>
|
<% 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 class="card" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:16px;padding:16px;margin-bottom:24px">
|
||||||
<div>
|
<div>
|
||||||
<div style="font-size:1.6rem;font-weight:700"><%= @replay_stats[:available_count] %></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>
|
<div>
|
||||||
<div style="font-size:1.6rem;font-weight:700"><%= @replay_stats[:expiring_soon_count] %></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>
|
<div>
|
||||||
<div style="font-size:1.6rem;font-weight:700"><%= number_to_human_size(@replay_stats[:total_bytes]) %></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>
|
<div>
|
||||||
<div style="font-size:1.6rem;font-weight:700"><%= @replay_stats[:total_views] %></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>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<h2>Squadre</h2>
|
<h2><%= t("club.dashboard.teams_heading") %></h2>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<% if @teams.any? %>
|
<% if @teams.any? %>
|
||||||
<table class="data">
|
<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>
|
<tbody>
|
||||||
<% @teams.each do |team| %>
|
<% @teams.each do |team| %>
|
||||||
<tr>
|
<tr>
|
||||||
@@ -68,28 +68,28 @@
|
|||||||
</td>
|
</td>
|
||||||
<td style="color:#888;font-size:0.88rem">
|
<td style="color:#888;font-size:0.88rem">
|
||||||
<% if team.logo_url.present? || team.logo_file.attached? || team.primary_color.present? %>
|
<% if team.logo_url.present? || team.logo_file.attached? || team.primary_color.present? %>
|
||||||
Personalizzato
|
<%= t("club.dashboard.branding_custom") %>
|
||||||
<% else %>
|
<% else %>
|
||||||
Eredita dalla società
|
<%= t("club.dashboard.branding_inherited") %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</td>
|
</td>
|
||||||
<td style="white-space:nowrap">
|
<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>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<% end %>
|
<% end %>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<% else %>
|
<% 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 %>
|
<% end %>
|
||||||
</div>
|
</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). L’abbonamento vale per tutta la società.</p>
|
<p style="color:#888;margin-top:24px"><%= t("club.dashboard.footer_note") %></p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,21 +1,26 @@
|
|||||||
<% content_for :title, "Invito squadra — Match Live TV" %>
|
<% content_for :title, t("auth.invitation.meta_title") %>
|
||||||
<% content_for :meta_description, "Accetta l'invito per entrare nello staff della squadra su Match Live TV." %>
|
<% content_for :meta_description, t("auth.invitation.meta_description") %>
|
||||||
<% content_for :robots, "noindex, nofollow" %>
|
<% content_for :robots, "noindex, nofollow" %>
|
||||||
|
|
||||||
<div class="card" style="max-width:480px">
|
<div class="card" style="max-width:480px">
|
||||||
<h1>Accesso staff — <%= @invitation.team.name %></h1>
|
<h1><%= raw t("auth.invitation.title_html", team_name: @invitation.team.name) %></h1>
|
||||||
<p>Sei stato aggiunto come <strong>responsabile trasmissione</strong> (<%= @invitation.email %>).</p>
|
<p><%= raw t("auth.invitation.role_notice_html", email: @invitation.email) %></p>
|
||||||
<p class="muted" style="font-size:0.9rem;margin-bottom:16px">
|
<p class="muted" style="font-size:0.9rem;margin-bottom:16px">
|
||||||
Nell’app Match Live TV accedi con <strong><%= @invitation.email %></strong>, accetta l’invito e collega il tuo canale YouTube.
|
<%= raw t("auth.invitation.instructions_html", email: @invitation.email) %>
|
||||||
</p>
|
</p>
|
||||||
<% if logged_in? %>
|
<% 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 %>
|
<% else %>
|
||||||
<p><%= link_to "Accedi", public_login_path %> o <%= link_to "registrati", public_signup_path %> con <%= @invitation.email %>.</p>
|
<p><%= raw t(
|
||||||
<%= button_to "Accetta (se già loggato)", public_invitation_path(token: @token), method: :post, class: "btn btn-secondary" %>
|
"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 %>
|
<% end %>
|
||||||
<p style="margin-top:16px;font-size:0.88rem;color:#888">
|
<p style="margin-top:16px;font-size:0.88rem;color:#888">
|
||||||
App mobile:
|
<%= t("auth.invitation.mobile_app_label") %>
|
||||||
<a href="matchlivetv://join/<%= @token %>">Apri invito nell’app</a>
|
<a href="matchlivetv://join/<%= @token %>"><%= t("auth.invitation.open_in_app") %></a>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
badge_class = local_assigns[:badge_class]
|
badge_class = local_assigns[:badge_class]
|
||||||
|
|
||||||
unless badge_label
|
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
|
end
|
||||||
|
|
||||||
unless badge_class
|
unless badge_class
|
||||||
|
|||||||
@@ -1,33 +1,33 @@
|
|||||||
<% content_for :title, "Dirette live partite giovanili in corso — Match Live TV" %>
|
<% content_for :title, t("live.index.meta_title") %>
|
||||||
<% 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 :meta_description, t("live.index.meta_description") %>
|
||||||
<% content_for :canonical_url, seo_absolute_url(public_live_index_path) %>
|
<% content_for :canonical_url, seo_absolute_url(public_live_index_path) %>
|
||||||
|
|
||||||
<% live_index_params = @club ? { club_id: @club.id } : {} %>
|
<% live_index_params = @club ? { club_id: @club.id } : {} %>
|
||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
<% if @club %>
|
<% if @club %>
|
||||||
<p class="results-hint" style="margin-bottom:8px">
|
<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>
|
</p>
|
||||||
<h1>Dirette e partite — <%= @club.name %></h1>
|
<h1><%= t("live.index.club_title", name: @club.name) %></h1>
|
||||||
<p class="results-hint">
|
<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>
|
</p>
|
||||||
<div class="live-actions" style="display:flex;gap:12px;flex-wrap:wrap;margin-bottom:16px">
|
<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>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<h1>Dirette e partite in programma</h1>
|
<h1><%= t("live.index.default_title") %></h1>
|
||||||
<p class="results-hint">
|
<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>
|
</p>
|
||||||
<div class="live-actions" style="display:flex;gap:12px;flex-wrap:wrap;margin-bottom:20px">
|
<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>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<% if @club && @can_schedule_match %>
|
<% if @club && @can_schedule_match %>
|
||||||
<div style="display:flex;gap:12px;flex-wrap:wrap;margin-bottom:16px">
|
<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>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
@@ -39,25 +39,25 @@
|
|||||||
type="search"
|
type="search"
|
||||||
name="q"
|
name="q"
|
||||||
value="<%= @query %>"
|
value="<%= @query %>"
|
||||||
placeholder="<%= @club ? "Cerca società, squadra, avversario o luogo…" : "Es. Crazy Volley, Serie D, avversario…" %>"
|
placeholder="<%= @club ? t("live.index.search_placeholder_club") : t("live.index.search_placeholder_default") %>"
|
||||||
aria-label="Cerca squadra"
|
aria-label="<%= t("live.index.search_aria_label") %>"
|
||||||
autocomplete="off"
|
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? %>
|
<% 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 %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<% if @query.present? %>
|
<% if @query.present? %>
|
||||||
|
<% club_suffix = @club ? t("live.index.results_hint_club_suffix", name: @club.name) : "" %>
|
||||||
<p class="results-hint">
|
<p class="results-hint">
|
||||||
Risultati per «<%= h @query %>»<%= @club ? " in #{@club.name}" : "" %>:
|
<%= t("live.index.results_hint", query: @query, club_suffix: club_suffix, live_count: @sessions.size, upcoming_count: @upcoming_matches.size) %>
|
||||||
<%= @sessions.size %> in diretta · <%= @upcoming_matches.size %> in programma
|
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<% if @sessions.any? %>
|
<% 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">
|
<div class="live-grid">
|
||||||
<% @sessions.each do |session| %>
|
<% @sessions.each do |session| %>
|
||||||
<% match = session.match %>
|
<% match = session.match %>
|
||||||
@@ -72,33 +72,33 @@
|
|||||||
<p class="card-score">
|
<p class="card-score">
|
||||||
<span class="card-sets"><%= live_score_sets_label(session.score_state) %></span>
|
<span class="card-sets"><%= live_score_sets_label(session.score_state) %></span>
|
||||||
<% if live_score_partials_label(session.score_state).present? %>
|
<% 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 %>
|
<% end %>
|
||||||
<span class="card-points"><%= session.score_state.home_points %> - <%= session.score_state.away_points %></span>
|
<span class="card-points"><%= session.score_state.home_points %> - <%= session.score_state.away_points %></span>
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
<div class="badges">
|
<div class="badges">
|
||||||
<% if on_air %>
|
<% 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" %>
|
<% 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" %>
|
<% 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" %>
|
<% elsif session.status == "reconnecting" %>
|
||||||
<span class="badge badge-connecting">Riconnessione</span>
|
<span class="badge badge-connecting"><%= t("live.index.badge_reconnecting") %></span>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="badge badge-wait">In avvio</span>
|
<span class="badge badge-wait"><%= t("live.index.badge_starting") %></span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</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>
|
</article>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<% if @upcoming_matches.any? %>
|
<% if @upcoming_matches.any? %>
|
||||||
<h2 class="section-heading<%= ' section-heading--spaced' if @sessions.any? %>">Prossime partite</h2>
|
<h2 class="section-heading<%= ' section-heading--spaced' if @sessions.any? %>"><%= t("live.index.section_upcoming") %></h2>
|
||||||
<p class="results-hint">Programmate da app o sito: la diretta partirà quando lo staff avvierà la trasmissione.</p>
|
<p class="results-hint"><%= t("live.index.upcoming_hint") %></p>
|
||||||
<div class="live-grid upcoming-grid">
|
<div class="live-grid upcoming-grid">
|
||||||
<% @upcoming_matches.each do |match| %>
|
<% @upcoming_matches.each do |match| %>
|
||||||
<article class="live-card live-card--upcoming">
|
<article class="live-card live-card--upcoming">
|
||||||
@@ -111,7 +111,7 @@
|
|||||||
<%= live_scheduled_relative(match.scheduled_at) %>
|
<%= live_scheduled_relative(match.scheduled_at) %>
|
||||||
</p>
|
</p>
|
||||||
<div class="badges">
|
<div class="badges">
|
||||||
<span class="badge badge-scheduled">In programma</span>
|
<span class="badge badge-scheduled"><%= t("live.index.badge_scheduled") %></span>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -121,59 +121,59 @@
|
|||||||
<% if @sessions.empty? %>
|
<% if @sessions.empty? %>
|
||||||
<% if @upcoming_matches.any? %>
|
<% if @upcoming_matches.any? %>
|
||||||
<div class="empty-state empty-state--soft">
|
<div class="empty-state empty-state--soft">
|
||||||
<p><strong>Nessuna diretta in questo momento.</strong></p>
|
<p><strong><%= t("live.index.empty_soft_title") %></strong></p>
|
||||||
<p>Torna all’orario indicato: quando la squadra avvierà lo streaming, la partita comparirà in «In diretta adesso».</p>
|
<p><%= t("live.index.empty_soft_body") %></p>
|
||||||
</div>
|
</div>
|
||||||
<% elsif @club %>
|
<% elsif @club %>
|
||||||
<div class="empty-state">
|
<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>
|
<p>
|
||||||
<% if @can_schedule_match %>
|
<% if @can_schedule_match %>
|
||||||
<%= link_to "Programma la prima partita", public_new_club_match_path(@club), class: "btn btn-primary" %>
|
<%= link_to t("live.index.empty_club_schedule_link"), public_new_club_match_path(@club), class: "btn btn-primary" %>
|
||||||
oppure dall’app Match Live TV.
|
<%= t("live.index.empty_club_schedule_suffix") %>
|
||||||
<% elsif @club.teams.any? %>
|
<% 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 %>
|
<% else %>
|
||||||
Aggiungi una squadra dalla
|
<%= raw t(
|
||||||
<%= link_to "pagina società", public_club_path(@club) %>
|
"live.index.empty_club_add_team_html",
|
||||||
e programma la prima partita.
|
club_link: link_to(t("live.index.empty_club_page_link"), public_club_path(@club))
|
||||||
|
) %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</p>
|
</p>
|
||||||
<p style="margin-top:16px">
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="empty-hero">
|
<div class="empty-hero">
|
||||||
<div class="empty-hero-copy">
|
<div class="empty-hero-copy">
|
||||||
<h2>Nessuna diretta adesso — ma puoi già organizzarti</h2>
|
<h2><%= t("live.index.empty_hero_title") %></h2>
|
||||||
<p>
|
<p>
|
||||||
Se la tua squadra usa Match Live TV, dal telefono si programma la partita con data e ora.
|
<%= t("live.index.empty_hero_body") %>
|
||||||
Genitori e parenti trovano qui la diretta quando il fischio d’inizio è vicino.
|
|
||||||
</p>
|
</p>
|
||||||
<ul class="empty-hero-list">
|
<ul class="empty-hero-list">
|
||||||
<li>Programma la gara dall’app (senza avviare subito la diretta)</li>
|
<li><%= t("live.index.empty_hero_list_item1") %></li>
|
||||||
<li>La partita compare in «Prossime partite» con orario e campo</li>
|
<li><%= t("live.index.empty_hero_list_item2") %></li>
|
||||||
<li>Lo streaming parte solo quando premi avvio in palestra</li>
|
<li><%= t("live.index.empty_hero_list_item3") %></li>
|
||||||
</ul>
|
</ul>
|
||||||
<p class="empty-hero-cta">
|
<p class="empty-hero-cta">
|
||||||
<%= link_to "Registra la tua squadra", public_signup_path, class: "btn btn-primary" %>
|
<%= link_to t("live.index.empty_hero_cta_signup"), 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_features"), public_features_path, class: "btn btn-secondary" %>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<aside class="demo-live-card" aria-label="Esempio di diretta attiva">
|
<aside class="demo-live-card" aria-label="<%= t("live.index.demo_aria_label") %>">
|
||||||
<p class="demo-label">Esempio — così appare una diretta attiva</p>
|
<p class="demo-label"><%= t("live.index.demo_label") %></p>
|
||||||
<article class="live-card live-card--demo">
|
<article class="live-card live-card--demo">
|
||||||
<h3>Tigers Volley vs ASD Eagles</h3>
|
<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">
|
<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>
|
<span class="card-points">18 - 16</span>
|
||||||
</p>
|
</p>
|
||||||
<div class="badges">
|
<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>
|
</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>
|
</article>
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,6 +1,12 @@
|
|||||||
<% club_name = @match.team.club&.name %>
|
<% club_name = @match.team.club&.name %>
|
||||||
<% content_for :title do %><%= club_name %> · <%= @match.team.name %> vs <%= @match.opponent_name %> — Diretta live<% 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 %>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 :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 :robots, @session.publicly_listed? ? "index, follow" : "noindex, nofollow" %>
|
||||||
|
|
||||||
<% content_for :head do %>
|
<% content_for :head do %>
|
||||||
@@ -10,45 +16,45 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="wrap">
|
<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) %>
|
<%= live_match_page_heading(@match) %>
|
||||||
|
|
||||||
<% if @stream_closed %>
|
<% if @stream_closed %>
|
||||||
<div id="stream-ended" class="stream-ended" role="status" aria-live="polite">
|
<div id="stream-ended" class="stream-ended" role="status" aria-live="polite">
|
||||||
<div class="stream-ended-icon" aria-hidden="true">📡</div>
|
<div class="stream-ended-icon" aria-hidden="true">📡</div>
|
||||||
<h2>Diretta terminata</h2>
|
<h2><%= t("live.show.stream_ended_title") %></h2>
|
||||||
<p>Lo streaming di questa partita è stato chiuso.</p>
|
<p><%= t("live.show.stream_ended_body") %></p>
|
||||||
<% if @session.ended_at %>
|
<% 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 %>
|
<% end %>
|
||||||
<p class="stream-ended-hint">Il punteggio è visibile nel video se la diretta era ancora in corso.</p>
|
<p class="stream-ended-hint"><%= t("live.show.stream_ended_hint") %></p>
|
||||||
<%= link_to "Tutte le dirette", public_live_index_path, class: "btn btn-secondary stream-ended-btn" %>
|
<%= link_to t("live.show.all_live_link"), public_live_index_path, class: "btn btn-secondary stream-ended-btn" %>
|
||||||
</div>
|
</div>
|
||||||
<% elsif @session.platform == "youtube" %>
|
<% elsif @session.platform == "youtube" %>
|
||||||
<div class="stream-ended youtube-live-cta" role="status" aria-live="polite">
|
<div class="stream-ended youtube-live-cta" role="status" aria-live="polite">
|
||||||
<div class="stream-ended-icon" aria-hidden="true">▶</div>
|
<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? %>
|
<% if @session.youtube_watch_url.present? %>
|
||||||
<p>Questa partita è trasmessa sul canale YouTube Match Live TV, non sul player del sito.</p>
|
<p><%= t("live.show.youtube_available_body") %></p>
|
||||||
<%= link_to "Guarda su YouTube", @session.youtube_watch_url, class: "btn stream-ended-btn", target: "_blank", rel: "noopener" %>
|
<%= link_to t("live.show.youtube_watch_link"), @session.youtube_watch_url, class: "btn stream-ended-btn", target: "_blank", rel: "noopener" %>
|
||||||
<% else %>
|
<% 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">
|
<meta http-equiv="refresh" content="8">
|
||||||
<% end %>
|
<% 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>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="live-player-wrap">
|
<div class="live-player-wrap">
|
||||||
<video id="player" controls playsinline muted autoplay poster="/images/copertina-canale.png"></video>
|
<video id="player" controls playsinline muted autoplay poster="/images/copertina-canale.png"></video>
|
||||||
<%= render "public/live/player_overlays", match: @match, session: @session %>
|
<%= render "public/live/player_overlays", match: @match, session: @session %>
|
||||||
<button type="button" id="play-hint" class="live-play-hint" hidden>
|
<button type="button" id="play-hint" class="live-play-hint" hidden>
|
||||||
▶ Avvia la diretta
|
<%= t("live.show.play_hint") %>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</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="offline-msg" class="live-status-msg" hidden><%= t("live.show.offline_msg") %></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="awaiting-msg" class="live-status-msg" hidden><%= t("live.show.awaiting_msg") %></p>
|
||||||
<p id="paused-msg" class="live-status-msg" hidden>Trasmissione in pausa — il flusso continua (copertina server).</p>
|
<p id="paused-msg" class="live-status-msg" hidden><%= t("live.show.paused_msg") %></p>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
const sessionId = "<%= @session.id %>";
|
const sessionId = "<%= @session.id %>";
|
||||||
@@ -63,19 +69,19 @@
|
|||||||
function syncStreamBadge(data) {
|
function syncStreamBadge(data) {
|
||||||
if (!streamBadge) return;
|
if (!streamBadge) return;
|
||||||
if (data.stream_closed) {
|
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";
|
streamBadge.className = "live-ovl-badge live-ovl-badge--right badge-ended";
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const paused = !!(data.paused || data.status === "paused");
|
const paused = !!(data.paused || data.status === "paused");
|
||||||
if (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";
|
streamBadge.className = "live-ovl-badge live-ovl-badge--right badge-wait";
|
||||||
} else if (data.on_air) {
|
} 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";
|
streamBadge.className = "live-ovl-badge live-ovl-badge--right badge-live";
|
||||||
} else {
|
} else {
|
||||||
streamBadge.textContent = "In attesa";
|
streamBadge.textContent = "<%= j t("live.overlays.badge_waiting") %>";
|
||||||
streamBadge.className = "live-ovl-badge live-ovl-badge--right badge-wait";
|
streamBadge.className = "live-ovl-badge live-ovl-badge--right badge-wait";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -158,10 +164,10 @@
|
|||||||
panel.setAttribute("role", "status");
|
panel.setAttribute("role", "status");
|
||||||
panel.innerHTML = `
|
panel.innerHTML = `
|
||||||
<div class="stream-ended-icon" aria-hidden="true">📡</div>
|
<div class="stream-ended-icon" aria-hidden="true">📡</div>
|
||||||
<h2>Diretta terminata</h2>
|
<h2><%= j t("live.show.stream_ended_title") %></h2>
|
||||||
<p>Lo streaming di questa partita è stato chiuso.</p>
|
<p><%= j t("live.show.stream_ended_body") %></p>
|
||||||
<p class="stream-ended-hint">Ricarica la pagina per l’ultimo stato registrato.</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">Tutte le dirette</a>
|
<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);
|
video.parentNode.insertBefore(panel, video.nextSibling);
|
||||||
}
|
}
|
||||||
@@ -277,7 +283,7 @@
|
|||||||
offlineMsg.hidden = true;
|
offlineMsg.hidden = true;
|
||||||
if (!publisherOnline && showingCover && !paused) {
|
if (!publisherOnline && showingCover && !paused) {
|
||||||
awaitingMsg.hidden = false;
|
awaitingMsg.hidden = false;
|
||||||
awaitingMsg.textContent = "In riconnessione - in attesa del video dall'evento";
|
awaitingMsg.textContent = "<%= j t("live.show.js_reconnecting_awaiting") %>";
|
||||||
} else {
|
} else {
|
||||||
awaitingMsg.hidden = !awaitingSignal;
|
awaitingMsg.hidden = !awaitingSignal;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
<%= label_tag "match[opponent_name]", "Avversario" %>
|
<%= label_tag "match[opponent_name]", t("matches.opponent_label") %>
|
||||||
<%= text_field_tag "match[opponent_name]", match.opponent_name, required: true, placeholder: "es. Volley Milano" %>
|
<%= text_field_tag "match[opponent_name]", match.opponent_name, required: true, placeholder: t("matches.opponent_placeholder") %>
|
||||||
|
|
||||||
<%= label_tag "match[location]", "Luogo (opzionale)" %>
|
<%= label_tag "match[location]", t("matches.location_label") %>
|
||||||
<%= text_field_tag "match[location]", match.location, placeholder: "Palestra, città" %>
|
<%= 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") %>
|
<% 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 %>
|
<%= datetime_local_field_tag "match[scheduled_at]", scheduled_value, required: true %>
|
||||||
|
|
||||||
<p style="color:#888;font-size:0.88rem;margin:12px 0 0">
|
<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>
|
</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" %>
|
<% content_for :robots, "noindex, nofollow" %>
|
||||||
|
|
||||||
<div class="wrap" style="padding-top:20px;max-width:560px">
|
<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>
|
<p style="color:#888"><%= @team.name %> vs <strong><%= @match.opponent_name %></strong></p>
|
||||||
<div class="card" style="margin-top:16px">
|
<div class="card" style="margin-top:16px">
|
||||||
<%= render "form",
|
<%= render "form",
|
||||||
form_url: public_team_match_path(@team, @match),
|
form_url: public_team_match_path(@team, @match),
|
||||||
form_method: :patch,
|
form_method: :patch,
|
||||||
submit_label: "Salva modifiche" %>
|
submit_label: t("matches.edit.submit") %>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
|
|||||||
@@ -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" %>
|
<% content_for :robots, "noindex, nofollow" %>
|
||||||
|
|
||||||
<div class="wrap" style="padding-top:20px;max-width:720px">
|
<div class="wrap" style="padding-top:20px;max-width:720px">
|
||||||
@@ -7,17 +7,16 @@
|
|||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<header style="margin:16px 0 20px">
|
<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">
|
<p style="color:#888;margin-top:8px">
|
||||||
Programma qui da PC data, avversario e luogo. Quando sei in palestra apri l’app Match Live TV,
|
<%= t("matches.index.lead") %>
|
||||||
seleziona questa squadra e avvia la diretta sulla partita.
|
|
||||||
</p>
|
</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div style="display:flex;gap:12px;flex-wrap:wrap;margin-bottom:20px">
|
<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 t("matches.index.schedule_match"), 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 t("matches.index.team_details"), 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.live_and_schedule"), public_live_index_path(club_id: @club.id), class: "btn btn-secondary" %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card">
|
<div class="card">
|
||||||
@@ -25,10 +24,10 @@
|
|||||||
<table class="data">
|
<table class="data">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Avversario</th>
|
<th><%= t("matches.index.col_opponent") %></th>
|
||||||
<th>Data e ora</th>
|
<th><%= t("matches.index.col_datetime") %></th>
|
||||||
<th>Luogo</th>
|
<th><%= t("matches.index.col_location") %></th>
|
||||||
<th>Stato</th>
|
<th><%= t("matches.index.col_status") %></th>
|
||||||
<th></th>
|
<th></th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@@ -40,25 +39,25 @@
|
|||||||
<% if match.scheduled_at %>
|
<% if match.scheduled_at %>
|
||||||
<%= l_local(match.scheduled_at) %>
|
<%= l_local(match.scheduled_at) %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span style="color:#888">Senza orario</span>
|
<span style="color:#888"><%= t("matches.index.no_time") %></span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</td>
|
</td>
|
||||||
<td><%= match.location.presence || "—" %></td>
|
<td><%= match.location.presence || "—" %></td>
|
||||||
<td>
|
<td>
|
||||||
<% label = match.public_status_label %>
|
<% 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>
|
<span style="color:#e53935;font-weight:600"><%= label %></span>
|
||||||
<% else %>
|
<% else %>
|
||||||
<%= label %>
|
<%= label %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</td>
|
</td>
|
||||||
<td style="white-space:nowrap">
|
<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? %>
|
<% 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,
|
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",
|
class: "btn btn-secondary",
|
||||||
style: "padding:4px 10px;font-size:0.8rem;display:inline" %>
|
style: "padding:4px 10px;font-size:0.8rem;display:inline" %>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -69,8 +68,7 @@
|
|||||||
</table>
|
</table>
|
||||||
<% else %>
|
<% else %>
|
||||||
<p style="color:#888;margin:0">
|
<p style="color:#888;margin:0">
|
||||||
Nessuna partita in calendario.
|
<%= raw t("matches.index.empty_html", link: link_to(t("matches.index.schedule_first"), new_public_team_match_path(@team))) %>
|
||||||
<%= link_to "Programma la prima", new_public_team_match_path(@team) %>.
|
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</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" %>
|
<% content_for :robots, "noindex, nofollow" %>
|
||||||
|
|
||||||
<div class="wrap" style="padding-top:20px;max-width:560px">
|
<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>
|
<p style="color:#888"><%= @club.name %> · <strong><%= @team.name %></strong></p>
|
||||||
<div class="card" style="margin-top:16px">
|
<div class="card" style="margin-top:16px">
|
||||||
<%= render "form",
|
<%= render "form",
|
||||||
form_url: public_team_matches_path(@team),
|
form_url: public_team_matches_path(@team),
|
||||||
form_method: :post,
|
form_method: :post,
|
||||||
submit_label: "Salva in programma" %>
|
submit_label: t("matches.new.submit") %>
|
||||||
</div>
|
</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>
|
</div>
|
||||||
|
|||||||
@@ -1,150 +1,146 @@
|
|||||||
<% content_for :title, "Cookie policy — Match Live TV" %>
|
<% content_for :title, t("legal.cookies.title") %>
|
||||||
<% content_for :meta_description, "Cookie policy di Match Live TV: cookie tecnici, sessione, Google Analytics e gestione del consenso." %>
|
<% content_for :meta_description, t("legal.cookies.meta_description") %>
|
||||||
<% content_for :canonical_url, seo_absolute_url(public_cookies_path) %>
|
<% content_for :canonical_url, seo_absolute_url(public_cookies_path) %>
|
||||||
|
|
||||||
<div class="wrap legal-doc">
|
<div class="wrap legal-doc">
|
||||||
<h1>Cookie policy</h1>
|
<h1><%= t("legal.cookies.h1") %></h1>
|
||||||
<p class="legal-meta">
|
<p class="legal-meta">
|
||||||
Ultimo aggiornamento: <%= cookie_policy_last_updated %>
|
<%= raw t(
|
||||||
· Vedi anche <%= link_to "informativa privacy", public_privacy_path %>
|
"legal.cookies.meta_html",
|
||||||
|
date: cookie_policy_last_updated,
|
||||||
|
privacy_link: link_to(t("legal.cookies.privacy_link_text"), public_privacy_path)
|
||||||
|
) %>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2>1. Cosa sono i cookie</h2>
|
<h2><%= t("legal.cookies.s1_title") %></h2>
|
||||||
<p>
|
<p>
|
||||||
I cookie sono piccoli file di testo che i siti salvano sul dispositivo del visitatore.
|
<%= t("legal.cookies.s1_body") %>
|
||||||
Servono a far funzionare il sito, ricordare preferenze o, previo consenso, analizzare l’uso del servizio.
|
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2>2. Titolare</h2>
|
<h2><%= t("legal.cookies.s2_title") %></h2>
|
||||||
<p>
|
<p>
|
||||||
<strong><%= MatchLiveTv.privacy_controller_name %></strong> —
|
<strong><%= MatchLiveTv.privacy_controller_name %></strong> —
|
||||||
<%= MatchLiveTv.privacy_controller_address %> —
|
<%= MatchLiveTv.privacy_controller_address %> —
|
||||||
<a href="mailto:<%= MatchLiveTv.privacy_controller_email %>"><%= MatchLiveTv.privacy_controller_email %></a>
|
<a href="mailto:<%= MatchLiveTv.privacy_controller_email %>"><%= MatchLiveTv.privacy_controller_email %></a>
|
||||||
<% if MatchLiveTv.privacy_controller_vat.present? %>
|
<% if MatchLiveTv.privacy_controller_vat.present? %>
|
||||||
· P. IVA <%= MatchLiveTv.privacy_controller_vat %>
|
· <%= t("legal.cookies.s2_vat_label") %> <%= MatchLiveTv.privacy_controller_vat %>
|
||||||
<% end %>
|
<% end %>
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2>3. Come gestiamo il consenso</h2>
|
<h2><%= t("legal.cookies.s3_title") %></h2>
|
||||||
<p>
|
<p>
|
||||||
Al primo accesso mostriamo un banner che ti permette di accettare tutti i cookie,
|
<%= raw t("legal.cookies.s3_p1_html") %>
|
||||||
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.
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
La preferenza viene memorizzata in un cookie tecnico di prima parte
|
<%= raw t("legal.cookies.s3_p2_html") %>
|
||||||
(<code>mltv_cookie_consent</code>) e in <code>localStorage</code> del browser.
|
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<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>
|
<h3><%= t("legal.cookies.s4_1_title") %></h3>
|
||||||
<p>Non richiedono consenso perché indispensabili al funzionamento del sito e dell’area riservata.</p>
|
<p><%= t("legal.cookies.s4_1_intro") %></p>
|
||||||
<table class="legal-table">
|
<table class="legal-table">
|
||||||
<thead>
|
<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>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>_app_session</code> (cookie di sessione)</td>
|
<td><%= raw t("legal.cookies.s4_1_row1_name_html") %></td>
|
||||||
<td>Mantiene l’accesso dell’utente registrato, protezione CSRF, preferenze di navigazione sicura</td>
|
<td><%= t("legal.cookies.s4_1_row1_purpose") %></td>
|
||||||
<td>Sessione / fino a chiusura browser</td>
|
<td><%= t("legal.cookies.s4_1_row1_duration") %></td>
|
||||||
<td>Match Live TV (prima parte)</td>
|
<td><%= t("legal.cookies.s4_1_row1_provider") %></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>mltv_cookie_consent</code></td>
|
<td><%= raw t("legal.cookies.s4_1_row2_name_html") %></td>
|
||||||
<td>Memorizza le scelte sul banner cookie (necessari / analytics)</td>
|
<td><%= t("legal.cookies.s4_1_row2_purpose") %></td>
|
||||||
<td>12 mesi</td>
|
<td><%= t("legal.cookies.s4_1_row2_duration") %></td>
|
||||||
<td>Match Live TV (prima parte)</td>
|
<td><%= t("legal.cookies.s4_1_row2_provider") %></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<h3>4.2 Analytics (solo con il tuo consenso)</h3>
|
<h3><%= t("legal.cookies.s4_2_title") %></h3>
|
||||||
<p>
|
<p>
|
||||||
Se accetti «Tutti i cookie» o abiliti le statistiche nel banner, carichiamo
|
<%= raw t("legal.cookies.s4_2_p1_html") %>
|
||||||
<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.
|
|
||||||
</p>
|
</p>
|
||||||
<% if MatchLiveTv.google_analytics_configured? %>
|
<% 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 %>
|
<% else %>
|
||||||
<p class="muted">Google Analytics è configurato solo quando il titolare imposta l’ID misurazione sul server.</p>
|
<p class="muted"><%= t("legal.cookies.s4_2_inactive") %></p>
|
||||||
<% end %>
|
<% end %>
|
||||||
<table class="legal-table">
|
<table class="legal-table">
|
||||||
<thead>
|
<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>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>_ga</code></td>
|
<td><%= raw t("legal.cookies.s4_2_row1_name_html") %></td>
|
||||||
<td>Distingue gli utenti (statistiche)</td>
|
<td><%= t("legal.cookies.s4_2_row1_purpose") %></td>
|
||||||
<td>2 anni</td>
|
<td><%= t("legal.cookies.s4_2_row1_duration") %></td>
|
||||||
<td>Google</td>
|
<td><%= t("legal.cookies.s4_2_row1_provider") %></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>_ga_*</code></td>
|
<td><%= raw t("legal.cookies.s4_2_row2_name_html") %></td>
|
||||||
<td>Mantiene lo stato della sessione Analytics</td>
|
<td><%= t("legal.cookies.s4_2_row2_purpose") %></td>
|
||||||
<td>2 anni</td>
|
<td><%= t("legal.cookies.s4_2_row2_duration") %></td>
|
||||||
<td>Google</td>
|
<td><%= t("legal.cookies.s4_2_row2_provider") %></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><code>_gid</code></td>
|
<td><%= raw t("legal.cookies.s4_2_row3_name_html") %></td>
|
||||||
<td>Distingue gli utenti (statistiche)</td>
|
<td><%= t("legal.cookies.s4_2_row3_purpose") %></td>
|
||||||
<td>24 ore</td>
|
<td><%= t("legal.cookies.s4_2_row3_duration") %></td>
|
||||||
<td>Google</td>
|
<td><%= t("legal.cookies.s4_2_row3_provider") %></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
<p>
|
<p>
|
||||||
Puoi revocare il consenso dal banner o dalle impostazioni del browser.
|
<%= raw t(
|
||||||
Informazioni Google:
|
"legal.cookies.s4_2_p2_html",
|
||||||
<a href="https://policies.google.com/privacy" target="_blank" rel="noopener noreferrer">Privacy Policy Google</a>,
|
google_privacy_link: link_to(t("legal.cookies.s4_2_google_privacy_link_text"), "https://policies.google.com/privacy", target: "_blank", rel: "noopener noreferrer"),
|
||||||
<a href="https://tools.google.com/dlpage/gaoptout" target="_blank" rel="noopener noreferrer">componente opt-out Analytics</a>.
|
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>
|
</p>
|
||||||
|
|
||||||
<h3>4.3 Cookie di terze parti durante il pagamento</h3>
|
<h3><%= t("legal.cookies.s4_3_title") %></h3>
|
||||||
<p>
|
<p>
|
||||||
Se acquisti un piano Premium, vieni reindirizzato a <strong>Stripe</strong> (checkout sicuro).
|
<%= raw t(
|
||||||
Stripe può impostare cookie propri sul dominio <code>stripe.com</code> per prevenire frodi e completare il pagamento.
|
"legal.cookies.s4_3_body_html",
|
||||||
Non controlliamo direttamente tali cookie: consulta la
|
stripe_link: link_to(t("legal.cookies.s4_3_stripe_link_text"), "https://stripe.com/privacy", target: "_blank", rel: "noopener noreferrer")
|
||||||
<a href="https://stripe.com/privacy" target="_blank" rel="noopener noreferrer">privacy policy di Stripe</a>.
|
) %>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h3>4.4 YouTube e servizi collegati</h3>
|
<h3><%= t("legal.cookies.s4_4_title") %></h3>
|
||||||
<p>
|
<p>
|
||||||
L’integrazione YouTube per le dirette avviene tramite API lato server; il sito pubblico non incorpora
|
<%= t("legal.cookies.s4_4_body") %>
|
||||||
player YouTube con cookie di profilazione. I visitatori che aprono un link YouTube esterno
|
|
||||||
sono soggetti alle policy di Google/YouTube su quel dominio.
|
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2>5. Come disabilitare i cookie dal browser</h2>
|
<h2><%= t("legal.cookies.s5_title") %></h2>
|
||||||
<p>
|
<p>
|
||||||
Puoi bloccare o cancellare i cookie dalle impostazioni del browser (Chrome, Firefox, Safari, Edge).
|
<%= t("legal.cookies.s5_body") %>
|
||||||
Disabilitando i cookie necessari alcune funzioni (es. login) potrebbero non funzionare.
|
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2>6. Diritti e contatti</h2>
|
<h2><%= t("legal.cookies.s6_title") %></h2>
|
||||||
<p>
|
<p>
|
||||||
Per esercitare i diritti previsti dal GDPR (accesso, cancellazione, opposizione, revoca consenso)
|
<%= raw t(
|
||||||
scrivi a <a href="mailto:<%= MatchLiveTv.privacy_controller_email %>"><%= MatchLiveTv.privacy_controller_email %></a>.
|
"legal.cookies.s6_body_html",
|
||||||
Dettagli nel <%= link_to "documento privacy", public_privacy_path %>.
|
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>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<p style="margin-top:24px">
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,83 +1,79 @@
|
|||||||
<% content_for :title, "FAQ — Diretta live partite giovanili | Match Live TV" %>
|
<% content_for :title, t("pages.faq.meta_title") %>
|
||||||
<% 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 :meta_description, t("pages.faq.meta_description") %>
|
||||||
<% content_for :canonical_url, seo_absolute_url(public_faq_path) %>
|
<% content_for :canonical_url, seo_absolute_url(public_faq_path) %>
|
||||||
|
|
||||||
<div class="wrap seo-page">
|
<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">
|
<p class="seo-lead">
|
||||||
Risposte per genitori, allenatori e dirigenti che vogliono mandare in diretta live le partite
|
<%= t("pages.faq.lead") %>
|
||||||
di pallavolo, calcio e altri sport dilettantistici.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="faq-list">
|
<div class="faq-list">
|
||||||
<details class="faq-item" open>
|
<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>
|
<p>
|
||||||
La società condivide un link (via WhatsApp, email o gruppo squadra). Apri il link dal telefono o dal computer:
|
<%= raw t("pages.faq.q1_answer_html") %>
|
||||||
vedi la <strong>diretta live</strong> nel browser, senza registrarti. Funziona per nonni, parenti e amici lontani.
|
|
||||||
</p>
|
</p>
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details class="faq-item">
|
<details class="faq-item">
|
||||||
<summary>Chi può avviare la diretta dalla palestra?</summary>
|
<summary><%= t("pages.faq.q2_question") %></summary>
|
||||||
<p>
|
<p>
|
||||||
Coach, dirigenti o volontari invitati dalla società. Usano l’app Match Live TV sul telefono per filmare e,
|
<%= t("pages.faq.q2_answer") %>
|
||||||
se serve, un secondo telefono per aggiornare il punteggio. La diretta parte solo quando premo avvio in palestra.
|
|
||||||
</p>
|
</p>
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details class="faq-item">
|
<details class="faq-item">
|
||||||
<summary>È adatto alla pallavolo giovanile (Under 14, Under 16)?</summary>
|
<summary><%= t("pages.faq.q3_question") %></summary>
|
||||||
<p>
|
<p>
|
||||||
Sì. Il punteggio a set è pensato per la pallavolo; puoi personalizzare regole per tornei particolari.
|
<%= raw t(
|
||||||
Scopri la pagina dedicata: <%= link_to "Match Live TV per pallavolo giovanile", public_pallavolo_path %>.
|
"pages.faq.q3_answer_html",
|
||||||
|
volleyball_link: link_to(t("pages.faq.q3_volleyball_link"), public_pallavolo_path)
|
||||||
|
) %>
|
||||||
</p>
|
</p>
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details class="faq-item">
|
<details class="faq-item">
|
||||||
<summary>Cosa succede se perdo la diretta?</summary>
|
<summary><%= t("pages.faq.q4_question") %></summary>
|
||||||
<p>
|
<p>
|
||||||
Con i piani Premium Light o Full la partita viene salvata in archivio (30 o 90 giorni) e puoi rivederla dal sito.
|
<%= t("pages.faq.q4_answer") %>
|
||||||
Il piano Free non include replay su server, ma la diretta resta gratuita per chi trasmette e per chi guarda.
|
|
||||||
</p>
|
</p>
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details class="faq-item">
|
<details class="faq-item">
|
||||||
<summary>Quanto costa per la società?</summary>
|
<summary><%= t("pages.faq.q5_question") %></summary>
|
||||||
<p>
|
<p>
|
||||||
Puoi iniziare con il piano <strong>Free</strong> (limiti su staff e una diretta alla volta).
|
<%= raw t(
|
||||||
Premium Light e Full aggiungono più partite in parallelo, archivio e YouTube.
|
"pages.faq.q5_answer_html",
|
||||||
<%= link_to "Confronta i prezzi", public_prezzi_path %>.
|
pricing_link: link_to(t("pages.faq.q5_pricing_link"), public_prezzi_path)
|
||||||
|
) %>
|
||||||
</p>
|
</p>
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details class="faq-item">
|
<details class="faq-item">
|
||||||
<summary>Posso programmare una partita prima del giorno gara?</summary>
|
<summary><%= t("pages.faq.q6_question") %></summary>
|
||||||
<p>
|
<p>
|
||||||
Sì, dall’app si imposta data e ora: la partita compare in «Prossime partite» sul sito.
|
<%= t("pages.faq.q6_answer") %>
|
||||||
Lo streaming parte solo quando lo staff avvia la trasmissione in palestra.
|
|
||||||
</p>
|
</p>
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details class="faq-item">
|
<details class="faq-item">
|
||||||
<summary>La diretta va anche su YouTube?</summary>
|
<summary><%= t("pages.faq.q7_question") %></summary>
|
||||||
<p>
|
<p>
|
||||||
Con Premium Light puoi mandarla sul canale Match Live TV; con Premium Full anche sul canale YouTube della società.
|
<%= t("pages.faq.q7_answer") %>
|
||||||
Chi preferisce resta sul link Match Live TV, comodo per le famiglie.
|
|
||||||
</p>
|
</p>
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
<details class="faq-item">
|
<details class="faq-item">
|
||||||
<summary>Devo aprire porte sul router o avere attrezzatura da TV?</summary>
|
<summary><%= t("pages.faq.q8_question") %></summary>
|
||||||
<p>
|
<p>
|
||||||
No per chi guarda. Per la società che trasmette basta lo smartphone e una buona connessione in palestra;
|
<%= t("pages.faq.q8_answer") %>
|
||||||
lo staff tecnico della piattaforma gestisce l’infrastruttura. Niente camere broadcast o mixer.
|
|
||||||
</p>
|
</p>
|
||||||
</details>
|
</details>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p style="text-align:center;margin:40px 0">
|
<p style="text-align:center;margin:40px 0">
|
||||||
<%= link_to "Registra la squadra", public_signup_path, class: "btn btn-primary" %>
|
<%= link_to t("pages.faq.cta_signup"), 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_live"), public_live_index_path, class: "btn btn-secondary" %>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,64 +1,64 @@
|
|||||||
<% content_for :title, "Funzionalità streaming partite giovanili — Match Live TV" %>
|
<% content_for :title, t("pages.features.meta_title") %>
|
||||||
<% 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 :meta_description, t("pages.features.meta_description") %>
|
||||||
<% content_for :canonical_url, seo_absolute_url(public_features_path) %>
|
<% content_for :canonical_url, seo_absolute_url(public_features_path) %>
|
||||||
|
|
||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
<h1 style="margin-top:24px">Funzionalità</h1>
|
<h1 style="margin-top:24px"><%= t("pages.features.title") %></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>
|
<p style="color:#aaa;max-width:640px"><%= t("pages.features.lead") %></p>
|
||||||
|
|
||||||
<section class="section">
|
<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="features-grid">
|
||||||
<div class="feature-card">
|
<div class="feature-card">
|
||||||
<h3>Inviti con la propria email</h3>
|
<h3><%= t("pages.features.staff_invite_title") %></h3>
|
||||||
<p>Il presidente o l’owner invita chi filma e chi aggiorna il punteggio: ognuno accede con la sua email, senza condividere password.</p>
|
<p><%= t("pages.features.staff_invite_body") %></p>
|
||||||
</div>
|
</div>
|
||||||
<div class="feature-card">
|
<div class="feature-card">
|
||||||
<h3>Gestione accessi</h3>
|
<h3><%= t("pages.features.staff_access_title") %></h3>
|
||||||
<p>Puoi togliere l’accesso a un volontario o annullare un invito ancora in sospeso dalla dashboard della squadra.</p>
|
<p><%= t("pages.features.staff_access_body") %></p>
|
||||||
</div>
|
</div>
|
||||||
<div class="feature-card">
|
<div class="feature-card">
|
||||||
<h3>Piani su misura</h3>
|
<h3><%= t("pages.features.staff_plans_title") %></h3>
|
||||||
<p>Con Free, Premium Light o Premium Full aumenti quante persone possono aiutare in diretta e quante partite possono andare in parallelo.</p>
|
<p><%= t("pages.features.staff_plans_body") %></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section class="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="features-grid">
|
||||||
<div class="feature-card">
|
<div class="feature-card">
|
||||||
<h3>Filma dal telefono</h3>
|
<h3><%= t("pages.features.live_phone_title") %></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>
|
<p><%= t("pages.features.live_phone_body") %></p>
|
||||||
</div>
|
</div>
|
||||||
<div class="feature-card">
|
<div class="feature-card">
|
||||||
<h3>Punteggio da un secondo telefono</h3>
|
<h3><%= t("pages.features.live_score_title") %></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>
|
<p><%= t("pages.features.live_score_body") %></p>
|
||||||
</div>
|
</div>
|
||||||
<div class="feature-card">
|
<div class="feature-card">
|
||||||
<h3>Link per famiglie e tifosi</h3>
|
<h3><%= t("pages.features.live_link_title") %></h3>
|
||||||
<p>Condividi un link: genitori, nonni e amici guardano dal browser del telefono o del computer, senza installare app.</p>
|
<p><%= t("pages.features.live_link_body") %></p>
|
||||||
</div>
|
</div>
|
||||||
<div class="feature-card">
|
<div class="feature-card">
|
||||||
<h3>Archivio partite</h3>
|
<h3><%= t("pages.features.live_archive_title") %></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>
|
<p><%= t("pages.features.live_archive_body") %></p>
|
||||||
</div>
|
</div>
|
||||||
<div class="feature-card">
|
<div class="feature-card">
|
||||||
<h3>Anche su YouTube (Match Live TV)</h3>
|
<h3><%= t("pages.features.live_youtube_mltv_title") %></h3>
|
||||||
<p>Con Premium Light la diretta è visibile anche sul canale YouTube Match Live TV, oltre al link sul sito.</p>
|
<p><%= t("pages.features.live_youtube_mltv_body") %></p>
|
||||||
</div>
|
</div>
|
||||||
<div class="feature-card">
|
<div class="feature-card">
|
||||||
<h3>Canale YouTube della società</h3>
|
<h3><%= t("pages.features.live_youtube_club_title") %></h3>
|
||||||
<p>Con Premium Full puoi mandare la stessa diretta anche sul canale YouTube ufficiale del club.</p>
|
<p><%= t("pages.features.live_youtube_club_body") %></p>
|
||||||
</div>
|
</div>
|
||||||
<div class="feature-card">
|
<div class="feature-card">
|
||||||
<h3>Sul sito della società</h3>
|
<h3><%= t("pages.features.live_website_title") %></h3>
|
||||||
<p>Integrazione per incorporare il player sul sito del club — in arrivo con Premium Full.</p>
|
<p><%= t("pages.features.live_website_body") %></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<p style="text-align:center;margin:40px 0">
|
<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>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,67 +1,60 @@
|
|||||||
<% content_for :title, "Diretta live pallavolo giovanile — Under 14, Under 16 | Match Live TV" %>
|
<% content_for :title, t("pages.volleyball.meta_title") %>
|
||||||
<% 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 :meta_description, t("pages.volleyball.meta_description") %>
|
||||||
<% content_for :canonical_url, seo_absolute_url(public_pallavolo_path) %>
|
<% content_for :canonical_url, seo_absolute_url(public_pallavolo_path) %>
|
||||||
|
|
||||||
<div class="wrap seo-page">
|
<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">
|
<p class="seo-lead">
|
||||||
La soluzione per società, coach e genitori che vogliono una <strong>diretta live</strong> affidabile
|
<%= raw t("pages.volleyball.lead_html") %>
|
||||||
delle partite <strong>Under 14, Under 16, Under 18</strong> e settori giovanili — senza diventare telecronisti.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<section class="seo-prose">
|
<section class="seo-prose">
|
||||||
<h2>Perché la pallavolo giovanile ha bisogno di uno streaming semplice</h2>
|
<h2><%= t("pages.volleyball.section1_title") %></h2>
|
||||||
<p>
|
<p>
|
||||||
In palestra i genitori non sono sempre tutti in tribuna: turni di lavoro, fratelli piccoli, trasferte.
|
<%= raw t("pages.volleyball.section1_body_html") %>
|
||||||
Un nonno in un’altra città o un parente all’estero 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.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2>Cosa fa il coach in due minuti</h2>
|
<h2><%= t("pages.volleyball.section2_title") %></h2>
|
||||||
<ul>
|
<ul>
|
||||||
<li>Programma la partita con data, avversario e campo dall’app</li>
|
<li><%= t("pages.volleyball.section2_item1") %></li>
|
||||||
<li>Il giorno gara appoggia il telefono e avvia la diretta</li>
|
<li><%= t("pages.volleyball.section2_item2") %></li>
|
||||||
<li>Un volontario può aggiornare set e punti da un secondo cellulare</li>
|
<li><%= t("pages.volleyball.section2_item3") %></li>
|
||||||
<li>Condivide il link con il gruppo genitori: tutti guardano dal browser</li>
|
<li><%= t("pages.volleyball.section2_item4") %></li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
<h2>Cosa vede la famiglia a casa</h2>
|
<h2><%= t("pages.volleyball.section3_title") %></h2>
|
||||||
<p>
|
<p>
|
||||||
Nessuna app da installare. Apri il link e segui la partita in diretta live: nome squadre, punteggio,
|
<%= t("pages.volleyball.section3_body") %>
|
||||||
stato del set. Se hai perso l’inizio, con Premium la gara resta in archivio per rivederla quando vuoi.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2>Pallavolo: punteggio e regole</h2>
|
<h2><%= t("pages.volleyball.section4_title") %></h2>
|
||||||
<p>
|
<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):
|
<%= t("pages.volleyball.section4_body") %>
|
||||||
in campo resta concentrato sul gioco, non sul telefono.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<h2>Altri sport giovanili</h2>
|
<h2><%= t("pages.volleyball.section5_title") %></h2>
|
||||||
<p>
|
<p>
|
||||||
La stessa piattaforma funziona anche per calcio, basket e sport di squadra dilettantistici.
|
<%= t("pages.volleyball.section5_body") %>
|
||||||
La pallavolo resta lo sport con cui molte società iniziano perché il punteggio a set è già integrato.
|
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<div class="features-grid" style="margin-top:32px">
|
<div class="features-grid" style="margin-top:32px">
|
||||||
<div class="feature-card">
|
<div class="feature-card">
|
||||||
<h3>Genitori e nonni</h3>
|
<h3><%= t("pages.volleyball.card1_title") %></h3>
|
||||||
<p>Link su WhatsApp, visione da telefono o TV con browser. Zero registrazione per chi guarda.</p>
|
<p><%= t("pages.volleyball.card1_body") %></p>
|
||||||
</div>
|
</div>
|
||||||
<div class="feature-card">
|
<div class="feature-card">
|
||||||
<h3>Società e staff</h3>
|
<h3><%= t("pages.volleyball.card2_title") %></h3>
|
||||||
<p>Inviti con email, più operatori con Premium, revoca accessi dalla dashboard web.</p>
|
<p><%= t("pages.volleyball.card2_body") %></p>
|
||||||
</div>
|
</div>
|
||||||
<div class="feature-card">
|
<div class="feature-card">
|
||||||
<h3>Archivio gare</h3>
|
<h3><%= t("pages.volleyball.card3_title") %></h3>
|
||||||
<p>Premium Light/Full: replay per chi ha perso la diretta o vuole rivedere un punto chiave.</p>
|
<p><%= t("pages.volleyball.card3_body") %></p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p style="text-align:center;margin:40px 0">
|
<p style="text-align:center;margin:40px 0">
|
||||||
<%= link_to "Inizia gratis", public_signup_path, class: "btn btn-primary" %>
|
<%= link_to t("pages.volleyball.cta_start"), public_signup_path, class: "btn btn-primary" %>
|
||||||
<%= link_to "Domande frequenti", public_faq_path, class: "btn btn-secondary" %>
|
<%= link_to t("pages.volleyball.cta_faq"), public_faq_path, class: "btn btn-secondary" %>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,18 +1,22 @@
|
|||||||
<% content_for :title, "Prezzi streaming partite giovanili — Match Live TV" %>
|
<% content_for :title, t("pages.pricing.meta_title") %>
|
||||||
<% 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 :meta_description, t("pages.pricing.meta_description") %>
|
||||||
<% content_for :canonical_url, seo_absolute_url(public_prezzi_path) %>
|
<% content_for :canonical_url, seo_absolute_url(public_prezzi_path) %>
|
||||||
|
|
||||||
<div class="wrap" style="padding-top:24px">
|
<div class="wrap" style="padding-top:24px">
|
||||||
<% if @club %>
|
<% if @club %>
|
||||||
<h1>Piani per la tua società</h1>
|
<h1><%= t("pages.pricing.club_title") %></h1>
|
||||||
<% if @entitlements %>
|
<% if @entitlements %>
|
||||||
<%= render "shared/club_subscription_status", club: @club, entitlements: @entitlements, subscription: @subscription %>
|
<%= render "shared/club_subscription_status", club: @club, entitlements: @entitlements, subscription: @subscription %>
|
||||||
<% else %>
|
<% 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 %>
|
<% end %>
|
||||||
<% else %>
|
<% else %>
|
||||||
<h1>Piani per la tua squadra</h1>
|
<h1><%= t("pages.pricing.team_title") %></h1>
|
||||||
<p style="color:#aaa">Abbonamento annuale per società: i responsabili trasmissione avviano la diretta dall’app; il punteggio si gestisce con un link condivisibile.</p>
|
<p style="color:#aaa"><%= t("pages.pricing.team_lead") %></p>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= render "shared/stripe_secure_payment" %>
|
<%= render "shared/stripe_secure_payment" %>
|
||||||
|
|
||||||
@@ -23,19 +27,19 @@
|
|||||||
<tr><th></th><th>Free</th><th>Premium Light</th><th>Premium Full</th></tr>
|
<tr><th></th><th>Free</th><th>Premium Light</th><th>Premium Full</th></tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr><td>Responsabili trasmissione / anno</td><td>1</td><td>5</td><td>Illimitato</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>Partite in contemporanea</td><td>1</td><td>3</td><td>10</td></tr>
|
<tr><td><%= t("pages.pricing.table_matches") %></td><td>1</td><td>3</td><td>10</td></tr>
|
||||||
<tr><td>Live su Match Live TV</td><td>Sì</td><td>Sì</td><td>Sì</td></tr>
|
<tr><td><%= 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>YouTube</td><td>No</td><td>Match Live TV</td><td>Canale società</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>Replay server</td><td>No</td><td>30 gg</td><td>90 gg</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>Download telefono</td><td>No</td><td>Sì</td><td>Sì</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>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_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>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
<div class="card" style="margin-top:32px;text-align:center">
|
<div class="card" style="margin-top:32px;text-align:center">
|
||||||
<h3 style="margin-top:0">Esigenze diverse?</h3>
|
<h3 style="margin-top:0"><%= t("pages.pricing.different_title") %></h3>
|
||||||
<p style="color:#aaa;margin-bottom:16px">Per società con più squadre, tornei o integrazioni custom, contattaci: troviamo insieme l'offerta migliore.</p>
|
<p style="color:#aaa;margin-bottom:16px"><%= t("pages.pricing.different_body") %></p>
|
||||||
<%= mail_to "info@matchlive.it", "Contattaci", class: "btn btn-primary" %>
|
<%= mail_to "info@matchlive.it", t("pages.pricing.contact_button"), class: "btn btn-primary" %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,192 +1,200 @@
|
|||||||
<% content_for :title, "Informativa privacy — Match Live TV" %>
|
<% content_for :title, t("legal.privacy.title") %>
|
||||||
<% 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 :meta_description, t("legal.privacy.meta_description") %>
|
||||||
<% content_for :canonical_url, seo_absolute_url(public_privacy_path) %>
|
<% content_for :canonical_url, seo_absolute_url(public_privacy_path) %>
|
||||||
|
|
||||||
<div class="wrap legal-doc">
|
<div class="wrap legal-doc">
|
||||||
<h1>Informativa sul trattamento dei dati personali</h1>
|
<h1><%= t("legal.privacy.h1") %></h1>
|
||||||
<p class="legal-meta">Ultimo aggiornamento: <%= legal_last_updated %> · Ai sensi del Regolamento (UE) 2016/679 (GDPR)</p>
|
<p class="legal-meta"><%= raw t("legal.privacy.meta_html", date: legal_last_updated) %></p>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2>1. Titolare del trattamento</h2>
|
<h2><%= t("legal.privacy.s1_title") %></h2>
|
||||||
<p>
|
<p>
|
||||||
Il titolare del trattamento dei dati personali raccolti tramite il sito e i servizi Match Live TV è:
|
<%= t("legal.privacy.s1_intro") %>
|
||||||
</p>
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong><%= MatchLiveTv.privacy_controller_name %></strong></li>
|
<li><strong><%= MatchLiveTv.privacy_controller_name %></strong></li>
|
||||||
<li>Indirizzo: <%= MatchLiveTv.privacy_controller_address %></li>
|
<li><%= t("legal.privacy.s1_label_address") %> <%= 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_email") %> <a href="mailto:<%= MatchLiveTv.privacy_controller_email %>"><%= MatchLiveTv.privacy_controller_email %></a></li>
|
||||||
<% if MatchLiveTv.privacy_controller_vat.present? %>
|
<% 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 %>
|
<% end %>
|
||||||
</ul>
|
</ul>
|
||||||
<p>Per richieste relative a questa informativa o per esercitare i diritti previsti dal GDPR, scrivi all’indirizzo email indicato.</p>
|
<p><%= t("legal.privacy.s1_outro") %></p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2>2. Ambito di applicazione</h2>
|
<h2><%= t("legal.privacy.s2_title") %></h2>
|
||||||
<p>La presente informativa si applica a:</p>
|
<p><%= t("legal.privacy.s2_intro") %></p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>visitatori del sito web Match Live TV (pagine informative, elenco dirette, registrazione squadra);</li>
|
<li><%= t("legal.privacy.s2_item1") %></li>
|
||||||
<li>utenti registrati (referenti di società sportive, coach, staff invitato);</li>
|
<li><%= t("legal.privacy.s2_item2") %></li>
|
||||||
<li>spettatori che accedono alle dirette tramite link (in genere senza creare un account);</li>
|
<li><%= t("legal.privacy.s2_item3") %></li>
|
||||||
<li>utilizzo dell’app mobile collegata al servizio.</li>
|
<li><%= t("legal.privacy.s2_item4") %></li>
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2>3. Tipologie di dati trattati</h2>
|
<h2><%= t("legal.privacy.s3_title") %></h2>
|
||||||
<p>Possiamo trattare, a seconda dell’uso del servizio:</p>
|
<p><%= t("legal.privacy.s3_intro") %></p>
|
||||||
<ul>
|
<ul>
|
||||||
<li><strong>Dati identificativi e di contatto:</strong> nome, indirizzo email, credenziali di accesso (password conservata in forma crittografata).</li>
|
<li><%= raw t("legal.privacy.s3_item1_html") %></li>
|
||||||
<li><strong>Dati della squadra:</strong> nome società, sport, partite programmate, avversario, luogo, orari.</li>
|
<li><%= raw t("legal.privacy.s3_item2_html") %></li>
|
||||||
<li><strong>Dati di streaming e tecnici:</strong> stato sessione, punteggio, telemetria di rete/bitrate, log di connessione al servizio video.</li>
|
<li><%= raw t("legal.privacy.s3_item3_html") %></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><%= raw t("legal.privacy.s3_item4_html") %></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><%= raw t("legal.privacy.s3_item5_html") %></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_item6_html") %></li>
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2>4. Trattamento di immagini e dati di atleti minorenni</h2>
|
<h2><%= t("legal.privacy.s4_title") %></h2>
|
||||||
<p>
|
<p>
|
||||||
Match Live TV è destinato anche a competizioni e categorie giovanili (es. Under 14, Under 16, Under 18).
|
<%= raw t("legal.privacy.s4_p1_html") %>
|
||||||
Le dirette possono riprendere <strong>atleti minorenni</strong> identificabili (immagine, voce, numero di maglia, dati di gioco).
|
|
||||||
</p>
|
</p>
|
||||||
<p><strong>Chi è responsabile del trattamento delle immagini dei minori</strong></p>
|
<p><strong><%= t("legal.privacy.s4_lead1") %></strong></p>
|
||||||
<p>
|
<p>
|
||||||
La <strong>società sportiva</strong> che avvia la trasmissione, in qualità di organizzatrice dell’evento sportivo,
|
<%= raw t("legal.privacy.s4_p2_html") %>
|
||||||
è 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.
|
|
||||||
</p>
|
</p>
|
||||||
<p><strong>Ruolo di Match Live TV</strong></p>
|
<p><strong><%= t("legal.privacy.s4_lead2") %></strong></p>
|
||||||
<p>
|
<p>
|
||||||
Il titolare del servizio fornisce la piattaforma tecnica e, per le trasmissioni effettuate dagli utenti autorizzati,
|
<%= raw t("legal.privacy.s4_p3_html") %>
|
||||||
può agire come <strong>responsabile del trattamento</strong> su istruzioni della società o, in alcuni casi, come titolare
|
|
||||||
per i trattamenti strettamente necessari all’erogazione del servizio (hosting, sicurezza, fatturazione).
|
|
||||||
</p>
|
</p>
|
||||||
<p><strong>Misure richieste alla società sportiva</strong></p>
|
<p><strong><%= t("legal.privacy.s4_lead3") %></strong></p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>informare genitori/tutori sull’uso di video in diretta e in archivio;</li>
|
<li><%= t("legal.privacy.s4_item1") %></li>
|
||||||
<li>raccogliere e documentare i consensi necessari prima della diretta;</li>
|
<li><%= t("legal.privacy.s4_item2") %></li>
|
||||||
<li>limitare la diffusione del link a persone autorizzate (familiari, staff);</li>
|
<li><%= t("legal.privacy.s4_item3") %></li>
|
||||||
<li>non pubblicare su canali aperti se non previsto dal consenso o dal regolamento sportivo;</li>
|
<li><%= t("legal.privacy.s4_item4") %></li>
|
||||||
<li>richiedere rimozione di registrazioni quando ne ha diritto l’interessato o il tutore.</li>
|
<li><%= t("legal.privacy.s4_item5") %></li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>
|
<p>
|
||||||
Le richieste dei genitori/tutori relative a immagini di minori possono essere inoltrate alla società sportiva
|
<%= raw t(
|
||||||
e, per supporto tecnico o cancellazione da archivio, anche a
|
"legal.privacy.s4_p4_html",
|
||||||
<a href="mailto:<%= MatchLiveTv.privacy_controller_email %>"><%= MatchLiveTv.privacy_controller_email %></a>.
|
email_link: link_to(MatchLiveTv.privacy_controller_email, "mailto:#{MatchLiveTv.privacy_controller_email}")
|
||||||
|
) %>
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2>5. Finalità e basi giuridiche</h2>
|
<h2><%= t("legal.privacy.s5_title") %></h2>
|
||||||
<table class="legal-table">
|
<table class="legal-table">
|
||||||
<thead>
|
<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>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Registrazione account, gestione squadra e staff</td>
|
<td><%= t("legal.privacy.s5_row1_purpose") %></td>
|
||||||
<td>Esecuzione del contratto / misure precontrattuali</td>
|
<td><%= t("legal.privacy.s5_row1_basis") %></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Erogazione dirette, punteggio live, archivio (piani Premium)</td>
|
<td><%= t("legal.privacy.s5_row2_purpose") %></td>
|
||||||
<td>Esecuzione del contratto; legittimo interesse alla sicurezza del servizio</td>
|
<td><%= t("legal.privacy.s5_row2_basis") %></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Pagamenti e fatturazione (Stripe)</td>
|
<td><%= t("legal.privacy.s5_row3_purpose") %></td>
|
||||||
<td>Esecuzione del contratto; obblighi di legge</td>
|
<td><%= t("legal.privacy.s5_row3_basis") %></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Assistenza, sicurezza, prevenzione abusi</td>
|
<td><%= t("legal.privacy.s5_row4_purpose") %></td>
|
||||||
<td>Legittimo interesse; obblighi di legge</td>
|
<td><%= t("legal.privacy.s5_row4_basis") %></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Comunicazioni di servizio (es. reset password)</td>
|
<td><%= t("legal.privacy.s5_row5_purpose") %></td>
|
||||||
<td>Esecuzione del contratto</td>
|
<td><%= t("legal.privacy.s5_row5_basis") %></td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>Marketing diretto (solo se previsto e con consenso ove richiesto)</td>
|
<td><%= t("legal.privacy.s5_row6_purpose") %></td>
|
||||||
<td>Consenso o legittimo interesse, secondo i casi</td>
|
<td><%= t("legal.privacy.s5_row6_basis") %></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2>6. Destinatari e responsabili del trattamento</h2>
|
<h2><%= t("legal.privacy.s6_title") %></h2>
|
||||||
<p>I dati possono essere comunicati a fornitori che agiscono come responsabili del trattamento, tra cui:</p>
|
<p><%= t("legal.privacy.s6_intro") %></p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>fornitore di hosting e infrastruttura cloud/server;</li>
|
<li><%= t("legal.privacy.s6_item1") %></li>
|
||||||
<li><strong>Stripe</strong> (pagamenti) — <a href="https://stripe.com/it/privacy" rel="noopener" target="_blank">privacy Stripe</a>;</li>
|
<li>
|
||||||
<li>fornitore streaming video (MediaMTX / infrastruttura RTMP-HLS);</li>
|
<%= raw t(
|
||||||
<li>eventuale integrazione <strong>YouTube</strong> se attivata dalla società (soggetto a policy Google);</li>
|
"legal.privacy.s6_item2_html",
|
||||||
<li>fornitore email transazionale (invio link reset password, inviti).</li>
|
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>
|
</ul>
|
||||||
<p>I dati non sono venduti a terzi per finalità di profilazione commerciale.</p>
|
<p><%= t("legal.privacy.s6_outro") %></p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2>7. Trasferimenti extra SEE</h2>
|
<h2><%= t("legal.privacy.s7_title") %></h2>
|
||||||
<p>
|
<p>
|
||||||
Alcuni fornitori (es. Stripe, Google/YouTube) possono trattare dati anche fuori dallo Spazio Economico Europeo.
|
<%= t("legal.privacy.s7_body") %>
|
||||||
In tal caso si applicano garanzie adeguate previste dal GDPR (clausole contrattuali standard, decisioni di adeguatezza).
|
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2>8. Conservazione</h2>
|
<h2><%= t("legal.privacy.s8_title") %></h2>
|
||||||
<ul>
|
<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><%= raw t("legal.privacy.s8_item1_html") %></li>
|
||||||
<li><strong>Registrazioni partite (replay):</strong> secondo il piano (es. 30 o 90 giorni), poi cancellazione automatica o su richiesta.</li>
|
<li><%= raw t("legal.privacy.s8_item2_html") %></li>
|
||||||
<li><strong>Log tecnici:</strong> periodo limitato (es. 30–90 giorni) salvo obblighi di sicurezza.</li>
|
<li><%= raw t("legal.privacy.s8_item3_html") %></li>
|
||||||
<li><strong>Token reset password:</strong> validità massima <%= MatchLiveTv.password_reset_expiry_hours %> ore.</li>
|
<li><%= raw t("legal.privacy.s8_item4_html", hours: MatchLiveTv.password_reset_expiry_hours) %></li>
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2>9. Diritti degli interessati</h2>
|
<h2><%= t("legal.privacy.s9_title") %></h2>
|
||||||
<p>In qualità di interessato hai diritto di:</p>
|
<p><%= t("legal.privacy.s9_intro") %></p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>accedere ai tuoi dati e ottenerne copia;</li>
|
<li><%= t("legal.privacy.s9_item1") %></li>
|
||||||
<li>rettificarli se inesatti;</li>
|
<li><%= t("legal.privacy.s9_item2") %></li>
|
||||||
<li>chiederne la cancellazione (nei limiti di legge);</li>
|
<li><%= t("legal.privacy.s9_item3") %></li>
|
||||||
<li>limitare o opporti al trattamento, ove applicabile;</li>
|
<li><%= t("legal.privacy.s9_item4") %></li>
|
||||||
<li>revocare il consenso senza pregiudicare la liceità del trattamento basato sul consenso prestato prima della revoca;</li>
|
<li><%= t("legal.privacy.s9_item5") %></li>
|
||||||
<li>proporre reclamo all’Autorità Garante per la protezione dei dati personali (<a href="https://www.garanteprivacy.it" rel="noopener" target="_blank">garanteprivacy.it</a>).</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>
|
</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>
|
<p>
|
||||||
Adottiamo misure tecniche e organizzative adeguate (accesso autenticato, password crittografate,
|
<%= raw t(
|
||||||
comunicazioni HTTPS, segregazione ambienti, limitazione accessi staff). Nessun sistema è invulnerabile:
|
"legal.privacy.s9_outro_html",
|
||||||
se sospetti un accesso non autorizzato, cambia password e contattaci.
|
email_link: link_to(MatchLiveTv.privacy_controller_email, "mailto:#{MatchLiveTv.privacy_controller_email}")
|
||||||
|
) %>
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2>11. Cookie e tecnologie simili</h2>
|
<h2><%= t("legal.privacy.s10_title") %></h2>
|
||||||
<p>
|
<p>
|
||||||
Il sito utilizza cookie tecnici necessari (sessione di login, sicurezza, memorizzazione delle preferenze cookie)
|
<%= t("legal.privacy.s10_body") %>
|
||||||
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 %>.
|
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2>12. Modifiche</h2>
|
<h2><%= t("legal.privacy.s11_title") %></h2>
|
||||||
<p>
|
<p>
|
||||||
Il titolare può aggiornare questa informativa. La data in alto indica l’ultima revisione.
|
<%= raw t("legal.privacy.s11_p1_html") %>
|
||||||
In caso di modifiche rilevanti, sarà fornita comunicazione adeguata (es. avviso sul sito o email agli account registrati).
|
</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>
|
</p>
|
||||||
</section>
|
</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>
|
</div>
|
||||||
|
|||||||
@@ -1,134 +1,128 @@
|
|||||||
<% content_for :title, "Termini di servizio — Match Live TV" %>
|
<% content_for :title, t("legal.terms.title") %>
|
||||||
<% 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 :meta_description, t("legal.terms.meta_description") %>
|
||||||
<% content_for :canonical_url, seo_absolute_url(public_termini_path) %>
|
<% content_for :canonical_url, seo_absolute_url(public_termini_path) %>
|
||||||
|
|
||||||
<div class="wrap legal-doc">
|
<div class="wrap legal-doc">
|
||||||
<h1>Termini e condizioni di servizio</h1>
|
<h1><%= t("legal.terms.h1") %></h1>
|
||||||
<p class="legal-meta">Ultimo aggiornamento: <%= legal_last_updated %></p>
|
<p class="legal-meta"><%= t("legal.terms.meta", date: legal_last_updated) %></p>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2>1. Oggetto</h2>
|
<h2><%= t("legal.terms.s1_title") %></h2>
|
||||||
<p>
|
<p>
|
||||||
I presenti Termini regolano l’uso della piattaforma <strong>Match Live TV</strong> (sito web, applicazione mobile
|
<%= raw t("legal.terms.s1_p1_html") %>
|
||||||
e servizi connessi) per la gestione di dirette sportive dilettantistiche/giovanili, punteggio live,
|
|
||||||
condivisione link agli spettatori e funzioni Premium (archivio, YouTube, ecc.).
|
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Il servizio è erogato da <strong><%= MatchLiveTv.privacy_controller_name %></strong>
|
<%= raw t(
|
||||||
(di seguito «Fornitore»). Registrandoti o utilizzando il servizio accetti integralmente questi Termini
|
"legal.terms.s1_p2_html",
|
||||||
e l’<%= link_to "informativa privacy", public_privacy_path %>.
|
provider_name: MatchLiveTv.privacy_controller_name,
|
||||||
|
privacy_link: link_to(t("legal.terms.s1_privacy_link_text"), public_privacy_path)
|
||||||
|
) %>
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2>2. Chi può usare il servizio</h2>
|
<h2><%= t("legal.terms.s2_title") %></h2>
|
||||||
<ul>
|
<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><%= raw t("legal.terms.s2_item1_html") %></li>
|
||||||
<li>I responsabili trasmissione invitati usano credenziali personali e si impegnano a custodirle.</li>
|
<li><%= t("legal.terms.s2_item2") %></li>
|
||||||
<li>Gli spettatori che aprono il link della diretta di solito non necessitano di account.</li>
|
<li><%= t("legal.terms.s2_item3") %></li>
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2>3. Account, piani e pagamenti</h2>
|
<h2><%= t("legal.terms.s3_title") %></h2>
|
||||||
<p>
|
<p>
|
||||||
Sono disponibili piani Free e Premium (Light / Full) con limiti su staff, dirette concorrenti, archivio e integrazioni.
|
<%= raw t(
|
||||||
I prezzi sono indicati nella pagina <%= link_to "Prezzi", public_prezzi_path %>.
|
"legal.terms.s3_p1_html",
|
||||||
|
pricing_link: link_to(t("legal.terms.s3_pricing_link_text"), public_prezzi_path)
|
||||||
|
) %>
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
I pagamenti dei piani a pagamento sono gestiti da <strong>Stripe</strong>. Accettando il checkout,
|
<%= raw t("legal.terms.s3_p2_html") %>
|
||||||
accetti anche i termini di Stripe per il pagamento. Il Fornitore non memorizza i dati completi della carta.
|
|
||||||
</p>
|
</p>
|
||||||
<p>Rinnovi, disdetta e diritto di recesso seguono quanto comunicato al momento dell’acquisto e la normativa applicabile.</p>
|
<p><%= t("legal.terms.s3_p3") %></p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2>4. Dirette, contenuti e protezione dei minori</h2>
|
<h2><%= t("legal.terms.s4_title") %></h2>
|
||||||
<p><strong>Responsabilità della società sportiva</strong></p>
|
<p><strong><%= t("legal.terms.s4_lead1") %></strong></p>
|
||||||
<p>
|
<p>
|
||||||
La società che avvia una diretta è responsabile dei contenuti trasmessi (immagini, audio, commenti)
|
<%= raw t("legal.terms.s4_p1_html") %>
|
||||||
e della conformità alle leggi, al regolamento federale e ai consensi necessari, in particolare quando
|
|
||||||
sono ripresi <strong>atleti minorenni</strong>.
|
|
||||||
</p>
|
</p>
|
||||||
<p>La società si impegna a:</p>
|
<p><%= t("legal.terms.s4_p2") %></p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>ottenere e conservare i consensi genitoriali/tutori o altra base giuridica prima della ripresa;</li>
|
<li><%= t("legal.terms.s4_item1") %></li>
|
||||||
<li>informare genitori e atleti su dove e per quanto tempo è visibile la diretta/archivio;</li>
|
<li><%= t("legal.terms.s4_item2") %></li>
|
||||||
<li>non trasmettere contenuti illegali, diffamatori, violenti o non pertinenti all’evento sportivo;</li>
|
<li><%= t("legal.terms.s4_item3") %></li>
|
||||||
<li>rispettare il diritto all’immagine e alla voce di tutte le persone inquadrate.</li>
|
<li><%= t("legal.terms.s4_item4") %></li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>
|
<p>
|
||||||
Il Fornitore può sospendere o interrompere trasmissioni e account in caso di segnalazioni fondate
|
<%= t("legal.terms.s4_p3") %>
|
||||||
o violazioni gravi, senza pregiudizio di azioni legali.
|
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2>5. Uso consentito e divieti</h2>
|
<h2><%= t("legal.terms.s5_title") %></h2>
|
||||||
<p>È vietato:</p>
|
<p><%= t("legal.terms.s5_intro") %></p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>usare il servizio per finalità non sportive o commerciali non autorizzate;</li>
|
<li><%= t("legal.terms.s5_item1") %></li>
|
||||||
<li>aggirare limiti tecnici o di piano (staff, dirette concorrenti);</li>
|
<li><%= t("legal.terms.s5_item2") %></li>
|
||||||
<li>condividere credenziali o pubblicare link su canali che espongono minori a rischi non previsti dal consenso;</li>
|
<li><%= t("legal.terms.s5_item3") %></li>
|
||||||
<li>caricare malware, effettuare attacchi o interferire con l’infrastruttura;</li>
|
<li><%= t("legal.terms.s5_item4") %></li>
|
||||||
<li>rivendere il servizio senza accordo scritto con il Fornitore.</li>
|
<li><%= t("legal.terms.s5_item5") %></li>
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2>6. Proprietà intellettuale</h2>
|
<h2><%= t("legal.terms.s6_title") %></h2>
|
||||||
<p>
|
<p>
|
||||||
Marchi, software, design e documentazione del servizio restano del Fornitore o dei suoi licenzianti.
|
<%= t("legal.terms.s6_body") %>
|
||||||
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.
|
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2>7. Disponibilità e limitazioni</h2>
|
<h2><%= t("legal.terms.s7_title") %></h2>
|
||||||
<p>
|
<p>
|
||||||
Il servizio dipende da reti mobili, hardware e fornitori terzi. Non garantiamo assenza assoluta di interruzioni,
|
<%= t("legal.terms.s7_body") %>
|
||||||
ritardi o perdita di dati, pur adottando misure ragionevoli. La qualità video può variare in base alla connessione in palestra.
|
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2>8. Limitazione di responsabilità</h2>
|
<h2><%= t("legal.terms.s8_title") %></h2>
|
||||||
<p>
|
<p>
|
||||||
Nei limiti consentiti dalla legge italiana, il Fornitore non risponde di danni indiretti, perdita di profitto,
|
<%= t("legal.terms.s8_p1") %>
|
||||||
o contenuti trasmessi dagli utenti. La responsabilità complessiva verso un cliente professionale non eccede,
|
|
||||||
ove possibile, l’importo pagato negli ultimi 12 mesi per il piano sottoscritto.
|
|
||||||
</p>
|
</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>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2>9. Recesso, sospensione e cancellazione</h2>
|
<h2><%= t("legal.terms.s9_title") %></h2>
|
||||||
<p>
|
<p>
|
||||||
Puoi richiedere la chiusura dell’account scrivendo a
|
<%= raw t(
|
||||||
<a href="mailto:<%= MatchLiveTv.privacy_controller_email %>"><%= MatchLiveTv.privacy_controller_email %></a>.
|
"legal.terms.s9_body_html",
|
||||||
Il Fornitore può sospendere l’account per inadempienza, mancato pagamento o violazione dei Termini.
|
email_link: link_to(MatchLiveTv.privacy_controller_email, "mailto:#{MatchLiveTv.privacy_controller_email}")
|
||||||
Alla cessazione, i dati sono trattati come indicato in privacy (conservazione limitata o cancellazione).
|
) %>
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2>10. Legge applicabile e foro</h2>
|
<h2><%= t("legal.terms.s10_title") %></h2>
|
||||||
<p>
|
<p>
|
||||||
I Termini sono regolati dalla <strong>legge italiana</strong>. Per controversie con consumatori si applicano
|
<%= raw t("legal.terms.s10_body_html") %>
|
||||||
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.
|
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<section>
|
<section>
|
||||||
<h2>11. Contatti</h2>
|
<h2><%= t("legal.terms.s11_title") %></h2>
|
||||||
<p>
|
<p>
|
||||||
<%= MatchLiveTv.privacy_controller_name %><br>
|
<%= raw t(
|
||||||
<%= MatchLiveTv.privacy_controller_address %><br>
|
"legal.terms.s11_body_html",
|
||||||
Email: <a href="mailto:<%= MatchLiveTv.privacy_controller_email %>"><%= MatchLiveTv.privacy_controller_email %></a>
|
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>
|
</p>
|
||||||
</section>
|
</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>
|
</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" %>
|
<% content_for :robots, "noindex, nofollow" %>
|
||||||
|
|
||||||
<section class="auth-page">
|
<section class="auth-page">
|
||||||
<h1>Scegli una nuova password</h1>
|
<h1><%= t("auth.password_reset.title") %></h1>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<%= form_with url: public_password_reset_path, method: :patch, local: true do %>
|
<%= form_with url: public_password_reset_path, method: :patch, local: true do %>
|
||||||
<%= hidden_field_tag :token, @token %>
|
<%= hidden_field_tag :token, @token %>
|
||||||
<%= render "shared/input_toggle",
|
<%= render "shared/input_toggle",
|
||||||
name: :password,
|
name: :password,
|
||||||
label: "Nuova password (min. 8 caratteri)",
|
label: t("auth.password_reset.new_password_label"),
|
||||||
input_type: "text",
|
input_type: "text",
|
||||||
required: true,
|
required: true,
|
||||||
minlength: 8,
|
minlength: 8,
|
||||||
autocomplete: "new-password" %>
|
autocomplete: "new-password" %>
|
||||||
<%= render "shared/input_toggle",
|
<%= render "shared/input_toggle",
|
||||||
name: :password_confirmation,
|
name: :password_confirmation,
|
||||||
label: "Conferma password",
|
label: t("auth.password_confirmation"),
|
||||||
input_type: "text",
|
input_type: "text",
|
||||||
required: true,
|
required: true,
|
||||||
autocomplete: "new-password" %>
|
autocomplete: "new-password" %>
|
||||||
<%= submit_tag "Salva password", class: "btn btn-primary" %>
|
<%= submit_tag t("auth.password_reset.submit"), class: "btn btn-primary" %>
|
||||||
<% end %>
|
<% 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>
|
</div>
|
||||||
</section>
|
</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" %>
|
<% content_for :robots, "noindex, nofollow" %>
|
||||||
|
|
||||||
<section class="auth-page">
|
<section class="auth-page">
|
||||||
<h1>Password dimenticata</h1>
|
<h1><%= t("auth.password_forgot.title") %></h1>
|
||||||
<p class="auth-lead">Inserisci l’email dell’account: ti invieremo un link per reimpostare la password.</p>
|
<p class="auth-lead"><%= t("auth.password_forgot.lead") %></p>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<%= form_with url: public_password_forgot_path, method: :post, local: true do %>
|
<%= 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" %>
|
<%= 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 %>
|
<% 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>
|
</div>
|
||||||
</section>
|
</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>
|
<button type="button" class="regia-btn regia-btn--minus" data-action="away_undo" style="width:100%;margin-top:4px">−</button>
|
||||||
</div>
|
</div>
|
||||||
</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-score-line" id="score-line"><%= live_score_points_label(@match, @score) %></p>
|
||||||
<p class="regia-partials" id="partials-line" hidden></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>
|
<button type="button" class="regia-btn regia-btn--minus" data-action="away_undo" style="width:100%;margin-top:6px">−</button>
|
||||||
</div>
|
</div>
|
||||||
</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 || {} %>
|
<% 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-score-line" id="score-line" hidden></p>
|
||||||
<p class="regia-partials" id="partials-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>
|
<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">
|
<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_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">Reset</button>
|
<button type="button" class="regia-btn regia-btn--outline" data-action="clock_reset"><%= t("regia.board.reset") %></button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<p class="regia-sets" id="sets-line"><%= live_score_sets_label(@score) || "—" %></p>
|
<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-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? %>>
|
<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>
|
</p>
|
||||||
|
|
||||||
<div class="regia-team-row">
|
<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>
|
<button type="button" class="regia-btn regia-btn--minus" data-action="home_undo" style="width:100%;margin-top:6px">−</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="regia-center-hint" id="center-hint">
|
<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>
|
<div>
|
||||||
<div class="regia-team-name"><%= @match.opponent_name %></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>
|
<button type="button" class="regia-btn regia-btn--minus" data-action="away_undo" style="width:100%;margin-top:6px">−</button>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
||||||
|
|||||||
@@ -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 :robots, "noindex, nofollow" %>
|
||||||
|
|
||||||
<% content_for :head do %>
|
<% content_for :head do %>
|
||||||
@@ -8,6 +8,7 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="regia-page" id="regia-app"
|
<div class="regia-page" id="regia-app"
|
||||||
|
data-i18n="<%= j regia_js_i18n_json %>"
|
||||||
data-token="<%= j params[:token] %>"
|
data-token="<%= j params[:token] %>"
|
||||||
data-board="<%= @match.effective_board_type %>"
|
data-board="<%= @match.effective_board_type %>"
|
||||||
data-status-url="<%= j public_regia_status_path(params[:token]) %>"
|
data-status-url="<%= j public_regia_status_path(params[:token]) %>"
|
||||||
@@ -29,34 +30,34 @@
|
|||||||
|
|
||||||
<header class="regia-header">
|
<header class="regia-header">
|
||||||
<h1><%= @team.name %> vs <%= @match.opponent_name %></h1>
|
<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')) %>">
|
<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>
|
</span>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<% if @live_share_url.present? %>
|
<% if @live_share_url.present? %>
|
||||||
<div class="regia-share-group">
|
<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">
|
<div class="regia-share">
|
||||||
<button type="button" class="regia-btn regia-btn--outline" id="btn-share-live" style="margin:0">
|
<button type="button" class="regia-btn regia-btn--outline" id="btn-share-live" style="margin:0">
|
||||||
Condividi diretta
|
<%= t("regia.share_live") %>
|
||||||
</button>
|
</button>
|
||||||
<button type="button" class="regia-btn regia-btn--outline" id="btn-copy-live" style="margin:0">
|
<button type="button" class="regia-btn regia-btn--outline" id="btn-copy-live" style="margin:0">
|
||||||
Copia link
|
<%= t("regia.copy_link") %>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<div class="regia-share-group">
|
<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">
|
<div class="regia-share">
|
||||||
<button type="button" class="regia-btn regia-btn--outline" id="btn-share-regia" style="margin:0">
|
<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>
|
||||||
<button type="button" class="regia-btn regia-btn--outline" id="btn-copy-regia" style="margin:0">
|
<button type="button" class="regia-btn regia-btn--outline" id="btn-copy-regia" style="margin:0">
|
||||||
Copia link
|
<%= t("regia.copy_link") %>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -66,7 +67,7 @@
|
|||||||
<video id="regia-preview" playsinline muted autoplay></video>
|
<video id="regia-preview" playsinline muted autoplay></video>
|
||||||
<% end %>
|
<% end %>
|
||||||
<div id="regia-preview-placeholder" class="regia-preview__placeholder">
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -75,8 +76,8 @@
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<% unless @stream_closed %>
|
<% 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--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">Chiudi diretta</button>
|
<button type="button" class="regia-btn regia-btn--danger" id="btn-stop"><%= t("regia.stop") %></button>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -84,11 +85,11 @@
|
|||||||
|
|
||||||
<div id="regia-modal" class="regia-modal" aria-hidden="true">
|
<div id="regia-modal" class="regia-modal" aria-hidden="true">
|
||||||
<div class="regia-modal__card">
|
<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>
|
<p id="modal-body"></p>
|
||||||
<div class="regia-modal__actions">
|
<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--yellow" id="modal-confirm"><%= t("regia.modal.confirm") %></button>
|
||||||
<button type="button" class="regia-btn regia-btn--outline" id="modal-cancel">Annulla</button>
|
<button type="button" class="regia-btn regia-btn--outline" id="modal-cancel"><%= t("regia.modal.cancel") %></button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
<% content_for :title, "Registra la squadra — Match Live TV" %>
|
<% content_for :title, t("auth.signup.meta_title") %>
|
||||||
<% 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 :meta_description, t("auth.signup.meta_description") %>
|
||||||
<% content_for :robots, "noindex, follow" %>
|
<% content_for :robots, "noindex, follow" %>
|
||||||
|
|
||||||
<section class="auth-page">
|
<section class="auth-page">
|
||||||
<h1>Registra la squadra</h1>
|
<h1><%= t("auth.signup.title") %></h1>
|
||||||
<p class="auth-lead">Crea l'account del referente (coach o dirigente), poi configurerai la squadra.</p>
|
<p class="auth-lead"><%= t("auth.signup.lead") %></p>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<%= form_with model: @user, url: public_signup_path do |f| %>
|
<%= 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 %>
|
<%= f.text_field :name, required: true %>
|
||||||
<%= render "shared/input_toggle",
|
<%= render "shared/input_toggle",
|
||||||
name: "user[email]",
|
name: "user[email]",
|
||||||
id: "user_email",
|
id: "user_email",
|
||||||
label: "Email",
|
label: t("auth.email"),
|
||||||
value: @user.email,
|
value: @user.email,
|
||||||
input_type: "email",
|
input_type: "email",
|
||||||
required: true,
|
required: true,
|
||||||
@@ -20,7 +20,7 @@
|
|||||||
<%= render "shared/input_toggle",
|
<%= render "shared/input_toggle",
|
||||||
name: "user[password]",
|
name: "user[password]",
|
||||||
id: "user_password",
|
id: "user_password",
|
||||||
label: "Password",
|
label: t("auth.password"),
|
||||||
input_type: "text",
|
input_type: "text",
|
||||||
required: true,
|
required: true,
|
||||||
minlength: 8,
|
minlength: 8,
|
||||||
@@ -28,24 +28,25 @@
|
|||||||
<%= render "shared/input_toggle",
|
<%= render "shared/input_toggle",
|
||||||
name: "user[password_confirmation]",
|
name: "user[password_confirmation]",
|
||||||
id: "user_password_confirmation",
|
id: "user_password_confirmation",
|
||||||
label: "Conferma password",
|
label: t("auth.password_confirmation"),
|
||||||
input_type: "text",
|
input_type: "text",
|
||||||
required: true,
|
required: true,
|
||||||
autocomplete: "new-password" %>
|
autocomplete: "new-password" %>
|
||||||
<label class="legal-accept">
|
<label class="legal-accept">
|
||||||
<%= check_box_tag :accept_terms, "1", false, required: true %>
|
<%= check_box_tag :accept_terms, "1", false, required: true %>
|
||||||
<span class="legal-accept-text">
|
<span class="legal-accept-text">
|
||||||
Ho letto e accetto l’<%= link_to "informativa privacy", public_privacy_path, target: "_blank", rel: "noopener" %>
|
<%= raw t(
|
||||||
e i <%= link_to "termini di servizio", public_termini_path, target: "_blank", rel: "noopener" %>.
|
"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>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<p class="legal-accept-hint">
|
<p class="legal-accept-hint">
|
||||||
Registrandoti dichiari di essere maggiorenne e, se operi per una società sportiva con atleti minorenni,
|
<%= t("auth.signup.legal_hint") %>
|
||||||
di avere legittima facoltà (es. dirigente, coach autorizzato) e di rispettare le regole su immagini dei minori
|
|
||||||
descritte in privacy.
|
|
||||||
</p>
|
</p>
|
||||||
<%= f.submit "Continua", class: "btn btn-primary" %>
|
<%= f.submit t("auth.signup.submit"), class: "btn btn-primary" %>
|
||||||
<% end %>
|
<% 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>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
<% content_for :title, "Replay — Match Live TV" %>
|
<% content_for :title, t("replay.index.meta_title") %>
|
||||||
<% content_for :meta_description, "Archivio replay delle dirette sportive su Match Live TV. Cerca per società, squadra o avversario." %>
|
<% content_for :meta_description, t("replay.index.meta_description") %>
|
||||||
|
|
||||||
<div class="wrap replay-index">
|
<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>
|
<h1><%= t("replay.index.title") %></h1>
|
||||||
<p class="results-hint">Replay pubblici delle società sportive — riguarda le partite già trasmesse.</p>
|
<p class="results-hint"><%= t("replay.index.hint") %></p>
|
||||||
|
|
||||||
<%= form_with url: public_replay_index_path, method: :get, local: true, class: "search-form" do %>
|
<%= form_with url: public_replay_index_path, method: :get, local: true, class: "search-form" do %>
|
||||||
<% if params[:club_id].present? %>
|
<% if params[:club_id].present? %>
|
||||||
@@ -15,13 +15,13 @@
|
|||||||
type="search"
|
type="search"
|
||||||
name="q"
|
name="q"
|
||||||
value="<%= @query %>"
|
value="<%= @query %>"
|
||||||
placeholder="Es. Tigers Volley, avversario, società, luogo…"
|
placeholder="<%= t("replay.index.search_placeholder") %>"
|
||||||
aria-label="Cerca replay"
|
aria-label="<%= t("replay.index.search_aria_label") %>"
|
||||||
autocomplete="off"
|
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? %>
|
<% 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 %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
@@ -30,9 +30,9 @@
|
|||||||
<% if @query.present? %>
|
<% if @query.present? %>
|
||||||
<%= hidden_field_tag :q, @query %>
|
<%= hidden_field_tag :q, @query %>
|
||||||
<% end %>
|
<% 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()">
|
<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| %>
|
<% @clubs.each do |club| %>
|
||||||
<option value="<%= club.id %>"<%= " selected" if params[:club_id].to_s == club.id.to_s %>><%= club.name %></option>
|
<option value="<%= club.id %>"<%= " selected" if params[:club_id].to_s == club.id.to_s %>><%= club.name %></option>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -41,9 +41,9 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<% if @query.present? %>
|
<% if @query.present? %>
|
||||||
|
<% club_suffix = @filter_club ? t("replay.index.results_hint_club_suffix", name: @filter_club.name) : "" %>
|
||||||
<p class="results-hint">
|
<p class="results-hint">
|
||||||
Risultati per «<%= h @query %>»<% if @filter_club %> · <%= @filter_club.name %><% end %>:
|
<%= t("replay.index.results_hint", query: @query, club_suffix: club_suffix, count: @recordings.size) %>
|
||||||
<%= @recordings.size %> replay
|
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
@@ -81,12 +81,12 @@
|
|||||||
<% else %>
|
<% else %>
|
||||||
<div class="empty-state">
|
<div class="empty-state">
|
||||||
<% if @query.present? || params[:club_id].present? %>
|
<% if @query.present? || params[:club_id].present? %>
|
||||||
<p><strong>Nessun replay trovato</strong> con i filtri selezionati.</p>
|
<p><strong><%= t("replay.index.empty_filtered_title") %></strong><%= t("replay.index.empty_filtered_suffix") %></p>
|
||||||
<p><%= link_to "Mostra tutti i replay pubblici", public_replay_index_path, class: "btn btn-secondary" %></p>
|
<p><%= link_to t("replay.index.empty_filtered_show_all"), public_replay_index_path, class: "btn btn-secondary" %></p>
|
||||||
<% else %>
|
<% else %>
|
||||||
<p><strong>Nessun replay pubblico al momento.</strong></p>
|
<p><strong><%= t("replay.index.empty_default_title") %></strong></p>
|
||||||
<p>Quando una società rende pubblica una registrazione, comparirà qui.</p>
|
<p><%= t("replay.index.empty_default_body") %></p>
|
||||||
<%= link_to "Vai alle dirette live", public_live_index_path, class: "btn btn-secondary", style: "margin-top:12px;display:inline-block" %>
|
<%= 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 %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
<% club_name = @match.team.club&.name %>
|
<% club_name = @match.team.club&.name %>
|
||||||
<% content_for :title, "Replay — #{@recording.title_or_default}" %>
|
<% content_for :title, t("replay.show.title", title: @recording.title_or_default) %>
|
||||||
<% content_for :meta_description, "Replay #{@recording.title_or_default} su Match Live TV." %>
|
<% content_for :meta_description, t("replay.show.meta_description", title: @recording.title_or_default) %>
|
||||||
<% content_for :robots, @recording.publicly_listed? ? "index, follow" : "noindex, nofollow" %>
|
<% content_for :robots, @recording.publicly_listed? ? "index, follow" : "noindex, nofollow" %>
|
||||||
|
|
||||||
<div class="wrap">
|
<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) %>
|
<%= live_match_page_heading(@match) %>
|
||||||
|
|
||||||
<% if @recording.status == "processing" %>
|
<% if @recording.status == "processing" %>
|
||||||
<div class="stream-ended" role="status">
|
<div class="stream-ended" role="status">
|
||||||
<div class="stream-ended-icon" aria-hidden="true">⏳</div>
|
<div class="stream-ended-icon" aria-hidden="true">⏳</div>
|
||||||
<h2>Replay in elaborazione</h2>
|
<h2><%= t("replay.show.processing_title") %></h2>
|
||||||
<p>La registrazione viene preparata. Riceverai un’email quando sarà pronta.</p>
|
<p><%= t("replay.show.processing_body") %></p>
|
||||||
</div>
|
</div>
|
||||||
<% elsif @recording.ready? && @recording.storage_key.present? %>
|
<% elsif @recording.ready? && @recording.storage_key.present? %>
|
||||||
<div class="live-player-wrap">
|
<div class="live-player-wrap">
|
||||||
@@ -25,18 +25,18 @@
|
|||||||
session: @session,
|
session: @session,
|
||||||
stream_closed: true,
|
stream_closed: true,
|
||||||
on_air: false,
|
on_air: false,
|
||||||
badge_label: "REPLAY",
|
badge_label: t("replay.show.badge_label"),
|
||||||
badge_class: "badge-ended" %>
|
badge_class: "badge-ended" %>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="replay-show__meta-line">
|
<p class="replay-show__meta-line">
|
||||||
<%= l_local(@recording.recorded_at_or_fallback) %>
|
<%= l_local(@recording.recorded_at_or_fallback) %>
|
||||||
· Durata <%= @recording.duration_label %>
|
· <%= t("replay.show.meta_line_duration", value: @recording.duration_label) %>
|
||||||
· <%= @recording.views_label %>
|
· <%= @recording.views_label %>
|
||||||
<% if @recording.expires_at %>
|
<% if @recording.expires_at %>
|
||||||
· Fino al <%= l_local(@recording.expires_at) %>
|
· <%= t("replay.show.meta_line_until", date: l_local(@recording.expires_at)) %>
|
||||||
<% end %>
|
<% 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 != "—" %>
|
<% if @recording.source_platform_label != "—" %>
|
||||||
· <%= @recording.source_platform_label %>
|
· <%= @recording.source_platform_label %>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -46,10 +46,10 @@
|
|||||||
<% if (ent.phone_download_enabled? && (logged_in? || @recording.unlisted?)) || @recording.youtube_watch_url %>
|
<% if (ent.phone_download_enabled? && (logged_in? || @recording.unlisted?)) || @recording.youtube_watch_url %>
|
||||||
<div class="replay-show__actions">
|
<div class="replay-show__actions">
|
||||||
<% if ent.phone_download_enabled? && (logged_in? || @recording.unlisted?) %>
|
<% 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 %>
|
<% end %>
|
||||||
<% if @recording.youtube_watch_url %>
|
<% 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 %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -65,28 +65,28 @@
|
|||||||
session: @session,
|
session: @session,
|
||||||
stream_closed: true,
|
stream_closed: true,
|
||||||
on_air: false,
|
on_air: false,
|
||||||
badge_label: "REPLAY",
|
badge_label: t("replay.show.badge_label"),
|
||||||
badge_class: "badge-ended" %>
|
badge_class: "badge-ended" %>
|
||||||
</div>
|
</div>
|
||||||
<p class="replay-show__meta-line">
|
<p class="replay-show__meta-line">
|
||||||
Replay su YouTube (copia server non presente).
|
<%= t("replay.show.youtube_only_body") %>
|
||||||
<%= link_to "Apri su YouTube", @recording.youtube_watch_url, target: "_blank", rel: "noopener" %>
|
<%= link_to t("replay.show.youtube_link"), @recording.youtube_watch_url, target: "_blank", rel: "noopener" %>
|
||||||
</p>
|
</p>
|
||||||
<% elsif @recording.ready? %>
|
<% elsif @recording.ready? %>
|
||||||
<div class="stream-ended" role="status">
|
<div class="stream-ended" role="status">
|
||||||
<h2>File non disponibile</h2>
|
<h2><%= t("replay.show.file_missing_title") %></h2>
|
||||||
<p>Il video non è più sul server (archivio rimosso o in migrazione).</p>
|
<p><%= t("replay.show.file_missing_body") %></p>
|
||||||
<p class="stream-ended-meta">Durata registrata: <%= @recording.duration_label %> · <%= @recording.byte_size_label %></p>
|
<p class="stream-ended-meta"><%= t("replay.show.file_missing_meta", duration: @recording.duration_label, size: @recording.byte_size_label) %></p>
|
||||||
</div>
|
</div>
|
||||||
<% elsif @recording.status == "failed" %>
|
<% elsif @recording.status == "failed" %>
|
||||||
<div class="stream-ended" role="status">
|
<div class="stream-ended" role="status">
|
||||||
<h2>Replay non disponibile</h2>
|
<h2><%= t("replay.show.failed_title") %></h2>
|
||||||
<p><%= @recording.error_message.presence || "Errore di elaborazione della registrazione." %></p>
|
<p><%= @recording.error_message.presence || t("replay.show.failed_default_body") %></p>
|
||||||
</div>
|
</div>
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="stream-ended" role="status">
|
<div class="stream-ended" role="status">
|
||||||
<h2>Replay non disponibile</h2>
|
<h2><%= t("replay.show.unavailable_title") %></h2>
|
||||||
<p>Questa registrazione non è più accessibile.</p>
|
<p><%= t("replay.show.unavailable_body") %></p>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,26 +1,26 @@
|
|||||||
<% content_for :title, "Accedi — Match Live TV" %>
|
<% content_for :title, t("auth.login.meta_title") %>
|
||||||
<% content_for :meta_description, "Accesso area riservata per staff e società registrate su Match Live TV." %>
|
<% content_for :meta_description, t("auth.login.meta_description") %>
|
||||||
<% content_for :robots, "noindex, nofollow" %>
|
<% content_for :robots, "noindex, nofollow" %>
|
||||||
|
|
||||||
<section class="auth-page">
|
<section class="auth-page">
|
||||||
<h1>Accedi</h1>
|
<h1><%= t("auth.login.title") %></h1>
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<%= form_with url: public_login_path do |f| %>
|
<%= form_with url: public_login_path do |f| %>
|
||||||
<%= render "shared/input_toggle",
|
<%= render "shared/input_toggle",
|
||||||
name: :email,
|
name: :email,
|
||||||
label: "Email",
|
label: t("auth.email"),
|
||||||
value: params[:email],
|
value: params[:email],
|
||||||
input_type: "email",
|
input_type: "email",
|
||||||
required: true,
|
required: true,
|
||||||
autocomplete: "username" %>
|
autocomplete: "username" %>
|
||||||
<%= render "shared/input_toggle",
|
<%= render "shared/input_toggle",
|
||||||
name: :password,
|
name: :password,
|
||||||
label: "Password",
|
label: t("auth.password"),
|
||||||
required: true,
|
required: true,
|
||||||
autocomplete: "current-password" %>
|
autocomplete: "current-password" %>
|
||||||
<p class="auth-forgot"><%= link_to "Password dimenticata?", public_password_forgot_path %></p>
|
<p class="auth-forgot"><%= link_to t("auth.login.forgot_password"), public_password_forgot_path %></p>
|
||||||
<%= submit_tag "Entra", class: "btn btn-primary" %>
|
<%= submit_tag t("auth.login.submit"), class: "btn btn-primary" %>
|
||||||
<% end %>
|
<% 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>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -1,12 +1,11 @@
|
|||||||
<% content_for :title, "Squadre su Match Live TV — Dirette e replay sport giovanili" %>
|
<% content_for :title, t("team_pages.index.meta_title") %>
|
||||||
<% 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 :meta_description, t("team_pages.index.meta_description") %>
|
||||||
<% content_for :canonical_url, seo_absolute_url(public_team_pages_path(filter_params_for_canonical)) %>
|
<% content_for :canonical_url, seo_absolute_url(public_team_pages_path(filter_params_for_canonical)) %>
|
||||||
|
|
||||||
<div class="wrap team-directory">
|
<div class="wrap team-directory">
|
||||||
<h1>Squadre su Match Live TV</h1>
|
<h1><%= t("team_pages.index.heading") %></h1>
|
||||||
<p class="results-hint">
|
<p class="results-hint">
|
||||||
Società e squadre con dirette, replay pubblici o partite in programma.
|
<%= t("team_pages.index.hint") %>
|
||||||
Cerca la tua squadra e apri la pagina per seguire calendario e archivio.
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<%= form_with url: public_team_pages_path, method: :get, local: true, class: "team-directory-filters" do %>
|
<%= form_with url: public_team_pages_path, method: :get, local: true, class: "team-directory-filters" do %>
|
||||||
@@ -15,18 +14,18 @@
|
|||||||
type="search"
|
type="search"
|
||||||
name="q"
|
name="q"
|
||||||
value="<%= @query %>"
|
value="<%= @query %>"
|
||||||
placeholder="Nome squadra, società, città o palestra…"
|
placeholder="<%= t("team_pages.index.search_placeholder") %>"
|
||||||
aria-label="Cerca squadra"
|
aria-label="<%= t("team_pages.index.search_aria_label") %>"
|
||||||
autocomplete="off"
|
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>
|
||||||
|
|
||||||
<div class="team-directory-filters__row">
|
<div class="team-directory-filters__row">
|
||||||
<div class="team-directory-filters__field">
|
<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()">
|
<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| %>
|
<% @sport_options.each do |sport| %>
|
||||||
<option value="<%= sport[:key] %>"<%= " selected" if @sport == sport[:key] %>><%= sport[:label] %></option>
|
<option value="<%= sport[:key] %>"<%= " selected" if @sport == sport[:key] %>><%= sport[:label] %></option>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -34,20 +33,20 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="team-directory-filters__field team-directory-filters__field--chips">
|
<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">
|
<div class="team-directory-chips" role="group" aria-labelledby="team-filter-chips-label">
|
||||||
<label class="filter-chip<%= " is-active" if @live_filter %>">
|
<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()" %>
|
<%= 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__face" aria-hidden="true">
|
||||||
<span class="filter-chip__dot filter-chip__dot--live"></span>
|
<span class="filter-chip__dot filter-chip__dot--live"></span>
|
||||||
In diretta
|
<%= t("team_pages.index.chip_live") %>
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
<label class="filter-chip<%= " is-active" if @replays_filter %>">
|
<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()" %>
|
<%= 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__face" aria-hidden="true">
|
||||||
<span class="filter-chip__icon">▶</span>
|
<span class="filter-chip__icon">▶</span>
|
||||||
Con replay
|
<%= t("team_pages.index.chip_replays") %>
|
||||||
</span>
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
@@ -56,7 +55,7 @@
|
|||||||
<% if @query.present? || @sport.present? || @live_filter || @replays_filter %>
|
<% if @query.present? || @sport.present? || @live_filter || @replays_filter %>
|
||||||
<div class="team-directory-filters__actions">
|
<div class="team-directory-filters__actions">
|
||||||
<span class="team-directory-filters__label team-directory-filters__label--spacer" aria-hidden="true"> </span>
|
<span class="team-directory-filters__label team-directory-filters__label--spacer" aria-hidden="true"> </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>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
@@ -64,12 +63,12 @@
|
|||||||
|
|
||||||
<% if @query.present? || @sport.present? || @live_filter || @replays_filter %>
|
<% if @query.present? || @sport.present? || @live_filter || @replays_filter %>
|
||||||
<p class="results-hint">
|
<p class="results-hint">
|
||||||
<%= @entries.size %> squadre trovate
|
<%= t("team_pages.index.results_found", count: @entries.size) %>
|
||||||
<% if @query.present? %> per «<%= h @query %>»<% end %>
|
<% if @query.present? %><%= t("team_pages.index.results_query_suffix", query: @query) %><% end %>
|
||||||
<% if @sport.present? %> · <%= Sports::Catalog.find_optional(@sport)&.dig(:label) || @sport %><% end %>
|
<% if @sport.present? %><%= t("team_pages.index.results_sport_suffix", sport: Sports::Catalog.find_optional(@sport)&.dig(:label) || @sport) %><% end %>
|
||||||
</p>
|
</p>
|
||||||
<% else %>
|
<% 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 %>
|
<% end %>
|
||||||
|
|
||||||
<% if @entries.any? %>
|
<% if @entries.any? %>
|
||||||
@@ -96,13 +95,13 @@
|
|||||||
<p class="team-directory-card__meta"><%= team.sport_label %></p>
|
<p class="team-directory-card__meta"><%= team.sport_label %></p>
|
||||||
<div class="team-directory-card__badges">
|
<div class="team-directory-card__badges">
|
||||||
<% if entry.live_now %>
|
<% 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 %>
|
<% end %>
|
||||||
<% if entry.upcoming_count.positive? %>
|
<% 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 %>
|
<% end %>
|
||||||
<% if entry.replay_count.positive? %>
|
<% 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 %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -112,18 +111,18 @@
|
|||||||
<% else %>
|
<% else %>
|
||||||
<div class="empty-state">
|
<div class="empty-state">
|
||||||
<% if @query.present? || @sport.present? || @live_filter || @replays_filter %>
|
<% if @query.present? || @sport.present? || @live_filter || @replays_filter %>
|
||||||
<p><strong>Nessuna squadra trovata</strong> con i filtri selezionati.</p>
|
<p><strong><%= t("team_pages.index.empty_filtered_title") %></strong><%= t("team_pages.index.empty_filtered_suffix") %></p>
|
||||||
<p><%= link_to "Mostra tutte le squadre attive", public_team_pages_path, class: "btn btn-secondary" %></p>
|
<p><%= link_to t("team_pages.index.empty_filtered_show_all"), public_team_pages_path, class: "btn btn-secondary" %></p>
|
||||||
<% else %>
|
<% else %>
|
||||||
<p><strong>Nessuna squadra attiva al momento.</strong></p>
|
<p><strong><%= t("team_pages.index.empty_default_title") %></strong></p>
|
||||||
<p>Quando una società avvierà dirette o pubblicherà replay, comparirà qui.</p>
|
<p><%= t("team_pages.index.empty_default_body") %></p>
|
||||||
<%= link_to "Vai alle dirette live", public_live_index_path, class: "btn btn-secondary", style: "margin-top:12px;display:inline-block" %>
|
<%= 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 %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<p class="team-directory-footer muted">
|
<p class="team-directory-footer muted">
|
||||||
<%= link_to "Dirette in corso", public_live_index_path %> ·
|
<%= link_to t("team_pages.index.footer_live_link"), public_live_index_path %> ·
|
||||||
<%= link_to "Archivio replay", public_replay_index_path %>
|
<%= link_to t("team_pages.index.footer_replay_link"), public_replay_index_path %>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</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 :meta_description, team_page_meta_description(@team, @club) %>
|
||||||
<% content_for :canonical_url, seo_absolute_url(public_team_page_path(@team.slug)) %>
|
<% content_for :canonical_url, seo_absolute_url(public_team_page_path(@team.slug)) %>
|
||||||
<% content_for :head do %>
|
<% content_for :head do %>
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
<div class="wrap team-public-page">
|
<div class="wrap team-public-page">
|
||||||
<nav class="team-dashboard-nav" aria-label="Percorso">
|
<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>
|
</nav>
|
||||||
|
|
||||||
<header class="roster-hero card team-public-hero" style="--club-primary:<%= @team.effective_primary_color %>;--club-secondary:<%= @team.effective_secondary_color %>">
|
<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">
|
<p class="team-public-meta">
|
||||||
<%= @team.sport_label %>
|
<%= @team.sport_label %>
|
||||||
<% if @live_sessions.any? %>
|
<% 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 %>
|
<% end %>
|
||||||
</p>
|
</p>
|
||||||
<% if @team.description.present? %>
|
<% if @team.description.present? %>
|
||||||
@@ -27,11 +27,11 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
<div class="team-public-actions">
|
<div class="team-public-actions">
|
||||||
<% if @live_sessions.any? %>
|
<% 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 %>
|
<% 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? %>
|
<% 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 %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
|
|
||||||
<% if @live_sessions.any? %>
|
<% if @live_sessions.any? %>
|
||||||
<section class="team-public-section" aria-labelledby="team-live-heading">
|
<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">
|
<div class="live-grid">
|
||||||
<% @live_sessions.each do |session| %>
|
<% @live_sessions.each do |session| %>
|
||||||
<% match = session.match %>
|
<% match = session.match %>
|
||||||
@@ -66,21 +66,21 @@
|
|||||||
<p class="card-score">
|
<p class="card-score">
|
||||||
<span class="card-sets"><%= live_score_sets_label(session.score_state, match) %></span>
|
<span class="card-sets"><%= live_score_sets_label(session.score_state, match) %></span>
|
||||||
<% if live_score_partials_label(session.score_state).present? %>
|
<% 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 %>
|
<% end %>
|
||||||
<span class="card-points"><%= session.score_state.home_points %> - <%= session.score_state.away_points %></span>
|
<span class="card-points"><%= session.score_state.home_points %> - <%= session.score_state.away_points %></span>
|
||||||
</p>
|
</p>
|
||||||
<% end %>
|
<% end %>
|
||||||
<div class="badges">
|
<div class="badges">
|
||||||
<% if on_air %>
|
<% 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? %>
|
<% elsif session.paused? %>
|
||||||
<span class="badge badge-connecting">In pausa</span>
|
<span class="badge badge-connecting"><%= t("live.index.badge_paused") %></span>
|
||||||
<% else %>
|
<% else %>
|
||||||
<span class="badge badge-live">Live</span>
|
<span class="badge badge-live"><%= t("live.index.badge_live") %></span>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</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>
|
</article>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
@@ -89,8 +89,8 @@
|
|||||||
|
|
||||||
<% if @upcoming_matches.any? %>
|
<% if @upcoming_matches.any? %>
|
||||||
<section class="team-public-section" aria-labelledby="team-upcoming-heading">
|
<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>
|
<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">Programmate dal club: la diretta partirà quando lo staff avvierà la trasmissione.</p>
|
<p class="results-hint"><%= t("team_pages.show.upcoming_hint") %></p>
|
||||||
<div class="live-grid upcoming-grid">
|
<div class="live-grid upcoming-grid">
|
||||||
<% @upcoming_matches.each do |match| %>
|
<% @upcoming_matches.each do |match| %>
|
||||||
<article class="live-card live-card--upcoming">
|
<article class="live-card live-card--upcoming">
|
||||||
@@ -103,7 +103,7 @@
|
|||||||
</p>
|
</p>
|
||||||
<p class="upcoming-when"><%= live_scheduled_relative(match.scheduled_at) %></p>
|
<p class="upcoming-when"><%= live_scheduled_relative(match.scheduled_at) %></p>
|
||||||
<div class="badges">
|
<div class="badges">
|
||||||
<span class="badge badge-scheduled">In programma</span>
|
<span class="badge badge-scheduled"><%= t("live.index.badge_scheduled") %></span>
|
||||||
</div>
|
</div>
|
||||||
</article>
|
</article>
|
||||||
<% end %>
|
<% end %>
|
||||||
@@ -113,7 +113,7 @@
|
|||||||
|
|
||||||
<% if @recordings.any? %>
|
<% if @recordings.any? %>
|
||||||
<section class="team-public-section" aria-labelledby="team-replays-heading">
|
<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">
|
<div class="replay-grid">
|
||||||
<% @recordings.each do |rec| %>
|
<% @recordings.each do |rec| %>
|
||||||
<% match = rec.stream_session.match %>
|
<% match = rec.stream_session.match %>
|
||||||
@@ -137,21 +137,21 @@
|
|||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</div>
|
||||||
<p class="team-public-more">
|
<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>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<% if @roster_by_category %>
|
<% if @roster_by_category %>
|
||||||
<section class="team-public-section" aria-labelledby="team-roster-heading">
|
<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">
|
<div class="team-public-roster">
|
||||||
<% TeamRosterMember::DISPLAY_ORDER.each do |category| %>
|
<% TeamRosterMember::DISPLAY_ORDER.each do |category| %>
|
||||||
<% members = @roster_by_category[category] %>
|
<% members = @roster_by_category[category] %>
|
||||||
<% next if members.blank? %>
|
<% next if members.blank? %>
|
||||||
<section class="roster-section card">
|
<section class="roster-section card">
|
||||||
<header class="roster-section__head">
|
<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>
|
<span class="roster-section__count"><%= members.size %></span>
|
||||||
</header>
|
</header>
|
||||||
<div class="roster-list">
|
<div class="roster-list">
|
||||||
@@ -167,9 +167,9 @@
|
|||||||
|
|
||||||
<% if @live_sessions.empty? && @upcoming_matches.empty? && @recordings.empty? %>
|
<% if @live_sessions.empty? && @upcoming_matches.empty? && @recordings.empty? %>
|
||||||
<div class="empty-state empty-state--soft team-public-empty">
|
<div class="empty-state empty-state--soft team-public-empty">
|
||||||
<p><strong>Nessuna diretta o replay al momento.</strong></p>
|
<p><strong><%= t("team_pages.show.empty_title") %></strong></p>
|
||||||
<p>Torna a controllare prima delle prossime gare: qui compariranno dirette, calendario e archivio della squadra.</p>
|
<p><%= t("team_pages.show.empty_body") %></p>
|
||||||
<%= link_to "Vedi tutte le dirette", public_live_index_path, class: "btn btn-secondary" %>
|
<%= link_to t("team_pages.show.empty_link"), public_live_index_path, class: "btn btn-secondary" %>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
</div>
|
</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" %>
|
<% content_for :robots, "noindex, nofollow" %>
|
||||||
|
|
||||||
<div class="wrap" style="padding-top:20px;max-width:520px">
|
<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>
|
<h1><%= @member.full_name %></h1>
|
||||||
<%= render "shared/roster_member_form", member: @member, team: @team %>
|
<%= render "shared/roster_member_form", member: @member, team: @team %>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user