Compare commits
1 Commits
research/m
...
39a7a310e9
| Author | SHA1 | Date | |
|---|---|---|---|
| 39a7a310e9 |
@@ -4,7 +4,10 @@ declare(strict_types=1);
|
|||||||
|
|
||||||
namespace App\Application\Queries\Marketing;
|
namespace App\Application\Queries\Marketing;
|
||||||
|
|
||||||
|
use App\Domain\Marketing\PortfolioVertical;
|
||||||
use App\Models\PortfolioCase;
|
use App\Models\PortfolioCase;
|
||||||
|
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||||
|
use Illuminate\Database\Eloquent\Builder;
|
||||||
use Illuminate\Database\Eloquent\Collection;
|
use Illuminate\Database\Eloquent\Collection;
|
||||||
|
|
||||||
final class GetPublishedPortfolioCases
|
final class GetPublishedPortfolioCases
|
||||||
@@ -12,12 +15,37 @@ final class GetPublishedPortfolioCases
|
|||||||
/**
|
/**
|
||||||
* @return Collection<int, PortfolioCase>
|
* @return Collection<int, PortfolioCase>
|
||||||
*/
|
*/
|
||||||
public function __invoke(): Collection
|
public function __invoke(?PortfolioVertical $vertical = null): Collection
|
||||||
{
|
{
|
||||||
return PortfolioCase::query()
|
return $this->baseQuery($vertical)->get();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return LengthAwarePaginator<int, PortfolioCase>
|
||||||
|
*/
|
||||||
|
public function paginate(?PortfolioVertical $vertical = null, int $perPage = 9): LengthAwarePaginator
|
||||||
|
{
|
||||||
|
return $this->baseQuery($vertical)->paginate($perPage)->withQueryString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Builder<PortfolioCase>
|
||||||
|
*/
|
||||||
|
private function baseQuery(?PortfolioVertical $vertical): Builder
|
||||||
|
{
|
||||||
|
$query = PortfolioCase::query()
|
||||||
->published()
|
->published()
|
||||||
->with(['images'])
|
->with(['images'])
|
||||||
->orderBy('sort_order')
|
->orderBy('sort_order');
|
||||||
->get();
|
|
||||||
|
if ($vertical !== null) {
|
||||||
|
$query->where(function (Builder $builder) use ($vertical): void {
|
||||||
|
foreach ($vertical->eventTypePatterns() as $pattern) {
|
||||||
|
$builder->orWhere('event_type', 'ilike', $pattern);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return $query;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
45
app/Domain/Marketing/PortfolioVertical.php
Normal file
45
app/Domain/Marketing/PortfolioVertical.php
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Domain\Marketing;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Public portfolio verticals used to separate Casamentos and Corporate proof.
|
||||||
|
*
|
||||||
|
* Matching is intentionally tolerant of free-text `event_type` values already
|
||||||
|
* stored in the CMS (e.g. "Casamento", "Mini wedding", "Corporativo").
|
||||||
|
*/
|
||||||
|
enum PortfolioVertical: string
|
||||||
|
{
|
||||||
|
case Casamentos = 'casamentos';
|
||||||
|
case Corporate = 'corporate';
|
||||||
|
|
||||||
|
public function label(): string
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::Casamentos => 'Casamentos',
|
||||||
|
self::Corporate => 'Corporate',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return list<string>
|
||||||
|
*/
|
||||||
|
public function eventTypePatterns(): array
|
||||||
|
{
|
||||||
|
return match ($this) {
|
||||||
|
self::Casamentos => ['%casamento%', '%wedding%', '%social%'],
|
||||||
|
self::Corporate => ['%corporat%', '%empresa%', '%business%'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function tryFromQuery(?string $value): ?self
|
||||||
|
{
|
||||||
|
if ($value === null || $value === '') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::tryFrom($value);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,33 +6,35 @@ namespace App\Http\Controllers\PublicSite;
|
|||||||
|
|
||||||
use App\Application\Data\PageMeta;
|
use App\Application\Data\PageMeta;
|
||||||
use App\Application\Queries\Marketing\FindPublishedPortfolioCaseBySlug;
|
use App\Application\Queries\Marketing\FindPublishedPortfolioCaseBySlug;
|
||||||
|
use App\Application\Queries\Marketing\GetPublishedPortfolioCases;
|
||||||
|
use App\Domain\Marketing\PortfolioVertical;
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
use App\Models\PortfolioCase;
|
|
||||||
use App\Models\SiteSetting;
|
use App\Models\SiteSetting;
|
||||||
use Illuminate\Contracts\View\View;
|
use Illuminate\Contracts\View\View;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Http\Response;
|
use Illuminate\Http\Response;
|
||||||
use Illuminate\Pagination\LengthAwarePaginator;
|
|
||||||
|
|
||||||
final class PortfolioController extends Controller
|
final class PortfolioController extends Controller
|
||||||
{
|
{
|
||||||
public function index(): View
|
public function index(Request $request, GetPublishedPortfolioCases $getPublishedPortfolioCases): View
|
||||||
{
|
{
|
||||||
$settings = SiteSetting::instance();
|
$settings = SiteSetting::instance();
|
||||||
|
$vertical = PortfolioVertical::tryFromQuery($request->query('vertente'));
|
||||||
/** @var LengthAwarePaginator<int, PortfolioCase> $cases */
|
$cases = $getPublishedPortfolioCases->paginate($vertical);
|
||||||
$cases = PortfolioCase::query()
|
|
||||||
->published()
|
|
||||||
->with(['images'])
|
|
||||||
->orderBy('sort_order')
|
|
||||||
->paginate(9);
|
|
||||||
|
|
||||||
return view('pages.portfolio.index', [
|
return view('pages.portfolio.index', [
|
||||||
'cases' => $cases,
|
'cases' => $cases,
|
||||||
|
'vertical' => $vertical,
|
||||||
'siteSettings' => $settings,
|
'siteSettings' => $settings,
|
||||||
'pageMeta' => PageMeta::forPage(
|
'pageMeta' => PageMeta::forPage(
|
||||||
canonical: route('portfolio.index'),
|
canonical: route('portfolio.index', array_filter([
|
||||||
|
'vertente' => $vertical?->value,
|
||||||
|
])),
|
||||||
settings: $settings,
|
settings: $settings,
|
||||||
title: PageMeta::withBrandSuffix('Portfólio', $settings),
|
title: PageMeta::withBrandSuffix(
|
||||||
|
$vertical === null ? 'Portfólio' : 'Portfólio — '.$vertical->label(),
|
||||||
|
$settings,
|
||||||
|
),
|
||||||
description: 'Casos reais de eventos conduzidos pela '.$settings->brand_name.'.',
|
description: 'Casos reais de eventos conduzidos pela '.$settings->brand_name.'.',
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -1,92 +0,0 @@
|
|||||||
# MAN-136 — Inventário de merges órfãos no Gitea
|
|
||||||
|
|
||||||
**Issue:** [MAN-136](https://linear.app/maneco-workspace/issue/MAN-136/research-inventariar-merges-orfaos-no-gitea)
|
|
||||||
**Branch:** `research/man-136-orphan-gitea-merges`
|
|
||||||
**HEAD inventariado:** `origin/main` @ `93b680c`
|
|
||||||
**Data:** 2026-08-13
|
|
||||||
**Escopo:** inventário only — **não** criar issues Linear retroativas.
|
|
||||||
|
|
||||||
## Critério de janela
|
|
||||||
|
|
||||||
**Início inclusivo:** commit `84f6d7c` (`ci: migrate GitHub Actions para Gitea Actions`, 2026-08-12).
|
|
||||||
**Fim:** `HEAD` de `origin/main` (`93b680c`).
|
|
||||||
|
|
||||||
Justificativa: primeiro commit que troca CI/registry/docs para Gitea (`git.hellomanoel.com`). Commits anteriores (GitHub PR #5–#60 era) ficam fora. A fronteira é o SHA da migração de Actions, não a data de criação do repo Gitea.
|
|
||||||
|
|
||||||
## Critério de “issue correspondente”
|
|
||||||
|
|
||||||
Um merge/commit **tem** issue Linear correspondente no projeto **Amare — Lançamento MVP** se **qualquer** for verdade:
|
|
||||||
|
|
||||||
1. Body/assunto do PR ou commit cita identifier `MAN-xxx` desse projeto; ou
|
|
||||||
2. Existe issue no projeto cujo título + critérios de aceite descrevem de forma única a mudança shipped (match funcional 1:1).
|
|
||||||
|
|
||||||
**Não conta como correspondente:**
|
|
||||||
|
|
||||||
- Epic/issue já **Done** antes do merge que “poderia” cobrir trabalho novo (ex.: MAN-102 Done em 2026-08-08 vs remodel `/sobre` em 2026-08-12).
|
|
||||||
- Issue ampla de copy/arquitetura sem AC específico para o ship (ex.: MAN-97).
|
|
||||||
- Mencionar “Gitea” só em issues de template (MAN-133) não cobre a migração de CI.
|
|
||||||
|
|
||||||
Classificação pedida: **órfão shipped** = chegou em `main` sem issue correspondente. Sem criar tickets.
|
|
||||||
|
|
||||||
## Fontes
|
|
||||||
|
|
||||||
| Fonte | Uso |
|
|
||||||
| --- | --- |
|
|
||||||
| `git log origin/main` (`84f6d7c^..HEAD`) | commits da janela |
|
|
||||||
| `tea pr list --state all` + API Gitea `/pulls/{1..4}` | PRs Gitea merged |
|
|
||||||
| Linear `list_issues` project `Amare — Lançamento MVP` (incl. archived) | universo de issues |
|
|
||||||
|
|
||||||
## Matriz PRs Gitea #1–#4
|
|
||||||
|
|
||||||
| PR | Título | Merge SHA | Issue Linear? | Classificação |
|
|
||||||
| --- | --- | --- | --- | --- |
|
|
||||||
| [#1](https://git.hellomanoel.com/manoel-freitas/amare/pulls/1) | feat: remodelar `/sobre` conforme mock editorial | `fc1c617` | Nenhuma (sem citação; MAN-102 já Done; sem issue dedicada) | **órfão shipped** |
|
|
||||||
| [#2](https://git.hellomanoel.com/manoel-freitas/amare/pulls/2) | fix: CTAs de modalidades em `/servicos` via WhatsApp | `e2af1f6` | [MAN-127](https://linear.app/maneco-workspace/issue/MAN-127/ajustar-ctas-consultivos-no-whatsapp-e-briefing-comercial) (Fluxo 1 — CTAs WhatsApp nos cards de modalidade; PR não cita, match por AC) | matched (não órfão) |
|
|
||||||
| [#3](https://git.hellomanoel.com/manoel-freitas/amare/pulls/3) | fix(about): alinhar layout `/sobre` ao ritmo editorial | `ba38894` | Nenhuma (follow-up de #1; sem citação) | **órfão shipped** |
|
|
||||||
| [#4](https://git.hellomanoel.com/manoel-freitas/amare/pulls/4) | docs: require Linear issues and auto-ship PRs | `93b680c` | [MAN-132](https://linear.app/maneco-workspace/issue/MAN-132/exigir-issue-linear-em-todo-trabalho-e-pr) + [MAN-133](https://linear.app/maneco-workspace/issue/MAN-133/adicionar-pr-template-no-gitea-e-no-github) (citados no body) | matched (não órfão) |
|
|
||||||
|
|
||||||
## Órfãos shipped (tabela canônica)
|
|
||||||
|
|
||||||
| Nome descritivo | Evidência | Link |
|
|
||||||
| --- | --- | --- |
|
|
||||||
| Remodelar `/sobre` conforme mock editorial | Gitea PR #1 merged | https://git.hellomanoel.com/manoel-freitas/amare/pulls/1 |
|
|
||||||
| Alinhar layout `/sobre` ao ritmo editorial | Gitea PR #3 merged | https://git.hellomanoel.com/manoel-freitas/amare/pulls/3 |
|
|
||||||
| Migração CI GitHub → Gitea Actions (+ follow-ups de runner/registry/staging) | 10 commits direct-push em `main`, **sem** PR Gitea | sem PR — ver série abaixo |
|
|
||||||
|
|
||||||
### Série vizinha sem PR (direct push)
|
|
||||||
|
|
||||||
Todos sem citação `MAN-*` e sem issue de migração Gitea no projeto Amare MVP:
|
|
||||||
|
|
||||||
| SHA | Assunto |
|
|
||||||
| --- | --- |
|
|
||||||
| `84f6d7c` | ci: migrate GitHub Actions para Gitea Actions |
|
|
||||||
| `716196d` | fix: owner path manoel-freitas/amare no registry Gitea |
|
|
||||||
| `1e215ac` | docs: alinhar remotes e registry para Gitea |
|
|
||||||
| `f730e76` | fix: secrets de registry sem prefixo GITEA_ |
|
|
||||||
| `1468a23` | fix: remover actions/cache no Gitea Actions |
|
|
||||||
| `87a0191` | fix: CI Postgres via hostname, sem bind :5432 no host |
|
|
||||||
| `dd82ca2` | fix: paralelizar CI no act_runner |
|
|
||||||
| `91c0d41` | fix: nomes únicos para containers CI no docker.sock compartilhado |
|
|
||||||
| `ac08b5f` | fix: staging deploy via needs no CI, não workflow_run |
|
|
||||||
| `26a68e1` | fix: restaurar deploy-staging via workflow_run (Gitea ≥1.25) |
|
|
||||||
|
|
||||||
Tratados como **um** órfão shipped agregado (mesmo tema ops/CI), não dez tickets potenciais.
|
|
||||||
|
|
||||||
## Não-órfãos na janela (referência)
|
|
||||||
|
|
||||||
- PR #2 → MAN-127 (parcial; issue ainda Todo no Linear — status Linear ≠ presença de issue).
|
|
||||||
- PR #4 → MAN-132 + MAN-133.
|
|
||||||
|
|
||||||
## Ambiguidades registradas
|
|
||||||
|
|
||||||
1. **PR #2 sem citação explícita:** match por AC de MAN-127 (Fluxo 1). Se o inventário MAN-135 exigir citação literal, reclassificar #2 como órfão — este doc **não** faz isso.
|
|
||||||
2. **`/sobre` vs MAN-97 / MAN-98 / MAN-102:** rejeitado como correspondente (Done prévio ou escopo sem AC de remodel layout).
|
|
||||||
3. **Merge commits vs squash:** PR #1 landa como `fc1c617` (sem merge commit separado); PR #3 tem merge `ba38894`. Ambos contam como merges shipped.
|
|
||||||
|
|
||||||
## Resumo executivo
|
|
||||||
|
|
||||||
**3 órfãos shipped** na era pós-migração Gitea:
|
|
||||||
|
|
||||||
1. Remodelar `/sobre` — [PR #1](https://git.hellomanoel.com/manoel-freitas/amare/pulls/1)
|
|
||||||
2. Fix layout `/sobre` — [PR #3](https://git.hellomanoel.com/manoel-freitas/amare/pulls/3)
|
|
||||||
3. Migração CI/Gitea Actions (+ follow-ups) — direct push, sem PR
|
|
||||||
@@ -134,7 +134,8 @@
|
|||||||
overflow-wrap: break-word;
|
overflow-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
|
||||||
[id$='-heading'] {
|
[id$='-heading'],
|
||||||
|
.home-chapter[id] {
|
||||||
scroll-margin-top: 5.5rem;
|
scroll-margin-top: 5.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -58,8 +58,8 @@
|
|||||||
<header class="site-header sticky top-0 z-40 border-b border-amare-border bg-amare-bg/95 backdrop-blur-sm">
|
<header class="site-header sticky top-0 z-40 border-b border-amare-border bg-amare-bg/95 backdrop-blur-sm">
|
||||||
<div class="container-amare grid grid-cols-[auto_1fr_auto] items-center gap-4 py-4 md:grid-cols-[1fr_auto_1fr] md:h-[74px] md:py-0">
|
<div class="container-amare grid grid-cols-[auto_1fr_auto] items-center gap-4 py-4 md:grid-cols-[1fr_auto_1fr] md:h-[74px] md:py-0">
|
||||||
<nav id="main-nav" class="main-nav order-3 col-span-3 hidden flex-col gap-4 border-t border-amare-border pt-4 md:order-1 md:col-span-1 md:flex md:flex-row md:items-center md:gap-1 md:border-0 md:pt-0" aria-label="Principal" data-main-nav>
|
<nav id="main-nav" class="main-nav order-3 col-span-3 hidden flex-col gap-4 border-t border-amare-border pt-4 md:order-1 md:col-span-1 md:flex md:flex-row md:items-center md:gap-1 md:border-0 md:pt-0" aria-label="Principal" data-main-nav>
|
||||||
<a href="{{ route('home') }}" class="btn btn-ghost min-h-0 px-0 py-3 text-[13px] font-semibold uppercase tracking-[0.08em] text-amare-muted hover:text-amare-accent md:px-3">Início</a>
|
<a href="{{ url('/#casamentos') }}" class="btn btn-ghost min-h-0 px-0 py-3 text-[13px] font-semibold uppercase tracking-[0.08em] text-amare-muted hover:text-amare-accent md:px-3">Casamentos</a>
|
||||||
<a href="{{ route('services.index') }}" class="btn btn-ghost min-h-0 px-0 py-3 text-[13px] font-semibold uppercase tracking-[0.08em] text-amare-muted hover:text-amare-accent md:px-3">Serviços</a>
|
<a href="{{ url('/#corporate') }}" class="btn btn-ghost min-h-0 px-0 py-3 text-[13px] font-semibold uppercase tracking-[0.08em] text-amare-muted hover:text-amare-accent md:px-3">Corporate</a>
|
||||||
<a href="{{ route('portfolio.index') }}" class="btn btn-ghost min-h-0 px-0 py-3 text-[13px] font-semibold uppercase tracking-[0.08em] text-amare-muted hover:text-amare-accent md:px-3">Portfólio</a>
|
<a href="{{ route('portfolio.index') }}" class="btn btn-ghost min-h-0 px-0 py-3 text-[13px] font-semibold uppercase tracking-[0.08em] text-amare-muted hover:text-amare-accent md:px-3">Portfólio</a>
|
||||||
<a href="{{ route('about') }}" class="btn btn-ghost min-h-0 px-0 py-3 text-[13px] font-semibold uppercase tracking-[0.08em] text-amare-muted hover:text-amare-accent md:px-3">Amare</a>
|
<a href="{{ route('about') }}" class="btn btn-ghost min-h-0 px-0 py-3 text-[13px] font-semibold uppercase tracking-[0.08em] text-amare-muted hover:text-amare-accent md:px-3">Amare</a>
|
||||||
<a href="{{ route('briefing') }}" class="btn btn-ghost min-h-0 px-0 py-3 text-[13px] font-semibold uppercase tracking-[0.08em] text-amare-muted hover:text-amare-accent md:hidden">Solicitar proposta</a>
|
<a href="{{ route('briefing') }}" class="btn btn-ghost min-h-0 px-0 py-3 text-[13px] font-semibold uppercase tracking-[0.08em] text-amare-muted hover:text-amare-accent md:hidden">Solicitar proposta</a>
|
||||||
@@ -73,7 +73,6 @@
|
|||||||
<a href="{{ route('briefing') }}" class="btn btn-outline hidden min-h-0 px-6 text-xs font-bold uppercase tracking-[0.09em] md:inline-flex">
|
<a href="{{ route('briefing') }}" class="btn btn-outline hidden min-h-0 px-6 text-xs font-bold uppercase tracking-[0.09em] md:inline-flex">
|
||||||
Conte seu evento
|
Conte seu evento
|
||||||
</a>
|
</a>
|
||||||
</a>
|
|
||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@@ -111,29 +110,39 @@
|
|||||||
$footerSocials = collect($siteSettings->social_links ?? [])->filter(static fn ($url) => filled($url));
|
$footerSocials = collect($siteSettings->social_links ?? [])->filter(static fn ($url) => filled($url));
|
||||||
@endphp
|
@endphp
|
||||||
|
|
||||||
<div class="container-amare flex flex-col gap-8 py-14 md:flex-row md:items-end md:justify-between md:py-[58px]">
|
<div class="container-amare flex flex-col gap-10 py-14 md:py-[58px]">
|
||||||
<div class="space-y-3">
|
<div class="flex flex-col gap-8 md:flex-row md:items-end md:justify-between">
|
||||||
<x-brand.logo variant="on-light" class="h-10 w-auto" />
|
<div class="space-y-3">
|
||||||
<p class="text-sm text-amare-muted">Assessoria & produção de eventos • São Paulo</p>
|
<x-brand.logo variant="on-light" class="h-10 w-auto" />
|
||||||
|
<p class="text-sm text-amare-muted">Boutique de assessoria & produção • São Paulo</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="text-sm text-amare-muted md:text-right">
|
||||||
|
@if ($footerSocials->isEmpty())
|
||||||
|
Instagram · WhatsApp · E-mail · LinkedIn (quando confirmado)
|
||||||
|
@else
|
||||||
|
@foreach ($footerSocials as $network => $url)
|
||||||
|
<a href="{{ $url }}" class="inline-flex min-h-11 items-center transition-colors hover:text-amare-accent" rel="noopener noreferrer" target="_blank">{{ $footerSocialLabel((string) $network) }}</a>@unless ($loop->last) · @endunless
|
||||||
|
@endforeach
|
||||||
|
@endif
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="text-sm text-amare-muted md:text-right">
|
<nav aria-label="Rodapé" class="flex flex-col gap-4 border-t border-amare-border pt-8 text-sm text-amare-muted md:flex-row md:flex-wrap md:items-center md:gap-x-6 md:gap-y-2">
|
||||||
@if ($footerSocials->isEmpty())
|
<a href="{{ url('/#casamentos') }}" class="inline-flex min-h-11 items-center transition-colors hover:text-amare-accent">Casamentos</a>
|
||||||
Instagram · WhatsApp · E-mail · LinkedIn (quando confirmado)
|
<a href="{{ url('/#corporate') }}" class="inline-flex min-h-11 items-center transition-colors hover:text-amare-accent">Corporate</a>
|
||||||
@else
|
<a href="{{ route('services.index') }}" class="inline-flex min-h-11 items-center transition-colors hover:text-amare-accent">Serviços</a>
|
||||||
@foreach ($footerSocials as $network => $url)
|
<a href="{{ route('portfolio.index') }}" class="inline-flex min-h-11 items-center transition-colors hover:text-amare-accent">Portfólio</a>
|
||||||
<a href="{{ $url }}" class="inline-flex min-h-11 items-center transition-colors hover:text-amare-accent" rel="noopener noreferrer" target="_blank">{{ $footerSocialLabel((string) $network) }}</a>@unless ($loop->last) · @endunless
|
<a href="{{ route('about') }}" class="inline-flex min-h-11 items-center transition-colors hover:text-amare-accent">Sobre</a>
|
||||||
@endforeach
|
<a href="{{ route('briefing') }}" class="inline-flex min-h-11 items-center transition-colors hover:text-amare-accent">Briefing</a>
|
||||||
@endif
|
<a href="{{ route('contact') }}" class="inline-flex min-h-11 items-center transition-colors hover:text-amare-accent">Parcerias</a>
|
||||||
</p>
|
<a href="{{ route('privacy') }}" class="inline-flex min-h-11 items-center transition-colors hover:text-amare-accent">Privacidade</a>
|
||||||
|
</nav>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="border-t border-amare-border">
|
<div class="border-t border-amare-border">
|
||||||
<div class="container-amare flex flex-col gap-2 py-6 text-sm text-amare-muted md:flex-row md:items-center md:justify-between">
|
<div class="container-amare flex flex-col gap-2 py-6 text-sm text-amare-muted md:flex-row md:items-center md:justify-between">
|
||||||
<p>© {{ now()->year }} {{ $siteSettings->brand_name }}. Todos os direitos reservados.</p>
|
<p>© {{ now()->year }} {{ $siteSettings->brand_name }}. Todos os direitos reservados.</p>
|
||||||
<p>
|
|
||||||
<a href="{{ route('privacy') }}" class="inline-flex min-h-11 items-center transition-colors hover:text-amare-accent">Política de privacidade</a>
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|||||||
@@ -5,9 +5,38 @@
|
|||||||
|
|
||||||
<div class="border-b border-amare-border bg-amare-bg-deep">
|
<div class="border-b border-amare-border bg-amare-bg-deep">
|
||||||
<div class="container-amare space-y-10 py-16 md:py-24">
|
<div class="container-amare space-y-10 py-16 md:py-24">
|
||||||
|
<nav aria-label="Vertentes do portfólio" class="flex flex-wrap gap-x-6 gap-y-2 border-b border-amare-border pb-6 text-sm">
|
||||||
|
<a
|
||||||
|
href="{{ route('portfolio.index') }}"
|
||||||
|
@class([
|
||||||
|
'inline-flex min-h-11 items-center uppercase tracking-[0.08em] transition-colors',
|
||||||
|
'font-semibold text-amare-accent' => $vertical === null,
|
||||||
|
'text-amare-muted hover:text-amare-accent' => $vertical !== null,
|
||||||
|
])
|
||||||
|
>Todos</a>
|
||||||
|
@foreach (\App\Domain\Marketing\PortfolioVertical::cases() as $option)
|
||||||
|
<a
|
||||||
|
href="{{ route('portfolio.index', ['vertente' => $option->value]) }}"
|
||||||
|
@class([
|
||||||
|
'inline-flex min-h-11 items-center uppercase tracking-[0.08em] transition-colors',
|
||||||
|
'font-semibold text-amare-accent' => $vertical === $option,
|
||||||
|
'text-amare-muted hover:text-amare-accent' => $vertical !== $option,
|
||||||
|
])
|
||||||
|
>{{ $option->label() }}</a>
|
||||||
|
@endforeach
|
||||||
|
</nav>
|
||||||
|
|
||||||
@if ($cases->isEmpty())
|
@if ($cases->isEmpty())
|
||||||
<p class="text-amare-muted">Novos casos serão publicados assim que o acervo estiver organizado. Enquanto isso, fale conosco para conhecer o nosso trabalho.</p>
|
@if ($vertical === \App\Domain\Marketing\PortfolioVertical::Corporate)
|
||||||
|
<div class="flex min-h-[280px] flex-col items-center justify-center gap-4 bg-amare-bg p-10 text-center">
|
||||||
|
<p class="text-xs font-bold uppercase tracking-[0.16em] text-amare-accent-deep">Portfólio Corporate</p>
|
||||||
|
<h2 class="text-[clamp(1.5625rem,2.7vw,2.125rem)] font-medium leading-tight text-amare-text">Conteúdo em construção</h2>
|
||||||
|
<p class="max-w-lg text-amare-text-muted">Ainda não publicamos cases corporativos autorizados. Em vez de inventar prova, mantemos este espaço pronto para projetos reais.</p>
|
||||||
|
<a href="{{ route('briefing') }}" class="btn btn-outline mt-2 text-xs font-bold uppercase tracking-[0.09em]">Falar sobre um evento corporativo</a>
|
||||||
|
</div>
|
||||||
|
@else
|
||||||
|
<p class="text-amare-muted">Novos casos serão publicados assim que o acervo estiver organizado. Enquanto isso, fale conosco para conhecer o nosso trabalho.</p>
|
||||||
|
@endif
|
||||||
@else
|
@else
|
||||||
@php
|
@php
|
||||||
$hasPrioritizedImage = false;
|
$hasPrioritizedImage = false;
|
||||||
@@ -53,6 +82,9 @@
|
|||||||
@endphp
|
@endphp
|
||||||
@endif
|
@endif
|
||||||
<div class="space-y-2 border-t border-amare-border pt-4">
|
<div class="space-y-2 border-t border-amare-border pt-4">
|
||||||
|
@if (filled($case->event_type))
|
||||||
|
<p class="text-xs font-bold uppercase tracking-[0.16em] text-amare-accent-deep">{{ $case->event_type }}</p>
|
||||||
|
@endif
|
||||||
<h2 class="text-2xl font-medium text-amare-text">
|
<h2 class="text-2xl font-medium text-amare-text">
|
||||||
<a href="{{ route('portfolio.show', $case->slug) }}" class="transition-colors hover:text-amare-accent">{{ $case->title }}</a>
|
<a href="{{ route('portfolio.show', $case->slug) }}" class="transition-colors hover:text-amare-accent">{{ $case->title }}</a>
|
||||||
</h2>
|
</h2>
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ declare(strict_types=1);
|
|||||||
namespace Tests\Feature\Application\Queries\Marketing;
|
namespace Tests\Feature\Application\Queries\Marketing;
|
||||||
|
|
||||||
use App\Application\Queries\Marketing\GetPublishedPortfolioCases;
|
use App\Application\Queries\Marketing\GetPublishedPortfolioCases;
|
||||||
|
use App\Domain\Marketing\PortfolioVertical;
|
||||||
use App\Models\PortfolioCase;
|
use App\Models\PortfolioCase;
|
||||||
use App\Models\PortfolioImage;
|
use App\Models\PortfolioImage;
|
||||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
@@ -58,4 +59,26 @@ class GetPublishedPortfolioCasesTest extends TestCase
|
|||||||
$this->assertTrue($cases->get(0)?->relationLoaded('images'));
|
$this->assertTrue($cases->get(0)?->relationLoaded('images'));
|
||||||
$this->assertCount(2, $cases->get(0)?->images ?? []);
|
$this->assertCount(2, $cases->get(0)?->images ?? []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_filters_published_cases_by_portfolio_vertical(): void
|
||||||
|
{
|
||||||
|
PortfolioCase::factory()->published()->create([
|
||||||
|
'title' => 'Wedding Case',
|
||||||
|
'event_type' => 'Mini wedding',
|
||||||
|
'sort_order' => 10,
|
||||||
|
]);
|
||||||
|
PortfolioCase::factory()->published()->create([
|
||||||
|
'title' => 'Corporate Case',
|
||||||
|
'event_type' => 'Evento corporativo',
|
||||||
|
'sort_order' => 20,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$weddings = (new GetPublishedPortfolioCases)(PortfolioVertical::Casamentos);
|
||||||
|
$corporate = (new GetPublishedPortfolioCases)(PortfolioVertical::Corporate);
|
||||||
|
|
||||||
|
$this->assertCount(1, $weddings);
|
||||||
|
$this->assertSame('Wedding Case', $weddings->first()?->title);
|
||||||
|
$this->assertCount(1, $corporate);
|
||||||
|
$this->assertSame('Corporate Case', $corporate->first()?->title);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
60
tests/Feature/PublicSite/PortfolioVerticalFilterTest.php
Normal file
60
tests/Feature/PublicSite/PortfolioVerticalFilterTest.php
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Tests\Feature\PublicSite;
|
||||||
|
|
||||||
|
use App\Models\PortfolioCase;
|
||||||
|
use App\Models\SiteSetting;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class PortfolioVerticalFilterTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
public function test_portfolio_index_filters_by_vertical_and_shows_corporate_empty_state(): void
|
||||||
|
{
|
||||||
|
SiteSetting::instance();
|
||||||
|
|
||||||
|
PortfolioCase::factory()->published()->create([
|
||||||
|
'title' => 'Casamento Jardim',
|
||||||
|
'slug' => 'casamento-jardim',
|
||||||
|
'event_type' => 'Casamento',
|
||||||
|
'sort_order' => 10,
|
||||||
|
]);
|
||||||
|
|
||||||
|
PortfolioCase::factory()->published()->create([
|
||||||
|
'title' => 'Convenção Anual',
|
||||||
|
'slug' => 'convencao-anual',
|
||||||
|
'event_type' => 'Corporativo',
|
||||||
|
'sort_order' => 20,
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->get(route('portfolio.index'))
|
||||||
|
->assertOk()
|
||||||
|
->assertSee('Casamento Jardim')
|
||||||
|
->assertSee('Convenção Anual')
|
||||||
|
->assertSee('Vertentes do portfólio')
|
||||||
|
->assertSee('Casamentos')
|
||||||
|
->assertSee('Corporate');
|
||||||
|
|
||||||
|
$this->get(route('portfolio.index', ['vertente' => 'casamentos']))
|
||||||
|
->assertOk()
|
||||||
|
->assertSee('Casamento Jardim')
|
||||||
|
->assertDontSee('Convenção Anual');
|
||||||
|
|
||||||
|
$this->get(route('portfolio.index', ['vertente' => 'corporate']))
|
||||||
|
->assertOk()
|
||||||
|
->assertSee('Convenção Anual')
|
||||||
|
->assertDontSee('Casamento Jardim');
|
||||||
|
|
||||||
|
PortfolioCase::query()->where('slug', 'convencao-anual')->update(['published_at' => null]);
|
||||||
|
|
||||||
|
$this->get(route('portfolio.index', ['vertente' => 'corporate']))
|
||||||
|
->assertOk()
|
||||||
|
->assertSee('Portfólio Corporate')
|
||||||
|
->assertSee('Conteúdo em construção')
|
||||||
|
->assertDontSee('Convenção Anual');
|
||||||
|
}
|
||||||
|
}
|
||||||
43
tests/Feature/PublicSite/PublicNavigationVerticalsTest.php
Normal file
43
tests/Feature/PublicSite/PublicNavigationVerticalsTest.php
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Tests\Feature\PublicSite;
|
||||||
|
|
||||||
|
use App\Models\SiteSetting;
|
||||||
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
|
use Tests\TestCase;
|
||||||
|
|
||||||
|
class PublicNavigationVerticalsTest extends TestCase
|
||||||
|
{
|
||||||
|
use RefreshDatabase;
|
||||||
|
|
||||||
|
public function test_header_and_footer_expose_casamentos_and_corporate_without_implying_weddings_only(): void
|
||||||
|
{
|
||||||
|
SiteSetting::instance();
|
||||||
|
|
||||||
|
$response = $this->get(route('about'))->assertOk();
|
||||||
|
|
||||||
|
$homeCasamentos = url('/#casamentos');
|
||||||
|
$homeCorporate = url('/#corporate');
|
||||||
|
|
||||||
|
$response
|
||||||
|
->assertSee('id="main-nav"', false)
|
||||||
|
->assertSee('href="'.$homeCasamentos.'"', false)
|
||||||
|
->assertSee('>Casamentos</a>', false)
|
||||||
|
->assertSee('href="'.$homeCorporate.'"', false)
|
||||||
|
->assertSee('>Corporate</a>', false)
|
||||||
|
->assertSee('href="'.route('portfolio.index').'"', false)
|
||||||
|
->assertSee('href="'.route('about').'"', false)
|
||||||
|
->assertSee('href="'.route('services.index').'"', false)
|
||||||
|
->assertSee('href="'.route('privacy').'"', false)
|
||||||
|
->assertSee('aria-label="Rodapé"', false)
|
||||||
|
->assertSeeInOrder([
|
||||||
|
'aria-label="Rodapé"',
|
||||||
|
'href="'.$homeCasamentos.'"',
|
||||||
|
'Casamentos',
|
||||||
|
'href="'.$homeCorporate.'"',
|
||||||
|
'Corporate',
|
||||||
|
], false);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user