* feat(home): alinhar cadência editorial * test(home): ajustar contrato do hero dividido
316 lines
12 KiB
PHP
316 lines
12 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),
|
|
PublicImageUploadRules::fileUpload('hero_image_path', 'Imagem do hero', 'content/home'),
|
|
PublicImageUploadRules::altTextField('hero_image_alt', 'hero_image_path'),
|
|
Textarea::make('about_summary')
|
|
->label('Resumo institucional')
|
|
->rows(3),
|
|
])
|
|
->columns(2),
|
|
Section::make('Imagens das aberturas')
|
|
->description('Fotos reais e autorizadas para as primeiras dobras. Sem imagem, cada rota mantém a abertura tonal.')
|
|
->schema([
|
|
PublicImageUploadRules::fileUpload('hero_image_path', 'Imagem da home', 'content/heroes'),
|
|
PublicImageUploadRules::altTextField('hero_image_alt', 'hero_image_path'),
|
|
PublicImageUploadRules::fileUpload('services_hero_image_path', 'Imagem de Serviços', 'content/heroes'),
|
|
PublicImageUploadRules::altTextField('services_hero_image_alt', 'services_hero_image_path'),
|
|
PublicImageUploadRules::fileUpload('portfolio_hero_image_path', 'Imagem de Portfólio', 'content/heroes'),
|
|
PublicImageUploadRules::altTextField('portfolio_hero_image_alt', 'portfolio_hero_image_path'),
|
|
])
|
|
->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;
|
|
}
|
|
}
|