Compare commits
68 Commits
ft-add-git
...
master
Author | SHA1 | Date | |
---|---|---|---|
33cd5d0b5c | |||
04de306091 | |||
70f7f67d9c | |||
cf0d894092 | |||
d971a10cf4 | |||
8499636209 | |||
94247c5693 | |||
a5fcb20296 | |||
765e0ae521 | |||
09532de67b | |||
bfdb76cabe | |||
aacbad3357 | |||
0218c80aa9 | |||
92ee2dc05f | |||
b4d3ad6ac8 | |||
5610260120 | |||
7d85e5e4f6 | |||
a9045d65d6 | |||
4344d4fa42 | |||
173dd2d4c9 | |||
80801e3fa6 | |||
0bd719904a | |||
876920e057 | |||
f41c502e37 | |||
37b8827007 | |||
1ab2424710 | |||
da38676847 | |||
d2a7f0d30b | |||
c791d6ad6d | |||
6ec81ae0d7 | |||
527eebeefb | |||
50bd774478 | |||
2ae0ed13fc | |||
c9e8758c4f | |||
9ffaaca568 | |||
44d052ca48 | |||
677a05ba1d | |||
f1a8b27a0e | |||
29e9b1f944 | |||
49bb94562d | |||
67c56ad724 | |||
c57dd13356 | |||
515e7b3dd8 | |||
5e2aca14a9 | |||
16b72f4fb6 | |||
a6b021dd98 | |||
cf6bb1abff | |||
8ba727b367 | |||
cd5b04d695 | |||
61dcc46242 | |||
9a7caf9eb3 | |||
b281e87304 | |||
a538204d3b | |||
7262775a48 | |||
9d90d9b70c | |||
922125fb18 | |||
2ee273ddf1 | |||
04a36990cb | |||
8531520665 | |||
09ef49b04d | |||
8c08b632b7 | |||
1f580fa829 | |||
92e2a8bb37 | |||
689cd6fff6 | |||
87676d426c | |||
575f36565b | |||
dfe6fd105e | |||
c8dab87a66 |
70
.github/workflows/deploy.yml
vendored
@ -1,60 +1,40 @@
|
||||
name: Deploy to Shuttle
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
branches: [master]
|
||||
types: [closed]
|
||||
push:
|
||||
branches: [ master ]
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
if: github.event.pull_request.merged == true
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
# Install wasm32-unknown-unknown target
|
||||
- name: Install wasm32-unknown-unknown target
|
||||
run: rustup target add wasm32-unknown-unknown
|
||||
|
||||
# Build frontend
|
||||
- name: Install Node.js
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: 16
|
||||
- name: Install Tailwind CSS
|
||||
run: npm install -D tailwindcss
|
||||
working-directory: ./frontend
|
||||
- name: Install trunk
|
||||
run: cargo install trunk
|
||||
- name: Rename Trunk.toml
|
||||
run: mv Trunk.toml Trunk.bkp
|
||||
working-directory: ./frontend
|
||||
- name: Build frontend
|
||||
run: trunk build --release
|
||||
working-directory: ./frontend
|
||||
- name: Rename Trunk.toml back
|
||||
run: mv Trunk.bkp Trunk.toml
|
||||
working-directory: ./frontend
|
||||
- name: Commit build files
|
||||
uses: stefanzweifel/git-auto-commit-action@v5
|
||||
with:
|
||||
commit_message: "Build files from GitHub Actions"
|
||||
node-version: '22'
|
||||
|
||||
# Deploy backend
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
- name: Cache Node.js modules
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
toolchain: stable
|
||||
override: true
|
||||
- name: Deploy to shuttle.rs
|
||||
uses: shuttle-hq/deploy-action@main
|
||||
path: ~/.npm
|
||||
key: ${{ runner.OS }}-node-${{ hashFiles('**/package-lock.json') }}
|
||||
restore-keys: |
|
||||
${{ runner.OS }}-node-
|
||||
|
||||
- name: Build Frontend
|
||||
run: |
|
||||
cd frontend
|
||||
npm i -g @angular/cli
|
||||
npm ci
|
||||
ng build
|
||||
|
||||
- uses: shuttle-hq/deploy-action@v2
|
||||
with:
|
||||
deploy-key: ${{ secrets.SHUTTLE_TOKEN }}
|
||||
# allow-dirty: "true"
|
||||
# run: cargo install --locked cargo-shuttle
|
||||
# - name: Deploy to Shuttle
|
||||
# env:
|
||||
# SHUTTLE_TOKEN: ${{ secrets.SHUTTLE_TOKEN }}
|
||||
# run: |
|
||||
# cargo shuttle login --api-key $SHUTTLE_TOKEN
|
||||
# cargo shuttle deploy
|
||||
shuttle-api-key: ${{ secrets.SHUTTLE_API_KEY }}
|
||||
project-id: proj_01JNH9KPMRS34FKC2NHWQ5YNNB
|
||||
secrets: |
|
||||
SMTP_MAIL = '${{ secrets.SMTP_MAIL }}'
|
||||
SMTP_SECRET = '${{ secrets.SMTP_SECRET }}'
|
||||
SMTP_PROVIDER = '${{ secrets.SMTP_PROVIDER }}'
|
5
.gitignore
vendored
@ -24,7 +24,8 @@ Cargo.lock
|
||||
# already existing elements were commented out
|
||||
|
||||
#/target
|
||||
|
||||
.shuttle/
|
||||
node_modules/
|
||||
|
||||
Secrets.toml
|
||||
Secrets.dev.toml
|
||||
**/*.pdf
|
||||
|
19
Cargo.toml
@ -1,11 +1,18 @@
|
||||
[package]
|
||||
name = "digitaler-frieden"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
version = "0.1.1"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
axum = "0.7.4"
|
||||
shuttle-axum = "0.45.0"
|
||||
shuttle-runtime = "0.45.0"
|
||||
axum = "0.8.1"
|
||||
email_address = "0.2.9"
|
||||
serde_json = "1.0.140"
|
||||
shuttle-axum = "0.53.0"
|
||||
shuttle-runtime = "0.53.0"
|
||||
tokio = "1.28.2"
|
||||
tower-http = { version = "0.5.2", features = ["fs"] }
|
||||
tower-http = { version = "0.6.2", features = ["fs"] }
|
||||
tracing = "0.1.41"
|
||||
serde = { version = "1.0.203", features = ["derive"] }
|
||||
lettre = { version = "0.11.15", default-features = false, features = ["builder", "smtp-transport", "ring", "rustls", "rustls-tls"] }
|
||||
uuid = { version = "1.16.0", features = ["serde", "v4"] }
|
||||
chrono = "0.4.38"
|
||||
|
12
Shuttle.toml
Normal file
@ -0,0 +1,12 @@
|
||||
[build]
|
||||
assets = [
|
||||
"frontend/dist/frontend/browser/*",
|
||||
]
|
||||
|
||||
[deploy]
|
||||
include = [
|
||||
"frontend/dist/frontend/browser/*",
|
||||
]
|
||||
assets = [
|
||||
"frontend/dist/frontend/browser/*",
|
||||
]
|
40
flake.lock
generated
@ -5,11 +5,11 @@
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1705309234,
|
||||
"narHash": "sha256-uNRRNRKmJyCRC/8y1RqBkqWBLM034y4qN7EprSdmgyA=",
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "1ef2e671c3b0c19053962c07dbda38332dcebf26",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
@ -20,46 +20,24 @@
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1706487304,
|
||||
"narHash": "sha256-LE8lVX28MV2jWJsidW13D2qrHU/RUUONendL2Q/WlJg=",
|
||||
"owner": "NixOS",
|
||||
"lastModified": 1740828860,
|
||||
"narHash": "sha256-cjbHI+zUzK5CPsQZqMhE3npTyYFt9tJ3+ohcfaOF/WM=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "90f456026d284c22b3e3497be980b2e47d0b28ac",
|
||||
"rev": "303bd8071377433a2d8f76e684ec773d70c5b642",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"owner": "nixos",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"rust-overlay",
|
||||
"nixpkgs"
|
||||
],
|
||||
"rust-overlay": "rust-overlay"
|
||||
}
|
||||
},
|
||||
"rust-overlay": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1717121863,
|
||||
"narHash": "sha256-/3sxIe7MZqF/jw1RTQCSmgTjwVod43mmrk84m50MJQ4=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "2a7b53172ed08f856b8382d7dcfd36a4e0cbd866",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
|
74
flake.nix
@ -1,67 +1,21 @@
|
||||
{
|
||||
description = "Example Rust development environment for Zero to Nix";
|
||||
|
||||
# Flake inputs
|
||||
inputs = {
|
||||
# nixpkgs.url = "https://flakehub.com/f/NixOS/nixpkgs/*.tar.gz";
|
||||
rust-overlay.url = "github:oxalica/rust-overlay"; # A helper for Rust + Nix
|
||||
# cargo2nix.url = "github:cargo2nix/cargo2nix/";
|
||||
nixpkgs.follows = "rust-overlay/nixpkgs";
|
||||
nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
|
||||
# Flake outputs
|
||||
outputs = { self, nixpkgs, rust-overlay}:
|
||||
#let
|
||||
# Overlays enable you to customize the Nixpkgs attribute set
|
||||
#overlays = [
|
||||
# Makes a `rust-bin` attribute available in Nixpkgs
|
||||
#(import rust-overlay)
|
||||
# Provides a `rustToolchain` attribute for Nixpkgs that we can use to
|
||||
# create a Rust environment
|
||||
#(self: super: {
|
||||
#rustToolchain = super.rust-bin.stable.latest.default;
|
||||
#})
|
||||
# ];
|
||||
|
||||
# Systems supported
|
||||
allSystems = [
|
||||
"x86_64-linux" # 64-bit Intel/AMD Linux
|
||||
"aarch64-linux" # 64-bit ARM Linux
|
||||
"x86_64-darwin" # 64-bit Intel macOS
|
||||
"aarch64-darwin" # 64-bit ARM macOS
|
||||
];
|
||||
|
||||
# rustTarget = nixpkgs.pkgs.rust-bin.selectLatestNightlyWith (toolchain: toolchain.default.override {
|
||||
# extensions = [ "rust-src" "rustup" "rust-analyzer" "rust-std" ];
|
||||
# targets = [ "x86_64-unknown-linux-gnu" "wasm32-unknown-unknown" ];
|
||||
# });
|
||||
|
||||
# Helper to provide system-specific attributes
|
||||
forAllSystems = f: nixpkgs.lib.genAttrs allSystems (system: f {
|
||||
pkgs = import nixpkgs { inherit overlays system; };
|
||||
});
|
||||
in
|
||||
{
|
||||
# Development environment output
|
||||
devShells = forAllSystems ({ pkgs }: {
|
||||
default = pkgs.mkShell {
|
||||
buildInputs = [
|
||||
pkgs.cargo-shuttle
|
||||
outputs = { self, nixpkgs, flake-utils }:
|
||||
flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgs = import nixpkgs { inherit system; };
|
||||
in {
|
||||
devShell = pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
nodejs
|
||||
nodePackages.npm
|
||||
nodePackages."@angular/cli"
|
||||
];
|
||||
# shellHook = ''
|
||||
# rustup target add wasm32-unknown-unknown
|
||||
# '';
|
||||
# The Nix packages provided in the environment
|
||||
packages = (with pkgs; [
|
||||
# The package provided by our custom overlay. Includes cargo, Clippy, cargo-fmt,
|
||||
# rustdoc, rustfmt, and other tools.
|
||||
# rust-analyzer
|
||||
# clippy
|
||||
# trunk
|
||||
# tailwindcss
|
||||
# rustToolchain
|
||||
]) ++ pkgs.lib.optionals pkgs.stdenv.isDarwin (with pkgs; [ libiconv ]);
|
||||
};
|
||||
});
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
|
17
frontend/.editorconfig
Normal file
@ -0,0 +1,17 @@
|
||||
# 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
|
||||
ij_typescript_use_double_quotes = false
|
||||
|
||||
[*.md]
|
||||
max_line_length = off
|
||||
trim_trailing_whitespace = false
|
42
frontend/.gitignore
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
# See https://docs.github.com/get-started/getting-started-with-git/ignoring-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/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
|
4
frontend/.vscode/extensions.json
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=827846
|
||||
"recommendations": ["angular.ng-template"]
|
||||
}
|
20
frontend/.vscode/launch.json
vendored
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "ng serve",
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "npm: start",
|
||||
"url": "http://localhost:4200/"
|
||||
},
|
||||
{
|
||||
"name": "ng test",
|
||||
"type": "chrome",
|
||||
"request": "launch",
|
||||
"preLaunchTask": "npm: test",
|
||||
"url": "http://localhost:9876/debug.html"
|
||||
}
|
||||
]
|
||||
}
|
42
frontend/.vscode/tasks.json
vendored
Normal file
@ -0,0 +1,42 @@
|
||||
{
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?LinkId=733558
|
||||
"version": "2.0.0",
|
||||
"tasks": [
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "start",
|
||||
"isBackground": true,
|
||||
"problemMatcher": {
|
||||
"owner": "typescript",
|
||||
"pattern": "$tsc",
|
||||
"background": {
|
||||
"activeOnStart": true,
|
||||
"beginsPattern": {
|
||||
"regexp": "(.*?)"
|
||||
},
|
||||
"endsPattern": {
|
||||
"regexp": "bundle generation complete"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "npm",
|
||||
"script": "test",
|
||||
"isBackground": true,
|
||||
"problemMatcher": {
|
||||
"owner": "typescript",
|
||||
"pattern": "$tsc",
|
||||
"background": {
|
||||
"activeOnStart": true,
|
||||
"beginsPattern": {
|
||||
"regexp": "(.*?)"
|
||||
},
|
||||
"endsPattern": {
|
||||
"regexp": "bundle generation complete"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
59
frontend/README.md
Normal file
@ -0,0 +1,59 @@
|
||||
# Frontend
|
||||
|
||||
This project was generated using [Angular CLI](https://github.com/angular/angular-cli) version 19.1.8.
|
||||
|
||||
## Development server
|
||||
|
||||
To start a local development server, run:
|
||||
|
||||
```bash
|
||||
ng serve
|
||||
```
|
||||
|
||||
Once the server is running, open your browser and navigate to `http://localhost:4200/`. The application will automatically reload whenever you modify any of the source files.
|
||||
|
||||
## Code scaffolding
|
||||
|
||||
Angular CLI includes powerful code scaffolding tools. To generate a new component, run:
|
||||
|
||||
```bash
|
||||
ng generate component component-name
|
||||
```
|
||||
|
||||
For a complete list of available schematics (such as `components`, `directives`, or `pipes`), run:
|
||||
|
||||
```bash
|
||||
ng generate --help
|
||||
```
|
||||
|
||||
## Building
|
||||
|
||||
To build the project run:
|
||||
|
||||
```bash
|
||||
ng build
|
||||
```
|
||||
|
||||
This will compile your project and store the build artifacts in the `dist/` directory. By default, the production build optimizes your application for performance and speed.
|
||||
|
||||
## Running unit tests
|
||||
|
||||
To execute unit tests with the [Karma](https://karma-runner.github.io) test runner, use the following command:
|
||||
|
||||
```bash
|
||||
ng test
|
||||
```
|
||||
|
||||
## Running end-to-end tests
|
||||
|
||||
For end-to-end (e2e) testing, run:
|
||||
|
||||
```bash
|
||||
ng e2e
|
||||
```
|
||||
|
||||
Angular CLI does not come with an end-to-end testing framework by default. You can choose one that suits your needs.
|
||||
|
||||
## Additional Resources
|
||||
|
||||
For more information on using the Angular CLI, including detailed command references, visit the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.
|
103
frontend/angular.json
Normal file
@ -0,0 +1,103 @@
|
||||
{
|
||||
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||
"version": 1,
|
||||
"newProjectRoot": "projects",
|
||||
"projects": {
|
||||
"frontend": {
|
||||
"projectType": "application",
|
||||
"schematics": {},
|
||||
"root": "",
|
||||
"sourceRoot": "src",
|
||||
"prefix": "app",
|
||||
"architect": {
|
||||
"build": {
|
||||
"builder": "@angular-devkit/build-angular:application",
|
||||
"options": {
|
||||
"outputPath": "dist/frontend",
|
||||
"index": "src/index.html",
|
||||
"browser": "src/main.ts",
|
||||
"polyfills": [
|
||||
"zone.js"
|
||||
],
|
||||
"tsConfig": "tsconfig.app.json",
|
||||
"assets": [
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "public",
|
||||
"output": "/public"
|
||||
}
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.css"
|
||||
],
|
||||
"scripts": []
|
||||
},
|
||||
"configurations": {
|
||||
"production": {
|
||||
"budgets": [
|
||||
{
|
||||
"type": "initial",
|
||||
"maximumWarning": "500kB",
|
||||
"maximumError": "1MB"
|
||||
},
|
||||
{
|
||||
"type": "anyComponentStyle",
|
||||
"maximumWarning": "4kB",
|
||||
"maximumError": "8kB"
|
||||
}
|
||||
],
|
||||
"outputHashing": "all"
|
||||
},
|
||||
"development": {
|
||||
"optimization": false,
|
||||
"extractLicenses": false,
|
||||
"sourceMap": true,
|
||||
"fileReplacements": [
|
||||
{
|
||||
"replace": "src/environments/environment.ts",
|
||||
"with": "src/environments/environment.development.ts"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "production"
|
||||
},
|
||||
"serve": {
|
||||
"builder": "@angular-devkit/build-angular:dev-server",
|
||||
"configurations": {
|
||||
"production": {
|
||||
"buildTarget": "frontend:build:production"
|
||||
},
|
||||
"development": {
|
||||
"buildTarget": "frontend:build:development"
|
||||
}
|
||||
},
|
||||
"defaultConfiguration": "development"
|
||||
},
|
||||
"extract-i18n": {
|
||||
"builder": "@angular-devkit/build-angular:extract-i18n"
|
||||
},
|
||||
"test": {
|
||||
"builder": "@angular-devkit/build-angular:karma",
|
||||
"options": {
|
||||
"polyfills": [
|
||||
"zone.js",
|
||||
"zone.js/testing"
|
||||
],
|
||||
"tsConfig": "tsconfig.spec.json",
|
||||
"assets": [
|
||||
{
|
||||
"glob": "**/*",
|
||||
"input": "public"
|
||||
}
|
||||
],
|
||||
"styles": [
|
||||
"src/styles.css"
|
||||
],
|
||||
"scripts": []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
14929
frontend/package-lock.json
generated
Normal file
38
frontend/package.json
Normal file
@ -0,0 +1,38 @@
|
||||
{
|
||||
"name": "digitaler-frieden",
|
||||
"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": "^19.1.0",
|
||||
"@angular/common": "^19.1.0",
|
||||
"@angular/compiler": "^19.1.0",
|
||||
"@angular/core": "^19.1.0",
|
||||
"@angular/forms": "^19.1.0",
|
||||
"@angular/platform-browser": "^19.1.0",
|
||||
"@angular/platform-browser-dynamic": "^19.1.0",
|
||||
"@angular/router": "^19.1.0",
|
||||
"rxjs": "~7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"zone.js": "~0.15.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@angular-devkit/build-angular": "^19.1.8",
|
||||
"@angular/cli": "^19.1.8",
|
||||
"@angular/compiler-cli": "^19.1.0",
|
||||
"@types/jasmine": "~5.1.0",
|
||||
"jasmine-core": "~5.5.0",
|
||||
"karma": "~6.4.0",
|
||||
"karma-chrome-launcher": "~3.2.0",
|
||||
"karma-coverage": "~2.2.0",
|
||||
"karma-jasmine": "~5.1.0",
|
||||
"karma-jasmine-html-reporter": "~2.1.0",
|
||||
"typescript": "~5.7.2"
|
||||
}
|
||||
}
|
BIN
frontend/public/assets/Mockup.jpg
Normal file
After Width: | Height: | Size: 1.1 MiB |
1
frontend/public/assets/add.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#FFFFFF"><path d="M0 0h24v24H0z" fill="none"/><path d="M13 7h-2v4H7v2h4v4h2v-4h4v-2h-4V7zm-1-5C6.49 2 2 6.49 2 12s4.49 10 10 10 10-4.49 10-10S17.51 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"/></svg>
|
After Width: | Height: | Size: 315 B |
BIN
frontend/public/assets/chat_bubbles.jpg
Normal file
After Width: | Height: | Size: 290 KiB |
BIN
frontend/public/assets/chat_bubbles.png
Normal file
After Width: | Height: | Size: 332 KiB |
BIN
frontend/public/assets/digitaler-frieden_logo.jpg
Normal file
After Width: | Height: | Size: 182 KiB |
8
frontend/public/assets/digitaler-frieden_logo.svg
Normal file
@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<svg style="enable-background:new 0 0 3000 2000" version="1.1" viewBox="0 0 3e3 2e3" xmlns="http://www.w3.org/2000/svg">
|
||||
<style type="text/css">.st0{fill:#FFFFFF;}</style>
|
||||
<path class="st0" d="m1199.2 993.15 308.76-519.02-0.28-0.29-184.43-153.02h-393.98l282.78 474.48-43.11 72.38-370.14-621.09h551.29l254.22 211.26-35.97 60.53h471.2l-44.25 74.23h-471.2l-238.53 400.54zm444.65-746.56-62.38 104.92 57.67 47.96 46.96-78.8h471.2l44.11-74.08z" style="fill:#fff">
|
||||
<title>logo</title>
|
||||
</path>
|
||||
<path class="st0" d="m2072.1 1753.3h14.43v-107.56l84.7 107.56h11.82v-131.18h-14.45v105.13l-82.64-105.13h-13.86zm-200.57-117.69h80.02v-13.49h-94.82v131.18h95.75v-13.49h-80.95v-45.92h71.59v-13.49h-71.59zm-207.72-13.4h-45.54v131.18h45.54c41.11 0 69.66-28.83 69.66-65.81 0-37.11-28.55-65.37-69.66-65.37zm0 117.33h-30.83v-103.78h30.83c33.12 0 54.24 22.98 54.24 52.1 0 29.27-21.12 51.68-54.24 51.68zm-246.06-103.93h80.02v-13.49h-94.83v131.18h95.77v-13.49h-80.96v-45.92h71.58v-13.49h-71.58zm-160.07-13.49h14.81v131.18h-14.81zm-160 77.6c20.56-3.85 35.54-16.56 35.54-38.4 0-23.7-18.7-39.11-47.39-39.11h-56.39v131.18h14.7v-50.96h37.54l37.97 50.96h18.27zm-53.53-10.71v-53.24h40.54c21.13 0 33.55 9.85 33.55 25.98 0 17.13-14.13 27.26-33.69 27.26zm-141.13-6.36h-71.03v-46.85h79.45v-13.68h-94.26v131.18h14.81v-57.16h71.03zm1644.4-314.39h3c45.82 0 73.8-25.27 73.8-59.52 0-34.4-27.98-58.38-73.8-58.38h-108.63v220.83h28.26l0.29-102.92h44.11l75.51 102.63h36.54zm-77.08-24.69v-69.23h80.08c28.55 0 44.11 12.42 44.11 34.12 0 21.27-15.56 35.12-44.11 35.12h-80.08zm-277.07 103.06v-75.37h119.76v-23.98h-119.76v-72.94h139.18v-24.27h-167.73v220.83h170.72v-24.27zm-258.94 0v-196.56h-28.41v220.83h158.31v-24.27zm-230.1-196.56h-32.97l-101.21 220.83h31.83l25.98-55.53h120.76l25.27 55.53h31.55zm-65.09 141.46 48.82-112.91 49.82 112.91zm-309.62-141.46v24.27h83.65v196.56h28.55v-196.56h84.08v-24.27zm-128.33 0v220.83h28.41v-220.83zm-108.48 103.92h-125.05v23.84l94.64 0.43c-2.28 10.56-6 20.13-11.13 29.26-3 5.57-7.28 10.56-11.7 15.27l-0.57 0.57c-15.27 14.85-36.54 24.55-62.24 26.55-3 0.43-6.28 0.43-9.56 0.43h-3.71c-35.83-1.14-64.38-16.99-79.65-42.25h-0.43c-7.85-13.56-12.56-29.55-12.56-47.82 0-52.82 39.54-89.64 96.35-89.64 37.83 0 68.38 15.99 84.08 42.82l32.12-0.43c-18.56-41.11-62.09-67.09-116.19-67.09-72.37 0-125.9 46.96-125.9 114.34 0 66.8 51.82 113.63 122.91 114.62h9.56c3.71-0.28 7.71-0.71 11.42-0.71l6.28-1c11.85-2 23.55-5.28 33.55-9.56 13.27-5.71 25.12-13.28 34.83-22.55 0.29-0.43 0.71-0.71 0.71-1.14 4.85-4.71 9.56-9.85 13.27-15.56l0.86-1c11.42-16.56 17.7-36.11 18.7-58.1-0.02-6.57-0.45-8.57-0.59-11.28zm-391.55-103.92v220.83h28.55v-220.83zm-238.53 0h-89.07v220.83h89.07c72.8 0 126.61-42.97 126.61-110.63 0-67.38-53.81-110.2-126.61-110.2zm0 196.13h-60.38v-171.58h60.38c58.81 0 97.07 34.12 97.07 85.65 0 51.82-38.26 85.93-97.07 85.93z" style="fill:#fff"/>
|
||||
</svg>
|
After Width: | Height: | Size: 2.8 KiB |
BIN
frontend/public/assets/icon.png
Normal file
After Width: | Height: | Size: 6.9 KiB |
107
frontend/public/assets/icon.svg
Normal file
@ -0,0 +1,107 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
|
||||
<svg
|
||||
version="1.1"
|
||||
id="Layer_1"
|
||||
x="0px"
|
||||
y="0px"
|
||||
viewBox="0 0 3072000 2048000"
|
||||
xml:space="preserve"
|
||||
sodipodi:docname="vector-SVG.svg"
|
||||
width="3072000"
|
||||
height="2048000"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||
id="defs20" /><sodipodi:namedview
|
||||
id="namedview20"
|
||||
pagecolor="#ffffff"
|
||||
bordercolor="#000000"
|
||||
borderopacity="0.25"
|
||||
inkscape:showpageshadow="2"
|
||||
inkscape:pageopacity="0.0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
inkscape:deskcolor="#d1d1d1" /> <style
|
||||
type="text/css"
|
||||
id="style1"> .st0{fill:#FFFFFF;} </style> <rect
|
||||
x="-2963012"
|
||||
y="-975512"
|
||||
width="1024"
|
||||
height="1024"
|
||||
id="rect1"
|
||||
style="stroke-width:0.418046" /> <g
|
||||
id="g20"
|
||||
transform="translate(-2964000.1,-975619.87)"> <g
|
||||
id="g19"
|
||||
transform="matrix(0.57036525,0,0,0.57036525,644.47575,266.3177)"
|
||||
inkscape:export-filename="icon.png"
|
||||
inkscape:export-xdpi="32.768002"
|
||||
inkscape:export-ydpi="32.768002"> <g
|
||||
id="g4"> <g
|
||||
id="g1"> <path
|
||||
class="st0"
|
||||
d="m 1643.8,246.59 -62.38,104.92 57.67,47.96 46.96,-78.8 h 471.2 l 44.11,-74.08 z"
|
||||
id="path1" /> </g> <g
|
||||
id="g3"> <g
|
||||
id="g2"> <polygon
|
||||
class="st0"
|
||||
points="1507.63,473.84 1323.2,320.82 929.22,320.82 1212,795.3 1168.89,867.68 798.75,246.59 1350.04,246.59 1604.26,457.85 1568.29,518.38 2039.49,518.38 1995.24,592.61 1524.04,592.61 1285.51,993.15 1199.15,993.15 1507.91,474.13 "
|
||||
id="polygon1" /> </g> </g> </g> <g
|
||||
id="g18"
|
||||
style="display:none"> <g
|
||||
id="g17"> <g
|
||||
id="g16"> <path
|
||||
class="st0"
|
||||
d="m 462.6,1250.09 h -89.07 v 220.83 h 89.07 c 72.8,0 126.61,-42.97 126.61,-110.63 0,-67.38 -53.81,-110.2 -126.61,-110.2 z m 0,196.13 h -60.38 v -171.58 h 60.38 c 58.81,0 97.07,34.12 97.07,85.65 0,51.82 -38.26,85.93 -97.07,85.93 z"
|
||||
id="path4" /> <path
|
||||
class="st0"
|
||||
d="m 701.13,1250.09 v 220.83 h 28.55 v -220.83 z"
|
||||
id="path5" /> <path
|
||||
class="st0"
|
||||
d="M 1092.68,1354.01 H 967.63 v 23.84 l 94.64,0.43 c -2.28,10.56 -6,20.13 -11.13,29.26 -3,5.57 -7.28,10.56 -11.7,15.27 l -0.57,0.57 c -15.27,14.85 -36.54,24.55 -62.24,26.55 -3,0.43 -6.28,0.43 -9.56,0.43 h -3.71 c -35.83,-1.14 -64.38,-16.99 -79.65,-42.25 h -0.43 c -7.85,-13.56 -12.56,-29.55 -12.56,-47.82 0,-52.82 39.54,-89.64 96.35,-89.64 37.83,0 68.38,15.99 84.08,42.82 l 32.12,-0.43 c -18.56,-41.11 -62.09,-67.09 -116.19,-67.09 -72.37,0 -125.9,46.96 -125.9,114.34 0,66.8 51.82,113.63 122.91,114.62 h 9.56 c 3.71,-0.28 7.71,-0.71 11.42,-0.71 l 6.28,-1 c 11.85,-2 23.55,-5.28 33.55,-9.56 13.27,-5.71 25.12,-13.28 34.83,-22.55 0.29,-0.43 0.71,-0.71 0.71,-1.14 4.85,-4.71 9.56,-9.85 13.27,-15.56 l 0.86,-1 c 11.42,-16.56 17.7,-36.11 18.7,-58.1 -0.02,-6.57 -0.45,-8.57 -0.59,-11.28 z"
|
||||
id="path6" /> <path
|
||||
class="st0"
|
||||
d="m 1201.16,1250.09 v 220.83 h 28.41 v -220.83 z"
|
||||
id="path7" /> <path
|
||||
class="st0"
|
||||
d="m 1329.49,1250.09 v 24.27 h 83.65 v 196.56 h 28.55 v -196.56 h 84.08 v -24.27 z"
|
||||
id="path8" /> <path
|
||||
class="st0"
|
||||
d="m 1704.2,1250.09 h -32.97 l -101.21,220.83 h 31.83 l 25.98,-55.53 h 120.76 l 25.27,55.53 h 31.55 z m -65.09,141.46 48.82,-112.91 49.82,112.91 z"
|
||||
id="path9" /> <path
|
||||
class="st0"
|
||||
d="m 1934.3,1446.65 v -196.56 h -28.41 v 220.83 h 158.31 v -24.27 z"
|
||||
id="path10" /> <path
|
||||
class="st0"
|
||||
d="m 2193.24,1446.65 v -75.37 H 2313 v -23.98 h -119.76 v -72.94 h 139.18 v -24.27 h -167.73 v 220.83 h 170.72 v -24.27 z"
|
||||
id="path11" /> <path
|
||||
class="st0"
|
||||
d="m 2547.39,1368.28 h 3 c 45.82,0 73.8,-25.27 73.8,-59.52 0,-34.4 -27.98,-58.38 -73.8,-58.38 h -108.63 v 220.83 h 28.26 l 0.29,-102.92 h 44.11 l 75.51,102.63 h 36.54 z m -77.08,-24.69 v -69.23 h 80.08 c 28.55,0 44.11,12.42 44.11,34.12 0,21.27 -15.56,35.12 -44.11,35.12 h -80.08 z"
|
||||
id="path12" /> <g
|
||||
id="g15"> <polygon
|
||||
class="st0"
|
||||
points="911.43,1635.82 911.43,1622.14 817.17,1622.14 817.17,1753.32 831.98,1753.32 831.98,1696.16 903.01,1696.16 903.01,1682.67 831.98,1682.67 831.98,1635.82 "
|
||||
id="polygon12" /> <path
|
||||
class="st0"
|
||||
d="m 1097.67,1699.74 c 20.56,-3.85 35.54,-16.56 35.54,-38.4 0,-23.7 -18.7,-39.11 -47.39,-39.11 h -56.39 v 131.18 h 14.7 v -50.96 h 37.54 l 37.97,50.96 h 18.27 z m -53.53,-10.71 v -53.24 h 40.54 c 21.13,0 33.55,9.85 33.55,25.98 0,17.13 -14.13,27.26 -33.69,27.26 z"
|
||||
id="path13" /> <rect
|
||||
x="1257.67"
|
||||
y="1622.14"
|
||||
class="st0"
|
||||
width="14.81"
|
||||
height="131.17999"
|
||||
id="rect13" /> <polygon
|
||||
class="st0"
|
||||
points="1402.93,1622.14 1402.93,1753.32 1498.7,1753.32 1498.7,1739.83 1417.74,1739.83 1417.74,1693.91 1489.32,1693.91 1489.32,1680.42 1417.74,1680.42 1417.74,1635.63 1497.76,1635.63 1497.76,1622.14 "
|
||||
id="polygon13" /> <path
|
||||
class="st0"
|
||||
d="m 1663.8,1622.23 h -45.54 v 131.18 h 45.54 c 41.11,0 69.66,-28.83 69.66,-65.81 0,-37.11 -28.55,-65.37 -69.66,-65.37 z m 0,117.33 h -30.83 v -103.78 h 30.83 c 33.12,0 54.24,22.98 54.24,52.1 0,29.27 -21.12,51.68 -54.24,51.68 z"
|
||||
id="path14" /> <polygon
|
||||
class="st0"
|
||||
points="1856.72,1622.14 1856.72,1753.32 1952.47,1753.32 1952.47,1739.83 1871.52,1739.83 1871.52,1693.91 1943.11,1693.91 1943.11,1680.42 1871.52,1680.42 1871.52,1635.63 1951.54,1635.63 1951.54,1622.14 "
|
||||
id="polygon14" /> <polygon
|
||||
class="st0"
|
||||
points="2171.22,1753.32 2183.04,1753.32 2183.04,1622.14 2168.59,1622.14 2168.59,1727.27 2085.95,1622.14 2072.09,1622.14 2072.09,1753.32 2086.52,1753.32 2086.52,1645.76 "
|
||||
id="polygon15" /> </g> </g> </g> </g> </g> </g> </svg>
|
After Width: | Height: | Size: 5.9 KiB |
1
frontend/public/assets/icon_document_upload.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 -960 960 960" width="24px" fill="#e8eaed"><path d="M440-200h80v-167l64 64 56-57-160-160-160 160 57 56 63-63v167ZM240-80q-33 0-56.5-23.5T160-160v-640q0-33 23.5-56.5T240-880h320l240 240v480q0 33-23.5 56.5T720-80H240Zm280-520v-200H240v640h480v-440H520ZM240-800v200-200 640-640Z"/></svg>
|
After Width: | Height: | Size: 348 B |
BIN
frontend/public/assets/icon_round.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
frontend/public/assets/icons/chair_magnifier.png
Normal file
After Width: | Height: | Size: 45 KiB |
BIN
frontend/public/assets/icons/conversation.png
Normal file
After Width: | Height: | Size: 23 KiB |
BIN
frontend/public/assets/icons/financial_growth.png
Normal file
After Width: | Height: | Size: 56 KiB |
BIN
frontend/public/assets/icons/icon.jpg
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
frontend/public/assets/icons/map_search.png
Normal file
After Width: | Height: | Size: 19 KiB |
BIN
frontend/public/assets/icons/message_lookout.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
frontend/public/assets/icons/office.png
Normal file
After Width: | Height: | Size: 21 KiB |
BIN
frontend/public/assets/icons/people_cheering.png
Normal file
After Width: | Height: | Size: 64 KiB |
1
frontend/public/assets/mail.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#FFFFFF"><path d="M0 0h24v24H0z" fill="none"/><path d="M20 4H4c-1.1 0-1.99.9-1.99 2L2 18c0 1.1.9 2 2 2h16c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 14H4V8l8 5 8-5v10zm-8-7L4 6h16l-8 5z"/></svg>
|
After Width: | Height: | Size: 279 B |
1
frontend/public/assets/paper_plane.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#FFFFFF"><path d="M0 0h24v24H0z" fill="none"/><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg>
|
After Width: | Height: | Size: 194 B |
1
frontend/public/assets/remove.svg
Normal file
@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="#FFFFFF"><path d="M0 0h24v24H0z" fill="none"/><path d="M7 11v2h10v-2H7zm5-9C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"/></svg>
|
After Width: | Height: | Size: 296 B |
1177
frontend/public/assets/styles.css
Normal file
BIN
frontend/public/assets/vector-EPS.eps
Normal file
69
frontend/public/assets/vector-SVG.svg
Normal file
@ -0,0 +1,69 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 24.3.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 3000 2000" style="enable-background:new 0 0 3000 2000;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
</style>
|
||||
<rect x="0" y="0" width="3000" height="2000"/>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M1643.8,246.59l-62.38,104.92l57.67,47.96l46.96-78.8h471.2l44.11-74.08H1643.8z"/>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<polygon class="st0" points="2039.49,518.38 1995.24,592.61 1524.04,592.61 1285.51,993.15 1199.15,993.15 1507.91,474.13
|
||||
1507.63,473.84 1323.2,320.82 929.22,320.82 1212,795.3 1168.89,867.68 798.75,246.59 1350.04,246.59 1604.26,457.85
|
||||
1568.29,518.38 "/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
<g>
|
||||
<g>
|
||||
<path class="st0" d="M462.6,1250.09h-89.07v220.83h89.07c72.8,0,126.61-42.97,126.61-110.63
|
||||
C589.21,1292.91,535.4,1250.09,462.6,1250.09z M462.6,1446.22h-60.38v-171.58h60.38c58.81,0,97.07,34.12,97.07,85.65
|
||||
C559.67,1412.11,521.41,1446.22,462.6,1446.22z"/>
|
||||
<path class="st0" d="M701.13,1250.09v220.83h28.55v-220.83H701.13z"/>
|
||||
<path class="st0" d="M1092.68,1354.01H967.63v23.84l94.64,0.43c-2.28,10.56-6,20.13-11.13,29.26c-3,5.57-7.28,10.56-11.7,15.27
|
||||
l-0.57,0.57c-15.27,14.85-36.54,24.55-62.24,26.55c-3,0.43-6.28,0.43-9.56,0.43h-3.71c-35.83-1.14-64.38-16.99-79.65-42.25
|
||||
h-0.43c-7.85-13.56-12.56-29.55-12.56-47.82c0-52.82,39.54-89.64,96.35-89.64c37.83,0,68.38,15.99,84.08,42.82l32.12-0.43
|
||||
c-18.56-41.11-62.09-67.09-116.19-67.09c-72.37,0-125.9,46.96-125.9,114.34c0,66.8,51.82,113.63,122.91,114.62h9.56
|
||||
c3.71-0.28,7.71-0.71,11.42-0.71l6.28-1c11.85-2,23.55-5.28,33.55-9.56c13.27-5.71,25.12-13.28,34.83-22.55
|
||||
c0.29-0.43,0.71-0.71,0.71-1.14c4.85-4.71,9.56-9.85,13.27-15.56l0.86-1c11.42-16.56,17.7-36.11,18.7-58.1
|
||||
C1093.25,1358.72,1092.82,1356.72,1092.68,1354.01z"/>
|
||||
<path class="st0" d="M1201.16,1250.09v220.83h28.41v-220.83H1201.16z"/>
|
||||
<path class="st0" d="M1329.49,1250.09v24.27h83.65v196.56h28.55v-196.56h84.08v-24.27H1329.49z"/>
|
||||
<path class="st0" d="M1704.2,1250.09h-32.97l-101.21,220.83h31.83l25.98-55.53h120.76l25.27,55.53h31.55L1704.2,1250.09z
|
||||
M1639.11,1391.55l48.82-112.91l49.82,112.91H1639.11z"/>
|
||||
<path class="st0" d="M1934.3,1446.65v-196.56h-28.41v220.83h158.31v-24.27H1934.3z"/>
|
||||
<path class="st0" d="M2193.24,1446.65v-75.37h119.76v-23.98h-119.76v-72.94h139.18v-24.27h-167.73v220.83h170.72v-24.27
|
||||
H2193.24z"/>
|
||||
<path class="st0" d="M2547.39,1368.28h3c45.82,0,73.8-25.27,73.8-59.52c0-34.4-27.98-58.38-73.8-58.38h-108.63v220.83h28.26
|
||||
l0.29-102.92h44.11l75.51,102.63h36.54L2547.39,1368.28z M2470.31,1343.59v-69.23h80.08c28.55,0,44.11,12.42,44.11,34.12
|
||||
c0,21.27-15.56,35.12-44.11,35.12H2470.31z"/>
|
||||
<g>
|
||||
<polygon class="st0" points="817.17,1753.32 831.98,1753.32 831.98,1696.16 903.01,1696.16 903.01,1682.67 831.98,1682.67
|
||||
831.98,1635.82 911.43,1635.82 911.43,1622.14 817.17,1622.14 "/>
|
||||
<path class="st0" d="M1097.67,1699.74c20.56-3.85,35.54-16.56,35.54-38.4c0-23.7-18.7-39.11-47.39-39.11h-56.39v131.18h14.7
|
||||
v-50.96h37.54l37.97,50.96h18.27L1097.67,1699.74z M1044.14,1689.03v-53.24h40.54c21.13,0,33.55,9.85,33.55,25.98
|
||||
c0,17.13-14.13,27.26-33.69,27.26H1044.14z"/>
|
||||
<rect x="1257.67" y="1622.14" class="st0" width="14.81" height="131.18"/>
|
||||
<polygon class="st0" points="1417.74,1693.91 1489.32,1693.91 1489.32,1680.42 1417.74,1680.42 1417.74,1635.63
|
||||
1497.76,1635.63 1497.76,1622.14 1402.93,1622.14 1402.93,1753.32 1498.7,1753.32 1498.7,1739.83 1417.74,1739.83 "/>
|
||||
<path class="st0" d="M1663.8,1622.23h-45.54v131.18h45.54c41.11,0,69.66-28.83,69.66-65.81
|
||||
C1733.46,1650.49,1704.91,1622.23,1663.8,1622.23z M1663.8,1739.56h-30.83v-103.78h30.83c33.12,0,54.24,22.98,54.24,52.1
|
||||
C1718.04,1717.15,1696.92,1739.56,1663.8,1739.56z"/>
|
||||
<polygon class="st0" points="1871.52,1693.91 1943.11,1693.91 1943.11,1680.42 1871.52,1680.42 1871.52,1635.63
|
||||
1951.54,1635.63 1951.54,1622.14 1856.72,1622.14 1856.72,1753.32 1952.47,1753.32 1952.47,1739.83 1871.52,1739.83 "/>
|
||||
<polygon class="st0" points="2168.59,1622.14 2168.59,1727.27 2085.95,1622.14 2072.09,1622.14 2072.09,1753.32
|
||||
2086.52,1753.32 2086.52,1645.76 2171.22,1753.32 2183.04,1753.32 2183.04,1622.14 "/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 4.4 KiB |
BIN
frontend/public/favicon.ico
Normal file
After Width: | Height: | Size: 12 KiB |
0
frontend/src/app/app.component.css
Normal file
1
frontend/src/app/app.component.html
Normal file
@ -0,0 +1 @@
|
||||
<router-outlet/>
|
29
frontend/src/app/app.component.spec.ts
Normal file
@ -0,0 +1,29 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
import { AppComponent } from './app.component';
|
||||
|
||||
describe('AppComponent', () => {
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [AppComponent],
|
||||
}).compileComponents();
|
||||
});
|
||||
|
||||
it('should create the app', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app).toBeTruthy();
|
||||
});
|
||||
|
||||
it(`should have the 'frontend' title`, () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
const app = fixture.componentInstance;
|
||||
expect(app.title).toEqual('frontend');
|
||||
});
|
||||
|
||||
it('should render title', () => {
|
||||
const fixture = TestBed.createComponent(AppComponent);
|
||||
fixture.detectChanges();
|
||||
const compiled = fixture.nativeElement as HTMLElement;
|
||||
expect(compiled.querySelector('h1')?.textContent).toContain('Hello, frontend');
|
||||
});
|
||||
});
|
12
frontend/src/app/app.component.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {RouterOutlet} from '@angular/router';
|
||||
|
||||
@Component({
|
||||
selector: 'app-root',
|
||||
imports: [RouterOutlet],
|
||||
templateUrl: './app.component.html',
|
||||
styleUrl: './app.component.css'
|
||||
})
|
||||
export class AppComponent {
|
||||
title = 'Digitaler Frieden';
|
||||
}
|
9
frontend/src/app/app.config.ts
Normal file
@ -0,0 +1,9 @@
|
||||
import {ApplicationConfig, provideZoneChangeDetection} from '@angular/core';
|
||||
import {provideRouter} from '@angular/router';
|
||||
|
||||
import {routes} from './app.routes';
|
||||
import {provideHttpClient} from "@angular/common/http";
|
||||
|
||||
export const appConfig: ApplicationConfig = {
|
||||
providers: [provideZoneChangeDetection({eventCoalescing: true}), provideRouter(routes), provideHttpClient()]
|
||||
};
|
43
frontend/src/app/app.routes.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import {Routes} from '@angular/router';
|
||||
import {StartComponent} from "./page/onboarding/start/start.component";
|
||||
import {NotificationComponent} from "./page/onboarding/notification/notification.component";
|
||||
import {RegistrationComponent} from "./page/onboarding/registration/registration.component";
|
||||
import {LoginComponent} from "./page/login/login.component";
|
||||
import {VerificationComponent} from "./page/onboarding/verification/verification.component";
|
||||
import {PrivacyComponent} from "./page/privacy/privacy.component";
|
||||
import {TermsOfServiceComponent} from "./page/terms-of-service/terms-of-service.component";
|
||||
|
||||
export const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: StartComponent,
|
||||
},
|
||||
{
|
||||
path: 'onboarding/notifications',
|
||||
component: NotificationComponent,
|
||||
},
|
||||
{
|
||||
path: 'onboarding/registration',
|
||||
component: RegistrationComponent,
|
||||
},
|
||||
{
|
||||
path: 'onboarding/verification',
|
||||
component: VerificationComponent,
|
||||
},
|
||||
{
|
||||
path: 'privacy',
|
||||
component: PrivacyComponent,
|
||||
},
|
||||
{
|
||||
path: 'terms',
|
||||
component: TermsOfServiceComponent,
|
||||
},
|
||||
{
|
||||
path: 'login',
|
||||
component: LoginComponent,
|
||||
},
|
||||
{
|
||||
path: '**',
|
||||
component: StartComponent,
|
||||
},
|
||||
];
|
@ -0,0 +1,25 @@
|
||||
input {
|
||||
background-color: rgba(132, 129, 122, 1.0);
|
||||
border: none;
|
||||
font-size: 1.2em;
|
||||
line-height: 2.5em;
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
border-radius: 5px;
|
||||
color: var(--primary-text-color);
|
||||
}
|
||||
|
||||
.valid {
|
||||
|
||||
}
|
||||
|
||||
.invalid {
|
||||
border-color: var(--error-color);
|
||||
border-width: 2px;
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
input:focus {
|
||||
outline-color: var(--primary-background-color);
|
||||
outline-width: 100px;
|
||||
}
|
@ -0,0 +1 @@
|
||||
<input [(ngModel)]="value" [disabled]="disabled" [ngClass]="{'invalid': value != '' && !verify_mail()}" type="email"/>
|
@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { EmailFieldComponent } from './email-field.component';
|
||||
|
||||
describe('EmailFieldComponent', () => {
|
||||
let component: EmailFieldComponent;
|
||||
let fixture: ComponentFixture<EmailFieldComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [EmailFieldComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(EmailFieldComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,34 @@
|
||||
import {Component, EventEmitter, Input, Output} from '@angular/core';
|
||||
import {FormsModule} from "@angular/forms";
|
||||
import {verify_email} from "../../../model/util";
|
||||
import {NgClass} from "@angular/common";
|
||||
|
||||
@Component({
|
||||
selector: 'atomic-email-field',
|
||||
imports: [
|
||||
FormsModule,
|
||||
NgClass
|
||||
],
|
||||
templateUrl: './email-field.component.html',
|
||||
styleUrl: './email-field.component.css'
|
||||
})
|
||||
export class EmailFieldComponent {
|
||||
@Input() disabled: boolean = false;
|
||||
|
||||
private _value: string = '';
|
||||
@Input()
|
||||
get value(): string {
|
||||
return this._value;
|
||||
}
|
||||
|
||||
set value(val: string) {
|
||||
this._value = val;
|
||||
this.valueChange.emit(this._value);
|
||||
}
|
||||
|
||||
@Output() valueChange = new EventEmitter<string>();
|
||||
|
||||
verify_mail(): boolean {
|
||||
return verify_email(this._value);
|
||||
}
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
.container {
|
||||
display: flex;
|
||||
column-gap: 2rem;
|
||||
row-gap: 2rem;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-around;
|
||||
margin-top: 20px;
|
||||
color: var(--primary-text-color);
|
||||
max-width: 25rem;
|
||||
}
|
||||
|
||||
.item {
|
||||
width: 25rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
li > p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-weight: 600;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-weight: 600;
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
h3, h2 {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.product-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
column-gap: 0.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.product-info {
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.info-box {
|
||||
display: none;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 100%;
|
||||
width: 300px;
|
||||
background-color: #333;
|
||||
color: var(--primary-text-color);
|
||||
padding: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
|
||||
z-index: 10;
|
||||
white-space: pre-line;
|
||||
text-align: left;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.product-info:hover .info-box {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.product {
|
||||
background-color: #1a1a1a;
|
||||
width: 100%;
|
||||
padding-top: 0.8rem;
|
||||
padding-bottom: 0.8rem;
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.product-light {
|
||||
background-color: rgba(132, 129, 122, 1.0);
|
||||
}
|
||||
|
||||
.product-standard {
|
||||
background-color: rgba(33, 140, 116, 1.0);
|
||||
}
|
||||
|
||||
.product-premium {
|
||||
background-color: rgba(34, 112, 147, 1.0);
|
||||
}
|
||||
|
||||
.item > ul > li {
|
||||
list-style-type: none;
|
||||
text-align: start;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: start;
|
||||
align-items: center;
|
||||
column-gap: 1rem;
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
<div class="container">
|
||||
<div class="item" *ngFor="let product of products">
|
||||
<div class="product-header">
|
||||
<div class="product" [ngClass]="product.class">
|
||||
<h3>{{ product.name }}</h3>
|
||||
<h2>{{ product.price }} Euro / einmalig</h2>
|
||||
</div>
|
||||
<div class="product-info">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="2rem" viewBox="0 -960 960 960" width="2rem"
|
||||
fill="rgba(51, 217, 178,1.0)">
|
||||
<path d="M440-280h80v-240h-80v240Zm40-320q17 0 28.5-11.5T520-640q0-17-11.5-28.5T480-680q-17 0-28.5 11.5T440-640q0 17 11.5 28.5T480-600Zm0 520q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"/>
|
||||
</svg>
|
||||
<div class="info-box" *ngIf="product.info">
|
||||
{{ product.info }}
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="product-info">-->
|
||||
<!-- <svg xmlns="http://www.w3.org/2000/svg" height="2rem" viewBox="0 -960 960 960" width="2rem"-->
|
||||
<!-- fill="rgba(51, 217, 178,1.0)">-->
|
||||
<!-- <path d="M440-280h80v-240h-80v240Zm40-320q17 0 28.5-11.5T520-640q0-17-11.5-28.5T480-680q-17 0-28.5 11.5T440-640q0 17 11.5 28.5T480-600Zm0 520q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"/>-->
|
||||
<!-- </svg>-->
|
||||
<!-- <!– <p>{{ product.info }}</p>–>-->
|
||||
<!-- </div>-->
|
||||
</div>
|
||||
<ul>
|
||||
<li *ngFor="let feature of product.features">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" height="2rem" viewBox="0 -960 960 960" width="2rem"
|
||||
fill="rgba(64, 64, 122,1.0)">
|
||||
<path d="m424-296 282-282-56-56-226 226-114-114-56 56 170 170Zm56 216q-83 0-156-31.5T197-197q-54-54-85.5-127T80-480q0-83 31.5-156T197-763q54-54 127-85.5T480-880q83 0 156 31.5T763-763q54 54 85.5 127T880-480q0 83-31.5 156T763-197q-54 54-127 85.5T480-80Zm0-80q134 0 227-93t93-227q0-134-93-227t-227-93q-134 0-227 93t-93 227q0 134 93 227t227 93Zm0-320Z"/>
|
||||
</svg>
|
||||
<!-- <img src="/public/assets/digitaler-frieden_logo.svg" style="width: 2rem; height: 2rem;">-->
|
||||
<p>
|
||||
{{ feature }}
|
||||
</p>
|
||||
<!-- </div>-->
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ProductOverviewComponent } from './product-overview.component';
|
||||
|
||||
describe('ProductOverviewComponent', () => {
|
||||
let component: ProductOverviewComponent;
|
||||
let fixture: ComponentFixture<ProductOverviewComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [ProductOverviewComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(ProductOverviewComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,36 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {NgClass, NgForOf, NgIf} from "@angular/common";
|
||||
|
||||
@Component({
|
||||
selector: 'atomic-product-overview',
|
||||
imports: [
|
||||
NgForOf,
|
||||
NgClass,
|
||||
NgIf
|
||||
],
|
||||
templateUrl: './product-overview.component.html',
|
||||
styleUrl: './product-overview.component.css'
|
||||
})
|
||||
export class ProductOverviewComponent {
|
||||
products: any[] = [
|
||||
{name: 'Light', price: 119, features: ['Abfrage bei 100 Anbietern'], info: '', class: 'product-light'},
|
||||
{
|
||||
name: 'Standard',
|
||||
price: 179,
|
||||
features: ['Abfrage bei 300 Anbietern', 'Abfrage bei Banken'],
|
||||
info: '',
|
||||
class: 'product-standard'
|
||||
},
|
||||
{
|
||||
name: 'Premium Plus',
|
||||
price: 199,
|
||||
features: ['Abfrage bei 400 Anbietern', 'Abfrage bei Banken', 'Löschen von Personenbezogenen Daten', 'Löschen von E-Mail Adressen aus E-Mail Verteilern', 'MyDF Dashboard'],
|
||||
info: 'Wir suchen bei über 400 Anbietern nach Anmeldungen/Konten oder Registrierungen im Auftrag der Erben von Verstorbenen.\n' +
|
||||
'Eine Übersicht über den aktuellen Stand sehen Sie in Ihrem MYDF-Dashboard.\n' +
|
||||
'Dieses listet Ihnen alle gefundenen Anbieter mit Interaktion auf.\n' +
|
||||
'Per Klick einfach und übersichtlich Spuren löschen.\n' +
|
||||
'KEINE WERBUNG mehr nach Jahren. KEINE STÖRUNG.'
|
||||
, class: 'product-premium'
|
||||
},
|
||||
];
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
button {
|
||||
background-color: var(--primary-background-color);
|
||||
color: var(--primary-text-color);
|
||||
font-size: 1.5rem;
|
||||
font-weight: bold;
|
||||
text-align: center;
|
||||
padding: 0.8rem 1rem;
|
||||
border: none;
|
||||
border-color: var(--primary-background-color);
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
button:hover {
|
||||
transform: translateY(3px);
|
||||
transition: transform 0.1s ease-in-out;
|
||||
}
|
@ -0,0 +1 @@
|
||||
<button [disabled]="disabled">{{ text }}</button>
|
@ -0,0 +1,23 @@
|
||||
import {ComponentFixture, TestBed} from '@angular/core/testing';
|
||||
|
||||
import {TextButtonComponent} from './text-button.component';
|
||||
|
||||
describe('ButtonComponent', () => {
|
||||
let component: TextButtonComponent;
|
||||
let fixture: ComponentFixture<TextButtonComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [TextButtonComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(TextButtonComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
23
frontend/src/app/atomic/text-button/text-button.component.ts
Normal file
@ -0,0 +1,23 @@
|
||||
// import {Component} from '@angular/core';
|
||||
import {Component, Input} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'atomic-text-button',
|
||||
imports: [],
|
||||
templateUrl: './text-button.component.html',
|
||||
styleUrl: './text-button.component.css'
|
||||
})
|
||||
export class TextButtonComponent {
|
||||
@Input() text: string = '';
|
||||
|
||||
private _disabled: boolean = false;
|
||||
|
||||
get disabled(): boolean {
|
||||
return this._disabled;
|
||||
}
|
||||
|
||||
@Input()
|
||||
set disabled(value: boolean | undefined) {
|
||||
this._disabled = value === undefined || value === true;
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
a, div {
|
||||
text-decoration: none;
|
||||
color: var(--primary-background-color);
|
||||
}
|
||||
|
||||
a:hover, div:hover {
|
||||
cursor: pointer;
|
||||
color: var(--primary-background-color-hover);
|
||||
}
|
@ -0,0 +1,2 @@
|
||||
<a *ngIf="href" [href]="href">{{ text }}</a>
|
||||
<div *ngIf="!href">{{ text }}</div>
|
@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { TextLinkComponent } from './text-link.component';
|
||||
|
||||
describe('TextLinkComponent', () => {
|
||||
let component: TextLinkComponent;
|
||||
let fixture: ComponentFixture<TextLinkComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [TextLinkComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(TextLinkComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
15
frontend/src/app/atomic/text-link/text-link.component.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import {Component, Input} from '@angular/core';
|
||||
import {NgIf} from "@angular/common";
|
||||
|
||||
@Component({
|
||||
selector: 'atomic-text-link',
|
||||
imports: [
|
||||
NgIf
|
||||
],
|
||||
templateUrl: './text-link.component.html',
|
||||
styleUrl: './text-link.component.css'
|
||||
})
|
||||
export class TextLinkComponent {
|
||||
@Input({required: true}) text!: string;
|
||||
@Input() href: string = '';
|
||||
}
|
11
frontend/src/app/model/onboarding.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import {verify_email} from "./util";
|
||||
|
||||
export interface Onboarding {
|
||||
notifications: boolean;
|
||||
mail: string;
|
||||
verified: boolean;
|
||||
}
|
||||
|
||||
export function verify_onboarding(state: Onboarding): boolean {
|
||||
return state.verified && verify_email(state.mail);
|
||||
}
|
5
frontend/src/app/model/util.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export const EMAIL_REGEX = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
|
||||
|
||||
export function verify_email(email: string): boolean {
|
||||
return email.length > 0 && EMAIL_REGEX.test(email);
|
||||
}
|
0
frontend/src/app/page/login/login.component.css
Normal file
1
frontend/src/app/page/login/login.component.html
Normal file
@ -0,0 +1 @@
|
||||
<p>login works!</p>
|
23
frontend/src/app/page/login/login.component.spec.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { LoginComponent } from './login.component';
|
||||
|
||||
describe('LoginComponent', () => {
|
||||
let component: LoginComponent;
|
||||
let fixture: ComponentFixture<LoginComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [LoginComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(LoginComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
11
frontend/src/app/page/login/login.component.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-login',
|
||||
imports: [],
|
||||
templateUrl: './login.component.html',
|
||||
styleUrl: './login.component.css'
|
||||
})
|
||||
export class LoginComponent {
|
||||
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
img {
|
||||
max-width: 25rem;
|
||||
width: 90%;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
justify-items: center;
|
||||
height: 100%;
|
||||
padding: 0 1.5rem;
|
||||
}
|
||||
|
||||
.content > p {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.button-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 1.3rem;
|
||||
width: 100%;
|
||||
max-width: 25rem;
|
||||
}
|
||||
|
||||
.button-container > atomic-text-link {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.back-button-container {
|
||||
margin-bottom: 2rem;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
font-size: 1.5rem;
|
||||
}
|
@ -0,0 +1,17 @@
|
||||
<div class="back-button-container">
|
||||
<atomic-text-link id="back-button" [text]="'< Zurück'" [routerLink]="''"></atomic-text-link>
|
||||
</div>
|
||||
<div class="content">
|
||||
|
||||
<img src="public/assets/chat_bubbles.png" alt="Chat Bubbles"/>
|
||||
<h1>Erhalte Mitteilungen</h1>
|
||||
<p>Du erhältst z. B. eine Mitteilung sobald wir eine Digitale Spur gefunden haben.</p>
|
||||
<p>Die Mitteilungen kannst du jederzeit wieder deaktivieren.</p>
|
||||
<div class="button-container">
|
||||
<atomic-text-button [text]="'Mitteilungen erhalten'"
|
||||
(click)="enable_notifications()"
|
||||
[routerLink]="'/onboarding/registration'"></atomic-text-button>
|
||||
<atomic-text-link [text]="'Später'" (click)="disable_notifications()"
|
||||
[routerLink]="'/onboarding/registration'"></atomic-text-link>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { NotificationComponent } from './notification.component';
|
||||
|
||||
describe('NotificationComponent', () => {
|
||||
let component: NotificationComponent;
|
||||
let fixture: ComponentFixture<NotificationComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [NotificationComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(NotificationComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,49 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {RouterLink} from "@angular/router";
|
||||
import {TextButtonComponent} from "../../../atomic/text-button/text-button.component";
|
||||
import {TextLinkComponent} from "../../../atomic/text-link/text-link.component";
|
||||
import {Onboarding} from "../../../model/onboarding";
|
||||
|
||||
@Component({
|
||||
selector: 'app-notification',
|
||||
imports: [
|
||||
RouterLink,
|
||||
TextButtonComponent,
|
||||
TextLinkComponent
|
||||
],
|
||||
templateUrl: './notification.component.html',
|
||||
styleUrl: './notification.component.css'
|
||||
})
|
||||
export class NotificationComponent {
|
||||
enable_notifications() {
|
||||
let onboarding_raw = localStorage.getItem('onboarding');
|
||||
let onboarding: Onboarding;
|
||||
if (!onboarding_raw) {
|
||||
onboarding = {
|
||||
notifications: true,
|
||||
mail: '',
|
||||
verified: false
|
||||
};
|
||||
} else {
|
||||
onboarding = JSON.parse(onboarding_raw);
|
||||
onboarding.notifications = true;
|
||||
}
|
||||
localStorage.setItem('onboarding', JSON.stringify(onboarding));
|
||||
}
|
||||
|
||||
disable_notifications() {
|
||||
let onboarding_raw = localStorage.getItem('onboarding');
|
||||
let onboarding: Onboarding;
|
||||
if (!onboarding_raw) {
|
||||
onboarding = {
|
||||
notifications: false,
|
||||
mail: '',
|
||||
verified: false
|
||||
};
|
||||
} else {
|
||||
onboarding = JSON.parse(onboarding_raw);
|
||||
onboarding.notifications = false;
|
||||
}
|
||||
localStorage.setItem('onboarding', JSON.stringify(onboarding));
|
||||
}
|
||||
}
|
@ -0,0 +1,50 @@
|
||||
.back-button-container {
|
||||
margin-bottom: 2rem;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
p {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
justify-items: center;
|
||||
height: 100%;
|
||||
/*padding: 0 1.5rem;*/
|
||||
row-gap: 2rem;
|
||||
}
|
||||
|
||||
.form-container {
|
||||
width: 100%;
|
||||
max-width: 25rem;
|
||||
}
|
||||
|
||||
.button-container {
|
||||
display: flex;
|
||||
flex-direction: row-reverse;
|
||||
flex-wrap: wrap;
|
||||
column-gap: 1.3rem;
|
||||
row-gap: 2rem;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
justify-items: center;
|
||||
width: 100%;
|
||||
max-width: 25rem;
|
||||
}
|
||||
|
||||
atomic-text-button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.button-container > atomic-text-link {
|
||||
font-size: 1.3rem;
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
<div class="back-button-container">
|
||||
<atomic-text-link id="back-button" [text]="'< Zurück'"
|
||||
[routerLink]="'/onboarding/notifications'"></atomic-text-link>
|
||||
</div>
|
||||
<div class="content">
|
||||
<h1>Jetzt registrieren</h1>
|
||||
<div class="form-container">
|
||||
<p>Gib deine E-Mail Adresse ein.</p>
|
||||
<atomic-email-field [(value)]="mail"></atomic-email-field>
|
||||
</div>
|
||||
<div class="button-container">
|
||||
<atomic-text-button (click)="register()" [disabled]="!verify_mail()" [text]="'Weiter'">
|
||||
</atomic-text-button>
|
||||
<atomic-text-link [text]="'Stattdessen anmelden'" [routerLink]="'/login'"></atomic-text-link>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { RegistrationComponent } from './registration.component';
|
||||
|
||||
describe('RegistrationComponent', () => {
|
||||
let component: RegistrationComponent;
|
||||
let fixture: ComponentFixture<RegistrationComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [RegistrationComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(RegistrationComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,79 @@
|
||||
import {Component, OnInit} from '@angular/core';
|
||||
import {EmailFieldComponent} from "../../../atomic/input/email-field/email-field.component";
|
||||
import {TextLinkComponent} from "../../../atomic/text-link/text-link.component";
|
||||
import {Router, RouterLink} from "@angular/router";
|
||||
import {TextButtonComponent} from "../../../atomic/text-button/text-button.component";
|
||||
import {Onboarding} from "../../../model/onboarding";
|
||||
import {verify_email} from "../../../model/util";
|
||||
import {ApiService} from "../../../service/api.service";
|
||||
|
||||
@Component({
|
||||
selector: 'app-registration',
|
||||
imports: [
|
||||
EmailFieldComponent,
|
||||
TextLinkComponent,
|
||||
RouterLink,
|
||||
TextButtonComponent
|
||||
],
|
||||
templateUrl: './registration.component.html',
|
||||
styleUrl: './registration.component.css'
|
||||
})
|
||||
export class RegistrationComponent implements OnInit {
|
||||
mail: string = '';
|
||||
|
||||
constructor(private router: Router, private api: ApiService) {
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
let onboarding_raw = localStorage.getItem('onboarding');
|
||||
if (onboarding_raw) {
|
||||
let onboarding: Onboarding = JSON.parse(onboarding_raw);
|
||||
this.mail = onboarding.mail;
|
||||
}
|
||||
}
|
||||
|
||||
verify_mail(): boolean {
|
||||
return verify_email(this.mail);
|
||||
}
|
||||
|
||||
register() {
|
||||
const focusedElement = document.activeElement as HTMLElement;
|
||||
if (focusedElement) {
|
||||
focusedElement.blur();
|
||||
}
|
||||
|
||||
if (!this.mail || !this.verify_mail()) {
|
||||
return;
|
||||
}
|
||||
let onboarding_raw = localStorage.getItem('onboarding');
|
||||
let onboarding: Onboarding;
|
||||
if (!onboarding_raw) {
|
||||
onboarding = {
|
||||
notifications: false,
|
||||
mail: this.mail,
|
||||
verified: false
|
||||
};
|
||||
} else {
|
||||
onboarding = JSON.parse(onboarding_raw);
|
||||
if (onboarding.mail === this.mail) {
|
||||
this.router.navigateByUrl('/onboarding/verification').then(_ => {
|
||||
});
|
||||
return;
|
||||
}
|
||||
onboarding.mail = this.mail;
|
||||
}
|
||||
localStorage.setItem('onboarding', JSON.stringify(onboarding));
|
||||
this.api.register(this.mail).subscribe(
|
||||
{
|
||||
next: (resp: any) => {
|
||||
console.info(resp);
|
||||
this.router.navigateByUrl('/onboarding/verification').then(_ => {
|
||||
});
|
||||
},
|
||||
error: (error) => {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
60
frontend/src/app/page/onboarding/start/start.component.css
Normal file
@ -0,0 +1,60 @@
|
||||
img {
|
||||
max-width: 25rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.button-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
row-gap: 1.3rem;
|
||||
margin-top: 3rem;
|
||||
width: 100%;
|
||||
max-width: 25rem;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style-type: none;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
column-gap: 1rem;
|
||||
}
|
||||
|
||||
.login-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
column-gap: 1rem;
|
||||
justify-content: space-evenly;
|
||||
justify-items: center;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.login-container > p {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
#login-button {
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
padding: 0 1.5rem;
|
||||
}
|
||||
|
||||
.terms-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
font-size: 0.8rem;
|
||||
color: var(--primary-text-color);
|
||||
}
|
28
frontend/src/app/page/onboarding/start/start.component.html
Normal file
@ -0,0 +1,28 @@
|
||||
<div class="content">
|
||||
|
||||
<img src="public/assets/digitaler-frieden_logo.svg" alt="Logo Digitaler Frieden"/>
|
||||
|
||||
<h1>Hallo. Digitale Spuren entfernen per Knopfdruck.</h1>
|
||||
<p>Mit uns finden Sie Ihre Digitalen Spuren und können diese entfernen.</p>
|
||||
|
||||
<!-- <atomic-product-overview></atomic-product-overview>-->
|
||||
<div class="button-container">
|
||||
<atomic-text-button [text]="'Weiter'" [routerLink]="'/onboarding/notifications'"></atomic-text-button>
|
||||
<div class="login-container">
|
||||
<p>Du hast bereits ein Konto?</p>
|
||||
<atomic-text-link id="login-button" [text]="'Login'" [routerLink]="'/login'"></atomic-text-link>
|
||||
</div>
|
||||
</div>
|
||||
<div class="terms-container">
|
||||
<p>Mit der weiteren Nutzung stimmst du folgenden Bedingungen zu:</p>
|
||||
<ul>
|
||||
<li>
|
||||
<atomic-text-link [text]="'AGB'" [routerLink]="'terms'"></atomic-text-link>
|
||||
</li>
|
||||
<li>
|
||||
<atomic-text-link [text]="'Datenschutzerklärung'" [routerLink]="'privacy'"></atomic-text-link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { StartComponent } from './start.component';
|
||||
|
||||
describe('StartComponent', () => {
|
||||
let component: StartComponent;
|
||||
let fixture: ComponentFixture<StartComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [StartComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(StartComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
20
frontend/src/app/page/onboarding/start/start.component.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {TextButtonComponent} from "../../../atomic/text-button/text-button.component";
|
||||
import {RouterLink} from "@angular/router";
|
||||
import {TextLinkComponent} from "../../../atomic/text-link/text-link.component";
|
||||
import {ProductOverviewComponent} from "../../../atomic/product-overview/product-overview.component";
|
||||
|
||||
@Component({
|
||||
selector: 'app-start',
|
||||
imports: [
|
||||
TextButtonComponent,
|
||||
RouterLink,
|
||||
TextLinkComponent,
|
||||
ProductOverviewComponent
|
||||
],
|
||||
templateUrl: './start.component.html',
|
||||
styleUrl: './start.component.css'
|
||||
})
|
||||
export class StartComponent {
|
||||
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
.back-button-container {
|
||||
margin-bottom: 2rem;
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
justify-items: center;
|
||||
height: 100%;
|
||||
row-gap: 1rem;
|
||||
}
|
||||
|
||||
.form-container {
|
||||
width: 100%;
|
||||
max-width: 25rem;
|
||||
text-align: start;
|
||||
font-size: 1.3rem;
|
||||
margin-left: 2rem;
|
||||
}
|
||||
|
||||
.form-container > p {
|
||||
line-height: 0.8rem;
|
||||
}
|
||||
|
||||
.form-container > p:nth-last-child(2) {
|
||||
margin-top: 3rem;
|
||||
font-weight: bold;
|
||||
font-size: 1.5rem;
|
||||
line-height: 2rem;
|
||||
}
|
||||
|
||||
.button-container {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
column-gap: 1.3rem;
|
||||
width: 100%;
|
||||
max-width: 25rem;
|
||||
}
|
||||
|
||||
atomic-text-button {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.button-container > atomic-text-link {
|
||||
font-size: 1.3rem;
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
<div class="back-button-container">
|
||||
<atomic-text-link [routerLink]="'/onboarding/registration'" [text]="'< Zurück'"
|
||||
id="back-button"></atomic-text-link>
|
||||
</div>
|
||||
<div class="content">
|
||||
<h1>Verifizieren</h1>
|
||||
<div class="form-container">
|
||||
<p>Wir haben dir eine E-Mail geschickt.</p>
|
||||
<p>Bitte verifiziere deine E-Mail Adresse.</p>
|
||||
<p>Dann geht es hier
|
||||
<i>automatisch</i>
|
||||
weiter.
|
||||
</p>
|
||||
<div class="button-container">
|
||||
<p>Noch keine E-Mail erhalten? Auch im Spam Ordner nicht?</p>
|
||||
<atomic-text-link (click)="tbd()" [text]="'Erneut senden'"></atomic-text-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { VerificationComponent } from './verification.component';
|
||||
|
||||
describe('VerificationComponent', () => {
|
||||
let component: VerificationComponent;
|
||||
let fixture: ComponentFixture<VerificationComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [VerificationComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(VerificationComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,19 @@
|
||||
import {Component} from '@angular/core';
|
||||
import {TextLinkComponent} from "../../../atomic/text-link/text-link.component";
|
||||
import {RouterLink} from "@angular/router";
|
||||
|
||||
@Component({
|
||||
selector: 'app-verification',
|
||||
imports: [
|
||||
TextLinkComponent,
|
||||
RouterLink
|
||||
],
|
||||
templateUrl: './verification.component.html',
|
||||
styleUrl: './verification.component.css'
|
||||
})
|
||||
export class VerificationComponent {
|
||||
// TODO: Implement the E-Mail verification re-send logic
|
||||
tbd() {
|
||||
alert('E-Mail sent message - TBD');
|
||||
}
|
||||
}
|
0
frontend/src/app/page/privacy/privacy.component.css
Normal file
1
frontend/src/app/page/privacy/privacy.component.html
Normal file
@ -0,0 +1 @@
|
||||
<p>privacy works!</p>
|
23
frontend/src/app/page/privacy/privacy.component.spec.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { PrivacyComponent } from './privacy.component';
|
||||
|
||||
describe('PrivacyComponent', () => {
|
||||
let component: PrivacyComponent;
|
||||
let fixture: ComponentFixture<PrivacyComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [PrivacyComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(PrivacyComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
11
frontend/src/app/page/privacy/privacy.component.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-privacy',
|
||||
imports: [],
|
||||
templateUrl: './privacy.component.html',
|
||||
styleUrl: './privacy.component.css'
|
||||
})
|
||||
export class PrivacyComponent {
|
||||
|
||||
}
|
@ -0,0 +1 @@
|
||||
<p>terms-of-service works!</p>
|
@ -0,0 +1,23 @@
|
||||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { TermsOfServiceComponent } from './terms-of-service.component';
|
||||
|
||||
describe('TermsOfServiceComponent', () => {
|
||||
let component: TermsOfServiceComponent;
|
||||
let fixture: ComponentFixture<TermsOfServiceComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [TermsOfServiceComponent]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(TermsOfServiceComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
@ -0,0 +1,11 @@
|
||||
import { Component } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-terms-of-service',
|
||||
imports: [],
|
||||
templateUrl: './terms-of-service.component.html',
|
||||
styleUrl: './terms-of-service.component.css'
|
||||
})
|
||||
export class TermsOfServiceComponent {
|
||||
|
||||
}
|
16
frontend/src/app/service/api.service.spec.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { ApiService } from './api.service';
|
||||
|
||||
describe('ApiService', () => {
|
||||
let service: ApiService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(ApiService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
19
frontend/src/app/service/api.service.ts
Normal file
@ -0,0 +1,19 @@
|
||||
import {Injectable} from '@angular/core';
|
||||
import {HttpClient} from "@angular/common/http";
|
||||
import {environment} from "../../environments/environment";
|
||||
import {Observable} from "rxjs";
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ApiService {
|
||||
|
||||
constructor(private http: HttpClient) {
|
||||
}
|
||||
|
||||
register(mail: string): Observable<any> {
|
||||
return this.http.post<any>(`${environment.apiUrl}/api/v1/onboarding/register`, JSON.stringify({"email_address": mail}), {
|
||||
headers: {'Content-Type': 'application/json'}
|
||||
});
|
||||
}
|
||||
}
|
4
frontend/src/environments/environment.development.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export const environment = {
|
||||
production: false,
|
||||
apiUrl: 'http://127.0.0.1:8000'
|
||||
};
|
4
frontend/src/environments/environment.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export const environment = {
|
||||
production: true,
|
||||
apiUrl: '',
|
||||
};
|
13
frontend/src/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Digitaler Frieden</title>
|
||||
<base href="/">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="icon" type="image/x-icon" href="public/favicon.ico">
|
||||
</head>
|
||||
<body>
|
||||
<app-root></app-root>
|
||||
</body>
|
||||
</html>
|
16
frontend/src/main.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import {bootstrapApplication} from '@angular/platform-browser';
|
||||
import {appConfig} from './app/app.config';
|
||||
import {AppComponent} from './app/app.component';
|
||||
import {environment} from "./environments/environment";
|
||||
import {enableProdMode} from "@angular/core";
|
||||
|
||||
environment.apiUrl = `${window.location.protocol}//${window.location.host}`;
|
||||
|
||||
if (environment.production) {
|
||||
enableProdMode();
|
||||
} else {
|
||||
environment.apiUrl += '8000';
|
||||
}
|
||||
|
||||
bootstrapApplication(AppComponent, appConfig)
|
||||
.catch((err) => console.error(err));
|
18
frontend/src/styles.css
Normal file
@ -0,0 +1,18 @@
|
||||
/* You can add global styles to this file, and also import other style files */
|
||||
|
||||
:root {
|
||||
--primary-background-color: rgba(51, 217, 178, 1.0);
|
||||
--primary-background-color-hover: rgba(33, 140, 116, 1.0);
|
||||
--primary-text-color: rgba(255, 255, 255, 1.0);
|
||||
--error-color: rgba(255, 82, 82, 1.0);
|
||||
}
|
||||
|
||||
body {
|
||||
max-width: clamp(320px, 90%, 1000px);
|
||||
margin: auto;
|
||||
padding-top: 1rem;
|
||||
/*font-size: 1.25rem;*/
|
||||
line-height: 1.25;
|
||||
background-color: black;
|
||||
color: var(--primary-text-color);
|
||||
}
|
15
frontend/tsconfig.app.json
Normal file
@ -0,0 +1,15 @@
|
||||
/* To learn more about Typescript configuration file: https://www.typescriptlang.org/docs/handbook/tsconfig-json.html. */
|
||||
/* To learn more about Angular compiler options: https://angular.dev/reference/configs/angular-compiler-options. */
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./out-tsc/app",
|
||||
"types": []
|
||||
},
|
||||
"files": [
|
||||
"src/main.ts"
|
||||
],
|
||||
"include": [
|
||||
"src/**/*.d.ts"
|
||||
]
|
||||
}
|