転職したらスマレジだった件

スマレジのエンジニアやまてのテックブログです。マジレス大歓迎です。

家族ユーザー招待機能 の詳細設計と実装(1) - はじめてのWebアプリ開発を振り返る Part5

こんにちは!

スマレジ・テックファームのWebエンジニアやまてと申します。

はじめに

今回は、Web業界実務未経験での転職活動用にポートフォリオとして作成した『はじめてのWebアプリ開発を振り返る』記事です。


※ 作成したポートフォリオは、絵本を読み聞かせしたことの記録・管理を、家族と共有できるWebアプリケーションです


qiita.com

実装したオリジナルの機能の一つである「家族ユーザー招待」機能について、記事にします。

概要と基本設計

ryamate.hatenablog.com

詳細設計と実装

(1) 家族招待メール送信フォームの作成【今回】

(2) マイグレーションファイルからのテーブル作成

(3) 家族招待メール(テキスト版)の送信処理の作成

(4) 家族招待メール(テキスト版)のテンプレートの作成

(5) ユーザー登録フォームと登録処理の作成

今回は、「詳細設計と実装」の1回目です。

目次

使用技術、サービスなど

  • フロントエンド
  • バックエンド
    • PHP 7.4.13
    • Laravel 6.20.20
    • MySQL 8.0.23
  • メール関連
    • MailHog(開発者向けのメールテストツール、開発環境)

      github.com

    • SendGrid(メール配信サービス、本番環境)

      sendgrid.kke.co.jp


1. ルーティングの追加

ルーティングを追加します。機能に必要なルーティングは、以下の 4 つです。

  • ①「家族招待メール送信フォーム画面表示」処理
  • ②「家族招待メール送信」処理
  • ③「家族招待用の登録フォーム画面表示」処理
  • ④「家族招待用のユーザー登録」処理

①「家族招待メール送信フォーム画面表示」処理 & ②「家族招待メール送信」処理

③「家族招待用の登録フォーム画面表示」処理 & ④「家族招待用のユーザー登録」処理

1-1. ルーティングの編集

以下のファイルを編集します。

  • 編集:backend/routes/web.php
<?php

// 省略

Route::prefix('register')->name('register.')->group(function () {
    Route::get('/invited/{token}', 'Auth\RegisterController@showInvitedUserRegistrationForm')->name('invited.{token}');
    Route::post('/invited', 'Auth\RegisterController@registerInvitedUser')->name('invited');
    // 省略
});

// 省略

Route::get('invite', 'InviteController@showLinkRequestForm')->name('invite')->middleware('auth');
Route::post('invite', 'InviteController@sendInviteFamilyEmail')->name('invite.email')->middleware('auth');

参考:backend/routes/web.php(全文)

<?php

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Auth::routes(['verify' => true]);
Route::prefix('login')->name('login.')->group(function () {
    Route::get('/{provider}', 'Auth\LoginController@redirectToProvider')->name('{provider}');
    Route::get('/{provider}/callback', 'Auth\LoginController@handleProviderCallback')->name('{provider}.callback');
});

Route::prefix('register')->name('register.')->group(function () {
    Route::get('/invited/{token}', 'Auth\RegisterController@showInvitedUserRegistrationForm')->name('invited.{token}');
    Route::post('/invited', 'Auth\RegisterController@registerInvitedUser')->name('invited');
    Route::get('/{provider}', 'Auth\RegisterController@showProviderUserRegistrationForm')->name('{provider}');
    Route::post('/{provider}', 'Auth\RegisterController@registerProviderUser')->name('{provider}');
});

Route::get('/email/verified', 'Auth\VerificationController@verified');

Route::get('/', 'HomeController@home')->name('home');
Route::get('/about', 'HomeController@about')->name('about');

Route::resource('/picture_books', 'PictureBookController')->except(['index', 'edit', 'destroy', 'update', 'show'])->middleware('auth');
Route::prefix('picture_books')->name('picture_books.')->group(function () {
    Route::get('/search', 'PictureBookController@search')->name('search');
    Route::get('/{picture_book}', 'PictureBookController@show')->name('show');
    Route::middleware('auth')->group(function () {
        Route::delete('/{picture_book}', 'PictureBookController@destroy')->name('destroy');
        Route::get('/{picture_book}/edit', 'PictureBookController@edit')->name('edit');
        Route::match(['put', 'patch'], '/{picture_book}', 'PictureBookController@update')->name('update');
        Route::put('/{picture_book}/like', 'PictureBookController@like')->name('like');
        Route::delete('/{picture_book}/like', 'PictureBookController@unlike')->name('unlike');
    });
});

