feature: fetch, edit and add address
- fetch existing addresses from api, - user can edit existing address - user can add new address
This commit is contained in:
parent
3059a923b4
commit
24bdfe9cc6
@ -3,10 +3,10 @@
|
||||
<app-go-back route="/" text="Home" />
|
||||
<div class="grid grid-cols-3 gap-x-10">
|
||||
<div class="col-span-2 flex flex-col space-y-4">
|
||||
<app-address-select />
|
||||
<app-address-select />
|
||||
<app-address-select />
|
||||
<app-address-form />
|
||||
@for (address of addresses(); track address.id) {
|
||||
<app-address-select [address]="address" (addressUpdated)="updateAddress($event)" />
|
||||
}
|
||||
<app-address-form (submitAddress)="createNewAddress($event)" />
|
||||
</div>
|
||||
<div>
|
||||
<app-order-summery />
|
||||
|
||||
@ -1,9 +1,16 @@
|
||||
import { Component, inject, OnInit } from "@angular/core";
|
||||
import { Component, inject, OnInit, signal } from "@angular/core";
|
||||
import { AddressForm } from "../components/address-form/address-form";
|
||||
import { GoBack } from "@app/shared/components/go-back/go-back";
|
||||
import { AddressSelect } from "../components/address-select/address-select";
|
||||
import { OrderSummery } from "../components/order-summery/order-summery";
|
||||
import { AddressService } from "@app/features/checkout/services/address-service";
|
||||
import {
|
||||
AddressRequest,
|
||||
AddressResponse,
|
||||
AddressService,
|
||||
} from "@app/features/checkout/services/address-service";
|
||||
import { AuthService } from "@core/services/auth-service";
|
||||
import { User } from "@core/models/user.model";
|
||||
import { switchMap } from "rxjs";
|
||||
|
||||
@Component({
|
||||
selector: "app-address",
|
||||
@ -13,6 +20,41 @@ import { AddressService } from "@app/features/checkout/services/address-service"
|
||||
})
|
||||
export class Address implements OnInit {
|
||||
addressService = inject(AddressService);
|
||||
authService = inject(AuthService);
|
||||
private user: User | undefined;
|
||||
protected addresses = signal<AddressResponse[]>([]);
|
||||
|
||||
ngOnInit() {}
|
||||
ngOnInit(): void {
|
||||
this.authService
|
||||
.getCurrentUser()
|
||||
.pipe(
|
||||
switchMap((user) => {
|
||||
this.user = user;
|
||||
if (user?.id) {
|
||||
return this.addressService.fetchAddresses(user.id);
|
||||
}
|
||||
return [];
|
||||
}),
|
||||
)
|
||||
.subscribe({
|
||||
next: (addresses) => {
|
||||
this.addresses.set(addresses.data);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
createNewAddress(addressData: AddressRequest) {
|
||||
this.addressService.createAddress(this.user!.id, addressData).subscribe({
|
||||
next: (address) => this.addresses.update((addresses) => [...addresses, address]),
|
||||
});
|
||||
}
|
||||
updateAddress(addressData: AddressResponse) {
|
||||
console.log(addressData);
|
||||
this.addressService.updateAddress(addressData.id, addressData).subscribe({
|
||||
next: (address) =>
|
||||
this.addresses.update((addresses) =>
|
||||
addresses.map((a) => (a.id === address.id ? address : a)),
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,14 @@
|
||||
<details class="card p-0!" open>
|
||||
<summary class="p-4">
|
||||
<label for="currentAddress" class="font-medium text-gray-600 ml-2">Add new address</label>
|
||||
<details class="card p-0!" title="Click to add a new address">
|
||||
<summary class="p-6">
|
||||
<label for="currentAddress" class="font-medium text-gray-600 ml-2"
|
||||
>{{isEditing() ? 'Update address' : 'Add new address'}}</label
|
||||
>
|
||||
</summary>
|
||||
<form [formGroup]="addressForm" class="w-full flex flex-col gap-y-2 pt-0 p-4">
|
||||
<form
|
||||
[formGroup]="addressForm"
|
||||
(ngSubmit)="submitForm()"
|
||||
class="w-full flex flex-col gap-y-2 pt-0 p-4"
|
||||
>
|
||||
<fieldset class="flex space-x-4 w-full">
|
||||
<fieldset class="fieldset w-full">
|
||||
<legend class="fieldset-legend">First Name</legend>
|
||||
@ -17,13 +23,8 @@
|
||||
</fieldset>
|
||||
<fieldset class="fieldset w-full">
|
||||
<legend class="fieldset-legend">Street Address</legend>
|
||||
<input
|
||||
type="text"
|
||||
class="input"
|
||||
formControlName="streetAddress"
|
||||
placeholder="Your street address"
|
||||
/>
|
||||
<app-error fieldName="Street address" [control]="addressForm.get('streetAddress')" />
|
||||
<input type="text" class="input" formControlName="street" placeholder="Your street address" />
|
||||
<app-error fieldName="Street address" [control]="addressForm.get('street')" />
|
||||
</fieldset>
|
||||
<fieldset class="flex space-x-4 w-full">
|
||||
<fieldset class="fieldset w-full">
|
||||
@ -43,8 +44,12 @@
|
||||
</fieldset>
|
||||
</fieldset>
|
||||
<div class="ml-auto flex space-x-4">
|
||||
<button type="button" class="btn btn-ghost px-3 text-sm">Cancel</button>
|
||||
<button class="btn btn-primary px-3 text-sm">Use this address</button>
|
||||
<button type="button" (click)="cancelEditing()" class="btn btn-ghost px-3 text-sm">
|
||||
Cancel
|
||||
</button>
|
||||
<button class="btn btn-primary px-3 text-sm">
|
||||
{{isEditing() ? 'Update this address' : 'Use this address'}}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
</details>
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { Component, EventEmitter, Input, Output, signal } from "@angular/core";
|
||||
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from "@angular/forms";
|
||||
import { Error } from "@app/shared/components/error/error";
|
||||
import { AddressRequest, AddressResponse } from "@app/features/checkout/services/address-service";
|
||||
|
||||
@Component({
|
||||
selector: "app-address-form",
|
||||
@ -9,12 +10,55 @@ import { Error } from "@app/shared/components/error/error";
|
||||
styleUrl: "./address-form.css",
|
||||
})
|
||||
export class AddressForm {
|
||||
@Input() set initialData(address: AddressResponse) {
|
||||
if (address) {
|
||||
this.addressForm.patchValue(address);
|
||||
this.address.set(address);
|
||||
this.isEditing.set(true);
|
||||
}
|
||||
}
|
||||
@Output() submitAddress: EventEmitter<AddressRequest> = new EventEmitter<AddressRequest>();
|
||||
@Output() updateAddress: EventEmitter<AddressResponse> = new EventEmitter<AddressResponse>();
|
||||
@Output() editingCanceled: EventEmitter<void> = new EventEmitter<void>();
|
||||
|
||||
protected isEditing = signal(false);
|
||||
protected address = signal<AddressResponse | null>(null);
|
||||
|
||||
addressForm = new FormGroup({
|
||||
firstName: new FormControl("", { validators: Validators.required }),
|
||||
lastName: new FormControl("", { validators: Validators.required }),
|
||||
streetAddress: new FormControl("", { validators: Validators.required }),
|
||||
firstName: new FormControl("", {
|
||||
validators: [Validators.required, Validators.pattern("^[a-zA-Z]\\S+$")],
|
||||
}),
|
||||
lastName: new FormControl("", {
|
||||
validators: [Validators.required, Validators.pattern("^[a-zA-Z]\\S+$")],
|
||||
}),
|
||||
street: new FormControl("", { validators: Validators.required }),
|
||||
city: new FormControl("", { validators: Validators.required }),
|
||||
state: new FormControl("", { validators: Validators.required }),
|
||||
pinCode: new FormControl("", { validators: Validators.required }),
|
||||
pinCode: new FormControl("", {
|
||||
validators: [Validators.required, Validators.pattern("^[0-9]{6}$")],
|
||||
}),
|
||||
});
|
||||
|
||||
submitForm() {
|
||||
if (this.addressForm.invalid) {
|
||||
this.addressForm.markAllAsTouched();
|
||||
return;
|
||||
}
|
||||
|
||||
const emittedData = this.addressForm.getRawValue() as AddressRequest;
|
||||
this.addressForm.reset();
|
||||
|
||||
if (this.isEditing()) {
|
||||
const mergedData = { ...this.address(), ...emittedData };
|
||||
console.log(mergedData);
|
||||
this.updateAddress.emit(mergedData as unknown as AddressResponse);
|
||||
} else {
|
||||
this.submitAddress.emit(emittedData);
|
||||
}
|
||||
}
|
||||
|
||||
cancelEditing() {
|
||||
this.addressForm.reset();
|
||||
this.editingCanceled.emit();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,10 +1,20 @@
|
||||
@if (!isEditing()) {
|
||||
<div class="flex justify-between card">
|
||||
<div class="flex space-x-4 items-center">
|
||||
<input type="radio" name="address" />
|
||||
<p class="text-gray-600 font-medium">Kushal Saha</p>
|
||||
<p class="text-gray-400 text-sm">48 St, Park Avenue, New Towm, 700021</p>
|
||||
<p class="text-gray-600 font-medium">{{[address.firstName, address.lastName] | fullname}}</p>
|
||||
<p class="text-gray-400 text-sm">
|
||||
{{`${address.street}, ${address.city}, ${address.pinCode}`}}
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-ghost text-sm px-2">Edit</button>
|
||||
<button (click)="editForm()" class="btn btn-ghost text-sm px-2">Edit</button>
|
||||
</div>
|
||||
</div>
|
||||
} @else{
|
||||
<app-address-form
|
||||
[initialData]="address"
|
||||
(editingCanceled)="cancelEditing()"
|
||||
(updateAddress)="updateAddress($event)"
|
||||
/>
|
||||
}
|
||||
|
||||
@ -1,9 +1,30 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { Component, EventEmitter, Input, Output, signal } from "@angular/core";
|
||||
import { AddressResponse } from "@app/features/checkout/services/address-service";
|
||||
import { FullnamePipe } from "@shared/pipes/fullname-pipe";
|
||||
import { AddressForm } from "@app/features/checkout/components/address-form/address-form";
|
||||
|
||||
@Component({
|
||||
selector: "app-address-select",
|
||||
imports: [],
|
||||
imports: [FullnamePipe, AddressForm],
|
||||
templateUrl: "./address-select.html",
|
||||
styleUrl: "./address-select.css",
|
||||
})
|
||||
export class AddressSelect {}
|
||||
export class AddressSelect {
|
||||
@Input() address!: AddressResponse;
|
||||
@Output() addressUpdated: EventEmitter<AddressResponse> = new EventEmitter<AddressResponse>();
|
||||
|
||||
protected isEditing = signal(false);
|
||||
|
||||
editForm() {
|
||||
this.isEditing.set(true);
|
||||
}
|
||||
|
||||
cancelEditing() {
|
||||
this.isEditing.set(false);
|
||||
}
|
||||
|
||||
updateAddress(address: AddressResponse) {
|
||||
this.isEditing.set(false);
|
||||
this.addressUpdated.emit(address);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,20 @@
|
||||
import { inject, Injectable } from "@angular/core";
|
||||
import { HttpClient } from "@angular/common/http";
|
||||
import { API_URL } from "@core/tokens/api-url-tokens";
|
||||
import { PaginatedResponse } from "@core/models/paginated.model";
|
||||
|
||||
export interface AddressRequest {
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
street: string;
|
||||
city: string;
|
||||
state: string;
|
||||
pinCode: string;
|
||||
}
|
||||
|
||||
export interface AddressResponse extends AddressRequest {
|
||||
id: number;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: "root",
|
||||
@ -10,6 +24,20 @@ export class AddressService {
|
||||
apiUrl = inject(API_URL);
|
||||
|
||||
fetchAddresses(userId: number) {
|
||||
return this.http.get(`${this.apiUrl}/user/${userId}/addresses`);
|
||||
return this.http.get<PaginatedResponse<AddressResponse>>(
|
||||
`${this.apiUrl}/user/${userId}/addresses`,
|
||||
);
|
||||
}
|
||||
|
||||
createAddress(userId: number, data: AddressRequest) {
|
||||
return this.http.post<AddressResponse>(`${this.apiUrl}/user/${userId}/addresses`, data);
|
||||
}
|
||||
updateAddress(addressId: number, data: AddressRequest) {
|
||||
return this.http.patch<AddressResponse>(`${this.apiUrl}/addresses/${addressId}`, data);
|
||||
}
|
||||
deleteAddress(userId: number, addressId: number) {
|
||||
return this.http.delete<AddressResponse>(
|
||||
`${this.apiUrl}/user/${userId}/addresses/${addressId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
8
src/app/shared/pipes/fullname-pipe.spec.ts
Normal file
8
src/app/shared/pipes/fullname-pipe.spec.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { FullnamePipe } from "./fullname-pipe";
|
||||
|
||||
describe("FullnamePipe", () => {
|
||||
it("create an instance", () => {
|
||||
const pipe = new FullnamePipe();
|
||||
expect(pipe).toBeTruthy();
|
||||
});
|
||||
});
|
||||
13
src/app/shared/pipes/fullname-pipe.ts
Normal file
13
src/app/shared/pipes/fullname-pipe.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { Pipe, PipeTransform } from "@angular/core";
|
||||
import { TitleCasePipe } from "@angular/common";
|
||||
|
||||
@Pipe({
|
||||
name: "fullname",
|
||||
})
|
||||
export class FullnamePipe implements PipeTransform {
|
||||
titlecase = new TitleCasePipe();
|
||||
|
||||
transform(values: string[]): unknown {
|
||||
return this.titlecase.transform(values.join(" "));
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user