Auth: convert auth views and verification email to Nova layout
This commit is contained in:
27
resources/views/forum/community/edit-post.blade.php
Normal file
27
resources/views/forum/community/edit-post.blade.php
Normal file
@@ -0,0 +1,27 @@
|
||||
@extends('layouts.nova')
|
||||
|
||||
@section('content')
|
||||
<div class="legacy-page max-w-3xl">
|
||||
<div class="mb-6">
|
||||
<a href="{{ route('forum.thread.show', ['thread' => $thread->id, 'slug' => $thread->slug]) }}" class="text-sm text-sky-300 hover:text-sky-200">← Back to thread</a>
|
||||
<h1 class="mt-2 text-2xl font-semibold text-white">Edit post</h1>
|
||||
</div>
|
||||
|
||||
<form method="POST" action="{{ route('forum.post.update', ['post' => $post->id]) }}" class="space-y-4 rounded-lg border border-white/10 bg-zinc-900/70 p-4">
|
||||
@csrf
|
||||
@method('PUT')
|
||||
|
||||
<div>
|
||||
<label for="content" class="mb-1 block text-sm font-medium text-zinc-200">Content</label>
|
||||
<textarea id="content" name="content" rows="10" required class="w-full rounded border border-white/10 bg-zinc-950 px-3 py-2 text-zinc-100">{{ old('content', $post->content) }}</textarea>
|
||||
@error('content')
|
||||
<div class="mt-1 text-xs text-red-400">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button type="submit" class="rounded bg-sky-600 px-4 py-2 text-sm font-medium text-white hover:bg-sky-500">Save changes</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@endsection
|
||||
34
resources/views/forum/community/new-thread.blade.php
Normal file
34
resources/views/forum/community/new-thread.blade.php
Normal file
@@ -0,0 +1,34 @@
|
||||
@extends('layouts.nova')
|
||||
|
||||
@section('content')
|
||||
<div class="legacy-page max-w-3xl">
|
||||
<div class="mb-6">
|
||||
<a href="{{ route('forum.category.show', ['category' => $category->slug]) }}" class="text-sm text-sky-300 hover:text-sky-200">← Back to section</a>
|
||||
<h1 class="mt-2 text-2xl font-semibold text-white">Create thread in {{ $category->name }}</h1>
|
||||
</div>
|
||||
|
||||
<form method="POST" action="{{ route('forum.thread.store', ['category' => $category->slug]) }}" class="space-y-4 rounded-lg border border-white/10 bg-zinc-900/70 p-4">
|
||||
@csrf
|
||||
|
||||
<div>
|
||||
<label for="title" class="mb-1 block text-sm font-medium text-zinc-200">Title</label>
|
||||
<input id="title" name="title" value="{{ old('title') }}" required maxlength="255" class="w-full rounded border border-white/10 bg-zinc-950 px-3 py-2 text-zinc-100" />
|
||||
@error('title')
|
||||
<div class="mt-1 text-xs text-red-400">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label for="content" class="mb-1 block text-sm font-medium text-zinc-200">Content</label>
|
||||
<textarea id="content" name="content" rows="10" required class="w-full rounded border border-white/10 bg-zinc-950 px-3 py-2 text-zinc-100">{{ old('content') }}</textarea>
|
||||
@error('content')
|
||||
<div class="mt-1 text-xs text-red-400">{{ $message }}</div>
|
||||
@enderror
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button type="submit" class="rounded bg-sky-600 px-4 py-2 text-sm font-medium text-white hover:bg-sky-500">Publish thread</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
@endsection
|
||||
74
resources/views/forum/community/topic.blade.php
Normal file
74
resources/views/forum/community/topic.blade.php
Normal file
@@ -0,0 +1,74 @@
|
||||
@extends('layouts.nova')
|
||||
|
||||
@php
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Support\Str;
|
||||
@endphp
|
||||
|
||||
@section('content')
|
||||
<div class="legacy-page">
|
||||
<div class="mb-6">
|
||||
<a href="{{ route('forum.index') }}" class="text-sm text-sky-300 hover:text-sky-200">← Back to forum</a>
|
||||
<h1 class="mt-2 text-2xl font-semibold text-white">{{ $topic->topic ?? $topic->title ?? 'Topic' }}</h1>
|
||||
@if (!empty($topic->discuss))
|
||||
<p class="mt-1 text-sm text-zinc-300">{!! Str::limit(strip_tags((string) $topic->discuss), 220) !!}</p>
|
||||
@endif
|
||||
@if (isset($category) && auth()->check())
|
||||
<div class="mt-3">
|
||||
<a href="{{ route('forum.thread.create', ['category' => $category->slug]) }}" class="rounded bg-sky-600 px-3 py-2 text-xs font-medium text-white hover:bg-sky-500">New thread</a>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<div class="overflow-hidden rounded-lg border border-white/10 bg-zinc-900/70">
|
||||
<div class="border-b border-white/10 px-4 py-3 text-sm font-semibold text-zinc-100">Threads</div>
|
||||
<div class="overflow-x-auto">
|
||||
<table class="min-w-full divide-y divide-white/10 text-sm">
|
||||
<thead class="bg-zinc-800/60 text-zinc-300">
|
||||
<tr>
|
||||
<th class="px-4 py-3 text-left font-medium">Thread</th>
|
||||
<th class="px-4 py-3 text-center font-medium">Posts</th>
|
||||
<th class="px-4 py-3 text-center font-medium">By</th>
|
||||
<th class="px-4 py-3 text-right font-medium">Last Update</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="divide-y divide-white/10 text-zinc-100">
|
||||
@forelse (($subtopics ?? []) as $sub)
|
||||
@php
|
||||
$id = (int) ($sub->topic_id ?? $sub->id ?? 0);
|
||||
$title = $sub->topic ?? $sub->title ?? 'Untitled';
|
||||
@endphp
|
||||
<tr class="hover:bg-white/5">
|
||||
<td class="px-4 py-3 align-top">
|
||||
<a class="font-medium text-sky-300 hover:text-sky-200" href="{{ route('forum.thread.show', ['thread' => $id, 'slug' => Str::slug($title)]) }}">{{ $title }}</a>
|
||||
@if (!empty($sub->discuss))
|
||||
<div class="mt-1 text-xs text-zinc-400">{!! Str::limit(strip_tags((string) $sub->discuss), 180) !!}</div>
|
||||
@endif
|
||||
</td>
|
||||
<td class="px-4 py-3 text-center text-zinc-300">{{ $sub->num_posts ?? 0 }}</td>
|
||||
<td class="px-4 py-3 text-center text-zinc-300">{{ $sub->uname ?? 'Unknown' }}</td>
|
||||
<td class="px-4 py-3 text-right text-zinc-400">
|
||||
@if (!empty($sub->last_update))
|
||||
{{ Carbon::parse($sub->last_update)->format('d.m.Y H:i') }}
|
||||
@elseif (!empty($sub->post_date))
|
||||
{{ Carbon::parse($sub->post_date)->format('d.m.Y H:i') }}
|
||||
@else
|
||||
-
|
||||
@endif
|
||||
</td>
|
||||
</tr>
|
||||
@empty
|
||||
<tr>
|
||||
<td colspan="4" class="px-4 py-6 text-center text-zinc-400">No threads in this section yet.</td>
|
||||
</tr>
|
||||
@endforelse
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (isset($subtopics) && method_exists($subtopics, 'links'))
|
||||
<div class="mt-4">{{ $subtopics->withQueryString()->links() }}</div>
|
||||
@endif
|
||||
</div>
|
||||
@endsection
|
||||
50
resources/views/forum/components/category-card.blade.php
Normal file
50
resources/views/forum/components/category-card.blade.php
Normal file
@@ -0,0 +1,50 @@
|
||||
@php
|
||||
$name = data_get($category, 'name', 'Untitled');
|
||||
$slug = data_get($category, 'slug');
|
||||
$categoryUrl = !empty($slug) ? route('forum.category.show', ['category' => $slug]) : '#';
|
||||
$threads = (int) data_get($category, 'thread_count', 0);
|
||||
$posts = (int) data_get($category, 'post_count', 0);
|
||||
$lastActivity = data_get($category, 'last_activity_at');
|
||||
$preview = data_get($category, 'preview_image', config('forum.preview_images.default'));
|
||||
@endphp
|
||||
|
||||
<a
|
||||
href="{{ $categoryUrl }}"
|
||||
aria-label="Open {{ $name }} category"
|
||||
role="listitem"
|
||||
class="group relative block overflow-hidden rounded-xl border border-white/5 bg-slate-900/80 shadow-xl backdrop-blur transition-all duration-300 hover:shadow-cyan-500/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-cyan-400 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-950"
|
||||
>
|
||||
<div class="relative aspect-[4/3] sm:aspect-[16/9]">
|
||||
<img
|
||||
src="{{ $preview }}"
|
||||
alt="{{ $name }} preview"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
class="h-full w-full object-cover object-center transition-transform duration-300 group-hover:scale-[1.02]"
|
||||
/>
|
||||
<div class="absolute inset-0 bg-gradient-to-t from-black/80 via-black/40 to-transparent"></div>
|
||||
|
||||
<div class="absolute inset-x-0 bottom-0 p-4">
|
||||
<div class="mb-2 inline-flex h-8 w-8 items-center justify-center rounded-lg bg-cyan-400/15 text-cyan-300">
|
||||
<i class="fa-solid fa-comments" aria-hidden="true"></i>
|
||||
</div>
|
||||
|
||||
<h3 class="text-lg font-semibold text-white">{{ $name }}</h3>
|
||||
<p class="mt-1 text-xs text-white/60">
|
||||
Last activity:
|
||||
@if ($lastActivity)
|
||||
<time datetime="{{ \Illuminate\Support\Carbon::parse($lastActivity)->toIso8601String() }}">
|
||||
{{ \Illuminate\Support\Carbon::parse($lastActivity)->diffForHumans() }}
|
||||
</time>
|
||||
@else
|
||||
No activity yet
|
||||
@endif
|
||||
</p>
|
||||
|
||||
<div class="mt-3 flex items-center gap-4 text-sm text-cyan-300">
|
||||
<span>{{ number_format($posts) }} posts</span>
|
||||
<span>{{ number_format($threads) }} topics</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
24
resources/views/forum/index.blade.php
Normal file
24
resources/views/forum/index.blade.php
Normal file
@@ -0,0 +1,24 @@
|
||||
@extends('layouts.nova')
|
||||
|
||||
@section('content')
|
||||
<main class="min-h-screen bg-slate-950 px-4 py-10" aria-labelledby="forum-page-title">
|
||||
<div class="mx-auto max-w-7xl">
|
||||
<header class="mb-8">
|
||||
<h1 id="forum-page-title" class="text-3xl font-semibold text-white">Forum</h1>
|
||||
<p class="mt-2 text-sm text-white/60">Browse forum sections and latest activity.</p>
|
||||
</header>
|
||||
|
||||
@if (($categories ?? collect())->isEmpty())
|
||||
<div class="rounded-xl border border-white/10 bg-slate-900/60 p-8 text-center text-white/70">
|
||||
No forum categories available yet.
|
||||
</div>
|
||||
@else
|
||||
<div class="grid grid-cols-1 gap-6 md:grid-cols-2 xl:grid-cols-3" role="list" aria-label="Forum categories">
|
||||
@foreach ($categories as $category)
|
||||
<x-forum.category-card :category="$category" />
|
||||
@endforeach
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</main>
|
||||
@endsection
|
||||
@@ -0,0 +1,84 @@
|
||||
@php
|
||||
$attachments = collect($attachments ?? []);
|
||||
$filesBaseUrl = rtrim((string) config('cdn.files_url', ''), '/');
|
||||
|
||||
$toUrl = function (?string $path) use ($filesBaseUrl): string {
|
||||
$cleanPath = ltrim((string) $path, '/');
|
||||
return $filesBaseUrl !== '' ? ($filesBaseUrl . '/' . $cleanPath) : ('/' . $cleanPath);
|
||||
};
|
||||
|
||||
$formatBytes = function ($bytes): string {
|
||||
$size = max((int) $bytes, 0);
|
||||
if ($size < 1024) {
|
||||
return $size . ' B';
|
||||
}
|
||||
|
||||
$units = ['KB', 'MB', 'GB'];
|
||||
$value = $size / 1024;
|
||||
$unitIndex = 0;
|
||||
|
||||
while ($value >= 1024 && $unitIndex < count($units) - 1) {
|
||||
$value /= 1024;
|
||||
$unitIndex++;
|
||||
}
|
||||
|
||||
return number_format($value, 1) . ' ' . $units[$unitIndex];
|
||||
};
|
||||
@endphp
|
||||
|
||||
@if ($attachments->isNotEmpty())
|
||||
<div class="mt-4 space-y-3 border-t border-white/10 pt-4">
|
||||
<h4 class="text-xs font-semibold uppercase tracking-wide text-zinc-400">Attachments</h4>
|
||||
<ul class="grid grid-cols-1 gap-3 sm:grid-cols-2">
|
||||
@foreach ($attachments as $attachment)
|
||||
@php
|
||||
$mime = (string) ($attachment->mime_type ?? '');
|
||||
$isImage = str_starts_with($mime, 'image/');
|
||||
$url = $toUrl($attachment->file_path ?? '');
|
||||
$modalId = 'attachment-modal-' . (string) data_get($attachment, 'id', uniqid());
|
||||
@endphp
|
||||
<li class="rounded-lg border border-white/10 bg-slate-900/60 p-3">
|
||||
@if ($isImage)
|
||||
<a href="#{{ $modalId }}" class="block overflow-hidden rounded-md border border-white/10">
|
||||
<img
|
||||
src="{{ $url }}"
|
||||
alt="Attachment preview"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
class="h-36 w-full object-cover"
|
||||
/>
|
||||
</a>
|
||||
@endif
|
||||
|
||||
<div class="mt-2 flex items-center justify-between gap-3 text-xs">
|
||||
<span class="truncate text-zinc-300">{{ basename((string) ($attachment->file_path ?? 'file')) }}</span>
|
||||
<span class="text-zinc-500">{{ $formatBytes($attachment->file_size ?? 0) }}</span>
|
||||
</div>
|
||||
|
||||
<a href="{{ $url }}" target="_blank" rel="noopener noreferrer" class="mt-2 inline-flex text-xs font-medium text-sky-300 hover:text-sky-200">
|
||||
Download
|
||||
</a>
|
||||
|
||||
@if ($isImage)
|
||||
<div id="{{ $modalId }}" class="pointer-events-none fixed inset-0 z-50 hidden bg-black/80 p-4 target:pointer-events-auto target:block" role="dialog" aria-label="Attachment preview">
|
||||
<div class="mx-auto flex h-full w-full max-w-5xl items-center justify-center">
|
||||
<div class="w-full overflow-hidden rounded-xl border border-white/10 bg-slate-950/95">
|
||||
<div class="flex items-center justify-between border-b border-white/10 px-4 py-2">
|
||||
<span class="truncate text-xs text-zinc-300">{{ basename((string) ($attachment->file_path ?? 'file')) }}</span>
|
||||
<a href="#" class="text-xs text-zinc-400 hover:text-zinc-200">Close</a>
|
||||
</div>
|
||||
<div class="max-h-[80vh] overflow-auto p-3">
|
||||
<img src="{{ $url }}" alt="Attachment full preview" class="mx-auto h-auto max-h-[72vh] w-auto max-w-full object-contain" />
|
||||
</div>
|
||||
<div class="border-t border-white/10 px-4 py-2 text-right">
|
||||
<a href="{{ $url }}" target="_blank" rel="noopener noreferrer" class="text-xs font-medium text-sky-300 hover:text-sky-200">Open original</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
@endif
|
||||
@@ -0,0 +1,32 @@
|
||||
@php
|
||||
$user = $user ?? null;
|
||||
$name = data_get($user, 'name', 'Anonymous');
|
||||
$avatar = data_get($user, 'profile.avatar_url') ?? \App\Support\AvatarUrl::forUser((int) data_get($user, 'id', 0));
|
||||
$role = strtolower((string) data_get($user, 'role', 'member'));
|
||||
|
||||
$roleLabel = match ($role) {
|
||||
'admin' => 'Admin',
|
||||
'moderator' => 'Moderator',
|
||||
default => 'Member',
|
||||
};
|
||||
|
||||
$roleClasses = match ($role) {
|
||||
'admin' => 'bg-red-500/15 text-red-300',
|
||||
'moderator' => 'bg-amber-500/15 text-amber-300',
|
||||
default => 'bg-sky-500/15 text-sky-300',
|
||||
};
|
||||
@endphp
|
||||
|
||||
<div class="flex items-center gap-3">
|
||||
<img
|
||||
src="{{ $avatar }}"
|
||||
alt="{{ $name }} avatar"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
class="h-10 w-10 rounded-full border border-white/10 object-cover"
|
||||
/>
|
||||
<div class="min-w-0">
|
||||
<div class="truncate text-sm font-semibold text-zinc-100">{{ $name }}</div>
|
||||
<span class="inline-flex rounded-full px-2 py-0.5 text-[11px] font-medium {{ $roleClasses }}">{{ $roleLabel }}</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,30 @@
|
||||
@php
|
||||
$thread = $thread ?? null;
|
||||
$category = $category ?? null;
|
||||
@endphp
|
||||
|
||||
<nav class="text-sm text-zinc-400" aria-label="Breadcrumb" itemscope itemtype="https://schema.org/BreadcrumbList">
|
||||
<ol class="flex flex-wrap items-center gap-2">
|
||||
<li itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
|
||||
<a itemprop="item" href="{{ url('/') }}" class="hover:text-zinc-200"><span itemprop="name">Home</span></a>
|
||||
<meta itemprop="position" content="1">
|
||||
</li>
|
||||
<li aria-hidden="true">/</li>
|
||||
<li itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
|
||||
<a itemprop="item" href="{{ route('forum.index') }}" class="hover:text-zinc-200"><span itemprop="name">Forum</span></a>
|
||||
<meta itemprop="position" content="2">
|
||||
</li>
|
||||
<li aria-hidden="true">/</li>
|
||||
<li itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
|
||||
<a itemprop="item" href="{{ isset($category) ? route('forum.category.show', ['category' => $category->slug]) : route('forum.index') }}" class="hover:text-zinc-200">
|
||||
<span itemprop="name">{{ $category->name ?? 'Category' }}</span>
|
||||
</a>
|
||||
<meta itemprop="position" content="3">
|
||||
</li>
|
||||
<li aria-hidden="true">/</li>
|
||||
<li class="text-zinc-200" itemprop="itemListElement" itemscope itemtype="https://schema.org/ListItem">
|
||||
<span itemprop="name">{{ $thread->title ?? 'Thread' }}</span>
|
||||
<meta itemprop="position" content="4">
|
||||
</li>
|
||||
</ol>
|
||||
</nav>
|
||||
71
resources/views/forum/thread/components/post-card.blade.php
Normal file
71
resources/views/forum/thread/components/post-card.blade.php
Normal file
@@ -0,0 +1,71 @@
|
||||
@php
|
||||
$post = $post ?? null;
|
||||
$thread = $thread ?? null;
|
||||
$isOp = (bool) ($isOp ?? false);
|
||||
$author = data_get($post, 'user');
|
||||
$postedAt = data_get($post, 'created_at');
|
||||
$editedAt = data_get($post, 'edited_at');
|
||||
$content = (string) data_get($post, 'content', '');
|
||||
$rendered = \App\Support\ForumPostContent::render($content);
|
||||
@endphp
|
||||
|
||||
<article class="overflow-hidden rounded-xl border border-white/5 bg-slate-900/70 backdrop-blur" id="post-{{ data_get($post, 'id') }}">
|
||||
<header class="border-b border-white/10 px-4 py-3 sm:px-5">
|
||||
<div class="flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between">
|
||||
<x-forum.thread.author-badge :user="$author" />
|
||||
<div class="text-xs text-zinc-400">
|
||||
@if ($postedAt)
|
||||
<time datetime="{{ \Illuminate\Support\Carbon::parse($postedAt)->toIso8601String() }}">
|
||||
{{ \Illuminate\Support\Carbon::parse($postedAt)->format('d.m.Y H:i') }}
|
||||
</time>
|
||||
@endif
|
||||
@if ($isOp)
|
||||
<span class="ml-2 rounded-full bg-cyan-500/15 px-2 py-0.5 text-[11px] font-medium text-cyan-300">OP</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="px-4 py-4 sm:px-5">
|
||||
<div class="prose prose-invert max-w-none text-sm leading-6 prose-pre:overflow-x-auto">
|
||||
{!! $rendered !!}
|
||||
</div>
|
||||
|
||||
@if (data_get($post, 'is_edited') && $editedAt)
|
||||
<p class="mt-3 text-xs text-zinc-500">
|
||||
Edited <time datetime="{{ \Illuminate\Support\Carbon::parse($editedAt)->toIso8601String() }}">{{ \Illuminate\Support\Carbon::parse($editedAt)->diffForHumans() }}</time>
|
||||
</p>
|
||||
@endif
|
||||
|
||||
<x-forum.thread.attachment-list :attachments="data_get($post, 'attachments', [])" />
|
||||
</div>
|
||||
|
||||
<footer class="flex flex-wrap items-center gap-3 border-t border-white/10 px-4 py-3 text-xs text-zinc-400 sm:px-5">
|
||||
<button type="button" disabled aria-disabled="true" title="Like coming soon" class="cursor-not-allowed rounded border border-white/10 px-2 py-0.5 text-zinc-500">Like</button>
|
||||
@if (!empty(data_get($thread, 'id')))
|
||||
<a href="{{ route('forum.thread.show', ['thread' => data_get($thread, 'id'), 'slug' => data_get($thread, 'slug'), 'quote' => data_get($post, 'id')]) }}#reply-content" class="hover:text-zinc-200">Quote</a>
|
||||
@else
|
||||
<a href="#post-{{ data_get($post, 'id') }}" class="hover:text-zinc-200">Quote</a>
|
||||
@endif
|
||||
@auth
|
||||
@if ((int) data_get($post, 'user_id') !== (int) auth()->id())
|
||||
<form method="POST" action="{{ route('forum.post.report', ['post' => data_get($post, 'id')]) }}" class="inline">
|
||||
@csrf
|
||||
<button type="submit" class="hover:text-zinc-200">Report</button>
|
||||
</form>
|
||||
@endif
|
||||
@else
|
||||
<a href="{{ route('login') }}" class="hover:text-zinc-200">Report</a>
|
||||
@endauth
|
||||
|
||||
@auth
|
||||
@if ((int) data_get($post, 'user_id') === (int) auth()->id() || Gate::allows('moderate-forum'))
|
||||
<a href="{{ route('forum.post.edit', ['post' => data_get($post, 'id')]) }}" class="hover:text-zinc-200">Edit</a>
|
||||
@endif
|
||||
@endauth
|
||||
|
||||
@can('moderate-forum')
|
||||
<span class="ml-auto text-amber-300">Moderation tools available</span>
|
||||
@endcan
|
||||
</footer>
|
||||
</article>
|
||||
124
resources/views/forum/thread/show.blade.php
Normal file
124
resources/views/forum/thread/show.blade.php
Normal file
@@ -0,0 +1,124 @@
|
||||
@extends('layouts.nova')
|
||||
|
||||
@section('content')
|
||||
<main class="min-h-screen bg-slate-950 px-4 py-8" aria-labelledby="thread-title">
|
||||
<div class="mx-auto max-w-5xl space-y-5">
|
||||
<x-forum.thread.breadcrumbs :thread="$thread" :category="$category" />
|
||||
|
||||
@if (session('status'))
|
||||
<div class="rounded-xl border border-emerald-500/20 bg-emerald-500/10 px-4 py-3 text-sm text-emerald-300">
|
||||
{{ session('status') }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<section class="rounded-xl border border-white/5 bg-slate-900/70 p-5 backdrop-blur">
|
||||
<div class="flex flex-col gap-4 sm:flex-row sm:items-start sm:justify-between">
|
||||
<div>
|
||||
<h1 id="thread-title" class="text-2xl font-semibold text-white">{{ $thread->title }}</h1>
|
||||
<div class="mt-2 flex flex-wrap items-center gap-2 text-xs text-zinc-400">
|
||||
<span>By {{ $author->name ?? 'Unknown' }}</span>
|
||||
<span aria-hidden="true">•</span>
|
||||
<time datetime="{{ optional($thread->created_at)?->toIso8601String() }}">{{ optional($thread->created_at)?->format('d.m.Y H:i') }}</time>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-2 text-xs">
|
||||
<span class="rounded-full bg-sky-500/15 px-2.5 py-1 text-sky-300">{{ number_format((int) ($thread->views ?? 0)) }} views</span>
|
||||
<span class="rounded-full bg-cyan-500/15 px-2.5 py-1 text-cyan-300">{{ number_format((int) ($reply_count ?? 0)) }} replies</span>
|
||||
@if ($thread->is_pinned)
|
||||
<span class="rounded-full bg-amber-500/15 px-2.5 py-1 text-amber-300">Pinned</span>
|
||||
@endif
|
||||
@if ($thread->is_locked)
|
||||
<span class="rounded-full bg-red-500/15 px-2.5 py-1 text-red-300">Locked</span>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@can('moderate-forum')
|
||||
<div class="mt-4 flex flex-wrap items-center gap-2 border-t border-white/10 pt-3 text-xs">
|
||||
@if ($thread->is_locked)
|
||||
<form method="POST" action="{{ route('forum.thread.unlock', ['thread' => $thread->id]) }}">
|
||||
@csrf
|
||||
<button type="submit" class="rounded-md bg-red-500/15 px-2.5 py-1 text-red-300 hover:bg-red-500/25">Unlock thread</button>
|
||||
</form>
|
||||
@else
|
||||
<form method="POST" action="{{ route('forum.thread.lock', ['thread' => $thread->id]) }}">
|
||||
@csrf
|
||||
<button type="submit" class="rounded-md bg-red-500/15 px-2.5 py-1 text-red-300 hover:bg-red-500/25">Lock thread</button>
|
||||
</form>
|
||||
@endif
|
||||
|
||||
@if ($thread->is_pinned)
|
||||
<form method="POST" action="{{ route('forum.thread.unpin', ['thread' => $thread->id]) }}">
|
||||
@csrf
|
||||
<button type="submit" class="rounded-md bg-amber-500/15 px-2.5 py-1 text-amber-300 hover:bg-amber-500/25">Unpin thread</button>
|
||||
</form>
|
||||
@else
|
||||
<form method="POST" action="{{ route('forum.thread.pin', ['thread' => $thread->id]) }}">
|
||||
@csrf
|
||||
<button type="submit" class="rounded-md bg-amber-500/15 px-2.5 py-1 text-amber-300 hover:bg-amber-500/25">Pin thread</button>
|
||||
</form>
|
||||
@endif
|
||||
</div>
|
||||
@endcan
|
||||
</section>
|
||||
|
||||
@if (isset($opPost) && $opPost)
|
||||
<x-forum.thread.post-card :post="$opPost" :thread="$thread" :is-op="true" />
|
||||
@endif
|
||||
|
||||
<section class="space-y-4" aria-label="Replies">
|
||||
@forelse ($posts as $post)
|
||||
<x-forum.thread.post-card :post="$post" :thread="$thread" />
|
||||
@empty
|
||||
<div class="rounded-xl border border-white/10 bg-slate-900/60 px-4 py-6 text-center text-zinc-400">
|
||||
No replies yet.
|
||||
</div>
|
||||
@endforelse
|
||||
</section>
|
||||
|
||||
@if (method_exists($posts, 'links'))
|
||||
<div class="sticky bottom-3 z-10 rounded-xl border border-white/10 bg-slate-900/80 p-2 backdrop-blur supports-[backdrop-filter]:bg-slate-900/70">
|
||||
{{ $posts->withQueryString()->links() }}
|
||||
</div>
|
||||
@endif
|
||||
|
||||
@auth
|
||||
@if (!$thread->is_locked)
|
||||
<form method="POST" action="{{ route('forum.thread.reply', ['thread' => $thread->id]) }}" class="space-y-3 rounded-xl border border-white/5 bg-slate-900/70 p-4 backdrop-blur">
|
||||
@csrf
|
||||
<div class="flex items-center justify-between">
|
||||
<label for="reply-content" class="text-sm font-medium text-zinc-200">Reply</label>
|
||||
<span class="text-xs text-zinc-500">Minimum 2 characters</span>
|
||||
</div>
|
||||
<div class="rounded-lg border border-white/10 bg-slate-950 p-2">
|
||||
<div class="mb-2 flex items-center gap-2 text-xs">
|
||||
<button type="button" class="rounded bg-slate-800 px-2 py-1 text-zinc-200" aria-pressed="true">Write</button>
|
||||
<span class="rounded bg-slate-900 px-2 py-1 text-zinc-500">Preview (coming soon)</span>
|
||||
</div>
|
||||
<textarea id="reply-content" name="content" rows="6" required minlength="2" maxlength="10000" class="w-full rounded-lg border border-white/10 bg-slate-950 px-3 py-2 text-zinc-100 placeholder-zinc-500 focus:border-cyan-400 focus:outline-none focus:ring-1 focus:ring-cyan-400">{{ $reply_prefill ?? old('content') }}</textarea>
|
||||
</div>
|
||||
@error('content')
|
||||
<p class="text-xs text-red-400">{{ $message }}</p>
|
||||
@enderror
|
||||
@if (!empty($quoted_post))
|
||||
<p class="text-xs text-cyan-300">Replying with quote from {{ data_get($quoted_post, 'user.name', 'Anonymous') }}.</p>
|
||||
@endif
|
||||
<div class="flex items-center justify-between">
|
||||
<p class="text-xs text-zinc-500">Markdown/BBCode + attachments will be enabled in next pass</p>
|
||||
<button type="submit" class="rounded-lg bg-sky-600 px-4 py-2 text-sm font-medium text-white transition hover:bg-sky-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-sky-400">Post reply</button>
|
||||
</div>
|
||||
</form>
|
||||
@else
|
||||
<div class="rounded-xl border border-red-500/20 bg-red-500/5 px-4 py-3 text-sm text-red-300">
|
||||
This thread is locked. Replies are disabled.
|
||||
</div>
|
||||
@endif
|
||||
@else
|
||||
<div class="rounded-xl border border-white/10 bg-slate-900/60 px-4 py-4 text-sm text-zinc-300">
|
||||
<a href="{{ route('login') }}" class="text-sky-300 hover:text-sky-200">Sign in</a> to post a reply.
|
||||
</div>
|
||||
@endauth
|
||||
</div>
|
||||
</main>
|
||||
@endsection
|
||||
Reference in New Issue
Block a user