! Schema::hasTable($table))); if ($missingTables !== []) { $this->components->error('Content composition tables are missing: '.implode(', ', $missingTables).'.'); $this->components->info('Run php artisan migrate before installing the Saasland composition.'); return self::FAILURE; } DB::transaction(function (): void { $this->installSharedSettings(); $this->installMenus(); if ($this->option('force')) { PageContent::query()->where('page_key', 'home')->delete(); } $order = [ 'site-header' => ['header', 10], 'hero' => ['body', 10], 'feature-grid' => ['body', 20], 'product-feed' => ['body', 30], 'testimonial-feed' => ['body', 40], 'team-feed' => ['body', 50], 'blog-feed' => ['body', 60], 'news-feed' => ['body', 70], 'faq-feed' => ['body', 80], 'newsletter-form' => ['body', 90], 'call-to-action' => ['body', 100], 'site-footer' => ['footer', 10], ]; foreach ($this->definitions() as $key => $definition) { $type = ContentType::query()->updateOrCreate(['key' => $key], $definition['type']); $document = ContentDocument::query()->firstOrCreate( ['slug' => 'saasland-'.$key], [ 'content_type_id' => $type->id, 'title' => $definition['title'], 'data' => $definition['data'], 'status' => 'published', 'published_at' => now(), ] ); $currentData = is_array($document->data) ? $document->data : []; $mergedData = $this->mergeMissingDefaults($currentData, $definition['data']); $document->forceFill([ 'content_type_id' => $type->id, 'data' => $mergedData, ])->save(); [$region, $sort] = $order[$key]; PageContent::query()->updateOrCreate( ['page_key' => 'home', 'region' => $region, 'sort_order' => $sort], ['content_document_id' => $document->id, 'is_enabled' => true] ); } }); $this->components->info('Saasland content composition installed with editable schemas.'); return self::SUCCESS; } private function installSharedSettings(): void { $settings = app(SettingsRepository::class); $legacy = []; if (Schema::hasTable('stylist_theme_settings')) { $stored = DB::table('stylist_theme_settings')->where('theme_key', 'saasland')->value('settings'); $legacy = is_string($stored) ? (json_decode($stored, true) ?: []) : []; } $defaults = [ 'site_name' => $legacy['site_title'] ?? config('app.name'), 'site_description' => $legacy['site_description'] ?? 'Modern software for modern companies.', 'company_name' => $legacy['site_title'] ?? config('app.name'), 'site_url' => url('/'), 'logo_url' => $legacy['logo_url'] ?? '', 'facebook_url' => $legacy['facebook_url'] ?? '', 'x_url' => $legacy['x_url'] ?? '', 'linkedin_url' => $legacy['linkedin_url'] ?? '', 'dashboard_label' => 'Dashboard', 'login_label' => 'Log in', 'registration_label' => 'Get started', ]; foreach ($defaults as $key => $value) { if ($settings->get($key, null, 'site') === null) { $settings->put($key, $value, 'site', 'string', true); } } } private function installMenus(): void { if (! Schema::hasTable('menus')) { return; } $menus = [ 'primary' => ['Primary navigation', [['enabled' => true, 'label' => 'Home', 'url' => '/'], ['enabled' => true, 'label' => 'Blog', 'url' => '/blog'], ['enabled' => true, 'label' => 'News', 'url' => '/news']]], 'footer' => ['Footer — Explore', [['enabled' => true, 'label' => 'Home', 'url' => '/'], ['enabled' => true, 'label' => 'Blog', 'url' => '/blog'], ['enabled' => true, 'label' => 'News', 'url' => '/news']]], 'footer_account' => ['Footer — Account', [['enabled' => true, 'label' => 'Dashboard', 'url' => '/dashboard'], ['enabled' => true, 'label' => 'Profile', 'url' => '/settings/profile']]], 'footer_legal' => ['Footer — Legal', []], ]; foreach ($menus as $location => [$name, $items]) { Menu::query()->firstOrCreate(['location' => $location], ['name' => $name, 'items' => $items, 'enabled' => true]); } } private function mergeMissingDefaults(array $current, array $defaults): array { foreach ($defaults as $key => $defaultValue) { if (! array_key_exists($key, $current)) { $current[$key] = $defaultValue; continue; } if ( is_array($defaultValue) && is_array($current[$key]) && ! array_is_list($defaultValue) && ! array_is_list($current[$key]) ) { $current[$key] = $this->mergeMissingDefaults( $current[$key], $defaultValue ); } } return $current; } private function definitions(): array { $field = static fn (string $key, string $label, string $type = 'text', array $extra = []): array => array_merge(compact('key', 'label', 'type'), $extra); $type = static fn (string $name, string $renderer, array $regions, array $schema): array => [ 'name' => $name, 'renderer' => $renderer, 'allowed_regions' => $regions, 'is_system' => true, 'is_active' => true, 'schema' => $schema, ]; $section = [$field('enabled', 'Show this section', 'boolean', ['default' => true])]; $feed = array_merge($section, [ $field('eyebrow', 'Eyebrow'), $field('heading', 'Heading'), $field('description', 'Description', 'textarea'), $field('limit', 'Maximum items', 'number', ['min' => 1, 'max' => 50]), $field('include_ids', 'Only show record IDs', 'csv', ['help' => 'Optional comma-separated IDs. Leave blank to use the latest records.']), $field('exclude_ids', 'Exclude record IDs', 'csv', ['help' => 'Optional comma-separated IDs to hide from this section.']), ]); return [ 'site-header' => [ 'title' => 'Main website header', 'type' => $type('Site header', 'site-header', ['header'], [ $field('enabled', 'Show header', 'boolean', ['default' => true]), $field('menu_location', 'Navigation menu location', 'text', ['help' => 'Usually primary. Edit links from Administration → Menus.']), $field('show_navigation', 'Show navigation', 'boolean', ['default' => true]), $field('show_dashboard', 'Show dashboard link', 'boolean', ['default' => true]), $field('show_user_name', 'Show signed-in user', 'boolean', ['default' => true]), $field('show_login', 'Show login', 'boolean', ['default' => true]), $field('show_registration', 'Show registration', 'boolean', ['default' => true]), $field('show_mobile_menu', 'Show mobile menu', 'boolean', ['default' => true]), ]), 'data' => ['enabled' => true, 'menu_location' => 'primary', 'show_navigation' => true, 'show_dashboard' => true, 'show_user_name' => true, 'show_login' => true, 'show_registration' => true, 'show_mobile_menu' => true], ], 'hero' => [ 'title' => 'Homepage hero', 'type' => $type('Hero', 'hero', ['body'], [ ...$section, $field('eyebrow', 'Eyebrow'), $field('heading', 'Heading'), $field('description', 'Description', 'textarea'), $field('primary_label', 'Primary button label'), $field('primary_url', 'Primary button URL', 'url'), $field('secondary_label', 'Secondary button label'), $field('secondary_url', 'Secondary button URL', 'url'), $field('show_metrics', 'Show metrics panel', 'boolean', ['default' => true]), $field('metrics', 'Metrics', 'repeater', ['fields' => [ $field('enabled', 'Show item', 'boolean', ['default' => true]), $field('label', 'Label'), $field('value', 'Value'), $field('change', 'Change'), $field('height', 'Chart height (%)', 'number', ['min' => 0, 'max' => 100]), ]]), ]), 'data' => ['enabled' => true, 'eyebrow' => 'A better way to run your business', 'heading' => 'Transform Your Business With Our SaaS Solution', 'description' => 'Streamline operations, boost productivity, and improve customer satisfaction with one connected platform built for modern teams.', 'primary_label' => 'Get started free', 'primary_url' => '/register', 'secondary_label' => 'Book demo', 'secondary_url' => '#contact', 'show_metrics' => true, 'metrics' => [ ['enabled' => true, 'label' => 'Monthly revenue', 'value' => '£48,240', 'change' => '+18.4%', 'height' => 62], ['enabled' => true, 'label' => 'Active users', 'value' => '12,842', 'change' => '+9.2%', 'height' => 78], ['enabled' => true, 'label' => 'Conversion', 'value' => '7.8%', 'change' => '+2.1%', 'height' => 92], ]], ], 'feature-grid' => [ 'title' => 'Core features', 'type' => $type('Feature grid', 'feature-grid', ['body'], [ ...$section, $field('eyebrow', 'Eyebrow'), $field('heading', 'Heading'), $field('description', 'Description', 'textarea'), $field('items', 'Features', 'repeater', ['fields' => [$field('enabled', 'Show item', 'boolean', ['default' => true]), $field('title', 'Title'), $field('description', 'Description', 'textarea')]]), ]), 'data' => ['enabled' => true, 'eyebrow' => 'Powerful features', 'heading' => 'Everything your team needs to grow', 'description' => 'Planning, collaboration, reporting, publishing, commerce and customer insight in one extensible platform.', 'items' => [ ['enabled' => true, 'title' => 'Smart automation', 'description' => 'Remove repetitive work and delays.'], ['enabled' => true, 'title' => 'Real-time analytics', 'description' => 'Turn activity into useful business insight.'], ['enabled' => true, 'title' => 'Content operations', 'description' => 'Create, review and publish confidently.'], ['enabled' => true, 'title' => 'Secure by design', 'description' => 'Protect users and data with dependable controls.'], ['enabled' => true, 'title' => 'Flexible integrations', 'description' => 'Connect the tools your organisation already uses.'], ['enabled' => true, 'title' => 'Modular architecture', 'description' => 'Activate only the capabilities each site needs.'], ]], ], 'product-feed' => ['title' => 'Pricing plans', 'type' => $type('Product feed', 'product-feed', ['body'], $feed), 'data' => ['enabled' => true, 'eyebrow' => 'Simple pricing', 'heading' => 'Choose a plan that fits', 'description' => 'Plans are managed from Products & Pricing in the CMS.', 'limit' => 6, 'include_ids' => '', 'exclude_ids' => '']], 'testimonial-feed' => ['title' => 'Customer stories', 'type' => $type('Testimonial feed', 'testimonial-feed', ['body'], $feed), 'data' => ['enabled' => true, 'eyebrow' => 'Customer stories', 'heading' => 'Trusted by ambitious teams', 'description' => '', 'limit' => 6, 'include_ids' => '', 'exclude_ids' => '']], 'team-feed' => ['title' => 'Team', 'type' => $type('Team feed', 'team-feed', ['body'], $feed), 'data' => ['enabled' => true, 'eyebrow' => 'Our team', 'heading' => 'People behind the platform', 'description' => '', 'limit' => 8, 'include_ids' => '', 'exclude_ids' => '']], 'blog-feed' => ['title' => 'Latest blog posts', 'type' => $type('Blog feed', 'blog-feed', ['body'], $feed), 'data' => ['enabled' => true, 'eyebrow' => 'Insights', 'heading' => 'Latest from the blog', 'description' => '', 'limit' => 3, 'include_ids' => '', 'exclude_ids' => '']], 'news-feed' => ['title' => 'Latest news', 'type' => $type('News feed', 'news-feed', ['body'], $feed), 'data' => ['enabled' => true, 'eyebrow' => 'Newsroom', 'heading' => 'Latest news', 'description' => '', 'limit' => 3, 'include_ids' => '', 'exclude_ids' => '']], 'faq-feed' => ['title' => 'Frequently asked questions', 'type' => $type('FAQ feed', 'faq-feed', ['body'], $feed), 'data' => ['enabled' => true, 'eyebrow' => 'FAQ', 'heading' => 'Answers before you begin', 'description' => '', 'limit' => 12, 'include_ids' => '', 'exclude_ids' => '']], 'newsletter-form' => [ 'title' => 'Newsletter signup', 'type' => $type('Newsletter form', 'newsletter-form', ['body'], [...$section, $field('eyebrow', 'Eyebrow'), $field('heading', 'Heading'), $field('description', 'Description', 'textarea'), $field('placeholder', 'Email placeholder'), $field('button_label', 'Button label')]), 'data' => ['enabled' => true, 'eyebrow' => 'Newsletter', 'heading' => 'Useful updates, no noise', 'description' => 'Receive product news, publishing guidance, and practical platform updates.', 'placeholder' => 'you@example.com', 'button_label' => 'Subscribe'], ], 'call-to-action' => [ 'title' => 'Homepage final call to action', 'type' => $type('Call to action', 'call-to-action', ['body'], [...$section, $field('anchor', 'Section anchor'), $field('heading', 'Heading'), $field('description', 'Description', 'textarea'), $field('button_label', 'Button label'), $field('button_url', 'Button URL', 'url')]), 'data' => ['enabled' => true, 'anchor' => 'contact', 'heading' => 'Ready to transform your business?', 'description' => 'Start building a faster, more connected operation today.', 'button_label' => 'Get started free', 'button_url' => '/register'], ], 'site-footer' => [ 'title' => 'Main website footer', 'type' => $type('Site footer', 'site-footer', ['footer'], [ $field('enabled', 'Show footer', 'boolean', ['default' => true]), $field('explore_heading', 'Explore column heading'), $field('explore_menu_location', 'Explore menu location'), $field('account_heading', 'Account column heading'), $field('account_menu_location', 'Account menu location'), $field('legal_heading', 'Legal column heading'), $field('legal_menu_location', 'Legal menu location'), $field('show_back_to_top', 'Show back-to-top link', 'boolean', ['default' => true]), $field('back_to_top_label', 'Back-to-top label'), ]), 'data' => ['enabled' => true, 'explore_heading' => 'Explore', 'explore_menu_location' => 'footer', 'account_heading' => 'Account', 'account_menu_location' => 'footer_account', 'legal_heading' => 'Legal', 'legal_menu_location' => 'footer_legal', 'show_back_to_top' => true, 'back_to_top_label' => 'Back to top ↑'], ], ]; } }