start over with nextjs

This commit is contained in:
Thomas Ruoff
2021-02-25 00:31:28 +01:00
parent 3a77e7f22b
commit 214d95d3d7
68 changed files with 3047 additions and 18576 deletions

8
components/App.test.tsx Normal file
View File

@@ -0,0 +1,8 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
it('renders without crashing', () => {
const div = document.createElement('div');
ReactDOM.render(<App />, div);
});

100
components/App.tsx Normal file
View File

@@ -0,0 +1,100 @@
import React, { Component } from 'react';
import LetterOptions from './LetterOptions';
import Button from './Button';
import Preview from './Preview';
import LatestList from './LatestList';
import {generatePdf, getLatest, removeLatest} from './apiHelper';
//import './App.css';
class App extends Component {
componentDidMount() {
this._getLatest();
}
render() {
const state = this.state || {};
return (
<div className="home">
<div>
<LetterOptions onChange={this._onChange.bind(this)} {...state.options}/>
<LatestList latest={state.latest} onSelect={this._onSelectLatest.bind(this)}
onRemove={this._onRemoveLatest.bind(this)}
/>
</div>
<div>
<div>
<Button onClick={this._onGenerate.bind(this)}>Backe PDF</Button>
<Button onClick={this._onClear.bind(this)}>Alles Löschen</Button>
</div>
<Preview
pdfUrl={state.pdfUrl}
pdfIsLoading={state.pdfIsLoading}
pdfError={state.pdfError}
/>
</div>
</div>
);
}
_onSelectLatest(selectedOptions) {
this.setState({options: Object.assign({}, selectedOptions)});
}
_onRemoveLatest(item) {
removeLatest(item)
.then(() => {
const latest = this.state.latest
.filter(curr => curr.id !== item.id);
this.setState({ latest });
});
}
_onClear() {
window.location.reload();
}
_onGenerate() {
const state = this.state || {};
this.setState({
pdfIsLoading: true,
pdfError: null
})
generatePdf(state.options)
.then((data) => {
const {id} = data;
this.setState({
pdfIsLoading: false,
pdfUrl: `/api/pdf/${id}`
});
})
.catch((error) => {
this.setState({
pdfIsLoading: false,
pdfError: error.message,
pdfUrl: null
});
})
.then(() => this._getLatest())
}
_getLatest() {
getLatest()
.then(latest => this.setState({latest}))
.catch(err => console.error('Unable to get latest:', err));
}
_onChange(name, event) {
if (!name) {
return;
}
const value = event && event.target && event.target.value;
const options = Object.assign({}, this.state.options, {[name]: value || undefined});
this.setState({options});
}
}
export default App;

7
components/Button.tsx Normal file
View File

@@ -0,0 +1,7 @@
import React from 'react';
export default function(props) {
return (
<button className="p-button" onClick={props.onClick}>{props.children}</button>
);
}

9
components/Header.tsx Normal file
View File

@@ -0,0 +1,9 @@
import React from 'react';
export default function() {
return (
<header className="header">
<h1>PDFer</h1>
</header>
)
}

16
components/Input.tsx Normal file
View File

@@ -0,0 +1,16 @@
import React from 'react';
export default function(props) {
return (
<label>
{props.text}
<input
className={props.name}
type="text"
placeholder={props.placeholder}
onChange={props.onchange.bind(null, props.name)}
value={props.value}
/>
</label>
);
}

24
components/LatestList.tsx Normal file
View File

@@ -0,0 +1,24 @@
import React from 'react';
import Button from './Button';
export default function(props) {
const latest = props.latest || [];
const latestElements = latest.map(item => {
const created = new Date(item.created);
const hrefId = `#item-${item.id}`;
return (
<li key={item.id}>
<a href={hrefId} onClick={() => props.onSelect(item)}>{created.toLocaleString()}</a>
<span> </span>
<Button onClick={() => props.onRemove(item)}>Remove</Button>
</li>
);
});
return (
<div>
<h4>Vergangene Briefe:</h4>
<ul>{latestElements}</ul>
</div>
);
}

14
components/Layout.tsx Normal file
View File

@@ -0,0 +1,14 @@
import React from 'react';
import Header from './Header';
export default function(props) {
return (
<div id="app">
<Header />
<div id="content">
{ props.children }
</div>
</div>
);
}

View File

