Appearance
User: order history
- On the Order history page, a user can view all the orders the user has ever placed
REMARKS
- The history is a static view with no interaction, so we can use a simple Blade view for this page or a Livewire component
- We will use a Livewire component for this page, so we can easily add more functionality later on if we want to
Preparation
Create a Histories component
- Create a new Livewire component with the terminal command
php artisan livewire:make User/History
- app/Livewire/User/History.php (the component class)
- resources/views/livewire/user/history.blade.php (the component view)
- Open the component class and change the layout to
layouts.vinylshop
php
class History extends Component
<?php
namespace App\Livewire\User;
use Livewire\Attributes\Layout;
use Livewire\Component;
class History extends Component
{
#[Layout('layouts.vinylshop', ['title' => 'Your order history', 'description' => 'Your order history'])]
public function render()
{
return view('livewire.user.history');
}
}
class History extends Component
<?php
namespace App\Livewire\User;
use Livewire\Attributes\Layout;
use Livewire\Component;
class History extends Component
{
#[Layout('layouts.vinylshop', ['title' => 'Your order history', 'description' => 'Your order history'])]
public function render()
{
return view('livewire.user.history');
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Add a new route
- Add a new get-route for the History to the routes/web.php file
- Update the navigation menu in resources/views/components/layouts/nav-bar.blade.php
- Add the route in the new user group
- Protected the group with the
auth
andactive
middleware, so only logged-in, active users can access this routes- The URL is user/history (prefix is already set to user)
- The view is user/history
- The route name is user.history (the group name is already set to user.)
php
Route::view('/', 'home')->name('home');
Route::get('shop', Shop::class)->name('shop');
Route::view('contact', 'contact')->name('contact');
Route::get('basket', Basket::class)->name('basket');
Route::view('playground', 'playground')->name('playground');
Route::view('under-construction', 'under-construction')->name('under-construction');
Route::get('itunes', Itunes::class)->name('itunes');
Route::middleware(['auth', 'active'])->prefix('user')->name('user.')->group(function () {
Route::redirect('/', '/user/history');
Route::get('history', History::class)->name('history');
});
...
Route::view('/', 'home')->name('home');
Route::get('shop', Shop::class)->name('shop');
Route::view('contact', 'contact')->name('contact');
Route::get('basket', Basket::class)->name('basket');
Route::view('playground', 'playground')->name('playground');
Route::view('under-construction', 'under-construction')->name('under-construction');
Route::get('itunes', Itunes::class)->name('itunes');
Route::middleware(['auth', 'active'])->prefix('user')->name('user.')->group(function () {
Route::redirect('/', '/user/history');
Route::get('history', History::class)->name('history');
});
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
Basic scaffolding for view
- Open resources/views/livewire/user/history.blade.php and replace the content with the following code:
- Add some classes to the outer
div
tag - Add a basic layout for one order
php
<div class="grid grid-cols-1 lg:grid-cols-2 2xl:grid-cols-3 gap-8 items-start">
<div class="flex flex-col bg-white border border-gray-300 shadow-md rounded-lg overflow-hidden">
<div class="p-2 bg-gray-100 border-b border-b-gray-300">
<p class="font-bold">Order date: <span class="font-normal">...</span></p>
</div>
<div class="p-2 flex-1 flex gap-4">
<img src="{{ asset('storage/covers/no-cover.png') }}" alt="" class="w-28 h-28 object-cover">
<div class="flex-1 flex flex-col">
<p class="font-medium">Artist</p>
<p class="italic">Title</p>
<p class="font-sm text-gray-400">Quantity: ...</p>
<p class="font-sm text-gray-400">Price: € ...</p>
</div>
</div>
<div class="p-2 bg-gray-100 border-t border-t-gray-300">
<p class="font-bold">Total price: <span class="font-normal"> € ...</span></p>
</div>
</div>
</div>
<div class="grid grid-cols-1 lg:grid-cols-2 2xl:grid-cols-3 gap-8 items-start">
<div class="flex flex-col bg-white border border-gray-300 shadow-md rounded-lg overflow-hidden">
<div class="p-2 bg-gray-100 border-b border-b-gray-300">
<p class="font-bold">Order date: <span class="font-normal">...</span></p>
</div>
<div class="p-2 flex-1 flex gap-4">
<img src="{{ asset('storage/covers/no-cover.png') }}" alt="" class="w-28 h-28 object-cover">
<div class="flex-1 flex flex-col">
<p class="font-medium">Artist</p>
<p class="italic">Title</p>
<p class="font-sm text-gray-400">Quantity: ...</p>
<p class="font-sm text-gray-400">Price: € ...</p>
</div>
</div>
<div class="p-2 bg-gray-100 border-t border-t-gray-300">
<p class="font-bold">Total price: <span class="font-normal"> € ...</span></p>
</div>
</div>
</div>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
Orders overview
- The only thing we need to do is get all the orders with the orderlines and send them to the view
- Line 6: get all the
orders
for the current user - Line 7: include the
orderlines
for each order - Line 8: order the
orders
by the date (the newest first) - Line 9: compact the
orders
and send them to the view
php
class History extends Component
{
#[Layout('layouts.vinylshop', ['title' => 'Your order history', 'description' => 'Your order history'])]
public function render()
{
$orders = Order::where('user_id', auth()->id())
->with('orderlines')
->orderByDesc('created_at')
->get();
return view('livewire.user.history', compact('orders'));
}
}
class History extends Component
{
#[Layout('layouts.vinylshop', ['title' => 'Your order history', 'description' => 'Your order history'])]
public function render()
{
$orders = Order::where('user_id', auth()->id())
->with('orderlines')
->orderByDesc('created_at')
->get();
return view('livewire.user.history', compact('orders'));
}
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
Get the cover image
- We don't have the cover image yet, so we need to get it from the
mb_id
of the orderline - Open the app/Models/Orderline.php model and make a new attribute
cover
and add it to the$appends
array
php
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Storage;
class Orderline extends Model
{
use HasFactory;
protected $guarded = ['id', 'created_at', 'updated_at'];
// Relationship between models
public function order() { ... }
protected function cover(): Attribute
{
return Attribute::make(
get: function ($value, $attributes) {
return Storage::disk('public')->exists('covers/' . $attributes['mb_id'] . '.jpg')
? Storage::url('covers/' . $attributes['mb_id'] . '.jpg')
: Storage::url('covers/no-cover.png');
},
);
}
protected $appends = ['cover'];
}
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Casts\Attribute;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Storage;
class Orderline extends Model
{
use HasFactory;
protected $guarded = ['id', 'created_at', 'updated_at'];
// Relationship between models
public function order() { ... }
protected function cover(): Attribute
{
return Attribute::make(
get: function ($value, $attributes) {
return Storage::disk('public')->exists('covers/' . $attributes['mb_id'] . '.jpg')
? Storage::url('covers/' . $attributes['mb_id'] . '.jpg')
: Storage::url('covers/no-cover.png');
},
);
}
protected $appends = ['cover'];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
- Replace the
img
tag in the resources/views/livewire/user/history.blade.php view with the$orderline->cover
attribute:- Replace de src attribute with
$orderline->cover
- Replace de src attribute with
php
<img src="{{ $orderline->cover }}" alt="" class="w-28 h-28 object-cover">
<img src="{{ $orderline->cover }}" alt="" class="w-28 h-28 object-cover">
1