wip: checkout page
- add button to go address page in cart ui - add template for address page
This commit is contained in:
parent
50c956c051
commit
6d1cb81e6b
3
.phpactor.json
Normal file
3
.phpactor.json
Normal file
@ -0,0 +1,3 @@
|
||||
{
|
||||
"indexer.exclude_patterns": ["/node_modules/**/*", "/backend/**/*"]
|
||||
}
|
||||
@ -19,4 +19,9 @@ export const routes: Routes = [
|
||||
canActivate: [authGuard, roleGuard],
|
||||
data: { roles: ["admin", "broker"] },
|
||||
},
|
||||
{
|
||||
path: "checkout",
|
||||
loadChildren: () =>
|
||||
import("./features/checkout/checkout.routes").then((routes) => routes.checkoutRoutes),
|
||||
},
|
||||
];
|
||||
|
||||
@ -20,14 +20,14 @@
|
||||
<div class="flex space-x-4">
|
||||
<div class="flex text-gray-600">
|
||||
<button
|
||||
class="btn btn-ghost py-1 px-2 rounded-r-none!"
|
||||
class="btn btn-ghost py-1 px-3 rounded-r-none!"
|
||||
popovertarget="popover-1"
|
||||
style="anchor-name: --anchor-1"
|
||||
>
|
||||
<lucide-angular [img]="UserIcon" class="w-5" />
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-ghost py-1 px-2 rounded-l-none! border-l-0! relative"
|
||||
class="btn btn-ghost py-1 px-3 rounded-l-none! border-l-0! relative"
|
||||
popovertarget="popover-2"
|
||||
style="anchor-name: --anchor-2"
|
||||
>
|
||||
@ -40,11 +40,11 @@
|
||||
|
||||
<ul class="dropdown" id="popover-1" popover style="position-anchor: --anchor-1">
|
||||
@if (authService.authState() === AuthState.Unauthenticated) {
|
||||
<li><a class="block h-full w-full" routerLink="/login">Login</a></li>
|
||||
<li><a class="block h-full w-full" routerLink="/login">Login</a></li>
|
||||
} @else if (authService.authState() === AuthState.Loading) {
|
||||
<li><a class="block h-full w-full">Loading</a></li>
|
||||
<li><a class="block h-full w-full">Loading</a></li>
|
||||
} @else {
|
||||
<li><a class="block h-full w-full" routerLink="/logout">Logout</a></li>
|
||||
<li><a class="block h-full w-full" routerLink="/logout">Logout</a></li>
|
||||
}
|
||||
<li><a class="block h-full w-full" href="">My Account</a></li>
|
||||
<li><a class="block h-full w-full" href="">Orders</a></li>
|
||||
|
||||
0
src/app/features/checkout/address/address.css
Normal file
0
src/app/features/checkout/address/address.css
Normal file
16
src/app/features/checkout/address/address.html
Normal file
16
src/app/features/checkout/address/address.html
Normal file
@ -0,0 +1,16 @@
|
||||
<section class="my-10">
|
||||
<div class="">
|
||||
<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 />
|
||||
</div>
|
||||
<div>
|
||||
<app-order-summery />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
22
src/app/features/checkout/address/address.spec.ts
Normal file
22
src/app/features/checkout/address/address.spec.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
|
||||
import { Address } from "./address";
|
||||
|
||||
describe("Address", () => {
|
||||
let component: Address;
|
||||
let fixture: ComponentFixture<Address>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [Address],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(Address);
|
||||
component = fixture.componentInstance;
|
||||
await fixture.whenStable();
|
||||
});
|
||||
|
||||
it("should create", () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
13
src/app/features/checkout/address/address.ts
Normal file
13
src/app/features/checkout/address/address.ts
Normal file
@ -0,0 +1,13 @@
|
||||
import { Component } 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";
|
||||
|
||||
@Component({
|
||||
selector: "app-address",
|
||||
imports: [AddressSelect, AddressForm, GoBack, OrderSummery],
|
||||
templateUrl: "./address.html",
|
||||
styleUrl: "./address.css",
|
||||
})
|
||||
export class Address {}
|
||||
0
src/app/features/checkout/checkout.css
Normal file
0
src/app/features/checkout/checkout.css
Normal file
6
src/app/features/checkout/checkout.html
Normal file
6
src/app/features/checkout/checkout.html
Normal file
@ -0,0 +1,6 @@
|
||||
<section class="wrapper my-10">
|
||||
<div class="wrapper grid place-content-center w-full">
|
||||
<app-stepper [steps]="steps" [currentStep]="1" />
|
||||
</div>
|
||||
<router-outlet />
|
||||
</section>
|
||||
16
src/app/features/checkout/checkout.routes.ts
Normal file
16
src/app/features/checkout/checkout.routes.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { Routes } from "@angular/router";
|
||||
import { Address } from "./address/address";
|
||||
import { Checkout } from "./checkout";
|
||||
|
||||
export const checkoutRoutes: Routes = [
|
||||
{
|
||||
path: "",
|
||||
component: Checkout,
|
||||
children: [
|
||||
{
|
||||
path: "address/:cartId",
|
||||
component: Address,
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
22
src/app/features/checkout/checkout.spec.ts
Normal file
22
src/app/features/checkout/checkout.spec.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
|
||||
import { Checkout } from "./checkout";
|
||||
|
||||
describe("Checkout", () => {
|
||||
let component: Checkout;
|
||||
let fixture: ComponentFixture<Checkout>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [Checkout],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(Checkout);
|
||||
component = fixture.componentInstance;
|
||||
await fixture.whenStable();
|
||||
});
|
||||
|
||||
it("should create", () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
18
src/app/features/checkout/checkout.ts
Normal file
18
src/app/features/checkout/checkout.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { RouterOutlet } from "@angular/router";
|
||||
import { Stepper, Steps } from "@app/shared/components/stepper/stepper";
|
||||
|
||||
@Component({
|
||||
selector: "app-checkout",
|
||||
imports: [RouterOutlet, Stepper],
|
||||
templateUrl: "./checkout.html",
|
||||
styleUrl: "./checkout.css",
|
||||
})
|
||||
export class Checkout {
|
||||
steps: Steps[] = [
|
||||
{ label: "Cart" },
|
||||
{ label: "Address" },
|
||||
{ label: "Payment" },
|
||||
{ label: "Confirm" },
|
||||
];
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
<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>
|
||||
</summary>
|
||||
<form [formGroup]="addressForm" 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>
|
||||
<input type="text" formControlName="firstName" class="input" placeholder="Example: Jhon" />
|
||||
<app-error fieldName="First name" [control]="addressForm.get('firstName')" />
|
||||
</fieldset>
|
||||
<fieldset class="fieldset w-full">
|
||||
<legend class="fieldset-legend">Last Name</legend>
|
||||
<input type="text" class="input" formControlName="lastName" placeholder="Example: Doe" />
|
||||
<app-error fieldName="Last name" [control]="addressForm.get('lastName')" />
|
||||
</fieldset>
|
||||
</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')" />
|
||||
</fieldset>
|
||||
<fieldset class="flex space-x-4 w-full">
|
||||
<fieldset class="fieldset w-full">
|
||||
<legend class="fieldset-legend">City</legend>
|
||||
<input type="text" class="input" formControlName="city" placeholder="Your city" />
|
||||
<app-error fieldName="City" [control]="addressForm.get('city')" />
|
||||
</fieldset>
|
||||
<fieldset class="fieldset w-full">
|
||||
<legend class="fieldset-legend">State</legend>
|
||||
<input type="text" class="input" formControlName="state" placeholder="State Name" />
|
||||
<app-error fieldName="State" [control]="addressForm.get('state')" />
|
||||
</fieldset>
|
||||
<fieldset class="fieldset w-full">
|
||||
<legend class="fieldset-legend">Pin Code</legend>
|
||||
<input type="text" class="input" formControlName="pinCode" placeholder="7XX XX1" />
|
||||
<app-error fieldName="Pin Code" [control]="addressForm.get('pinCode')" />
|
||||
</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>
|
||||
</div>
|
||||
</form>
|
||||
</details>
|
||||
@ -0,0 +1,22 @@
|
||||
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
|
||||
import { AddressForm } from "./address-form";
|
||||
|
||||
describe("AddressForm", () => {
|
||||
let component: AddressForm;
|
||||
let fixture: ComponentFixture<AddressForm>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [AddressForm],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(AddressForm);
|
||||
component = fixture.componentInstance;
|
||||
await fixture.whenStable();
|
||||
});
|
||||
|
||||
it("should create", () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,20 @@
|
||||
import { Component } from "@angular/core";
|
||||
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from "@angular/forms";
|
||||
import { Error } from "@app/shared/components/error/error";
|
||||
|
||||
@Component({
|
||||
selector: "app-address-form",
|
||||
imports: [ReactiveFormsModule, Error],
|
||||
templateUrl: "./address-form.html",
|
||||
styleUrl: "./address-form.css",
|
||||
})
|
||||
export class AddressForm {
|
||||
addressForm = new FormGroup({
|
||||
firstName: new FormControl("", { validators: Validators.required }),
|
||||
lastName: new FormControl("", { validators: Validators.required }),
|
||||
streetAddress: new FormControl("", { validators: Validators.required }),
|
||||
city: new FormControl("", { validators: Validators.required }),
|
||||
state: new FormControl("", { validators: Validators.required }),
|
||||
pinCode: new FormControl("", { validators: Validators.required }),
|
||||
});
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
<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>
|
||||
</div>
|
||||
<div>
|
||||
<button class="btn btn-ghost text-sm px-2">Edit</button>
|
||||
</div>
|
||||
</div>
|
||||
@ -0,0 +1,22 @@
|
||||
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
|
||||
import { AddressSelect } from "./address-select";
|
||||
|
||||
describe("AddressSelect", () => {
|
||||
let component: AddressSelect;
|
||||
let fixture: ComponentFixture<AddressSelect>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [AddressSelect],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(AddressSelect);
|
||||
component = fixture.componentInstance;
|
||||
await fixture.whenStable();
|
||||
});
|
||||
|
||||
it("should create", () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,9 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
@Component({
|
||||
selector: "app-address-select",
|
||||
imports: [],
|
||||
templateUrl: "./address-select.html",
|
||||
styleUrl: "./address-select.css",
|
||||
})
|
||||
export class AddressSelect {}
|
||||
@ -0,0 +1,3 @@
|
||||
<div class="card h-155">
|
||||
<p class="text-gray-600 font-medium">Order Summery</p>
|
||||
</div>
|
||||
@ -0,0 +1,22 @@
|
||||
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
|
||||
import { OrderSummery } from "./order-summery";
|
||||
|
||||
describe("OrderSummery", () => {
|
||||
let component: OrderSummery;
|
||||
let fixture: ComponentFixture<OrderSummery>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [OrderSummery],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(OrderSummery);
|
||||
component = fixture.componentInstance;
|
||||
await fixture.whenStable();
|
||||
});
|
||||
|
||||
it("should create", () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,9 @@
|
||||
import { Component } from "@angular/core";
|
||||
|
||||
@Component({
|
||||
selector: "app-order-summery",
|
||||
imports: [],
|
||||
templateUrl: "./order-summery.html",
|
||||
styleUrl: "./order-summery.css",
|
||||
})
|
||||
export class OrderSummery {}
|
||||
16
src/app/features/checkout/services/address-service.spec.ts
Normal file
16
src/app/features/checkout/services/address-service.spec.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import { TestBed } from "@angular/core/testing";
|
||||
|
||||
import { AddressService } from "./address-service";
|
||||
|
||||
describe("AddressService", () => {
|
||||
let service: AddressService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(AddressService);
|
||||
});
|
||||
|
||||
it("should be created", () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
6
src/app/features/checkout/services/address-service.ts
Normal file
6
src/app/features/checkout/services/address-service.ts
Normal file
@ -0,0 +1,6 @@
|
||||
import { Injectable } from "@angular/core";
|
||||
|
||||
@Injectable({
|
||||
providedIn: "root",
|
||||
})
|
||||
export class AddressService {}
|
||||
@ -24,3 +24,6 @@
|
||||
</div>
|
||||
}
|
||||
</ul>
|
||||
<a [routerLink]="`/checkout/address/${cart.id}`" class="btn btn-primary px-4"
|
||||
>Proceed to checkout</a
|
||||
>
|
||||
|
||||
@ -4,10 +4,11 @@ import { CartItem } from "../cart-item/cart-item";
|
||||
import { AuthService, AuthState } from "@app/features/auth/services/auth-service";
|
||||
import { CartService } from "@app/core/services/cart-service";
|
||||
import { finalize, tap } from "rxjs";
|
||||
import { RouterLink } from "@angular/router";
|
||||
|
||||
@Component({
|
||||
selector: "app-cart",
|
||||
imports: [CartItem],
|
||||
imports: [CartItem, RouterLink],
|
||||
templateUrl: "./cart.html",
|
||||
styleUrl: "./cart.css",
|
||||
})
|
||||
|
||||
0
src/app/shared/components/go-back/go-back.css
Normal file
0
src/app/shared/components/go-back/go-back.css
Normal file
4
src/app/shared/components/go-back/go-back.html
Normal file
4
src/app/shared/components/go-back/go-back.html
Normal file
@ -0,0 +1,4 @@
|
||||
<a [routerLink]="route" class="flex space-x-2 my-4 text-gray-600 hover:text-blue-500 text-sm">
|
||||
<lucide-angular [img]="MoveLeftIcon" class="w-4" />
|
||||
<p class="font-medium">{{ text }}</p>
|
||||
</a>
|
||||
22
src/app/shared/components/go-back/go-back.spec.ts
Normal file
22
src/app/shared/components/go-back/go-back.spec.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
|
||||
import { GoBack } from "./go-back";
|
||||
|
||||
describe("GoBack", () => {
|
||||
let component: GoBack;
|
||||
let fixture: ComponentFixture<GoBack>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [GoBack],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(GoBack);
|
||||
component = fixture.componentInstance;
|
||||
await fixture.whenStable();
|
||||
});
|
||||
|
||||
it("should create", () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
15
src/app/shared/components/go-back/go-back.ts
Normal file
15
src/app/shared/components/go-back/go-back.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import { Component, Input } from "@angular/core";
|
||||
import { RouterLink } from "@angular/router";
|
||||
import { LucideAngularModule, MoveLeft } from "lucide-angular";
|
||||
|
||||
@Component({
|
||||
selector: "app-go-back",
|
||||
imports: [RouterLink, LucideAngularModule],
|
||||
templateUrl: "./go-back.html",
|
||||
styleUrl: "./go-back.css",
|
||||
})
|
||||
export class GoBack {
|
||||
@Input() route: string = "#";
|
||||
@Input() text: string = "";
|
||||
MoveLeftIcon = MoveLeft;
|
||||
}
|
||||
0
src/app/shared/components/stepper/stepper.css
Normal file
0
src/app/shared/components/stepper/stepper.css
Normal file
29
src/app/shared/components/stepper/stepper.html
Normal file
29
src/app/shared/components/stepper/stepper.html
Normal file
@ -0,0 +1,29 @@
|
||||
<ol class="flex">
|
||||
@for (step of steps; track step) {
|
||||
<li class="flex flex-col items-start">
|
||||
<div class="flex items-center">
|
||||
<button
|
||||
[class.bg-blue-600]="$index <= currentStep"
|
||||
class="btn py-0! px-1 rounded-full! w-min"
|
||||
>
|
||||
<lucide-angular
|
||||
[class.text-white]="$index <= currentStep"
|
||||
[class.text-gray-400]="$index > currentStep"
|
||||
[img]="$index <= currentStep ? CheckIcon : CirecleIcon"
|
||||
class="w-4"
|
||||
/>
|
||||
</button>
|
||||
|
||||
<!-- Connected line -->
|
||||
@if (!$last) {
|
||||
<hr
|
||||
[class.border-blue-600]="$index < currentStep"
|
||||
[class.border-gray-200]="$index >= currentStep"
|
||||
class="border w-20"
|
||||
/>
|
||||
}
|
||||
</div>
|
||||
<p class="text-xs text-gray-600">{{ step.label }}</p>
|
||||
</li>
|
||||
}
|
||||
</ol>
|
||||
22
src/app/shared/components/stepper/stepper.spec.ts
Normal file
22
src/app/shared/components/stepper/stepper.spec.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { ComponentFixture, TestBed } from "@angular/core/testing";
|
||||
|
||||
import { Stepper } from "./stepper";
|
||||
|
||||
describe("Stepper", () => {
|
||||
let component: Stepper;
|
||||
let fixture: ComponentFixture<Stepper>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
imports: [Stepper],
|
||||
}).compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(Stepper);
|
||||
component = fixture.componentInstance;
|
||||
await fixture.whenStable();
|
||||
});
|
||||
|
||||
it("should create", () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
20
src/app/shared/components/stepper/stepper.ts
Normal file
20
src/app/shared/components/stepper/stepper.ts
Normal file
@ -0,0 +1,20 @@
|
||||
import { Component, Input } from "@angular/core";
|
||||
import { Check, Circle, LucideAngularModule } from "lucide-angular";
|
||||
|
||||
export type Steps = {
|
||||
label: string;
|
||||
};
|
||||
|
||||
@Component({
|
||||
selector: "app-stepper",
|
||||
imports: [LucideAngularModule],
|
||||
templateUrl: "./stepper.html",
|
||||
styleUrl: "./stepper.css",
|
||||
})
|
||||
export class Stepper {
|
||||
@Input() currentStep: number = 0;
|
||||
@Input() steps: Steps[] = [];
|
||||
|
||||
CheckIcon = Check;
|
||||
CirecleIcon = Circle;
|
||||
}
|
||||
@ -13,7 +13,7 @@
|
||||
}
|
||||
|
||||
body {
|
||||
@apply bg-gray-100 antialiased m-0;
|
||||
@apply bg-gray-50 antialiased m-0;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
@ -33,11 +33,11 @@ body {
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
@apply text-blue-100 bg-blue-600 border border-b-3 border-blue-900 hover:bg-blue-700;
|
||||
@apply text-blue-100 bg-blue-600 border-b border-b-3 border-blue-900 hover:bg-blue-700;
|
||||
}
|
||||
|
||||
.card {
|
||||
@apply bg-gray-50 rounded-xl border border-gray-300 hover:border-gray-400 p-4 hover:shadow-xl transition-all duration-300 ease-in-out;
|
||||
@apply bg-white rounded-xl border-2 border-gray-200 p-4;
|
||||
}
|
||||
|
||||
.fieldset {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user