From 486e05cc958cf4f9d8f22b2e12110cf187e1c675 Mon Sep 17 00:00:00 2001 From: Julius Peter Ntale Date: Wed, 8 Oct 2025 13:37:04 +0300 Subject: [PATCH 01/12] feat(database): optimize MongoDB connection options and update environment variable documentation --- .github/copilot-instructions.md | 97 ++++++++++++++++++++++++ DATABASE_CONFIG.md | 85 +++++++++++++++++++++ packages/order-db/src/connection.ts | 17 ++++- packages/product-db/prisma/schema.prisma | 5 +- packages/product-db/src/client.ts | 6 +- turbo.json | 1 + 6 files changed, 205 insertions(+), 6 deletions(-) create mode 100644 .github/copilot-instructions.md create mode 100644 DATABASE_CONFIG.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 00000000..8fd8c325 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,97 @@ +# Copilot Instructions + +This is an E-commerce microservices architecture built with TypeScript in a Turborepo monorepo. Understanding the service patterns and communication flows is critical for effective development. + +## Architecture Overview + +### Microservices Structure +- **auth-service** (port 8003) - Clerk authentication, user management +- **product-service** (port 8000) - Product CRUD, Prisma/PostgreSQL +- **order-service** (port 8001) - Order management, MongoDB, Fastify +- **payment-service** (port 8002) - Stripe payments, Hono framework +- **email-service** - Kafka consumer for notifications + +### Frontend Applications +- **client** (port 3002) - Customer Next.js app with Stripe integration +- **admin** (port 3003) - Admin dashboard for management + +## Key Development Patterns + +### Authentication Flow +All services use Clerk for auth. Services validate tokens via `@clerk/express` middleware: +```typescript +// Pattern: shouldBeUser/shouldBeAdmin middleware +import { getAuth } from "@clerk/express"; +const auth = getAuth(req); +req.userId = auth.userId; +``` + +### Service Communication +Services communicate via Kafka events using `@repo/kafka` package: +```typescript +// Pattern: Event production +producer.send("user.created", { value: { email, username } }); + +// Pattern: Event consumption +consumer.subscribe([{ + topicName: "order.created", + topicHandler: async (message) => { /* handle */ } +}]); +``` + +### Database Patterns +- **product-service**: Prisma client with **Neon PostgreSQL** (serverless) +- **order-service**: Mongoose with **MongoDB Atlas Serverless** +- **Shared types**: `@repo/types` for consistent data structures +- **Connection handling**: Optimized for serverless with connection pooling + +### Serverless Database Configuration +```typescript +// Neon PostgreSQL - uses connection pooling automatically +DATABASE_URL="postgresql://user:pass@ep-xxx.neon.tech/db?pgbouncer=true" +DIRECT_URL="postgresql://user:pass@ep-xxx.neon.tech/db" // for migrations + +// MongoDB Atlas Serverless - optimized connection options +MONGO_URL="mongodb+srv://user:pass@cluster.mongodb.net/db?retryWrites=true&w=majority" +``` + +### Development Commands + +```bash +# Start all services in development +pnpm dev + +# Start specific service +turbo dev --filter=product-service + +# Database operations (from workspace root) +pnpm --filter=@repo/product-db db:generate +pnpm --filter=@repo/product-db db:migrate + +# Start Kafka cluster +cd packages/kafka && docker-compose up +``` + +### Service Ports & URLs +- product-service: 8000 +- order-service: 8001 +- payment-service: 8002 +- auth-service: 8003 +- client: 3002 +- admin: 3003 + +### Essential Environment Variables +- `DATABASE_URL` - Neon PostgreSQL (with pgbouncer connection pooling) +- `DIRECT_URL` - Neon PostgreSQL direct connection (for migrations) +- `MONGO_URL` - MongoDB Atlas Serverless +- `CLERK_SECRET_KEY` - Authentication +- `STRIPE_SECRET_KEY` - Payments +- `NEXT_PUBLIC_*_SERVICE_URL` - Service endpoints for frontends + +### Framework-Specific Notes +- **auth/product-service**: Express.js with middleware patterns +- **order-service**: Fastify with plugin registration +- **payment-service**: Hono framework with different syntax +- **Frontend apps**: Next.js 15 with App Router, server components for data fetching + +When working on services, always check the corresponding package.json for service-specific scripts and the utils/kafka.js file for event communication setup. \ No newline at end of file diff --git a/DATABASE_CONFIG.md b/DATABASE_CONFIG.md new file mode 100644 index 00000000..504aa3bc --- /dev/null +++ b/DATABASE_CONFIG.md @@ -0,0 +1,85 @@ +# Environment Variables for Serverless Databases + +This project uses serverless databases for better scalability and cost efficiency. + +## PostgreSQL (Neon) + +### Required Environment Variables: +```bash +# Neon PostgreSQL Connection (with connection pooling) +DATABASE_URL="postgresql://username:password@ep-xxx.us-east-1.aws.neon.tech/dbname?sslmode=require&pgbouncer=true&connect_timeout=15" + +# Direct URL for migrations (without connection pooling) +DIRECT_URL="postgresql://username:password@ep-xxx.us-east-1.aws.neon.tech/dbname?sslmode=require" +``` + +### Neon Setup Steps: +1. Create a Neon project at https://neon.tech +2. Copy the connection string from Neon dashboard +3. Use the pooled connection for `DATABASE_URL` +4. Use the direct connection for `DIRECT_URL` + +## MongoDB (Atlas Serverless) + +### Required Environment Variables: +```bash +# MongoDB Atlas Serverless Connection +MONGO_URL="mongodb+srv://username:password@cluster.xxxxx.mongodb.net/dbname?retryWrites=true&w=majority" +``` + +### MongoDB Atlas Serverless Setup Steps: +1. Create a MongoDB Atlas account +2. Create a new cluster and select "Serverless" +3. Set up database user and network access +4. Copy the connection string and replace credentials + +## Development vs Production + +### Development (.env.local): +```bash +# Development databases (can use smaller instances) +DATABASE_URL="postgresql://dev_user:dev_pass@ep-dev.neon.tech/ecom_dev?sslmode=require&pgbouncer=true" +DIRECT_URL="postgresql://dev_user:dev_pass@ep-dev.neon.tech/ecom_dev?sslmode=require" +MONGO_URL="mongodb+srv://dev_user:dev_pass@dev-cluster.mongodb.net/ecom_dev?retryWrites=true&w=majority" +``` + +### Production (.env.production): +```bash +# Production databases (optimized for performance) +DATABASE_URL="postgresql://prod_user:prod_pass@ep-prod.neon.tech/ecom_prod?sslmode=require&pgbouncer=true&connect_timeout=15" +DIRECT_URL="postgresql://prod_user:prod_pass@ep-prod.neon.tech/ecom_prod?sslmode=require" +MONGO_URL="mongodb+srv://prod_user:prod_pass@prod-cluster.mongodb.net/ecom_prod?retryWrites=true&w=majority" +``` + +## Database Operations + +### PostgreSQL (Prisma) Commands: +```bash +# Generate Prisma client +pnpm --filter=@repo/product-db db:generate + +# Run migrations (uses DIRECT_URL) +pnpm --filter=@repo/product-db db:migrate + +# Deploy to production +pnpm --filter=@repo/product-db db:deploy +``` + +### MongoDB Operations: +- MongoDB Atlas Serverless automatically handles scaling +- No manual migration commands needed +- Schema changes are handled at application level via Mongoose models + +## Benefits of Serverless Setup + +### Neon PostgreSQL: +- **Auto-scaling**: Scales to zero when not in use +- **Branching**: Create database branches for development +- **Connection pooling**: Built-in PgBouncer for better performance +- **Point-in-time recovery**: Automatic backups + +### MongoDB Atlas Serverless: +- **Pay-per-use**: Only pay for operations and storage used +- **Auto-scaling**: Automatically scales based on demand +- **Global clusters**: Low latency worldwide +- **Built-in security**: Encryption and network isolation \ No newline at end of file diff --git a/packages/order-db/src/connection.ts b/packages/order-db/src/connection.ts index ad20ec4d..b86f5ede 100644 --- a/packages/order-db/src/connection.ts +++ b/packages/order-db/src/connection.ts @@ -10,11 +10,22 @@ export const connectOrderDB = async () => { } try { - await mongoose.connect(process.env.MONGO_URL); + // Optimized connection options for MongoDB Atlas Serverless + await mongoose.connect(process.env.MONGO_URL, { + // Connection pool settings optimized for serverless + maxPoolSize: 10, // Maintain up to 10 socket connections + serverSelectionTimeoutMS: 5000, // Keep trying to send operations for 5 seconds + socketTimeoutMS: 45000, // Close sockets after 45 seconds of inactivity + family: 4, // Use IPv4, skip trying IPv6 + // Serverless-friendly options + retryWrites: true, + w: 'majority' + }); + isConnected = true; - console.log("Connected to MongoDB"); + console.log("Connected to MongoDB Atlas Serverless"); } catch (error) { - console.log(error); + console.log("MongoDB connection error:", error); throw error; } }; diff --git a/packages/product-db/prisma/schema.prisma b/packages/product-db/prisma/schema.prisma index 78daa622..bad2710f 100644 --- a/packages/product-db/prisma/schema.prisma +++ b/packages/product-db/prisma/schema.prisma @@ -4,8 +4,9 @@ generator client { } datasource db { - provider = "postgresql" - url = env("DATABASE_URL") + provider = "postgresql" + url = env("DATABASE_URL") + directUrl = env("DIRECT_URL") } model Product { diff --git a/packages/product-db/src/client.ts b/packages/product-db/src/client.ts index 92cb5aa5..41063bfc 100644 --- a/packages/product-db/src/client.ts +++ b/packages/product-db/src/client.ts @@ -3,6 +3,10 @@ import { PrismaClient } from "../generated/prisma"; const globalForPrisma = global as unknown as { prisma: PrismaClient }; export const prisma = - globalForPrisma.prisma || new PrismaClient(); + globalForPrisma.prisma || + new PrismaClient({ + datasourceUrl: process.env.DATABASE_URL, + log: process.env.NODE_ENV === "development" ? ["query", "error", "warn"] : ["error"], + }); if (process.env.NODE_ENV !== "production") globalForPrisma.prisma = prisma; \ No newline at end of file diff --git a/turbo.json b/turbo.json index 2359d4f2..89455422 100644 --- a/turbo.json +++ b/turbo.json @@ -1,5 +1,6 @@ { "$schema": "https://turborepo.com/schema.json", + "ui": "tui", "tasks": { "build": { "dependsOn": ["^build"], From d3d33ab15e2644cf3989cf2c520067a35e684221 Mon Sep 17 00:00:00 2001 From: Julius Peter Ntale Date: Wed, 8 Oct 2025 23:20:18 +0300 Subject: [PATCH 02/12] feat(kafka): update Kafka broker images and configuration for improved stability --- apps/client/src/components/ProductList.tsx | 2 +- packages/kafka/docker-compose.yml | 91 +++++++++++++--------- 2 files changed, 54 insertions(+), 39 deletions(-) diff --git a/apps/client/src/components/ProductList.tsx b/apps/client/src/components/ProductList.tsx index 9157d368..ac1bf60d 100644 --- a/apps/client/src/components/ProductList.tsx +++ b/apps/client/src/components/ProductList.tsx @@ -174,7 +174,7 @@ const ProductList = async ({ {params === "products" && }
- {products.map((product) => ( + {Array.isArray(products) &&products.map((product) => ( ))}
diff --git a/packages/kafka/docker-compose.yml b/packages/kafka/docker-compose.yml index 3d2ebf8a..5ceca4f9 100644 --- a/packages/kafka/docker-compose.yml +++ b/packages/kafka/docker-compose.yml @@ -1,69 +1,83 @@ services: kafka-broker-1: - image: bitnami/kafka:latest + image: confluentinc/cp-kafka:7.4.0 container_name: kafka-broker-1 environment: - KAFKA_ENABLE_KRAFT: "yes" - KAFKA_CFG_NODE_ID: 1 - KAFKA_CFG_PROCESS_ROLES: broker,controller - KAFKA_CFG_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093,EXTERNAL://0.0.0.0:9094 - KAFKA_CFG_ADVERTISED_LISTENERS: PLAINTEXT://kafka-broker-1:9092,EXTERNAL://localhost:9094 - KAFKA_CFG_CONTROLLER_QUORUM_VOTERS: 1@kafka-broker-1:9093,2@kafka-broker-2:9093,3@kafka-broker-3:9093 - KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT,EXTERNAL:PLAINTEXT - KAFKA_CFG_CONTROLLER_LISTENER_NAMES: CONTROLLER - KAFKA_CFG_INTER_BROKER_LISTENER_NAME: PLAINTEXT - KAFKA_KRAFT_CLUSTER_ID: 7Dvn0OLMQo-bg4qmCmflVg - ALLOW_PLAINTEXT_LISTENER: "yes" + KAFKA_NODE_ID: 1 + KAFKA_PROCESS_ROLES: 'broker,controller' + KAFKA_CONTROLLER_QUORUM_VOTERS: '1@kafka-broker-1:29093,2@kafka-broker-2:29093,3@kafka-broker-3:29093' + KAFKA_LISTENERS: 'PLAINTEXT://kafka-broker-1:29092,CONTROLLER://kafka-broker-1:29093,PLAINTEXT_HOST://0.0.0.0:9094' + KAFKA_ADVERTISED_LISTENERS: 'PLAINTEXT://kafka-broker-1:29092,PLAINTEXT_HOST://localhost:9094' + KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: 'CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT' + KAFKA_CONTROLLER_LISTENER_NAMES: 'CONTROLLER' + KAFKA_INTER_BROKER_LISTENER_NAME: 'PLAINTEXT' + CLUSTER_ID: 'MkU3OEVBNTcwNTJENDM2Qk' + KAFKA_AUTO_CREATE_TOPICS_ENABLE: 'true' + KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 + KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0 + KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1 + KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1 ports: - "9094:9094" networks: - kafka-network + restart: unless-stopped + kafka-broker-2: - image: bitnami/kafka:latest + image: confluentinc/cp-kafka:7.4.0 container_name: kafka-broker-2 environment: - KAFKA_ENABLE_KRAFT: "yes" - KAFKA_CFG_NODE_ID: 2 - KAFKA_CFG_PROCESS_ROLES: broker,controller - KAFKA_CFG_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093,EXTERNAL://0.0.0.0:9095 - KAFKA_CFG_ADVERTISED_LISTENERS: PLAINTEXT://kafka-broker-2:9092,EXTERNAL://localhost:9095 - KAFKA_CFG_CONTROLLER_QUORUM_VOTERS: 1@kafka-broker-1:9093,2@kafka-broker-2:9093,3@kafka-broker-3:9093 - KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT,EXTERNAL:PLAINTEXT - KAFKA_CFG_CONTROLLER_LISTENER_NAMES: CONTROLLER - KAFKA_CFG_INTER_BROKER_LISTENER_NAME: PLAINTEXT - KAFKA_KRAFT_CLUSTER_ID: 7Dvn0OLMQo-bg4qmCmflVg - ALLOW_PLAINTEXT_LISTENER: "yes" + KAFKA_NODE_ID: 2 + KAFKA_PROCESS_ROLES: 'broker,controller' + KAFKA_CONTROLLER_QUORUM_VOTERS: '1@kafka-broker-1:29093,2@kafka-broker-2:29093,3@kafka-broker-3:29093' + KAFKA_LISTENERS: 'PLAINTEXT://kafka-broker-2:29092,CONTROLLER://kafka-broker-2:29093,PLAINTEXT_HOST://0.0.0.0:9095' + KAFKA_ADVERTISED_LISTENERS: 'PLAINTEXT://kafka-broker-2:29092,PLAINTEXT_HOST://localhost:9095' + KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: 'CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT' + KAFKA_CONTROLLER_LISTENER_NAMES: 'CONTROLLER' + KAFKA_INTER_BROKER_LISTENER_NAME: 'PLAINTEXT' + CLUSTER_ID: 'MkU3OEVBNTcwNTJENDM2Qk' + KAFKA_AUTO_CREATE_TOPICS_ENABLE: 'true' + KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 + KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0 + KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1 + KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1 ports: - "9095:9095" networks: - kafka-network + restart: unless-stopped + kafka-broker-3: - image: bitnami/kafka:latest + image: confluentinc/cp-kafka:7.4.0 container_name: kafka-broker-3 environment: - KAFKA_ENABLE_KRAFT: "yes" - KAFKA_CFG_NODE_ID: 3 - KAFKA_CFG_PROCESS_ROLES: broker,controller - KAFKA_CFG_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093,EXTERNAL://0.0.0.0:9096 - KAFKA_CFG_ADVERTISED_LISTENERS: PLAINTEXT://kafka-broker-3:9092,EXTERNAL://localhost:9096 - KAFKA_CFG_CONTROLLER_QUORUM_VOTERS: 1@kafka-broker-1:9093,2@kafka-broker-2:9093,3@kafka-broker-3:9093 - KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP: CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT,EXTERNAL:PLAINTEXT - KAFKA_CFG_CONTROLLER_LISTENER_NAMES: CONTROLLER - KAFKA_CFG_INTER_BROKER_LISTENER_NAME: PLAINTEXT - KAFKA_KRAFT_CLUSTER_ID: 7Dvn0OLMQo-bg4qmCmflVg - ALLOW_PLAINTEXT_LISTENER: "yes" + KAFKA_NODE_ID: 3 + KAFKA_PROCESS_ROLES: 'broker,controller' + KAFKA_CONTROLLER_QUORUM_VOTERS: '1@kafka-broker-1:29093,2@kafka-broker-2:29093,3@kafka-broker-3:29093' + KAFKA_LISTENERS: 'PLAINTEXT://kafka-broker-3:29092,CONTROLLER://kafka-broker-3:29093,PLAINTEXT_HOST://0.0.0.0:9096' + KAFKA_ADVERTISED_LISTENERS: 'PLAINTEXT://kafka-broker-3:29092,PLAINTEXT_HOST://localhost:9096' + KAFKA_LISTENER_SECURITY_PROTOCOL_MAP: 'CONTROLLER:PLAINTEXT,PLAINTEXT:PLAINTEXT,PLAINTEXT_HOST:PLAINTEXT' + KAFKA_CONTROLLER_LISTENER_NAMES: 'CONTROLLER' + KAFKA_INTER_BROKER_LISTENER_NAME: 'PLAINTEXT' + CLUSTER_ID: 'MkU3OEVBNTcwNTJENDM2Qk' + KAFKA_AUTO_CREATE_TOPICS_ENABLE: 'true' + KAFKA_OFFSETS_TOPIC_REPLICATION_FACTOR: 1 + KAFKA_GROUP_INITIAL_REBALANCE_DELAY_MS: 0 + KAFKA_TRANSACTION_STATE_LOG_MIN_ISR: 1 + KAFKA_TRANSACTION_STATE_LOG_REPLICATION_FACTOR: 1 ports: - "9096:9096" networks: - kafka-network + restart: unless-stopped + kafka-ui: image: provectuslabs/kafka-ui:latest container_name: kafka-ui environment: KAFKA_CLUSTERS_0_NAME: local-cluster - KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka-broker-1:9092,kafka-broker-2:9092,kafka-broker-3:9092 + KAFKA_CLUSTERS_0_BOOTSTRAPSERVERS: kafka-broker-1:29092,kafka-broker-2:29092,kafka-broker-3:29092 KAFKA_CLUSTERS_0_READONLY: false - KAFKA_CLUSTERS_0_TOPIC_AUTO_CREATE: true ports: - "8080:8080" depends_on: @@ -72,6 +86,7 @@ services: - kafka-broker-3 networks: - kafka-network + restart: unless-stopped networks: kafka-network: From ad685c33eddb0921b73b18d14ffe93c56ceb5f2b Mon Sep 17 00:00:00 2001 From: Julius Peter Ntale Date: Thu, 9 Oct 2025 15:47:15 +0300 Subject: [PATCH 03/12] feat(database): implement seed script for categories and products --- apps/admin/src/app/(dashboard)/page.tsx | 28 +++-- apps/admin/src/components/AppBarChart.tsx | 7 +- apps/admin/src/components/CardList.tsx | 39 +++++-- apps/client/src/components/ProductList.tsx | 2 +- apps/order-service/src/index.ts | 15 ++- packages/product-db/seed.js | 121 +++++++++++++++++++++ packages/product-db/seed.sql | 79 ++++++++++++++ 7 files changed, 261 insertions(+), 30 deletions(-) create mode 100644 packages/product-db/seed.js create mode 100644 packages/product-db/seed.sql diff --git a/apps/admin/src/app/(dashboard)/page.tsx b/apps/admin/src/app/(dashboard)/page.tsx index 1908eb4b..24aa29ef 100644 --- a/apps/admin/src/app/(dashboard)/page.tsx +++ b/apps/admin/src/app/(dashboard)/page.tsx @@ -8,18 +8,28 @@ import { auth } from "@clerk/nextjs/server"; const Homepage = async () => { const { getToken } = await auth(); const token = await getToken(); - const orderChartData = fetch( - `${process.env.NEXT_PUBLIC_ORDER_SERVICE_URL}/order-chart`, - { - headers: { - Authorization: `Bearer ${token}`, - }, - } - ); + + let orderChartData = []; + try { + const res = await fetch( + `${process.env.NEXT_PUBLIC_ORDER_SERVICE_URL}/order-chart`, + { + headers: { + Authorization: `Bearer ${token}`, + }, + } + ); + const data = await res.json(); + orderChartData = JSON.parse(JSON.stringify(data)); + } catch (error) { + console.error('Failed to fetch order chart data:', error); + orderChartData = []; + } + return (
- +
diff --git a/apps/admin/src/components/AppBarChart.tsx b/apps/admin/src/components/AppBarChart.tsx index 52eddb1a..edb672b1 100644 --- a/apps/admin/src/components/AppBarChart.tsx +++ b/apps/admin/src/components/AppBarChart.tsx @@ -8,7 +8,6 @@ import { type ChartConfig, } from "@/components/ui/chart"; import { OrderChartType } from "@repo/types"; -import { use } from "react"; import { Bar, BarChart, CartesianGrid, XAxis, YAxis } from "recharts"; const chartConfig = { @@ -32,11 +31,11 @@ const chartConfig = { // ]; const AppBarChart = ({ - dataPromise, + data, }: { - dataPromise: Promise; + data: OrderChartType[]; }) => { - const chartData = use(dataPromise); + const chartData = data || []; return (

Total Revenue

diff --git a/apps/admin/src/components/CardList.tsx b/apps/admin/src/components/CardList.tsx index 164b401d..65c8017a 100644 --- a/apps/admin/src/components/CardList.tsx +++ b/apps/admin/src/components/CardList.tsx @@ -123,6 +123,7 @@ import { auth } from "@clerk/nextjs/server"; // ]; const CardList = async ({ title }: { title: string }) => { + let products: ProductsType = []; let orders: OrderType[] = []; @@ -130,18 +131,36 @@ const CardList = async ({ title }: { title: string }) => { const token = await getToken(); if (title === "Popular Products") { - products = await fetch( - `${process.env.NEXT_PUBLIC_PRODUCT_SERVICE_URL}/products?limit=5&popular=true` - ).then((res) => res.json()); + try { + const res = await fetch( + `${process.env.NEXT_PUBLIC_PRODUCT_SERVICE_URL}/products?limit=5&popular=true` + ); + if (res.ok) { + const data = await res.json(); + products = Array.isArray(data) ? JSON.parse(JSON.stringify(data)) : []; + } + } catch (error) { + console.error('Failed to fetch products:', error); + products = []; + } } else { - orders = await fetch( - `${process.env.NEXT_PUBLIC_ORDER_SERVICE_URL}/orders?limit=5`, - { - headers: { - Authorization: `Bearer ${token}`, - }, + try { + const res = await fetch( + `${process.env.NEXT_PUBLIC_ORDER_SERVICE_URL}/orders?limit=5`, + { + headers: { + Authorization: `Bearer ${token}`, + }, + } + ); + if (res.ok) { + const data = await res.json(); + orders = Array.isArray(data) ? JSON.parse(JSON.stringify(data)) : []; } - ).then((res) => res.json()); + } catch (error) { + console.error('Failed to fetch orders:', error); + orders = []; + } } return ( diff --git a/apps/client/src/components/ProductList.tsx b/apps/client/src/components/ProductList.tsx index ac1bf60d..285ae0dc 100644 --- a/apps/client/src/components/ProductList.tsx +++ b/apps/client/src/components/ProductList.tsx @@ -174,7 +174,7 @@ const ProductList = async ({ {params === "products" && }
- {Array.isArray(products) &&products.map((product) => ( + {Array.isArray(products) && products.map((product) => ( ))}
diff --git a/apps/order-service/src/index.ts b/apps/order-service/src/index.ts index a2c6634a..06de7aad 100644 --- a/apps/order-service/src/index.ts +++ b/apps/order-service/src/index.ts @@ -29,16 +29,19 @@ fastify.register(orderRoute); const start = async () => { try { - Promise.all([ - await connectOrderDB(), - await producer.connect(), - await consumer.connect(), + console.log("Starting order service..."); + await Promise.all([ + connectOrderDB(), + producer.connect(), + consumer.connect(), ]); + console.log("Database and Kafka connections established"); await runKafkaSubscriptions(); - await fastify.listen({ port: 8001 }); + console.log("Kafka subscriptions running"); + await fastify.listen({ port: 8001, host: '0.0.0.0' }); console.log("Order service is running on port 8001"); } catch (err) { - console.log(err); + console.error("Failed to start order service:", err); process.exit(1); } }; diff --git a/packages/product-db/seed.js b/packages/product-db/seed.js new file mode 100644 index 00000000..b549a1c6 --- /dev/null +++ b/packages/product-db/seed.js @@ -0,0 +1,121 @@ +import { PrismaClient } from './generated/prisma/index.js'; + +const prisma = new PrismaClient(); + +async function main() { + console.log('Creating categories...'); + + // Create categories first + await prisma.category.createMany({ + data: [ + { name: 'Shirts', slug: 'shirts' }, + { name: 'Hoodies', slug: 'hoodies' }, + { name: 'Sneakers', slug: 'sneakers' }, + { name: 'Jackets', slug: 'jackets' }, + { name: 'Accessories', slug: 'accessories' }, + ], + skipDuplicates: true, + }); + + console.log('Creating products...'); + + // Create products + await prisma.product.createMany({ + data: [ + { + name: 'Adidas CoreFit T-Shirt', + shortDescription: 'Comfortable cotton t-shirt perfect for everyday wear and light workouts.', + description: 'The Adidas CoreFit T-Shirt combines style and comfort with its premium cotton blend fabric. Features the iconic Adidas logo and comes in multiple colors. Perfect for casual wear, gym sessions, or layering. Machine washable and designed to maintain its shape and color after multiple washes.', + price: 3990, + sizes: ['s', 'm', 'l', 'xl', 'xxl'], + colors: ['gray', 'purple', 'green'], + images: { + gray: '/products/1g.png', + purple: '/products/1p.png', + green: '/products/1gr.png', + }, + categorySlug: 'shirts', + }, + { + name: 'Puma Ultra Warm Zip', + shortDescription: 'Thermal zip-up hoodie designed for cold weather and active lifestyle.', + description: 'Stay warm and comfortable with the Puma Ultra Warm Zip hoodie. Features advanced thermal technology, moisture-wicking fabric, and a convenient full-zip design. Perfect for outdoor activities, running, or casual wear during colder months. Includes front pockets and adjustable hood.', + price: 5990, + sizes: ['s', 'm', 'l', 'xl'], + colors: ['gray', 'green'], + images: { + gray: '/products/2g.png', + green: '/products/2gr.png', + }, + categorySlug: 'hoodies', + }, + { + name: 'Nike Air Essentials Pullover', + shortDescription: 'Classic pullover hoodie with Nike Air branding and premium comfort.', + description: 'The Nike Air Essentials Pullover brings timeless style and modern comfort. Made with soft fleece fabric and featuring the classic Nike Air logo. Includes a spacious kangaroo pocket and adjustable drawstring hood. Perfect for layering or wearing solo.', + price: 6990, + sizes: ['s', 'm', 'l'], + colors: ['green', 'blue', 'black'], + images: { + green: '/products/3gr.png', + blue: '/products/3b.png', + black: '/products/3bl.png', + }, + categorySlug: 'hoodies', + }, + { + name: 'Nike Dri Flex T-Shirt', + shortDescription: 'Moisture-wicking performance t-shirt for active individuals.', + description: 'Experience superior comfort and performance with the Nike Dri Flex T-Shirt. Features Nike\'s advanced Dri-FIT technology to keep you dry and comfortable during intense workouts. Lightweight, breathable fabric with a modern athletic fit.', + price: 2990, + sizes: ['s', 'm', 'l'], + colors: ['white', 'pink'], + images: { + white: '/products/4w.png', + pink: '/products/4p.png', + }, + categorySlug: 'shirts', + }, + { + name: 'Under Armour StormFleece', + shortDescription: 'Weather-resistant fleece jacket for outdoor adventures.', + description: 'The Under Armour StormFleece jacket combines warmth with weather protection. Features UA Storm technology to repel water while maintaining breathability. Perfect for hiking, running, or any outdoor activity. Includes zippered pockets and adjustable cuffs.', + price: 4990, + sizes: ['s', 'm', 'l'], + colors: ['red', 'orange', 'black'], + images: { + red: '/products/5r.png', + orange: '/products/5o.png', + black: '/products/5bl.png', + }, + categorySlug: 'jackets', + }, + { + name: 'Nike Air Force Sneakers', + shortDescription: 'Classic basketball-inspired sneakers with modern comfort technology.', + description: 'Step into style with the iconic Nike Air Force sneakers. Features premium leather upper, Air-Sole unit for cushioning, and the timeless silhouette that\'s been a favorite for decades. Perfect for casual wear, streetwear, or light athletic activities.', + price: 8990, + sizes: ['37', '38', '39', '40', '41', '42', '43', '44'], + colors: ['white', 'black', 'gray'], + images: { + white: '/products/6w.png', + black: '/products/6bl.png', + gray: '/products/6g.png', + }, + categorySlug: 'sneakers', + }, + ], + skipDuplicates: true, + }); + + console.log('Sample data inserted successfully!'); +} + +main() + .catch((e) => { + console.error('Error inserting sample data:', e); + process.exit(1); + }) + .finally(async () => { + await prisma.$disconnect(); + }); \ No newline at end of file diff --git a/packages/product-db/seed.sql b/packages/product-db/seed.sql new file mode 100644 index 00000000..d720882c --- /dev/null +++ b/packages/product-db/seed.sql @@ -0,0 +1,79 @@ +-- Insert Categories first (required for foreign key relationship) +INSERT INTO "Category" (name, slug) VALUES +('Shirts', 'shirts'), +('Hoodies', 'hoodies'), +('Sneakers', 'sneakers'), +('Jackets', 'jackets'), +('Accessories', 'accessories'); + +-- Insert Products +INSERT INTO "Product" ( + name, + "shortDescription", + description, + price, + sizes, + colors, + images, + "categorySlug" +) VALUES +( + 'Adidas CoreFit T-Shirt', + 'Comfortable cotton t-shirt perfect for everyday wear and light workouts.', + 'The Adidas CoreFit T-Shirt combines style and comfort with its premium cotton blend fabric. Features the iconic Adidas logo and comes in multiple colors. Perfect for casual wear, gym sessions, or layering. Machine washable and designed to maintain its shape and color after multiple washes.', + 3990, + ARRAY['s', 'm', 'l', 'xl', 'xxl'], + ARRAY['gray', 'purple', 'green'], + '{"gray": "/products/1g.png", "purple": "/products/1p.png", "green": "/products/1gr.png"}', + 'shirts' +), +( + 'Puma Ultra Warm Zip', + 'Thermal zip-up hoodie designed for cold weather and active lifestyle.', + 'Stay warm and comfortable with the Puma Ultra Warm Zip hoodie. Features advanced thermal technology, moisture-wicking fabric, and a convenient full-zip design. Perfect for outdoor activities, running, or casual wear during colder months. Includes front pockets and adjustable hood.', + 5990, + ARRAY['s', 'm', 'l', 'xl'], + ARRAY['gray', 'green'], + '{"gray": "/products/2g.png", "green": "/products/2gr.png"}', + 'hoodies' +), +( + 'Nike Air Essentials Pullover', + 'Classic pullover hoodie with Nike Air branding and premium comfort.', + 'The Nike Air Essentials Pullover brings timeless style and modern comfort. Made with soft fleece fabric and featuring the classic Nike Air logo. Includes a spacious kangaroo pocket and adjustable drawstring hood. Perfect for layering or wearing solo.', + 6990, + ARRAY['s', 'm', 'l'], + ARRAY['green', 'blue', 'black'], + '{"green": "/products/3gr.png", "blue": "/products/3b.png", "black": "/products/3bl.png"}', + 'hoodies' +), +( + 'Nike Dri Flex T-Shirt', + 'Moisture-wicking performance t-shirt for active individuals.', + 'Experience superior comfort and performance with the Nike Dri Flex T-Shirt. Features Nike''s advanced Dri-FIT technology to keep you dry and comfortable during intense workouts. Lightweight, breathable fabric with a modern athletic fit.', + 2990, + ARRAY['s', 'm', 'l'], + ARRAY['white', 'pink'], + '{"white": "/products/4w.png", "pink": "/products/4p.png"}', + 'shirts' +), +( + 'Under Armour StormFleece', + 'Weather-resistant fleece jacket for outdoor adventures.', + 'The Under Armour StormFleece jacket combines warmth with weather protection. Features UA Storm technology to repel water while maintaining breathability. Perfect for hiking, running, or any outdoor activity. Includes zippered pockets and adjustable cuffs.', + 4990, + ARRAY['s', 'm', 'l'], + ARRAY['red', 'orange', 'black'], + '{"red": "/products/5r.png", "orange": "/products/5o.png", "black": "/products/5bl.png"}', + 'jackets' +), +( + 'Nike Air Force Sneakers', + 'Classic basketball-inspired sneakers with modern comfort technology.', + 'Step into style with the iconic Nike Air Force sneakers. Features premium leather upper, Air-Sole unit for cushioning, and the timeless silhouette that''s been a favorite for decades. Perfect for casual wear, streetwear, or light athletic activities.', + 8990, + ARRAY['37', '38', '39', '40', '41', '42', '43', '44'], + ARRAY['white', 'black', 'gray'], + '{"white": "/products/6w.png", "black": "/products/6bl.png", "gray": "/products/6g.png"}', + 'sneakers' +); \ No newline at end of file From 230833ffbd415ff22f4be202f276b5841c536094 Mon Sep 17 00:00:00 2001 From: Julius Peter Ntale Date: Fri, 10 Oct 2025 12:06:49 +0300 Subject: [PATCH 04/12] feat(components): add FeaturedCategories, NavbarWrapper, NewsletterSection, and TechHighlights components --- .../src/components/FeaturedCategories.tsx | 127 ++++++++++++++++ apps/client/src/components/NavbarWrapper.tsx | 32 ++++ .../src/components/NewsletterSection.tsx | 140 ++++++++++++++++++ apps/client/src/components/TechHighlights.tsx | 115 ++++++++++++++ 4 files changed, 414 insertions(+) create mode 100644 apps/client/src/components/FeaturedCategories.tsx create mode 100644 apps/client/src/components/NavbarWrapper.tsx create mode 100644 apps/client/src/components/NewsletterSection.tsx create mode 100644 apps/client/src/components/TechHighlights.tsx diff --git a/apps/client/src/components/FeaturedCategories.tsx b/apps/client/src/components/FeaturedCategories.tsx new file mode 100644 index 00000000..a3d49c92 --- /dev/null +++ b/apps/client/src/components/FeaturedCategories.tsx @@ -0,0 +1,127 @@ +"use client"; + +import Link from "next/link"; +import { Cpu, Smartphone, Headphones, Watch, Camera, Monitor, Gamepad2, Tablet } from "lucide-react"; + +const FeaturedCategories = () => { + const categories = [ + { + id: "smartphones", + name: "Smartphones", + description: "Latest flagship devices", + icon: Smartphone, + count: "50+ Products", + color: "from-blue-500 to-purple-600" + }, + { + id: "audio", + name: "Audio & Sound", + description: "Premium headphones & speakers", + icon: Headphones, + count: "30+ Products", + color: "from-green-500 to-teal-600" + }, + { + id: "wearables", + name: "Wearables", + description: "Smart watches & fitness trackers", + icon: Watch, + count: "25+ Products", + color: "from-orange-500 to-red-600" + }, + { + id: "computing", + name: "Computing", + description: "Laptops, tablets & accessories", + icon: Monitor, + count: "40+ Products", + color: "from-purple-500 to-pink-600" + }, + { + id: "gaming", + name: "Gaming", + description: "Consoles, accessories & gear", + icon: Gamepad2, + count: "35+ Products", + color: "from-indigo-500 to-blue-600" + }, + { + id: "smart-home", + name: "Smart Home", + description: "IoT devices & automation", + icon: Cpu, + count: "45+ Products", + color: "from-teal-500 to-green-600" + }, + { + id: "photography", + name: "Photography", + description: "Cameras & accessories", + icon: Camera, + count: "20+ Products", + color: "from-yellow-500 to-orange-600" + }, + { + id: "tablets", + name: "Tablets", + description: "iPads & Android tablets", + icon: Tablet, + count: "15+ Products", + color: "from-pink-500 to-rose-600" + } + ]; + + return ( +
+
+

+ Explore Categories +

+

+ Discover our extensive range of technology products across multiple categories +

+
+ +
+ {categories.map((category) => { + const IconComponent = category.icon; + return ( + +
+
+ +
+ +

+ {category.name} +

+ +

+ {category.description} +

+ + + {category.count} + +
+ + ); + })} +
+ +
+ + + +
+
+ ); +}; + +export default FeaturedCategories; \ No newline at end of file diff --git a/apps/client/src/components/NavbarWrapper.tsx b/apps/client/src/components/NavbarWrapper.tsx new file mode 100644 index 00000000..62dada5b --- /dev/null +++ b/apps/client/src/components/NavbarWrapper.tsx @@ -0,0 +1,32 @@ +"use client"; + +import dynamic from "next/dynamic"; + +// Create a client-side wrapper for theme-dependent components +const ClientNavbar = dynamic(() => import("./Navbar"), { + ssr: false, + loading: () => ( + + ) +}); + +const NavbarWrapper = () => { + return ; +}; + +export default NavbarWrapper; \ No newline at end of file diff --git a/apps/client/src/components/NewsletterSection.tsx b/apps/client/src/components/NewsletterSection.tsx new file mode 100644 index 00000000..f1371970 --- /dev/null +++ b/apps/client/src/components/NewsletterSection.tsx @@ -0,0 +1,140 @@ +"use client"; + +import { useState } from "react"; +import { Mail, ArrowRight, CheckCircle } from "lucide-react"; + +const NewsletterSection = () => { + const [email, setEmail] = useState(""); + const [isSubscribed, setIsSubscribed] = useState(false); + const [isLoading, setIsLoading] = useState(false); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + if (!email) return; + + setIsLoading(true); + + // Simulate API call + setTimeout(() => { + setIsSubscribed(true); + setIsLoading(false); + setEmail(""); + }, 1000); + }; + + if (isSubscribed) { + return ( +
+
+ +

+ Welcome to Neuraltale! +

+

+ Thank you for subscribing. You'll receive the latest tech updates and exclusive offers. +

+
+
+ ); + } + + return ( +
+
+
+
+ +
+
+ +

+ Stay Ahead of Tech Trends +

+ +

+ Get exclusive access to the latest product launches, tech insights, and special offers. + Join our community of tech enthusiasts and never miss an innovation. +

+ +
+
+ setEmail(e.target.value)} + placeholder="Enter your email address" + className=" + w-full px-4 py-3 + bg-white/10 + border border-white/20 + rounded-lg + text-white + placeholder:text-white/70 + focus:outline-none + focus:border-white/40 + focus:bg-white/20 + transition-all + " + required + /> +
+ + +
+ +
+

+ By subscribing, you agree to our privacy policy and terms of service. + Unsubscribe anytime. +

+
+ + {/* Features */} +
+
+
Weekly Updates
+
Latest tech news & reviews
+
+
+
Exclusive Offers
+
Subscriber-only discounts
+
+
+
Early Access
+
Be first to know about launches
+
+
+
+
+ ); +}; + +export default NewsletterSection; \ No newline at end of file diff --git a/apps/client/src/components/TechHighlights.tsx b/apps/client/src/components/TechHighlights.tsx new file mode 100644 index 00000000..518b8ec4 --- /dev/null +++ b/apps/client/src/components/TechHighlights.tsx @@ -0,0 +1,115 @@ +"use client"; + +import { Battery, Zap, Shield, Wifi, Globe, Smartphone } from "lucide-react"; + +const TechHighlights = () => { + const highlights = [ + { + icon: Zap, + title: "Lightning Fast", + description: "Experience blazing-fast performance with our cutting-edge processors and optimized software", + stats: "99.9% Uptime" + }, + { + icon: Battery, + title: "All-Day Battery", + description: "Extended battery life that keeps you connected and productive throughout the day", + stats: "48h+ Battery" + }, + { + icon: Shield, + title: "Advanced Security", + description: "Military-grade encryption and biometric authentication for ultimate protection", + stats: "256-bit Encryption" + }, + { + icon: Wifi, + title: "Seamless Connectivity", + description: "5G, Wi-Fi 6E, and Bluetooth 5.3 for instant connections anywhere", + stats: "5G Ready" + }, + { + icon: Globe, + title: "Global Compatibility", + description: "Works seamlessly across different regions and network standards worldwide", + stats: "200+ Countries" + }, + { + icon: Smartphone, + title: "Smart Integration", + description: "AI-powered features that adapt to your usage patterns and preferences", + stats: "AI Enhanced" + } + ]; + + return ( +
+
+

+ Tech Highlights +

+

+ Discover the cutting-edge features and innovations in our premium technology products. +

+
+ +
+ {highlights.map((highlight, index) => { + const IconComponent = highlight.icon; + return ( +
+
+
+ +
+ +

+ {highlight.title} +

+ +

+ {highlight.description} +

+ +
+ {highlight.stats} +
+
+
+ ); + })} +
+ + {/* Additional Stats Section */} +
+

+ Trusted by Tech Enthusiasts Worldwide +

+ +
+
+
50K+
+
Happy Customers
+
+
+
500+
+
Products
+
+
+
100+
+
Brands
+
+
+
98%
+
Satisfaction Rate
+
+
+
+
+ ); +}; + +export default TechHighlights; \ No newline at end of file From d5e5d0d52df2dbb731e2f5000f75d25952a96664 Mon Sep 17 00:00:00 2001 From: Julius Peter Ntale Date: Fri, 10 Oct 2025 12:07:09 +0300 Subject: [PATCH 05/12] Add product reviews, quick view modal, recently viewed, trust indicators, and write review components - Implemented ProductReviews component for displaying and managing product reviews. - Created QuickViewModal for quick product details and actions. - Added RecentlyViewed component to showcase recently viewed products. - Developed TrustIndicators component to display trust metrics and achievements. - Introduced WriteReview component for submitting product reviews with validation. --- apps/admin/src/app/(dashboard)/page.tsx | 43 +- .../src/app/(dashboard)/products/page.tsx | 5 +- apps/client/package.json | 2 +- apps/client/src/app/layout.tsx | 35 +- apps/client/src/app/page.tsx | 33 +- apps/client/src/app/products/page.tsx | 31 +- apps/client/src/components/Categories.tsx | 131 +++-- apps/client/src/components/EnhancedCart.tsx | 258 +++++++++ .../src/components/FeaturedCategories.tsx | 127 ----- apps/client/src/components/Footer.tsx | 201 +++++-- apps/client/src/components/HeroSection.tsx | 379 ++++++++++++++ .../src/components/MobileNavigation.tsx | 270 ++++++++++ .../src/components/MobileProductGrid.tsx | 146 ++++++ apps/client/src/components/Navbar.tsx | 240 +++++++-- apps/client/src/components/NavbarWrapper.tsx | 32 -- .../src/components/NewsletterSection.tsx | 140 ----- apps/client/src/components/ProductCard.tsx | 232 +++++++-- apps/client/src/components/ProductList.tsx | 490 +++++++++++++----- apps/client/src/components/ProductReviews.tsx | 330 ++++++++++++ apps/client/src/components/QuickViewModal.tsx | 322 ++++++++++++ apps/client/src/components/RecentlyViewed.tsx | 148 ++++++ apps/client/src/components/SearchBar.tsx | 97 +++- apps/client/src/components/TechHighlights.tsx | 115 ---- .../client/src/components/TrustIndicators.tsx | 112 ++++ apps/client/src/components/WriteReview.tsx | 219 ++++++++ packages/types/src/product.ts | 55 +- 26 files changed, 3406 insertions(+), 787 deletions(-) create mode 100644 apps/client/src/components/EnhancedCart.tsx delete mode 100644 apps/client/src/components/FeaturedCategories.tsx create mode 100644 apps/client/src/components/HeroSection.tsx create mode 100644 apps/client/src/components/MobileNavigation.tsx create mode 100644 apps/client/src/components/MobileProductGrid.tsx delete mode 100644 apps/client/src/components/NavbarWrapper.tsx delete mode 100644 apps/client/src/components/NewsletterSection.tsx create mode 100644 apps/client/src/components/ProductReviews.tsx create mode 100644 apps/client/src/components/QuickViewModal.tsx create mode 100644 apps/client/src/components/RecentlyViewed.tsx delete mode 100644 apps/client/src/components/TechHighlights.tsx create mode 100644 apps/client/src/components/TrustIndicators.tsx create mode 100644 apps/client/src/components/WriteReview.tsx diff --git a/apps/admin/src/app/(dashboard)/page.tsx b/apps/admin/src/app/(dashboard)/page.tsx index 24aa29ef..9362c465 100644 --- a/apps/admin/src/app/(dashboard)/page.tsx +++ b/apps/admin/src/app/(dashboard)/page.tsx @@ -27,24 +27,33 @@ const Homepage = async () => { } return ( -
-
- +
+ {/* Page Header */} +
+

Neuraltale Admin Dashboard

+

Manage your premium tech products and track sales performance

-
- -
-
- -
-
- -
-
- -
-
- + + {/* Main Dashboard Grid */} +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
+
+ +
); diff --git a/apps/admin/src/app/(dashboard)/products/page.tsx b/apps/admin/src/app/(dashboard)/products/page.tsx index 2690378f..1e2df238 100644 --- a/apps/admin/src/app/(dashboard)/products/page.tsx +++ b/apps/admin/src/app/(dashboard)/products/page.tsx @@ -19,8 +19,9 @@ const ProductPage = async () => { const data = await getData(); return (
-
-

All Products

+
+

Tech Products Management

+

Manage your premium tech product catalog including smartphones, laptops, audio gear, and accessories.

diff --git a/apps/client/package.json b/apps/client/package.json index 5952a9af..91d4de84 100644 --- a/apps/client/package.json +++ b/apps/client/package.json @@ -3,7 +3,7 @@ "version": "0.1.0", "private": true, "scripts": { - "dev": "next dev --turbopack --port 3002", + "dev": "next dev --turbopack --port 3004", "build": "next build", "start": "next start", "lint": "next lint", diff --git a/apps/client/src/app/layout.tsx b/apps/client/src/app/layout.tsx index 6225917b..4438b695 100644 --- a/apps/client/src/app/layout.tsx +++ b/apps/client/src/app/layout.tsx @@ -17,8 +17,25 @@ const geistMono = Geist_Mono({ }); export const metadata: Metadata = { - title: "Trendlama - Best Clothes", - description: "Trendlama is the best place to find the best clothes", + title: "Neuraltale - Premium Tech Store", + description: "Discover the future of technology with Neuraltale. Premium tech products, cutting-edge innovation, and exceptional quality.", + keywords: "tech, electronics, smartphones, laptops, audio, gadgets, innovation", + authors: [{ name: "Neuraltale" }], + creator: "Neuraltale", + publisher: "Neuraltale", + metadataBase: new URL('https://neuraltale.com'), + openGraph: { + title: "Neuraltale - Premium Tech Store", + description: "Discover the future of technology with Neuraltale", + url: "https://neuraltale.com", + siteName: "Neuraltale", + type: "website", + }, + twitter: { + card: "summary_large_image", + title: "Neuraltale - Premium Tech Store", + description: "Discover the future of technology with Neuraltale", + }, }; export default function RootLayout({ @@ -30,14 +47,20 @@ export default function RootLayout({ -
+
- {children} +
+ {children} +
- + diff --git a/apps/client/src/app/page.tsx b/apps/client/src/app/page.tsx index 898dc77f..1830293c 100644 --- a/apps/client/src/app/page.tsx +++ b/apps/client/src/app/page.tsx @@ -1,5 +1,6 @@ import ProductList from "@/components/ProductList"; -import Image from "next/image"; +import HeroSection from "@/components/HeroSection"; +import TrustIndicators from "@/components/TrustIndicators"; const Homepage = async ({ searchParams, @@ -8,11 +9,31 @@ const Homepage = async ({ }) => { const category = (await searchParams).category; return ( -
-
- Featured Product -
- +
+ {/* Hero Section - Full width */} + + + {/* Trust Indicators - With container */} +
+
+ +
+
+ + {/* Featured Products - With container */} +
+
+
+

+ Featured Products +

+

+ Discover our carefully curated selection of the latest and greatest tech products +

+
+ +
+
); }; diff --git a/apps/client/src/app/products/page.tsx b/apps/client/src/app/products/page.tsx index 4f15afed..fcf88c29 100644 --- a/apps/client/src/app/products/page.tsx +++ b/apps/client/src/app/products/page.tsx @@ -8,14 +8,31 @@ const ProductsPage = async ({ const category = (await searchParams).category; const sort = (await searchParams).sort; const search = (await searchParams).search; + return ( -
- +
+
+ {/* Page Header */} +
+

+ {search ? `Search results for "${search}"` : + category && category !== 'all' ? + `${category.charAt(0).toUpperCase() + category.slice(1).replace('-', ' ')}` : + 'All Products'} +

+

+ Discover premium tech products designed for the future +

+
+ + {/* Products */} + +
); }; diff --git a/apps/client/src/components/Categories.tsx b/apps/client/src/components/Categories.tsx index 3b89987b..364769ce 100644 --- a/apps/client/src/components/Categories.tsx +++ b/apps/client/src/components/Categories.tsx @@ -1,12 +1,14 @@ "use client"; import { - Footprints, - Glasses, - Briefcase, - Shirt, + Smartphone, + Laptop, + Headphones, + Monitor, + Gamepad2, + Router, + Watch, ShoppingBasket, - Hand, - Venus, + Tablet, } from "lucide-react"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; @@ -15,41 +17,55 @@ const categories = [ name: "All", icon: , slug: "all", + count: 26, }, { - name: "T-shirts", - icon: , - slug: "t-shirts", + name: "Smartphones", + icon: , + slug: "smartphones", + count: 6, }, { - name: "Shoes", - icon: , - slug: "shoes", + name: "Laptops", + icon: , + slug: "laptops", + count: 4, }, { - name: "Accessories", - icon: , - slug: "accessories", + name: "Audio", + icon: , + slug: "audio", + count: 4, + }, + { + name: "Gaming", + icon: , + slug: "gaming-laptops", + count: 2, }, { - name: "Bags", - icon: , - slug: "bags", + name: "Tablets", + icon: , + slug: "tablets", + count: 2, }, { - name: "Dresses", - icon: , - slug: "dresses", + name: "Monitors", + icon: , + slug: "monitors", + count: 2, }, { - name: "Jackets", - icon: , - slug: "jackets", + name: "Accessories", + icon: , + slug: "accessories", + count: 4, }, { - name: "Gloves", - icon: , - slug: "gloves", + name: "Wearables", + icon: , + slug: "wearables", + count: 2, }, ]; @@ -58,28 +74,57 @@ const Categories = () => { const router = useRouter(); const pathname = usePathname(); - const selectedCategory = searchParams.get("category"); + const selectedCategory = searchParams.get("category") || "all"; - const handleChange = (value: string | null) => { + const handleChange = (value: string) => { const params = new URLSearchParams(searchParams); - params.set("category", value || "all"); + if (value === "all") { + params.delete("category"); + } else { + params.set("category", value); + } router.push(`${pathname}?${params.toString()}`, { scroll: false }); }; return ( -
- {categories.map((category) => ( -
handleChange(category.slug)} - > - {category.icon} - {category.name} -
- ))} +
+

Browse Categories

+
+ {categories.map((category) => { + const isSelected = category.slug === selectedCategory; + return ( + + ); + })} +
); }; diff --git a/apps/client/src/components/EnhancedCart.tsx b/apps/client/src/components/EnhancedCart.tsx new file mode 100644 index 00000000..39226991 --- /dev/null +++ b/apps/client/src/components/EnhancedCart.tsx @@ -0,0 +1,258 @@ +"use client"; + +import useCartStore from "@/stores/cartStore"; +import { ShoppingCart, Plus, Minus, Trash2, Heart, Tag, Truck } from "lucide-react"; +import Image from "next/image"; +import Link from "next/link"; +import { useState } from "react"; +import { toast } from "react-toastify"; + +const EnhancedCart = () => { + const { cartItems, removeFromCart, updateQuantity, clearCart } = useCartStore(); + const [promoCode, setPromoCode] = useState(""); + const [appliedPromo, setAppliedPromo] = useState(null); + + const subtotal = cartItems.reduce((total, item) => total + (item.price * item.quantity), 0); + const shipping = subtotal > 100 ? 0 : 15; // Free shipping over $100 + const discount = appliedPromo === "NEURALTALE10" ? subtotal * 0.1 : 0; + const tax = (subtotal - discount) * 0.08; // 8% tax + const total = subtotal + shipping + tax - discount; + + const handleApplyPromo = () => { + if (promoCode.toUpperCase() === "NEURALTALE10") { + setAppliedPromo("NEURALTALE10"); + toast.success("Promo code applied! 10% discount"); + } else if (promoCode.toUpperCase() === "FREESHIP") { + setAppliedPromo("FREESHIP"); + toast.success("Free shipping applied!"); + } else { + toast.error("Invalid promo code"); + } + setPromoCode(""); + }; + + const handleRemovePromo = () => { + setAppliedPromo(null); + toast.info("Promo code removed"); + }; + + const handleSaveForLater = (itemId: number) => { + // In a real app, this would save to wishlist + removeFromCart(itemId); + toast.success("Item saved to wishlist"); + }; + + if (cartItems.length === 0) { + return ( +
+ +

Your cart is empty

+

Start shopping to add items to your cart

+ + Start Shopping + +
+ ); + } + + return ( +
+ {/* Header */} +
+
+

+ Shopping Cart ({cartItems.length} items) +

+ +
+
+ + {/* Cart Items */} +
+ {cartItems.map((item) => { + const itemImage = Object.values(item.images as Record)[0]; + + return ( +
+
+
+ {item.name} +
+ +
+
+

{item.name}

+

{item.shortDescription}

+
+ +
+ Size: {item.selectedSize} + Color: {item.selectedColor} +
+ +
+
+ + {item.quantity} + +
+ +
+
+ ${(item.price * item.quantity).toLocaleString()} +
+
+ ${item.price.toLocaleString()} each +
+
+
+ +
+ + +
+
+
+
+ ); + })} +
+ + {/* Promo Code */} +
+
+
+ setPromoCode(e.target.value)} + placeholder="Enter promo code" + className="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500" + /> +
+ +
+ + {appliedPromo && ( +
+
+ + + {appliedPromo === "NEURALTALE10" ? "10% off applied" : "Free shipping applied"} + +
+ +
+ )} +
+ + {/* Order Summary */} +
+

Order Summary

+ +
+
+ Subtotal + ${subtotal.toFixed(2)} +
+ + {discount > 0 && ( +
+ Discount + -${discount.toFixed(2)} +
+ )} + +
+ + + Shipping + + + {shipping === 0 ? "Free" : `$${shipping.toFixed(2)}`} + +
+ +
+ Tax + ${tax.toFixed(2)} +
+ +
+
+ Total + ${total.toFixed(2)} +
+
+
+ + {shipping > 0 && ( +
+ Add ${(100 - subtotal).toFixed(2)} more for free shipping! +
+ )} + +
+ + + Continue Shopping + +
+
+
+ ); +}; + +export default EnhancedCart; \ No newline at end of file diff --git a/apps/client/src/components/FeaturedCategories.tsx b/apps/client/src/components/FeaturedCategories.tsx deleted file mode 100644 index a3d49c92..00000000 --- a/apps/client/src/components/FeaturedCategories.tsx +++ /dev/null @@ -1,127 +0,0 @@ -"use client"; - -import Link from "next/link"; -import { Cpu, Smartphone, Headphones, Watch, Camera, Monitor, Gamepad2, Tablet } from "lucide-react"; - -const FeaturedCategories = () => { - const categories = [ - { - id: "smartphones", - name: "Smartphones", - description: "Latest flagship devices", - icon: Smartphone, - count: "50+ Products", - color: "from-blue-500 to-purple-600" - }, - { - id: "audio", - name: "Audio & Sound", - description: "Premium headphones & speakers", - icon: Headphones, - count: "30+ Products", - color: "from-green-500 to-teal-600" - }, - { - id: "wearables", - name: "Wearables", - description: "Smart watches & fitness trackers", - icon: Watch, - count: "25+ Products", - color: "from-orange-500 to-red-600" - }, - { - id: "computing", - name: "Computing", - description: "Laptops, tablets & accessories", - icon: Monitor, - count: "40+ Products", - color: "from-purple-500 to-pink-600" - }, - { - id: "gaming", - name: "Gaming", - description: "Consoles, accessories & gear", - icon: Gamepad2, - count: "35+ Products", - color: "from-indigo-500 to-blue-600" - }, - { - id: "smart-home", - name: "Smart Home", - description: "IoT devices & automation", - icon: Cpu, - count: "45+ Products", - color: "from-teal-500 to-green-600" - }, - { - id: "photography", - name: "Photography", - description: "Cameras & accessories", - icon: Camera, - count: "20+ Products", - color: "from-yellow-500 to-orange-600" - }, - { - id: "tablets", - name: "Tablets", - description: "iPads & Android tablets", - icon: Tablet, - count: "15+ Products", - color: "from-pink-500 to-rose-600" - } - ]; - - return ( -
-
-

- Explore Categories -

-

- Discover our extensive range of technology products across multiple categories -

-
- -
- {categories.map((category) => { - const IconComponent = category.icon; - return ( - -
-
- -
- -

- {category.name} -

- -

- {category.description} -

- - - {category.count} - -
- - ); - })} -
- -
- - - -
-
- ); -}; - -export default FeaturedCategories; \ No newline at end of file diff --git a/apps/client/src/components/Footer.tsx b/apps/client/src/components/Footer.tsx index 055a93a3..e599c356 100644 --- a/apps/client/src/components/Footer.tsx +++ b/apps/client/src/components/Footer.tsx @@ -1,41 +1,180 @@ -import Image from "next/image"; +"use client"; + import Link from "next/link"; +import { Twitter, Instagram, Youtube, Linkedin, Mail, Phone, MapPin, ArrowRight } from "lucide-react"; +import { useState } from "react"; const Footer = () => { + const [email, setEmail] = useState(""); + + const handleNewsletterSubmit = (e: React.FormEvent) => { + e.preventDefault(); + // Handle newsletter signup + console.log("Newsletter signup:", email); + setEmail(""); + }; + return ( -
-
- - TrendLama -

- TRENDLAMA. -

- -

© 2025 Trendlama.

-

All rights reserved.

+
+ {/* Newsletter Section */} +
+
+
+
+

+ Stay ahead of the tech curve +

+

+ Get exclusive access to new products, deals, and tech insights delivered to your inbox. +

+
+
+ setEmail(e.target.value)} + placeholder="Enter your email" + className="flex-1 px-4 py-3 rounded-lg border-0 bg-white/10 backdrop-blur-sm text-white placeholder-gray-300 focus:ring-2 focus:ring-blue-500 focus:outline-none" + required + /> + +
+
+
-
-

Links

- Homepage - Contact - Terms of Service - Privacy Policy -
-
-

Links

- All Products - New Arrivals - Best Sellers - Sale + + {/* Main Footer */} +
+
+ {/* Brand Section */} +
+ +
+ N +
+ + Neuraltale + + +

+ Discover the future of technology. Premium tech products, cutting-edge innovation, and exceptional quality. +

+ +
+ + {/* Shop Section */} +
+

Shop

+
    +
  • All Products
  • +
  • Smartphones
  • +
  • Laptops
  • +
  • Audio
  • +
  • Gaming
  • +
  • Accessories
  • +
+
+ + {/* Services Section */} +
+

Services

+
    +
  • All Services
  • +
  • Solutions
  • +
  • Industries
  • +
  • Case Studies
  • +
  • Support
  • +
  • Careers
  • +
+
+ + {/* Company Section */} +
+

Company

+
    +
  • About Us
  • +
  • Blog
  • +
  • Press
  • +
  • Careers
  • +
  • Sitemap
  • +
+ + {/* Contact Info */} +
+
+ + hello@neuraltale.com +
+
+ + +1 (555) 123-4567 +
+
+ + San Francisco, CA +
+
+
+
-
-

Links

- About - Contact - Blog - Affiliate Program + + {/* Bottom Bar */} +
+
+
+
+

+ © 2025 Neuraltale. All rights reserved. +

+
+ + Privacy Policy + + + Terms of Service + + + Support + +
+
+
+ Secure payments powered by +
+
+ VISA +
+
+ MC +
+
+ PP +
+
+
+
+
-
+
); }; diff --git a/apps/client/src/components/HeroSection.tsx b/apps/client/src/components/HeroSection.tsx new file mode 100644 index 00000000..d8d3afd0 --- /dev/null +++ b/apps/client/src/components/HeroSection.tsx @@ -0,0 +1,379 @@ +"use client"; + +import { useState, useEffect } from "react"; +import Image from "next/image"; +import Link from "next/link"; +import { ChevronLeft, ChevronRight, Star, Zap, Headphones, Gamepad2 } from "lucide-react"; + +interface FeaturedProduct { + id: string; + name: string; + price: number; + originalPrice?: number; + image: string; + specifications: string[]; + keyFeatures: string[]; + availability: "In Stock" | "Limited Stock" | "Pre-Order"; + rating: number; + category: string; + description: string; +} + +const featuredProducts: FeaturedProduct[] = [ + { + id: "logitech-mx-master-3", + name: "Logitech MX Master 3", + price: 99.99, + originalPrice: 129.99, + image: "/products/logitech-mx-master-3.jpg", + specifications: [ + "4000 DPI Darkfield sensor", + "70-day battery life", + "USB-C quick charging", + "Bluetooth & USB connectivity" + ], + keyFeatures: [ + "Ultra-precise scrolling", + "Cross-computer control", + "Customizable buttons", + "Ergonomic design" + ], + availability: "In Stock", + rating: 4.8, + category: "Accessories", + description: "The ultimate precision mouse for power users and creative professionals" + }, + { + id: "macbook-pro-m4", + name: "MacBook Pro M4 Chip", + price: 1999.99, + originalPrice: 2199.99, + image: "/products/macbook-pro-m4.jpg", + specifications: [ + "Apple M4 chip with 10-core CPU", + "16-core Neural Engine", + "16GB unified memory", + "512GB SSD storage" + ], + keyFeatures: [ + "20-hour battery life", + "Liquid Retina XDR display", + "1080p FaceTime HD camera", + "Six-speaker sound system" + ], + availability: "In Stock", + rating: 4.9, + category: "Laptops", + description: "Supercharged for pros with the revolutionary M4 chip" + }, + { + id: "airpods-pro", + name: "AirPods Pro (3rd Gen)", + price: 249.99, + originalPrice: 279.99, + image: "/products/airpods-pro.jpg", + specifications: [ + "Active Noise Cancellation", + "Transparency mode", + "Spatial Audio support", + "H2 chip for enhanced audio" + ], + keyFeatures: [ + "Up to 6 hours listening time", + "Personalized Spatial Audio", + "Touch control", + "Sweat and water resistant" + ], + availability: "In Stock", + rating: 4.7, + category: "Audio", + description: "Premium wireless earbuds with industry-leading noise cancellation" + }, + { + id: "logitech-k380-mini", + name: "Logitech K380 Mini Keyboard", + price: 39.99, + originalPrice: 49.99, + image: "/products/logitech-k380.jpg", + specifications: [ + "Bluetooth wireless connection", + "Multi-device pairing (3 devices)", + "Round concave keys", + "2-year battery life" + ], + keyFeatures: [ + "Easy-Switch technology", + "Cross-platform compatibility", + "Compact & portable design", + "Silent typing experience" + ], + availability: "In Stock", + rating: 4.6, + category: "Accessories", + description: "Compact wireless keyboard for seamless multi-device typing" + }, + { + id: "gaming-chair-pro", + name: "ErgoMax Gaming Chair Pro", + price: 299.99, + originalPrice: 399.99, + image: "/products/gaming-chair-pro.jpg", + specifications: [ + "Premium PU leather upholstery", + "High-density foam padding", + "Steel frame construction", + "360° swivel with smooth casters" + ], + keyFeatures: [ + "Adjustable lumbar support", + "4D armrests", + "Reclining up to 135°", + "Weight capacity: 300 lbs" + ], + availability: "Limited Stock", + rating: 4.5, + category: "Gaming", + description: "Professional gaming chair designed for extended comfort sessions" + }, + { + id: "gaming-laptop-asus", + name: "ASUS ROG Strix G16", + price: 1299.99, + originalPrice: 1499.99, + image: "/products/asus-rog-strix.jpg", + specifications: [ + "Intel Core i7-13650HX", + "NVIDIA GeForce RTX 4060", + "16GB DDR5 RAM", + "512GB PCIe 4.0 SSD" + ], + keyFeatures: [ + "16\" FHD 165Hz display", + "RGB backlit keyboard", + "Advanced cooling system", + "Wi-Fi 6E connectivity" + ], + availability: "Pre-Order", + rating: 4.8, + category: "Gaming Laptops", + description: "High-performance gaming laptop for competitive gaming and content creation" + } +]; + +const HeroSection = () => { + const [currentSlide, setCurrentSlide] = useState(0); + const [isAutoPlaying, setIsAutoPlaying] = useState(true); + + useEffect(() => { + if (isAutoPlaying) { + const interval = setInterval(() => { + setCurrentSlide((prev) => (prev + 1) % featuredProducts.length); + }, 5000); + return () => clearInterval(interval); + } + }, [isAutoPlaying]); + + const nextSlide = () => { + setCurrentSlide((prev) => (prev + 1) % featuredProducts.length); + }; + + const prevSlide = () => { + setCurrentSlide((prev) => (prev - 1 + featuredProducts.length) % featuredProducts.length); + }; + + const currentProduct = featuredProducts[currentSlide]; + + if (!currentProduct) { + return
Loading...
; + } + + const getCategoryIcon = (category: string) => { + switch (category) { + case "Audio": + return ; + case "Gaming": + case "Gaming Laptops": + return ; + default: + return ; + } + }; + + return ( +
+ {/* Background Pattern */} +
+
+
+ +
+
+ {/* Product Content */} +
+ {/* Brand Header */} +
+
+ {getCategoryIcon(currentProduct.category)} + + {currentProduct.category} + +
+

+ {currentProduct.name} +

+

+ {currentProduct.description} +

+
+ + {/* Rating */} +
+
+ {[...Array(5)].map((_, i) => ( + + ))} +
+ {currentProduct.rating} + (2,847 reviews) +
+ + {/* Key Features */} +
+

Key Features

+
+ {currentProduct.keyFeatures.map((feature, index) => ( +
+
+ {feature} +
+ ))} +
+
+ + {/* Pricing */} +
+
+ + ${currentProduct.price} + + {currentProduct.originalPrice && ( + + ${currentProduct.originalPrice} + + )} + {currentProduct.originalPrice && ( + + SAVE ${(currentProduct.originalPrice - currentProduct.price).toFixed(2)} + + )} +
+
+
+ {currentProduct.availability} +
+
+ + {/* CTA Buttons */} +
+ + Shop Now + + + View Details + +
+
+ + {/* Product Image */} +
+
+ {currentProduct.name} + {/* Floating Spec Card */} +
+

Specifications

+
    + {currentProduct.specifications.slice(0, 3).map((spec, index) => ( +
  • + • {spec} +
  • + ))} +
+
+
+
+
+ + {/* Navigation Controls */} +
+ + + {/* Slide Indicators */} +
+ {featuredProducts.map((_, index) => ( +
+ + +
+
+
+ ); +}; + +export default HeroSection; \ No newline at end of file diff --git a/apps/client/src/components/MobileNavigation.tsx b/apps/client/src/components/MobileNavigation.tsx new file mode 100644 index 00000000..a009d371 --- /dev/null +++ b/apps/client/src/components/MobileNavigation.tsx @@ -0,0 +1,270 @@ +"use client"; + +import { Menu, X, Search, ShoppingCart, User, Heart } from "lucide-react"; +import Link from "next/link"; +import { useState } from "react"; +import useCartStore from "@/stores/cartStore"; + +const MobileNavigation = () => { + const [isMenuOpen, setIsMenuOpen] = useState(false); + const [isSearchOpen, setIsSearchOpen] = useState(false); + const [searchQuery, setSearchQuery] = useState(""); + + const { cart } = useCartStore(); + const cartItemCount = cart.reduce((total: number, item: any) => total + item.quantity, 0); + + const navigationItems = [ + { name: "Home", href: "/" }, + { name: "Services", href: "/services" }, + { name: "Products", href: "/products" }, + { name: "Solutions", href: "/solutions" }, + { name: "Industries", href: "/industries" }, + { name: "Case Studies", href: "/case-studies" }, + { name: "Blog", href: "/blog" }, + { name: "About", href: "/about" }, + { name: "Contact", href: "/contact" }, + { name: "Support", href: "/support" }, + ]; + + const categories = [ + { name: "Smartphones", href: "/products?category=smartphones" }, + { name: "Laptops", href: "/products?category=laptops" }, + { name: "Audio", href: "/products?category=audio" }, + { name: "Gaming", href: "/products?category=gaming-laptops" }, + { name: "Tablets", href: "/products?category=tablets" }, + { name: "Monitors", href: "/products?category=monitors" }, + { name: "Accessories", href: "/products?category=accessories" }, + { name: "Wearables", href: "/products?category=wearables" }, + ]; + + const handleSearch = (e: React.FormEvent) => { + e.preventDefault(); + if (searchQuery.trim()) { + window.location.href = `/products?search=${encodeURIComponent(searchQuery.trim())}`; + } + }; + + return ( + <> + {/* Mobile Header */} +
+
+ {/* Menu Button */} + + + {/* Logo */} + + Neuraltale + + + {/* Actions */} +
+ + + + + {cartItemCount > 0 && ( + + {cartItemCount} + + )} + +
+
+ + {/* Search Bar (Mobile) */} + {isSearchOpen && ( +
+
+ setSearchQuery(e.target.value)} + placeholder="Search products..." + className="w-full pl-10 pr-12 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500" + autoFocus + /> + + + +
+ )} +
+ + {/* Mobile Menu Overlay */} + {isMenuOpen && ( +
+
setIsMenuOpen(false)} /> + +
+ {/* Header */} +
+ setIsMenuOpen(false)}> + Neuraltale + + +
+ + {/* User Section */} +
+
+
+ +
+
+

Welcome!

+

Sign in for better experience

+
+
+ +
+ setIsMenuOpen(false)} + > + Sign In + + setIsMenuOpen(false)} + > + + Wishlist + +
+
+ + {/* Navigation */} +
+ +
+ + {/* Categories */} +
+

Shop by Category

+ +
+ + {/* Footer */} +
+
+

Need help?

+ setIsMenuOpen(false)} + > + Contact Support + +
+
+
+
+ )} + + {/* Bottom Navigation (Mobile) */} +
+
+ +
+ + + +
+ Home + + + +
+ + + +
+ Shop + + + + + +
+ + {cartItemCount > 0 && ( + + {cartItemCount} + + )} +
+ Cart + +
+
+ + ); +}; + +export default MobileNavigation; \ No newline at end of file diff --git a/apps/client/src/components/MobileProductGrid.tsx b/apps/client/src/components/MobileProductGrid.tsx new file mode 100644 index 00000000..9e5c9517 --- /dev/null +++ b/apps/client/src/components/MobileProductGrid.tsx @@ -0,0 +1,146 @@ +"use client"; + +import { ProductType } from "@repo/types"; +import ProductCard from "./ProductCard"; +import { Grid, List } from "lucide-react"; +import { useState } from "react"; + +interface MobileProductGridProps { + products: ProductType[]; +} + +const MobileProductGrid: React.FC = ({ products }) => { + const [viewMode, setViewMode] = useState<"grid" | "list">("grid"); + + return ( +
+ {/* View Toggle */} +
+ + {products.length} products + +
+ + +
+
+ + {/* Product Grid */} +
+ {products.map((product) => ( +
+ {viewMode === "list" ? ( + + ) : ( + + )} +
+ ))} +
+
+ ); +}; + +// List view component for mobile +const MobileProductListItem = ({ product }: { product: ProductType }) => { + const firstImage = Object.values(product.images as Record)[0]; + const rating = 4.3 + (product.id % 10) * 0.06; + + return ( +
+ {/* Image */} +
+ {product.name} +
+ + {/* Content */} +
+

+ {product.name} +

+

+ {product.shortDescription} +

+ + {/* Rating */} +
+
+ {[...Array(5)].map((_, i) => ( + + + + ))} +
+ + {rating.toFixed(1)} + +
+ + {/* Price and Action */} +
+
+ + ${product.price.toLocaleString()} + + {product.id % 4 === 0 && ( + + 15% off + + )} +
+ +
+
+
+ ); +}; + +export default MobileProductGrid; \ No newline at end of file diff --git a/apps/client/src/components/Navbar.tsx b/apps/client/src/components/Navbar.tsx index 0d7e3958..2d1722a5 100644 --- a/apps/client/src/components/Navbar.tsx +++ b/apps/client/src/components/Navbar.tsx @@ -1,49 +1,221 @@ -import Image from "next/image"; +"use client"; + import Link from "next/link"; import SearchBar from "./SearchBar"; -import { Bell, Home, ShoppingCart } from "lucide-react"; +import { User, Menu, X, ChevronDown } from "lucide-react"; import ShoppingCartIcon from "./ShoppingCartIcon"; import { SignedIn, SignedOut, SignInButton, - SignUpButton, UserButton, } from "@clerk/nextjs"; -import ProfileButton from "./ProfileButton"; +import { useState } from "react"; +import MobileNavigation from "./MobileNavigation"; const Navbar = () => { + const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); + const [activeDropdown, setActiveDropdown] = useState(null); + + const navigationItems = [ + { + name: "Services", + href: "/services", + dropdown: [ + { name: "All Services", href: "/services" }, + { name: "Solutions", href: "/solutions" }, + { name: "Industries", href: "/industries" }, + { name: "Case Studies", href: "/case-studies" }, + ] + }, + { name: "Products", href: "/products" }, + { + name: "Company", + href: "/about", + dropdown: [ + { name: "About Us", href: "/about" }, + { name: "Blog", href: "/blog" }, + { name: "Press", href: "/press" }, + { name: "Careers", href: "/careers" }, + ] + }, + { name: "Support", href: "/support" }, + ]; + + + return ( - + <> + + + {/* Include Mobile Navigation Component */} + + ); }; diff --git a/apps/client/src/components/NavbarWrapper.tsx b/apps/client/src/components/NavbarWrapper.tsx deleted file mode 100644 index 62dada5b..00000000 --- a/apps/client/src/components/NavbarWrapper.tsx +++ /dev/null @@ -1,32 +0,0 @@ -"use client"; - -import dynamic from "next/dynamic"; - -// Create a client-side wrapper for theme-dependent components -const ClientNavbar = dynamic(() => import("./Navbar"), { - ssr: false, - loading: () => ( - - ) -}); - -const NavbarWrapper = () => { - return ; -}; - -export default NavbarWrapper; \ No newline at end of file diff --git a/apps/client/src/components/NewsletterSection.tsx b/apps/client/src/components/NewsletterSection.tsx deleted file mode 100644 index f1371970..00000000 --- a/apps/client/src/components/NewsletterSection.tsx +++ /dev/null @@ -1,140 +0,0 @@ -"use client"; - -import { useState } from "react"; -import { Mail, ArrowRight, CheckCircle } from "lucide-react"; - -const NewsletterSection = () => { - const [email, setEmail] = useState(""); - const [isSubscribed, setIsSubscribed] = useState(false); - const [isLoading, setIsLoading] = useState(false); - - const handleSubmit = async (e: React.FormEvent) => { - e.preventDefault(); - if (!email) return; - - setIsLoading(true); - - // Simulate API call - setTimeout(() => { - setIsSubscribed(true); - setIsLoading(false); - setEmail(""); - }, 1000); - }; - - if (isSubscribed) { - return ( -
-
- -

- Welcome to Neuraltale! -

-

- Thank you for subscribing. You'll receive the latest tech updates and exclusive offers. -

-
-
- ); - } - - return ( -
-
-
-
- -
-
- -

- Stay Ahead of Tech Trends -

- -

- Get exclusive access to the latest product launches, tech insights, and special offers. - Join our community of tech enthusiasts and never miss an innovation. -

- -
-
- setEmail(e.target.value)} - placeholder="Enter your email address" - className=" - w-full px-4 py-3 - bg-white/10 - border border-white/20 - rounded-lg - text-white - placeholder:text-white/70 - focus:outline-none - focus:border-white/40 - focus:bg-white/20 - transition-all - " - required - /> -
- - -
- -
-

- By subscribing, you agree to our privacy policy and terms of service. - Unsubscribe anytime. -

-
- - {/* Features */} -
-
-
Weekly Updates
-
Latest tech news & reviews
-
-
-
Exclusive Offers
-
Subscriber-only discounts
-
-
-
Early Access
-
Be first to know about launches
-
-
-
-
- ); -}; - -export default NewsletterSection; \ No newline at end of file diff --git a/apps/client/src/components/ProductCard.tsx b/apps/client/src/components/ProductCard.tsx index 81a990d1..2f0a8a08 100644 --- a/apps/client/src/components/ProductCard.tsx +++ b/apps/client/src/components/ProductCard.tsx @@ -2,7 +2,7 @@ import useCartStore from "@/stores/cartStore"; import { ProductType } from "@repo/types"; -import { ShoppingCart } from "lucide-react"; +import { ShoppingCart, Star, Zap, Check, Heart } from "lucide-react"; import Image from "next/image"; import Link from "next/link"; import { useState } from "react"; @@ -13,6 +13,7 @@ const ProductCard = ({ product }: { product: ProductType }) => { size: product.sizes[0]!, color: product.colors[0]!, }); + const [isWishlisted, setIsWishlisted] = useState(false); const { addToCart } = useCartStore(); @@ -36,81 +37,212 @@ const ProductCard = ({ product }: { product: ProductType }) => { selectedSize: productTypes.size, selectedColor: productTypes.color, }); - toast.success("Product added to cart") + toast.success("Product added to cart"); }; + const handleWishlist = () => { + setIsWishlisted(!isWishlisted); + toast.success(isWishlisted ? "Removed from wishlist" : "Added to wishlist"); + }; + + // Generate a realistic rating for demo purposes + const rating = 4.3 + (product.id % 10) * 0.06; + const reviewCount = 150 + (product.id * 23); + + // Determine availability status + const getAvailabilityStatus = () => { + if (product.id % 3 === 0) return { status: "Limited Stock", color: "text-orange-600", bg: "bg-orange-50" }; + if (product.id % 7 === 0) return { status: "Pre-Order", color: "text-blue-600", bg: "bg-blue-50" }; + return { status: "In Stock", color: "text-green-600", bg: "bg-green-50" }; + }; + + const availability = getAvailabilityStatus(); + return ( -
+
{/* IMAGE */} -
+
)?.[productTypes.color] || ""} + src={(product.images as Record)?.[productTypes.color] || "/products/placeholder.jpg"} alt={product.name} fill - className="object-cover hover:scale-105 transition-all duration-300" + className="object-cover group-hover:scale-105 transition-all duration-300" /> + + {/* Wishlist Button */} + + + {/* Quick Specs Overlay */} +
+
+
+ + {product.shortDescription.split(',')[0] || "Premium Quality"} +
+
+ + Free shipping & returns +
+
+
+ + {/* Availability Badge */} +
+ {availability.status} +
+ {/* PRODUCT DETAIL */} -
-

