Aggiunge i18n IT/EN/FR/DE/ES, pagine pubbliche squadre e UX archivio.
Il selettore lingua funziona sul web e sulle app native; su Android la preferenza è persistita e applicata al riavvio. Incluse anche eliminazione replay a scope e campi pagina pubblica squadra. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -14,7 +14,9 @@ module Api
|
||||
|
||||
def destroy
|
||||
authorize_manage!
|
||||
Recordings::Delete.new(@recording).call
|
||||
scope = params[:delete_scope].to_s
|
||||
scope = "both" unless Recordings::Delete::SCOPES.include?(scope)
|
||||
Recordings::Delete.new(@recording, scope: scope).call
|
||||
head :no_content
|
||||
end
|
||||
|
||||
|
||||
@@ -35,8 +35,9 @@ module Recordings
|
||||
end
|
||||
|
||||
def destroy
|
||||
Recordings::Delete.new(@recording).call
|
||||
redirect_to archive_index_path, notice: "Replay eliminato"
|
||||
scope = delete_scope_param
|
||||
Recordings::Delete.new(@recording, scope: scope).call
|
||||
redirect_to archive_index_path, notice: delete_notice_for(scope)
|
||||
end
|
||||
|
||||
def publish_youtube
|
||||
@@ -99,6 +100,22 @@ module Recordings
|
||||
params.require(:recording).permit(:privacy_status, :title, :expires_at, :extend_days)
|
||||
end
|
||||
|
||||
def delete_scope_param
|
||||
value = params[:delete_scope].to_s
|
||||
Recordings::Delete::SCOPES.include?(value) ? value : "both"
|
||||
end
|
||||
|
||||
def delete_notice_for(scope)
|
||||
case scope
|
||||
when "site"
|
||||
"Replay eliminato dal sito (YouTube invariato)"
|
||||
when "youtube"
|
||||
"Video rimosso da YouTube (resta disponibile sul sito)"
|
||||
else
|
||||
"Replay eliminato dal sito e da YouTube"
|
||||
end
|
||||
end
|
||||
|
||||
def admin_expiry_update_requested?
|
||||
return false unless archive_namespace == :admin
|
||||
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
module Public
|
||||
class LocalesController < SiteBaseController
|
||||
def update
|
||||
locale = LocaleResolver.persist!(cookies, params[:locale])
|
||||
I18n.locale = locale
|
||||
redirect_back fallback_location: root_path
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -5,14 +5,30 @@ module Public
|
||||
include ::SeoHelper
|
||||
include ::LegalHelper
|
||||
helper ApplicationHelper
|
||||
helper_method :current_user, :logged_in?
|
||||
helper_method :current_user, :logged_in?, :current_locale, :language_options, :current_language_option
|
||||
|
||||
protect_from_forgery with: :exception
|
||||
before_action :set_site_locale
|
||||
|
||||
private
|
||||
|
||||
def set_site_locale
|
||||
I18n.locale = :it
|
||||
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 current_user
|
||||
|
||||
@@ -9,11 +9,20 @@ module Public
|
||||
{ loc: "#{base}/pallavolo-giovanile", changefreq: "monthly", priority: "0.85" },
|
||||
{ loc: "#{base}/faq", changefreq: "monthly", priority: "0.8" },
|
||||
{ loc: "#{base}/live", changefreq: "hourly", priority: "0.85" },
|
||||
{ loc: "#{base}/squadre", changefreq: "daily", priority: "0.85" },
|
||||
{ loc: "#{base}/privacy", changefreq: "yearly", priority: "0.3" },
|
||||
{ loc: "#{base}/cookie", changefreq: "yearly", priority: "0.3" },
|
||||
{ loc: "#{base}/termini", changefreq: "yearly", priority: "0.3" }
|
||||
]
|
||||
|
||||
Team.find_each do |team|
|
||||
@entries << {
|
||||
loc: "#{base}/squadre/#{team.slug}",
|
||||
changefreq: "daily",
|
||||
priority: "0.7"
|
||||
}
|
||||
end
|
||||
|
||||
respond_to do |format|
|
||||
format.xml { render layout: false }
|
||||
end
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
module Public
|
||||
class TeamPagesController < SiteBaseController
|
||||
include Public::LiveHelper
|
||||
|
||||
layout "marketing_live"
|
||||
|
||||
def index
|
||||
@query = params[:q].to_s.strip
|
||||
@sport = params[:sport].to_s.strip.presence
|
||||
@live_filter = params[:live].present?
|
||||
@replays_filter = params[:replays].present?
|
||||
@entries = Teams::PublicDirectory.call(
|
||||
q: @query,
|
||||
sport: @sport,
|
||||
live: @live_filter,
|
||||
replays: @replays_filter
|
||||
)
|
||||
@sport_options = Sports::Catalog.as_api_list
|
||||
end
|
||||
|
||||
def show
|
||||
@team = Team.includes(:club, roster_members: []).find_by!(slug: params[:slug])
|
||||
@club = @team.club
|
||||
load_live_content!
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def load_live_content!
|
||||
@online_paths = fetch_online_paths
|
||||
|
||||
@live_sessions = StreamSession
|
||||
.broadcasting
|
||||
.publicly_listed
|
||||
.where(platform: "matchlivetv")
|
||||
.includes(:score_state, match: { team: :club })
|
||||
.joins(:match)
|
||||
.where(matches: { team_id: @team.id })
|
||||
.order(Arel.sql("started_at DESC NULLS LAST"), created_at: :desc)
|
||||
|
||||
broadcasting_match_ids = StreamSession.broadcasting.select(:match_id)
|
||||
@upcoming_matches = @team.matches
|
||||
.scheduled_for_live
|
||||
.where.not(id: broadcasting_match_ids)
|
||||
.order(scheduled_at: :asc)
|
||||
.limit(20)
|
||||
|
||||
@recordings = @team.recordings
|
||||
.ready
|
||||
.publicly_listed
|
||||
.includes(stream_session: :match)
|
||||
.order(recorded_at: :desc, created_at: :desc)
|
||||
.limit(12)
|
||||
|
||||
@roster_by_category = @team.roster_public? ? @team.roster_by_category : nil
|
||||
end
|
||||
|
||||
def fetch_online_paths
|
||||
Mediamtx::Client.new.online_path_names
|
||||
rescue Mediamtx::Client::Error, Errno::ECONNREFUSED, SocketError
|
||||
[]
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -165,7 +165,7 @@ module Public
|
||||
end
|
||||
|
||||
def team_params
|
||||
p = params.require(:team).permit(:name, :sport, :sport_key, :description, :logo_url, :primary_color, :secondary_color)
|
||||
p = params.require(:team).permit(:name, :sport, :sport_key, :description, :logo_url, :primary_color, :secondary_color, :slug, :roster_public)
|
||||
if p[:sport].present? && p[:sport_key].blank?
|
||||
p[:sport_key] = p.delete(:sport)
|
||||
elsif p[:sport_key].present?
|
||||
|
||||
@@ -9,12 +9,26 @@ module ApplicationHelper
|
||||
PLAN_ICONS[plan.slug] || "fa-solid fa-circle"
|
||||
end
|
||||
|
||||
# Data/ora nel fuso dell'app (Europe/Rome) e nella lingua del sito.
|
||||
# Data/ora nel fuso dell'app (Europe/Rome) e nella lingua corrente.
|
||||
def l_local(date_or_time, format: :long)
|
||||
return nil if date_or_time.blank?
|
||||
|
||||
value = date_or_time.respond_to?(:in_time_zone) ? date_or_time.in_time_zone : date_or_time
|
||||
I18n.with_locale(:it) { I18n.l(value, format: format) }
|
||||
I18n.l(value, format: format)
|
||||
end
|
||||
|
||||
def og_locale_tag
|
||||
{
|
||||
it: "it_IT",
|
||||
en: "en_US",
|
||||
fr: "fr_FR",
|
||||
de: "de_DE",
|
||||
es: "es_ES"
|
||||
}.fetch(I18n.locale.to_sym, "it_IT")
|
||||
end
|
||||
|
||||
def html_lang
|
||||
I18n.locale.to_s
|
||||
end
|
||||
|
||||
def sport_catalog_options(selected = nil)
|
||||
|
||||
@@ -50,13 +50,18 @@ module Public
|
||||
end
|
||||
end
|
||||
|
||||
def live_match_card_heading(match)
|
||||
def live_match_card_heading(match, link_team: true)
|
||||
team = match.team
|
||||
club_name = team.club&.name.presence || "Società"
|
||||
team_label = if link_team && team.slug.present?
|
||||
link_to(team.name, public_team_page_path(team.slug), class: "live-card__team-link")
|
||||
else
|
||||
team.name
|
||||
end
|
||||
content_tag(:h3, class: "live-card__title") do
|
||||
safe_join([
|
||||
content_tag(:span, club_name, class: "live-card__club"),
|
||||
content_tag(:span, "#{team.name} vs #{match.opponent_name}", class: "live-card__matchup")
|
||||
content_tag(:span, safe_join([team_label, " vs ", match.opponent_name]), class: "live-card__matchup")
|
||||
])
|
||||
end
|
||||
end
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
module Public
|
||||
module TeamPagesHelper
|
||||
def team_page_meta_description(team, club)
|
||||
"Segui #{team.name} (#{club.name}): dirette live, calendario partite e replay su Match Live TV."
|
||||
end
|
||||
|
||||
def filter_params_for_canonical
|
||||
params.permit(:q, :sport, :live, :replays).to_h.compact_blank
|
||||
end
|
||||
|
||||
def team_page_structured_data(team, club)
|
||||
{
|
||||
"@context" => "https://schema.org",
|
||||
"@type" => "SportsTeam",
|
||||
"name" => team.name,
|
||||
"sport" => team.sport_label,
|
||||
"url" => seo_absolute_url(public_team_page_path(team.slug)),
|
||||
"memberOf" => {
|
||||
"@type" => "SportsOrganization",
|
||||
"name" => club.name
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -13,9 +13,14 @@ class Team < ApplicationRecord
|
||||
|
||||
validates :name, presence: true
|
||||
validates :sport_key, presence: true
|
||||
validates :slug, presence: true, uniqueness: true,
|
||||
format: { with: /\A[a-z0-9]+(?:-[a-z0-9]+)*\z/, message: "solo lettere minuscole, numeri e trattini" }
|
||||
validate :sport_key_known
|
||||
validate :photo_file_type, if: -> { photo_file.attached? }
|
||||
|
||||
before_validation :assign_slug, on: :create
|
||||
before_validation :normalize_slug, if: -> { slug_changed? && slug.present? }
|
||||
|
||||
before_validation :normalize_sport_key
|
||||
|
||||
def subscription
|
||||
@@ -47,6 +52,14 @@ class Team < ApplicationRecord
|
||||
end
|
||||
end
|
||||
|
||||
def roster_public?
|
||||
roster_public
|
||||
end
|
||||
|
||||
def public_page_path
|
||||
Rails.application.routes.url_helpers.public_team_page_path(slug)
|
||||
end
|
||||
|
||||
def sport_label
|
||||
Sports::Catalog.find_optional(sport_key)&.dig(:label) || sport_key.to_s.humanize
|
||||
end
|
||||
@@ -86,6 +99,14 @@ class Team < ApplicationRecord
|
||||
club
|
||||
end
|
||||
|
||||
def assign_slug
|
||||
self.slug = Teams::GenerateSlug.call(self) if slug.blank?
|
||||
end
|
||||
|
||||
def normalize_slug
|
||||
self.slug = slug.to_s.parameterize
|
||||
end
|
||||
|
||||
def default_primary_color
|
||||
club&.primary_color.presence || super
|
||||
end
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module LocaleResolver
|
||||
COOKIE_NAME = "mltv_locale"
|
||||
COOKIE_MAX_AGE = 2.years.to_i
|
||||
|
||||
module_function
|
||||
|
||||
def available
|
||||
I18n.available_locales.map(&:to_sym)
|
||||
end
|
||||
|
||||
def normalize(value)
|
||||
code = value.to_s.strip.downcase.split(/[-_]/).first
|
||||
return nil if code.blank?
|
||||
|
||||
sym = code.to_sym
|
||||
available.include?(sym) ? sym : nil
|
||||
end
|
||||
|
||||
def from_cookie(cookies)
|
||||
return nil if cookies.nil?
|
||||
|
||||
value = if cookies.respond_to?(:[])
|
||||
cookies[COOKIE_NAME]
|
||||
elsif cookies.respond_to?(:fetch)
|
||||
cookies.fetch(COOKIE_NAME, nil)
|
||||
end
|
||||
normalize(value)
|
||||
end
|
||||
|
||||
def from_accept_language(header)
|
||||
return nil if header.blank?
|
||||
|
||||
header.to_s.split(",").each do |part|
|
||||
tag = part.split(";").first.to_s.strip
|
||||
locale = normalize(tag)
|
||||
return locale if locale
|
||||
end
|
||||
nil
|
||||
end
|
||||
|
||||
def resolve(cookie_jar: nil, cookies: nil, accept_language: nil)
|
||||
jar = cookie_jar || cookies
|
||||
from_cookie(jar) || from_accept_language(accept_language) || I18n.default_locale
|
||||
end
|
||||
|
||||
def persist!(cookie_jar, locale)
|
||||
normalized = normalize(locale) || I18n.default_locale
|
||||
cookie_jar[COOKIE_NAME] = {
|
||||
value: normalized.to_s,
|
||||
expires: COOKIE_MAX_AGE.seconds.from_now,
|
||||
path: "/",
|
||||
same_site: :lax,
|
||||
httponly: false
|
||||
}
|
||||
normalized
|
||||
end
|
||||
|
||||
def language_options
|
||||
[
|
||||
{ code: :it, label: "Italiano", native: "Italiano", flag: "🇮🇹" },
|
||||
{ code: :en, label: "English", native: "English", flag: "🇬🇧" },
|
||||
{ code: :fr, label: "Français", native: "Français", flag: "🇫🇷" },
|
||||
{ code: :de, label: "Deutsch", native: "Deutsch", flag: "🇩🇪" },
|
||||
{ code: :es, label: "Español", native: "Español", flag: "🇪🇸" }
|
||||
].select { |opt| available.include?(opt[:code]) }
|
||||
end
|
||||
|
||||
def current_language_option
|
||||
language_options.find { |opt| opt[:code] == I18n.locale.to_sym } || language_options.first
|
||||
end
|
||||
end
|
||||
@@ -1,11 +1,33 @@
|
||||
module Recordings
|
||||
class Delete
|
||||
def initialize(recording, reason: :manual)
|
||||
SCOPES = %w[both site youtube].freeze
|
||||
|
||||
def initialize(recording, reason: :manual, scope: :both)
|
||||
@recording = recording
|
||||
@reason = reason
|
||||
@scope = normalize_scope(scope)
|
||||
end
|
||||
|
||||
def call
|
||||
case @scope
|
||||
when "youtube"
|
||||
delete_youtube_only!
|
||||
when "site"
|
||||
delete_site_only!
|
||||
else
|
||||
delete_both!
|
||||
end
|
||||
@recording
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def normalize_scope(scope)
|
||||
value = scope.to_s.presence || "both"
|
||||
SCOPES.include?(value) ? value : "both"
|
||||
end
|
||||
|
||||
def delete_both!
|
||||
delete_youtube_video
|
||||
delete_storage_object if @recording.storage_key.present?
|
||||
cleanup_local_artifacts
|
||||
@@ -18,10 +40,27 @@ module Recordings
|
||||
youtube_video_id: nil,
|
||||
youtube_published_at: nil
|
||||
)
|
||||
@recording
|
||||
end
|
||||
|
||||
private
|
||||
def delete_site_only!
|
||||
delete_storage_object if @recording.storage_key.present?
|
||||
cleanup_local_artifacts
|
||||
|
||||
@recording.update!(
|
||||
status: "expired",
|
||||
deleted_at: Time.current,
|
||||
storage_key: nil,
|
||||
thumbnail_storage_key: nil
|
||||
)
|
||||
end
|
||||
|
||||
def delete_youtube_only!
|
||||
delete_youtube_video
|
||||
@recording.update!(
|
||||
youtube_video_id: nil,
|
||||
youtube_published_at: nil
|
||||
)
|
||||
end
|
||||
|
||||
def delete_youtube_video
|
||||
return if @recording.youtube_video_id.blank?
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
module Teams
|
||||
class GenerateSlug
|
||||
def self.call(team)
|
||||
new(team).call
|
||||
end
|
||||
|
||||
def initialize(team)
|
||||
@team = team
|
||||
end
|
||||
|
||||
def call
|
||||
base = @team.name.to_s.parameterize.presence || "squadra"
|
||||
slug = base
|
||||
n = 2
|
||||
while conflict?(slug)
|
||||
slug = "#{base}-#{n}"
|
||||
n += 1
|
||||
end
|
||||
slug
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def conflict?(slug)
|
||||
scope = Team.where(slug: slug)
|
||||
scope = scope.where.not(id: @team.id) if @team.persisted?
|
||||
scope.exists?
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,144 @@
|
||||
module Teams
|
||||
# Elenco pubblico delle squadre con presenza su Match Live TV (diretta, replay o calendario).
|
||||
class PublicDirectory
|
||||
Entry = Struct.new(
|
||||
:team,
|
||||
:live_now,
|
||||
:replay_count,
|
||||
:upcoming_count,
|
||||
:last_activity_at,
|
||||
keyword_init: true
|
||||
)
|
||||
|
||||
def self.call(q: nil, sport: nil, live: false, replays: false)
|
||||
new(q: q, sport: sport, live: live, replays: replays).call
|
||||
end
|
||||
|
||||
def initialize(q: nil, sport: nil, live: false, replays: false)
|
||||
@q = q.to_s.strip
|
||||
@sport = normalize_sport(sport)
|
||||
@live = cast_bool(live)
|
||||
@replays = cast_bool(replays)
|
||||
end
|
||||
|
||||
def call
|
||||
teams = base_teams
|
||||
return [] if teams.empty?
|
||||
|
||||
entries = build_entries(teams)
|
||||
entries = apply_toggle_filters(entries)
|
||||
sort_entries(entries)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def normalize_sport(value)
|
||||
key = value.to_s.strip
|
||||
return nil if key.blank?
|
||||
return nil unless Sports::Catalog.find_optional(key)
|
||||
|
||||
Sports::Catalog.normalize_key(key)
|
||||
end
|
||||
|
||||
def cast_bool(value)
|
||||
ActiveModel::Type::Boolean.new.cast(value)
|
||||
end
|
||||
|
||||
def base_teams
|
||||
scope = Team.includes(:club).where(id: active_team_ids)
|
||||
scope = scope.where(sport_key: @sport) if @sport
|
||||
scope = apply_search(scope) if @q.present?
|
||||
scope.to_a
|
||||
end
|
||||
|
||||
def active_team_ids
|
||||
live = live_team_ids
|
||||
replays = replay_team_ids
|
||||
upcoming = upcoming_team_ids
|
||||
(live + replays + upcoming).uniq
|
||||
end
|
||||
|
||||
def live_team_ids
|
||||
StreamSession.broadcasting
|
||||
.publicly_listed
|
||||
.where(platform: "matchlivetv")
|
||||
.joins(:match)
|
||||
.distinct
|
||||
.pluck("matches.team_id")
|
||||
end
|
||||
|
||||
def replay_team_ids
|
||||
Recording.ready.publicly_listed.joins(stream_session: :match).distinct.pluck("matches.team_id")
|
||||
end
|
||||
|
||||
def upcoming_team_ids
|
||||
broadcasting_match_ids = StreamSession.broadcasting.select(:match_id)
|
||||
Match.scheduled_for_live
|
||||
.where.not(id: broadcasting_match_ids)
|
||||
.distinct
|
||||
.pluck(:team_id)
|
||||
end
|
||||
|
||||
def apply_search(scope)
|
||||
term = "%#{ActiveRecord::Base.sanitize_sql_like(@q)}%"
|
||||
scope.left_joins(:club, :matches).where(
|
||||
"teams.name ILIKE :term OR clubs.name ILIKE :term OR matches.location ILIKE :term OR matches.opponent_name ILIKE :term",
|
||||
term: term
|
||||
).distinct
|
||||
end
|
||||
|
||||
def build_entries(teams)
|
||||
team_ids = teams.map(&:id)
|
||||
live_set = live_team_ids.to_set
|
||||
replay_counts = Recording.ready.publicly_listed
|
||||
.joins(stream_session: :match)
|
||||
.where(matches: { team_id: team_ids })
|
||||
.group("matches.team_id")
|
||||
.count
|
||||
last_replay_at = Recording.ready.publicly_listed
|
||||
.joins(stream_session: :match)
|
||||
.where(matches: { team_id: team_ids })
|
||||
.group("matches.team_id")
|
||||
.maximum(:recorded_at)
|
||||
last_live_at = StreamSession.broadcasting
|
||||
.publicly_listed
|
||||
.joins(:match)
|
||||
.where(matches: { team_id: team_ids })
|
||||
.group("matches.team_id")
|
||||
.maximum(:started_at)
|
||||
broadcasting_match_ids = StreamSession.broadcasting.select(:match_id)
|
||||
upcoming_counts = Match.scheduled_for_live
|
||||
.where(team_id: team_ids)
|
||||
.where.not(id: broadcasting_match_ids)
|
||||
.group(:team_id)
|
||||
.count
|
||||
|
||||
teams.map do |team|
|
||||
last_activity = [last_replay_at[team.id], last_live_at[team.id]].compact.max
|
||||
Entry.new(
|
||||
team: team,
|
||||
live_now: live_set.include?(team.id),
|
||||
replay_count: replay_counts[team.id].to_i,
|
||||
upcoming_count: upcoming_counts[team.id].to_i,
|
||||
last_activity_at: last_activity
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
def apply_toggle_filters(entries)
|
||||
entries = entries.select(&:live_now) if @live
|
||||
entries = entries.select { |entry| entry.replay_count.positive? } if @replays
|
||||
entries
|
||||
end
|
||||
|
||||
def sort_entries(entries)
|
||||
entries.sort_by do |entry|
|
||||
[
|
||||
entry.live_now ? 0 : 1,
|
||||
-(entry.last_activity_at&.to_i || 0),
|
||||
entry.team.name.downcase
|
||||
]
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -6,12 +6,14 @@
|
||||
<meta name="robots" content="noindex, nofollow">
|
||||
<link rel="stylesheet" href="/admin.css?v=2">
|
||||
<% if content_for?(:replay_archive_styles) %>
|
||||
<link rel="stylesheet" href="/marketing.css?v=1">
|
||||
<link rel="stylesheet" href="/marketing.css?v=42">
|
||||
<% end %>
|
||||
<% if controller_name == "dashboard" %>
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js" crossorigin="anonymous"></script>
|
||||
<script src="/admin-dashboard.js?v=1" defer></script>
|
||||
<% end %>
|
||||
<link rel="stylesheet" href="/confirm-forms.css?v=4">
|
||||
<script src="/confirm-forms.js?v=6" defer></script>
|
||||
</head>
|
||||
<body class="<%= content_for?(:body_class) ? yield(:body_class) : 'admin-body' %>">
|
||||
<header class="admin-header">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="it">
|
||||
<html lang="<%= html_lang %>">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
@@ -8,7 +8,7 @@
|
||||
<%= render "shared/meta_tags" %>
|
||||
<%= yield :head %>
|
||||
<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=38">
|
||||
<link rel="stylesheet" href="/marketing.css?v=44">
|
||||
</head>
|
||||
<body<% if MatchLiveTv.google_analytics_configured? %> data-ga-id="<%= MatchLiveTv.google_analytics_measurement_id %>"<% end %>>
|
||||
<%= render "shared/cookie_banner" %>
|
||||
@@ -22,6 +22,8 @@
|
||||
<script src="/branding-form.js?v=1" defer></script>
|
||||
<script src="/roster-form.js?v=1" defer></script>
|
||||
<script src="/password-toggle.js?v=2" defer></script>
|
||||
<link rel="stylesheet" href="/confirm-forms.css?v=4">
|
||||
<script src="/confirm-forms.js?v=6" defer></script>
|
||||
<script src="/cookie-consent.js?v=1" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="it">
|
||||
<html lang="<%= html_lang %>">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title><%= content_for?(:title) ? yield(:title) : "Match Live TV" %></title>
|
||||
<%= render "shared/meta_tags" %>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css" integrity="sha512-SnH5WK+bZxgPHs44uWIX+LLJAJ9/2PkPKZ5QiAj6Ta86w+fsb2TkcmfRyVX3pBnMFcV7oQPJkl9QevSCWr3W6A==" crossorigin="anonymous" referrerpolicy="no-referrer">
|
||||
<link rel="stylesheet" href="/marketing.css?v=38">
|
||||
<link rel="stylesheet" href="/marketing.css?v=44">
|
||||
<link rel="stylesheet" href="/live.css?v=26">
|
||||
<%= yield :head %>
|
||||
</head>
|
||||
@@ -17,6 +17,8 @@
|
||||
<%= yield %>
|
||||
</main>
|
||||
<%= render "shared/marketing_footer" %>
|
||||
<link rel="stylesheet" href="/confirm-forms.css?v=4">
|
||||
<script src="/confirm-forms.js?v=6" defer></script>
|
||||
<script src="/cookie-consent.js?v=1" defer></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -37,9 +37,9 @@
|
||||
<%= link_to "Prezzi", public_pricing_path %>
|
||||
<% if logged_in? %>
|
||||
<% if current_user.primary_club %>
|
||||
· <%= link_to "Società", public_club_path(current_user.primary_club) %>
|
||||
· <%= link_to "La mia società", public_club_path(current_user.primary_club) %>
|
||||
<% elsif current_user.manageable_teams.any? %>
|
||||
· <%= link_to "Dettagli squadra", public_team_details_path(current_user.manageable_teams.first) %>
|
||||
· <%= link_to "La mia squadra", public_team_details_path(current_user.manageable_teams.first) %>
|
||||
<% end %>
|
||||
· <%= button_to "Esci", public_logout_path, method: :delete, form: { style: "display:inline" }, class: "btn btn-secondary", style: "padding:6px 12px;font-size:0.85rem" %>
|
||||
<% else %>
|
||||
|
||||
@@ -74,6 +74,8 @@
|
||||
<% end %>
|
||||
</td>
|
||||
<td style="white-space:nowrap">
|
||||
<%= link_to "Pagina pubblica", public_team_page_path(team.slug), target: "_blank", rel: "noopener" %>
|
||||
·
|
||||
<%= link_to "Dettagli", public_team_details_path(team) %>
|
||||
·
|
||||
<%= link_to "Partite", public_team_matches_path(team) %>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<% content_for :title, "Match Live TV — Diretta live partite giovanili da telefono" %>
|
||||
<% content_for :meta_description, "Non puoi andare in palestra? Guarda la diretta live della partita di tuo figlio dal telefono o dal computer, senza installare app. Pallavolo, calcio e sport giovanili: link da condividere con nonni e parenti, archivio se te la perdi." %>
|
||||
<% content_for :title, t("home.title") %>
|
||||
<% content_for :meta_description, t("home.meta_description") %>
|
||||
<% content_for :canonical_url, seo_absolute_url(root_path) %>
|
||||
<% content_for :head do %>
|
||||
<script type="application/ld+json">
|
||||
@@ -10,7 +10,7 @@
|
||||
"url" => seo_absolute_url(root_path),
|
||||
"applicationCategory" => "SportsApplication",
|
||||
"operatingSystem" => "Web, Android, iOS",
|
||||
"description" => "Piattaforma per dirette live e archivio partite delle società sportive giovanili."
|
||||
"description" => t("home.schema_description")
|
||||
}.to_json) %>
|
||||
</script>
|
||||
<% end %>
|
||||
@@ -20,104 +20,94 @@
|
||||
<div class="hero-content">
|
||||
<p class="hero-app-name">Match Live TV</p>
|
||||
<p class="hero-live-badge"><span class="hero-live-dot" aria-hidden="true"></span> LIVE</p>
|
||||
<h1>Ogni partita, ogni evento, per chi <span class="hero-accent">non può esserci.</span></h1>
|
||||
<p class="tagline">
|
||||
Trasmetti in diretta dallo smartphone. I nonni, i parenti lontani, gli amici —
|
||||
guardano dal browser, senza installare nulla.
|
||||
E se te la sei persa, la ritrovi nell'archivio.
|
||||
</p>
|
||||
<h1><%= raw t("home.headline_html") %></h1>
|
||||
<p class="tagline"><%= t("home.tagline") %></p>
|
||||
<div class="hero-cta">
|
||||
<%= link_to "Registra la tua squadra", public_signup_path, class: "btn btn-primary" %>
|
||||
<%= link_to "Guarda le dirette", public_live_index_path, class: "btn btn-secondary" %>
|
||||
<%= link_to t("home.cta_signup"), public_signup_path, class: "btn btn-primary" %>
|
||||
<%= link_to t("home.cta_live"), public_live_index_path, class: "btn btn-secondary" %>
|
||||
</div>
|
||||
<ul class="hero-features" aria-label="Vantaggi principali">
|
||||
<ul class="hero-features" aria-label="<%= t('home.features_aria') %>">
|
||||
<li class="hero-feature-item">
|
||||
<span class="hero-feature-icon" aria-hidden="true"><i class="fa-solid fa-tower-broadcast"></i></span>
|
||||
<span class="hero-feature-label">Dirette stabili</span>
|
||||
<span class="hero-feature-label"><%= t("home.feature_stable") %></span>
|
||||
</li>
|
||||
<li class="hero-feature-item">
|
||||
<span class="hero-feature-icon" aria-hidden="true"><i class="fa-solid fa-cloud"></i></span>
|
||||
<span class="hero-feature-label">Archivio sicuro</span>
|
||||
<span class="hero-feature-label"><%= t("home.feature_archive") %></span>
|
||||
</li>
|
||||
<li class="hero-feature-item">
|
||||
<span class="hero-feature-icon" aria-hidden="true"><i class="fa-solid fa-user-group"></i></span>
|
||||
<span class="hero-feature-label">Condividi con chi vuoi</span>
|
||||
<span class="hero-feature-label"><%= t("home.feature_share") %></span>
|
||||
</li>
|
||||
<li class="hero-feature-item hero-feature-item--last">
|
||||
<span class="hero-feature-icon" aria-hidden="true"><i class="fa-solid fa-mobile-screen"></i></span>
|
||||
<span class="hero-feature-label">Tutto dal tuo telefono</span>
|
||||
<span class="hero-feature-label"><%= t("home.feature_phone") %></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="hero-visual">
|
||||
<%= image_tag "/hero-devices.png", alt: "Smartphone su treppiede per la diretta e secondo telefono per il punteggio in palestra", class: "hero-devices-img", loading: "eager", fetchpriority: "high" %>
|
||||
<%= image_tag "/hero-devices.png", alt: t("home.hero_alt"), class: "hero-devices-img", loading: "eager", fetchpriority: "high" %>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section wrap">
|
||||
<h2>Come funziona lo streaming per squadre giovanili</h2>
|
||||
<h2><%= t("home.how_title") %></h2>
|
||||
<div class="steps">
|
||||
<div class="step step--visual">
|
||||
<div class="step-visual">
|
||||
<%= image_tag "/home-step-registra-squadra.png?v=1", alt: "Registrazione squadra: scudo, pallone e modulo con email, password e nome squadra", class: "step-visual-img", loading: "lazy" %>
|
||||
<%= image_tag "/home-step-registra-squadra.png?v=1", alt: t("home.step1_title"), class: "step-visual-img", loading: "lazy" %>
|
||||
</div>
|
||||
<div class="step-num">01</div>
|
||||
<h3>Registra la squadra</h3>
|
||||
<p>La società si iscrive sul sito e sceglie il piano più adatto.</p>
|
||||
<h3><%= t("home.step1_title") %></h3>
|
||||
<p><%= t("home.step1_body") %></p>
|
||||
</div>
|
||||
<div class="step step--visual">
|
||||
<div class="step-visual">
|
||||
<%= image_tag "/home-step-invita-trasmette.png?v=1", alt: "Invito a trasmettere: smartphone con invio email e busta con accetta invito", class: "step-visual-img", loading: "lazy" %>
|
||||
<%= image_tag "/home-step-invita-trasmette.png?v=1", alt: t("home.step2_title"), class: "step-visual-img", loading: "lazy" %>
|
||||
</div>
|
||||
<div class="step-num">02</div>
|
||||
<h3>Invita chi trasmette</h3>
|
||||
<p>Coach e volontari ricevono un invito: ognuno accede con la propria email.</p>
|
||||
<h3><%= t("home.step2_title") %></h3>
|
||||
<p><%= t("home.step2_body") %></p>
|
||||
</div>
|
||||
<div class="step step--visual">
|
||||
<div class="step-visual">
|
||||
<%= image_tag "/home-step-vai-in-diretta.png?v=1", alt: "Vai in diretta: smartphone su treppiede, punteggio live e condivisione link", class: "step-visual-img", loading: "lazy" %>
|
||||
<%= image_tag "/home-step-vai-in-diretta.png?v=1", alt: t("home.step3_title"), class: "step-visual-img", loading: "lazy" %>
|
||||
</div>
|
||||
<div class="step-num">03</div>
|
||||
<h3>Vai in diretta</h3>
|
||||
<p>Dal telefono si avvia la partita: punteggio aggiornato e link da condividere con le famiglie.</p>
|
||||
<h3><%= t("home.step3_title") %></h3>
|
||||
<p><%= t("home.step3_body") %></p>
|
||||
</div>
|
||||
<div class="step step--visual">
|
||||
<div class="step-visual">
|
||||
<%= image_tag "/home-step-tutti-guardano.png?v=2", alt: "Tutti guardano da casa: archivio partite, partita salvata e link per genitori e parenti", class: "step-visual-img", loading: "lazy" %>
|
||||
<%= image_tag "/home-step-tutti-guardano.png?v=2", alt: t("home.step4_title"), class: "step-visual-img", loading: "lazy" %>
|
||||
</div>
|
||||
<div class="step-num">04</div>
|
||||
<h3>Tutti guardano da casa</h3>
|
||||
<p>Genitori, nonni e parenti aprono il link nel browser. La partita resta anche in archivio.</p>
|
||||
<h3><%= t("home.step4_title") %></h3>
|
||||
<p><%= t("home.step4_body") %></p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section wrap plans-teaser">
|
||||
<h2>Piani per dirette live e archivio partite</h2>
|
||||
<p class="plans-teaser-lead">Inizia gratis. Passa a Premium quando vuoi più partite in contemporanea, archivio più lungo e diretta anche su YouTube.</p>
|
||||
<h2><%= t("home.plans_title") %></h2>
|
||||
<p class="plans-teaser-lead"><%= t("home.plans_lead") %></p>
|
||||
<div class="plans-teaser-visual">
|
||||
<%= image_tag "/home-piani-ecosistema.png?v=1", alt: "Ecosistema Match Live TV: smartphone in diretta, laptop con live e statistiche, tablet con archivio partite e cloud", class: "plans-teaser-img", loading: "lazy" %>
|
||||
<%= image_tag "/home-piani-ecosistema.png?v=1", alt: t("home.plans_alt"), class: "plans-teaser-img", loading: "lazy" %>
|
||||
</div>
|
||||
<%= link_to "Confronta i piani", public_prezzi_path, class: "btn btn-primary" %>
|
||||
<%= link_to t("home.plans_cta"), public_prezzi_path, class: "btn btn-primary" %>
|
||||
</section>
|
||||
|
||||
<section class="section wrap seo-prose">
|
||||
<h2>Streaming partite giovanili: perché le famiglie scelgono Match Live TV</h2>
|
||||
<h2><%= t("home.seo_title") %></h2>
|
||||
<p><%= raw t("home.seo_p1_html") %></p>
|
||||
<p><%= raw t("home.seo_p2_html") %></p>
|
||||
<p>
|
||||
Allenatori e dirigenti cercano un modo semplice per mandare in <strong>diretta live</strong> le partite
|
||||
di <strong>pallavolo giovanile</strong>, calcio, basket e altri sport: senza attrezzature da TV,
|
||||
solo con lo smartphone in palestra. Match Live TV è pensato per le società dilettantistiche che vogliono
|
||||
far guardare <strong>Under 14, Under 16, Under 18</strong> e settori giovanili a chi è lontano.
|
||||
</p>
|
||||
<p>
|
||||
Un genitore che lavora, un nonno che non può viaggiare, un parente all’estero: aprono un link e seguono
|
||||
la partita dal browser. Se arrivano in ritardo, la <strong>registrazione in archivio</strong> (con i piani Premium)
|
||||
resta disponibile per giorni. Niente account complicati per chi guarda — solo per lo staff che trasmette.
|
||||
</p>
|
||||
<p>
|
||||
Hai dubbi su come funziona? Leggi le <%= link_to "domande frequenti", public_faq_path %>,
|
||||
scopri <%= link_to "Match Live TV per la pallavolo giovanile", public_pallavolo_path %>
|
||||
o <%= link_to "registra la squadra gratis", public_signup_path %>.
|
||||
<%= raw t(
|
||||
"home.seo_p3_html",
|
||||
faq_link: link_to(t("home.seo_faq_link"), public_faq_path),
|
||||
volleyball_link: link_to(t("home.seo_volleyball_link"), public_pallavolo_path),
|
||||
signup_link: link_to(t("home.seo_signup_link"), public_signup_path)
|
||||
) %>
|
||||
</p>
|
||||
</section>
|
||||
|
||||
@@ -0,0 +1,129 @@
|
||||
<% content_for :title, "Squadre su Match Live TV — Dirette e replay sport giovanili" %>
|
||||
<% content_for :meta_description, "Scopri le squadre sportive giovanili che trasmettono su Match Live TV. Cerca per nome, filtra per sport, trova dirette in corso e archivi replay." %>
|
||||
<% content_for :canonical_url, seo_absolute_url(public_team_pages_path(filter_params_for_canonical)) %>
|
||||
|
||||
<div class="wrap team-directory">
|
||||
<h1>Squadre su Match Live TV</h1>
|
||||
<p class="results-hint">
|
||||
Società e squadre con dirette, replay pubblici o partite in programma.
|
||||
Cerca la tua squadra e apri la pagina per seguire calendario e archivio.
|
||||
</p>
|
||||
|
||||
<%= form_with url: public_team_pages_path, method: :get, local: true, class: "team-directory-filters" do %>
|
||||
<div class="search-form team-directory-filters__search">
|
||||
<input
|
||||
type="search"
|
||||
name="q"
|
||||
value="<%= @query %>"
|
||||
placeholder="Nome squadra, società, città o palestra…"
|
||||
aria-label="Cerca squadra"
|
||||
autocomplete="off"
|
||||
/>
|
||||
<button type="submit" class="btn btn-primary">Cerca</button>
|
||||
</div>
|
||||
|
||||
<div class="team-directory-filters__row">
|
||||
<div class="team-directory-filters__field">
|
||||
<label for="sport" class="team-directory-filters__label">Sport</label>
|
||||
<select name="sport" id="sport" class="team-directory-filters__select" onchange="this.form.requestSubmit()">
|
||||
<option value="">Tutti gli sport</option>
|
||||
<% @sport_options.each do |sport| %>
|
||||
<option value="<%= sport[:key] %>"<%= " selected" if @sport == sport[:key] %>><%= sport[:label] %></option>
|
||||
<% end %>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="team-directory-filters__field team-directory-filters__field--chips">
|
||||
<span class="team-directory-filters__label" id="team-filter-chips-label">Mostra</span>
|
||||
<div class="team-directory-chips" role="group" aria-labelledby="team-filter-chips-label">
|
||||
<label class="filter-chip<%= " is-active" if @live_filter %>">
|
||||
<%= check_box_tag :live, "1", @live_filter, id: "filter_live", class: "filter-chip__input", onchange: "this.form.requestSubmit()" %>
|
||||
<span class="filter-chip__face" aria-hidden="true">
|
||||
<span class="filter-chip__dot filter-chip__dot--live"></span>
|
||||
In diretta
|
||||
</span>
|
||||
</label>
|
||||
<label class="filter-chip<%= " is-active" if @replays_filter %>">
|
||||
<%= check_box_tag :replays, "1", @replays_filter, id: "filter_replays", class: "filter-chip__input", onchange: "this.form.requestSubmit()" %>
|
||||
<span class="filter-chip__face" aria-hidden="true">
|
||||
<span class="filter-chip__icon">▶</span>
|
||||
Con replay
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<% if @query.present? || @sport.present? || @live_filter || @replays_filter %>
|
||||
<div class="team-directory-filters__actions">
|
||||
<span class="team-directory-filters__label team-directory-filters__label--spacer" aria-hidden="true"> </span>
|
||||
<%= link_to "Azzera", public_team_pages_path, class: "btn btn-secondary team-directory-filters__reset" %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<% if @query.present? || @sport.present? || @live_filter || @replays_filter %>
|
||||
<p class="results-hint">
|
||||
<%= @entries.size %> squadre trovate
|
||||
<% if @query.present? %> per «<%= h @query %>»<% end %>
|
||||
<% if @sport.present? %> · <%= Sports::Catalog.find_optional(@sport)&.dig(:label) || @sport %><% end %>
|
||||
</p>
|
||||
<% else %>
|
||||
<p class="results-hint"><%= @entries.size %> squadre attive</p>
|
||||
<% end %>
|
||||
|
||||
<% if @entries.any? %>
|
||||
<div class="team-directory-grid">
|
||||
<% @entries.each do |entry| %>
|
||||
<% team = entry.team %>
|
||||
<% club = team.club %>
|
||||
<% logo = team.effective_logo_url.presence || club.effective_logo_url %>
|
||||
<%= link_to public_team_page_path(team.slug), class: "team-directory-card" do %>
|
||||
<div class="team-directory-card__media">
|
||||
<% if team.team_photo_url.present? %>
|
||||
<%= image_tag team.team_photo_url, alt: "", class: "team-directory-card__photo", loading: "lazy" %>
|
||||
<% elsif logo.present? %>
|
||||
<%= image_tag logo, alt: "", class: "team-directory-card__photo team-directory-card__photo--logo", loading: "lazy" %>
|
||||
<% else %>
|
||||
<div class="team-directory-card__photo team-directory-card__photo--fallback" aria-hidden="true">
|
||||
<%= team.name.first(2).upcase %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<div class="team-directory-card__body">
|
||||
<p class="team-directory-card__club"><%= club.name %></p>
|
||||
<h2 class="team-directory-card__name"><%= team.name %></h2>
|
||||
<p class="team-directory-card__meta"><%= team.sport_label %></p>
|
||||
<div class="team-directory-card__badges">
|
||||
<% if entry.live_now %>
|
||||
<span class="badge badge-on-air">In diretta</span>
|
||||
<% end %>
|
||||
<% if entry.upcoming_count.positive? %>
|
||||
<span class="badge badge-scheduled"><%= entry.upcoming_count %> in programma</span>
|
||||
<% end %>
|
||||
<% if entry.replay_count.positive? %>
|
||||
<span class="badge badge-wait"><%= entry.replay_count %> replay</span>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% else %>
|
||||
<div class="empty-state">
|
||||
<% if @query.present? || @sport.present? || @live_filter || @replays_filter %>
|
||||
<p><strong>Nessuna squadra trovata</strong> con i filtri selezionati.</p>
|
||||
<p><%= link_to "Mostra tutte le squadre attive", public_team_pages_path, class: "btn btn-secondary" %></p>
|
||||
<% else %>
|
||||
<p><strong>Nessuna squadra attiva al momento.</strong></p>
|
||||
<p>Quando una società avvierà dirette o pubblicherà replay, comparirà qui.</p>
|
||||
<%= link_to "Vai alle dirette live", public_live_index_path, class: "btn btn-secondary", style: "margin-top:12px;display:inline-block" %>
|
||||
<% end %>
|
||||
</div>
|
||||
<% end %>
|
||||
|
||||
<p class="team-directory-footer muted">
|
||||
<%= link_to "Dirette in corso", public_live_index_path %> ·
|
||||
<%= link_to "Archivio replay", public_replay_index_path %>
|
||||
</p>
|
||||
</div>
|
||||
@@ -0,0 +1,175 @@
|
||||
<% content_for :title, "#{@team.name} — #{@club.name} | Match Live TV" %>
|
||||
<% content_for :meta_description, team_page_meta_description(@team, @club) %>
|
||||
<% content_for :canonical_url, seo_absolute_url(public_team_page_path(@team.slug)) %>
|
||||
<% content_for :head do %>
|
||||
<script type="application/ld+json">
|
||||
<%= raw(team_page_structured_data(@team, @club).to_json) %>
|
||||
</script>
|
||||
<% end %>
|
||||
|
||||
<div class="wrap team-public-page">
|
||||
<nav class="team-dashboard-nav" aria-label="Percorso">
|
||||
<%= link_to "← Tutte le squadre", public_team_pages_path, class: "team-dashboard-nav__club" %>
|
||||
</nav>
|
||||
|
||||
<header class="roster-hero card team-public-hero" style="--club-primary:<%= @team.effective_primary_color %>;--club-secondary:<%= @team.effective_secondary_color %>">
|
||||
<div class="roster-hero__intro">
|
||||
<p class="roster-hero__club"><%= @club.name %></p>
|
||||
<h1 class="roster-hero__title"><%= @team.name %></h1>
|
||||
<p class="team-public-meta">
|
||||
<%= @team.sport_label %>
|
||||
<% if @live_sessions.any? %>
|
||||
· <span class="hero-live-dot" aria-hidden="true"></span> <strong>In diretta ora</strong>
|
||||
<% end %>
|
||||
</p>
|
||||
<% if @team.description.present? %>
|
||||
<div class="roster-hero__desc"><%= simple_format @team.description %></div>
|
||||
<% end %>
|
||||
<div class="team-public-actions">
|
||||
<% if @live_sessions.any? %>
|
||||
<%= link_to "Guarda la diretta", public_live_path(@live_sessions.first), class: "btn btn-primary" %>
|
||||
<% end %>
|
||||
<%= link_to "Tutte le dirette", public_live_index_path(club_id: @club.id), class: "btn btn-secondary" %>
|
||||
<% if @recordings.any? %>
|
||||
<%= link_to "Archivio replay", public_replay_index_path(team_id: @team.id), class: "btn btn-secondary" %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
<div class="roster-hero__media">
|
||||
<% logo = @team.effective_logo_url.presence || @club.effective_logo_url %>
|
||||
<% if @team.team_photo_url.present? %>
|
||||
<%= image_tag @team.team_photo_url, alt: @team.name, class: "roster-hero__photo" %>
|
||||
<% elsif logo.present? %>
|
||||
<%= image_tag logo, alt: @team.name, class: "roster-hero__photo roster-hero__photo--logo" %>
|
||||
<% else %>
|
||||
<div class="roster-hero__photo roster-hero__photo--empty team-public-logo-fallback" aria-hidden="true">
|
||||
<span><%= @team.name.first(2).upcase %></span>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<% if @live_sessions.any? %>
|
||||
<section class="team-public-section" aria-labelledby="team-live-heading">
|
||||
<h2 id="team-live-heading" class="section-heading">In diretta adesso</h2>
|
||||
<div class="live-grid">
|
||||
<% @live_sessions.each do |session| %>
|
||||
<% match = session.match %>
|
||||
<% on_air = @online_paths.include?(session.mediamtx_path_name) %>
|
||||
<article class="live-card">
|
||||
<%= live_match_card_heading(match, link_team: false) %>
|
||||
<p class="meta">
|
||||
<% if match.location.present? %><%= match.location %> · <% end %>
|
||||
Match Live TV
|
||||
</p>
|
||||
<% if session.score_state %>
|
||||
<p class="card-score">
|
||||
<span class="card-sets"><%= live_score_sets_label(session.score_state, match) %></span>
|
||||
<% if live_score_partials_label(session.score_state).present? %>
|
||||
<span class="card-partials">Parziali: <%= live_score_partials_label(session.score_state) %></span>
|
||||
<% end %>
|
||||
<span class="card-points"><%= session.score_state.home_points %> - <%= session.score_state.away_points %></span>
|
||||
</p>
|
||||
<% end %>
|
||||
<div class="badges">
|
||||
<% if on_air %>
|
||||
<span class="badge badge-on-air">In onda</span>
|
||||
<% elsif session.paused? %>
|
||||
<span class="badge badge-connecting">In pausa</span>
|
||||
<% else %>
|
||||
<span class="badge badge-live">Live</span>
|
||||
<% end %>
|
||||
</div>
|
||||
<%= link_to "Guarda diretta →", public_live_path(session), class: "btn-watch" %>
|
||||
</article>
|
||||
<% end %>
|
||||
</div>
|
||||
</section>
|
||||
<% end %>
|
||||
|
||||
<% if @upcoming_matches.any? %>
|
||||
<section class="team-public-section" aria-labelledby="team-upcoming-heading">
|
||||
<h2 id="team-upcoming-heading" class="section-heading<%= " section-heading--spaced" if @live_sessions.any? %>">Prossime partite</h2>
|
||||
<p class="results-hint">Programmate dal club: la diretta partirà quando lo staff avvierà la trasmissione.</p>
|
||||
<div class="live-grid upcoming-grid">
|
||||
<% @upcoming_matches.each do |match| %>
|
||||
<article class="live-card live-card--upcoming">
|
||||
<h3 class="live-card__title">
|
||||
<span class="live-card__matchup"><%= @team.name %> vs <%= match.opponent_name %></span>
|
||||
</h3>
|
||||
<p class="meta">
|
||||
<% if match.location.present? %><%= match.location %> · <% end %>
|
||||
<%= match.category.presence || @team.sport_label %>
|
||||
</p>
|
||||
<p class="upcoming-when"><%= live_scheduled_relative(match.scheduled_at) %></p>
|
||||
<div class="badges">
|
||||
<span class="badge badge-scheduled">In programma</span>
|
||||
</div>
|
||||
</article>
|
||||
<% end %>
|
||||
</div>
|
||||
</section>
|
||||
<% end %>
|
||||
|
||||
<% if @recordings.any? %>
|
||||
<section class="team-public-section" aria-labelledby="team-replays-heading">
|
||||
<h2 id="team-replays-heading" class="section-heading section-heading--spaced">Replay recenti</h2>
|
||||
<div class="replay-grid">
|
||||
<% @recordings.each do |rec| %>
|
||||
<% match = rec.stream_session.match %>
|
||||
<%= link_to public_replay_path(rec.stream_session_id), class: "replay-card" do %>
|
||||
<div class="replay-card__media">
|
||||
<% if rec.thumbnail_url %>
|
||||
<img src="<%= rec.thumbnail_url %>" alt="" class="replay-card__thumb" loading="lazy" width="320" height="180">
|
||||
<% else %>
|
||||
<div class="replay-card__thumb replay-card__thumb--placeholder" aria-hidden="true">
|
||||
<span class="replay-card__placeholder-icon">▶</span>
|
||||
</div>
|
||||
<% end %>
|
||||
<span class="replay-card__duration"><%= rec.duration_label %></span>
|
||||
<span class="replay-card__play" aria-hidden="true">▶</span>
|
||||
</div>
|
||||
<div class="replay-card__body">
|
||||
<strong class="replay-card__title"><%= rec.title_or_default %></strong>
|
||||
<p class="replay-card__meta"><%= l_local(rec.recorded_at_or_fallback) %></p>
|
||||
</div>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
<p class="team-public-more">
|
||||
<%= link_to "Vedi tutti i replay di #{@team.name} →", public_replay_index_path(team_id: @team.id) %>
|
||||
</p>
|
||||
</section>
|
||||
<% end %>
|
||||
|
||||
<% if @roster_by_category %>
|
||||
<section class="team-public-section" aria-labelledby="team-roster-heading">
|
||||
<h2 id="team-roster-heading" class="section-heading section-heading--spaced">Organico</h2>
|
||||
<div class="team-public-roster">
|
||||
<% TeamRosterMember::DISPLAY_ORDER.each do |category| %>
|
||||
<% members = @roster_by_category[category] %>
|
||||
<% next if members.blank? %>
|
||||
<section class="roster-section card">
|
||||
<header class="roster-section__head">
|
||||
<h3 class="roster-section__title"><%= TeamRosterMember::CATEGORY_LABELS[category] %></h3>
|
||||
<span class="roster-section__count"><%= members.size %></span>
|
||||
</header>
|
||||
<div class="roster-list">
|
||||
<% members.each do |person| %>
|
||||
<%= render "shared/roster_person_card", person: person, team: @team, editable: false, compact: true %>
|
||||
<% end %>
|
||||
</div>
|
||||
</section>
|
||||
<% end %>
|
||||
</div>
|
||||
</section>
|
||||
<% end %>
|
||||
|
||||
<% if @live_sessions.empty? && @upcoming_matches.empty? && @recordings.empty? %>
|
||||
<div class="empty-state empty-state--soft team-public-empty">
|
||||
<p><strong>Nessuna diretta o replay al momento.</strong></p>
|
||||
<p>Torna a controllare prima delle prossime gare: qui compariranno dirette, calendario e archivio della squadra.</p>
|
||||
<%= link_to "Vedi tutte le dirette", public_live_index_path, class: "btn btn-secondary" %>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
@@ -55,6 +55,7 @@
|
||||
<div class="team-details-actions">
|
||||
<% if @can_manage %>
|
||||
<%= link_to "← Società", public_club_path(@club), class: "btn btn-secondary" %>
|
||||
<%= link_to "Pagina pubblica", public_team_page_path(@team.slug), class: "btn btn-secondary", target: "_blank", rel: "noopener" %>
|
||||
<%= link_to "Responsabili trasmissione", public_team_invite_path(@team), class: "btn btn-secondary" %>
|
||||
<% end %>
|
||||
<% if current_user.can_stream_for?(@team) %>
|
||||
|
||||
@@ -25,6 +25,21 @@
|
||||
</div>
|
||||
|
||||
<%= render "shared/branding_fields", record: @team, show_inherit_hint: true, legend: "Branding squadra (override)" %>
|
||||
|
||||
<%= label_tag "team[slug]", "Indirizzo pagina pubblica" %>
|
||||
<p class="branding-hint" style="margin-top:0">
|
||||
Genitori e tifosi trovano la squadra su
|
||||
<strong><%= MatchLiveTv.app_public_url.chomp("/") %>/squadre/<span id="team-slug-preview"><%= @team.slug %></span></strong>
|
||||
</p>
|
||||
<%= text_field_tag "team[slug]", @team.slug, pattern: "[a-z0-9]+(-[a-z0-9]+)*", title: "Solo lettere minuscole, numeri e trattini" %>
|
||||
|
||||
<%= label_tag "team[roster_public]", class: "checkbox-label", style: "display:flex;align-items:center;gap:8px;margin-top:16px" do %>
|
||||
<%= hidden_field_tag "team[roster_public]", "0" %>
|
||||
<%= check_box_tag "team[roster_public]", "1", @team.roster_public?, id: "team_roster_public" %>
|
||||
Mostra l'organico sulla pagina pubblica
|
||||
<% end %>
|
||||
<p class="branding-hint">Se attivo, giocatori e staff sono visibili a chiunque abbia il link. Puoi lasciarlo disattivato per privacy.</p>
|
||||
|
||||
<p class="branding-hint">
|
||||
Colori società:
|
||||
<span class="color-swatch" style="background:<%= @club.effective_primary_color %>"></span>
|
||||
|
||||
@@ -135,29 +135,56 @@
|
||||
</td>
|
||||
<td class="replay-archive-table__col-status">
|
||||
<span class="replay-archive__status replay-archive__status--<%= rec.status %>"><%= rec.status_label %></span>
|
||||
<%= form_with url: paths.update.call(rec), method: :patch, local: true, class: "replay-archive__privacy-form" do %>
|
||||
<% filter_params.each { |key, value| concat hidden_field_tag(key, value) } %>
|
||||
<%= select_tag "recording[privacy_status]",
|
||||
options_for_select([["Pubblico", "public"], ["Privato", "unlisted"]], rec.privacy_status),
|
||||
class: "replay-archive__privacy-select",
|
||||
onchange: "this.form.submit()" %>
|
||||
<% end %>
|
||||
</td>
|
||||
<td class="replay-archive-table__col-actions">
|
||||
<div class="replay-archive__actions">
|
||||
<% next_privacy = rec.publicly_listed? ? "unlisted" : "public" %>
|
||||
<% privacy_label = rec.publicly_listed? ? "Pubblico" : "Privato" %>
|
||||
<% privacy_hint = rec.publicly_listed? ? "Visibile a tutti — clicca per rendere privato" : "Solo con link — clicca per rendere pubblico" %>
|
||||
<%= button_to paths.update.call(rec),
|
||||
method: :patch,
|
||||
params: filter_params.merge(recording: { privacy_status: next_privacy }),
|
||||
class: "replay-archive__privacy-toggle replay-archive__privacy-toggle--#{rec.privacy_status}",
|
||||
title: "#{privacy_label}: #{privacy_hint}",
|
||||
form: { class: "replay-archive__privacy-form" } do %>
|
||||
<% if rec.publicly_listed? %>
|
||||
<svg class="replay-archive__privacy-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
|
||||
<circle cx="12" cy="12" r="9" fill="none" stroke="currentColor" stroke-width="1.8"/>
|
||||
<path d="M3 12h18M12 3c2.8 2.6 4.2 5.6 4.2 9s-1.4 6.4-4.2 9c-2.8-2.6-4.2-5.6-4.2-9S9.2 5.6 12 3z" fill="none" stroke="currentColor" stroke-width="1.8"/>
|
||||
</svg>
|
||||
<% else %>
|
||||
<svg class="replay-archive__privacy-icon" viewBox="0 0 24 24" aria-hidden="true" focusable="false">
|
||||
<rect x="5" y="11" width="14" height="10" rx="2" fill="none" stroke="currentColor" stroke-width="1.8"/>
|
||||
<path d="M8 11V8a4 4 0 0 1 8 0v3" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/>
|
||||
</svg>
|
||||
<% end %>
|
||||
<span class="visually-hidden"><%= privacy_label %></span>
|
||||
<% end %>
|
||||
<% if ent.phone_download_enabled? && rec.ready? %>
|
||||
<%= link_to "MP4", public_replay_download_path(rec.stream_session_id), class: "replay-archive__action replay-archive__action--secondary", title: "Scarica MP4" %>
|
||||
<% end %>
|
||||
<% if ent.premium_full? && ent.youtube_enabled? && rec.ready? && rec.youtube_video_id.blank? %>
|
||||
<%= button_to "YT", paths.publish_youtube.call(rec), method: :post, class: "replay-archive__action replay-archive__action--secondary", title: "Pubblica su YouTube" %>
|
||||
<%= button_to "YT", paths.publish_youtube.call(rec), method: :post, class: "replay-archive__action replay-archive__action--secondary", title: "Pubblica su YouTube", form: { class: "replay-archive__action-form" } %>
|
||||
<% elsif rec.youtube_watch_url %>
|
||||
<%= link_to "YT", rec.youtube_watch_url, class: "replay-archive__action replay-archive__action--secondary", target: "_blank", rel: "noopener", title: "Apri su YouTube" %>
|
||||
<% end %>
|
||||
<% has_youtube = rec.youtube_video_id.present? && !rec.youtube_video_id.to_s.start_with?("mock_") %>
|
||||
<% has_site = rec.storage_key.present? || %w[ready processing failed].include?(rec.status) %>
|
||||
<% delete_confirm = "Scegli dove eliminare il replay. L'operazione è irreversibile." %>
|
||||
<%= button_to paths.destroy.call(rec), method: :delete,
|
||||
params: filter_params,
|
||||
class: "replay-archive__action replay-archive__action--danger",
|
||||
title: "Elimina replay",
|
||||
form: { data: { turbo_confirm: "Eliminare definitivamente questo replay? Verranno rimossi i file sul server e il video YouTube collegato." }, class: "replay-archive__action-form" } do %>
|
||||
form: {
|
||||
class: "replay-archive__action-form",
|
||||
data: {
|
||||
confirm: delete_confirm,
|
||||
turbo_confirm: delete_confirm,
|
||||
confirm_mode: "delete-replay",
|
||||
has_site: has_site ? "1" : "0",
|
||||
has_youtube: has_youtube ? "1" : "0"
|
||||
}
|
||||
} do %>
|
||||
✕
|
||||
<% end %>
|
||||
</div>
|
||||
|
||||
@@ -1,20 +1,21 @@
|
||||
<div id="cookie-banner" class="cookie-banner" role="dialog" aria-labelledby="cookie-banner-title" aria-describedby="cookie-banner-desc" hidden>
|
||||
<div class="cookie-banner__inner">
|
||||
<div class="cookie-banner__text">
|
||||
<p id="cookie-banner-title" class="cookie-banner__title">Cookie e privacy</p>
|
||||
<p id="cookie-banner-title" class="cookie-banner__title"><%= t("cookie.title") %></p>
|
||||
<p id="cookie-banner-desc" class="cookie-banner__desc">
|
||||
Usiamo cookie necessari per login e sicurezza. Con il tuo consenso attiviamo anche
|
||||
<strong>Google Analytics</strong> per statistiche aggregate sul sito.
|
||||
<%= link_to "Cookie policy", public_cookies_path, class: "cookie-banner__link" %>
|
||||
e <%= link_to "Privacy", public_privacy_path, class: "cookie-banner__link" %>.
|
||||
<%= raw t(
|
||||
"cookie.body_html",
|
||||
cookie_link: link_to(t("cookie.policy"), public_cookies_path, class: "cookie-banner__link"),
|
||||
privacy_link: link_to(t("cookie.privacy"), public_privacy_path, class: "cookie-banner__link")
|
||||
) %>
|
||||
</p>
|
||||
</div>
|
||||
<div class="cookie-banner__actions">
|
||||
<button type="button" class="btn btn-secondary cookie-banner__btn" data-cookie-reject>
|
||||
Solo necessari
|
||||
<%= t("cookie.reject") %>
|
||||
</button>
|
||||
<button type="button" class="btn btn-primary cookie-banner__btn" data-cookie-accept-all>
|
||||
Accetta tutti
|
||||
<%= t("cookie.accept_all") %>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
<%# Selettore lingua a bandiere (allineato a destra nel menu) %>
|
||||
<% current = current_language_option %>
|
||||
<div class="lang-switcher" data-lang-switcher>
|
||||
<button
|
||||
type="button"
|
||||
class="lang-switcher__toggle"
|
||||
id="lang-switcher-toggle"
|
||||
aria-haspopup="listbox"
|
||||
aria-expanded="false"
|
||||
aria-controls="lang-switcher-menu"
|
||||
aria-label="<%= t('language.choose') %>: <%= current[:native] %>"
|
||||
title="<%= current[:native] %>"
|
||||
>
|
||||
<span class="lang-switcher__flag" aria-hidden="true"><%= current[:flag] %></span>
|
||||
<span class="lang-switcher__chevron" aria-hidden="true"></span>
|
||||
</button>
|
||||
|
||||
<div
|
||||
class="lang-switcher__menu"
|
||||
id="lang-switcher-menu"
|
||||
role="listbox"
|
||||
aria-label="<%= t('language.label') %>"
|
||||
hidden
|
||||
>
|
||||
<% language_options.each do |opt| %>
|
||||
<% selected = opt[:code].to_s == current_locale.to_s %>
|
||||
<%= button_to public_locale_path,
|
||||
method: :patch,
|
||||
params: { locale: opt[:code] },
|
||||
class: "lang-switcher__option#{' is-active' if selected}",
|
||||
form: { class: "lang-switcher__option-form" },
|
||||
role: "option",
|
||||
aria: { selected: selected, label: opt[:native] },
|
||||
title: opt[:native] do %>
|
||||
<span class="lang-switcher__flag" aria-hidden="true"><%= opt[:flag] %></span>
|
||||
<span class="visually-hidden"><%= opt[:native] %></span>
|
||||
<% end %>
|
||||
<% end %>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
(function () {
|
||||
var root = document.querySelector("[data-lang-switcher]");
|
||||
if (!root || root.dataset.bound === "1") return;
|
||||
root.dataset.bound = "1";
|
||||
|
||||
var toggle = root.querySelector(".lang-switcher__toggle");
|
||||
var menu = root.querySelector(".lang-switcher__menu");
|
||||
if (!toggle || !menu) return;
|
||||
|
||||
function setOpen(open) {
|
||||
menu.hidden = !open;
|
||||
toggle.setAttribute("aria-expanded", open ? "true" : "false");
|
||||
root.classList.toggle("is-open", open);
|
||||
}
|
||||
|
||||
toggle.addEventListener("click", function (e) {
|
||||
e.stopPropagation();
|
||||
setOpen(menu.hidden);
|
||||
});
|
||||
|
||||
document.addEventListener("click", function (e) {
|
||||
if (!root.contains(e.target)) setOpen(false);
|
||||
});
|
||||
|
||||
document.addEventListener("keydown", function (e) {
|
||||
if (e.key === "Escape") setOpen(false);
|
||||
});
|
||||
})();
|
||||
</script>
|
||||
@@ -1,20 +1,20 @@
|
||||
<footer class="site-footer">
|
||||
<div class="wrap">
|
||||
<div>
|
||||
<strong style="color:#fff">Match Live TV</strong> — Ogni partita, ogni evento, per chi non può esserci
|
||||
<strong style="color:#fff">Match Live TV</strong> — <%= t("footer.tagline") %>
|
||||
</div>
|
||||
<div>
|
||||
<%= link_to "Prezzi", public_prezzi_path %> ·
|
||||
<%= link_to "Dirette", public_live_index_path %> ·
|
||||
<%= link_to "FAQ", public_faq_path %> ·
|
||||
<%= link_to "Privacy", public_privacy_path %> ·
|
||||
<%= link_to "Cookie", public_cookies_path %> ·
|
||||
<%= link_to "Termini", public_termini_path %>
|
||||
· <button type="button" class="footer-link-btn" data-cookie-manage>Gestisci cookie</button>
|
||||
<%= link_to t("common.pricing"), public_prezzi_path %> ·
|
||||
<%= link_to t("footer.live"), public_live_index_path %> ·
|
||||
<%= link_to t("common.faq"), public_faq_path %> ·
|
||||
<%= link_to t("common.privacy"), public_privacy_path %> ·
|
||||
<%= link_to t("common.cookies"), public_cookies_path %> ·
|
||||
<%= link_to t("common.terms"), public_termini_path %>
|
||||
· <button type="button" class="footer-link-btn" data-cookie-manage><%= t("footer.manage_cookies") %></button>
|
||||
</div>
|
||||
<div class="site-footer__legal">
|
||||
<p>© 2026 Emiliano Frascaro – P. IVA 14230270960</p>
|
||||
<p>I contenuti trasmessi sono di esclusiva responsabilità delle società sportive che li pubblicano.</p>
|
||||
<p><%= t("footer.copyright") %></p>
|
||||
<p><%= t("footer.responsibility") %></p>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -7,35 +7,41 @@
|
||||
<span class="brand" aria-hidden="true">Match <span>Live TV</span></span>
|
||||
<% end %>
|
||||
|
||||
<button type="button" class="nav-toggle" aria-label="Apri menu" aria-expanded="false" aria-controls="site-nav">
|
||||
<span class="nav-toggle-bar" aria-hidden="true"></span>
|
||||
<span class="nav-toggle-bar" aria-hidden="true"></span>
|
||||
<span class="nav-toggle-bar" aria-hidden="true"></span>
|
||||
</button>
|
||||
<div class="mast-tools">
|
||||
<button type="button" class="nav-toggle" aria-label="<%= t('nav.open_menu') %>" aria-expanded="false" aria-controls="site-nav">
|
||||
<span class="nav-toggle-bar" aria-hidden="true"></span>
|
||||
<span class="nav-toggle-bar" aria-hidden="true"></span>
|
||||
<span class="nav-toggle-bar" aria-hidden="true"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav id="site-nav" class="nav" aria-label="Menu principale" aria-hidden="true">
|
||||
<nav id="site-nav" class="nav" aria-label="<%= t('nav.main_menu') %>" aria-hidden="true">
|
||||
<div class="nav-panel">
|
||||
<%= link_to "Home", root_path, class: (request.path == "/" ? "nav-active" : nil) %>
|
||||
<%= link_to "Funzionalità", public_features_path, class: (request.path == "/funzionalita" ? "nav-active" : nil) %>
|
||||
<%= link_to "Prezzi", public_prezzi_path, class: (request.path == "/prezzi" ? "nav-active" : nil) %>
|
||||
<%= link_to "FAQ", public_faq_path, class: (request.path == "/faq" ? "nav-active" : nil) %>
|
||||
<%= link_to "Dirette live", public_live_index_path, class: (live_section ? "nav-active" : nil) %>
|
||||
<%= link_to t("nav.home"), root_path, class: (request.path == "/" ? "nav-active" : nil) %>
|
||||
<%= link_to t("nav.features"), public_features_path, class: (request.path == "/funzionalita" ? "nav-active" : nil) %>
|
||||
<%= link_to t("nav.pricing"), public_prezzi_path, class: (request.path == "/prezzi" ? "nav-active" : nil) %>
|
||||
<%= link_to t("nav.faq"), public_faq_path, class: (request.path == "/faq" ? "nav-active" : nil) %>
|
||||
<%= link_to t("nav.live"), public_live_index_path, class: (live_section ? "nav-active" : nil) %>
|
||||
<%= link_to t("nav.teams"), public_team_pages_path, class: (request.path.start_with?("/squadre") ? "nav-active" : nil) %>
|
||||
<div class="nav-actions">
|
||||
<% if logged_in? %>
|
||||
<% if current_user.primary_club || current_user.manageable_teams.any? %>
|
||||
<% if current_user.primary_club %>
|
||||
<%= link_to "Società", public_club_path(current_user.primary_club), class: "nav-link-item" %>
|
||||
<%= link_to t("nav.my_club"), public_club_path(current_user.primary_club), class: "nav-link-item" %>
|
||||
<% elsif current_user.manageable_teams.first %>
|
||||
<%= link_to "Dettagli squadra", public_team_details_path(current_user.manageable_teams.first), class: "nav-link-item" %>
|
||||
<%= link_to t("nav.my_team"), public_team_details_path(current_user.manageable_teams.first), class: "nav-link-item" %>
|
||||
<% end %>
|
||||
<% end %>
|
||||
<%= button_to "Esci", public_logout_path, method: :delete, class: "btn btn-secondary nav-btn" %>
|
||||
<%= button_to t("nav.logout"), public_logout_path, method: :delete, class: "btn btn-secondary nav-btn" %>
|
||||
<% else %>
|
||||
<%= link_to "Accedi", public_login_path, class: "nav-link-item" %>
|
||||
<%= link_to "Registra squadra", public_signup_path, class: "btn btn-primary nav-btn" %>
|
||||
<%= link_to t("nav.login"), public_login_path, class: "nav-link-item" %>
|
||||
<%= link_to t("nav.signup"), public_signup_path, class: "btn btn-primary nav-btn" %>
|
||||
<% end %>
|
||||
<div class="nav-lang">
|
||||
<%= render "shared/language_switcher" %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
@@ -50,12 +56,14 @@
|
||||
var backdrop = document.getElementById("nav-backdrop");
|
||||
var nav = document.getElementById("site-nav");
|
||||
if (!chrome || !toggle || !nav) return;
|
||||
var openLabel = <%= raw t("nav.open_menu").to_json %>;
|
||||
var closeLabel = <%= raw t("nav.close_menu").to_json %>;
|
||||
|
||||
function setOpen(open) {
|
||||
chrome.classList.toggle("nav-open", open);
|
||||
document.body.classList.toggle("nav-menu-open", open);
|
||||
toggle.setAttribute("aria-expanded", open ? "true" : "false");
|
||||
toggle.setAttribute("aria-label", open ? "Chiudi menu" : "Apri menu");
|
||||
toggle.setAttribute("aria-label", open ? closeLabel : openLabel);
|
||||
nav.setAttribute("aria-hidden", open ? "false" : "true");
|
||||
if (backdrop) backdrop.setAttribute("aria-hidden", open ? "false" : "true");
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<% description = content_for?(:meta_description) ? yield(:meta_description) : "Diretta live delle partite giovanili dal telefono: genitori e nonni guardano dal browser, senza app. Archivio partite per non perderle." %>
|
||||
<% page_title = content_for?(:title) ? yield(:title) : "Match Live TV — Diretta live partite giovanili" %>
|
||||
<% description = content_for?(:meta_description) ? yield(:meta_description) : t("meta.default_description") %>
|
||||
<% page_title = content_for?(:title) ? yield(:title) : t("meta.default_title") %>
|
||||
<meta name="description" content="<%= description %>">
|
||||
<meta name="application-name" content="Match Live TV">
|
||||
<meta name="robots" content="<%= seo_robots_content %>">
|
||||
@@ -9,9 +9,9 @@
|
||||
<meta property="og:description" content="<%= description %>">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="<%= seo_canonical_url %>">
|
||||
<meta property="og:locale" content="it_IT">
|
||||
<meta property="og:locale" content="<%= og_locale_tag %>">
|
||||
<meta property="og:image" content="<%= seo_og_image_url %>">
|
||||
<meta property="og:image:alt" content="Match Live TV — diretta live partite giovanili da smartphone">
|
||||
<meta property="og:image:alt" content="<%= t('meta.og_image_alt') %>">
|
||||
<meta name="twitter:card" content="summary_large_image">
|
||||
<meta name="twitter:title" content="<%= page_title %>">
|
||||
<meta name="twitter:description" content="<%= description %>">
|
||||
|
||||
Reference in New Issue
Block a user