From 1a8693eb8a8dd7d7488f53eedd668e1206af29c5 Mon Sep 17 00:00:00 2001 From: Prabath Samarasinghe Date: Wed, 7 May 2025 14:33:19 +0530 Subject: [PATCH 1/2] vendor side pages --- src/app/app.routes.ts | 15 +- .../product-section.component.html | 3 +- .../product-section.component.ts | 11 + .../components/sidebar/sidebar.component.ts | 6 + .../cart-summary/cart-summary.component.css | 0 .../cart-summary/cart-summary.component.html | 313 ++++++++++++++++++ .../cart-summary.component.spec.ts | 23 ++ .../cart-summary/cart-summary.component.ts | 183 ++++++++++ .../vendor-page/cart/cart.component.css | 0 .../vendor-page/cart/cart.component.html | 145 ++++++++ .../vendor-page/cart/cart.component.spec.ts | 23 ++ .../vendor-page/cart/cart.component.ts | 75 +++++ .../order-summary/order-summary.component.css | 0 .../order-summary.component.html | 180 ++++++++++ .../order-summary.component.spec.ts | 23 ++ .../order-summary/order-summary.component.ts | 132 ++++++++ .../vendor-page/orders/orders.component.css | 0 .../vendor-page/orders/orders.component.html | 230 +++++++++++++ .../orders/orders.component.spec.ts | 23 ++ .../vendor-page/orders/orders.component.ts | 228 +++++++++++++ .../vendor-page/vender/vendor.component.css | 0 .../vendor-page/vender/vendor.component.html | 67 ++++ .../vender/vendor.component.spec.ts | 23 ++ .../vendor-page/vender/vendor.component.ts | 15 + 24 files changed, 1716 insertions(+), 2 deletions(-) create mode 100644 src/app/components/vendor-page/cart-summary/cart-summary.component.css create mode 100644 src/app/components/vendor-page/cart-summary/cart-summary.component.html create mode 100644 src/app/components/vendor-page/cart-summary/cart-summary.component.spec.ts create mode 100644 src/app/components/vendor-page/cart-summary/cart-summary.component.ts create mode 100644 src/app/components/vendor-page/cart/cart.component.css create mode 100644 src/app/components/vendor-page/cart/cart.component.html create mode 100644 src/app/components/vendor-page/cart/cart.component.spec.ts create mode 100644 src/app/components/vendor-page/cart/cart.component.ts create mode 100644 src/app/components/vendor-page/order-summary/order-summary.component.css create mode 100644 src/app/components/vendor-page/order-summary/order-summary.component.html create mode 100644 src/app/components/vendor-page/order-summary/order-summary.component.spec.ts create mode 100644 src/app/components/vendor-page/order-summary/order-summary.component.ts create mode 100644 src/app/components/vendor-page/orders/orders.component.css create mode 100644 src/app/components/vendor-page/orders/orders.component.html create mode 100644 src/app/components/vendor-page/orders/orders.component.spec.ts create mode 100644 src/app/components/vendor-page/orders/orders.component.ts create mode 100644 src/app/components/vendor-page/vender/vendor.component.css create mode 100644 src/app/components/vendor-page/vender/vendor.component.html create mode 100644 src/app/components/vendor-page/vender/vendor.component.spec.ts create mode 100644 src/app/components/vendor-page/vender/vendor.component.ts diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 7308758..ec50cf8 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -15,6 +15,12 @@ import { authGuard } from './gaurds/auth.guard' import { roleGuard } from './gaurds/role.guard'; import { VendorSignupComponent } from './components/auth-page/vendor-signup/vendor-signup.component'; import { SupplierSignupComponent } from './components/auth-page/supplier-signup/supplier-signup.component'; +import { VendorComponent } from './components/vendor-page/vender/vendor.component'; +import { ProductSectionComponent } from './components/home-page/product-section/product-section.component'; +import { CartComponent } from './components/vendor-page/cart/cart.component'; +import { OrderSummaryComponent } from './components/vendor-page/order-summary/order-summary.component'; +import { CartSummaryComponent } from './components/vendor-page/cart-summary/cart-summary.component'; +import { OrdersComponent } from './components/vendor-page/orders/orders.component'; export const routes: Routes = [ { @@ -35,7 +41,7 @@ export const routes: Routes = [ { path: 'dashboard', component: DashboardLayoutComponent, - canActivate: [authGuard], + // canActivate: [authGuard], children: [ { path: '', component: ProfileComponent, pathMatch: 'full' }, // /dashboard { path: 'profile', component: ProfileComponent, pathMatch: 'full' }, @@ -51,6 +57,13 @@ export const routes: Routes = [ // { path: 'deliveries', component: DeliveriesComponent }, // /dashboard/deliveries // { path: 'vendors', component: VendorsComponent }, { path: 'supplier', component: SupplierDashboard, pathMatch: 'full' }, + { path: 'vendor', component: VendorComponent, pathMatch: 'full' }, + { path: 'vendor/products', component: ProductSectionComponent, pathMatch: 'full' }, + { path: 'vendor/cart', component: CartComponent, pathMatch: 'full' }, + { path: 'vendor/orders/order-details/:id', component: OrderSummaryComponent, pathMatch: 'full' }, + { path: 'vendor/cart-summery', component: CartSummaryComponent, pathMatch: 'full' }, + { path: 'vendor/cart/cart-summery', component: CartSummaryComponent, pathMatch: 'full' }, + { path: 'vendor/orders', component: OrdersComponent, pathMatch: 'full' }, ] }, { path: '**', redirectTo: 'home' }, // Handle 404/unknown routes diff --git a/src/app/components/home-page/product-section/product-section.component.html b/src/app/components/home-page/product-section/product-section.component.html index b68850d..59291f6 100644 --- a/src/app/components/home-page/product-section/product-section.component.html +++ b/src/app/components/home-page/product-section/product-section.component.html @@ -102,6 +102,7 @@

@@ -110,7 +111,7 @@

+
+ First name is required +
+
+
+ + +
+ Last name is required +
+
+

+ +
+ + +
+ Address is required +
+
+ +
+
+ + +
+ City is required +
+
+
+ + +
+ Country is required +
+
+
+ + +
+ Postal code is required +
+
+
+ +
+ + +
+ Valid phone number is required +
+
+ + + + + +
+
+

+ Order Summary +

+ +
+
+

+ Subtotal ({{ getTotalItems() }} items): + ${{ getSubtotal() }} +

+

+ Shipping: + ${{ shippingCost.toFixed(2) }} +

+

+ Tax ({{ taxRate }}%): + ${{ calculateTax().toFixed(2) }} +

+
+ + +
+
+ + +
+
+ Discount applied: -${{ discountAmount.toFixed(2) }} +
+
+ + +
+ Total: + ${{ getTotal() }} +
+ + + + + +
+
+
+ + + + +
+
+ + + +
+

Your cart is currently empty.

+ Browse Products +
+
+ + + + +
+
+ © 2025 Vendor Ordering System. All rights reserved. +
+
+ \ No newline at end of file diff --git a/src/app/components/vendor-page/cart-summary/cart-summary.component.spec.ts b/src/app/components/vendor-page/cart-summary/cart-summary.component.spec.ts new file mode 100644 index 0000000..3f495bb --- /dev/null +++ b/src/app/components/vendor-page/cart-summary/cart-summary.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CartSummaryComponent } from './cart-summary.component'; + +describe('CartSummaryComponent', () => { + let component: CartSummaryComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [CartSummaryComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(CartSummaryComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/vendor-page/cart-summary/cart-summary.component.ts b/src/app/components/vendor-page/cart-summary/cart-summary.component.ts new file mode 100644 index 0000000..ae58ad7 --- /dev/null +++ b/src/app/components/vendor-page/cart-summary/cart-summary.component.ts @@ -0,0 +1,183 @@ +import { Component, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormBuilder, FormGroup, Validators, ReactiveFormsModule } from '@angular/forms'; +import { FormsModule } from '@angular/forms'; +import { Router, RouterLink } from '@angular/router'; + +interface CartItem { + id: number; + name: string; + description: string; + price: number; + quantity: number; + image: string; +} + +@Component({ + selector: 'app-cart-summary', + templateUrl: './cart-summary.component.html', + styleUrls: ['./cart-summary.component.css'], + standalone: true, + imports: [CommonModule, ReactiveFormsModule, FormsModule, RouterLink] +}) +export class CartSummaryComponent implements OnInit { + cartItems: CartItem[] = []; + shippingForm: FormGroup; + shippingCost: number = 5.99; + taxRate: number = 7; + discountCode: string = ''; + discountApplied: boolean = false; + discountAmount: number = 0; + user = { name: 'Guest User' }; // Placeholder, would normally come from a user service + + constructor( + private fb: FormBuilder, + private router: Router + ) { + this.shippingForm = this.fb.group({ + firstName: ['', Validators.required], + lastName: ['', Validators.required], + address: ['', Validators.required], + city: ['', Validators.required], + country: ['', Validators.required], + postalCode: ['', Validators.required], + phone: ['', [Validators.required, Validators.pattern('^[0-9]{10,15}$')]] + }); + } + + ngOnInit(): void { + // Load cart items from local storage or service + this.loadCartItems(); + // Load user data if available + this.loadUserData(); + } + + loadCartItems(): void { + // This would typically come from a service or localStorage + // For demo purposes, we'll initialize with some sample data + this.cartItems = [ + { + id: 1, + name: 'Product 1', + description: 'High-quality product with premium features', + price: 49.99, + quantity: 2, + image: '/assets/images/product1.jpg' + }, + { + id: 2, + name: 'Product 2', + description: 'Versatile and durable for everyday use', + price: 29.99, + quantity: 1, + image: '/assets/images/product2.jpg' + } + ]; + } + + loadUserData(): void { + // In a real app, this would load data from a user service + const savedUser = localStorage.getItem('user'); + if (savedUser) { + this.user = JSON.parse(savedUser); + + // Pre-fill the form with user data if available + const userData = JSON.parse(savedUser); + if (userData.address) { + this.shippingForm.patchValue({ + firstName: userData.firstName || '', + lastName: userData.lastName || '', + address: userData.address.street || '', + city: userData.address.city || '', + country: userData.address.country || '', + postalCode: userData.address.postalCode || '', + phone: userData.phone || '' + }); + } + } + } + + increaseQuantity(index: number): void { + this.cartItems[index].quantity += 1; + this.updateCart(); + } + + decreaseQuantity(index: number): void { + if (this.cartItems[index].quantity > 1) { + this.cartItems[index].quantity -= 1; + this.updateCart(); + } + } + + removeItem(index: number): void { + this.cartItems.splice(index, 1); + this.updateCart(); + } + + updateCart(): void { + // Update cart in local storage or service + localStorage.setItem('cartItems', JSON.stringify(this.cartItems)); + } + + getSubtotal(): string { + const subtotal = this.cartItems.reduce((acc, item) => acc + (item.price * item.quantity), 0); + return subtotal.toFixed(2); + } + + calculateTax(): number { + return (parseFloat(this.getSubtotal()) * this.taxRate) / 100; + } + + getTotalItems(): number { + return this.cartItems.reduce((acc, item) => acc + item.quantity, 0); + } + + getTotal(): string { + const subtotal = parseFloat(this.getSubtotal()); + const tax = this.calculateTax(); + const total = subtotal + this.shippingCost + tax - this.discountAmount; + return total.toFixed(2); + } + + applyDiscount(): void { + // Simple discount logic - could be expanded to check against valid codes from backend + const validDiscountCodes = { + 'SAVE10': 10, + 'WELCOME20': 20, + 'LOYAL15': 15 + }; + + type DiscountCodeKey = keyof typeof validDiscountCodes; + + if (this.discountCode && (this.discountCode as DiscountCodeKey) in validDiscountCodes) { + const discountPercent = validDiscountCodes[this.discountCode as DiscountCodeKey]; + this.discountAmount = (parseFloat(this.getSubtotal()) * discountPercent) / 100; + this.discountApplied = true; + } else { + this.discountAmount = 0; + this.discountApplied = false; + // You could add an error message here + } + } + + canProceed(): boolean { + return this.cartItems.length > 0 && this.shippingForm.valid; + } + + proceedToPayment(): void { + if (this.canProceed()) { + // Save shipping information + const shippingData = this.shippingForm.value; + localStorage.setItem('shippingDetails', JSON.stringify(shippingData)); + + // Navigate to payment page + this.router.navigate(['/payment']); + } else { + // Mark all form fields as touched to trigger validation messages + Object.keys(this.shippingForm.controls).forEach(key => { + const control = this.shippingForm.get(key); + control?.markAsTouched(); + }); + } + } +} \ No newline at end of file diff --git a/src/app/components/vendor-page/cart/cart.component.css b/src/app/components/vendor-page/cart/cart.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/components/vendor-page/cart/cart.component.html b/src/app/components/vendor-page/cart/cart.component.html new file mode 100644 index 0000000..fe6ea22 --- /dev/null +++ b/src/app/components/vendor-page/cart/cart.component.html @@ -0,0 +1,145 @@ +
+ +
+
+
+ + + +
+
+ + +
+
+
+ Your Shopping Cart +
+

+ Carefully selected for you, +

+

+ {{ user.name }} +

+

Review your selections

+
+
+
+ + +
+
+

+ Items in Your Cart +

+ +
+
+ {{ item.name }} +

{{ item.name }}

+

{{ item.subtitle }}

+

+ Category: {{ item.category }} +

+

+ ${{ item.price.toFixed(2) }} +

+
+
+ Qty: + +
+ +
+
+
+ + +
+
+
+ Subtotal: + ${{ getSubtotal() }} +
+
+ Shipping: + ${{ getShipping() }} +
+
+ Total: + ${{ getTotal() }} +
+
+ +
+ + + +
+
+ + + +
+

Your cart is currently empty.

+ + Browse Products + +
+
+
+
+ + +
+
+ © 2025 Vendor Ordering System. All rights reserved. +
+
+
\ No newline at end of file diff --git a/src/app/components/vendor-page/cart/cart.component.spec.ts b/src/app/components/vendor-page/cart/cart.component.spec.ts new file mode 100644 index 0000000..03dcc4a --- /dev/null +++ b/src/app/components/vendor-page/cart/cart.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CartComponent } from './cart.component'; + +describe('CartComponent', () => { + let component: CartComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [CartComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(CartComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/vendor-page/cart/cart.component.ts b/src/app/components/vendor-page/cart/cart.component.ts new file mode 100644 index 0000000..c3f7981 --- /dev/null +++ b/src/app/components/vendor-page/cart/cart.component.ts @@ -0,0 +1,75 @@ +import { Component, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { Router } from '@angular/router'; +import { RouterLink } from '@angular/router'; +import { FormsModule } from '@angular/forms'; + +@Component({ + selector: 'app-cart', + standalone: true, + imports: [CommonModule, RouterLink, FormsModule], + templateUrl: './cart.component.html', +}) +export class CartComponent implements OnInit { + user = { + name: "Alice Fernando", + role: "Tea Supplier", + }; + + cartItems: any[] = []; + shippingCost: number = 5.99; + + constructor(private router: Router) {} + + ngOnInit(): void { + this.loadCartItems(); + } + + loadCartItems(): void { + this.cartItems = JSON.parse(localStorage.getItem("cart") || '[]'); + } + + removeFromCart(index: number): void { + this.cartItems.splice(index, 1); + this.saveCartToStorage(); + } + + updateQuantity(index: number, quantity: number): void { + if (quantity > 0) { + this.cartItems[index].quantity = quantity; + this.saveCartToStorage(); + } + } + + saveCartToStorage(): void { + localStorage.setItem("cart", JSON.stringify(this.cartItems)); + } + + getSubtotal(): string { + let subtotal = 0; + this.cartItems.forEach((item: any) => { + subtotal += item.price * (item.quantity || 1); + }); + return subtotal.toFixed(2); + } + + getShipping(): string { + return this.cartItems.length > 0 ? this.shippingCost.toFixed(2) : '0.00'; + } + + getTotal(): string { + const subtotal = parseFloat(this.getSubtotal()); + const shipping = parseFloat(this.getShipping()); + return (subtotal + shipping).toFixed(2); + } + + proceedToCheckout(): void { + if (this.cartItems.length > 0) { + this.router.navigate(['order-summery']); + } + } + + continueShopping(): void { + this.router.navigate(['/products']); + } +} \ No newline at end of file diff --git a/src/app/components/vendor-page/order-summary/order-summary.component.css b/src/app/components/vendor-page/order-summary/order-summary.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/components/vendor-page/order-summary/order-summary.component.html b/src/app/components/vendor-page/order-summary/order-summary.component.html new file mode 100644 index 0000000..0439921 --- /dev/null +++ b/src/app/components/vendor-page/order-summary/order-summary.component.html @@ -0,0 +1,180 @@ +
+ +
+
+
+ + + +
+
+ + +
+
+
+ Order Summary +
+

Thanks for your order,

+

{{ user.name }}

+

Here's a summary of your purchase

+
+
+
+ + +
+
+ +
+
+

Order Status

+ {{ order.status }} +
+ +
+ +
+
+
+ + +
+
+
+ + {{ i + 1 }} +
+ {{ step }} +
+
+
+
+ + +
+ +
+

Your Items

+ +
+
+ {{ item.name }} +
+
+

{{ item.name }}

+

+ ${{ (item.price * item.quantity).toFixed(2) }} +

+
+

{{ item.description }}

+
+ Qty: {{ item.quantity }} + Unit Price: ${{ item.price.toFixed(2) }} +
+
+ {{ item.status }} +
+
+
+
+
+ + +
+
+

Order Details

+ +
+

+ Order ID: + {{ order.id }} +

+

+ Date: + {{ getFormattedDate() }} +

+

+ Estimated Delivery: + {{ getEstimatedDelivery() }} +

+
+ +
+

Shipping Address

+

{{ order.shippingAddress }}

+
+ +
+ +
+

+ Subtotal: + ${{ getSubtotal() }} +

+

+ Shipping: + ${{ order.shippingCost.toFixed(2) }} +

+

+ Tax: + ${{ order.tax.toFixed(2) }} +

+
+ +
+ Total: + ${{ getTotal() }} +
+ +
+ + +
+
+
+
+ + + +
+
+ + + +
+

You don't have any orders yet.

+ + Start Shopping + +
+
+
+
+ + +
+
+ © 2025 Vendor Ordering System. All rights reserved. +
+
+
\ No newline at end of file diff --git a/src/app/components/vendor-page/order-summary/order-summary.component.spec.ts b/src/app/components/vendor-page/order-summary/order-summary.component.spec.ts new file mode 100644 index 0000000..3fe9901 --- /dev/null +++ b/src/app/components/vendor-page/order-summary/order-summary.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { OrderSummaryComponent } from './order-summary.component'; + +describe('OrderSummaryComponent', () => { + let component: OrderSummaryComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [OrderSummaryComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(OrderSummaryComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/vendor-page/order-summary/order-summary.component.ts b/src/app/components/vendor-page/order-summary/order-summary.component.ts new file mode 100644 index 0000000..3817bb2 --- /dev/null +++ b/src/app/components/vendor-page/order-summary/order-summary.component.ts @@ -0,0 +1,132 @@ +import { Component, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { RouterLink } from '@angular/router'; +import { Router } from '@angular/router'; + +@Component({ + selector: 'app-order-summary', + standalone: true, + imports: [CommonModule, RouterLink], + templateUrl: './order-summary.component.html', + styleUrl: './order-summary.component.css', +}) +export class OrderSummaryComponent implements OnInit { + user = { + name: 'Prabath', + email: 'prabath@example.com', + role: 'Customer' + }; + + order = { + id: 'ORD123456', + date: '2025-05-07', + shippingAddress: '123 Main Street, Colombo, Sri Lanka', + status: 'Processing', + shippingCost: 5.99, + tax: 2.50, + estimatedDelivery: '2025-05-14' + }; + + orderItems = [ + { + name: 'Ceylon Cinnamon', + quantity: 2, + price: 10.0, + image: 'path/to/image1.jpg', + description: 'Premium quality Ceylon cinnamon sticks, 100g package', + status: 'Processing' + }, + { + name: 'Black Pepper', + quantity: 1, + price: 5.5, + image: 'path/to/image2.jpg', + description: 'Organic black pepper, 50g package', + status: 'Processing' + }, + ]; + + // Order progress tracking + orderSteps = ['Confirmed', 'Processing', 'Shipped', 'Delivered']; + currentStep = 1; // 0-based index (1 = Processing) + + constructor(private router: Router) {} + + ngOnInit(): void { + // Load order data from localStorage if available + const savedOrder = localStorage.getItem('currentOrder'); + if (savedOrder) { + try { + const orderData = JSON.parse(savedOrder); + if (orderData.items && orderData.items.length > 0) { + this.orderItems = orderData.items; + } + + // Clear cart after successful order + localStorage.removeItem('cart'); + } catch (error) { + console.error('Error parsing order data:', error); + } + } + } + + getSubtotal(): string { + return this.orderItems + .reduce((acc, item) => acc + item.price * item.quantity, 0) + .toFixed(2); + } + + getTotal(): string { + const subtotal = parseFloat(this.getSubtotal()); + const shipping = this.order.shippingCost; + const tax = this.order.tax; + + return (subtotal + shipping + tax).toFixed(2); + } + + getFormattedDate(): string { + const date = new Date(this.order.date); + return date.toLocaleDateString('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric' + }); + } + + getEstimatedDelivery(): string { + const date = new Date(this.order.estimatedDelivery); + return date.toLocaleDateString('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric' + }); + } + + getProgressWidth(): string { + const progress = ((this.currentStep + 1) / this.orderSteps.length) * 100; + return `${progress}%`; + } + + getStatusClass(status: string): string { + const baseClasses = 'px-3 py-1 rounded-full text-xs font-medium'; + + switch(status.toLowerCase()) { + case 'processing': + return `${baseClasses} bg-blue-100 text-blue-800`; + case 'shipped': + return `${baseClasses} bg-purple-100 text-purple-800`; + case 'delivered': + return `${baseClasses} bg-green-100 text-green-800`; + case 'cancelled': + return `${baseClasses} bg-red-100 text-red-800`; + default: + return `${baseClasses} bg-gray-100 text-gray-800`; + } + } + + trackOrder(): void { + // Implementation for order tracking functionality + // This could navigate to a dedicated tracking page + alert(`Tracking order ${this.order.id}...`); + } +} \ No newline at end of file diff --git a/src/app/components/vendor-page/orders/orders.component.css b/src/app/components/vendor-page/orders/orders.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/components/vendor-page/orders/orders.component.html b/src/app/components/vendor-page/orders/orders.component.html new file mode 100644 index 0000000..c386993 --- /dev/null +++ b/src/app/components/vendor-page/orders/orders.component.html @@ -0,0 +1,230 @@ +
+ +
+
+
+ + + +
+
+ + +
+
+
+ Order History +
+

Your purchase history,

+

{{ user.name }}

+

Track and manage your previous orders

+
+
+
+ + +
+
+ +
+
+

Your Orders

+ + {{ orders.length }} orders + +
+ +
+ +
+ +
+ + + +
+
+ + + + + + +
+
+ + +
+
+ + + + +
+
+ +
+
+
+

Order #{{ order.id }}

+

{{ formatDate(order.date) }}

+
+ + {{ order.status }} + +
+

Items: {{ order.itemsCount }}

+

Total: ${{ order.total.toFixed(2) }}

+
+ + +
+
+ + + +
+
+
+ + +
+
+ Showing {{ paginationStart }} - {{ paginationEnd }} of {{ orders.length }} orders +
+
+ + +
+ +
+ + +
+
+
+ + + +
+
+ + + +
+

No orders found

+

+ {{ orders.length > 0 ? 'No orders match your current filters.' : 'You haven\'t placed any orders yet.' }} +

+
+ + + Browse Products + +
+
+
+
+
+
\ No newline at end of file diff --git a/src/app/components/vendor-page/orders/orders.component.spec.ts b/src/app/components/vendor-page/orders/orders.component.spec.ts new file mode 100644 index 0000000..4f12d69 --- /dev/null +++ b/src/app/components/vendor-page/orders/orders.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { OrdersComponent } from './orders.component'; + +describe('OrdersComponent', () => { + let component: OrdersComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [OrdersComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(OrdersComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/vendor-page/orders/orders.component.ts b/src/app/components/vendor-page/orders/orders.component.ts new file mode 100644 index 0000000..95a0679 --- /dev/null +++ b/src/app/components/vendor-page/orders/orders.component.ts @@ -0,0 +1,228 @@ +import { Component, OnInit } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { FormsModule } from '@angular/forms'; +import { RouterModule } from '@angular/router'; + +interface Order { + id: string; + date: Date; + itemsCount: number; + total: number; + status: 'processing' | 'shipped' | 'delivered' | 'cancelled'; + trackingNumber?: string; +} + +interface User { + name: string; + // Add other user properties as needed +} + +@Component({ + selector: 'app-orders', + standalone: true, + imports: [CommonModule, FormsModule, RouterModule], + templateUrl: './orders.component.html', + styleUrl: './orders.component.css' +}) +export class OrdersComponent implements OnInit { + // User data + user: User = { + name: 'John Doe' + }; + + // Orders data + orders: Order[] = []; + filteredOrders: Order[] = []; + + // Pagination + currentPage: number = 1; + itemsPerPage: number = 5; + totalPages: number = 1; + pageNumbers: number[] = []; + paginationStart: number = 0; + paginationEnd: number = 0; + + // Filters + searchQuery: string = ''; + statusFilter: string = 'all'; + dateFilter: string = 'all'; + + constructor() {} + + ngOnInit(): void { + // Load mock order data + this.loadMockOrders(); + this.filterOrders(); + this.calculatePagination(); + } + + loadMockOrders(): void { + // Mock data for demonstration + this.orders = [ + { + id: '1001', + date: new Date(2025, 3, 15), // April 15, 2025 + itemsCount: 3, + total: 129.99, + status: 'delivered' + }, + { + id: '1002', + date: new Date(2025, 4, 1), // May 1, 2025 + itemsCount: 1, + total: 49.99, + status: 'shipped', + trackingNumber: 'TRACK123456' + }, + { + id: '1003', + date: new Date(2025, 4, 5), // May 5, 2025 + itemsCount: 2, + total: 89.98, + status: 'processing' + }, + { + id: '1004', + date: new Date(2025, 3, 10), // April 10, 2025 + itemsCount: 4, + total: 159.96, + status: 'cancelled' + }, + { + id: '1005', + date: new Date(2025, 2, 20), // March 20, 2025 + itemsCount: 2, + total: 79.98, + status: 'delivered' + }, + { + id: '1006', + date: new Date(2025, 1, 15), // February 15, 2025 + itemsCount: 1, + total: 29.99, + status: 'delivered' + }, + { + id: '1007', + date: new Date(2025, 4, 3), // May 3, 2025 + itemsCount: 3, + total: 109.97, + status: 'shipped', + trackingNumber: 'TRACK789012' + } + ]; + } + + filterOrders(): void { + let filtered = [...this.orders]; + + // Apply search filter + if (this.searchQuery.trim()) { + const query = this.searchQuery.toLowerCase().trim(); + filtered = filtered.filter(order => + order.id.toLowerCase().includes(query) + ); + } + + // Apply status filter + if (this.statusFilter !== 'all') { + filtered = filtered.filter(order => + order.status === this.statusFilter + ); + } + + // Apply date filter + if (this.dateFilter !== 'all') { + const today = new Date(); + const daysAgo = parseInt(this.dateFilter); + const cutoffDate = new Date(); + cutoffDate.setDate(today.getDate() - daysAgo); + + filtered = filtered.filter(order => + order.date >= cutoffDate + ); + } + + this.filteredOrders = filtered; + this.currentPage = 1; // Reset to first page when filters change + this.calculatePagination(); + } + + calculatePagination(): void { + this.totalPages = Math.ceil(this.filteredOrders.length / this.itemsPerPage); + + // Generate page numbers array + this.pageNumbers = []; + for (let i = 1; i <= this.totalPages; i++) { + this.pageNumbers.push(i); + } + + // Calculate items showing + const startIndex = (this.currentPage - 1) * this.itemsPerPage; + this.paginationStart = startIndex + 1; + this.paginationEnd = Math.min(startIndex + this.itemsPerPage, this.filteredOrders.length); + } + + prevPage(): void { + if (this.currentPage > 1) { + this.currentPage--; + this.calculatePagination(); + } + } + + nextPage(): void { + if (this.currentPage < this.totalPages) { + this.currentPage++; + this.calculatePagination(); + } + } + + goToPage(page: number): void { + this.currentPage = page; + this.calculatePagination(); + } + + resetFilters(): void { + this.searchQuery = ''; + this.statusFilter = 'all'; + this.dateFilter = 'all'; + this.filterOrders(); + } + + canTrack(order: Order): boolean { + return (order.status === 'shipped' || order.status === 'processing') && + !!order.trackingNumber; + } + + trackOrder(orderId: string): void { + // This would typically navigate to a tracking page or open a modal + const order = this.orders.find(o => o.id === orderId); + if (order?.trackingNumber) { + alert(`Tracking order #${orderId} with tracking number: ${order.trackingNumber}`); + // In a real application, you would implement proper tracking functionality + } + } + + formatDate(date: Date): string { + return date.toLocaleDateString('en-US', { + year: 'numeric', + month: 'short', + day: 'numeric' + }); + } + + getStatusClass(status: string): string { + switch (status) { + case 'processing': + return 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-blue-100 text-blue-800'; + case 'shipped': + return 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-yellow-100 text-yellow-800'; + case 'delivered': + return 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-green-100 text-green-800'; + case 'cancelled': + return 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-red-100 text-red-800'; + default: + return 'inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium bg-gray-100 text-gray-800'; + } + } +} \ No newline at end of file diff --git a/src/app/components/vendor-page/vender/vendor.component.css b/src/app/components/vendor-page/vender/vendor.component.css new file mode 100644 index 0000000..e69de29 diff --git a/src/app/components/vendor-page/vender/vendor.component.html b/src/app/components/vendor-page/vender/vendor.component.html new file mode 100644 index 0000000..6200e2d --- /dev/null +++ b/src/app/components/vendor-page/vender/vendor.component.html @@ -0,0 +1,67 @@ +
+ +
+
+
+ + + +
+
+ + +
+
+
+ Vendor Ordering Flow +
+

Welcome back,

+

{{ user.name }}

+

{{ user.role }}

+
+
+
+ + + + + +
+
+ © 2025 Vendor Ordering System. All rights reserved. +
+
+
diff --git a/src/app/components/vendor-page/vender/vendor.component.spec.ts b/src/app/components/vendor-page/vender/vendor.component.spec.ts new file mode 100644 index 0000000..c0d25f1 --- /dev/null +++ b/src/app/components/vendor-page/vender/vendor.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { VendorComponent } from './vendor.component'; + +describe('VendorComponent', () => { + let component: VendorComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [VendorComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(VendorComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/vendor-page/vender/vendor.component.ts b/src/app/components/vendor-page/vender/vendor.component.ts new file mode 100644 index 0000000..adab49a --- /dev/null +++ b/src/app/components/vendor-page/vender/vendor.component.ts @@ -0,0 +1,15 @@ +import { Component } from '@angular/core'; +import { RouterLink } from '@angular/router'; + +@Component({ + selector: 'app-vendor', + templateUrl: './vendor.component.html', + styleUrls: ['./vendor.component.css'], + imports: [RouterLink], +}) +export class VendorComponent { + user = { + name: "Alice Fernando", + role: "Tea Supplier", + }; +} From 11cf09ba60dba264e24ec91ebb41e0d502fd5794 Mon Sep 17 00:00:00 2001 From: Prabath Samarasinghe Date: Thu, 8 May 2025 11:44:06 +0530 Subject: [PATCH 2/2] Order processing method --- src/app/app.routes.ts | 3 +- .../cart-summary/cart-summary.component.html | 568 ++++++++++++++---- .../cart-summary/cart-summary.component.ts | 166 ++++- .../vendor-page/cart/cart.component.html | 13 +- .../product-section.component.css | 25 + .../product-section.component.html | 167 +++++ .../product-section.component.spec.ts | 23 + .../product-section.component.ts | 171 ++++++ 8 files changed, 983 insertions(+), 153 deletions(-) create mode 100644 src/app/components/vendor-page/product-section/product-section.component.css create mode 100644 src/app/components/vendor-page/product-section/product-section.component.html create mode 100644 src/app/components/vendor-page/product-section/product-section.component.spec.ts create mode 100644 src/app/components/vendor-page/product-section/product-section.component.ts diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index ec50cf8..2172773 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -16,7 +16,7 @@ import { roleGuard } from './gaurds/role.guard'; import { VendorSignupComponent } from './components/auth-page/vendor-signup/vendor-signup.component'; import { SupplierSignupComponent } from './components/auth-page/supplier-signup/supplier-signup.component'; import { VendorComponent } from './components/vendor-page/vender/vendor.component'; -import { ProductSectionComponent } from './components/home-page/product-section/product-section.component'; +import { ProductSectionComponent } from './components/vendor-page/product-section/product-section.component'; import { CartComponent } from './components/vendor-page/cart/cart.component'; import { OrderSummaryComponent } from './components/vendor-page/order-summary/order-summary.component'; import { CartSummaryComponent } from './components/vendor-page/cart-summary/cart-summary.component'; @@ -62,7 +62,6 @@ export const routes: Routes = [ { path: 'vendor/cart', component: CartComponent, pathMatch: 'full' }, { path: 'vendor/orders/order-details/:id', component: OrderSummaryComponent, pathMatch: 'full' }, { path: 'vendor/cart-summery', component: CartSummaryComponent, pathMatch: 'full' }, - { path: 'vendor/cart/cart-summery', component: CartSummaryComponent, pathMatch: 'full' }, { path: 'vendor/orders', component: OrdersComponent, pathMatch: 'full' }, ] }, diff --git a/src/app/components/vendor-page/cart-summary/cart-summary.component.html b/src/app/components/vendor-page/cart-summary/cart-summary.component.html index e38913d..e7661aa 100644 --- a/src/app/components/vendor-page/cart-summary/cart-summary.component.html +++ b/src/app/components/vendor-page/cart-summary/cart-summary.component.html @@ -1,107 +1,126 @@
- -
-
-
- - - -
+ +
+
+
+ + +
- - -
-
-
- Cart Summary -
-

Review your order,

-

{{ user.name }}

-

Confirm your selections before purchase

+
+ + +
+
+
+ Cart Summary + Payment + Order Confirmation
+

+ Review your order, + Almost done, + Thank you for your order, +

+

{{ user.name }}

+

+ Confirm your selections before purchase + Secure checkout to complete your purchase + Your order has been successfully placed +

- - -
-
- -
-
- -
-
-
- - -
-
-
- -
- Cart +
+ + +
+
+ +
+
+ +
+
+
+ + +
+
+
+
-
-
- -
- Summary + Cart +
+
+
+
-
-
- 3 -
- Payment + Summary +
+
+
+ 3
-
-
- 4 -
- Confirmation + Payment +
+
+
+ 4
+ Confirmation
- +
+ + +
-

Order Items

+

Order Items

+ class="bg-white rounded-lg shadow-sm p-5 border border-slate-200 mb-4 flex flex-col sm:flex-row"> {{ item.name }}
-

{{ item.name }}

-

+

{{ item.name }}

+

${{ (item.price * item.quantity).toFixed(2) }}

-

{{ item.description }}

+

{{ item.description }}

- Qty: -
+ Qty: +
- {{ item.quantity }} + class="px-3 py-1 text-slate-600 hover:bg-slate-100">- + {{ item.quantity }} + class="px-3 py-1 text-slate-600 hover:bg-slate-100">+
@@ -111,17 +130,17 @@

{{ item.name }}

-
-

Shipping Address

+
+

Shipping Address

- +
@@ -129,11 +148,11 @@

Shipping Address

- +
@@ -143,11 +162,11 @@

Shipping Address

- +
@@ -157,11 +176,11 @@

Shipping Address

- +
@@ -169,10 +188,10 @@

Shipping Address

- +
@@ -200,11 +219,11 @@

Shipping Address

- +
@@ -214,42 +233,42 @@

Shipping Address

- +
-
-

+
+

Order Summary

-

+

Subtotal ({{ getTotalItems() }} items): ${{ getSubtotal() }}

-

+

Shipping: ${{ shippingCost.toFixed(2) }}

-

+

Tax ({{ taxRate }}%): ${{ calculateTax().toFixed(2) }}

-
+
@@ -260,7 +279,7 @@

+
Total: ${{ getTotal() }}
@@ -269,45 +288,374 @@

Proceed to Payment - +

- +
-
+
-

Your cart is currently empty.

+

Your cart is currently empty.

Browse Products
-
- - -
-
- © 2025 Vendor Ordering System. All rights reserved. + + +
+ +
+ +
+

Payment Method

+ + +
+
+
+ {{ method.name }} + {{ method.name }} +
+
+
+ + +
+
+
+ + +
+ Cardholder name is required +
+
+ +
+ +
+ +
+
+ Visa + Mastercard + Amex +
+
+
+
+ Enter a valid card number +
+
+ +
+
+ + +
+ Enter a valid expiry date +
+
+
+ + +
+ Enter a valid CVV +
+
+
+ +
+ + +
+
+
+ + +
+ PayPal +

After clicking "Complete Order", you will be redirected to PayPal to complete your purchase securely.

+
+

Total: ${{ getTotal() }}

+
+
+ + +
+ +

After clicking "Complete Order", you'll be prompted to confirm payment.

+
+

Total: ${{ getTotal() }}

+
+
+ + +
+
+

Billing Address

+
+ + +
+
+ +
+
+
+
+ + +
+
+ + +
+
+ +
+ + +
+ +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+
+
+ + +
+
+

+ Order Summary +

+ +
+
+
+
+ {{ item.name }} +
+

{{ item.name }}

+

Qty: {{ item.quantity }}

+
+
+

+ ${{ (item.price * item.quantity).toFixed(2) }} +

+
+
+ +
+

+ Subtotal: + ${{ getSubtotal() }} +

+

+ Shipping: + ${{ shippingCost.toFixed(2) }} +

+

+ Tax: + ${{ calculateTax().toFixed(2) }} +

+
+ Discount: + -${{ discountAmount.toFixed(2) }} +
+
+ +
+ Total: + ${{ getTotal() }} +
+ + + + +
+
+
+
+ + +
+
+
+ + + +
+ +

Order Confirmed!

+

Thank you for your purchase, {{ user.name }}. Your order has been received and is being processed.

+ +
+
+
+

ORDER NUMBER

+

{{ orderNumber }}

+
+
+

DATE

+

{{ orderDate | date:'mediumDate' }}

+
+
+

TOTAL

+

${{ getTotal() }}

+
+
+

PAYMENT METHOD

+

{{ getSelectedPaymentMethod()?.name }}

+
+
+
+ + +
+
+
+
+ + +
+
+ © 2025 Vendor Ordering System. All rights reserved.
-
\ No newline at end of file +

+
\ No newline at end of file diff --git a/src/app/components/vendor-page/cart-summary/cart-summary.component.ts b/src/app/components/vendor-page/cart-summary/cart-summary.component.ts index ae58ad7..7be7342 100644 --- a/src/app/components/vendor-page/cart-summary/cart-summary.component.ts +++ b/src/app/components/vendor-page/cart-summary/cart-summary.component.ts @@ -13,6 +13,12 @@ interface CartItem { image: string; } +interface PaymentMethod { + id: string; + name: string; + icon: string; +} + @Component({ selector: 'app-cart-summary', templateUrl: './cart-summary.component.html', @@ -21,14 +27,30 @@ interface CartItem { imports: [CommonModule, ReactiveFormsModule, FormsModule, RouterLink] }) export class CartSummaryComponent implements OnInit { + currentStep: 'summary' | 'payment' | 'confirmation' = 'summary'; cartItems: CartItem[] = []; shippingForm: FormGroup; + billingForm: FormGroup; + paymentForm: FormGroup; + shippingCost: number = 5.99; taxRate: number = 7; discountCode: string = ''; discountApplied: boolean = false; discountAmount: number = 0; - user = { name: 'Guest User' }; // Placeholder, would normally come from a user service + + user = { name: 'Guest User' }; + sameAsShipping: boolean = true; + + paymentMethods: PaymentMethod[] = [ + { id: 'credit_card', name: 'Credit Card', icon: 'https://th.bing.com/th/id/R.d4653ffdddd3f73959889432f9d7d5f8?rik=0ZtmYR5u4PqXJQ&pid=ImgRaw&r=0' }, + { id: 'paypal', name: 'PayPal', icon: 'https://logodix.com/logo/370282.jpg' }, + { id: 'bank_transfer', name: 'Bank Transfer', icon: 'https://www.sevenjackpots.com/wp-content/uploads/2021/04/bank-transfer-logo.png' } + ]; + + selectedPaymentMethod: string = 'credit_card'; + orderNumber: string = ''; + orderDate: Date = new Date(); constructor( private fb: FormBuilder, @@ -43,19 +65,35 @@ export class CartSummaryComponent implements OnInit { postalCode: ['', Validators.required], phone: ['', [Validators.required, Validators.pattern('^[0-9]{10,15}$')]] }); + + this.billingForm = this.fb.group({ + firstName: ['', Validators.required], + lastName: ['', Validators.required], + address: ['', Validators.required], + city: ['', Validators.required], + country: ['', Validators.required], + postalCode: ['', Validators.required] + }); + + this.paymentForm = this.fb.group({ + cardholderName: ['', Validators.required], + cardNumber: ['', [Validators.required, Validators.pattern('^[0-9]{16,19}$')]], + expiryDate: ['', [Validators.required, Validators.pattern('^(0[1-9]|1[0-2])\/?([0-9]{2})$')]], + cvv: ['', [Validators.required, Validators.pattern('^[0-9]{3,4}$')]], + saveCard: [false] + }); } ngOnInit(): void { - // Load cart items from local storage or service this.loadCartItems(); - // Load user data if available this.loadUserData(); + this.generateOrderNumber(); } loadCartItems(): void { - // This would typically come from a service or localStorage - // For demo purposes, we'll initialize with some sample data - this.cartItems = [ + // Load from localStorage or service + const savedCart = localStorage.getItem('cart'); + this.cartItems = savedCart ? JSON.parse(savedCart) : [ { id: 1, name: 'Product 1', @@ -76,12 +114,10 @@ export class CartSummaryComponent implements OnInit { } loadUserData(): void { - // In a real app, this would load data from a user service const savedUser = localStorage.getItem('user'); if (savedUser) { this.user = JSON.parse(savedUser); - // Pre-fill the form with user data if available const userData = JSON.parse(savedUser); if (userData.address) { this.shippingForm.patchValue({ @@ -97,6 +133,11 @@ export class CartSummaryComponent implements OnInit { } } + generateOrderNumber(): void { + const randomNum = Math.floor(Math.random() * 90000) + 10000; + this.orderNumber = `ORD-${randomNum}`; + } + increaseQuantity(index: number): void { this.cartItems[index].quantity += 1; this.updateCart(); @@ -115,48 +156,40 @@ export class CartSummaryComponent implements OnInit { } updateCart(): void { - // Update cart in local storage or service - localStorage.setItem('cartItems', JSON.stringify(this.cartItems)); + localStorage.setItem('cart', JSON.stringify(this.cartItems)); } - getSubtotal(): string { - const subtotal = this.cartItems.reduce((acc, item) => acc + (item.price * item.quantity), 0); - return subtotal.toFixed(2); + getSubtotal(): number { + return parseFloat((this.cartItems.reduce((acc, item) => acc + (item.price * item.quantity), 0)).toFixed(2)); } calculateTax(): number { - return (parseFloat(this.getSubtotal()) * this.taxRate) / 100; + return parseFloat((this.getSubtotal() * this.taxRate / 100).toFixed(2)); } getTotalItems(): number { return this.cartItems.reduce((acc, item) => acc + item.quantity, 0); } - getTotal(): string { - const subtotal = parseFloat(this.getSubtotal()); + getTotal(): number { + const subtotal = this.getSubtotal(); const tax = this.calculateTax(); - const total = subtotal + this.shippingCost + tax - this.discountAmount; - return total.toFixed(2); + return parseFloat((subtotal + this.shippingCost + tax - this.discountAmount).toFixed(2)); } applyDiscount(): void { - // Simple discount logic - could be expanded to check against valid codes from backend - const validDiscountCodes = { + const validDiscountCodes: { [key: string]: number } = { 'SAVE10': 10, 'WELCOME20': 20, 'LOYAL15': 15 }; - type DiscountCodeKey = keyof typeof validDiscountCodes; - - if (this.discountCode && (this.discountCode as DiscountCodeKey) in validDiscountCodes) { - const discountPercent = validDiscountCodes[this.discountCode as DiscountCodeKey]; - this.discountAmount = (parseFloat(this.getSubtotal()) * discountPercent) / 100; + if (this.discountCode && validDiscountCodes[this.discountCode]) { + this.discountAmount = (this.getSubtotal() * validDiscountCodes[this.discountCode]) / 100; this.discountApplied = true; } else { this.discountAmount = 0; this.discountApplied = false; - // You could add an error message here } } @@ -166,18 +199,85 @@ export class CartSummaryComponent implements OnInit { proceedToPayment(): void { if (this.canProceed()) { - // Save shipping information const shippingData = this.shippingForm.value; localStorage.setItem('shippingDetails', JSON.stringify(shippingData)); - - // Navigate to payment page - this.router.navigate(['/payment']); + this.currentStep = 'payment'; + this.updateBillingAddress(); } else { - // Mark all form fields as touched to trigger validation messages Object.keys(this.shippingForm.controls).forEach(key => { - const control = this.shippingForm.get(key); - control?.markAsTouched(); + this.shippingForm.get(key)?.markAsTouched(); + }); + } + } + + selectPaymentMethod(methodId: string): void { + this.selectedPaymentMethod = methodId; + } + + getSelectedPaymentMethod(): PaymentMethod | undefined { + return this.paymentMethods.find(method => method.id === this.selectedPaymentMethod); + } + + updateBillingAddress(): void { + if (this.sameAsShipping) { + const shippingData = this.shippingForm.value; + this.billingForm.patchValue({ + firstName: shippingData.firstName, + lastName: shippingData.lastName, + address: shippingData.address, + city: shippingData.city, + country: shippingData.country, + postalCode: shippingData.postalCode }); } } + + canCompleteOrder(): boolean { + if (this.selectedPaymentMethod === 'credit_card') { + return this.paymentForm.valid; + } + return true; // For other payment methods that don't require form validation + } + + completeOrder(): void { + if (this.canCompleteOrder()) { + // In a real app, you would send the order to your backend here + const orderData = { + orderNumber: this.orderNumber, + date: new Date(), + items: this.cartItems, + shipping: this.shippingForm.value, + billing: this.sameAsShipping ? this.shippingForm.value : this.billingForm.value, + payment: { + method: this.selectedPaymentMethod, + details: this.selectedPaymentMethod === 'credit_card' ? this.paymentForm.value : null + }, + subtotal: this.getSubtotal(), + shippingCost: this.shippingCost, + tax: this.calculateTax(), + discount: this.discountAmount, + total: this.getTotal() + }; + + // Save order to localStorage (in a real app, you would send to backend) + localStorage.setItem('currentOrder', JSON.stringify(orderData)); + + // Clear cart + localStorage.removeItem('cart'); + this.cartItems = []; + + // Move to confirmation step + this.currentStep = 'confirmation'; + } else { + if (this.selectedPaymentMethod === 'credit_card') { + Object.keys(this.paymentForm.controls).forEach(key => { + this.paymentForm.get(key)?.markAsTouched(); + }); + } + } + } + + backToSummary(): void { + this.currentStep = 'summary'; + } } \ No newline at end of file diff --git a/src/app/components/vendor-page/cart/cart.component.html b/src/app/components/vendor-page/cart/cart.component.html index fe6ea22..e38029f 100644 --- a/src/app/components/vendor-page/cart/cart.component.html +++ b/src/app/components/vendor-page/cart/cart.component.html @@ -67,12 +67,9 @@

{{ item.name }}

Qty: - +

+ {{ item.quantity }} +

Proceed to Checkout @@ -126,7 +123,7 @@

{{ item.name }}

Your cart is currently empty.

Browse Products diff --git a/src/app/components/vendor-page/product-section/product-section.component.css b/src/app/components/vendor-page/product-section/product-section.component.css new file mode 100644 index 0000000..f86e874 --- /dev/null +++ b/src/app/components/vendor-page/product-section/product-section.component.css @@ -0,0 +1,25 @@ +/* Add to product-section.component.css */ +.animate-fade-in { + animation: fadeIn 0.3s ease-in-out; +} + +.animate-slide-up { + animation: slideUp 0.3s ease-out; +} + +@keyframes fadeIn { + from { opacity: 0; } + to { opacity: 1; } +} + +@keyframes slideUp { + from { + opacity: 0; + transform: translateY(20px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + diff --git a/src/app/components/vendor-page/product-section/product-section.component.html b/src/app/components/vendor-page/product-section/product-section.component.html new file mode 100644 index 0000000..c9b7b1e --- /dev/null +++ b/src/app/components/vendor-page/product-section/product-section.component.html @@ -0,0 +1,167 @@ +
+
+ +
+
+
+ + + +
+
+ + +
+ + + +
+
+ Premium Spice Collection +
+

+ Discover Our Authentic Flavors +

+

+ Handcrafted blends from around the world +

+
+
+
+ + +
+ @for (category of categories; track category) { + + } +
+ + +
+ @for (product of filteredProducts; track product.name) { +
+
+ +
+
+

+ {{ product.name }} +

+

{{ product.subtitle }}

+

+ ${{ product.price }} +

+ +
+
+ } +
+ + + @if(selectedProduct) { +
+
+ + + + +
+ +

+ {{ selectedProduct.name }} +

+

{{ selectedProduct.subtitle }}

+

+ ${{ selectedProduct.price }} +

+

+ Our spices are ethically sourced and expertly curated for + exceptional flavor in every dish. +

+
+ +
+ Quantity: +
+ + +
+ + + +
+
+
+
+
+
+
+ } +
+
\ No newline at end of file diff --git a/src/app/components/vendor-page/product-section/product-section.component.spec.ts b/src/app/components/vendor-page/product-section/product-section.component.spec.ts new file mode 100644 index 0000000..e2856b3 --- /dev/null +++ b/src/app/components/vendor-page/product-section/product-section.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { ProductSectionComponent } from './product-section.component'; + +describe('ProductSectionComponent', () => { + let component: ProductSectionComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [ProductSectionComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(ProductSectionComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/vendor-page/product-section/product-section.component.ts b/src/app/components/vendor-page/product-section/product-section.component.ts new file mode 100644 index 0000000..5f12113 --- /dev/null +++ b/src/app/components/vendor-page/product-section/product-section.component.ts @@ -0,0 +1,171 @@ +import { CommonModule } from '@angular/common'; +import { Component, signal, OnInit, OnDestroy } from '@angular/core'; +import { RouterLink } from '@angular/router'; +import { FormsModule } from '@angular/forms'; + +interface Product { + name: string; + subtitle: string; + price: number; + category: string; + image: string; +} + +interface CartItem { + name: string; + subtitle: string; + price: number; + image: string; + quantity: number; +} + +@Component({ + selector: 'app-product-section', + standalone: true, + imports: [ + CommonModule, + RouterLink, + FormsModule, + ], + templateUrl: './product-section.component.html', + styleUrl: './product-section.component.css', +}) +export class ProductSectionComponent implements OnInit, OnDestroy { + selectedCategory = 'All'; + categories = ['All', 'Powders', 'Whole Spices', 'Blends']; + selectedProduct: Product | null = null; + selectedQuantity = 1; + + // Cart state + cartCount = signal(0); + cartItems = signal([]); + + products = [ + { + name: 'Turmeric Powder', + subtitle: 'Organic • 200g', + price: 4.99, + category: 'Powders', + image: 'assets/images/products/spices-bg1.jpg', + }, + { + name: 'Garam Masala', + subtitle: 'Premium Blend • 150g', + price: 6.49, + category: 'Blends', + image: 'assets/images/products/spices-bg1.jpg', + }, + { + name: 'Cinnamon Sticks', + subtitle: 'Ceylon • 100g', + price: 3.99, + category: 'Whole Spices', + image: 'assets/images/products/spices-bg1.jpg', + }, + { + name: 'Chili Powder', + subtitle: 'Smoky Heat • 100g', + price: 2.99, + category: 'Powders', + image: 'assets/images/products/spices-bg1.jpg', + }, + { + name: 'Cloves', + subtitle: 'Whole • 50g', + price: 5.99, + category: 'Whole Spices', + image: 'assets/images/products/spices-bg1.jpg', + }, + { + name: 'Black Pepper', + subtitle: 'Tellicherry • 100g', + price: 4.49, + category: 'Whole Spices', + image: 'assets/images/products/spices-bg1.jpg', + }, + { + name: 'Cardamom Pods', + subtitle: 'Green • 75g', + price: 7.99, + category: 'Whole Spices', + image: 'assets/images/products/spices-bg1.jpg', + }, + { + name: 'Cumin Seeds', + subtitle: 'Whole • 150g', + price: 2.49, + category: 'Whole Spices', + image: 'assets/images/products/spices-bg1.jpg', + }, + ]; + + constructor() { + this.loadCart(); + } + + get filteredProducts() { + return this.selectedCategory === 'All' + ? this.products + : this.products.filter((p) => p.category === this.selectedCategory); + } + + updateQuantity(value: string) { + this.selectedQuantity = +value; + } + + openProductModal(product: Product) { + this.selectedProduct = product; + this.selectedQuantity = 1; // Reset quantity when opening modal + } + + closeProductModal() { + this.selectedProduct = null; + } + + addToCart(product: Product) { + const currentCart = this.cartItems(); + const existingItem = currentCart.find(item => item.name === product.name); + + if (existingItem) { + // Update quantity if item exists + existingItem.quantity += this.selectedQuantity; + } else { + // Add new item to cart + currentCart.push({ + ...product, + quantity: this.selectedQuantity + }); + } + + // Update cart state + this.cartItems.set([...currentCart]); + this.cartCount.set(currentCart.reduce((sum) => sum + 1, 0)); + this.saveCart(); + this.closeProductModal(); + } + + private saveCart() { + localStorage.setItem('cart', JSON.stringify(this.cartItems())); + } + + private loadCart() { + const savedCart = localStorage.getItem('cart'); + if (savedCart) { + this.cartItems.set(JSON.parse(savedCart)); + this.cartCount.set(this.cartItems().reduce((sum, item) => sum + item.quantity, 0)); + } + } + + // Handle keyboard events + ngOnInit(): void { + window.addEventListener('keydown', this.handleKeydown); + } + + ngOnDestroy(): void { + window.removeEventListener('keydown', this.handleKeydown); + } + + handleKeydown = (e: KeyboardEvent) => { + if (e.key === 'Escape') this.closeProductModal(); + }; +} \ No newline at end of file