Commit 7cf06998 authored by Micha Lutz's avatar Micha Lutz
Browse files

Initial Commit

parents
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
# testing
/coverage
# production
/build
# misc
package-lock.json
config.js
\ No newline at end of file
# Sender App
Diese Web-Anwendung von fortiss GmbH ist ein Referenz-Client für die Application Sender API des FIT-Connect PoC. Sie wurde als Minimal Viable Product (MVP) entwickelt, um die Grundidee des FIT-Connect PoC aus Sicht eines BürgerInnen-Portals zu demonstrieren.
Die Sender App wird durch die Subscriber App ergänzt, welche den FIT-Connect PoC aus Sicht eines Behördenportals demonstriert:
https://git.fortiss.org/fit-connect/subscriber-app
_*** ENGLISH VERSION ***_
_This web application by fortiss GmbH is a reference client for the Application Sender API of the FIT-Connect PoC. It has been developed as a minimal viable product (MVP) to showcase the main idea of the FIT-Connect PoC from the point of view of a citizen portal._
_The Sender App is complemented by the Subscriber App, which demonstrates the FIT-Connect PoC from the point of view of a government agency portal:
https://git.fortiss.org/fit-connect/subscriber-app_
## FIT-Connect PoC
FIT-Connect wird als PoC von [FITKO](https://www.fitko.de/) bereitgestellt. Das Ziel von FIT-Connect ist eine einfache, schnelle Integration von Anwendungen zu OZG-Verfahren in medienbruchfreie Antragsprozesse.
Weitere Informationen sind in der FIT-Connect Dokumentation erhältlich: https://docs.fiep-poc.de/docs
## Funktionsweise der Sender App
Die Web-Anwendung basiert auf dem Node JS React Framework. Beim Aufrufen der Web-Anwendung wird die Komponente src/App.js geladen, welche initial folgende Schritte durchführt:
1. Access Token vom Authentication Provider abrufen (Zugangsdaten in src/config.js)
Anschließend wird ein Formular angezeigt, in das folgende Daten eingegeben werden können:
1. Destination (Zustellpunkt einer Behörde) aus der Subscriber App
2. Daten zum Antragsteller / zur Antragstellerin
3. Daten zur zu beantragenden Verwaltungsleistung
4. Daten aus dem Antragsformular gemäß XFall-Spezifikation
5. Dokumente zum Antrag
Sobald alle Daten eingegeben wurden kann über einen Button der Antrag zum Zustellpunkt der Behörde der Subscriber App gesendet werden und über einen weiteren Button der Status des Antrags abgefragt werden.
## Installation
Befolgen Sie die folgenden Schritte, um die Web-Anwendung lokal zu installieren
### 1. Node JS und NPM installieren
Falls Node JS und NPM noch nicht auf Ihrem Rechner installiert sind, laden Sie die für Ihr System passende Installationsdatei hier herunter und führen diese aus: https://nodejs.org/en/download/
Im Anschluss sollten folgende Befehle in der Eingabeaufforderung die entsprechenden Versionsnummern fehlerfrei anzeigen:
```
node --version
npm --version
```
Diese Web-Anwendung wurde unter der Node JS Version 12.16.1 entwickelt.
### 2. Quellcode herunterladen
Laden Sie den Quellcode aus diesem Git Repository in ein neues Verzeichnis auf Ihren Rechner. Sie können hierzu in Gitlab den Download-Button nutzen, um den Quellcode als ZIP-Datei herunterzuladen und diese anschließend zu entpacken.
### 3. FIT-Connect Zugangsdaten
Kopieren Sie in dem Verzeichnis, in dem sich nun der Quellcode der Web-Anwendung befindet, die Datei `src/config.js.template` in die Datei `src/config.js` und fügen Sie dort Application Id, Client Id, Client Secret und Scope aus dem FIT-Connect-Portal ein.
### 4. Anwendung installieren
Öffnen Sie die Eingabeaufforderung und wechseln sie in das Verzeichnis, in dem sich nun der Quellcode der Web-Anwendung befindet. Führen Sie anschließend folgenden Befehl aus. Es werden nun alle für diese Anwendung benötigten Node JS Pakete in das Unterverzeichnis node_modules heruntergeladen.
```
npm install
```
### 5. Anwendung im Entwicklermodus starten
Führen Sie abschließend folgenden Befehl aus. Die Web-Anwendung öffnet sich in einem neuen Browser fenster. Sie können die Anwendung beenden, indem sie in der Eingabeaufforderung Strg+C und J eingeben oder die Eingabeaufforderung schließen.
```
npm start
```
## Kontakt
Diese Anwendung wurde von Micha Lutz bei fortiss GmbH entwickelt. Kontakt: Peter Kuhn, pkuhn@fortiss.org
## Lizenz
This project is licensed under the terms of the MIT license.
Copyright (c) 2020 fortiss GmbH
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
{
"name": "sender-app",
"version": "0.1.0",
"private": true,
"dependencies": {
"@material-ui/core": "^4.10.2",
"@material-ui/icons": "^4.9.1",
"axios": "^0.19.2",
"react": "^16.13.1",
"react-dom": "^16.13.1",
"react-scripts": "3.4.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
}
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width" />
<title>Sender App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
import React, { useState, useEffect } from "react";
import {
AppBar,
Button,
CircularProgress,
Container,
FormControl,
FormLabel,
Grid,
IconButton,
Menu,
MenuItem,
TextField,
Toolbar,
Typography,
} from "@material-ui/core";
import MenuIcon from "@material-ui/icons/Menu";
import { getAccessToken, sendApplication, getStatus } from "./requests";
import { useStyles } from "./styles";
function App() {
// load styles
const classes = useStyles();
// state variables and hook functions
const [status, setStatus] = useState("init");
const [accessToken, setAccessToken] = useState("");
const [destinationId, setDestinationId] = useState("");
const [applicationApplicant, setApplicationApplicant] = useState({ givenName: "Werner", familyName: "Mustermann" });
const [applicationServiceType, setApplicationServiceType] = useState({
leikaId: "99050012104000",
name: "Gewerbeanmeldung",
});
const [applicationData, setApplicationData] = useState(
JSON.stringify(
{
field1: "data1",
field2: { field21: "data21", field22: "data22" },
},
null,
2
)
);
const [applicationId, setApplicationId] = useState("");
const [applicationStatus, setApplicationStatus] = useState("");
const [menuAnchorEl, setMenuAnchorEl] = useState(null);
// execute this callback once page has rendered initially
useEffect(() => {
// get the access token
setStatus("getAccessToken");
getAccessToken().then((newAccessToken) => {
if (newAccessToken === "error") {
setStatus("error");
return;
}
// update access token state var
setAccessToken(newAccessToken);
setStatus("ready");
});
}, []);
// handle function to send the application to Sender API
const handleSendApplication = async () => {
setStatus("sendApplication");
const newApplicationId = await sendApplication(
destinationId,
applicationApplicant,
applicationServiceType,
applicationData,
accessToken
);
if (newApplicationId === "error") {
setStatus("error");
return;
}
setStatus("readySent");
setApplicationId(newApplicationId);
};
// handle function to get the current status of the application from Sender API
const handleGetStatus = async () => {
setStatus("getStatus");
const newStatus = await getStatus(applicationId, destinationId, accessToken);
if (newStatus === "error") {
setStatus("error");
return;
}
setApplicationStatus(newStatus);
setStatus("readySent");
};
// handle function to reset the form
const handleNewApplication = () => {
setApplicationApplicant({ givenName: "Werner", familyName: "Mustermann" });
setApplicationServiceType({
leikaId: "99050012104000",
name: "Gewerbeanmeldung",
});
setApplicationId("");
setApplicationStatus("");
setStatus("ready");
};
// function to check if string parameter is in JSON
const isValidJSON = (stringJSON) => {
try {
JSON.parse(stringJSON);
return true;
} catch (error) {
return false;
}
};
// finally return our JSX
return (
<div className={classes.root}>
<AppBar position="static" color="secondary">
<Toolbar variant="dense">
<IconButton
edge="start"
onClick={(event) => setMenuAnchorEl(event.currentTarget.parentNode)}
className={classes.menuButton}
color="inherit"
aria-label="menu"
>
<MenuIcon />
</IconButton>
<Menu
id="simple-menu"
anchorEl={menuAnchorEl}
getContentAnchorEl={null}
anchorOrigin={{ vertical: "bottom", horizontal: "left" }}
transformOrigin={{ vertical: "top", horizontal: "right" }}
keepMounted
open={Boolean(menuAnchorEl)}
onClose={() => setMenuAnchorEl(null)}
className={classes.menu}
>
<MenuItem onClick={() => (window.location.href = "/sender-app")}>Sender App</MenuItem>
<MenuItem onClick={() => (window.location.href = "/subscriber-app")}>Subscriber App</MenuItem>
</Menu>
<Typography variant="h6" className={classes.title}>
Sender App
</Typography>
</Toolbar>
</AppBar>
<Container maxWidth="md">
<Grid container className={classes.formRow}>
<header className={classes.formInput}>
<h1>Citizen Portal</h1>
</header>
</Grid>
<FormControl className={classes.formRow}>
<Grid container>
<Grid item xs={3}>
<FormLabel>Agency</FormLabel>
</Grid>
<Grid item xs={9}>
<TextField
label="Destination ID"
value={destinationId}
onChange={(event) => setDestinationId(event.target.value)}
className={classes.formInput}
/>
</Grid>
</Grid>
</FormControl>
<FormControl className={classes.formRow}>
<Grid container>
<Grid item xs={3}>
<FormLabel>Applicant</FormLabel>
</Grid>
<Grid item xs={9}>
<TextField
label="Given Name"
value={applicationApplicant.givenName}
onChange={(event) =>
setApplicationApplicant({ ...applicationApplicant, givenName: event.target.value })
}
className={classes.formInput}
/>
<TextField
label="Family Name"
value={applicationApplicant.familyName}
onChange={(event) =>
setApplicationApplicant({ ...applicationApplicant, familyName: event.target.value })
}
className={classes.formInput}
/>
</Grid>
</Grid>
</FormControl>
<FormControl className={classes.formRow}>
<Grid container>
<Grid item xs={3}>
<FormLabel>Service Type</FormLabel>
</Grid>
<Grid item xs={9}>
<TextField
label="Leika ID"
value={applicationServiceType.leikaId}
onChange={(event) =>
setApplicationServiceType({ ...applicationServiceType, leikaId: event.target.value })
}
className={classes.formInput}
/>
<TextField
label="Name"
value={applicationServiceType.name}
onChange={(event) => setApplicationServiceType({ ...applicationServiceType, name: event.target.value })}
className={classes.formInput}
/>
</Grid>
</Grid>
</FormControl>
<FormControl className={classes.formRow}>
<Grid container>
<Grid item xs={3}>
<FormLabel>Application</FormLabel>
</Grid>
<Grid item xs={9}>
<TextField
label="Application Data"
value={applicationData}
onChange={(event) => setApplicationData(event.target.value)}
className={classes.formInput}
multiline
rows={5}
error={!isValidJSON(applicationData)}
/>
</Grid>
</Grid>
</FormControl>
{(status === "ready" || status === "sendApplication") && (
<Grid container className={classes.formRow}>
<Grid item xs={3}></Grid>
<Grid item xs={9} align="center">
<Button
onClick={handleSendApplication}
disabled={
!destinationId ||
!applicationApplicant.givenName ||
!applicationApplicant.familyName ||
!applicationServiceType.leikaId ||
!applicationServiceType.name ||
!isValidJSON(applicationData)
}
>
{status !== "sendApplication" && "Send Application"}
{status === "sendApplication" && <CircularProgress />}
</Button>
</Grid>
</Grid>
)}
{(status === "readySent" || status === "getStatus") && (
<div>
<Grid container className={classes.formRow}>
<Grid item xs={3}></Grid>
<Grid item xs={9}>
{applicationStatus}
</Grid>
</Grid>
<Grid container className={classes.formRow}>
<Grid item xs={3}></Grid>
<Grid item xs={9} align="center">
<Button onClick={handleGetStatus}>
{status !== "getStatus" && "Update Status"}
{status === "getStatus" && <CircularProgress />}
</Button>
</Grid>
</Grid>
<Grid container className={classes.formRow}>
<Grid item xs={3}></Grid>
<Grid item xs={9} align="center">
<Button onClick={handleNewApplication}>New Application</Button>
</Grid>
</Grid>
</div>
)}
{status === "error" && (
<Grid container align="center">
<Grid item xs={3}></Grid>
<Grid item xs={9} align="center">
<span>an error occured, see console for details</span>
</Grid>
</Grid>
)}
</Container>
</div>
);
}
export default App;
// PLEASE SET YOUR CREDENTIALS FROM FIT-CONNECT PORTAL HERE
export default {
senderId: "",
clientId: "",
clientSecret: "",
scope: "",
};
import React from "react";
import ReactDOM from "react-dom";
import App from "./App";
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById("root")
);
import axios from "axios";
import config from "./config";
// function to initially request an access token from Sender API and save it in state variable
export const getAccessToken = async () => {
try {
const response = await axios.get(
"https://oauth.fiep-poc.de/invoke/pub.apigateway.oauth2/getAccessToken?grant_type=client_credentials&client_id=" +
config.clientId +
"&client_secret=" +
config.clientSecret +
"&scope=" +
config.scope
);
return response.data.access_token;
} catch (error) {
console.log(error);
return "error";
}
};
// function to send a new application to Sender API
export const sendApplication = async (
destinationId,
applicationApplicant,
applicationServiceType,
applicationData,
accessToken
) => {
try {
// call endpoint Create Application
const requestBody = {
applicant: [
{
type: "person",
identityInfo: { givenName: applicationApplicant.givenName, familyName: applicationApplicant.familyName },
},
],
publicServiceType: {
name: applicationServiceType.name,
leikaId: applicationServiceType.leikaId,
},
};
const response = await axios.post(
"https://sender.fiep-poc.de/beta5/" + config.senderId + "/" + destinationId,
requestBody,
{
headers: { Authorization: "Bearer " + accessToken },
}
);
const applicationId = response.data.applicationId;
// call endpoint Add Application Data
await axios.put(
"https://sender.fiep-poc.de/beta5/" + config.senderId + "/" + destinationId + "/" + applicationId + "/data",
JSON.parse(applicationData),
{
headers: { Authorization: "Bearer " + accessToken },
}
);
// call endpoint Send Application
await axios.post(
"https://sender.fiep-poc.de/beta5/" + config.senderId + "/" + destinationId + "/" + applicationId,
{},
{
headers: { Authorization: "Bearer " + accessToken },
}
);
return applicationId;
} catch (error) {
console.log(error);
return "error";
}
};
// function to get the current status of an application from Sender API
export const getStatus = async (applicationId, destinationId, accessToken) => {
try {
const response = await axios.get(
"https://sender.fiep-poc.de/beta5/" + config.senderId + "/" + destinationId + "/" + applicationId + "/status",
{
headers: { Authorization: "Bearer " + accessToken },
}
);
return response.current.code;
} catch (error) {
console.log(error);
return "error";
}
};
import { makeStyles } from "@material-ui/core/styles";
// define styles
export const useStyles = makeStyles((theme) => ({
root: {
flexGrow: 1,
fontFamily: "Roboto, Helvetica, Arial, sans-serif",
},
menu: {
marginLeft: -8,
},
menuButton: {
marginRight: theme.spacing(2),
},
title: {
flexGrow: 1,
},
formRow: {
width: "100%",
marginBottom: 20,
},
formInput: {
width: "100%",
marginBottom: 10,
},
}));
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