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" />
|
<app-go-back route="/" text="Home" />
|
||||||
<div class="grid grid-cols-3 gap-x-10">
|
<div class="grid grid-cols-3 gap-x-10">
|
||||||
<div class="col-span-2 flex flex-col space-y-4">
|
<div class="col-span-2 flex flex-col space-y-4">
|
||||||
<app-address-select />
|
@for (address of addresses(); track address.id) {
|
||||||
<app-address-select />
|
<app-address-select [address]="address" (addressUpdated)="updateAddress($event)" />
|
||||||
<app-address-select />
|
}
|
||||||
<app-address-form />
|
<app-address-form (submitAddress)="createNewAddress($event)" />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<app-order-summery />
|
<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 { AddressForm } from "../components/address-form/address-form";
|
||||||
import { GoBack } from "@app/shared/components/go-back/go-back";
|
import { GoBack } from "@app/shared/components/go-back/go-back";
|
||||||
import { AddressSelect } from "../components/address-select/address-select";
|
import { AddressSelect } from "../components/address-select/address-select";
|
||||||
import { OrderSummery } from "../components/order-summery/order-summery";
|
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({
|
@Component({
|
||||||
selector: "app-address",
|
selector: "app-address",
|
||||||
@ -13,6 +20,41 @@ import { AddressService } from "@app/features/checkout/services/address-service"
|
|||||||
})
|
})
|
||||||
export class Address implements OnInit {
|
export class Address implements OnInit {
|
||||||
addressService = inject(AddressService);
|
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>
|
<details class="card p-0!" title="Click to add a new address">
|
||||||
<summary class="p-4">
|
<summary class="p-6">
|
||||||
<label for="currentAddress" class="font-medium text-gray-600 ml-2">Add new address</label>
|
<label for="currentAddress" class="font-medium text-gray-600 ml-2"
|
||||||
|
>{{isEditing() ? 'Update address' : 'Add new address'}}</label
|
||||||
|
>
|
||||||
</summary>
|
</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="flex space-x-4 w-full">
|
||||||
<fieldset class="fieldset w-full">
|
<fieldset class="fieldset w-full">
|
||||||
<legend class="fieldset-legend">First Name</legend>
|
<legend class="fieldset-legend">First Name</legend>
|
||||||
@ -17,13 +23,8 @@
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset class="fieldset w-full">
|
<fieldset class="fieldset w-full">
|
||||||
<legend class="fieldset-legend">Street Address</legend>
|
<legend class="fieldset-legend">Street Address</legend>
|
||||||
<input
|
<input type="text" class="input" formControlName="street" placeholder="Your street address" />
|
||||||
type="text"
|
<app-error fieldName="Street address" [control]="addressForm.get('street')" />
|
||||||
class="input"
|
|
||||||
formControlName="streetAddress"
|
|
||||||
placeholder="Your street address"
|
|
||||||
/>
|
|
||||||
<app-error fieldName="Street address" [control]="addressForm.get('streetAddress')" />
|
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<fieldset class="flex space-x-4 w-full">
|
<fieldset class="flex space-x-4 w-full">
|
||||||
<fieldset class="fieldset w-full">
|
<fieldset class="fieldset w-full">
|
||||||
@ -43,8 +44,12 @@
|
|||||||
</fieldset>
|
</fieldset>
|
||||||
</fieldset>
|
</fieldset>
|
||||||
<div class="ml-auto flex space-x-4">
|
<div class="ml-auto flex space-x-4">
|
||||||
<button type="button" class="btn btn-ghost px-3 text-sm">Cancel</button>
|
<button type="button" (click)="cancelEditing()" class="btn btn-ghost px-3 text-sm">
|
||||||
<button class="btn btn-primary px-3 text-sm">Use this address</button>
|
Cancel
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-primary px-3 text-sm">
|
||||||
|
{{isEditing() ? 'Update this address' : 'Use this address'}}
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</details>
|
</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 { FormControl, FormGroup, ReactiveFormsModule, Validators } from "@angular/forms";
|
||||||
import { Error } from "@app/shared/components/error/error";
|
import { Error } from "@app/shared/components/error/error";
|
||||||
|
import { AddressRequest, AddressResponse } from "@app/features/checkout/services/address-service";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: "app-address-form",
|
selector: "app-address-form",
|
||||||
@ -9,12 +10,55 @@ import { Error } from "@app/shared/components/error/error";
|
|||||||
styleUrl: "./address-form.css",
|
styleUrl: "./address-form.css",
|
||||||
})
|
})
|
||||||
export class AddressForm {
|
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({
|
addressForm = new FormGroup({
|
||||||
firstName: new FormControl("", { validators: Validators.required }),
|
firstName: new FormControl("", {
|
||||||
lastName: new FormControl("", { validators: Validators.required }),
|
validators: [Validators.required, Validators.pattern("^[a-zA-Z]\\S+$")],
|
||||||
streetAddress: new FormControl("", { validators: Validators.required }),
|
}),
|
||||||
|
lastName: new FormControl("", {
|
||||||
|
validators: [Validators.required, Validators.pattern("^[a-zA-Z]\\S+$")],
|
||||||
|
}),
|
||||||
|
street: new FormControl("", { validators: Validators.required }),
|
||||||
city: new FormControl("", { validators: Validators.required }),
|
city: new FormControl("", { validators: Validators.required }),
|
||||||
state: 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 justify-between card">
|
||||||
<div class="flex space-x-4 items-center">
|
<div class="flex space-x-4 items-center">
|
||||||
<input type="radio" name="address" />
|
<input type="radio" name="address" />
|
||||||
<p class="text-gray-600 font-medium">Kushal Saha</p>
|
<p class="text-gray-600 font-medium">{{[address.firstName, address.lastName] | fullname}}</p>
|
||||||
<p class="text-gray-400 text-sm">48 St, Park Avenue, New Towm, 700021</p>
|
<p class="text-gray-400 text-sm">
|
||||||
|
{{`${address.street}, ${address.city}, ${address.pinCode}`}}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<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>
|
||||||
</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({
|
@Component({
|
||||||
selector: "app-address-select",
|
selector: "app-address-select",
|
||||||
imports: [],
|
imports: [FullnamePipe, AddressForm],
|
||||||
templateUrl: "./address-select.html",
|
templateUrl: "./address-select.html",
|
||||||
styleUrl: "./address-select.css",
|
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 { inject, Injectable } from "@angular/core";
|
||||||
import { HttpClient } from "@angular/common/http";
|
import { HttpClient } from "@angular/common/http";
|
||||||
import { API_URL } from "@core/tokens/api-url-tokens";
|
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({
|
@Injectable({
|
||||||
providedIn: "root",
|
providedIn: "root",
|
||||||
@ -10,6 +24,20 @@ export class AddressService {
|
|||||||
apiUrl = inject(API_URL);
|
apiUrl = inject(API_URL);
|
||||||
|
|
||||||
fetchAddresses(userId: number) {
|
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