simplified user registration process
This commit is contained in:
parent
59a0b6b9b7
commit
2e9790b3b2
@ -22,12 +22,9 @@ public class AuthController {
|
|||||||
|
|
||||||
@PostMapping("/register")
|
@PostMapping("/register")
|
||||||
@Operation(summary = "Registriert einen neuen User inkl. Zählpunkt",
|
@Operation(summary = "Registriert einen neuen User inkl. Zählpunkt",
|
||||||
description = "Setzt den User auf Status PENDING und triggert den asynchronen EDA-Check.")
|
description = "Setzt den User auf Status PENDING und versendet eine Verifizierungs-E-Mail.")
|
||||||
public ResponseEntity<Void> register(@Valid @RequestBody RegistrationRequest request) {
|
public ResponseEntity<Void> register(@Valid @RequestBody RegistrationRequest request) {
|
||||||
authService.register(request);
|
authService.register(request);
|
||||||
// Logik: MeteringPoint anlegen
|
|
||||||
// Event feuern: EdaTopologyCheckEvent
|
|
||||||
|
|
||||||
return ResponseEntity.status(HttpStatus.CREATED).build();
|
return ResponseEntity.status(HttpStatus.CREATED).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,9 +1,10 @@
|
|||||||
package at.mueller.eeg.backend.iam.api.dto;
|
package at.mueller.eeg.backend.iam.api.dto;
|
||||||
|
|
||||||
|
import at.mueller.eeg.backend.iam.domain.ParticipantType;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import jakarta.validation.constraints.Email;
|
import jakarta.validation.constraints.Email;
|
||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
import jakarta.validation.constraints.Pattern;
|
import jakarta.validation.constraints.NotNull;
|
||||||
import jakarta.validation.constraints.Size;
|
import jakarta.validation.constraints.Size;
|
||||||
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
@ -12,15 +13,19 @@ public interface IamDtos {
|
|||||||
|
|
||||||
@Schema(description = "Payload für die Registrierung eines neuen Users")
|
@Schema(description = "Payload für die Registrierung eines neuen Users")
|
||||||
record RegistrationRequest(
|
record RegistrationRequest(
|
||||||
@NotBlank @Schema(example = "Max") String firstName,
|
@NotNull
|
||||||
@NotBlank @Schema(example = "Mustermann") String lastName,
|
@Schema(description = "Privatperson oder juristische Person (Firma/Verein/Gemeinde)", example = "PRIVATE")
|
||||||
@Email @NotBlank @Schema(example = "max@example.com") String email,
|
ParticipantType participantType,
|
||||||
@NotBlank @Size(min = 8) @Schema(example = "SecurePass123!") String password,
|
|
||||||
|
|
||||||
@NotBlank
|
@NotBlank
|
||||||
@Pattern(regexp = "^AT[0-9]{31}$", message = "Muss eine gültige österreichische 33-stellige Zählpunktnummer sein")
|
@Schema(description = "Vorname (bei juristischer Person: Vorname der Ansprechperson)", example = "Max")
|
||||||
@Schema(example = "AT0010000000000000000000001234567")
|
String firstName,
|
||||||
String atNumber
|
@NotBlank
|
||||||
|
@Schema(description = "Nachname (bei juristischer Person: Nachname der Ansprechperson)", example = "Mustermann")
|
||||||
|
String lastName,
|
||||||
|
@Schema(description = "Nur bei juristischer Person: Firmen-, Vereins- oder Gemeindename", example = "Musterverein")
|
||||||
|
String organizationName,
|
||||||
|
@Email @NotBlank @Schema(example = "max@example.com") String email,
|
||||||
|
@NotBlank @Size(min = 8) @Schema(example = "SecurePass123!") String password
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Schema(description = "Payload für den Login")
|
@Schema(description = "Payload für den Login")
|
||||||
@ -39,13 +44,13 @@ public interface IamDtos {
|
|||||||
@Schema(description = "Repräsentation eines Users für das Admin-Dashboard")
|
@Schema(description = "Repräsentation eines Users für das Admin-Dashboard")
|
||||||
record UserProfileResponse(
|
record UserProfileResponse(
|
||||||
UUID id,
|
UUID id,
|
||||||
|
ParticipantType participantType,
|
||||||
String firstName,
|
String firstName,
|
||||||
String lastName,
|
String lastName,
|
||||||
|
String organizationName,
|
||||||
String email,
|
String email,
|
||||||
String status,
|
String status
|
||||||
String primaryAtNumber
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
record ErrorResponse(String message, int status) {}
|
record ErrorResponse(String message, int status) {}
|
||||||
|
}
|
||||||
}
|
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
package at.mueller.eeg.backend.iam.domain;
|
||||||
|
|
||||||
|
public enum ParticipantType {
|
||||||
|
PRIVATE,
|
||||||
|
COMPANY
|
||||||
|
}
|
||||||
@ -29,12 +29,16 @@ public class User implements UserDetails {
|
|||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
private String passwordHash;
|
private String passwordHash;
|
||||||
|
|
||||||
|
@Enumerated(EnumType.STRING)
|
||||||
|
@Column(nullable = false)
|
||||||
|
private ParticipantType participantType = ParticipantType.PRIVATE;
|
||||||
|
|
||||||
|
private String organizationName;
|
||||||
|
|
||||||
private String firstName;
|
private String firstName;
|
||||||
|
|
||||||
private String lastName;
|
private String lastName;
|
||||||
|
|
||||||
private String iban;
|
|
||||||
|
|
||||||
@Column(nullable = false)
|
@Column(nullable = false)
|
||||||
private boolean emailVerified = false;
|
private boolean emailVerified = false;
|
||||||
|
|
||||||
|
|||||||
@ -15,14 +15,12 @@ public interface UserMapper {
|
|||||||
|
|
||||||
List<IamDtos.UserProfileResponse> mapToDto(List<User> users);
|
List<IamDtos.UserProfileResponse> mapToDto(List<User> users);
|
||||||
|
|
||||||
@Mapping(target = "primaryAtNumber", ignore = true)
|
|
||||||
IamDtos.UserProfileResponse mapToDto(User user);
|
IamDtos.UserProfileResponse mapToDto(User user);
|
||||||
|
|
||||||
@Mapping(target = "status", constant = "PENDING")
|
@Mapping(target = "status", constant = "PENDING")
|
||||||
@Mapping(target = "role", constant = "MEMBER")
|
@Mapping(target = "role", constant = "MEMBER")
|
||||||
@Mapping(target = "passwordHash", source = "password", qualifiedBy = UserMapperAnnotations.PasswordHash.class)
|
@Mapping(target = "passwordHash", source = "password", qualifiedBy = UserMapperAnnotations.PasswordHash.class)
|
||||||
@Mapping(target = "id", ignore = true)
|
@Mapping(target = "id", ignore = true)
|
||||||
@Mapping(target = "iban", ignore = true)
|
|
||||||
@Mapping(target = "enabled", constant = "false")
|
@Mapping(target = "enabled", constant = "false")
|
||||||
@Mapping(target = "authorities", ignore = true)
|
@Mapping(target = "authorities", ignore = true)
|
||||||
@Mapping(target = "emailVerified", ignore = true)
|
@Mapping(target = "emailVerified", ignore = true)
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package at.mueller.eeg.backend.iam.service;
|
package at.mueller.eeg.backend.iam.service;
|
||||||
|
|
||||||
|
import at.mueller.eeg.backend.iam.domain.ParticipantType;
|
||||||
import at.mueller.eeg.backend.iam.domain.User;
|
import at.mueller.eeg.backend.iam.domain.User;
|
||||||
import at.mueller.eeg.backend.iam.mapper.UserMapper;
|
import at.mueller.eeg.backend.iam.mapper.UserMapper;
|
||||||
import at.mueller.eeg.backend.iam.repository.UserRepository;
|
import at.mueller.eeg.backend.iam.repository.UserRepository;
|
||||||
@ -62,6 +63,12 @@ public class AuthService {
|
|||||||
throw new RuntimeException("E-Mail already exists!");
|
throw new RuntimeException("E-Mail already exists!");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (request.participantType() == ParticipantType.COMPANY
|
||||||
|
&& (request.organizationName() == null || request.organizationName().isBlank())) {
|
||||||
|
throw new IllegalArgumentException(
|
||||||
|
"Für juristische Personen ist ein Firmen-/Vereins-/Gemeindename erforderlich.");
|
||||||
|
}
|
||||||
|
|
||||||
User newUser = userMapper.mapToEntity(request, new User());
|
User newUser = userMapper.mapToEntity(request, new User());
|
||||||
String token = UUID.randomUUID().toString();
|
String token = UUID.randomUUID().toString();
|
||||||
newUser.setVerificationToken(token);
|
newUser.setVerificationToken(token);
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
package at.mueller.eeg.backend.backend;
|
package at.mueller.eeg.backend;
|
||||||
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
package at.mueller.eeg.backend.iam.service;
|
||||||
|
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
public class FakeMailService implements MailService{
|
||||||
|
@Override
|
||||||
|
public void sendVerificationEmail(String toEmail, String token) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -122,8 +122,7 @@ paths:
|
|||||||
tags:
|
tags:
|
||||||
- Authentication
|
- Authentication
|
||||||
summary: Registriert einen neuen User inkl. Zählpunkt
|
summary: Registriert einen neuen User inkl. Zählpunkt
|
||||||
description: Setzt den User auf Status PENDING und triggert den asynchronen
|
description: Setzt den User auf Status PENDING und versendet eine Verifizierungs-E-Mail.
|
||||||
EDA-Check.
|
|
||||||
operationId: register
|
operationId: register
|
||||||
requestBody:
|
requestBody:
|
||||||
content:
|
content:
|
||||||
@ -220,6 +219,30 @@ paths:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: "#/components/schemas/EnergyCommunityDto"
|
$ref: "#/components/schemas/EnergyCommunityDto"
|
||||||
|
/api/dashboard/user:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- dashboard-controller
|
||||||
|
operationId: getUserDashboard
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
'*/*':
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/UserStats"
|
||||||
|
/api/dashboard/admin:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- dashboard-controller
|
||||||
|
operationId: getAdminDashboard
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
|
content:
|
||||||
|
'*/*':
|
||||||
|
schema:
|
||||||
|
$ref: "#/components/schemas/AdminStats"
|
||||||
/api/community/metering-points/{id}/eligible-communities:
|
/api/community/metering-points/{id}/eligible-communities:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@ -241,6 +264,20 @@ paths:
|
|||||||
type: array
|
type: array
|
||||||
items:
|
items:
|
||||||
$ref: "#/components/schemas/EligibleCommunityResponse"
|
$ref: "#/components/schemas/EligibleCommunityResponse"
|
||||||
|
/api/auth/verify-email:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- Authentication
|
||||||
|
operationId: verifyEmail
|
||||||
|
parameters:
|
||||||
|
- name: token
|
||||||
|
in: query
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: OK
|
||||||
/api/admin/users/pending:
|
/api/admin/users/pending:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
@ -306,14 +343,27 @@ components:
|
|||||||
type: object
|
type: object
|
||||||
description: Payload für die Registrierung eines neuen Users
|
description: Payload für die Registrierung eines neuen Users
|
||||||
properties:
|
properties:
|
||||||
|
participantType:
|
||||||
|
type: string
|
||||||
|
description: Privatperson oder juristische Person (Firma/Verein/Gemeinde)
|
||||||
|
enum:
|
||||||
|
- PRIVATE
|
||||||
|
- COMPANY
|
||||||
|
example: PRIVATE
|
||||||
firstName:
|
firstName:
|
||||||
type: string
|
type: string
|
||||||
|
description: "Vorname (bei juristischer Person: Vorname der Ansprechperson)"
|
||||||
example: Max
|
example: Max
|
||||||
minLength: 1
|
minLength: 1
|
||||||
lastName:
|
lastName:
|
||||||
type: string
|
type: string
|
||||||
|
description: "Nachname (bei juristischer Person: Nachname der Ansprechperson)"
|
||||||
example: Mustermann
|
example: Mustermann
|
||||||
minLength: 1
|
minLength: 1
|
||||||
|
organizationName:
|
||||||
|
type: string
|
||||||
|
description: "Nur bei juristischer Person: Firmen-, Vereins- oder Gemeindename"
|
||||||
|
example: Musterverein
|
||||||
email:
|
email:
|
||||||
type: string
|
type: string
|
||||||
format: email
|
format: email
|
||||||
@ -324,16 +374,11 @@ components:
|
|||||||
example: SecurePass123!
|
example: SecurePass123!
|
||||||
maxLength: 2147483647
|
maxLength: 2147483647
|
||||||
minLength: 8
|
minLength: 8
|
||||||
atNumber:
|
|
||||||
type: string
|
|
||||||
example: AT0010000000000000000000001234567
|
|
||||||
minLength: 1
|
|
||||||
pattern: "^AT[0-9]{31}$"
|
|
||||||
required:
|
required:
|
||||||
- atNumber
|
|
||||||
- email
|
- email
|
||||||
- firstName
|
- firstName
|
||||||
- lastName
|
- lastName
|
||||||
|
- participantType
|
||||||
- password
|
- password
|
||||||
LoginRequest:
|
LoginRequest:
|
||||||
type: object
|
type: object
|
||||||
@ -366,6 +411,30 @@ components:
|
|||||||
type: string
|
type: string
|
||||||
description: Rolle des Users
|
description: Rolle des Users
|
||||||
example: USER
|
example: USER
|
||||||
|
UserStats:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
totalMeteringPoints:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
activeMemberships:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
pendingMemberships:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
AdminStats:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
totalCommunities:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
pendingUsers:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
|
totalActiveMemberships:
|
||||||
|
type: integer
|
||||||
|
format: int64
|
||||||
EligibleCommunityResponse:
|
EligibleCommunityResponse:
|
||||||
type: object
|
type: object
|
||||||
properties:
|
properties:
|
||||||
@ -389,13 +458,18 @@ components:
|
|||||||
id:
|
id:
|
||||||
type: string
|
type: string
|
||||||
format: uuid
|
format: uuid
|
||||||
|
participantType:
|
||||||
|
type: string
|
||||||
|
enum:
|
||||||
|
- PRIVATE
|
||||||
|
- COMPANY
|
||||||
firstName:
|
firstName:
|
||||||
type: string
|
type: string
|
||||||
lastName:
|
lastName:
|
||||||
type: string
|
type: string
|
||||||
|
organizationName:
|
||||||
|
type: string
|
||||||
email:
|
email:
|
||||||
type: string
|
type: string
|
||||||
status:
|
status:
|
||||||
type: string
|
type: string
|
||||||
primaryAtNumber:
|
|
||||||
type: string
|
|
||||||
|
|||||||
@ -31,12 +31,14 @@
|
|||||||
{{ authService.currentUserRole() === 'ADMIN' ? 'Verwaltung' : 'Mein Bereich' }}
|
{{ authService.currentUserRole() === 'ADMIN' ? 'Verwaltung' : 'Mein Bereich' }}
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<div class="flex items-center gap-2 text-sm text-slate-600 bg-slate-50 px-3 py-1.5 rounded-full border border-slate-200">
|
<div class="flex items-center gap-4">
|
||||||
<span class="relative flex h-2.5 w-2.5">
|
<span class="text-sm text-gray-500">
|
||||||
<span class="animate-ping absolute inline-flex h-full w-full rounded-full bg-emerald-400 opacity-75"></span>
|
Rolle: <span class="font-semibold text-gray-700">{{ authService.currentUserRole() }}</span>
|
||||||
<span class="relative inline-flex rounded-full h-2.5 w-2.5 bg-emerald-500"></span>
|
</span>
|
||||||
</span>
|
<button (click)="onLogout()"
|
||||||
{{ authService.currentUserRole() }}
|
class="text-sm font-medium text-red-600 hover:text-red-700 border border-red-200 hover:border-red-300 px-3 py-1.5 rounded-md transition duration-150">
|
||||||
|
Abmelden
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import {Component, computed, inject} from '@angular/core';
|
import {Component, computed, inject} from '@angular/core';
|
||||||
import {RouterLink, RouterLinkActive, RouterOutlet} from '@angular/router';
|
import {Router, RouterLink, RouterLinkActive, RouterOutlet} from '@angular/router';
|
||||||
import {AuthService} from '../../services/auth';
|
import {AuthService} from '../../services/auth';
|
||||||
import {NAV_ITEMS} from './dashboard-nav-items';
|
import {NAV_ITEMS} from './dashboard-nav-items';
|
||||||
|
|
||||||
@ -12,9 +12,15 @@ import {NAV_ITEMS} from './dashboard-nav-items';
|
|||||||
})
|
})
|
||||||
export class DashboardLayout {
|
export class DashboardLayout {
|
||||||
authService = inject(AuthService);
|
authService = inject(AuthService);
|
||||||
|
private router = inject(Router);
|
||||||
|
|
||||||
visibleNavItems = computed(() => {
|
visibleNavItems = computed(() => {
|
||||||
const role = this.authService.currentUserRole();
|
const role = this.authService.currentUserRole();
|
||||||
return NAV_ITEMS.filter(item => role !== null && item.roles.includes(role));
|
return NAV_ITEMS.filter(item => role !== null && item.roles.includes(role));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
onLogout() {
|
||||||
|
this.authService.logout();
|
||||||
|
this.router.navigate(['/']);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,6 +22,6 @@ export class HeaderComponent {
|
|||||||
|
|
||||||
onLogout() {
|
onLogout() {
|
||||||
this.authService.logout();
|
this.authService.logout();
|
||||||
this.router.navigate(['/login']);
|
this.router.navigate(['/']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -43,12 +43,6 @@
|
|||||||
<div class="text-sm text-gray-500">{{ user.email }}</div>
|
<div class="text-sm text-gray-500">{{ user.email }}</div>
|
||||||
</td>
|
</td>
|
||||||
|
|
||||||
<td class="p-4">
|
|
||||||
<div class="text-gray-900 font-mono text-sm">
|
|
||||||
{{ user.primaryAtNumber || 'Kein Zählpunkt hinterlegt' }}
|
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
|
|
||||||
<td class="p-4">
|
<td class="p-4">
|
||||||
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800">
|
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800">
|
||||||
{{ user.status }}
|
{{ user.status }}
|
||||||
|
|||||||
@ -7,104 +7,65 @@
|
|||||||
<h2 class="mt-2 text-xl font-semibold text-slate-600">Erstelle dein Mitgliedskonto</h2>
|
<h2 class="mt-2 text-xl font-semibold text-slate-600">Erstelle dein Mitgliedskonto</h2>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="sm:mx-auto sm:w-full sm:max-w-3xl bg-white shadow-md rounded-2xl border border-slate-100 overflow-hidden">
|
<div class="sm:mx-auto sm:w-full sm:max-w-md bg-white shadow-md rounded-2xl border border-slate-100 overflow-hidden">
|
||||||
|
|
||||||
<mat-stepper linear #stepper class="!bg-transparent">
|
@if (!registeredSuccessfully) {
|
||||||
|
<form [formGroup]="accountForm" (ngSubmit)="onSubmit()" class="p-6 flex flex-col gap-4">
|
||||||
|
<p class="text-sm text-slate-500 mb-2">
|
||||||
|
Für die Registrierung reichen deine Kontaktdaten. Zählpunktnummer und IBAN kannst du nach der
|
||||||
|
Freischaltung deines Kontos in deinem Mitgliederbereich hinterlegen.
|
||||||
|
</p>
|
||||||
|
|
||||||
<mat-step [stepControl]="accountForm">
|
<mat-button-toggle-group formControlName="participantType" class="w-full mb-2">
|
||||||
<ng-template matStepLabel>Konto</ng-template>
|
<mat-button-toggle value="PRIVATE" class="flex-1">Privatperson</mat-button-toggle>
|
||||||
<form [formGroup]="accountForm" class="p-6 flex flex-col gap-4">
|
<mat-button-toggle value="COMPANY" class="flex-1">Firma / Verein / Gemeinde</mat-button-toggle>
|
||||||
<p class="text-sm text-slate-500 mb-2">Trage deine persönlichen Daten für den Portalzugang ein.</p>
|
</mat-button-toggle-group>
|
||||||
|
|
||||||
|
@if (isCompany) {
|
||||||
<mat-form-field appearance="outline" class="w-full">
|
<mat-form-field appearance="outline" class="w-full">
|
||||||
<mat-label>Vollständiger Name / Vereinsname</mat-label>
|
<mat-label>Firmen-, Vereins- oder Gemeindename</mat-label>
|
||||||
<input matInput formControlName="name" placeholder="Max Mustermann">
|
<input matInput formControlName="organizationName" placeholder="Musterverein">
|
||||||
|
</mat-form-field>
|
||||||
|
}
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
<mat-form-field appearance="outline" class="w-full">
|
||||||
|
<mat-label>{{ isCompany ? 'Vorname (Ansprechperson)' : 'Vorname' }}</mat-label>
|
||||||
|
<input matInput formControlName="firstName" placeholder="Max">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="w-full">
|
<mat-form-field appearance="outline" class="w-full">
|
||||||
<mat-label>E-Mail-Adresse</mat-label>
|
<mat-label>{{ isCompany ? 'Nachname (Ansprechperson)' : 'Nachname' }}</mat-label>
|
||||||
<input matInput type="email" formControlName="email" placeholder="max@beispiel.at">
|
<input matInput formControlName="lastName" placeholder="Mustermann">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="w-full">
|
<mat-form-field appearance="outline" class="w-full">
|
||||||
<mat-label>Passwort</mat-label>
|
<mat-label>E-Mail-Adresse</mat-label>
|
||||||
<input matInput type="password" formControlName="password">
|
<input matInput type="email" formControlName="email" placeholder="max@beispiel.at">
|
||||||
</mat-form-field>
|
</mat-form-field>
|
||||||
|
|
||||||
<div class="flex justify-end mt-4">
|
<mat-form-field appearance="outline" class="w-full">
|
||||||
<button mat-flat-button color="primary" matStepperNext type="button">Weiter</button>
|
<mat-label>Passwort</mat-label>
|
||||||
</div>
|
<input matInput type="password" formControlName="password">
|
||||||
</form>
|
</mat-form-field>
|
||||||
</mat-step>
|
|
||||||
|
|
||||||
<mat-step [stepControl]="energyForm">
|
<div class="flex justify-end mt-4">
|
||||||
<ng-template matStepLabel>Anlagendaten</ng-template>
|
<button mat-flat-button color="accent" type="submit" [disabled]="accountForm.invalid">
|
||||||
<form [formGroup]="energyForm" class="p-6 flex flex-col gap-4">
|
Registrierung abschließen
|
||||||
<p class="text-sm text-slate-500 mb-2">Diese Daten benötigen wir für die Zuordnung beim Netzbetreiber.</p>
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
}
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="w-full">
|
@if (registeredSuccessfully) {
|
||||||
<mat-label>Zählpunktnummer (33-stellig)</mat-label>
|
<div class="p-8 text-center">
|
||||||
<input matInput formControlName="accountingPointId" placeholder="AT0010000000000000000000000000000" maxlength="35">
|
<h3 class="text-lg font-semibold text-slate-800 mb-2">Fast geschafft!</h3>
|
||||||
<mat-hint>Beginnt mit AT und findest du auf deiner Stromrechnung.</mat-hint>
|
<p class="text-sm text-slate-500">
|
||||||
</mat-form-field>
|
Wir haben dir eine Bestätigungs-E-Mail geschickt. Bitte verifiziere deine E-Mail-Adresse.
|
||||||
|
Anschließend prüft ein Administrator dein Konto und schaltet es frei.
|
||||||
<div class="grid grid-cols-1 md:grid-cols-3 gap-4 mt-2">
|
</p>
|
||||||
<mat-form-field appearance="outline" class="md:col-span-2">
|
</div>
|
||||||
<mat-label>Straße & Hausnummer</mat-label>
|
}
|
||||||
<input matInput formControlName="street">
|
|
||||||
</mat-form-field>
|
|
||||||
<mat-form-field appearance="outline">
|
|
||||||
<mat-label>PLZ</mat-label>
|
|
||||||
<input matInput formControlName="zip">
|
|
||||||
</mat-form-field>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="w-full">
|
|
||||||
<mat-label>Ort</mat-label>
|
|
||||||
<input matInput formControlName="city">
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<div class="flex justify-between mt-4">
|
|
||||||
<button mat-button matStepperPrevious type="button">Zurück</button>
|
|
||||||
<button mat-flat-button color="primary" matStepperNext type="button">Weiter</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</mat-step>
|
|
||||||
|
|
||||||
<mat-step [stepControl]="paymentForm">
|
|
||||||
<ng-template matStepLabel>Abrechnung</ng-template>
|
|
||||||
<form [formGroup]="paymentForm" (ngSubmit)="onSubmit()" class="p-6 flex flex-col gap-4">
|
|
||||||
<p class="text-sm text-slate-500 mb-2">Für den Einzug der Netzkosten-Ersparnisse/Beiträge sowie Gutschriften für Einspeisung.</p>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="w-full">
|
|
||||||
<mat-label>IBAN</mat-label>
|
|
||||||
<input matInput formControlName="iban" placeholder="AT.. .... .... .... ....">
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<mat-form-field appearance="outline" class="w-full">
|
|
||||||
<mat-label>BIC (Optional)</mat-label>
|
|
||||||
<input matInput formControlName="bic">
|
|
||||||
</mat-form-field>
|
|
||||||
|
|
||||||
<div class="bg-slate-50 p-4 rounded-xl border border-slate-200 text-xs text-slate-600 my-2 space-y-2">
|
|
||||||
<p class="font-bold">SEPA-Lastschriftmandat (Creditor ID: ATXXXXXXXXXXXXXXXX)</p>
|
|
||||||
<p>Ich ermächtige die Energiegemeinschaft, Zahlungen von meinem Konto mittels Lastschrift einzuziehen. Zugleich weise ich mein Kreditinstitut an, die eingelösten Lastschriften zu bezahlen.</p>
|
|
||||||
<p class="italic text-slate-400">Hinweis: Ich kann innerhalb von 8 Wochen, beginnend mit dem Belastungsdatum, die Erstattung des belasteten Betrages verlangen.</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<mat-checkbox formControlName="acceptMandate" color="primary" class="text-sm">
|
|
||||||
Ich erteile das SEPA-Lastschriftmandat elektronisch.
|
|
||||||
</mat-checkbox>
|
|
||||||
|
|
||||||
<div class="flex justify-between mt-4">
|
|
||||||
<button mat-button matStepperPrevious type="button">Zurück</button>
|
|
||||||
<button mat-flat-button color="accent" type="submit" [disabled]="accountForm.invalid || energyForm.invalid || paymentForm.invalid">
|
|
||||||
Registrierung abschließen
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</mat-step>
|
|
||||||
|
|
||||||
</mat-stepper>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,25 +1,25 @@
|
|||||||
import {Component, inject} from '@angular/core';
|
import {Component, inject} from '@angular/core';
|
||||||
import {FormBuilder, ReactiveFormsModule, Validators} from '@angular/forms';
|
import {FormBuilder, ReactiveFormsModule, Validators} from '@angular/forms';
|
||||||
import {MatStepperModule} from '@angular/material/stepper';
|
|
||||||
import {MatFormFieldModule} from '@angular/material/form-field';
|
import {MatFormFieldModule} from '@angular/material/form-field';
|
||||||
import {MatInputModule} from '@angular/material/input';
|
import {MatInputModule} from '@angular/material/input';
|
||||||
import {MatButtonModule} from '@angular/material/button';
|
import {MatButtonModule} from '@angular/material/button';
|
||||||
import {MatIconModule} from '@angular/material/icon';
|
import {MatIconModule} from '@angular/material/icon';
|
||||||
import {MatCheckboxModule} from '@angular/material/checkbox';
|
import {MatButtonToggleModule} from '@angular/material/button-toggle';
|
||||||
import {Router} from '@angular/router';
|
import {AuthenticationService, RegistrationRequest, UserProfileResponse} from '../../api';
|
||||||
import {AuthenticationService, RegistrationRequest} from '../../api';
|
import {RouterLink} from '@angular/router';
|
||||||
|
import ParticipantTypeEnum = UserProfileResponse.ParticipantTypeEnum;
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-register',
|
selector: 'app-register',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [
|
imports: [
|
||||||
ReactiveFormsModule,
|
ReactiveFormsModule,
|
||||||
MatStepperModule,
|
|
||||||
MatFormFieldModule,
|
MatFormFieldModule,
|
||||||
MatInputModule,
|
MatInputModule,
|
||||||
MatButtonModule,
|
MatButtonModule,
|
||||||
MatIconModule,
|
MatIconModule,
|
||||||
MatCheckboxModule
|
MatButtonToggleModule,
|
||||||
|
RouterLink
|
||||||
],
|
],
|
||||||
templateUrl: './register.html',
|
templateUrl: './register.html',
|
||||||
styleUrls: ['./register.scss']
|
styleUrls: ['./register.scss']
|
||||||
@ -27,52 +27,62 @@ import {AuthenticationService, RegistrationRequest} from '../../api';
|
|||||||
export class Register {
|
export class Register {
|
||||||
private fb = inject(FormBuilder);
|
private fb = inject(FormBuilder);
|
||||||
private authService = inject(AuthenticationService);
|
private authService = inject(AuthenticationService);
|
||||||
private router = inject(Router);
|
|
||||||
|
|
||||||
// Schritt 1: Login & Stammdaten
|
// Für die Toggle-Buttons im Template
|
||||||
|
readonly ParticipantType = RegistrationRequest.ParticipantTypeEnum;
|
||||||
|
|
||||||
|
registeredSuccessfully = false;
|
||||||
|
|
||||||
accountForm = this.fb.group({
|
accountForm = this.fb.group({
|
||||||
name: ['', Validators.required],
|
participantType: this.fb.control<ParticipantTypeEnum>(
|
||||||
|
RegistrationRequest.ParticipantTypeEnum.Private,
|
||||||
|
{nonNullable: true, validators: Validators.required}
|
||||||
|
),
|
||||||
|
organizationName: [''],
|
||||||
|
firstName: ['', Validators.required],
|
||||||
|
lastName: ['', Validators.required],
|
||||||
email: ['', [Validators.required, Validators.email]],
|
email: ['', [Validators.required, Validators.email]],
|
||||||
password: ['', [Validators.required, Validators.minLength(8)]]
|
password: ['', [Validators.required, Validators.minLength(8)]]
|
||||||
});
|
});
|
||||||
|
|
||||||
// Schritt 2: Energiegemeinschaft-Spezifische Daten
|
constructor() {
|
||||||
energyForm = this.fb.group({
|
this.accountForm.get('participantType')!.valueChanges.subscribe(type => {
|
||||||
accountingPointId: ['', [Validators.required, Validators.pattern(/^AT\d{31}$/)]], // AT + 33 Ziffern für Zählpunkt
|
const orgControl = this.accountForm.get('organizationName')!;
|
||||||
street: ['', Validators.required],
|
if (type === RegistrationRequest.ParticipantTypeEnum.Company) {
|
||||||
zip: ['', Validators.required],
|
orgControl.setValidators([Validators.required]);
|
||||||
city: ['', Validators.required]
|
} else {
|
||||||
});
|
orgControl.clearValidators();
|
||||||
|
}
|
||||||
|
orgControl.updateValueAndValidity();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Schritt 3: SEPA Lastschrift / Mandat
|
get isCompany(): boolean {
|
||||||
paymentForm = this.fb.group({
|
return this.accountForm.value.participantType === RegistrationRequest.ParticipantTypeEnum.Company;
|
||||||
iban: ['', [Validators.required, Validators.pattern(/^[A-Z]{2}[0-9]{2}[A-Z0-9]{12,30}$/)]],
|
}
|
||||||
bic: [''],
|
|
||||||
acceptMandate: [false, Validators.requiredTrue]
|
|
||||||
});
|
|
||||||
|
|
||||||
onSubmit() {
|
onSubmit() {
|
||||||
if (this.accountForm.valid && this.energyForm.valid && this.paymentForm.valid) {
|
if (this.accountForm.invalid) {
|
||||||
const registrationData: RegistrationRequest = {
|
this.accountForm.markAllAsTouched();
|
||||||
firstName: this.accountForm.value.name!,
|
return;
|
||||||
lastName: this.accountForm.value.name!,
|
|
||||||
email: this.accountForm.value.email!,
|
|
||||||
password: this.accountForm.value.password!,
|
|
||||||
atNumber: this.energyForm.value.accountingPointId!
|
|
||||||
};
|
|
||||||
console.log('Registrierungsdaten für Backend:', registrationData);
|
|
||||||
|
|
||||||
this.authService.register(registrationData).subscribe({
|
|
||||||
next: (response) => {
|
|
||||||
console.log('Erfolgreich registriert!', response);
|
|
||||||
// Bei Erfolg z.B. auf eine Bestätigungsseite oder zum Login leiten
|
|
||||||
// this.router.navigate(['/login']);
|
|
||||||
},
|
|
||||||
error: (err) => {
|
|
||||||
console.error('Fehler bei der Registrierung', err);
|
|
||||||
// Hier könntest du später einen Angular Material SnackBar einblenden
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const registrationData: RegistrationRequest = {
|
||||||
|
participantType: this.accountForm.value.participantType!,
|
||||||
|
firstName: this.accountForm.value.firstName!,
|
||||||
|
lastName: this.accountForm.value.lastName!,
|
||||||
|
organizationName: this.accountForm.value.organizationName || undefined,
|
||||||
|
email: this.accountForm.value.email!,
|
||||||
|
password: this.accountForm.value.password!
|
||||||
|
};
|
||||||
|
|
||||||
|
this.authService.register(registrationData).subscribe({
|
||||||
|
next: () => {
|
||||||
|
this.registeredSuccessfully = true;
|
||||||
|
},
|
||||||
|
error: (err) => {
|
||||||
|
console.error('Fehler bei der Registrierung', err);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user