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/apps/admin/src/app/(dashboard)/page.tsx b/apps/admin/src/app/(dashboard)/page.tsx index 1908eb4b..9362c465 100644 --- a/apps/admin/src/app/(dashboard)/page.tsx +++ b/apps/admin/src/app/(dashboard)/page.tsx @@ -8,33 +8,52 @@ 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 ( -
-
- -
-
- -
-
- +
+ {/* 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/admin/src/app/(dashboard)/users/data-table.tsx b/apps/admin/src/app/(dashboard)/users/data-table.tsx index dbaeddc8..2a82c7bd 100644 --- a/apps/admin/src/app/(dashboard)/users/data-table.tsx +++ b/apps/admin/src/app/(dashboard)/users/data-table.tsx @@ -39,8 +39,11 @@ export function DataTable({ const [sorting, setSorting] = useState([]); const [rowSelection, setRowSelection] = useState({}); + // Ensure data is always an array + const safeData = Array.isArray(data) ? data : []; + const table = useReactTable({ - data, + data: safeData, columns, getCoreRowModel: getCoreRowModel(), getPaginationRowModel: getPaginationRowModel(), @@ -61,10 +64,10 @@ export function DataTable({ const token = await getToken(); const selectedRows = table.getSelectedRowModel().rows; - Promise.all( + await Promise.all( selectedRows.map(async (row) => { const userId = (row.original as User).id; - const res = await fetch( + await fetch( `${process.env.NEXT_PUBLIC_AUTH_SERVICE_URL}/users/${userId}`, { method: "DELETE", diff --git a/apps/admin/src/app/(dashboard)/users/page.tsx b/apps/admin/src/app/(dashboard)/users/page.tsx index 2e002208..7a1f1061 100644 --- a/apps/admin/src/app/(dashboard)/users/page.tsx +++ b/apps/admin/src/app/(dashboard)/users/page.tsx @@ -14,10 +14,21 @@ const getData = async (): Promise<{ data: User[]; totalCount: number }> => { }, } ); + + if (!res.ok) { + console.error(`Failed to fetch users: ${res.status} ${res.statusText}`); + return { data: [], totalCount: 0 }; + } + const data = await res.json(); - return data; + + // Ensure data has the expected structure + return { + data: Array.isArray(data.data) ? data.data : [], + totalCount: typeof data.totalCount === 'number' ? data.totalCount : 0 + }; } catch (err) { - console.log(err); + console.error("Error fetching users:", err); return { data: [], totalCount: 0 }; } }; diff --git a/apps/admin/src/app/error.tsx b/apps/admin/src/app/error.tsx new file mode 100644 index 00000000..857c0cdb --- /dev/null +++ b/apps/admin/src/app/error.tsx @@ -0,0 +1,143 @@ +"use client"; + +import { useEffect } from "react"; +import Link from "next/link"; +import { AlertTriangle, Home, RefreshCcw, ArrowLeft, LifeBuoy } from "lucide-react"; + +export default function Error({ + error, + reset, +}: { + error: Error & { digest?: string }; + reset: () => void; +}) { + useEffect(() => { + // Log the error to an error reporting service + console.error("Admin Error caught by error boundary:", error); + }, [error]); + + return ( +
+
+ {/* Error Card */} +
+ {/* Icon */} +
+
+ +
+
+ + {/* Title */} +

+ Admin Panel Error +

+ + {/* Description */} +

+ An unexpected error occurred in the admin dashboard. +

+

+ The system has logged this error for investigation. +

+ + {/* Error Details (Development only) */} + {process.env.NODE_ENV === "development" && ( +
+
+ +

+ Error Details (Development Mode) +

+
+
+

+ {error.message || "Unknown error"} +

+ {error.stack && ( +
+ + Stack Trace + +
+                      {error.stack}
+                    
+
+ )} +
+ {error.digest && ( +

+ Error ID: {error.digest} +

+ )} +
+ )} + + {/* Action Buttons */} +
+ + + + + Dashboard Home + + + +
+ + {/* Admin Help Section */} +
+
+ +

+ Need Technical Support? +

+
+
+

+ For urgent issues, contact the technical team: +

+ +
+
+
+ + {/* Additional Info */} +
+

+ Error Code: {error.digest || "UNKNOWN"} | Neuraltale Admin Panel v1.0 +

+
+
+
+ ); +} 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/auth-service/src/routes/user.route.ts b/apps/auth-service/src/routes/user.route.ts index 2de2c3ad..3108a80b 100644 --- a/apps/auth-service/src/routes/user.route.ts +++ b/apps/auth-service/src/routes/user.route.ts @@ -5,8 +5,21 @@ import { producer } from "../utils/kafka"; const router: Router = Router(); router.get("/", async (req, res) => { - const users = await clerkClient.users.getUserList(); - res.status(200).json(users); + try { + const users = await clerkClient.users.getUserList(); + // Clerk returns { data: User[], totalCount: number } + res.status(200).json({ + data: users.data || [], + totalCount: users.totalCount || 0 + }); + } catch (error) { + console.error("Error fetching users:", error); + res.status(500).json({ + data: [], + totalCount: 0, + error: "Failed to fetch users" + }); + } }); router.get("/:id", async (req, res) => { diff --git a/apps/client/next.config.ts b/apps/client/next.config.ts index 465ae4bc..9d3cfb8b 100644 --- a/apps/client/next.config.ts +++ b/apps/client/next.config.ts @@ -11,6 +11,14 @@ const nextConfig: NextConfig = { protocol: "https", hostname: "res.cloudinary.com", }, + { + protocol: "https", + hostname: "images.unsplash.com", + }, + { + protocol: "https", + hostname: "upload.wikimedia.org", + }, ], }, }; 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/public/favicon.png b/apps/client/public/favicon.png new file mode 100644 index 00000000..09e87571 Binary files /dev/null and b/apps/client/public/favicon.png differ diff --git a/apps/client/public/featured.png b/apps/client/public/featured.png deleted file mode 100644 index 909a1688..00000000 Binary files a/apps/client/public/featured.png and /dev/null differ diff --git a/apps/client/public/logo.png b/apps/client/public/logo.png deleted file mode 100644 index 7716e4c8..00000000 Binary files a/apps/client/public/logo.png and /dev/null differ diff --git a/apps/client/public/products/1g.png b/apps/client/public/products/1g.png deleted file mode 100644 index d73aac42..00000000 Binary files a/apps/client/public/products/1g.png and /dev/null differ diff --git a/apps/client/public/products/1gr.png b/apps/client/public/products/1gr.png deleted file mode 100644 index fdc6ec5d..00000000 Binary files a/apps/client/public/products/1gr.png and /dev/null differ diff --git a/apps/client/public/products/1p.png b/apps/client/public/products/1p.png deleted file mode 100644 index a589132e..00000000 Binary files a/apps/client/public/products/1p.png and /dev/null differ diff --git a/apps/client/public/products/2g.png b/apps/client/public/products/2g.png deleted file mode 100644 index 5fe4a943..00000000 Binary files a/apps/client/public/products/2g.png and /dev/null differ diff --git a/apps/client/public/products/2gr.png b/apps/client/public/products/2gr.png deleted file mode 100644 index 0f15bc73..00000000 Binary files a/apps/client/public/products/2gr.png and /dev/null differ diff --git a/apps/client/public/products/3b.png b/apps/client/public/products/3b.png deleted file mode 100644 index db2bee27..00000000 Binary files a/apps/client/public/products/3b.png and /dev/null differ diff --git a/apps/client/public/products/3bl.png b/apps/client/public/products/3bl.png deleted file mode 100644 index 6c643e85..00000000 Binary files a/apps/client/public/products/3bl.png and /dev/null differ diff --git a/apps/client/public/products/3gr.png b/apps/client/public/products/3gr.png deleted file mode 100644 index 0d81d5aa..00000000 Binary files a/apps/client/public/products/3gr.png and /dev/null differ diff --git a/apps/client/public/products/4p.png b/apps/client/public/products/4p.png deleted file mode 100644 index 2f76025a..00000000 Binary files a/apps/client/public/products/4p.png and /dev/null differ diff --git a/apps/client/public/products/4w.png b/apps/client/public/products/4w.png deleted file mode 100644 index 6699256b..00000000 Binary files a/apps/client/public/products/4w.png and /dev/null differ diff --git a/apps/client/public/products/5bl.png b/apps/client/public/products/5bl.png deleted file mode 100644 index 7afeea53..00000000 Binary files a/apps/client/public/products/5bl.png and /dev/null differ diff --git a/apps/client/public/products/5o.png b/apps/client/public/products/5o.png deleted file mode 100644 index 27071598..00000000 Binary files a/apps/client/public/products/5o.png and /dev/null differ diff --git a/apps/client/public/products/5r.png b/apps/client/public/products/5r.png deleted file mode 100644 index 50be51fe..00000000 Binary files a/apps/client/public/products/5r.png and /dev/null differ diff --git a/apps/client/public/products/6g.png b/apps/client/public/products/6g.png deleted file mode 100644 index 08ab9749..00000000 Binary files a/apps/client/public/products/6g.png and /dev/null differ diff --git a/apps/client/public/products/6w.png b/apps/client/public/products/6w.png deleted file mode 100644 index 57ad9e37..00000000 Binary files a/apps/client/public/products/6w.png and /dev/null differ diff --git a/apps/client/public/products/7g.png b/apps/client/public/products/7g.png deleted file mode 100644 index 2c782d58..00000000 Binary files a/apps/client/public/products/7g.png and /dev/null differ diff --git a/apps/client/public/products/7p.png b/apps/client/public/products/7p.png deleted file mode 100644 index 2965edde..00000000 Binary files a/apps/client/public/products/7p.png and /dev/null differ diff --git a/apps/client/public/products/8b.png b/apps/client/public/products/8b.png deleted file mode 100644 index 611cc7eb..00000000 Binary files a/apps/client/public/products/8b.png and /dev/null differ diff --git a/apps/client/public/products/8gr.png b/apps/client/public/products/8gr.png deleted file mode 100644 index a6be86cf..00000000 Binary files a/apps/client/public/products/8gr.png and /dev/null differ diff --git a/apps/client/src/app/api/categories/route.ts b/apps/client/src/app/api/categories/route.ts new file mode 100644 index 00000000..af74f09e --- /dev/null +++ b/apps/client/src/app/api/categories/route.ts @@ -0,0 +1,25 @@ +import { NextResponse } from 'next/server'; + +export async function GET() { + try { + console.log('API Route: Fetching categories from product service'); + const response = await fetch('http://localhost:8000/categories', { + headers: { + 'Content-Type': 'application/json', + }, + }); + + if (!response.ok) { + console.error('API Route: Failed to fetch categories', response.status, response.statusText); + return NextResponse.json({ error: 'Failed to fetch categories' }, { status: response.status }); + } + + const categories = await response.json(); + console.log('API Route: Successfully fetched', categories.length, 'categories'); + + return NextResponse.json(categories); + } catch (error) { + console.error('API Route: Error fetching categories:', error); + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }); + } +} \ No newline at end of file diff --git a/apps/client/src/app/api/products/route.ts b/apps/client/src/app/api/products/route.ts new file mode 100644 index 00000000..4f1ba7eb --- /dev/null +++ b/apps/client/src/app/api/products/route.ts @@ -0,0 +1,25 @@ +import { NextResponse } from 'next/server'; + +export async function GET() { + try { + console.log('API Route: Fetching products from product service'); + const response = await fetch('http://localhost:8000/products', { + headers: { + 'Content-Type': 'application/json', + }, + }); + + if (!response.ok) { + console.error('API Route: Failed to fetch products', response.status, response.statusText); + return NextResponse.json({ error: 'Failed to fetch products' }, { status: response.status }); + } + + const products = await response.json(); + console.log('API Route: Successfully fetched products'); + + return NextResponse.json(products); + } catch (error) { + console.error('API Route: Error fetching products:', error); + return NextResponse.json({ error: 'Internal server error' }, { status: 500 }); + } +} \ No newline at end of file diff --git a/apps/client/src/app/cart/page.tsx b/apps/client/src/app/cart/page.tsx index 6769d3f5..2bd9f9dd 100644 --- a/apps/client/src/app/cart/page.tsx +++ b/apps/client/src/app/cart/page.tsx @@ -1,9 +1,8 @@ "use client"; import ShippingForm from "@/components/ShippingForm"; -import StripePaymentForm from "@/components/StripePaymentForm"; import useCartStore from "@/stores/cartStore"; -import { CartItemsType, ShippingFormInputs } from "@repo/types"; +import { ShippingFormInputs } from "@repo/types"; import { ArrowRight, Trash2 } from "lucide-react"; import Image from "next/image"; import { useRouter, useSearchParams } from "next/navigation"; @@ -20,7 +19,7 @@ const steps = [ }, { id: 3, - title: "Payment Method", + title: "Confirm Order", }, ]; @@ -120,9 +119,9 @@ const CartPage = () => { ))}
{/* STEPS & DETAILS */} -
+
{/* STEPS */} -
+
{activeStep === 1 ? ( cart.map((item) => ( // SINGLE CART ITEM @@ -131,9 +130,9 @@ const CartPage = () => { key={item.id + item.selectedSize + item.selectedColor} > {/* IMAGE AND DETAILS */} -
+
{/* IMAGE */} -
+
)?.[ @@ -147,34 +146,131 @@ const CartPage = () => {
{/* ITEM DETAILS */}
-
-

{item.name}

-

+

+

{item.name}

+

Quantity: {item.quantity}

-

+

Size: {item.selectedSize}

-

+

Color: {item.selectedColor}

-

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

+

TZs {(item.price * item.quantity * 2300).toLocaleString()}

{/* DELETE BUTTON */}
)) ) : activeStep === 2 ? ( ) : activeStep === 3 && shippingForm ? ( - + // Confirm Order Section +
+

Confirm Your Order

+ + {/* Delivery Options */} +
+

Choose Delivery Method

+
+ + +
+
+ + {/* Payment Methods */} +
+

Pay by Card or Mobile Money

+ + {/* Payment Icons */} +
+
+ Visa +
+
+ MasterCard +
+
+ Amex +
+
+ M-Pesa TZ +
+
+ TigoPesa TZ +
+
+ Pesapal E-wallet +
+
+ + {/* Pesapal Logo */} +
+
+
+ pesapal +
+
+
+ + {/* Payment Instructions */} +
+

+ Swahili: Lipia kwa Tigopesa au Card za bank zote zinakubalika. + Payment via Pesapal Gateway for credit/debit card or mobile money via Tigopesa. +

+

+ English: Pay with Tigopesa or all bank cards are accepted. + Payment via Pesapal Gateway for credit/debit card or mobile money via Tigopesa. +

+
+ + {/* Contact Instructions */} +
+

+ Swahili: Asante kwa oda yako, Ukimaliza malipo tafadhali tupigie kuconfirm payment yako. +

+

+ English: Thanks for your order! Please call 0653520829 or WhatsApp us to confirm your order once you are done with the payment. +

+
+ + {/* Terms and Conditions */} +
+ + +
+ + {/* Place Order Button */} + +
+
) : (

Please fill in the shipping form to continue. @@ -182,34 +278,28 @@ const CartPage = () => { )}

{/* DETAILS */} -
-

Cart Details

+
+

Order Summary

Subtotal

- $ - {cart - .reduce((acc, item) => acc + item.price * item.quantity, 0) - .toFixed(2)} + TZs {(cart.reduce((acc, item) => acc + item.price * item.quantity, 0) * 2300).toLocaleString()}

-

Discount(10%)

-

$ 10

+

Flat rate:

+

TZs 0

-

Shipping Fee

-

$10

+

Surcharge Fee

+

TZs 0


-

Total

+

Total (includes TZs TRA Tax)

- $ - {cart - .reduce((acc, item) => acc + item.price * item.quantity, 0) - .toFixed(2)} + TZs {(cart.reduce((acc, item) => acc + item.price * item.quantity, 0) * 2300).toLocaleString()}

diff --git a/apps/client/src/app/error.tsx b/apps/client/src/app/error.tsx new file mode 100644 index 00000000..7b9124b7 --- /dev/null +++ b/apps/client/src/app/error.tsx @@ -0,0 +1,112 @@ +"use client"; + +import { useEffect } from "react"; +import Link from "next/link"; +import { AlertTriangle, Home, RefreshCcw, ArrowLeft } from "lucide-react"; + +export default function Error({ + error, + reset, +}: { + error: Error & { digest?: string }; + reset: () => void; +}) { + useEffect(() => { + // Log the error to an error reporting service + console.error("Error caught by error boundary:", error); + }, [error]); + + return ( +
+
+ {/* Error Card */} +
+ {/* Icon */} +
+
+ +
+
+ + {/* Title */} +

+ Oops! Something went wrong +

+ + {/* Description */} +

+ We encountered an unexpected error while processing your request. +

+

+ Don't worry, our team has been notified and we're working to fix it. +

+ + {/* Error Details (Development only) */} + {process.env.NODE_ENV === "development" && ( +
+

+ Error Details (Development Mode): +

+

+ {error.message || "Unknown error"} +

+ {error.digest && ( +

+ Error ID: {error.digest} +

+ )} +
+ )} + + {/* Action Buttons */} +
+ + + + + Go Home + + + +
+ + {/* Help Text */} +
+

+ If this problem persists, please{" "} + + contact our support team + + . +

+
+
+ + {/* Additional Info */} +
+

+ Error Code: {error.digest || "UNKNOWN"} | Neuraltale E-Commerce +

+
+
+
+ ); +} diff --git a/apps/client/src/app/favicon.ico b/apps/client/src/app/favicon.ico index 1b59cd8d..09e87571 100644 Binary files a/apps/client/src/app/favicon.ico and b/apps/client/src/app/favicon.ico differ diff --git a/apps/client/src/app/layout.tsx b/apps/client/src/app/layout.tsx index 6225917b..e2db3688 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,21 @@ export default function RootLayout({ -
+ {/* Main page layout: Navbar at top, content in the middle, Footer at bottom */} +
- {children} +
+ {children} +
- + diff --git a/apps/client/src/app/page.tsx b/apps/client/src/app/page.tsx index 898dc77f..a25b54aa 100644 --- a/apps/client/src/app/page.tsx +++ b/apps/client/src/app/page.tsx @@ -1,5 +1,7 @@ import ProductList from "@/components/ProductList"; -import Image from "next/image"; +import HeroSection from "@/components/HeroSection"; +import TrustIndicators from "@/components/TrustIndicators"; +import ShopByCategory from "@/components/ShopByCategory"; const Homepage = async ({ searchParams, @@ -8,11 +10,38 @@ const Homepage = async ({ }) => { const category = (await searchParams).category; return ( -
-
- Featured Product +
+ {/* Hero Section - Full width */} + + + + + {/* Featured Products - With container */} +
+
+
+

+ Premium Tech Products +

+

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

- + +
+
+ + {/* Trust Indicators - With container */} +
+
+ +
+
+
+ {/* Shop By Category Section */} + +
); }; diff --git a/apps/client/src/app/products/[id]/page.tsx b/apps/client/src/app/products/[id]/page.tsx index c24baa93..70ae21eb 100644 --- a/apps/client/src/app/products/[id]/page.tsx +++ b/apps/client/src/app/products/[id]/page.tsx @@ -1,27 +1,22 @@ import ProductInteraction from "@/components/ProductInteraction"; +import { formatTzs } from "@/utils/currency"; import { ProductType } from "@repo/types"; import Image from "next/image"; +import { + Star, + Shield, + Truck, + RotateCcw, + Clock, + CheckCircle, + Heart, + Share2, + Tag, + Package, + Zap, + Globe +} from "lucide-react"; -// TEMPORARY -// const product: ProductType = { -// 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: 59.9, -// sizes: ["xs", "s", "m", "l", "xl"], -// 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(), -// }; const fetchProduct = async (id: string) => { const res = await fetch( @@ -57,63 +52,474 @@ const ProductPage = async ({ const product = await fetchProduct(id); - const selectedSize = size || (product.sizes[0] as string); - const selectedColor = color || (product.colors[0] as string); + const selectedSize = size || (product.sizes?.[0] as string) || ""; + const selectedColor = color || (product.colors?.[0] as string) || ""; + + // Extract specifications from product data + const specifications = [ + { label: "Product Name", value: product.name }, + { label: "Short Description", value: product.shortDescription }, + { label: "Category", value: product.categorySlug.replace(/-/g, ' ').replace(/\b\w/g, l => l.toUpperCase()) }, + { label: "Available Sizes", value: product.sizes?.join(", ") || "Standard" }, + { label: "Available Colors", value: product.colors?.join(", ") || "Default" }, + { label: "Product ID", value: `#${product.id}` }, + { label: "Last Updated", value: new Date(product.updatedAt).toLocaleDateString() }, + ]; + + // Extract key features from shortDescription for gaming laptops + const getKeyFeatures = (shortDesc: string, category: string) => { + const features = []; + + // Parse processor info + if (shortDesc.includes('AMD Ryzen')) { + const ryzenMatch = shortDesc.match(/AMD Ryzen \w+ \w+/); + if (ryzenMatch) features.push({ label: "Processor", value: ryzenMatch[0] }); + } else if (shortDesc.includes('Intel Core')) { + const intelMatch = shortDesc.match(/Intel Core [^,]+/); + if (intelMatch) features.push({ label: "Processor", value: intelMatch[0] }); + } + + // Parse graphics info + if (shortDesc.includes('RTX')) { + const rtxMatch = shortDesc.match(/RTX \w+/); + if (rtxMatch) features.push({ label: "Graphics", value: `NVIDIA GeForce ${rtxMatch[0]}` }); + } + + // Parse display info + if (shortDesc.includes('Hz')) { + const displayMatch = shortDesc.match(/\d+Hz[^,]*/); + if (displayMatch) features.push({ label: "Display", value: displayMatch[0] }); + } + + // Add category-specific features + if (category === 'gaming-laptops') { + features.push({ label: "Type", value: "Gaming Laptop" }); + features.push({ label: "Target Use", value: "Gaming & Content Creation" }); + } + + return features; + }; + + const keyFeatures = getKeyFeatures(product.shortDescription, product.categorySlug); + + // Service features + const serviceFeatures = [ + { icon: Shield, text: "2 Year Warranty" }, + { icon: Truck, text: "Free Shipping" }, + { icon: RotateCcw, text: "30-Day Returns" }, + { icon: Zap, text: "Fast Processing" } + ]; + return ( -
- {/* IMAGE */} -
- )?.[selectedColor] || "" - } - alt={product.name} - fill - className="object-contain rounded-md" - /> -
- {/* DETAILS */} -
-

{product.name}

-

{product.description}

-

${product.price.toFixed(2)}

- - {/* CARD INFO */} -
- klarna - cards - stripe + {/* Breadcrumb */} + + +
+ {/* IMAGE SECTION */} +
+ {/* Main Image */} +
+ )?.[selectedColor] || "/products/1g.png" + } + alt={product.name} + fill + className="object-contain p-8" + /> + {/* Badges */} +
+ + In Stock + + {product.price < 50000 && ( + + Sale + + )} +
+ {/* Action buttons */} +
+ + +
+
+ + {/* Thumbnail Images */} +
+ {product.colors?.map((color) => ( +
+ )?.[color] || "/products/1g.png"} + alt={`${product.name} in ${color}`} + width={80} + height={80} + className="object-contain w-full h-full p-2 bg-gray-50" + /> +
+ ))} +
+
+ + {/* PRODUCT DETAILS */} +
+ {/* Header */} +
+
+ + {product.categorySlug.replace(/-/g, ' ')} + + +
+

{product.name}

+ + {/* Short Description as subtitle */} +
+

{product.shortDescription}

+
+ + {/* Product Meta Info */} +
+ Product ID: #{product.id} + + Added: {new Date(product.createdAt).toLocaleDateString()} + + Updated: {new Date(product.updatedAt).toLocaleDateString()} +
+
+ + {/* Rating */} +
+
+ {[...Array(5)].map((_, i) => ( + + ))} +
+ (4.2 out of 5 • 127 reviews) +
+ + {/* Price */} +
+
+ + {formatTzs(product.price / 100, true)} + + {product.price < 50000 && ( + + {formatTzs((product.price * 1.2) / 100, true)} + + )} +
+

✓ Price includes VAT

+
+ + {/* Specifications */} +
+

Product Specifications

+
+ {/* Available Sizes */} + {product.sizes && product.sizes.length > 0 && ( +
+

Available Sizes

+
+ {product.sizes.map((sizeOption) => ( + + {sizeOption} + + ))} +
+
+ )} + + {/* Available Colors */} + {product.colors && product.colors.length > 0 && ( +
+

Available Colors

+
+ {product.colors.map((colorOption) => ( + + {colorOption} + + ))} +
+
+ )} +
+
+ + + + {/* Key Product Features */} + {keyFeatures.length > 0 && ( +
+

Key Features

+
+ {keyFeatures.map((feature, index) => ( +
+ {feature.label} + {feature.value} +
+ ))} +
+
+ )} + + {/* Service Features */} +
+ {serviceFeatures.map((feature, index) => ( +
+ + {feature.text} +
+ ))} +
+ + {/* Payment Methods */} +
+

Payment Methods

+
+ Klarna + Credit Cards + Stripe +
+
+ + {/* Trust Signals */} +
+
+ + Secure Purchase +
+

+ Your payment information is processed securely. We do not store credit card details. +

+
+
+
+ + {/* DETAILED DESCRIPTION & SPECS */} +
+ {/* Description */} +
+

Product Description

+
+ {/* Short Description Highlight */} +
+

+ + Key Highlights +

+

+ {product.shortDescription} +

+
+ + {/* Full Description */} +
+

+ {product.description} +

+
+ + {/* Enhanced Product Details Grid */} +
+
+

+ + What's Included +

+
    +
  • + + {product.name} +
  • +
  • + + Power Adapter & Cable +
  • +
  • + + User Manual & Documentation +
  • +
  • + + Warranty Card +
  • +
  • + + Original Packaging +
  • +
+
+
+

+ + Product Information +

+
    +
  • + Product ID: + #{product.id} +
  • +
  • + Category: + {product.categorySlug.replace(/-/g, ' ')} +
  • +
  • + Available Sizes: + {product.sizes?.length || 0} +
  • +
  • + Color Options: + {product.colors?.length || 0} +
  • +
  • + Added: + {new Date(product.createdAt).toLocaleDateString()} +
  • +
+
+
+
+
+ + {/* Specifications */} +
+

Specifications

+
+
+ {specifications.map((spec, index) => ( +
+
{spec.label}
+
{spec.value}
+
+ ))} +
+
+
+ + {/* Shipping & Returns */} +
+

Shipping & Returns

+
+
+
+
+ +
+

Free Shipping

+
+

+ Free standard shipping on orders over TZS 50,000. Express shipping available. +

+
+ +
+
+
+ +
+

Easy Returns

+
+

+ 30-day return policy. Items must be in original condition and packaging. +

+
+ +
+
+
+ +
+

Fast Processing

+
+

+ Orders are processed within 24 hours. Track your order every step of the way. +

+
+
+
+ + {/* Legal Notice */} +
+

+ By clicking "Add to Cart" or "Buy Now", you agree to our{" "} + Terms & Conditions{" "} + and Privacy Policy + . You authorize us to charge your selected payment method for the + total amount shown. All sales are subject to our return and{" "} + Refund Policies. +

-

- By clicking Pay Now, you agree to our{" "} - Terms & Conditions{" "} - and Privacy Policy - . You authorize us to charge your selected payment method for the - total amount shown. All sales are subject to our return and{" "} - Refund Policies. -

); diff --git a/apps/client/src/app/products/page.tsx b/apps/client/src/app/products/page.tsx index 4f15afed..7d9d93cf 100644 --- a/apps/client/src/app/products/page.tsx +++ b/apps/client/src/app/products/page.tsx @@ -1,4 +1,6 @@ import ProductList from "@/components/ProductList"; +import CategoryFilter from "@/components/CategoryFilter"; +import CategoryFilterSheet from "@/components/CategoryFilterSheet"; const ProductsPage = async ({ searchParams, @@ -8,14 +10,34 @@ const ProductsPage = async ({ const category = (await searchParams).category; const sort = (await searchParams).sort; const search = (await searchParams).search; + return ( -
- +
+
+ {/* Mobile Filter Button */} +
+ +
+ +
+ {/* Desktop Sidebar Filter */} + + + {/* Products */} +
+ +
+
+
); }; diff --git a/apps/client/src/components/Categories.tsx b/apps/client/src/components/Categories.tsx index 3b89987b..8c49dfab 100644 --- a/apps/client/src/components/Categories.tsx +++ b/apps/client/src/components/Categories.tsx @@ -1,85 +1,292 @@ "use client"; +import React, { useEffect, useState } from "react"; import { - Footprints, - Glasses, - Briefcase, - Shirt, + Smartphone, + Laptop, + Monitor, + Gamepad2, ShoppingBasket, - Hand, - Venus, + HardDrive, + Cpu, + Keyboard, + Network, + Code, + Tablet, + Mouse, + Headphones, + Watch, + Car, + Home, + Camera, + Wifi, + Speaker, + Lightbulb, + Shield, + Zap, + Package, + Grid3X3, } from "lucide-react"; import { usePathname, useRouter, useSearchParams } from "next/navigation"; -const categories = [ - { - name: "All", - icon: , - slug: "all", - }, - { - name: "T-shirts", - icon: , - slug: "t-shirts", - }, - { - name: "Shoes", - icon: , - slug: "shoes", - }, - { - name: "Accessories", - icon: , - slug: "accessories", - }, - { - name: "Bags", - icon: , - slug: "bags", - }, - { - name: "Dresses", - icon: , - slug: "dresses", - }, - { - name: "Jackets", - icon: , - slug: "jackets", - }, - { - name: "Gloves", - icon: , - slug: "gloves", - }, -]; +// Icon mapping for categories +const iconMap: Record = { + all: , + smartphones: , + laptops: , + "gaming-laptops": , + tablets: , + monitors: , + audio: , + accessories: , + wearables: , + "graphics-cards": , + processors: , + ram: , + ssds: , + "hard-drives": , + keyboards: , + mice: , + webcams: , + speakers: , + networking: , + routers: , + "wifi-adapters": , + storage: , + "gaming-chairs": , + desks: , + lighting: , + cables: , + "power-supplies": , + cooling: , + cases: , + motherboards: , + "smart-home": , + drones: , + "vr-headsets": , + "action-cameras": , + "home-security": , + "fitness-tech": , + "productivity-software": , + "security-software": , + "digital-games": , +}; + +interface Category { + id: number; + name: string; + slug: string; +} + +interface CategoryWithCount extends Category { + count?: number; + icon: React.ReactElement; +} + +// We'll fetch categories dynamically from the database const Categories = () => { + const [categories, setCategories] = useState([]); + const [loading, setLoading] = useState(true); + const [debugInfo, setDebugInfo] = useState(''); const searchParams = useSearchParams(); const router = useRouter(); const pathname = usePathname(); - const selectedCategory = searchParams.get("category"); + const selectedCategory = searchParams.get("category") || "all"; - const handleChange = (value: string | null) => { + // Fetch categories from the database + useEffect(() => { + const fetchCategories = async () => { + try { + setDebugInfo('FETCH STARTED'); + console.log('FETCH STARTED - Categories component mounting'); + console.log('Fetching categories from:', `${process.env.NEXT_PUBLIC_PRODUCT_SERVICE_URL}/categories`); + const apiUrl = '/api/categories'; // Use Next.js API route to avoid CORS + console.log('API URL:', apiUrl); + console.log('Environment variable set:', !!process.env.NEXT_PUBLIC_PRODUCT_SERVICE_URL); + + setDebugInfo('FETCHING...'); + const res = await fetch(apiUrl, { + method: 'GET', + headers: { + 'Content-Type': 'application/json', + }, + cache: 'no-store' + }); + console.log('Response received:', res); + console.log('Response status:', res.status); + console.log('Response ok:', res.ok); + + if (res.ok) { + setDebugInfo('PARSING DATA...'); + const data: Category[] = await res.json(); + console.log('Categories data received:', data); + console.log('Categories data length:', data.length); + console.log('First few categories:', data.slice(0, 3)); + + // Fetch total product count + const productsRes = await fetch('/api/products'); + let totalProducts = 150; // fallback + if (productsRes.ok) { + const productsData = await productsRes.json(); + totalProducts = productsData.Count || 150; + console.log('Total products from API:', totalProducts); + } + + setDebugInfo(`SUCCESS: ${data.length} categories loaded`); + // Add "All" category and map icons + const categoriesWithIcons: CategoryWithCount[] = [ + { + id: 0, + name: "All", + slug: "all", + count: totalProducts, + icon: iconMap.all!, + }, + ...data.map(category => ({ + ...category, + icon: iconMap[category.slug] || , + count: Math.floor(totalProducts / data.length), // Distribute products evenly for now + })) + ]; + + setCategories(categoriesWithIcons); + console.log('Categories loaded successfully:', categoriesWithIcons.length, 'total categories'); + console.log('Final categories array:', categoriesWithIcons.map(c => c.name)); + } else { + setDebugInfo(`FETCH FAILED: ${res.status} ${res.statusText}`); + console.error('Failed to fetch categories:', res.statusText); + // Fallback to default categories if fetch fails + setCategories([ + { + id: 0, + name: "All", + slug: "all", + count: 150, + icon: iconMap.all!, + } + ]); + } + } catch (error) { + setDebugInfo(`ERROR: ${error instanceof Error ? error.message : String(error)}`); + console.error("Error fetching categories:", error); + console.error("Error details:", error instanceof Error ? error.message : String(error)); + // Fallback to default "All" category if API fails + setCategories([ + { + id: 0, + name: "All", + slug: "all", + count: 150, + icon: iconMap.all!, + } + ]); + console.log('Using fallback categories due to error'); + } finally { + setLoading(false); + } + }; + + fetchCategories(); + }, []); + + 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 }); }; + if (loading) { + return ( +
+
+

Browse Categories

+

Loading categories...

+
+
+ {Array.from({ length: 6 }).map((_, index) => ( +
+
+
+ ))} +
+
+ ); + } + return ( -
- {categories.map((category) => ( -
handleChange(category.slug)} - > - {category.icon} - {category.name} +
+
+

+ Browse Categories +

+
+ Find exactly what you're looking for +
+
+ + {/* Horizontal Scrollable Categories */} +
+
+ {categories.map((category) => { + const isSelected = category.slug === selectedCategory; + return ( +
+ +
+ ); + })} +
+ + {/* Scroll indicators */} +
+
+
+
+
+
+
+ + {/* Compact Stats */} +
+
+ + {categories.find(cat => cat.slug === 'all')?.count || 0} Products • {categories.length > 0 ? categories.length - 1 : 0} Categories +
- ))} + +
); }; diff --git a/apps/client/src/components/CategoryFilter.tsx b/apps/client/src/components/CategoryFilter.tsx new file mode 100644 index 00000000..02cf9e78 --- /dev/null +++ b/apps/client/src/components/CategoryFilter.tsx @@ -0,0 +1,327 @@ +"use client"; + +import { useState } from "react"; +import { ChevronDown, ChevronUp, Star, X, SlidersHorizontal } from "lucide-react"; + +interface FilterState { + brands: string[]; + rating: number; + priceMin: string; + priceMax: string; + batteryCapacity: string[]; +} + +interface CategoryFilterProps { + onFilterChange?: (filters: FilterState) => void; + isSheet?: boolean; + onClose?: () => void; +} + +const CategoryFilter = ({ onFilterChange, isSheet = false, onClose }: CategoryFilterProps) => { + const [expandedSections, setExpandedSections] = useState({ + brands: true, + price: true, + rating: true, + battery: true, + }); + + const [filters, setFilters] = useState({ + brands: [], + rating: 0, + priceMin: "", + priceMax: "", + batteryCapacity: [], + }); + + const brands = [ + "Apple", + "Samsung", + "Sony", + "Dell", + "HP", + "Lenovo", + "Asus", + "Microsoft", + "LG", + "Canon", + "Nikon", + "JBL", + ]; + + const batteryOptions = [ + "Up to 3000mAh", + "3000-4000mAh", + "4000-5000mAh", + "5000mAh+", + ]; + + const toggleSection = (section: keyof typeof expandedSections) => { + setExpandedSections((prev) => ({ + ...prev, + [section]: !prev[section], + })); + }; + + const handleBrandToggle = (brand: string) => { + const newBrands = filters.brands.includes(brand) + ? filters.brands.filter((b) => b !== brand) + : [...filters.brands, brand]; + + const newFilters = { ...filters, brands: newBrands }; + setFilters(newFilters); + onFilterChange?.(newFilters); + }; + + const handleRatingChange = (rating: number) => { + const newFilters = { ...filters, rating }; + setFilters(newFilters); + onFilterChange?.(newFilters); + }; + + const handlePriceChange = (type: "min" | "max", value: string) => { + const newFilters = { + ...filters, + [type === "min" ? "priceMin" : "priceMax"]: value, + }; + setFilters(newFilters); + onFilterChange?.(newFilters); + }; + + const handleBatteryToggle = (capacity: string) => { + const newBattery = filters.batteryCapacity.includes(capacity) + ? filters.batteryCapacity.filter((b) => b !== capacity) + : [...filters.batteryCapacity, capacity]; + + const newFilters = { ...filters, batteryCapacity: newBattery }; + setFilters(newFilters); + onFilterChange?.(newFilters); + }; + + const clearAllFilters = () => { + const resetFilters: FilterState = { + brands: [], + rating: 0, + priceMin: "", + priceMax: "", + batteryCapacity: [], + }; + setFilters(resetFilters); + onFilterChange?.(resetFilters); + }; + + const activeFiltersCount = + filters.brands.length + + (filters.rating > 0 ? 1 : 0) + + (filters.priceMin || filters.priceMax ? 1 : 0) + + filters.batteryCapacity.length; + + return ( +
+ {/* Header */} +
+
+ +

Filters

+ {activeFiltersCount > 0 && ( + + {activeFiltersCount} + + )} +
+
+ {activeFiltersCount > 0 && ( + + )} + {isSheet && onClose && ( + + )} +
+
+ + {/* Filter Content */} +
+ {/* Brands Section */} +
+ + + {expandedSections.brands && ( +
+ {brands.map((brand) => ( + + ))} +
+ )} +
+ + {/* Price Range Section */} +
+ + + {expandedSections.price && ( +
+
+
+ + handlePriceChange("min", e.target.value)} + className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" + /> +
+ - +
+ + handlePriceChange("max", e.target.value)} + className="w-full px-3 py-2 border border-gray-300 rounded-lg text-sm focus:outline-none focus:ring-2 focus:ring-blue-500" + /> +
+
+
+ )} +
+ + {/* Rating Section */} +
+ + + {expandedSections.rating && ( +
+ {[5, 4, 3, 2, 1].map((rating) => ( + + ))} +
+ )} +
+ + {/* Battery Capacity Section */} +
+ + + {expandedSections.battery && ( +
+ {batteryOptions.map((option) => ( + + ))} +
+ )} +
+
+
+ ); +}; + +export default CategoryFilter; diff --git a/apps/client/src/components/CategoryFilterSheet.tsx b/apps/client/src/components/CategoryFilterSheet.tsx new file mode 100644 index 00000000..7c57d1d2 --- /dev/null +++ b/apps/client/src/components/CategoryFilterSheet.tsx @@ -0,0 +1,71 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { SlidersHorizontal } from "lucide-react"; +import CategoryFilter from "./CategoryFilter"; + +interface FilterState { + brands: string[]; + rating: number; + priceMin: string; + priceMax: string; + batteryCapacity: string[]; +} + +interface CategoryFilterSheetProps { + onFilterChange?: (filters: FilterState) => void; +} + +const CategoryFilterSheet = ({ onFilterChange }: CategoryFilterSheetProps) => { + const [isOpen, setIsOpen] = useState(false); + + // Prevent body scroll when sheet is open + useEffect(() => { + if (isOpen) { + document.body.style.overflow = "hidden"; + } else { + document.body.style.overflow = "unset"; + } + return () => { + document.body.style.overflow = "unset"; + }; + }, [isOpen]); + + return ( + <> + {/* Trigger Button */} + + + {/* Backdrop */} + {isOpen && ( +
setIsOpen(false)} + /> + )} + + {/* Sheet */} +
+ setIsOpen(false)} + onFilterChange={(filters) => { + onFilterChange?.(filters); + }} + /> +
+ + ); +}; + +export default CategoryFilterSheet; diff --git a/apps/client/src/components/Footer.tsx b/apps/client/src/components/Footer.tsx index 055a93a3..d85920b3 100644 --- a/apps/client/src/components/Footer.tsx +++ b/apps/client/src/components/Footer.tsx @@ -1,41 +1,205 @@ -import Image from "next/image"; +"use client"; + import Link from "next/link"; +import Image from "next/image"; +import { Twitter, Instagram, Youtube, Linkedin, Mail, Phone, MapPin,Github } 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.

