From 294e73e189a9c2cceafbd4968076b383a713eb5d Mon Sep 17 00:00:00 2001 From: Snider Date: Mon, 26 Jan 2026 20:46:49 +0000 Subject: [PATCH] feat(footer): add customizable footer component with dynamic content and links --- .../Config/Console/ConfigExportCommand.php | 10 +- .../Config/Console/ConfigImportCommand.php | 8 +- .../Config/Console/ConfigVersionCommand.php | 10 +- .../satellite/footer-custom.blade.php | 114 ++++++++++++ .../components/satellite/layout.blade.php | 86 ++++++++-- .../src/Website/Service/Routes/web.php | 2 + .../Service/View/Blade/features.blade.php | 92 ++++++++++ .../src/Website/Service/View/Features.php | 162 ++++++++++++++++++ 8 files changed, 448 insertions(+), 36 deletions(-) create mode 100644 packages/core-php/src/Core/Front/Components/View/Blade/components/satellite/footer-custom.blade.php create mode 100644 packages/core-php/src/Website/Service/View/Blade/features.blade.php create mode 100644 packages/core-php/src/Website/Service/View/Features.php diff --git a/packages/core-php/src/Core/Config/Console/ConfigExportCommand.php b/packages/core-php/src/Core/Config/Console/ConfigExportCommand.php index 9a55a74..11ea0f6 100644 --- a/packages/core-php/src/Core/Config/Console/ConfigExportCommand.php +++ b/packages/core-php/src/Core/Config/Console/ConfigExportCommand.php @@ -94,21 +94,17 @@ class ConfigExportCommand extends Command /** * Get autocompletion suggestions. - * - * @return array */ - public function complete(CompletionInput $input, array $suggestions): array + public function complete(CompletionInput $input, \Symfony\Component\Console\Completion\CompletionSuggestions $suggestions): void { if ($input->mustSuggestOptionValuesFor('workspace')) { if (class_exists(\Core\Mod\Tenant\Models\Workspace::class)) { - return \Core\Mod\Tenant\Models\Workspace::pluck('slug')->toArray(); + $suggestions->suggestValues(\Core\Mod\Tenant\Models\Workspace::pluck('slug')->toArray()); } } if ($input->mustSuggestOptionValuesFor('category')) { - return \Core\Config\Models\ConfigKey::distinct()->pluck('category')->toArray(); + $suggestions->suggestValues(\Core\Config\Models\ConfigKey::distinct()->pluck('category')->toArray()); } - - return $suggestions; } } diff --git a/packages/core-php/src/Core/Config/Console/ConfigImportCommand.php b/packages/core-php/src/Core/Config/Console/ConfigImportCommand.php index 55d37ba..d9805e0 100644 --- a/packages/core-php/src/Core/Config/Console/ConfigImportCommand.php +++ b/packages/core-php/src/Core/Config/Console/ConfigImportCommand.php @@ -172,17 +172,13 @@ class ConfigImportCommand extends Command /** * Get autocompletion suggestions. - * - * @return array */ - public function complete(CompletionInput $input, array $suggestions): array + public function complete(CompletionInput $input, \Symfony\Component\Console\Completion\CompletionSuggestions $suggestions): void { if ($input->mustSuggestOptionValuesFor('workspace')) { if (class_exists(\Core\Mod\Tenant\Models\Workspace::class)) { - return \Core\Mod\Tenant\Models\Workspace::pluck('slug')->toArray(); + $suggestions->suggestValues(\Core\Mod\Tenant\Models\Workspace::pluck('slug')->toArray()); } } - - return $suggestions; } } diff --git a/packages/core-php/src/Core/Config/Console/ConfigVersionCommand.php b/packages/core-php/src/Core/Config/Console/ConfigVersionCommand.php index ab7a83f..ee5befa 100644 --- a/packages/core-php/src/Core/Config/Console/ConfigVersionCommand.php +++ b/packages/core-php/src/Core/Config/Console/ConfigVersionCommand.php @@ -400,21 +400,17 @@ class ConfigVersionCommand extends Command /** * Get autocompletion suggestions. - * - * @return array */ - public function complete(CompletionInput $input, array $suggestions): array + public function complete(CompletionInput $input, \Symfony\Component\Console\Completion\CompletionSuggestions $suggestions): void { if ($input->mustSuggestArgumentValuesFor('action')) { - return ['list', 'create', 'show', 'rollback', 'compare', 'diff', 'delete']; + $suggestions->suggestValues(['list', 'create', 'show', 'rollback', 'compare', 'diff', 'delete']); } if ($input->mustSuggestOptionValuesFor('workspace')) { if (class_exists(\Core\Mod\Tenant\Models\Workspace::class)) { - return \Core\Mod\Tenant\Models\Workspace::pluck('slug')->toArray(); + $suggestions->suggestValues(\Core\Mod\Tenant\Models\Workspace::pluck('slug')->toArray()); } } - - return $suggestions; } } diff --git a/packages/core-php/src/Core/Front/Components/View/Blade/components/satellite/footer-custom.blade.php b/packages/core-php/src/Core/Front/Components/View/Blade/components/satellite/footer-custom.blade.php new file mode 100644 index 0000000..f431c6c --- /dev/null +++ b/packages/core-php/src/Core/Front/Components/View/Blade/components/satellite/footer-custom.blade.php @@ -0,0 +1,114 @@ +{{-- + Custom Footer Content Partial + + Variables: + $customContent - Raw HTML content + $customLinks - Array of ['label' => '', 'url' => '', 'icon' => ''] links + $socialLinks - Array of ['platform' => '', 'url' => '', 'icon' => ''] social links + $contactEmail - Email address + $contactPhone - Phone number + $showCopyright - Whether to show copyright (for replace mode) + $copyrightText - Custom copyright text + $workspaceName - Workspace name for default copyright + $appName - App name for default copyright + $appIcon - App icon path +--}} +@php + $showCopyright = $showCopyright ?? false; + $copyrightText = $copyrightText ?? null; + $workspaceName = $workspaceName ?? null; + $appName = $appName ?? config('core.app.name', 'Core PHP'); + $appIcon = $appIcon ?? config('core.app.icon', '/images/icon.svg'); +@endphp + +
+ {{-- Raw HTML custom content --}} + @if(!empty($customContent)) + + @endif + + {{-- Structured content grid --}} + @if(!empty($customLinks) || !empty($socialLinks) || $contactEmail || $contactPhone) +
+ + {{-- Contact information --}} + @if($contactEmail || $contactPhone) +
+ @if($contactEmail) + + + {{ $contactEmail }} + + @endif + @if($contactPhone) + + + {{ $contactPhone }} + + @endif +
+ @endif + + {{-- Custom links --}} + @if(!empty($customLinks)) +
+ @foreach($customLinks as $link) + + @if(!empty($link['icon'])) + + @endif + {{ $link['label'] }} + + @endforeach +
+ @endif + + {{-- Social links --}} + @if(!empty($socialLinks)) +
+ @foreach($socialLinks as $social) + @php + // Get icon from the social link or generate based on platform + $socialIcon = $social['icon'] ?? match(strtolower($social['platform'] ?? '')) { + 'twitter', 'x' => 'fa-brands fa-x-twitter', + 'facebook' => 'fa-brands fa-facebook', + 'instagram' => 'fa-brands fa-instagram', + 'linkedin' => 'fa-brands fa-linkedin', + 'youtube' => 'fa-brands fa-youtube', + 'tiktok' => 'fa-brands fa-tiktok', + 'github' => 'fa-brands fa-github', + 'discord' => 'fa-brands fa-discord', + 'mastodon' => 'fa-brands fa-mastodon', + 'bluesky' => 'fa-brands fa-bluesky', + 'threads' => 'fa-brands fa-threads', + 'pinterest' => 'fa-brands fa-pinterest', + default => 'fa-solid fa-link', + }; + @endphp + + + + @endforeach +
+ @endif +
+ @endif + + {{-- Copyright for replace mode --}} + @if($showCopyright) +
+ {{ $appName }} + + {!! $copyrightText ?? '© '.date('Y').' '.e($workspaceName ?? $appName) !!} + +
+ @endif +
diff --git a/packages/core-php/src/Core/Front/Components/View/Blade/components/satellite/layout.blade.php b/packages/core-php/src/Core/Front/Components/View/Blade/components/satellite/layout.blade.php index ce6545a..de39db1 100644 --- a/packages/core-php/src/Core/Front/Components/View/Blade/components/satellite/layout.blade.php +++ b/packages/core-php/src/Core/Front/Components/View/Blade/components/satellite/layout.blade.php @@ -4,6 +4,18 @@ $appUrl = config('app.url', 'https://core.test'); $privacyUrl = config('core.urls.privacy', '/privacy'); $termsUrl = config('core.urls.terms', '/terms'); + + // Footer settings - can be passed as array or FooterSettings object + $footer = $footer ?? null; + $footerShowDefault = $footer['show_default_links'] ?? $footer?->showDefaultLinks ?? true; + $footerPosition = $footer['position'] ?? $footer?->position ?? 'above_default'; + $footerCustomContent = $footer['custom_content'] ?? $footer?->customContent ?? null; + $footerCustomLinks = $footer['custom_links'] ?? $footer?->customLinks ?? []; + $footerSocialLinks = $footer['social_links'] ?? $footer?->socialLinks ?? []; + $footerCopyright = $footer['copyright_text'] ?? $footer?->copyrightText ?? null; + $footerContactEmail = $footer['contact_email'] ?? $footer?->contactEmail ?? null; + $footerContactPhone = $footer['contact_phone'] ?? $footer?->contactPhone ?? null; + $footerHasCustom = $footerCustomContent || !empty($footerCustomLinks) || !empty($footerSocialLinks) || $footerContactEmail || $footerContactPhone; @endphp
-
-
-
- {{ $appName }} - - © {{ date('Y') }} {{ $workspace?->name ?? $appName }} - -
-
- Privacy - Terms - - - Powered by {{ $appName }} - + + {{-- Custom footer content (above default) --}} + @if($footerHasCustom && $footerPosition === 'above_default') + @include('front::components.satellite.footer-custom', [ + 'customContent' => $footerCustomContent, + 'customLinks' => $footerCustomLinks, + 'socialLinks' => $footerSocialLinks, + 'contactEmail' => $footerContactEmail, + 'contactPhone' => $footerContactPhone, + ]) + @endif + + {{-- Custom footer content (replace default) --}} + @if($footerHasCustom && $footerPosition === 'replace_default') + @include('front::components.satellite.footer-custom', [ + 'customContent' => $footerCustomContent, + 'customLinks' => $footerCustomLinks, + 'socialLinks' => $footerSocialLinks, + 'contactEmail' => $footerContactEmail, + 'contactPhone' => $footerContactPhone, + 'showCopyright' => true, + 'copyrightText' => $footerCopyright, + 'workspaceName' => $workspace?->name, + 'appName' => $appName, + 'appIcon' => $appIcon, + ]) + @endif + + {{-- Default footer --}} + @if($footerShowDefault && $footerPosition !== 'replace_default') +
+
+
+ {{ $appName }} + + {!! $footerCopyright ?? '© '.date('Y').' '.e($workspace?->name ?? $appName) !!} + +
+
-
+ @endif + + {{-- Custom footer content (below default) --}} + @if($footerHasCustom && $footerPosition === 'below_default') + @include('front::components.satellite.footer-custom', [ + 'customContent' => $footerCustomContent, + 'customLinks' => $footerCustomLinks, + 'socialLinks' => $footerSocialLinks, + 'contactEmail' => $footerContactEmail, + 'contactPhone' => $footerContactPhone, + ]) + @endif diff --git a/packages/core-php/src/Website/Service/Routes/web.php b/packages/core-php/src/Website/Service/Routes/web.php index d10df76..c8a68fb 100644 --- a/packages/core-php/src/Website/Service/Routes/web.php +++ b/packages/core-php/src/Website/Service/Routes/web.php @@ -2,6 +2,7 @@ declare(strict_types=1); +use Core\Website\Service\View\Features; use Core\Website\Service\View\Landing; use Illuminate\Support\Facades\Route; @@ -16,3 +17,4 @@ use Illuminate\Support\Facades\Route; */ Route::get('/', Landing::class)->name('service.home'); +Route::get('/features', Features::class)->name('service.features'); diff --git a/packages/core-php/src/Website/Service/View/Blade/features.blade.php b/packages/core-php/src/Website/Service/View/Blade/features.blade.php new file mode 100644 index 0000000..312a72e --- /dev/null +++ b/packages/core-php/src/Website/Service/View/Blade/features.blade.php @@ -0,0 +1,92 @@ +
+ {{-- Hero Section --}} +
+
+
+ {{-- Badge --}} +
+ + {{ $workspace['name'] ?? 'Service' }} Features +
+ + {{-- Headline --}} +