@@ -0,0 +1,126 @@
import React from 'react';
import Collapsible from 'react-collapsible';
import Input from './Input';
import TextAreaInput from './TextAreaInput';
import Select from './Select';
//import './LetterOptions.css';
const EXAMPLE_ADDRESS = 'Max Mustermann\nMusterstr. 73\n12345 Musterstadt';
export default function(props) {
const templateTypeOptions = [
{
value: 'brief-fam',
name: 'Familie'
},
{
value: 'brief-valerie',
name: 'Valerie'
},
{
value: 'brief-rain',
name: 'Rain Baumeister'
},
{
value: 'brief-thomas',
name: 'Thomas'
}
];
return (
<div className="letter-options">
<Select
name="template"
text="Vorlage"
onchange={props.onChange}
value={props.template}
options={templateTypeOptions}
/>
<TextAreaInput
name="address"
text="Adresse"
placeholder={EXAMPLE_ADDRESS}
onchange={props.onChange}
value={props.address}
/>
<Input
name="subject"
text="Betreff"
placeholder="Betreffzeile"
onchange={props.onChange}
value={props.subject}
/>
<Collapsible trigger="Bezugszeichenzeile">
<Input
name="yourRef"
text="Ihr Zeichen"
onchange={props.onChange}
value={props.yourRef}
/>
<Input
name="yourMail"
text="Ihr Schreiben vom"
onchange={props.onChange}
value={props.yourMail}
/>
<Input
name="myRef"
text="Unser Zeichen"
onchange={props.onChange}
value={props.myRef}
/>
<Input
name="customer"
text="Kundernummer"
onchange={props.onChange}
value={props.customer}
/>
<Input
name="invoice"
text="Rechnung"
onchange={props.onChange}
value={props.invoice}
/>
</Collapsible>
<Collapsible trigger="Sonstige Einstellungen">
<Input
name="date"
text="Datum"
placeholder="Heute"
onchange={props.onChange}
value={props.date}
/>
<Input
name="opening"
text="Eröffnung"
placeholder="Sehr geehrte Damen und Herren"
onchange={props.onChange}
value={props.opening}
/>
<Input
name="closing"
text="Grußform"
placeholder="Mit freundlichen Grüßen"
onchange={props.onChange}
value={props.closing}
/>
<Input
name="specialMail"
text="Versandhinweis"
onchange={props.onChange}
value={props.specialMail}
/>
</Collapsible>
<TextAreaInput
name="body"
text="Brieftext"
onchange={props.onChange}
placeholder="Inhalt des Briefes"
value={props.body}
/>
</div>
);
};

19
components/List.tsx Normal file
View File

@@ -0,0 +1,19 @@
import * as React from 'react'
import ListItem from './ListItem'
import { User } from '../interfaces'
type Props = {
items: User[]
}
const List = ({ items }: Props) => (
<ul>
{items.map((item) => (
<li key={item.id}>
<ListItem data={item} />
</li>
))}
</ul>
)
export default List

16
components/ListDetail.tsx Normal file
View File

@@ -0,0 +1,16 @@
import * as React from 'react'
import { User } from '../interfaces'
type ListDetailProps = {
item: User
}
const ListDetail = ({ item: user }: ListDetailProps) => (
<div>
<h1>Detail for {user.name}</h1>
<p>ID: {user.id}</p>
</div>
)
export default ListDetail

18
components/ListItem.tsx Normal file
View File

@@ -0,0 +1,18 @@
import React from 'react'
import Link from 'next/link'
import { User } from '../interfaces'
type Props = {
data: User
}
const ListItem = ({ data }: Props) => (
<Link href="/users/[id]" as={`/users/${data.id}`}>
<a>
{data.id}: {data.name}
</a>
</Link>
)
export default ListItem

37
components/Preview.tsx Normal file
View File

@@ -0,0 +1,37 @@
import React from 'react';
export default props => {
if (props.pdfIsLoading) {
return (
<div>Lade&hellip;</div>
);
}
const errorStyles = {
padding: '6px 12px',
color: 'white',
backgroundColor: '#d81e1e',
borderRadius: '3px',
};
if (props.pdfError) {
return (
<div style={errorStyles}><span role="img" aria-label="Crying Man">😢</span> {props.pdfError}</div>
);
}
if (!props.pdfUrl) {
return (
<div>Knopf drücken dann gibts hier was zu sehen!</div>
);
}
const styles = {
width: '700px',
height: '1050px'
};
return (
<embed src={props.pdfUrl} style={styles} type="application/pdf" />
);
};

18
components/Select.tsx Normal file
View File

@@ -0,0 +1,18 @@
import React from 'react';
export default (props) => {
const { options = [] } = props;
return (
<label>
{props.text}
<select
className={props.name}
onChange={props.onchange.bind(null, props.name)}
value={props.value}
>
{options.map((option) => <option value={option.value} key={option.value}>{option.name}</option>)}
</select>
</label>
);
}

View File

@@ -0,0 +1,15 @@
import React from 'react';
export default function(props) {
return (
<label>
{props.text}
<textarea
className={props.name}
placeholder={props.placeholder}
onChange={props.onchange.bind(null, props.name)}
value={props.value}
/>
</label>
);
}

32
components/apiHelper.tsx Normal file
View File

@@ -0,0 +1,32 @@
function checkStatus(res) {
if (res.status < 200 || res.status >= 400) {
throw new Error(`Something went wrong (Status ${res.status}) - I do feel very sorry!`);
}
return res;
}
export function generatePdf(state){
const options = {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(state)
};
return fetch('/api/pdf/generate/brief', options)
.then(checkStatus)
.then(res => res.json());
}
export function getLatest() {
return fetch('/api/pdf/latest')
.then(checkStatus)
.then(res => res.json());
}
export function removeLatest(item) {
return fetch(`/api/pdf/latest/${item.id}`, {method: 'DELETE'})
.then(checkStatus);
}

9
components/index.tsx Normal file
View File

@@ -0,0 +1,9 @@
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import './index.css';
ReactDOM.render(
<App />,
document.getElementById('root')
);