feat(footer): add customizable footer component with dynamic content and links
This commit is contained in:
parent
b0e3ef461f
commit
294e73e189
8 changed files with 448 additions and 36 deletions
|
|
@ -94,21 +94,17 @@ class ConfigExportCommand extends Command
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get autocompletion suggestions.
|
* Get autocompletion suggestions.
|
||||||
*
|
|
||||||
* @return array<string>
|
|
||||||
*/
|
*/
|
||||||
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 ($input->mustSuggestOptionValuesFor('workspace')) {
|
||||||
if (class_exists(\Core\Mod\Tenant\Models\Workspace::class)) {
|
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')) {
|
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -172,17 +172,13 @@ class ConfigImportCommand extends Command
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get autocompletion suggestions.
|
* Get autocompletion suggestions.
|
||||||
*
|
|
||||||
* @return array<string>
|
|
||||||
*/
|
*/
|
||||||
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 ($input->mustSuggestOptionValuesFor('workspace')) {
|
||||||
if (class_exists(\Core\Mod\Tenant\Models\Workspace::class)) {
|
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -400,21 +400,17 @@ class ConfigVersionCommand extends Command
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get autocompletion suggestions.
|
* Get autocompletion suggestions.
|
||||||
*
|
|
||||||
* @return array<string>
|
|
||||||
*/
|
*/
|
||||||
public function complete(CompletionInput $input, array $suggestions): array
|
public function complete(CompletionInput $input, \Symfony\Component\Console\Completion\CompletionSuggestions $suggestions): void
|
||||||
{
|
{
|
||||||
if ($input->mustSuggestArgumentValuesFor('action')) {
|
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 ($input->mustSuggestOptionValuesFor('workspace')) {
|
||||||
if (class_exists(\Core\Mod\Tenant\Models\Workspace::class)) {
|
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
||||||
|
<div class="max-w-5xl mx-auto px-4 sm:px-6 py-8">
|
||||||
|
{{-- Raw HTML custom content --}}
|
||||||
|
@if(!empty($customContent))
|
||||||
|
<div class="custom-footer-content mb-6">
|
||||||
|
{!! $customContent !!}
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Structured content grid --}}
|
||||||
|
@if(!empty($customLinks) || !empty($socialLinks) || $contactEmail || $contactPhone)
|
||||||
|
<div class="flex flex-col sm:flex-row items-start sm:items-center justify-between gap-6 @if(!empty($customContent)) pt-6 border-t border-slate-200 dark:border-slate-700/50 @endif">
|
||||||
|
|
||||||
|
{{-- Contact information --}}
|
||||||
|
@if($contactEmail || $contactPhone)
|
||||||
|
<div class="flex flex-col sm:flex-row items-start sm:items-center gap-4 text-sm text-slate-500">
|
||||||
|
@if($contactEmail)
|
||||||
|
<a href="mailto:{{ $contactEmail }}" class="hover:text-slate-700 dark:hover:text-slate-300 transition flex items-center gap-2">
|
||||||
|
<i class="fa-solid fa-envelope text-xs"></i>
|
||||||
|
{{ $contactEmail }}
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
@if($contactPhone)
|
||||||
|
<a href="tel:{{ preg_replace('/[^0-9+]/', '', $contactPhone) }}" class="hover:text-slate-700 dark:hover:text-slate-300 transition flex items-center gap-2">
|
||||||
|
<i class="fa-solid fa-phone text-xs"></i>
|
||||||
|
{{ $contactPhone }}
|
||||||
|
</a>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Custom links --}}
|
||||||
|
@if(!empty($customLinks))
|
||||||
|
<div class="flex flex-wrap items-center gap-4 text-sm text-slate-500">
|
||||||
|
@foreach($customLinks as $link)
|
||||||
|
<a href="{{ $link['url'] }}" class="hover:text-slate-700 dark:hover:text-slate-300 transition flex items-center gap-2">
|
||||||
|
@if(!empty($link['icon']))
|
||||||
|
<i class="{{ $link['icon'] }} text-xs"></i>
|
||||||
|
@endif
|
||||||
|
{{ $link['label'] }}
|
||||||
|
</a>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Social links --}}
|
||||||
|
@if(!empty($socialLinks))
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
@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
|
||||||
|
<a
|
||||||
|
href="{{ $social['url'] }}"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
class="w-8 h-8 flex items-center justify-center rounded-full text-slate-400 hover:text-slate-600 dark:hover:text-slate-200 hover:bg-slate-100 dark:hover:bg-slate-800 transition"
|
||||||
|
aria-label="{{ ucfirst($social['platform'] ?? 'Social link') }}"
|
||||||
|
>
|
||||||
|
<i class="{{ $socialIcon }}"></i>
|
||||||
|
</a>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
|
||||||
|
{{-- Copyright for replace mode --}}
|
||||||
|
@if($showCopyright)
|
||||||
|
<div class="flex items-center gap-4 mt-6 pt-6 border-t border-slate-200 dark:border-slate-700/50">
|
||||||
|
<img src="{{ $appIcon }}" alt="{{ $appName }}" class="w-6 h-6 opacity-50">
|
||||||
|
<span class="text-sm text-slate-500">
|
||||||
|
{!! $copyrightText ?? '© '.date('Y').' '.e($workspaceName ?? $appName) !!}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
@ -4,6 +4,18 @@
|
||||||
$appUrl = config('app.url', 'https://core.test');
|
$appUrl = config('app.url', 'https://core.test');
|
||||||
$privacyUrl = config('core.urls.privacy', '/privacy');
|
$privacyUrl = config('core.urls.privacy', '/privacy');
|
||||||
$termsUrl = config('core.urls.terms', '/terms');
|
$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
|
@endphp
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en" class="scroll-smooth overscroll-none"
|
<html lang="en" class="scroll-smooth overscroll-none"
|
||||||
|
|
@ -145,24 +157,66 @@
|
||||||
<footer class="mt-auto">
|
<footer class="mt-auto">
|
||||||
<!-- Footer gradient border -->
|
<!-- Footer gradient border -->
|
||||||
<div class="h-px w-full bg-gradient-to-r from-transparent via-violet-500/20 to-transparent"></div>
|
<div class="h-px w-full bg-gradient-to-r from-transparent via-violet-500/20 to-transparent"></div>
|
||||||
<div class="max-w-5xl mx-auto px-4 sm:px-6 py-8">
|
|
||||||
<div class="flex flex-col sm:flex-row items-center justify-between gap-4">
|
{{-- Custom footer content (above default) --}}
|
||||||
<div class="flex items-center gap-4">
|
@if($footerHasCustom && $footerPosition === 'above_default')
|
||||||
<img src="{{ $appIcon }}" alt="{{ $appName }}" class="w-6 h-6 opacity-50">
|
@include('front::components.satellite.footer-custom', [
|
||||||
<span class="text-sm text-slate-500">
|
'customContent' => $footerCustomContent,
|
||||||
© {{ date('Y') }} {{ $workspace?->name ?? $appName }}
|
'customLinks' => $footerCustomLinks,
|
||||||
</span>
|
'socialLinks' => $footerSocialLinks,
|
||||||
</div>
|
'contactEmail' => $footerContactEmail,
|
||||||
<div class="flex items-center gap-6 text-sm text-slate-500">
|
'contactPhone' => $footerContactPhone,
|
||||||
<a href="{{ $privacyUrl }}" class="hover:text-slate-700 dark:hover:text-slate-300 transition">Privacy</a>
|
])
|
||||||
<a href="{{ $termsUrl }}" class="hover:text-slate-700 dark:hover:text-slate-300 transition">Terms</a>
|
@endif
|
||||||
<a href="{{ $appUrl }}" class="hover:text-slate-700 dark:hover:text-slate-300 transition flex items-center gap-1">
|
|
||||||
<i class="fa-solid fa-bolt text-violet-400 text-xs"></i>
|
{{-- Custom footer content (replace default) --}}
|
||||||
Powered by {{ $appName }}
|
@if($footerHasCustom && $footerPosition === 'replace_default')
|
||||||
</a>
|
@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')
|
||||||
|
<div class="max-w-5xl mx-auto px-4 sm:px-6 py-8">
|
||||||
|
<div class="flex flex-col sm:flex-row items-center justify-between gap-4">
|
||||||
|
<div class="flex items-center gap-4">
|
||||||
|
<img src="{{ $appIcon }}" alt="{{ $appName }}" class="w-6 h-6 opacity-50">
|
||||||
|
<span class="text-sm text-slate-500">
|
||||||
|
{!! $footerCopyright ?? '© '.date('Y').' '.e($workspace?->name ?? $appName) !!}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center gap-6 text-sm text-slate-500">
|
||||||
|
<a href="{{ $privacyUrl }}" class="hover:text-slate-700 dark:hover:text-slate-300 transition">Privacy</a>
|
||||||
|
<a href="{{ $termsUrl }}" class="hover:text-slate-700 dark:hover:text-slate-300 transition">Terms</a>
|
||||||
|
<a href="{{ $appUrl }}" class="hover:text-slate-700 dark:hover:text-slate-300 transition flex items-center gap-1">
|
||||||
|
<i class="fa-solid fa-bolt text-violet-400 text-xs"></i>
|
||||||
|
Powered by {{ $appName }}
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
@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
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
|
|
||||||
declare(strict_types=1);
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Core\Website\Service\View\Features;
|
||||||
use Core\Website\Service\View\Landing;
|
use Core\Website\Service\View\Landing;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
|
|
@ -16,3 +17,4 @@ use Illuminate\Support\Facades\Route;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
Route::get('/', Landing::class)->name('service.home');
|
Route::get('/', Landing::class)->name('service.home');
|
||||||
|
Route::get('/features', Features::class)->name('service.features');
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,92 @@
|
||||||
|
<div>
|
||||||
|
{{-- Hero Section --}}
|
||||||
|
<section class="relative overflow-hidden">
|
||||||
|
<div class="relative max-w-7xl mx-auto px-6 md:px-10 xl:px-8 py-20 lg:py-28">
|
||||||
|
<div class="text-center max-w-3xl mx-auto">
|
||||||
|
{{-- Badge --}}
|
||||||
|
<div class="inline-flex items-center gap-2 px-4 py-2 rounded-full text-sm mb-6" style="background-color: color-mix(in srgb, var(--service-accent) 15%, transparent); border: 1px solid color-mix(in srgb, var(--service-accent) 30%, transparent); color: var(--service-accent);">
|
||||||
|
<i class="fa-solid fa-{{ $workspace['icon'] ?? 'cube' }}"></i>
|
||||||
|
{{ $workspace['name'] ?? 'Service' }} Features
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{-- Headline --}}
|
||||||
|
<h1 class="text-4xl sm:text-5xl lg:text-6xl font-bold text-white mb-6 leading-tight">
|
||||||
|
Everything you need to
|
||||||
|
<span style="color: var(--service-accent);">succeed</span>
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
{{-- Description --}}
|
||||||
|
<p class="text-lg text-slate-400 mb-8 max-w-xl mx-auto">
|
||||||
|
{{ $workspace['description'] ?? 'Powerful features built for creators and businesses.' }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{{-- Features Grid --}}
|
||||||
|
<section class="py-20 lg:py-28 bg-slate-900/50">
|
||||||
|
<div class="max-w-7xl mx-auto px-6 md:px-10 xl:px-8">
|
||||||
|
<div class="grid md:grid-cols-2 lg:grid-cols-3 gap-8">
|
||||||
|
@foreach($features as $feature)
|
||||||
|
<div class="rounded-2xl p-8 transition group" style="background-color: color-mix(in srgb, var(--service-accent) 5%, rgb(30 41 59)); border: 1px solid color-mix(in srgb, var(--service-accent) 15%, transparent);">
|
||||||
|
<div class="w-14 h-14 rounded-xl flex items-center justify-center mb-6 transition" style="background-color: color-mix(in srgb, var(--service-accent) 15%, transparent);">
|
||||||
|
<i class="fa-solid fa-{{ $feature['icon'] }} text-xl" style="color: var(--service-accent);"></i>
|
||||||
|
</div>
|
||||||
|
<h3 class="text-xl font-semibold text-white mb-3">{{ $feature['title'] }}</h3>
|
||||||
|
<p class="text-slate-400 leading-relaxed">{{ $feature['description'] }}</p>
|
||||||
|
</div>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{{-- Integration Section --}}
|
||||||
|
<section class="py-20 lg:py-28">
|
||||||
|
<div class="max-w-4xl mx-auto px-6 md:px-10 xl:px-8 text-center">
|
||||||
|
<h2 class="text-3xl sm:text-4xl font-bold text-white mb-4">
|
||||||
|
Part of the Host UK ecosystem
|
||||||
|
</h2>
|
||||||
|
<p class="text-lg text-slate-400 mb-10 max-w-2xl mx-auto">
|
||||||
|
{{ $workspace['name'] ?? 'This service' }} works seamlessly with other Host UK services.
|
||||||
|
One account, unified billing, integrated tools.
|
||||||
|
</p>
|
||||||
|
<div class="flex flex-wrap justify-center gap-4">
|
||||||
|
<a href="https://{{ app()->environment('local') ? 'social.host.test' : 'social.host.uk.com' }}" class="px-4 py-2 rounded-lg bg-slate-800 text-slate-300 text-sm hover:bg-slate-700 transition">
|
||||||
|
<i class="fa-solid fa-share-nodes mr-2"></i>SocialHost
|
||||||
|
</a>
|
||||||
|
<a href="https://{{ app()->environment('local') ? 'analytics.host.test' : 'analytics.host.uk.com' }}" class="px-4 py-2 rounded-lg bg-slate-800 text-slate-300 text-sm hover:bg-slate-700 transition">
|
||||||
|
<i class="fa-solid fa-chart-line mr-2"></i>AnalyticsHost
|
||||||
|
</a>
|
||||||
|
<a href="https://{{ app()->environment('local') ? 'notify.host.test' : 'notify.host.uk.com' }}" class="px-4 py-2 rounded-lg bg-slate-800 text-slate-300 text-sm hover:bg-slate-700 transition">
|
||||||
|
<i class="fa-solid fa-bell mr-2"></i>NotifyHost
|
||||||
|
</a>
|
||||||
|
<a href="https://{{ app()->environment('local') ? 'trust.host.test' : 'trust.host.uk.com' }}" class="px-4 py-2 rounded-lg bg-slate-800 text-slate-300 text-sm hover:bg-slate-700 transition">
|
||||||
|
<i class="fa-solid fa-star mr-2"></i>TrustHost
|
||||||
|
</a>
|
||||||
|
<a href="https://{{ app()->environment('local') ? 'support.host.test' : 'support.host.uk.com' }}" class="px-4 py-2 rounded-lg bg-slate-800 text-slate-300 text-sm hover:bg-slate-700 transition">
|
||||||
|
<i class="fa-solid fa-headset mr-2"></i>SupportHost
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
{{-- CTA Section --}}
|
||||||
|
<section class="py-20" style="background: linear-gradient(135deg, var(--service-accent), color-mix(in srgb, var(--service-accent) 70%, black));">
|
||||||
|
<div class="max-w-4xl mx-auto px-6 md:px-10 xl:px-8 text-center">
|
||||||
|
<h2 class="text-3xl sm:text-4xl font-bold mb-4 text-slate-900">
|
||||||
|
Ready to get started?
|
||||||
|
</h2>
|
||||||
|
<p class="text-lg mb-8 text-slate-800">
|
||||||
|
Start your free trial today. No credit card required.
|
||||||
|
</p>
|
||||||
|
<div class="flex flex-col sm:flex-row gap-4 justify-center">
|
||||||
|
<a href="{{ config('app.url') }}/register" class="inline-flex items-center justify-center px-8 py-3 font-semibold rounded-xl transition shadow-lg bg-slate-900" style="color: var(--service-accent);">
|
||||||
|
Get started free
|
||||||
|
</a>
|
||||||
|
<a href="/pricing" class="inline-flex items-center justify-center px-8 py-3 font-semibold rounded-xl transition bg-white/20 text-slate-900 border border-slate-900/20 hover:bg-white/30">
|
||||||
|
View pricing
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
162
packages/core-php/src/Website/Service/View/Features.php
Normal file
162
packages/core-php/src/Website/Service/View/Features.php
Normal file
|
|
@ -0,0 +1,162 @@
|
||||||
|
<?php
|
||||||
|
/*
|
||||||
|
* Core PHP Framework
|
||||||
|
*
|
||||||
|
* Licensed under the European Union Public Licence (EUPL) v1.2.
|
||||||
|
* See LICENSE file for details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Core\Website\Service\View;
|
||||||
|
|
||||||
|
use Illuminate\Contracts\View\View;
|
||||||
|
use Livewire\Component;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generic Service Features Page.
|
||||||
|
*
|
||||||
|
* Displays service features with dynamic theming.
|
||||||
|
*/
|
||||||
|
class Features extends Component
|
||||||
|
{
|
||||||
|
public array $workspace = [];
|
||||||
|
|
||||||
|
public function mount(): void
|
||||||
|
{
|
||||||
|
// Extract subdomain from host (e.g., social.host.test → social)
|
||||||
|
$host = request()->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,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue