feat(design): refined dark editorial design audit across all views
- CSS: metric-num glow, card-shadow depth, card-accent-top treatment, table-row-hover with left accent bar, badge utility, nim-reveal staggered page-load animation, nim-skeleton shimmer, bg-grid, polished scrollbar, tighter letter-spacing on headings - Dashboard: accent-topped metric cards, gradient fill chart, JetBrains Mono tooltips, leading-zero rank numbers, improved top-links table - Links: refined table with tracking-widest thead, badge status pills with dot indicator, blue slug hover on A/B action - QR Codes: type-colored icons, display_type badge, bar-chart empty state SVG - Bio: bars icon, slug with blue highlight, published dot indicator - Analytics: grouped range button pill, progress bar distributions per country/device, nim-reveal stagger, three metric cards with card-accent-top - Topbar: sticky + backdrop-blur, accent-gradient avatar Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>main
parent
257d1b94f1
commit
063da474f9
|
|
@ -89,9 +89,116 @@
|
|||
html { font-size: 14px; }
|
||||
body { font-size: 0.875rem; line-height: 1.5; }
|
||||
|
||||
h1 { font-size: 1.5rem; line-height: 2rem; font-weight: 600; }
|
||||
h2 { font-size: 1.25rem; line-height: 1.75rem; font-weight: 600; }
|
||||
h3 { font-size: 1.125rem; line-height: 1.5rem; font-weight: 500; }
|
||||
h1 { font-size: 1.5rem; line-height: 2rem; font-weight: 700; letter-spacing: -0.02em; }
|
||||
h2 { font-size: 1.25rem; line-height: 1.75rem; font-weight: 600; letter-spacing: -0.015em; }
|
||||
h3 { font-size: 1.125rem; line-height: 1.5rem; font-weight: 500; letter-spacing: -0.01em; }
|
||||
|
||||
/* Refined scrollbar */
|
||||
::-webkit-scrollbar { width: 4px; height: 4px; }
|
||||
::-webkit-scrollbar-track { background: transparent; }
|
||||
::-webkit-scrollbar-thumb { background: var(--color-t3); border-radius: 2px; }
|
||||
::-webkit-scrollbar-thumb:hover { background: var(--color-t2); }
|
||||
|
||||
/* Text rendering */
|
||||
body { -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility; }
|
||||
.font-mono, code, pre { font-variant-numeric: tabular-nums; }
|
||||
|
||||
/* Number glow for metrics */
|
||||
.metric-num {
|
||||
font-family: var(--font-mono);
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.03em;
|
||||
text-shadow: 0 0 24px color-mix(in srgb, var(--color-accent) 30%, transparent);
|
||||
}
|
||||
|
||||
/* Card depth shadow */
|
||||
.card-shadow {
|
||||
box-shadow:
|
||||
0 1px 0 0 rgba(255,255,255,0.04) inset,
|
||||
0 -1px 0 0 rgba(0,0,0,0.2) inset,
|
||||
0 4px 16px rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
/* Accent-top card — top border is accent gradient */
|
||||
.card-accent-top {
|
||||
position: relative;
|
||||
}
|
||||
.card-accent-top::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0; left: 0; right: 0;
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, var(--color-accent) 0%, var(--color-accent-2) 100%);
|
||||
border-radius: inherit;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
/* Page-load stagger reveal */
|
||||
@keyframes nim-fade-up {
|
||||
from { opacity: 0; transform: translateY(8px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
.nim-reveal {
|
||||
opacity: 0;
|
||||
animation: nim-fade-up 0.35s ease forwards;
|
||||
}
|
||||
.nim-reveal-1 { animation-delay: 0.05s; }
|
||||
.nim-reveal-2 { animation-delay: 0.12s; }
|
||||
.nim-reveal-3 { animation-delay: 0.19s; }
|
||||
.nim-reveal-4 { animation-delay: 0.26s; }
|
||||
.nim-reveal-5 { animation-delay: 0.33s; }
|
||||
|
||||
/* Shimmer for loading skeletons */
|
||||
@keyframes nim-shimmer {
|
||||
0% { background-position: -200% 0; }
|
||||
100% { background-position: 200% 0; }
|
||||
}
|
||||
.nim-skeleton {
|
||||
background: linear-gradient(90deg, var(--color-s2) 25%, var(--color-s3) 50%, var(--color-s2) 75%);
|
||||
background-size: 200% 100%;
|
||||
animation: nim-shimmer 1.4s infinite;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
/* Subtle grid background for stat sections */
|
||||
.bg-grid {
|
||||
background-image:
|
||||
linear-gradient(rgba(255,255,255,0.015) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(255,255,255,0.015) 1px, transparent 1px);
|
||||
background-size: 24px 24px;
|
||||
}
|
||||
|
||||
/* Table row hover with left accent bar */
|
||||
.table-row-hover {
|
||||
position: relative;
|
||||
transition: background 0.12s ease;
|
||||
}
|
||||
.table-row-hover::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
left: 0; top: 0; bottom: 0;
|
||||
width: 2px;
|
||||
background: linear-gradient(180deg, var(--color-accent), var(--color-accent-2));
|
||||
transform: scaleY(0);
|
||||
transition: transform 0.15s ease;
|
||||
border-radius: 0 2px 2px 0;
|
||||
}
|
||||
.table-row-hover:hover::before {
|
||||
transform: scaleY(1);
|
||||
}
|
||||
|
||||
/* Pill badge base */
|
||||
.badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
padding: 0.125rem 0.5rem;
|
||||
border-radius: 9999px;
|
||||
font-size: 0.6875rem;
|
||||
font-weight: 500;
|
||||
letter-spacing: 0.01em;
|
||||
}
|
||||
|
||||
@layer utilities {
|
||||
.bg-accent-gradient {
|
||||
|
|
@ -106,4 +213,5 @@ h3 { font-size: 1.125rem; line-height: 1.5rem; font-weight: 500; }
|
|||
.border-accent-gradient {
|
||||
border-image: linear-gradient(135deg, var(--color-accent), var(--color-accent-2)) 1;
|
||||
}
|
||||
.tabular-nums { font-variant-numeric: tabular-nums; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
}
|
||||
@endphp
|
||||
|
||||
<header class="h-14 flex-shrink-0 bg-s1 border-b border-white/[.06] flex items-center justify-between px-4 gap-4">
|
||||
<header class="h-14 flex-shrink-0 bg-s1/80 backdrop-blur-sm border-b border-white/[.06] flex items-center justify-between px-4 gap-4 sticky top-0 z-30">
|
||||
<!-- Left: Hamburger (mobile) + Title -->
|
||||
<div class="flex items-center gap-3 min-w-0">
|
||||
<!-- Mobile hamburger -->
|
||||
|
|
@ -56,23 +56,13 @@
|
|||
</button>
|
||||
|
||||
<!-- Notifications Bell -->
|
||||
<div class="relative">
|
||||
<button
|
||||
class="w-8 h-8 flex items-center justify-center rounded-lg text-t2 hover:text-t1 hover:bg-s2 transition-colors"
|
||||
aria-label="Notifications"
|
||||
>
|
||||
<svg class="w-4 h-4" fill="none" viewBox="0 0 16 16" stroke="currentColor" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M8 1.5a5 5 0 00-5 5v2.5l-1 1.5h12l-1-1.5V6.5a5 5 0 00-5-5zM6.5 13.5a1.5 1.5 0 003 0"/>
|
||||
</svg>
|
||||
</button>
|
||||
<span class="absolute top-1 right-1 w-3.5 h-3.5 bg-blue rounded-full text-white text-[9px] font-medium flex items-center justify-center leading-none pointer-events-none">3</span>
|
||||
</div>
|
||||
<livewire:components.notifications-bell />
|
||||
|
||||
<!-- User Dropdown -->
|
||||
<div class="relative ml-1" x-data="{ userOpen: false }">
|
||||
<button
|
||||
@click="userOpen = !userOpen"
|
||||
class="w-8 h-8 rounded-full bg-blue flex items-center justify-center text-white text-xs font-semibold hover:opacity-90 transition-opacity"
|
||||
class="w-8 h-8 rounded-full bg-accent-gradient flex items-center justify-center text-white text-xs font-bold hover:opacity-90 transition-opacity"
|
||||
aria-label="User menu"
|
||||
>
|
||||
{{ $initials }}
|
||||
|
|
|
|||
|
|
@ -1,16 +1,19 @@
|
|||
<div>
|
||||
{{-- Header --}}
|
||||
<div class="flex flex-wrap items-center justify-between gap-3 mb-6">
|
||||
<h1 class="text-2xl font-bold text-t1">Analytics</h1>
|
||||
<div class="flex flex-wrap items-center justify-between gap-3 mb-6 nim-reveal nim-reveal-1">
|
||||
<div>
|
||||
<h1 class="text-t1">Analytics</h1>
|
||||
<p class="text-xs text-t3 mt-0.5">Click-through data for your workspace</p>
|
||||
</div>
|
||||
|
||||
{{-- Range buttons --}}
|
||||
<div class="flex gap-1.5">
|
||||
<div class="flex gap-1 bg-s2 border border-white/[.06] rounded-lg p-0.5">
|
||||
@foreach(['24h' => '24h', '7d' => '7d', '30d' => '30d', '90d' => '90d'] as $key => $label)
|
||||
<button wire:click="setRange('{{ $key }}')"
|
||||
class="px-3 py-1.5 text-xs rounded-lg font-medium transition-all
|
||||
class="px-3 py-1.5 text-xs rounded-md font-semibold transition-all
|
||||
{{ $range === $key
|
||||
? 'bg-accent-gradient text-white'
|
||||
: 'bg-s2 text-t2 border border-white/[.06] hover:text-t1' }}">
|
||||
? 'bg-accent-gradient text-white shadow-sm'
|
||||
: 'text-t2 hover:text-t1' }}">
|
||||
{{ $label }}
|
||||
</button>
|
||||
@endforeach
|
||||
|
|
@ -53,51 +56,82 @@
|
|||
</div>
|
||||
|
||||
{{-- Summary metrics --}}
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 gap-4 mb-6">
|
||||
<div class="bg-s1 border border-white/[.06] rounded-xl p-5">
|
||||
<div class="text-xs font-medium text-t2 uppercase tracking-wider">{{ strtoupper($range) }} Clicks</div>
|
||||
<div class="text-3xl font-bold font-mono text-t1 mt-2">{{ number_format($totalFiltered) }}</div>
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 gap-3 mb-6">
|
||||
<div class="bg-s1 border border-white/[.06] rounded-xl p-4 card-shadow card-accent-top nim-reveal nim-reveal-1">
|
||||
<div class="text-xs font-semibold text-t3 uppercase tracking-widest mb-3">{{ strtoupper($range) }}</div>
|
||||
<div class="metric-num text-3xl text-t1">{{ number_format($totalFiltered) }}</div>
|
||||
<div class="text-xs text-t3 mt-1.5">filtered clicks</div>
|
||||
</div>
|
||||
<div class="bg-s1 border border-white/[.06] rounded-xl p-5">
|
||||
<div class="text-xs font-medium text-t2 uppercase tracking-wider">All-time Clicks</div>
|
||||
<div class="text-3xl font-bold font-mono text-t1 mt-2">{{ number_format($allTime) }}</div>
|
||||
<div class="bg-s1 border border-white/[.06] rounded-xl p-4 card-shadow nim-reveal nim-reveal-2">
|
||||
<div class="text-xs font-semibold text-t3 uppercase tracking-widest mb-3">All-time</div>
|
||||
<div class="metric-num text-3xl text-t1">{{ number_format($allTime) }}</div>
|
||||
<div class="text-xs text-t3 mt-1.5">total clicks</div>
|
||||
</div>
|
||||
<div class="bg-s1 border border-white/[.06] rounded-xl p-5">
|
||||
<div class="text-xs font-medium text-t2 uppercase tracking-wider">Today</div>
|
||||
<div class="text-3xl font-bold font-mono text-t1 mt-2">{{ number_format($totalToday) }}</div>
|
||||
<div class="bg-s1 border border-white/[.06] rounded-xl p-4 card-shadow nim-reveal nim-reveal-3">
|
||||
<div class="text-xs font-semibold text-t3 uppercase tracking-widest mb-3">Today</div>
|
||||
<div class="metric-num text-3xl text-t1">{{ number_format($totalToday) }}</div>
|
||||
<div class="text-xs text-t3 mt-1.5">since midnight</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
{{-- Top Countries --}}
|
||||
<div class="bg-s1 border border-white/[.06] rounded-xl p-5">
|
||||
<h3 class="text-sm font-semibold text-t1 mb-4">Top Countries</h3>
|
||||
@forelse($byCountry as $row)
|
||||
<div class="flex items-center justify-between py-2 border-b border-white/[.03] last:border-0">
|
||||
<button wire:click="$set('country', '{{ $row->country }}')"
|
||||
class="text-sm text-t1 hover:text-blue transition-colors text-left {{ $country === $row->country ? 'text-blue font-medium' : '' }}">
|
||||
{{ $row->country }}
|
||||
<div class="bg-s1 border border-white/[.06] rounded-xl overflow-hidden card-shadow nim-reveal nim-reveal-4">
|
||||
<div class="px-5 py-3.5 border-b border-white/[.06] flex items-center justify-between">
|
||||
<div class="text-xs font-semibold text-t1 uppercase tracking-wide">Top Countries</div>
|
||||
@if($country)
|
||||
<button wire:click="$set('country', '')" class="text-xs text-t3 hover:text-red transition-colors flex items-center gap-1">
|
||||
<x-heroicon-o-x-mark class="w-3 h-3" />
|
||||
Clear
|
||||
</button>
|
||||
<span class="text-sm font-mono text-t2">{{ number_format($row->total) }}</span>
|
||||
@endif
|
||||
</div>
|
||||
@forelse($byCountry as $row)
|
||||
@php $pct = $totalFiltered > 0 ? round(($row->total / $totalFiltered) * 100) : 0; @endphp
|
||||
<div class="px-5 py-2.5 border-b border-white/[.03] last:border-0 group">
|
||||
<div class="flex items-center justify-between mb-1">
|
||||
<button wire:click="$set('country', '{{ $row->country }}')"
|
||||
class="text-sm text-t1 hover:text-blue transition-colors {{ $country === $row->country ? 'text-blue font-semibold' : '' }}">
|
||||
{{ $row->country }}
|
||||
</button>
|
||||
<span class="font-mono text-xs text-t2 tabular-nums">{{ number_format($row->total) }}</span>
|
||||
</div>
|
||||
<div class="h-0.5 bg-s3 rounded-full overflow-hidden">
|
||||
<div class="h-full bg-accent-gradient rounded-full transition-all" style="width: {{ $pct }}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<p class="text-sm text-t3">No data for this period</p>
|
||||
<div class="px-5 py-8 text-center text-xs text-t3">No data for this period</div>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
{{-- By Device --}}
|
||||
<div class="bg-s1 border border-white/[.06] rounded-xl p-5">
|
||||
<h3 class="text-sm font-semibold text-t1 mb-4">By Device</h3>
|
||||
@forelse($byDevice as $row)
|
||||
<div class="flex items-center justify-between py-2 border-b border-white/[.03] last:border-0">
|
||||
<button wire:click="$set('device', '{{ $row->device }}')"
|
||||
class="text-sm text-t1 capitalize hover:text-blue transition-colors text-left {{ $device === $row->device ? 'text-blue font-medium' : '' }}">
|
||||
{{ $row->device ?? 'Unknown' }}
|
||||
<div class="bg-s1 border border-white/[.06] rounded-xl overflow-hidden card-shadow nim-reveal nim-reveal-5">
|
||||
<div class="px-5 py-3.5 border-b border-white/[.06] flex items-center justify-between">
|
||||
<div class="text-xs font-semibold text-t1 uppercase tracking-wide">By Device</div>
|
||||
@if($device)
|
||||
<button wire:click="$set('device', '')" class="text-xs text-t3 hover:text-red transition-colors flex items-center gap-1">
|
||||
<x-heroicon-o-x-mark class="w-3 h-3" />
|
||||
Clear
|
||||
</button>
|
||||
<span class="text-sm font-mono text-t2">{{ number_format($row->total) }}</span>
|
||||
@endif
|
||||
</div>
|
||||
@forelse($byDevice as $row)
|
||||
@php $pct = $totalFiltered > 0 ? round(($row->total / $totalFiltered) * 100) : 0; @endphp
|
||||
<div class="px-5 py-2.5 border-b border-white/[.03] last:border-0">
|
||||
<div class="flex items-center justify-between mb-1">
|
||||
<button wire:click="$set('device', '{{ $row->device }}')"
|
||||
class="text-sm text-t1 capitalize hover:text-blue transition-colors {{ $device === $row->device ? 'text-blue font-semibold' : '' }}">
|
||||
{{ $row->device ?? 'Unknown' }}
|
||||
</button>
|
||||
<span class="font-mono text-xs text-t2 tabular-nums">{{ number_format($row->total) }}</span>
|
||||
</div>
|
||||
<div class="h-0.5 bg-s3 rounded-full overflow-hidden">
|
||||
<div class="h-full bg-green/60 rounded-full transition-all" style="width: {{ $pct }}%"></div>
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<p class="text-sm text-t3">No data for this period</p>
|
||||
<div class="px-5 py-8 text-center text-xs text-t3">No data for this period</div>
|
||||
@endforelse
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,54 +1,62 @@
|
|||
<div>
|
||||
@php $ws = current_workspace(); @endphp
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h1 class="text-2xl font-semibold text-t1">Link-in-Bio</h1>
|
||||
<div class="flex items-center justify-between mb-6 nim-reveal nim-reveal-1">
|
||||
<div>
|
||||
<h1 class="text-t1">Link-in-Bio</h1>
|
||||
<p class="text-xs text-t3 mt-0.5">{{ $bioPages->total() }} pages</p>
|
||||
</div>
|
||||
@if($ws)
|
||||
<button
|
||||
@click="$dispatch('openModal', {component: 'modals.create-bio-page', arguments: {workspaceUlid: '{{ $ws->ulid }}'}})"
|
||||
class="px-4 py-2 bg-accent-gradient text-white rounded-lg text-sm font-medium hover:opacity-90 transition-opacity">
|
||||
+ New Bio Page
|
||||
class="px-4 py-2 bg-accent-gradient text-white rounded-lg text-sm font-semibold hover:opacity-90 transition-opacity flex items-center gap-1.5">
|
||||
<x-heroicon-o-plus class="w-4 h-4" />
|
||||
New Bio Page
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="bg-s1 border border-white/[.06] rounded-xl overflow-hidden">
|
||||
<div class="bg-s1 border border-white/[.06] rounded-xl overflow-hidden card-shadow nim-reveal nim-reveal-2">
|
||||
@forelse($bioPages as $page)
|
||||
<div class="flex items-center gap-4 px-5 py-4 border-b border-white/[.04] group hover:bg-white/[.02] transition-colors">
|
||||
<div class="w-10 h-10 bg-s2 rounded-lg flex items-center justify-center flex-shrink-0">
|
||||
<svg class="w-5 h-5 text-t3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5M3.75 17.25h7.5"/>
|
||||
</svg>
|
||||
@php $title = is_array($page->title) ? ($page->title['en'] ?? $page->slug) : ($page->title ?? $page->slug); @endphp
|
||||
<div class="flex items-center gap-4 px-5 py-3.5 border-b border-white/[.03] last:border-0 table-row-hover hover:bg-white/[.015] group relative">
|
||||
<div class="w-8 h-8 bg-s2 border border-white/[.06] rounded-lg flex items-center justify-center flex-shrink-0">
|
||||
<x-heroicon-o-bars-3-bottom-left class="w-4 h-4 text-t3" />
|
||||
</div>
|
||||
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="text-sm font-medium text-t1">{{ is_array($page->title) ? ($page->title['en'] ?? $page->slug) : ($page->title ?? $page->slug) }}</div>
|
||||
<div class="text-xs text-t2 font-mono">nimu.li/{{ $page->slug }}</div>
|
||||
<div class="text-sm font-semibold text-t1">{{ $title }}</div>
|
||||
<div class="text-xs text-t3 font-mono mt-0.5">nimu.li/<span class="text-blue">{{ $page->slug }}</span></div>
|
||||
</div>
|
||||
<span class="text-xs px-2 py-0.5 rounded-full {{ $page->is_published ? 'bg-green/10 text-green' : 'bg-white/[.06] text-t3' }}">
|
||||
|
||||
<span class="badge {{ $page->is_published ? 'bg-green/10 text-green' : 'bg-white/[.06] text-t3' }}">
|
||||
@if($page->is_published)
|
||||
<span class="w-1 h-1 rounded-full bg-green inline-block"></span>
|
||||
@endif
|
||||
{{ $page->is_published ? 'Published' : 'Draft' }}
|
||||
</span>
|
||||
<div class="flex items-center gap-3 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
|
||||
<div class="flex items-center gap-3 opacity-0 group-hover:opacity-100 transition-all duration-150">
|
||||
<button
|
||||
@click="$dispatch('openModal', {component: 'modals.edit-bio-page', arguments: {bioUlid: '{{ $page->ulid }}'}})"
|
||||
class="text-t2 text-xs hover:text-t1">Edit</button>
|
||||
class="text-t2 text-xs hover:text-t1 transition-colors">Edit</button>
|
||||
<button
|
||||
@click="$dispatch('openModal', {component: 'modals.delete-bio-page', arguments: {bioUlid: '{{ $page->ulid }}'}})"
|
||||
class="text-red text-xs hover:opacity-80">Delete</button>
|
||||
class="text-t3 text-xs hover:text-red transition-colors">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<div class="p-12 text-center">
|
||||
<div class="w-16 h-16 bg-s2 rounded-2xl flex items-center justify-center mx-auto mb-4">
|
||||
<svg class="w-8 h-8 text-t3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 6.75h16.5M3.75 12h16.5M3.75 17.25h7.5"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-lg font-medium text-t1 mb-2">No bio pages yet</h3>
|
||||
<p class="text-sm text-t2 mb-6">Create a beautiful link-in-bio page for Instagram, TikTok and more.</p>
|
||||
<div class="p-16 text-center">
|
||||
<svg class="w-10 h-10 mx-auto text-t3 mb-4" fill="none" viewBox="0 0 40 40" stroke="currentColor" stroke-width="1">
|
||||
<rect x="6" y="4" width="28" height="32" rx="3"/>
|
||||
<path stroke-linecap="round" d="M12 12h16M12 18h16M12 24h10"/>
|
||||
</svg>
|
||||
<p class="text-sm text-t2 mb-1.5">No bio pages yet</p>
|
||||
<p class="text-xs text-t3 mb-6">Create a link-in-bio page for Instagram, TikTok and more.</p>
|
||||
@if($ws)
|
||||
<button
|
||||
@click="$dispatch('openModal', {component: 'modals.create-bio-page', arguments: {workspaceUlid: '{{ $ws->ulid }}'}})"
|
||||
class="px-4 py-2.5 bg-accent-gradient text-white rounded-lg text-sm font-medium hover:opacity-90 transition-opacity">
|
||||
Create your first Bio Page
|
||||
class="px-4 py-2.5 bg-accent-gradient text-white rounded-lg text-sm font-semibold hover:opacity-90 transition-opacity">
|
||||
Create first Bio Page
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,54 +1,60 @@
|
|||
<div>
|
||||
{{-- Quick Actions --}}
|
||||
<div class="flex items-center gap-3 mb-6">
|
||||
<a href="{{ route('w.links.index', request()->route('workspace')) }}"
|
||||
class="px-4 py-2 bg-accent-gradient text-white rounded-lg text-sm font-medium hover:opacity-90 transition-opacity">
|
||||
+ Create Link
|
||||
</a>
|
||||
<a href="{{ route('w.qr.index', request()->route('workspace')) }}"
|
||||
class="px-4 py-2 bg-s2 text-t1 border border-white/[.06] rounded-lg text-sm font-medium hover:bg-s3 transition-colors">
|
||||
+ Create QR Code
|
||||
</a>
|
||||
<a href="{{ route('w.analytics.index', request()->route('workspace')) }}"
|
||||
class="px-4 py-2 text-t2 text-sm font-medium hover:text-t1 transition-colors">
|
||||
View Analytics →
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{{-- Hero Metrics --}}
|
||||
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{{-- Clicks Today --}}
|
||||
<div class="bg-s1 border border-white/[.06] rounded-xl p-5">
|
||||
<div class="text-xs font-medium text-t2 uppercase tracking-wider">Clicks Today</div>
|
||||
<div class="text-3xl font-semibold font-mono text-t1 mt-2">{{ number_format($today) }}</div>
|
||||
<div class="text-xs text-t3 mt-1">since midnight</div>
|
||||
{{-- Page header --}}
|
||||
<div class="flex items-center justify-between mb-7 nim-reveal nim-reveal-1">
|
||||
<div>
|
||||
<h1 class="text-t1">Overview</h1>
|
||||
<p class="text-xs text-t3 mt-0.5">{{ now()->format('l, F j') }}</p>
|
||||
</div>
|
||||
|
||||
{{-- Last 7 Days --}}
|
||||
<div class="bg-s1 border border-white/[.06] rounded-xl p-5">
|
||||
<div class="text-xs font-medium text-t2 uppercase tracking-wider">Last 7 Days</div>
|
||||
<div class="text-3xl font-semibold font-mono text-t1 mt-2">{{ number_format($last7) }}</div>
|
||||
<div class="text-xs text-t3 mt-1">rolling 7-day window</div>
|
||||
</div>
|
||||
|
||||
{{-- Last 30 Days --}}
|
||||
<div class="bg-s1 border border-white/[.06] rounded-xl p-5">
|
||||
<div class="text-xs font-medium text-t2 uppercase tracking-wider">Last 30 Days</div>
|
||||
<div class="text-3xl font-semibold font-mono text-t1 mt-2">{{ number_format($last30) }}</div>
|
||||
<div class="text-xs text-t3 mt-1">rolling 30-day window</div>
|
||||
</div>
|
||||
|
||||
{{-- Total Links --}}
|
||||
<div class="bg-s1 border border-white/[.06] rounded-xl p-5">
|
||||
<div class="text-xs font-medium text-t2 uppercase tracking-wider">Total Links</div>
|
||||
<div class="text-3xl font-semibold font-mono text-t1 mt-2">{{ number_format($totalLinks) }}</div>
|
||||
<div class="text-xs text-t3 mt-1">active links</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<a href="{{ route('w.analytics.index', request()->route('workspace')) }}"
|
||||
wire:navigate
|
||||
class="px-3 py-2 text-xs text-t2 hover:text-t1 transition-colors flex items-center gap-1.5">
|
||||
<x-heroicon-o-arrow-trending-up class="w-3.5 h-3.5" />
|
||||
Analytics
|
||||
</a>
|
||||
<a href="{{ route('w.qr.index', request()->route('workspace')) }}"
|
||||
wire:navigate
|
||||
class="px-3 py-2 bg-s2 border border-white/[.06] text-t1 rounded-lg text-xs font-medium hover:bg-s3 transition-colors">
|
||||
+ QR Code
|
||||
</a>
|
||||
<a href="{{ route('w.links.index', request()->route('workspace')) }}"
|
||||
wire:navigate
|
||||
class="px-3 py-2 bg-accent-gradient text-white rounded-lg text-xs font-semibold hover:opacity-90 transition-opacity">
|
||||
+ New Link
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- 30-Day Chart --}}
|
||||
{{-- Metric cards --}}
|
||||
<div class="grid grid-cols-2 lg:grid-cols-4 gap-3 mb-5">
|
||||
<div class="bg-s1 border border-white/[.06] rounded-xl p-4 card-shadow card-accent-top nim-reveal nim-reveal-1">
|
||||
<div class="text-xs font-medium text-t3 uppercase tracking-widest mb-3">Today</div>
|
||||
<div class="metric-num text-3xl text-t1">{{ number_format($today) }}</div>
|
||||
<div class="text-xs text-t3 mt-1.5">clicks since midnight</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-s1 border border-white/[.06] rounded-xl p-4 card-shadow nim-reveal nim-reveal-2">
|
||||
<div class="text-xs font-medium text-t3 uppercase tracking-widest mb-3">7 Days</div>
|
||||
<div class="metric-num text-3xl text-t1">{{ number_format($last7) }}</div>
|
||||
<div class="text-xs text-t3 mt-1.5">rolling window</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-s1 border border-white/[.06] rounded-xl p-4 card-shadow nim-reveal nim-reveal-3">
|
||||
<div class="text-xs font-medium text-t3 uppercase tracking-widest mb-3">30 Days</div>
|
||||
<div class="metric-num text-3xl text-t1">{{ number_format($last30) }}</div>
|
||||
<div class="text-xs text-t3 mt-1.5">rolling window</div>
|
||||
</div>
|
||||
|
||||
<div class="bg-s1 border border-white/[.06] rounded-xl p-4 card-shadow nim-reveal nim-reveal-4">
|
||||
<div class="text-xs font-medium text-t3 uppercase tracking-widest mb-3">Links</div>
|
||||
<div class="metric-num text-3xl text-t1">{{ number_format($totalLinks) }}</div>
|
||||
<div class="text-xs text-t3 mt-1.5">active links</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Chart --}}
|
||||
<div
|
||||
class="bg-s1 border border-white/[.06] rounded-xl p-5 mt-4"
|
||||
class="bg-s1 border border-white/[.06] rounded-xl p-5 mb-4 card-shadow nim-reveal nim-reveal-5"
|
||||
x-data
|
||||
x-init="
|
||||
const ctx = document.getElementById('clicksChart');
|
||||
|
|
@ -58,26 +64,51 @@
|
|||
labels: {{ Js::from($labels) }},
|
||||
datasets: [{
|
||||
data: {{ Js::from($data) }},
|
||||
borderColor: '#4f8ef7',
|
||||
backgroundColor: 'rgba(79,142,247,0.08)',
|
||||
borderColor: '#7c3aed',
|
||||
backgroundColor: (ctx) => {
|
||||
const g = ctx.chart.ctx.createLinearGradient(0, 0, 0, 160);
|
||||
g.addColorStop(0, 'rgba(124,58,237,0.18)');
|
||||
g.addColorStop(1, 'rgba(124,58,237,0.00)');
|
||||
return g;
|
||||
},
|
||||
fill: true,
|
||||
tension: 0.4,
|
||||
pointRadius: 0,
|
||||
borderWidth: 2,
|
||||
pointHoverRadius: 4,
|
||||
pointHoverBackgroundColor: '#7c3aed',
|
||||
borderWidth: 1.5,
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: { legend: { display: false } },
|
||||
interaction: { mode: 'index', intersect: false },
|
||||
plugins: {
|
||||
legend: { display: false },
|
||||
tooltip: {
|
||||
backgroundColor: '#111117',
|
||||
borderColor: 'rgba(255,255,255,0.08)',
|
||||
borderWidth: 1,
|
||||
titleColor: '#8888a0',
|
||||
bodyColor: '#f0f0f5',
|
||||
titleFont: { family: 'JetBrains Mono', size: 10 },
|
||||
bodyFont: { family: 'JetBrains Mono', size: 13, weight: '700' },
|
||||
padding: 10,
|
||||
displayColors: false,
|
||||
callbacks: { label: ctx => ctx.parsed.y.toLocaleString() + ' clicks' }
|
||||
}
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
grid: { color: 'rgba(255,255,255,0.04)' },
|
||||
ticks: { color: '#8888a0', font: { size: 11 } }
|
||||
grid: { color: 'rgba(255,255,255,0.03)', drawTicks: false },
|
||||
border: { display: false },
|
||||
ticks: { color: '#44445a', font: { family: 'JetBrains Mono', size: 10 }, maxTicksLimit: 8, maxRotation: 0 }
|
||||
},
|
||||
y: {
|
||||
grid: { color: 'rgba(255,255,255,0.04)' },
|
||||
ticks: { color: '#8888a0', font: { size: 11 } },
|
||||
position: 'right',
|
||||
grid: { color: 'rgba(255,255,255,0.03)', drawTicks: false },
|
||||
border: { display: false },
|
||||
ticks: { color: '#44445a', font: { family: 'JetBrains Mono', size: 10 }, maxTicksLimit: 4 },
|
||||
beginAtZero: true
|
||||
}
|
||||
}
|
||||
|
|
@ -85,43 +116,54 @@
|
|||
});
|
||||
"
|
||||
>
|
||||
<h3 class="text-sm font-medium text-t1 mb-4">Clicks — Last 30 Days</h3>
|
||||
<div class="h-48">
|
||||
<div class="flex items-center justify-between mb-4">
|
||||
<div class="text-xs font-semibold text-t1 tracking-wide">CLICKS — LAST 30 DAYS</div>
|
||||
<div class="w-2 h-2 rounded-full bg-accent-gradient"></div>
|
||||
</div>
|
||||
<div class="h-44">
|
||||
<canvas id="clicksChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{{-- Top 5 Links --}}
|
||||
<div class="bg-s1 border border-white/[.06] rounded-xl mt-4 overflow-hidden">
|
||||
<div class="px-5 py-4 border-b border-white/[.06]">
|
||||
<h3 class="text-sm font-medium text-t1">Top Links</h3>
|
||||
{{-- Top Links --}}
|
||||
<div class="bg-s1 border border-white/[.06] rounded-xl overflow-hidden card-shadow nim-reveal nim-reveal-5">
|
||||
<div class="px-5 py-3.5 border-b border-white/[.06] flex items-center justify-between">
|
||||
<div class="text-xs font-semibold text-t1 tracking-wide uppercase">Top Links</div>
|
||||
<a href="{{ route('w.links.index', request()->route('workspace')) }}" wire:navigate
|
||||
class="text-xs text-t3 hover:text-t1 transition-colors">View all →</a>
|
||||
</div>
|
||||
<table class="w-full text-sm">
|
||||
<table class="w-full">
|
||||
<thead>
|
||||
<tr class="text-xs font-medium text-t2 uppercase tracking-wider border-b border-white/[.06]">
|
||||
<th class="px-5 py-3 text-left w-10">#</th>
|
||||
<th class="px-5 py-3 text-left">Slug</th>
|
||||
<th class="px-5 py-3 text-left">Title</th>
|
||||
<th class="px-5 py-3 text-right">Clicks</th>
|
||||
<tr class="text-t3 text-xs uppercase tracking-widest border-b border-white/[.04]">
|
||||
<th class="px-5 py-2.5 text-left font-medium w-8">#</th>
|
||||
<th class="px-5 py-2.5 text-left font-medium">Slug</th>
|
||||
<th class="px-5 py-2.5 text-left font-medium hidden md:table-cell">Title</th>
|
||||
<th class="px-5 py-2.5 text-right font-medium">Clicks</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-white/[.04]">
|
||||
<tbody>
|
||||
@forelse($topLinks as $index => $link)
|
||||
<tr class="hover:bg-s2/50 transition-colors">
|
||||
<td class="px-5 py-3 text-t3 font-mono text-xs">{{ $index + 1 }}</td>
|
||||
<tr class="border-b border-white/[.03] last:border-0 table-row-hover hover:bg-white/[.015]">
|
||||
<td class="px-5 py-3 text-t3 font-mono text-xs tabular-nums">{{ str_pad($index + 1, 2, '0', STR_PAD_LEFT) }}</td>
|
||||
<td class="px-5 py-3">
|
||||
<span class="font-mono text-blue text-sm">{{ $link->slug }}</span>
|
||||
</td>
|
||||
<td class="px-5 py-3 text-t2 text-sm truncate max-w-xs">
|
||||
{{ $link->title ?: '—' }}
|
||||
</td>
|
||||
<td class="px-5 py-3 text-right font-mono text-t1 font-semibold">
|
||||
{{ number_format($link->total_clicks) }}
|
||||
<div class="flex items-baseline gap-1">
|
||||
<span class="text-t3 text-xs">nimu.li/</span><span class="font-mono text-blue text-sm font-medium">{{ $link->slug }}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="px-5 py-3 text-t3 text-xs truncate max-w-xs hidden md:table-cell">{{ $link->title ?: '—' }}</td>
|
||||
<td class="px-5 py-3 text-right font-mono text-t1 font-bold text-sm tabular-nums">{{ number_format($link->total_clicks) }}</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="4" class="px-5 py-10 text-center text-t3">No links yet.</td>
|
||||
<td colspan="4" class="px-5 py-12 text-center">
|
||||
<div class="flex flex-col items-center gap-3">
|
||||
<svg class="w-8 h-8 text-t3" fill="none" viewBox="0 0 32 32" stroke="currentColor" stroke-width="1">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M19 11l-8 8M11 11h8v8"/>
|
||||
<circle cx="16" cy="16" r="13" stroke-dasharray="4 3"/>
|
||||
</svg>
|
||||
<p class="text-xs text-t3">No links yet — create your first one</p>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
<div>
|
||||
@php $ws = current_workspace(); @endphp
|
||||
{{-- Header --}}
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h1 class="text-2xl font-bold text-t1">Links</h1>
|
||||
<div class="flex items-center justify-between mb-6 nim-reveal nim-reveal-1">
|
||||
<h1 class="text-t1">Links</h1>
|
||||
@if($ws)
|
||||
<div class="flex items-center gap-2">
|
||||
<button @click="$dispatch('openModal', {component: 'modals.utm-builder'})"
|
||||
|
|
@ -34,26 +34,26 @@
|
|||
</div>
|
||||
|
||||
{{-- Table --}}
|
||||
<div class="bg-s1 border border-white/[.06] rounded-xl overflow-hidden">
|
||||
<div class="bg-s1 border border-white/[.06] rounded-xl overflow-hidden card-shadow">
|
||||
<table class="w-full text-sm">
|
||||
<thead class="border-b border-white/[.06]">
|
||||
<tr class="text-t3 text-xs uppercase tracking-wider">
|
||||
<th class="px-4 py-3 text-left font-medium">Link</th>
|
||||
<th class="px-4 py-3 text-left font-medium">Target</th>
|
||||
<th class="px-4 py-3 text-left font-medium hidden md:table-cell">Tags</th>
|
||||
<th class="px-4 py-3 text-right font-medium">Clicks</th>
|
||||
<th class="px-4 py-3 text-center font-medium">Status</th>
|
||||
<th class="px-4 py-3"></th>
|
||||
<thead class="border-b border-white/[.06] bg-s2/40">
|
||||
<tr class="text-t3 text-xs uppercase tracking-widest">
|
||||
<th class="px-4 py-3 text-left font-semibold">Link</th>
|
||||
<th class="px-4 py-3 text-left font-semibold">Target</th>
|
||||
<th class="px-4 py-3 text-left font-semibold hidden md:table-cell">Tags</th>
|
||||
<th class="px-4 py-3 text-right font-semibold">Clicks</th>
|
||||
<th class="px-4 py-3 text-center font-semibold">Status</th>
|
||||
<th class="px-4 py-3 w-32"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-white/[.04]">
|
||||
<tbody>
|
||||
@forelse($links as $link)
|
||||
<tr class="hover:bg-white/[.02] transition-colors group">
|
||||
<tr class="border-b border-white/[.03] last:border-0 table-row-hover hover:bg-white/[.015] group relative">
|
||||
{{-- Slug + title --}}
|
||||
<td class="px-4 py-3">
|
||||
<div class="flex items-baseline gap-1">
|
||||
<span class="text-t3 text-xs">nimu.li/</span>
|
||||
<span class="font-mono text-blue text-sm">{{ $link->slug }}</span>
|
||||
<span class="font-mono text-blue text-sm font-semibold">{{ $link->slug }}</span>
|
||||
</div>
|
||||
@if($link->title)
|
||||
<div class="text-t3 text-xs mt-0.5 truncate max-w-xs">{{ $link->title }}</div>
|
||||
|
|
@ -61,7 +61,7 @@
|
|||
</td>
|
||||
|
||||
{{-- Target URL --}}
|
||||
<td class="px-4 py-3 text-t2 text-xs max-w-xs">
|
||||
<td class="px-4 py-3 text-t3 text-xs max-w-xs">
|
||||
<span class="block truncate" title="{{ $link->target_url }}">{{ $link->target_url }}</span>
|
||||
</td>
|
||||
|
||||
|
|
@ -70,7 +70,7 @@
|
|||
@if(!empty($link->tags))
|
||||
<div class="flex flex-wrap gap-1">
|
||||
@foreach($link->tags as $tag)
|
||||
<span class="bg-s2 text-t3 text-xs px-2 py-0.5 rounded-full">{{ $tag }}</span>
|
||||
<span class="badge bg-s3 text-t3">{{ $tag }}</span>
|
||||
@endforeach
|
||||
</div>
|
||||
@else
|
||||
|
|
@ -79,27 +79,30 @@
|
|||
</td>
|
||||
|
||||
{{-- Clicks --}}
|
||||
<td class="px-4 py-3 text-right font-mono text-t1 text-sm">
|
||||
<td class="px-4 py-3 text-right font-mono text-t1 text-sm font-bold tabular-nums">
|
||||
{{ number_format($link->clicks_count ?? 0) }}
|
||||
</td>
|
||||
|
||||
{{-- Status Badge --}}
|
||||
<td class="px-4 py-3 text-center">
|
||||
@if($link->status === 'active')
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-green/10 text-green">active</span>
|
||||
<span class="badge bg-green/10 text-green">
|
||||
<span class="w-1 h-1 rounded-full bg-green inline-block"></span>
|
||||
active
|
||||
</span>
|
||||
@elseif($link->status === 'expired')
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-amber/10 text-amber">expired</span>
|
||||
<span class="badge bg-amber/10 text-amber">expired</span>
|
||||
@else
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded-full text-xs font-medium bg-white/[.06] text-t3">{{ $link->status }}</span>
|
||||
<span class="badge bg-white/[.06] text-t3">{{ $link->status }}</span>
|
||||
@endif
|
||||
</td>
|
||||
|
||||
{{-- Actions --}}
|
||||
<td class="px-4 py-3 text-right">
|
||||
<div class="flex items-center justify-end gap-3 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
<div class="flex items-center justify-end gap-3 opacity-0 group-hover:opacity-100 transition-all duration-150">
|
||||
<button
|
||||
@click="$dispatch('openModal', {component: 'modals.link-variants', arguments: {linkUlid: '{{ $link->ulid }}'}})"
|
||||
class="text-t2 text-xs hover:text-purple transition-colors flex items-center gap-1">
|
||||
class="text-t3 text-xs hover:text-blue transition-colors flex items-center gap-1">
|
||||
<x-heroicon-o-chart-bar class="w-3 h-3" />
|
||||
A/B
|
||||
</button>
|
||||
|
|
@ -110,7 +113,7 @@
|
|||
</button>
|
||||
<button
|
||||
@click="$dispatch('openModal', {component: 'modals.delete-link', arguments: {linkUlid: '{{ $link->ulid }}'}})"
|
||||
class="text-red text-xs hover:opacity-80 transition-opacity">
|
||||
class="text-t3 text-xs hover:text-red transition-colors">
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,54 +1,79 @@
|
|||
<div>
|
||||
@php $ws = current_workspace(); @endphp
|
||||
<div class="flex items-center justify-between mb-6">
|
||||
<h1 class="text-2xl font-semibold text-t1">QR Codes</h1>
|
||||
<div class="flex items-center justify-between mb-6 nim-reveal nim-reveal-1">
|
||||
<div>
|
||||
<h1 class="text-t1">QR Codes</h1>
|
||||
<p class="text-xs text-t3 mt-0.5">{{ $qrCodes->total() }} total</p>
|
||||
</div>
|
||||
@if($ws)
|
||||
<button
|
||||
@click="$dispatch('openModal', {component: 'modals.create-qr-code', arguments: {workspaceUlid: '{{ $ws->ulid }}'}})"
|
||||
class="px-4 py-2 bg-accent-gradient text-white rounded-lg text-sm font-medium hover:opacity-90 transition-opacity">
|
||||
+ Create QR Code
|
||||
class="px-4 py-2 bg-accent-gradient text-white rounded-lg text-sm font-semibold hover:opacity-90 transition-opacity flex items-center gap-1.5">
|
||||
<x-heroicon-o-plus class="w-4 h-4" />
|
||||
New QR Code
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="bg-s1 border border-white/[.06] rounded-xl overflow-hidden">
|
||||
@if($search ?? false)
|
||||
<div class="mb-4 nim-reveal nim-reveal-1">
|
||||
<input wire:model.live.debounce.300ms="search" type="text" placeholder="Search QR codes…"
|
||||
class="w-full max-w-sm px-3 py-2 bg-s1 border border-white/[.06] rounded-lg text-sm text-t1 placeholder-t3 focus:outline-none focus:ring-1 focus:ring-blue/50">
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<div class="bg-s1 border border-white/[.06] rounded-xl overflow-hidden card-shadow nim-reveal nim-reveal-2">
|
||||
@forelse($qrCodes as $qr)
|
||||
<div class="flex items-center gap-4 px-5 py-4 border-b border-white/[.04] group hover:bg-white/[.02] transition-colors">
|
||||
<div class="w-10 h-10 bg-s2 rounded-lg flex items-center justify-center flex-shrink-0">
|
||||
<svg class="w-5 h-5 text-t3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5A1.125 1.125 0 013.75 9.375v-4.5zM3.75 14.625c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5a1.125 1.125 0 01-1.125-1.125v-4.5zM13.5 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5A1.125 1.125 0 0113.5 9.375v-4.5z"/>
|
||||
</svg>
|
||||
<div class="flex items-center gap-4 px-5 py-3.5 border-b border-white/[.03] last:border-0 table-row-hover hover:bg-white/[.015] group relative">
|
||||
{{-- QR type icon --}}
|
||||
<div class="w-8 h-8 bg-s2 border border-white/[.06] rounded-lg flex items-center justify-center flex-shrink-0">
|
||||
@php
|
||||
$displayType = $qr->payload['display_type'] ?? $qr->type;
|
||||
$typeColors = ['url' => 'text-blue', 'vcard' => 'text-green', 'wifi' => 'text-amber', 'email' => 'text-purple', 'default' => 'text-t3'];
|
||||
$typeColor = $typeColors[$displayType] ?? $typeColors['default'];
|
||||
@endphp
|
||||
<x-heroicon-o-qr-code class="w-4 h-4 {{ $typeColor }}" />
|
||||
</div>
|
||||
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="text-sm font-medium text-t1">{{ $qr->label ?: $qr->ulid }}</div>
|
||||
<div class="text-xs text-t2 truncate">{{ $qr->type }} · {{ $qr->payload['url'] ?? '' }}</div>
|
||||
<div class="text-sm font-semibold text-t1">{{ $qr->payload['label'] ?? ($qr->label ?? $qr->ulid) }}</div>
|
||||
<div class="text-xs text-t3 truncate mt-0.5 font-mono">{{ $qr->payload['data'] ?? ($qr->payload['url'] ?? '') }}</div>
|
||||
</div>
|
||||
<span class="text-xs px-2 py-0.5 rounded-full {{ $qr->is_dynamic ? 'bg-blue/10 text-blue' : 'bg-white/[.06] text-t3' }}">
|
||||
{{ $qr->is_dynamic ? 'Dynamic' : 'Static' }}
|
||||
</span>
|
||||
<div class="flex items-center gap-3 opacity-0 group-hover:opacity-100 transition-opacity">
|
||||
|
||||
<div class="flex items-center gap-2 flex-shrink-0">
|
||||
<span class="badge bg-s2 text-t3 border border-white/[.04] uppercase tracking-wide">{{ strtoupper($qr->payload['display_type'] ?? $qr->type) }}</span>
|
||||
<span class="badge {{ $qr->is_dynamic ? 'bg-blue/10 text-blue' : 'bg-white/[.06] text-t3' }}">
|
||||
{{ $qr->is_dynamic ? 'Dynamic' : 'Static' }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-3 opacity-0 group-hover:opacity-100 transition-all duration-150 flex-shrink-0">
|
||||
<button
|
||||
@click="$dispatch('openModal', {component: 'modals.edit-qr-code', arguments: {qrUlid: '{{ $qr->ulid }}'}})"
|
||||
class="text-t2 text-xs hover:text-t1">Edit</button>
|
||||
class="text-t2 text-xs hover:text-t1 transition-colors">Edit</button>
|
||||
<button
|
||||
@click="$dispatch('openModal', {component: 'modals.delete-qr-code', arguments: {qrUlid: '{{ $qr->ulid }}'}})"
|
||||
class="text-red text-xs hover:opacity-80">Delete</button>
|
||||
class="text-t3 text-xs hover:text-red transition-colors">Delete</button>
|
||||
</div>
|
||||
</div>
|
||||
@empty
|
||||
<div class="p-12 text-center">
|
||||
<div class="w-16 h-16 bg-s2 rounded-2xl flex items-center justify-center mx-auto mb-4">
|
||||
<svg class="w-8 h-8 text-t3" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="1.5">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M3.75 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5A1.125 1.125 0 013.75 9.375v-4.5zM3.75 14.625c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5a1.125 1.125 0 01-1.125-1.125v-4.5zM13.5 4.875c0-.621.504-1.125 1.125-1.125h4.5c.621 0 1.125.504 1.125 1.125v4.5c0 .621-.504 1.125-1.125 1.125h-4.5A1.125 1.125 0 0113.5 9.375v-4.5z"/>
|
||||
</svg>
|
||||
</div>
|
||||
<h3 class="text-lg font-medium text-t1 mb-2">No QR Codes yet</h3>
|
||||
<p class="text-sm text-t2 mb-6">Create QR codes for URLs, vCards, WiFi and more.</p>
|
||||
<div class="p-16 text-center">
|
||||
<svg class="w-12 h-12 mx-auto text-t3 mb-4" fill="none" viewBox="0 0 48 48" stroke="currentColor" stroke-width="1">
|
||||
<rect x="4" y="4" width="16" height="16" rx="2"/>
|
||||
<rect x="28" y="4" width="16" height="16" rx="2"/>
|
||||
<rect x="4" y="28" width="16" height="16" rx="2"/>
|
||||
<rect x="8" y="8" width="8" height="8"/>
|
||||
<rect x="32" y="8" width="8" height="8"/>
|
||||
<rect x="8" y="32" width="8" height="8"/>
|
||||
<path stroke-linecap="round" d="M28 28h4M36 28h4M28 32v4M32 32h4M36 32v4M28 40h4M32 36h4M36 36v4M40 40h4"/>
|
||||
</svg>
|
||||
<p class="text-sm text-t2 mb-1.5">No QR codes yet</p>
|
||||
<p class="text-xs text-t3 mb-6">Create QR codes for URLs, vCards, WiFi and more.</p>
|
||||
@if($ws)
|
||||
<button
|
||||
@click="$dispatch('openModal', {component: 'modals.create-qr-code', arguments: {workspaceUlid: '{{ $ws->ulid }}'}})"
|
||||
class="px-4 py-2.5 bg-accent-gradient text-white rounded-lg text-sm font-medium hover:opacity-90 transition-opacity">
|
||||
Create your first QR Code
|
||||
class="px-4 py-2.5 bg-accent-gradient text-white rounded-lg text-sm font-semibold hover:opacity-90 transition-opacity">
|
||||
Create first QR Code
|
||||
</button>
|
||||
@endif
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue