Files
amare/app/Filament/Pages/ManageSiteSettings.php
manoel freitas 33788af172 feat: visual placeholders, type scale, CTAs and about image
Replace tiny JPEG stubs with per-slot Unsplash editorial placeholders,
align public type scale to DESIGN.md display/headline tokens, add
final-cta on all subpages, about_image_path on site settings, and harden
visual regression with solid-color VisualContentSeeder fixtures.
2026-08-06 09:38:13 -03:00

303 lines
11 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Filament\Pages;
use App\Models\SiteSetting;
use App\Support\PublicImageUploadRules;
use BackedEnum;
use Filament\Actions\Action;
use Filament\Actions\ActionGroup;
use Filament\Forms\Components\KeyValue;
use Filament\Forms\Components\Repeater;
use Filament\Forms\Components\TagsInput;
use Filament\Forms\Components\Textarea;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\Toggle;
use Filament\Notifications\Notification;
use Filament\Pages\Concerns\CanUseDatabaseTransactions;
use Filament\Pages\Page;
use Filament\Schemas\Components\Actions;
use Filament\Schemas\Components\Component;
use Filament\Schemas\Components\EmbeddedSchema;
use Filament\Schemas\Components\Form;
use Filament\Schemas\Components\Section;
use Filament\Schemas\Schema;
use Filament\Support\Exceptions\Halt;
use Filament\Support\Facades\FilamentView;
use Filament\Support\Icons\Heroicon;
use Livewire\Attributes\Locked;
use Throwable;
use UnitEnum;
/**
* @property-read Schema $form
*/
class ManageSiteSettings extends Page
{
use CanUseDatabaseTransactions;
protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedCog6Tooth;
protected static ?string $navigationLabel = 'Configurações';
protected static ?string $title = 'Configurações do site';
protected static string|UnitEnum|null $navigationGroup = 'Conteúdo do site';
protected static ?int $navigationSort = 1;
/**
* @var array<string, mixed>|null
*/
public ?array $data = [];
#[Locked]
public SiteSetting $record;
public static function canAccess(): bool
{
$user = auth()->user();
return $user !== null && $user->isAdmin();
}
public function mount(): void
{
$this->record = SiteSetting::instance();
abort_unless(auth()->user()?->can('update', $this->record), 403);
$this->fillForm();
}
public function hydrate(): void
{
abort_unless(auth()->user()?->can('update', $this->record), 403);
}
protected function fillForm(): void
{
$this->form->fill($this->record->attributesToArray());
}
public function save(): void
{
try {
$this->beginDatabaseTransaction();
$data = $this->form->getState();
$this->record->update($data);
} catch (Halt $exception) {
$exception->shouldRollbackDatabaseTransaction() ?
$this->rollBackDatabaseTransaction() :
$this->commitDatabaseTransaction();
return;
} catch (Throwable $exception) {
$this->rollBackDatabaseTransaction();
throw $exception;
}
$this->commitDatabaseTransaction();
Notification::make()
->success()
->title('Configurações salvas com sucesso.')
->send();
if ($redirectUrl = $this->getRedirectUrl()) {
$this->redirect($redirectUrl, navigate: FilamentView::hasSpaMode($redirectUrl));
}
}
protected function getRedirectUrl(): ?string
{
return null;
}
public function defaultForm(Schema $schema): Schema
{
return $schema
->operation('edit')
->model($this->record)
->statePath('data');
}
public function form(Schema $schema): Schema
{
return $schema
->components([
Section::make('Marca e hero')
->schema([
TextInput::make('brand_name')
->label('Nome da marca')
->required()
->maxLength(255),
PublicImageUploadRules::fileUpload('logo_path', 'Logo da marca', 'content/logo'),
PublicImageUploadRules::altTextField('logo_alt', 'logo_path', 'Texto alternativo do logo'),
TextInput::make('hero_eyebrow')
->label('Eyebrow do hero')
->maxLength(255),
TextInput::make('hero_title')
->label('Título do hero')
->required()
->maxLength(255),
Textarea::make('hero_subtitle')
->label('Subtítulo do hero')
->required()
->rows(3),
TextInput::make('hero_cta_label')
->label('Texto do CTA principal')
->required()
->maxLength(255),
TextInput::make('hero_secondary_cta_label')
->label('Texto do CTA secundário')
->maxLength(255),
Textarea::make('hero_note')
->label('Nota do hero')
->rows(2),
Textarea::make('about_summary')
->label('Resumo institucional')
->rows(3),
])
->columns(2),
Section::make('Manifesto editorial')
->schema([
TextInput::make('manifesto_title')
->label('Título do manifesto')
->maxLength(255),
Textarea::make('manifesto_lead')
->label('Lead do manifesto')
->rows(3),
Textarea::make('manifesto_body')
->label('Corpo do manifesto')
->rows(4),
]),
Section::make('Método')
->schema([
Textarea::make('method_intro')
->label('Introdução do método')
->rows(2),
Repeater::make('method_steps')
->label('Passos do método')
->schema([
TextInput::make('title')
->label('Título')
->required()
->maxLength(255),
Textarea::make('body')
->label('Descrição')
->required()
->rows(2),
])
->defaultItems(0)
->maxItems(4)
->reorderable()
->columnSpanFull(),
]),
Section::make('Princípios')
->schema([
TagsInput::make('principles')
->label('Princípios')
->placeholder('Adicionar princípio')
->helperText('Até quatro princípios editoriais.')
->columnSpanFull(),
]),
Section::make('Contato')
->schema([
TextInput::make('email')
->label('E-mail')
->email()
->required()
->maxLength(255),
TextInput::make('phone')
->label('Telefone')
->required()
->maxLength(255),
TextInput::make('city')
->label('Cidade')
->maxLength(255),
KeyValue::make('social_links')
->label('Redes sociais')
->keyLabel('Rede')
->valueLabel('URL')
->addActionLabel('Adicionar rede'),
])
->columns(2),
Section::make('Página Sobre')
->schema([
PublicImageUploadRules::fileUpload('about_image_path', 'Imagem da página Sobre', 'content/about'),
PublicImageUploadRules::altTextField('about_image_alt', 'about_image_path'),
])
->columns(2),
Section::make('SEO padrão')
->schema([
TextInput::make('default_meta_title')
->label('Meta title padrão')
->required()
->maxLength(255),
Textarea::make('default_meta_description')
->label('Meta description padrão')
->required()
->rows(3),
PublicImageUploadRules::fileUpload('default_og_image_path', 'Imagem Open Graph padrão', 'content/og'),
PublicImageUploadRules::altTextField('default_og_image_alt', 'default_og_image_path'),
]),
Section::make('Analytics')
->schema([
Toggle::make('analytics_enabled')
->label('Analytics habilitado')
->default(false),
Textarea::make('analytics_script')
->label('Script de analytics')
->rows(4)
->visible(fn (callable $get): bool => (bool) $get('analytics_enabled')),
]),
]);
}
/**
* @return array<Action|ActionGroup>
*/
protected function getFormActions(): array
{
return [
Action::make('save')
->label('Salvar')
->submit('save')
->keyBindings(['mod+s']),
];
}
public function content(Schema $schema): Schema
{
return $schema
->components([
$this->getFormContentComponent(),
]);
}
public function getFormContentComponent(): Component
{
return Form::make([EmbeddedSchema::make('form')])
->id('form')
->livewireSubmitHandler('save')
->footer([
Actions::make($this->getFormActions())
->alignment($this->getFormActionsAlignment())
->fullWidth($this->hasFullWidthFormActions())
->sticky($this->areFormActionsSticky())
->key('form-actions'),
]);
}
protected function hasFullWidthFormActions(): bool
{
return false;
}
}