fix(FIX-13): 8 bugs — sidebar flash, mobile hamburger, AI insights, button overlay, QR modal collapse, dashboard always-active, bio themes
- BUG 1: blocking script in <head> reads localStorage before first paint; CSS rule html[data-sidebar-collapsed] .skel-sidebar{width:64px} eliminates width flash
- BUG 2: sidebar store gains mobileOpen/toggleMobile()/closeMobile(); hamburger calls toggleMobile(); backdrop and sidebar :style use mobileOpen; SPA navigated handler calls closeMobile()
- BUG 3: text-purple → text-accent in AI Insights view (text-purple is undefined)
- BUG 4: ui/button.blade.php — spinner is now absolute inset-0 overlay; content fades to opacity-40 via wire:loading.class instead of hiding
- BUG 5: insights generate button and QR modal save/cancel migrated to x-ui.button component
- BUG 6: QR modal Styling/Frame/Advanced sections replaced from <details> (destroyed by Livewire re-render) to Alpine x-data{open:false} divs with x-show — state survives wire:model.live updates
- BUG 7: updateActiveNav() now checks el.dataset.navExact; Dashboard <a> gets data-nav-exact="1" so it only activates on exact pathname match
- BUG 8: bio_pages.theme JSON applied in public show.blade.php via inline CSS vars (4 presets: dark/light/purple/blue); EditBioPage gains theme property with preset selector in modal
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
main
parent
e211770312
commit
436d80a097
|
|
@ -20,6 +20,8 @@ class EditBioPage extends ModalComponent
|
||||||
|
|
||||||
public bool $isPublished = false;
|
public bool $isPublished = false;
|
||||||
|
|
||||||
|
public string $theme = 'dark';
|
||||||
|
|
||||||
public function mount(string $bioUlid): void
|
public function mount(string $bioUlid): void
|
||||||
{
|
{
|
||||||
$bio = BioPage::where('ulid', $bioUlid)->firstOrFail();
|
$bio = BioPage::where('ulid', $bioUlid)->firstOrFail();
|
||||||
|
|
@ -31,6 +33,7 @@ class EditBioPage extends ModalComponent
|
||||||
$this->title = $bio->getTranslation('title', 'en') ?? '';
|
$this->title = $bio->getTranslation('title', 'en') ?? '';
|
||||||
$this->slug = $bio->slug;
|
$this->slug = $bio->slug;
|
||||||
$this->isPublished = $bio->is_published;
|
$this->isPublished = $bio->is_published;
|
||||||
|
$this->theme = is_array($bio->theme) ? ($bio->theme['preset'] ?? 'dark') : 'dark';
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return array<string, string> */
|
/** @return array<string, string> */
|
||||||
|
|
@ -40,6 +43,7 @@ class EditBioPage extends ModalComponent
|
||||||
'title' => 'required|max:200',
|
'title' => 'required|max:200',
|
||||||
'slug' => 'nullable|alpha_dash|max:80',
|
'slug' => 'nullable|alpha_dash|max:80',
|
||||||
'isPublished' => 'boolean',
|
'isPublished' => 'boolean',
|
||||||
|
'theme' => 'in:dark,light,purple,blue',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -55,6 +59,7 @@ class EditBioPage extends ModalComponent
|
||||||
'title' => ['en' => $this->title, 'de' => $this->title],
|
'title' => ['en' => $this->title, 'de' => $this->title],
|
||||||
'slug' => $this->slug ?: $bio->slug,
|
'slug' => $this->slug ?: $bio->slug,
|
||||||
'is_published' => $this->isPublished,
|
'is_published' => $this->isPublished,
|
||||||
|
'theme' => ['preset' => $this->theme],
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$this->dispatch('bio-updated')->to(Index::class);
|
$this->dispatch('bio-updated')->to(Index::class);
|
||||||
|
|
|
||||||
|
|
@ -60,12 +60,23 @@ document.addEventListener('alpine:init', () => {
|
||||||
});
|
});
|
||||||
|
|
||||||
Alpine.store('sidebar', {
|
Alpine.store('sidebar', {
|
||||||
open: false,
|
mobileOpen: false,
|
||||||
collapsed: localStorage.getItem('sidebarCollapsed') === 'true',
|
collapsed: localStorage.getItem('sidebarCollapsed') === 'true',
|
||||||
|
init() { this._syncDataset(); },
|
||||||
|
_syncDataset() {
|
||||||
|
if (this.collapsed) {
|
||||||
|
document.documentElement.dataset.sidebarCollapsed = '1';
|
||||||
|
} else {
|
||||||
|
delete document.documentElement.dataset.sidebarCollapsed;
|
||||||
|
}
|
||||||
|
},
|
||||||
toggle() {
|
toggle() {
|
||||||
this.collapsed = ! this.collapsed;
|
this.collapsed = ! this.collapsed;
|
||||||
localStorage.setItem('sidebarCollapsed', this.collapsed);
|
localStorage.setItem('sidebarCollapsed', this.collapsed);
|
||||||
|
this._syncDataset();
|
||||||
window.dispatchEvent(new CustomEvent('sidebar-toggled', { detail: { collapsed: this.collapsed } }));
|
window.dispatchEvent(new CustomEvent('sidebar-toggled', { detail: { collapsed: this.collapsed } }));
|
||||||
},
|
},
|
||||||
|
toggleMobile() { this.mobileOpen = ! this.mobileOpen; },
|
||||||
|
closeMobile() { this.mobileOpen = false; },
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,13 @@
|
||||||
|
@php
|
||||||
|
$preset = is_array($page->theme) ? ($page->theme['preset'] ?? 'dark') : 'dark';
|
||||||
|
$themes = [
|
||||||
|
'dark' => ['bg' => '#0a0a0f', 'card' => '#111117', 'border' => 'rgba(255,255,255,0.08)', 'text' => '#f0f0f5', 'sub' => '#b8b8d0', 'accent' => '#7c3aed'],
|
||||||
|
'light' => ['bg' => '#ffffff', 'card' => '#f9fafb', 'border' => '#e5e7eb', 'text' => '#111827', 'sub' => '#6b7280', 'accent' => '#7c3aed'],
|
||||||
|
'purple' => ['bg' => '#13001a', 'card' => '#1e0028', 'border' => 'rgba(139,92,246,0.25)', 'text' => '#f0e8ff', 'sub' => '#c4b5fd', 'accent' => '#a855f7'],
|
||||||
|
'blue' => ['bg' => '#000d1a', 'card' => '#001c33', 'border' => 'rgba(59,130,246,0.25)', 'text' => '#e8f4ff', 'sub' => '#93c5fd', 'accent' => '#3b82f6'],
|
||||||
|
];
|
||||||
|
$t = $themes[$preset] ?? $themes['dark'];
|
||||||
|
@endphp
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="{{ app()->getLocale() }}">
|
<html lang="{{ app()->getLocale() }}">
|
||||||
<head>
|
<head>
|
||||||
|
|
@ -5,21 +15,42 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>{{ $page->getTranslation('title', app()->getLocale()) }}</title>
|
<title>{{ $page->getTranslation('title', app()->getLocale()) }}</title>
|
||||||
@vite(['resources/css/app.css'])
|
@vite(['resources/css/app.css'])
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
background: {{ $t['bg'] }};
|
||||||
|
color: {{ $t['text'] }};
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
padding: 3rem 1rem;
|
||||||
|
font-family: 'Inter', system-ui, -apple-system, sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
}
|
||||||
|
.bio-card {
|
||||||
|
background: {{ $t['card'] }};
|
||||||
|
border: 1px solid {{ $t['border'] }};
|
||||||
|
border-radius: 0.75rem;
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
.bio-sub { color: {{ $t['sub'] }}; }
|
||||||
|
.bio-accent { color: {{ $t['accent'] }}; }
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-gray-950 text-gray-100 min-h-screen flex flex-col items-center py-12">
|
<body>
|
||||||
<div class="w-full max-w-md">
|
<div class="w-full max-w-md">
|
||||||
<h1 class="text-2xl font-bold text-center mb-2">
|
<h1 class="text-2xl font-bold text-center mb-2">
|
||||||
{{ $page->getTranslation('title', app()->getLocale()) }}
|
{{ $page->getTranslation('title', app()->getLocale()) }}
|
||||||
</h1>
|
</h1>
|
||||||
@if($page->getTranslation('description', app()->getLocale()))
|
@if($page->getTranslation('description', app()->getLocale()))
|
||||||
<p class="text-gray-400 text-center mb-8">
|
<p class="bio-sub text-center mb-8">
|
||||||
{{ $page->getTranslation('description', app()->getLocale()) }}
|
{{ $page->getTranslation('description', app()->getLocale()) }}
|
||||||
</p>
|
</p>
|
||||||
@endif
|
@endif
|
||||||
<div class="space-y-3">
|
<div class="space-y-3">
|
||||||
@foreach($page->blocks as $block)
|
@foreach($page->blocks as $block)
|
||||||
<div class="bg-gray-900 rounded-xl p-4 border border-gray-800">
|
<div class="bio-card">
|
||||||
<div class="text-sm text-gray-300">{{ $block->config['content'] ?? '' }}</div>
|
<div class="text-sm bio-sub">{{ $block->config['content'] ?? '' }}</div>
|
||||||
</div>
|
</div>
|
||||||
@endforeach
|
@endforeach
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,7 @@
|
||||||
x-data="{ wsOpen: false }"
|
x-data="{ wsOpen: false }"
|
||||||
:class="$store.sidebar.collapsed ? 'w-16' : 'w-60'"
|
:class="$store.sidebar.collapsed ? 'w-16' : 'w-60'"
|
||||||
class="fixed inset-y-0 left-0 h-screen flex flex-col bg-s1 border-r border-white/[.06] z-40 -translate-x-full md:translate-x-0 transition-[width,transform] duration-200 ease-in-out"
|
class="fixed inset-y-0 left-0 h-screen flex flex-col bg-s1 border-r border-white/[.06] z-40 -translate-x-full md:translate-x-0 transition-[width,transform] duration-200 ease-in-out"
|
||||||
:style="$store.sidebar?.open ? 'transform: translateX(0)' : null"
|
:style="$store.sidebar?.mobileOpen ? 'transform: translateX(0)' : null"
|
||||||
>
|
>
|
||||||
<!-- Header: Logo + Workspace Switcher -->
|
<!-- Header: Logo + Workspace Switcher -->
|
||||||
<div class="flex-shrink-0 relative" @click.outside="wsOpen = false">
|
<div class="flex-shrink-0 relative" @click.outside="wsOpen = false">
|
||||||
|
|
@ -183,6 +183,7 @@
|
||||||
href="{{ $url }}"
|
href="{{ $url }}"
|
||||||
wire:navigate
|
wire:navigate
|
||||||
data-nav-link="{{ $item['route'] }}"
|
data-nav-link="{{ $item['route'] }}"
|
||||||
|
@if(in_array($item['route'], ['w.dashboard', 'dashboard'])) data-nav-exact="1" @endif
|
||||||
:title="$store.sidebar.collapsed ? '{{ $item['label'] }}' : ''"
|
:title="$store.sidebar.collapsed ? '{{ $item['label'] }}' : ''"
|
||||||
:class="$store.sidebar.collapsed ? 'justify-center px-2 gap-0' : 'gap-3 px-3'"
|
:class="$store.sidebar.collapsed ? 'justify-center px-2 gap-0' : 'gap-3 px-3'"
|
||||||
class="flex items-center py-2 rounded-lg text-sm transition-colors group
|
class="flex items-center py-2 rounded-lg text-sm transition-colors group
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
<!-- Mobile hamburger -->
|
<!-- Mobile hamburger -->
|
||||||
<button
|
<button
|
||||||
class="md:hidden text-t2 hover:text-t1 transition-colors p-1 -ml-1"
|
class="md:hidden text-t2 hover:text-t1 transition-colors p-1 -ml-1"
|
||||||
@click="$store.sidebar.open = !$store.sidebar.open"
|
@click="$store.sidebar.toggleMobile()"
|
||||||
aria-label="Toggle sidebar"
|
aria-label="Toggle sidebar"
|
||||||
>
|
>
|
||||||
<svg class="w-5 h-5" fill="none" viewBox="0 0 20 20" stroke="currentColor" stroke-width="1.5">
|
<svg class="w-5 h-5" fill="none" viewBox="0 0 20 20" stroke="currentColor" stroke-width="1.5">
|
||||||
|
|
|
||||||
|
|
@ -21,16 +21,15 @@ $cls = $base . ' ' . ($variants[$variant] ?? $variants['primary']);
|
||||||
{{ $attributes->merge(['type' => $type, 'class' => $cls]) }}
|
{{ $attributes->merge(['type' => $type, 'class' => $cls]) }}
|
||||||
@if($action) wire:loading.attr="disabled" wire:target="{{ $action }}" @endif
|
@if($action) wire:loading.attr="disabled" wire:target="{{ $action }}" @endif
|
||||||
>
|
>
|
||||||
<span @if($action) wire:loading.remove wire:target="{{ $action }}" @endif class="flex items-center gap-2">
|
<span @if($action) wire:loading.class="opacity-40" wire:target="{{ $action }}" @endif class="flex items-center gap-2">
|
||||||
{{ $slot->isNotEmpty() ? $slot : $label }}
|
{{ $slot->isNotEmpty() ? $slot : $label }}
|
||||||
</span>
|
</span>
|
||||||
@if($action)
|
@if($action)
|
||||||
<span wire:loading wire:target="{{ $action }}" class="flex items-center gap-2">
|
<span wire:loading wire:target="{{ $action }}" class="absolute inset-0 flex items-center justify-center">
|
||||||
<svg class="animate-spin h-4 w-4 flex-shrink-0" viewBox="0 0 24 24" fill="none">
|
<svg class="animate-spin h-4 w-4 flex-shrink-0" viewBox="0 0 24 24" fill="none">
|
||||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/>
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"/>
|
||||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"/>
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8v4a4 4 0 00-4 4H4z"/>
|
||||||
</svg>
|
</svg>
|
||||||
{{ $loadingLabel }}
|
|
||||||
</span>
|
</span>
|
||||||
@endif
|
@endif
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,17 @@
|
||||||
<meta name="theme-color" content="#0a0a0f" media="(prefers-color-scheme: dark)">
|
<meta name="theme-color" content="#0a0a0f" media="(prefers-color-scheme: dark)">
|
||||||
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
|
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
|
||||||
|
|
||||||
|
<!-- Sidebar-init: set collapsed attribute before first paint — eliminates width flash -->
|
||||||
|
<script>
|
||||||
|
(function () {
|
||||||
|
try {
|
||||||
|
if (localStorage.getItem('sidebarCollapsed') === 'true') {
|
||||||
|
document.documentElement.dataset.sidebarCollapsed = '1';
|
||||||
|
}
|
||||||
|
} catch (e) {}
|
||||||
|
})();
|
||||||
|
</script>
|
||||||
|
|
||||||
<!-- Theme-init: runs synchronously before any paint — eliminates FOUC -->
|
<!-- Theme-init: runs synchronously before any paint — eliminates FOUC -->
|
||||||
<script>
|
<script>
|
||||||
(function () {
|
(function () {
|
||||||
|
|
@ -76,6 +87,7 @@
|
||||||
animation: nim-spin 0.55s linear infinite;
|
animation: nim-spin 0.55s linear infinite;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
html[data-sidebar-collapsed] .skel-sidebar { width: 64px; }
|
||||||
body.app-ready .app-spinner,
|
body.app-ready .app-spinner,
|
||||||
body.app-ready .skel-sidebar,
|
body.app-ready .skel-sidebar,
|
||||||
body.app-ready .skel-topbar { display: none; }
|
body.app-ready .skel-topbar { display: none; }
|
||||||
|
|
@ -95,14 +107,14 @@
|
||||||
<!-- Mobile Sidebar Overlay -->
|
<!-- Mobile Sidebar Overlay -->
|
||||||
<div
|
<div
|
||||||
class="fixed inset-0 z-30 bg-black/60 md:hidden"
|
class="fixed inset-0 z-30 bg-black/60 md:hidden"
|
||||||
x-show="$store.sidebar?.open"
|
x-show="$store.sidebar?.mobileOpen"
|
||||||
x-transition:enter="transition ease-out duration-200"
|
x-transition:enter="transition ease-out duration-200"
|
||||||
x-transition:enter-start="opacity-0"
|
x-transition:enter-start="opacity-0"
|
||||||
x-transition:enter-end="opacity-100"
|
x-transition:enter-end="opacity-100"
|
||||||
x-transition:leave="transition ease-in duration-150"
|
x-transition:leave="transition ease-in duration-150"
|
||||||
x-transition:leave-start="opacity-100"
|
x-transition:leave-start="opacity-100"
|
||||||
x-transition:leave-end="opacity-0"
|
x-transition:leave-end="opacity-0"
|
||||||
@click="$store.sidebar.open = false"
|
@click="$store.sidebar.closeMobile()"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
x-cloak
|
x-cloak
|
||||||
></div>
|
></div>
|
||||||
|
|
@ -145,7 +157,7 @@
|
||||||
// Close mobile sidebar after SPA navigation
|
// Close mobile sidebar after SPA navigation
|
||||||
document.addEventListener('livewire:navigated', function () {
|
document.addEventListener('livewire:navigated', function () {
|
||||||
if (window.Alpine && Alpine.store('sidebar')) {
|
if (window.Alpine && Alpine.store('sidebar')) {
|
||||||
Alpine.store('sidebar').open = false;
|
Alpine.store('sidebar').closeMobile();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
@ -154,7 +166,7 @@
|
||||||
var path = window.location.pathname;
|
var path = window.location.pathname;
|
||||||
document.querySelectorAll('[data-nav-link]').forEach(function (el) {
|
document.querySelectorAll('[data-nav-link]').forEach(function (el) {
|
||||||
var href = new URL(el.href, location.origin).pathname;
|
var href = new URL(el.href, location.origin).pathname;
|
||||||
var isActive = href && (path === href || path.startsWith(href + '/'));
|
var isActive = href && (path === href || (! el.dataset.navExact && path.startsWith(href + '/')));
|
||||||
el.classList.toggle('bg-blue/10', isActive);
|
el.classList.toggle('bg-blue/10', isActive);
|
||||||
el.classList.toggle('text-blue', isActive);
|
el.classList.toggle('text-blue', isActive);
|
||||||
el.classList.toggle('text-t2', !isActive);
|
el.classList.toggle('text-t2', !isActive);
|
||||||
|
|
|
||||||
|
|
@ -54,9 +54,15 @@
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<details class="border border-white/[.06] rounded-lg mb-2">
|
<div x-data="{ open: false }" class="border border-white/[.06] rounded-lg mb-2">
|
||||||
<summary class="px-4 py-3 text-sm font-medium text-t1 cursor-pointer select-none">Styling</summary>
|
<button type="button" @click="open = !open"
|
||||||
<div class="px-4 pb-4 space-y-3 mt-2">
|
class="flex items-center justify-between w-full px-4 py-3 text-sm font-medium text-t1 select-none">
|
||||||
|
<span>Styling</span>
|
||||||
|
<svg class="w-4 h-4 text-t3 transition-transform" :class="open && 'rotate-180'" fill="none" viewBox="0 0 16 16" stroke="currentColor" stroke-width="1.5">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M4 6l4 4 4-4"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<div x-show="open" class="px-4 pb-4 space-y-3 border-t border-white/[.06] pt-3">
|
||||||
<div class="grid grid-cols-2 gap-3">
|
<div class="grid grid-cols-2 gap-3">
|
||||||
<div>
|
<div>
|
||||||
<label class="block text-xs text-t2 mb-1">Foreground</label>
|
<label class="block text-xs text-t2 mb-1">Foreground</label>
|
||||||
|
|
@ -86,11 +92,17 @@
|
||||||
class="block w-full px-3 py-2 bg-s2 border border-white/[.06] rounded-lg text-sm text-t1 placeholder-t3 focus:outline-none">
|
class="block w-full px-3 py-2 bg-s2 border border-white/[.06] rounded-lg text-sm text-t1 placeholder-t3 focus:outline-none">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</div>
|
||||||
|
|
||||||
<details class="border border-white/[.06] rounded-lg mb-2">
|
<div x-data="{ open: false }" class="border border-white/[.06] rounded-lg mb-2">
|
||||||
<summary class="px-4 py-3 text-sm font-medium text-t1 cursor-pointer select-none">Frame</summary>
|
<button type="button" @click="open = !open"
|
||||||
<div class="px-4 pb-4 space-y-3 mt-2">
|
class="flex items-center justify-between w-full px-4 py-3 text-sm font-medium text-t1 select-none">
|
||||||
|
<span>Frame</span>
|
||||||
|
<svg class="w-4 h-4 text-t3 transition-transform" :class="open && 'rotate-180'" fill="none" viewBox="0 0 16 16" stroke="currentColor" stroke-width="1.5">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M4 6l4 4 4-4"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<div x-show="open" class="px-4 pb-4 space-y-3 border-t border-white/[.06] pt-3">
|
||||||
<select wire:model.live="frame" class="block w-full px-3 py-2 bg-s2 border border-white/[.06] rounded-lg text-sm text-t1 focus:outline-none">
|
<select wire:model.live="frame" class="block w-full px-3 py-2 bg-s2 border border-white/[.06] rounded-lg text-sm text-t1 focus:outline-none">
|
||||||
<option value="none">No frame</option>
|
<option value="none">No frame</option>
|
||||||
<option value="label-bottom">Label bottom</option>
|
<option value="label-bottom">Label bottom</option>
|
||||||
|
|
@ -103,11 +115,17 @@
|
||||||
<input wire:model.live="frameColor" type="color" class="w-full h-9 rounded cursor-pointer border border-white/[.06]">
|
<input wire:model.live="frameColor" type="color" class="w-full h-9 rounded cursor-pointer border border-white/[.06]">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</div>
|
||||||
|
|
||||||
<details class="border border-white/[.06] rounded-lg mb-4">
|
<div x-data="{ open: false }" class="border border-white/[.06] rounded-lg mb-4">
|
||||||
<summary class="px-4 py-3 text-sm font-medium text-t1 cursor-pointer select-none">Advanced</summary>
|
<button type="button" @click="open = !open"
|
||||||
<div class="px-4 pb-4 space-y-3 mt-2">
|
class="flex items-center justify-between w-full px-4 py-3 text-sm font-medium text-t1 select-none">
|
||||||
|
<span>Advanced</span>
|
||||||
|
<svg class="w-4 h-4 text-t3 transition-transform" :class="open && 'rotate-180'" fill="none" viewBox="0 0 16 16" stroke="currentColor" stroke-width="1.5">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M4 6l4 4 4-4"/>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
<div x-show="open" class="px-4 pb-4 space-y-3 border-t border-white/[.06] pt-3">
|
||||||
<label class="flex items-center gap-2 text-sm text-t2 cursor-pointer">
|
<label class="flex items-center gap-2 text-sm text-t2 cursor-pointer">
|
||||||
<input wire:model.live="isDynamic" type="checkbox"> Dynamic QR (target changeable later)
|
<input wire:model.live="isDynamic" type="checkbox"> Dynamic QR (target changeable later)
|
||||||
</label>
|
</label>
|
||||||
|
|
@ -122,18 +140,11 @@
|
||||||
class="block w-full px-3 py-2 bg-s2 border border-white/[.06] rounded-lg text-sm text-t1 focus:outline-none">
|
class="block w-full px-3 py-2 bg-s2 border border-white/[.06] rounded-lg text-sm text-t1 focus:outline-none">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</div>
|
||||||
|
|
||||||
<div class="flex gap-3">
|
<div class="flex gap-3">
|
||||||
<button wire:click="save" wire:loading.attr="disabled" wire:target="save"
|
<x-ui.button wire:click="save" action="save" label="Create QR Code" class="flex-1" />
|
||||||
class="flex-1 px-4 py-2.5 bg-accent-gradient text-white text-sm font-medium rounded-lg hover:opacity-90 disabled:opacity-60 transition-opacity">
|
<x-ui.button wire:click="closeModal" variant="secondary" label="Cancel" />
|
||||||
<span wire:loading.remove wire:target="save">Create QR Code</span>
|
|
||||||
<span wire:loading wire:target="save">Creating...</span>
|
|
||||||
</button>
|
|
||||||
<button wire:click="closeModal" type="button"
|
|
||||||
class="px-4 py-2.5 bg-s2 text-t2 text-sm font-medium rounded-lg hover:text-t1 transition-colors border border-white/[.06]">
|
|
||||||
Cancel
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,20 @@
|
||||||
@error('slug')<p class="mt-1 text-xs text-red">{{ $message }}</p>@enderror
|
@error('slug')<p class="mt-1 text-xs text-red">{{ $message }}</p>@enderror
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label class="block text-xs font-medium text-t2 mb-2">Theme</label>
|
||||||
|
<div class="flex gap-2 flex-wrap">
|
||||||
|
@foreach(['dark' => ['#0a0a0f', '#f0f0f5'], 'light' => ['#ffffff', '#111827'], 'purple' => ['#13001a', '#f0e8ff'], 'blue' => ['#000d1a', '#e8f4ff']] as $preset => [$bg, $fg])
|
||||||
|
<button type="button" wire:click="$set('theme', '{{ $preset }}')"
|
||||||
|
class="flex items-center gap-2 px-3 py-1.5 rounded-lg border text-xs font-medium transition-colors
|
||||||
|
{{ $theme === $preset ? 'border-blue text-t1' : 'border-white/[.06] text-t2 hover:border-white/20' }}">
|
||||||
|
<span class="w-4 h-4 rounded-full border border-white/20 flex-shrink-0" style="background: {{ $bg }}"></span>
|
||||||
|
{{ ucfirst($preset) }}
|
||||||
|
</button>
|
||||||
|
@endforeach
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="flex items-center gap-3">
|
<div class="flex items-center gap-3">
|
||||||
<label class="relative inline-flex items-center cursor-pointer">
|
<label class="relative inline-flex items-center cursor-pointer">
|
||||||
<input wire:model="isPublished" type="checkbox" class="sr-only peer">
|
<input wire:model="isPublished" type="checkbox" class="sr-only peer">
|
||||||
|
|
|
||||||
|
|
@ -4,25 +4,15 @@
|
||||||
<h1 class="text-2xl font-bold text-t1">AI Insights</h1>
|
<h1 class="text-2xl font-bold text-t1">AI Insights</h1>
|
||||||
<p class="text-sm text-t3 mt-1">Analyze your link performance with AI</p>
|
<p class="text-sm text-t3 mt-1">Analyze your link performance with AI</p>
|
||||||
</div>
|
</div>
|
||||||
<button wire:click="generate" wire:loading.attr="disabled" wire:target="generate"
|
<x-ui.button wire:click="generate" action="generate">
|
||||||
class="px-4 py-2 bg-accent-gradient text-white rounded-lg text-sm font-medium hover:opacity-90 transition-opacity disabled:opacity-60 flex items-center gap-2">
|
<x-heroicon-o-sparkles class="w-4 h-4" />
|
||||||
<span wire:loading.remove wire:target="generate">
|
|
||||||
<x-heroicon-o-sparkles class="w-4 h-4 inline mr-1" />
|
|
||||||
Generate Insights
|
Generate Insights
|
||||||
</span>
|
</x-ui.button>
|
||||||
<span wire:loading wire:target="generate" class="flex items-center gap-2">
|
|
||||||
<svg class="animate-spin h-4 w-4" fill="none" viewBox="0 0 24 24">
|
|
||||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
||||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
|
|
||||||
</svg>
|
|
||||||
Analyzing…
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@if($insights)
|
@if($insights)
|
||||||
<div class="p-5 bg-s1 border border-white/[.06] rounded-xl">
|
<div class="p-5 bg-s1 border border-white/[.06] rounded-xl">
|
||||||
<div class="flex items-center gap-2 mb-3 text-purple text-xs font-medium">
|
<div class="flex items-center gap-2 mb-3 text-accent text-xs font-medium">
|
||||||
<x-heroicon-o-sparkles class="w-3.5 h-3.5" />
|
<x-heroicon-o-sparkles class="w-3.5 h-3.5" />
|
||||||
AI Analysis
|
AI Analysis
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue