From 4f1025fd75229e2adfbf37426661d4af35fdbfd8 Mon Sep 17 00:00:00 2001 From: Abdullah Z Date: Sun, 21 Sep 2025 12:27:39 +0300 Subject: [PATCH 1/9] ci(m-62): create draft ci workflow - update root .gitignore to ignore system files --- .github/workflows/ci.yml | 66 ++++++++++++++++++++++++++++++++++++++++ .gitignore | 1 + 2 files changed, 67 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..5830543 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,66 @@ +name: CI - Build Containers + +on: + push: + branches: ['main', 'dev'] + pull_request: + branches: ['main', 'dev'] + +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + validate-compose: + name: Validate docker-compose configuration + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Ensure .env for docker-compose + run: | + if [ -f .env ]; then + echo ".env already exists" + elif [ -f .env.example ]; then + cp .env.example .env + else + touch .env + fi + + - name: Validate docker-compose.yaml + run: docker compose -f docker-compose.yaml config -q + + build-images: + name: Build Docker images (no push) + needs: validate-compose + runs-on: ubuntu-latest + strategy: + # Realistically, in production, `fail-fast` should be true, + # but we want to see every issue during these early stages of implementing + fail-fast: false + matrix: + include: + - name: server + context: server + dockerfile: server/Dockerfile + - name: client + context: client + dockerfile: client/Dockerfile + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build ${{ matrix.name }} image + uses: docker/build-push-action@v6 + with: + context: ${{ matrix.context }} + file: ${{ matrix.dockerfile }} + platforms: linux/amd64 + push: false + tags: ghcr.io/${{ github.repository }}:${{ matrix.name }}-ci + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/.gitignore b/.gitignore index 0b36567..3d7fb73 100644 --- a/.gitignore +++ b/.gitignore @@ -6,5 +6,6 @@ *.env +# System files .DS_Store Thumbs.db From 1613715a0ab726adce56b7e62e78f70ceee7b61d Mon Sep 17 00:00:00 2001 From: Abdullah Z Date: Sun, 21 Sep 2025 13:45:54 +0300 Subject: [PATCH 2/9] chore(m-62): BROKEN add chromium headless for testing angular --- .github/workflows/ci.yml | 2 +- client/Dockerfile | 14 ++++++++++++ client/karma.conf.js | 47 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 client/karma.conf.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5830543..21743e8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,7 +32,7 @@ jobs: run: docker compose -f docker-compose.yaml config -q build-images: - name: Build Docker images (no push) + name: Build Docker images needs: validate-compose runs-on: ubuntu-latest strategy: diff --git a/client/Dockerfile b/client/Dockerfile index 5c100b0..42ee95c 100644 --- a/client/Dockerfile +++ b/client/Dockerfile @@ -2,6 +2,20 @@ FROM node:22.18-alpine3.22 WORKDIR /app +# Install Chrome for testing +RUN apk add --no-cache \ + chromium \ + nss \ + freetype \ + freetype-dev \ + harfbuzz \ + ca-certificates \ + ttf-freefont + +# Set Chrome binary path +ENV CHROME_BIN=/usr/bin/chromium-browser +ENV CHROME_PATH=/usr/bin/chromium-browser + RUN npm install -g pnpm COPY package.json pnpm-lock.yaml ./ diff --git a/client/karma.conf.js b/client/karma.conf.js new file mode 100644 index 0000000..f51c274 --- /dev/null +++ b/client/karma.conf.js @@ -0,0 +1,47 @@ +// Karma configuration file, see link for more information +// https://karma-runner.github.io/1.0/config/configuration-file.html + +module.exports = function (config) { + config.set({ + basePath: '', + frameworks: ['jasmine', '@angular-devkit/build-angular'], + plugins: [ + require('karma-jasmine'), + require('karma-chrome-launcher'), + require('karma-jasmine-html-reporter'), + require('karma-coverage'), + require('@angular-devkit/build-angular/plugins/karma'), + ], + client: { + jasmine: { + // you can add configuration options for Jasmine here + // the possible options are listed at https://jasmine.github.io/api/edge/Configuration.html + // for example, you can disable the random execution order + // random: false + }, + clearContext: false, // leave Jasmine Spec Runner output visible in browser + }, + jasmineHtmlReporter: { + suppressAll: true, // removes the duplicated traces + }, + coverageReporter: { + dir: require('path').join(__dirname, './coverage/source-client'), + subdir: '.', + reporters: [{ type: 'html' }, { type: 'text-summary' }], + }, + reporters: ['progress', 'kjhtml'], + browsers: ['ChromeHeadless'], + customLaunchers: { + ChromeHeadless: { + base: 'Chrome', + flags: [ + '--no-sandbox', + '--disable-web-security', + '--disable-gpu', + '--remote-debugging-port=9222', + ], + }, + }, + restartOnFileChange: true, + }) +} From b393d8ed359c9cf7a8f5e64002e0f2399b2c80c9 Mon Sep 17 00:00:00 2001 From: Abdullah Z Date: Mon, 22 Sep 2025 19:57:45 +0300 Subject: [PATCH 3/9] fix(backend): fix 401 bug when changing accounts - was happening because `auth()->attempt()` is required to log the user in, even on registration --- .../Http/Controllers/Common/AuthController.php | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/server/app/Http/Controllers/Common/AuthController.php b/server/app/Http/Controllers/Common/AuthController.php index 712514b..c459099 100644 --- a/server/app/Http/Controllers/Common/AuthController.php +++ b/server/app/Http/Controllers/Common/AuthController.php @@ -108,13 +108,11 @@ public function pluginLogout(Request $request): JsonResponse */ public function login(Request $request): JsonResponse { - $request->validate([ + $credentials = $request->validate([ 'email' => 'required|email', 'password' => 'required|string', ]); - $credentials = $request->only('email', 'password'); - if (!auth()->attempt($credentials)) { return $this->unauthorizedResponse('Invalid credentials'); } @@ -169,8 +167,7 @@ public function register(Request $request): JsonResponse 'password' => 'required|string|min:8|confirmed', 'avatar_url' => 'nullable|url', ]); - - $payload = []; + $credentials = $request->only(['email', 'password']); $user = User::create([ 'name' => $validated['name'], @@ -178,13 +175,20 @@ public function register(Request $request): JsonResponse 'password' => Hash::make($validated['password']), 'avatar_url' => $validated['avatar_url'] ?? null, ]); - $payload['user'] = $user; + + if (!auth()->attempt($credentials)) { + return $this->unauthorizedResponse('Invalid credentials'); + } + + $payload = []; + // $user = auth()->user(); if ($request->hasSession()) { $request->session()->regenerate(); } else { $payload['token'] = $user->createToken('auth_token')->plainTextToken; } + $payload['user'] = $user; return $this->responseJson($payload, 'Registration successful', 201); } @@ -205,6 +209,8 @@ public function logout(Request $request): JsonResponse { if ($request->hasSession()) { auth()->guard('web')->logout(); + $request->session()->invalidate(); + $request->session()->regenerateToken(); } else { $request->user()->currentAccessToken()?->delete(); From 76a4a67a72b5f98de0dd078e974f36e88979b605 Mon Sep 17 00:00:00 2001 From: Abdullah Z Date: Mon, 22 Sep 2025 20:34:05 +0300 Subject: [PATCH 4/9] fix(full): fix auth bugs & refactor frontend - remove most of FormData uses to cleaner JSON - move some API calling methods to repositories for cleaner API calls --- .../app/core/repositories/auth.repository.ts | 39 ++++++++++++ .../app/core/repositories/user.repository.ts | 12 ++-- client/src/app/core/services/auth.service.ts | 60 ++---------------- .../components/register-form/register-form.ts | 58 ++++++++++------- .../modules/auth/containers/login/login.html | 2 + .../modules/auth/containers/login/login.ts | 20 +++--- .../containers/dashboard/dashboard.html | 7 +-- .../containers/dashboard/dashboard.ts | 15 +++++ .../components/ai-chat-panel/ai-chat-panel.ts | 63 +++++++------------ .../brevo-template-selector.ts | 5 -- .../comments-panel/comments-panel.ts | 5 +- .../create-audit-dialog.ts | 20 +++--- .../repositories/ai-chat.respository.ts | 46 ++++++++++++++ .../shared/repositories/brevo.repository.ts | 5 +- .../app/shared/components/navbar/navbar.html | 2 +- .../app/shared/components/navbar/navbar.ts | 14 ++++- .../Controllers/Common/AuthController.php | 1 - 17 files changed, 208 insertions(+), 166 deletions(-) create mode 100644 client/src/app/core/repositories/auth.repository.ts create mode 100644 client/src/app/modules/dashboard/containers/projects/shared/repositories/ai-chat.respository.ts diff --git a/client/src/app/core/repositories/auth.repository.ts b/client/src/app/core/repositories/auth.repository.ts new file mode 100644 index 0000000..ef72e93 --- /dev/null +++ b/client/src/app/core/repositories/auth.repository.ts @@ -0,0 +1,39 @@ +import { HttpClient, HttpResponse } from '@angular/common/http' +import { inject, Injectable } from '@angular/core' +import { Observable } from 'rxjs' +import { LaravelApiResponse } from '~/shared/interfaces/laravel-api-response.interface' +import { SessionUser } from '~/shared/interfaces/session-user.interface' + +type MeResponse = LaravelApiResponse +type LoginResponse = LaravelApiResponse<{ + token: string + user: SessionUser +}> +type RegisterResponse = LoginResponse + +@Injectable({ providedIn: 'root' }) +export class AuthRepository { + protected http = inject(HttpClient) + + me(): Observable { + return this.http.get('/api/me') + } + + register(formValue: { + name: string + email: string + password: string + password_confirmation: string + avatar_url?: string + }): Observable> { + return this.http.post('/api/register', formValue, { observe: 'response' }) + } + + login(formValue: { email: string; password: string }): Observable> { + return this.http.post('/api/login', formValue, { observe: 'response' }) + } + + logout(): Observable> { + return this.http.post('/api/logout', null, { observe: 'response' }) + } +} diff --git a/client/src/app/core/repositories/user.repository.ts b/client/src/app/core/repositories/user.repository.ts index e0f95f8..984952d 100644 --- a/client/src/app/core/repositories/user.repository.ts +++ b/client/src/app/core/repositories/user.repository.ts @@ -12,15 +12,15 @@ export class UserRepository { protected http = inject(HttpClient) storeFigmaToken(token: string): Observable { - const formData = new FormData() - formData.append('figma_access_token', token) - return this.http.post('/api/profile/figma-token', formData) + return this.http.post('/api/profile/figma-token', { + figma_access_token: token, + }) } storeBrevoToken(token: string): Observable { - const formData = new FormData() - formData.append('brevo_api_token', token) - return this.http.post('/api/profile/brevo-token', formData) + return this.http.post('/api/profile/brevo-token', { + brevo_api_token: token, + }) } removeFigmaToken(): Observable { diff --git a/client/src/app/core/services/auth.service.ts b/client/src/app/core/services/auth.service.ts index ab1d318..c1167e0 100644 --- a/client/src/app/core/services/auth.service.ts +++ b/client/src/app/core/services/auth.service.ts @@ -1,26 +1,18 @@ -import { HttpClient, HttpErrorResponse } from '@angular/common/http' import { inject, Injectable, signal } from '@angular/core' import { Observable, of } from 'rxjs' import { catchError, map } from 'rxjs/operators' -import { LaravelApiResponse } from '~/shared/interfaces/laravel-api-response.interface' import { SessionUser } from '~/shared/interfaces/session-user.interface' - -type MeResponse = LaravelApiResponse -type LoginResponse = LaravelApiResponse<{ - token: string - user: SessionUser -}> -type RegisterResponse = LoginResponse +import { AuthRepository } from '~/core/repositories/auth.repository' @Injectable({ providedIn: 'root' }) export class AuthService { - protected http = inject(HttpClient) + protected authRepository = inject(AuthRepository) public user = signal(null) public isAuthenticated = signal(false) checkIfAuthenticated(): Observable { - return this.http.get('/api/me').pipe( + return this.authRepository.me().pipe( map(res => { this.user.set(res.payload) this.isAuthenticated.set(true) @@ -30,56 +22,12 @@ export class AuthService { ) } - register(formData: FormData): Observable<{ success: boolean; errorMessage: string | null }> { - return this.http - .post('/api/register', formData, { observe: 'response' }) - .pipe( - map(res => { - if (res.ok && res.body?.payload?.user) { - this.user.set(res.body.payload.user) - this.isAuthenticated.set(true) - return { success: true, errorMessage: null } - } - return { success: false, errorMessage: res.body?.message || 'Login failed.' } - }), - catchError((errResp: HttpErrorResponse) => { - const errorMessage = - (errResp.error?.message as string) || - (typeof errResp.error === 'string' ? errResp.error : null) || - 'Login failed. Please try again.' - - return of({ success: false, errorMessage }) - }), - ) - } - - login(formData: FormData): Observable<{ success: boolean; errorMessage: string | null }> { - return this.http.post('/api/login', formData, { observe: 'response' }).pipe( - map(res => { - if (res.ok && res.body?.payload?.user) { - this.user.set(res.body.payload.user) - this.isAuthenticated.set(true) - return { success: true, errorMessage: null } - } - return { success: false, errorMessage: res.body?.message || 'Login failed.' } - }), - catchError((errResp: HttpErrorResponse) => { - const errorMessage = - (errResp.error?.message as string) || - (typeof errResp.error === 'string' ? errResp.error : null) || - 'Login failed. Please try again.' - - return of({ success: false, errorMessage }) - }), - ) - } - getUser() { return this.user.asReadonly() } logout(): Observable { - return this.http.post('/api/logout', null, { observe: 'response' }).pipe( + return this.authRepository.logout().pipe( map(res => { if (res.ok) { this.user.set(null) diff --git a/client/src/app/modules/auth/components/register-form/register-form.ts b/client/src/app/modules/auth/components/register-form/register-form.ts index 5bea3d6..2b42b75 100644 --- a/client/src/app/modules/auth/components/register-form/register-form.ts +++ b/client/src/app/modules/auth/components/register-form/register-form.ts @@ -8,10 +8,13 @@ import { } from '@angular/forms' import { Router, RouterLink } from '@angular/router' import { HttpErrorResponse } from '@angular/common/http' +import { catchError } from 'rxjs/operators' +import { of } from 'rxjs' import { Message } from 'primeng/message' import { Button } from 'primeng/button' import { InputText } from 'primeng/inputtext' import { AuthService } from '~/core/services/auth.service' +import { AuthRepository } from '~/core/repositories/auth.repository' @Component({ selector: 'app-register-form', @@ -22,6 +25,7 @@ import { AuthService } from '~/core/services/auth.service' export class RegisterForm { protected fb = inject(NonNullableFormBuilder) protected authService = inject(AuthService) + protected authRepository = inject(AuthRepository) protected router = inject(Router) protected isLoading = signal(false) @@ -58,32 +62,38 @@ export class RegisterForm { this.isLoading.set(true) this.errorMessage.set(null) - const formData = new FormData() - formData.append('name', this.registerFb.get('name')?.value || '') - formData.append('email', this.registerFb.get('email')?.value || '') - formData.append('password', this.registerFb.get('password')?.value || '') - formData.append( - 'password_confirmation', - this.registerFb.get('password_confirmation')?.value || '', - ) + const formValue = this.registerFb.getRawValue() - this.authService.register(formData).subscribe({ - next: ({ success, errorMessage }) => { + this.authRepository + .register(formValue) + .pipe( + catchError((err: HttpErrorResponse) => { + const errMsg = + (err.error?.message as string) || + (typeof err.error === 'string' ? err.error : null) || + 'Registration failed. Please try again.' + + this.errorMessage.set(errMsg) + this.isLoading.set(false) + return of(null) + }), + ) + .subscribe(async resp => { this.isLoading.set(false) - if (success) { - this.router.navigate(['/dashboard']) - } else { - this.errorMessage.set(errorMessage || 'Registration failed. Please try again.') + + if (!resp) { + return } - }, - error: (err: HttpErrorResponse) => { - this.isLoading.set(false) - const errMsg = - (err.error?.message as string) || - (typeof err.error === 'string' ? err.error : null) || - 'Registration failed. Please try again.' - this.errorMessage.set(errMsg) - }, - }) + + if (!(resp.ok && resp.body?.payload?.user)) { + const message = resp.body?.message || 'Registration failed. Please try again.' + this.errorMessage.set(message) + return + } + + this.authService.user.set(resp.body.payload.user) + this.authService.isAuthenticated.set(true) + await this.router.navigate(['/dashboard']) + }) } } diff --git a/client/src/app/modules/auth/containers/login/login.html b/client/src/app/modules/auth/containers/login/login.html index 0bf9990..fd63faf 100644 --- a/client/src/app/modules/auth/containers/login/login.html +++ b/client/src/app/modules/auth/containers/login/login.html @@ -18,6 +18,7 @@

