Umstellung auf firebase

This commit is contained in:
2025-02-20 17:51:54 -06:00
parent f6d1b8623c
commit 521e799bff
40 changed files with 495 additions and 261 deletions

View File

@@ -26,11 +26,10 @@
"@angular/router": "^18.1.3",
"@bluehalo/ngx-leaflet": "^18.0.2",
"@fortawesome/angular-fontawesome": "^0.15.0",
"@fortawesome/fontawesome-free": "^6.5.2",
"@fortawesome/fontawesome-svg-core": "^6.5.2",
"@fortawesome/free-brands-svg-icons": "^6.5.2",
"@fortawesome/free-regular-svg-icons": "^6.5.2",
"@fortawesome/free-solid-svg-icons": "^6.5.2",
"@fortawesome/fontawesome-free": "^6.7.2",
"@fortawesome/fontawesome-svg-core": "^6.7.2",
"@fortawesome/free-brands-svg-icons": "^6.7.2",
"@fortawesome/free-solid-svg-icons": "^6.7.2",
"@ng-select/ng-select": "^13.4.1",
"@ngneat/until-destroy": "^10.0.0",
"@stripe/stripe-js": "^4.3.0",
@@ -77,4 +76,4 @@
"tailwindcss": "^3.4.4",
"typescript": "~5.4.5"
}
}
}

View File

