Skip to content
This repository was archived by the owner on Jul 20, 2026. It is now read-only.

Commit 58e4796

Browse files
committed
add a product card with customisable items
1 parent b3f7517 commit 58e4796

11 files changed

Lines changed: 446 additions & 22 deletions
12.6 KB
Loading

frontend/src/app/account.component.html

Lines changed: 17 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -11,64 +11,64 @@ <h2 class="card-title">Account Details</h2>
1111
<div class="info-grid">
1212
<div class="info-field">
1313
<label>First name</label>
14-
<p>Jean</p>
14+
<p>{{ accountForm().first_name }}</p>
1515
</div>
1616
<div class="info-field">
1717
<label>Last name</label>
18-
<p>Dupont</p>
18+
<p>{{ accountForm().last_name }}</p>
1919
</div>
2020
<div class="info-field">
2121
<label>Email</label>
22-
<p>jean.dupont&#64;email.com</p>
22+
<p>{{ accountForm().email }}</p>
2323
</div>
2424
<div class="info-field">
2525
<label>Phone</label>
26-
<p>+33 6 12 34 56 78</p>
26+
<p>{{ accountForm().phone }}</p>
2727
</div>
2828
</div>
2929
</section>
3030

31-
<section class="card">
31+
<form (ngSubmit)="onUpdateAccount()" class="card">
3232
<h2 class="card-title">Update account</h2>
3333
<div class="form-grid">
3434
<div class="form-field">
3535
<label>First name</label>
36-
<input type="text" placeholder="Jean" />
36+
<input type="text" [(ngModel)]="accountForm().first_name" name="first_name" />
3737
</div>
3838
<div class="form-field">
3939
<label>Last name</label>
40-
<input type="text" placeholder="Dupont" />
40+
<input type="text" [(ngModel)]="accountForm().last_name" name="last_name" />
4141
</div>
4242
<div class="form-field">
4343
<label>Email</label>
44-
<input type="email" placeholder="jean.dupont@email.com" />
44+
<input type="email" [(ngModel)]="accountForm().email" name="email" />
4545
</div>
4646
<div class="form-field">
4747
<label>Phone</label>
48-
<input type="text" placeholder="+33 6 12 34 56 78" />
48+
<input type="text" [(ngModel)]="accountForm().phone" name="phone" />
4949
</div>
5050
</div>
51-
<button class="btn-fire">Update</button>
52-
</section>
51+
<button type="submit" class="btn-fire">Update</button>
52+
</form>
5353

54-
<section class="card">
54+
<form (ngSubmit)="onUpdatePassword()" class="card">
5555
<h2 class="card-title">Update password</h2>
5656
<div class="form-grid">
5757
<div class="form-field">
5858
<label>New password</label>
59-
<input type="password" placeholder="••••••••" />
59+
<input type="password" [(ngModel)]="passwordForm().new_password" name="new_password" />
6060
</div>
6161
<div class="form-field">
6262
<label>Confirm new password</label>
63-
<input type="password" placeholder="••••••••" />
63+
<input type="password" [(ngModel)]="passwordForm().confirm_new_password" name="confirm_new_password" />
6464
</div>
6565
<div class="form-field">
6666
<label>Actual password</label>
67-
<input type="password" placeholder="••••••••" />
67+
<input type="password" [(ngModel)]="passwordForm().actual_password" name="actual_password" />
6868
</div>
6969
</div>
70-
<button class="btn-fire">Update</button>
71-
</section>
70+
<button type="submit" class="btn-fire">Update</button>
71+
</form>
7272

7373
</div>
7474
</main>
Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,33 @@
1-
import { Component } from '@angular/core';
1+
import { Component, signal } from '@angular/core';
2+
import { FormsModule } from '@angular/forms';
23

34
@Component({
45
selector: 'app-account',
56
standalone: true,
6-
imports: [],
7+
imports: [FormsModule],
78
templateUrl: './account.component.html',
89
styleUrl: './account.component.scss'
910
})
10-
export class AccountComponent {}
11+
export class AccountComponent {
12+
13+
accountForm = signal({
14+
first_name: 'Jean',
15+
last_name: 'Dupont',
16+
email: 'jean.dupont@gmail.com',
17+
phone:'+33 6 12 34 56 78'
18+
});
19+
20+
passwordForm = signal({
21+
new_password: '',
22+
confirm_new_password: '',
23+
actual_password: ''
24+
});
25+
26+
onUpdateAccount() {
27+
// this.http.post('/route', this.accountForm);
28+
}
29+
30+
onUpdatePassword() {
31+
// this.http.post('/route', this.accountForm);
32+
}
33+
}

