Files
ladill-link/resources/views/links/show.blade.php
T
isaacclad 86cac606e0
Deploy Ladill Link / deploy (push) Successful in 1m40s
Surface destination edits as the core dynamic short-link feature.
Move destination editing to the top of the link page, clarify that the short URL never changes, prevent caches from pinning old redirects, and test that later destination updates take effect immediately.
2026-07-16 20:12:14 +00:00

156 lines
9.8 KiB
PHP

<x-user-layout>
<x-slot name="title">{{ $link->label ?: $link->slug }}</x-slot>
<div class="mx-auto max-w-6xl space-y-6">
<div class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
<div>
<div class="flex flex-wrap items-center gap-2">
<h1 class="text-2xl font-semibold text-slate-900">{{ $link->label ?: $link->slug }}</h1>
<span class="rounded-full bg-slate-100 px-2 py-0.5 text-xs font-medium text-slate-600">{{ $link->sourceAppLabel() }}</span>
@if ($link->is_managed_here)
<span class="rounded-full bg-emerald-50 px-2 py-0.5 text-xs font-medium text-emerald-700">Dynamic destination</span>
@endif
</div>
<div class="mt-1 flex flex-wrap items-center gap-2">
<a href="{{ $link->publicUrl() }}" target="_blank" rel="noopener"
class="inline-flex items-center gap-1 text-emerald-600 hover:text-emerald-700">
{{ $link->publicUrl() }}
<svg class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M13.5 6H5.25A2.25 2.25 0 0 0 3 8.25v10.5A2.25 2.25 0 0 0 5.25 21h10.5A2.25 2.25 0 0 0 18 18.75V10.5m-10.5 6L21 3m0 0h-5.25M21 3v5.25" /></svg>
</a>
<x-copy-button :text="$link->publicUrl()" label="Copy" class="text-xs font-medium text-slate-500 hover:text-emerald-700" />
</div>
<p class="mt-2 max-w-xl text-sm text-slate-500">
Your short link never changes. Update the destination anytime the next click goes to the new URL.
</p>
</div>
@if ($link->is_managed_here)
<form method="POST" action="{{ route('user.links.destroy', $link) }}" onsubmit="return confirm('Delete this link?')">
@csrf @method('DELETE')
<button type="submit" class="text-sm text-red-600 hover:text-red-700">Delete</button>
</form>
@elseif ($link->manage_url)
<a href="{{ $link->manage_url }}" target="_blank" rel="noopener"
class="inline-flex items-center rounded-lg border border-slate-200 px-3 py-1.5 text-sm font-medium text-slate-700 hover:bg-slate-50">
Manage in {{ $link->sourceAppLabel() }}
</a>
@endif
</div>
@if (! $link->is_managed_here)
<div class="rounded-xl border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-900">
This {{ \App\Models\ShortLink::publicHost() }} slug was created in {{ $link->sourceAppLabel() }}. Edit the destination there the short link stays the same and updates automatically.
</div>
@endif
@if ($link->is_managed_here)
<form method="POST" action="{{ route('user.links.update', $link) }}" class="space-y-4 rounded-xl border border-emerald-200 bg-white p-6 shadow-sm">
@csrf @method('PATCH')
<div>
<h2 class="text-base font-semibold text-slate-900">Destination</h2>
<p class="mt-0.5 text-sm text-slate-500">
Change where <span class="font-medium text-emerald-700">{{ $link->publicUrl() }}</span> sends people.
Printed cards, QR codes, and old shares keep working.
</p>
</div>
<div>
<label for="destination_url" class="block text-sm font-medium text-slate-700">Destination URL</label>
<input type="url" name="destination_url" id="destination_url" value="{{ old('destination_url', $link->destination_url) }}" required
placeholder="https://example.com/new-page"
class="mt-1 block w-full rounded-lg border-slate-300 shadow-sm focus:border-emerald-500 focus:ring-emerald-500">
@error('destination_url')<p class="mt-1 text-sm text-red-600">{{ $message }}</p>@enderror
@error('link')<p class="mt-1 text-sm text-red-600">{{ $message }}</p>@enderror
</div>
<div>
<label for="label" class="block text-sm font-medium text-slate-700">Label <span class="font-normal text-slate-400">(optional)</span></label>
<input type="text" name="label" id="label" value="{{ old('label', $link->label) }}"
class="mt-1 block w-full rounded-lg border-slate-300 shadow-sm focus:border-emerald-500 focus:ring-emerald-500">
</div>
<label class="flex items-center gap-2 text-sm text-slate-700">
<input type="hidden" name="is_active" value="0">
<input type="checkbox" name="is_active" value="1" @checked(old('is_active', $link->is_active))
class="rounded border-slate-300 text-emerald-600 focus:ring-emerald-500">
Link is active
</label>
<button type="submit" class="btn-primary">Update destination</button>
</form>
@else
<div class="rounded-xl border border-slate-200 bg-white p-6">
<h2 class="text-base font-semibold text-slate-900">Destination</h2>
<p class="mt-1 break-all text-sm text-slate-600">{{ $link->destination_url }}</p>
</div>
@endif
<div class="space-y-5">
<h2 class="text-base font-semibold text-slate-900">Analytics</h2>
<div class="grid grid-cols-2 gap-4 sm:grid-cols-4">
<div class="rounded-xl border border-slate-200 bg-white p-4">
<p class="text-2xl font-bold text-slate-900">{{ number_format($summary['total_clicks']) }}</p>
<p class="mt-0.5 text-xs text-slate-500">Total clicks</p>
</div>
<div class="rounded-xl border border-slate-200 bg-white p-4">
<p class="text-2xl font-bold text-slate-900">{{ number_format($summary['unique_clicks']) }}</p>
<p class="mt-0.5 text-xs text-slate-500">Unique clicks</p>
</div>
<div class="rounded-xl border border-slate-200 bg-white p-4">
<p class="text-2xl font-bold text-slate-900">{{ number_format($summary['clicks_7d']) }}</p>
<p class="mt-0.5 text-xs text-slate-500">Last 7 days</p>
</div>
<div class="rounded-xl border border-slate-200 bg-white p-4">
<p class="text-2xl font-bold text-slate-900">{{ number_format($summary['clicks_30d']) }}</p>
<p class="mt-0.5 text-xs text-slate-500">Last 30 days</p>
</div>
</div>
<div class="rounded-xl border border-slate-200 bg-white p-6">
<h3 class="text-sm font-semibold text-slate-900">Clicks last 30 days</h3>
@php $maxDaily = max(1, $dailyClicks->max('total')); @endphp
<div class="mt-5 flex h-28 items-end gap-px">
@foreach($dailyClicks as $day)
<div class="group relative flex flex-1 flex-col items-center justify-end" title="{{ $day->date }}: {{ $day->total }}">
<div class="w-full rounded-t-sm bg-emerald-400/70 transition-colors group-hover:bg-emerald-600"
style="height: {{ max(2, ($day->total / $maxDaily) * 100) }}%"></div>
</div>
@endforeach
</div>
</div>
<div class="grid gap-4 sm:grid-cols-2 lg:grid-cols-4">
@include('links.partials.analytics-breakdown', ['title' => 'Devices', 'rows' => $devices, 'empty' => 'No clicks yet'])
@include('links.partials.analytics-breakdown', ['title' => 'Browsers', 'rows' => $browsers, 'empty' => 'No clicks yet'])
@include('links.partials.analytics-breakdown', ['title' => 'Platforms', 'rows' => $platforms, 'empty' => 'No clicks yet'])
@include('links.partials.analytics-breakdown', ['title' => 'Countries', 'rows' => $countries, 'empty' => 'No location data yet'])
</div>
<div class="grid gap-4 lg:grid-cols-2">
@include('links.partials.analytics-breakdown', ['title' => 'Top referrers', 'rows' => $referrers, 'empty' => 'No referrer data yet'])
<div class="rounded-xl border border-slate-200 bg-white">
<div class="border-b border-slate-100 px-5 py-4">
<h3 class="text-sm font-semibold text-slate-900">Recent clicks</h3>
</div>
<ul class="divide-y divide-slate-100">
@forelse($recentClicks as $click)
<li class="flex items-center justify-between gap-4 px-5 py-3 text-sm">
<div class="min-w-0">
<p class="truncate text-slate-700">{{ $click->contextLabel() }}</p>
@if ($click->is_unique)
<p class="text-xs text-emerald-600">Unique visit</p>
@endif
</div>
<time class="shrink-0 text-xs text-slate-400">{{ $click->clicked_at->diffForHumans() }}</time>
</li>
@empty
<li class="px-5 py-8 text-center text-sm text-slate-400">No clicks recorded yet.</li>
@endforelse
</ul>
</div>
</div>
</div>
</div>
</x-user-layout>