Files
filament-short-url/resources/views/table/empty-state-folders.blade.php
Bartłomiej Janczak 2107a3e6c8 feat: v5.0.0 — Live Feed page, Folders & Tags, Link Archiving, API scopes
## Analytics — Live Activity Feed
- Dedicated /stats/live standalone page (mirrors /stats/logs pattern)
- Consistent 3-tab navigation across Statistics / Live Feed / Visit Logs
- checkForUpdates() with skipRender(): O(1) MAX(id) poll — ~100-byte response on no-change
- latestVisitId sentinel (-1) prevents unnecessary first-poll re-render
- Cache key versioned by latestVisitId — fixes bug where concurrent users
  could permanently miss a visit due to stale thundering-herd cache
- 3-second thundering-herd cache (key: url_id + date + filters + latestVisitId)
- Country flags via flagcdn.com (precomputed in PHP, Alpine.js CSP-safe onerror)
- time_ago precomputed in PHP — no Carbon::parse() double-instantiation in Blade
- SELECT only 12 needed columns instead of SELECT *; limit 25 rows
- wire:poll.5s.visible — polling stops when widget is off-screen

## Link Organization
- Folders: one folder per link, clickable → filtered link list, link count badge
- Tags: up to 5 tags per link, clickable → filtered link list, link count badge
- Archiving: soft-archive links instead of permanent deletion; restorable

## REST API
- Per-key API scopes: links:read-only (GET) / links:read-write (full CRUD)
- Per-key rate limiting: individual requests/minute per API key

## Translations
- Added stats_live_feed_poll_interval (EN: 'Updates every 5s', PL: 'Aktualizacja co 5s')

## Docs
- README.md: corrected API scopes/rate-limiting docs (features were already implemented)
- README.md: added Folders, Tags, Archiving to features list
- README.md: added v5.0.0 changelog entry
2026-06-06 02:16:07 +02:00

134 lines
7.7 KiB
PHP