Route::prefix('users')->name('users.')->group(function () {
    Route::get('/edit', 'UserController@edit')->name('edit');
    Route::post('/update', 'UserController@update')->name('update');
    Route::get('/{name}/setting_profile', 'UserController@showSettingProfile')->name('show_setting_profile')->middleware('auth');
    Route::get('/{name}', 'UserController@show')->name('show');
    Route::get('/{name}/likes', 'UserController@likes')->name('likes');
    Route::get('/{name}/followings', 'UserController@followings')->name('followings');
    Route::get('/{name}/followers', 'UserController@followers')->name('followers');
    Route::middleware('auth')->group(function () {
        Route::put('/{name}/follow', 'UserController@follow')->name('follow');
        Route::delete('/{name}/follow', 'UserController@unfollow')->name('unfollow');
    });
});
Route::get('/tags/{name}', 'TagController@show')->name('tags.show');

Route::prefix('families')->name('families.')->group(function () {
    Route::get('/{id}', 'FamilyController@index')->name('index');
    Route::get('/{id}/bookshelf', 'FamilyController@bookshelf')->name('bookshelf');
});

Route::get('invite', 'InviteController@showLinkRequestForm')->name('invite')->middleware('auth');
Route::post('invite', 'InviteController@sendInviteFamilyEmail')->name('invite.email')->middleware('auth');

編集内容の補足

以下は ①「家族招待メール送信フォーム画面表示」処理 についてのルーティングです。

<?php
// 省略
Route::get('invite', 'InviteController@showLinkRequestForm')->name('invite')->middleware('auth');

Route::getRoute ファサードget というメソッド)に、2つの引数を渡します。

第一引数には、文字列を渡すと URL は以下になります。

http://localhost:8880/invite

第二引数には、どのコントローラーで何のメソッドを実行するのかを文字列で渡します。コントローラー名@メソッド名という書き方です。

/invite というURLに GET リクエスト(ブラウザなどからのアクセス)があったら、InviteControllershowLinkRequestForm アクションメソッドを動かす、ということが定義されます。

以下は ②「家族招待メール送信」処理 についてです。

<?php
// 省略
Route::post('invite', 'InviteController@sendInviteFamilyEmail')->name('invite.email')->middleware('auth');

以下は ③「家族招待用の登録フォーム画面表示」処理 についてです。

<?php
// 省略
Route::prefix('register')->name('register.')->group(function () {
    Route::get('/invited/{token}', 'Auth\RegisterController@showInvitedUserRegistrationForm')->name('invited.{token}');
    // 省略
});

URL は prefix メソッド部分と合わせて以下になります。

http://localhost:8880/register/invited/{token}

以下は ④「家族招待用のユーザー登録」処理 についてです。

<?php
// 省略
Route::prefix('register')->name('register.')->group(function () {
    // 省略
    Route::post('/invited', 'Auth\RegisterController@registerInvitedUser')->name('invited');
    // 省略
});

1-2. ルーティングの確認

ルーティングを確認するため、以下コマンドを実行します。

$ php artisan route:list
  • 確認:ルーティング(抜粋)
# r_yamate @ mbp in ~/Documents/code/Yonde-app on git:doc/mod_erd x [7:21:46]
$ docker-compose exec app php artisan route:list
+--------+-----------+-----------------------------------+--------------------------------+-------------------------------------------------------------------------------+------------------------------------------------------+
| Domain | Method    | URI                               | Name                           | Action                                                                        | Middleware                                           |
+--------+-----------+-----------------------------------+--------------------------------+-------------------------------------------------------------------------------+------------------------------------------------------+
# 省略
|        | GET|HEAD  | invite                            | invite                         | App\Http\Controllers\InviteController@showLinkRequestForm                     | web,auth                                             |
|        | POST      | invite                            | invite.email                   | App\Http\Controllers\InviteController@sendInviteFamilyEmail                   | web,auth                                             |
# 省略
|        | POST      | register/invited                  | register.invited               | App\Http\Controllers\Auth\RegisterController@registerInvitedUser              | web,guest                                            |
|        | GET|HEAD  | register/invited/{token}          | register.invited.{token}       | App\Http\Controllers\Auth\RegisterController@showInvitedUserRegistrationForm  | web,guest                                            |
# 省略
+--------+-----------+-----------------------------------+--------------------------------+-------------------------------------------------------------------------------+------------------------------------------------------+

※ docker-compose コマンドで実行しています。

参考:ルーティング(全文)