@@ -1,5 +1,5 @@
{
"/api": {
"/bizmatch": {
"target": "http://localhost:3000",
"secure": false,
"changeOrigin": true,

View File

@@ -1,9 +1,9 @@
<!-- <div class="container"> -->
<div class="flex flex-col" [ngClass]="{ 'bg-slate-100 print:bg-white': actualRoute !== 'home' }">
@if (actualRoute !=='home' && actualRoute !=='login'){
<div class="wrapper" [ngClass]="{ 'bg-slate-100 print:bg-white': actualRoute !== 'home' }">
@if (actualRoute !=='home' && actualRoute !=='login' && actualRoute!=='emailVerification' && actualRoute!=='email-authorized'){
<header></header>
}
<main class="flex-grow">
<main class="flex-1">
<router-outlet></router-outlet>
</main>

View File

@@ -2,6 +2,8 @@ import { Routes } from '@angular/router';
import { LogoutComponent } from './components/logout/logout.component';
import { NotFoundComponent } from './components/not-found/not-found.component';
import { EmailAuthorizedComponent } from './components/email-authorized/email-authorized.component';
import { EmailVerificationComponent } from './components/email-verification/email-verification.component';
import { LoginRegisterComponent } from './components/login-register/login-register.component';
import { AuthGuard } from './guards/auth.guard';
import { ListingCategoryGuard } from './guards/listing-category.guard';
@@ -147,6 +149,14 @@ export const routes: Routes = [
path: 'pricing',
component: PricingComponent,
},
{
path: 'emailVerification',
component: EmailVerificationComponent,
},
{
path: 'email-authorized',
component: EmailAuthorizedComponent,
},
{
path: 'pricingOverview',
component: PricingComponent,

View File

@@ -0,0 +1,16 @@
<div class="container mx-auto p-4 text-center min-h-screen bg-gray-100">
<ng-container *ngIf="verificationStatus === 'pending'">
<p class="text-lg text-gray-600">Verifying your email...</p>
</ng-container>
<ng-container *ngIf="verificationStatus === 'success'">
<h2 class="text-2xl font-bold text-green-600 mb-5">Your email has been verified</h2>
<!-- <p class="text-gray-700 mb-4">You can now sign in with your new account</p> -->
<a routerLink="/account" class="inline-block px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition-colors">Follow this link to access your Account Page </a>
</ng-container>
<ng-container *ngIf="verificationStatus === 'error'">
<h2 class="text-2xl font-bold text-red-600 mb-2">Verification failed</h2>
<p class="text-gray-700">{{ errorMessage }}</p>
</ng-container>
</div>

View File

@@ -0,0 +1,47 @@
import { CommonModule } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, RouterModule } from '@angular/router';
import { environment } from '../../../environments/environment';
import { AuthService } from '../../services/auth.service';
import { UserService } from '../../services/user.service';
@Component({
selector: 'app-email-authorized',
standalone: true,
imports: [CommonModule, RouterModule],
templateUrl: './email-authorized.component.html',
})
export class EmailAuthorizedComponent implements OnInit {
verificationStatus: 'pending' | 'success' | 'error' = 'pending';
errorMessage: string | null = null;
constructor(private route: ActivatedRoute, private http: HttpClient, private authService: AuthService, private userService: UserService) {}
ngOnInit(): void {
const oobCode = this.route.snapshot.queryParamMap.get('oobCode');
const email = this.route.snapshot.queryParamMap.get('email');
const mode = this.route.snapshot.queryParamMap.get('mode');
if (mode === 'verifyEmail' && oobCode && email) {
this.verifyEmail(oobCode, email);
} else {
this.verificationStatus = 'error';
this.errorMessage = 'Invalid verification link';
}
}
private verifyEmail(oobCode: string, email: string): void {
this.http.post(`${environment.apiBaseUrl}/bizmatch/auth/verify-email`, { oobCode, email }).subscribe({
next: async () => {
this.verificationStatus = 'success';
await this.authService.refreshToken();
const user = await this.userService.getByMail(email);
},
error: err => {
this.verificationStatus = 'error';
this.errorMessage = err.error?.message || 'Verification failed';
},
});
}
}

View File

@@ -1,7 +1,7 @@
<div class="flex flex-col items-center justify-center min-h-screen bg-gray-100">
<div class="bg-white p-8 rounded shadow-md w-full max-w-md">
<h2 class="text-2xl font-bold mb-6 text-center">
{{ isLoginMode ? 'Login' : 'Registrierung' }}
<div class="bg-white p-8 rounded-lg shadow-md w-full max-w-md">
<h2 class="text-2xl font-bold mb-6 text-center text-gray-800">
{{ isLoginMode ? 'Login' : 'Sign Up' }}
</h2>
<!-- Toggle Switch mit Flowbite -->
@@ -18,34 +18,99 @@
<!-- E-Mail Eingabe -->
<div class="mb-4">
<label for="email" class="block text-gray-700 mb-2">EMail</label>
<input id="email" type="email" [(ngModel)]="email" placeholder="Please enter EMail Address" class="w-full px-3 py-2 border rounded focus:outline-none focus:border-blue-500" />
<label for="email" class="block text-gray-700 mb-2 font-medium">E-Mail</label>
<div class="relative">
<input
id="email"
type="email"
[(ngModel)]="email"
placeholder="Please enter E-Mail Address"
class="w-full px-3 py-2 pl-10 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
<fa-icon [icon]="envelope" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"></fa-icon>
</div>
</div>
<!-- Passwort Eingabe -->
<div class="mb-4">
<label for="password" class="block text-gray-700 mb-2">Password</label>
<input id="password" type="password" [(ngModel)]="password" placeholder="Please enter Passwort" class="w-full px-3 py-2 border rounded focus:outline-none focus:border-blue-500" />
<label for="password" class="block text-gray-700 mb-2 font-medium">Password</label>
<div class="relative">
<input
id="password"
type="password"
[(ngModel)]="password"
placeholder="Please enter Password"
class="w-full px-3 py-2 pl-10 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
<fa-icon [icon]="lock" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"></fa-icon>
</div>
</div>
<!-- Passwort-Bestätigung nur im Registrierungsmodus -->
<div *ngIf="!isLoginMode" class="mb-6">
<label for="confirmPassword" class="block text-gray-700 mb-2">Confirm Password</label>
<input id="confirmPassword" type="password" [(ngModel)]="confirmPassword" placeholder="Repeat Password" class="w-full px-3 py-2 border rounded focus:outline-none focus:border-blue-500" />
<label for="confirmPassword" class="block text-gray-700 mb-2 font-medium">Confirm Password</label>
<div class="relative">
<input
id="confirmPassword"
type="password"
[(ngModel)]="confirmPassword"
placeholder="Repeat Password"
class="w-full px-3 py-2 pl-10 border rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
/>
<fa-icon [icon]="lock" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400"></fa-icon>
</div>
</div>
<div *ngIf="errorMessage" class="text-red-500 text-center mb-4">
<!-- Fehlermeldung -->
<div *ngIf="errorMessage" class="text-red-500 text-center mb-4 text-sm">
{{ errorMessage }}
</div>
<button (click)="onSubmit()" class="w-full bg-blue-500 hover:bg-blue-600 text-white py-2 rounded-lg mb-4">
<!-- Submit Button -->
<button (click)="onSubmit()" class="w-full flex items-center justify-center bg-blue-600 hover:bg-blue-700 text-white py-2.5 rounded-lg mb-4 transition-colors duration-200">
<!-- <fa-icon [icon]="isLoginMode ? 'fas fas-user-plus' : 'userplus'" class="mr-2"></fa-icon> -->
<i *ngIf="isLoginMode" class="fa-solid fa-user-plus mr-2"></i>
<i *ngIf="!isLoginMode" class="fa-solid fa-arrow-right mr-2"></i>
{{ isLoginMode ? 'Sign in with Email' : 'Register' }}
</button>
<!-- Trennlinie -->
<div class="flex items-center justify-center my-4">
<span class="border-b w-1/5 md:w-1/4"></span>
<span class="text-xs text-center text-gray-500 uppercase mx-2">or</span>
<span class="border-b w-1/5 md:w-1/4"></span>
<span class="border-b w-1/5 md:w-1/4 border-gray-300"></span>
<span class="text-xs text-gray-500 uppercase mx-2">or</span>
<span class="border-b w-1/5 md:w-1/4 border-gray-300"></span>
</div>
<button (click)="loginWithGoogle()" class="w-full bg-red-500 hover:bg-red-600 text-white py-2 rounded-lg">Continue with Google</button>
<!-- Google Button -->
<button (click)="loginWithGoogle()" class="w-full flex items-center justify-center bg-white border border-gray-300 hover:bg-gray-50 text-gray-700 py-2.5 rounded-lg transition-colors duration-200">
<!-- <svg class="h-5 w-5 mr-2" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
<path
d="M12.24 10.32V13.8H15.48C15.336 14.688 14.568 16.368 12.24 16.368C10.224 16.368 8.568 14.688 8.568 12.672C8.568 10.656 10.224 8.976 12.24 8.976C13.32 8.976 14.16 9.432 14.688 10.08L16.704 8.208C15.528 7.032 14.04 6.384 12.24 6.384C8.832 6.384 6 9.216 6 12.672C6 16.128 8.832 18.96 12.24 18.96C15.696 18.96 18.12 16.656 18.12 12.672C18.12 11.952 18.024 11.28 17.88 10.656L12.24 10.32Z"
fill="currentColor"
/>
</svg> -->
<svg class="w-6 h-6 mr-2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48">
<path
fill="#FFC107"
d="M43.611 20.083H42V20H24v8h11.303c-1.649 4.657-6.08 8-11.303 8-6.627 0-12-5.373-12-12s5.373-12 12-12c3.059 0 5.842 1.154 7.961 3.039l5.657-5.657C34.046 6.053 29.268 4 24 4 12.955 4 4 12.955 4 24s8.955 20 20 20 20-8.955 20-20c0-1.341-.138-2.65-.389-3.917z"
/>
<path fill="#FF3D00" d="M6.306 14.691l6.571 4.819C14.655 15.108 18.961 12 24 12c3.059 0 5.842 1.154 7.961 3.039l5.657-5.657C34.046 6.053 29.268 4 24 4 16.318 4 9.656 8.337 6.306 14.691z" />
<path fill="#4CAF50" d="M24 44c5.166 0 9.86-1.977 13.409-5.192l-6.19-5.238A11.91 11.91 0 0124 36c-5.202 0-9.619-3.317-11.283-7.946l-6.522 5.025C9.505 39.556 16.227 44 24 44z" />
<path fill="#1976D2" d="M43.611 20.083H42V20H24v8h11.303a12.04 12.04 0 01-4.087 5.571l.003-.002 6.19 5.238C36.971 39.205 44 34 44 24c0-1.341-.138-2.65-.389-3.917z" />
</svg>
Continue with Google
</button>
<!-- <button (click)="loginWithGoogle()" class="bg-white text-blue-600 px-6 py-3 rounded-lg shadow-lg hover:bg-gray-100 transition duration-300 flex items-center justify-center">
<svg class="w-6 h-6 mr-2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48">
<path
fill="#FFC107"
d="M43.611 20.083H42V20H24v8h11.303c-1.649 4.657-6.08 8-11.303 8-6.627 0-12-5.373-12-12s5.373-12 12-12c3.059 0 5.842 1.154 7.961 3.039l5.657-5.657C34.046 6.053 29.268 4 24 4 12.955 4 4 12.955 4 24s8.955 20 20 20 20-8.955 20-20c0-1.341-.138-2.65-.389-3.917z"
/>
<path fill="#FF3D00" d="M6.306 14.691l6.571 4.819C14.655 15.108 18.961 12 24 12c3.059 0 5.842 1.154 7.961 3.039l5.657-5.657C34.046 6.053 29.268 4 24 4 16.318 4 9.656 8.337 6.306 14.691z" />
<path fill="#4CAF50" d="M24 44c5.166 0 9.86-1.977 13.409-5.192l-6.19-5.238A11.91 11.91 0 0124 36c-5.202 0-9.619-3.317-11.283-7.946l-6.522 5.025C9.505 39.556 16.227 44 24 44z" />
<path fill="#1976D2" d="M43.611 20.083H42V20H24v8h11.303a12.04 12.04 0 01-4.087 5.571l.003-.002 6.19 5.238C36.971 39.205 44 34 44 24c0-1.341-.138-2.65-.389-3.917z" />
</svg>
Continue with Google
</button> -->
</div>
</div>

View File

@@ -2,12 +2,14 @@ import { CommonModule } from '@angular/common';
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { FontAwesomeModule } from '@fortawesome/angular-fontawesome';
import { faArrowRight, faEnvelope, faLock, faUserPlus } from '@fortawesome/free-solid-svg-icons';
import { AuthService } from '../../services/auth.service';
import { LoadingService } from '../../services/loading.service';
@Component({
selector: 'app-login-register',
standalone: true,
imports: [CommonModule, FormsModule],
imports: [CommonModule, FormsModule, FontAwesomeModule],
templateUrl: './login-register.component.html',
})
export class LoginRegisterComponent {
@@ -16,8 +18,11 @@ export class LoginRegisterComponent {
confirmPassword: string = '';
isLoginMode: boolean = true; // true: Login, false: Registration
errorMessage: string = '';
constructor(private authService: AuthService, private route: ActivatedRoute, private router: Router) {}
envelope = faEnvelope;
lock = faLock;
arrowRight = faArrowRight;
userplus = faUserPlus;
constructor(private authService: AuthService, private route: ActivatedRoute, private router: Router, private loadingService: LoadingService) {}
ngOnInit(): void {
// Set mode based on query parameter "mode"
@@ -53,12 +58,16 @@ export class LoginRegisterComponent {
this.errorMessage = 'Passwords do not match.';
return;
}
this.loadingService.startLoading('googleAuth');
this.authService
.registerWithEmail(this.email, this.password)
.then(userCredential => {
console.log('Successfully registered:', userCredential);
this.loadingService.stopLoading('googleAuth');
this.router.navigate(['emailVerification']);
})
.catch(error => {
this.loadingService.stopLoading('googleAuth');
console.error('Error during registration:', error);
if (error.code === 'auth/email-already-in-use') {
this.errorMessage = 'This email address is already in use. Please try logging in.';
@@ -76,6 +85,7 @@ export class LoginRegisterComponent {
.loginWithGoogle()
.then(userCredential => {
console.log('Successfully logged in with Google:', userCredential);
this.router.navigate([`home`]);
})
.catch(error => {
console.error('Error during Google login:', error);

View File

@@ -1,8 +1,8 @@
// auth.interceptor.ts
import { HttpEvent, HttpHandler, HttpInterceptor, HttpRequest } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Observable, from } from 'rxjs';
import { switchMap } from 'rxjs/operators';
import { environment } from '../../environments/environment';
import { AuthService } from '../services/auth.service';
@Injectable()
@@ -10,6 +10,15 @@ export class AuthInterceptor implements HttpInterceptor {
constructor(private authService: AuthService) {}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
// Prüfe, ob die Anfrage an die apiBaseUrl gerichtet ist
const isApiRequest = req.url.startsWith(environment.apiBaseUrl);
if (!isApiRequest) {
// Wenn es keine API-Anfrage ist, leite die Anfrage unverändert weiter
return next.handle(req);
}
// Wenn es eine API-Anfrage ist, füge den Token hinzu (falls vorhanden)
return from(this.authService.getToken()).pipe(
switchMap(token => {
if (token) {

View File

@@ -1,4 +1,4 @@
<header class="w-full flex justify-between items-center p-4 bg-white fixed top-0 z-10 h-16 md:h-20">
<header class="w-full flex justify-between items-center p-4 bg-white top-0 z-10 h-16 md:h-20">
<img src="assets/images/header-logo.png" alt="Logo" class="h-8 md:h-10" />
<div class="hidden md:flex items-center space-x-4">
@if(user){
@@ -34,7 +34,7 @@
</div>
</div>
<main class="flex flex-col items-center justify-center mt-16 md:mt-20 lg:px-4 w-full flex-grow">
<main class="flex flex-col items-center justify-center lg:px-4 w-full flex-grow">
<div class="bg-cover-custom py-20 md:py-40 flex flex-col w-full">
<div class="flex justify-center w-full">
<div class="w-11/12 md:w-2/3 lg:w-1/2">

View File

@@ -226,7 +226,7 @@
</button>
</div>
</form>
<div class="mt-8 max-lg:hidden">
<!-- <div class="mt-8 max-lg:hidden">
<h3 class="text-lg font-medium text-gray-700 mb-2">Membership Level</h3>
<div class="overflow-x-auto">
<div class="inline-block min-w-full">
@@ -290,8 +290,8 @@
</div>
}
</div>
</div>
@if(user.subscriptionPlan==='free'){
</div> -->
<!-- @if(user.subscriptionPlan==='free'){
<div class="flex justify-start">
<button
routerLink="/pricing"
@@ -300,7 +300,7 @@
Upgrade Subscription Plan
</button>
</div>
}
} -->
</div>
}
</div>

View File

@@ -10,7 +10,7 @@ import { ImageCropperComponent } from 'ngx-image-cropper';
import { QuillModule } from 'ngx-quill';
import { lastValueFrom } from 'rxjs';
import { User } from '../../../../../../bizmatch-server/src/models/db.model';
import { AutoCompleteCompleteEvent, Invoice, StripeSubscription, UploadParams, ValidationMessage, emailToDirName } from '../../../../../../bizmatch-server/src/models/main.model';
import { AutoCompleteCompleteEvent, Invoice, UploadParams, ValidationMessage, emailToDirName } from '../../../../../../bizmatch-server/src/models/main.model';
import { environment } from '../../../../environments/environment';
import { ConfirmationComponent } from '../../../components/confirmation/confirmation.component';
import { ConfirmationService } from '../../../components/confirmation/confirmation.service';
@@ -31,10 +31,9 @@ import { ImageService } from '../../../services/image.service';
import { LoadingService } from '../../../services/loading.service';
import { SelectOptionsService } from '../../../services/select-options.service';
import { SharedService } from '../../../services/shared.service';
import { SubscriptionsService } from '../../../services/subscriptions.service';
import { UserService } from '../../../services/user.service';
import { SharedModule } from '../../../shared/shared/shared.module';
import { checkAndUpdate, isAdmin, map2User } from '../../../utils/utils';
import { isAdmin, map2User } from '../../../utils/utils';
import { TOOLBAR_OPTIONS } from '../../utils/defaults';
@Component({
selector: 'app-account',
@@ -79,7 +78,7 @@ export class AccountComponent {
customerSubTypeOptions: Array<{ value: string; label: string }> = [];
tooltipTargetAreasServed = 'tooltip-areasServed';
tooltipTargetLicensed = 'tooltip-licensedIn';
subscriptions: StripeSubscription[] | any[];
// subscriptions: StripeSubscription[] | any[];
constructor(
public userService: UserService,
private geoService: GeoService,
@@ -94,7 +93,7 @@ export class AccountComponent {
private sharedService: SharedService,
private titleCasePipe: TitleCasePipe,
private validationMessagesService: ValidationMessagesService,
private subscriptionService: SubscriptionsService,
// private subscriptionService: SubscriptionsService,
private datePipe: DatePipe,
private router: Router,
private authService: AuthService,
@@ -112,57 +111,57 @@ export class AccountComponent {
this.user = await this.userService.getByMail(email);
}
this.subscriptions = await lastValueFrom(this.subscriptionService.getAllSubscriptions(this.user.email));
await this.synchronizeSubscriptions(this.subscriptions);
// this.subscriptions = await lastValueFrom(this.subscriptionService.getAllSubscriptions(this.user.email));
// await this.synchronizeSubscriptions(this.subscriptions);
this.profileUrl = this.user.hasProfile ? `${this.env.imageBaseUrl}/pictures/profile/${emailToDirName(this.user.email)}.avif?_ts=${new Date().getTime()}` : `/assets/images/placeholder.png`;
this.companyLogoUrl = this.user.hasCompanyLogo ? `${this.env.imageBaseUrl}/pictures/logo/${emailToDirName(this.user.email)}.avif?_ts=${new Date().getTime()}` : `/assets/images/placeholder.png`;
this.customerTypeOptions = this.selectOptions.customerTypes
.filter(ct => ct.value === 'buyer' || ct.value === 'seller' || this.user.customerType === 'professional')
// .filter(ct => ct.value === 'buyer' || ct.value === 'seller' || this.user.customerType === 'professional')
.map(type => ({
value: type.value,
label: this.titleCasePipe.transform(type.name),
}));
this.customerSubTypeOptions = this.selectOptions.customerSubTypes
.filter(ct => ct.value !== 'broker' || this.user.customerSubType === 'broker')
// .filter(ct => ct.value !== 'broker' || this.user.customerSubType === 'broker')
.map(type => ({
value: type.value,
label: this.titleCasePipe.transform(type.name),
}));
}
async synchronizeSubscriptions(subscriptions: StripeSubscription[]) {
let changed = false;
if (this.isAdmin()) {
return;
}
if (this.subscriptions.length === 0) {
if (!this.user.subscriptionPlan) {
this.router.navigate(['pricing']);
} else {
this.subscriptions = [{ ended_at: null, start_date: Math.floor(new Date(this.user.created).getTime() / 1000), status: null, metadata: { plan: 'Free Plan' } }];
changed = checkAndUpdate(changed, this.user.customerType !== 'buyer' && this.user.customerType !== 'seller', () => (this.user.customerType = 'buyer'));
changed = checkAndUpdate(changed, !!this.user.customerSubType, () => (this.user.customerSubType = null));
changed = checkAndUpdate(changed, this.user.subscriptionPlan !== 'free', () => (this.user.subscriptionPlan = 'free'));
changed = checkAndUpdate(changed, !!this.user.subscriptionId, () => (this.user.subscriptionId = null));
}
} else {
const subscription = subscriptions[0];
changed = checkAndUpdate(changed, subscription.metadata['plan'] === 'Broker Plan' && this.user.customerType !== 'professional', () => (this.user.customerType = 'professional'));
changed = checkAndUpdate(changed, subscription.metadata['plan'] === 'Broker Plan' && this.user.customerSubType !== 'broker', () => (this.user.customerSubType = 'broker'));
changed = checkAndUpdate(changed, subscription.metadata['plan'] === 'Broker Plan' && this.user.subscriptionPlan !== 'broker', () => (this.user.subscriptionPlan = 'broker'));
changed = checkAndUpdate(changed, subscription.metadata['plan'] === 'Broker Plan' && !this.user.subscriptionId, () => (this.user.subscriptionId = subscription.id));
// async synchronizeSubscriptions(subscriptions: StripeSubscription[]) {
// let changed = false;
// if (this.isAdmin()) {
// return;
// }
// if (this.subscriptions.length === 0) {
// if (!this.user.subscriptionPlan) {
// this.router.navigate(['pricing']);
// } else {
// this.subscriptions = [{ ended_at: null, start_date: Math.floor(new Date(this.user.created).getTime() / 1000), status: null, metadata: { plan: 'Free Plan' } }];
// changed = checkAndUpdate(changed, this.user.customerType !== 'buyer' && this.user.customerType !== 'seller', () => (this.user.customerType = 'buyer'));
// changed = checkAndUpdate(changed, !!this.user.customerSubType, () => (this.user.customerSubType = null));
// changed = checkAndUpdate(changed, this.user.subscriptionPlan !== 'free', () => (this.user.subscriptionPlan = 'free'));
// changed = checkAndUpdate(changed, !!this.user.subscriptionId, () => (this.user.subscriptionId = null));
// }
// } else {
// const subscription = subscriptions[0];
// changed = checkAndUpdate(changed, subscription.metadata['plan'] === 'Broker Plan' && this.user.customerType !== 'professional', () => (this.user.customerType = 'professional'));
// changed = checkAndUpdate(changed, subscription.metadata['plan'] === 'Broker Plan' && this.user.customerSubType !== 'broker', () => (this.user.customerSubType = 'broker'));
// changed = checkAndUpdate(changed, subscription.metadata['plan'] === 'Broker Plan' && this.user.subscriptionPlan !== 'broker', () => (this.user.subscriptionPlan = 'broker'));
// changed = checkAndUpdate(changed, subscription.metadata['plan'] === 'Broker Plan' && !this.user.subscriptionId, () => (this.user.subscriptionId = subscription.id));
changed = checkAndUpdate(changed, subscription.metadata['plan'] === 'Professional Plan' && this.user.customerType !== 'professional', () => (this.user.customerType = 'professional'));
changed = checkAndUpdate(changed, subscription.metadata['plan'] === 'Professional Plan' && this.user.subscriptionPlan !== 'professional', () => (this.user.subscriptionPlan = 'professional'));
changed = checkAndUpdate(changed, subscription.metadata['plan'] === 'Professional Plan' && this.user.subscriptionId !== 'professional', () => (this.user.subscriptionId = subscription.id));
}
if (changed) {
await this.userService.saveGuaranteed(this.user);
this.cdref.detectChanges();
this.cdref.markForCheck();
}
}
// changed = checkAndUpdate(changed, subscription.metadata['plan'] === 'Professional Plan' && this.user.customerType !== 'professional', () => (this.user.customerType = 'professional'));
// changed = checkAndUpdate(changed, subscription.metadata['plan'] === 'Professional Plan' && this.user.subscriptionPlan !== 'professional', () => (this.user.subscriptionPlan = 'professional'));
// changed = checkAndUpdate(changed, subscription.metadata['plan'] === 'Professional Plan' && this.user.subscriptionId !== 'professional', () => (this.user.subscriptionId = subscription.id));
// }
// if (changed) {
// await this.userService.saveGuaranteed(this.user);
// this.cdref.detectChanges();
// this.cdref.markForCheck();
// }
// }
ngOnDestroy() {
this.validationMessagesService.clearMessages(); // Löschen Sie alle bestehenden Validierungsnachrichten
@@ -273,19 +272,19 @@ export class AccountComponent {
this.user.areasServed[index].county = null;
}
}
getLevel(i: number) {
return this.subscriptions[i].metadata.plan;
}
getStartDate(i: number) {
return this.datePipe.transform(new Date(this.subscriptions[i].start_date * 1000));
}
getEndDate(i: number) {
return this.subscriptions[i].status === 'trialing' ? this.datePipe.transform(new Date(this.subscriptions[i].current_period_end * 1000)) : '---';
}
getNextSettlement(i: number) {
return this.subscriptions[i].status === 'active' ? this.datePipe.transform(new Date(this.subscriptions[i].current_period_end * 1000)) : '---';
}
getStatus(i: number) {
return this.subscriptions[i].status ? this.subscriptions[i].status : '';
}
// getLevel(i: number) {
// return this.subscriptions[i].metadata.plan;
// }
// getStartDate(i: number) {
// return this.datePipe.transform(new Date(this.subscriptions[i].start_date * 1000));
// }
// getEndDate(i: number) {
// return this.subscriptions[i].status === 'trialing' ? this.datePipe.transform(new Date(this.subscriptions[i].current_period_end * 1000)) : '---';
// }
// getNextSettlement(i: number) {
// return this.subscriptions[i].status === 'active' ? this.datePipe.transform(new Date(this.subscriptions[i].current_period_end * 1000)) : '---';
// }
// getStatus(i: number) {
// return this.subscriptions[i].status ? this.subscriptions[i].status : '';
// }
}

View File

@@ -86,7 +86,16 @@ export class AuthService {
return false;
}
}
private isEMailVerified(token: string): boolean {
try {
const payloadBase64 = token.split('.')[1];
const payloadJson = atob(payloadBase64.replace(/-/g, '+').replace(/_/g, '/'));
const payload = JSON.parse(payloadJson);
return payload.email_verified;
} catch (e) {
return false;
}
}
// Versucht, mit dem RefreshToken einen neuen Access Token zu erhalten
async refreshToken(): Promise<string | null> {
const storedRefreshToken = localStorage.getItem('refreshToken');
@@ -122,7 +131,9 @@ export class AuthService {
*/
async getToken(): Promise<string | null> {
const token = localStorage.getItem('authToken');
if (token && this.isTokenValid(token)) {
if (token && !this.isEMailVerified(token)) {
return null;
} else if (token && this.isTokenValid(token) && this.isEMailVerified(token)) {
return token;
} else {
return await this.refreshToken();

View File

@@ -1,7 +1,7 @@
export const hostname = window.location.hostname;
export const environment_base = {
// apiBaseUrl: 'http://localhost:3000',
apiBaseUrl: `http://${hostname}:3000`,
apiBaseUrl: `http://${hostname}:4200`,
imageBaseUrl: 'https://dev.bizmatch.net',
buildVersion: '<BUILD_VERSION>',
mailinfoUrl: 'https://dev.bizmatch.net',

View File

@@ -22,10 +22,11 @@
<meta name="twitter:card" content="summary_large_image" />
<base href="/" />
<link rel="icon" href="assets/cropped-Favicon-32x32.png" sizes="32x32" />
<!-- <link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet" /> -->
<!-- <link rel="icon" href="cropped-Favicon-192x192.png" sizes="192x192"> -->
<!-- <link rel="apple-touch-icon" href="cropped-Favicon-180x180.png"> -->
</head>
<body>
<body class="flex flex-col min-h-screen">
<app-root></app-root>
</body>
</html>

View File

@@ -56,6 +56,24 @@ textarea {
-moz-osx-font-smoothing: grayscale;
}
.wrapper {
min-height: 100%;
display: flex;
flex-direction: column;
}
header {
height: 64px; /* Feste Höhe */
}
main {
flex: 1 0 auto; /* Füllt den verfügbaren Platz */
}
footer {
flex-shrink: 0; /* Verhindert Schrumpfen */
}
*:focus,
.p-focus {
box-shadow: none !important;