Compare commits

..

68 Commits

Author SHA1 Message Date
33cd5d0b5c feat: implement event model for registration process with payload handling 2025-03-15 22:15:59 +01:00
04de306091 feat: enhance registration process with API integration and environment configuration 2025-03-14 23:20:24 +01:00
70f7f67d9c feat: return JSON responses for email registration errors and success messages 2025-03-14 23:19:39 +01:00
cf0d894092 feat: add SMTP provider configuration to support multiple email services 2025-03-14 21:52:27 +01:00
d971a10cf4 feat: add SMTP configuration and registration endpoint for email verification 2025-03-14 21:40:03 +01:00
8499636209 feat: implement API versioning and health check endpoints 2025-03-12 22:36:54 +01:00
94247c5693 feat: update component styles to use 100% height for better responsiveness 2025-03-10 21:03:35 +01:00
a5fcb20296 feat: add product overview component with styling and product listing 2025-03-10 21:03:03 +01:00
765e0ae521 feat: update verify_mail method to return boolean in email field and registration components 2025-03-05 23:05:23 +01:00
09532de67b feat: implement OnInit lifecycle hook in registration component and fix styling in verification component 2025-03-05 23:03:33 +01:00
bfdb76cabe feat: enhance onboarding process with email handling and verification UI 2025-03-05 22:45:03 +01:00
aacbad3357 feat: add disabled state to text button component 2025-03-05 22:44:17 +01:00
0218c80aa9 feat: enhance email field component with validation and styling 2025-03-05 22:44:00 +01:00
92ee2dc05f feat: add error color variable to styles for improved UI feedback 2025-03-05 22:43:25 +01:00
b4d3ad6ac8 feat: add wildcard route to redirect to StartComponent for unmatched paths 2025-03-04 23:42:58 +01:00
5610260120 fix: update fallback service to serve index.html for not found routes 2025-03-04 23:42:48 +01:00
7d85e5e4f6 fix: update router links in registration and start components for correct navigation 2025-03-04 23:27:57 +01:00
a9045d65d6 fix: update router link in registration component for correct navigation 2025-03-04 23:19:46 +01:00
4344d4fa42 feat: add routes for Verification, Privacy, and Terms of Service components 2025-03-04 23:07:41 +01:00
173dd2d4c9 feat: add notification toggle functionality in NotificationComponent 2025-03-04 23:07:30 +01:00
80801e3fa6 feat: enhance registration component with layout and navigation elements 2025-03-04 23:07:20 +01:00
0bd719904a fix: update router links in start component for correct navigation 2025-03-04 23:07:09 +01:00
876920e057 style: adjust padding for text button component 2025-03-04 23:06:35 +01:00
f41c502e37 feat: add VerificationComponent with template, styles, and tests 2025-03-04 23:06:20 +01:00
37b8827007 feat: add TermsOfServiceComponent with template, styles, and tests 2025-03-04 23:06:10 +01:00
1ab2424710 feat: add PrivacyComponent with template, styles, and tests 2025-03-04 23:05:56 +01:00
da38676847 feat: add email field component with styling and basic functionality 2025-03-04 23:05:45 +01:00
d2a7f0d30b chore: update styles.css to use primary text color variable 2025-03-04 21:48:21 +01:00
c791d6ad6d chore: install Angular CLI globally in deployment workflow 2025-03-04 21:39:54 +01:00
6ec81ae0d7 chore: update deploy action version in deploy.yml to v2 2025-03-04 21:34:45 +01:00
527eebeefb chore: rename deploy key to shuttle-api-key in deploy.yml 2025-03-04 21:33:55 +01:00
50bd774478 chore: add project ID to deploy action in deploy.yml 2025-03-04 21:32:07 +01:00
2ae0ed13fc chore: update deploy action version in deploy.yml to use main branch 2025-03-04 21:29:06 +01:00
c9e8758c4f chore: add deployment workflow for Shuttle in deploy.yml 2025-03-04 21:27:06 +01:00
9ffaaca568 chore: add deployment workflow for Shuttle in deploy.yml 2025-03-04 21:24:50 +01:00
44d052ca48 chore: update body styles in styles.css to set background color 2025-03-04 21:08:50 +01:00
677a05ba1d chore: add include paths for frontend assets in Shuttle.toml 2025-03-04 20:57:40 +01:00
f1a8b27a0e chore: update asset paths in Shuttle.toml for build and deploy 2025-03-04 20:51:58 +01:00
29e9b1f944 Merge remote-tracking branch 'origin/master' 2025-03-04 20:49:06 +01:00
49bb94562d chore: remove github workflows and fix Shuttle.toml 2025-03-04 20:48:49 +01:00
67c56ad724 chore: remove github workflows 2025-03-04 20:44:44 +01:00
c57dd13356 chore: update dependencies for axum and shuttle packages 2025-03-04 20:42:05 +01:00
515e7b3dd8 feat: enhance notification page and start components with new layout and styling 2025-03-04 20:06:16 +01:00
5e2aca14a9 fix: update favicon path in index.html 2025-03-04 00:13:49 +01:00
16b72f4fb6 feat: add routing and styles for onboarding components and assets 2025-03-04 00:05:25 +01:00
a6b021dd98 feat: add TextButtonComponent 2025-03-03 22:43:08 +01:00
cf6bb1abff feat: add custom CSS variables for primary colors 2025-03-03 22:42:21 +01:00
8ba727b367 chore: comment out deployment triggers in deploy.yml 2025-03-03 21:48:22 +01:00
cd5b04d695 chore/initialize new angular frontend and update flake 2025-03-03 21:28:02 +01:00
61dcc46242
Merge pull request #12 from itsscb/ft-add-github-action-for-deployment
chore/remove current frontend - will be replaced with angular
2025-03-03 20:44:08 +01:00
9a7caf9eb3
Merge pull request #11 from itsscb/fix-reparis-gh-action
fix: repairs gh action
2024-08-23 16:26:57 +02:00
b281e87304
rollback 2024-08-23 16:24:22 +02:00
a538204d3b
Update deploy.yml 2024-08-23 15:40:45 +02:00
7262775a48
feat: adds trunk output to cache & installs tailwindcss locally 2024-08-23 15:37:54 +02:00
9d90d9b70c
feat: adds trunk and node to gh action cache 2024-08-23 15:22:52 +02:00
922125fb18
Update deploy.yml 2024-08-23 15:11:00 +02:00
2ee273ddf1
Merge pull request #10 from itsscb/feat-improve-gh-action
feat: improve gh action
2024-08-23 14:57:56 +02:00
04a36990cb
feat: improve gh action 2024-08-23 14:57:44 +02:00
8531520665
Merge pull request #9 from itsscb/revert-8-ft-add-github-action-for-deployment
Revert "ft/adds commit after build"
2024-06-01 01:06:25 +02:00
09ef49b04d
Revert "ft/adds commit after build" 2024-06-01 01:06:01 +02:00
8c08b632b7
Merge pull request #8 from itsscb/ft-add-github-action-for-deployment
ft/adds commit after build
2024-06-01 00:55:45 +02:00
1f580fa829
Merge pull request #7 from itsscb/ft-add-github-action-for-deployment
fix/adds allow-dirty
2024-06-01 00:36:39 +02:00
92e2a8bb37
Merge pull request #6 from itsscb/ft-add-github-action-for-deployment
Ft add GitHub action for deployment
2024-06-01 00:24:50 +02:00
689cd6fff6
Merge pull request #5 from itsscb/ft-add-github-action-for-deployment
rf/moves to official shuttle-hq action
2024-06-01 00:19:18 +02:00
87676d426c
Merge pull request #4 from itsscb/ft-add-github-action-for-deployment
Ft add GitHub action for deployment
2024-05-31 23:59:12 +02:00
575f36565b
Merge pull request #3 from itsscb/ft-add-github-action-for-deployment
fix/adds tailwindcss to gh action
2024-05-31 23:45:16 +02:00
dfe6fd105e
Merge pull request #2 from itsscb/ft-add-github-action-for-deployment
fix/gh action file
2024-05-31 23:24:58 +02:00
c8dab87a66
Merge pull request #1 from itsscb/ft-add-github-action-for-deployment
ft/add github action for deployment
2024-05-31 23:13:10 +02:00
115 changed files with 18307 additions and 153 deletions

