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;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\RegisterRequest;
use App\Http\Resources\Api\UserResource;
use App\Models\User;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
......@@ -23,7 +24,10 @@ class PassportAuthController extends Controller
]);
return response()->json(
['token' => $user->createToken('LaravelAuthApp')->accessToken],
[
'token' => $user->createToken('LaravelAuthApp')->accessToken,
'user' => new UserResource($user)
],
201
);
}
......@@ -36,7 +40,10 @@ class PassportAuthController extends Controller
{
if (auth()->attempt($request->only(['email', 'password']))) {
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);
......
......@@ -2,7 +2,7 @@ import {Table} from "semantic-ui-react";
import React from "react";
import Item from "./Item/Item";
const ArticlesPageTable = ({articles}) => {
const ArticlesPageTable = ({articles, isLoading, onDeleteHandler}) => {
return <div>
<Table>
<thead>
......@@ -15,7 +15,12 @@ const ArticlesPageTable = ({articles}) => {
</tr>
</thead>
<tbody>
{articles.map(article => <Item article={article} key={article.id}/>)}
{articles.map(article => <Item
article={article}
key={article.id}
isLoading={isLoading}
onDeleteHandler={onDeleteHandler}
/>)}
</tbody>
</Table>
</div>;
......
......@@ -2,7 +2,7 @@ import {Button} from "semantic-ui-react";
import {Link} from "react-router-dom";
import React from "react";
const Item = ({article}) => {
const Item = ({article, isLoading, onDeleteHandler}) => {
return <tr>
<td>{article.id}</td>
<td>{article.title}</td>
......@@ -10,13 +10,11 @@ const Item = ({article}) => {
<td>{article.user.name}</td>
<td>
<Button.Group>
<Button color="blue" as={Link} to={`/articles/${article.id}`}>
Show
</Button>
<Button color="blue" as={Link} to={`/articles/${article.id}`}>Show</Button>
<Button.Or />
<Button color="orange">Edit</Button>
<Button color="orange" as={Link} to={`/articles/${article.id}/update`}>Edit</Button>
<Button.Or />
<Button color="red">Delete</Button>
<Button loading={isLoading} color="red" onClick={() => onDeleteHandler(article.id)}>Delete</Button>
</Button.Group>
</td>
</tr>;
......
......@@ -23,7 +23,8 @@ const Navbar = () => {
const handleLogOut = (e) => {
setState({ isAuth: false, activeItem: 'logout' });
localStorage.removeItem('token');
navigate("/login");
localStorage.removeItem('user');
return navigate("/login");
};
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 './ArticlesPage.css';
import {API_BASE_URL} from "../../config";
import {Header, Message} from "semantic-ui-react";
import ArticlesPageTable from "../../components/ArticlesPageTable/ArticlesPageTable";
import {useNavigate} from "react-router-dom";
import {get} from "../../services/request";
const ArticlesPage = () => {
import './ArticleList.css';
import {API_BASE_URL} from "../../../config";
import {Button, Header, Message} from "semantic-ui-react";
import ArticlesPageTable from "../../../components/ArticlesPageTable/ArticlesPageTable";
import {Link} from "react-router-dom";
import {get, remove} from "../../../services/request";
const ArticleList = () => {
const [state, setState] = useState({
articles: null,
isLoading: false
});
const navigate = useNavigate();
const getArticles = () => {
const token = localStorage.getItem('token');
if (!token) navigate('/login');
try {
setState(prevState => ({...prevState, isLoading: true}));
const response = get(API_BASE_URL + "/articles", token);
......@@ -30,20 +26,34 @@ const ArticlesPage = () => {
}
useEffect(() => {
const token = localStorage.getItem('token');
if (!token) navigate('/login');
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 (
<div>
<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.articles && <ArticlesPageTable articles={state.articles} />}
{
state.articles && <ArticlesPageTable
articles={state.articles}
isLoading={state.isLoading}
onDeleteHandler={handleDelete}
/>
}
</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 = () => {
email: state.email,
password: state.password
};
const res = post(API_BASE_URL + "/login", data);
res.then(response => {
post(API_BASE_URL + "/login", data)
.then(response => {
if (Object.keys(response).includes('token')) {
localStorage.setItem('token', response.token);
navigate('/');
localStorage.setItem('user', JSON.stringify(response.user));
return navigate('/');
} else if (Object.keys(response).includes('error')) {
setState(prevState => ({...prevState, error: response.error}));
}
......@@ -44,8 +45,7 @@ const Login = () => {
errors: response.errors,
isLoading: false
}));
}
);
});
};
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";
import Root from "./Root";
import React from 'react';
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 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([
{
......@@ -19,7 +23,19 @@ const router = createBrowserRouter([
},
{
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',
......
......@@ -33,3 +33,32 @@ export function post(url, data, token) {
}
).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