Log in to your ac pInputText id="email" formControlName="email" + autocomplete="email" type="email" name="email" class="w-full" @@ -38,6 +39,7 @@

Log in to your ac pInputText id="password" formControlName="password" + autocomplete="current-password" type="password" name="password" class="w-full" diff --git a/client/src/app/modules/auth/containers/login/login.ts b/client/src/app/modules/auth/containers/login/login.ts index 0e8730b..537163a 100644 --- a/client/src/app/modules/auth/containers/login/login.ts +++ b/client/src/app/modules/auth/containers/login/login.ts @@ -8,6 +8,7 @@ import { AuthService } from '~/core/services/auth.service' import { Logo } from '~/shared/components/logo/logo' import { InputText } from 'primeng/inputtext' import { Button } from 'primeng/button' +import { AuthRepository } from '~/core/repositories/auth.repository' @Component({ selector: 'app-login', @@ -18,6 +19,7 @@ import { Button } from 'primeng/button' export class Login implements OnInit { protected fb = inject(NonNullableFormBuilder) protected authService = inject(AuthService) + protected authRepository = inject(AuthRepository) protected router = inject(Router) protected isLoading = signal(false) @@ -40,17 +42,19 @@ export class Login implements OnInit { this.isLoading.set(true) this.errorMessage.set(null) - const formData = new FormData() - formData.append('email', this.loginForm.get('email')?.value || '') - formData.append('password', this.loginForm.get('password')?.value || '') + const formValue = this.loginForm.getRawValue() - this.authService.login(formData).subscribe({ - next: ({ success, errorMessage }) => { + this.authRepository.login(formValue).subscribe({ + next: async resp => { this.isLoading.set(false) - if (success) { - this.router.navigate(['/dashboard']) + if (resp.ok && resp.body?.payload?.user) { + this.authService.user.set(resp.body.payload.user) + this.authService.isAuthenticated.set(true) + + await this.router.navigate(['/dashboard']) } else { - this.errorMessage.set(errorMessage || 'Login failed. Please try again.') + const message = resp.body?.message || 'Login failed. Please try again.' + this.errorMessage.set(message) } }, error: (err: HttpErrorResponse) => { diff --git a/client/src/app/modules/dashboard/containers/dashboard/dashboard.html b/client/src/app/modules/dashboard/containers/dashboard/dashboard.html index 51b375a..4d36b3c 100644 --- a/client/src/app/modules/dashboard/containers/dashboard/dashboard.html +++ b/client/src/app/modules/dashboard/containers/dashboard/dashboard.html @@ -42,14 +42,13 @@ Back to projects - Log out - + diff --git a/client/src/app/modules/dashboard/containers/dashboard/dashboard.ts b/client/src/app/modules/dashboard/containers/dashboard/dashboard.ts index 388dd25..ca91535 100644 --- a/client/src/app/modules/dashboard/containers/dashboard/dashboard.ts +++ b/client/src/app/modules/dashboard/containers/dashboard/dashboard.ts @@ -47,4 +47,19 @@ export class Dashboard implements OnInit { .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe() } + + logoutAndGoHome() { + this.authService + .logout() + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe({ + next: async () => { + await this.router.navigate(['/']) + }, + error: err => { + alert('something wrong happened') + console.log(err) + }, + }) + } } diff --git a/client/src/app/modules/dashboard/containers/projects/components/ai-chat-panel/ai-chat-panel.ts b/client/src/app/modules/dashboard/containers/projects/components/ai-chat-panel/ai-chat-panel.ts index 1f46cca..04770bc 100644 --- a/client/src/app/modules/dashboard/containers/projects/components/ai-chat-panel/ai-chat-panel.ts +++ b/client/src/app/modules/dashboard/containers/projects/components/ai-chat-panel/ai-chat-panel.ts @@ -8,7 +8,7 @@ import { DestroyRef, } from '@angular/core' import { FormsModule } from '@angular/forms' -import { HttpClient } from '@angular/common/http' +import { HttpClient, HttpErrorResponse } from '@angular/common/http' import { catchError, of } from 'rxjs' import { Button } from 'primeng/button' import { InputText } from 'primeng/inputtext' @@ -20,6 +20,7 @@ import { AiChatMessageData } from '../../shared/interfaces/ai-chat-message-data. import { EmptyState } from '~/shared/components/empty-state/empty-state' import { takeUntilDestroyed } from '@angular/core/rxjs-interop' import { MessageService } from '~/core/services/message.service' +import { AiChatRepository } from '../../shared/repositories/ai-chat.respository' @Component({ selector: 'app-ai-chat-panel', @@ -29,8 +30,9 @@ import { MessageService } from '~/core/services/message.service' host: { class: 'h-full' }, }) export class AiChatPanel implements OnInit { - http = inject(HttpClient) - message = inject(MessageService) + protected http = inject(HttpClient) + protected message = inject(MessageService) + protected aiChatRepository = inject(AiChatRepository) destroyRef = inject(DestroyRef) emailTemplateId = input() @@ -49,7 +51,7 @@ export class AiChatPanel implements OnInit { return this.emailTemplateId() ?? this.screenId() ?? 0 } - get chatType(): string { + get chatType() { return this.emailTemplateId() ? 'email-templates' : 'screens' } @@ -57,14 +59,11 @@ export class AiChatPanel implements OnInit { if (!this.chatId) return this.isLoading.set(true) - this.http - .get>( - `/api/${this.chatType}/${encodeURIComponent(this.chatId)}/chats`, - ) + this.aiChatRepository + .getChatMessages(this.chatType, this.chatId) .pipe( takeUntilDestroyed(this.destroyRef), - catchError(err => { - console.warn('Failed to load chat messages:', err) + catchError((err: HttpErrorResponse) => { this.message.error( 'Error', `Failed to load chat. ${err.error?.message || err.message}`, @@ -84,10 +83,10 @@ export class AiChatPanel implements OnInit { return } - // Add user message immediately + // Add user message immediately (optimistic ui) const userMessage: AiChatMessageData = { id: Date.now(), - user_id: 1, + user_id: 1, // placeholder content: messageContent, sender: 'user', created_at: new Date().toISOString(), @@ -98,36 +97,18 @@ export class AiChatPanel implements OnInit { this.newMessage.set('') this.isWaitingForAiResponse.set(true) - const formData = new FormData() - formData.set('content', messageContent) - - // For email templates, set update_template to true to enable AI template updates - if (this.chatType === 'email-templates') { - formData.set('update_template', '1') - } - - this.http - .post< - LaravelApiResponse<{ - user: AiChatMessageData - ai: { content: string } - template_updated?: boolean - }> - >(`/api/${this.chatType}/${encodeURIComponent(this.chatId)}/chats`, formData) + this.aiChatRepository + .sendMessage(this.chatType, this.chatId, { + content: messageContent, + update_template: this.chatType === 'email-templates', + }) .pipe( - catchError(err => { - console.warn('Failed to send message:', err) + catchError((err: HttpErrorResponse) => { this.message.error( 'Error', `Failed to send message. ${err.error?.message || err.message}`, ) - return of< - LaravelApiResponse<{ - user: AiChatMessageData - ai: { content: string } - template_updated?: boolean - }> - >({ + return of>({ message: '', payload: null, }) @@ -137,12 +118,12 @@ export class AiChatPanel implements OnInit { if (response.payload) { // Add AI message const aiMessage: AiChatMessageData = { - id: Date.now() + 1, + id: response.payload.ai.id, user_id: null, - content: response.payload!.ai.content, + content: response.payload.ai.content, sender: 'ai', - created_at: new Date().toISOString(), - updated_at: new Date().toISOString(), + created_at: response.payload.ai.created_at, + updated_at: response.payload.ai.updated_at, } this.messages.update(messages => [...messages, aiMessage]) diff --git a/client/src/app/modules/dashboard/containers/projects/components/brevo-template-selector/brevo-template-selector.ts b/client/src/app/modules/dashboard/containers/projects/components/brevo-template-selector/brevo-template-selector.ts index 3f60a2a..dae5928 100644 --- a/client/src/app/modules/dashboard/containers/projects/components/brevo-template-selector/brevo-template-selector.ts +++ b/client/src/app/modules/dashboard/containers/projects/components/brevo-template-selector/brevo-template-selector.ts @@ -158,7 +158,6 @@ export class BrevoTemplateSelector { .pipe(takeUntilDestroyed(this.destroyRef)) .subscribe({ next: response => { - this.message.success('Success', 'Successfully imported template!') this.templatesImported.emit([response.payload!]) this.onVisibleChange(false) }, @@ -186,10 +185,6 @@ export class BrevoTemplateSelector { }), ) .subscribe(response => { - this.message.success( - 'Success', - `Successfully imported ${response.payload?.length ?? 0} templates!`, - ) this.templatesImported.emit(response.payload ?? []) this.onVisibleChange(false) }) diff --git a/client/src/app/modules/dashboard/containers/projects/components/comments-panel/comments-panel.ts b/client/src/app/modules/dashboard/containers/projects/components/comments-panel/comments-panel.ts index 0da75a1..e420a34 100644 --- a/client/src/app/modules/dashboard/containers/projects/components/comments-panel/comments-panel.ts +++ b/client/src/app/modules/dashboard/containers/projects/components/comments-panel/comments-panel.ts @@ -63,7 +63,6 @@ export class CommentsPanel implements OnInit { .pipe( takeUntilDestroyed(this.destroyRef), catchError(err => { - console.warn('Failed to load comments:', err) this.message.error( 'Error', `Failed to load comments. ${err.error?.message || err.message}`, @@ -84,13 +83,11 @@ export class CommentsPanel implements OnInit { } this.isSubmitting.set(true) - const formData = new FormData() - formData.set('content', commentContent) this.http .post>( `/api/${this.commentType}/${this.commentId}/comments`, - formData, + { content: commentContent }, ) .pipe( takeUntilDestroyed(this.destroyRef), diff --git a/client/src/app/modules/dashboard/containers/projects/containers/audits/components/create-audit-dialog/create-audit-dialog.ts b/client/src/app/modules/dashboard/containers/projects/containers/audits/components/create-audit-dialog/create-audit-dialog.ts index c631873..bbe66c4 100644 --- a/client/src/app/modules/dashboard/containers/projects/containers/audits/components/create-audit-dialog/create-audit-dialog.ts +++ b/client/src/app/modules/dashboard/containers/projects/containers/audits/components/create-audit-dialog/create-audit-dialog.ts @@ -9,7 +9,7 @@ import { signal, DestroyRef, } from '@angular/core' -import { FormBuilder, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms' +import { NonNullableFormBuilder, ReactiveFormsModule, Validators } from '@angular/forms' import { Dialog } from 'primeng/dialog' import { Button } from 'primeng/button' import { InputText } from 'primeng/inputtext' @@ -23,7 +23,6 @@ import { AuditRepository } from '~/modules/dashboard/containers/projects/contain import { ScreenData } from '~/modules/dashboard/containers/projects/shared/interfaces/screen.interface' import { LaravelApiResponse } from '~/shared/interfaces/laravel-api-response.interface' import { takeUntilDestroyed } from '@angular/core/rxjs-interop' -import { CreateAuditRequest } from '../../shared/interfaces/audit.interface' @Component({ selector: 'app-create-audit-dialog', @@ -34,7 +33,7 @@ import { CreateAuditRequest } from '../../shared/interfaces/audit.interface' }) export class CreateAuditDialog implements OnInit { private auditRepository = inject(AuditRepository) - private fb = inject(FormBuilder) + private fb = inject(NonNullableFormBuilder) private message = inject(MessageService) private http = inject(HttpClient) destroyRef = inject(DestroyRef) @@ -49,10 +48,13 @@ export class CreateAuditDialog implements OnInit { private isLoading = signal(false) private isSubmitting = signal(false) - auditForm: FormGroup = this.fb.group({ - name: ['', [Validators.required, Validators.maxLength(255)]], - description: [''], - screen_ids: [[], [Validators.required, Validators.minLength(2), Validators.maxLength(7)]], + auditForm = this.fb.group({ + name: this.fb.control('', [Validators.required, Validators.maxLength(255)]), + description: this.fb.control(''), + screen_ids: this.fb.control( + [], + [Validators.required, Validators.minLength(2), Validators.maxLength(7)], + ), }) screensList = this.screens.asReadonly() @@ -114,10 +116,10 @@ export class CreateAuditDialog implements OnInit { } this.isSubmitting.set(true) - const formData = this.auditForm.value as CreateAuditRequest + const formValue = this.auditForm.getRawValue() this.auditRepository - .createAudit(this.projectId(), formData) + .createAudit(this.projectId(), formValue) .pipe( takeUntilDestroyed(this.destroyRef), catchError(err => { diff --git a/client/src/app/modules/dashboard/containers/projects/shared/repositories/ai-chat.respository.ts b/client/src/app/modules/dashboard/containers/projects/shared/repositories/ai-chat.respository.ts new file mode 100644 index 0000000..f9263a2 --- /dev/null +++ b/client/src/app/modules/dashboard/containers/projects/shared/repositories/ai-chat.respository.ts @@ -0,0 +1,46 @@ +import { HttpClient } from '@angular/common/http' +import { inject, Injectable } from '@angular/core' +import { Observable } from 'rxjs' +import { AiChatMessageData } from '../interfaces/ai-chat-message-data.interface' +import { LaravelApiResponse } from '~/shared/interfaces/laravel-api-response.interface' + +export interface SendMessageResponse { + user: AiChatMessageData + ai: AiChatMessageData + template_updated?: boolean +} + +const e = encodeURIComponent + +interface SendMessageBody { + content: string + update_template: boolean +} + +@Injectable({ providedIn: 'root' }) +export class AiChatRepository { + protected http = inject(HttpClient) + + getChatMessages( + chatType: 'email-templates' | 'screens', + chatId: number, + ): Observable> { + return this.http.get>( + `/api/${e(chatType)}/${e(chatId)}/chats`, + ) + } + + sendMessage( + chatType: 'email-templates' | 'screens', + chatId: number, + { content, update_template = false }: SendMessageBody, + ): Observable> { + return this.http.post>( + `/api/${e(chatType)}/${e(chatId)}/chats`, + { + content, + update_template, + }, + ) + } +} diff --git a/client/src/app/modules/dashboard/containers/projects/shared/repositories/brevo.repository.ts b/client/src/app/modules/dashboard/containers/projects/shared/repositories/brevo.repository.ts index 1bd3fee..5301df6 100644 --- a/client/src/app/modules/dashboard/containers/projects/shared/repositories/brevo.repository.ts +++ b/client/src/app/modules/dashboard/containers/projects/shared/repositories/brevo.repository.ts @@ -26,12 +26,9 @@ export class BrevoRepository { projectId: string, brevoTemplateId: string, ): Observable { - const formData = new FormData() - formData.append('brevo_template_id', brevoTemplateId) - return this.http.post( `/api/projects/${projectId}/email-templates/import-brevo`, - formData, + { brevo_template_id: brevoTemplateId }, ) } diff --git a/client/src/app/shared/components/navbar/navbar.html b/client/src/app/shared/components/navbar/navbar.html index 2735109..8d403d9 100644 --- a/client/src/app/shared/components/navbar/navbar.html +++ b/client/src/app/shared/components/navbar/navbar.html @@ -13,7 +13,7 @@ Dashboard

- - diff --git a/client/src/app/modules/dashboard/containers/projects/components/release-card/release-card.ts b/client/src/app/modules/dashboard/containers/projects/components/release-card/release-card.ts index 3822dfb..6ca3eb0 100644 --- a/client/src/app/modules/dashboard/containers/projects/components/release-card/release-card.ts +++ b/client/src/app/modules/dashboard/containers/projects/components/release-card/release-card.ts @@ -11,7 +11,6 @@ import { Chip } from 'primeng/chip' import { Button } from 'primeng/button' import { Menu } from 'primeng/menu' import { MenuItem } from 'primeng/api' -import { ConfirmDialog } from 'primeng/confirmdialog' import { ConfirmationService } from 'primeng/api' import { ReleaseData } from '../../shared/interfaces/release.interface' import { DatePipe, TitleCasePipe } from '@angular/common' @@ -22,7 +21,7 @@ import { catchError, of } from 'rxjs' @Component({ selector: 'app-release-card', - imports: [Chip, DatePipe, TitleCasePipe, Button, Menu, ConfirmDialog], + imports: [Chip, DatePipe, TitleCasePipe, Button, Menu], templateUrl: './release-card.html', changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'block' }, @@ -82,6 +81,12 @@ export class ReleaseCard { message: 'Are you sure you want to delete this release? This action cannot be undone.', header: 'Confirm Delete', icon: 'pi pi-exclamation-triangle', + rejectButtonProps: { + outlined: true, + severity: 'secondary', + label: 'Cancel', + }, + acceptButtonProps: { severity: 'danger', label: 'Delete' }, accept: () => this.deleteRelease(), }) } diff --git a/client/src/app/modules/dashboard/containers/projects/containers/audits/components/audit-card/audit-card.html b/client/src/app/modules/dashboard/containers/projects/containers/audits/components/audit-card/audit-card.html index c79a08e..d02c68b 100644 --- a/client/src/app/modules/dashboard/containers/projects/containers/audits/components/audit-card/audit-card.html +++ b/client/src/app/modules/dashboard/containers/projects/containers/audits/components/audit-card/audit-card.html @@ -59,5 +59,3 @@

{{ audit().name }}

} - - diff --git a/client/src/app/modules/dashboard/containers/projects/containers/audits/components/audit-card/audit-card.ts b/client/src/app/modules/dashboard/containers/projects/containers/audits/components/audit-card/audit-card.ts index 5ec4d7b..28b8ef8 100644 --- a/client/src/app/modules/dashboard/containers/projects/containers/audits/components/audit-card/audit-card.ts +++ b/client/src/app/modules/dashboard/containers/projects/containers/audits/components/audit-card/audit-card.ts @@ -12,7 +12,6 @@ import { Button } from 'primeng/button' import { Tag } from 'primeng/tag' import { Menu } from 'primeng/menu' import { MenuItem } from 'primeng/api' -import { ConfirmDialog } from 'primeng/confirmdialog' import { ProgressSpinner } from 'primeng/progressspinner' import { DatePipe } from '@angular/common' import { ConfirmationService } from 'primeng/api' @@ -24,8 +23,7 @@ import { AuditData } from '../../shared/interfaces/audit.interface' @Component({ selector: 'app-audit-card', - imports: [Button, Tag, Menu, ConfirmDialog, ProgressSpinner, DatePipe], - providers: [ConfirmationService], + imports: [Button, Tag, Menu, ProgressSpinner, DatePipe], templateUrl: './audit-card.html', changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'block' }, @@ -130,6 +128,12 @@ export class AuditCard { message: 'Are you sure you want to delete this audit? This action cannot be undone.', header: 'Confirm Delete', icon: 'pi pi-exclamation-triangle', + rejectButtonProps: { + outlined: true, + severity: 'secondary', + label: 'Cancel', + }, + acceptButtonProps: { severity: 'danger', label: 'Delete' }, accept: () => this.deleteAudit(), }) } diff --git a/client/src/app/modules/dashboard/containers/projects/containers/audits/components/create-audit-dialog/create-audit-dialog.html b/client/src/app/modules/dashboard/containers/projects/containers/audits/components/create-audit-dialog/create-audit-dialog.html index 60e58af..000e3ae 100644 --- a/client/src/app/modules/dashboard/containers/projects/containers/audits/components/create-audit-dialog/create-audit-dialog.html +++ b/client/src/app/modules/dashboard/containers/projects/containers/audits/components/create-audit-dialog/create-audit-dialog.html @@ -48,7 +48,7 @@

Create New Audit

id="description" name="description" placeholder="Describe what this audit will analyze..." - [rows]="3" + [rows]="7" class="resize-none" fluid > diff --git a/client/src/app/modules/dashboard/containers/projects/containers/audits/containers/audits/audits.html b/client/src/app/modules/dashboard/containers/projects/containers/audits/containers/audits/audits.html index b783a92..5570a30 100644 --- a/client/src/app/modules/dashboard/containers/projects/containers/audits/containers/audits/audits.html +++ b/client/src/app/modules/dashboard/containers/projects/containers/audits/containers/audits/audits.html @@ -51,3 +51,4 @@

Audits

/> + diff --git a/client/src/app/modules/dashboard/containers/projects/containers/audits/containers/audits/audits.ts b/client/src/app/modules/dashboard/containers/projects/containers/audits/containers/audits/audits.ts index 1ca7e14..f20493f 100644 --- a/client/src/app/modules/dashboard/containers/projects/containers/audits/containers/audits/audits.ts +++ b/client/src/app/modules/dashboard/containers/projects/containers/audits/containers/audits/audits.ts @@ -21,10 +21,20 @@ import { MessageService } from '~/core/services/message.service' import { AuditCard } from '../../components/audit-card/audit-card' import { CreateAuditDialog } from '../../components/create-audit-dialog/create-audit-dialog' import { AuditData } from '../../shared/interfaces/audit.interface' +import { ConfirmDialog } from 'primeng/confirmdialog' @Component({ selector: 'app-audits', - imports: [Button, ProgressSpinner, Toast, AuditCard, CreateAuditDialog, EmptyState, NgClass], + imports: [ + Button, + ProgressSpinner, + Toast, + AuditCard, + CreateAuditDialog, + EmptyState, + NgClass, + ConfirmDialog, + ], templateUrl: './audits.html', changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'flex flex-1 flex-col' }, diff --git a/client/src/app/modules/dashboard/containers/projects/containers/releases/releases.html b/client/src/app/modules/dashboard/containers/projects/containers/releases/releases.html index 5be4e16..99abe76 100644 --- a/client/src/app/modules/dashboard/containers/projects/containers/releases/releases.html +++ b/client/src/app/modules/dashboard/containers/projects/containers/releases/releases.html @@ -52,3 +52,4 @@ /> + diff --git a/client/src/app/modules/dashboard/containers/projects/containers/releases/releases.ts b/client/src/app/modules/dashboard/containers/projects/containers/releases/releases.ts index 07a4092..dd81a0d 100644 --- a/client/src/app/modules/dashboard/containers/projects/containers/releases/releases.ts +++ b/client/src/app/modules/dashboard/containers/projects/containers/releases/releases.ts @@ -20,10 +20,19 @@ import { ReleaseData } from '../../shared/interfaces/release.interface' import { ReleaseRepository } from '../../shared/repositories/release.repository' import { MessageService } from '~/core/services/message.service' import { EmptyState } from '~/shared/components/empty-state/empty-state' +import { ConfirmDialog } from 'primeng/confirmdialog' @Component({ selector: 'app-releases', - imports: [Button, ReleaseCard, NewReleaseDialog, Toast, EmptyState, ProgressSpinner], + imports: [ + Button, + ReleaseCard, + NewReleaseDialog, + Toast, + EmptyState, + ProgressSpinner, + ConfirmDialog, + ], providers: [ConfirmationService], templateUrl: './releases.html', changeDetection: ChangeDetectionStrategy.OnPush, From 66193e1ee0455055cae32d6c4d2cdc9677346872 Mon Sep 17 00:00:00 2001 From: Abdullah Z Date: Mon, 22 Sep 2025 21:37:55 +0300 Subject: [PATCH 7/9] feat(frontend): create basic project settings page --- .../dashboard/containers/account/account.ts | 1 - .../projects/containers/releases/releases.ts | 2 - .../containers/settings/settings.html | 36 ++++++- .../projects/containers/settings/settings.ts | 95 +++++++++++++++++-- .../shared/repositories/project.repository.ts | 16 ++++ 5 files changed, 140 insertions(+), 10 deletions(-) diff --git a/client/src/app/modules/dashboard/containers/account/account.ts b/client/src/app/modules/dashboard/containers/account/account.ts index 052c7b6..0c30539 100644 --- a/client/src/app/modules/dashboard/containers/account/account.ts +++ b/client/src/app/modules/dashboard/containers/account/account.ts @@ -14,7 +14,6 @@ import { MessageService } from '~/core/services/message.service' @Component({ selector: 'app-account', imports: [ReactiveFormsModule, Card, InputText, Button, Message, Toast, ConfirmPopup], - providers: [ConfirmationService], templateUrl: './account.html', changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'grow' }, diff --git a/client/src/app/modules/dashboard/containers/projects/containers/releases/releases.ts b/client/src/app/modules/dashboard/containers/projects/containers/releases/releases.ts index dd81a0d..2e743a3 100644 --- a/client/src/app/modules/dashboard/containers/projects/containers/releases/releases.ts +++ b/client/src/app/modules/dashboard/containers/projects/containers/releases/releases.ts @@ -13,7 +13,6 @@ import { catchError, of } from 'rxjs' import { ProgressSpinner } from 'primeng/progressspinner' import { Button } from 'primeng/button' import { Toast } from 'primeng/toast' -import { ConfirmationService } from 'primeng/api' import { ReleaseCard } from '../../components/release-card/release-card' import { NewReleaseDialog } from '../../components/new-release-dialog/new-release-dialog' import { ReleaseData } from '../../shared/interfaces/release.interface' @@ -33,7 +32,6 @@ import { ConfirmDialog } from 'primeng/confirmdialog' ProgressSpinner, ConfirmDialog, ], - providers: [ConfirmationService], templateUrl: './releases.html', changeDetection: ChangeDetectionStrategy.OnPush, }) diff --git a/client/src/app/modules/dashboard/containers/projects/containers/settings/settings.html b/client/src/app/modules/dashboard/containers/projects/containers/settings/settings.html index 4ab2a41..b1a3798 100644 --- a/client/src/app/modules/dashboard/containers/projects/containers/settings/settings.html +++ b/client/src/app/modules/dashboard/containers/projects/containers/settings/settings.html @@ -1 +1,35 @@ -

settings works!

+
+
+ + +
+ +
+ + +
+ + + + + diff --git a/client/src/app/modules/dashboard/containers/projects/containers/settings/settings.ts b/client/src/app/modules/dashboard/containers/projects/containers/settings/settings.ts index a2d46fc..9f2da55 100644 --- a/client/src/app/modules/dashboard/containers/projects/containers/settings/settings.ts +++ b/client/src/app/modules/dashboard/containers/projects/containers/settings/settings.ts @@ -1,11 +1,94 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { + ChangeDetectionStrategy, + Component, + DestroyRef, + inject, + OnInit, + signal, +} from '@angular/core' +import { takeUntilDestroyed } from '@angular/core/rxjs-interop' +import { HttpErrorResponse } from '@angular/common/http' +import { Button } from 'primeng/button' +import { InputText } from 'primeng/inputtext' +import { ReactiveFormsModule, Validators, NonNullableFormBuilder } from '@angular/forms' +import { ActivatedRoute } from '@angular/router' +import { ProjectRepository } from '../../shared/repositories/project.repository' +import { ProjectData } from '../../shared/interfaces/project-data.interface' +import { LaravelApiResponse } from '~/shared/interfaces/laravel-api-response.interface' +import { MessageService } from '~/core/services/message.service' +import { Textarea } from 'primeng/textarea' +import { Toast } from 'primeng/toast' @Component({ - selector: 'app-settings', - imports: [], - templateUrl: './settings.html', - changeDetection: ChangeDetectionStrategy.OnPush + selector: 'app-settings', + imports: [ReactiveFormsModule, InputText, Button, Textarea, Toast], + templateUrl: './settings.html', + changeDetection: ChangeDetectionStrategy.OnPush, }) -export class Settings { +export class Settings implements OnInit { + private fb = inject(NonNullableFormBuilder) + private route = inject(ActivatedRoute) + private projectRepository = inject(ProjectRepository) + private message = inject(MessageService) + destroyRef = inject(DestroyRef) + isSubmitting = signal(false) + + projectId = this.route.parent!.snapshot.paramMap.get('projectId')! + + basicInfoForm = this.fb.group({ + name: ['', Validators.required], + description: [''], + }) + + ngOnInit() { + this.projectRepository + .getProject(this.projectId) + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe({ + next: (response: LaravelApiResponse) => { + this.basicInfoForm.patchValue({ + name: response.payload!.name, + description: response.payload!.description ?? '', + }) + }, + error: (err: HttpErrorResponse) => { + this.message.error( + 'Error', + `Failed to load project details. ${err.error?.message || err.message}`, + ) + }, + }) + } + + saveBasicInfo(): void { + if (this.basicInfoForm.invalid) { + return + } + + this.isSubmitting.set(true) + this.basicInfoForm.disable() + + const formValue = this.basicInfoForm.getRawValue() + + this.projectRepository + .updateProject(this.projectId, formValue) + .pipe(takeUntilDestroyed(this.destroyRef)) + .subscribe({ + next: () => { + this.message.success('Success', 'Project updated successfully!') + + this.isSubmitting.set(false) + this.basicInfoForm.enable() + }, + error: (err: HttpErrorResponse) => { + this.message.error( + 'Error', + `Failed to update project. ${err.error?.message || err.message}`, + ) + this.isSubmitting.set(false) + this.basicInfoForm.enable() + }, + }) + } } diff --git a/client/src/app/modules/dashboard/containers/projects/shared/repositories/project.repository.ts b/client/src/app/modules/dashboard/containers/projects/shared/repositories/project.repository.ts index 2f3a1ea..fed1b37 100644 --- a/client/src/app/modules/dashboard/containers/projects/shared/repositories/project.repository.ts +++ b/client/src/app/modules/dashboard/containers/projects/shared/repositories/project.repository.ts @@ -4,6 +4,8 @@ import { Observable } from 'rxjs' import { LaravelApiResponse } from '~/shared/interfaces/laravel-api-response.interface' import { ProjectData } from '../interfaces/project-data.interface' +const e = encodeURIComponent + @Injectable({ providedIn: 'root' }) export class ProjectRepository { protected http = inject(HttpClient) @@ -12,10 +14,24 @@ export class ProjectRepository { return this.http.get>('/api/projects') } + getProject(projectId: string): Observable> { + return this.http.get>(`/api/projects/${e(projectId)}`) + } + createProject(formValue: { name: string description?: string }): Observable> { return this.http.post>('/api/projects', formValue) } + + updateProject( + projectId: string, + formValue: { name: string; description: string }, + ): Observable> { + return this.http.put>( + `/api/projects/${e(projectId)}`, + formValue, + ) + } } From d8e757cc03b565bf6fac78653fdeef6dcc00709a Mon Sep 17 00:00:00 2001 From: Abdullah Z Date: Mon, 22 Sep 2025 21:48:59 +0300 Subject: [PATCH 8/9] refactor(frontend): move toast component up the tree --- .../modules/dashboard/containers/account/account.html | 6 +++--- .../dashboard/containers/dashboard/dashboard.ts | 11 ++++++++--- .../components/ai-chat-panel/ai-chat-panel.html | 2 -- .../components/ai-chat-panel/ai-chat-panel.ts | 3 +-- .../components/comments-panel/comments-panel.ts | 1 - .../projects/components/release-card/release-card.ts | 1 - .../containers/audits/containers/audits/audits.html | 1 - .../containers/audits/containers/audits/audits.ts | 10 +++++----- .../containers/email-templates/email-templates.html | 2 -- .../containers/email-templates/email-templates.ts | 2 -- .../containers/project-layout/project-layout.html | 2 ++ .../containers/project-layout/project-layout.ts | 3 ++- .../projects/containers/releases/releases.html | 1 - .../projects/containers/releases/releases.ts | 11 +---------- .../projects/containers/settings/settings.html | 2 -- 15 files changed, 22 insertions(+), 36 deletions(-) diff --git a/client/src/app/modules/dashboard/containers/account/account.html b/client/src/app/modules/dashboard/containers/account/account.html index 7ec56d6..d83a3e8 100644 --- a/client/src/app/modules/dashboard/containers/account/account.html +++ b/client/src/app/modules/dashboard/containers/account/account.html @@ -1,6 +1,3 @@ - - -

Account Settings

@@ -146,3 +143,6 @@

+ + + diff --git a/client/src/app/modules/dashboard/containers/dashboard/dashboard.ts b/client/src/app/modules/dashboard/containers/dashboard/dashboard.ts index ca91535..10d8b03 100644 --- a/client/src/app/modules/dashboard/containers/dashboard/dashboard.ts +++ b/client/src/app/modules/dashboard/containers/dashboard/dashboard.ts @@ -15,6 +15,7 @@ import { Avatar } from 'primeng/avatar' import { Toolbar } from 'primeng/toolbar' import { Popover } from 'primeng/popover' import { AuthService } from '~/core/services/auth.service' +import { MessageService } from '~/core/services/message.service' @Component({ selector: 'app-dashboard', @@ -23,11 +24,12 @@ import { AuthService } from '~/core/services/auth.service' changeDetection: ChangeDetectionStrategy.OnPush, }) export class Dashboard implements OnInit { - destroyRef = inject(DestroyRef) protected router = inject(Router) protected activatedRoute = inject(ActivatedRoute) protected authService = inject(AuthService) + protected message = inject(MessageService) protected isInsideProject = signal(false) + destroyRef = inject(DestroyRef) protected user = this.authService.getUser() @@ -57,8 +59,11 @@ export class Dashboard implements OnInit { await this.router.navigate(['/']) }, error: err => { - alert('something wrong happened') - console.log(err) + console.warn(err) + this.message.error( + 'Oops!', + "Something wrong happened and we couldn't log you out normally", + ) }, }) } diff --git a/client/src/app/modules/dashboard/containers/projects/components/ai-chat-panel/ai-chat-panel.html b/client/src/app/modules/dashboard/containers/projects/components/ai-chat-panel/ai-chat-panel.html index fcd7e18..e967e58 100644 --- a/client/src/app/modules/dashboard/containers/projects/components/ai-chat-panel/ai-chat-panel.html +++ b/client/src/app/modules/dashboard/containers/projects/components/ai-chat-panel/ai-chat-panel.html @@ -1,5 +1,3 @@ - -
@if (isLoading()) { diff --git a/client/src/app/modules/dashboard/containers/projects/components/ai-chat-panel/ai-chat-panel.ts b/client/src/app/modules/dashboard/containers/projects/components/ai-chat-panel/ai-chat-panel.ts index 04770bc..14a6567 100644 --- a/client/src/app/modules/dashboard/containers/projects/components/ai-chat-panel/ai-chat-panel.ts +++ b/client/src/app/modules/dashboard/containers/projects/components/ai-chat-panel/ai-chat-panel.ts @@ -15,7 +15,6 @@ import { InputText } from 'primeng/inputtext' import { ProgressSpinner } from 'primeng/progressspinner' import { AiChatMessage } from '../ai-chat-message/ai-chat-message' import { LaravelApiResponse } from '~/shared/interfaces/laravel-api-response.interface' -import { Toast } from 'primeng/toast' import { AiChatMessageData } from '../../shared/interfaces/ai-chat-message-data.interface' import { EmptyState } from '~/shared/components/empty-state/empty-state' import { takeUntilDestroyed } from '@angular/core/rxjs-interop' @@ -24,7 +23,7 @@ import { AiChatRepository } from '../../shared/repositories/ai-chat.respository' @Component({ selector: 'app-ai-chat-panel', - imports: [FormsModule, InputText, Button, ProgressSpinner, AiChatMessage, Toast, EmptyState], + imports: [FormsModule, InputText, Button, ProgressSpinner, AiChatMessage, EmptyState], templateUrl: './ai-chat-panel.html', changeDetection: ChangeDetectionStrategy.OnPush, host: { class: 'h-full' }, diff --git a/client/src/app/modules/dashboard/containers/projects/components/comments-panel/comments-panel.ts b/client/src/app/modules/dashboard/containers/projects/components/comments-panel/comments-panel.ts index e420a34..710c3f4 100644 --- a/client/src/app/modules/dashboard/containers/projects/components/comments-panel/comments-panel.ts +++ b/client/src/app/modules/dashboard/containers/projects/components/comments-panel/comments-panel.ts @@ -92,7 +92,6 @@ export class CommentsPanel implements OnInit { .pipe( takeUntilDestroyed(this.destroyRef), catchError(err => { - console.warn('Failed to send comment:', err) this.message.error( 'Error', `Failed to send comment. ${err.error?.message || err.message}`, diff --git a/client/src/app/modules/dashboard/containers/projects/components/release-card/release-card.ts b/client/src/app/modules/dashboard/containers/projects/components/release-card/release-card.ts index 6ca3eb0..7abfe71 100644 --- a/client/src/app/modules/dashboard/containers/projects/components/release-card/release-card.ts +++ b/client/src/app/modules/dashboard/containers/projects/components/release-card/release-card.ts @@ -97,7 +97,6 @@ export class ReleaseCard { .pipe( takeUntilDestroyed(this.destroyRef), catchError(err => { - console.error('Failed to delete release:', err) this.message.error( 'Error', `Failed to delete release: ${err.error?.message || err.message}`, diff --git a/client/src/app/modules/dashboard/containers/projects/containers/audits/containers/audits/audits.html b/client/src/app/modules/dashboard/containers/projects/containers/audits/containers/audits/audits.html index 5570a30..cc0576d 100644 --- a/client/src/app/modules/dashboard/containers/projects/containers/audits/containers/audits/audits.html +++ b/client/src/app/modules/dashboard/containers/projects/containers/audits/containers/audits/audits.html @@ -50,5 +50,4 @@

Audits

(auditCreated)="onAuditCreated()" /> - diff --git a/client/src/app/modules/dashboard/containers/projects/containers/audits/containers/audits/audits.ts b/client/src/app/modules/dashboard/containers/projects/containers/audits/containers/audits/audits.ts index f20493f..142751b 100644 --- a/client/src/app/modules/dashboard/containers/projects/containers/audits/containers/audits/audits.ts +++ b/client/src/app/modules/dashboard/containers/projects/containers/audits/containers/audits/audits.ts @@ -11,7 +11,6 @@ import { import { ActivatedRoute } from '@angular/router' import { Button } from 'primeng/button' import { ProgressSpinner } from 'primeng/progressspinner' -import { Toast } from 'primeng/toast' import { catchError, finalize, interval, of, Subscription, switchMap, takeWhile } from 'rxjs' import { AuditRepository } from '~/modules/dashboard/containers/projects/containers/audits/shared/repositories/audit.repository' import { EmptyState } from '~/shared/components/empty-state/empty-state' @@ -28,7 +27,6 @@ import { ConfirmDialog } from 'primeng/confirmdialog' imports: [ Button, ProgressSpinner, - Toast, AuditCard, CreateAuditDialog, EmptyState, @@ -73,9 +71,11 @@ export class Audits implements OnInit, OnDestroy { .getAudits(this.projectId()) .pipe( takeUntilDestroyed(this.destroyRef), - catchError(error => { - console.error('Failed to load audits:', error) - this.message.error('Error', 'Failed to load audits. Please try again.') + catchError(err => { + this.message.error( + 'Error', + `Failed to load audits. ${err.error?.message || err.message}`, + ) return of({ message: '', payload: [] }) }), finalize(() => this.isLoading.set(false)), diff --git a/client/src/app/modules/dashboard/containers/projects/containers/email-templates/email-templates.html b/client/src/app/modules/dashboard/containers/projects/containers/email-templates/email-templates.html index 819a837..b325b3b 100644 --- a/client/src/app/modules/dashboard/containers/projects/containers/email-templates/email-templates.html +++ b/client/src/app/modules/dashboard/containers/projects/containers/email-templates/email-templates.html @@ -1,5 +1,3 @@ - - @let id = shownEmailId(); @if (id !== null) {
diff --git a/client/src/app/modules/dashboard/containers/projects/containers/email-templates/email-templates.ts b/client/src/app/modules/dashboard/containers/projects/containers/email-templates/email-templates.ts index 04debeb..6c2d7a1 100644 --- a/client/src/app/modules/dashboard/containers/projects/containers/email-templates/email-templates.ts +++ b/client/src/app/modules/dashboard/containers/projects/containers/email-templates/email-templates.ts @@ -10,7 +10,6 @@ import { FormsModule } from '@angular/forms' import { HttpClient } from '@angular/common/http' import { ActivatedRoute } from '@angular/router' import { catchError, of } from 'rxjs' -import { Toast } from 'primeng/toast' import { Button } from 'primeng/button' import { Drawer } from 'primeng/drawer' import { Select } from 'primeng/select' @@ -35,7 +34,6 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop' Drawer, Select, TabsModule, - Toast, ProgressSpinner, ExpandedImage, EmptyState, diff --git a/client/src/app/modules/dashboard/containers/projects/containers/project-layout/project-layout.html b/client/src/app/modules/dashboard/containers/projects/containers/project-layout/project-layout.html index 492367f..de5afdb 100644 --- a/client/src/app/modules/dashboard/containers/projects/containers/project-layout/project-layout.html +++ b/client/src/app/modules/dashboard/containers/projects/containers/project-layout/project-layout.html @@ -27,3 +27,5 @@
+ + diff --git a/client/src/app/modules/dashboard/containers/projects/containers/project-layout/project-layout.ts b/client/src/app/modules/dashboard/containers/projects/containers/project-layout/project-layout.ts index 05da1fd..27ed9fe 100644 --- a/client/src/app/modules/dashboard/containers/projects/containers/project-layout/project-layout.ts +++ b/client/src/app/modules/dashboard/containers/projects/containers/project-layout/project-layout.ts @@ -1,9 +1,10 @@ import { ChangeDetectionStrategy, Component } from '@angular/core' import { RouterLink, RouterLinkActive, RouterOutlet } from '@angular/router' +import { Toast } from 'primeng/toast' @Component({ selector: 'app-project-layout', - imports: [RouterOutlet, RouterLink, RouterLinkActive], + imports: [RouterOutlet, RouterLink, RouterLinkActive, Toast], templateUrl: './project-layout.html', changeDetection: ChangeDetectionStrategy.OnPush, host: { diff --git a/client/src/app/modules/dashboard/containers/projects/containers/releases/releases.html b/client/src/app/modules/dashboard/containers/projects/containers/releases/releases.html index 99abe76..2f630c6 100644 --- a/client/src/app/modules/dashboard/containers/projects/containers/releases/releases.html +++ b/client/src/app/modules/dashboard/containers/projects/containers/releases/releases.html @@ -51,5 +51,4 @@ (onNewRelease)="addNewRelease($event)" /> - diff --git a/client/src/app/modules/dashboard/containers/projects/containers/releases/releases.ts b/client/src/app/modules/dashboard/containers/projects/containers/releases/releases.ts index 2e743a3..a030a49 100644 --- a/client/src/app/modules/dashboard/containers/projects/containers/releases/releases.ts +++ b/client/src/app/modules/dashboard/containers/projects/containers/releases/releases.ts @@ -12,7 +12,6 @@ import { takeUntilDestroyed } from '@angular/core/rxjs-interop' import { catchError, of } from 'rxjs' import { ProgressSpinner } from 'primeng/progressspinner' import { Button } from 'primeng/button' -import { Toast } from 'primeng/toast' import { ReleaseCard } from '../../components/release-card/release-card' import { NewReleaseDialog } from '../../components/new-release-dialog/new-release-dialog' import { ReleaseData } from '../../shared/interfaces/release.interface' @@ -23,15 +22,7 @@ import { ConfirmDialog } from 'primeng/confirmdialog' @Component({ selector: 'app-releases', - imports: [ - Button, - ReleaseCard, - NewReleaseDialog, - Toast, - EmptyState, - ProgressSpinner, - ConfirmDialog, - ], + imports: [Button, ReleaseCard, NewReleaseDialog, EmptyState, ProgressSpinner, ConfirmDialog], templateUrl: './releases.html', changeDetection: ChangeDetectionStrategy.OnPush, }) diff --git a/client/src/app/modules/dashboard/containers/projects/containers/settings/settings.html b/client/src/app/modules/dashboard/containers/projects/containers/settings/settings.html index b1a3798..3c66e47 100644 --- a/client/src/app/modules/dashboard/containers/projects/containers/settings/settings.html +++ b/client/src/app/modules/dashboard/containers/projects/containers/settings/settings.html @@ -31,5 +31,3 @@ [loading]="isSubmitting()" /> - - From f730027ded9aa9f61d6211db2758b46fe63c341b Mon Sep 17 00:00:00 2001 From: Abdullah Z Date: Mon, 22 Sep 2025 21:54:19 +0300 Subject: [PATCH 9/9] chore(frontend): update audit details styles --- .../audit-details/audit-details.html | 38 ++++++++++--------- 1 file changed, 20 insertions(+), 18 deletions(-) diff --git a/client/src/app/modules/dashboard/containers/projects/containers/audits/containers/audit-details/audit-details.html b/client/src/app/modules/dashboard/containers/projects/containers/audits/containers/audit-details/audit-details.html index c29eb76..8649bf2 100644 --- a/client/src/app/modules/dashboard/containers/projects/containers/audits/containers/audit-details/audit-details.html +++ b/client/src/app/modules/dashboard/containers/projects/containers/audits/containers/audit-details/audit-details.html @@ -9,28 +9,30 @@ />
} @else if (audit) { -
-
-
-

{{ audit.name }}

- @if (audit.description) { -

{{ audit.description }}

- } -
-
- - @if (audit.overall_score !== null) { -
-
- {{ audit.overall_score }}/10 -
-
Overall Score
+
+
+

{{ audit.name }}

+ @if (audit.description) { +

+ {{ audit.description }} +

+ } +
+
+ + @if (audit.overall_score !== null) { +
+
+ {{ audit.overall_score }}/10
- } -
+
Overall Score
+
+ }
+

Findings

+ @if (audit.status === 'completed' && audit.results) {