feat: contactos editáveis, equipa com título/aptidões, mensalidades e reviews Google
- Contactos: nova página no backoffice (fitme3070_info) e 964 201 137 como telefone principal; o telefone secundário só aparece quando existe. - Equipa: título, aptidões/funções e formações; o cartão mostra foto, nome e título e abre um cartão horizontal de detalhe (hover no desktop, toque no telemóvel) sem mexer na grelha. - Mensalidades: cartões editáveis com um destaque, modal de inscrição que guarda o pedido (RGPD + estado + campos reservados para o CRM) e avisa o ginásio por e-mail; lista de pedidos com estados, notas e CSV. - Reviews Google via Places API (New), filtradas por classificação mínima e em cache configurável; página de configuração com pesquisa do Place ID. - Assets com ?v=<mtime>: site/assets é servido com cache imutável de 1 ano. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8a494d59a5
commit
f95c9ebc52
27 files changed
+2063
-16
No files matched your search
+200
-6
@@ -11,6 +11,8 @@ $modalidades = site_modalidades($pdo);
|
||||
$horario = site_horario($pdo);
|
||||
$whours = site_work_hours($pdo);
|
||||
$events = site_active_events($pdo); // time-limited events, banner below the hero
|
||||
$memberships = site_memberships($pdo);
|
||||
$reviews = site_reviews(); // null when not configured / nothing qualifies
|
||||
|
||||
// ── Hero section ────────────────────────────────────────────────────────────────
|
||||
?>
|
||||
@@ -118,15 +120,46 @@ if ($team):
|
||||
</div>
|
||||
<div class="section-title text-center">
|
||||
<div class="row auto-grid" id="powerRotation">
|
||||
<?php foreach ($team as $d): if ($d['status'] == 'ativo'): ?>
|
||||
<?php foreach ($team as $d): if ($d['status'] == 'ativo'):
|
||||
// Default card: photo + name + title. The horizontal detail card
|
||||
// (hover on desktop, tap on touch) only exists when there is
|
||||
// something extra to show, so a bare member stays a plain card.
|
||||
$t_title = trim((string) ($d['title'] ?? ''));
|
||||
$t_skills = text_lines($d['skills'] ?? '');
|
||||
$t_certs = text_lines($d['certifications'] ?? '');
|
||||
$t_bio = trim(strip_tags((string) ($d['description'] ?? ''))) !== '' ? clean_richtext((string) $d['description']) : '';
|
||||
$t_detail = $t_skills || $t_certs || $t_bio !== '';
|
||||
$t_img = site_img_url($d['photo'], 'uploads/team/foto');
|
||||
$t_id = 'td-' . (int) $d['id'];
|
||||
?>
|
||||
<div class="col-6 col-md-4 col-lg-3" style="margin-top: 20px;">
|
||||
<div class="trainer-item">
|
||||
<div class="trainer-item<?= $t_detail ? ' has-detail' : '' ?>"<?= $t_detail ? ' tabindex="0" role="button" aria-expanded="false" aria-controls="' . $t_id . '" aria-label="Ver detalhes de ' . htmlspecialchars((string) $d['name']) . '"' : '' ?>>
|
||||
<div class="trainer-pic">
|
||||
<img loading="lazy" src="<?= site_img_url($d['photo'], 'uploads/team/foto') ?>" alt="<?= htmlspecialchars((string) $d['name']) ?>">
|
||||
<img loading="lazy" src="<?= $t_img ?>" alt="<?= htmlspecialchars((string) $d['name']) ?>">
|
||||
</div>
|
||||
<h4><?= htmlspecialchars((string) $d['name']) ?></h4>
|
||||
<?php if ($t_title !== ''): ?><p class="trainer-title"><?= htmlspecialchars($t_title) ?></p><?php endif; ?>
|
||||
<?php if ($t_detail): ?>
|
||||
<span class="trainer-more" aria-hidden="true">Saber mais <i class="fas fa-plus"></i></span>
|
||||
<div class="trainer-detail" id="<?= $t_id ?>">
|
||||
<button type="button" class="td-close" aria-label="Fechar">×</button>
|
||||
<div class="td-photo"><img loading="lazy" src="<?= $t_img ?>" alt=""></div>
|
||||
<div class="td-body">
|
||||
<strong class="td-name"><?= htmlspecialchars((string) $d['name']) ?></strong>
|
||||
<?php if ($t_title !== ''): ?><span class="td-title"><?= htmlspecialchars($t_title) ?></span><?php endif; ?>
|
||||
<?php if ($t_bio !== ''): ?><div class="td-bio"><?= $t_bio ?></div><?php endif; ?>
|
||||
<?php if ($t_skills): ?>
|
||||
<div class="td-block"><span class="td-label">Aptidões / Funções</span>
|
||||
<ul><?php foreach ($t_skills as $x): ?><li><?= htmlspecialchars($x) ?></li><?php endforeach; ?></ul></div>
|
||||
<?php endif; ?>
|
||||
<?php if ($t_certs): ?>
|
||||
<div class="td-block"><span class="td-label">Formações</span>
|
||||
<ul><?php foreach ($t_certs as $x): ?><li><?= htmlspecialchars($x) ?></li><?php endforeach; ?></ul></div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?= htmlspecialchars((string) $d['description']) ?>
|
||||
</div>
|
||||
<?php endif; endforeach; ?>
|
||||
</div>
|
||||
@@ -387,6 +420,108 @@ if (!in_array($default_day, $week_values, true)) { $default_day = $week_values[0
|
||||
</script>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
// ── Mensalidades ────────────────────────────────────────────────────────────────
|
||||
// Cards come from the backoffice (Gestão → Mensalidades); one may be featured.
|
||||
// "Inscrever" opens #joinModal pre-set to that card (site/inscricao.php stores
|
||||
// the request as a lead and emails the gym).
|
||||
if ($memberships):
|
||||
?>
|
||||
<section id="mensalidades" class="pricing-section">
|
||||
<div class="container">
|
||||
<div class="row justify-content-md-center mb-4">
|
||||
<div class="col-lg-8">
|
||||
<div class="section-title text-center deco">
|
||||
<span class="section-eyebrow">Mensalidades</span>
|
||||
<h2>Escolhe o teu plano</h2>
|
||||
<p class="section-subtitle">Encontra a mensalidade que encaixa na tua rotina.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php if (($_GET['inscricao'] ?? '') === 'ok'): ?>
|
||||
<div class="alert alert-success text-center" role="alert">Pedido de inscrição enviado! Vamos entrar em contacto contigo muito em breve.</div>
|
||||
<?php elseif (($_GET['inscricao'] ?? '') === 'erro'): ?>
|
||||
<div class="alert alert-danger text-center" role="alert">Não foi possível enviar o pedido. Verifica os dados e tenta novamente.</div>
|
||||
<?php endif; ?>
|
||||
<div class="pricing-grid">
|
||||
<?php foreach ($memberships as $m): $features = text_lines($m['features']); ?>
|
||||
<article class="price-card<?= $m['is_featured'] ? ' is-featured' : '' ?>">
|
||||
<?php if ($m['is_featured']): ?><span class="pc-badge"><i class="fas fa-star"></i> <?= htmlspecialchars($m['featured_label']) ?></span><?php endif; ?>
|
||||
<h3 class="pc-name"><?= htmlspecialchars($m['name']) ?></h3>
|
||||
<?php if ($m['subtitle'] !== ''): ?><p class="pc-sub"><?= htmlspecialchars($m['subtitle']) ?></p><?php endif; ?>
|
||||
<div class="pc-price">
|
||||
<span class="pc-amount<?= $m['price'] === null ? ' is-text' : '' ?>"><?= htmlspecialchars(membership_price_label($m)) ?></span>
|
||||
<?php if ($m['price'] !== null && $m['price_suffix'] !== ''): ?><span class="pc-suffix"><?= htmlspecialchars($m['price_suffix']) ?></span><?php endif; ?>
|
||||
</div>
|
||||
<?php if ($features): ?>
|
||||
<ul class="pc-features">
|
||||
<?php foreach ($features as $f): ?><li><i class="fas fa-check" aria-hidden="true"></i><?= htmlspecialchars($f) ?></li><?php endforeach; ?>
|
||||
</ul>
|
||||
<?php endif; ?>
|
||||
<button type="button" class="site-btn pc-cta" data-toggle="modal" data-target="#joinModal"
|
||||
data-membership="<?= (int) $m['id'] ?>"><?= htmlspecialchars($m['cta_label']) ?></button>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
// ── Reviews Google ──────────────────────────────────────────────────────────────
|
||||
// Live from the Places API (functions_reviews.php), cached; only reviews at or
|
||||
// above the backoffice minimum rating. Hidden when nothing qualifies.
|
||||
if ($reviews):
|
||||
$stars = static function (float $r): string {
|
||||
$out = '';
|
||||
for ($i = 1; $i <= 5; $i++) {
|
||||
$cls = $r >= $i ? 'fas fa-star' : ($r >= $i - 0.5 ? 'fas fa-star-half-alt' : 'far fa-star');
|
||||
$out .= '<i class="' . $cls . '" aria-hidden="true"></i>';
|
||||
}
|
||||
return $out;
|
||||
};
|
||||
?>
|
||||
<section id="reviews" class="reviews-section">
|
||||
<div class="container">
|
||||
<div class="row justify-content-md-center mb-4">
|
||||
<div class="col-lg-8">
|
||||
<div class="section-title text-center deco">
|
||||
<span class="section-eyebrow">Reviews</span>
|
||||
<h2>O que dizem de nós</h2>
|
||||
<?php if ($reviews['rating'] !== null): ?>
|
||||
<p class="reviews-summary">
|
||||
<strong><?= number_format((float) $reviews['rating'], 1, ',', '') ?></strong>
|
||||
<span class="rv-stars" role="img" aria-label="<?= number_format((float) $reviews['rating'], 1, ',', '') ?> de 5 estrelas"><?= $stars((float) $reviews['rating']) ?></span>
|
||||
<span><?= (int) $reviews['total'] ?> avaliações no Google</span>
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="reviews-grid">
|
||||
<?php foreach ($reviews['reviews'] as $r): ?>
|
||||
<figure class="review-card">
|
||||
<div class="rv-stars" role="img" aria-label="<?= (int) $r['rating'] ?> de 5 estrelas"><?= $stars((float) $r['rating']) ?></div>
|
||||
<blockquote class="rv-text"><?= nl2br(htmlspecialchars($r['text'])) ?></blockquote>
|
||||
<figcaption class="rv-author">
|
||||
<?php if ($r['author_photo'] !== ''): ?><img src="<?= htmlspecialchars($r['author_photo']) ?>" alt="" loading="lazy" referrerpolicy="no-referrer" width="40" height="40"><?php endif; ?>
|
||||
<span>
|
||||
<?php if ($r['author_uri'] !== ''): ?><a href="<?= htmlspecialchars($r['author_uri']) ?>" target="_blank" rel="noopener nofollow"><?= htmlspecialchars($r['author']) ?></a><?php else: ?><?= htmlspecialchars($r['author']) ?><?php endif; ?>
|
||||
<small><?= htmlspecialchars($r['relative']) ?></small>
|
||||
</span>
|
||||
</figcaption>
|
||||
</figure>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<div class="reviews-actions">
|
||||
<?php if ($reviews['maps_uri'] !== ''): ?><a class="site-btn sb-line" href="<?= htmlspecialchars($reviews['maps_uri']) ?>" target="_blank" rel="noopener">Ver todas no Google</a><?php endif; ?>
|
||||
<a class="site-btn" href="<?= htmlspecialchars($reviews['write_uri']) ?>" target="_blank" rel="noopener">Deixa a tua review</a>
|
||||
</div>
|
||||
<p class="reviews-attribution">Reviews via Google Maps</p>
|
||||
</div>
|
||||
</section>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
// ── Contactos + Horário de Funcionamento + Mapa ────────────────────────────────
|
||||
$assunto = '';
|
||||
@@ -436,9 +571,11 @@ $session = session();
|
||||
<a href="tel:<?= htmlspecialchars(str_replace(' ', '', $info['phone'] ?? '')) ?>">
|
||||
<p><?= htmlspecialchars($info['phone'] ?? '') ?></p>
|
||||
</a>
|
||||
<a href="tel:<?= htmlspecialchars(str_replace(' ', '', $info['telephone'] ?? '')) ?>">
|
||||
<p><?= htmlspecialchars($info['telephone'] ?? '') ?></p>
|
||||
<?php if (($info['telephone'] ?? '') !== ''): ?>
|
||||
<a href="tel:<?= htmlspecialchars(str_replace(' ', '', $info['telephone'])) ?>">
|
||||
<p><?= htmlspecialchars($info['telephone']) ?></p>
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -618,6 +755,63 @@ $work_hours_days = ['Segunda-Sexta'=>'segunda','Sábado'=>'sabado','Domingo/Feri
|
||||
<script>document.addEventListener('DOMContentLoaded',function(){if(window.jQuery){jQuery('#trialModal').modal('show');}});</script>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
// ── Inscrição (mensalidades) modal ──────────────────────────────────────────
|
||||
// The select is pre-set by main.js from the clicked card's data-membership;
|
||||
// without JS it still works as a plain chooser.
|
||||
if ($memberships):
|
||||
?>
|
||||
<div class="modal fade trial-modal-wrap" id="joinModal" tabindex="-1" role="dialog" aria-labelledby="joinModalLabel" aria-hidden="true">
|
||||
<div class="modal-dialog modal-dialog-centered" role="document">
|
||||
<div class="modal-content trial-modal">
|
||||
<div class="trial-modal-head">
|
||||
<span class="section-eyebrow"><?= htmlspecialchars($info['name'] !== '' ? $info['name'] : APP_NAME) ?></span>
|
||||
<h3 id="joinModalLabel">Inscreve-te</h3>
|
||||
<p>Deixa os teus contactos e entramos em contacto para concluir a inscrição.</p>
|
||||
<button type="button" class="trial-close" data-dismiss="modal" aria-label="Fechar">×</button>
|
||||
</div>
|
||||
<form class="trial-form" method="post" action="<?= base_url('home/inscricao') ?>">
|
||||
<?= csrf_field() ?>
|
||||
<input type="text" name="website" class="event-hp" tabindex="-1" autocomplete="off" aria-hidden="true">
|
||||
<div class="form-row">
|
||||
<div class="col-12 mb-3">
|
||||
<select name="membership_id" aria-label="Mensalidade" required>
|
||||
<?php foreach ($memberships as $m): ?>
|
||||
<option value="<?= (int) $m['id'] ?>"<?= $m['is_featured'] ? ' selected' : '' ?>><?= htmlspecialchars($m['name'] . ' — ' . membership_price_label($m)) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-12 col-md-6 mb-3">
|
||||
<input type="text" name="nome" required maxlength="150" placeholder="Nome *" aria-label="Nome">
|
||||
</div>
|
||||
<div class="col-12 col-md-6 mb-3">
|
||||
<input type="tel" name="telefone" required maxlength="40" placeholder="Telemóvel *" aria-label="Telemóvel">
|
||||
</div>
|
||||
<div class="col-12 mb-3">
|
||||
<input type="email" name="email" maxlength="190" placeholder="E-mail" aria-label="E-mail">
|
||||
</div>
|
||||
<div class="col-12 mb-3">
|
||||
<textarea name="mensagem" rows="3" maxlength="1000" placeholder="Alguma dúvida ou objetivo? (opcional)" aria-label="Mensagem"></textarea>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<label class="consent-check">
|
||||
<input type="checkbox" name="consent" value="1" required>
|
||||
<span>Autorizo o contacto e o tratamento dos meus dados para efeitos desta inscrição, nos termos da <a href="<?= base_url('home/privacidade') ?>" target="_blank" rel="noopener">Política de Privacidade</a>.</span>
|
||||
</label>
|
||||
</div>
|
||||
<div class="col-12">
|
||||
<button type="submit" class="site-btn w-100">Enviar pedido</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<?php if (($_GET['inscricao'] ?? '') === 'erro'): ?>
|
||||
<script>document.addEventListener('DOMContentLoaded',function(){if(window.jQuery){jQuery('#joinModal').modal('show');}});</script>
|
||||
<?php endif; ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php
|
||||
// ── Newsletter + Footer ───────────────────────────────────────────────────────
|
||||
echo view('newsletter');
|
||||
|
||||
Reference in new issue
Block a user