make client own package (using create-react-app)

This commit is contained in:
Thomas Ruoff
2017-02-23 00:45:39 +01:00
parent 8bf72b470e
commit 2441268fb8
44 changed files with 1816 additions and 576 deletions

12
client/.editorconfig Normal file
View File

@@ -0,0 +1,12 @@
; EditorConfig file: http://EditorConfig.org
; Install the "EditorConfig" plugin into Sublime Text to use
root = true
[*]
charset = utf-8
end_of_line = lf
insert_final_newline = true
indent_style = space
indent_size = 2
trim_trailing_whitespace = true

18
client/.gitignore vendored Normal file
View File

@@ -0,0 +1,18 @@
# See http://help.github.com/ignore-files/ for more about ignoring files.
# dependencies
/node_modules
# testing
/coverage
# production
/build
# misc
.DS_Store
.env
npm-debug.log*
yarn-debug.log*
yarn-error.log*

1356
client/README.md Normal file

File diff suppressed because it is too large Load Diff

21
client/package.json Normal file
View File

@@ -0,0 +1,21 @@
{
"name": "pdfer",
"version": "0.1.0",
"private": true,
"devDependencies": {
"react-scripts": "0.9.0"
},
"dependencies": {
"react": "^15.4.2",
"react-collapsible": "^1.3.0",
"react-dom": "^15.4.2"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test --env=jsdom",
"eject": "react-scripts eject"
},
"proxy": "http://localhost:5000",
"homepage": "http://pi"
}

BIN
client/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

32
client/public/index.html Normal file
View File