-
-
-

Links

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

Links

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

+ Discover the future of technology and exceptional quality. +

+ + + {/* Newsletter - Integrated */} +
+
+ Stay Updated +
+

+ Get exclusive deals and tech insights. +

+
+ setEmail(e.target.value)} + placeholder="Your email" + className="flex-1 px-2 py-1.5 text-xs rounded border border-gray-300 focus:ring-1 focus:ring-blue-500 focus:border-blue-500 focus:outline-none" + required + /> + +
+
+ + +
+ + {/* Shop Section */} +
+

Shop

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

Support

+
    +
  • Help Center
  • +
  • Contact Us
  • +
  • Shipping Info
  • +
  • Returns
  • +
  • Warranty
  • +
  • Track Order
  • +
+
+ + {/* Company Section */} +
+

Company

+ + + {/* 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 + + + Cookie Policy + + + Sitemap + +
+
+
+ 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..c916a997 --- /dev/null +++ b/apps/client/src/components/HeroSection.tsx @@ -0,0 +1,380 @@ +"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.slice(0, 4).map((feature, index) => ( +
+
+ {feature} +
+ ))} +
+
+ + {/* Pricing */} +
+
+ + TZs {(currentProduct.price * 2300).toLocaleString()} + + {currentProduct.originalPrice && ( + + TZs {(currentProduct.originalPrice * 2300).toLocaleString()} + + )} + {currentProduct.originalPrice && ( + + SAVE TZs {((currentProduct.originalPrice - currentProduct.price) * 2300).toLocaleString()} + + )} +
+
+
+ {currentProduct.availability} +
+
+ + {/* CTA Buttons */} +
+ + Shop Now + + + View Details + +
+
+ + {/* Product Image */} +
+
+ {currentProduct.name} + {/* Floating Spec Card - Hidden on very small screens */} +
+

