feat: forum rich-text editor, emoji picker, mentions, discover nav, feed, uploads, profile
Forum: - TipTap WYSIWYG editor with full toolbar - @emoji-mart/react emoji picker (consistent with tweets) - @mention autocomplete with user search API - Fix PHP 8.4 parse errors in Blade templates - Fix thread data display (paginator items) - Align forum page widths to max-w-5xl Discover: - Extract shared _nav.blade.php partial - Add missing nav links to for-you page - Add Following link for authenticated users Feed/Posts: - Post model, controllers, policies, migrations - Feed page components (PostComposer, FeedCard, etc) - Post reactions, comments, saves, reports, sharing - Scheduled publishing support - Link preview controller Profile: - Profile page components (ProfileHero, ProfileTabs) - Profile API controller Uploads: - Upload wizard enhancements - Scheduled publish picker - Studio status bar and readiness checklist
This commit is contained in:
@@ -1,49 +1,103 @@
|
||||
@extends('layouts.nova')
|
||||
|
||||
@php($gridV2 = request()->query('grid') === 'v2')
|
||||
@php
|
||||
$galleryItems = collect($artworks->items())->map(fn ($art) => [
|
||||
'id' => $art->id ?? null,
|
||||
'name' => $art->name ?? $art->title ?? null,
|
||||
'title' => $art->title ?? $art->name ?? null,
|
||||
'thumb' => $art->thumb ?? $art->thumb_url ?? null,
|
||||
'thumb_url' => $art->thumb_url ?? $art->thumb ?? null,
|
||||
'slug' => $art->slug ?? '',
|
||||
'author' => $art->author ?? '',
|
||||
'uname' => $art->uname ?? $art->author ?? '',
|
||||
'username' => $art->username ?? '',
|
||||
'avatar_url' => $art->avatar_url ?? null,
|
||||
'category_name' => $art->category_name ?? '',
|
||||
'category_slug' => $art->category_slug ?? '',
|
||||
'width' => $art->width ?? null,
|
||||
'height' => $art->height ?? null,
|
||||
'likes' => $art->likes ?? 0,
|
||||
'comments_count' => $art->comments_count ?? 0,
|
||||
])->values();
|
||||
@endphp
|
||||
|
||||
@push('scripts')
|
||||
@vite('resources/js/entry-masonry-gallery.jsx')
|
||||
@endpush
|
||||
|
||||
@push('head')
|
||||
<style>
|
||||
.nb-hero-fade {
|
||||
background: linear-gradient(180deg, rgba(17,24,39,0) 0%, rgba(7,10,15,0.9) 60%, rgba(7,10,15,1) 100%);
|
||||
}
|
||||
.nb-hero-gradient {
|
||||
background: linear-gradient(135deg, rgba(224,122,33,0.08) 0%, rgba(15,23,36,0) 50%, rgba(21,36,58,0.4) 100%);
|
||||
animation: nb-hero-shimmer 8s ease-in-out infinite alternate;
|
||||
}
|
||||
@keyframes nb-hero-shimmer {
|
||||
0% { opacity: 0.6; }
|
||||
100% { opacity: 1; }
|
||||
}
|
||||
</style>
|
||||
@endpush
|
||||
|
||||
@section('content')
|
||||
<div class="container mx-auto py-8">
|
||||
<h1 class="text-2xl font-semibold mb-4">Favourites</h1>
|
||||
<div class="container-fluid legacy-page">
|
||||
<div class="pt-0">
|
||||
<div class="mx-auto w-full">
|
||||
<div class="relative min-h-[calc(100vh-64px)]">
|
||||
<main class="w-full">
|
||||
<div class="relative overflow-hidden nb-hero-radial">
|
||||
<div class="absolute inset-0 nb-hero-gradient" aria-hidden="true"></div>
|
||||
<div class="absolute inset-0 opacity-20 bg-[radial-gradient(ellipse_80%_60%_at_50%_-10%,#E07A2130,transparent)]" aria-hidden="true"></div>
|
||||
|
||||
<div class="mb-4 flex items-center justify-between">
|
||||
<div class="text-sm text-muted">Showing your favourites</div>
|
||||
<div>
|
||||
<form method="GET" class="inline">
|
||||
<label class="text-sm mr-2">Sort</label>
|
||||
<select name="sort" onchange="this.form.submit()" class="rounded bg-panel px-2 py-1 text-sm">
|
||||
<option value="newest" {{ ($sort ?? 'newest') === 'newest' ? 'selected' : '' }}>Newest first</option>
|
||||
<option value="oldest" {{ ($sort ?? '') === 'oldest' ? 'selected' : '' }}>Oldest first</option>
|
||||
</select>
|
||||
</form>
|
||||
<div class="relative px-6 py-10 md:px-10 md:py-14">
|
||||
<nav class="flex items-center gap-1.5 flex-wrap text-sm text-neutral-400" aria-label="Breadcrumb">
|
||||
<a class="hover:text-white transition-colors" href="/browse">Gallery</a>
|
||||
<span class="opacity-40" aria-hidden="true">›</span>
|
||||
<span class="text-white/85">Favourites</span>
|
||||
</nav>
|
||||
|
||||
<div class="mt-4 py-5">
|
||||
<h1 class="text-3xl md:text-4xl font-bold tracking-tight text-white/95 leading-tight">
|
||||
My Favourites
|
||||
</h1>
|
||||
<p class="mt-2 text-sm leading-6 text-neutral-400 max-w-xl">
|
||||
Artworks you saved, displayed in the same gallery layout as Browse.
|
||||
</p>
|
||||
@if($artworks->total() > 0)
|
||||
<div class="mt-3 flex items-center gap-1.5 text-xs text-neutral-500">
|
||||
<svg class="h-3.5 w-3.5 text-accent/70" fill="none" viewBox="0 0 24 24" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z" />
|
||||
</svg>
|
||||
<span>{{ number_format($artworks->total()) }} artworks</span>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="absolute left-0 right-0 bottom-0 h-16 nb-hero-fade pointer-events-none" aria-hidden="true"></div>
|
||||
</div>
|
||||
|
||||
<section class="px-6 pb-10 pt-8 md:px-10">
|
||||
@if($artworks->isEmpty())
|
||||
<div class="rounded-xl border border-white/10 bg-white/5 p-8 text-center text-white/60">
|
||||
You have no favourites yet.
|
||||
</div>
|
||||
@else
|
||||
<div
|
||||
data-react-masonry-gallery
|
||||
data-artworks='@json($galleryItems)'
|
||||
data-gallery-type="dashboard-favorites"
|
||||
@if($artworks->nextPageUrl()) data-next-page-url="{{ $artworks->nextPageUrl() }}" @endif
|
||||
data-limit="20"
|
||||
class="min-h-32"
|
||||
></div>
|
||||
@endif
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if($artworks->isEmpty())
|
||||
<p class="text-sm text-gray-500">You have no favourites yet.</p>
|
||||
@else
|
||||
<section data-nova-gallery data-gallery-type="dashboard-favorites">
|
||||
<div class="{{ $gridV2 ? 'gallery' : 'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-6' }}" data-gallery-grid>
|
||||
@foreach($artworks as $art)
|
||||
<div class="relative gallery-item">
|
||||
<x-artwork-card :art="$art" />
|
||||
<div class="absolute right-2 top-2 z-40">
|
||||
<form method="POST" action="{{ route('dashboard.favorites.destroy', ['artwork' => $art->id]) }}" onsubmit="return confirm('Really remove from favourites?');">
|
||||
@csrf
|
||||
@method('DELETE')
|
||||
<button type="submit" class="rounded-md border border-white/15 bg-black/60 px-2 py-1 text-xs text-red-300 hover:text-red-200">Remove</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
<div class="mt-6" data-gallery-pagination>{{ $artworks->links() }}</div>
|
||||
<div class="hidden" data-gallery-skeleton-template aria-hidden="true">
|
||||
<x-skeleton.artwork-card />
|
||||
</div>
|
||||
<div class="hidden mt-8" data-gallery-skeleton></div>
|
||||
</section>
|
||||
@endif
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
Reference in New Issue
Block a user