diff --git a/src/app/app.html b/src/app/app.html index 41056f6..c9f68e3 100644 --- a/src/app/app.html +++ b/src/app/app.html @@ -1,5 +1,8 @@ -
-
+
+ + + + diff --git a/src/app/pages/project-create/project-create.component.css b/src/app/pages/project-create/project-create.component.css new file mode 100644 index 0000000..c875260 --- /dev/null +++ b/src/app/pages/project-create/project-create.component.css @@ -0,0 +1,89 @@ +.create-project { + min-height: 100vh; + background-color: #f5f7fa; + padding: 40px; + display: flex; + justify-content: center; + align-items: flex-start; +} + +.card { + width: 100%; + max-width: 640px; + height: fit-content; + background: white; + border-radius: 12px; + padding: 32px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +.card h1 { + margin: 0 0 24px 0; + font-size: 28px; + color: #2c3e50; +} + +.form-group { + margin-bottom: 20px; +} + +.form-group label { + display: block; + margin-bottom: 8px; + font-weight: 600; + color: #374151; +} + +.form-group input, +.form-group textarea { + width: 100%; + padding: 12px 14px; + border: 1px solid #e5e7eb; + border-radius: 8px; + font-size: 14px; + box-sizing: border-box; +} + +.form-group textarea { + resize: vertical; +} + +.actions { + display: flex; + justify-content: flex-end; + gap: 12px; + margin-top: 12px; +} + +.btn-primary { + padding: 10px 16px; + background-color: #10b981; + color: #fff; + border: none; + border-radius: 8px; + font-weight: 600; + cursor: pointer; +} + +.btn-primary:disabled { + opacity: 0.6; + cursor: not-allowed; +} + +.btn-secondary { + padding: 10px 16px; + background: none; + border: 1px solid #cbd5f0; + color: #334155; + border-radius: 8px; + font-weight: 600; + cursor: pointer; +} + +.error { + margin-bottom: 16px; + padding: 12px; + border-radius: 8px; + background-color: #fee2e2; + color: #b91c1c; +} diff --git a/src/app/pages/project-create/project-create.component.html b/src/app/pages/project-create/project-create.component.html new file mode 100644 index 0000000..7709931 --- /dev/null +++ b/src/app/pages/project-create/project-create.component.html @@ -0,0 +1,49 @@ +
+
+

Create project

+ + @if (errorMessage()) { +
{{ errorMessage() }}
+ } + +
+
+ + +
+ +
+ + +
+ +
+ + +
+
+
+
diff --git a/src/app/pages/project-create/project-create.component.ts b/src/app/pages/project-create/project-create.component.ts new file mode 100644 index 0000000..fbb7af7 --- /dev/null +++ b/src/app/pages/project-create/project-create.component.ts @@ -0,0 +1,59 @@ +import { Component, inject, signal } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { Router } from '@angular/router'; +import { ApiService } from '../../services/api.service'; +import { CreateProjectRequest, Project } from '../../models/projects.models'; + +@Component({ + selector: 'app-project-create', + standalone: true, + imports: [CommonModule, FormsModule], + templateUrl: './project-create.component.html', + styleUrl: './project-create.component.css' +}) +export class ProjectCreateComponent { + private apiService = inject(ApiService); + private router = inject(Router); + + name = ''; + description = ''; + isSaving = signal(false); + errorMessage = signal(''); + + onSubmit() { + if (!this.name.trim()) { + this.errorMessage.set('Project name is required.'); + return; + } + + const payload: CreateProjectRequest = { + name: this.name.trim(), + description: this.description.trim() || undefined + }; + + this.isSaving.set(true); + this.errorMessage.set(''); + + this.apiService.post('/projects', payload).subscribe({ + next: (project) => { + this.isSaving.set(false); + if (project?.id != null) { + this.router.navigate(['/projects', project.id]); + } else { + this.router.navigate(['/']); + } + }, + error: (error) => { + this.isSaving.set(false); + this.errorMessage.set( + error?.error?.message || 'Failed to create project. Please try again.' + ); + } + }); + } + + onCancel() { + this.router.navigate(['/']); + } +}