Синхронизация моделей, Docker настройки и исправление Angular шаблонов

- V1__init.sql: синхронизированы таблицы с Java моделями (users, folders, messages, contacts)
- docker-compose.yml: обновлён контекст сборки, переменные R2DBC/Flyway, порты (5433, 3026, 1443)
- backend/Dockerfile: обновлены пути COPY для нового контекста сборки
- settings.gradle: удалён frontend проект из Gradle
- contacts.component.html: исправлен ternary оператор с HTML на Angular @if/@else
- Фронтенд компоненты: обновления compose, inbox, layout, login, message-detail, register
- index.html, styles.css: обновления стилей и мета-данных

Co-authored-by: Junie <junie@jetbrains.com>
This commit is contained in:
2026-11-05 03:08:36 +01:00
co-authored by Junie
parent 0d16342f9f
commit b6f6d41813
13 changed files with 88 additions and 48 deletions
+3 -2
View File
@@ -1,9 +1,10 @@
# Stage 1: Build # Stage 1: Build
FROM eclipse-temurin:21-jdk-alpine AS build FROM eclipse-temurin:21-jdk-alpine AS build
WORKDIR /app WORKDIR /app
COPY build.gradle settings.gradle gradlew ./ COPY backend/build.gradle ./
COPY gradlew ./
COPY gradle gradle COPY gradle gradle
COPY src src COPY backend/src src
RUN chmod +x gradlew RUN chmod +x gradlew
RUN ./gradlew bootJar --no-daemon RUN ./gradlew bootJar --no-daemon
@@ -2,8 +2,11 @@
CREATE TABLE users ( CREATE TABLE users (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL, email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL, password VARCHAR(255) NOT NULL,
display_name VARCHAR(255) NOT NULL, display_name VARCHAR(255) NOT NULL,
quota_bytes BIGINT NOT NULL DEFAULT 1073741824,
used_bytes BIGINT NOT NULL DEFAULT 0,
is_active BOOLEAN NOT NULL DEFAULT TRUE,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
); );
@@ -12,7 +15,8 @@ CREATE TABLE folders (
id BIGSERIAL PRIMARY KEY, id BIGSERIAL PRIMARY KEY,
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
name VARCHAR(50) NOT NULL, name VARCHAR(50) NOT NULL,
type VARCHAR(20) NOT NULL, message_count INTEGER NOT NULL DEFAULT 0,
unread_count INTEGER NOT NULL DEFAULT 0,
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
); );
@@ -25,10 +29,13 @@ CREATE TABLE messages (
recipient_email VARCHAR(255) NOT NULL, recipient_email VARCHAR(255) NOT NULL,
subject VARCHAR(1000), subject VARCHAR(1000),
body TEXT, body TEXT,
is_html BOOLEAN NOT NULL DEFAULT FALSE,
is_read BOOLEAN NOT NULL DEFAULT FALSE, is_read BOOLEAN NOT NULL DEFAULT FALSE,
is_flagged BOOLEAN NOT NULL DEFAULT FALSE,
is_spam BOOLEAN NOT NULL DEFAULT FALSE, is_spam BOOLEAN NOT NULL DEFAULT FALSE,
message_size BIGINT, message_size BIGINT,
received_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP received_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
message_id VARCHAR(255)
); );
-- Таблица вложений -- Таблица вложений
@@ -58,6 +65,8 @@ CREATE TABLE contacts (
user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE, user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
name VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL,
email VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL,
phone VARCHAR(50),
company VARCHAR(255),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE (user_id, email) UNIQUE (user_id, email)
); );
+12 -7
View File
@@ -2,18 +2,23 @@ version: '3.8'
services: services:
mail-server: mail-server:
build: ./backend build:
context: .
dockerfile: ./backend/Dockerfile
ports: ports:
- "8080:8080" # REST API - "8080:8080" # REST API
- "3025:3025" # SMTP (приём писем) - "3026:3025" # SMTP (приём писем)
- "143:143" # IMAP (доступ к письмам) - "1443:143" # IMAP (доступ к письмам)
volumes: volumes:
- attachments:/app/attachments - attachments:/app/attachments
- dkim-keys:/app/dkim-keys - dkim-keys:/app/dkim-keys
environment: environment:
- SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/mailserver - SPRING_R2DBC_URL=r2dbc:postgresql://db:5432/mailserver
- SPRING_DATASOURCE_USERNAME=mail - SPRING_R2DBC_USERNAME=mail
- SPRING_DATASOURCE_PASSWORD=secret - SPRING_R2DBC_PASSWORD=secret
- SPRING_FLYWAY_URL=jdbc:postgresql://db:5432/mailserver
- SPRING_FLYWAY_USER=mail
- SPRING_FLYWAY_PASSWORD=secret
- SPRING_MAIL_HOST=mailpit - SPRING_MAIL_HOST=mailpit
- SPRING_MAIL_PORT=1025 - SPRING_MAIL_PORT=1025
depends_on: depends_on:
@@ -29,7 +34,7 @@ services:
volumes: volumes:
- pg-data:/var/lib/postgresql/data - pg-data:/var/lib/postgresql/data
ports: ports:
- "5432:5432" - "5433:5432"
mailpit: mailpit:
image: axllent/mailpit image: axllent/mailpit
@@ -12,7 +12,7 @@ import { Router, ActivatedRoute } from '@angular/router';
template: ` template: `
<div class="glass-panel p-6"> <div class="glass-panel p-6">
<h2 class="text-lg font-orbitron font-bold text-cyan-500 mb-6 flex items-center gap-2"> <h2 class="text-lg font-orbitron font-bold text-cyan-500 mb-6 flex items-center gap-2">
<span class="text-pink-500">✉️</span> COMPOSE MESSAGE <i class="fa-solid fa-pen-to-square text-pink-500"></i> COMPOSE MESSAGE
</h2> </h2>
@if (success) { @if (success) {
@@ -53,14 +53,14 @@ import { Router, ActivatedRoute } from '@angular/router';
class="w-full bg-slate-950/80 border border-cyan-500/40 rounded-xl px-4 py-3 text-sm text-slate-200 font-mono focus:outline-none focus:ring-2 focus:ring-pink-500 focus:border-cyan-500 transition-all resize-none" class="w-full bg-slate-950/80 border border-cyan-500/40 rounded-xl px-4 py-3 text-sm text-slate-200 font-mono focus:outline-none focus:ring-2 focus:ring-pink-500 focus:border-cyan-500 transition-all resize-none"
[placeholder]="isHtml ? 'Write HTML or Markdown...' : 'Write your message...'"></textarea> [placeholder]="isHtml ? 'Write HTML or Markdown...' : 'Write your message...'"></textarea>
@if (isHtml) { @if (isHtml) {
<p class="text-xs text-slate-500 font-mono mt-1">💡 Supports Markdown: **bold**, *italic*, - lists, \`\`\`code\`\`\`</p> <p class="text-xs text-slate-500 font-mono mt-1"><i class="fa-solid fa-lightbulb mr-1"></i>Supports Markdown: **bold**, *italic*, - lists, &nbsp;code&nbsp;</p>
} }
</div> </div>
<div class="flex gap-3 pt-4"> <div class="flex gap-3 pt-4">
<button type="submit" [disabled]="sending" <button type="submit" [disabled]="sending"
class="cyber-btn px-8 py-3 rounded-xl text-sm font-bold"> class="cyber-btn px-8 py-3 rounded-xl text-sm font-bold">
{{ sending ? 'SENDING...' : 'SEND' }} <i class="fa-solid fa-paper-plane mr-1"></i> {{ sending ? 'SENDING...' : 'SEND' }}
</button> </button>
<a routerLink="/inbox" class="px-6 py-3 rounded-xl text-xs font-mono text-slate-400 border border-slate-600 hover:text-slate-200 hover:border-slate-400 transition-all text-center"> <a routerLink="/inbox" class="px-6 py-3 rounded-xl text-xs font-mono text-slate-400 border border-slate-600 hover:text-slate-200 hover:border-slate-400 transition-all text-center">
CANCEL CANCEL
@@ -1,13 +1,13 @@
<div class="space-y-4"> <div class="space-y-4">
<h2 class="text-lg font-orbitron font-bold text-cyan-300 flex items-center gap-2"> <h2 class="text-lg font-orbitron font-bold text-cyan-300 flex items-center gap-2">
<span class="text-pink-500">👥</span> CONTACTS <i class="fa-solid fa-users text-pink-500"></i> CONTACTS
<span class="text-sm font-mono text-slate-400 ml-auto">{{ contacts().length }} contacts</span> <span class="text-sm font-mono text-slate-400 ml-auto">{{ contacts().length }} contacts</span>
</h2> </h2>
<!-- Форма добавления --> <!-- Форма добавления -->
<div class="glass-panel p-5"> <div class="glass-panel p-5">
<h3 class="text-sm font-orbitron font-bold text-yellow-400 mb-3 flex items-center gap-2"> <h3 class="text-sm font-orbitron font-bold text-yellow-400 mb-3 flex items-center gap-2">
<span class="text-pink-500">✏️</span> {{ editingContact() ? 'EDIT CONTACT' : 'ADD NEW CONTACT' }} <i class="fa-solid fa-pen-to-square text-pink-500"></i> {{ editingContact() ? 'EDIT CONTACT' : 'ADD NEW CONTACT' }}
</h3> </h3>
<form (ngSubmit)="saveContact()" class="grid grid-cols-1 md:grid-cols-2 gap-3"> <form (ngSubmit)="saveContact()" class="grid grid-cols-1 md:grid-cols-2 gap-3">
<div> <div>
@@ -37,12 +37,16 @@
<div class="md:col-span-2 flex gap-2"> <div class="md:col-span-2 flex gap-2">
<button type="submit" <button type="submit"
class="cyber-btn px-6 py-2 rounded-xl text-xs font-bold"> class="cyber-btn px-6 py-2 rounded-xl text-xs font-bold">
{{ editingContact() ? '💾 UPDATE' : '➕ ADD' }} @if (editingContact()) {
<i class="fa-solid fa-save mr-1"></i> UPDATE
} @else {
<i class="fa-solid fa-plus mr-1"></i> ADD
}
</button> </button>
@if (editingContact()) { @if (editingContact()) {
<button type="button" (click)="cancelEdit()" <button type="button" (click)="cancelEdit()"
class="px-4 py-2 rounded-xl text-xs font-mono text-slate-400 border border-slate-600/40 hover:text-slate-200 hover:border-slate-400 transition-all"> class="px-4 py-2 rounded-xl text-xs font-mono text-slate-400 border border-slate-600/40 hover:text-slate-200 hover:border-slate-400 transition-all">
✖ CANCEL <i class="fa-solid fa-xmark mr-1"></i> CANCEL
</button> </button>
} }
</div> </div>
@@ -53,7 +57,7 @@
<div class="glass-panel p-5"> <div class="glass-panel p-5">
@if (contacts().length === 0) { @if (contacts().length === 0) {
<div class="text-center py-12 text-slate-400 font-mono"> <div class="text-center py-12 text-slate-400 font-mono">
<p class="text-4xl mb-4">📇</p> <i class="fa-solid fa-address-card text-4xl mb-4 text-slate-600"></i>
<p>No contacts yet</p> <p>No contacts yet</p>
</div> </div>
} @else { } @else {
@@ -83,11 +87,11 @@
<div class="flex gap-1"> <div class="flex gap-1">
<button (click)="editContact(contact)" <button (click)="editContact(contact)"
class="px-2 py-1 text-xs font-mono text-cyan-400 hover:bg-cyan-500/10 rounded-lg transition-all"> class="px-2 py-1 text-xs font-mono text-cyan-400 hover:bg-cyan-500/10 rounded-lg transition-all">
✏️ <i class="fa-solid fa-pen"></i>
</button> </button>
<button (click)="deleteContact(contact.id!)" <button (click)="deleteContact(contact.id!)"
class="px-2 py-1 text-xs font-mono text-pink-400 hover:bg-pink-500/10 rounded-lg transition-all"> class="px-2 py-1 text-xs font-mono text-pink-400 hover:bg-pink-500/10 rounded-lg transition-all">
🗑️ <i class="fa-solid fa-trash"></i>
</button> </button>
</div> </div>
</div> </div>
@@ -19,9 +19,9 @@ import { of } from 'rxjs';
<div class="flex gap-3"> <div class="flex gap-3">
<input [(ngModel)]="searchQuery" (keyup.enter)="loadMessages()" <input [(ngModel)]="searchQuery" (keyup.enter)="loadMessages()"
class="flex-1 bg-slate-950/80 border border-cyan-500/40 rounded-xl px-4 py-2 text-sm text-slate-200 font-mono focus:outline-none focus:ring-2 focus:ring-pink-500 focus:border-cyan-400 transition-all" class="flex-1 bg-slate-950/80 border border-cyan-500/40 rounded-xl px-4 py-2 text-sm text-slate-200 font-mono focus:outline-none focus:ring-2 focus:ring-pink-500 focus:border-cyan-400 transition-all"
placeholder="🔍 Search messages..."> placeholder="Search messages...">
<button (click)="loadMessages()" class="cyber-btn px-6 py-2 rounded-xl text-xs font-bold"> <button (click)="loadMessages()" class="cyber-btn px-6 py-2 rounded-xl text-xs font-bold">
SEARCH <i class="fa-solid fa-magnifying-glass mr-1"></i> SEARCH
</button> </button>
</div> </div>
</div> </div>
@@ -29,13 +29,13 @@ import { of } from 'rxjs';
<!-- Список писем --> <!-- Список писем -->
<div class="glass-panel p-4"> <div class="glass-panel p-4">
<h2 class="text-lg font-orbitron font-bold text-cyan-300 mb-4 flex items-center gap-2"> <h2 class="text-lg font-orbitron font-bold text-cyan-300 mb-4 flex items-center gap-2">
<span class="text-pink-500">📬</span> {{ folderName() }} <i class="fa-solid fa-inbox text-pink-500"></i> {{ folderName() }}
<span class="text-sm font-mono text-slate-400 ml-auto">{{ messages()?.length || 0 }} messages</span> <span class="text-sm font-mono text-slate-400 ml-auto">{{ messages()?.length || 0 }} messages</span>
</h2> </h2>
@if (!messages() || messages().length === 0) { @if (!messages() || messages().length === 0) {
<div class="text-center py-12 text-slate-400 font-mono"> <div class="text-center py-12 text-slate-400 font-mono">
<p class="text-4xl mb-4">📭</p> <i class="fa-solid fa-envelope-open-text text-4xl mb-4 text-slate-600"></i>
<p>No messages yet</p> <p>No messages yet</p>
</div> </div>
} @else { } @else {
@@ -64,7 +64,7 @@ import { of } from 'rxjs';
</span> </span>
} }
@if (msg.flagged) { @if (msg.flagged) {
<span class="text-yellow-400 text-xs">★</span> <i class="fa-solid fa-star text-yellow-400 text-xs"></i>
} }
</div> </div>
<div class="text-sm text-cyan-400 font-rajdhani truncate group-hover:text-pink-400 transition-colors"> <div class="text-sm text-cyan-400 font-rajdhani truncate group-hover:text-pink-400 transition-colors">
@@ -80,7 +80,7 @@ import { of } from 'rxjs';
{{ msg.sentAt | date:'MMM d, HH:mm' }} {{ msg.sentAt | date:'MMM d, HH:mm' }}
</div> </div>
@if (msg.hasAttachments) { @if (msg.hasAttachments) {
<span class="text-xs text-purple-400 font-mono">📎 {{ msg.attachmentCount }}</span> <span class="text-xs text-purple-400 font-mono"><i class="fa-solid fa-paperclip mr-1"></i>{{ msg.attachmentCount }}</span>
} }
</div> </div>
</div> </div>
@@ -95,7 +95,7 @@ import { of } from 'rxjs';
<div class="glass-panel p-4 flex items-center justify-center gap-2"> <div class="glass-panel p-4 flex items-center justify-center gap-2">
<button (click)="goToPage(page() - 1)" [disabled]="page() === 0" <button (click)="goToPage(page() - 1)" [disabled]="page() === 0"
class="px-3 py-1 rounded-lg text-xs font-mono text-cyan-400 border border-cyan-500/30 hover:bg-cyan-500/10 disabled:opacity-30 disabled:cursor-not-allowed transition-all"> class="px-3 py-1 rounded-lg text-xs font-mono text-cyan-400 border border-cyan-500/30 hover:bg-cyan-500/10 disabled:opacity-30 disabled:cursor-not-allowed transition-all">
← Prev <i class="fa-solid fa-chevron-left mr-1"></i> Prev
</button> </button>
@for (p of pageNumbers; track p) { @for (p of pageNumbers; track p) {
<button (click)="goToPage(p)" <button (click)="goToPage(p)"
@@ -107,7 +107,7 @@ import { of } from 'rxjs';
} }
<button (click)="goToPage(page() + 1)" [disabled]="page() >= totalPages() - 1" <button (click)="goToPage(page() + 1)" [disabled]="page() >= totalPages() - 1"
class="px-3 py-1 rounded-lg text-xs font-mono text-cyan-400 border border-cyan-500/30 hover:bg-cyan-500/10 disabled:opacity-30 disabled:cursor-not-allowed transition-all"> class="px-3 py-1 rounded-lg text-xs font-mono text-cyan-400 border border-cyan-500/30 hover:bg-cyan-500/10 disabled:opacity-30 disabled:cursor-not-allowed transition-all">
Next → Next <i class="fa-solid fa-chevron-right ml-1"></i>
</button> </button>
<span class="text-xs font-mono text-slate-500 ml-2"> <span class="text-xs font-mono text-slate-500 ml-2">
Page {{ page() + 1 }} of {{ totalPages() }} Page {{ page() + 1 }} of {{ totalPages() }}
@@ -76,7 +76,7 @@ const THEMES = [
[attr.aria-pressed]="isHighContrast()" [attr.aria-pressed]="isHighContrast()"
title="Toggle High Contrast Mode" title="Toggle High Contrast Mode"
class="px-3 py-1.5 rounded-lg border border-cyan-500/40 bg-slate-900/80 text-xs font-mono hover:bg-cyan-500/20 focus:outline-none focus:ring-2 focus:ring-pink-500 transition-all flex items-center gap-1.5"> class="px-3 py-1.5 rounded-lg border border-cyan-500/40 bg-slate-900/80 text-xs font-mono hover:bg-cyan-500/20 focus:outline-none focus:ring-2 focus:ring-pink-500 transition-all flex items-center gap-1.5">
<span class="text-yellow-400">◐</span> <i class="fa-solid fa-circle-half-stroke text-yellow-400"></i>
<span>Contrast</span> <span>Contrast</span>
</button> </button>
@@ -87,7 +87,7 @@ const THEMES = [
<!-- Logout --> <!-- Logout -->
<button (click)="onLogout()" class="cyber-btn px-4 py-1.5 rounded-lg text-xs font-bold"> <button (click)="onLogout()" class="cyber-btn px-4 py-1.5 rounded-lg text-xs font-bold">
⏻ Logout <i class="fa-solid fa-power-off mr-1"></i> Logout
</button> </button>
</div> </div>
</div> </div>
@@ -99,7 +99,7 @@ const THEMES = [
<aside class="w-64 flex-shrink-0"> <aside class="w-64 flex-shrink-0">
<div class="glass-panel p-4"> <div class="glass-panel p-4">
<h2 class="text-sm font-orbitron font-bold text-cyan-300 mb-3 flex items-center gap-2"> <h2 class="text-sm font-orbitron font-bold text-cyan-300 mb-3 flex items-center gap-2">
<span class="text-pink-500">📁</span> FOLDERS <i class="fa-solid fa-folder-tree text-pink-500"></i> FOLDERS
</h2> </h2>
<nav class="space-y-1"> <nav class="space-y-1">
@for (folder of folders(); track folder.id) { @for (folder of folders(); track folder.id) {
@@ -123,14 +123,14 @@ const THEMES = [
<a routerLink="/contacts" <a routerLink="/contacts"
routerLinkActive="bg-cyan-500/20 text-cyan-300 border-cyan-500" routerLinkActive="bg-cyan-500/20 text-cyan-300 border-cyan-500"
class="block px-3 py-2 rounded-lg text-sm font-rajdhani text-slate-300 hover:bg-cyan-500/10 hover:text-cyan-300 border border-transparent transition-all"> class="block px-3 py-2 rounded-lg text-sm font-rajdhani text-slate-300 hover:bg-cyan-500/10 hover:text-cyan-300 border border-transparent transition-all">
👥 Contacts <i class="fa-solid fa-address-book mr-2"></i> Contacts
</a> </a>
</div> </div>
<!-- Кнопка написать --> <!-- Кнопка написать -->
<div class="glass-panel p-4 mt-4"> <div class="glass-panel p-4 mt-4">
<a routerLink="/compose" class="cyber-btn w-full py-3 rounded-xl text-xs font-bold block text-center"> <a routerLink="/compose" class="cyber-btn w-full py-3 rounded-xl text-xs font-bold block text-center">
✉️ NEW MESSAGE <i class="fa-solid fa-pen-to-square mr-2"></i> NEW MESSAGE
</a> </a>
</div> </div>
</aside> </aside>
@@ -146,8 +146,8 @@ const THEMES = [
<div class="max-w-7xl mx-auto flex flex-col sm:flex-row items-center justify-between gap-2"> <div class="max-w-7xl mx-auto flex flex-col sm:flex-row items-center justify-between gap-2">
<div>MAILSERVER SYNTHWAVE &copy; 2026</div> <div>MAILSERVER SYNTHWAVE &copy; 2026</div>
<div class="flex items-center space-x-4"> <div class="flex items-center space-x-4">
<span class="text-cyan-400">◉ A11Y READY</span> <span class="text-cyan-400"><i class="fa-solid fa-universal-access mr-1"></i> A11Y READY</span>
<span class="text-pink-400">⚡ HIGH PERF CSS3D</span> <span class="text-pink-400"><i class="fa-solid fa-bolt mr-1"></i> HIGH PERF CSS3D</span>
</div> </div>
</div> </div>
</footer> </footer>
@@ -55,7 +55,11 @@ import { Router } from '@angular/router';
<button type="submit" [disabled]="loading" <button type="submit" [disabled]="loading"
class="cyber-btn w-full py-3 rounded-xl text-sm font-bold mt-6"> class="cyber-btn w-full py-3 rounded-xl text-sm font-bold mt-6">
{{ loading ? '⏳ CONNECTING...' : '⚡ LOGIN' }} @if (loading) {
<i class="fa-solid fa-spinner fa-spin mr-1"></i> CONNECTING...
} @else {
<i class="fa-solid fa-bolt mr-1"></i> LOGIN
}
</button> </button>
</form> </form>
@@ -31,7 +31,7 @@ import { Message } from '../../models/mail.models';
@if (message.spam) { @if (message.spam) {
<div class="mt-3 px-3 py-1.5 inline-block rounded-full bg-yellow-500/20 border border-yellow-500/40 text-yellow-500 text-xs font-mono"> <div class="mt-3 px-3 py-1.5 inline-block rounded-full bg-yellow-500/20 border border-yellow-500/40 text-yellow-500 text-xs font-mono">
⚠️ MARKED AS SPAM <i class="fa-solid fa-triangle-exclamation mr-1"></i> MARKED AS SPAM
</div> </div>
} }
</div> </div>
@@ -47,7 +47,7 @@ import { Message } from '../../models/mail.models';
@if (message.hasAttachments) { @if (message.hasAttachments) {
<div class="mb-6 p-4 rounded-xl bg-slate-950/60 border border-purple-500/30"> <div class="mb-6 p-4 rounded-xl bg-slate-950/60 border border-purple-500/30">
<h3 class="text-sm font-orbitron font-bold text-purple-500 mb-3"> <h3 class="text-sm font-orbitron font-bold text-purple-500 mb-3">
📎 ATTACHMENTS ({{ message.attachmentCount }}) <i class="fa-solid fa-paperclip mr-1"></i> ATTACHMENTS ({{ message.attachmentCount }})
</h3> </h3>
<p class="text-xs text-slate-400 font-mono"> <p class="text-xs text-slate-400 font-mono">
Attachments are available via the API Attachments are available via the API
@@ -58,24 +58,24 @@ import { Message } from '../../models/mail.models';
<!-- Действия --> <!-- Действия -->
<div class="flex gap-3 pt-4 border-t border-cyan-500/20"> <div class="flex gap-3 pt-4 border-t border-cyan-500/20">
<a routerLink="/inbox" class="cyber-btn px-6 py-2 rounded-xl text-xs font-bold"> <a routerLink="/inbox" class="cyber-btn px-6 py-2 rounded-xl text-xs font-bold">
← BACK TO INBOX <i class="fa-solid fa-chevron-left mr-1"></i> BACK TO INBOX
</a> </a>
<a [routerLink]="['/compose']" [queryParams]="{replyTo: message.sender, subject: 'Re: ' + (message.subject || '')}" <a [routerLink]="['/compose']" [queryParams]="{replyTo: message.sender, subject: 'Re: ' + (message.subject || '')}"
class="px-6 py-2 rounded-xl text-xs font-mono text-cyan-500 border border-cyan-500/40 hover:bg-cyan-500/20 transition-all"> class="px-6 py-2 rounded-xl text-xs font-mono text-cyan-500 border border-cyan-500/40 hover:bg-cyan-500/20 transition-all">
↩️ REPLY <i class="fa-solid fa-reply mr-1"></i> REPLY
</a> </a>
<a [routerLink]="['/compose']" [queryParams]="{forwardTo: '', forwardSubject: 'Fwd: ' + (message.subject || ''), forwardBody: message.body}" <a [routerLink]="['/compose']" [queryParams]="{forwardTo: '', forwardSubject: 'Fwd: ' + (message.subject || ''), forwardBody: message.body}"
class="px-6 py-2 rounded-xl text-xs font-mono text-purple-500 border border-purple-500/40 hover:bg-purple-500/20 transition-all"> class="px-6 py-2 rounded-xl text-xs font-mono text-purple-500 border border-purple-500/40 hover:bg-purple-500/20 transition-all">
➡️ FORWARD <i class="fa-solid fa-share mr-1"></i> FORWARD
</a> </a>
<button (click)="onDelete()" class="px-6 py-2 rounded-xl text-xs font-mono text-pink-500 border border-pink-500/40 hover:bg-pink-500/20 transition-all"> <button (click)="onDelete()" class="px-6 py-2 rounded-xl text-xs font-mono text-pink-500 border border-pink-500/40 hover:bg-pink-500/20 transition-all">
🗑️ DELETE <i class="fa-solid fa-trash mr-1"></i> DELETE
</button> </button>
</div> </div>
</div> </div>
} @else { } @else {
<div class="glass-panel p-6 text-center py-12"> <div class="glass-panel p-6 text-center py-12">
<p class="text-4xl mb-4">📭</p> <i class="fa-solid fa-envelope-open-text text-4xl mb-4 text-slate-600"></i>
<p class="text-slate-400 font-mono">Message not found</p> <p class="text-slate-400 font-mono">Message not found</p>
<a routerLink="/inbox" class="cyber-btn px-6 py-2 rounded-xl text-xs font-bold mt-4 inline-block"> <a routerLink="/inbox" class="cyber-btn px-6 py-2 rounded-xl text-xs font-bold mt-4 inline-block">
← BACK TO INBOX ← BACK TO INBOX
@@ -67,7 +67,11 @@ import { Router } from '@angular/router';
<button type="submit" [disabled]="loading" <button type="submit" [disabled]="loading"
class="cyber-btn w-full py-3 rounded-xl text-sm font-bold mt-6"> class="cyber-btn w-full py-3 rounded-xl text-sm font-bold mt-6">
{{ loading ? '⏳ CREATING...' : '⚡ REGISTER' }} @if (loading) {
<i class="fa-solid fa-spinner fa-spin mr-1"></i> CREATING...
} @else {
<i class="fa-solid fa-bolt mr-1"></i> REGISTER
}
</button> </button>
</form> </form>
+3
View File
@@ -6,9 +6,12 @@
<base href="/"> <base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="favicon.ico"> <link rel="icon" type="image/x-icon" href="favicon.ico">
<!-- Google Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;600;800;900&family=Rajdhani:wght@400;500;600;700&family=Fira+Code:wght@400;500;600&display=swap" rel="stylesheet"> <link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;600;800;900&family=Rajdhani:wght@400;500;600;700&family=Fira+Code:wght@400;500;600&display=swap" rel="stylesheet">
<!-- FontAwesome Icons -->
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
</head> </head>
<body class="bg-synth-dark"> <body class="bg-synth-dark">
<app-root></app-root> <app-root></app-root>
+12
View File
@@ -253,6 +253,18 @@ body {
outline-offset: 3px; outline-offset: 3px;
} }
/* ARIA Inspector Focus Highlight */
.aria-inspect-active {
outline: 2px dashed var(--highlight-color) !important;
outline-offset: 4px;
animation: pulseAria 1.5s infinite;
}
@keyframes pulseAria {
0%, 100% { outline-color: var(--highlight-color); }
50% { outline-color: var(--primary-color); }
}
/* Custom Scrollbar */ /* Custom Scrollbar */
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 8px; width: 8px;
-2
View File
@@ -1,5 +1,3 @@
include 'backend' include 'backend'
include 'frontend'
project(':backend').projectDir = file('backend') project(':backend').projectDir = file('backend')
project(':frontend').projectDir = file('frontend')