Initial commit
This commit is contained in:
commit
76232e4856
16
.editorconfig
Normal file
16
.editorconfig
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
# Editor configuration, see https://editorconfig.org
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
charset = utf-8
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 2
|
||||||
|
insert_final_newline = true
|
||||||
|
trim_trailing_whitespace = true
|
||||||
|
|
||||||
|
[*.ts]
|
||||||
|
quote_type = single
|
||||||
|
|
||||||
|
[*.md]
|
||||||
|
max_line_length = off
|
||||||
|
trim_trailing_whitespace = false
|
||||||
31
.github/workflows/node.js.yml
vendored
Normal file
31
.github/workflows/node.js.yml
vendored
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
# This workflow will do a clean installation of node dependencies, cache/restore them, build the source code and run tests across different versions of node
|
||||||
|
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-nodejs
|
||||||
|
|
||||||
|
name: Node.js CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "prod" ]
|
||||||
|
pull_request:
|
||||||
|
branches: [ "prod" ]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
node-version: [20.x]
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- name: Use Node.js ${{ matrix.node-version }}
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node-version }}
|
||||||
|
cache: 'npm'
|
||||||
|
- run: npm install
|
||||||
|
- run: npm install -g @angular/cli
|
||||||
|
- run: npm run build
|
||||||
|
- run: npm test
|
||||||
45
.gitignore
vendored
Normal file
45
.gitignore
vendored
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# See http://help.github.com/ignore-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
.*
|
||||||
|
|
||||||
|
# Compiled output
|
||||||
|
/dist
|
||||||
|
/tmp
|
||||||
|
/out-tsc
|
||||||
|
/bazel-out
|
||||||
|
|
||||||
|
# Node
|
||||||
|
/node_modules
|
||||||
|
npm-debug.log
|
||||||
|
yarn-error.log
|
||||||
|
|
||||||
|
# IDEs and editors
|
||||||
|
.idea/
|
||||||
|
.project
|
||||||
|
.classpath
|
||||||
|
.c9/
|
||||||
|
*.launch
|
||||||
|
.settings/
|
||||||
|
*.sublime-workspace
|
||||||
|
|
||||||
|
# Visual Studio Code
|
||||||
|
.vscode
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
.history/*
|
||||||
|
|
||||||
|
# Miscellaneous
|
||||||
|
/.angular/cache
|
||||||
|
.sass-cache/
|
||||||
|
/connect.lock
|
||||||
|
/coverage
|
||||||
|
/libpeerconnection.log
|
||||||
|
testem.log
|
||||||
|
/typings
|
||||||
|
|
||||||
|
# System files
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
27
README.md
Normal file
27
README.md
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
# Homepage
|
||||||
|
|
||||||
|
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 15.2.6.
|
||||||
|
|
||||||
|
## Development server
|
||||||
|
|
||||||
|
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files.
|
||||||
|
|
||||||
|
## Code scaffolding
|
||||||
|
|
||||||
|
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
|
||||||
|
|
||||||
|
## Build
|
||||||
|
|
||||||
|
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory.
|
||||||
|
|
||||||
|
## Running unit tests
|
||||||
|
|
||||||
|
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
|
||||||
|
|
||||||
|
## Running end-to-end tests
|
||||||
|
|
||||||
|
Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
|
||||||
|
|
||||||
|
## Further help
|
||||||
|
|
||||||
|
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.io/cli) page.
|
||||||
126
angular.json
Normal file
126
angular.json
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||||
|
"version": 1,
|
||||||
|
"newProjectRoot": "projects",
|
||||||
|
"projects": {
|
||||||
|
"homepage": {
|
||||||
|
"projectType": "application",
|
||||||
|
"schematics": {
|
||||||
|
"@schematics/angular:component": {
|
||||||
|
"style": "scss",
|
||||||
|
"skipTests": true
|
||||||
|
},
|
||||||
|
"@schematics/angular:class": {
|
||||||
|
"skipTests": true
|
||||||
|
},
|
||||||
|
"@schematics/angular:directive": {
|
||||||
|
"skipTests": true
|
||||||
|
},
|
||||||
|
"@schematics/angular:guard": {
|
||||||
|
"skipTests": true
|
||||||
|
},
|
||||||
|
"@schematics/angular:module": {
|
||||||
|
"skipTests": true
|
||||||
|
},
|
||||||
|
"@schematics/angular:pipe": {
|
||||||
|
"skipTests": true
|
||||||
|
},
|
||||||
|
"@schematics/angular:service": {
|
||||||
|
"skipTests": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "",
|
||||||
|
"sourceRoot": "src",
|
||||||
|
"prefix": "app",
|
||||||
|
"architect": {
|
||||||
|
"build": {
|
||||||
|
"builder": "@angular-devkit/build-angular:browser",
|
||||||
|
"options": {
|
||||||
|
"outputPath": "dist/homepage",
|
||||||
|
"index": "src/index.html",
|
||||||
|
"main": "src/main.ts",
|
||||||
|
"polyfills": [
|
||||||
|
"zone.js"
|
||||||
|
],
|
||||||
|
"tsConfig": "tsconfig.app.json",
|
||||||
|
"assets": [
|
||||||
|
"src/assets",
|
||||||
|
"src/favicon.ico"
|
||||||
|
],
|
||||||
|
"styles": [
|
||||||
|
"@angular/material/prebuilt-themes/deeppurple-amber.css",
|
||||||
|
"src/styles.scss"
|
||||||
|
],
|
||||||
|
"scripts": []
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"budgets": [
|
||||||
|
{
|
||||||
|
"type": "initial",
|
||||||
|
"maximumWarning": "800kb",
|
||||||
|
"maximumError": "2mb"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "anyComponentStyle",
|
||||||
|
"maximumWarning": "2kb",
|
||||||
|
"maximumError": "4kb"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"outputHashing": "all"
|
||||||
|
},
|
||||||
|
"development": {
|
||||||
|
"buildOptimizer": false,
|
||||||
|
"optimization": false,
|
||||||
|
"vendorChunk": true,
|
||||||
|
"extractLicenses": false,
|
||||||
|
"sourceMap": true,
|
||||||
|
"namedChunks": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultConfiguration": "production"
|
||||||
|
},
|
||||||
|
"serve": {
|
||||||
|
"builder": "@angular-devkit/build-angular:dev-server",
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"browserTarget": "homepage:build:production"
|
||||||
|
},
|
||||||
|
"development": {
|
||||||
|
"browserTarget": "homepage:build:development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultConfiguration": "development"
|
||||||
|
},
|
||||||
|
"extract-i18n": {
|
||||||
|
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||||
|
"options": {
|
||||||
|
"browserTarget": "homepage:build"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"test": {
|
||||||
|
"builder": "@angular-devkit/build-angular:karma",
|
||||||
|
"options": {
|
||||||
|
"polyfills": [
|
||||||
|
"zone.js",
|
||||||
|
"zone.js/testing"
|
||||||
|
],
|
||||||
|
"tsConfig": "tsconfig.spec.json",
|
||||||
|
"assets": [
|
||||||
|
"src/assets",
|
||||||
|
"src/favicon.ico"
|
||||||
|
],
|
||||||
|
"styles": [
|
||||||
|
"@angular/material/prebuilt-themes/deeppurple-amber.scss",
|
||||||
|
"src/styles.scss"
|
||||||
|
],
|
||||||
|
"scripts": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"cli": {
|
||||||
|
"analytics": false
|
||||||
|
}
|
||||||
|
}
|
||||||
3
bin/script/build
Executable file
3
bin/script/build
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||||
|
$SCRIPT_DIR/exec build
|
||||||
3
bin/script/down
Executable file
3
bin/script/down
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||||
|
$SCRIPT_DIR/exec down
|
||||||
23
bin/script/exec
Executable file
23
bin/script/exec
Executable file
@ -0,0 +1,23 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
COMMAND="$@"
|
||||||
|
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||||
|
|
||||||
|
#LOAD ENV VARIABLES FROM .ENV
|
||||||
|
#export $(grep -v '^#' "${SCRIPT_DIR}/../../.env" | xargs)
|
||||||
|
|
||||||
|
#MAC
|
||||||
|
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||||
|
docker-compose -f "${SCRIPT_DIR}/../../docker/docker-compose-mac.yml" $COMMAND
|
||||||
|
|
||||||
|
#LINUX
|
||||||
|
elif [[ "$OSTYPE" == "linux-gnu" ]]; then
|
||||||
|
docker-compose -f "${SCRIPT_DIR}/../../docker/docker-compose.yml" $COMMAND
|
||||||
|
|
||||||
|
else
|
||||||
|
echo "Dieses Skript wird auf deinem Gerät nicht unterstützt"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
#UNSET ENV VARIABLES FROM .ENV
|
||||||
|
#unset $(grep -v '^#' "${SCRIPT_DIR}/../../.env" | sed -E 's/(.*)=.*/\1/' | xargs)
|
||||||
19
bin/script/init
Executable file
19
bin/script/init
Executable file
@ -0,0 +1,19 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||||
|
PROJECT_DIR=$(realpath $SCRIPT_DIR/../../)
|
||||||
|
ENV_DIR=$(realpath $PROJECT_DIR/../../)
|
||||||
|
|
||||||
|
# Build and start docker containers
|
||||||
|
$SCRIPT_DIR/exec build
|
||||||
|
$SCRIPT_DIR/exec up -d
|
||||||
|
|
||||||
|
# Source drun
|
||||||
|
source $ENV_DIR/bin/script/drun
|
||||||
|
|
||||||
|
# Install node Packages
|
||||||
|
drun homepage-frontend npm install
|
||||||
|
drun homepage-frontend npm install -g @angular/cli
|
||||||
|
|
||||||
|
# Initial build of website
|
||||||
|
drun homepage-frontend npm run build
|
||||||
3
bin/script/stop
Executable file
3
bin/script/stop
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||||
|
$SCRIPT_DIR/exec stop
|
||||||
3
bin/script/up
Executable file
3
bin/script/up
Executable file
@ -0,0 +1,3 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
|
||||||
|
$SCRIPT_DIR/exec up -d
|
||||||
31
docker/docker-compose-mac.yml
Normal file
31
docker/docker-compose-mac.yml
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
networks:
|
||||||
|
homepage:
|
||||||
|
external: true
|
||||||
|
|
||||||
|
services:
|
||||||
|
homepage-frontend-app:
|
||||||
|
image: homepage-frontend-app
|
||||||
|
networks:
|
||||||
|
- homepage
|
||||||
|
volumes:
|
||||||
|
- /Users/flo/dev/frontend/homepage/:/var/www/
|
||||||
|
build:
|
||||||
|
context: ./../
|
||||||
|
dockerfile: ./docker/npm/dockerfile
|
||||||
|
tty: true
|
||||||
|
|
||||||
|
homepage-frontend-nginx:
|
||||||
|
image: homepage-frontend-nginx
|
||||||
|
networks:
|
||||||
|
- homepage
|
||||||
|
volumes:
|
||||||
|
- /Users/flo/dev/frontend/homepage/:/var/www/html:z
|
||||||
|
build:
|
||||||
|
context: ./../
|
||||||
|
dockerfile: ./docker/nginx/dockerfile
|
||||||
|
ports:
|
||||||
|
- 80:80
|
||||||
|
depends_on:
|
||||||
|
- homepage-frontend-app
|
||||||
31
docker/docker-compose.yml
Normal file
31
docker/docker-compose.yml
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
version: '3'
|
||||||
|
|
||||||
|
networks:
|
||||||
|
homepage:
|
||||||
|
external: true
|
||||||
|
|
||||||
|
services:
|
||||||
|
homepage-frontend-app:
|
||||||
|
image: homepage-frontend-app
|
||||||
|
networks:
|
||||||
|
- homepage
|
||||||
|
volumes:
|
||||||
|
- ./../:/var/www/
|
||||||
|
build:
|
||||||
|
context: ./../
|
||||||
|
dockerfile: ./docker/npm/dockerfile
|
||||||
|
tty: true
|
||||||
|
|
||||||
|
homepage-frontend-nginx:
|
||||||
|
image: homepage-frontend-nginx
|
||||||
|
networks:
|
||||||
|
- homepage
|
||||||
|
volumes:
|
||||||
|
- ./../:/var/www/html:z
|
||||||
|
build:
|
||||||
|
context: ./../
|
||||||
|
dockerfile: ./docker/nginx/dockerfile
|
||||||
|
ports:
|
||||||
|
- 80:80
|
||||||
|
depends_on:
|
||||||
|
- homepage-frontend-app
|
||||||
25
docker/nginx/config/nginx.conf
Normal file
25
docker/nginx/config/nginx.conf
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
upstream host-backend-nginx {
|
||||||
|
server homepage-backend-nginx:80;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
server{
|
||||||
|
listen 80 default_server;
|
||||||
|
|
||||||
|
root /var/www/html/dist/homepage;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri$args $uri$args/ /index.html;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /api/ {
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header Host $http_host;
|
||||||
|
proxy_set_header X-NginX-Proxy true;
|
||||||
|
|
||||||
|
proxy_pass http://host-backend-nginx;
|
||||||
|
}
|
||||||
|
}
|
||||||
5
docker/nginx/dockerfile
Normal file
5
docker/nginx/dockerfile
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
FROM nginx:alpine
|
||||||
|
|
||||||
|
COPY docker/nginx/config/nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
|
|
||||||
|
CMD ["nginx", "-g", "daemon off;"]
|
||||||
12
docker/npm/dockerfile
Normal file
12
docker/npm/dockerfile
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
FROM node:latest
|
||||||
|
|
||||||
|
WORKDIR /var/www/html
|
||||||
|
|
||||||
|
# confirm installation
|
||||||
|
RUN node -v
|
||||||
|
RUN npm -v
|
||||||
|
|
||||||
|
# use newest npm version
|
||||||
|
RUN npm install npm
|
||||||
|
|
||||||
|
CMD ["/bin/bash"]
|
||||||
12673
package-lock.json
generated
Normal file
12673
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
40
package.json
Normal file
40
package.json
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
{
|
||||||
|
"name": "homepage",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"ng": "ng",
|
||||||
|
"start": "ng serve",
|
||||||
|
"build": "ng build",
|
||||||
|
"watch": "ng build --watch --configuration development",
|
||||||
|
"test": "ng test"
|
||||||
|
},
|
||||||
|
"private": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@angular/animations": "^15.2.0",
|
||||||
|
"@angular/cdk": "^15.2.9",
|
||||||
|
"@angular/common": "^15.2.0",
|
||||||
|
"@angular/compiler": "^15.2.0",
|
||||||
|
"@angular/core": "^15.2.0",
|
||||||
|
"@angular/forms": "^15.2.0",
|
||||||
|
"@angular/material": "^15.2.9",
|
||||||
|
"@angular/platform-browser": "^15.2.0",
|
||||||
|
"@angular/platform-browser-dynamic": "^15.2.0",
|
||||||
|
"@angular/router": "^15.2.0",
|
||||||
|
"rxjs": "~7.8.0",
|
||||||
|
"tslib": "^2.3.0",
|
||||||
|
"zone.js": "~0.12.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@angular-devkit/build-angular": "^15.2.6",
|
||||||
|
"@angular/cli": "~15.2.6",
|
||||||
|
"@angular/compiler-cli": "^15.2.0",
|
||||||
|
"@types/jasmine": "~4.3.0",
|
||||||
|
"jasmine-core": "~4.5.0",
|
||||||
|
"karma": "~6.4.0",
|
||||||
|
"karma-chrome-launcher": "~3.1.0",
|
||||||
|
"karma-coverage": "~2.2.0",
|
||||||
|
"karma-jasmine": "~5.1.0",
|
||||||
|
"karma-jasmine-html-reporter": "~2.0.0",
|
||||||
|
"typescript": "~4.9.4"
|
||||||
|
}
|
||||||
|
}
|
||||||
20
src/app/app-routing.module.ts
Normal file
20
src/app/app-routing.module.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
import { LoginComponent } from './auth/login/login.component';
|
||||||
|
import { HomeComponent } from './home/home.component';
|
||||||
|
import { RegistrationComponent } from './auth/registration/registration.component';
|
||||||
|
import { RegistrationConfirmationComponent } from './auth/registration-confirmation/registration-confirmation.component';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{ path: 'auth', component: LoginComponent },
|
||||||
|
{ path: 'auth/registration', component: RegistrationComponent },
|
||||||
|
{ path: 'auth/registration/:id', component: RegistrationConfirmationComponent },
|
||||||
|
{ path: 'home', component: HomeComponent },
|
||||||
|
{ path: '', redirectTo: '/auth', pathMatch: 'full' },
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forRoot(routes)],
|
||||||
|
exports: [RouterModule]
|
||||||
|
})
|
||||||
|
export class AppRoutingModule { }
|
||||||
1
src/app/app.component.html
Normal file
1
src/app/app.component.html
Normal file
@ -0,0 +1 @@
|
|||||||
|
<router-outlet></router-outlet>
|
||||||
4
src/app/app.component.scss
Normal file
4
src/app/app.component.scss
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
app-login {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
}
|
||||||
9
src/app/app.component.ts
Normal file
9
src/app/app.component.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-root',
|
||||||
|
templateUrl: './app.component.html',
|
||||||
|
styleUrls: ['./app.component.scss']
|
||||||
|
})
|
||||||
|
export class AppComponent {
|
||||||
|
}
|
||||||
74
src/app/app.module.ts
Normal file
74
src/app/app.module.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
import { BrowserModule } from '@angular/platform-browser';
|
||||||
|
import { HttpClientModule } from '@angular/common/http';
|
||||||
|
|
||||||
|
import { MatInputModule } from '@angular/material/input';
|
||||||
|
import { MatSelectModule } from '@angular/material/select';
|
||||||
|
|
||||||
|
import { MatSidenavModule } from '@angular/material/sidenav';
|
||||||
|
import { MatToolbarModule } from '@angular/material/toolbar';
|
||||||
|
import { MatMenuModule } from '@angular/material/menu';
|
||||||
|
import { MatListModule } from '@angular/material/list';
|
||||||
|
import { MatIconModule } from '@angular/material/icon';
|
||||||
|
import { MatGridListModule } from '@angular/material/grid-list'
|
||||||
|
import { MatButtonModule } from '@angular/material/button';
|
||||||
|
import { MatCardModule } from '@angular/material/card';
|
||||||
|
import { MatExpansionModule } from '@angular/material/expansion';
|
||||||
|
import { MatSlideToggleModule } from '@angular/material/slide-toggle';
|
||||||
|
import { MatFormFieldModule } from '@angular/material/form-field';
|
||||||
|
import { MatDividerModule } from '@angular/material/divider';
|
||||||
|
import {MatDialogModule} from '@angular/material/dialog';
|
||||||
|
import {MatSnackBarModule} from '@angular/material/snack-bar';
|
||||||
|
|
||||||
|
import { AppRoutingModule } from './app-routing.module';
|
||||||
|
import { AppComponent } from './app.component';
|
||||||
|
import { ProductListComponent } from './product/product-list/product-list.component';
|
||||||
|
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||||
|
import { HomeComponent } from './home/home.component';
|
||||||
|
import { ProductDetailsComponent } from './product/product-details/product-details.component';
|
||||||
|
import { LoginComponent } from './auth/login/login.component';
|
||||||
|
import { CreateProductComponent } from './product/create-product/create-product.component';
|
||||||
|
import { RegistrationComponent } from './auth/registration/registration.component';
|
||||||
|
import { RegistrationConfirmationComponent } from './auth/registration-confirmation/registration-confirmation.component';
|
||||||
|
import { ProductComponent } from './product/product/product.component';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
AppComponent,
|
||||||
|
ProductListComponent,
|
||||||
|
ProductDetailsComponent,
|
||||||
|
LoginComponent,
|
||||||
|
HomeComponent,
|
||||||
|
CreateProductComponent,
|
||||||
|
RegistrationComponent,
|
||||||
|
RegistrationConfirmationComponent,
|
||||||
|
ProductComponent,
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
BrowserModule,
|
||||||
|
HttpClientModule,
|
||||||
|
AppRoutingModule,
|
||||||
|
BrowserAnimationsModule,
|
||||||
|
MatSlideToggleModule,
|
||||||
|
MatCardModule,
|
||||||
|
MatButtonModule,
|
||||||
|
MatIconModule,
|
||||||
|
MatGridListModule,
|
||||||
|
MatFormFieldModule,
|
||||||
|
FormsModule,
|
||||||
|
MatExpansionModule,
|
||||||
|
MatMenuModule,
|
||||||
|
MatListModule,
|
||||||
|
MatToolbarModule,
|
||||||
|
MatSidenavModule,
|
||||||
|
MatInputModule,
|
||||||
|
MatSelectModule,
|
||||||
|
MatDividerModule,
|
||||||
|
MatDialogModule,
|
||||||
|
MatSnackBarModule
|
||||||
|
],
|
||||||
|
providers: [],
|
||||||
|
bootstrap: [AppComponent]
|
||||||
|
})
|
||||||
|
export class AppModule { }
|
||||||
21
src/app/app.service.ts
Normal file
21
src/app/app.service.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class AppService {
|
||||||
|
|
||||||
|
private username: string|null;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.username = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getUsername(): string|null {
|
||||||
|
return this.username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public setUsername(name: string|null): void {
|
||||||
|
this.username = name;
|
||||||
|
}
|
||||||
|
}
|
||||||
41
src/app/auth/login/login.component.html
Normal file
41
src/app/auth/login/login.component.html
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
<mat-card>
|
||||||
|
<mat-card-header>
|
||||||
|
<mat-card-title>Login</mat-card-title>
|
||||||
|
<mat-card-subtitle>Bitte melde Dich an</mat-card-subtitle>
|
||||||
|
</mat-card-header>
|
||||||
|
|
||||||
|
<mat-card-content>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>Identifier</mat-label>
|
||||||
|
<input matInput [(ngModel)]="identifier">
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>Password</mat-label>
|
||||||
|
<input matInput type="password" [(ngModel)]="password">
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md">
|
||||||
|
<button mat-fab
|
||||||
|
extended
|
||||||
|
color="disabled"
|
||||||
|
(click)="register()">
|
||||||
|
Registrieren
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="col-md">
|
||||||
|
<button mat-fab
|
||||||
|
extended
|
||||||
|
color="primary"
|
||||||
|
(click)="login()">
|
||||||
|
Login
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
23
src/app/auth/login/login.component.scss
Normal file
23
src/app/auth/login/login.component.scss
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
mat-card {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* For tablet: */
|
||||||
|
@media only screen and (min-width: 600px) {
|
||||||
|
mat-card {
|
||||||
|
width: 70%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* For desktop: */
|
||||||
|
@media only screen and (min-width: 768px) {
|
||||||
|
mat-card {
|
||||||
|
width: 40%;
|
||||||
|
}
|
||||||
|
}
|
||||||
60
src/app/auth/login/login.component.ts
Normal file
60
src/app/auth/login/login.component.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { RequestService } from '../../request.service';
|
||||||
|
import { Router } from "@angular/router"
|
||||||
|
import { AppService } from '../../app.service';
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-login',
|
||||||
|
templateUrl: './login.component.html',
|
||||||
|
styleUrls: ['./login.component.scss']
|
||||||
|
})
|
||||||
|
export class LoginComponent implements OnInit {
|
||||||
|
identifier: string|null = null;
|
||||||
|
password: string|null = null;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public appService : AppService,
|
||||||
|
public requestService : RequestService,
|
||||||
|
private router: Router
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.stateRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
login() {
|
||||||
|
this.requestService.post(
|
||||||
|
'auth/login-user',
|
||||||
|
{
|
||||||
|
identifier: this.identifier,
|
||||||
|
password: this.password
|
||||||
|
},
|
||||||
|
(response:any) => {
|
||||||
|
if(response.hasOwnProperty('sessionId')) {
|
||||||
|
this.router.navigate(['/home']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
register() {
|
||||||
|
console.log('goto registration');
|
||||||
|
this.router.navigate(['/auth/registration']);
|
||||||
|
}
|
||||||
|
|
||||||
|
private stateRequest() {
|
||||||
|
this.requestService.get(
|
||||||
|
'user/state',
|
||||||
|
{},
|
||||||
|
(response:any) => {
|
||||||
|
if (response.hasOwnProperty('sessionId')) {
|
||||||
|
this.appService.setUsername(response.username);
|
||||||
|
this.router.navigate(['/home']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,33 @@
|
|||||||
|
<mat-card>
|
||||||
|
<mat-card-header>
|
||||||
|
<mat-card-title>Registrierung bestätigen</mat-card-title>
|
||||||
|
<mat-card-subtitle>Bitte gib dein Passwort ein</mat-card-subtitle>
|
||||||
|
</mat-card-header>
|
||||||
|
|
||||||
|
<mat-card-content>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>Passwort</mat-label>
|
||||||
|
<input matInput type="password" [(ngModel)]="password">
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>Passwort wiederholen</mat-label>
|
||||||
|
<input matInput type="password" [(ngModel)]="passwordConfirmation">
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md">
|
||||||
|
<button mat-fab
|
||||||
|
extended
|
||||||
|
color="primary"
|
||||||
|
(click)="confirm()">
|
||||||
|
Bestätigen
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
@ -0,0 +1,23 @@
|
|||||||
|
mat-card {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* For tablet: */
|
||||||
|
@media only screen and (min-width: 600px) {
|
||||||
|
mat-card {
|
||||||
|
width: 70%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* For desktop: */
|
||||||
|
@media only screen and (min-width: 768px) {
|
||||||
|
mat-card {
|
||||||
|
width: 40%;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,40 @@
|
|||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { ActivatedRoute, Router } from '@angular/router';
|
||||||
|
import { RequestService } from '../../request.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-registration-confirmation',
|
||||||
|
templateUrl: './registration-confirmation.component.html',
|
||||||
|
styleUrls: ['./registration-confirmation.component.scss']
|
||||||
|
})
|
||||||
|
export class RegistrationConfirmationComponent implements OnInit {
|
||||||
|
public id: string;
|
||||||
|
public password: string|null = null;
|
||||||
|
public passwordConfirmation: string|null = null;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
public requestService : RequestService,
|
||||||
|
private router: Router
|
||||||
|
) {
|
||||||
|
this.id = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.id = this.route.snapshot.paramMap.get('id') ?? '';
|
||||||
|
}
|
||||||
|
|
||||||
|
confirm() {
|
||||||
|
this.requestService.post(
|
||||||
|
'auth/confirm-registration',
|
||||||
|
{
|
||||||
|
id: this.id,
|
||||||
|
password: this.password,
|
||||||
|
passwordConfirmation: this.passwordConfirmation,
|
||||||
|
},
|
||||||
|
(response:any) => {
|
||||||
|
this.router.navigate(['/auth']);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
33
src/app/auth/registration/registration.component.html
Normal file
33
src/app/auth/registration/registration.component.html
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
<mat-card>
|
||||||
|
<mat-card-header>
|
||||||
|
<mat-card-title>Registrieren</mat-card-title>
|
||||||
|
<mat-card-subtitle>Bitte gib deine Benutzerdaten an:</mat-card-subtitle>
|
||||||
|
</mat-card-header>
|
||||||
|
|
||||||
|
<mat-card-content>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>Benutzername</mat-label>
|
||||||
|
<input matInput [(ngModel)]="username">
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>E-Mail</mat-label>
|
||||||
|
<input matInput [(ngModel)]="mail">
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md">
|
||||||
|
<button mat-fab
|
||||||
|
extended
|
||||||
|
color="primary"
|
||||||
|
(click)="register()">
|
||||||
|
Registrieren
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</mat-card-content>
|
||||||
|
</mat-card>
|
||||||
23
src/app/auth/registration/registration.component.scss
Normal file
23
src/app/auth/registration/registration.component.scss
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
mat-card {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
button {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* For tablet: */
|
||||||
|
@media only screen and (min-width: 600px) {
|
||||||
|
mat-card {
|
||||||
|
width: 70%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* For desktop: */
|
||||||
|
@media only screen and (min-width: 768px) {
|
||||||
|
mat-card {
|
||||||
|
width: 40%;
|
||||||
|
}
|
||||||
|
}
|
||||||
39
src/app/auth/registration/registration.component.ts
Normal file
39
src/app/auth/registration/registration.component.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { AppService } from '../../app.service';
|
||||||
|
import { RequestService } from '../../request.service';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-registration',
|
||||||
|
templateUrl: './registration.component.html',
|
||||||
|
styleUrls: ['./registration.component.scss']
|
||||||
|
})
|
||||||
|
export class RegistrationComponent {
|
||||||
|
username: string|null = null;
|
||||||
|
mail: string|null = null;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public appService : AppService,
|
||||||
|
public requestService : RequestService,
|
||||||
|
private router: Router
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.username = "";
|
||||||
|
this.mail = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
register(): void {
|
||||||
|
this.requestService.post(
|
||||||
|
'auth/register-user',
|
||||||
|
{
|
||||||
|
username: this.username,
|
||||||
|
mail: this.mail
|
||||||
|
},
|
||||||
|
(response:any) => {
|
||||||
|
this.router.navigate(['/auth']);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
26
src/app/home/home.component.html
Normal file
26
src/app/home/home.component.html
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<mat-toolbar color="primary">
|
||||||
|
<button mat-icon-button (click)="drawer.toggle(); list.onGetProductList()">
|
||||||
|
<mat-icon>menu</mat-icon>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
|
||||||
|
<span>
|
||||||
|
{{title}}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<button mat-button [matMenuTriggerFor]="menu">{{username}}</button>
|
||||||
|
<mat-menu #menu="matMenu">
|
||||||
|
<button mat-menu-item (click)="logout()">Logout</button>
|
||||||
|
</mat-menu>
|
||||||
|
</mat-toolbar>
|
||||||
|
|
||||||
|
<mat-drawer-container class="content">
|
||||||
|
<mat-drawer #drawer class="example-sidenav" mode="over">
|
||||||
|
<app-product-list #list (productSelected)="selectProduct($event); drawer.toggle(); list.onGetProductList()">
|
||||||
|
</app-product-list>
|
||||||
|
</mat-drawer>
|
||||||
|
|
||||||
|
<div class="example-sidenav-content">
|
||||||
|
<app-product-details [product]="selectedProduct"></app-product-details>
|
||||||
|
</div>
|
||||||
|
</mat-drawer-container>
|
||||||
13
src/app/home/home.component.scss
Normal file
13
src/app/home/home.component.scss
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
span {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.example-sidenav-content {
|
||||||
|
margin: 1em;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
52
src/app/home/home.component.ts
Normal file
52
src/app/home/home.component.ts
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { Product } from '../product/product-list/product-list.component';
|
||||||
|
import { RequestService } from '../request.service';
|
||||||
|
import { Router } from "@angular/router"
|
||||||
|
import { AppService } from '../app.service';
|
||||||
|
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-home',
|
||||||
|
templateUrl: './home.component.html',
|
||||||
|
styleUrls: ['./home.component.scss']
|
||||||
|
})
|
||||||
|
export class HomeComponent {
|
||||||
|
title = 'Homepage';
|
||||||
|
username: string = "(null)";
|
||||||
|
selectedProduct: Product|null = null;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public appService : AppService,
|
||||||
|
public requestService : RequestService,
|
||||||
|
private router: Router
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.requestService.get(
|
||||||
|
'user/state',
|
||||||
|
{},
|
||||||
|
(response:any) => {
|
||||||
|
if (response.status == 401) {
|
||||||
|
this.router.navigate(['/auth']);
|
||||||
|
} else {
|
||||||
|
this.username = response.username;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
logout() {
|
||||||
|
this.requestService.post(
|
||||||
|
'auth/logout-user',
|
||||||
|
{},
|
||||||
|
(response:any) => {
|
||||||
|
this.router.navigate(['/auth']);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
selectProduct(product: any) {
|
||||||
|
this.selectedProduct = product;
|
||||||
|
}
|
||||||
|
}
|
||||||
28
src/app/product/create-product/create-product.component.html
Normal file
28
src/app/product/create-product/create-product.component.html
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
<mat-card>
|
||||||
|
<mat-card-header>
|
||||||
|
<mat-card-title>Neues Produkt</mat-card-title>
|
||||||
|
<mat-card-subtitle>Bitte gebe die Daten des neuen Produkts an</mat-card-subtitle>
|
||||||
|
</mat-card-header>
|
||||||
|
|
||||||
|
<mat-card-content>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>Identifier</mat-label>
|
||||||
|
<input matInput [(ngModel)]="identifier">
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>Name</mat-label>
|
||||||
|
<input matInput [(ngModel)]="name">
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</mat-card-content>
|
||||||
|
|
||||||
|
<mat-card-actions>
|
||||||
|
<button mat-button (click)="cancel()">Abbrechen</button>
|
||||||
|
<button mat-button [mat-dialog-close]="{identifier: identifier, name: name}" cdkFocusInitial>Ok</button>
|
||||||
|
</mat-card-actions>
|
||||||
|
</mat-card>
|
||||||
24
src/app/product/create-product/create-product.component.ts
Normal file
24
src/app/product/create-product/create-product.component.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
import { MatDialogRef } from '@angular/material/dialog';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-create-product',
|
||||||
|
templateUrl: './create-product.component.html',
|
||||||
|
styleUrls: ['./create-product.component.scss']
|
||||||
|
})
|
||||||
|
export class CreateProductComponent {
|
||||||
|
name: string
|
||||||
|
identifier: string
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private dialogRef: MatDialogRef<CreateProductComponent>
|
||||||
|
) {
|
||||||
|
this.name = '';
|
||||||
|
this.identifier = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
cancel(){
|
||||||
|
this.dialogRef.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@ -0,0 +1,56 @@
|
|||||||
|
<iframe *ngIf="url != null" [src]="url">
|
||||||
|
|
||||||
|
</iframe>
|
||||||
|
|
||||||
|
<mat-card *ngIf="product != null">
|
||||||
|
<mat-card-header>
|
||||||
|
<mat-card-title>{{product.name}}</mat-card-title>
|
||||||
|
<mat-card-subtitle>{{product.identifier}} - {{product.id}}</mat-card-subtitle>
|
||||||
|
</mat-card-header>
|
||||||
|
|
||||||
|
<mat-divider/>
|
||||||
|
|
||||||
|
<mat-card-content>
|
||||||
|
<div class="container">
|
||||||
|
<div class="row">
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>Identifier</mat-label>
|
||||||
|
<input matInput [(ngModel)]="product.identifier">
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>Name</mat-label>
|
||||||
|
<input matInput [(ngModel)]="product.name">
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<mat-form-field>
|
||||||
|
<mat-label>Url</mat-label>
|
||||||
|
<input matInput [(ngModel)]="product.url">
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<div class="col-md">
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Erstellt am</mat-label>
|
||||||
|
<input matInput [(ngModel)]="product.created" readonly>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
<div class="col-md">
|
||||||
|
<mat-form-field appearance="outline">
|
||||||
|
<mat-label>Zuletzt geändert am</mat-label>
|
||||||
|
<input matInput [(ngModel)]="product.updated" readonly>
|
||||||
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</mat-card-content>
|
||||||
|
|
||||||
|
<mat-card-actions align="end">
|
||||||
|
<button mat-fab color="primary"
|
||||||
|
(click)="updateProduct(product)">
|
||||||
|
<mat-icon>save</mat-icon>
|
||||||
|
</button>
|
||||||
|
</mat-card-actions>
|
||||||
|
</mat-card>
|
||||||
@ -0,0 +1,36 @@
|
|||||||
|
app-login {
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-form-field {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-divider {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
mat-card {
|
||||||
|
margin-right: auto;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
iframe {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* For tablet: */
|
||||||
|
@media only screen and (min-width: 600px) {
|
||||||
|
mat-card {
|
||||||
|
max-width: 85%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* For desktop: */
|
||||||
|
@media only screen and (min-width: 768px) {
|
||||||
|
mat-card {
|
||||||
|
max-width: 85%;
|
||||||
|
}
|
||||||
|
}
|
||||||
40
src/app/product/product-details/product-details.component.ts
Normal file
40
src/app/product/product-details/product-details.component.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { Component, Input, OnChanges, SimpleChanges } from '@angular/core';
|
||||||
|
import { RequestService } from '../../request.service';
|
||||||
|
import { Product } from '../product-list/product-list.component';
|
||||||
|
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-product-details',
|
||||||
|
templateUrl: './product-details.component.html',
|
||||||
|
styleUrls: ['./product-details.component.scss']
|
||||||
|
})
|
||||||
|
export class ProductDetailsComponent implements OnChanges {
|
||||||
|
@Input() product: Product|null = null
|
||||||
|
url: SafeResourceUrl|null = null;
|
||||||
|
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public requestService : RequestService,
|
||||||
|
private domSanitizer: DomSanitizer
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnChanges(changes: SimpleChanges) {
|
||||||
|
if (this.product != null && this.product.url != null) {
|
||||||
|
this.url = this.domSanitizer.bypassSecurityTrustResourceUrl(this.product.url);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.url = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateProduct(product: Product) {
|
||||||
|
this.requestService.post(
|
||||||
|
'product/update-product',
|
||||||
|
product,
|
||||||
|
(response:any) => {
|
||||||
|
this.product = response
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
17
src/app/product/product-list/product-list.component.html
Normal file
17
src/app/product/product-list/product-list.component.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<mat-list>
|
||||||
|
<mat-list-item class="listItem" *ngFor="let product of products" (click)="selectProduct(product)">
|
||||||
|
<mat-icon matListItemIcon (click)="removeProduct(product)"> delete</mat-icon>
|
||||||
|
<span matListItemTitle>{{product.name}}</span>
|
||||||
|
<span matListItemLine>{{product.identifier}}</span>
|
||||||
|
</mat-list-item>
|
||||||
|
</mat-list>
|
||||||
|
|
||||||
|
<mat-divider></mat-divider>
|
||||||
|
|
||||||
|
<button mat-fab
|
||||||
|
extended
|
||||||
|
color="primary"
|
||||||
|
(click)="createProduct()">
|
||||||
|
<mat-icon>add</mat-icon>
|
||||||
|
Add
|
||||||
|
</button>
|
||||||
18
src/app/product/product-list/product-list.component.scss
Normal file
18
src/app/product/product-list/product-list.component.scss
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
mat-divider {
|
||||||
|
margin-top: 0.5em;
|
||||||
|
margin-bottom: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.listItem {
|
||||||
|
margin-right: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.listItem:hover {
|
||||||
|
background-color: lightgray;
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
margin-left: 1em;
|
||||||
|
margin-right: 1em;
|
||||||
|
width: 90%;
|
||||||
|
}
|
||||||
82
src/app/product/product-list/product-list.component.ts
Normal file
82
src/app/product/product-list/product-list.component.ts
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import { Component, EventEmitter, Output } from '@angular/core';
|
||||||
|
import { RequestService } from '../../request.service';
|
||||||
|
import {MatDialog, MAT_DIALOG_DATA, MatDialogRef} from '@angular/material/dialog';
|
||||||
|
import { CreateProductComponent } from '../create-product/create-product.component';
|
||||||
|
|
||||||
|
export interface Product {
|
||||||
|
id: string,
|
||||||
|
identifier: string,
|
||||||
|
name: string|null,
|
||||||
|
url: string|null,
|
||||||
|
created: string,
|
||||||
|
updated: string
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-product-list',
|
||||||
|
templateUrl: './product-list.component.html',
|
||||||
|
styleUrls: ['./product-list.component.scss']
|
||||||
|
})
|
||||||
|
export class ProductListComponent {
|
||||||
|
@Output() productSelected = new EventEmitter<Product|null>();
|
||||||
|
products: Product[] = []
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public requestService : RequestService,
|
||||||
|
public dialog: MatDialog
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
onGetProductList(): void {
|
||||||
|
this.requestService.get(
|
||||||
|
'product/product-list',
|
||||||
|
{},
|
||||||
|
(answer:any) => this.products = answer
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
selectProduct(product: Product|null) {
|
||||||
|
this.productSelected.emit(product);
|
||||||
|
}
|
||||||
|
|
||||||
|
createProduct() {
|
||||||
|
|
||||||
|
const createProductDialogRef = this.dialog.open(
|
||||||
|
CreateProductComponent
|
||||||
|
)
|
||||||
|
|
||||||
|
createProductDialogRef.afterClosed().subscribe(
|
||||||
|
result => {
|
||||||
|
if(result != undefined) {
|
||||||
|
this.requestService.post(
|
||||||
|
'product/create-product',
|
||||||
|
{
|
||||||
|
identifier: result.identifier,
|
||||||
|
name: result.name
|
||||||
|
},
|
||||||
|
(answer:any) => {
|
||||||
|
this.onGetProductList();
|
||||||
|
this.selectProduct(answer);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
removeProduct(product: Product) {
|
||||||
|
this.selectProduct(null);
|
||||||
|
this.requestService.post(
|
||||||
|
'product/delete-product',
|
||||||
|
{
|
||||||
|
id: product.id
|
||||||
|
},
|
||||||
|
(answer:any) => {
|
||||||
|
this.selectProduct(null);
|
||||||
|
this.onGetProductList();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
0
src/app/product/product/product.component.html
Normal file
0
src/app/product/product/product.component.html
Normal file
0
src/app/product/product/product.component.scss
Normal file
0
src/app/product/product/product.component.scss
Normal file
10
src/app/product/product/product.component.ts
Normal file
10
src/app/product/product/product.component.ts
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
import { Component } from '@angular/core';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-product',
|
||||||
|
templateUrl: './product.component.html',
|
||||||
|
styleUrls: ['./product.component.scss']
|
||||||
|
})
|
||||||
|
export class ProductComponent {
|
||||||
|
|
||||||
|
}
|
||||||
83
src/app/request.service.ts
Normal file
83
src/app/request.service.ts
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
import { HttpClient } from '@angular/common/http';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class RequestService {
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private http: HttpClient,
|
||||||
|
private router: Router,
|
||||||
|
private snackBar: MatSnackBar
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
post(apiPath: string, body: any, fct: Function)
|
||||||
|
{
|
||||||
|
let url = this.obtainUrl(apiPath);
|
||||||
|
let observable = this.http.post(url, body).subscribe(
|
||||||
|
(answer:any) => {
|
||||||
|
fct(answer);
|
||||||
|
},
|
||||||
|
(error:any) => {
|
||||||
|
this.handleError(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get(apiPath: string, body: any, fct: Function)
|
||||||
|
{
|
||||||
|
let url = this.obtainUrl(apiPath);
|
||||||
|
let observable = this.http.get(url, body).subscribe(
|
||||||
|
(answer:any) => {
|
||||||
|
fct(answer);
|
||||||
|
},
|
||||||
|
(error:any) => {
|
||||||
|
this.handleError(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private obtainUrl(apiPath: string): string {
|
||||||
|
let hostString = window.location.host;
|
||||||
|
let protocol = window.location.protocol;
|
||||||
|
return protocol + '//' + hostString + '/api/' + apiPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleError(answer: any): void {
|
||||||
|
|
||||||
|
if (answer.status == 401) {
|
||||||
|
console.log('Deine Sitzung konnte nicht gefunden werden');
|
||||||
|
this.router.navigate(['/auth']);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (answer.status == 403) {
|
||||||
|
this.showSnackBar('Du bist nicht für diese Aktion autorisiert', 'Ok');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
let errorObject = answer.error.error;
|
||||||
|
|
||||||
|
if (errorObject.hasOwnProperty('message')) {
|
||||||
|
throw errorObject.message.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errorObject.hasOwnProperty('code')) {
|
||||||
|
throw errorObject.code.toString();
|
||||||
|
}
|
||||||
|
} catch(error:any) {
|
||||||
|
this.showSnackBar(error.toString(), 'Ok');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private showSnackBar(message: string, action?: string) {
|
||||||
|
this.snackBar.open(message.toString(), action, {
|
||||||
|
duration: 3000,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
0
src/assets/.gitkeep
Normal file
0
src/assets/.gitkeep
Normal file
1
src/assets/homepage-icon.svg
Normal file
1
src/assets/homepage-icon.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 20C7.6 20 4 16.4 4 12S7.6 4 12 4 20 7.6 20 12 16.4 20 12 20M12 2C6.5 2 2 6.5 2 12S6.5 22 12 22 22 17.5 22 12 17.5 2 12 2M11 14H13V17H16V12H18L12 7L6 12H8V17H11V14" /></svg>
|
||||||
|
After Width: | Height: | Size: 245 B |
BIN
src/favicon.ico
Normal file
BIN
src/favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 4.2 KiB |
17
src/index.html
Normal file
17
src/index.html
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Homepage</title>
|
||||||
|
<base href="/" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="icon" type="image/x-icon" href="favicon.ico">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500&display=swap" rel="stylesheet">
|
||||||
|
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
|
||||||
|
</head>
|
||||||
|
<body class="mat-typography mat-app-background">
|
||||||
|
<app-root></app-root>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
7
src/main.ts
Normal file
7
src/main.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
|
||||||
|
|
||||||
|
import { AppModule } from './app/app.module';
|
||||||
|
|
||||||
|
|
||||||
|
platformBrowserDynamic().bootstrapModule(AppModule)
|
||||||
|
.catch(err => console.error(err));
|
||||||
13
src/styles.scss
Normal file
13
src/styles.scss
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
padding: 0;
|
||||||
|
max-width: unset;
|
||||||
|
}
|
||||||
|
|
||||||
|
app-root {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
14
tsconfig.app.json
Normal file
14
tsconfig.app.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./out-tsc/app",
|
||||||
|
"types": []
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"src/main.ts"
|
||||||
|
],
|
||||||
|
"include": [
|
||||||
|
"src/**/*.d.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
33
tsconfig.json
Normal file
33
tsconfig.json
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||||
|
{
|
||||||
|
"compileOnSave": false,
|
||||||
|
"compilerOptions": {
|
||||||
|
"baseUrl": "./",
|
||||||
|
"outDir": "./dist/out-tsc",
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"strict": true,
|
||||||
|
"noImplicitOverride": true,
|
||||||
|
"noPropertyAccessFromIndexSignature": true,
|
||||||
|
"noImplicitReturns": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"declaration": false,
|
||||||
|
"downlevelIteration": true,
|
||||||
|
"experimentalDecorators": true,
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"importHelpers": true,
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "ES2022",
|
||||||
|
"useDefineForClassFields": false,
|
||||||
|
"lib": [
|
||||||
|
"ES2022",
|
||||||
|
"dom"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"angularCompilerOptions": {
|
||||||
|
"enableI18nLegacyMessageIdFormat": false,
|
||||||
|
"strictInjectionParameters": true,
|
||||||
|
"strictInputAccessModifiers": true,
|
||||||
|
"strictTemplates": true
|
||||||
|
}
|
||||||
|
}
|
||||||
14
tsconfig.spec.json
Normal file
14
tsconfig.spec.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/* To learn more about this file see: https://angular.io/config/tsconfig. */
|
||||||
|
{
|
||||||
|
"extends": "./tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "./out-tsc/spec",
|
||||||
|
"types": [
|
||||||
|
"jasmine"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.spec.ts",
|
||||||
|
"src/**/*.d.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user