Skip to content

Commit 18a24ac

Browse files
authored
Merge pull request #7 from otrocadev/refactor/manage-the-core-from-service
Refactor/manage the core from service
2 parents d97a543 + cc9f8b3 commit 18a24ac

30 files changed

+998
-351
lines changed

src/app/app.component.html

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,5 @@
11
<app-header></app-header>
2-
<app-budget-calculator></app-budget-calculator>
2+
<main>
3+
<app-budget-calculator></app-budget-calculator>
4+
<app-budget-application-form></app-budget-application-form>
5+
</main>

src/app/app.component.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,15 @@
11
import { Component } from '@angular/core';
22
import { BudgetCalculatorComponent } from './budget-calculator/budget-calculator.component';
33
import { HeaderComponent } from './header/header.component';
4+
import { BudgetApplicationFormComponent } from './budget-application-form/budget-application-form.component';
45

56
@Component({
67
selector: 'app-root',
7-
imports: [BudgetCalculatorComponent, HeaderComponent],
8+
imports: [
9+
BudgetCalculatorComponent,
10+
HeaderComponent,
11+
BudgetApplicationFormComponent,
12+
],
813
templateUrl: './app.component.html',
914
styleUrl: './app.component.css',
1015
})
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
.form-card {
2+
display: flex;
3+
justify-content: center;
4+
max-width: var(--container-max-width);
5+
margin: auto;
6+
align-items: center;
7+
flex-direction: column;
8+
width: 100%;
9+
gap: 2rem;
10+
border-radius: var(--card-border-radius);
11+
}
12+
13+
form {
14+
display: flex;
15+
justify-content: center;
16+
align-items: center;
17+
gap: 1rem;
18+
}
19+
20+
input {
21+
padding: 0.5rem;
22+
border-radius: 0.5rem;
23+
font-family: inherit;
24+
border: 1px solid var(--details-light);
25+
26+
@media (prefers-color-scheme: dark) {
27+
border-color: var(--details-dark);
28+
}
29+
}
30+
31+
.error-status {
32+
border-color: var(--error-color);
33+
}
34+
35+
.error-message {
36+
color: var(--error-color);
37+
font-size: var(--font-size-m);
38+
margin: 0;
39+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<section
2+
class="card form-card"
3+
[ngClass]="
4+
budgetListService.errorStatus()[1] === 'valid' ? '' : 'error-status'
5+
"
6+
>
7+
<h2>Ask for a budget</h2>
8+
<form (ngSubmit)="onSubmit()">
9+
<input
10+
type="text"
11+
id="form_name"
12+
name="name"
13+
placeholder="Name"
14+
[(ngModel)]="formData.name"
15+
/>
16+
<input
17+
type="email"
18+
id="form_email"
19+
name="email"
20+
placeholder="Email"
21+
[(ngModel)]="formData.email"
22+
/>
23+
<input
24+
type="text"
25+
id="form_phone"
26+
name="phone"
27+
placeholder="Phone"
28+
[(ngModel)]="formData.phone"
29+
/>
30+
<button type="submit">Submit</button>
31+
</form>
32+
@if (budgetListService.errorStatus()[1] !== 'valid') {
33+
<p class="error-message">{{ errorStatusMessage() }}</p>
34+
}
35+
</section>
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
import { ComponentFixture, TestBed } from '@angular/core/testing';
2+
3+
import { BudgetApplicationFormComponent } from './budget-application-form.component';
4+
5+
describe('BudgetApplicationFormComponent', () => {
6+
let component: BudgetApplicationFormComponent;
7+
let fixture: ComponentFixture<BudgetApplicationFormComponent>;
8+
9+
beforeEach(async () => {
10+
await TestBed.configureTestingModule({
11+
imports: [BudgetApplicationFormComponent],
12+
}).compileComponents();
13+
14+
fixture = TestBed.createComponent(BudgetApplicationFormComponent);
15+
component = fixture.componentInstance;
16+
fixture.detectChanges();
17+
});
18+
19+
it('should create', () => {
20+
expect(component).toBeTruthy();
21+
});
22+
23+
it('should display an error message when name is empty', () => {
24+
component.formData.name = '';
25+
component.onSubmit();
26+
27+
fixture.detectChanges();
28+
const errorMessage = fixture.nativeElement.querySelector('.error-message');
29+
expect(errorMessage).toBeTruthy();
30+
expect(errorMessage.textContent).toBe('Name is required');
31+
});
32+
33+
it('should display an error message when name is not a string', () => {
34+
// @ts-ignore
35+
component.formData.name = 123;
36+
component.onSubmit();
37+
38+
fixture.detectChanges();
39+
const errorMessage = fixture.nativeElement.querySelector('.error-message');
40+
expect(errorMessage).toBeTruthy();
41+
expect(errorMessage.textContent).toBe('Name must be a string');
42+
});
43+
44+
it('should display an error message when name is not enough characters', () => {
45+
component.formData.name = 'b';
46+
component.onSubmit();
47+
48+
fixture.detectChanges();
49+
const errorMessage = fixture.nativeElement.querySelector('.error-message');
50+
expect(errorMessage).toBeTruthy();
51+
expect(errorMessage.textContent).toBe(
52+
'Name must be at least 2 characters long'
53+
);
54+
});
55+
56+
it('should display an error message when name is not letters only', () => {
57+
component.formData.name = '123';
58+
component.onSubmit();
59+
60+
fixture.detectChanges();
61+
const errorMessage = fixture.nativeElement.querySelector('.error-message');
62+
expect(errorMessage).toBeTruthy();
63+
expect(errorMessage.textContent).toBe('Name must contain only letters');
64+
});
65+
66+
it('should display an error message when email is empty', () => {
67+
component.formData.name = 'John Doe';
68+
component.formData.email = '';
69+
component.onSubmit();
70+
71+
fixture.detectChanges();
72+
const errorMessage = fixture.nativeElement.querySelector('.error-message');
73+
expect(errorMessage).toBeTruthy();
74+
expect(errorMessage.textContent).toBe('Email is required');
75+
});
76+
77+
it('should display an error message when email is not a string', () => {
78+
// @ts-ignore
79+
component.formData.email = 123;
80+
component.formData.name = 'John Doe';
81+
component.onSubmit();
82+
83+
fixture.detectChanges();
84+
const errorMessage = fixture.nativeElement.querySelector('.error-message');
85+
expect(errorMessage).toBeTruthy();
86+
expect(errorMessage.textContent).toBe('Email must be a string');
87+
});
88+
89+
it('should display an error message when email does not contain an @', () => {
90+
component.formData.name = 'John Doe';
91+
component.formData.email = '123';
92+
component.onSubmit();
93+
94+
fixture.detectChanges();
95+
const errorMessage = fixture.nativeElement.querySelector('.error-message');
96+
expect(errorMessage).toBeTruthy();
97+
expect(errorMessage.textContent).toBe('Email must contain @');
98+
});
99+
100+
it('should display an error message when email does not contain a dot after @', () => {
101+
component.formData.name = 'John Doe';
102+
component.formData.email = '123@';
103+
component.onSubmit();
104+
105+
fixture.detectChanges();
106+
const errorMessage = fixture.nativeElement.querySelector('.error-message');
107+
expect(errorMessage).toBeTruthy();
108+
expect(errorMessage.textContent).toBe('Email must contain .');
109+
});
110+
111+
it('should display an error message when email does contain a dot before @ and not after', () => {
112+
component.formData.name = 'John Doe';
113+
component.formData.email = 'john.doe@';
114+
component.onSubmit();
115+
116+
fixture.detectChanges();
117+
const errorMessage = fixture.nativeElement.querySelector('.error-message');
118+
expect(errorMessage).toBeTruthy();
119+
expect(errorMessage.textContent).toBe('Email must contain .');
120+
});
121+
122+
it('should display an error message when phone is empty', () => {
123+
component.formData.name = 'John Doe';
124+
component.formData.email = 'john.doe@gmail.com';
125+
component.formData.phone = '';
126+
component.onSubmit();
127+
128+
fixture.detectChanges();
129+
const errorMessage = fixture.nativeElement.querySelector('.error-message');
130+
expect(errorMessage).toBeTruthy();
131+
expect(errorMessage.textContent).toBe('Phone is required');
132+
});
133+
134+
it('should display an error message when phone is not a string', () => {
135+
// @ts-ignore
136+
component.formData.phone = 123;
137+
component.formData.name = 'John Doe';
138+
component.formData.email = 'john.doe@gmail.com';
139+
component.onSubmit();
140+
141+
fixture.detectChanges();
142+
const errorMessage = fixture.nativeElement.querySelector('.error-message');
143+
expect(errorMessage).toBeTruthy();
144+
expect(errorMessage.textContent).toBe('Phone must be a string');
145+
});
146+
147+
it('should display an error message when phone does not contain only numbers', () => {
148+
component.formData.name = 'John Doe';
149+
component.formData.email = 'john.doe@gmail.com';
150+
component.formData.phone = '123a';
151+
component.onSubmit();
152+
153+
fixture.detectChanges();
154+
const errorMessage = fixture.nativeElement.querySelector('.error-message');
155+
expect(errorMessage).toBeTruthy();
156+
expect(errorMessage.textContent).toBe('Phone must contain only numbers');
157+
});
158+
159+
it('should display an error message when phone does not contain enough characters', () => {
160+
component.formData.name = 'John Doe';
161+
component.formData.email = 'john.doe@gmail.com';
162+
component.formData.phone = '123';
163+
component.onSubmit();
164+
165+
fixture.detectChanges();
166+
const errorMessage = fixture.nativeElement.querySelector('.error-message');
167+
expect(errorMessage).toBeTruthy();
168+
expect(errorMessage.textContent).toBe(
169+
'Phone must be at least 6 characters long'
170+
);
171+
});
172+
173+
it('should display an error message when phone does contain too many characters', () => {
174+
component.formData.name = 'John Doe';
175+
component.formData.email = 'john.doe@gmail.com';
176+
component.formData.phone = '1234567890154545';
177+
component.onSubmit();
178+
179+
fixture.detectChanges();
180+
const errorMessage = fixture.nativeElement.querySelector('.error-message');
181+
expect(errorMessage).toBeTruthy();
182+
expect(errorMessage.textContent).toBe(
183+
'Phone must be at most 9 characters long'
184+
);
185+
});
186+
});
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { Component, inject, signal } from '@angular/core';
2+
import { FormsModule } from '@angular/forms';
3+
import { BudgetListService } from '../../services/budgetListService';
4+
import { BudgetStateService } from '../../services/budgetStatusService';
5+
import type { BudgetService } from '../../types/budgetTypes';
6+
import { ERROR_MESSAGES } from '../../config/errorMessageConfig';
7+
import { NgClass } from '@angular/common';
8+
9+
@Component({
10+
selector: 'app-budget-application-form',
11+
imports: [FormsModule, NgClass],
12+
templateUrl: './budget-application-form.component.html',
13+
styleUrl: './budget-application-form.component.css',
14+
})
15+
export class BudgetApplicationFormComponent {
16+
budgetListService = inject(BudgetListService);
17+
budgetStateService = inject(BudgetStateService);
18+
19+
private services: BudgetService[] = [];
20+
ERROR_MESSAGES = ERROR_MESSAGES;
21+
22+
errorStatusMessage = signal<string | undefined>(undefined);
23+
24+
formData = {
25+
name: '',
26+
email: '',
27+
phone: '',
28+
};
29+
30+
private getErrorStatus() {
31+
this.errorStatusMessage.set(
32+
ERROR_MESSAGES[this.budgetListService.errorStatus()[0]][
33+
this.budgetListService.errorStatus()[1]
34+
]
35+
);
36+
}
37+
38+
private manageServices() {
39+
this.services = [];
40+
this.budgetStateService.services().forEach((service) => {
41+
if (service.selected) {
42+
let newService = {
43+
title: service.title,
44+
secondaryServices: service.secondaryServices,
45+
};
46+
this.services.push(newService);
47+
}
48+
});
49+
}
50+
51+
onSubmit() {
52+
this.manageServices();
53+
this.budgetListService.addBudget({
54+
name: this.formData.name,
55+
email: this.formData.email,
56+
phone: this.formData.phone,
57+
budgetServices: {
58+
services: this.services,
59+
},
60+
total: this.budgetStateService.total(),
61+
});
62+
this.getErrorStatus();
63+
}
64+
}
Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,9 @@
11
<section id="services-calculator">
2-
@for(service of servicesData; track service.title) {
3-
<app-service-card
4-
[service]="service"
5-
(selectedChange)="manageServiceTotals($event)"
6-
></app-service-card>
2+
@for(service of servicesConfig; track service.title) {
3+
<app-service-card [service]="service"></app-service-card>
74
}
85
<span id="total-price"
96
><p>Total:</p>
10-
<p>{{ total }}€</p></span
7+
<p>{{ budgetStateService.total() }}€</p></span
118
>
129
</section>

0 commit comments

Comments
 (0)