some progress: users slugs, bookmarks page, upvotes page, design
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class GenerateUserSlugs extends Command
|
||||
{
|
||||
/**
|
||||
* The name and signature of the console command.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $signature = 'users:generate-slugs';
|
||||
|
||||
/**
|
||||
* The console command description.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $description = 'Generate unique slugs for all users without slugs';
|
||||
|
||||
/**
|
||||
* Execute the console command.
|
||||
*/
|
||||
public function handle()
|
||||
{
|
||||
$this->info('Generating slugs for users...');
|
||||
|
||||
$usersWithoutSlugs = User::whereNull('slug')->get();
|
||||
|
||||
if ($usersWithoutSlugs->isEmpty()) {
|
||||
$this->info('All users already have slugs!');
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
|
||||
$bar = $this->output->createProgressBar($usersWithoutSlugs->count());
|
||||
$bar->start();
|
||||
|
||||
foreach ($usersWithoutSlugs as $user) {
|
||||
$user->slug = User::generateUniqueSlug($user->name);
|
||||
$user->save();
|
||||
$bar->advance();
|
||||
}
|
||||
|
||||
$bar->finish();
|
||||
$this->newLine();
|
||||
$this->info("Successfully generated slugs for {$usersWithoutSlugs->count()} users!");
|
||||
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -67,6 +67,7 @@ class RegisterController extends Controller
|
||||
'name' => $data['name'],
|
||||
'email' => $data['email'],
|
||||
'password' => Hash::make($data['password']),
|
||||
'slug' => User::generateUniqueSlug($data['name']),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,24 @@ use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class BookmarkController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the user's bookmarked products.
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
$bookmarks = $user->bookmarks()
|
||||
->with(['product.category', 'product.user'])
|
||||
->whereHas('product', function ($query) {
|
||||
$query->approved();
|
||||
})
|
||||
->latest()
|
||||
->paginate(12);
|
||||
|
||||
return view('bookmarks.index', compact('bookmarks'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle bookmark for a product.
|
||||
*/
|
||||
|
||||
@@ -73,6 +73,11 @@ class ProfileController extends Controller
|
||||
$validated['avatar'] = $avatarName;
|
||||
}
|
||||
|
||||
// Regenerate slug if name changed
|
||||
if ($request->name !== $user->name) {
|
||||
$validated['slug'] = User::generateUniqueSlug($request->name);
|
||||
}
|
||||
|
||||
// Update user profile
|
||||
$user->update($validated);
|
||||
|
||||
|
||||
@@ -10,6 +10,24 @@ use Illuminate\Support\Facades\Auth;
|
||||
|
||||
class UpvoteController extends Controller
|
||||
{
|
||||
/**
|
||||
* Display the user's upvoted products.
|
||||
*/
|
||||
public function index()
|
||||
{
|
||||
$user = Auth::user();
|
||||
|
||||
$upvotes = $user->upvotes()
|
||||
->with(['product.category', 'product.user'])
|
||||
->whereHas('product', function ($query) {
|
||||
$query->approved();
|
||||
})
|
||||
->latest()
|
||||
->paginate(12);
|
||||
|
||||
return view('upvotes.index', compact('upvotes'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle upvote for a product.
|
||||
*/
|
||||
|
||||
@@ -8,6 +8,7 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||
use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class User extends Authenticatable
|
||||
{
|
||||
@@ -23,6 +24,7 @@ class User extends Authenticatable
|
||||
'name',
|
||||
'email',
|
||||
'password',
|
||||
'slug',
|
||||
'avatar',
|
||||
'bio',
|
||||
'website',
|
||||
@@ -144,4 +146,30 @@ class User extends Authenticatable
|
||||
{
|
||||
return $this->hasMany(Bookmark::class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a unique slug for the user based on their name.
|
||||
*/
|
||||
public static function generateUniqueSlug(string $name): string
|
||||
{
|
||||
$slug = Str::slug($name);
|
||||
$originalSlug = $slug;
|
||||
$count = 1;
|
||||
|
||||
// Keep appending numbers until we find a unique slug
|
||||
while (static::where('slug', $slug)->exists()) {
|
||||
$slug = $originalSlug . '-' . $count;
|
||||
$count++;
|
||||
}
|
||||
|
||||
return $slug;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the route key for the model.
|
||||
*/
|
||||
public function getRouteKeyName(): string
|
||||
{
|
||||
return 'slug';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->string('slug')->unique()->after('name')->nullable();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('users', function (Blueprint $table) {
|
||||
$table->dropColumn('slug');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -1,7 +1,16 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
/* Dark mode configuration */
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
}
|
||||
}
|
||||
|
||||
/* Custom Styles */
|
||||
@theme {
|
||||
/* Enable class-based dark mode */
|
||||
--color-scheme: class;
|
||||
--color-primary-50: #f0f9ff;
|
||||
--color-primary-100: #e0f2fe;
|
||||
--color-primary-200: #bae6fd;
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<div class="bg-gradient-to-br from-primary-50 via-white to-accent-50 border-b border-gray-200">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12 sm:py-16">
|
||||
<div class="max-w-3xl">
|
||||
<h1 class="text-4xl sm:text-5xl font-bold text-gray-900 mb-4">
|
||||
My Bookmarks
|
||||
</h1>
|
||||
<p class="text-lg text-gray-600">
|
||||
Products you've saved for later
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
<!-- Results Summary -->
|
||||
@if($bookmarks->total() > 0)
|
||||
<div class="mb-6">
|
||||
<p class="text-sm text-gray-600">
|
||||
You have <span class="font-semibold text-gray-900">{{ $bookmarks->total() }}</span> bookmarked {{ Str::plural('product', $bookmarks->total()) }}
|
||||
</p>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Bookmarked Products Grid -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-8">
|
||||
@forelse($bookmarks as $bookmark)
|
||||
@include('components.product.card', ['product' => $bookmark->product])
|
||||
@empty
|
||||
<!-- Empty State -->
|
||||
<div class="col-span-full">
|
||||
<div class="text-center py-16">
|
||||
<svg class="mx-auto h-16 w-16 text-gray-400 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 5a2 2 0 012-2h10a2 2 0 012 2v16l-7-3.5L5 21V5z"></path>
|
||||
</svg>
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-2">No bookmarks yet</h3>
|
||||
<p class="text-gray-600 mb-6">
|
||||
Start bookmarking products to save them for later!
|
||||
</p>
|
||||
<a href="{{ route('products.index') }}" class="inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-primary-600 hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500">
|
||||
<svg class="mr-2 h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
||||
</svg>
|
||||
Browse Products
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
@if($bookmarks->hasPages())
|
||||
<div class="flex justify-center">
|
||||
{{ $bookmarks->links() }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endsection
|
||||
@@ -1,4 +1,4 @@
|
||||
<nav class="bg-white border-b border-gray-200 sticky top-0 z-50" x-data="{ mobileMenuOpen: false, userMenuOpen: false }">
|
||||
<nav class="bg-white dark:bg-gray-900 border-b border-gray-200 dark:border-gray-700 sticky top-0 z-50" x-data="{ mobileMenuOpen: false, userMenuOpen: false }">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex justify-between h-16">
|
||||
<!-- Logo and Primary Navigation -->
|
||||
@@ -6,23 +6,23 @@
|
||||
<!-- Logo -->
|
||||
<div class="flex-shrink-0 flex items-center">
|
||||
<a href="{{ route('homepage') }}" class="flex items-center space-x-2">
|
||||
<svg class="h-8 w-8 text-primary-600" fill="currentColor" viewBox="0 0 24 24">
|
||||
<svg class="h-8 w-8 text-primary-600 dark:text-primary-400" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"></path>
|
||||
</svg>
|
||||
<span class="text-xl font-bold text-gray-900">{{ config('app.name', 'BestTech') }}</span>
|
||||
<span class="text-xl font-bold text-gray-900 dark:text-white">{{ config('app.name', 'BestTech') }}</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<!-- Desktop Navigation -->
|
||||
<div class="hidden sm:ml-8 sm:flex sm:space-x-8">
|
||||
<a href="{{ route('products.index') }}"
|
||||
class="inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium {{ request()->routeIs('products.*') ? 'border-primary-500 text-gray-900' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700' }}">
|
||||
class="inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium {{ request()->routeIs('products.*') ? 'border-primary-500 text-gray-900 dark:text-white' : 'border-transparent text-gray-500 dark:text-gray-400 hover:border-gray-300 dark:hover:border-gray-600 hover:text-gray-700 dark:hover:text-gray-300' }}">
|
||||
Products
|
||||
</a>
|
||||
@auth
|
||||
@if(auth()->user()->hasAnyRole(['admin', 'moderator']))
|
||||
<a href="{{ route('products.manage') }}"
|
||||
class="inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium {{ request()->routeIs('products.manage') ? 'border-primary-500 text-gray-900' : 'border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700' }}">
|
||||
class="inline-flex items-center px-1 pt-1 border-b-2 text-sm font-medium {{ request()->routeIs('products.manage') ? 'border-primary-500 text-gray-900 dark:text-white' : 'border-transparent text-gray-500 dark:text-gray-400 hover:border-gray-300 dark:hover:border-gray-600 hover:text-gray-700 dark:hover:text-gray-300' }}">
|
||||
Manage
|
||||
</a>
|
||||
@endif
|
||||
@@ -41,7 +41,7 @@
|
||||
<input type="text"
|
||||
name="search"
|
||||
value="{{ request('search') }}"
|
||||
class="block w-full pl-10 pr-3 py-2 border border-gray-300 rounded-md leading-5 bg-white placeholder-gray-500 focus:outline-none focus:placeholder-gray-400 focus:ring-1 focus:ring-primary-500 focus:border-primary-500 sm:text-sm"
|
||||
class="block w-full pl-10 pr-3 py-2 border border-gray-300 dark:border-gray-600 rounded-md leading-5 bg-white dark:bg-gray-800 text-gray-900 dark:text-white placeholder-gray-500 dark:placeholder-gray-400 focus:outline-none focus:placeholder-gray-400 focus:ring-1 focus:ring-primary-500 focus:border-primary-500 sm:text-sm"
|
||||
placeholder="Search products...">
|
||||
</div>
|
||||
</form>
|
||||
@@ -50,8 +50,11 @@
|
||||
|
||||
<!-- Right Side -->
|
||||
<div class="hidden sm:ml-6 sm:flex sm:items-center sm:space-x-4">
|
||||
<!-- Theme Toggle -->
|
||||
@include('components.ui.theme-toggle')
|
||||
|
||||
@guest
|
||||
<a href="{{ route('login') }}" class="text-gray-700 hover:text-gray-900 px-3 py-2 rounded-md text-sm font-medium">
|
||||
<a href="{{ route('login') }}" class="text-gray-700 dark:text-gray-300 hover:text-gray-900 dark:hover:text-white px-3 py-2 rounded-md text-sm font-medium">
|
||||
Sign in
|
||||
</a>
|
||||
<a href="{{ route('register') }}" class="btn-primary">
|
||||
@@ -86,21 +89,37 @@
|
||||
x-transition:leave="transition ease-in duration-75"
|
||||
x-transition:leave-start="transform opacity-100 scale-100"
|
||||
x-transition:leave-end="transform opacity-0 scale-95"
|
||||
class="origin-top-right absolute right-0 mt-2 w-48 rounded-lg shadow-lg py-1 bg-white ring-1 ring-black ring-opacity-5"
|
||||
class="origin-top-right absolute right-0 mt-2 w-48 rounded-lg shadow-lg py-1 bg-white dark:bg-gray-800 ring-1 ring-black ring-opacity-5"
|
||||
style="display: none;">
|
||||
<div class="px-4 py-2 border-b border-gray-100">
|
||||
<p class="text-sm font-medium text-gray-900">{{ Auth::user()->name }}</p>
|
||||
<p class="text-xs text-gray-500">{{ Auth::user()->email }}</p>
|
||||
<div class="px-4 py-2 border-b border-gray-100 dark:border-gray-700">
|
||||
<p class="text-sm font-medium text-gray-900 dark:text-white">{{ Auth::user()->name }}</p>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">{{ Auth::user()->email }}</p>
|
||||
</div>
|
||||
<a href="{{ route('profile.show', Auth::user()) }}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
|
||||
<a href="{{ route('profile.show', Auth::user()) }}" class="block px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700">
|
||||
Your Profile
|
||||
</a>
|
||||
<a href="{{ route('profile.edit') }}" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
|
||||
<a href="{{ route('bookmarks.index') }}" class="block px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700">
|
||||
<div class="flex items-center">
|
||||
<svg class="mr-2 h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 5a2 2 0 012-2h10a2 2 0 012 2v16l-7-3.5L5 21V5z"></path>
|
||||
</svg>
|
||||
Bookmarks
|
||||
</div>
|
||||
</a>
|
||||
<a href="{{ route('upvotes.index') }}" class="block px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700">
|
||||
<div class="flex items-center">
|
||||
<svg class="mr-2 h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 15l7-7 7 7"></path>
|
||||
</svg>
|
||||
Upvotes
|
||||
</div>
|
||||
</a>
|
||||
<a href="{{ route('profile.edit') }}" class="block px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700">
|
||||
Settings
|
||||
</a>
|
||||
<form method="POST" action="{{ route('logout') }}">
|
||||
@csrf
|
||||
<button type="submit" class="block w-full text-left px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
|
||||
<button type="submit" class="block w-full text-left px-4 py-2 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-700">
|
||||
Sign out
|
||||
</button>
|
||||
</form>
|
||||
@@ -110,7 +129,10 @@
|
||||
</div>
|
||||
|
||||
<!-- Mobile menu button -->
|
||||
<div class="flex items-center sm:hidden">
|
||||
<div class="flex items-center sm:hidden space-x-2">
|
||||
<!-- Theme Toggle (Mobile) -->
|
||||
@include('components.ui.theme-toggle')
|
||||
|
||||
<button @click="mobileMenuOpen = !mobileMenuOpen"
|
||||
class="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100 focus:outline-none focus:ring-2 focus:ring-inset focus:ring-primary-500">
|
||||
<svg class="h-6 w-6" :class="{'hidden': mobileMenuOpen, 'block': !mobileMenuOpen }" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
@@ -168,6 +190,12 @@
|
||||
<a href="{{ route('profile.show', Auth::user()) }}" class="block px-4 py-2 text-base font-medium text-gray-500 hover:text-gray-800 hover:bg-gray-100">
|
||||
Your Profile
|
||||
</a>
|
||||
<a href="{{ route('bookmarks.index') }}" class="block px-4 py-2 text-base font-medium text-gray-500 hover:text-gray-800 hover:bg-gray-100">
|
||||
Bookmarks
|
||||
</a>
|
||||
<a href="{{ route('upvotes.index') }}" class="block px-4 py-2 text-base font-medium text-gray-500 hover:text-gray-800 hover:bg-gray-100">
|
||||
Upvotes
|
||||
</a>
|
||||
<a href="{{ route('profile.edit') }}" class="block px-4 py-2 text-base font-medium text-gray-500 hover:text-gray-800 hover:bg-gray-100">
|
||||
Settings
|
||||
</a>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
@props(['product'])
|
||||
|
||||
<div class="card group">
|
||||
<div class="bg-white dark:bg-gray-800 rounded-lg shadow-sm border border-gray-200 dark:border-gray-700 overflow-hidden hover:shadow-md transition-shadow group">
|
||||
<a href="{{ route('products.show', $product) }}" class="block">
|
||||
<!-- Thumbnail -->
|
||||
<div class="aspect-video bg-gradient-to-br from-gray-100 to-gray-200 overflow-hidden">
|
||||
<div class="aspect-video bg-gradient-to-br from-gray-100 to-gray-200 dark:from-gray-700 dark:to-gray-600 overflow-hidden">
|
||||
@if($product->thumbnail_url)
|
||||
<img src="{{ $product->thumbnail_url }}"
|
||||
alt="{{ $product->title }}"
|
||||
@@ -11,7 +11,7 @@
|
||||
loading="lazy">
|
||||
@else
|
||||
<div class="w-full h-full flex items-center justify-center">
|
||||
<svg class="h-16 w-16 text-gray-400" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<svg class="h-16 w-16 text-gray-400 dark:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" 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"></path>
|
||||
</svg>
|
||||
</div>
|
||||
@@ -23,8 +23,8 @@
|
||||
<div class="p-4">
|
||||
<div class="flex items-start justify-between mb-2">
|
||||
<div class="flex-1 min-w-0">
|
||||
<a href="{{ route('products.show', $product) }}" class="group-hover:text-primary-600 transition-colors">
|
||||
<h3 class="text-lg font-semibold text-gray-900 truncate">{{ $product->title }}</h3>
|
||||
<a href="{{ route('products.show', $product) }}" class="group-hover:text-primary-600 dark:group-hover:text-primary-400 transition-colors">
|
||||
<h3 class="text-lg font-semibold text-gray-900 dark:text-white truncate">{{ $product->title }}</h3>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -33,15 +33,15 @@
|
||||
</div>
|
||||
|
||||
<!-- Description -->
|
||||
<p class="text-sm text-gray-600 line-clamp-2 mb-3">
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400 line-clamp-2 mb-3">
|
||||
{{ Str::limit($product->description, 100) }}
|
||||
</p>
|
||||
|
||||
<!-- Meta Information -->
|
||||
<div class="flex items-center justify-between text-xs text-gray-500">
|
||||
<div class="flex items-center justify-between text-xs text-gray-500 dark:text-gray-400">
|
||||
<div class="flex items-center space-x-3">
|
||||
<!-- Category Badge -->
|
||||
<span class="badge bg-gray-100 text-gray-700">
|
||||
<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-gray-100 dark:bg-gray-700 text-gray-700 dark:text-gray-300">
|
||||
{{ $product->category->name }}
|
||||
</span>
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
</div>
|
||||
|
||||
<!-- User -->
|
||||
<a href="{{ route('profile.show', $product->user) }}" class="flex items-center space-x-1 hover:text-gray-900">
|
||||
<a href="{{ route('profile.show', $product->user) }}" class="flex items-center space-x-1 hover:text-gray-900 dark:hover:text-white">
|
||||
<div class="h-5 w-5 rounded-full bg-gradient-to-br from-primary-500 to-accent-500 flex items-center justify-center text-white text-xs font-semibold">
|
||||
{{ strtoupper(substr($product->user->name, 0, 1)) }}
|
||||
</div>
|
||||
@@ -64,9 +64,9 @@
|
||||
</div>
|
||||
|
||||
<!-- Action Buttons -->
|
||||
<div class="mt-3 pt-3 border-t border-gray-100 flex items-center justify-between">
|
||||
<div class="mt-3 pt-3 border-t border-gray-100 dark:border-gray-700 flex items-center justify-between">
|
||||
<a href="{{ route('products.show', $product) }}"
|
||||
class="text-sm font-medium text-primary-600 hover:text-primary-700 flex items-center">
|
||||
class="text-sm font-medium text-primary-600 dark:text-primary-400 hover:text-primary-700 dark:hover:text-primary-300 flex items-center">
|
||||
View details
|
||||
<svg class="h-4 w-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7"></path>
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
<button
|
||||
type="button"
|
||||
onclick="window.toggleTheme()"
|
||||
class="p-2 rounded-md text-gray-500 hover:text-gray-700 dark:text-gray-400 dark:hover:text-gray-200 hover:bg-gray-100 dark:hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500 dark:focus:ring-offset-gray-900 transition-colors"
|
||||
aria-label="Toggle theme">
|
||||
<!-- Sun icon (visible in dark mode) -->
|
||||
<svg class="h-5 w-5 hidden dark:block" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z"></path>
|
||||
</svg>
|
||||
<!-- Moon icon (visible in light mode) -->
|
||||
<svg class="h-5 w-5 block dark:hidden" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z"></path>
|
||||
</svg>
|
||||
</button>
|
||||
@@ -1,5 +1,5 @@
|
||||
<!doctype html>
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" class="h-full bg-gray-50">
|
||||
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}" class="h-full">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
@@ -9,6 +9,30 @@
|
||||
|
||||
<title>{{ $title ?? config('app.name', 'BestTech') }}</title>
|
||||
|
||||
<!-- Dark Mode Script (Must be in head to prevent flash) -->
|
||||
<script>
|
||||
// Check for saved theme preference or default to light mode
|
||||
if (localStorage.theme === 'dark' || (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||
document.documentElement.classList.add('dark');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
|
||||
// Toggle theme function
|
||||
window.toggleTheme = function() {
|
||||
const html = document.documentElement;
|
||||
const isDark = html.classList.contains('dark');
|
||||
|
||||
if (isDark) {
|
||||
html.classList.remove('dark');
|
||||
localStorage.setItem('theme', 'light');
|
||||
} else {
|
||||
html.classList.add('dark');
|
||||
localStorage.setItem('theme', 'dark');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<!-- Fonts -->
|
||||
<link rel="preconnect" href="https://fonts.bunny.net">
|
||||
<link href="https://fonts.bunny.net/css?family=inter:300,400,500,600,700,800" rel="stylesheet">
|
||||
@@ -22,7 +46,7 @@
|
||||
<!-- Additional Head Content -->
|
||||
@stack('styles')
|
||||
</head>
|
||||
<body class="h-full antialiased">
|
||||
<body class="h-full antialiased bg-white dark:bg-gray-900 text-gray-900 dark:text-gray-100">
|
||||
<div id="app" class="min-h-full flex flex-col">
|
||||
<!-- Navigation -->
|
||||
@include('components.layout.navbar')
|
||||
@@ -33,55 +57,55 @@
|
||||
</main>
|
||||
|
||||
<!-- Footer -->
|
||||
<footer class="bg-white border-t border-gray-200 mt-auto">
|
||||
<footer class="bg-white dark:bg-gray-800 border-t border-gray-200 dark:border-gray-700 mt-auto">
|
||||
<div class="max-w-7xl mx-auto py-12 px-4 sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-1 md:grid-cols-4 gap-8">
|
||||
<!-- Company Info -->
|
||||
<div class="col-span-1">
|
||||
<div class="flex items-center space-x-2 mb-4">
|
||||
<svg class="h-8 w-8 text-primary-600" fill="currentColor" viewBox="0 0 24 24">
|
||||
<svg class="h-8 w-8 text-primary-600 dark:text-primary-400" fill="currentColor" viewBox="0 0 24 24">
|
||||
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"></path>
|
||||
</svg>
|
||||
<span class="text-lg font-bold text-gray-900">{{ config('app.name', 'BestTech') }}</span>
|
||||
<span class="text-lg font-bold text-gray-900 dark:text-white">{{ config('app.name', 'BestTech') }}</span>
|
||||
</div>
|
||||
<p class="text-sm text-gray-600">
|
||||
<p class="text-sm text-gray-600 dark:text-gray-400">
|
||||
Discover the best tech products from the EU.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<!-- Product Links -->
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-gray-900 uppercase tracking-wider mb-4">Product</h3>
|
||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-white uppercase tracking-wider mb-4">Product</h3>
|
||||
<ul class="space-y-3">
|
||||
<li><a href="{{ route('products.index') }}" class="text-sm text-gray-600 hover:text-gray-900">Discover</a></li>
|
||||
<li><a href="{{ route('products.index') }}" class="text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white">Discover</a></li>
|
||||
@auth
|
||||
<li><a href="{{ route('products.create') }}" class="text-sm text-gray-600 hover:text-gray-900">Submit Product</a></li>
|
||||
<li><a href="{{ route('products.create') }}" class="text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white">Submit Product</a></li>
|
||||
@endauth
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Company Links -->
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-gray-900 uppercase tracking-wider mb-4">Company</h3>
|
||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-white uppercase tracking-wider mb-4">Company</h3>
|
||||
<ul class="space-y-3">
|
||||
<li><a href="#" class="text-sm text-gray-600 hover:text-gray-900">About</a></li>
|
||||
<li><a href="#" class="text-sm text-gray-600 hover:text-gray-900">Blog</a></li>
|
||||
<li><a href="#" class="text-sm text-gray-600 hover:text-gray-900">Contact</a></li>
|
||||
<li><a href="#" class="text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white">About</a></li>
|
||||
<li><a href="#" class="text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white">Blog</a></li>
|
||||
<li><a href="#" class="text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white">Contact</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Legal Links -->
|
||||
<div>
|
||||
<h3 class="text-sm font-semibold text-gray-900 uppercase tracking-wider mb-4">Legal</h3>
|
||||
<h3 class="text-sm font-semibold text-gray-900 dark:text-white uppercase tracking-wider mb-4">Legal</h3>
|
||||
<ul class="space-y-3">
|
||||
<li><a href="#" class="text-sm text-gray-600 hover:text-gray-900">Privacy</a></li>
|
||||
<li><a href="#" class="text-sm text-gray-600 hover:text-gray-900">Terms</a></li>
|
||||
<li><a href="#" class="text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white">Privacy</a></li>
|
||||
<li><a href="#" class="text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white">Terms</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-8 pt-8 border-t border-gray-200">
|
||||
<p class="text-sm text-gray-500 text-center">
|
||||
<div class="mt-8 pt-8 border-t border-gray-200 dark:border-gray-700">
|
||||
<p class="text-sm text-gray-500 dark:text-gray-400 text-center">
|
||||
© {{ date('Y') }} {{ config('app.name', 'BestTech') }}. All rights reserved.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('content')
|
||||
<div class="bg-gradient-to-br from-primary-50 via-white to-accent-50 border-b border-gray-200">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12 sm:py-16">
|
||||
<div class="max-w-3xl">
|
||||
<h1 class="text-4xl sm:text-5xl font-bold text-gray-900 mb-4">
|
||||
My Upvotes
|
||||
</h1>
|
||||
<p class="text-lg text-gray-600">
|
||||
Products you've upvoted and supported
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
||||
<!-- Results Summary -->
|
||||
@if($upvotes->total() > 0)
|
||||
<div class="mb-6">
|
||||
<p class="text-sm text-gray-600">
|
||||
You have upvoted <span class="font-semibold text-gray-900">{{ $upvotes->total() }}</span> {{ Str::plural('product', $upvotes->total()) }}
|
||||
</p>
|
||||
</div>
|
||||
@endif
|
||||
|
||||
<!-- Upvoted Products Grid -->
|
||||
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6 mb-8">
|
||||
@forelse($upvotes as $upvote)
|
||||
@include('components.product.card', ['product' => $upvote->product])
|
||||
@empty
|
||||
<!-- Empty State -->
|
||||
<div class="col-span-full">
|
||||
<div class="text-center py-16">
|
||||
<svg class="mx-auto h-16 w-16 text-gray-400 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 15l7-7 7 7"></path>
|
||||
</svg>
|
||||
<h3 class="text-lg font-medium text-gray-900 mb-2">No upvotes yet</h3>
|
||||
<p class="text-gray-600 mb-6">
|
||||
Start upvoting products you love to show your support!
|
||||
</p>
|
||||
<a href="{{ route('products.index') }}" class="inline-flex items-center px-4 py-2 border border-transparent shadow-sm text-sm font-medium rounded-md text-white bg-primary-600 hover:bg-primary-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-primary-500">
|
||||
<svg class="mr-2 h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
||||
</svg>
|
||||
Browse Products
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@endforelse
|
||||
</div>
|
||||
|
||||
<!-- Pagination -->
|
||||
@if($upvotes->hasPages())
|
||||
<div class="flex justify-center">
|
||||
{{ $upvotes->links() }}
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
@endsection
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
@section('content')
|
||||
<!-- Hero Section -->
|
||||
<div class="bg-gradient-to-br from-primary-50 to-white py-20">
|
||||
<div class="bg-gradient-to-br from-primary-50 to-white dark:from-gray-900 dark:to-gray-800 py-20">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="text-center">
|
||||
<h1 class="text-4xl font-extrabold text-gray-900 sm:text-5xl md:text-6xl">
|
||||
Discover the Best <span class="text-primary-600">Tech Products</span> from Europe
|
||||
<h1 class="text-4xl font-extrabold text-gray-900 dark:text-white sm:text-5xl md:text-6xl">
|
||||
Discover the Best <span class="text-primary-600 dark:text-primary-400">Tech Products</span> from Europe
|
||||
</h1>
|
||||
<p class="mt-3 max-w-md mx-auto text-base text-gray-500 sm:text-lg md:mt-5 md:text-xl md:max-w-3xl">
|
||||
<p class="mt-3 max-w-md mx-auto text-base text-gray-500 dark:text-gray-400 sm:text-lg md:mt-5 md:text-xl md:max-w-3xl">
|
||||
The premier platform for discovering innovative tech products, tools, and services built by European makers and entrepreneurs.
|
||||
</p>
|
||||
<div class="mt-5 max-w-md mx-auto sm:flex sm:justify-center md:mt-8">
|
||||
@@ -21,14 +21,14 @@
|
||||
@auth
|
||||
<div class="mt-3 rounded-md shadow sm:mt-0 sm:ml-3">
|
||||
<a href="{{ route('products.create') }}"
|
||||
class="w-full flex items-center justify-center px-8 py-3 border border-transparent text-base font-medium rounded-md text-primary-600 bg-white hover:bg-gray-50 md:py-4 md:text-lg md:px-10">
|
||||
class="w-full flex items-center justify-center px-8 py-3 border border-transparent text-base font-medium rounded-md text-primary-600 dark:text-primary-400 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 md:py-4 md:text-lg md:px-10">
|
||||
Submit Your Product
|
||||
</a>
|
||||
</div>
|
||||
@else
|
||||
<div class="mt-3 rounded-md shadow sm:mt-0 sm:ml-3">
|
||||
<a href="{{ route('register') }}"
|
||||
class="w-full flex items-center justify-center px-8 py-3 border border-transparent text-base font-medium rounded-md text-primary-600 bg-white hover:bg-gray-50 md:py-4 md:text-lg md:px-10">
|
||||
class="w-full flex items-center justify-center px-8 py-3 border border-transparent text-base font-medium rounded-md text-primary-600 dark:text-primary-400 bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-700 md:py-4 md:text-lg md:px-10">
|
||||
Get Started
|
||||
</a>
|
||||
</div>
|
||||
@@ -40,14 +40,14 @@
|
||||
|
||||
<!-- Trending Products Section -->
|
||||
@if($trendingProducts->count() > 0)
|
||||
<div class="bg-white py-16">
|
||||
<div class="bg-white dark:bg-gray-900 py-16">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex items-center justify-between mb-8">
|
||||
<div>
|
||||
<h2 class="text-3xl font-extrabold text-gray-900">
|
||||
<h2 class="text-3xl font-extrabold text-gray-900 dark:text-white">
|
||||
🔥 Trending This Week
|
||||
</h2>
|
||||
<p class="mt-2 text-lg text-gray-600">Most popular products of the last 7 days</p>
|
||||
<p class="mt-2 text-lg text-gray-600 dark:text-gray-400">Most popular products of the last 7 days</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -62,17 +62,17 @@
|
||||
|
||||
<!-- Categories Section -->
|
||||
@if($categories->count() > 0)
|
||||
<div class="bg-gray-50 py-16">
|
||||
<div class="bg-gray-50 dark:bg-gray-800 py-16">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="text-center mb-12">
|
||||
<h2 class="text-3xl font-extrabold text-gray-900">Browse by Category</h2>
|
||||
<p class="mt-2 text-lg text-gray-600">Explore products in your favorite categories</p>
|
||||
<h2 class="text-3xl font-extrabold text-gray-900 dark:text-white">Browse by Category</h2>
|
||||
<p class="mt-2 text-lg text-gray-600 dark:text-gray-400">Explore products in your favorite categories</p>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4 sm:grid-cols-3 lg:grid-cols-4">
|
||||
@foreach($categories as $category)
|
||||
<a href="{{ route('products.index', ['category' => $category->id]) }}"
|
||||
class="group relative bg-white rounded-lg border border-gray-200 hover:border-primary-500 hover:shadow-md transition-all p-6 flex flex-col items-center justify-center text-center">
|
||||
class="group relative bg-white dark:bg-gray-800 rounded-lg border border-gray-200 dark:border-gray-700 hover:border-primary-500 hover:shadow-md transition-all p-6 flex flex-col items-center justify-center text-center">
|
||||
<div class="mb-3">
|
||||
<div class="w-12 h-12 rounded-lg bg-primary-100 group-hover:bg-primary-200 flex items-center justify-center">
|
||||
<svg class="h-6 w-6 text-primary-600" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
@@ -81,13 +81,13 @@
|
||||
</div>
|
||||
</div>
|
||||
<h3 class="text-sm font-semibold text-gray-900 group-hover:text-primary-600">{{ $category->name }}</h3>
|
||||
<p class="mt-1 text-xs text-gray-500">{{ $category->products_count }} {{ Str::plural('product', $category->products_count) }}</p>
|
||||
<p class="mt-1 text-xs text-gray-500 dark:text-gray-400">{{ $category->products_count }} {{ Str::plural('product', $category->products_count) }}</p>
|
||||
</a>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
<div class="mt-10 text-center">
|
||||
<a href="{{ route('products.index') }}" class="text-primary-600 hover:text-primary-700 font-medium">
|
||||
<a href="{{ route('products.index') }}" class="text-primary-600 dark:text-primary-400 hover:text-primary-700 dark:hover:text-primary-300 font-medium">
|
||||
View all products →
|
||||
</a>
|
||||
</div>
|
||||
@@ -97,16 +97,16 @@
|
||||
|
||||
<!-- Recent Products Section -->
|
||||
@if($recentProducts->count() > 0)
|
||||
<div class="bg-white py-16">
|
||||
<div class="bg-white dark:bg-gray-900 py-16">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="flex items-center justify-between mb-8">
|
||||
<div>
|
||||
<h2 class="text-3xl font-extrabold text-gray-900">
|
||||
<h2 class="text-3xl font-extrabold text-gray-900 dark:text-white">
|
||||
✨ Recently Added
|
||||
</h2>
|
||||
<p class="mt-2 text-lg text-gray-600">Fresh products from the community</p>
|
||||
<p class="mt-2 text-lg text-gray-600 dark:text-gray-400">Fresh products from the community</p>
|
||||
</div>
|
||||
<a href="{{ route('products.index') }}" class="text-primary-600 hover:text-primary-700 font-medium hidden sm:block">
|
||||
<a href="{{ route('products.index') }}" class="text-primary-600 dark:text-primary-400 hover:text-primary-700 dark:hover:text-primary-300 font-medium hidden sm:block">
|
||||
See all →
|
||||
</a>
|
||||
</div>
|
||||
@@ -118,7 +118,7 @@
|
||||
</div>
|
||||
|
||||
<div class="mt-10 text-center sm:hidden">
|
||||
<a href="{{ route('products.index') }}" class="text-primary-600 hover:text-primary-700 font-medium">
|
||||
<a href="{{ route('products.index') }}" class="text-primary-600 dark:text-primary-400 hover:text-primary-700 dark:hover:text-primary-300 font-medium">
|
||||
See all products →
|
||||
</a>
|
||||
</div>
|
||||
@@ -160,29 +160,29 @@
|
||||
</div>
|
||||
|
||||
<!-- Social Proof / Stats Section (Optional) -->
|
||||
<div class="bg-gray-50 py-12">
|
||||
<div class="bg-gray-50 dark:bg-gray-800 py-12">
|
||||
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||
<div class="grid grid-cols-1 gap-8 sm:grid-cols-3">
|
||||
<div class="text-center">
|
||||
<div class="text-4xl font-extrabold text-primary-600">
|
||||
{{ App\Models\Product::approved()->count() }}+
|
||||
</div>
|
||||
<div class="mt-2 text-lg font-medium text-gray-900">Products Listed</div>
|
||||
<div class="mt-1 text-sm text-gray-500">Discover amazing tools</div>
|
||||
<div class="mt-2 text-lg font-medium text-gray-900 dark:text-white">Products Listed</div>
|
||||
<div class="mt-1 text-sm text-gray-500 dark:text-gray-400">Discover amazing tools</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<div class="text-4xl font-extrabold text-primary-600">
|
||||
{{ App\Models\User::count() }}+
|
||||
</div>
|
||||
<div class="mt-2 text-lg font-medium text-gray-900">Community Members</div>
|
||||
<div class="mt-1 text-sm text-gray-500">Join the community</div>
|
||||
<div class="mt-2 text-lg font-medium text-gray-900 dark:text-white">Community Members</div>
|
||||
<div class="mt-1 text-sm text-gray-500 dark:text-gray-400">Join the community</div>
|
||||
</div>
|
||||
<div class="text-center">
|
||||
<div class="text-4xl font-extrabold text-primary-600">
|
||||
{{ App\Models\Category::count() }}+
|
||||
</div>
|
||||
<div class="mt-2 text-lg font-medium text-gray-900">Categories</div>
|
||||
<div class="mt-1 text-sm text-gray-500">Find your niche</div>
|
||||
<div class="mt-2 text-lg font-medium text-gray-900 dark:text-white">Categories</div>
|
||||
<div class="mt-1 text-sm text-gray-500 dark:text-gray-400">Find your niche</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -35,6 +35,7 @@ Route::middleware(['auth'])->group(function () {
|
||||
|
||||
// Upvotes
|
||||
Route::post('/products/{product}/upvote', [UpvoteController::class, 'store'])->name('products.upvote');
|
||||
Route::get('/upvotes', [UpvoteController::class, 'index'])->name('upvotes.index');
|
||||
|
||||
// Comments
|
||||
Route::post('/products/{product}/comments', [CommentController::class, 'store'])->name('products.comments.store');
|
||||
@@ -42,6 +43,7 @@ Route::middleware(['auth'])->group(function () {
|
||||
|
||||
// Bookmarks
|
||||
Route::post('/products/{product}/bookmark', [BookmarkController::class, 'store'])->name('products.bookmark');
|
||||
Route::get('/bookmarks', [BookmarkController::class, 'index'])->name('bookmarks.index');
|
||||
});
|
||||
|
||||
// Admin/Moderator Product Management Routes
|
||||
|
||||
Reference in New Issue
Block a user