DARK MODE 

Posted on Friday, May 27, 2022 by

Create Instagram Clone with Laravel (Part 8): Creating the 'Create Post' Page for User

Sit tight everyone! As we're going to be making a lot of things!

In this post, I covered from minute 1:28:10 until 2:07:44 of this video.

Display a proper error page for non-existent profile


Let's try to open a profile of a user that doesn't exist. You will definitely encounter an error as shown above. That is not what we want. What we need to do is to properly display a 404 error for users that don't exist. So in the Profile controller, we need to use findOrFail() instead of find(). What this will do is, instead of failing ungracefully, it will actually return a proper response for the user.

10
11
12
13
14
15
16
    public function index($id)
    {
        $user = User::findOrFail($id);
        return view('home', [
            'user' => $user
        ]);
    }
app > Http > Controllers > ProfilesController.php


Refactoring profile page

Right now, the front-end view of the profile page is using the home.blade.php. We would want to change it to something that makes sense so that in the future, it's easier for you to work on your site. Let's change it.
  1. Create a new directory in resources > views directory and name it profiles
  2. Move home.blade.php into the newly created directory
  3. Rename home.blade.php to index.blade.php
  4. Inside ProfileController.php, change home to profiles/index or profiles.index so that it will return the index.blade.php view inside the profiles directory
  5. 10
    11
    12
    13
    14
    15
    16
        public function index($id)
        {
            $user = User::findOrFail($id);
            return view('profiles/index', [
                'user' => $user
            ]);
        }
    
    app > Http > Controllers > ProfilesController.php
Good. Now, the structure and the code of our site makes more sense. If you done everything correct, you should still be able to view http://127.0.0.1:8000/profile/1.

Create a new model and migration for Posts

As you already  know, in Instagram, users can create a post and upload an image to that post, that later will be displayed in their profile. So we're going to make a new model and migration to store the data for that. Here's an updated ERD I made.

In part 7, we've learned how to make a model for Profile, right? The process is the same in making the model for Post. But this time, for Post, it is a one-to-many relationship. In simpler words, one user can have many posts. Unlike profile, which one user can only have one profile.

  1. Run php artisan make:model Post -m
  2. Edit the newly created migration file to describe our database. We want to add user id, caption, and image
  3. 14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
        public function up()
        {
            Schema::create('posts', function (Blueprint $table) {
                $table->id();
                $table->foreignId('user_id');
                $table->string('caption');
                $table->string('image');
                $table->timestamps();
    
                $table->index('user_id');
            });
        }
    
    database > migrations > 2021_12_31_023812_create_posts_table.php
  4. Run php artisan migrate after saving changes to the migration file
  5. Describe the one-to-many relationship in User and Profile model
  6. 45
    46
    47
    48
    49
    50
    51
    52
    53
        public function posts()
        {
            return $this->hasMany(Post::class);
        }
    
        public function profile()
        {
            return $this->hasOne(Profile::class);
        }
    
    app > Models > User.php
     8
     9
    10
    11
    12
    13
    14
    15
    16
    class Post extends Model
    {
        use HasFactory;
    
        public function user()
        {
            return $this->belongsTo(User::class);
        }
    }
    
    app > Models > Profile.php
Please take note that in User.php, we use posts() (plural) unlike profile because of the 'has many' relationship.

Create a 'Add New Post' page - Designing the UI on profile
This is not a feature that Instagram web version has but for the sake of learning, we're going to make it. First, we create the link to add a new post on the profile as shown in photo below.



10
11
12
13
            <div class="d-flex justify-content-between align-items-baseline">
                <h2 class="font-weight-light">{{ $user->username }}</h2>
                <a href="#">Add New Post</a>
            </div>
resources > views > profiles > index.blade.php

Line 10 explanation:

d-flex To make the link show up beside the username, instead of at the bottom of it
justify-content-between To make the link float at the right side of the screen and the username remain on the left side
align-items-baseline To make the link align with the username on the baseline