# r_yamate @ mbp in ~/Documents/code/Yonde-app on git:doc/mod_erd x [7:21:46]
$ docker-compose exec app php artisan route:list
+--------+-----------+-----------------------------------+--------------------------------+-------------------------------------------------------------------------------+------------------------------------------------------+
| Domain | Method    | URI                               | Name                           | Action                                                                        | Middleware                                           |
+--------+-----------+-----------------------------------+--------------------------------+-------------------------------------------------------------------------------+------------------------------------------------------+
|        | GET|HEAD  | /                                 | home                           | App\Http\Controllers\HomeController@home                                      | web                                                  |
|        | GET|HEAD  | _debugbar/assets/javascript       | debugbar.assets.js             | Barryvdh\Debugbar\Controllers\AssetController@js                              | Barryvdh\Debugbar\Middleware\DebugbarEnabled,Closure |
|        | GET|HEAD  | _debugbar/assets/stylesheets      | debugbar.assets.css            | Barryvdh\Debugbar\Controllers\AssetController@css                             | Barryvdh\Debugbar\Middleware\DebugbarEnabled,Closure |
|        | DELETE    | _debugbar/cache/{key}/{tags?}     | debugbar.cache.delete          | Barryvdh\Debugbar\Controllers\CacheController@delete                          | Barryvdh\Debugbar\Middleware\DebugbarEnabled,Closure |
|        | GET|HEAD  | _debugbar/clockwork/{id}          | debugbar.clockwork             | Barryvdh\Debugbar\Controllers\OpenHandlerController@clockwork                 | Barryvdh\Debugbar\Middleware\DebugbarEnabled,Closure |
|        | GET|HEAD  | _debugbar/open                    | debugbar.openhandler           | Barryvdh\Debugbar\Controllers\OpenHandlerController@handle                    | Barryvdh\Debugbar\Middleware\DebugbarEnabled,Closure |
|        | GET|HEAD  | _debugbar/telescope/{id}          | debugbar.telescope             | Barryvdh\Debugbar\Controllers\TelescopeController@show                        | Barryvdh\Debugbar\Middleware\DebugbarEnabled,Closure |
|        | GET|HEAD  | about                             | about                          | App\Http\Controllers\HomeController@about                                     | web                                                  |
|        | GET|HEAD  | children/create                   | children.create                | App\Http\Controllers\ChildController@create                                   | web,auth                                             |
|        | POST      | children/store                    | children.store                 | App\Http\Controllers\ChildController@store                                    | web,auth                                             |
|        | DELETE    | children/{id}                     | children.destroy               | App\Http\Controllers\ChildController@destroy                                  | web,auth                                             |
|        | GET|HEAD  | children/{id}                     | children.show                  | App\Http\Controllers\ChildController@show                                     | web,auth                                             |
|        | GET|HEAD  | children/{id}/edit                | children.edit                  | App\Http\Controllers\ChildController@edit                                     | web,auth                                             |
|        | POST      | children/{id}/update              | children.update                | App\Http\Controllers\ChildController@update                                   | web,auth                                             |
|        | POST      | contact                           | contact.post                   | App\Http\Controllers\ContactController@post                                   | web                                                  |
|        | GET|HEAD  | contact                           | contact.show                   | App\Http\Controllers\ContactController@show                                   | web                                                  |
|        | GET|HEAD  | contact/confirm                   | contact.confirm                | App\Http\Controllers\ContactController@confirm                                | web                                                  |
|        | POST      | contact/confirm                   | contact.send                   | App\Http\Controllers\ContactController@send                                   | web                                                  |
|        | GET|HEAD  | contact/thanks                    | contact.complete               | App\Http\Controllers\ContactController@complete                               | web                                                  |
|        | POST      | email/resend                      | verification.resend            | App\Http\Controllers\Auth\VerificationController@resend                       | web,auth,throttle:6,1                                |
|        | GET|HEAD  | email/verified                    |                                | App\Http\Controllers\Auth\VerificationController@verified                     | web,auth                                             |
|        | GET|HEAD  | email/verify                      | verification.notice            | App\Http\Controllers\Auth\VerificationController@show                         | web,auth                                             |
|        | GET|HEAD  | email/verify/{id}/{hash}          | verification.verify            | App\Http\Controllers\Auth\VerificationController@verify                       | web,auth,throttle:6,1                                |
|        | GET|HEAD  | families/edit                     | families.edit                  | App\Http\Controllers\FamilyController@edit                                    | web,auth                                             |
|        | GET|HEAD  | families/setting                  | families.setting               | App\Http\Controllers\FamilyController@setting                                 | web,auth                                             |
|        | POST      | families/update                   | families.update                | App\Http\Controllers\FamilyController@update                                  | web,auth                                             |
|        | GET|HEAD  | families/{name}                   | families.index                 | App\Http\Controllers\FamilyController@index                                   | web,auth                                             |
|        | GET|HEAD  | families/{name}/bookshelf         | families.bookshelf             | App\Http\Controllers\FamilyController@bookshelf                               | web,auth                                             |
|        | GET|HEAD  | families/{name}/curious           | families.curious               | App\Http\Controllers\FamilyController@booksCurious                            | web,auth                                             |
|        | DELETE    | families/{name}/follow            | families.unfollow              | App\Http\Controllers\FamilyController@unfollow                                | web,auth                                             |
|        | PUT       | families/{name}/follow            | families.follow                | App\Http\Controllers\FamilyController@follow                                  | web,auth                                             |
|        | GET|HEAD  | families/{name}/read              | families.read                  | App\Http\Controllers\FamilyController@booksRead                               | web,auth                                             |
|        | GET|HEAD  | families/{name}/read_record       | families.read_record           | App\Http\Controllers\FamilyController@readRecord                              | web,auth                                             |
|        | GET|HEAD  | families/{name}/{picture_book}    | families.show                  | App\Http\Controllers\FamilyController@show                                    | web,auth                                             |
|        | GET|HEAD  | invite                            | invite                         | App\Http\Controllers\InviteController@showLinkRequestForm                     | web,auth                                             |
|        | POST      | invite                            | invite.email                   | App\Http\Controllers\InviteController@sendInviteFamilyEmail                   | web,auth                                             |
|        | POST      | login                             |                                | App\Http\Controllers\Auth\LoginController@login                               | web,guest                                            |
|        | GET|HEAD  | login                             | login                          | App\Http\Controllers\Auth\LoginController@showLoginForm                       | web,guest                                            |
|        | GET|HEAD  | login/guest                       | login.guest                    | App\Http\Controllers\Auth\LoginController@guestLogin                          | web,guest                                            |
|        | GET|HEAD  | login/{provider}                  | login.{provider}               | App\Http\Controllers\Auth\LoginController@redirectToProvider                  | web,guest                                            |
|        | GET|HEAD  | login/{provider}/callback         | login.{provider}.callback      | App\Http\Controllers\Auth\LoginController@handleProviderCallback              | web,guest                                            |
|        | POST      | logout                            | logout                         | App\Http\Controllers\Auth\LoginController@logout                              | web                                                  |
|        | POST      | password/confirm                  |                                | App\Http\Controllers\Auth\ConfirmPasswordController@confirm                   | web,auth                                             |
|        | GET|HEAD  | password/confirm                  | password.confirm               | App\Http\Controllers\Auth\ConfirmPasswordController@showConfirmForm           | web,auth                                             |
|        | POST      | password/email                    | password.email                 | App\Http\Controllers\Auth\ForgotPasswordController@sendResetLinkEmail         | web                                                  |
|        | POST      | password/reset                    | password.update                | App\Http\Controllers\Auth\ResetPasswordController@reset                       | web                                                  |
|        | GET|HEAD  | password/reset                    | password.request               | App\Http\Controllers\Auth\ForgotPasswordController@showLinkRequestForm        | web                                                  |
|        | GET|HEAD  | password/reset/{token}            | password.reset                 | App\Http\Controllers\Auth\ResetPasswordController@showResetForm               | web                                                  |
|        | POST      | picture_books                     | picture_books.store            | App\Http\Controllers\PictureBookController@store                              | web,auth,can:create,App\PictureBook                  |
|        | GET|HEAD  | picture_books/create              | picture_books.create           | App\Http\Controllers\PictureBookController@create                             | web,auth,can:create,App\PictureBook                  |
|        | GET|HEAD  | picture_books/search              | picture_books.search           | App\Http\Controllers\PictureBookController@search                             | web                                                  |
|        | GET|HEAD  | picture_books/search_bookshelf    | picture_books.search_bookshelf | App\Http\Controllers\PictureBookController@searchBookshelf                    | web                                                  |
|        | PUT|PATCH | picture_books/{picture_book}      | picture_books.update           | App\Http\Controllers\PictureBookController@update                             | web,auth,can:update,picture_book                     |
|        | DELETE    | picture_books/{picture_book}      | picture_books.destroy          | App\Http\Controllers\PictureBookController@destroy                            | web,auth,can:delete,picture_book                     |
|        | GET|HEAD  | picture_books/{picture_book}      | picture_books.show             | App\Http\Controllers\PictureBookController@show                               | web,can:view,picture_book                            |
|        | GET|HEAD  | picture_books/{picture_book}/edit | picture_books.edit             | App\Http\Controllers\PictureBookController@edit                               | web,auth,can:update,picture_book                     |
|        | DELETE    | picture_books/{picture_book}/like | picture_books.unlike           | App\Http\Controllers\PictureBookController@unlike                             | web,auth                                             |
|        | PUT       | picture_books/{picture_book}/like | picture_books.like             | App\Http\Controllers\PictureBookController@like                               | web,auth                                             |
|        | GET|HEAD  | privacy                           | privacy                        | App\Http\Controllers\HomeController@privacy                                   | web                                                  |
|        | GET|HEAD  | read_records                      | read_records.index             | App\Http\Controllers\ReadRecordController@index                               | web,auth                                             |
|        | POST      | read_records                      | read_records.store             | App\Http\Controllers\ReadRecordController@store                               | web,auth                                             |
|        | GET|HEAD  | read_records/create               | read_records.create            | App\Http\Controllers\ReadRecordController@create                              | web,auth                                             |
|        | DELETE    | read_records/{read_record}        | read_records.destroy           | App\Http\Controllers\ReadRecordController@destroy                             | web,auth                                             |
|        | PUT|PATCH | read_records/{read_record}        | read_records.update            | App\Http\Controllers\ReadRecordController@update                              | web,auth                                             |
|        | GET|HEAD  | read_records/{read_record}        | read_records.show              | App\Http\Controllers\ReadRecordController@show                                | web,auth                                             |
|        | GET|HEAD  | read_records/{read_record}/edit   | read_records.edit              | App\Http\Controllers\ReadRecordController@edit                                | web,auth                                             |
|        | POST      | register                          |                                | App\Http\Controllers\Auth\RegisterController@register                         | web,guest                                            |
|        | GET|HEAD  | register                          | register                       | App\Http\Controllers\Auth\RegisterController@showRegistrationForm             | web,guest                                            |
|        | POST      | register/invited                  | register.invited               | App\Http\Controllers\Auth\RegisterController@registerInvitedUser              | web,guest                                            |
|        | GET|HEAD  | register/invited/{token}          | register.invited.{token}       | App\Http\Controllers\Auth\RegisterController@showInvitedUserRegistrationForm  | web,guest                                            |
|        | POST      | register/{provider}               | register.{provider}            | App\Http\Controllers\Auth\RegisterController@registerProviderUser             | web,guest                                            |
|        | GET|HEAD  | register/{provider}               | register.{provider}            | App\Http\Controllers\Auth\RegisterController@showProviderUserRegistrationForm | web,guest                                            |
|        | GET|HEAD  | tags/{name}                       | tags.show                      | App\Http\Controllers\TagController@show                                       | web                                                  |
|        | GET|HEAD  | terms                             | terms                          | App\Http\Controllers\HomeController@terms                                     | web                                                  |
|        | GET|HEAD  | users/create_password             | users.create_password          | App\Http\Controllers\UserController@createPassword                            | web,auth                                             |
|        | POST      | users/delete_data                 | users.delete_data              | App\Http\Controllers\UserController@deleteData                                | web,auth                                             |
|        | GET|HEAD  | users/edit                        | users.edit                     | App\Http\Controllers\UserController@edit                                      | web,auth                                             |
|        | GET|HEAD  | users/edit_email                  | users.edit_email               | App\Http\Controllers\UserController@editEmail                                 | web,auth                                             |
|        | GET|HEAD  | users/edit_password               | users.edit_password            | App\Http\Controllers\UserController@editPassword                              | web,auth                                             |
|        | GET|HEAD  | users/resign                      | users.resign                   | App\Http\Controllers\UserController@resign                                    | web,auth                                             |
|        | GET|HEAD  | users/resigned                    | users.resigned                 | App\Http\Controllers\UserController@resigned                                  | web,auth                                             |
|        | GET|HEAD  | users/setting_profile             | users.setting_profile          | App\Http\Controllers\UserController@settingProfile                            | web,auth                                             |
|        | POST      | users/store_password              | users.store_password           | App\Http\Controllers\UserController@storePassword                             | web,auth                                             |
|        | POST      | users/update                      | users.update                   | App\Http\Controllers\UserController@update                                    | web,auth                                             |
|        | POST      | users/update_email                | users.update_email             | App\Http\Controllers\UserController@updateEmail                               | web,auth                                             |
|        | POST      | users/update_password             | users.update_password          | App\Http\Controllers\UserController@updatePassword                            | web,auth                                             |
|        | GET|HEAD  | users/{name}                      | users.index                    | App\Http\Controllers\UserController@index                                     | web,auth                                             |
|        | GET|HEAD  | users/{name}/followers            | users.followers                | App\Http\Controllers\UserController@followers                                 | web,auth                                             |
|        | GET|HEAD  | users/{name}/followings           | users.followings               | App\Http\Controllers\UserController@followings                                | web,auth                                             |
|        | GET|HEAD  | users/{name}/liked                | users.liked                    | App\Http\Controllers\UserController@liked                                     | web,auth                                             |
|        | GET|HEAD  | users/{name}/likes                | users.likes                    | App\Http\Controllers\UserController@likes                                     | web,auth                                             |
|        | GET|HEAD  | users/{name}/read_record          | users.read_record              | App\Http\Controllers\UserController@readRecord                                | web,auth                                             |
+--------+-----------+-----------------------------------+--------------------------------+-------------------------------------------------------------------------------+------------------------------------------------------+