@@ -0,0 +1,32 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<!--
Notice the use of %PUBLIC_URL% in the tag above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>PDFer</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css" />
</head>
<body>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start`.
To create a production bundle, use `npm run build`.
-->
</body>
</html>

17
client/src/App.css Normal file
View File

@@ -0,0 +1,17 @@
header h1 {
margin-left: 12px;
}
button {
display: block;
margin-top: 1em;
margin-bottom: 12px;
}
.home {
display: flex;
}
.home > div {
padding: 12px;
}

63
client/src/App.js Normal file
View File

@@ -0,0 +1,63 @@
import React, { Component } from 'react';
import LetterOptions from './LetterOptions';
import Button from './Button';
import Preview from './Preview';
import {generatePdf} from './apiHelper';
import './App.css';
class App extends Component {
render() {
const state = this.state || {};
return (
<div className="home">
<div>
<LetterOptions onChange={this._onChange.bind(this)} />
</div>
<div>
<Button onClick={this._onGenerate.bind(this)} text="Backe PDF"/>
<Preview
pdfUrl={state.pdfUrl}
pdfIsLoading={state.pdfIsLoading}
pdfError={state.pdfError}
/>
</div>
</div>
);
}
_onGenerate() {
const state = this.state || {};
this.setState({
pdfIsLoading: true,
pdfError: null
})
generatePdf(state)
.then((data) => {
const {id} = data;
this.setState({
pdfIsLoading: false,
pdfUrl: `/api/pdf/${id}`
});
})
.catch((error) => {
this.setState({
pdfIsLoading: false,
pdfError: error,
pdfUrl: null
});
});
}
_onChange(name, event) {
if (!name) {
return;
}
const value = event && event.target && event.target.value;
this.setState({[name]: value || undefined})
}
}
export default App;

8
client/src/App.test.js 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);
});

7
client/src/Button.js Normal file
View File

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

9
client/src/Header.js Normal file
View File

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

15
client/src/Input.js Normal file
View File

@@ -0,0 +1,15 @@
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)}
/>
</label>
);
}

14
client/src/Layout.js 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

@@ -1,15 +1,3 @@
html {
box-sizing: border-box;
}
*, *:before, *:after {
box-sizing: inherit;
}
header h1 {
margin-left: 12px;
}
.letter-options { .letter-options {
width: 320px; width: 320px;
} }
@@ -28,13 +16,24 @@ header h1 {
width: 100%; width: 100%;
} }
.Collapsible { .letter-options textarea {
resize: vertical;
}
.letter-options textarea.address {
height: 80px;
}
.letter-options textarea.body {
height: 340px;
}
.letter-options .Collapsible {
background-color: #ddd; background-color: #ddd;
border-radius: 2px; border-radius: 2px;
} }
.letter-options .Collapsible__trigger {
.Collapsible__trigger {
padding-left: 6px; padding-left: 6px;
cursor: pointer; cursor: pointer;
line-height: 24px; line-height: 24px;
@@ -42,47 +41,21 @@ header h1 {
font-weight: bold; font-weight: bold;
} }
.Collapsible__trigger.is-closed:before { .letter-options .Collapsible__trigger.is-closed:before {
content: "> "; content: "> ";
} }
.Collapsible__trigger.is-open:before { .letter-options .Collapsible__trigger.is-open:before {
content: "< "; content: "< ";
} }
.Collapsible { .letter-options .Collapsible {
margin-bottom: 12px; margin-bottom: 12px;
} }
.Collapsible__contentInner { .letter-options .Collapsible__contentInner {
background-color: #fff; background-color: #fff;
padding-top: 12px; padding-top: 12px;
padding-left: 12px; padding-left: 12px;
} }
textarea {
resize: vertical;
}
textarea.address {
height: 80px;
}
textarea.body {
height: 340px;
}
button {
display: block;
margin-top: 1em;
margin-bottom: 12px;
}
.home {
display: flex;
}
.home > div {
padding: 12px;
}

120
client/src/LetterOptions.js Normal file
View File

@@ -0,0 +1,120 @@
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="customer"
text="Kundernummer"
onchange={props.onChange}
value={props.customer}
/>
<Input
name="invoice"
text="Rechnung"
onchange={props.onChange}
value={props.invoice}
/>
<Input
name="yourMail"
text="Ihr Schreiben vom"
onchange={props.onChange}
value={props.yourMail}
/>
</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}
/>
</Collapsible>
<TextAreaInput
name="body"
text="Brieftext"
onchange={props.onChange}
placeholder="Inhalt des Briefes"
value={props.body}
/>
</div>
);
};

30
client/src/Preview.js Normal file
View File

@@ -0,0 +1,30 @@
import React from 'react';
export default props => {
if (props.pdfIsLoading) {
return (
<div>Lade...</div>
);
}
if (props.pdfError) {
return (
<div>{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" />
);
};

17
client/src/Select.js Normal file
View File

@@ -0,0 +1,17 @@
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)}
>
{options.map((option) => <option label={option.name} value={option.value} key={option.value}/>)}
</select>
</label>
);
}

View File

@@ -0,0 +1,14 @@
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)}
/>
</label>
);
}

15
client/src/apiHelper.js Normal file
View File

@@ -0,0 +1,15 @@
export function generatePdf(state){
return fetch('/api/pdf/generate/brief', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(state)
}).then(function(res) {
if (res.status !== 200) {
console.error('request failed');
return;
}
return res.json();
});
}

7
client/src/index.css Normal file
View File

@@ -0,0 +1,7 @@
html {
box-sizing: border-box;
}
*, *:before, *:after {
box-sizing: inherit;
}

9
client/src/index.js 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')
);

7
client/src/logo.svg Normal file
View File

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 841.9 595.3">
<g fill="#61DAFB">
<path d="M666.3 296.5c0-32.5-40.7-63.3-103.1-82.4 14.4-63.6 8-114.2-20.2-130.4-6.5-3.8-14.1-5.6-22.4-5.6v22.3c4.6 0 8.3.9 11.4 2.6 13.6 7.8 19.5 37.5 14.9 75.7-1.1 9.4-2.9 19.3-5.1 29.4-19.6-4.8-41-8.5-63.5-10.9-13.5-18.5-27.5-35.3-41.6-50 32.6-30.3 63.2-46.9 84-46.9V78c-27.5 0-63.5 19.6-99.9 53.6-36.4-33.8-72.4-53.2-99.9-53.2v22.3c20.7 0 51.4 16.5 84 46.6-14 14.7-28 31.4-41.3 49.9-22.6 2.4-44 6.1-63.6 11-2.3-10-4-19.7-5.2-29-4.7-38.2 1.1-67.9 14.6-75.8 3-1.8 6.9-2.6 11.5-2.6V78.5c-8.4 0-16 1.8-22.6 5.6-28.1 16.2-34.4 66.7-19.9 130.1-62.2 19.2-102.7 49.9-102.7 82.3 0 32.5 40.7 63.3 103.1 82.4-14.4 63.6-8 114.2 20.2 130.4 6.5 3.8 14.1 5.6 22.5 5.6 27.5 0 63.5-19.6 99.9-53.6 36.4 33.8 72.4 53.2 99.9 53.2 8.4 0 16-1.8 22.6-5.6 28.1-16.2 34.4-66.7 19.9-130.1 62-19.1 102.5-49.9 102.5-82.3zm-130.2-66.7c-3.7 12.9-8.3 26.2-13.5 39.5-4.1-8-8.4-16-13.1-24-4.6-8-9.5-15.8-14.4-23.4 14.2 2.1 27.9 4.7 41 7.9zm-45.8 106.5c-7.8 13.5-15.8 26.3-24.1 38.2-14.9 1.3-30 2-45.2 2-15.1 0-30.2-.7-45-1.9-8.3-11.9-16.4-24.6-24.2-38-7.6-13.1-14.5-26.4-20.8-39.8 6.2-13.4 13.2-26.8 20.7-39.9 7.8-13.5 15.8-26.3 24.1-38.2 14.9-1.3 30-2 45.2-2 15.1 0 30.2.7 45 1.9 8.3 11.9 16.4 24.6 24.2 38 7.6 13.1 14.5 26.4 20.8 39.8-6.3 13.4-13.2 26.8-20.7 39.9zm32.3-13c5.4 13.4 10 26.8 13.8 39.8-13.1 3.2-26.9 5.9-41.2 8 4.9-7.7 9.8-15.6 14.4-23.7 4.6-8 8.9-16.1 13-24.1zM421.2 430c-9.3-9.6-18.6-20.3-27.8-32 9 .4 18.2.7 27.5.7 9.4 0 18.7-.2 27.8-.7-9 11.7-18.3 22.4-27.5 32zm-74.4-58.9c-14.2-2.1-27.9-4.7-41-7.9 3.7-12.9 8.3-26.2 13.5-39.5 4.1 8 8.4 16 13.1 24 4.7 8 9.5 15.8 14.4 23.4zM420.7 163c9.3 9.6 18.6 20.3 27.8 32-9-.4-18.2-.7-27.5-.7-9.4 0-18.7.2-27.8.7 9-11.7 18.3-22.4 27.5-32zm-74 58.9c-4.9 7.7-9.8 15.6-14.4 23.7-4.6 8-8.9 16-13 24-5.4-13.4-10-26.8-13.8-39.8 13.1-3.1 26.9-5.8 41.2-7.9zm-90.5 125.2c-35.4-15.1-58.3-34.9-58.3-50.6 0-15.7 22.9-35.6 58.3-50.6 8.6-3.7 18-7 27.7-10.1 5.7 19.6 13.2 40 22.5 60.9-9.2 20.8-16.6 41.1-22.2 60.6-9.9-3.1-19.3-6.5-28-10.2zM310 490c-13.6-7.8-19.5-37.5-14.9-75.7 1.1-9.4 2.9-19.3 5.1-29.4 19.6 4.8 41 8.5 63.5 10.9 13.5 18.5 27.5 35.3 41.6 50-32.6 30.3-63.2 46.9-84 46.9-4.5-.1-8.3-1-11.3-2.7zm237.2-76.2c4.7 38.2-1.1 67.9-14.6 75.8-3 1.8-6.9 2.6-11.5 2.6-20.7 0-51.4-16.5-84-46.6 14-14.7 28-31.4 41.3-49.9 22.6-2.4 44-6.1 63.6-11 2.3 10.1 4.1 19.8 5.2 29.1zm38.5-66.7c-8.6 3.7-18 7-27.7 10.1-5.7-19.6-13.2-40-22.5-60.9 9.2-20.8 16.6-41.1 22.2-60.6 9.9 3.1 19.3 6.5 28.1 10.2 35.4 15.1 58.3 34.9 58.3 50.6-.1 15.7-23 35.6-58.4 50.6zM320.8 78.4z"/>
<circle cx="420.9" cy="296.5" r="45.7"/>
<path d="M520.5 78.1z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@@ -1,8 +0,0 @@
module.exports = {
presets: [
['es2015', {modules: false}]
],
plugins: [
['transform-react-jsx', {pragma: 'h'}]
]
};

View File

@@ -1,47 +0,0 @@
const { join } = require('path');
const webpack = require('webpack');
const Dashboard = require('webpack-dashboard/plugin');
const Clean = require('clean-webpack-plugin');
const Copy = require('copy-webpack-plugin');
const HTML = require('html-webpack-plugin');
const uglify = require('./uglify');
const babel = require('./babel');
const root = join(__dirname, '..');
module.exports = isProd => {
// base plugins array
const plugins = [
new Clean(['dist'], { root }),
new Copy([{ context: 'webapp/static/', from: '**/*.*' }]),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify(isProd ? 'production' : 'development')
}),
new HTML({ template: 'webapp/index.html' }),
new webpack.LoaderOptionsPlugin({
options: {
babel
}
})
];
if (isProd) {
plugins.push(
new webpack.LoaderOptionsPlugin({ minimize: true, debug: false }),
new webpack.optimize.UglifyJsPlugin(uglify)
);
} else {
// dev only
plugins.push(
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin(),
new webpack.DefinePlugin({
__DEVELOPMENT__: !isProd
}),
new Dashboard()
);
}
return plugins;
};

View File

@@ -1,16 +0,0 @@
module.exports = {
output: {
comments: 0
},
compress: {
unused: 1,
warnings: 0,
comparisons: 1,
conditionals: 1,
negate_iife: 0, // <- for `LazyParseWebpackPlugin()`
dead_code: 1,
if_return: 1,
join_vars: 1,
evaluate: 1
}
};

View File

@@ -1,46 +0,0 @@
const { join } = require('path');
const setup = require('./setup');
const dist = join(__dirname, '../dist');
const exclude = /node_modules/;
module.exports = env => {
const isProd = env && env.production;
return {
entry: {
app: './webapp/index.js',
vendor: [
'preact'
]
},
output: {
path: dist,
filename: '[name].[hash].js',
publicPath: '/'
},
resolve: {
alias: {
'react': 'preact-compat',
'react-dom': 'preact-compat'
}
},
module: {
rules: [{
test: /\.jsx?$/,
exclude: exclude,
loader: 'babel-loader'
}]
},
plugins: setup(isProd),
devtool: !isProd && 'source-map',
devServer: {
contentBase: dist,
port: process.env.PORT || 5050,
historyApiFallback: true,
compress: isProd,
inline: !isProd,
hot: !isProd
}
};
};

View File

@@ -9,8 +9,8 @@ const {getPdfPath} = require('./utils');
app.use(bodyParser.json()); app.use(bodyParser.json());
app.options('/pdf/generate/:template', cors()); app.options('/api/pdf/generate/:template', cors());
app.post('/pdf/generate/:template', cors(), (req, res) => { app.post('/api/pdf/generate/:template', cors(), (req, res) => {
const templateName = req.params.template; const templateName = req.params.template;
const options = req.body; const options = req.body;
@@ -35,12 +35,12 @@ app.post('/pdf/generate/:template', cors(), (req, res) => {
}); });
}); });
app.options('/pdf/:id', cors()); app.options('/api/pdf/:id', cors());
app.get('/pdf/:id', cors(), (req, res) => { app.get('/api/pdf/:id', cors(), (req, res) => {
const {id} = req.params; const {id} = req.params;
res.sendFile(getPdfPath(id)); res.sendFile(getPdfPath(id));
}); });
app.use(express.static('dist')); app.use(express.static('./client/build'));
app.listen(5000); app.listen(5000);

View File

@@ -5,10 +5,8 @@
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack --env.production --config config/webpack",
"watch": "webpack-dashboard -- webpack-dev-server --config config/webpack",
"start": "node index.js", "start": "node index.js",
"server:watch": "nodemon index.js" "watch": "nodemon index.js"
}, },
"author": "Thomas Ruoff <tomru@mail.id0.link>", "author": "Thomas Ruoff <tomru@mail.id0.link>",
"license": "ISC", "license": "ISC",
@@ -16,27 +14,10 @@
"body-parser": "^1.16.1", "body-parser": "^1.16.1",
"cors": "^2.8.1", "cors": "^2.8.1",
"express": "^4.14.1", "express": "^4.14.1",
"preact": "^7.2.0",
"preact-compat": "^3.13.1",
"preact-router": "^2.4.1",
"react-pdf": "0.0.10",
"uuid": "^3.0.1" "uuid": "^3.0.1"
}, },
"devDependencies": { "devDependencies": {
"babel-core": "^6.23.1",
"babel-loader": "^6.3.0",
"babel-plugin-transform-react-jsx": "^6.23.0",
"babel-preset-es2015": "^6.22.0",
"babel-preset-react": "^6.23.0",
"clean-webpack-plugin": "^0.1.15",
"copy-webpack-plugin": "^4.0.1",
"eslint": "^3.16.0", "eslint": "^3.16.0",
"html-webpack-plugin": "^2.28.0", "nodemon": "^1.11.0"
"nodemon": "^1.11.0",
"react-collapsible": "^1.2.1",
"styled-components": "^1.4.3",
"webpack": "^2.2.1",
"webpack-dashboard": "^0.3.0",
"webpack-dev-server": "^2.3.0"
} }
} }

View File

@@ -1,25 +0,0 @@
{
"parser": "babel-eslint",
"parserOptions": {
"ecmaVersion": 8,
"ecmaFeatures": {
"jsx": true
},
"sourceType": "module"
},
"env": {
"browser": true,
"es6": true
},
"plugins": [
"react"
],
"settings": {
"react": {
"pragma": "h"
}
},
"globals": {
"require": true,
}
}

View File

@@ -1,15 +0,0 @@
export function generatePdf(state){
return fetch('//localhost:5000/pdf/generate/brief', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(state)
}).then(function(res) {
if (res.status !== 200) {
console.error('request failed');
return;
}
return res.json();
});
}

View File

@@ -1,16 +0,0 @@
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8">
<title>PDFer</title>
<meta name="application-name" content="PDFer">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/normalize/5.0.0/normalize.min.css" />
<link rel="stylesheet" href="/styles.css">
</head>
<body>
<div id="root">
<div id="app">Loading</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fetch/2.0.2/fetch.min.js"></script>
</body>
</html>

View File

@@ -1,18 +0,0 @@
import { render } from 'preact';
function init() {
const App = require('./views').default;
render(App, document.getElementById('root'), document.getElementById('app'));
}
init();
if (process.env.NODE_ENV === 'production') {
console.log('What are ya looking at?');
} else {
require('preact/devtools');
// listen for HMR
if (module.hot) {
module.hot.accept('./views', init);
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@@ -1,7 +0,0 @@
import { h } from 'preact';
export default function(props) {
return (
<button onClick={props.onClick}>{props.text}</button>
);
}

View File

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

View File

@@ -1,15 +0,0 @@
import { h } from 'preact';
export default function(props) {
return (
<label>
{props.text}
<input
class={props.name}
type="text"
placeholder={props.placeholder}
onchange={props.onchange.bind(null, props.name)}
/>
</label>
);
}

View File

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

View File

@@ -1,117 +0,0 @@
import { h } from 'preact';
import Collapsible from 'react-collapsible';
import Input from './Input';
import TextAreaInput from './TextAreaInput';
import Select from './Select';
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="customer"
text="Kundernummer"
onchange={props.onChange}
value={props.customer}
/>
<Input
name="invoice"
text="Rechnung"
onchange={props.onChange}
value={props.invoice}
/>
<Input
name="yourMail"
text="Ihr Schreiben vom"
onchange={props.onChange}
value={props.yourMail}
/>
</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}
/>
</Collapsible>
<TextAreaInput
name="body"
text="Brieftext"
onchange={props.onChange}
placeholder="Inhalt des Briefes"
value={props.body}
/>
</div>
);
};

View File

@@ -1,25 +0,0 @@
import { h } from 'preact';
export default props => {
if (props.pdfIsLoading) {
return (
<div>Lade...</div>
);
}
if (props.pdfError) {
return (
<div>{props.pdfError}</div>
);
}
if (!props.pdfUrl) {
return (
<div>Knopf drücken dann gibts hier was zu sehen!</div>
);
}
return (
<iframe src={props.pdfUrl} style="width:700px; height:1050px;" frameborder="0"></iframe>
);
};

View File

@@ -1,27 +0,0 @@
import { h } from 'preact';
export default (props) => {
const { options = [] } = props;
const optionsMarkup = options.map(option => {
return (
<option
label={option.name}
value={option.value}
selected={props.value === option.value}
/>
);
});
return (
<label>
{props.text}
<select
class={props.name}
onchange={props.onchange.bind(null, props.name)}
>
{optionsMarkup}
</select>
</label>
);
}

View File

@@ -1,14 +0,0 @@
import { h } from 'preact';
export default function(props) {
return (
<label>
{props.text}
<textarea
class={props.name}
placeholder={props.placeholder}
onchange={props.onchange.bind(null, props.name)}
/>
</label>
);
}

View File

@@ -1,15 +0,0 @@
import { h } from 'preact'
import { Router } from 'preact-router';
import Home from './pages/Home';
import Layout from './components/Layout';
import Error404 from './pages/404';
export default (
<Layout>
<Router>
<Home path="/" />
<Error404 default />
</Router>
</Layout>
);

View File

@@ -1,10 +0,0 @@
import { h } from 'preact';
export default function(props) {
return (
<div>
<h2>No hope!</h2>
<p>Not sure what you desire, but look for somewhere else...</p>
</div>
);
}

View File

@@ -1,62 +0,0 @@
import { Component, h } from 'preact';
import PDF from 'react-pdf';
import LetterOptions from '../components/LetterOptions';
import Button from '../components/Button';
import Preview from '../components/Preview';
import {generatePdf} from '../../apiHelper';
class Home extends Component {
render(props) {
const component = this;
return (
<div className="home">
<div>
<LetterOptions onChange={this._onChange.bind(this)} />
</div>
<div>
<Button onClick={this._onGenerate.bind(this)} text="Backe PDF"/>
<Preview
pdfUrl={this.state.pdfUrl}
pdfIsLoading={this.state.pdfIsLoading}
pdfError={this.state.pdfError}
/>
</div>
</div>
);
}
_onGenerate() {
this.setState({
pdfIsLoading: true,
pdfError: null
})
generatePdf(this.state)
.then((data) => {
const {id} = data;
this.setState({
pdfIsLoading: false,
pdfUrl: `//localhost:5000/pdf/${id}`
});
})
.catch((error) => {
this.setState({
pdfIsLoading: false,
pdfError: error,
pdfUrl: null
});
});
}
_onChange(name, event) {
if (!name) {
return;
}
const value = event && event.target && event.target.value;
this.setState({[name]: value || undefined})
}
}
export default Home;