Managing frontend user profiles from Twill

This is a simple solution to allow site administrators to manage frontend user profiles from the CMS. It can be used as a starting point to implement a user approval workflow and content access restrictions.

Objectives:

  • Add a Profiles section to the CMS for site administrators
  • Automatically create and assign Profile records to users upon registration

Requirements:

Create the profiles module

This module will be used to attach extra information to User records:

1php artisan twill:make:module Profiles

Edit the migration

Update the generated migration to add the required fields:

File:

database/migrations/2021_08_01_204153_create_profiles_tables.php

1<?php
2 
3use Illuminate\Database\Migrations\Migration;
4use Illuminate\Database\Schema\Blueprint;
5 
6return new class extends Migration
7{
8 public function up()
9 {
10 Schema::create('profiles', function (Blueprint $table) {
11 createDefaultTableFields($table);
12 
13 $table->string('name', 200)->nullable();
14 
15 $table->text('description')->nullable();
16 
17 $table->boolean('is_vip')->default(false);
18 
19 $table->foreignId('user_id')
20 ->constrained()
21 ->onUpdate('cascade')
22 ->onDelete('cascade');
23 });
24 }
25 
26 public function down()
27 {
28 Schema::dropIfExists('profiles');
29 }
30};

Then, run the migration:

1php artisan migrate

Edit the Profile model

Edit the fillable fields to match the new schema and add the Profile > User relationship:

File:

resources/views/admin/repeaters/tasks.blade.php

1<?php
2 
3namespace App\Models;
4 
5use A17\Twill\Models\Model;
6 
7class Profile extends Model
8{
9 protected $fillable = [
10 'published',
11 'name',
12 'description',
13 'is_vip',
14 ];
15 
16 public function user()
17 {
18 return $this->belongsTo(User::class);
19 }
20}

Edit the User model

Define the User > Profile relationship and use the model's booted() method to hook into the created event. When a user is created through the Laravel Breeze user registration form, automatically create and assign a Profile record.

Finally, define the name attribute accessor. This will allow existing Laravel Breeze code to access the user name from the attached profile:

File:

app/Models/User.php

1<?php
2 
3namespace App\Models;
4 
5use Illuminate\Contracts\Auth\MustVerifyEmail;...
6use Illuminate\Database\Eloquent\Factories\HasFactory;
7use Illuminate\Foundation\Auth\User as Authenticatable;
8use Illuminate\Notifications\Notifiable;
9 
10class User extends Authenticatable
11{
12 use HasFactory, Notifiable;
13 
14 protected $fillable = [...
15 'name',
16 'email',
17 'password',
18 ];
19 
20 protected $hidden = [...
21 'password',
22 'remember_token',
23 ];
24 
25 protected $casts = [...
26 'email_verified_at' => 'datetime',
27 ];
28 
29 public function profile()
30 {
31 return $this->hasOne(Profile::class);
32 }
33 
34 protected static function booted()
35 {
36 static::created(function ($user) {
37 $profile = Profile::make();
38 $profile->name = $user->name;
39 $profile->user_id = $user->id;
40 $profile->save();
41 
42 $user->name = '';
43 $user->save();
44 });
45 }
46 
47 public function getNameAttribute()
48 {
49 return $this->profile->name;
50 }
51}

Edit ProfileController

Define our custom name column to be used instead of the default title and prevent administrators from creating and deleting profiles in the CMS

File:

app/Http/Controllers/Twill/ProfileController.php

1<?php
2 
3namespace App\Http\Controllers\Admin;
4 
5use A17\Twill\Http\Controllers\Admin\ModuleController;
6 
7class ProfileController extends ModuleController
8{
9 protected $moduleName = 'profiles';
10 
11 protected $titleColumnKey = 'name';
12 
13 protected $indexOptions = [
14 'create' => false,
15 'delete' => false,
16 ];
17}

Edit the profile form

File:

resources/views/twill/profiles/form.blade.php

1@extends('twill::layouts.form')
2 
3@section('contentFields')
4 <x-twill::input
5 type="textarea"
6 name="description"
7 label="Description"
8 :maxlength="1000"
9 />
10 
11 <x-twill::checkbox
12 name="is_vip"
13 label="Can access all VIP content"
14 />
15@stop

Edit ProfileRequest

File:

app/Http/Requests/Twill/ProfileRequest.php

1<?php
2 
3namespace App\Http\Requests\Admin;
4 
5use A17\Twill\Http\Requests\Admin\Request;
6 
7class ProfileRequest extends Request
8{
9 public function rulesForCreate()...
10 {
11 return [];
12 }
13 
14 public function rulesForUpdate()
15 {
16 return [
17 'name' => 'required'
18 ];
19 }
20}

Finishing touches

Add the module to your twill-navigation.php and to your admin.php routes and you are done!

Where to go from here?

Site administrators now have access to a Profiles section in the CMS, to edit basic user information.

Within your site's views and controllers, you can access the profile information for the current user via Auth::user()->profile.

User approval & restricted content

Upon registration, a user profile is created with a draft status (ie. not published). This can be used to implement a user approval workflow:

1@if (Auth::user()->profile->published)
2{{-- Account has been approved, show the dashboard --}}
3@else
4{{-- Account has not yet been approved, show "pending approval" message --}}
5@endif

The same technique also applies for granular access control (e.g. a VIP section with additional content).

Frontend profile editing

A frontend form can be added to allow users to edit their descriptions. As always, make sure to sanitize user input before storing it in the database.

Complete user management from the CMS

For simplicity, this implementation prevents site administrators from creating and deleting users from the CMS.

A few methods from ModuleRepository can be extended in ProfileRepository to implement the feature: