mirror of
https://github.com/tomru/pdfer.git
synced 2026-03-03 06:27:19 +01:00
start over with nextjs
This commit is contained in:
8
components/App.test.tsx
Normal file
8
components/App.test.tsx
Normal 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
100
components/App.tsx
Normal 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
7
components/Button.tsx
Normal 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
9
components/Header.tsx
Normal 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
16
components/Input.tsx
Normal 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
24
components/LatestList.tsx
Normal 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
14
components/Layout.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
126
components/LetterOptions.tsx
Normal file
126
components/LetterOptions.tsx
Normal 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
19
components/List.tsx
Normal 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
16
components/ListDetail.tsx
Normal 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
18
components/ListItem.tsx
Normal 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
37
components/Preview.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import React from 'react';
|
||||
|
||||
export default props => {
|
||||
if (props.pdfIsLoading) {
|
||||
return (
|
||||
<div>Lade…</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
18
components/Select.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
15
components/TextAreaInput.tsx
Normal file
15
components/TextAreaInput.tsx
Normal 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
32
components/apiHelper.tsx
Normal 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
9
components/index.tsx
Normal 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')
|
||||
);
|
||||
Reference in New Issue
Block a user