+ Everything you need to + succeed +

+ + {{-- Description --}} +

+ {{ $workspace['description'] ?? 'Powerful features built for creators and businesses.' }} +

+
+
+
+ + {{-- Features Grid --}} +
+
+
+ @foreach($features as $feature) +
+
+ +
+

{{ $feature['title'] }}

+

{{ $feature['description'] }}

+
+ @endforeach +
+
+
+ + {{-- Integration Section --}} +
+
+

+ Part of the Host UK ecosystem +

+

+ {{ $workspace['name'] ?? 'This service' }} works seamlessly with other Host UK services. + One account, unified billing, integrated tools. +

+ +
+
+ + {{-- CTA Section --}} +
+
+

+ Ready to get started? +

+

+ Start your free trial today. No credit card required. +

+ +
+
+
diff --git a/packages/core-php/src/Website/Service/View/Features.php b/packages/core-php/src/Website/Service/View/Features.php new file mode 100644 index 0000000..3759f26 --- /dev/null +++ b/packages/core-php/src/Website/Service/View/Features.php @@ -0,0 +1,162 @@ +getHost(); + $slug = $this->extractSubdomain($host); + + // Try to resolve workspace from app container if service exists + if ($slug && app()->bound('workspace.service')) { + $this->workspace = app('workspace.service')->get($slug) ?? []; + } + + // Fallback to app config defaults + if (empty($this->workspace)) { + $this->workspace = [ + 'name' => config('core.app.name', config('app.name', 'Service')), + 'slug' => 'service', + 'icon' => config('core.app.icon', 'cube'), + 'color' => config('core.app.color', 'violet'), + 'description' => config('core.app.description', 'A powerful platform'), + ]; + } + } + + /** + * Extract subdomain from hostname. + */ + protected function extractSubdomain(string $host): ?string + { + if (preg_match('/^([a-z]+)\.host\.(test|localhost|uk\.com)$/i', $host, $matches)) { + return $matches[1]; + } + + return null; + } + + /** + * Get detailed features for this service. + */ + public function getFeatures(): array + { + $slug = $this->workspace['slug'] ?? 'service'; + + // Service-specific features + return match ($slug) { + 'social' => $this->getSocialFeatures(), + 'analytics' => $this->getAnalyticsFeatures(), + 'notify' => $this->getNotifyFeatures(), + 'trust' => $this->getTrustFeatures(), + 'support' => $this->getSupportFeatures(), + default => $this->getDefaultFeatures(), + }; + } + + protected function getSocialFeatures(): array + { + return [ + ['icon' => 'calendar', 'title' => 'Schedule posts', 'description' => 'Plan your content calendar weeks in advance with our visual scheduler.'], + ['icon' => 'share-nodes', 'title' => 'Multi-platform publishing', 'description' => 'Publish to 20+ social networks from a single dashboard.'], + ['icon' => 'chart-pie', 'title' => 'Analytics & insights', 'description' => 'Track engagement, reach, and growth across all your accounts.'], + ['icon' => 'users', 'title' => 'Team collaboration', 'description' => 'Work together with approval workflows and role-based access.'], + ['icon' => 'wand-magic-sparkles', 'title' => 'AI content assistant', 'description' => 'Generate captions, hashtags, and post ideas with AI.'], + ['icon' => 'inbox', 'title' => 'Unified inbox', 'description' => 'Manage comments and messages from all platforms in one place.'], + ]; + } + + protected function getAnalyticsFeatures(): array + { + return [ + ['icon' => 'cookie-bite', 'title' => 'No cookies required', 'description' => 'Privacy-focused tracking without consent banners.'], + ['icon' => 'bolt', 'title' => 'Lightweight script', 'description' => 'Under 1KB script that won\'t slow down your site.'], + ['icon' => 'chart-line', 'title' => 'Real-time dashboard', 'description' => 'See visitors on your site as they browse.'], + ['icon' => 'route', 'title' => 'Goal tracking', 'description' => 'Set up funnels and conversion goals to measure success.'], + ['icon' => 'flask', 'title' => 'A/B testing', 'description' => 'Test variations and measure statistical significance.'], + ['icon' => 'file-export', 'title' => 'Data export', 'description' => 'Export your data anytime in CSV or JSON format.'], + ]; + } + + protected function getNotifyFeatures(): array + { + return [ + ['icon' => 'bell', 'title' => 'Web push notifications', 'description' => 'Reach subscribers directly in their browser.'], + ['icon' => 'users-gear', 'title' => 'Audience segments', 'description' => 'Target specific groups based on behaviour and preferences.'], + ['icon' => 'diagram-project', 'title' => 'Automation flows', 'description' => 'Create drip campaigns triggered by user actions.'], + ['icon' => 'vials', 'title' => 'A/B testing', 'description' => 'Test different messages to optimise engagement.'], + ['icon' => 'chart-simple', 'title' => 'Delivery analytics', 'description' => 'Track delivery, clicks, and conversion rates.'], + ['icon' => 'clock', 'title' => 'Scheduled sends', 'description' => 'Schedule notifications for optimal delivery times.'], + ]; + } + + protected function getTrustFeatures(): array + { + return [ + ['icon' => 'comment-dots', 'title' => 'Social proof popups', 'description' => 'Show real-time purchase and signup notifications.'], + ['icon' => 'star', 'title' => 'Review collection', 'description' => 'Collect and display customer reviews automatically.'], + ['icon' => 'bullseye', 'title' => 'Smart targeting', 'description' => 'Show notifications to the right visitors at the right time.'], + ['icon' => 'palette', 'title' => 'Custom styling', 'description' => 'Match notifications to your brand with full CSS control.'], + ['icon' => 'chart-column', 'title' => 'Conversion tracking', 'description' => 'Measure the impact on your conversion rates.'], + ['icon' => 'plug', 'title' => 'Easy integration', 'description' => 'Add to any website with a single script tag.'], + ]; + } + + protected function getSupportFeatures(): array + { + return [ + ['icon' => 'inbox', 'title' => 'Shared inbox', 'description' => 'Manage customer emails from a unified team inbox.'], + ['icon' => 'book', 'title' => 'Help centre', 'description' => 'Build a self-service knowledge base for customers.'], + ['icon' => 'comments', 'title' => 'Live chat widget', 'description' => 'Embed a chat widget on your website for instant support.'], + ['icon' => 'clock-rotate-left', 'title' => 'SLA tracking', 'description' => 'Set response time targets and track performance.'], + ['icon' => 'reply', 'title' => 'Canned responses', 'description' => 'Save time with pre-written replies for common questions.'], + ['icon' => 'tags', 'title' => 'Ticket management', 'description' => 'Organise conversations with tags and custom fields.'], + ]; + } + + protected function getDefaultFeatures(): array + { + return [ + ['icon' => 'rocket', 'title' => 'Easy to use', 'description' => 'Get started in minutes with our intuitive interface.'], + ['icon' => 'shield-check', 'title' => 'Secure by default', 'description' => 'Built with security and privacy at the core.'], + ['icon' => 'chart-line', 'title' => 'Analytics included', 'description' => 'Track performance with built-in analytics.'], + ['icon' => 'puzzle-piece', 'title' => 'Modular architecture', 'description' => 'Extend with modules to fit your exact needs.'], + ['icon' => 'headset', 'title' => 'UK-based support', 'description' => 'Get help from our friendly support team.'], + ['icon' => 'code', 'title' => 'Developer friendly', 'description' => 'Full API access and webhook integrations.'], + ]; + } + + public function render(): View + { + $appName = config('core.app.name', config('app.name', 'Service')); + + return view('service::features', [ + 'workspace' => $this->workspace, + 'features' => $this->getFeatures(), + ])->layout('service::layouts.service', [ + 'title' => 'Features - ' . ($this->workspace['name'] ?? $appName), + 'workspace' => $this->workspace, + ]); + } +}