Specifications

+
    + {currentProduct.specifications.slice(0, 2).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/MobileProductGrid.tsx b/apps/client/src/components/MobileProductGrid.tsx new file mode 100644 index 00000000..85aec3c7 --- /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 */} +
+
+ + TZs {(product.price * 2300).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..8c708acb 100644 --- a/apps/client/src/components/Navbar.tsx +++ b/apps/client/src/components/Navbar.tsx @@ -1,49 +1,382 @@ -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"; const Navbar = () => { + const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false); + const [isCategoriesOpen, setIsCategoriesOpen] = useState(false); + + const categories = [ + { + name: "Laptops", + href: "/products?category=laptops", + subcategories: [ + { name: "Gaming Laptops", href: "/products?category=gaming-laptops" }, + { name: "Business Laptops", href: "/products?category=business-laptops" }, + { name: "Ultrabooks", href: "/products?category=ultrabooks" }, + { name: "MacBooks", href: "/products?category=macbooks" }, + ] + }, + { + name: "Desktops", + href: "/products?category=desktops", + subcategories: [ + { name: "Gaming PCs", href: "/products?category=gaming-pcs" }, + { name: "Workstations", href: "/products?category=workstations" }, + { name: "All-in-One PCs", href: "/products?category=all-in-one" }, + { name: "Mini PCs", href: "/products?category=mini-pcs" }, + ] + }, + { + name: "Computer Monitors", + href: "/products?category=monitors", + subcategories: [ + { name: "Gaming Monitors", href: "/products?category=gaming-monitors" }, + { name: "4K Monitors", href: "/products?category=4k-monitors" }, + { name: "Ultrawide", href: "/products?category=ultrawide-monitors" }, + { name: "Professional", href: "/products?category=professional-monitors" }, + ] + }, + { + name: "Storage", + href: "/products?category=storage", + subcategories: [ + { name: "SSDs", href: "/products?category=ssds" }, + { name: "Hard Drives", href: "/products?category=hard-drives" }, + { name: "External Storage", href: "/products?category=external-storage" }, + { name: "NAS", href: "/products?category=nas" }, + ] + }, + { + name: "Components", + href: "/products?category=components", + subcategories: [ + { name: "Processors", href: "/products?category=processors" }, + { name: "Graphics Cards", href: "/products?category=graphics-cards" }, + { name: "Motherboards", href: "/products?category=motherboards" }, + { name: "RAM", href: "/products?category=ram" }, + ] + }, + { + name: "Peripherals", + href: "/products?category=peripherals", + subcategories: [ + { name: "Keyboards", href: "/products?category=keyboards" }, + { name: "Mice", href: "/products?category=mice" }, + { name: "Headsets", href: "/products?category=headsets" }, + { name: "Webcams", href: "/products?category=webcams" }, + ] + }, + { + name: "Networking", + href: "/products?category=networking", + subcategories: [ + { name: "Routers", href: "/products?category=routers" }, + { name: "WiFi Adapters", href: "/products?category=wifi-adapters" }, + { name: "Switches", href: "/products?category=switches" }, + { name: "Access Points", href: "/products?category=access-points" }, + ] + }, + { + name: "Gadgets", + href: "/products?category=gadgets", + subcategories: [ + { name: "Smartphones", href: "/products?category=smartphones" }, + { name: "Tablets", href: "/products?category=tablets" }, + { name: "Smartwatches", href: "/products?category=smartwatches" }, + { name: "Audio", href: "/products?category=audio" }, + ] + }, + { + name: "Gaming", + href: "/products?category=gaming", + subcategories: [ + { name: "Gaming Chairs", href: "/products?category=gaming-chairs" }, + { name: "Controllers", href: "/products?category=controllers" }, + { name: "VR Headsets", href: "/products?category=vr-headsets" }, + { name: "Gaming Accessories", href: "/products?category=gaming-accessories" }, + ] + }, + { + name: "Software & Digital", + href: "/products?category=software", + subcategories: [ + { name: "Operating Systems", href: "/products?category=operating-systems" }, + { name: "Productivity", href: "/products?category=productivity-software" }, + { name: "Security", href: "/products?category=security-software" }, + { name: "Games", href: "/products?category=digital-games" }, + ] + }, + ]; + + const navigationItems = [ + { name: "All Products", href: "/products" }, + { name: "Deals", href: "/products?sort=price-asc" }, + { name: "Services", href: "https://www.neuraltale.com/services", external: true }, + { name: "About", href: "https://www.neuraltale.com/about", external: true }, + ]; + return ( - + <> + + ); }; diff --git a/apps/client/src/components/OrderPlacementForm.tsx b/apps/client/src/components/OrderPlacementForm.tsx new file mode 100644 index 00000000..e69de29b diff --git a/apps/client/src/components/ProductCard.tsx b/apps/client/src/components/ProductCard.tsx index 81a990d1..0785ed38 100644 --- a/apps/client/src/components/ProductCard.tsx +++ b/apps/client/src/components/ProductCard.tsx @@ -1,8 +1,9 @@ "use client"; import useCartStore from "@/stores/cartStore"; +import { formatTzs } from "@/utils/currency"; import { ProductType } from "@repo/types"; -import { ShoppingCart } from "lucide-react"; +import { ShoppingCart, Star, Heart } from "lucide-react"; import Image from "next/image"; import Link from "next/link"; import { useState } from "react"; @@ -10,9 +11,10 @@ import { toast } from "react-toastify"; const ProductCard = ({ product }: { product: ProductType }) => { const [productTypes, setProductTypes] = useState({ - size: product.sizes[0]!, - color: product.colors[0]!, + size: product.sizes?.[0] || "", + color: product.colors?.[0] || "", }); + const [isWishlisted, setIsWishlisted] = useState(false); const { addToCart } = useCartStore(); @@ -36,81 +38,147 @@ 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; + + // 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 */} + + + {/* Availability Badge */} +
+ {availability.status} +
+ {/* PRODUCT DETAIL */} -
-

{product.name}

-

{product.shortDescription}

- {/* PRODUCT TYPES */} -
- {/* SIZES */} -
- Size - +
+ + {rating.toFixed(1)} +
- {/* COLORS */} -
- Color -
- {product.colors.map((color) => ( -
- handleProductType({ type: "color", value: color }) - } +
+ + {/* PRODUCT TYPES - Simplified for mobile */} +
+ {/* Storage/Size Options - Show only 2 on mobile */} +
+ + {product.categorySlug === "smartphones" || product.categorySlug === "tablets" ? "Storage" : "Size"} + +
+ {product.sizes.slice(0, 2).map((size) => ( + ))}
+ +
- {/* PRICE AND ADD TO CART BUTTON */} -
-

${product.price.toFixed(2)}

+ + {/* PRICE AND ACTIONS */} +
+
+
+ + {formatTzs(product.price, true)} + + {/* Show savings if applicable */} + {product.id % 4 === 0 && ( +
+ + {formatTzs(product.price * 1.15, true)} + + + Save 15% + +
+ )} +
+
+ + +
diff --git a/apps/client/src/components/ProductInteraction.tsx b/apps/client/src/components/ProductInteraction.tsx index 8e169293..7e664e11 100644 --- a/apps/client/src/components/ProductInteraction.tsx +++ b/apps/client/src/components/ProductInteraction.tsx @@ -54,42 +54,50 @@ const ProductInteraction = ({
Size
- {product.sizes.map((size) => ( -
handleTypeChange("size", size)} - > + {product.sizes?.length > 0 ? ( + product.sizes.map((size) => (
handleTypeChange("size", size)} > - {size.toUpperCase()} +
+ {size.toUpperCase()} +
-
- ))} + )) + ) : ( + No sizes available + )}
{/* COLOR */}
Color
- {product.colors.map((color) => ( -
handleTypeChange("color", color)} - > -
-
- ))} + {product.colors?.length > 0 ? ( + product.colors.map((color) => ( +
handleTypeChange("color", color)} + > +
+
+ )) + ) : ( + No colors available + )}
{/* QUANTITY */} diff --git a/apps/client/src/components/ProductList.tsx b/apps/client/src/components/ProductList.tsx index 9157d368..db0fc166 100644 --- a/apps/client/src/components/ProductList.tsx +++ b/apps/client/src/components/ProductList.tsx @@ -4,142 +4,6 @@ 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(), -// }, -// ]; - const fetchData = async ({ category, sort, @@ -151,12 +15,63 @@ const fetchData = async ({ search?: string; params: "homepage" | "products"; }) => { - 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; + try { + // Build query parameters + const queryParams = new URLSearchParams(); + + // Category filter (exclude 'all' category) + if (category && category !== "all") { + queryParams.append("category", category); + } + + // Search filter + if (search) { + queryParams.append("search", search); + } + + // Sort mapping to match backend + let sortParam = "newest"; // default + switch (sort) { + case "price-asc": + sortParam = "asc"; + break; + case "price-desc": + sortParam = "desc"; + break; + case "oldest": + sortParam = "oldest"; + break; + default: + sortParam = "newest"; + } + queryParams.append("sort", sortParam); + + // Limit for homepage + if (params === "homepage") { + queryParams.append("limit", "8"); + } + + const url = `${process.env.NEXT_PUBLIC_PRODUCT_SERVICE_URL || 'http://localhost:8000'}/products?${queryParams.toString()}`; + + console.log('Fetching products from:', url); + + const res = await fetch(url, { + next: { revalidate: 60 }, // Cache for 1 minute + }); + + if (!res.ok) { + console.error(`Failed to fetch products: ${res.status} ${res.statusText}`); + return []; + } + + const data: ProductType[] = await res.json(); + return Array.isArray(data) ? data : []; + } catch (error) { + console.error("Error fetching products:", error); + return []; + } }; + const ProductList = async ({ category, sort, @@ -171,21 +86,30 @@ const ProductList = async ({ const products = await fetchData({ category, sort, search, params }); return (
+ {/* Section Header */} + + {params === "products" && } -
- {products.map((product) => ( + +
+ {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/RecentlyViewed.tsx b/apps/client/src/components/RecentlyViewed.tsx new file mode 100644 index 00000000..57246381 --- /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} +

+

+ TZs {(product.price * 2300).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/ShopByCategory.tsx b/apps/client/src/components/ShopByCategory.tsx new file mode 100644 index 00000000..5fa5a5a8 --- /dev/null +++ b/apps/client/src/components/ShopByCategory.tsx @@ -0,0 +1,209 @@ +"use client"; + +import Image from "next/image"; +import Link from "next/link"; +import { ArrowRight } from "lucide-react"; + +interface CategoryCard { + id: number; + badge: string; + descriptor: string; + name: string; + bgColor: string; + textColor: string; + buttonColor: string; + image: string; + link: string; + size: "small" | "large"; +} + +const categories: CategoryCard[] = [ + { + id: 1, + badge: "Enjoy", + descriptor: "With", + name: "HEADPHONE", + bgColor: "bg-purple-600", + textColor: "text-white", + buttonColor: "bg-white text-purple-600 hover:bg-purple-50", + image: "https://images.unsplash.com/photo-1505740420928-5e560c06d30e?w=400&h=400&fit=crop", + link: "/products?category=audio", + size: "small", + }, + { + id: 2, + badge: "New", + descriptor: "Smart", + name: "WATCH", + bgColor: "bg-orange-500", + textColor: "text-white", + buttonColor: "bg-white text-orange-500 hover:bg-orange-50", + image: "https://images.unsplash.com/photo-1523275335684-37898b6baf30?w=400&h=400&fit=crop", + link: "/products?category=wearables", + size: "small", + }, + { + id: 3, + badge: "Productivity", + descriptor: "Gaming", + name: "LAPTOP", + bgColor: "bg-cyan-500", + textColor: "text-white", + buttonColor: "bg-white text-cyan-600 hover:bg-cyan-50", + image: "https://images.unsplash.com/photo-1496181133206-80ce9b88a853?w=400&h=400&fit=crop", + link: "/products?category=laptops", + size: "large", + }, + { + id: 4, + badge: "Play", + descriptor: "Game", + name: "CONSOLE", + bgColor: "bg-gray-900", + textColor: "text-white", + buttonColor: "bg-white text-gray-900 hover:bg-gray-100", + image: "https://images.unsplash.com/photo-1606144042614-b2417e99c4e3?w=400&h=400&fit=crop", + link: "/products?category=gaming", + size: "large", + }, + { + id: 5, + badge: "Play", + descriptor: "VR", + name: "OCULUS", + bgColor: "bg-green-600", + textColor: "text-white", + buttonColor: "bg-white text-green-600 hover:bg-green-50", + image: "https://images.unsplash.com/photo-1622979135225-d2ba269cf1ac?w=400&h=400&fit=crop", + link: "/products?category=vr-headsets", + size: "small", + }, + { + id: 6, + badge: "New", + descriptor: "Smart", + name: "SPEAKER", + bgColor: "bg-blue-600", + textColor: "text-white", + buttonColor: "bg-white text-blue-600 hover:bg-blue-50", + image: "https://images.unsplash.com/photo-1608043152269-423dbba4e7e1?w=400&h=400&fit=crop", + link: "/products?category=audio", + size: "small", + }, +]; + +const ShopByCategory = () => { + return ( +
+
+ {/* Header */} +
+

+ CATEGORY +

+

+ Shop By Category +

+

+ Explore our gallery to learn more about our amazing products and their features. +

+
+ + {/* Category Grid - 2 Rows x 3 Columns */} +
+ {/* Row 1: 3 Cards */} + {categories.slice(0, 3).map((category) => ( + + {/* Background Image - Full Cover */} +
+ {category.name} + {/* Overlay for better text readability */} +
+
+ + {/* Badge */} +
+ {category.badge} +
+ + {/* Content */} +
+
+

+ {category.descriptor} +

+

+ {category.name} +

+
+ + {/* Browse Button */} + +
+ + ))} + + {/* Row 2: 3 Cards */} + {categories.slice(3, 6).map((category) => ( + + {/* Background Image - Full Cover */} +
+ {category.name} + {/* Overlay for better text readability */} +
+
+ + {/* Badge */} +
+ {category.badge} +
+ + {/* Content */} +
+
+

+ {category.descriptor} +

+

+ {category.name} +

+
+ + {/* Browse Button */} + +
+ + ))} +
+
+
+ ); +}; + +export default ShopByCategory; diff --git a/apps/client/src/components/TrustIndicators.tsx b/apps/client/src/components/TrustIndicators.tsx new file mode 100644 index 00000000..d41ca39d --- /dev/null +++ b/apps/client/src/components/TrustIndicators.tsx @@ -0,0 +1,199 @@ +"use client"; + +import { Star, Shield, Truck, RotateCcw } from "lucide-react"; +import Image from "next/image"; + +const TrustIndicators = () => { + const trustMetrics = [ + { + icon: , + rating: "4.9/5", + label: "Rating", + description: "12.8K reviews" + }, + { + icon: , + label: "Free Shipping", + description: "Orders 115K+" + }, + { + icon: , + label: "Secure Pay", + description: "SSL encrypted" + }, + { + icon: , + label: "Easy Returns", + description: "30-day policy" + } + ]; + + const brands = [ + { + name: "Apple", + logo: "https://upload.wikimedia.org/wikipedia/commons/f/fa/Apple_logo_black.svg", + }, + { + name: "Samsung", + logo: "https://upload.wikimedia.org/wikipedia/commons/2/24/Samsung_Logo.svg", + }, + { + name: "Sony", + logo: "https://upload.wikimedia.org/wikipedia/commons/c/ca/Sony_logo.svg", + }, + { + name: "Dell", + logo: "https://upload.wikimedia.org/wikipedia/commons/4/48/Dell_Logo.svg", + }, + { + name: "HP", + logo: "https://upload.wikimedia.org/wikipedia/commons/a/ad/HP_logo_2012.svg", + }, + { + name: "Lenovo", + logo: "https://upload.wikimedia.org/wikipedia/commons/0/03/Lenovo_Global_Corporate_Logo.svg", + }, + { + name: "Asus", + logo: "https://upload.wikimedia.org/wikipedia/commons/2/2e/ASUS_Logo.svg", + }, + { + name: "Microsoft", + logo: "https://upload.wikimedia.org/wikipedia/commons/4/44/Microsoft_logo.svg", + }, + { + name: "LG", + logo: "https://upload.wikimedia.org/wikipedia/commons/b/bf/LG_logo_%282015%29.svg", + }, + { + name: "Canon", + logo: "https://upload.wikimedia.org/wikipedia/commons/4/45/Canon_wordmark.svg", + }, + ]; + + return ( +
+
+ {/* Compact Trust Indicators */} +
+ {trustMetrics.map((metric, index) => ( +
+
+ {metric.icon} +
+
+ {metric.rating && ( +
+ {metric.rating} +
+ )} +
+ {metric.label} +
+
+ {metric.description} +
+
+
+ ))} +
+ + {/* Brand Logo Cloud */} +
+

+ Trusted Brands We Partner With +

+
+ +
+ {/* Gradient Overlays */} +
+
+ + {/* Scrolling Logo Container */} +
+ {/* First set of logos */} +
+ {brands.map((brand, index) => ( +
+ {`${brand.name} +
+ ))} +
+ + {/* Duplicate set for seamless loop */} +
+ {brands.map((brand, index) => ( +
+ {`${brand.name} +
+ ))} +
+
+
+ + {/* Add custom CSS for animation */} + + + {/* Bottom Trust Line */} +
+
+ + 256-bit SSL +
+
+ + 50K+ Happy Customers +
+
+ + PCI DSS Compliant +
+
+ + Trusted Delivery Partners +
+
+
+
+ ); +}; + +export default TrustIndicators; \ No newline at end of file diff --git a/apps/client/src/utils/currency.ts b/apps/client/src/utils/currency.ts new file mode 100644 index 00000000..4228efe0 --- /dev/null +++ b/apps/client/src/utils/currency.ts @@ -0,0 +1,80 @@ +/** + * Currency utility functions for Neuraltale E-commerce + * Handles conversion between USD and Tanzanian Shillings (TZS) + */ + +// Exchange rate: 1 USD = 2300 TZS (approximate) +export const USD_TO_TZS_RATE = 2300; + +/** + * Convert USD amount to TZS + * @param usdAmount - Amount in USD + * @returns Amount in TZS + */ +export function convertUsdToTzs(usdAmount: number): number { + return usdAmount * USD_TO_TZS_RATE; +} + +/** + * Convert TZS amount to USD + * @param tzsAmount - Amount in TZS + * @returns Amount in USD + */ +export function convertTzsToUsd(tzsAmount: number): number { + return tzsAmount / USD_TO_TZS_RATE; +} + +/** + * Format TZS amount with proper formatting + * @param amount - Amount to format (can be in USD or TZS) + * @param fromUsd - Whether to convert from USD first (default: false) + * @returns Formatted TZS string + */ +export function formatTzs(amount: number, fromUsd: boolean = false): string { + const tzsAmount = fromUsd ? convertUsdToTzs(amount) : amount; + return `TZs ${tzsAmount.toLocaleString()}`; +} + +/** + * Format USD amount with proper formatting + * @param amount - Amount in USD + * @returns Formatted USD string + */ +export function formatUsd(amount: number): string { + return `$${amount.toLocaleString()}`; +} + +/** + * Get price display for UI components + * @param usdPrice - Price in USD (base price) + * @returns Object with both USD and TZS formatted prices + */ +export function getPriceDisplay(usdPrice: number) { + return { + usd: formatUsd(usdPrice), + tzs: formatTzs(usdPrice, true), + tzsAmount: convertUsdToTzs(usdPrice), + usdAmount: usdPrice + }; +} + +/** + * Calculate savings in TZS + * @param originalUsdPrice - Original price in USD + * @param discountedUsdPrice - Discounted price in USD + * @returns Savings amount in TZS + */ +export function calculateSavingsTzs(originalUsdPrice: number, discountedUsdPrice: number): number { + return convertUsdToTzs(originalUsdPrice - discountedUsdPrice); +} + +/** + * Format savings display + * @param originalUsdPrice - Original price in USD + * @param discountedUsdPrice - Discounted price in USD + * @returns Formatted savings string + */ +export function formatSavings(originalUsdPrice: number, discountedUsdPrice: number): string { + const savings = calculateSavingsTzs(originalUsdPrice, discountedUsdPrice); + return `SAVE ${formatTzs(savings)}`; +} \ No newline at end of file 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/apps/payment-service/src/routes/session.route.ts b/apps/payment-service/src/routes/session.route.ts index ce6daecb..1b546768 100644 --- a/apps/payment-service/src/routes/session.route.ts +++ b/apps/payment-service/src/routes/session.route.ts @@ -15,7 +15,7 @@ sessionRoute.post("/create-checkout-session", shouldBeUser, async (c) => { const unitAmount = await getStripeProductPrice(item.id); return { price_data: { - currency: "usd", + currency: "tzs", // Tanzanian Shilling product_data: { name: item.name, }, diff --git a/apps/payment-service/src/utils/stripeProduct.ts b/apps/payment-service/src/utils/stripeProduct.ts index cb1a897f..be8a6c0f 100644 --- a/apps/payment-service/src/utils/stripeProduct.ts +++ b/apps/payment-service/src/utils/stripeProduct.ts @@ -7,8 +7,8 @@ export const createStripeProduct = async (item: StripeProductType) => { id: item.id, name: item.name, default_price_data: { - currency: "usd", - unit_amount: item.price * 100, + currency: "tzs", // Tanzanian Shilling + unit_amount: item.price * 100 * 2300, // Convert USD to TZS (1 USD ≈ 2300 TZS) and to cents }, }); return res; diff --git a/apps/product-service/src/index.ts b/apps/product-service/src/index.ts index 21b7086b..8a467717 100644 --- a/apps/product-service/src/index.ts +++ b/apps/product-service/src/index.ts @@ -8,7 +8,7 @@ import { consumer, producer } from "./utils/kafka.js"; const app = express(); app.use( cors({ - origin: ["http://localhost:3002", "http://localhost:3003"], + origin: ["http://localhost:3002", "http://localhost:3003", "http://localhost:3004"], credentials: true, }) ); @@ -39,7 +39,14 @@ app.use((err: any, req: Request, res: Response, next: NextFunction) => { const start = async () => { try { - Promise.all([await producer.connect(), await consumer.connect()]); + // Try to connect to Kafka, but don't fail if it's not available + try { + await Promise.all([producer.connect(), consumer.connect()]); + console.log("Kafka connected successfully"); + } catch (kafkaError) { + console.log("Kafka connection failed (service will run without it):", kafkaError.message); + } + app.listen(8000, () => { console.log("Product service is running on 8000"); }); diff --git a/neuraltale-database-setup-fixed.sql b/neuraltale-database-setup-fixed.sql new file mode 100644 index 00000000..bdceb370 --- /dev/null +++ b/neuraltale-database-setup-fixed.sql @@ -0,0 +1,166 @@ +-- ====================================== +-- NEURALTALE E-COMMERCE DATABASE SETUP +-- ====================================== +-- This script sets up the complete database schema and sample data for Neuraltale e-commerce platform +-- All prices are in Tanzanian Shillings (TZS) + +-- ====================================== +-- INSERT CATEGORIES +-- ====================================== + +INSERT INTO "Category" (name, slug) VALUES +('Smartphones', 'smartphones'), +('Laptops', 'laptops'), +('Gaming Laptops', 'gaming-laptops'), +('Audio', 'audio'), +('Accessories', 'accessories'), +('Tablets', 'tablets'), +('Monitors', 'monitors'), +('Wearables', 'wearables'), +('Graphics Cards', 'graphics-cards'), +('Processors', 'processors'), +('RAM', 'ram'), +('SSDs', 'ssds'), +('Hard Drives', 'hard-drives'), +('Keyboards', 'keyboards'), +('Mice', 'mice'), +('Webcams', 'webcams'), +('Speakers', 'speakers'), +('Networking', 'networking'), +('Storage', 'storage'), +('Gaming Chairs', 'gaming-chairs'), +('Desks', 'desks'), +('Lighting', 'lighting'), +('Cables', 'cables'), +('Power Supplies', 'power-supplies'), +('Cooling', 'cooling'), +('Cases', 'cases'), +('Motherboards', 'motherboards'), +('Smart Home', 'smart-home'), +('Drones', 'drones'), +('VR Headsets', 'vr-headsets'), +('Action Cameras', 'action-cameras'), +('Home Security', 'home-security'), +('Fitness Tech', 'fitness-tech'), +('E-readers', 'e-readers'), +('Portable Chargers', 'portable-chargers'), +('Phone Cases', 'phone-cases'), +('Screen Protectors', 'screen-protectors'), +('Memory Cards', 'memory-cards'), +('Adapters', 'adapters'), +('Hubs', 'hubs'), +('Stands', 'stands'), +('Mounts', 'mounts'), +('Cleaning Kits', 'cleaning-kits'), +('Surge Protectors', 'surge-protectors'), +('UPS Systems', 'ups-systems'), +('External Drives', 'external-drives'), +('Bluetooth Devices', 'bluetooth-devices'), +('Smart Watches', 'smart-watches'), +('Earbuds', 'earbuds'), +('Headphones', 'headphones'), +('Microphones', 'microphones'), +('Studio Equipment', 'studio-equipment'), +('Productivity Software', 'productivity-software'), +('Security Software', 'security-software'), +('Digital Games', 'digital-games'); + +-- ====================================== +-- INSERT PRODUCTS - SMARTPHONES +-- ====================================== + +INSERT INTO "Product" (name, "shortDescription", description, price, sizes, colors, images, "categorySlug", "createdAt", "updatedAt") VALUES +('iPhone 15 Pro Max', 'The ultimate iPhone with titanium design, Action Button, and powerful A17 Pro chip.', '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.', 119999, '{"128GB", "256GB", "512GB", "1TB"}', '{"Natural Titanium", "Blue Titanium", "White Titanium", "Black Titanium"}', '{"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"}', 'smartphones', NOW(), NOW()), + +('Samsung Galaxy S24 Ultra', 'AI-powered smartphone with S Pen, 200MP camera, and brilliant 6.8" Dynamic AMOLED display.', '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.', 129999, '{"256GB", "512GB", "1TB"}', '{"Titanium Gray", "Titanium Black", "Titanium Violet", "Titanium Yellow"}', '{"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"}', 'smartphones', NOW(), NOW()), + +('Google Pixel 9 Pro', 'AI-powered smartphone with advanced computational photography and pure Android experience.', 'Capture life''s moments with extraordinary detail using the Pixel 9 Pro''s advanced AI photography features. The Google Tensor G4 chip delivers intelligent performance, while the pure Android experience ensures seamless updates and security. Features include Magic Eraser, Real Tone technology, and Live Translate for global communication.', 99999, '{"128GB", "256GB", "512GB"}', '{"Obsidian", "Porcelain", "Hazel", "Rose"}', '{"Obsidian": "/products/pixel-9-pro-obsidian.jpg", "Porcelain": "/products/pixel-9-pro-porcelain.jpg", "Hazel": "/products/pixel-9-pro-hazel.jpg", "Rose": "/products/pixel-9-pro-rose.jpg"}', 'smartphones', NOW(), NOW()); + +-- ====================================== +-- INSERT PRODUCTS - LAPTOPS +-- ====================================== + +INSERT INTO "Product" (name, "shortDescription", description, price, sizes, colors, images, "categorySlug", "createdAt", "updatedAt") VALUES +('MacBook Pro 16-inch M4 Pro', 'Professional laptop with M4 Pro chip, Liquid Retina XDR display, and up to 22-hour battery life.', '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.', 249999, '{"512GB", "1TB", "2TB", "4TB"}', '{"Space Black", "Silver"}', '{"Space Black": "/products/macbook-pro-16-space-black.jpg", "Silver": "/products/macbook-pro-16-silver.jpg"}', 'laptops', NOW(), NOW()), + +('Dell XPS 15 OLED', 'Premium Windows laptop with 4K OLED InfinityEdge display and 13th Gen Intel processors.', '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.', 189999, '{"512GB", "1TB", "2TB"}', '{"Platinum Silver", "Graphite"}', '{"Platinum Silver": "/products/dell-xps-15-silver.jpg", "Graphite": "/products/dell-xps-15-graphite.jpg"}', 'laptops', NOW(), NOW()); + +-- ====================================== +-- INSERT PRODUCTS - GAMING LAPTOPS +-- ====================================== + +INSERT INTO "Product" (name, "shortDescription", description, price, sizes, colors, images, "categorySlug", "createdAt", "updatedAt") VALUES +('ASUS ROG Strix G15', 'AMD Ryzen 7 5800H, RTX 3060, 144Hz FHD', 'High-performance gaming laptop with AMD Ryzen 7 processor and NVIDIA GeForce RTX 3060 graphics. Features a 144Hz Full HD display for smooth gaming.', 129900, '{"15.6in", "17.3in"}', '{"Eclipse Gray", "Electro Punk"}', '{"Eclipse Gray": "/products/asus-rog-gray.jpg", "Electro Punk": "/products/asus-rog-pink.jpg"}', 'gaming-laptops', NOW(), NOW()), + +('MSI Katana 15', 'Intel Core i7-12650H, RTX 4060, 144Hz', 'Gaming laptop with Intel 12th Gen processor and RTX 4060 graphics. Perfect for gaming and content creation with high refresh rate display.', 149900, '{"15.6in"}', '{"Black", "Blue"}', '{"Black": "/products/msi-katana-black.jpg", "Blue": "/products/msi-katana-blue.jpg"}', 'gaming-laptops', NOW(), NOW()); + +-- ====================================== +-- INSERT PRODUCTS - AUDIO +-- ====================================== + +INSERT INTO "Product" (name, "shortDescription", description, price, sizes, colors, images, "categorySlug", "createdAt", "updatedAt") VALUES +('Sony WH-1000XM5', 'Industry-leading noise canceling headphones with 30-hour battery and crystal-clear calls.', '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.', 39999, '{"Standard"}', '{"Black", "Silver"}', '{"Black": "/products/sony-wh1000xm5-black.jpg", "Silver": "/products/sony-wh1000xm5-silver.jpg"}', 'audio', NOW(), NOW()), + +('AirPods Pro (3rd Generation)', 'Premium wireless earbuds with Active Noise Cancellation and Spatial Audio support.', '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.', 24999, '{"Standard"}', '{"White"}', '{"White": "/products/airpods-pro-3rd-gen.jpg"}', 'audio', NOW(), NOW()); + +-- ====================================== +-- INSERT PRODUCTS - ACCESSORIES +-- ====================================== + +INSERT INTO "Product" (name, "shortDescription", description, price, sizes, colors, images, "categorySlug", "createdAt", "updatedAt") VALUES +('Logitech MX Master 3S', 'Advanced wireless mouse with 8K DPI sensor, quiet clicks, and 70-day battery life.', '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 is perfect for professionals and creatives.', 9999, '{"Standard"}', '{"Graphite", "Pale Gray"}', '{"Graphite": "/products/mx-master-3s-graphite.jpg", "Pale Gray": "/products/mx-master-3s-pale-gray.jpg"}', 'accessories', NOW(), NOW()), + +('Logitech K380 Multi-Device Keyboard', 'Compact wireless keyboard with Easy-Switch technology for seamless multi-device typing.', '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.', 3999, '{"Compact"}', '{"Dark Grey", "Off-White", "Blue"}', '{"Dark Grey": "/products/k380-dark-grey.jpg", "Off-White": "/products/k380-off-white.jpg", "Blue": "/products/k380-blue.jpg"}', 'accessories', NOW(), NOW()); + +-- ====================================== +-- INSERT PRODUCTS - TABLETS +-- ====================================== + +INSERT INTO "Product" (name, "shortDescription", description, price, sizes, colors, images, "categorySlug", "createdAt", "updatedAt") VALUES +('iPad Pro 12.9"', 'M2 chip, 128GB, Wi-Fi + Cellular', 'Ultimate iPad experience with M2 chip. Features Liquid Retina XDR display and support for Apple Pencil (2nd generation).', 109900, '{"128GB", "256GB", "512GB", "1TB"}', '{"Space Gray", "Silver"}', '{"Space Gray": "/products/ipad-pro-gray.jpg", "Silver": "/products/ipad-pro-silver.jpg"}', 'tablets', NOW(), NOW()), + +('Samsung Galaxy Tab S9', 'Snapdragon 8 Gen 2, 128GB, 11 inch', 'Premium Android tablet with Snapdragon processor. Includes S Pen and features a stunning AMOLED display.', 79900, '{"128GB", "256GB"}', '{"Graphite", "Beige", "Mint"}', '{"Graphite": "/products/galaxy-tab-graphite.jpg", "Beige": "/products/galaxy-tab-beige.jpg", "Mint": "/products/galaxy-tab-mint.jpg"}', 'tablets', NOW(), NOW()); + +-- ====================================== +-- INSERT PRODUCTS - MONITORS +-- ====================================== + +INSERT INTO "Product" (name, "shortDescription", description, price, sizes, colors, images, "categorySlug", "createdAt", "updatedAt") VALUES +('LG UltraGear 27GL850', '27" QHD IPS, 144Hz, 1ms, G-Sync Compatible', 'High-performance gaming monitor with Nano IPS technology. Features 144Hz refresh rate and 1ms response time for competitive gaming.', 44900, '{"27in"}', '{"Black"}', '{"Black": "/products/lg-ultragear-black.jpg"}', 'monitors', NOW(), NOW()), + +('Samsung Odyssey G7', '32" Curved QLED, 240Hz, 1ms, G-Sync', 'Curved gaming monitor with QLED technology and 1000R curvature. Features 240Hz refresh rate for ultimate gaming performance.', 69900, '{"27in", "32in"}', '{"Black"}', '{"Black": "/products/samsung-odyssey-black.jpg"}', 'monitors', NOW(), NOW()); + +-- ====================================== +-- INSERT PRODUCTS - WEARABLES +-- ====================================== + +INSERT INTO "Product" (name, "shortDescription", description, price, sizes, colors, images, "categorySlug", "createdAt", "updatedAt") VALUES +('Apple Watch Series 9', 'GPS + Cellular, 45mm, Titanium', 'Advanced smartwatch with S9 SiP and Double Tap gesture. Features Always-On Retina display and comprehensive health tracking.', 74900, '{"41mm", "45mm"}', '{"Natural Titanium", "Blue Titanium", "Silver"}', '{"Natural Titanium": "/products/apple-watch-titanium.jpg", "Blue Titanium": "/products/apple-watch-blue.jpg", "Silver": "/products/apple-watch-silver.jpg"}', 'wearables', NOW(), NOW()), + +('Samsung Galaxy Watch6', '40mm, Bluetooth, Health Monitoring', 'Advanced smartwatch with comprehensive health monitoring. Features sleep tracking, heart rate monitoring, and long battery life.', 32900, '{"40mm", "44mm"}', '{"Graphite", "Gold", "Silver"}', '{"Graphite": "/products/galaxy-watch-graphite.jpg", "Gold": "/products/galaxy-watch-gold.jpg", "Silver": "/products/galaxy-watch-silver.jpg"}', 'wearables', NOW(), NOW()); + +-- ====================================== +-- INSERT PRODUCTS - GRAPHICS CARDS +-- ====================================== + +INSERT INTO "Product" (name, "shortDescription", description, price, sizes, colors, images, "categorySlug", "createdAt", "updatedAt") VALUES +('NVIDIA GeForce RTX 4080', 'High-performance graphics card with DLSS 3 and ray tracing support', 'Experience next-generation gaming with the NVIDIA GeForce RTX 4080. Features Ada Lovelace architecture, DLSS 3, and advanced ray tracing for stunning visuals and exceptional performance.', 119900, '{"16GB GDDR6X"}', '{"Black"}', '{"Black": "/products/rtx-4080-black.jpg"}', 'graphics-cards', NOW(), NOW()), + +('AMD Ryzen 9 7900X', '12-Core, 24-Thread Desktop Processor', 'High-performance desktop processor with 12 cores and 24 threads. Built on advanced 5nm process technology for exceptional gaming and productivity performance.', 42900, '{"Standard"}', '{"Silver"}', '{"Silver": "/products/ryzen-9-7900x.jpg"}', 'processors', NOW(), NOW()), + +('Corsair Vengeance DDR5-5600', '32GB (2x16GB) High-Speed Gaming Memory', 'Premium DDR5 memory optimized for gaming and high-performance computing. Features aluminum heat spreaders and Intel XMP 3.0 support.', 15900, '{"16GB", "32GB", "64GB"}', '{"Black", "White"}', '{"Black": "/products/corsair-vengeance-black.jpg", "White": "/products/corsair-vengeance-white.jpg"}', 'ram', NOW(), NOW()); + +-- ====================================== +-- INSERT PRODUCTS - STORAGE +-- ====================================== + +INSERT INTO "Product" (name, "shortDescription", description, price, sizes, colors, images, "categorySlug", "createdAt", "updatedAt") VALUES +('Samsung 980 PRO NVMe SSD', '2TB PCIe 4.0 Internal SSD with Heatsink', 'Ultra-fast NVMe SSD with PCIe 4.0 interface. Features sequential read speeds up to 7,000 MB/s and built-in heatsink for optimal thermal management.', 19900, '{"1TB", "2TB", "4TB"}', '{"Black"}', '{"Black": "/products/samsung-980-pro.jpg"}', 'ssds', NOW(), NOW()), + +('Western Digital Black 4TB HDD', 'High-Performance Desktop Hard Drive', 'Reliable desktop hard drive designed for gaming and high-performance computing. Features 7200 RPM speed and 256MB cache for fast data access.', 12900, '{"1TB", "2TB", "4TB", "6TB"}', '{"Black"}', '{"Black": "/products/wd-black-hdd.jpg"}', 'hard-drives', NOW(), NOW()); + +-- ====================================== +-- COMPLETION MESSAGE +-- ====================================== +-- Database setup complete! All categories and products have been inserted with Tanzanian Shilling pricing. +-- You can now query your products and categories from the Neon PostgreSQL database. \ No newline at end of file diff --git a/neuraltale-database-setup.sql b/neuraltale-database-setup.sql new file mode 100644 index 00000000..6187b55e --- /dev/null +++ b/neuraltale-database-setup.sql @@ -0,0 +1,223 @@ +-- ====================================== +-- NEURALTALE E-COMMERCE DATABASE SETUP +-- Comprehensive SQL Script for Neon PostgreSQL +-- =INSERT INTO "Product" (name, "shortDescription", description, price, sizes, colors, images, "categorySlug", "createdAt", "updatedAt") VALUES +('iPad Pro 12.9"', 'M2 chip, 128GB, Wi-Fi + Cellular', 'Ultimate iPad experience with M2 chip. Features Liquid Retina XDR display and support for Apple Pencil (2nd generation).', 109900, '{"128GB", "256GB", "512GB", "1TB"}', '{"Space Gray", "Silver"}', '{"Space Gray": "/products/ipad-pro-gray.jpg", "Silver": "/products/ipad-pro-silver.jpg"}', 'tablets', NOW(), NOW()), + +('Samsung Galaxy Tab S9', 'Snapdragon 8 Gen 2, 128GB, 11 inch', 'Premium Android tablet with Snapdragon processor. Includes S Pen and features a stunning AMOLED display.', 79900, '{"128GB", "256GB"}', '{"Graphite", "Beige", "Mint"}', '{"Graphite": "/products/galaxy-tab-graphite.jpg", "Beige": "/products/galaxy-tab-beige.jpg", "Mint": "/products/galaxy-tab-mint.jpg"}', 'tablets', NOW(), NOW());================================= + +-- Clear existing data (optional - uncomment if you want to start fresh) +-- DELETE FROM "Product"; +-- DELETE FROM "Category"; + +-- ====================================== +-- INSERT CATEGORIES +-- ====================================== + +-- Main Categories +INSERT INTO "Category" (name, slug) VALUES +("`Laptops", "`laptops"), +("`Desktops", "`desktops"), +("`Computer Monitors", "`monitors"), +("`Storage", "`storage"), +("`Components", "`components"), +("`Peripherals", "`peripherals"), +("`Networking", "`networking"), +("`Gadgets", "`gadgets"), +("`Gaming", "`gaming"), +("`Software & Digital", "`software"), + +-- Subcategories for Laptops +("`Gaming Laptops", "`gaming-laptops"), +("`Business Laptops", "`business-laptops"), +("`Ultrabooks", "`ultrabooks"), +("`MacBooks", "`macbooks"), + +-- Subcategories for Desktops +("`Gaming PCs", "`gaming-pcs"), +("`Workstations", "`workstations"), +("`All-in-One PCs", "`all-in-one"), +("`Mini PCs", "`mini-pcs"), + +-- Subcategories for Monitors +("`Gaming Monitors", "`gaming-monitors"), +("`4K Monitors", "`4k-monitors"), +("`Ultrawide Monitors", "`ultrawide-monitors"), +("`Professional Monitors", "`professional-monitors"), + +-- Subcategories for Storage +("`SSDs", "`ssds"), +("`Hard Drives", "`hard-drives"), +("`External Storage", "`external-storage"), +("`NAS", "`nas"), + +-- Subcategories for Components +("`Processors", "`processors"), +("`Graphics Cards", "`graphics-cards"), +("`Motherboards", "`motherboards"), +("`RAM", "`ram"), + +-- Subcategories for Peripherals +("`Keyboards", "`keyboards"), +("`Mice", "`mice"), +("`Headsets", "`headsets"), +("`Webcams", "`webcams"), + +-- Subcategories for Networking +("`Routers", "`routers"), +("`WiFi Adapters", "`wifi-adapters"), +("`Switches", "`switches"), +("`Access Points", "`access-points"), + +-- Subcategories for Gadgets +("`Smartphones", "`smartphones"), +("`Tablets", "`tablets"), +("`Smartwatches", "`smartwatches"), +("`Audio", "`audio"), +("`Wearables", "`wearables"), +("`Accessories", "`accessories"), + +-- Subcategories for Gaming +("`Gaming Chairs", "`gaming-chairs"), +("`Controllers", "`controllers"), +("`VR Headsets", "`vr-headsets"), +("`Gaming Accessories", "`gaming-accessories"), + +-- Subcategories for Software +("`Operating Systems", "`operating-systems"), +("`Productivity Software", "`productivity-software"), +("`Security Software", "`security-software"), +("`Digital Games", "`digital-games"); + +-- ====================================== +-- INSERT PRODUCTS - SMARTPHONES +-- ====================================== + +INSERT INTO "Product" (name, "shortDescription", description, price, sizes, colors, images, "categorySlug", "createdAt", "updatedAt") VALUES +("`iPhone 15 Pro Max", "`The ultimate iPhone with titanium design, Action Button, and powerful A17 Pro chip.", "`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.", 119999, "`{"128GB", "256GB", "512GB", "1TB"}", "`{"Natural Titanium", "Blue Titanium", "White Titanium", "Black Titanium"}", "`{"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"}", "`smartphones", NOW(), NOW()), + +("`Samsung Galaxy S24 Ultra", "`AI-powered smartphone with S Pen, 200MP camera, and brilliant 6.8" Dynamic AMOLED display.", "`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.", 129999, "`{"256GB", "512GB", "1TB"}", "`{"Titanium Gray", "Titanium Black", "Titanium Violet", "Titanium Yellow"}", "`{"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"}", "`smartphones", NOW(), NOW()); + +-- ====================================== +-- INSERT PRODUCTS - LAPTOPS +-- ====================================== + +INSERT INTO "Product" (name, "shortDescription", description, price, sizes, colors, images, "categorySlug", "createdAt", "updatedAt") VALUES +("`MacBook Pro 16-inch M4 Pro", "`Professional laptop with M4 Pro chip, Liquid Retina XDR display, and up to 22-hour battery life.", "`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.", 249999, "`{"512GB", "1TB", "2TB", "4TB"}", "`{"Space Black", "Silver"}", "`{"Space Black": "/products/macbook-pro-16-space-black.jpg", "Silver": "/products/macbook-pro-16-silver.jpg"}", "`laptops", NOW(), NOW()), + +("`Dell XPS 15 OLED", "`Premium Windows laptop with 4K OLED InfinityEdge display and 13th Gen Intel processors.", "`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.", 189999, "`{"512GB", "1TB", "2TB"}", "`{"Platinum Silver", "Graphite"}", "`{"Platinum Silver": "/products/dell-xps-15-silver.jpg", "Graphite": "/products/dell-xps-15-graphite.jpg"}", "`laptops", NOW(), NOW()); + +-- ====================================== +-- INSERT PRODUCTS - GAMING LAPTOPS +-- ====================================== + +INSERT INTO "Product" (name, "shortDescription", description, price, sizes, colors, images, "categorySlug", "createdAt", "updatedAt") VALUES +("`ASUS ROG Strix G15", "`AMD Ryzen 7 5800H, RTX 3060, 144Hz FHD", "`High-performance gaming laptop with AMD Ryzen 7 processor and NVIDIA GeForce RTX 3060 graphics. Features a 144Hz Full HD display for smooth gaming.", 129900, "`{"15.6in", "17.3in"}", "`{"Eclipse Gray", "Electro Punk"}", "`{"Eclipse Gray": "/products/asus-rog-gray.jpg", "Electro Punk": "/products/asus-rog-pink.jpg"}", "`gaming-laptops", NOW(), NOW()), + +("`MSI Katana 15", "`Intel Core i7-12650H, RTX 4060, 144Hz", "`Gaming laptop with Intel 12th Gen processor and RTX 4060 graphics. Perfect for gaming and content creation with high refresh rate display.", 149900, "`{"15.6in"}", "`{"Black", "Blue"}", "`{"Black": "/products/msi-katana-black.jpg", "Blue": "/products/msi-katana-blue.jpg"}", "`gaming-laptops", NOW(), NOW()); + +-- ====================================== +-- INSERT PRODUCTS - AUDIO +-- ====================================== + +INSERT INTO "Product" (name, "shortDescription", description, price, sizes, colors, images, "categorySlug", "createdAt", "updatedAt") VALUES +("`Sony WH-1000XM5", "`Industry-leading noise canceling headphones with 30-hour battery and crystal-clear calls.", "`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.", 39999, "`{"Standard"}", "`{"Black", "Silver"}", "`{"Black": "/products/sony-wh1000xm5-black.jpg", "Silver": "/products/sony-wh1000xm5-silver.jpg"}", "`audio", NOW(), NOW()), + +("`AirPods Pro (3rd Generation)", "`Premium wireless earbuds with Active Noise Cancellation and Spatial Audio support.", "`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.", 24999, "`{"Standard"}", "`{"White"}", "`{"White": "/products/airpods-pro-3rd-gen.jpg"}", "`audio", NOW(), NOW()); + +-- ====================================== +-- INSERT PRODUCTS - ACCESSORIES +-- ====================================== + +INSERT INTO "Product" (name, "shortDescription", description, price, sizes, colors, images, "categorySlug", "createdAt", "updatedAt") VALUES +("`Logitech MX Master 3S", "`Advanced wireless mouse with 8K DPI sensor, quiet clicks, and 70-day battery life.", "`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 is perfect for professionals and creatives.", 9999, "`{"Standard"}", "`{"Graphite", "Pale Gray"}", "`{"Graphite": "/products/mx-master-3s-graphite.jpg", "Pale Gray": "/products/mx-master-3s-pale-gray.jpg"}", "`accessories", NOW(), NOW()), + +("`Logitech K380 Multi-Device Keyboard", "`Compact wireless keyboard with Easy-Switch technology for seamless multi-device typing.", "`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.", 3999, "`{"Compact"}", "`{"Dark Grey", "Off-White", "Blue"}", "`{"Dark Grey": "/products/k380-dark-grey.jpg", "Off-White": "/products/k380-off-white.jpg", "Blue": "/products/k380-blue.jpg"}", "`accessories", NOW(), NOW()); + +-- ====================================== +-- INSERT PRODUCTS - TABLETS +-- ====================================== + +INSERT INTO "Product" (name, "shortDescription", description, price, sizes, colors, images, "categorySlug") VALUES +("`iPad Pro 12.9"", "`M2 chip, 128GB, Wi-Fi + Cellular", "`Ultimate iPad experience with M2 chip. Features Liquid Retina XDR display and support for Apple Pencil (2nd generation).", 109900, {"`128GB", "`256GB", "`512GB", "`1TB"}, {"`Space Gray", "`Silver"}, "`{"Space Gray": "/products/ipad-pro-gray.jpg", "Silver": "/products/ipad-pro-silver.jpg"}", "`tablets"), + +("`Samsung Galaxy Tab S9", "`Snapdragon 8 Gen 2, 128GB, 11 inch", "`Premium Android tablet with Snapdragon processor. Includes S Pen and features a stunning AMOLED display.", 79900, {"`128GB", "`256GB"}, {"`Graphite", "`Beige", "`Mint"}, "`{"Graphite": "/products/galaxy-tab-graphite.jpg", "Beige": "/products/galaxy-tab-beige.jpg", "Mint": "/products/galaxy-tab-mint.jpg"}", "`tablets"); + +-- ====================================== +-- INSERT PRODUCTS - MONITORS +-- ====================================== + +INSERT INTO "Product" (name, "shortDescription", description, price, sizes, colors, images, "categorySlug") VALUES +("`LG UltraGear 27GL850", "`27" QHD IPS, 144Hz, 1ms, G-Sync Compatible", "`High-performance gaming monitor with Nano IPS technology. Features 144Hz refresh rate and 1ms response time for competitive gaming.", 44900, "{`27in}", "{`Black}", "`{"Black": "/products/lg-ultragear-black.jpg"}", "`monitors"), + +("`Samsung Odyssey G7", "`32" Curved QLED, 240Hz, 1ms, G-Sync", "`Curved gaming monitor with QLED technology and 1000R curvature. Features 240Hz refresh rate for ultimate gaming performance.", 69900, {"`27in", "`32in"}, "{`Black}", "`{"Black": "/products/samsung-odyssey-black.jpg"}", "`monitors"); + +-- ====================================== +-- INSERT PRODUCTS - WEARABLES +-- ====================================== + +INSERT INTO "Product" (name, "shortDescription", description, price, sizes, colors, images, "categorySlug") VALUES +("`Apple Watch Series 9", "`GPS + Cellular, 45mm, Titanium", "`Advanced smartwatch with S9 SiP and Double Tap gesture. Features Always-On Retina display and comprehensive health tracking.", 74900, {"`41mm", "`45mm"}, {"`Natural Titanium", "`Blue Titanium", "`Silver"}, "`{"Natural Titanium": "/products/apple-watch-titanium.jpg", "Blue Titanium": "/products/apple-watch-blue.jpg", "Silver": "/products/apple-watch-silver.jpg"}", "`wearables"), + +("`Samsung Galaxy Watch6", "`40mm, Bluetooth, Health Monitoring", "`Advanced smartwatch with comprehensive health monitoring. Features sleep tracking, heart rate monitoring, and long battery life.", 32900, {"`40mm", "`44mm"}, {"`Graphite", "`Gold", "`Silver"}, "`{"Graphite": "/products/galaxy-watch-graphite.jpg", "Gold": "/products/galaxy-watch-gold.jpg", "Silver": "/products/galaxy-watch-silver.jpg"}", "`wearables"); + +-- ====================================== +-- INSERT PRODUCTS - COMPONENTS +-- ====================================== + +INSERT INTO "Product" (name, "shortDescription", description, price, sizes, colors, images, "categorySlug") VALUES +("`NVIDIA GeForce RTX 4080", "`High-performance graphics card with DLSS 3 and ray tracing support", "`Experience next-generation gaming with the NVIDIA GeForce RTX 4080. Features Ada Lovelace architecture, DLSS 3, and advanced ray tracing for stunning visuals and exceptional performance.", 119900, "{`16GB GDDR6X}", "{`Black}", "`{"Black": "/products/rtx-4080-black.jpg"}", "`graphics-cards"), + +("`AMD Ryzen 9 7900X", "`12-Core, 24-Thread Desktop Processor", "`High-performance desktop processor with 12 cores and 24 threads. Built on advanced 5nm process technology for exceptional gaming and productivity performance.", 42900, "{`Standard}", "{`Silver}", "`{"Silver": "/products/ryzen-9-7900x.jpg"}", "`processors"), + +("`Corsair Vengeance DDR5-5600", "`32GB (2x16GB) High-Speed Gaming Memory", "`Premium DDR5 memory optimized for gaming and high-performance computing. Features aluminum heat spreaders and Intel XMP 3.0 support.", 15900, {"`16GB", "`32GB", "`64GB"}, {"`Black", "`White"}, "`{"Black": "/products/corsair-vengeance-black.jpg", "White": "/products/corsair-vengeance-white.jpg"}", "`ram"); + +-- ====================================== +-- INSERT PRODUCTS - STORAGE +-- ====================================== + +INSERT INTO "Product" (name, "shortDescription", description, price, sizes, colors, images, "categorySlug") VALUES +("`Samsung 980 PRO NVMe SSD", "`2TB PCIe 4.0 Internal SSD with Heatsink", "`Ultra-fast NVMe SSD with PCIe 4.0 interface. Features sequential read speeds up to 7,000 MB/s and built-in heatsink for optimal thermal management.", 19900, {"`1TB", "`2TB", "`4TB"}, "{`Black}", "`{"Black": "/products/samsung-980-pro.jpg"}", "`ssds"), + +("`Western Digital Black 4TB HDD", "`High-Performance Desktop Hard Drive", "`Reliable desktop hard drive designed for gaming and high-performance computing. Features 7200 RPM speed and 256MB cache for fast data access.", 12900, {"`1TB", "`2TB", "`4TB", "`6TB"}, "{`Black}", "`{"Black": "/products/wd-black-hdd.jpg"}", "`hard-drives"); + +-- ====================================== +-- INSERT PRODUCTS - NETWORKING +-- ====================================== + +INSERT INTO "Product" (name, "shortDescription", description, price, sizes, colors, images, "categorySlug") VALUES +("`ASUS AX6000 WiFi 6 Router", "`Dual-Band Wireless Router with AiMesh Support", "`Next-generation WiFi 6 router with AX6000 speeds. Features MU-MIMO technology, adaptive QoS, and AiMesh support for whole-home coverage.", 29900, "{`Standard}", "{`Black}", "`{"Black": "/products/asus-ax6000-router.jpg"}", "`routers"), + +("`TP-Link AC1300 USB WiFi Adapter", "`Dual-Band USB 3.0 WiFi Adapter", "`High-speed USB WiFi adapter with AC1300 speeds. Features dual-band connectivity and USB 3.0 interface for maximum performance.", 3999, "{`Compact}", "{`Black}", "`{"Black": "/products/tp-link-ac1300-adapter.jpg"}", "`wifi-adapters"); + +-- ====================================== +-- VERIFICATION QUERIES +-- ====================================== + +-- Check total counts +SELECT "`Categories" as type, COUNT(*) as count FROM "Category" +UNION ALL +SELECT "`Products" as type, COUNT(*) as count FROM "Product"; + +-- View categories with product counts +SELECT + c.name as category_name, + c.slug as category_slug, + COUNT(p.id) as product_count +FROM "Category" c +LEFT JOIN "Product" p ON c.slug = p."categorySlug" +GROUP BY c.id, c.name, c.slug +ORDER BY product_count DESC, c.name; + +-- View sample products with pricing +SELECT + p.name as product_name, + ROUND(p.price / 100.0, 2) as price_usd, + ROUND(p.price * 23.0, 0) as price_tzs, + c.name as category, + array_length(p.colors, 1) as color_options, + array_length(p.sizes, 1) as size_options +FROM "Product" p +JOIN "Category" c ON p."categorySlug" = c.slug +ORDER BY p.price DESC +LIMIT 15; 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: 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/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 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/packages/types/src/product.ts b/packages/types/src/product.ts index 66362bda..6a7260d9 100644 --- a/packages/types/src/product.ts +++ b/packages/types/src/product.ts @@ -12,6 +12,7 @@ export type StripeProductType = { }; export const colors = [ + // Traditional colors "blue", "green", "red", @@ -23,30 +24,52 @@ export const colors = [ "gray", "black", "white", + + // Tech product colors + "Natural Titanium", + "Blue Titanium", + "White Titanium", + "Black Titanium", + "Titanium Gray", + "Titanium Black", + "Titanium Violet", + "Titanium Yellow", + "Space Black", + "Silver", + "Platinum Silver", + "Graphite", + "Off Black", + "Storm Grey", + "White Smoke", + "Moonstone Blue", + "Pale Gray", + "Off-White", + "Dark Grey", + "Natural", + "Platinum", + "Sapphire", + "Dune", + "Black/Cyan", + "White/Black", ] as const; export const sizes = [ + // Traditional clothing sizes "xs", - "s", + "s", "m", "l", "xl", "xxl", - "34", - "35", - "36", - "37", - "38", - "39", - "40", - "41", - "42", - "43", - "44", - "45", - "46", - "47", - "48", + + // Shoe sizes + "34", "35", "36", "37", "38", "39", "40", "41", "42", "43", "44", "45", "46", "47", "48", + + // Tech product sizes/capacities + "128GB", "256GB", "512GB", "1TB", "2TB", "4TB", + "Standard", "Compact", "Tenkeyless", + "27-inch", "32-inch", + "43mm", "47mm", "49mm", ] as const; export const ProductFormSchema = z 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"],