Add contacts, reply/forward, pagination, HTML toggle, Docker Compose for VPS
Co-authored-by: Junie <junie@jetbrains.com>
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
<div class="space-y-4">
|
||||
<h2 class="text-lg font-orbitron font-bold text-synth-cyan flex items-center gap-2">
|
||||
<span class="text-synth-pink">👥</span> Contacts
|
||||
<span class="text-sm font-mono text-slate-400 ml-auto">{{ contacts().length }} contacts</span>
|
||||
</h2>
|
||||
|
||||
<!-- Форма добавления -->
|
||||
<div class="glass-panel p-4">
|
||||
<h3 class="text-sm font-orbitron font-bold text-synth-yellow mb-3">
|
||||
{{ editingContact() ? 'Edit Contact' : 'Add New Contact' }}
|
||||
</h3>
|
||||
<form (ngSubmit)="saveContact()" class="grid grid-cols-1 md:grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label class="block text-xs font-mono text-slate-400 mb-1">Name</label>
|
||||
<input type="text" [(ngModel)]="formName" name="name"
|
||||
class="w-full bg-synth-dark/50 border border-synth-cyan/20 rounded-lg px-3 py-2 text-sm font-mono text-white focus:border-synth-cyan focus:outline-none focus:ring-1 focus:ring-synth-cyan/50"
|
||||
placeholder="John Doe">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-mono text-slate-400 mb-1">Email *</label>
|
||||
<input type="email" [(ngModel)]="formEmail" name="email" required
|
||||
class="w-full bg-synth-dark/50 border border-synth-cyan/20 rounded-lg px-3 py-2 text-sm font-mono text-white focus:border-synth-cyan focus:outline-none focus:ring-1 focus:ring-synth-cyan/50"
|
||||
placeholder="john@example.com">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-mono text-slate-400 mb-1">Phone</label>
|
||||
<input type="text" [(ngModel)]="formPhone" name="phone"
|
||||
class="w-full bg-synth-dark/50 border border-synth-cyan/20 rounded-lg px-3 py-2 text-sm font-mono text-white focus:border-synth-cyan focus:outline-none focus:ring-1 focus:ring-synth-cyan/50"
|
||||
placeholder="+1 234 567 8900">
|
||||
</div>
|
||||
<div>
|
||||
<label class="block text-xs font-mono text-slate-400 mb-1">Company</label>
|
||||
<input type="text" [(ngModel)]="formCompany" name="company"
|
||||
class="w-full bg-synth-dark/50 border border-synth-cyan/20 rounded-lg px-3 py-2 text-sm font-mono text-white focus:border-synth-cyan focus:outline-none focus:ring-1 focus:ring-synth-cyan/50"
|
||||
placeholder="Acme Corp">
|
||||
</div>
|
||||
<div class="md:col-span-2 flex gap-2">
|
||||
<button type="submit"
|
||||
class="px-4 py-2 bg-synth-pink/20 border border-synth-pink/40 rounded-lg text-sm font-mono text-synth-pink hover:bg-synth-pink/30 transition-all">
|
||||
{{ editingContact() ? '💾 Update' : '➕ Add' }}
|
||||
</button>
|
||||
@if (editingContact()) {
|
||||
<button type="button" (click)="cancelEdit()"
|
||||
class="px-4 py-2 bg-slate-700/50 border border-slate-600/40 rounded-lg text-sm font-mono text-slate-300 hover:bg-slate-600/50 transition-all">
|
||||
✖ Cancel
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Список контактов -->
|
||||
<div class="glass-panel p-4">
|
||||
@if (contacts().length === 0) {
|
||||
<div class="text-center py-12 text-slate-400 font-mono">
|
||||
<p class="text-4xl mb-4">📇</p>
|
||||
<p>No contacts yet</p>
|
||||
</div>
|
||||
} @else {
|
||||
<div class="space-y-2">
|
||||
@for (contact of contacts(); track contact.id) {
|
||||
<div class="flex items-center gap-3 p-3 rounded-xl border border-synth-cyan/10 hover:border-synth-cyan/40 hover:bg-synth-cyan/5 transition-all">
|
||||
<div class="w-10 h-10 rounded-full bg-synth-purple/30 flex items-center justify-center text-synth-cyan font-bold text-lg">
|
||||
{{ (contact.name || contact.email).charAt(0).toUpperCase() }}
|
||||
</div>
|
||||
<div class="flex-1 min-w-0">
|
||||
<div class="font-mono text-white truncate">{{ contact.name || '(No name)' }}</div>
|
||||
<div class="text-xs text-slate-400 font-mono truncate">{{ contact.email }}</div>
|
||||
@if (contact.phone || contact.company) {
|
||||
<div class="text-xs text-slate-500 font-mono">
|
||||
<span *ngIf="contact.company">{{ contact.company }}</span>
|
||||
<span *ngIf="contact.company && contact.phone"> • </span>
|
||||
<span *ngIf="contact.phone">{{ contact.phone }}</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="flex gap-1">
|
||||
<button (click)="editContact(contact)"
|
||||
class="px-2 py-1 text-xs font-mono text-synth-cyan hover:bg-synth-cyan/10 rounded transition-all">
|
||||
✏️
|
||||
</button>
|
||||
<button (click)="deleteContact(contact.id!)"
|
||||
class="px-2 py-1 text-xs font-mono text-synth-pink hover:bg-synth-pink/10 rounded transition-all">
|
||||
🗑️
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@@ -0,0 +1,95 @@
|
||||
import { Component, inject, signal } from '@angular/core';
|
||||
import { FormsModule } from '@angular/forms';
|
||||
import { CommonModule } from '@angular/common';
|
||||
import { MailService } from '../../services/mail.service';
|
||||
import { Contact } from '../../models/mail.models';
|
||||
|
||||
@Component({
|
||||
selector: 'app-contacts',
|
||||
standalone: true,
|
||||
imports: [FormsModule, CommonModule],
|
||||
providers: [MailService],
|
||||
templateUrl: './contacts.component.html',
|
||||
styles: ``
|
||||
})
|
||||
export default class ContactsComponent {
|
||||
private mailService = inject(MailService);
|
||||
|
||||
contacts = signal<Contact[]>([]);
|
||||
editingContact = signal<Contact | null>(null);
|
||||
|
||||
formName = '';
|
||||
formEmail = '';
|
||||
formPhone = '';
|
||||
formCompany = '';
|
||||
|
||||
constructor() {
|
||||
this.loadContacts();
|
||||
}
|
||||
|
||||
loadContacts(): void {
|
||||
this.mailService.getContacts().subscribe({
|
||||
next: (contacts) => this.contacts.set(contacts),
|
||||
error: (e) => console.error('Failed to load contacts', e)
|
||||
});
|
||||
}
|
||||
|
||||
saveContact(): void {
|
||||
if (!this.formEmail.trim()) return;
|
||||
|
||||
const contact: Contact = {
|
||||
name: this.formName.trim(),
|
||||
email: this.formEmail.trim(),
|
||||
phone: this.formPhone.trim() || undefined,
|
||||
company: this.formCompany.trim() || undefined
|
||||
};
|
||||
|
||||
if (this.editingContact()?.id) {
|
||||
this.mailService.updateContact(this.editingContact()!.id!, contact).subscribe({
|
||||
next: () => {
|
||||
this.resetForm();
|
||||
this.loadContacts();
|
||||
},
|
||||
error: (e) => console.error('Failed to update contact', e)
|
||||
});
|
||||
} else {
|
||||
this.mailService.addContact(contact).subscribe({
|
||||
next: () => {
|
||||
this.resetForm();
|
||||
this.loadContacts();
|
||||
},
|
||||
error: (e) => console.error('Failed to add contact', e)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
editContact(contact: Contact): void {
|
||||
this.editingContact.set(contact);
|
||||
this.formName = contact.name || '';
|
||||
this.formEmail = contact.email || '';
|
||||
this.formPhone = contact.phone || '';
|
||||
this.formCompany = contact.company || '';
|
||||
}
|
||||
|
||||
cancelEdit(): void {
|
||||
this.editingContact.set(null);
|
||||
this.resetForm();
|
||||
}
|
||||
|
||||
deleteContact(id: number): void {
|
||||
if (confirm('Delete this contact?')) {
|
||||
this.mailService.deleteContact(id).subscribe({
|
||||
next: () => this.loadContacts(),
|
||||
error: (e) => console.error('Failed to delete contact', e)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
resetForm(): void {
|
||||
this.formName = '';
|
||||
this.formEmail = '';
|
||||
this.formPhone = '';
|
||||
this.formCompany = '';
|
||||
this.editingContact.set(null);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user