<div class="flex flex-col items-center justify-center gap-6 px-4 py-10 md:min-h-[550px]">
<!-- Folders Visual Grid -->
<div class="relative flex items-center justify-center h-40 w-full max-w-xs md:max-w-md overflow-hidden rounded-2xl border border-neutral-200/60 dark:border-neutral-800 bg-neutral-50/50 dark:bg-neutral-900/30 p-6 shadow-inner">
<div class="absolute inset-0 bg-[linear-gradient(to_right,#8080800a_1px,transparent_1px),linear-gradient(to_bottom,#8080800a_1px,transparent_1px)] bg-[size:14px_24px] [mask-image:radial-gradient(ellipse_60%_50%_at_50%_0%,#000_70%,transparent_100%)]"></div>
<!-- 3D Folder Container -->
<div class="relative w-48 h-32 flex items-center justify-center z-10" style="perspective: 1000px;">
<!-- Folder Back -->
<div class="absolute bottom-2 w-28 h-18 bg-neutral-300 dark:bg-neutral-800 rounded-lg shadow-sm border border-neutral-400/30 dark:border-neutral-700/50" style="transform: translateZ(-30px);">
<!-- Folder Tab -->
<div class="absolute -top-2 left-3 w-10 h-3 bg-neutral-300 dark:bg-neutral-800 rounded-t-md border-t border-x border-neutral-400/30 dark:border-neutral-700/50"></div>
</div>
<!-- Floating Document/Link Cards (Middle Layer) -->
<!-- Card 1 -->
<div class="absolute w-24 h-12 rounded-lg bg-gradient-to-br from-blue-500/90 to-indigo-600/90 text-white shadow-md border border-white/20 flex flex-col justify-between p-2 select-none"
style="animation: fsu-folder-card-1 4s infinite cubic-bezier(0.25, 1, 0.5, 1); transform-style: preserve-3d;">
<div class="flex items-center gap-1.5">
<div class="w-3.5 h-3.5 rounded-full bg-white/25 flex items-center justify-center flex-shrink-0">
<svg class="w-2.5 h-2.5 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="3">
<path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 0 1 1.242 7.244l-4.5 4.5a4.5 4.5 0 0 1-6.364-6.364l1.757-1.757m13.35-.622 1.757-1.757a4.5 4.5 0 0 0-6.364-6.364l-4.5 4.5a4.5 4.5 0 0 0 1.242 7.244" />
</svg>
</div>
<div class="h-1.5 w-8 bg-white/30 rounded"></div>
</div>
<div class="h-1 w-12 bg-white/20 rounded"></div>
</div>
<!-- Card 2 -->
<div class="absolute w-24 h-12 rounded-lg bg-gradient-to-br from-violet-500/90 to-purple-600/90 text-white shadow-md border border-white/20 flex flex-col justify-between p-2 select-none"
style="animation: fsu-folder-card-2 4s infinite cubic-bezier(0.25, 1, 0.5, 1); animation-delay: 2s; transform-style: preserve-3d;">
<div class="flex items-center gap-1.5">
<div class="w-3.5 h-3.5 rounded-full bg-white/25 flex items-center justify-center flex-shrink-0">
<svg class="w-2.5 h-2.5 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="3">
<path stroke-linecap="round" stroke-linejoin="round" d="M13.19 8.688a4.5 4.5 0 0 1 1.242 7.244l-4.5 4.5a4.5 4.5 0 0 1-6.364-6.364l1.757-1.757m13.35-.622 1.757-1.757a4.5 4.5 0 0 0-6.364-6.364l-4.5 4.5a4.5 4.5 0 0 0 1.242 7.244" />
</svg>
</div>
<div class="h-1.5 w-8 bg-white/30 rounded"></div>
</div>
<div class="h-1 w-12 bg-white/20 rounded"></div>
</div>
<!-- Folder Front (Glassmorphic & Tilted) -->
<div class="absolute bottom-2 w-28 h-16 bg-neutral-100/50 dark:bg-neutral-900/50 border border-white/30 dark:border-neutral-800/80 rounded-lg shadow-lg origin-bottom transition-all"
style="backdrop-filter: blur(4px); -webkit-backdrop-filter: blur(4px); transform: rotateX(-16deg); transform-style: preserve-3d; animation: fsu-folder-front 4s infinite ease-in-out; box-shadow: 0 12px 20px -8px rgba(0,0,0,0.15), 0 4px 6px -2px rgba(0,0,0,0.05);">
<!-- Folder lock / branding line -->
<div class="absolute top-2 left-3 w-6 h-1 bg-neutral-300 dark:bg-neutral-700 rounded-full"></div>
<div class="absolute bottom-2 right-3 w-3 h-3 rounded-full border border-neutral-300 dark:border-neutral-750 flex items-center justify-center">
<div class="w-1 h-1 rounded-full bg-neutral-400 dark:bg-neutral-650"></div>
</div>
</div>
</div>
</div>
<style>
@keyframes fsu-folder-front {
0%, 100% {
transform: rotateX(-15deg);
}
50% {
transform: rotateX(-22deg);
}
}
@keyframes fsu-folder-card-1 {
0% {
transform: translate3d(35px, -60px, 10px) rotate(12deg) scale(0.8);
opacity: 0;
}
15% {
transform: translate3d(20px, -45px, 20px) rotate(6deg) scale(0.95);
opacity: 1;
}
35% {
transform: translate3d(0px, -20px, 0px) rotate(0deg) scale(1);
opacity: 1;
}
55% {
transform: translate3d(0px, 10px, -15px) rotate(-2deg) scale(0.9);
opacity: 0.8;
}
75%, 100% {
transform: translate3d(0px, 25px, -25px) rotate(-4deg) scale(0.75);
opacity: 0;
}
}
@keyframes fsu-folder-card-2 {
0% {
transform: translate3d(-35px, -60px, 10px) rotate(-12deg) scale(0.8);
opacity: 0;
}
15% {
transform: translate3d(-20px, -45px, 20px) rotate(-6deg) scale(0.95);
opacity: 1;
}
35% {
transform: translate3d(0px, -20px, 0px) rotate(0deg) scale(1);
opacity: 1;
}
55% {
transform: translate3d(0px, 10px, -15px) rotate(2deg) scale(0.9);
opacity: 0.8;
}
75%, 100% {
transform: translate3d(0px, 25px, -25px) rotate(4deg) scale(0.75);
opacity: 0;
}
}
</style>
<!-- Title and Description -->
<div class="max-w-md text-pretty text-center px-4">
<span class="text-base font-semibold text-neutral-900 dark:text-neutral-100">
{{ __('filament-short-url::default.empty_state_folders_heading') }}
</span>
<div class="mt-2 text-pretty text-sm text-neutral-500 dark:text-neutral-400">
{{ __('filament-short-url::default.empty_state_folders_description') }}
</div>
</div>
<!-- Create Button -->
@if(!isset($hideCreateButton) || !$hideCreateButton)
<div class="flex items-center gap-2">
<button type="button"
x-on:click="$wire.mountAction('create')"
class="group flex h-10 items-center justify-center gap-2 whitespace-nowrap rounded-lg border border-black bg-black text-white hover:bg-neutral-800 dark:border-white dark:bg-white dark:text-black dark:hover:bg-neutral-100 hover:ring-4 hover:ring-neutral-200 dark:hover:ring-neutral-800/50 px-4 text-sm font-semibold transition-all cursor-pointer">
<svg class="size-4 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2.5">
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4.5v15m7.5-7.5h-15" />
</svg>
<span>{{ __('filament-short-url::default.empty_state_folder_action') }}</span>
</button>
</div>
@endif
</div>