Commit f7ff7d99 authored by Even Gultvedt's avatar Even Gultvedt
Browse files

Added error handling and css

parent 6c06bf69
Pipeline #58746 passed with stage
in 1 minute and 20 seconds
/*Ticker CSS: https://codepen.io/lewismcarey/pen/GJZVoG*/
@-webkit-keyframes ticker {
0% {
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
visibility: visible;
}
100% {
-webkit-transform: translate3d(-100%, 0, 0);
transform: translate3d(-100%, 0, 0);
}
}
@keyframes ticker {
0% {
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
visibility: visible;
}
100% {
-webkit-transform: translate3d(-100%, 0, 0);
transform: translate3d(-100%, 0, 0);
transform: translate3d(-350%, 0, 0);
}
}
.ticker-wrap {
width: 100%;
overflow: hidden;
height: 4rem;
background-color: rgba(0, 0, 0, 0.9);
height: 3rem;
background-color: #2F4F4F;
padding-left: 100%;
box-sizing: content-box;
}
.ticker {
display: inline-block;
height: 4rem;
line-height: 4rem;
height: 3rem;
line-height: 3rem;
white-space: nowrap;
padding-right: 100%;
box-sizing: content-box;
-webkit-animation-iteration-count: infinite;
animation-iteration-count: infinite;
-webkit-animation-timing-function: linear;
animation-timing-function: linear;
-webkit-animation-name: ticker;
animation-name: ticker;
-webkit-animation-duration: 20s;
animation-duration: 20s;
}
.ticker-item{
display: inline-block;
padding: 0 2rem;
padding: 0 4rem;
font-size: 2rem;
color: white;
color: #DCDCDC !important;
transition: background-color 1s;
}
.ticker-item:hover{
animation-play-state: paused;
background-color: #000000 !important;
color: white !important;
}
body {
background-color: #708090;
}
.categoryLinks{
display: inline-block !important;
font-size: 1.3em;
padding: .4em .8em !important;
margin: 0 .5em;
height: 100%;
background-color: #DCDCDC;
transition: background-color 1s;
}
.categoryLinks:hover{
background-color: #C0C0C0;
}
.singleArticleImage{
align-content: center;
}
......@@ -5,7 +5,7 @@
<link rel="stylesheet" href="bootstrap.min.css"/>
<link rel="stylesheet" href="bootstrap-grid.css" />
<link rel="stylesheet" href="index.css"/>
<title>React App</title>
<title>Some Webbased Newspaper (C)</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
......
......@@ -23,7 +23,7 @@ export class ArticleGrid extends Component<{ category?: number }> {
return (
<div className='container' key={1}>
{
articleStore.articles.filter(a => ((!this.props.category && a.important) || (a.category === this.props.category))).map(article => (
articleStore.articles.filter(a => ((this.props.category === undefined && a.important) || (a.category === this.props.category))).map(article => (
<div className='container m-4 article-box' key={article.id}>
<NavLink className='card-link' exact to={'/articles/article/' + article.id}>
<Card title={article.title}>
......@@ -61,20 +61,22 @@ export class SingleArticle extends Component<{ match: { params: { id: number } }
<div>
<Header/>
<Card title={articleStore.currentArticle.title}>
<Image alt={articleStore.currentArticle.title} src={articleStore.currentArticle.imageURL}
<Image className='singleArticleImage' alt={articleStore.currentArticle.title} src={articleStore.currentArticle.imageURL}
style={{width: '200px'}} fluid/>
<p>{articleStore.currentArticle.content}</p>
<Row>
<Column><Button.Info
onClick={() => (history.push("/articles/edit/" + articleStore.currentArticle.id))}>Edit this article</Button.Info></Column>
<Column><Button.Danger
onClick={() => articleStore.deleteCurrentArticle()}>Delete this article</Button.Danger></Column>
</Row>
</Card>
<Card title="Comments">
{
articleStore.currentArticle.comments.map(comment => <SingleComment key={comment.id}
comment={comment}/>)
}
</Card>
<Card title="">{articleStore.currentArticle.content}</Card>
<Row>
<Column><Button.Info
onClick={() => (history.push("/articles/edit/" + articleStore.currentArticle.id))}>Edit</Button.Info></Column>
<Column><Button.Danger
onClick={() => articleStore.deleteCurrentArticle()}>Delete</Button.Danger></Column>
</Row>
{
articleStore.currentArticle.comments.map(comment => <SingleComment key={comment.id}
comment={comment}/>)
}
<AddComment article_id={articleStore.currentArticle.id}/>
</div>
);
......@@ -115,7 +117,7 @@ class AddOrEditArticle extends Component {
<form>
<Row>
<Column width={2}>
<span className='input-group-text'>Author</span>
<span className='input-group-text'>Brukernavn</span>
</Column>
<Column>
<input type='text' id='authorValue' ref={this.authorRef}
......@@ -129,7 +131,7 @@ class AddOrEditArticle extends Component {
</Row>
<Row>
<Column width={2}>
<span className='input-group-text'>Title</span>
<span className='input-group-text'>Overskrift</span>
</Column>
<Column>
<input type='text' id='titleValue' ref={this.titleRef}
......@@ -143,7 +145,7 @@ class AddOrEditArticle extends Component {
</Row>
<Row>
<Column width={2}>
<span className='input-group-text'>Image</span>
<span className='input-group-text'>Bilde (Bare lenke)</span>
</Column>
<Column>
<input type="text" className='form-control' id="imageURL" ref={this.imageRef}
......@@ -155,21 +157,11 @@ class AddOrEditArticle extends Component {
}
}}/>
</Column>
<Column>
<div className="input-group-append">
<button className="btn btn-outline-secondary" type="button"
onClick={(event: SyntheticInputEvent<HTMLButtonElement>) => this.imageURL = document.getElementById("imageURL").value}>Upload
</button>
</div>
</Column>
<Column>
<Image alt={this.title} src={this.imageURL === null ? null : this.imageURL}/>
</Column>
</Row>
<Row>
<Column width={2}>
<div className='input-group-prepend'>
<span className='input-group-text'>Content</span>
<span className='input-group-text'>Innhold</span>
</div>
</Column>
<Column>
......@@ -181,23 +173,23 @@ class AddOrEditArticle extends Component {
</Row>
<Row>
<Column width={2}>
Category
Kategori
</Column>
<Column>
<select id='categorySelection' ref={this.categoryRef} defaultValue={1}
<select id='categorySelection' ref={this.categoryRef} defaultValue={0}
className="custom-select"
onChange={event => {
this.category = event.target.options[event.target.selectedIndex].value
}}>
<option value={1}>News</option>
<option value={2}>Sports</option>
<option value={3}>Culture</option>
{
articleStore.categories.map(c => <option value={c.id}>{c.text}</option>)
}
</select>
</Column>
</Row>
<Row>
<Column width={2}>
Important
Hovedsideverdig?
</Column>
<Column>
<input id='importantValue' ref={this.importantRef} type='checkbox' onClick={event => {
......
......@@ -14,7 +14,7 @@ export class SingleComment extends Component<{ comment: Comment }> {
<div className='card-text'>{this.props.comment.content}</div>
</Column>
<Column>
<Button.Danger onClick={() => this.deleteComment()}>Delete Comment</Button.Danger>
<Button.Danger onClick={() => this.deleteComment()}>Delete comment</Button.Danger>
</Column>
</Row>
</Card>
......
import React from 'react';
import ReactDOM from 'react-dom';
import {Category, Home} from './myWidgets';
import {HashRouter, Route} from "react-router-dom";
import {Category, FindError, Home, ServerError} from './myWidgets';
import {HashRouter, Route, Switch} from "react-router-dom";
import {articleStore} from "./stores";
import {AddArticle, EditArticle, SingleArticle} from "./ArticleWidgets";
import {history} from "./myWidgets";
const root: any = document.getElementById('root');
if (root)
articleStore.getCategories().then(res => {
if (res.status === 500){
ReactDOM.render(
<HashRouter>
<div>
<Route path="/" component={ServerError}/>
</div>
</HashRouter>,
root
);
history.push("/server-error");
}
else{
ReactDOM.render(
<HashRouter>
<Switch>
<Route exact path="/" component={Home} />
<Route exact path="/articles" component={Home}/>
<Route exact path="/articles/article" component={Home}/>
{
res.map(c => <Route key={c.id} exact path={"/articles/" + c.text} render={(props) => <Category {...props} category={c.id}/>}/>)
}
<Route exact path="/articles/article/:id" component={SingleArticle}/>
<Route exact path="/articles/create" component={AddArticle}/>
<Route exact path="/articles/edit/:article_id" component={EditArticle}/>
<Route exact path="/server-error" component={ServerError}/>
<Route component={FindError}/>
</Switch>
</HashRouter>,
root
);
}
}).catch(error => {
ReactDOM.render(
<HashRouter>
<div>
<Route exact path="/" component={Home} />
<Route exact path="/articles" component={Home}/>
<Route exact path="/articles/article" component={Home}/>
{
res.map(c => <Route key={c.id} exact path={"/articles/" + c.text} render={(props) => <Category {...props} category={c.id}/>}/>)
}
<Route exact path="/articles/article/:id" component={SingleArticle}/>
<Route exact path="/articles/create" component={AddArticle}/>
<Route exact path="/articles/edit/:article_id" component={EditArticle}/>
<Route path="/" component={ServerError}/>
</div>
</HashRouter>,
root
);
history.push("/server-error");
console.log(error);
});
......@@ -4,13 +4,13 @@ import {NavLink} from "react-router-dom";
import * as React from "react";
import {createHashHistory} from "history";
import {ArticleGrid} from "./ArticleWidgets";
import {Card} from "./widgets";
const Article = require('../src/Article');
export const history = createHashHistory();
export class Header extends Component {
render() {
return (
<nav className='navbar navbar-expand-sm bg-light navbar-light'>
......@@ -18,11 +18,11 @@ export class Header extends Component {
alt='koalalogo'
style={{width: '50px'}}/></NavLink>
<div className='navbar-nav w-100'>
<NavLink className='nav-link text-info' activeClassName='active' exact to='/'>HJEM</NavLink>
<NavLink className='nav-link text-info categoryLinks' activeClassName='active' exact to='/'>Hjem</NavLink>
<CategoryLinks/>
</div>
<div className='navbar-nav w-25'>
<NavLink className='nav-link text-info' activeClassName='active' exact to='/articles/create'>Create
<NavLink className='nav-link text-info categoryLinks' activeClassName='active' exact to='/articles/create'>Create
Article</NavLink>
</div>
</nav>
......@@ -60,7 +60,7 @@ class CategoryLinks extends Component{
return (
<div>
{
articleStore.categories.map(c => <NavLink key={c.id} className='nav-link text-info' activeClassName='active' exact
articleStore.categories.map(c => <NavLink key={c.id} className='nav-link text-info categoryLinks' activeClassName='active' exact
to={'/articles/' + c.text}>{c.text}</NavLink>)
}
</div>
......@@ -104,4 +104,29 @@ export class Ticker extends Component {
mounted() {
articleStore.getAllArticles().catch(error => console.log(error));
}
}
export class ServerError extends Component{
render(){
return(
<div>
<Card title="Server error 500">
<p>Beklager forstyrrelsen, vi jobber med saken. <NavLink exact to="/" className="nav-link text-info">Her er en link tilbake til hovedsiden.</NavLink></p>
<p>Om problemet fortsetter, vennligst prøv igjen senere.</p>
</Card>
</div>
);
}
}
export class FindError extends Component{
render(){
return(
<div>
<Card title="Error 404: Cannot find page">
<p>Vi beklager, men denne siden finnes ikke. <NavLink exact to="/" className="nav-link text-info">Her er en link tilbake til hovedsiden.</NavLink></p>
</Card>
</div>
);
}
}
\ No newline at end of file
......@@ -18,11 +18,34 @@ class ArticleStore {
categories: any = {};
getIds(){
return axios.get("http://localhost:4001/ids").then(response => response.data).then(data => data.map(element => element.id));
return axios.get("http://localhost:4000/ids").then(response => {
if (response.status === 500){
history.push("/server-error");
return null;
}
else{
return response.data;
}
}).then(data => data.map(element => element.id))
.catch(error => {
history.push("/server-error");
console.log(error);
});
}
getCategories(){
return axios.get('http://localhost:4001/categories').then(response => this.categories = response.data);
return axios.get('http://localhost:4000/categories').then(response => {
if (response.status === 500){
history.push("/server-error");
this.categories = null;
return null;
}
this.categories = response.data;
return this.categories;
}).catch(error => {
history.push("/server-error");
console.log(error);
});
}
createArticle(response): Article{
......@@ -32,56 +55,92 @@ class ArticleStore {
}
getArticle(id: number): Article{
return axios.get('http://localhost:4001/article/' + id).then(response => {
if (response.data[0]){
this.currentArticle = this.createArticle(response.data[0]);
let article: Article = this.articles.find(a => this.currentArticle.id === a.id);
if (article){
Object.assign(article, {...this.currentArticle});
}
}
else{
console.log(id + ' not found');
this.currentArticle = null;
}
return this.currentArticle;
}).then(() => {
if (this.currentArticle !== null){
this.getComments(id).then(res => this.currentArticle.addComments(res));
}
});
return axios.get('http://localhost:4000/article/' + id).then(response => {
if (response.status === 500){
history.push("/server-error");
this.currentArticle = null;
return null;
}
if (response.data[0]){
this.currentArticle = this.createArticle(response.data[0]);
let article: Article = this.articles.find(a => this.currentArticle.id === a.id);
if (article){
Object.assign(article, {...this.currentArticle});
}
}
else{
this.currentArticle = null;
history.push("/server-error");
console.log(id + ' not found');
}
return this.currentArticle;
}).then(() => {
if (this.currentArticle !== null){
this.getComments(id).then(res => this.currentArticle.addComments(res));
}
})
.catch(error => {
history.push("/server-error");
console.log(error);
});
}
getAllArticles(): Article[]{
return axios.get('http://localhost:4001/article').then(response => {
return axios.get('http://localhost:4000/article').then(response => {
if (response.status === 500){
this.articles = null;
history.push("/server-error");
return null;
}
this.articles = response.data.map(responsePart => this.createArticle(responsePart));
return this.articles;
}).catch(error => {
history.push("/server-error");
console.log(error);
});
}
deleteCurrentArticle(){
console.log('Deleting ' + this.currentArticle.id);
axios.delete('http://localhost:4001/article',
axios.delete('http://localhost:4000/article',
{
headers: header,
data: JSON.stringify({id: this.currentArticle.id})
}).then(() => {
this.articles = this.articles.filter(article => (article.id !== this.currentArticle.id));
this.currentArticle = null;
history.push('/')
}).then(response => {
if (response.status === 500){
this.currentArticle = null;
history.push("/server-error");
}
else{
this.articles = this.articles.filter(article => (article.id !== this.currentArticle.id));
this.currentArticle = null;
history.push('/')
}
}).catch(error => {
history.push("/server-error");
console.log(error);
});
}
getComments(id: number): Comment[]{
return axios.get('http://localhost:4001/comments/' + id,
return axios.get('http://localhost:4000/comments/' + id,
{
headers: header,
data: JSON.stringify({article_id: id})
}).then(response => {
if (response.status === 500){
history.push("/server-error");
return null;
}
if (response.data.length > 0){
return response.data.map(commentData => new Comment(commentData.id, commentData.author, commentData.content));
}
return undefined;
}).catch(error => {
history.push("/server-error");
console.log(error);
});
}
......@@ -98,7 +157,20 @@ class ArticleStore {
content: article.content
};
return axios.post('http://localhost:4001/article', JSON.stringify(data), {headers: header}).then(res => res).catch(error => console.log(error));
return axios.post('http://localhost:4000/article', JSON.stringify(data), {headers: header})
.then(res => {
if (res.status === 500){
history.push("/server-error");
return null;
}
else{
return res;
}
})
.catch(error => {
history.push("/server-error");
console.log(error);
});
}
editArticle(article: Article){
......@@ -125,8 +197,21 @@ class ArticleStore {
important: article.important,
};
return axios.put('http://localhost:4001/article/edit/' + article.id,
JSON.stringify(data), {headers: header}).then(res => res).catch(error => console.log(error));
return axios.put('http://localhost:4000/article/edit/' + article.id,
JSON.stringify(data), {headers: header})
.then(res => {
if (res.status === 500){
history.push("/server-error");
return null;
}
else{
return res;
}
})
.catch(error => {
history.push("/server-error");
console.log(error);
});
}
......@@ -139,9 +224,19 @@ class ArticleStore {
articles_id: comment.articles_id
};
return axios.post('http://localhost:4001/comment', JSON.stringify(data), {headers: header}).then(res => {
return res;
}).catch(error => console.log(error));
return axios.post('http://localhost:4000/comment', JSON.stringify(data), {headers: header})
.then(res => {
if (res.status === 500){
history.push("/server-error");
return null;
}
else{
return res;
}
}).catch(error => {