Laravelの認証画面をReactで全とっかえ

やろうやろうと思いつつ中々実現できなかったヤツ
やっと大体できたので投稿
この構造がベストかと言えばそうでもないような気がするけど
やってみるといいReduxの練習になったと思う
Email Verificationと多言語はまだ未対応

追記：Email Verificationについては以下を参考にした
[Laravel：Email Verification（メールアドレスの確認）を使う](https://qiita.com/usaginooheso/items/d965f95191739f9335e8)
無駄にClass Componentになってるものはおいおい修正していきます

##準備
プロジェクトの準備とか
DBの準備とかメールサーバーとかの.envの設定は省略
React関連のパッケージのインストールとデフォルトが古いのでアップデートしておく

```terminal
$ composer create-project --prefer-dist laravel/laravel LaravelReact
$ cd LaravelReact
$ php artisan preset react
$ npm install --save-dev react react-dom connected-react-router history prop-types
$ npm install --save-dev react-bootstrap@next react-redux react-router react-router-dom
$ npm install --save-dev redux redux-persist @babel/preset-react axios bootstrap laravel-mix
```

##PHP側の追加
###ルートの追加
Auth::routes()で認証関連の処理が登録されるので
後から画面系のルートを名前付きで上書き
ワイルドカード受付は一番最後に書く

```diff:app/User.php
-use Illuminate\Contracts\Auth\MustVerifyEmail;
+use Illuminate\Contracts\Auth\MustVerifyEmail as MustVerifyEmailContract;
+use Illuminate\Auth\MustVerifyEmail;

-class User extends Authenticatable
+class User extends Authenticatable implements MustVerifyEmailContract
{
-    use Notifiable;
+    use MustVerifyEmail, Notifiable;
```

```diff:routes/web.php
Route::get('/', function(){
	return view('welcome');
});

+Auth::routes(['verify' => true]);

+Route::get('/login', 'ReactController@index')->name('login');
+Route::get('/register', 'ReactController@index')->name('register');
+Route::get('password/reset', 'ReactController@index')->name('password.request');
+Route::get('password/reset/{token}', 'ReactController@index')->name('password.reset');
+Route::get('email/verify', 'ReactController@index')->name('verification.notice');

+Route::post('/session', 'ReactController@session')->name('session');
+Route::get('/{router}', 'ReactController@index')->name('home');
```

###コントローラーの追加
画面表示用のindexとログインセッション判定用のsessionを定義

```php:app/Http/Controllers/ReactController.php
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
class ReactController extends Controller
{
	public function index(Request $request)
	{
		return view('react');
	}

	public function session(Request $request)
	{
		return $request->user();
	}
}
```
###テンプレートの追加
Laravelのステージ情報をlaravelStatus、エラー情報をlaravelErrorsとしてグローバル定義

```html:resources/views/react.blade.php
<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- CSRF Token -->
    <meta name="csrf-token" content="{{ csrf_token() }}">

    <title>{{ config('app.name', 'Laravel') }}</title>

    <!-- Fonts -->
    <link rel="dns-prefetch" href="//fonts.gstatic.com">
    <link href="https://fonts.googleapis.com/css?family=Nunito" rel="stylesheet" type="text/css">

    <!-- Styles -->
    <link href="{{ asset('css/app.css') }}" rel="stylesheet">
</head>
<body>
	<div id="react-root"></div>
	<script>
	var laravelSession = {};
	laravelSession['status']=@if(session('status'))'{{session('status')}}'@else''@endif;
	laravelSession['resent']=@if(session('resent'))'{{session('resent')}}'@else''@endif;
	var laravelErrors=@php print(htmlspecialchars_decode($errors))@endphp;
	</script>
    <!-- Scripts -->
	<script src="{{ asset('js/app.js') }}" defer></script>
</body>
</html>
```

##JS側の追加
###構成概要
app.js
bootstrap.js
ReactRoot.js
stores
┗
antions
┗
reducers
┗
routes
┗
components
┗Menus
┗Forms
┗Elements

###外枠の定義
以前の記事[ReactのClass ComponentでReact-Reduxに繋ぐ](https://qiita.com/shikigamix/items/321d5939c75f2c7a849c)にて作った構成を元に配備
一旦ログインセッションを問い合わせてからその結果を持ってからレンダリング処理へ
bootstrap.jsは既存のままで利用

```jsx:resources/js/app.js
require('./bootstrap');

// ↓以下を追記

import { Provider } from 'react-redux'
import React from 'react'
import ReactDOM from 'react-dom'
import ReactRoot from './ReactRoot'
import configureStore, { history } from './stores/configureStore'
import { persistStore } from 'redux-persist'
import { PersistGate } from 'redux-persist/integration/react'

const store = configureStore()

const pstore = persistStore(store)

const render = (props) => {
	ReactDOM.render(
		<Provider store={store}>
			<PersistGate loading={<p>loading...</p>} persistor={pstore}>
				<ReactRoot history={history} responseSession={props} />
			</PersistGate>
		</Provider>,
		document.getElementById('react-root')
	)
}

function authSession()
{
	let params = new URLSearchParams();
	let url = '/session';
	window.axios.post(url,params)
		.then((response)=>{
			render(response.data)
		})
}

authSession()
```
フォーム要素に対しエラーがあれば再描画エラーがなければリロードとしてフォーム要素をクリアするためReduxアクションを呼び出し
適用遅延？があるようなのでコンストラクタで設定したredux状態ではなく上のコンポーネントから受け取ったログイン状態を
ルート振り分けに渡す

```jsx:resources/js/ReactRoot.js
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { ConnectedRouter } from 'connected-react-router'
import {
	setSession,
	setName,
	setEmail,
	setRemember,
	setCSRF
} from './actions/authentications'
import routes from './routes'

class ReactRoot extends React.Component {
	constructor(props){
		super(props)
		props.setSession(props.responseSession)
		if(laravelErrors['name']===undefined
			&&laravelErrors['email']===undefined
			&&laravelErrors['password']===undefined)
		{
			this.props.setName('')
			this.props.setEmail('')
			this.props.setRemember(false)
		}
		let token = document.head.querySelector('meta[name="csrf-token"]')
		this.props.setCSRF(token.content)
	}
	render(){
		return (
			<ConnectedRouter history={this.props.history}>
				{ routes(this.props.responseSession) }
			</ConnectedRouter>
		)
	}
}

ReactRoot.propTypes = {
	history: PropTypes.object,
	csrf:  PropTypes.string,
	setSession: PropTypes.func.isRequired,
	setName: PropTypes.func.isRequired,
	setEmail: PropTypes.func.isRequired,
	setRemember: PropTypes.func.isRequired,

}

const mapStateToProps = state => ({
	csrf: state.csrf,
})

const mapDispatchToProps = dispatch => ({
	setCSRF: (csrf) => dispatch(setCSRF(csrf)),
	setSession: (session) => dispatch(setSession(session)),
	setName: (name) => dispatch(setName(name)),
	setEmail: (email) => dispatch(setEmail(email)),
	setRemember: (remember) => dispatch(setRemember(remember)),
})

export default connect(mapStateToProps, mapDispatchToProps)(ReactRoot)
```
###Store
ここも前回の記事参照

```jsx:resources/js/stores/configureStore.js
import { createBrowserHistory } from 'history'
import { applyMiddleware, compose, createStore } from 'redux'
import { routerMiddleware } from 'connected-react-router'
import createRootReducer from '../reducers'
import { persistReducer } from 'redux-persist'
import storage from 'redux-persist/lib/storage'

const persistConfig = {
	key: 'root',
	storage,
	blacklist: ['router'],
}

export const history = createBrowserHistory()

const persistedReducer = persistReducer(persistConfig, createRootReducer(history))

export default function configureStore(preloadedState) {
	const composeEnhancer = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose
	const store = createStore(
		persistedReducer,
		preloadedState,
		composeEnhancer(
			applyMiddleware(
				routerMiddleware(history),
			),
		),
	)
	return store
}
```
###Action
ログインセッション、フォーム利用要素（名前、メール、記憶チェック）、CSRFトークン、パスワードリセット時に使われるリクエストトークンをReduxで管理する

```jsx:resources/js/actions/authentications.js
export const setSession = (session) => ({
	type: 'SET_SESSION',
	payload: {
		id:session.id,
		name:session.name
	}, 
})

export const setName = (name) => ({
	type: 'SET_NAME',
	payload: {
		name
	}
})

export const setEmail = (email) => ({
	type: 'SET_EMAIL',
	payload: {
		email
	}
})

export const setRemember = (remember) => ({
	type: 'SET_REMEMBER',
	payload: {
		remember
	} 
})

export const setCSRF = (csrf) => ({
	type: 'SET_CSRF',
	payload: {
		csrf
	} 
})

export const setPrams = (request) => ({
	type: 'SET_PARAMS',
	payload: {
		request
	} 
})
```
#Reducer
認証関連のReducerは...authenticationsで展開

```jsx:resources/js/reducers/index.js
import { combineReducers } from 'redux'
import { connectRouter } from 'connected-react-router'
import * as authentications from './authentications'

const rootReducer = (history) => combineReducers({
	...authentications,
	router: connectRouter(history)
})

export default rootReducer
```
...authenticationsで展開するためreducer名は状態名をそのまま使用

```jsx:resources/js/reducers/authentications.js
export const session = (state = {auth:false,name:null}, action) => {
	switch (action.type) {
		case 'SET_SESSION':
			let session = (action.payload.id===undefined)
				? ({auth:false,name:null})
				: ({auth:true,name:action.payload.name})
			return session
		default:
			return state
	}
}

export const name = (state = null, action) => {
	switch (action.type) {
		case 'SET_NAME':
			return action.payload.name;
		default:
			return state
	}
}

export const email = (state = null, action) => {
	switch (action.type) {
		case 'SET_EMAIL':
			return action.payload.email;
		default:
			return state
	}
}

export const remember = (state = false, action) => {
	switch (action.type) {
		case 'SET_REMEMBER':
			return action.payload.remember;
		default:
			return state
	}
}

export const csrf = (state = '', action) => {
	switch (action.type) {
		case 'SET_CSRF':
			return action.payload.csrf;
		default:
			return state
	}
}

export const params = (state = '', action) => {
	switch (action.type) {
		case 'SET_PARAMS':
			return action.payload.request;
		default:
			return state
	}
}
```
###Route
認証済みか否かでコンテンツを切り分け
状態に適しないページはリダイレクトでデフォルトへ移動させる

```jsx:resources/js/routes/index.js
import React from 'react'
import { Route, Switch, Redirect } from 'react-router'
import NavBar from '../components/NavBar'
import CardTemplate from '../components/CardTemplate'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { setSession } from '../actions/authentications'

const routes = (session)=> {
	return(
		<>
			<NavBar />
			<div className="py-3">
				{(()=>{
					if(session.id===undefined){
						return (
							<Switch>
								<Route exact path="/login" render={() => <CardTemplate title="Login" content="LoginForm" />} />
								<Route exact path="/register" render={() => <CardTemplate title="Register" content="RegisterForm" />} />
								<Route exact path="/password/reset" render={() => <CardTemplate title="Reset Password" content="EMailForm" />} />
								<Route path="/password/reset/:id" render={(props) => <CardTemplate title="Reset Password" content="ResetForm" params={props.match.params} />} />
								<Redirect to="/login" />
							</Switch>
						)
					}
					else
					{
						if(session.email_verified_at===null)
						{
							return (
								<Switch>
									<Route exact path="/email/verify" render={() => <CardTemplate title="Verify Your Email Address" content="Verify" />} />
									<Redirect to="/email/verify" />
								</Switch>
							)
						}
						else
						{
							return (
								<Switch>
									<Route exact path="/" render={() => <CardTemplate title="Welcome" content="Welcome" />} />
									<Route path="/home" render={() => <CardTemplate title="Dashboard" content="Home" />} />
									<Redirect to="/home" />
								</Switch>
							)
						}
					}
				})()}
			</div>
		</>
	)
}

export default routes
```
###Component
####メニュー部分
認証状態によって振り分けるためReduxから認証状態を受け取る

```jsx:resources/js/components/NavBar.js
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { Link } from 'react-router-dom'
import {
	Container,
	Navbar, Nav,
} from 'react-bootstrap'
import LeftMenu from './Menus/LeftMenu'
import RightMenu from './Menus/RightMenu'

const NavBar = (props) => (
	<Navbar expand="md" bg="light" className="navbar-laravel">
		<Container>
			<Navbar.Brand href="/">
				Laravel
			</Navbar.Brand>
			<Navbar.Toggle data-toggle="collapse" data-target="#navbarSupportedContent" aria-controls="navbarSupportedContent" aria-expanded="false" aria-label="Toggle navigation" />
			<Navbar.Collapse id="navbarSupportedContent">
				<Nav className="mr-auto">
					<LeftMenu auth={props.auth} />
				</Nav>
				<Nav className="ml-auto">
					<RightMenu auth={props.auth} />
				</Nav>
			</Navbar.Collapse>
		</Container>
	</Navbar>
)

NavBar.propTypes = {
	auth: PropTypes.bool.isRequired,
}

const mapStateToProps = state => ({
	auth: state.session.auth,
})

const mapDispatchToProps = dispatch => ({
})

export default connect(mapStateToProps, mapDispatchToProps)(NavBar)
```

状態によって表示メニューを切り替え

```jsx:resources/js/components/Menus/LeftMenu.js
import React from 'react'
import LeftMemberMenu from './LeftMemberMenu'
import LeftGuestMenu from './LeftGuestMenu'

const LeftMenu = (props) => {
	if(props.auth)
	{
		return <LeftMemberMenu />
	}
	else
	{
		return <LeftGuestMenu />
	}
}

export default LeftMenu
```
非認証時のメニュー

```jsx:resources/js/components/Menus/LeftGuestMenu.js
import React from 'react'

const LeftGuestMenu = () => (
	<>
	</>
)
```
認証時のメニュー

```jsx:resources/js/components/Menus/LeftMemberMenu.js
import React from 'react'
import { Nav } from 'react-bootstrap'
import { Link } from 'react-router-dom'

const LeftMemberMenu = () => (
	<>
		<Nav.Item>
			<Link to="/home" className="nav-link">Home</Link>
		</Nav.Item >
	</>
)
```
状態によって表示メニューを切り替え

```jsx:resources/js/components/Menus/RightMenu.js
import React from 'react'
import { Link } from 'react-router-dom'
import {
	Nav, NavDropdown,
	Form, 
} from 'react-bootstrap'
import RightMemberMenu from './RightMemberMenu'
import RightGuestMenu from './RightGuestMenu'

const RightMenu = (props) => {
	if(props.auth)
	{
		return <RightMemberMenu />
	}
	else
	{
		return <RightGuestMenu />
	}
}
export default RightMenu
```
非認証時のメニュー

```jsx:resources/js/components/Menus/RightGuestMenu.js
import React from 'react'
import { Link } from 'react-router-dom'
import { Nav } from 'react-bootstrap'

const RightGuestMenu = () => (
	<>
		<Nav.Item>
			<Link to="/login" className="nav-link">Login</Link>
		</Nav.Item>
		<Nav.Item>
			<Link to="/register" className="nav-link">Register</Link>
		</Nav.Item>
	</>
)
export default RightGuestMenu
```
認証時のメニュー
表示するログインユーザー名をReduxから取得する

```jsx:resources/js/components/Menus/RightMemberMenu.js
import React from 'react'
import { Link } from 'react-router-dom'
import { NavDropdown, Form } from 'react-bootstrap'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import CSRFToken from '../Elements/CSRFToken'

class RightMemberMenu extends React.Component {
	constructor(props) {
		super(props)
	}
	render(){
		return (
			<NavDropdown title={this.props.name+' 様'}>
				<NavDropdown.Item href="/logout" onClick={this.preSubmit}>Logout</NavDropdown.Item>
				<Form id="logout-form" action="/logout" method="POST" style={{display:'none'}}>
					<CSRFToken />
				</Form>
			</NavDropdown>
		)
	}
	preSubmit(currentEvent){
		currentEvent.preventDefault();
		document.getElementById('logout-form').submit();
	}
}
RightMemberMenu.propTypes = {
	name: PropTypes.string,
}
const mapStateToProps = state => ({
	name: state.session.name,
})
const mapDispatchToProps = dispatch => ({
})
export default connect(mapStateToProps, mapDispatchToProps)(RightMemberMenu)
```
###コンテンツ部分
メインコンテンツのカード部分のテンプレート

```jsx:resources/js/components/CardTemplate.js
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { Container, Row, Col, Card } from 'react-bootstrap'
import ContentSelector from './ContentSelector'
import { setPrams } from '../actions/authentications'

class CardTemplate extends React.Component {
	constructor(props){
		super(props)
		if(props.params!==undefined)
		{
			props.setPrams(props.params)
		}
	}
	render(){
		return (
			<Container>
				<Row className="justify-content-center">
					<Col md="8">
						<Card>
							<Card.Header>{this.props.title}</Card.Header>
							<Card.Body>
								<ContentSelector content={this.props.content} />
							</Card.Body>
						</Card>
					</Col>
				</Row>
			</Container>
		)
	}
}
CardTemplate.propTypes = {
	setPrams: PropTypes.func.isRequired,
}
const mapStateToProps = state => ({
})
const mapDispatchToProps = dispatch => ({
	setPrams: (request) => dispatch(setPrams(request)),
})
export default connect(mapStateToProps, mapDispatchToProps)(CardTemplate)
```
ReactRouterから来たパスによって画面振り分け

```jsx:resources/js/components/ContentSelector.js
import React from 'react'
import LoginForm from './Forms/LoginForm'
import RegisterForm from './Forms/RegisterForm'
import EMailForm from './Forms/EMailForm'
import ResetForm from './Forms/ResetForm'
import Home from './Forms/Home'
import Verify from './Forms/Verify'

const ContentSelector = (props) => {
	switch(props.content){
		case 'LoginForm':
			return <LoginForm />
		case 'RegisterForm':
			return <RegisterForm />
		case 'EMailForm':
			return <EMailForm />
		case 'ResetForm':
			return <ResetForm />
		case 'Home':
			return <Home />
		case 'Verify':
			return <Verify />
		default:
			return <div>UnMatch</div>
	}
}
export default ContentSelector
```
ログインフォーム

```jsx:resources/js/components/Forms/LoginForm.js
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { Form } from 'react-bootstrap'
import TextInput from '../Elements/TextInput'
import CheckInput from '../Elements/CheckInput'
import LoginButton from '../Elements/LoginButton'
import CSRFToken from '../Elements/CSRFToken'
import { setEmail, setRemember } from '../../actions/authentications'

class LoginForm extends React.Component {
	constructor(props) {
		super(props)
	}
	render(){
		return (
			<Form method="POST" action="/login" id="login-form">
				<CSRFToken />
				<TextInput
					identity="email"
					controlType="email"
					label="E-Mail Address"
					defaultValue={this.props.email}
					action={this.props.setEmail}
					autoFocus={true}
					/>
				<TextInput
					identity="password"
					controlType="password"
					autoComplete="current-password"
					label="Password"
					/>
				<CheckInput
					identity="remember"
					label="Remember Me"
					checked={this.props.remember}
					action={this.props.setRemember}
					/>
				<LoginButton />
			</Form>
		)
	}
}
LoginForm.propTypes = {
	email:  PropTypes.string,
	remember:  PropTypes.bool,
	setEmail: PropTypes.func.isRequired,
	setRemember: PropTypes.func.isRequired,
}
const mapStateToProps = state => ({
	email: state.email,
	remember: state.remember,
})
const mapDispatchToProps = dispatch => ({
	setEmail: (email) => dispatch(setEmail(email)),
	setRemember: (remember) => dispatch(setRemember(remember)),
})
export default connect(mapStateToProps, mapDispatchToProps)(LoginForm)
```
新規登録フォーム

```jsx:resources/js/components/Forms/RegisterForm.js
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { Form } from 'react-bootstrap'
import TextInput from '../Elements/TextInput'
import SubmitButton from '../Elements/SubmitButton'
import CSRFToken from '../Elements/CSRFToken'
import { setName, setEmail } from '../../actions/authentications'

class LoginForm extends React.Component {
	constructor(props) {
		super(props)
	}
	render(){
		return (
			<Form method="POST" action="/register" id="login-form">
				<CSRFToken />
				<TextInput
					identity="name"
					controlType="text"
					label="Name"
					defaultValue={this.props.name}
					action={this.props.setName}
					autoFocus={true}
					/>
				<TextInput
					identity="email"
					controlType="email"
					label="E-Mail Address"
					defaultValue={this.props.email}
					action={this.props.setEmail}
					autoFocus={false}
					/>
				<TextInput
					identity="password"
					controlType="password"
					autoComplete="new-password"
					label="Password"
					/>
				<TextInput
					identity="password-confirm"
					controlType="password"
					name="password_confirmation"
					autoComplete="new-password"
					label="Confirm Password"
					/>
				<SubmitButton label="Register" />
			</Form>
		)
	}
}
LoginForm.propTypes = {
	name:  PropTypes.string,
	email:  PropTypes.string,
	setName: PropTypes.func.isRequired,
	setEmail: PropTypes.func.isRequired,
}
const mapStateToProps = state => ({
	name: state.name,
	email: state.email,
})
const mapDispatchToProps = dispatch => ({
	setName: (name) => dispatch(setName(name)),
	setEmail: (email) => dispatch(setEmail(email)),
})
export default connect(mapStateToProps, mapDispatchToProps)(LoginForm)
```
パスワードリセット申請フォーム

```jsx:resources/js/components/Forms/EMailForm.js
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { Form } from 'react-bootstrap'
import TextInput from '../Elements/TextInput'
import SubmitButton from '../Elements/SubmitButton'
import CSRFToken from '../Elements/CSRFToken'
import SessionAlert from '../Elements/SessionAlert '
import { setEmail } from '../../actions/authentications'

class ResetForm extends React.Component {
	constructor(props) {
		super(props)
	}
	render(){
		return (
			<>
				<SessionAlert target="status" />
				<Form method="POST" action="/password/email" id="login-form">
					<CSRFToken />
					<TextInput
						identity="email"
						controlType="email"
						label="E-Mail Address"
						defaultValue={this.props.email}
						action={this.props.setEmail}
						autoFocus={true}
						/>
					<SubmitButton label="Send Password Reset Link" />
				</Form>
			</>
		)
	}
}
ResetForm.propTypes = {
	email:  PropTypes.string,
	setEmail: PropTypes.func.isRequired,
}
const mapStateToProps = state => ({
	email: state.email,
})
const mapDispatchToProps = dispatch => ({
	setEmail: (email) => dispatch(setEmail(email)),
})
export default connect(mapStateToProps, mapDispatchToProps)(ResetForm)
```
パスワードリセットフォーム

```jsx:resources/js/components/Forms/ResetForm.js
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { Form } from 'react-bootstrap'
import TextInput from '../Elements/TextInput'
import SubmitButton from '../Elements/SubmitButton'
import CSRFToken from '../Elements/CSRFToken'
import RequestToken from '../Elements/RequestToken'
import SessionAlert from '../Elements/SessionAlert '
import { setEmail } from '../../actions/authentications'

class ResetForm extends React.Component {
	constructor(props) {
		super(props)
	}
	render(){
		return (
			<>
				<SessionAlert target="status" />
				<Form method="POST" action="/password/reset" id="login-form">
					<CSRFToken />
					<RequestToken />
					<TextInput
						identity="email"
						controlType="email"
						label="E-Mail Address"
						defaultValue={this.props.email}
						action={this.props.setEmail}
						autoFocus={true}
						/>
					<TextInput
						identity="password"
						controlType="password"
						autoComplete="new-password"
						label="Password"
						/>
					<TextInput
						identity="password-confirm"
						controlType="password"
						name="password_confirmation"
						autoComplete="new-password"
						label="Confirm Password"
						/>
					<SubmitButton label="Reset Password" />
				</Form>
			</>
		)
	}
}
ResetForm.propTypes = {
	email:  PropTypes.string,
	setEmail: PropTypes.func.isRequired,
}
const mapStateToProps = state => ({
	email: state.email,
})
const mapDispatchToProps = dispatch => ({
	setEmail: (email) => dispatch(setEmail(email)),
})
export default connect(mapStateToProps, mapDispatchToProps)(ResetForm)
```
ログイン後のフォーム画面
ステータスセッションがあればば表示する（パスワードリセット時に見られる）

```jsx:resources/js/components/Forms/Home.js
import React from 'react'
import { Container, Row, Col, Card } from 'react-bootstrap'
import StatusAlert from '../Elements/StatusAlert'

const Home = () => (
	<>
		<SessionAlert target="status" />
		You are logged in!
	</>
)
export default Home
```
メール認証前の画面
resentセッションがあればば表示する（メールの再送信に成功した時に見られる）

```jsx:resources/js/components/Forms/Verify.js
import React from 'react'
import { Container, Row, Col, Card } from 'react-bootstrap'
import SessionAlert from '../Elements/SessionAlert'

const Verify = () => (
	<>
		<SessionAlert target="resent" />
		Before proceeding, please check your email for a verification link.
		If you did not receive the email, <a href="/email/resend">click here to request another</a>.
	</>
)

export default Verify
```
####汎用部品
ReduxからCSRFトークンを取得したinput要素

```jsx:resources/js/components/Elements/CSRFToken.js
import React from 'react'
import { Form } from 'react-bootstrap'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'

const CSRFToken = (props) => (
	<Form.Control type="hidden" name="_token" defaultValue={props.csrf} />
)
CSRFToken.propTypes = {
	csrf: PropTypes.string,
}
const mapStateToProps = state => ({
	csrf: state.csrf,
})
const mapDispatchToProps = dispatch => ({
})
export default connect(mapStateToProps, mapDispatchToProps)(CSRFToken)
```
パスワードリセット時のリクエストトークンを取得したinput要素

```jsx:resources/js/components/Elements/RequestToken.js
import React from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import { Form } from 'react-bootstrap'

const RequestToken = (props) => (
	 <Form.Control type="hidden" name="token" defaultValue={props.params.id} />
)
RequestToken.propTypes = {
	params:  PropTypes.object,
}
const mapStateToProps = state => ({
	params: state.params,
})
const mapDispatchToProps = dispatch => ({
})
export default connect(mapStateToProps, mapDispatchToProps)(RequestToken)
```
セッションに通知情報があればをグローバル変数で取得してあればアラート表示

```jsx:resources/js/components/Elements/SessionAlert.js
import React from 'react'
import { Alert } from 'react-bootstrap'

const SessionAlert = (props) => {
	if(laravelSession[props.target]!=='')
	{
		return (
			<Alert  variant="success" role="alert">
				{ 
					props.target==="resent"
					?'A fresh verification link has been sent to your email address.'
					:laravelSession[props.target]
				}
			</Alert>
		)
	}
	else
	{
		return <></>
	}
}

export default SessionAlert
```

エラー情報をグローバル変数で取得してあればエラーメッセージ表示

```jsx:resources/js/components/Elements/FormError.js
import React from 'react'

const FormError = (props) => (
	<span className="invalid-feedback" role="alert">
		<strong>{props.message}</strong>
	</span>
)
export default FormError
```
テキスト系input要素とラベル、エラーメッセージを組み合わせた部品

```jsx:resources/js/components/Elements/TextInput.js
import React from 'react'
import { Row, Col, Form } from 'react-bootstrap'
import FormError from './FormError'

class TextInput extends React.Component {
	constructor(props){
		super(props)
		this.state={
			error:'',
			isInvalid:'',
		};
		if(laravelErrors[this.props.identity]!==undefined)
		{
			this.state.error=laravelErrors[this.props.identity][0]
			this.state.isInvalid=' is-invalid'
		}
	}
	render(){
		let inputProps = {}
		inputProps.id=this.props.identity
		inputProps.type=this.props.controlType===undefined?'text':this.props.controlType
		inputProps.className=this.state.isInvalid
		inputProps.name=this.props.name===undefined?this.props.identity:this.props.name
		inputProps.autoComplete=this.props.autoComplete===undefined?this.props.identity:this.props.autoComplete
		inputProps.required=this.props.required===undefined?true:this.props.required
		inputProps.autoFocus=this.props.autoFocus===undefined?false:this.props.autoFocus
		if(this.props.controlType!=='password')
		{
			inputProps.defaultValue=this.props.defaultValue
			inputProps.onChange=(text)=>{this.props.action(text.target.value)}
		}
		return (
			<Form.Group>
				<Row>
					<Form.Label htmlFor={this.props.identity} className="col-md-4 col-form-label text-md-right">{this.props.label}</Form.Label>
					<Col md="6">
						<Form.Control {...inputProps} />
						<FormError message={this.state.error} />
					</Col>
				</Row>
			</Form.Group>
		)
	}
}

export default TextInput

```
セッションを記憶ボタンから汎用化したチェックボックス部品

```jsx:resources/js/components/Elements/CheckInput.js
import React from 'react'
import { Row, Col, Form } from 'react-bootstrap'
import FormError from './FormError'

const CheckInput = (props) => (
	<Form.Group>
		<Row>
			<Col md="6" className="offset-md-4">
				<div className="form-check">
					<Form.Check
						id={props.identity}
						type="checkbox"
						className="form-check-input"
						name={props.identity}
						checked={props.checked}
						onChange={ check => props.action(check.target.checked) }
						/>
					<Form.Label className="form-check-label" htmlFor={props.identity}>
						{props.label}
					</Form.Label>
				</div>
			</Col>
		</Row>
	</Form.Group>
)
export default CheckInput
```
各種送信ボタン部品

```jsx:resources/js/components/Elements/SubmitButton.js
import React from 'react'
import { Row, Col, Button } from 'react-bootstrap'

const SubmitButton = (props) => (
	<Row className="form-group mb-0">
		<Col md="6" className="offset-md-4">
			<Button type="submit" variant="primary">
				{props.label}
			</Button>
		</Col>
	</Row>
)
export default SubmitButton
```
ログインボタンだけパスワード忘れのリンクが付いているので別定義

```jsx:resources/js/components/Elements/LoginButton.js
import React from 'react'
import { Row, Col, Button } from 'react-bootstrap'
import { Link } from 'react-router-dom'

const LoginButton = () => (
	<Row className="form-group mb-0">
		<Col md="8" className="offset-md-4">
			<Button type="submit" variant="primary">
				Login
			</Button>
			<Link to="/password/reset" className="btn btn-link">
				Forgot Your Password?
			</Link>
		</Col>
	</Row>
)
export default LoginButton
```