first commit

This commit is contained in:
Doar Mihai
2026-08-09 20:15:40 +03:00
commit 5301412a2a
165 changed files with 19789 additions and 0 deletions
+390
View File
@@ -0,0 +1,390 @@
<x-app-layout>
<div class="min-h-screen bg-gradient-to-br from-amber-50 via-orange-50 to-red-50 dark:from-gray-900 dark:via-gray-800 dark:to-gray-900">
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
<!-- Recipe Header -->
<div class="bg-white dark:bg-gray-800 rounded-2xl shadow-xl overflow-hidden mb-8">
<div class="md:flex">
<!-- Image -->
<div class="md:w-1/2 relative">
<div class="h-96 bg-gradient-to-br from-orange-200 to-red-200 dark:from-orange-900 dark:to-red-900">
@if($recipe->getRecipeImage())
<img src="{{ $recipe->getRecipeImage() }}" alt="{{ $recipe->title }}" class="w-full h-full object-cover">
@else
<div class="w-full h-full flex items-center justify-center text-9xl">🍳</div>
@endif
</div>
</div>
<!-- Info -->
<div class="md:w-1/2 p-8">
<div class="flex items-start justify-between mb-4">
<div class="flex-1">
<h1 class="text-4xl font-bold text-gray-900 dark:text-white mb-2">{{ $recipe->title }}</h1>
<!-- Rating & Views -->
<div class="flex items-center space-x-4 text-sm text-gray-600 dark:text-gray-400 mb-4">
@if($recipe->rating_avg)
<div class="flex items-center">
@for($i = 1; $i <= 5; $i++)
<svg class="w-5 h-5 {{ $i <= round($recipe->rating_avg) ? 'text-yellow-400' : 'text-gray-300 dark:text-gray-600' }}" fill="currentColor" viewBox="0 0 20 20">
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"></path>
</svg>
@endfor
<span class="ml-2">{{ number_format($recipe->rating_avg, 1) }} ({{ $recipe->rating_count }} {{ Str::plural('rating', $recipe->rating_count) }})</span>
</div>
@endif
<span>{{ $recipe->views_count }} views</span>
</div>
</div>
<!-- Favorite Button -->
@auth
<button
onclick="toggleFavorite({{ $recipe->id }})"
id="favorite-btn"
class="ml-4 p-3 rounded-full {{ $recipe->isFavoritedBy(auth()->user()) ? 'bg-red-100 dark:bg-red-900/30' : 'bg-gray-100 dark:bg-gray-700' }} hover:scale-110 transition-all"
>
<svg class="w-6 h-6 {{ $recipe->isFavoritedBy(auth()->user()) ? 'text-red-600 dark:text-red-400' : 'text-gray-600 dark:text-gray-400' }} fill-current" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M3.172 5.172a4 4 0 015.656 0L10 6.343l1.172-1.171a4 4 0 115.656 5.656L10 17.657l-6.828-6.829a4 4 0 010-5.656z" clip-rule="evenodd"></path>
</svg>
</button>
@endauth
</div>
<p class="text-gray-700 dark:text-gray-300 mb-6 text-lg">{{ $recipe->description }}</p>
<!-- Quick Info -->
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
@if($recipe->prep_time)
<div class="text-center p-4 bg-orange-50 dark:bg-orange-900/20 rounded-lg">
<div class="text-2xl font-bold text-orange-600 dark:text-orange-400">{{ $recipe->prep_time }}</div>
<div class="text-sm text-gray-600 dark:text-gray-400">Prep Time</div>
</div>
@endif
@if($recipe->cook_time)
<div class="text-center p-4 bg-orange-50 dark:bg-orange-900/20 rounded-lg">
<div class="text-2xl font-bold text-orange-600 dark:text-orange-400">{{ $recipe->cook_time }}</div>
<div class="text-sm text-gray-600 dark:text-gray-400">Cook Time</div>
</div>
@endif
@if($recipe->servings)
<div class="text-center p-4 bg-orange-50 dark:bg-orange-900/20 rounded-lg">
<div class="text-2xl font-bold text-orange-600 dark:text-orange-400">{{ $recipe->servings }}</div>
<div class="text-sm text-gray-600 dark:text-gray-400">Servings</div>
</div>
@endif
@if($recipe->difficulty)
<div class="text-center p-4 bg-orange-50 dark:bg-orange-900/20 rounded-lg">
<div class="text-lg font-bold text-orange-600 dark:text-orange-400">{{ $recipe->difficulty }}</div>
<div class="text-sm text-gray-600 dark:text-gray-400">Difficulty</div>
</div>
@endif
</div>
<!-- Tags -->
@if($recipe->tags->count() > 0)
<div class="flex flex-wrap gap-2">
@foreach($recipe->tags as $tag)
<span class="px-3 py-1 bg-orange-100 dark:bg-orange-900/30 text-orange-800 dark:text-orange-300 text-sm rounded-full">
{{ $tag->name }}
</span>
@endforeach
</div>
@endif
</div>
</div>
</div>
<div class="grid lg:grid-cols-3 gap-8">
<!-- Main Content -->
<div class="lg:col-span-2 space-y-8">
<!-- Ingredients -->
@if($recipe->ingredients->count() > 0)
<div class="bg-white dark:bg-gray-800 rounded-2xl shadow-lg p-8">
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-6 flex items-center">
<span class="text-3xl mr-3">🛒</span>
Ingredients
</h2>
<ul class="space-y-3">
@foreach($recipe->ingredients->sortBy('pivot.sort_order') as $ingredient)
<li class="flex items-start">
<svg class="w-5 h-5 text-orange-600 dark:text-orange-400 mr-3 mt-1 flex-shrink-0" fill="currentColor" viewBox="0 0 20 20">
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clip-rule="evenodd"></path>
</svg>
<span class="text-gray-700 dark:text-gray-300">
<strong>{{ $ingredient->pivot->quantity }} {{ $ingredient->pivot->unit }}</strong> {{ $ingredient->name }}
@if($ingredient->pivot->notes)
<span class="text-sm text-gray-500 dark:text-gray-400">({{ $ingredient->pivot->notes }})</span>
@endif
</span>
</li>
@endforeach
</ul>
</div>
@endif
<!-- Instructions -->
@if($recipe->steps->count() > 0)
<div class="bg-white dark:bg-gray-800 rounded-2xl shadow-lg p-8">
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-6 flex items-center">
<span class="text-3xl mr-3">👨‍🍳</span>
Instructions
</h2>
<ol class="space-y-6">
@foreach($recipe->steps->sortBy('sort_order') as $step)
<li class="flex">
<div class="flex-shrink-0 w-8 h-8 bg-orange-600 dark:bg-orange-500 text-white rounded-full flex items-center justify-center font-bold mr-4">
{{ $step->step_number }}
</div>
<div class="flex-1">
@if($step->title)
<h4 class="font-semibold text-gray-900 dark:text-white mb-2">{{ $step->title }}</h4>
@endif
<p class="text-gray-700 dark:text-gray-300">{{ $step->instruction }}</p>
@if($step->time_estimate)
<p class="text-sm text-gray-500 dark:text-gray-400 mt-1">⏱️ ~{{ $step->time_estimate }} minutes</p>
@endif
</div>
</li>
@endforeach
</ol>
</div>
@endif
<!-- Comments Section -->
<div class="bg-white dark:bg-gray-800 rounded-2xl shadow-lg p-8">
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-6 flex items-center">
<span class="text-3xl mr-3">💬</span>
Comments ({{ $recipe->comments->count() }})
</h2>
<!-- Add Comment Form -->
@auth
<form onsubmit="submitComment(event, {{ $recipe->id }})" class="mb-8">
<textarea
id="comment-input"
rows="3"
placeholder="Share your thoughts about this recipe..."
class="w-full rounded-lg border-gray-300 dark:border-gray-600 dark:bg-gray-700 dark:text-white focus:border-orange-500 focus:ring-orange-500"
required
></textarea>
<button
type="submit"
class="mt-2 bg-orange-600 hover:bg-orange-700 text-white font-medium py-2 px-6 rounded-lg transition duration-150"
>
Post Comment
</button>
</form>
@else
<div class="mb-8 p-4 bg-gray-100 dark:bg-gray-700 rounded-lg text-center">
<p class="text-gray-600 dark:text-gray-400">
<a href="{{ route('login') }}" class="text-orange-600 dark:text-orange-400 hover:underline">Log in</a> to leave a comment
</p>
</div>
@endauth
<!-- Comments List -->
<div id="comments-list" class="space-y-4">
@forelse($recipe->comments as $comment)
<div class="border-b border-gray-200 dark:border-gray-700 pb-4 last:border-0">
<div class="flex items-start justify-between mb-2">
<div class="flex items-center">
<div class="w-10 h-10 bg-orange-500 rounded-full flex items-center justify-center text-white font-bold mr-3">
{{ strtoupper(substr($comment->user->name, 0, 1)) }}
</div>
<div>
<div class="font-semibold text-gray-900 dark:text-white">{{ $comment->user->name }}</div>
<div class="text-sm text-gray-500 dark:text-gray-400">{{ $comment->created_at->diffForHumans() }}</div>
</div>
</div>
</div>
<p class="text-gray-700 dark:text-gray-300 ml-13">{{ $comment->comment }}</p>
</div>
@empty
<p class="text-gray-500 dark:text-gray-400 text-center py-8">No comments yet. Be the first to comment!</p>
@endforelse
</div>
</div>
</div>
<!-- Sidebar -->
<div class="lg:col-span-1 space-y-6">
<!-- Nutrition -->
@if($recipe->nutrition)
<div class="bg-white dark:bg-gray-800 rounded-2xl shadow-lg p-6">
<h3 class="text-xl font-bold text-gray-900 dark:text-white mb-4 flex items-center">
<span class="text-2xl mr-2">📊</span>
Nutrition Facts
</h3>
<div class="space-y-3">
@if($recipe->nutrition->calories)
<div class="flex justify-between items-center border-b border-gray-200 dark:border-gray-700 pb-2">
<span class="text-gray-600 dark:text-gray-400">Calories</span>
<span class="font-semibold text-gray-900 dark:text-white">{{ $recipe->nutrition->calories }}</span>
</div>
@endif
@if($recipe->nutrition->protein)
<div class="flex justify-between items-center border-b border-gray-200 dark:border-gray-700 pb-2">
<span class="text-gray-600 dark:text-gray-400">Protein</span>
<span class="font-semibold text-gray-900 dark:text-white">{{ $recipe->nutrition->protein }}g</span>
</div>
@endif
@if($recipe->nutrition->carbohydrates)
<div class="flex justify-between items-center border-b border-gray-200 dark:border-gray-700 pb-2">
<span class="text-gray-600 dark:text-gray-400">Carbs</span>
<span class="font-semibold text-gray-900 dark:text-white">{{ $recipe->nutrition->carbohydrates }}g</span>
</div>
@endif
@if($recipe->nutrition->fat)
<div class="flex justify-between items-center border-b border-gray-200 dark:border-gray-700 pb-2">
<span class="text-gray-600 dark:text-gray-400">Fat</span>
<span class="font-semibold text-gray-900 dark:text-white">{{ $recipe->nutrition->fat }}g</span>
</div>
@endif
</div>
</div>
@endif
<!-- Rate this Recipe -->
@auth
<div class="bg-white dark:bg-gray-800 rounded-2xl shadow-lg p-6">
<h3 class="text-xl font-bold text-gray-900 dark:text-white mb-4">Rate this Recipe</h3>
<div class="flex justify-center space-x-2" id="rating-stars">
@for($i = 1; $i <= 5; $i++)
<button
onclick="rateRecipe({{ $recipe->id }}, {{ $i }})"
class="text-4xl {{ ($recipe->userRating(auth()->user()) >= $i) ? 'text-yellow-400' : 'text-gray-300 dark:text-gray-600' }} hover:text-yellow-400 transition-colors"
>
★
</button>
@endfor
</div>
@if($recipe->userRating(auth()->user()))
<p class="text-sm text-center text-gray-600 dark:text-gray-400 mt-2">
You rated this {{ $recipe->userRating(auth()->user()) }} star{{ $recipe->userRating(auth()->user()) > 1 ? 's' : '' }}
</p>
@endif
</div>
@endauth
<!-- Similar Recipes -->
@if($similarRecipes->count() > 0)
<div class="bg-white dark:bg-gray-800 rounded-2xl shadow-lg p-6">
<h3 class="text-xl font-bold text-gray-900 dark:text-white mb-4">Similar Recipes</h3>
<div class="space-y-4">
@foreach($similarRecipes as $similar)
<a href="{{ route('recipes.show', $similar->slug) }}" class="block group">
<div class="flex space-x-3">
<div class="w-20 h-20 bg-gradient-to-br from-orange-200 to-red-200 dark:from-orange-900 dark:to-red-900 rounded-lg overflow-hidden flex-shrink-0">
@if($similar->getRecipeImage())
<img src="{{ $similar->getRecipeImage() }}" alt="{{ $similar->title }}" class="w-full h-full object-cover">
@else
<div class="w-full h-full flex items-center justify-center text-2xl">🍳</div>
@endif
</div>
<div class="flex-1">
<h4 class="font-semibold text-gray-900 dark:text-white group-hover:text-orange-600 dark:group-hover:text-orange-400 transition-colors line-clamp-2">
{{ $similar->title }}
</h4>
@if($similar->rating_avg)
<div class="flex items-center mt-1 text-sm text-gray-600 dark:text-gray-400">
<svg class="w-4 h-4 text-yellow-400 mr-1" fill="currentColor" viewBox="0 0 20 20">
<path d="M9.049 2.927c.3-.921 1.603-.921 1.902 0l1.07 3.292a1 1 0 00.95.69h3.462c.969 0 1.371 1.24.588 1.81l-2.8 2.034a1 1 0 00-.364 1.118l1.07 3.292c.3.921-.755 1.688-1.54 1.118l-2.8-2.034a1 1 0 00-1.175 0l-2.8 2.034c-.784.57-1.838-.197-1.539-1.118l1.07-3.292a1 1 0 00-.364-1.118L2.98 8.72c-.783-.57-.38-1.81.588-1.81h3.461a1 1 0 00.951-.69l1.07-3.292z"></path>
</svg>
{{ number_format($similar->rating_avg, 1) }}
</div>
@endif
</div>
</div>
</a>
@endforeach
</div>
</div>
@endif
</div>
</div>
</div>
</div>
@auth
<script>
function toggleFavorite(recipeId) {
fetch(`/recipes/${recipeId}/favorite`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': '{{ csrf_token() }}'
}
})
.then(response => response.json())
.then(data => {
const btn = document.getElementById('favorite-btn');
const svg = btn.querySelector('svg');
if (data.is_favorited) {
btn.classList.remove('bg-gray-100', 'dark:bg-gray-700');
btn.classList.add('bg-red-100', 'dark:bg-red-900/30');
svg.classList.remove('text-gray-600', 'dark:text-gray-400');
svg.classList.add('text-red-600', 'dark:text-red-400');
} else {
btn.classList.remove('bg-red-100', 'dark:bg-red-900/30');
btn.classList.add('bg-gray-100', 'dark:bg-gray-700');
svg.classList.remove('text-red-600', 'dark:text-red-400');
svg.classList.add('text-gray-600', 'dark:text-gray-400');
}
});
}
function rateRecipe(recipeId, rating) {
fetch(`/recipes/${recipeId}/rate`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': '{{ csrf_token() }}'
},
body: JSON.stringify({ rating })
})
.then(response => response.json())
.then(data => {
// Update star display
const stars = document.querySelectorAll('#rating-stars button');
stars.forEach((star, index) => {
if (index < rating) {
star.classList.remove('text-gray-300', 'dark:text-gray-600');
star.classList.add('text-yellow-400');
} else {
star.classList.remove('text-yellow-400');
star.classList.add('text-gray-300', 'dark:text-gray-600');
}
});
// Show success message
alert('Thank you for rating this recipe!');
});
}
function submitComment(event, recipeId) {
event.preventDefault();
const commentInput = document.getElementById('comment-input');
const comment = commentInput.value;
fetch(`/recipes/${recipeId}/comment`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': '{{ csrf_token() }}'
},
body: JSON.stringify({ comment })
})
.then(response => response.json())
.then(data => {
if (data.success) {
// Reload the page to show new comment
window.location.reload();
}
});
}
</script>
@endauth
</x-app-layout>