①「家族招待メール送信フォーム画面表示」処理

  • 名前付きルートは invite
  • アクションメソッドは InviteControllershowLinkRequestForm

②「家族招待メール送信」処理

  • 名前付きルートは invite.email
  • アクションメソッドは InviteControllersendInviteFamilyEmail

③「家族招待用の登録フォーム画面表示」処理

  • 名前付きルートは register.invited.{token}
  • アクションメソッドは RegisterControllershowInvitedUserRegistrationForm

④「家族招待用のユーザー登録」処理

  • 名前付きルートは register.invited
  • アクションメソッドは RegisterControllerregisterInvitedUser

2. コントローラ & 各アクションメソッドの作成

2-1. コントローラの作成(InviteController

下記コマンドで InviteController を作成する。

  • 作成:app/Http/Controllers/InviteController.php
$ php artisan make:controller InviteController

2-2. アクションメソッドの作成(showLinkRequestForm メソッド)

  • 編集:app/Http/Controllers/InviteController.php
<?php

namespace App\Http\Controllers;

use Auth;
use App\Invite;
use App\Mail\BareMail;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
use Illuminate\Notifications\Notifiable;
use App\Notifications\InvitationFamilyNotification;

class InviteController extends Controller
{
    use Notifiable;

    /**
     * 家族招待メール送信フォーム
     */
    public function showLinkRequestForm()
    {
        return view('invite');
    }
}

3. 家族招待メール送信フォームの Blade 作成

「家族招待メール送信フォーム」画面の Blade を作成します。

家族招待メール送信フォーム

3-1. invite.blade.php ファイルの作成・編集

resources/views/ ディレクトリに invite.blade.php を作成して、編集します。

  • 作成・編集:resources/views/invite.blade.php
@extends('app')

@section('title', '家族招待メール送信-よんで-')

@section('content')

@include('nav')

<div class="bg-paper py-4">
    <div class="container" style="max-width: 540px">
        <h3 class="text-center">
            家族招待メール送信
        </h3>
        <p style="font-size: 14px;">
             本棚の共有するために招待したい、家族のメールアドレスを入力してください。<br>
             入力したメールアドレス宛てに、招待用のユーザー登録ページの案内をお送りします。
        </p>

        @if (Auth::id() === config('const.GUEST_USER_ID'))
        <p class="text-danger">
            ※ゲストユーザーは、家族招待メールを送信できません。
        </p>
        @endif

        <div class="card my-4 shadow-sm">
            <div class="card-body">

                @include('error_card_list')

                @if (session('status'))
                <div class="card-text alert alert-success">
                    {{ session('status') }}
                </div>
                @endif

                <form method="POST" action="{{ route('invite.email') }}">
                    @csrf
                    <div class="form-group">
                        <label for="email">メールアドレス</label>
                        <input class="form-control" type="email" id="email" name="email" required
                            placeholder="メールアドレスを入力" value="{{ old('email') }}"
                            {{ Auth::id() === config('const.GUEST_USER_ID') ? 'readonly' : '' }}>
                        <p class="text-muted small ml-1 mb-0">※招待したい家族のメールアドレスを入力してください。</p>
                        <p class="text-muted small ml-1">※メール送信後、24時間以内に登録してください。</p>
                    </div>

                    @if (Auth::id() !== config('const.GUEST_USER_ID'))
                    <button type="submit" class="btn btn-block btn-teal1 mt-4">
                        <b>送信する</b>
                    </button>
                    @endif
                    <button type="button" onClick="history.back()"
                        class="btn btn-block bg-white btn-outline-teal1 text-decoration-none text-teal1 mt-3">
                        <i class="fas fa-arrow-left mr-1"></i>戻る
                    </button>
                </form>
            </div>
        </div>
    </div>
</div>

@include('footer')

@endsection

編集内容の補足

メールアドレスを入力して、メール送信ボタンを押すと、POST送信されます。

<form method="POST" action="{{ route('invite.email') }}">

POST 送信先は、名前付きルートinvite.emailとしています。

ここに POST 送信することで、以下の InviteControllersendInviteFamilyEmail アクションメソッドが処理されます。

# r_yamate @ mbp in ~/Documents/code/Yonde-app on git:doc/mod_erd x [7:21:46]
$ docker-compose exec app php artisan route:list
+--------+-----------+-------------------------+--------------------------------+-------------------------------------------------------------------------------+------------------------------------------------------+
| Domain | Method    | URI                     | Name                           | Action                                                                        | Middleware                                           |
+--------+-----------+-------------------------+--------------------------------+-------------------------------------------------------------------------------+------------------------------------------------------+
# 省略
|        | POST      | invite                  | invite.email                   | App\Http\Controllers\InviteController@sendInviteFamilyEmail                   | web,auth                                             |
# 省略
+--------+-----------+-------------------------+--------------------------------+-------------------------------------------------------------------------------+------------------------------------------------------+

4. 家族招待ボタンの設置

「家族設定」ページから「家族招待メール送信フォーム」画面へ遷移可能にします。

4-1. 家族招待ボタンの設置

「家族設定」ページに、家族招待ボタンを2カ所設置します。

  • 編集:resources/views/families/setting.blade.php(全文)
@extends('app')

@section('title', '家族設定-よんで-')

@section('content')

@include('nav')

<header>
    <div class="bg-paper">
        <div class="container" style="max-width: 900px;">
            <nav aria-label="breadcrumb">
                <ol class="breadcrumb bg-paper small pl-0 mb-0">
                    <li class="breadcrumb-item">
                        <a href="{{ route('home') }}" class="text-teal1">
                            よんで
                        </a>
                    </li>
                    <li class="breadcrumb-item active" aria-current="page">
                        {{ $family->nickname }}ファミリーの家族設定
                    </li>
                </ol>
            </nav>
        </div>
    </div>
</header>

<div class="bg-paper py-4">
    <div class="container" style="max-width: 540px">
        <h4>家族設定</h4>
        <div class="row">
            <div class="col-sm-8">
                <h5>家族を招待しよう</h5>
                <p class="pt-2 mb-0">
                    子どもの絵本を読んだ記録を簡単に共有できます
                </p>
            </div>
            <div class="col-sm-4 my-2">
                <a class="btn btn-block bg-paper btn-outline-teal1 text-teal1" href="{{ route('invite') }}">
                    <i class="fas fa-plus mr-1"></i><b>招待する</b>
                </a>
            </div>
        </div>
        <img src="{{ asset('image/setting_family.png') }}" class="img-fluid mx-auto d-block" alt="" width="80%">
        @include('families.setting_tabs', [
        'hasUser' => false,
        'hasFamily' => true,
        ])
        <div class="card mt-2 p-4 shadow-sm">
            <div class="card-body py-2">
                <p class="card-title text-secondary small mb-1">ファミリーID</p>
                <p class="card-text">{{ $family->name }}</p>
            </div>
            <div class="card-body py-2">
                <p class="card-title text-secondary small mb-1">ファミリーネーム</p>
                <p class="card-text">{{ $family->nickname }}</p>
            </div>
            <div class="card-body pt-2">
                <p class="card-title text-secondary small mb-1">家族紹介</p>
                <p class="card-text">{!! nl2br(e($family->introduction, false)) !!}</p>
            </div>
            <a class="btn btn-block bg-white btn-outline-teal1 text-decoration-none text-teal1 mb-4"
                href="{{ route('families.edit') }}">
                家族設定を編集
            </a>

            <div class="card-body border-top">
                <p class="card-title text-secondary">家族一覧</p>
                {{-- ユーザー本人 --}}
                <div class="row">
                    <div class="col-3 d-flex justify-content-end">
                        <a href="" class="">
                            @if ($user->icon_path)
                            <img src="{{ asset($user->icon_path) }}" alt="プロフィール画像" class="bg-white border"
                                style="width: 50px; height: 50px; background-position: center center; border-radius: 50%; object-fit:cover;">
                            @else
                            <p><i class="far fa-user-circle fa-3x text-secondary"></i></p>
                            @endif
                        </a>
                    </div>
                    <div class="col-9">
                        <a href="{{ route("users.index", ["name" => Auth::user()->name]) }}"
                            class="card-text text-teal1 text-decoration-none">
                            <b>{{ $user->nickname }}</b>
                        </a>
                        <p class="card-text small text-secondary">
                            {{ $user->relation }}
                        </p>
                    </div>
                </div>

                {{-- ユーザーの家族 --}}
                @foreach ($familyUsers as $familyUser)
                <div class="row">
                    <div class="col-3 d-flex justify-content-end mt-3">
                        <a href="{{ route("users.index", ["name" => $familyUser->name]) }}">
                            @if ($familyUser->icon_path)
                            <img src="{{ asset($familyUser->icon_path) }}" alt="プロフィール画像" class="bg-white border"
                                style="width: 50px; height: 50px; background-position: center center; border-radius: 50%;object-fit:cover;">
                            @else
                            <p class="mb-0">
                                <i class="far fa-user-circle fa-3x text-secondary"></i>
                            </p>
                            @endif
                        </a>
                    </div>
                    <div class="col-9 mt-3">
                        <a href="{{ route("users.index", ["name" => $familyUser->name]) }}"
                            class="card-text text-teal1 text-decoration-none">
                            {{ $familyUser->nickname }}
                        </a>
                        <p class="card-text small text-secondary">
                            {{ $familyUser->relation }}
                        </p>
                    </div>
                </div>
                @endforeach

                <a class="btn btn-block btn-outline-paper text-decoration-none text-teal1 px-0 mt-4"
                    href="{{ route('invite') }}">
                    <i class="fas fa-plus mr-1"></i>家族を招待する
                </a>
            </div>

            <div class="card-body border-top">
                <p class="card-title text-secondary">お子さま一覧</p>
                @foreach ($children as $child)
                <div class="row">
                    <div class="col-3 d-flex justify-content-end mt-3">
                        <a href="{{ route('children.show', ['id' => $child->id]) }}">
                            @if(Carbon\Carbon::parse($child->birthday)->lte(Carbon\Carbon::now()->subYear())
                            && $child->gender_code === 2)
                            <img src="{{ asset('image/girl.png') }}" alt="プロフィール画像" class="bg-paper border"
                                style="width: 50px; height:50px;background-position: center;border-radius: 50%;object-fit:cover; margin-right:1px" />
                            @elseif(Carbon\Carbon::parse($child->birthday)->lte(Carbon\Carbon::now()->subYear()))
                            <img src="{{ asset('image/boy.png') }}" alt="プロフィール画像" class="bg-paper border"
                                style="width: 50px; height:50px;background-position: center;border-radius: 50%;object-fit:cover; margin-right:1px" />
                            @else
                            <img src="{{ asset('image/baby.png') }}" alt="プロフィール画像" class="bg-paper border"
                                style="width: 50px; height:50px;background-position: center;border-radius: 50%;object-fit:cover; margin-right:1px" />
                            @endif
                        </a>
                    </div>
                    <div class="col-9 mt-3">
                        <a href="{{ route('children.edit', ['id' => $child->id]) }}"
                            class="card-text text-teal1 text-decoration-none">
                            {{ $child->name }}
                        </a>
                        <p class="card-text d-flex flex-wrap align-items-center small text-secondary">
                            @if ($child->gender_code === 1)
                            <span class="badge badge-dark-mocha">
                                男の子
                            </span>
                            @elseif($child->gender_code === 2)
                            <span class="badge badge-mocha">
                                女の子
                            </span>
                            @endif

                            @if ($child->birthday !== null)
                            <span class="mx-1">/</span>
                            <span>
                                {{ Carbon\Carbon::parse($child->birthday)->diff(Carbon\Carbon::now())->format('%y歳%mヶ月') }}
                            </span>
                            <span class="mx-1">/</span>
                            <span>
                                {{ Carbon\Carbon::parse($child->birthday)->format("Y年m月d日") }}生まれ
                            </span>
                            @endif
                        </p>

                    </div>
                </div>
                @endforeach
                <a class="btn btn-block btn-outline-paper text-decoration-none text-teal1 px-0 mt-4"
                    href="{{ route('children.create') }}">
                    <i class="fas fa-plus mr-1"></i>お子さまを追加する
                </a>
            </div>

        </div>
    </div>
</div>

@include('footer')

@endsection

4-2. 設置した家族招待ボタンの確認

設置した画面上部と家族一覧下部のボタンについて、イメージとコードを確認します。

  • 確認:「設置したボタン(画面上部)」のイメージ

設置したボタン(画面上部)

  • 確認:resources/views/families/setting.blade.php(「設置したボタン(画面上部)」抜粋)
<h4>家族設定</h4>
<div class="row">
    <div class="col-sm-8">
        <h5>家族を招待しよう</h5>
        <p class="pt-2 mb-0">
            子どもの絵本を読んだ記録を簡単に共有できます
        </p>
    </div>
    <div class="col-sm-4 my-2">
        <a class="btn btn-block bg-paper btn-outline-teal1 text-teal1" href="{{ route('invite') }}">
            <i class="fas fa-plus mr-1"></i><b>招待する</b>
        </a>
    </div>
</div>
  • 確認:「設置したボタン(家族一覧下部)」のイメージ

設置したボタン(家族一覧下部)

  • 確認:resources/views/families/setting.blade.php(「設置したボタン(家族一覧下部)」抜粋)
<a class="btn btn-block btn-outline-paper text-decoration-none text-teal1 px-0 mt-4"
        href="{{ route('invite') }}">
    <i class="fas fa-plus mr-1"></i>家族を招待する
</a>

5. 家族招待メール送信フォームの確認

「家族設定ページ」の「+招待する」を押すと「家族招待メール送信フォーム」画面に遷移するところまで完成しました。

おわりに

今回の記事は以上です!

次回も、詳細設計と実装の続きについて書きます。

ありがとうございました。



これまでの関連記事

これまでの関連記事です。

ryamate.hatenablog.com

ryamate.hatenablog.com

ryamate.hatenablog.com



頑張ってるアピールが功を奏しすぎて、最近めっちゃ褒められる機会が多くて恐れ多い。実力が伴うようにもっと頑張らなくては…笑