frontend/src/app/app.routes.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
import { Routes } from '@angular/router';
22
import { AccountComponent } from './account.component';
33
import { HomeComponent } from './home.component';
4+
import { ShopComponent } from './shop.component';
45

56
export const routes: Routes = [
67
{ path: '', component: HomeComponent},
7-
{ path: 'account', component: AccountComponent}
8+
{ path: 'account', component: AccountComponent},
9+
{ path: 'shop', component: ShopComponent }
810
];
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
<article class="product-card" [class.expanded]="expanded()">
2+
3+
<div class="product-image">
4+
<img [src]="product.image" [alt]="product.name" (error)="fallbackImage($event)" />
5+
</div>
6+
7+
<div class="product-header">
8+
<div>
9+
<h3 class="product-name">{{ product.name }}</h3>
10+
<p class="product-description">{{ product.description }}</p>
11+
</div>
12+
<span class="product-price">{{ totalPrice() }}€</span>
13+
</div>
14+
15+
<button class="toggle-btn" (click)="expanded.set(!expanded())">
16+
{{ expanded() ? 'Fermer' : 'Personnaliser' }}
17+
<span class="arrow" [class.open]="expanded()"></span>
18+
</button>
19+
20+
@if (expanded()) {
21+
<div class="customisation">
22+
23+
<div class="custom-section">
24+
<h4 class="custom-title">Ingrédients</h4>
25+
<div class="chips">
26+
@for (ingredient of ingredients(); track ingredient.id) {
27+
<button
28+
class="chip"
29+
[class.active]="ingredient.included"
30+
(click)="toggleIngredient(ingredient.id)"
31+
>
32+
{{ ingredient.included ? '✓' : '✕' }} {{ ingredient.name }}
33+
</button>
34+
}
35+
</div>
36+
</div>
37+
38+
<div class="custom-section">
39+
<h4 class="custom-title">Taille</h4>
40+
<div class="chips">
41+
@for (size of sizes; track size.id) {
42+
<button
43+
class="chip"
44+
[class.active]="size.selected"
45+
(click)="toggleSize(size.id)"
46+
>
47+
{{ size.name }}
48+
@if (size.price > 0) { <span class="chip-price-add">+{{ size.price.toFixed(2) }}€</span> }
49+
@if (size.price < 0) { <span class="chip-price-rem">{{ size.price.toFixed(2) }}€</span> }
50+
</button>
51+
}
52+
</div>
53+
</div>
54+
55+
<div class="custom-section">
56+
<h4 class="custom-title">Suppléments</h4>
57+
<div class="chips">
58+
@for (extra of supplements; track extra.id) {
59+
<button
60+
class="chip"
61+
[class.active]="extra.selected"
62+
(click)="toggleExtra(extra.id)"
63+
>
64+
{{ extra.name }}
65+
@if (extra.price > 0) { <span class="chip-price">+{{ extra.price.toFixed(2) }}€</span> }
66+
</button>
67+
}
68+
</div>
69+
</div>
70+
71+
<div class="custom-section">
72+
<h4 class="custom-title">Sauces</h4>
73+
<div class="chips">
74+
@for (sauce of sauces; track sauce.id) {
75+
<button
76+
class="chip"
77+
[class.active]="sauce.selected"
78+
(click)="toggleExtra(sauce.id)"
79+
>
80+
{{ sauce.name }}
81+
</button>
82+
}
83+
</div>
84+
</div>
85+
86+
</div>
87+
}
88+
<button class="btn-fire">Ajouter au panier — {{ totalPrice() }}€</button>
89+
90+
</article>
Lines changed: 176 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,176 @@
1+
@import url('https://fonts.googleapis.com/css2?family=Bebas+Neue&family=DM+Sans:wght@400;500&display=swap');
2+
3+
$fire: #E8390E;
4+
$coal: #1A1208;
5+
$ash: #2E2416;
6+
$cream: #F5ECD7;
7+
$muted: #9C8E7A;
8+
9+
.product-card {
10+
background: $coal;
11+
border: 1px solid $ash;
12+
border-radius: 6px;
13+
overflow: hidden;
14+
font-family: 'DM Sans', sans-serif;
15+
color: $cream;
16+
transition: border-color 0.2s;
17+
18+
&.expanded {
19+
border-color: $fire;
20+
}
21+
}
22+
23+
.product-image {
24+
height: 180px;
25+
overflow: hidden;
26+
background: $ash;
27+
28+
img {
29+
width: 100%;
30+
height: 100%;
31+
object-fit: cover;
32+
display: block;
33+
transition: transform 0.3s;
34+
}
35+
36+
&:hover img { transform: scale(1.04); }
37+
}
38+
39+
.product-header {
40+
display: flex;
41+
justify-content: space-between;
42+
align-items: flex-start;
43+
gap: 1rem;
44+
padding: 1rem 1rem 0.5rem;
45+
}
46+
47+
.product-name {
48+
font-family: 'Bebas Neue', sans-serif;
49+
font-size: 1.3rem;
50+
letter-spacing: 0.05em;
51+
margin: 0 0 0.25rem;
52+
color: $cream;
53+
}
54+
55+
.product-description {
56+
font-size: 0.82rem;
57+
color: $muted;
58+
margin: 0;
59+
line-height: 1.5;
60+
}
61+
62+
.product-price {
63+
font-family: 'Bebas Neue', sans-serif;
64+
font-size: 1.4rem;
65+
color: $fire;
66+
white-space: nowrap;
67+
}
68+
69+
.toggle-btn {
70+
width: 100%;
71+
background: none;
72+
border: none;
73+
border-top: 1px solid $ash;
74+
color: $muted;
75+
font-family: 'DM Sans', sans-serif;
76+
font-size: 0.85rem;
77+
padding: 0.6rem 1rem;
78+
cursor: pointer;
79+
text-align: left;
80+
display: flex;
81+
justify-content: space-between;
82+
align-items: center;
83+
transition: color 0.2s;
84+
margin-top: 0.5rem;
85+
86+
&:hover { color: $cream; }
87+
88+
.arrow {
89+
transition: transform 0.2s;
90+
&.open { transform: rotate(180deg); }
91+
}
92+
}
93+
94+
.customisation {
95+
padding: 1rem;
96+
border-top: 1px solid $ash;
97+
display: flex;
98+
flex-direction: column;
99+
gap: 1.25rem;
100+
}
101+
102+
.custom-section {
103+
display: flex;
104+
flex-direction: column;
105+
gap: 0.5rem;
106+
}
107+
108+
.custom-title {
109+
font-size: 0.72rem;
110+
letter-spacing: 0.1em;
111+
text-transform: uppercase;
112+
color: $muted;
113+
margin: 0;
114+
}
115+
116+
.chips {
117+
display: flex;
118+
flex-wrap: wrap;
119+
gap: 0.4rem;
120+
}
121+
122+
.chip {
123+
background: $ash;
124+
border: 1px solid lighten($ash, 8%);
125+
border-radius: 99px;
126+
color: $muted;
127+
font-family: 'DM Sans', sans-serif;
128+
font-size: 0.8rem;
129+
padding: 0.3rem 0.75rem;
130+
cursor: pointer;
131+
transition: all 0.15s;
132+
display: flex;
133+
align-items: center;
134+
gap: 0.3rem;
135+
136+
&:hover {
137+
border-color: $cream;
138+
color: $cream;
139+
}
140+
141+
&.active {
142+
background: rgba($fire, 0.15);
143+
border-color: $fire;
144+
color: $cream;
145+
}
146+
}
147+
148+
.chip-price-add {
149+
font-size: 0.72rem;
150+
color: $fire;
151+
}
152+
153+
.chip-price-rem {
154+
font-size: 0.72rem;
155+
color: $muted;
156+
}
157+
158+
.btn-fire {
159+
background: $fire;
160+
color: $cream;
161+
border: none;
162+
border-radius: 3px;
163+
padding: 0.75rem 1.5rem;
164+
font-family: 'DM Sans', sans-serif;
165+
font-size: 0.9rem;
166+
font-weight: 500;
167+
letter-spacing: 0.04em;
168+
text-transform: uppercase;
169+
cursor: pointer;
170+
width: 100%;
171+
transition: background 0.2s, transform 0.15s;
172+
173+
&:hover {
174+
background: darken($fire, 8%);
175+
}
176+
}

0 commit comments

Comments
 (0)