View File

@ -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
View File

@ -24,7 +24,8 @@ Cargo.lock
# already existing elements were commented out
#/target
.shuttle/
node_modules/
Secrets.toml
Secrets.dev.toml
**/*.pdf

View File

@ -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
View 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
View File

@ -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": {

View File

@ -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
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"
];
# 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
];
# 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
View 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
View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load Diff

38
frontend/package.json Normal file
View 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"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 290 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 KiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.9 KiB

View 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" />&#10;<style
type="text/css"
id="style1">&#10; .st0{fill:#FFFFFF;}&#10;</style>&#10;<rect
x="-2963012"
y="-975512"
width="1024"
height="1024"
id="rect1"
style="stroke-width:0.418046" />&#10;<g
id="g20"
transform="translate(-2964000.1,-975619.87)">&#10; <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">&#10; <g
id="g4">&#10; <g
id="g1">&#10; <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" />&#10; </g>&#10; <g
id="g3">&#10; <g
id="g2">&#10; <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" />&#10; </g>&#10; </g>&#10; </g>&#10; <g
id="g18"
style="display:none">&#10; <g
id="g17">&#10; <g
id="g16">&#10; <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" />&#10; <path
class="st0"
d="m 701.13,1250.09 v 220.83 h 28.55 v -220.83 z"
id="path5" />&#10; <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" />&#10; <path
class="st0"
d="m 1201.16,1250.09 v 220.83 h 28.41 v -220.83 z"
id="path7" />&#10; <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" />&#10; <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" />&#10; <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" />&#10; <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" />&#10; <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" />&#10; <g
id="g15">&#10; <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" />&#10; <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" />&#10; <rect
x="1257.67"
y="1622.14"
class="st0"
width="14.81"
height="131.17999"
id="rect13" />&#10; <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" />&#10; <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" />&#10; <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" />&#10; <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" />&#10; </g>&#10; </g>&#10; </g>&#10; </g>&#10; </g>&#10;</g>&#10;</svg>

After

Width:  |  Height:  |  Size: 5.9 KiB

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

View 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

View 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

View 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

File diff suppressed because it is too large Load Diff

Binary file not shown.

View 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

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

View File

@ -0,0 +1 @@
<router-outlet/>

View 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');
});
});

View 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';
}

View 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()]
};

View 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,
},
];

View File

@ -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;
}

View File

@ -0,0 +1 @@
<input [(ngModel)]="value" [disabled]="disabled" [ngClass]="{'invalid': value != '' && !verify_mail()}" type="email"/>

View File

@ -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();
});
});

View File

@ -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);
}
}

View File

@ -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;
}

View File

@ -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>-->
<!-- &lt;!&ndash; <p>{{ product.info }}</p>&ndash;&gt;-->
<!-- </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>

View File

@ -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();
});
});

View File

@ -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'
},
];
}

View File

@ -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;
}

View File

@ -0,0 +1 @@
<button [disabled]="disabled">{{ text }}</button>

View File

@ -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();
});
});

View 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;
}
}

View File

@ -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);
}

View File

@ -0,0 +1,2 @@
<a *ngIf="href" [href]="href">{{ text }}</a>
<div *ngIf="!href">{{ text }}</div>

View File

@ -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();
});
});

View 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 = '';
}

View 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);
}

View 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);
}

View File

@ -0,0 +1 @@
<p>login works!</p>

View 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();
});
});

View 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 {
}

View File

@ -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;
}

View File

@ -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&nbsp;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>

View File

@ -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();
});
});

View File

@ -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));
}
}

View File

@ -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;
}

View File

@ -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&nbsp;anmelden'" [routerLink]="'/login'"></atomic-text-link>
</div>
</div>

View File

@ -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();
});
});

View File

@ -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);
}
}
);
}
}

View 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);
}

View File

@ -0,0 +1,28 @@
<div class="content">
<img src="public/assets/digitaler-frieden_logo.svg" alt="Logo Digitaler Frieden"/>
<h1>Hallo. Digitale&nbsp;Spuren entfernen per Knopfdruck.</h1>
<p>Mit uns finden Sie Ihre Digitalen&nbsp;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>

View File

@ -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();
});
});

View 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 {
}

View File

@ -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;
}

View File

@ -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&nbsp;senden'"></atomic-text-link>
</div>
</div>
</div>

View File

@ -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();
});
});

View File

@ -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');
}
}

View File

@ -0,0 +1 @@
<p>privacy works!</p>

View 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();
});
});

View 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 {
}

View File

@ -0,0 +1 @@
<p>terms-of-service works!</p>

View File

@ -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();
});
});

View File

@ -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 {
}

View 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();
});
});

View 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'}
});
}
}

View File

@ -0,0 +1,4 @@
export const environment = {
production: false,
apiUrl: 'http://127.0.0.1:8000'
};

View File

@ -0,0 +1,4 @@
export const environment = {
production: true,
apiUrl: '',
};

13
frontend/src/index.html Normal file
View 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
View 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
View 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);
}

View 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"
]
}

Some files were not shown because too many files have changed in this diff Show More