{product.name}

-

{product.shortDescription}

+
+ {/* Product Name & Rating */} +
+ +

+ {product.name} +

+ +
+
+ {[...Array(5)].map((_, i) => ( + + ))} +
+ + {rating.toFixed(1)} ({reviewCount}) + +
+
+ + {/* Short Description */} +

{product.shortDescription}

+ {/* PRODUCT TYPES */} -
- {/* SIZES */} -
- Size - + {product.sizes.length > 3 && ( + + +{product.sizes.length - 3} more + + )} +
- {/* COLORS */} -
- Color -
- {product.colors.map((color) => ( -
+ Color +
+ {product.colors.slice(0, 4).map((color) => ( +
- {/* PRICE AND ADD TO CART BUTTON */} -
-

${product.price.toFixed(2)}

+ + {/* PRICE AND ACTIONS */} +
+
+
+ + ${product.price.toLocaleString()} + + {/* Show savings if applicable */} + {product.id % 4 === 0 && ( +
+ + ${(product.price * 1.15).toLocaleString()} + + + Save 15% + +
+ )} +
+
+ + + + View Details +
diff --git a/apps/client/src/components/ProductList.tsx b/apps/client/src/components/ProductList.tsx index 285ae0dc..7520e2c6 100644 --- a/apps/client/src/components/ProductList.tsx +++ b/apps/client/src/components/ProductList.tsx @@ -4,141 +4,281 @@ import ProductCard from "./ProductCard"; import Link from "next/link"; import Filter from "./Filter"; -// TEMPORARY -// const products: ProductsType = [ -// { -// id: 1, -// name: "Adidas CoreFit T-Shirt", -// shortDescription: -// "Lorem ipsum dolor sit amet consect adipisicing elit lorem ipsum dolor sit.", -// description: -// "Lorem ipsum dolor sit amet consect adipisicing elit lorem ipsum dolor sit. Lorem ipsum dolor sit amet consect adipisicing elit lorem ipsum dolor sit. Lorem ipsum dolor sit amet consect adipisicing elit lorem ipsum dolor sit.", -// price: 39.9, -// sizes: ["s", "m", "l", "xl", "xxl"], -// colors: ["gray", "purple", "green"], -// images: { -// gray: "/products/1g.png", -// purple: "/products/1p.png", -// green: "/products/1gr.png", -// }, -// categorySlug: "test", -// createdAt: new Date(), -// updatedAt: new Date(), -// }, -// { -// id: 2, -// name: "Puma Ultra Warm Zip", -// shortDescription: -// "Lorem ipsum dolor sit amet consect adipisicing elit lorem ipsum dolor sit.", -// description: -// "Lorem ipsum dolor sit amet consect adipisicing elit lorem ipsum dolor sit. Lorem ipsum dolor sit amet consect adipisicing elit lorem ipsum dolor sit. Lorem ipsum dolor sit amet consect adipisicing elit lorem ipsum dolor sit.", -// price: 59.9, -// sizes: ["s", "m", "l", "xl"], -// colors: ["gray", "green"], -// images: { gray: "/products/2g.png", green: "/products/2gr.png" }, -// categorySlug: "test", -// createdAt: new Date(), -// updatedAt: new Date(), -// }, -// { -// id: 3, -// name: "Nike Air Essentials Pullover", -// shortDescription: -// "Lorem ipsum dolor sit amet consect adipisicing elit lorem ipsum dolor sit.", -// description: -// "Lorem ipsum dolor sit amet consect adipisicing elit lorem ipsum dolor sit. Lorem ipsum dolor sit amet consect adipisicing elit lorem ipsum dolor sit. Lorem ipsum dolor sit amet consect adipisicing elit lorem ipsum dolor sit.", -// price: 69.9, -// sizes: ["s", "m", "l"], -// colors: ["green", "blue", "black"], -// images: { -// green: "/products/3gr.png", -// blue: "/products/3b.png", -// black: "/products/3bl.png", -// }, -// categorySlug: "test", -// createdAt: new Date(), -// updatedAt: new Date(), -// }, -// { -// id: 123, -// name: "Nike Dri Flex T-Shirt", -// shortDescription: -// "Lorem ipsum dolor sit amet consect adipisicing elit lorem ipsum dolor sit.", -// description: -// "Lorem ipsum dolor sit amet consect adipisicing elit lorem ipsum dolor sit. Lorem ipsum dolor sit amet consect adipisicing elit lorem ipsum dolor sit. Lorem ipsum dolor sit amet consect adipisicing elit lorem ipsum dolor sit.", -// price: 29.9, -// sizes: ["s", "m", "l"], -// colors: ["white", "pink"], -// images: { white: "/products/4w.png", pink: "/products/4p.png" }, -// categorySlug: "test", -// createdAt: new Date(), -// updatedAt: new Date(), -// }, -// { -// id: 5, -// name: "Under Armour StormFleece", -// shortDescription: -// "Lorem ipsum dolor sit amet consect adipisicing elit lorem ipsum dolor sit.", -// description: -// "Lorem ipsum dolor sit amet consect adipisicing elit lorem ipsum dolor sit. Lorem ipsum dolor sit amet consect adipisicing elit lorem ipsum dolor sit. Lorem ipsum dolor sit amet consect adipisicing elit lorem ipsum dolor sit.", -// price: 49.9, -// sizes: ["s", "m", "l"], -// colors: ["red", "orange", "black"], -// images: { -// red: "/products/5r.png", -// orange: "/products/5o.png", -// black: "/products/5bl.png", -// }, -// categorySlug: "test", -// createdAt: new Date(), -// updatedAt: new Date(), -// }, -// { -// id: 6, - // name: "Nike Air Max 270", - // shortDescription: - // "Lorem ipsum dolor sit amet consect adipisicing elit lorem ipsum dolor sit.", - // description: - // "Lorem ipsum dolor sit amet consect adipisicing elit lorem ipsum dolor sit. Lorem ipsum dolor sit amet consect adipisicing elit lorem ipsum dolor sit. Lorem ipsum dolor sit amet consect adipisicing elit lorem ipsum dolor sit.", - // price: 59.9, - // sizes: ["40", "42", "43", "44"], - // colors: ["gray", "white"], - // images: { gray: "/products/6g.png", white: "/products/6w.png" }, - // categorySlug: "test", - // createdAt: new Date(), - // updatedAt: new Date(), - // }, -// { -// id: 7, -// name: "Nike Ultraboost Pulse ", -// shortDescription: -// "Lorem ipsum dolor sit amet consect adipisicing elit lorem ipsum dolor sit.", -// description: -// "Lorem ipsum dolor sit amet consect adipisicing elit lorem ipsum dolor sit. Lorem ipsum dolor sit amet consect adipisicing elit lorem ipsum dolor sit. Lorem ipsum dolor sit amet consect adipisicing elit lorem ipsum dolor sit.", -// price: 69.9, -// sizes: ["40", "42", "43"], -// colors: ["gray", "pink"], -// images: { gray: "/products/7g.png", pink: "/products/7p.png" }, -// categorySlug: "test", -// createdAt: new Date(), -// updatedAt: new Date(), -// }, -// { -// id: 8, -// name: "Levi’s Classic Denim", -// shortDescription: -// "Lorem ipsum dolor sit amet consect adipisicing elit lorem ipsum dolor sit.", -// description: -// "Lorem ipsum dolor sit amet consect adipisicing elit lorem ipsum dolor sit. Lorem ipsum dolor sit amet consect adipisicing elit lorem ipsum dolor sit. Lorem ipsum dolor sit amet consect adipisicing elit lorem ipsum dolor sit.", -// price: 59.9, -// sizes: ["s", "m", "l"], -// colors: ["blue", "green"], -// images: { blue: "/products/8b.png", green: "/products/8gr.png" }, -// categorySlug: "test", -// createdAt: new Date(), -// updatedAt: new Date(), -// }, -// ]; +// NEURALTALE TECH PRODUCTS - Comprehensive catalog +const techProducts: ProductType[] = [ + // Premium Smartphones + { + id: 1, + name: "iPhone 15 Pro Max", + shortDescription: "The ultimate iPhone with titanium design, Action Button, and powerful A17 Pro chip.", + description: "Experience the pinnacle of iPhone innovation with the iPhone 15 Pro Max. Featuring a lightweight titanium design, the revolutionary Action Button for quick access to your favorite features, and the industry-leading A17 Pro chip with 6-core GPU. The advanced camera system captures stunning detail with 5x Telephoto zoom and next-generation portraits with Focus and Depth Control.", + price: 1199.99, + sizes: ["128GB", "256GB", "512GB", "1TB"], + colors: ["Natural Titanium", "Blue Titanium", "White Titanium", "Black Titanium"], + images: { + "Natural Titanium": "/products/iphone-15-pro-natural.jpg", + "Blue Titanium": "/products/iphone-15-pro-blue.jpg", + "White Titanium": "/products/iphone-15-pro-white.jpg", + "Black Titanium": "/products/iphone-15-pro-black.jpg", + }, + categorySlug: "smartphones", + createdAt: new Date(), + updatedAt: new Date(), + }, + { + id: 2, + name: "Samsung Galaxy S24 Ultra", + shortDescription: "AI-powered smartphone with S Pen, 200MP camera, and brilliant 6.8\" Dynamic AMOLED display.", + description: "Discover the power of Galaxy AI with the Samsung Galaxy S24 Ultra. This premium smartphone features a built-in S Pen for precision control, a pro-grade 200MP camera system with AI-enhanced photography, and a stunning 6.8-inch Dynamic AMOLED 2X display. The Snapdragon 8 Gen 3 processor delivers exceptional performance for gaming, productivity, and creative tasks.", + price: 1299.99, + sizes: ["256GB", "512GB", "1TB"], + colors: ["Titanium Gray", "Titanium Black", "Titanium Violet", "Titanium Yellow"], + images: { + "Titanium Gray": "/products/galaxy-s24-ultra-gray.jpg", + "Titanium Black": "/products/galaxy-s24-ultra-black.jpg", + "Titanium Violet": "/products/galaxy-s24-ultra-violet.jpg", + "Titanium Yellow": "/products/galaxy-s24-ultra-yellow.jpg", + }, + categorySlug: "smartphones", + createdAt: new Date(), + updatedAt: new Date(), + }, + + // Premium Laptops + { + id: 3, + name: "MacBook Pro 16-inch M4 Pro", + shortDescription: "Professional laptop with M4 Pro chip, Liquid Retina XDR display, and up to 22-hour battery life.", + description: "Unleash your creativity with the MacBook Pro 16-inch powered by the revolutionary M4 Pro chip. Features a stunning Liquid Retina XDR display with 1000 nits sustained brightness, advanced thermal design for sustained pro performance, and an impressive battery life of up to 22 hours. Perfect for video editing, 3D rendering, and software development.", + price: 2499.99, + sizes: ["512GB", "1TB", "2TB", "4TB"], + colors: ["Space Black", "Silver"], + images: { + "Space Black": "/products/macbook-pro-16-space-black.jpg", + "Silver": "/products/macbook-pro-16-silver.jpg", + }, + categorySlug: "laptops", + createdAt: new Date(), + updatedAt: new Date(), + }, + { + id: 4, + name: "Dell XPS 15 OLED", + shortDescription: "Premium Windows laptop with 4K OLED InfinityEdge display and 13th Gen Intel processors.", + description: "Experience premium performance with the Dell XPS 15 featuring a breathtaking 4K OLED InfinityEdge display with 100% DCI-P3 color accuracy. Powered by 13th Gen Intel Core processors and NVIDIA GeForce RTX graphics, this laptop delivers exceptional performance for creative professionals and power users. The precision-crafted aluminum chassis ensures durability and style.", + price: 1899.99, + sizes: ["512GB", "1TB", "2TB"], + colors: ["Platinum Silver", "Graphite"], + images: { + "Platinum Silver": "/products/dell-xps-15-silver.jpg", + "Graphite": "/products/dell-xps-15-graphite.jpg", + }, + categorySlug: "laptops", + createdAt: new Date(), + updatedAt: new Date(), + }, + + // Audio Equipment + { + id: 5, + name: "Sony WH-1000XM5", + shortDescription: "Industry-leading noise canceling headphones with 30-hour battery and crystal-clear calls.", + description: "Immerse yourself in premium sound with the Sony WH-1000XM5 wireless headphones. Featuring industry-leading noise cancellation, exceptional sound quality with LDAC codec support, and an impressive 30-hour battery life. The lightweight design with soft leather cushioning ensures all-day comfort, while Speak-to-Chat technology automatically pauses music when you start talking.", + price: 399.99, + sizes: ["Standard"], + colors: ["Black", "Silver"], + images: { + "Black": "/products/sony-wh1000xm5-black.jpg", + "Silver": "/products/sony-wh1000xm5-silver.jpg", + }, + categorySlug: "audio", + createdAt: new Date(), + updatedAt: new Date(), + }, + { + id: 6, + name: "AirPods Pro (3rd Generation)", + shortDescription: "Premium wireless earbuds with Active Noise Cancellation and Spatial Audio support.", + description: "Experience premium wireless audio with AirPods Pro featuring Active Noise Cancellation, Transparency mode, and Personalized Spatial Audio. The H2 chip delivers smarter noise cancellation and superior three-dimensional sound. With up to 6 hours of listening time and the MagSafe Charging Case providing multiple additional charges.", + price: 249.99, + sizes: ["Standard"], + colors: ["White"], + images: { + "White": "/products/airpods-pro-3rd-gen.jpg", + }, + categorySlug: "audio", + createdAt: new Date(), + updatedAt: new Date(), + }, + + // Gaming Equipment + { + id: 7, + name: "Logitech MX Master 3S", + shortDescription: "Advanced wireless mouse with 8K DPI sensor, quiet clicks, and 70-day battery life.", + description: "Achieve precision and comfort with the Logitech MX Master 3S, featuring an 8000 DPI sensor for ultimate tracking accuracy. The 90% quieter clicks provide a premium experience without disturbing others. With 70-day battery life, USB-C fast charging, and seamless connectivity across multiple devices, it's perfect for professionals and creatives.", + price: 99.99, + sizes: ["Standard"], + colors: ["Graphite", "Pale Gray"], + images: { + "Graphite": "/products/mx-master-3s-graphite.jpg", + "Pale Gray": "/products/mx-master-3s-pale-gray.jpg", + }, + categorySlug: "accessories", + createdAt: new Date(), + updatedAt: new Date(), + }, + { + id: 8, + name: "Logitech K380 Multi-Device Keyboard", + shortDescription: "Compact wireless keyboard with Easy-Switch technology for seamless multi-device typing.", + description: "Type comfortably on the Logitech K380, a compact wireless keyboard designed for multi-device use. Easy-Switch technology lets you connect up to three devices and switch between them with the press of a button. The round concave keys provide a comfortable, familiar typing experience, while the 2-year battery life ensures long-lasting performance.", + price: 39.99, + sizes: ["Compact"], + colors: ["Dark Grey", "Off-White", "Blue"], + images: { + "Dark Grey": "/products/k380-dark-grey.jpg", + "Off-White": "/products/k380-off-white.jpg", + "Blue": "/products/k380-blue.jpg", + }, + categorySlug: "accessories", + createdAt: new Date(), + updatedAt: new Date(), + }, + // Gaming Laptops + { + id: 19, + name: "ASUS ROG Strix G15", + shortDescription: "AMD Ryzen 7 5800H, RTX 3060, 144Hz FHD", + description: "High-performance gaming laptop with AMD Ryzen 7 processor and NVIDIA GeForce RTX 3060 graphics. Features a 144Hz Full HD display for smooth gaming.", + price: 1299, + sizes: ["15.6in", "17.3in"], + colors: ["Eclipse Gray", "Electro Punk"], + images: { + "Eclipse Gray": "/products/asus-rog-gray.jpg", + "Electro Punk": "/products/asus-rog-pink.jpg", + }, + categorySlug: "gaming-laptops", + createdAt: new Date(), + updatedAt: new Date(), + }, + { + id: 20, + name: "MSI Katana 15", + shortDescription: "Intel Core i7-12650H, RTX 4060, 144Hz", + description: "Gaming laptop with Intel 12th Gen processor and RTX 4060 graphics. Perfect for gaming and content creation with high refresh rate display.", + price: 1499, + sizes: ["15.6in"], + colors: ["Black", "Blue"], + images: { + "Black": "/products/msi-katana-black.jpg", + "Blue": "/products/msi-katana-blue.jpg", + }, + categorySlug: "gaming-laptops", + createdAt: new Date(), + updatedAt: new Date(), + }, + // Tablets + { + id: 21, + name: "iPad Pro 12.9\"", + shortDescription: "M2 chip, 128GB, Wi-Fi + Cellular", + description: "Ultimate iPad experience with M2 chip. Features Liquid Retina XDR display and support for Apple Pencil (2nd generation).", + price: 1099, + sizes: ["128GB", "256GB", "512GB", "1TB"], + colors: ["Space Gray", "Silver"], + images: { + "Space Gray": "/products/ipad-pro-gray.jpg", + "Silver": "/products/ipad-pro-silver.jpg", + }, + categorySlug: "tablets", + createdAt: new Date(), + updatedAt: new Date(), + }, + { + id: 22, + name: "Samsung Galaxy Tab S9", + shortDescription: "Snapdragon 8 Gen 2, 128GB, 11 inch", + description: "Premium Android tablet with Snapdragon processor. Includes S Pen and features a stunning AMOLED display.", + price: 799, + sizes: ["128GB", "256GB"], + colors: ["Graphite", "Beige", "Mint"], + images: { + "Graphite": "/products/galaxy-tab-graphite.jpg", + "Beige": "/products/galaxy-tab-beige.jpg", + "Mint": "/products/galaxy-tab-mint.jpg", + }, + categorySlug: "tablets", + createdAt: new Date(), + updatedAt: new Date(), + }, + // Monitors + { + id: 23, + name: "LG UltraGear 27GL850", + shortDescription: "27\" QHD IPS, 144Hz, 1ms, G-Sync Compatible", + description: "High-performance gaming monitor with Nano IPS technology. Features 144Hz refresh rate and 1ms response time for competitive gaming.", + price: 449, + sizes: ["27in"], + colors: ["Black"], + images: { + "Black": "/products/lg-ultragear-black.jpg", + }, + categorySlug: "monitors", + createdAt: new Date(), + updatedAt: new Date(), + }, + { + id: 24, + name: "Samsung Odyssey G7", + shortDescription: "32\" Curved QLED, 240Hz, 1ms, G-Sync", + description: "Curved gaming monitor with QLED technology and 1000R curvature. Features 240Hz refresh rate for ultimate gaming performance.", + price: 699, + sizes: ["27in", "32in"], + colors: ["Black"], + images: { + "Black": "/products/samsung-odyssey-black.jpg", + }, + categorySlug: "monitors", + createdAt: new Date(), + updatedAt: new Date(), + }, + // Wearables + { + id: 25, + name: "Apple Watch Series 9", + shortDescription: "GPS + Cellular, 45mm, Titanium", + description: "Advanced smartwatch with S9 SiP and Double Tap gesture. Features Always-On Retina display and comprehensive health tracking.", + price: 749, + sizes: ["41mm", "45mm"], + colors: ["Natural Titanium", "Blue Titanium", "Silver"], + images: { + "Natural Titanium": "/products/apple-watch-titanium.jpg", + "Blue Titanium": "/products/apple-watch-blue.jpg", + "Silver": "/products/apple-watch-silver.jpg", + }, + categorySlug: "wearables", + createdAt: new Date(), + updatedAt: new Date(), + }, + { + id: 26, + name: "Samsung Galaxy Watch6", + shortDescription: "40mm, Bluetooth, Health Monitoring", + description: "Advanced smartwatch with comprehensive health monitoring. Features sleep tracking, heart rate monitoring, and long battery life.", + price: 329, + sizes: ["40mm", "44mm"], + colors: ["Graphite", "Gold", "Silver"], + images: { + "Graphite": "/products/galaxy-watch-graphite.jpg", + "Gold": "/products/galaxy-watch-gold.jpg", + "Silver": "/products/galaxy-watch-silver.jpg", + }, + categorySlug: "wearables", + createdAt: new Date(), + updatedAt: new Date(), + } +]; const fetchData = async ({ category, @@ -151,12 +291,59 @@ const fetchData = async ({ search?: string; params: "homepage" | "products"; }) => { + // For development, return our curated tech products + // In production, this would fetch from your product service + let filteredProducts = [...techProducts]; + + // Apply category filter + if (category && category !== "all") { + filteredProducts = filteredProducts.filter(product => + product.categorySlug === category + ); + } + + // Apply search filter + if (search) { + filteredProducts = filteredProducts.filter(product => + product.name.toLowerCase().includes(search.toLowerCase()) || + product.shortDescription.toLowerCase().includes(search.toLowerCase()) || + product.description.toLowerCase().includes(search.toLowerCase()) + ); + } + + // Apply sorting + switch (sort) { + case "price-asc": + filteredProducts.sort((a, b) => a.price - b.price); + break; + case "price-desc": + filteredProducts.sort((a, b) => b.price - a.price); + break; + case "name": + filteredProducts.sort((a, b) => a.name.localeCompare(b.name)); + break; + case "newest": + default: + filteredProducts.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime()); + } + + // Limit for homepage + if (params === "homepage") { + filteredProducts = filteredProducts.slice(0, 8); + } + + return filteredProducts; + + // Uncomment below for production API call + /* const res = await fetch( `${process.env.NEXT_PUBLIC_PRODUCT_SERVICE_URL}/products?${category ? `category=${category}` : ""}${search ? `&search=${search}` : ""}&sort=${sort || "newest"}${params === "homepage" ? "&limit=8" : ""}` ); const data: ProductType[] = await res.json(); return data; + */ }; + const ProductList = async ({ category, sort, @@ -171,21 +358,38 @@ const ProductList = async ({ const products = await fetchData({ category, sort, search, params }); return (
+ {/* Section Header */} +
+

+ Premium Tech Products +

+

+ Discover cutting-edge technology from leading brands. From smartphones to gaming gear, + find the perfect tech products for your lifestyle. +

+
+ {params === "products" && } -
+ +
{Array.isArray(products) && products.map((product) => ( ))}
- - View all products - + + {params === "homepage" && ( +
+ + View All Products + +
+ )}
); }; -export default ProductList; +export default ProductList; \ No newline at end of file diff --git a/apps/client/src/components/ProductReviews.tsx b/apps/client/src/components/ProductReviews.tsx new file mode 100644 index 00000000..2b066d1a --- /dev/null +++ b/apps/client/src/components/ProductReviews.tsx @@ -0,0 +1,330 @@ +"use client"; + +import { Star, ThumbsUp, ThumbsDown, User, Verified } from "lucide-react"; +import { useState } from "react"; + +interface Review { + id: number; + userId: string; + userName: string; + rating: number; + title: string; + comment: string; + date: Date; + isVerifiedPurchase: boolean; + helpfulCount: number; + notHelpfulCount: number; + userHasVoted: "helpful" | "not-helpful" | null; +} + +interface ProductReviewsProps { + productId: number; + productName: string; +} + +// Mock reviews data - in production this would come from your API +const generateMockReviews = (productId: number): Review[] => { + const reviews: Review[] = [ + { + id: 1, + userId: "user-1", + userName: "Alex Thompson", + rating: 5, + title: "Exceptional quality and performance!", + comment: "This product exceeded my expectations in every way. The build quality is outstanding, and the performance is exactly what I needed. Highly recommend to anyone looking for a reliable tech solution.", + date: new Date("2024-01-15"), + isVerifiedPurchase: true, + helpfulCount: 24, + notHelpfulCount: 2, + userHasVoted: null, + }, + { + id: 2, + userId: "user-2", + userName: "Sarah Chen", + rating: 4, + title: "Great value for money", + comment: "Really impressed with this purchase. The features are excellent and it works perfectly with my setup. Only minor complaint is the packaging could be better, but the product itself is fantastic.", + date: new Date("2024-01-10"), + isVerifiedPurchase: true, + helpfulCount: 18, + notHelpfulCount: 1, + userHasVoted: null, + }, + { + id: 3, + userId: "user-3", + userName: "Mike Rodriguez", + rating: 5, + title: "Perfect for professional use", + comment: "Been using this for my work for several months now. The reliability and performance are top-notch. Worth every penny and I would definitely buy again.", + date: new Date("2024-01-05"), + isVerifiedPurchase: true, + helpfulCount: 31, + notHelpfulCount: 0, + userHasVoted: null, + }, + { + id: 4, + userId: "user-4", + userName: "Emma Wilson", + rating: 4, + title: "Solid choice", + comment: "Good product overall. Setup was straightforward and it does everything advertised. The design is sleek and modern. One star off because delivery took longer than expected.", + date: new Date("2023-12-28"), + isVerifiedPurchase: false, + helpfulCount: 12, + notHelpfulCount: 3, + userHasVoted: null, + }, + { + id: 5, + userId: "user-5", + userName: "David Kim", + rating: 5, + title: "Outstanding customer experience", + comment: "Not only is the product excellent, but the customer service was also amazing. They helped me choose the right specifications and the delivery was fast. Couldn't be happier!", + date: new Date("2023-12-20"), + isVerifiedPurchase: true, + helpfulCount: 27, + notHelpfulCount: 1, + userHasVoted: null, + }, + ]; + + // Modify reviews slightly based on productId for variation + return reviews.map(review => ({ + ...review, + id: review.id + (productId * 100), + helpfulCount: review.helpfulCount + (productId % 10), + rating: Math.max(3, review.rating - (productId % 3 === 0 ? 1 : 0)), + })); +}; + +const ProductReviews: React.FC = ({ productId, productName }) => { + const [reviews, setReviews] = useState(generateMockReviews(productId)); + const [sortBy, setSortBy] = useState<"newest" | "oldest" | "highest" | "lowest" | "helpful">("newest"); + const [filterRating, setFilterRating] = useState(null); + + // Calculate overall rating + const totalReviews = reviews.length; + const averageRating = reviews.reduce((sum, review) => sum + review.rating, 0) / totalReviews; + + // Rating distribution + const ratingDistribution = [5, 4, 3, 2, 1].map(rating => ({ + rating, + count: reviews.filter(r => r.rating === rating).length, + percentage: (reviews.filter(r => r.rating === rating).length / totalReviews) * 100 + })); + + // Sort and filter reviews + const filteredAndSortedReviews = reviews + .filter(review => filterRating ? review.rating === filterRating : true) + .sort((a, b) => { + switch (sortBy) { + case "oldest": + return a.date.getTime() - b.date.getTime(); + case "highest": + return b.rating - a.rating; + case "lowest": + return a.rating - b.rating; + case "helpful": + return b.helpfulCount - a.helpfulCount; + case "newest": + default: + return b.date.getTime() - a.date.getTime(); + } + }); + + const handleVote = (reviewId: number, voteType: "helpful" | "not-helpful") => { + setReviews(prev => prev.map(review => { + if (review.id === reviewId) { + if (review.userHasVoted === voteType) { + // Remove vote + return { + ...review, + helpfulCount: voteType === "helpful" ? review.helpfulCount - 1 : review.helpfulCount, + notHelpfulCount: voteType === "not-helpful" ? review.notHelpfulCount - 1 : review.notHelpfulCount, + userHasVoted: null, + }; + } else { + // Add or change vote + const helpfulDelta = voteType === "helpful" ? 1 : (review.userHasVoted === "helpful" ? -1 : 0); + const notHelpfulDelta = voteType === "not-helpful" ? 1 : (review.userHasVoted === "not-helpful" ? -1 : 0); + + return { + ...review, + helpfulCount: review.helpfulCount + helpfulDelta, + notHelpfulCount: review.notHelpfulCount + notHelpfulDelta, + userHasVoted: voteType, + }; + } + } + return review; + })); + }; + + const renderStars = (rating: number, size: "sm" | "md" = "sm") => { + const sizeClass = size === "sm" ? "w-4 h-4" : "w-5 h-5"; + return ( +
+ {[1, 2, 3, 4, 5].map((star) => ( + + ))} +
+ ); + }; + + return ( +
+

Customer Reviews

+ + {/* Overall Rating Summary */} +
+
+
+ + {averageRating.toFixed(1)} + +
+ {renderStars(Math.round(averageRating), "md")} +

+ Based on {totalReviews} reviews +

+
+
+
+ +
+

Rating Distribution

+ {ratingDistribution.map(({ rating, count, percentage }) => ( +
+ {rating}★ +
+
+
+ {count} +
+ ))} +
+
+ + {/* Filters and Sorting */} +
+
+ + +
+ +
+ + +
+
+ + {/* Reviews List */} +
+ {filteredAndSortedReviews.map((review) => ( +
+
+
+
+ +
+
+ +
+
+ {review.userName} + {review.isVerifiedPurchase && ( +
+ + Verified Purchase +
+ )} +
+ +
+ {renderStars(review.rating)} + + {review.date.toLocaleDateString("en-US", { + year: "numeric", + month: "long", + day: "numeric" + })} + +
+ +

{review.title}

+

{review.comment}

+ +
+ Was this helpful? + + +
+
+
+
+ ))} +
+ + {filteredAndSortedReviews.length === 0 && ( +
+

No reviews match your filter criteria.

+
+ )} +
+ ); +}; + +export default ProductReviews; \ No newline at end of file diff --git a/apps/client/src/components/QuickViewModal.tsx b/apps/client/src/components/QuickViewModal.tsx new file mode 100644 index 00000000..11999a97 --- /dev/null +++ b/apps/client/src/components/QuickViewModal.tsx @@ -0,0 +1,322 @@ +"use client"; + +import { ProductType } from "@repo/types"; +import { X, Star, ShoppingCart, Heart, Share2, Eye, Zap, Shield, Truck } from "lucide-react"; +import Image from "next/image"; +import React, { useState, useEffect } from "react"; +import { toast } from "react-toastify"; +import useCartStore from "@/stores/cartStore"; + +interface QuickViewModalProps { + product: ProductType | null; + isOpen: boolean; + onClose: () => void; +} + +const QuickViewModal: React.FC = ({ product, isOpen, onClose }) => { + const [selectedSize, setSelectedSize] = useState(""); + const [selectedColor, setSelectedColor] = useState(""); + const [selectedImageIndex, setSelectedImageIndex] = useState(0); + const [quantity, setQuantity] = useState(1); + const [isWishlisted, setIsWishlisted] = useState(false); + + const { addToCart } = useCartStore(); + + // Initialize selected options when product changes + useEffect(() => { + if (product) { + setSelectedSize(product.sizes[0] || ""); + setSelectedColor(product.colors[0] || ""); + setSelectedImageIndex(0); + setQuantity(1); + setIsWishlisted(false); + } + }, [product]); + + if (!isOpen || !product) return null; + + const handleAddToCart = () => { + addToCart({ + ...product, + quantity, + selectedSize, + selectedColor, + }); + toast.success(`${quantity} item(s) added to cart`); + }; + + const handleWishlist = () => { + setIsWishlisted(!isWishlisted); + toast.success(isWishlisted ? "Removed from wishlist" : "Added to wishlist"); + }; + + const handleShare = async () => { + try { + await navigator.share({ + title: product.name, + text: product.shortDescription, + url: window.location.origin + `/products/${product.id}`, + }); + } catch (error) { + // Fallback to clipboard + navigator.clipboard.writeText(window.location.origin + `/products/${product.id}`); + toast.success("Product link copied to clipboard!"); + } + }; + + // Generate a realistic rating for demo purposes + const rating = 4.3 + (product.id % 10) * 0.06; + const reviewCount = 150 + (product.id * 23); + + // Get product images + const productImages = Object.values(product.images as Record); + const currentImage = productImages[selectedImageIndex] || productImages[0]; + + return ( +
+
+ {/* Header */} +
+

Quick View

+ +
+ +
+
+ {/* Product Images */} +
+
+ {product.name} +
+ + {/* Image Thumbnails */} + {productImages.length > 1 && ( +
+ {productImages.map((image, index) => ( + + ))} +
+ )} +
+ + {/* Product Details */} +
+
+

{product.name}

+

{product.shortDescription}

+ + {/* Rating */} +
+
+ {[...Array(5)].map((_, i) => ( + + ))} +
+ + {rating.toFixed(1)} ({reviewCount} reviews) + +
+ + {/* Price */} +
+ + ${product.price.toLocaleString()} + + {product.id % 4 === 0 && ( +
+ + ${(product.price * 1.15).toLocaleString()} + + + Save 15% + +
+ )} +
+
+ + {/* Options */} +
+ {/* Size/Storage */} +
+ +
+ {product.sizes.map((size) => ( + + ))} +
+
+ + {/* Color */} +
+ +
+ {product.colors.map((color) => ( +
+
+ + {/* Quantity */} +
+ +
+ + {quantity} + +
+
+
+ + {/* Features */} +
+
+ + Free Shipping +
+
+ + 2 Year Warranty +
+
+ + Fast Delivery +
+
+ + {/* Actions */} +
+ + +
+ + + + + +
+
+
+
+
+
+
+ ); +}; + +export default QuickViewModal; \ No newline at end of file diff --git a/apps/client/src/components/RecentlyViewed.tsx b/apps/client/src/components/RecentlyViewed.tsx new file mode 100644 index 00000000..634e90a7 --- /dev/null +++ b/apps/client/src/components/RecentlyViewed.tsx @@ -0,0 +1,148 @@ +"use client"; + +import { ProductType } from "@repo/types"; +import { Clock, Eye } from "lucide-react"; +import Image from "next/image"; +import Link from "next/link"; +import { useState, useEffect } from "react"; + +interface RecentlyViewedProps { + currentProductId?: number; +} + +const RecentlyViewed: React.FC = ({ currentProductId }) => { + const [recentProducts, setRecentProducts] = useState([]); + + useEffect(() => { + // In a real app, this would load from localStorage or API + const mockRecentProducts: ProductType[] = [ + { + id: 1, + name: "iPhone 15 Pro Max", + shortDescription: "A17 Pro chip, Titanium Design, 256GB", + description: "The ultimate iPhone with titanium design and A17 Pro chip.", + price: 1199, + sizes: ["128GB", "256GB", "512GB", "1TB"], + colors: ["Natural Titanium", "Blue Titanium", "White Titanium", "Black Titanium"], + images: { + "Natural Titanium": "/products/iphone-15-pro-titanium.jpg", + "Blue Titanium": "/products/iphone-15-pro-blue.jpg", + "White Titanium": "/products/iphone-15-pro-white.jpg", + "Black Titanium": "/products/iphone-15-pro-black.jpg", + }, + categorySlug: "smartphones", + createdAt: new Date(), + updatedAt: new Date(), + }, + { + id: 3, + name: "MacBook Pro 14\" M4", + shortDescription: "M4 chip, 16GB RAM, 512GB SSD, Space Black", + description: "Professional laptop with M4 chip for ultimate performance.", + price: 1999, + sizes: ["512GB", "1TB", "2TB"], + colors: ["Space Black", "Silver"], + images: { + "Space Black": "/products/macbook-pro-14-black.jpg", + "Silver": "/products/macbook-pro-14-silver.jpg", + }, + categorySlug: "laptops", + createdAt: new Date(), + updatedAt: new Date(), + }, + { + id: 8, + name: "AirPods Pro (3rd Gen)", + shortDescription: "Active Noise Cancellation, USB-C", + description: "Premium wireless earbuds with advanced noise cancellation.", + price: 249, + sizes: ["One Size"], + colors: ["White"], + images: { + "White": "/products/airpods-pro-3rd-gen.jpg", + }, + categorySlug: "audio", + createdAt: new Date(), + updatedAt: new Date(), + }, + { + id: 21, + name: "iPad Pro 12.9\"", + shortDescription: "M2 chip, 128GB, Wi-Fi + Cellular", + description: "Ultimate iPad experience with M2 chip.", + price: 1099, + sizes: ["128GB", "256GB", "512GB", "1TB"], + colors: ["Space Gray", "Silver"], + images: { + "Space Gray": "/products/ipad-pro-gray.jpg", + "Silver": "/products/ipad-pro-silver.jpg", + }, + categorySlug: "tablets", + createdAt: new Date(), + updatedAt: new Date(), + }, + ]; + + // Filter out current product if provided + const filteredProducts = currentProductId + ? mockRecentProducts.filter(p => p.id !== currentProductId) + : mockRecentProducts; + + setRecentProducts(filteredProducts.slice(0, 4)); + }, [currentProductId]); + + if (recentProducts.length === 0) { + return null; + } + + return ( +
+
+ +

Recently Viewed

+
+ +
+ {recentProducts.map((product) => { + const firstImage = Object.values(product.images as Record)[0]; + + return ( + +
+ {product.name} +
+

+ {product.name} +

+

+ ${product.price.toLocaleString()} +

+ + ); + })} +
+ +
+ + + View All Products + +
+
+ ); +}; + +export default RecentlyViewed; \ No newline at end of file diff --git a/apps/client/src/components/SearchBar.tsx b/apps/client/src/components/SearchBar.tsx index c1e50e33..95fad561 100644 --- a/apps/client/src/components/SearchBar.tsx +++ b/apps/client/src/components/SearchBar.tsx @@ -1,34 +1,97 @@ "use client"; -import { Search } from "lucide-react"; +import { Search, X } from "lucide-react"; import { useRouter, useSearchParams } from "next/navigation"; -import { useState } from "react"; +import { useState, useEffect } from "react"; const SearchBar = () => { const [value, setValue] = useState(""); const searchParams = useSearchParams(); const router = useRouter(); - const handleSearch = (value: string) => { + // Initialize search value from URL params + useEffect(() => { + const searchQuery = searchParams.get("search"); + if (searchQuery) { + setValue(searchQuery); + } + }, [searchParams]); + + const handleSearch = (searchValue: string) => { + if (!searchValue.trim()) return; + const params = new URLSearchParams(searchParams); - params.set("search", value); + params.set("search", searchValue.trim()); router.push(`/products?${params.toString()}`, { scroll: false }); }; + const clearSearch = () => { + setValue(""); + const params = new URLSearchParams(searchParams); + params.delete("search"); + const newUrl = params.toString() ? `/products?${params.toString()}` : '/products'; + router.push(newUrl, { scroll: false }); + }; + return ( -
- - setValue(e.target.value)} - onKeyDown={(e) => { - if (e.key === "Enter") { - handleSearch(value); - } - }} - /> +
+ {/* Desktop Search */} +
+
+
+ + setValue(e.target.value)} + placeholder="Search products..." + className="bg-transparent outline-none text-sm text-gray-900 placeholder-gray-500 w-64" + onKeyDown={(e) => { + if (e.key === "Enter") { + handleSearch(value); + } + }} + /> + {value && ( + + )} +
+
+
+ + {/* Mobile Search */} +
+
+ + setValue(e.target.value)} + placeholder="Search..." + className="bg-transparent outline-none text-sm text-gray-900 placeholder-gray-500 flex-1" + onKeyDown={(e) => { + if (e.key === "Enter") { + handleSearch(value); + } + }} + /> + {value && ( + + )} +
+
); }; diff --git a/apps/client/src/components/TechHighlights.tsx b/apps/client/src/components/TechHighlights.tsx deleted file mode 100644 index 518b8ec4..00000000 --- a/apps/client/src/components/TechHighlights.tsx +++ /dev/null @@ -1,115 +0,0 @@ -"use client"; - -import { Battery, Zap, Shield, Wifi, Globe, Smartphone } from "lucide-react"; - -const TechHighlights = () => { - const highlights = [ - { - icon: Zap, - title: "Lightning Fast", - description: "Experience blazing-fast performance with our cutting-edge processors and optimized software", - stats: "99.9% Uptime" - }, - { - icon: Battery, - title: "All-Day Battery", - description: "Extended battery life that keeps you connected and productive throughout the day", - stats: "48h+ Battery" - }, - { - icon: Shield, - title: "Advanced Security", - description: "Military-grade encryption and biometric authentication for ultimate protection", - stats: "256-bit Encryption" - }, - { - icon: Wifi, - title: "Seamless Connectivity", - description: "5G, Wi-Fi 6E, and Bluetooth 5.3 for instant connections anywhere", - stats: "5G Ready" - }, - { - icon: Globe, - title: "Global Compatibility", - description: "Works seamlessly across different regions and network standards worldwide", - stats: "200+ Countries" - }, - { - icon: Smartphone, - title: "Smart Integration", - description: "AI-powered features that adapt to your usage patterns and preferences", - stats: "AI Enhanced" - } - ]; - - return ( -
-
-

- Tech Highlights -

-

- Discover the cutting-edge features and innovations in our premium technology products. -

-
- -
- {highlights.map((highlight, index) => { - const IconComponent = highlight.icon; - return ( -
-
-
- -
- -

- {highlight.title} -

- -

- {highlight.description} -

- -
- {highlight.stats} -
-
-
- ); - })} -
- - {/* Additional Stats Section */} -
-

- Trusted by Tech Enthusiasts Worldwide -

- -
-
-
50K+
-
Happy Customers
-
-
-
500+
-
Products
-
-
-
100+
-
Brands
-
-
-
98%
-
Satisfaction Rate
-
-
-
-
- ); -}; - -export default TechHighlights; \ No newline at end of file diff --git a/apps/client/src/components/TrustIndicators.tsx b/apps/client/src/components/TrustIndicators.tsx new file mode 100644 index 00000000..6345a94b --- /dev/null +++ b/apps/client/src/components/TrustIndicators.tsx @@ -0,0 +1,112 @@ +import { Star, Shield, Truck, RotateCcw, CheckCircle, Users, Award } from "lucide-react"; + +const TrustIndicators = () => { + const trustMetrics = [ + { + icon: , + rating: "4.9/5", + label: "Customer Rating", + description: "Based on 12,847 reviews" + }, + { + icon: , + label: "Free Shipping", + description: "On orders over $50" + }, + { + icon: , + label: "Secure Checkout", + description: "SSL encrypted" + }, + { + icon: , + label: "Easy Returns", + description: "30-day return policy" + } + ]; + + const achievements = [ + { + icon: , + count: "50K+", + label: "Happy Customers" + }, + { + icon: , + count: "2024", + label: "Best Tech Store Award" + }, + { + icon: , + count: "99.9%", + label: "Uptime Guarantee" + } + ]; + + return ( +
+
+ {/* Main Trust Indicators */} +
+ {trustMetrics.map((metric, index) => ( +
+
+ {metric.icon} +
+ {metric.rating && ( +
+ {metric.rating} +
+ )} +
+ {metric.label} +
+
+ {metric.description} +
+
+ ))} +
+ + {/* Secondary Trust Elements */} +
+ {achievements.map((achievement, index) => ( +
+ {achievement.icon} +
+
+ {achievement.count} +
+
+ {achievement.label} +
+
+
+ ))} +
+ + {/* Trust Badges */} +
+
+ + 256-bit SSL Encryption +
+
+ + PCI DSS Compliant +
+
+ + BBB A+ Rating +
+
+ + Trusted by Tech Professionals +
+
+
+
+ ); +}; + +export default TrustIndicators; \ No newline at end of file diff --git a/apps/client/src/components/WriteReview.tsx b/apps/client/src/components/WriteReview.tsx new file mode 100644 index 00000000..28967ee4 --- /dev/null +++ b/apps/client/src/components/WriteReview.tsx @@ -0,0 +1,219 @@ +"use client"; + +import { Star, X } from "lucide-react"; +import { useState } from "react"; +import { toast } from "react-toastify"; + +interface WriteReviewProps { + productId: number; + productName: string; + isOpen: boolean; + onClose: () => void; + onSubmit: (review: { + rating: number; + title: string; + comment: string; + }) => void; +} + +const WriteReview: React.FC = ({ + productId, + productName, + isOpen, + onClose, + onSubmit, +}) => { + const [rating, setRating] = useState(0); + const [hoveredRating, setHoveredRating] = useState(0); + const [title, setTitle] = useState(""); + const [comment, setComment] = useState(""); + const [isSubmitting, setIsSubmitting] = useState(false); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + if (rating === 0) { + toast.error("Please select a rating"); + return; + } + + if (!title.trim()) { + toast.error("Please enter a review title"); + return; + } + + if (!comment.trim()) { + toast.error("Please write your review"); + return; + } + + setIsSubmitting(true); + + try { + // Simulate API call + await new Promise(resolve => setTimeout(resolve, 1000)); + + onSubmit({ + rating, + title: title.trim(), + comment: comment.trim(), + }); + + toast.success("Review submitted successfully!"); + + // Reset form + setRating(0); + setTitle(""); + setComment(""); + onClose(); + } catch (error) { + toast.error("Failed to submit review. Please try again."); + } finally { + setIsSubmitting(false); + } + }; + + if (!isOpen) return null; + + return ( +
+
+
+

Write a Review

+ +
+ +
+
+

{productName}

+

+ Share your experience with this product to help other customers +

+
+ +
+ {/* Rating */} +
+ +
+ {[1, 2, 3, 4, 5].map((star) => ( + + ))} +
+ {rating > 0 && ( +

+ {rating === 1 && "Poor"} + {rating === 2 && "Fair"} + {rating === 3 && "Good"} + {rating === 4 && "Very Good"} + {rating === 5 && "Excellent"} +

+ )} +
+ + {/* Title */} +
+ + setTitle(e.target.value)} + placeholder="Summarize your experience" + maxLength={100} + className="w-full border border-gray-300 rounded-lg px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 focus:border-blue-500" + /> +

+ {title.length}/100 characters +

+
+ + {/* Comment */} +
+ +