Create a 'Add New Post' page - Designing the UI for 'Add New Post' page
For the new page, we are going to copy the form style from register.blade.php to save time. The design would look like this:


  1. Create a new directory in resources > views directory and name it posts
  2. Create a new blade inside the newly created directory and name it create.blade.php
  3. Copy the necessary style from resources > views > auth > register.blade.php to create.blade.php and adjust the code to make it look like the photo above
  4.  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
    32
    33
    34
    35
    36
    37
    38
    @extends('layouts.app')
    
    @section('content')
    <div class="container">
        <form action="/p" enctype="multipart/form-data" method="post">
            @csrf
    
            <div class="row">
                <div class="col-8 offset-2">
                    <!-- Title -->
                    <div class="row">
                        <h1>Add New Post</h1>
                    </div>
    
                    <!-- Post Caption -->
                    <div class="form-group row">
                        <label for="caption" class="col-md-4 col-form-label">Post Caption</label>
    
                        <input id="caption" type="text" class="form-control @error('caption') is-invalid @enderror" name="caption" value="{{ old('caption') }}" autocomplete="caption" autofocus>
                    </div>
    
                    <!-- Image -->
                    <div class="row">
                        <label for="image" class="col-md-4 col-form-label">Upload an Image</label>
    
                        <input type="file" class="form-control-file" id="image" name="image">
                    </div>
    
                    <!-- Add Button -->
                    <div class="row pt-4">
                        <button class="btn btn-primary">Add New Post</button>
                    </div>
                </div>
            </div>
    
        </form>
    </div>
    @endsection
    
    resources > views > posts > create.blade.php
Look at line 6. That one piece of code is very important when making a form in Laravel. It is for security purpose, to authenticate the form from getting submitted to an endpoint. If you didn't put it, whenever you use and submit the form, you will get a 419 Page Expired error. 

Create a 'Add New Post' page - Create Post controller and write the logic
Now, notice that in Instagram, if we access the direct link for a post, the link would look something like this: https://www.instagram.com/p/postid. We want to make exactly like this for the posts and for the page for creating the post. For the new page, we would want the url to be /p/create. Now, we have learn about RESTful resource controllers in part 6. If you look again at the table for actions handled by resource controller, the url structure look exactly like the second in the table. 

First, let's write the logic in the Post controller so it will return the form we created in create.blade.php.

 7
 8
 9
10
11
12
13
class PostsController extends Controller
{
    public function create()
    {
        return view('posts/create');
    }
}
app > http > Controllers > PostsController.php

Next, we create a new route for the create function according to the RESTful resource controller action for create.
22
23
24
Route::get('/p/create', [App\Http\Controllers\PostsController::class, 'create']);

Route::get('/profile/{user}', [App\Http\Controllers\ProfilesController::class, 'index'])->name('profile.show');
routes > web.php

Now we create the store method to store the data from the form.

 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    public function create()
    {
        return view('posts/create');
    }

    public function store()
    {
        $data = request()->validate([
            'caption' => 'required',
            'image' => 'required|image',
        ]);

        auth()->user()->posts()->create($data);

        dd(request()->all());
    }
app > http > Controllers > PostsController.php


And of course, don't forget to add a new route for store.

22
23

Route::get('/p/create', [App\Http\Controllers\PostsController::class, 'create']);
Route::post('/p', [App\Http\Controllers\PostsController::class, 'store']);

routes > web.php

Last but not least, we need to secure the create page so that only logged in user can access the page.

 9
10
11
12
13
14
15
16
17
    public function __construct()
    {
        $this->middleware('auth');
    }

    public function create()
    {
        return view('posts/create');
    }
app > http > Controllers > PostsController.php


Now everything is almost ready, we can test out the form.




I don't know why the filename, basename, pathname and all of that is saved as .tmp file, which looks very different with what the instructor have. But for now, I will leave it at that.





No comments:

Post a Comment