Compare commits
5 Commits
feat/man-1
...
feat/man-1
| Author | SHA1 | Date | |
|---|---|---|---|
| 900b76ad19 | |||
| 9ee9b51b0c | |||
| 220143390e | |||
| fd3a4eb5f1 | |||
| 3207b7e568 |
@@ -79,4 +79,4 @@ Default vocabulary: `needs-triage`, `needs-info`, `ready-for-agent`, `ready-for-
|
|||||||
|
|
||||||
### Domain docs
|
### Domain docs
|
||||||
|
|
||||||
Single-context repo. There is no `CONTEXT.md` — the domain is documented in `SPEC.md` (§8 is the domain model and database schema) and `PRODUCT.md`, with current capabilities described per-capability under `openspec/specs/`. `docs/adr/README.md` is an index only: ADR-001 through ADR-010 are decided in `SPEC.md` §21, and there are no standalone ADR files. `docs/agents/domain.md` describes the generic `CONTEXT.md`/`CONTEXT-MAP.md` layout that the engineering skills look for and instructs them to proceed silently when it's absent, which is the case here.
|
Single-context repo. There is no `CONTEXT.md` on purpose — **`SPEC.md` §8.0 is the ubiquitous-language SSOT** (domain model/schema continue in §8.1+). `PRODUCT.md` covers positioning; capabilities live under `openspec/specs/`. `docs/adr/README.md` is an index only: ADR-001 through ADR-010 are decided in `SPEC.md` §21. `docs/agents/domain.md` tells skills to use `SPEC.md` §8 instead of inventing a parallel `CONTEXT.md`.
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ Site-wide content is a singleton row reached via `SiteSetting::instance()`. Publ
|
|||||||
- **Livewire/Filament temp uploads are pinned to the `local` disk** when `FILESYSTEM_DISK=r2`, because the S3 driver would make the browser PUT straight to R2 and hit CORS. Final media still lands on `r2` via `App\Support\PublicImageUploadRules`. Set `LIVEWIRE_TEMPORARY_FILE_UPLOAD_DISK` explicitly to override.
|
- **Livewire/Filament temp uploads are pinned to the `local` disk** when `FILESYSTEM_DISK=r2`, because the S3 driver would make the browser PUT straight to R2 and hit CORS. Final media still lands on `r2` via `App\Support\PublicImageUploadRules`. Set `LIVEWIRE_TEMPORARY_FILE_UPLOAD_DISK` explicitly to override.
|
||||||
- **Contact form is rate limited**: named limiter `contact-briefing`, 5/min per IP, registered in `AppServiceProvider` and applied in `routes/web.php`.
|
- **Contact form is rate limited**: named limiter `contact-briefing`, 5/min per IP, registered in `AppServiceProvider` and applied in `routes/web.php`.
|
||||||
- **Filament 5 nested resource layout**: resources are split into `app/Filament/Resources/<Resource>/{Pages,Schemas,Tables,RelationManagers}` rather than a flat resource class. Follow the existing shape in `Resources/PortfolioCases/`.
|
- **Filament 5 nested resource layout**: resources are split into `app/Filament/Resources/<Resource>/{Pages,Schemas,Tables,RelationManagers}` rather than a flat resource class. Follow the existing shape in `Resources/PortfolioCases/`.
|
||||||
- **Everything user-facing is pt-BR**: routes are `/servicos`, `/portfolio`, `/portfolio/{slug}`, `/sobre`, `/privacidade`, `/contato`. `APP_LOCALE=pt_BR`, `APP_TIMEZONE=America/Sao_Paulo` (`config/app.php:68`).
|
- **Everything user-facing is pt-BR**: routes are `/servicos`, `/pacotes/{slug}`, `/portfolio`, `/portfolio/{slug}`, `/sobre`, `/briefing`, `/contato`, `/privacidade`. `APP_LOCALE=pt_BR`, `APP_TIMEZONE=America/Sao_Paulo` (`config/app.php:68`). Glossário canônico: `SPEC.md` §8.0.
|
||||||
- **Design tokens** live in `resources/css/tokens.css` (Heritage Editorial; see `DESIGN.md`). `tests/Feature/PublicSite/HeritageEditorialTokensTest.php` reads that file and asserts the exact hex values, `EB Garamond`, zero border radii, `--amare-container-max: 1120px`, and the *absence* of shadow tokens — so any token edit is a deliberate test change too. Motion lives in `resources/js/motion.js` and is asserted by `tests/Feature/PublicSite/MotionMarkupTest.php` + `tests/Browser/MotionTest.php`.
|
- **Design tokens** live in `resources/css/tokens.css` (Heritage Editorial; see `DESIGN.md`). `tests/Feature/PublicSite/HeritageEditorialTokensTest.php` reads that file and asserts the exact hex values, `EB Garamond`, zero border radii, `--amare-container-max: 1120px`, and the *absence* of shadow tokens — so any token edit is a deliberate test change too. Motion lives in `resources/js/motion.js` and is asserted by `tests/Feature/PublicSite/MotionMarkupTest.php` + `tests/Browser/MotionTest.php`.
|
||||||
|
|
||||||
## Navigating the normative docs
|
## Navigating the normative docs
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ No lançamento, o site público premium e o CMS interno formam a superfície ent
|
|||||||
## Operating Context
|
## Operating Context
|
||||||
|
|
||||||
- Atuação em São Paulo, capital.
|
- Atuação em São Paulo, capital.
|
||||||
- Serviços: casamentos, eventos corporativos e celebrações.
|
- Serviços: casamentos, eventos corporativos e celebrações. No site, casamento aparece como três **modalidades** (Essenza, Conduzione, Grand Jour); preço e limites comerciais ficam na proposta, não na página.
|
||||||
- Operação realizada por assessora principal e equipe pequena.
|
- Operação realizada por assessora principal e equipe pequena.
|
||||||
- Jornada de lançamento: descoberta no site, briefing por e-mail e contato comercial fora da plataforma.
|
- Jornada de lançamento: descoberta no site, briefing por e-mail e contato comercial fora da plataforma.
|
||||||
- Jornada futura prevista: qualificação do lead, pipeline comercial, conversão em evento, checklist, fornecedores, orçamento, pagamentos manuais e documentos.
|
- Jornada futura prevista: qualificação do lead, pipeline comercial, conversão em evento, checklist, fornecedores, orçamento, pagamentos manuais e documentos.
|
||||||
|
|||||||
69
SPEC.md
69
SPEC.md
@@ -283,11 +283,15 @@ Uma funcionalidade fora do MVP só poderá entrar quando:
|
|||||||
| Método | Rota | Nome sugerido | Finalidade |
|
| Método | Rota | Nome sugerido | Finalidade |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| GET | `/` | `home` | Home editorial |
|
| GET | `/` | `home` | Home editorial |
|
||||||
| GET | `/servicos` | `services.index` | Lista de serviços publicados |
|
| GET | `/servicos` | `services.index` | Lista de serviços / modalidades publicadas |
|
||||||
|
| GET | `/pacotes/{slug}` | `packages.show` | Detalhe de uma modalidade de casamento |
|
||||||
| GET | `/portfolio` | `portfolio.index` | Lista de casos publicados |
|
| GET | `/portfolio` | `portfolio.index` | Lista de casos publicados |
|
||||||
| GET | `/portfolio/{slug}` | `portfolio.show` | Detalhe de caso |
|
| GET | `/portfolio/{slug}` | `portfolio.show` | Detalhe de caso |
|
||||||
| GET | `/sobre` | `about` | História, método e credenciais |
|
| GET | `/sobre` | `about` | História, método e credenciais |
|
||||||
| GET | `/contato` | `contact` | Briefing de contato |
|
| GET | `/briefing` | `briefing` | Briefing comercial (pedido de proposta) |
|
||||||
|
| POST | `/briefing` | `briefing.store` | Envio do briefing comercial |
|
||||||
|
| GET | `/contato` | `contact` | Contato de parceiro / fornecedor (não é briefing) |
|
||||||
|
| POST | `/contato` | `contact.store` | Envio da consulta de parceiro |
|
||||||
| GET | `/privacidade` | `privacy` | Política de privacidade |
|
| GET | `/privacidade` | `privacy` | Política de privacidade |
|
||||||
| GET | `/sitemap.xml` | `sitemap` | Sitemap público |
|
| GET | `/sitemap.xml` | `sitemap` | Sitemap público |
|
||||||
| GET | `/robots.txt` | `robots` | Política de crawling |
|
| GET | `/robots.txt` | `robots` | Política de crawling |
|
||||||
@@ -324,7 +328,8 @@ Administração
|
|||||||
|
|
||||||
- Dashboard: `Filament Page` customizada com widgets orientados a exceção.
|
- Dashboard: `Filament Page` customizada com widgets orientados a exceção.
|
||||||
- Detalhe do evento: página customizada do Resource com resumo operacional.
|
- Detalhe do evento: página customizada do Resource com resumo operacional.
|
||||||
- Briefing público: Blade + Controller (`POST /contato`), ver §11.2.
|
- Briefing comercial: Blade + Controller (`POST /briefing`), ver §11.2.
|
||||||
|
- Contato de parceiro: Blade + Controller (`POST /contato`) — canal separado do briefing.
|
||||||
- Home: Blade com componentes de design reutilizáveis.
|
- Home: Blade com componentes de design reutilizáveis.
|
||||||
|
|
||||||
### 5.4 Decisões de UX YAGNI
|
### 5.4 Decisões de UX YAGNI
|
||||||
@@ -1243,6 +1248,35 @@ Auditoria não precisa ser um event sourcing. Registrar apenas operações crít
|
|||||||
|
|
||||||
## 8. Modelo de domínio e banco de dados
|
## 8. Modelo de domínio e banco de dados
|
||||||
|
|
||||||
|
Esta seção é a **fonte única de verdade** do vocabulário de domínio do produto. Issues Linear, OpenSpec, código e copy devem usar estes termos. Não criar glossário paralelo em `CONTEXT.md`.
|
||||||
|
|
||||||
|
### 8.0 Linguagem ubíqua (glossário)
|
||||||
|
|
||||||
|
**Vertente**:
|
||||||
|
Frente de marca da Amare — **Casamentos** ou **Corporate**.
|
||||||
|
_Avoid_: linha de negócio, vertical de produto (em copy pública), “área”
|
||||||
|
|
||||||
|
**Modalidade**:
|
||||||
|
Pacote/formato de acompanhamento de casamento gerenciado no CMS (`WeddingPackage`: Essenza, Conduzione, Grand Jour).
|
||||||
|
_Avoid_: pacote (em copy pública preferir “modalidade”), serviço genérico, plano
|
||||||
|
|
||||||
|
**Briefing**:
|
||||||
|
Pedido comercial de um potencial cliente descrevendo o evento e solicitando atendimento/proposta. Rota canônica: `/briefing`.
|
||||||
|
_Avoid_: contato (quando o sentido é pedido comercial), formulário de parceiro
|
||||||
|
|
||||||
|
**Contato parceiro**:
|
||||||
|
Consulta de fornecedor ou proposta de parceria. Rota canônica: `/contato`. Não mistura com Briefing.
|
||||||
|
_Avoid_: briefing, lead comercial
|
||||||
|
|
||||||
|
**CTA contextual**:
|
||||||
|
Link de conversão de uma Modalidade escolhida — WhatsApp com mensagem que nomeia a modalidade, ou fallback para `/briefing?servico_interesse=` quando não há número oficial válido.
|
||||||
|
|
||||||
|
**CTA orientação**:
|
||||||
|
Link de conversão para visitante que ainda não escolheu Modalidade — WhatsApp com mensagem de orientação humana (sem quiz), ou fallback para `/briefing` quando não há número oficial válido.
|
||||||
|
|
||||||
|
**Boutique**:
|
||||||
|
Conceito de posicionamento (cuidado, especialização, múltiplos serviços sob a mesma marca) — não é entidade de domínio nem rota.
|
||||||
|
|
||||||
### 8.1 Módulos
|
### 8.1 Módulos
|
||||||
|
|
||||||
| Módulo | Responsabilidade |
|
| Módulo | Responsabilidade |
|
||||||
@@ -1254,6 +1288,12 @@ Auditoria não precisa ser um event sourcing. Registrar apenas operações crít
|
|||||||
| Finance | Orçamento e pagamentos manuais |
|
| Finance | Orçamento e pagamentos manuais |
|
||||||
| Identity | Usuários, acesso e auditoria |
|
| Identity | Usuários, acesso e auditoria |
|
||||||
|
|
||||||
|
Vocabulário público vs persistência (Marketing):
|
||||||
|
|
||||||
|
- **Modalidade** é o nome público de cada oferta de acompanhamento de casamento. O visitante vê o nome próprio (Essenza, Conduzione, Grand Jour) e a tag de momento (Completa, Parcial, Final). Persistida em `wedding_packages`.
|
||||||
|
- **Vertente** é o contexto da marca (Casamentos vs Corporate). A tabela `services` descreve vertentes do catálogo, não as três modalidades.
|
||||||
|
- **Pacote**, no sentido de SKU com preço e limites operacionais, não é superfície pública. Preço, equipe no dia, teto de fornecedores, quilometragem e ferramentas de terceiros (incluindo gestão de convidados) ficam na proposta, não no registro público da modalidade.
|
||||||
|
|
||||||
### 8.2 Tabelas obrigatórias
|
### 8.2 Tabelas obrigatórias
|
||||||
|
|
||||||
#### `users`
|
#### `users`
|
||||||
@@ -1314,6 +1354,27 @@ Singleton:
|
|||||||
- timestamps;
|
- timestamps;
|
||||||
- soft delete opcional somente se recuperação for necessária.
|
- soft delete opcional somente se recuperação for necessária.
|
||||||
|
|
||||||
|
#### `wedding_packages`
|
||||||
|
|
||||||
|
Modalidades públicas de casamento (não confundir com `services`):
|
||||||
|
|
||||||
|
- `id`;
|
||||||
|
- `name`;
|
||||||
|
- `slug` unique;
|
||||||
|
- `level` (rótulo de momento, ex. `01 / COMPLETA`);
|
||||||
|
- `tag` nullable (ex. Assessoria completa);
|
||||||
|
- `subtitle` nullable;
|
||||||
|
- `summary`;
|
||||||
|
- `scope_items` jsonb (bullets de conceito nos cards);
|
||||||
|
- `cta_label`;
|
||||||
|
- `compare_heading` nullable;
|
||||||
|
- `compare_summary` nullable;
|
||||||
|
- campos editoriais da página `/pacotes/{slug}` (eyebrow, título, hero, benefícios, incluso em alto nível, audiência, CTA final, meta) — todos opcionais salvo o que a publicação exigir;
|
||||||
|
- `sort_order` integer indexed;
|
||||||
|
- `published_at` timestamp nullable indexed;
|
||||||
|
- timestamps;
|
||||||
|
- **sem** colunas de preço, centavos, equipe, limites de fornecedores/convidados/km ou ferramentas de terceiros.
|
||||||
|
|
||||||
#### `portfolio_cases`
|
#### `portfolio_cases`
|
||||||
|
|
||||||
- `id`;
|
- `id`;
|
||||||
@@ -1795,7 +1856,7 @@ Não transformar seções estáticas em componentes Livewire. Usar Blade quando
|
|||||||
|
|
||||||
### 11.2 Formulário de briefing
|
### 11.2 Formulário de briefing
|
||||||
|
|
||||||
> **Estado atual:** o formulário é implementado em Blade + Controller (`POST /contato`, `ContactBriefingRequest`), conforme WEB-05, e essa é a abordagem aceita — não um estágio provisório. Os requisitos abaixo valem independentemente da tecnologia; a criação de Lead segue para a Fase 2.
|
> **Estado atual:** o formulário de **Briefing** é Blade + Controller (`GET/POST /briefing`, `ContactBriefingRequest`), conforme WEB-05. A rota `/contato` é o canal de **Contato parceiro** (consulta não persistente), separado do briefing. A criação de Lead comercial persistente segue para a Fase 2.
|
||||||
|
|
||||||
O formulário deve:
|
O formulário deve:
|
||||||
|
|
||||||
|
|||||||
@@ -8,20 +8,45 @@ use App\Models\SiteSetting;
|
|||||||
|
|
||||||
final class PackageContactLink
|
final class PackageContactLink
|
||||||
{
|
{
|
||||||
|
public const ORIENTATION_MESSAGE = 'Olá, ainda não sei qual modalidade de assessoria faz sentido para o meu casamento e gostaria de orientação.';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolve the contextual contact link for a wedding package modality.
|
* Resolve the contextual contact link for a wedding package modality.
|
||||||
*
|
*
|
||||||
* @return array{href: string, isWhatsapp: bool}
|
* @return array{href: string, isWhatsapp: bool}
|
||||||
*/
|
*/
|
||||||
public static function for(SiteSetting $settings, string $packageName): array
|
public static function for(SiteSetting $settings, string $packageName): array
|
||||||
|
{
|
||||||
|
return self::resolve(
|
||||||
|
$settings,
|
||||||
|
'Olá, gostaria de conversar sobre a modalidade '.$packageName.' para meu casamento.',
|
||||||
|
['servico_interesse' => $packageName],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WhatsApp (or briefing fallback) when the visitor does not know which modality to choose.
|
||||||
|
*
|
||||||
|
* @return array{href: string, isWhatsapp: bool}
|
||||||
|
*/
|
||||||
|
public static function forOrientation(SiteSetting $settings): array
|
||||||
|
{
|
||||||
|
return self::resolve($settings, self::ORIENTATION_MESSAGE, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array<string, string> $briefingQuery
|
||||||
|
* @return array{href: string, isWhatsapp: bool}
|
||||||
|
*/
|
||||||
|
private static function resolve(SiteSetting $settings, string $whatsappMessage, array $briefingQuery): array
|
||||||
{
|
{
|
||||||
$digits = preg_replace('/\D/', '', (string) $settings->whatsapp_number);
|
$digits = preg_replace('/\D/', '', (string) $settings->whatsapp_number);
|
||||||
$isWhatsapp = strlen($digits) >= 10;
|
$isWhatsapp = strlen($digits) >= 10;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
'href' => $isWhatsapp
|
'href' => $isWhatsapp
|
||||||
? 'https://wa.me/'.$digits.'?text='.rawurlencode('Olá, gostaria de conversar sobre a modalidade '.$packageName.' para meu casamento.')
|
? 'https://wa.me/'.$digits.'?text='.rawurlencode($whatsappMessage)
|
||||||
: route('briefing', ['servico_interesse' => $packageName]),
|
: route('briefing', $briefingQuery),
|
||||||
'isWhatsapp' => $isWhatsapp,
|
'isWhatsapp' => $isWhatsapp,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,11 +9,10 @@ use Illuminate\Database\Seeder;
|
|||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wedding package modalities shown on the home page (03 — Amare Casamentos)
|
* Wedding package modalities shown on the home page, services page and
|
||||||
* and the services page. Copy is approved from the structural model preview
|
* `/pacotes/{slug}`. Public copy follows "Apresentação dos serviços Amare"
|
||||||
* (preview(2).html) and remains editable via the Filament resource. Detail
|
* (conceito e momento). Commercial limits, price and third-party tools stay
|
||||||
* copy follows the complete-assessoria preview
|
* off this seeder and off the public site.
|
||||||
* (amare-assessoria-completa.html) adapted to each modality.
|
|
||||||
*/
|
*/
|
||||||
class WeddingPackagesSeeder extends Seeder
|
class WeddingPackagesSeeder extends Seeder
|
||||||
{
|
{
|
||||||
@@ -27,141 +26,147 @@ class WeddingPackagesSeeder extends Seeder
|
|||||||
'slug' => 'essenza',
|
'slug' => 'essenza',
|
||||||
'level' => '01 / COMPLETA',
|
'level' => '01 / COMPLETA',
|
||||||
'tag' => 'Assessoria completa',
|
'tag' => 'Assessoria completa',
|
||||||
'subtitle' => 'Do planejamento ao grande dia.',
|
'subtitle' => 'Do primeiro planejamento ao grande dia.',
|
||||||
'summary' => 'Para casais que desejam contar com a Amare desde o início ou ainda estão estruturando etapas importantes.',
|
'summary' => 'Ideal para casais que desejam contar com a Amare desde o início da organização ou que ainda estão estruturando etapas importantes do casamento.',
|
||||||
'scope_items' => [
|
'scope_items' => [
|
||||||
'Estruturação do planejamento',
|
'Estruturação e acompanhamento do planejamento',
|
||||||
'Cronogramas, prazos e prioridades',
|
'Organização de cronogramas, prazos e prioridades',
|
||||||
'Orientação e gestão de fornecedores',
|
'Orientação na contratação e gestão de fornecedores',
|
||||||
'Alinhamento entre os envolvidos',
|
'Acompanhamento das etapas do projeto',
|
||||||
'Coordenação da execução',
|
'Organização e conferência de contratos e informações',
|
||||||
|
'Alinhamento entre casal, fornecedores e demais envolvidos',
|
||||||
|
'Construção do cronograma do grande dia',
|
||||||
|
'Coordenação e acompanhamento da execução do evento',
|
||||||
],
|
],
|
||||||
'cta_label' => 'Quero conhecer a Essenza',
|
'cta_label' => 'Quero conhecer a Essenza',
|
||||||
'compare_heading' => 'Começar com a Amare',
|
'compare_heading' => 'Começar com a Amare',
|
||||||
'compare_summary' => 'Acompanhamento mais amplo ao longo do planejamento.',
|
'compare_summary' => 'Acompanhamento do primeiro planejamento ao grande dia.',
|
||||||
'sort_order' => 1,
|
'sort_order' => 1,
|
||||||
'eyebrow' => 'Assessoria',
|
'eyebrow' => 'Assessoria',
|
||||||
'title_line' => 'Assessoria',
|
'title_line' => 'Assessoria',
|
||||||
'title_emphasis' => 'Completa',
|
'title_emphasis' => 'Completa',
|
||||||
'hero_lead' => 'Do planejamento ao grande dia, com calma, precisão e cuidado.',
|
'hero_lead' => 'A assessoria completa acompanha o projeto de forma estratégica e próxima, desde a construção do planejamento até a execução do evento.',
|
||||||
'benefits' => [
|
'benefits' => [
|
||||||
['icon_key' => 'calendar', 'label' => 'Organização integral'],
|
['icon_key' => 'calendar', 'label' => 'Planejamento organizado'],
|
||||||
['icon_key' => 'checklist', 'label' => 'Planejamento personalizado'],
|
['icon_key' => 'checklist', 'label' => 'Decisões mais seguras'],
|
||||||
['icon_key' => 'users', 'label' => 'Fornecedores selecionados'],
|
['icon_key' => 'users', 'label' => 'Fornecedores alinhados'],
|
||||||
['icon_key' => 'heart', 'label' => 'Experiência leve e inesquecível'],
|
['icon_key' => 'heart', 'label' => 'Grande dia com leveza'],
|
||||||
],
|
],
|
||||||
'included_items' => [
|
'included_items' => [
|
||||||
['icon_key' => 'heart', 'title' => 'Escuta e briefing', 'description' => 'Cada casal é único; começamos por ouvir.'],
|
['icon_key' => 'checklist', 'title' => 'Estruturação do planejamento', 'description' => 'Organização do projeto desde o início, no ritmo do casal.'],
|
||||||
['icon_key' => 'checklist', 'title' => 'Planejamento completo', 'description' => 'Cada etapa mapeada com clareza.'],
|
['icon_key' => 'calendar', 'title' => 'Cronogramas e prioridades', 'description' => 'Prazos e etapas visíveis para decidir com calma.'],
|
||||||
['icon_key' => 'map', 'title' => 'Gestão de fornecedores', 'description' => 'Contatos e prazos sob cuidado.'],
|
['icon_key' => 'map', 'title' => 'Contratação e gestão de fornecedores', 'description' => 'Orientação e acompanhamento de quem entra no projeto.'],
|
||||||
['icon_key' => 'calendar', 'title' => 'Acompanhamento contínuo', 'description' => 'Perto do casal em cada decisão.'],
|
['icon_key' => 'users', 'title' => 'Alinhamento entre envolvidos', 'description' => 'Casal, fornecedores e demais partes na mesma conversa.'],
|
||||||
['icon_key' => 'spark', 'title' => 'Produção e logística', 'description' => 'Dia a dia do evento organizado nos detalhes.'],
|
['icon_key' => 'spark', 'title' => 'Contratos e informações', 'description' => 'Conferência do que foi combinado antes do grande dia.'],
|
||||||
['icon_key' => 'users', 'title' => 'Dia do evento', 'description' => 'Coordenação total para o casal viver o momento.'],
|
['icon_key' => 'heart', 'title' => 'Coordenação da execução', 'description' => 'Acompanhamento do evento para o casal viver o momento.'],
|
||||||
],
|
],
|
||||||
'audience_heading' => 'Para quem é este pacote',
|
'audience_heading' => 'Para quem é esta modalidade',
|
||||||
'audience_intro' => 'Para quem quer uma assessoria completa, do início ao fim.',
|
'audience_intro' => 'Para quem deseja ter ao lado uma profissional conduzindo o processo como um todo, com organização, estratégia e tranquilidade.',
|
||||||
'audience_points' => [
|
'audience_points' => [
|
||||||
'Casamentos',
|
'Casais no início da organização',
|
||||||
'Aniversários',
|
'Quem ainda está estruturando etapas importantes',
|
||||||
'Eventos sociais e celebrações',
|
'Quem quer acompanhamento do planejamento à execução',
|
||||||
'Eventos corporativos',
|
|
||||||
],
|
],
|
||||||
'final_cta_heading' => 'Pronta para começar?',
|
'final_cta_heading' => 'Vamos nos conhecer?',
|
||||||
'final_cta_body' => 'Conte seu evento para a Amare e receba uma proposta.',
|
'final_cta_body' => 'Antes de definir qualquer caminho, queremos conhecer vocês. Basta entrar em contato para uma conversa.',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'Conduzione',
|
'name' => 'Conduzione',
|
||||||
'slug' => 'conduzione',
|
'slug' => 'conduzione',
|
||||||
'level' => '02 / PARCIAL',
|
'level' => '02 / PARCIAL',
|
||||||
'tag' => 'Assessoria parcial',
|
'tag' => 'Assessoria parcial',
|
||||||
'subtitle' => 'Para quem já começou e quer seguir acompanhado.',
|
'subtitle' => 'Para quem já começou, mas quer seguir acompanhado.',
|
||||||
'summary' => 'Para casais que já tomaram decisões e contrataram parte dos fornecedores, mas precisam estruturar o que falta.',
|
'summary' => 'Indicada para casais que já iniciaram o planejamento e possuem parte dos fornecedores ou decisões definidas, mas desejam organizar o que falta e conduzir as próximas etapas.',
|
||||||
'scope_items' => [
|
'scope_items' => [
|
||||||
'Diagnóstico do planejamento existente',
|
'Diagnóstico do planejamento já realizado',
|
||||||
'Organização das pendências',
|
'Organização das etapas pendentes',
|
||||||
'Gestão dos fornecedores contratados',
|
'Gestão e acompanhamento dos fornecedores já contratados',
|
||||||
'Orientação para próximas decisões',
|
'Orientação sobre as próximas decisões',
|
||||||
'Coordenação da execução',
|
'Acompanhamento de prazos e informações',
|
||||||
|
'Comunicação e alinhamento com fornecedores',
|
||||||
|
'Organização do cronograma do evento',
|
||||||
|
'Coordenação da execução no grande dia',
|
||||||
],
|
],
|
||||||
'cta_label' => 'Quero conhecer a Conduzione',
|
'cta_label' => 'Quero conhecer a Conduzione',
|
||||||
'compare_heading' => 'Trazer a Amare para o caminho',
|
'compare_heading' => 'Trazer a Amare para o caminho',
|
||||||
'compare_summary' => 'O planejamento existe, mas ainda há decisões e gestão pela frente.',
|
'compare_summary' => 'O planejamento existe; a Amare organiza o que falta e conduz as próximas etapas.',
|
||||||
'sort_order' => 2,
|
'sort_order' => 2,
|
||||||
'eyebrow' => 'Assessoria',
|
'eyebrow' => 'Assessoria',
|
||||||
'title_line' => 'Assessoria',
|
'title_line' => 'Assessoria',
|
||||||
'title_emphasis' => 'Parcial',
|
'title_emphasis' => 'Parcial',
|
||||||
'hero_lead' => 'Para retomar o planejamento, organizar o que falta e seguir com acompanhamento profissional.',
|
'hero_lead' => 'A Amare entra no momento em que vocês estão, compreende o que já foi construído e assume a gestão das etapas necessárias dentro do planejamento.',
|
||||||
'benefits' => [
|
'benefits' => [
|
||||||
['icon_key' => 'checklist', 'label' => 'Diagnóstico completo'],
|
['icon_key' => 'checklist', 'label' => 'Diagnóstico do que já existe'],
|
||||||
['icon_key' => 'calendar', 'label' => 'Organização integral'],
|
['icon_key' => 'calendar', 'label' => 'Pendências organizadas'],
|
||||||
['icon_key' => 'users', 'label' => 'Fornecedores selecionados'],
|
['icon_key' => 'users', 'label' => 'Fornecedores acompanhados'],
|
||||||
['icon_key' => 'heart', 'label' => 'Experiência leve e inesquecível'],
|
['icon_key' => 'heart', 'label' => 'Próximas decisões com clareza'],
|
||||||
],
|
],
|
||||||
'included_items' => [
|
'included_items' => [
|
||||||
['icon_key' => 'checklist', 'title' => 'Diagnóstico do planejamento', 'description' => 'Um olhar profissional sobre o que já existe.'],
|
['icon_key' => 'checklist', 'title' => 'Diagnóstico do planejamento', 'description' => 'Um olhar profissional sobre o que o casal já construiu.'],
|
||||||
['icon_key' => 'calendar', 'title' => 'Organização das pendências', 'description' => 'Prioridades claras, etapa a etapa.'],
|
['icon_key' => 'calendar', 'title' => 'Etapas pendentes', 'description' => 'O que falta ganha ordem, prazo e prioridade.'],
|
||||||
['icon_key' => 'map', 'title' => 'Gestão de fornecedores', 'description' => 'Contatos e prazos sob cuidado.'],
|
['icon_key' => 'map', 'title' => 'Fornecedores já contratados', 'description' => 'Gestão e comunicação com quem já está no projeto.'],
|
||||||
['icon_key' => 'spark', 'title' => 'Gestão dos próximos passos', 'description' => 'Um plano para chegar tranquilo ao grande dia.'],
|
['icon_key' => 'spark', 'title' => 'Próximas decisões', 'description' => 'Orientação para seguir sem retrabalho.'],
|
||||||
['icon_key' => 'users', 'title' => 'Acompanhamento contínuo', 'description' => 'Perto do casal em cada decisão.'],
|
['icon_key' => 'users', 'title' => 'Cronograma do evento', 'description' => 'A operação do grande dia começa a ficar visível.'],
|
||||||
['icon_key' => 'heart', 'title' => 'Dia do evento', 'description' => 'Coordenação total para o casal viver o momento.'],
|
['icon_key' => 'heart', 'title' => 'Coordenação no grande dia', 'description' => 'Execução acompanhada para o casal viver o momento.'],
|
||||||
],
|
],
|
||||||
'audience_heading' => 'Para quem é este pacote',
|
'audience_heading' => 'Para quem é esta modalidade',
|
||||||
'audience_intro' => 'Para quem já começou a planejar e quer retomar as rédeas com acompanhamento profissional.',
|
'audience_intro' => 'Para quem já deu os primeiros passos, mas deseja seguir com mais segurança, organização e tranquilidade.',
|
||||||
'audience_points' => [
|
'audience_points' => [
|
||||||
'Casamentos em andamento',
|
'Casais que já iniciaram o planejamento',
|
||||||
'Casais com pouco tempo disponível',
|
'Quem já tem parte dos fornecedores ou decisões definidas',
|
||||||
'Quem precisa priorizar pendências',
|
'Quem quer condução profissional nas etapas que faltam',
|
||||||
'Quem busca fornecedores confiáveis',
|
|
||||||
],
|
],
|
||||||
'final_cta_heading' => 'Pronta para retomar?',
|
'final_cta_heading' => 'Vamos organizar o que falta?',
|
||||||
'final_cta_body' => 'Conte onde seu planejamento está e receba uma proposta.',
|
'final_cta_body' => 'Conte onde o planejamento está. A Amare entra nesse momento e conduz as próximas etapas.',
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
'name' => 'Grand Jour',
|
'name' => 'Grand Jour',
|
||||||
'slug' => 'grand-jour',
|
'slug' => 'grand-jour',
|
||||||
'level' => '03 / FINAL',
|
'level' => '03 / FINAL',
|
||||||
'tag' => 'Assessoria final',
|
'tag' => 'Assessoria final',
|
||||||
'subtitle' => 'O planejamento está pronto. Agora é hora de viver.',
|
'subtitle' => 'Para quem já planejou e precisa de alguém para assumir a operação.',
|
||||||
'summary' => 'Para casais na reta final que precisam de uma equipe para assumir alinhamentos, cronograma e operação.',
|
'summary' => 'Indicada para casais que estão na reta final dos preparativos e já possuem a maior parte do casamento definida.',
|
||||||
'scope_items' => [
|
'scope_items' => [
|
||||||
'Imersão no planejamento existente',
|
'Imersão no planejamento existente',
|
||||||
'Conferência das informações',
|
'Conferência das informações e contratos dos fornecedores',
|
||||||
'Alinhamento dos fornecedores',
|
'Alinhamento e comunicação com os profissionais envolvidos',
|
||||||
'Organização do cronograma final',
|
'Organização do cronograma final',
|
||||||
'Gestão da operação do evento',
|
'Orientação sobre os últimos detalhes',
|
||||||
|
'Coordenação dos fornecedores no evento',
|
||||||
|
'Acompanhamento da montagem e dos momentos do casamento',
|
||||||
|
'Gestão da operação durante o grande dia',
|
||||||
],
|
],
|
||||||
'cta_label' => 'Quero conhecer a Grand Jour',
|
'cta_label' => 'Quero conhecer a Grand Jour',
|
||||||
'compare_heading' => 'Entregar a operação para a Amare',
|
'compare_heading' => 'Entregar a operação para a Amare',
|
||||||
'compare_summary' => 'O projeto está pronto e o foco passa a ser alinhamento e execução.',
|
'compare_summary' => 'O projeto está pronto; o foco passa a ser alinhamento e execução.',
|
||||||
'sort_order' => 3,
|
'sort_order' => 3,
|
||||||
'eyebrow' => 'Assessoria',
|
'eyebrow' => 'Assessoria',
|
||||||
'title_line' => 'Assessoria',
|
'title_line' => 'Assessoria',
|
||||||
'title_emphasis' => 'Final',
|
'title_emphasis' => 'Final',
|
||||||
'hero_lead' => 'Para assumir os alinhamentos e a operação quando o grande dia está próximo.',
|
'hero_lead' => 'A Amare assume a condução operacional, mergulha no planejamento construído pelo casal e transforma as informações em uma execução organizada e alinhada.',
|
||||||
'benefits' => [
|
'benefits' => [
|
||||||
['icon_key' => 'checklist', 'label' => 'Imersão no que já existe'],
|
['icon_key' => 'checklist', 'label' => 'Imersão no planejamento'],
|
||||||
['icon_key' => 'users', 'label' => 'Alinhamento de fornecedores'],
|
['icon_key' => 'users', 'label' => 'Fornecedores alinhados'],
|
||||||
['icon_key' => 'calendar', 'label' => 'Cronograma final'],
|
['icon_key' => 'calendar', 'label' => 'Cronograma final'],
|
||||||
['icon_key' => 'heart', 'label' => 'Operação com leveza'],
|
['icon_key' => 'heart', 'label' => 'Operação no grande dia'],
|
||||||
],
|
],
|
||||||
'included_items' => [
|
'included_items' => [
|
||||||
['icon_key' => 'checklist', 'title' => 'Imersão no planejamento', 'description' => 'Entendemos cada decisão já tomada.'],
|
['icon_key' => 'checklist', 'title' => 'Imersão no planejamento', 'description' => 'A Amare mergulha no que o casal já decidiu.'],
|
||||||
['icon_key' => 'map', 'title' => 'Alinhamento de fornecedores', 'description' => 'Contatos e prazos sob cuidado.'],
|
['icon_key' => 'map', 'title' => 'Contratos e informações', 'description' => 'Conferência do combinado com cada profissional.'],
|
||||||
['icon_key' => 'calendar', 'title' => 'Cronograma final', 'description' => 'Cada etapa posicionada na reta final.'],
|
['icon_key' => 'users', 'title' => 'Alinhamento dos envolvidos', 'description' => 'Comunicação centralizada na reta final.'],
|
||||||
['icon_key' => 'spark', 'title' => 'Ensaios e detalhes', 'description' => 'Produção e logística nos detalhes.'],
|
['icon_key' => 'calendar', 'title' => 'Cronograma final', 'description' => 'A operação do grande dia ganha ordem.'],
|
||||||
['icon_key' => 'users', 'title' => 'Operação do evento', 'description' => 'Gestão da equipe e do dia a dia.'],
|
['icon_key' => 'spark', 'title' => 'Últimos detalhes', 'description' => 'Orientação para fechar o que ainda falta.'],
|
||||||
['icon_key' => 'heart', 'title' => 'Dia do evento', 'description' => 'Coordenação total para o casal viver o momento.'],
|
['icon_key' => 'heart', 'title' => 'Gestão da operação', 'description' => 'Montagem, momentos e condução no dia do casamento.'],
|
||||||
],
|
],
|
||||||
'audience_heading' => 'Para quem é este pacote',
|
'audience_heading' => 'Para quem é esta modalidade',
|
||||||
'audience_intro' => 'Para casais já planejados que querem chegar ao grande dia com equipe e operação no lugar.',
|
'audience_intro' => 'Porque, na reta final, vocês não precisam de mais tarefas. Precisam de alguém que assuma a condução.',
|
||||||
'audience_points' => [
|
'audience_points' => [
|
||||||
'Casamentos já planejados',
|
'Casais na reta final dos preparativos',
|
||||||
'Casais na reta final',
|
'Quem já definiu a maior parte do casamento',
|
||||||
'Quem precisa de operação no dia',
|
'Quem quer entregar a operação do grande dia',
|
||||||
'Quem quer viver o momento com leveza',
|
|
||||||
],
|
],
|
||||||
'final_cta_heading' => 'Pronta para o grande dia?',
|
'final_cta_heading' => 'Vamos assumir a operação?',
|
||||||
'final_cta_body' => 'Conte a data do seu evento e receba uma proposta.',
|
'final_cta_body' => 'Conte a data e o que já está definido. A Amare entra para conduzir o grande dia.',
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -1,51 +1,28 @@
|
|||||||
# Domain Docs
|
# Domain Docs
|
||||||
|
|
||||||
How the engineering skills should consume this repo's domain documentation when exploring the codebase.
|
How engineering skills should consume this repo's domain documentation.
|
||||||
|
|
||||||
## Before exploring, read these
|
## Single source of truth
|
||||||
|
|
||||||
- **`CONTEXT.md`** at the repo root, or
|
**`SPEC.md` §8** (especially **§8.0 Linguagem ubíqua**) is the glossary and domain model for Amare. There is no root `CONTEXT.md` on purpose — do not create one. Prefer `SPEC.md` over Linear copy or OpenSpec prose when terms conflict.
|
||||||
- **`CONTEXT-MAP.md`** at the repo root if it exists — it points at one `CONTEXT.md` per context. Read each one relevant to the topic.
|
|
||||||
- **`docs/adr/`** — read ADRs that touch the area you're about to work in. In multi-context repos, also check `src/<context>/docs/adr/` for context-scoped decisions.
|
|
||||||
|
|
||||||
If any of these files don't exist, **proceed silently**. Don't flag their absence; don't suggest creating them upfront. The `/domain-modeling` skill (reached via `/grill-with-docs` and `/improve-codebase-architecture`) creates them lazily when terms or decisions actually get resolved.
|
Also useful:
|
||||||
|
|
||||||
## File structure
|
- **`PRODUCT.md`** — positioning and audience (not the glossary)
|
||||||
|
- **`openspec/specs/`** — current capability contracts
|
||||||
|
- **`docs/adr/README.md`** — index only; ADR-001–010 live in `SPEC.md` §21
|
||||||
|
|
||||||
Single-context repo (most repos):
|
## Before exploring
|
||||||
|
|
||||||
```
|
1. Read `SPEC.md` §8.0 for vocabulary; skim §5.1 for public routes and §8.2+ for persistence shape when relevant.
|
||||||
/
|
2. If a skill expects `CONTEXT.md` / `CONTEXT-MAP.md` and finds neither, **proceed using `SPEC.md` §8** — do not invent a parallel glossary file.
|
||||||
├── CONTEXT.md
|
|
||||||
├── docs/adr/
|
|
||||||
│ ├── 0001-event-sourced-orders.md
|
|
||||||
│ └── 0002-postgres-for-write-model.md
|
|
||||||
└── src/
|
|
||||||
```
|
|
||||||
|
|
||||||
Multi-context repo (presence of `CONTEXT-MAP.md` at the root):
|
|
||||||
|
|
||||||
```
|
|
||||||
/
|
|
||||||
├── CONTEXT-MAP.md
|
|
||||||
├── docs/adr/ ← system-wide decisions
|
|
||||||
└── src/
|
|
||||||
├── ordering/
|
|
||||||
│ ├── CONTEXT.md
|
|
||||||
│ └── docs/adr/ ← context-specific decisions
|
|
||||||
└── billing/
|
|
||||||
├── CONTEXT.md
|
|
||||||
└── docs/adr/
|
|
||||||
```
|
|
||||||
|
|
||||||
## Use the glossary's vocabulary
|
## Use the glossary's vocabulary
|
||||||
|
|
||||||
When your output names a domain concept (in an issue title, a refactor proposal, a hypothesis, a test name), use the term as defined in `CONTEXT.md`. Don't drift to synonyms the glossary explicitly avoids.
|
When output names a domain concept (issue title, refactor, hypothesis, test name), use the term as defined in `SPEC.md` §8.0. Don't drift to synonyms the glossary explicitly avoids (`_Avoid_`).
|
||||||
|
|
||||||
If the concept you need isn't in the glossary yet, that's a signal — either you're inventing language the project doesn't use (reconsider) or there's a real gap (note it for `/domain-modeling`).
|
If the concept isn't in §8.0 yet, either you're inventing language the project doesn't use (reconsider) or there's a real gap — resolve it by updating `SPEC.md` §8.0 via `/grill-with-docs` / domain-modeling, not by adding `CONTEXT.md`.
|
||||||
|
|
||||||
## Flag ADR conflicts
|
## Flag ADR conflicts
|
||||||
|
|
||||||
If your output contradicts an existing ADR, surface it explicitly rather than silently overriding:
|
If output contradicts an ADR in `SPEC.md` §21, surface it explicitly rather than silently overriding.
|
||||||
|
|
||||||
> _Contradicts ADR-0007 (event-sourced orders) — but worth reopening because…_
|
|
||||||
|
|||||||
@@ -14,9 +14,6 @@
|
|||||||
'note' => 'Nomenclaturas exibidas conforme materiais/reunião; confirmar versão final antes da publicação.',
|
'note' => 'Nomenclaturas exibidas conforme materiais/reunião; confirmar versão final antes da publicação.',
|
||||||
])
|
])
|
||||||
|
|
||||||
@php
|
|
||||||
$bandHref = $bandCtaHref ?? route('briefing');
|
|
||||||
@endphp
|
|
||||||
<section
|
<section
|
||||||
aria-labelledby="packages-heading"
|
aria-labelledby="packages-heading"
|
||||||
class="home-chapter border-b border-amare-border bg-amare-bg"
|
class="home-chapter border-b border-amare-border bg-amare-bg"
|
||||||
@@ -72,12 +69,20 @@
|
|||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
@php($orientationLink = \App\Support\PackageContactLink::forOrientation($settings))
|
||||||
|
@php($bandHref = filled($bandCtaHref) ? $bandCtaHref : $orientationLink['href'])
|
||||||
|
@php($bandIsWhatsapp = ! filled($bandCtaHref) && $orientationLink['isWhatsapp'])
|
||||||
<div class="mt-6 flex flex-col justify-between gap-8 bg-amare-accent-deep p-8 text-amare-accent-text md:flex-row md:items-center md:p-12" data-reveal data-reveal-from="up">
|
<div class="mt-6 flex flex-col justify-between gap-8 bg-amare-accent-deep p-8 text-amare-accent-text md:flex-row md:items-center md:p-12" data-reveal data-reveal-from="up">
|
||||||
<div class="max-w-xl space-y-3">
|
<div class="max-w-xl space-y-3">
|
||||||
<h3 class="text-[clamp(1.5625rem,2.7vw,2.125rem)] font-medium leading-tight">Ainda não sabe qual modalidade é ideal?</h3>
|
<h3 class="text-[clamp(1.5625rem,2.7vw,2.125rem)] font-medium leading-tight">Ainda não sabe qual modalidade é ideal?</h3>
|
||||||
<p class="text-amare-accent-text/80">{{ $bandBody }}</p>
|
<p class="text-amare-accent-text/80">{{ $bandBody }}</p>
|
||||||
</div>
|
</div>
|
||||||
<a href="{{ $bandHref }}" class="inline-flex min-h-[48px] items-center justify-center self-start border border-amare-accent-text px-6 text-xs font-bold uppercase tracking-[0.09em] text-amare-accent-text transition-colors hover:bg-amare-accent-text hover:text-amare-accent-deep md:self-auto">
|
<a
|
||||||
|
data-testid="package-guidance-cta"
|
||||||
|
href="{{ $bandHref }}"
|
||||||
|
@if ($bandIsWhatsapp) target="_blank" rel="noopener noreferrer" @endif
|
||||||
|
class="inline-flex min-h-[48px] items-center justify-center self-start border border-amare-accent-text px-6 text-xs font-bold uppercase tracking-[0.09em] text-amare-accent-text transition-colors hover:bg-amare-accent-text hover:text-amare-accent-deep md:self-auto"
|
||||||
|
>
|
||||||
Conversar com a Amare
|
Conversar com a Amare
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -47,7 +47,6 @@
|
|||||||
:show-intro="false"
|
:show-intro="false"
|
||||||
kicker="tag"
|
kicker="tag"
|
||||||
:show-subtitle="true"
|
:show-subtitle="true"
|
||||||
:band-cta-href="route('briefing')"
|
|
||||||
band-body="Conte um pouco sobre o casamento. A Amare entende o momento de vocês e orienta o melhor formato de acompanhamento sem depender de um quiz automático."
|
band-body="Conte um pouco sobre o casamento. A Amare entende o momento de vocês e orienta o melhor formato de acompanhamento sem depender de um quiz automático."
|
||||||
:show-note="false"
|
:show-note="false"
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -38,6 +38,33 @@ class WeddingPackageTest extends TestCase
|
|||||||
['Essenza', 'Conduzione', 'Grand Jour'],
|
['Essenza', 'Conduzione', 'Grand Jour'],
|
||||||
WeddingPackage::query()->published()->orderBy('sort_order')->pluck('name')->all(),
|
WeddingPackage::query()->published()->orderBy('sort_order')->pluck('name')->all(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$essenza = WeddingPackage::query()->where('name', 'Essenza')->first();
|
||||||
|
$this->assertNotNull($essenza);
|
||||||
|
$this->assertSame('Assessoria completa', $essenza->tag);
|
||||||
|
$this->assertSame('Do primeiro planejamento ao grande dia.', $essenza->subtitle);
|
||||||
|
$this->assertStringContainsString('desde o início da organização', (string) $essenza->summary);
|
||||||
|
$this->assertContains('Estruturação e acompanhamento do planejamento', $essenza->scope_items);
|
||||||
|
$this->assertSame(
|
||||||
|
['Casais no início da organização', 'Quem ainda está estruturando etapas importantes', 'Quem quer acompanhamento do planejamento à execução'],
|
||||||
|
$essenza->audience_points,
|
||||||
|
);
|
||||||
|
|
||||||
|
$publicCopy = WeddingPackage::query()->published()->get()->map(fn (WeddingPackage $package): string => json_encode([
|
||||||
|
$package->summary,
|
||||||
|
$package->subtitle,
|
||||||
|
$package->hero_lead,
|
||||||
|
$package->scope_items,
|
||||||
|
$package->included_items,
|
||||||
|
$package->audience_intro,
|
||||||
|
$package->final_cta_body,
|
||||||
|
], JSON_THROW_ON_ERROR))->implode(' ');
|
||||||
|
|
||||||
|
$this->assertStringNotContainsString('R$', $publicCopy);
|
||||||
|
$this->assertStringNotContainsString('3.500', $publicCopy);
|
||||||
|
$this->assertStringNotContainsString('Assessoria VIP', $publicCopy);
|
||||||
|
$this->assertStringNotContainsString('90 km', $publicCopy);
|
||||||
|
$this->assertStringNotContainsString('3,49%', $publicCopy);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_factory_generates_a_slug_and_autofills_blank_slug_from_name(): void
|
public function test_factory_generates_a_slug_and_autofills_blank_slug_from_name(): void
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use App\Models\PortfolioCase;
|
|||||||
use App\Models\SiteSetting;
|
use App\Models\SiteSetting;
|
||||||
use App\Models\Testimonial;
|
use App\Models\Testimonial;
|
||||||
use App\Models\WeddingPackage;
|
use App\Models\WeddingPackage;
|
||||||
|
use App\Support\PackageContactLink;
|
||||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
use Tests\TestCase;
|
use Tests\TestCase;
|
||||||
|
|
||||||
@@ -213,7 +214,24 @@ class HomePageContentTest extends TestCase
|
|||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
->assertSee('target="_blank"', false)
|
->assertSee('target="_blank"', false)
|
||||||
->assertDontSee(route('briefing', ['servico_interesse' => 'Grand Jour']), false);
|
->assertDontSee(route('briefing', ['servico_interesse' => 'Grand Jour']), false)
|
||||||
|
->assertSee('data-testid="package-guidance-cta"', false)
|
||||||
|
->assertSee(
|
||||||
|
'https://wa.me/5511988887777?text='.rawurlencode(PackageContactLink::ORIENTATION_MESSAGE),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function test_orientation_cta_falls_back_to_briefing_without_whatsapp_number(): void
|
||||||
|
{
|
||||||
|
SiteSetting::instance()->update(['whatsapp_number' => null]);
|
||||||
|
|
||||||
|
$response = $this->get(route('home'));
|
||||||
|
|
||||||
|
$response
|
||||||
|
->assertOk()
|
||||||
|
->assertSee('href="'.route('briefing').'"', false)
|
||||||
|
->assertDontSee(PackageContactLink::ORIENTATION_MESSAGE, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_corporate_steps_and_placeholder_render_from_settings(): void
|
public function test_corporate_steps_and_placeholder_render_from_settings(): void
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ namespace Tests\Feature\PublicSite;
|
|||||||
|
|
||||||
use App\Models\SiteSetting;
|
use App\Models\SiteSetting;
|
||||||
use App\Models\WeddingPackage;
|
use App\Models\WeddingPackage;
|
||||||
|
use App\Support\PackageContactLink;
|
||||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||||
use Tests\TestCase;
|
use Tests\TestCase;
|
||||||
|
|
||||||
@@ -31,7 +32,12 @@ class ServicesPageCtaTest extends TestCase
|
|||||||
false,
|
false,
|
||||||
)
|
)
|
||||||
->assertSee('target="_blank"', false)
|
->assertSee('target="_blank"', false)
|
||||||
->assertDontSee(route('briefing', ['servico_interesse' => 'Grand Jour']), false);
|
->assertDontSee(route('briefing', ['servico_interesse' => 'Grand Jour']), false)
|
||||||
|
->assertSee('data-testid="package-guidance-cta"', false)
|
||||||
|
->assertSee(
|
||||||
|
'https://wa.me/5511988887777?text='.rawurlencode(PackageContactLink::ORIENTATION_MESSAGE),
|
||||||
|
false,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function test_package_cta_falls_back_to_briefing_without_whatsapp_number(): void
|
public function test_package_cta_falls_back_to_briefing_without_whatsapp_number(): void
|
||||||
@@ -50,4 +56,18 @@ class ServicesPageCtaTest extends TestCase
|
|||||||
->assertSee('href="'.route('briefing', ['servico_interesse' => 'Essenza']).'"', false)
|
->assertSee('href="'.route('briefing', ['servico_interesse' => 'Essenza']).'"', false)
|
||||||
->assertDontSee('wa.me/', false);
|
->assertDontSee('wa.me/', false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function test_orientation_cta_uses_distinct_whatsapp_message_when_a_number_is_configured(): void
|
||||||
|
{
|
||||||
|
SiteSetting::instance()->update(['whatsapp_number' => '+55 11 98888-7777']);
|
||||||
|
|
||||||
|
$response = $this->get(route('services.index'));
|
||||||
|
|
||||||
|
$response
|
||||||
|
->assertOk()
|
||||||
|
->assertSee(
|
||||||
|
'https://wa.me/5511988887777?text='.rawurlencode(PackageContactLink::ORIENTATION_MESSAGE),
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user