Commit 3ced9e8e authored by Egor Kremnev's avatar Egor Kremnev

end crud article in frontend

parent 3d29669b
...@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api; ...@@ -4,6 +4,7 @@ namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Http\Requests\Api\RegisterRequest; use App\Http\Requests\Api\RegisterRequest;
use App\Http\Resources\Api\UserResource;
use App\Models\User; use App\Models\User;
use Illuminate\Http\JsonResponse; use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request; use Illuminate\Http\Request;
...@@ -23,7 +24,10 @@ class PassportAuthController extends Controller ...@@ -23,7 +24,10 @@ class PassportAuthController extends Controller
]); ]);
return response()->json( return response()->json(
['token' => $user->createToken('LaravelAuthApp')->accessToken], [
'token' => $user->createToken('LaravelAuthApp')->accessToken,
'user' => new UserResource($user)
],
201 201
); );
} }
...@@ -36,7 +40,10 @@ class PassportAuthController extends Controller ...@@ -36,7 +40,10 @@ class PassportAuthController extends Controller
{ {
if (auth()->attempt($request->only(['email', 'password']))) { if (auth()->attempt($request->only(['email', 'password']))) {
return response() return response()
->json(['token' => auth()->user()->createToken('LaravelAuthApp')->accessToken]); ->json([
'token' => auth()->user()->createToken('LaravelAuthApp')->accessToken,
'user' => new UserResource(auth()->user())
]);
} }
return response()->json(['error' => 'Unauthorised'], 401); return response()->json(['error' => 'Unauthorised'], 401);
......
...@@ -2,7 +2,7 @@ import {Table} from "semantic-ui-react"; ...@@ -2,7 +2,7 @@ import {Table} from "semantic-ui-react";
import React from "react"; import React from "react";
import Item from "./Item/Item"; import Item from "./Item/Item";
const ArticlesPageTable = ({articles}) => { const ArticlesPageTable = ({articles, isLoading, onDeleteHandler}) => {
return <div> return <div>
<Table> <Table>
<thead> <thead>
...@@ -15,7 +15,12 @@ const ArticlesPageTable = ({articles}) => { ...@@ -15,7 +15,12 @@ const ArticlesPageTable = ({articles}) => {
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{articles.map(article => <Item article={article} key={article.id}/>)} {articles.map(article => <Item
article={article}
key={article.id}
isLoading={isLoading}
onDeleteHandler={onDeleteHandler}
/>)}
</tbody> </tbody>
</Table> </Table>
</div>; </div>;
......
...@@ -2,7 +2,7 @@ import {Button} from "semantic-ui-react"; ...@@ -2,7 +2,7 @@ import {Button} from "semantic-ui-react";
import {Link} from "react-router-dom"; import {Link} from "react-router-dom";
import React from "react"; import React from "react";
const Item = ({article}) => { const Item = ({article, isLoading, onDeleteHandler}) => {
return <tr> return <tr>
<td>{article.id}</td> <td>{article.id}</td>
<td>{article.title}</td> <td>{article.title}</td>
...@@ -10,13 +10,11 @@ const Item = ({article}) => { ...@@ -10,13 +10,11 @@ const Item = ({article}) => {
<td>{article.user.name}</td> <td>{article.user.name}</td>
<td> <td>
<Button.Group> <Button.Group>
<Button color="blue" as={Link} to={`/articles/${article.id}`}> <Button color="blue" as={Link} to={`/articles/${article.id}`}>Show</Button>
Show
</Button>
<Button.Or /> <Button.Or />
<Button color="orange">Edit</Button> <Button color="orange" as={Link} to={`/articles/${article.id}/update`}>Edit</Button>
<Button.Or /> <Button.Or />
<Button color="red">Delete</Button> <Button loading={isLoading} color="red" onClick={() => onDeleteHandler(article.id)}>Delete</Button>
</Button.Group> </Button.Group>
</td> </td>
</tr>; </tr>;
......
...@@ -23,7 +23,8 @@ const Navbar = () => { ...@@ -23,7 +23,8 @@ const Navbar = () => {
const handleLogOut = (e) => { const handleLogOut = (e) => {
setState({ isAuth: false, activeItem: 'logout' }); setState({ isAuth: false, activeItem: 'logout' });
localStorage.removeItem('token'); localStorage.removeItem('token');
navigate("/login"); localStorage.removeItem('user');
return navigate("/login");
}; };
const authButtons = () => { const authButtons = () => {
......
import React, {useEffect, useState} from 'react';
import {get} from "../../../services/request";
import {API_BASE_URL} from "../../../config";
import {Message} from 'semantic-ui-react';
import ArticleForm from "../ArticleForm/ArticleForm";
import {useParams} from "react-router-dom";
const ArticleEdit = () => {
const [state, setState] = useState({
article: null,
isLoading: false,
});
const {id} = useParams();
useEffect(() => {
const token = localStorage.getItem('token');
if (!state.article) {
setState(prevState => ({...prevState, isLoading: true}));
get(API_BASE_URL + `/articles/${id}`, token)
.then(data => setState(prevState => ({...prevState, article: data.data, isLoading: false})));
}
}, [id]);
return (
<div>
{state.isLoading && <Message info header="Loading article..."/>}
{state.article && <ArticleForm article={state.article}/>}
</div>
);
};
export default ArticleEdit;
import React, {useEffect, useState} from 'react';
import {Button, Form, Message, TextArea} from 'semantic-ui-react';
import {API_BASE_URL} from "../../../config";
import {post, put} from "../../../services/request";
import {useNavigate} from 'react-router-dom';
import Errors from "../../../components/Errors/Errors";
const ArticleForm = ({article}) => {
const [state, setState] = useState( {
article,
title: null,
content: null,
isLoading: false,
errorMessage: null,
errors: null,
error: null
});
const navigate = useNavigate();
const handleSubmit = (e) => {
e.preventDefault();
setState(prevState => ({...prevState, isLoading: true}));
const token = localStorage.getItem('token');
const user = JSON.parse(localStorage.getItem('user'));
const data = {
title: state.title,
content: state.content,
user_id: user.id
};
if (state.article) {
put(API_BASE_URL + `/articles/${state.article.id}`, data, token)
.then(response => {
if (Object.keys(response).includes('data')) {
return navigate('/articles');
} else if (Object.keys(response).includes('error')) {
setState(prevState => ({...prevState, error: response.error}));
} else {
setState(prevState => ({...prevState, error: response.error, errorMessage: response.message,}));
}
});
} else {
post(API_BASE_URL + "/articles", data, token)
.then(response => {
if (Object.keys(response).includes('data')) {
return navigate('/articles');
} else if (Object.keys(response).includes('error')) {
setState(prevState => ({...prevState, error: response.error}));
} else {
setState(prevState => ({...prevState, error: response.error, errorMessage: response.message,}));
}
});
}
setState(prevState => ({...prevState, isLoading: false}));
};
useEffect(() => {
if (article) {
setState(prevState => ({
...prevState,
title: article.title,
content: article.content
}));
}
}, [article]);
const onChangeInputHandler = e => {
const {name, value} = e.currentTarget;
setState(prevState => ({
...prevState,
[name]: value,
errors: null
}));
};
return (
<div>
{state.error && <Message negative><Message.Header>{this.state.error}</Message.Header></Message>}
{state.errors && <Errors errors={state.errors} errorMessage={state.errorMessage} />}
<Form>
<Form.Field>
<label>Title</label>
<input type="text" placeholder='Title' value={state.title || ''} name='title'
onChange={onChangeInputHandler}
/>
</Form.Field>
<Form.Field>
<label>Content</label>
<TextArea value={state.content || ''} placeholder='Content' style={{minHeight: 100}}
name='content' onChange={onChangeInputHandler}
/>
</Form.Field>
<Button loading={state.isLoading} type='submit' onClick={handleSubmit}>
{state.article ? 'Edit article' : 'Create article'}
</Button>
</Form>
</div>
);
};
export default ArticleForm;
import React, {useEffect, useState} from "react"; import React, {useEffect, useState} from "react";
import './ArticlesPage.css'; import './ArticleList.css';
import {API_BASE_URL} from "../../config"; import {API_BASE_URL} from "../../../config";
import {Header, Message} from "semantic-ui-react"; import {Button, Header, Message} from "semantic-ui-react";
import ArticlesPageTable from "../../components/ArticlesPageTable/ArticlesPageTable"; import ArticlesPageTable from "../../../components/ArticlesPageTable/ArticlesPageTable";
import {useNavigate} from "react-router-dom"; import {Link} from "react-router-dom";
import {get} from "../../services/request"; import {get, remove} from "../../../services/request";
const ArticlesPage = () => { const ArticleList = () => {
const [state, setState] = useState({ const [state, setState] = useState({
articles: null, articles: null,
isLoading: false isLoading: false
}); });
const navigate = useNavigate();
const getArticles = () => { const getArticles = () => {
const token = localStorage.getItem('token'); const token = localStorage.getItem('token');
if (!token) navigate('/login');
try { try {
setState(prevState => ({...prevState, isLoading: true})); setState(prevState => ({...prevState, isLoading: true}));
const response = get(API_BASE_URL + "/articles", token); const response = get(API_BASE_URL + "/articles", token);
...@@ -30,20 +26,34 @@ const ArticlesPage = () => { ...@@ -30,20 +26,34 @@ const ArticlesPage = () => {
} }
useEffect(() => { useEffect(() => {
const token = localStorage.getItem('token');
if (!token) navigate('/login');
if (!state.articles) getArticles(); if (!state.articles) getArticles();
}, []); }, []);
const handleDelete = (articleId) => {
setState(prevState => ({...prevState, isLoading: true}));
const token = localStorage.getItem('token');
remove(API_BASE_URL + `/articles/${articleId}`, token)
.then(getArticles)
.finally(() => setState(prevState => ({...prevState, isLoading: false})));
};
return ( return (
<div> <div>
<Header as="h1">Articles</Header> <Header as="h1">Articles</Header>
<div>
<Button color="blue" as={Link} to='/articles/create'>Create article</Button>
</div>
{state.isLoading && <Message info header="Loading articles..." />} {state.isLoading && <Message info header="Loading articles..." />}
{state.articles && <ArticlesPageTable articles={state.articles} />} {
state.articles && <ArticlesPageTable
articles={state.articles}
isLoading={state.isLoading}
onDeleteHandler={handleDelete}
/>
}
</div> </div>
); );
}; };
export default ArticlesPage; export default ArticleList;
import React, {useEffect, useState} from 'react';
import {get} from "../../../services/request";
import {API_BASE_URL} from "../../../config";
import {Item, Message, Header} from 'semantic-ui-react';
import {useParams} from "react-router-dom";
const ArticleShow = () => {
const [state, setState] = useState({
article: null,
isLoading: false,
});
const {id} = useParams();
useEffect(() => {
const token = localStorage.getItem('token');
if (!state.article) {
setState(prevState => ({...prevState, isLoading: true}));
get(API_BASE_URL + `/articles/${id}`, token)
.then(data => setState(prevState => ({...prevState, article: data.data, isLoading: false})));
}
}, [id]);
return (
<div>
{state.isLoading && <Message info header="Loading article..."/>}
{state.article &&
<div>
<Header as="h1">
Article - {state.article.title}
</Header>
<Item>
<Item.Content>
<Item.Header as='h3'>{state.article.title}</Item.Header>
<Item.Description>
<p>{state.article.content}</p>
</Item.Description>
</Item.Content>
</Item>
</div>
}
</div>
);
};
export default ArticleShow;
...@@ -29,11 +29,12 @@ const Login = () => { ...@@ -29,11 +29,12 @@ const Login = () => {
email: state.email, email: state.email,
password: state.password password: state.password
}; };
const res = post(API_BASE_URL + "/login", data); post(API_BASE_URL + "/login", data)
res.then(response => { .then(response => {
if (Object.keys(response).includes('token')) { if (Object.keys(response).includes('token')) {
localStorage.setItem('token', response.token); localStorage.setItem('token', response.token);
navigate('/'); localStorage.setItem('user', JSON.stringify(response.user));
return navigate('/');
} else if (Object.keys(response).includes('error')) { } else if (Object.keys(response).includes('error')) {
setState(prevState => ({...prevState, error: response.error})); setState(prevState => ({...prevState, error: response.error}));
} }
...@@ -44,8 +45,7 @@ const Login = () => { ...@@ -44,8 +45,7 @@ const Login = () => {
errors: response.errors, errors: response.errors,
isLoading: false isLoading: false
})); }));
} });
);
}; };
const onChangeInputHandler = e => { const onChangeInputHandler = e => {
......
import {Navigate} from "react-router-dom";
import React from "react";
const PrivateRoute = ({component}) => {
const token = localStorage.getItem('token');
const user = JSON.parse(localStorage.getItem('user'));
return token && user ? component : <Navigate to="/login"/>;
};
export default PrivateRoute;
...@@ -3,9 +3,13 @@ import HomePage from "../components/HomePage/HomePage"; ...@@ -3,9 +3,13 @@ import HomePage from "../components/HomePage/HomePage";
import Root from "./Root"; import Root from "./Root";
import React from 'react'; import React from 'react';
import Error from "./Error"; import Error from "./Error";
import ArticlesPage from "../containers/ArticlesPage/ArticlesPage"; import ArticleList from "../containers/Article/ArticleList/ArticleList";
import Register from "../containers/Auth/Register/Register"; import Register from "../containers/Auth/Register/Register";
import Login from "../containers/Auth/Login/Login"; import Login from "../containers/Auth/Login/Login";
import ArticleForm from "../containers/Article/ArticleForm/ArticleForm";
import ArticleEdit from "../containers/Article/ArticleEdit/ArticleEdit";
import ArticleShow from "../containers/Article/ArticleShow/ArticleShow";
import PrivateRoute from "./PrivateRoute";
const router = createBrowserRouter([ const router = createBrowserRouter([
{ {
...@@ -19,7 +23,19 @@ const router = createBrowserRouter([ ...@@ -19,7 +23,19 @@ const router = createBrowserRouter([
}, },
{ {
path: '/articles', path: '/articles',
element: <ArticlesPage /> element: <PrivateRoute component={<ArticleList />}/>
},
{
path: '/articles/create',
element:<PrivateRoute component={<ArticleForm />}/>
},
{
path: '/articles/:id',
element: <PrivateRoute component={<ArticleShow />}/>
},
{
path: '/articles/:id/update',
element: <PrivateRoute component={<ArticleEdit />}/>
}, },
{ {
path: '/register', path: '/register',
......
...@@ -33,3 +33,32 @@ export function post(url, data, token) { ...@@ -33,3 +33,32 @@ export function post(url, data, token) {
} }
).then(playWithResponsePromise); ).then(playWithResponsePromise);
} }
export function remove(url, token) {
return fetch(
url,
{
method: 'DELETE',
headers: {
'Accept': 'application/json',
'Content-type': 'application/json',
...token && {'Authorization': 'Bearer ' + token}
}
}
);
}
export function put(url, data, token) {
return fetch(
url,
{
method: 'PUT',
body: JSON.stringify(data),
headers: {
'Accept': 'application/json',
'Content-type': 'application/json',
...token && {'Authorization': 'Bearer ' + token}
}
}
).then(playWithResponsePromise);
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment